You are here

background_batch.pages.inc in Background Process 7.2

Pages for background batch.

@todo Implement proper error page instead of just 404.

File

background_batch/background_batch.pages.inc
View source
<?php

/**
 * @file
 *
 * Pages for background batch.
 *
 * @todo Implement proper error page instead of just 404.
 */

/**
 * Overview of current and recent batch jobs.
 */
function background_batch_overview_page() {
  $data = array();
  $bids = db_select('batch', 'b', array(
    'target' => 'background_process',
  ))
    ->fields('b')
    ->orderBy('b.bid', 'DESC')
    ->execute()
    ->fetchAllAssoc('bid', PDO::FETCH_OBJ);
  $processes = array();
  foreach ($bids as $bid => $batch) {
    $process = BackgroundProcess::loadByHandle('background_batch:' . $bid);
    if ($process) {
      $progress = sprintf("%.02f%%", $process
        ->getProgress() * 100);
      $message = $process
        ->getProgressMessage();
      $start = format_date((int) $process
        ->getStartTime(), 'small');
      $eta = $process
        ->calculateETA();
      $eta = $eta ? format_date((int) $process
        ->calculateETA(), 'small') : t('N/A');
    }
    else {
      $batch = unserialize($batch->batch);
      $current_set = $batch['sets'][$batch['current_set']];
      list($progress, $message) = BackgroundBatchContext::processMessage($current_set);
      $progress = sprintf("%.02f%%", $progress * 100);
      $start = format_date((int) $current_set['start'], 'small');
      $eta = empty($batch['finish_time']) ? t('Not running') : format_date((int) $batch['finish_time'], 'small');
    }
    $data[] = array(
      l($bid, 'batch', array(
        'query' => array(
          'op' => 'start',
          'id' => $bid,
        ),
      )),
      $progress,
      $message,
      $start,
      $eta,
    );
  }
  $header = array(
    'Batch ID',
    'Progress',
    'Message',
    'Started',
    'Finished/ETA',
  );
  return theme('table', array(
    'header' => $header,
    'rows' => $data,
  ));
}

/**
 * State-based dispatcher for the batch processing page.
 */
function background_batch_page() {
  if (!variable_get('background_batch_enabled', BACKGROUND_BATCH_ENABLED)) {
    module_load_include('inc', 'system', 'system.admin');
    return system_batch_page();
  }
  $id = isset($_REQUEST['id']) ? $_REQUEST['id'] : FALSE;
  if (!$id) {
    return drupal_not_found();
  }

  // Retrieve the current state of batch from db.
  $data = db_query("SELECT batch FROM {batch} WHERE bid = :bid", array(
    ':bid' => $id,
  ), array(
    'target' => 'background_process',
  ))
    ->fetchColumn();
  if (!$data) {
    return drupal_not_found();
  }
  $batch =& batch_get();
  $batch = unserialize($data);

  // Check if the current user owns (has access to) this batch.
  global $user;
  if ($batch['uid'] != $user->uid) {
    return drupal_access_denied();
  }
  $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
  switch ($op) {
    case 'start':
      return _background_batch_page_start();
    case 'do':
      return _background_batch_page_do_js();
    case 'do_nojs':
      return _background_batch_page_do_nojs();
    case 'finished':
      return _batch_finished();
    default:
      drupal_goto('admin/config/system/batch/overview');
  }
}

/**
 * Start a batch job in the background
 */
function _background_batch_initiate($page) {
  require_once 'includes/batch.inc';
  $batch =& batch_get();
  $id = $batch['id'];
  $handle = 'background_batch:' . $id;
  $process = BackgroundProcess::loadByHandle($handle);
  if ($process) {
    if ($process
      ->getStatus() == BACKGROUND_PROCESS_STATUS_RUNNING) {

      // If batch is already in progress, goto to the status page instead of starting it.
      return;
    }

    // If process is locked and hasn't started for 10 seconds, then relaunch
    $process
      ->reDispatch();
    return;
  }

  // Let's start it!
  try {
    global $user;
    $process = BackgroundProcess::lock($handle)
      ->setServiceGroup(variable_get('background_batch_default_service_group', variable_get('background_process_default_service_group', 'default')))
      ->setUID($user->uid)
      ->setProgress(0, $batch['sets'][0]['init_message'])
      ->setCallback('_background_batch_process', array(
      $id,
    ))
      ->setOption('batch_lifespan', variable_get('background_batch_process_lifespan', BACKGROUND_BATCH_PROCESS_LIFESPAN))
      ->setShutdownCallback('_batch_shutdown')
      ->keepAlive()
      ->ensureServiceHost();
    if ($process
      ->getDispatcher() == 'foreground') {
      if ($page) {
        drupal_set_message(t('Batch is using the "foreground" dispatcher. If you leave this page, the batch processing will stop/pause.'), 'warning');
        return;
      }
      else {

        // Lifespan defaults to 1 second when running in foreground ... just like core.
        $process
          ->setOption('batch_lifespan', 1);
      }
    }
    $process
      ->dispatch();
  } catch (Exception $e) {

    // Race condition? Job already running?
    throw $e;
  }
}
function _background_batch_page_start() {
  _background_batch_initiate(TRUE);
  if (isset($_COOKIE['has_js']) && $_COOKIE['has_js']) {
    return _background_batch_page_progress_js();
  }
  else {
    return _background_batch_page_do_nojs();
  }
}

/**
 * Batch processing page with JavaScript support.
 */
function _background_batch_page_progress_js() {
  require_once 'includes/batch.inc';
  $batch = batch_get();
  $id = $batch['id'];
  $delay = variable_get('background_batch_delay', BACKGROUND_BATCH_DELAY) * 1000000;
  $handle = 'background_batch:' . $id;
  $process = BackgroundProcess::loadByHandle($handle);
  if ($process && $process
    ->getDispatcher() == 'foreground') {
    drupal_set_message(t('Batch is using the "foreground" dispatcher. If you leave this page, the batch processing will stop/pause.'), 'warning');
    $delay = 1;
  }
  $current_set = _batch_current_set();
  drupal_set_title($current_set['title'], PASS_THROUGH);

  // Merge required query parameters for batch processing into those provided by
  // batch_set() or hook_batch_alter().
  $batch['url_options']['query']['id'] = $batch['id'];
  $js_setting['batch'] = array();
  $js_setting['batch']['errorMessage'] = $current_set['error_message'] . '<br />' . $batch['error_message'];

  // Check wether ETA information should be shown.
  if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
    $js_setting['batch']['initMessage'] = 'ETA: ' . t('N/A') . '<br/>' . $current_set['init_message'];
  }
  else {
    $js_setting['batch']['initMessage'] = $current_set['init_message'];
  }
  $js_setting['batch']['uri'] = url($batch['url'], $batch['url_options']);
  $js_setting['batch']['delay'] = variable_get('background_batch_delay', BACKGROUND_BATCH_DELAY) * 1000000;
  drupal_add_js($js_setting, 'setting');
  drupal_add_library('background_batch', 'background-process.batch');
  return '<div id="progress"></div>';
}

/**
 * Do one pass of execution and inform back the browser about progression
 * (used for JavaScript-mode only).
 */
function _background_batch_page_do_js() {

  // HTTP POST required.
  if ($_SERVER['REQUEST_METHOD'] != 'POST') {
    drupal_set_message(t('HTTP POST is required.'), 'error');
    drupal_set_title(t('Error'));
    return '';
  }
  $batch =& batch_get();
  $id = $batch['id'];
  drupal_save_session(FALSE);

  // Get progress
  $handle = 'background_batch:' . $id;
  $process = BackgroundProcess::loadByHandle($handle);
  if ($process) {

    // If process is locked and hasn't started for 10 seconds, then relaunch

    #$process->reDispatch();
  }
  elseif ($batch['sets'][$batch['current_set']]['count'] == 0) {

    // The background process has self-destructed, and the batch job is done.
    $percentage = 100;
    $message = '';
  }
  else {

    // Not running?
    _background_batch_initiate(FALSE);
    $percentage = t('N/A');
    $message = '';

    // Refetch progress information
    $process = BackgroundProcess::loadByHandle($handle);
  }
  if ($process) {
    $percentage = $process
      ->getProgress() * 100;
    $message = $process
      ->getProgressMessage();
    $eta = $process
      ->calculateETA();

    // Check wether ETA information should be shown.
    if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
      $message = "ETA: " . ($eta ? format_date((int) $eta, 'large') : t('N/A')) . "<br/>{$message}";
    }
    else {
      $js_setting['batch']['initMessage'] = $message;
    }

    // If process is locked and hasn't started for 10 seconds, then relaunch
    $process
      ->reDispatch();
  }
  drupal_json_output(array(
    'status' => TRUE,
    'percentage' => sprintf("%.02f", $percentage),
    'message' => $message,
  ));
}

/**
 * Output a batch processing page without JavaScript support.
 *
 * @see _batch_process()
 */
function _background_batch_page_do_nojs() {
  $batch =& batch_get();
  $id = $batch['id'];
  $current_set = _batch_current_set();
  drupal_set_title($current_set['title'], PASS_THROUGH);
  $new_op = 'do_nojs';

  // This is one of the later requests; do some processing first.
  // Error handling: if PHP dies due to a fatal error (e.g. a nonexistent
  // function), it will output whatever is in the output buffer, followed by
  // the error message.
  ob_start();
  $fallback = $current_set['error_message'] . '<br />' . $batch['error_message'];
  $fallback = theme('maintenance_page', array(
    'content' => $fallback,
    'show_messages' => 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 </html> anyway.
  list($fallback) = explode('<!--partial-->', $fallback);
  print $fallback;

  // Perform actual processing.
  // Get progress
  $handle = 'background_batch:' . $id;
  $process = BackgroundProcess::loadByHandle($handle);
  if ($process) {
    if ($process
      ->getDispatcher() == 'foreground') {
      drupal_set_message(t('Batch is using the "foreground" dispatcher. If you leave this page, the batch processing will stop/pause.'), 'warning');
    }

    // If process is locked and hasn't started for 10 seconds, then relaunch

    #$process->reDispatch();
  }
  elseif ($batch['sets'][$batch['current_set']]['count'] == 0) {

    // The background process has self-destructed, and the batch job is done.
    $percentage = 100;
    $message = '';
  }
  else {

    // Not running?
    _background_batch_initiate(FALSE);
    $percentage = t('N/A');
    $message = '';

    // Refetch progress information
    $process = BackgroundProcess::loadByHandle($handle);
  }
  if ($process) {
    $percentage = $process
      ->getProgress() * 100;
    $message = $process
      ->getProgressMessage();
    $eta = $process
      ->calculateETA();

    // Check wether ETA information should be shown.
    if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
      $message = "ETA: " . format_date((int) $eta, 'large') . "<br/>{$message}";
    }
    else {
      $js_setting['batch']['initMessage'] = $message;
    }

    // If process is locked and hasn't started for 10 seconds, then relaunch
    $process
      ->reDispatch();
  }
  if ($percentage == 100) {
    $new_op = 'finished';
  }

  // PHP did not die; remove the fallback output.
  ob_end_clean();

  // 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' => sprintf("%.02f", $percentage),
    'message' => $message,
  ));
}

Functions

Namesort descending Description
background_batch_overview_page Overview of current and recent batch jobs.
background_batch_page State-based dispatcher for the batch processing page.
_background_batch_initiate Start a batch job in the background
_background_batch_page_do_js Do one pass of execution and inform back the browser about progression (used for JavaScript-mode only).
_background_batch_page_do_nojs Output a batch processing page without JavaScript support.
_background_batch_page_progress_js Batch processing page with JavaScript support.
_background_batch_page_start