You are here

revision_scheduler.module in Revision scheduler 7

File

revision_scheduler.module
View source
<?php

/**
 * Enable scheduling of revision operations on arbitrary dates.
 */

/**
 * Implements hook_permission().
 */
function revision_scheduler_permission() {
  $permission['schedule revisions'] = array(
    'title' => t('Schedule revisions'),
  );
  return $permission;
}

/**
 * Implements hook_menu().
 */
function revision_scheduler_menu() {

  // Both diff and workbench_moderation modules add a default local task, so
  // only add this item if those modules are not available.
  if (!module_exists('diff') && !module_exists('workbench_moderation')) {
    $items['node/%node/revisions/list'] = array(
      'title' => 'List',
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => -10,
      'file path' => drupal_get_path('module', 'node'),
    );
  }
  $items['node/%node/revisions/schedule'] = array(
    'title' => 'Schedule',
    'page callback' => 'revision_scheduler_list_page',
    'page arguments' => array(
      'node',
      1,
    ),
    'access arguments' => array(
      'schedule revisions',
    ),
    'file' => 'revision_scheduler.pages.inc',
    'type' => MENU_LOCAL_TASK,
  );
  $items['node/%node/revisions/schedule/add'] = array(
    'title' => 'Add scheduled revision',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'revision_scheduler_add_form',
      'node',
      1,
    ),
    'access callback' => 'revision_scheduler_operation_create_access',
    'access arguments' => array(
      'node',
      1,
      NULL,
      TRUE,
    ),
    'file' => 'revision_scheduler.pages.inc',
    'type' => MENU_LOCAL_ACTION,
  );

  // Support workbench moderation paths.
  if (module_exists('workbench_moderation')) {
    foreach ($items as $path => $item) {
      $moderation_path = str_replace('/revisions/', '/moderation/', $path);
      $items[$moderation_path] = $item;
    }
  }
  $items['revision-scheduler/%revision_scheduler_operation/run'] = array(
    'title' => 'Edit scheduled revision',
    'page callback' => 'revision_scheduler_operation_run',
    'page arguments' => array(
      1,
    ),
    'access callback' => 'revision_scheduler_operation_access',
    'access arguments' => array(
      'run',
      1,
    ),
    'type' => MENU_CALLBACK,
  );
  $items['revision-scheduler/%revision_scheduler_operation/edit'] = array(
    'title' => 'Edit scheduled revision',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'revision_scheduler_edit_form',
      1,
    ),
    'access callback' => 'revision_scheduler_operation_access',
    'access arguments' => array(
      'update',
      1,
    ),
    'file' => 'revision_scheduler.pages.inc',
  );
  $items['revision-scheduler/%revision_scheduler_operation/delete'] = array(
    'title' => 'Cancel scheduled revision',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'revision_scheduler_delete_form',
      1,
    ),
    'access callback' => 'revision_scheduler_operation_access',
    'access arguments' => array(
      'delete',
      1,
    ),
    'file' => 'revision_scheduler.pages.inc',
  );
  return $items;
}

/**
 * Implements hook_menu_alter().
 */
function revision_scheduler_menu_alter(array &$items) {
  $paths = array(
    'node/%node/revisions',
    'node/%node/revisions/list',
  );
  foreach ($paths as $path) {
    if (isset($items[$path]['access callback'])) {
      $override = '_revision_scheduler_override_' . $items[$path]['access callback'];
      if (function_exists($override)) {
        $items[$path]['access callback'] = $override;
      }
    }
  }
}
function _revision_scheduler_override__node_revision_access($node, $op = 'view', $account = NULL) {
  $access =& drupal_static(__FUNCTION__, array());
  $map = array(
    'view' => 'view revisions',
    'update' => 'revert revisions',
    'delete' => 'delete revisions',
  );
  if (!$node || !isset($map[$op])) {

    // If there was no node to check against, or the $op was not one of the
    // supported ones, we return access denied.
    return FALSE;
  }
  if (!isset($account)) {
    $account = $GLOBALS['user'];
  }

  // Statically cache access by revision ID, user account ID, and operation.
  $cid = $node->vid . ':' . $account->uid . ':' . $op;
  if (!isset($access[$cid])) {

    // Perform basic permission checks first.
    if (!user_access($map[$op], $account) && !user_access('administer nodes', $account)) {
      return $access[$cid] = FALSE;
    }
    $node_current_revision = node_load($node->nid);
    $is_current_revision = $node_current_revision->vid == $node->vid;

    // There should be at least two revisions. If the vid of the given node and
    // the vid of the current revision differ, then we already have two
    // different revisions so there is no need for a separate database check.
    // Also, if you try to revert to or delete the current revision, that's not
    // good.
    if ($is_current_revision && (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(
      ':nid' => $node->nid,
    ))
      ->fetchField() == 1 && !db_query("SELECT COUNT(revision_id) FROM {revision_scheduler} WHERE entity_type = 'node' AND entity_id = :nid", array(
      ':nid' => $node->nid,
    ))
      ->fetchField() || $op == 'update' || $op == 'delete')) {
      $access[$cid] = FALSE;
    }
    elseif (user_access('administer nodes', $account)) {
      $access[$cid] = TRUE;
    }
    else {

      // First check the access to the current revision and finally, if the node
      // passed in is not the current revision then access to that, too.
      $access[$cid] = node_access($op, $node_current_revision, $account) && ($is_current_revision || node_access($op, $node, $account));
    }
  }
  return $access[$cid];
}
function _revision_scheduler_override_diff_node_revision_access($node, $op = 'view') {
  $may_revision_this_type = variable_get('diff_enable_revisions_page_node_' . $node->type, TRUE) || variable_get('enable_revisions_page_' . $node->type, TRUE) || user_access('administer nodes');
  return $may_revision_this_type && _revision_scheduler_override__node_revision_access($node, $op);
}

/**
 * Preprocess the add scheduled revision local action to add a destination query.
 */
function revision_scheduler_preprocess_menu_local_action(&$variables) {
  $link =& $variables['element']['#link'];
  if (isset($link['href']) && !isset($link['localized_options']['query']['destination']) && preg_match('#node/\\d+/(revisions|moderation)/schedule/add#', $link['href'])) {
    $link += array(
      'localized_options' => array(),
    );
    $link['localized_options'] += array(
      'query' => array(),
    );
    $link['localized_options']['query'] += drupal_get_destination();
  }
}

/**
 * Implements hook_admin_paths().
 */
function revision_scheduler_admin_paths() {
  $paths = array();
  if (variable_get('node_admin_theme')) {
    $paths['node/*/revisions/list'] = TRUE;
    $paths['node/*/revisions/schedule'] = TRUE;
    $paths['node/*/revisions/schedule/add'] = TRUE;

    // Support the workbench moderation paths.
    if (module_exists('workbench_moderation')) {
      $paths['node/*/moderation/schedule'] = TRUE;
      $paths['node/*/moderation/schedule/add'] = TRUE;
    }
  }
  $paths['revision-scheduler/*'] = TRUE;
  return $paths;
}

/**
 * Implements hook_entity_delete().
 */
function revision_scheduler_entity_delete($entity, $entity_type) {

  // Delete all the entity's revisions from the revision_scheduler table.
  list($entity_id) = entity_extract_ids($entity_type, $entity);
  db_delete('revision_scheduler')
    ->condition('entity_type', $entity_type)
    ->condition('entity_id', $entity_id)
    ->execute();
}

/**
 * Implements hook_field_attach_delete_revision().
 */
function revision_scheduler_field_attach_delete_revision($entity_type, $entity) {

  // Delete the entity's revision from the revision_scheduler table.
  list($entity_id, $revision_id) = entity_extract_ids($entity_type, $entity);
  db_delete('revision_scheduler')
    ->condition('entity_type', $entity_type)
    ->condition('entity_id', $entity_id)
    ->condition('revision_id', $revision_id)
    ->condition('time_executed', 0)
    ->execute();
}

/**
 * Implements hook_user_cancel().
 */
function revision_scheduler_user_cancel($edit, $account, $method) {
  switch ($method) {
    case 'user_cancel_reassign':

      // Reassign scheduled operations to the anonymous user.
      db_update('revision_scheduler')
        ->fields(array(
        'uid' => 0,
      ))
        ->condition('uid', $account->uid)
        ->execute();
      break;
  }
}

/**
 * Implements hook_user_delete().
 */
function revision_scheduler_user_delete($account) {

  // Remove any operations scheduled by this user.
  db_delete('revision_scheduler')
    ->condition('uid', $account->uid)
    ->execute();
}
function revision_scheduler_entity_type_has_revisions($entity_type) {
  $info = entity_get_info($entity_type);
  return !empty($info['entity keys']['id']) && !empty($info['entity keys']['revision']) && !empty($info['revision table']);
}

/**
 * Return an array of all the revision IDs of a given entity.
 */
function revision_scheduler_get_all_entity_revision_ids($entity_type, $entity_id) {
  if (!revision_scheduler_entity_type_has_revisions($entity_type)) {
    return array();
  }
  $info = entity_get_info($entity_type);
  $id_key = $info['entity keys']['id'];
  $revision_key = $info['entity keys']['revision'];
  $query = db_select($info['revision table'], 'revision');
  $query
    ->addField('revision', $revision_key);
  $query
    ->condition('revision.' . $id_key, $entity_id);
  return $query
    ->execute()
    ->fetchCol();
}
function revision_scheduler_get_entity_revision_key($entity_type) {
  $info = entity_get_info($entity_type);
  return !empty($info['entity keys']['revision']) ? $info['entity keys']['revision'] : FALSE;
}

/**
 * Load a single entity with an optional revision ID.
 *
 * @param string $entity_type
 *   An entity type.
 * @param int $entity_id
 *   An entity ID to load.
 * @param int $revision_id
 *   (optional) An entity revision ID to use when loading the entity rather
 *   than the latest revision.
 *
 * @return object
 *   An entity object from entity_load().
 *
 * @see revision_scheduler_entity_revision_load_multiple()
 */
function revision_scheduler_entity_revision_load($entity_type, $entity_id, $revision_id = NULL) {
  if (empty($revision_id)) {
    $revisions = entity_load($entity_type, array(
      $entity_id,
    ));
  }
  else {
    $revisions = revision_scheduler_entity_revision_load_multiple($entity_type, $entity_id, array(
      $revision_id,
    ));
  }
  return !empty($revisions) ? reset($revisions) : FALSE;
}

/**
 * Load multiple entity revisions.
 *
 * @param string $entity_type
 *   An entity type.
 * @param int $entity_id
 *   An entity ID to load.
 * @param array $revision_ids
 *   An array of revision IDs to load.
 *
 * @return array
 *   An array of entity revision objects from entity_load().
 *
 * @see entity_load()
 */
function revision_scheduler_entity_revision_load_multiple($entity_type, $entity_id, array $revision_ids) {
  $revisions =& drupal_static(__FUNCTION__, array());
  if (!isset($revisions[$entity_type . ':' . $entity_id])) {
    $revisions[$entity_type . ':' . $entity_id] = array();
  }
  $entity_revisions =& $revisions[$entity_type . ':' . $entity_id];
  if ($revision_ids_to_load = array_diff($revision_ids, array_keys($entity_revisions))) {
    $revision_key = revision_scheduler_get_entity_revision_key($entity_type);
    foreach ($revision_ids_to_load as $revision_id) {
      if ($entities = entity_load($entity_type, array(
        $entity_id,
      ), array(
        $revision_key => $revision_id,
      ))) {
        $entity_revisions[$revision_id] = reset($entities);
      }
    }
  }
  return array_intersect_key($entity_revisions, array_flip($revision_ids));
}

/**
 * Implements hook_cron_queue_info().
 */
function revision_scheduler_cron_queue_info() {
  $info['revision_scheduler'] = array(
    'worker callback' => 'revision_scheduler_queue_process_item',
    'time' => variable_get('revision_scheduler_cron_time', 30),
    'skip on cron' => variable_get('revision_scheduler_cron_disable', FALSE),
  );
  return $info;
}

/**
 * Implements hook_cron().
 */
function revision_scheduler_cron() {

  // Ensure that the items are ordered in the same method as
  // revision_scheduler_operation_uasort()
  if ($ids = db_query("SELECT * FROM {revision_scheduler} WHERE time_scheduled <= :now AND time_queued = 0 AND time_executed = 0 ORDER BY time_scheduled ASC, id ASC", array(
    ':now' => REQUEST_TIME,
  ))
    ->fetchCol()) {
    $queue = DrupalQueue::get('revision_scheduler');
    foreach ($ids as $id) {
      if ($queue
        ->createItem($id)) {
        db_update('revision_scheduler')
          ->fields(array(
          'time_queued' => time(),
        ))
          ->condition('id', $id)
          ->execute();
      }
    }
  }
}

/**
 * Callback for the revision_scheduler queue.
 */
function revision_scheduler_queue_process_item($operation_id) {

  // Backwards support for any existing items that were in the queue, stored
  // as objects.
  $operation = is_object($operation_id) ? $operation_id : revision_scheduler_operation_load($operation_id);
  if ($operation) {
    revision_scheduler_operation_process($operation);
  }
}

/**
 * Process a single scheduled revision operation.
 */
function revision_scheduler_operation_process($operation) {
  $transaction = db_transaction();
  $args = array(
    '@entity-type' => $operation->entity_type,
    '@entity-id' => $operation->entity_id,
    '@operation' => $operation->operation,
    '@revision-id' => $operation->revision_id,
  );
  try {
    $entity = revision_scheduler_entity_revision_load($operation->entity_type, $operation->entity_id, $operation->revision_id);
    $operation_info = revision_scheduler_entity_revision_operation_get_info($operation->entity_type, $operation->operation);
    $entity_info = entity_get_info($operation->entity_type);
    if (empty($entity)) {
      throw new Exception(t('Failed to load entity @entity-type @entity-id.', $args));
    }
    elseif (empty($operation_info)) {
      throw new Exception(t('Failed to load revision_scheduler_entity_revision_operation_get_info(@entity-type, @operation).', $args));
    }
    elseif (empty($entity_info)) {
      throw new Exception(t('Failed to load entity_get_info(@entity-type).', $args));
    }

    // Allow modules to alter or validate the operation about to be processed.
    module_invoke_all('revision_scheduler_operation_preprocess', $entity, $operation);
    $callback = $operation_info['callback'];
    if (isset($operation_info['file'])) {
      include_once $operation_info['file'];
    }
    if (!is_callable($callback)) {
      throw new Exception(t('Revision operation @operation callback @callback does not exist.', $args + array(
        '@callback' => is_array($callback) ? implode('::', $callback) : $callback,
      )));
    }

    // Switch the user to the one that created the operation.
    $original_session_saving = drupal_save_session();
    $original_user = $GLOBALS['user'];
    if ($operation->uid != $GLOBALS['user']->uid) {
      drupal_save_session(FALSE);
      if ($operation->uid && ($account = user_load($operation->uid))) {
        $GLOBALS['user'] = $account;
      }
      else {
        $GLOBALS['user'] = drupal_anonymous_user();
      }
    }
    $operation->time_executed = time();
    revision_scheduler_operation_save($operation);

    // Run the operation callback with the entity and operation.
    call_user_func($callback, $entity, $operation);

    // Restore the user.
    $GLOBALS['user'] = $original_user;
    drupal_save_session($original_session_saving);

    // Allow modules to react to the operation after it has been processed.
    module_invoke_all('revision_scheduler_operation_postprocess', $entity, $operation);
    watchdog('revision_scheduler', 'Performed scheduled revision operation @operation on @entity-type @entity-id revision @revision-id.', $args);
  } catch (Exception $e) {
    $transaction
      ->rollback();
    watchdog_exception('revision_scheduler', $e);
  }
}

/**
 * Run a specific scheduled operation.
 */
function revision_scheduler_operation_run($operation) {
  $value = implode('-', array(
    'run',
    $operation->id,
    $operation->operation,
    $operation->time_scheduled,
  ));
  if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], $value)) {
    return MENU_ACCESS_DENIED;
  }
  revision_scheduler_operation_process($operation);
  drupal_goto();
}

/**
 * Load a single scheduled revision operation from the database.
 *
 * @param int $id
 *   A scheduled operation ID.
 *
 * @return object
 *   A scheduled revision operation record.
 *
 * @see revision_scheduler_operation_load_multiple()
 */
function revision_scheduler_operation_load($id) {
  $operations = revision_scheduler_operation_load_multiple(array(
    $id,
  ));
  return !empty($operations) ? reset($operations) : FALSE;
}

/**
 * Load multiple scheduled revision operations from the database.
 *
 * @param array $ids
 *   An array of scheduled operation IDs.
 *
 * @return array
 *   An array of scheduled revision operation records.
 */
function revision_scheduler_operation_load_multiple(array $ids) {
  if (empty($ids)) {
    return array();
  }
  $operations = db_query("SELECT * FROM {revision_scheduler} WHERE id IN (:ids)", array(
    ':ids' => $ids,
  ))
    ->fetchAllAssoc('id');
  return $operations;
}

/**
 * Save a scheduled revision operation to the database.
 *
 * @param object $operation
 *   A scheduled revision operation record.
 */
function revision_scheduler_operation_save($operation) {
  $operation->is_new = empty($operation->id);
  if (!isset($operation->uid)) {
    $operation->uid = $GLOBALS['user']->uid;
  }

  // Ensure the scheduled time is converted to a timestamp.
  if (!empty($operation->time_scheduled) && !is_numeric($operation->time_scheduled)) {
    $operation->time_scheduled = strtotime($operation->time_scheduled);
  }

  // Should we reset queued?

  //$operation->queued = 0;
  if ($operation->is_new) {
    drupal_write_record('revision_scheduler', $operation);
  }
  else {
    drupal_write_record('revision_scheduler', $operation, array(
      'id',
    ));
  }
  unset($operation->is_new);
}

/**
 * Delete a scheduled revision operation from the database.
 *
 * @param int $id
 *   A scheduled operation ID.
 *
 * @see revision_scheduler_operation_delete_multiple()
 */
function revision_scheduler_operation_delete($id) {
  return revision_scheduler_operation_delete_multiple(array(
    $id,
  ));
}

/**
 * Delete multiple scheduled revision operations from the database.
 *
 * @param array $ids
 *   An array of scheduled operation IDs.
 */
function revision_scheduler_operation_delete_multiple(array $ids) {
  if ($operations = revision_scheduler_operation_load_multiple($ids)) {
    db_delete('revision_scheduler')
      ->condition('id', $ids, 'IN')
      ->execute();
  }
}

/**
 * uasort() callback to order revision operations in proper execution order.
 */
function revision_scheduler_operation_uasort($a, $b) {
  if ($a->time_scheduled != $b->time_scheduled) {
    return $a->time_scheduled < $b->time_scheduled ? -1 : 1;
  }
  else {
    return $a->id < $b->id ? -1 : 1;
  }
}

/**
 * Fetch information about entity revision operations.
 *
 * @see hook_entity_revision_operation_info()
 * @see hook_entity_revision_operation_info_alter()
 */
function revision_scheduler_entity_revision_operation_get_info($entity_type = NULL, $operation = NULL) {
  $operations =& drupal_static(__FUNCTION__);
  if (!isset($operations)) {
    $cid = 'revision:operations:info:' . $GLOBALS['language']->language;
    if ($cache = cache_get($cid)) {
      $operations = $cache->data;
    }
    else {
      $operations = module_invoke_all('entity_revision_operation_info');
      drupal_alter('entity_revision_operation_info', $operations);
      foreach ($operations as &$entity_operations) {
        foreach (array_keys($entity_operations) as $key) {
          $entity_operations[$key]['operation'] = $key;
        }
      }
      cache_set($cid, $operations);
    }
  }
  if (isset($entity_type) && isset($operation)) {
    return isset($operations[$entity_type][$operation]) ? $operations[$entity_type][$operation] : FALSE;
  }
  if (isset($entity_type)) {
    return isset($operations[$entity_type]) ? $operations[$entity_type] : array();
  }
  return $operations;
}
function _revision_scheduler_operation_access($operation, $entity_type, $entity = NULL, $account = NULL) {
  $access = FALSE;
  if (isset($operation['access callback']) || isset($operation['access arguments'])) {
    $operation += array(
      'access callback' => 'user_access',
      'access arguments' => array(),
    );
    $access_callback = isset($operation['access callback']) ? $operation['access callback'] : 'user_access';
    if (is_bool($access_callback)) {
      $access = $access_callback;
    }
    else {
      $access_arguments = isset($operation['access arguments']) ? $operation['access arguments'] : array();
      if ($access_callback === 'user_access' && count($access_arguments) === 1) {
        $access_arguments[] = $account;
      }
      $access = call_user_func_array($access_callback, $access_arguments);
    }
  }
  $access_checks = module_invoke_all('entity_revision_operation_access', $operation, $entity_type, $entity, $account);
  if (in_array(FALSE, $access_checks, TRUE)) {
    return FALSE;
  }
  elseif (in_array(TRUE, $access_checks, TRUE)) {
    return TRUE;
  }
  return $access;
}

/**
 * Fetch the list of available entity revision operation that the user can add.
 */
function revision_scheduler_entity_revision_operation_get_options($entity_type, $entity = NULL, $account = NULL) {
  $options =& drupal_static(__FUNCTION__, array());
  if (!isset($account)) {
    $account = $GLOBALS['user'];
  }
  $cid = md5(serialize(array(
    $entity_type,
    $entity,
    $account,
  )));
  if (!isset($options[$cid])) {
    $options[$cid] = array();
    $operations = revision_scheduler_entity_revision_operation_get_info($entity_type);
    foreach ($operations as $key => $operation) {
      if (_revision_scheduler_operation_access($operation, $entity_type, $entity, $account)) {
        $options[$cid][$key] = $operation['label'];
      }
    }
  }
  return $options[$cid];
}

/**
 * Load a single entity with an optional revision ID.
 *
 * @param string $entity_type
 *   An entity type.
 * @param int $entity_id
 *   An entity ID to load.
 * @param int $revision_id
 *   (optional) An entity revision ID to use when loading the entity rather
 *   than the latest revision.
 *
 * @return object
 *   An entity objected from entity_load().
 *
 * @see entity_load()
 */
function revision_scheduler_operation_create_access($entity_type, $entity = NULL, $account = NULL, $check_all_revisions = FALSE) {
  if (!revision_scheduler_entity_type_has_revisions($entity_type)) {
    return FALSE;
  }
  if (!isset($account)) {
    $account = $GLOBALS['user'];
  }
  if (!user_access('schedule revisions', $account)) {
    return FALSE;
  }
  $access =& drupal_static(__FUNCTION__, array());
  $cid = md5(serialize(array(
    $entity_type,
    $entity,
    $account,
    $check_all_revisions,
  )));
  if (!isset($access[$cid])) {
    $access[$cid] = (bool) revision_scheduler_entity_revision_operation_get_options($entity_type, $entity, $account);

    // Fallback check if any of the other entity's revisions are schedulable.
    if ($check_all_revisions && !$access[$cid] && !empty($entity)) {
      list($entity_id, $entity_vid) = entity_extract_ids($entity_type, $entity);
      if (!empty($entity_id)) {

        // Check to ensure one of the revisions can have a scheduled operation.
        $revision_ids = revision_scheduler_get_all_entity_revision_ids($entity_type, $entity_id);
        foreach ($revision_ids as $revision_id) {
          if ($revision_id != $entity_vid && ($revision = revision_scheduler_entity_revision_load($entity_type, $entity_id, $revision_id))) {
            if (revision_scheduler_entity_revision_operation_get_options($entity_type, $revision, $account)) {
              $access[$cid] = TRUE;
              break;
            }
          }
        }
      }
    }
  }
  return $access[$cid];
}
function revision_scheduler_operation_access($op, $operation, $account = NULL) {
  if (!revision_scheduler_entity_type_has_revisions($operation->entity_type)) {
    return FALSE;
  }
  if (!user_access('schedule revisions', $account)) {
    return FALSE;
  }
  $entity = revision_scheduler_entity_revision_load($operation->entity_type, $operation->entity_id, $operation->revision_id);

  // If the revision does not exist, the operation cannot be run or updated.
  if ($op == 'run' || $op == 'update') {
    if (empty($entity)) {
      return FALSE;
    }
  }
  if ($op == 'update') {

    // The user must have the ability to select an operation in order to edit
    // it.
    // @todo Is this actually necessary?
    if (!revision_scheduler_entity_revision_operation_get_options($operation->entity_type, $entity, $account)) {
      return FALSE;
    }
  }
  if ($op == 'run' || $op == 'update' || $op == 'delete') {

    // In order to run, edit, or delete the operation, it must not have already
    // run or been queued for processing.
    return empty($operation->time_executed) && empty($operation->time_queued);
  }

  // Invalid $op.
  return FALSE;
}

/**
 * Implements hook_field_attach_form().
 */
function revision_scheduler_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {

  // Try to detect if this really is an entity edit / add form, if not skip
  // further processing. Unfortunately taxonomy term has a naming inconsistency
  // thus we need a special handling for it.
  $form_entity_type = $entity_type;
  if ($form_entity_type == 'taxonomy_term') {
    $form_entity_type = 'term';
  }
  if (!isset($form_state[$form_entity_type])) {
    return;
  }
  list($entity_id, $revision_id) = entity_extract_ids($entity_type, $entity);
  $form['revision_scheduler'] = array(
    '#type' => 'fieldset',
    '#title' => t('Revision scheduler'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#group' => 'additional_settings',
    '#attributes' => array(
      'class' => array(
        'entity-form-revision-scheduler',
      ),
    ),
    '#weight' => 21,
    '#access' => revision_scheduler_operation_create_access($entity_type, $entity, NULL),
    '#tree' => TRUE,
    '#parents' => array_merge($form['#parents'], array(
      'revision_scheduler',
    )),
  );

  // Pass the form into the edit form to get most of the form elements.
  $operation = new stdClass();
  $operation->id = NULL;
  $operation->entity_type = $entity_type;
  $operation->entity_id = $entity_id;
  $operation->revision_id = $revision_id;
  $operation->operation = NULL;
  $operation->uid = $GLOBALS['user']->uid;
  form_load_include($form_state, 'inc', 'revision_scheduler', 'revision_scheduler.pages');
  $form_state_clone = $form_state;
  $form_state_clone['build_info']['args'] = array(
    $operation,
    $entity,
  );
  $form['revision_scheduler'] = revision_scheduler_edit_form($form['revision_scheduler'], $form_state_clone, $operation, $entity);

  // Tweak the form elements.
  $form['revision_scheduler']['revision_id']['#access'] = FALSE;
  $form['revision_scheduler']['operation']['#required'] = FALSE;
  $form['revision_scheduler']['operation']['#empty_option'] = t('- None -');
  unset($form['revision_scheduler']['actions']);

  // Allow the revision fieldset form elements to be altered using
  // hook_form_revision_scheduler_edit_form_alter().
  drupal_alter('form_revision_scheduler_edit_form', $form['revision_scheduler'], $form_state_clone);
}

/**
 * Implements hook_entity_insert().
 */
function revision_scheduler_entity_insert($entity, $entity_type) {
  if (!empty($entity->revision_scheduler['operation'])) {
    $operation = (object) $entity->revision_scheduler;
    list($operation->entity_id, $operation->revision_id) = entity_extract_ids($entity_type, $entity);
    revision_scheduler_operation_save($operation);
    $entity->revision_scheduler = (array) $operation;
  }
}

/**
 * Implements hook_entity_update().
 */
function revision_scheduler_entity_update($entity, $entity_type) {
  if (!empty($entity->revision_scheduler['operation'])) {
    $operation = (object) $entity->revision_scheduler;

    // Update the revision ID in case a new revision was created.
    list(, $operation->revision_id) = entity_extract_ids($entity_type, $entity);
    revision_scheduler_operation_save($operation);
    $entity->revision_scheduler = (array) $operation;
  }
  drupal_static_reset('revision_scheduler_entity_revision_load_multiple');
}

/**
 * @name revision_scheduler_node Revision scheduler integration on behalf of node.module.
 * @{
 */

/**
 * Implements hook_entity_revision_operation_info() on behalf of node.module.
 */
function node_entity_revision_operation_info() {
  $operations['node']['revert'] = array(
    'label' => t('Revert'),
    'access arguments' => array(
      'revert revisions',
    ),
    'callback' => 'node_node_revision_operation_revert',
  );
  $operations['node']['delete'] = array(
    'label' => t('Delete'),
    'access arguments' => array(
      'delete revisions',
    ),
    'callback' => 'node_node_revision_operation_delete',
  );
  return $operations;
}

/**
 * Node revision operation callback: revert to revision.
 */
function node_node_revision_operation_revert($node) {
  $node->revision = 1;
  $node->log = t('Copy of the revision from %date.', array(
    '%date' => format_date($node->revision_timestamp),
  ));
  node_save($node);
  drupal_set_message(t('@type %title has been reverted back to the revision from %revision-date.', array(
    '@type' => node_type_get_name($node),
    '%title' => $node->title,
    '%revision-date' => format_date($node->revision_timestamp),
  )));
}

/**
 * Node revision operation callback: delete revision.
 */
function node_node_revision_operation_delete($node) {
  if (node_revision_delete($node->vid)) {
    drupal_set_message(t('Revision from %revision-date of @type %title has been deleted.', array(
      '%revision-date' => format_date($node->revision_timestamp),
      '@type' => node_type_get_name($node),
      '%title' => $node->title,
    )));
  }
  else {
    throw new Exception(t('Unable to delete node @nid revision @vid.', array(
      '@nid' => $node->nid,
      '@vid' => $node->vid,
    )));
  }
}

/**
 * Implements hook_entity_revision_operation_access() on behalf of node.module.
 */
function node_entity_revision_operation_access($operation, $entity_type, $entity, $account) {
  if ($entity_type == 'node') {
    switch ($operation['operation']) {
      case 'delete':
      case 'revert':
        if (empty($entity->nid)) {

          // Cannot schedule reversion or deletion of new nodes.
          return FALSE;
        }
        else {

          // node_revision_delete() will fail if this revision is the node's
          // current revision.
          // Also does not make sense to revert to the node's current revision.
          $node = node_load($entity->nid);
          if ($entity->vid == $node->vid) {
            return FALSE;
          }
        }
        break;
    }
  }
}

/**
 * @} End of "name revision_scheduler_node".
 */

/**
 * @name revision_scheduler_workbench_moderation Revision scheduler integration on behalf of workbench_moderation.module.
 * @{
 */

/**
 * Implements hook_entity_revision_operation_info().
 */
function workbench_moderation_entity_revision_operation_info() {
  $info = array();
  $states = workbench_moderation_state_labels();
  foreach ($states as $state => $label) {
    $info['node']['workbench_moderation_' . $state] = array(
      'label' => t('Moderate to @label', array(
        '@label' => $label,
      )),
      'access callback' => TRUE,
      'callback' => 'workbench_moderation_revision_operation_process',
    );
  }
  return $info;
}

/**
 * Implements hook_entity_revision_operation_access().
 */
function workbench_moderation_entity_revision_operation_access($operation, $entity_type, $entity, $account) {
  if (strpos($operation['operation'], 'workbench_moderation_') !== FALSE && !empty($entity) && $entity_type == 'node') {

    //$state = substr($operation['operation'], 21);
    if (!workbench_moderation_node_type_moderated($entity->type)) {
      return FALSE;
    }
  }
}

/**
 * Revision operation process callback for scheduled moderation state changes.
 */
function workbench_moderation_revision_operation_process($entity, $operation) {

  // Extract the transition to state from the operation key: 'workbench_moderation_state'.
  $state = substr($operation->operation, 21);
  workbench_moderation_moderate($entity, $state);
}

/**
 * @} End of "name revision_scheduler_workbench_moderation".
 */

Functions

Namesort descending Description
node_entity_revision_operation_access Implements hook_entity_revision_operation_access() on behalf of node.module.
node_entity_revision_operation_info Implements hook_entity_revision_operation_info() on behalf of node.module.
node_node_revision_operation_delete Node revision operation callback: delete revision.
node_node_revision_operation_revert Node revision operation callback: revert to revision.
revision_scheduler_admin_paths Implements hook_admin_paths().
revision_scheduler_cron Implements hook_cron().
revision_scheduler_cron_queue_info Implements hook_cron_queue_info().
revision_scheduler_entity_delete Implements hook_entity_delete().
revision_scheduler_entity_insert Implements hook_entity_insert().
revision_scheduler_entity_revision_load Load a single entity with an optional revision ID.
revision_scheduler_entity_revision_load_multiple Load multiple entity revisions.
revision_scheduler_entity_revision_operation_get_info Fetch information about entity revision operations.
revision_scheduler_entity_revision_operation_get_options Fetch the list of available entity revision operation that the user can add.
revision_scheduler_entity_type_has_revisions
revision_scheduler_entity_update Implements hook_entity_update().
revision_scheduler_field_attach_delete_revision Implements hook_field_attach_delete_revision().
revision_scheduler_field_attach_form Implements hook_field_attach_form().
revision_scheduler_get_all_entity_revision_ids Return an array of all the revision IDs of a given entity.
revision_scheduler_get_entity_revision_key
revision_scheduler_menu Implements hook_menu().
revision_scheduler_menu_alter Implements hook_menu_alter().
revision_scheduler_operation_access
revision_scheduler_operation_create_access Load a single entity with an optional revision ID.
revision_scheduler_operation_delete Delete a scheduled revision operation from the database.
revision_scheduler_operation_delete_multiple Delete multiple scheduled revision operations from the database.
revision_scheduler_operation_load Load a single scheduled revision operation from the database.
revision_scheduler_operation_load_multiple Load multiple scheduled revision operations from the database.
revision_scheduler_operation_process Process a single scheduled revision operation.
revision_scheduler_operation_run Run a specific scheduled operation.
revision_scheduler_operation_save Save a scheduled revision operation to the database.
revision_scheduler_operation_uasort uasort() callback to order revision operations in proper execution order.
revision_scheduler_permission Implements hook_permission().
revision_scheduler_preprocess_menu_local_action Preprocess the add scheduled revision local action to add a destination query.
revision_scheduler_queue_process_item Callback for the revision_scheduler queue.
revision_scheduler_user_cancel Implements hook_user_cancel().
revision_scheduler_user_delete Implements hook_user_delete().
workbench_moderation_entity_revision_operation_access Implements hook_entity_revision_operation_access().
workbench_moderation_entity_revision_operation_info Implements hook_entity_revision_operation_info().
workbench_moderation_revision_operation_process Revision operation process callback for scheduled moderation state changes.
_revision_scheduler_operation_access
_revision_scheduler_override_diff_node_revision_access
_revision_scheduler_override__node_revision_access