I looked at the code a bit, but I still don't understand what is going on behind the scenes.
Can someone familiar with the process describe how it works?
What happens is that in order to avoid PHP timeouts, the browser periodically passes through AJAX the URL ( http://example.com/batch?id= $ id), which causes the execution of batch operations.
See _ batch_page () , which is a function called by system_batch_page () , a menu callback for the "batch" path.
function _batch_page() { $batch = &batch_get(); // Retrieve the current state of batch from db. 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'])))) { $batch = unserialize($data); } else { return FALSE; } // Register database update for end of processing. register_shutdown_function('_batch_shutdown'); $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : ''; $output = NULL; switch ($op) { case 'start': $output = _batch_start(); break; case 'do': // JS-version AJAX callback. _batch_do(); break; case 'do_nojs': // Non-JS progress page. $output = _batch_progress_page_nojs(); break; case 'finished': $output = _batch_finished(); break; } return $output; }
In _ batch_progress_page_nojs () you will see the following code.
$url = url($batch['url'], array('query' => array('id' => $batch['id'], 'op' => $new_op))); drupal_set_html_head('<meta http-equiv="Refresh" content="0; URL=' . $url . '">'); $output = theme('progress_bar', $percentage, $message); return $output;
Setting the Refresh meta tag will refresh the page.
Similar code is present in Drupal 7; the difference is that the code has been ported and it uses the new features implemented by Drupal 7.
// Merge required query parameters for batch processing into those provided by // batch_set() or hook_batch_alter(). $batch['url_options']['query']['id'] = $batch['id']; $batch['url_options']['query']['op'] = $new_op; $url = url($batch['url'], $batch['url_options']); $element = array( '#tag' => 'meta', '#attributes' => array( 'http-equiv' => 'Refresh', 'content' => '0; URL=' . $url, ), ); drupal_add_html_head($element, 'batch_progress_meta_refresh'); return theme('progress_bar', array('percent' => $percentage, 'message' => $message));
When JavaScript is enabled, the code that does all the work is in the batch.js file.
/** * Attaches the batch behavior to progress bars. */ Drupal.behaviors.batch = function (context) { // This behavior attaches by ID, so is only valid once on a page. if ($('#progress.batch-processed').size()) { return; } $('#progress', context).addClass('batch-processed').each(function () { var holder = this; var uri = Drupal.settings.batch.uri; var initMessage = Drupal.settings.batch.initMessage; var errorMessage = Drupal.settings.batch.errorMessage; // Success: redirect to the summary. var updateCallback = function (progress, status, pb) { if (progress == 100) { pb.stopMonitoring(); window.location = uri+'&op=finished'; } }; var errorCallback = function (pb) { var div = document.createElement('p'); div.className = 'error'; $(div).html(errorMessage); $(holder).prepend(div); $('#wait').hide(); }; var progress = new Drupal.progressBar('updateprogress', updateCallback, "POST", errorCallback); progress.setProgress(-1, initMessage); $(holder).append(progress.element); progress.startMonitoring(uri+'&op=do', 10); }); };
Querying the batch URL starts with progress.startMonitoring(uri+'&op=do', 10) . The batch.js file depends on the functionality open in the Drupal.progressBar , which is defined in the progress.js file.
Similar code is used in Drupal 7, which uses a slightly different version of batch.js and progress.js .
(function ($) { Drupal.behaviors.batch = { attach: function (context, settings) { $('#progress', context).once('batch', function () { var holder = $(this);
The differences are that with Drupal 7 all jQuery code is wrapped in (function ($) { })(jQuery); and that jQuery as soon as the plugin is included in Drupal 7 . Drupal 7 also sets WAI-ARIA attributes for compatibility with screen readers; this also happens in HTML added from JavaScript code, such as the following in the progress.js file.
// The WAI-ARIA setting aria-live="polite" will announce changes after users // have completed their current activity and not interrupt the screen reader. this.element = $('<div class="progress" aria-live="polite"></div>').attr('id', id); this.element.html('<div class="bar"><div class="filled"></div></div>' + '<div class="percentage"></div>' + '<div class="message"> </div>');
When servicing a batch page, Drupal sets _ batch_shutdown () as a shutdown callback; when PHP shuts down due to a timeout, the function updates the array of packages in the database.
// Drupal 6. function _batch_shutdown() { if ($batch = batch_get()) { db_query("UPDATE {batch} SET batch = '%s' WHERE bid = %d", serialize($batch), $batch['id']); } }
// Drupal 7. function _batch_shutdown() { if ($batch = batch_get()) { db_update('batch') ->fields(array('batch' => serialize($batch))) ->condition('bid', $batch['id']) ->execute(); } }