You are here

workbench_scheduler.module in Workbench Scheduler 7

Same filename and directory in other branches
  1. 7.2 workbench_scheduler.module

Content scheduling for Workbench.

File

workbench_scheduler.module
View source
<?php

/**
 * @file
 * Content scheduling for Workbench.
 */

/**
 * Implements hook_menu().
 */
function workbench_scheduler_menu() {
  $items = array();

  // Schedules.
  $items['admin/config/workbench/scheduler'] = array(
    'title' => 'Workbench Schedules',
    'description' => 'Manage content workbench schedules.',
    'page callback' => 'workbench_scheduler_admin_page',
    'access arguments' => array(
      'administer workbench schedules',
    ),
    'file' => 'workbench_scheduler.admin.inc',
  );

  // Schedules.
  $items['admin/config/workbench/scheduler/schedules'] = array(
    'title' => 'Schedules',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -1,
  );

  // Add schedule.
  $items['admin/config/workbench/scheduler/schedules/add'] = array(
    'title' => 'Add Schedule',
    'description' => 'Create a new schedule.',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'workbench_scheduler_admin_edit_schedule',
    ),
    'access arguments' => array(
      'administer workbench schedules',
    ),
    'file' => 'workbench_scheduler.admin.inc',
  );

  // Edit schedule.
  $items['admin/config/workbench/scheduler/schedules/%/edit'] = array(
    'title' => 'Edit Schedule',
    'type' => MENU_CALLBACK,
    'description' => 'Edit a workbench schedule.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'workbench_scheduler_admin_edit_schedule',
      5,
    ),
    'access arguments' => array(
      'administer workbench schedules',
    ),
    'file' => 'workbench_scheduler.admin.inc',
  );

  // Delete schedule.
  $items['admin/config/workbench/scheduler/schedules/%/delete'] = array(
    'title' => 'Delete Schedule',
    'description' => 'Delete a workbench schedule.',
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'workbench_scheduler_admin_delete_schedule',
      5,
    ),
    'access arguments' => array(
      'administer workbench schedules',
    ),
    'file' => 'workbench_scheduler.admin.inc',
  );

  // Manage node schedules.
  $items['node/%node/manage_schedules'] = array(
    'title' => 'Manage Schedules',
    'description' => 'Manage the schedules set for this node.',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'workbench_scheduler_admin_manage_node_schedules',
      1,
    ),
    'access callback' => 'workbench_scheduler_manage_schedules_access_check',
    'file' => 'workbench_scheduler.admin.inc',
  );

  // Edit existing node schedule for a revision.
  $items['node/%node/manage_schedules/%/edit'] = array(
    'title' => 'Edit Revision Schedule',
    'description' => 'Edit the schedule for this revision',
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'workbench_scheduler_admin_edit_revision_schedule',
      1,
      3,
    ),
    'access callback' => 'workbench_scheduler_manage_schedules_access_check',
    'file' => 'workbench_scheduler.admin.inc',
  );
  return $items;
}

/**
 * Access callback to see if current node type allows schedules.
 *
 * @return bool
 *   Return boolean value.
 */
function workbench_scheduler_manage_schedules_access_check() {

  // First make sure user has access.
  if (user_access('set workbench schedule')) {

    // Attempt to load schedule types and node.
    if (($types = workbench_scheduler_get_types()) && ($node = node_load(arg(1)))) {

      // Check if node is a schedule type.
      return !empty($types) && in_array($node->type, $types);
    }
  }
  return FALSE;
}

/**
 * Implements hook_admin_paths().
 */
function workbench_scheduler_admin_paths() {
  if (variable_get('node_admin_theme', FALSE)) {
    $paths = array(
      'node/*/manage_schedules' => TRUE,
      'node/*/manage_schedules/*/edit' => TRUE,
    );
    return $paths;
  }
}

/**
 * Implements hook_permission().
 *
 * Provides permissions for workbench schedules.
 */
function workbench_scheduler_permission() {
  $permissions = array();
  $permissions['administer workbench schedules'] = array(
    'title' => t('Administer workbench schedules'),
  );

  // Expand for each content type.
  $permissions['set workbench schedule'] = array(
    'title' => t('Set workbench schedule'),
  );
  $permissions['set any workbench schedule'] = array(
    'title' => t('Set any workbench schedule'),
  );
  if ($schedules = workbench_scheduler_load_schedules()) {
    foreach ($schedules as $schedule) {
      $permissions['set workbench schedule for ' . $schedule->name] = array(
        'title' => t('Set workbench schedule for "@label"', array(
          '@label' => $schedule->label,
        )),
      );
    }
  }
  return $permissions;
}

/**
 * Implements hook_action_info().
 */
function workbench_scheduler_action_info() {
  module_load_include('inc', 'workbench_scheduler', 'actions/workbench_scheduler.action');
  return array(
    'workbench_scheduler_schedules_action' => array(
      'type' => 'node',
      'label' => t('Set Workbench schedule to a node'),
      'behavior' => array(
        'changes_property',
      ),
      'configurable' => FALSE,
      'vbo_configurable' => TRUE,
      'triggers' => array(
        'any',
      ),
    ),
  );
}

/**
 * Implements hook_node_load().
 */
function workbench_scheduler_node_load($nodes, $types) {

  // Are there any node types that have schedules setup?
  if ($scheduled_types = workbench_scheduler_get_types()) {

    // Any of these nodes match those types?
    if (count(array_intersect($scheduled_types, $types))) {

      // Fetch schedule data for each node based on nid & vid.
      foreach ($nodes as $nid => &$node) {

        // Is this node one of the scheduled types?
        if (in_array($node->type, $scheduled_types)) {

          // Fetch the scheduled data for this node.
          if ($schedule = workbench_scheduler_load_node_schedule($nid, $node->vid)) {
            $node->workbench_schedule = $schedule;
          }
        }
      }
    }
  }
}

/**
 * Implements hook_node_insert().
 */
function workbench_scheduler_node_insert($node) {
  if (isset($node->workbench_schedule)) {

    // Save the schedule.
    workbench_scheduler_save_node_schedule($node->nid, $node->vid, (array) $node->workbench_schedule);
  }
}

/**
 * Implements hook_node_update().
 */
function workbench_scheduler_node_update($node) {
  if (isset($node->workbench_schedule)) {

    // Update the schedule.
    workbench_scheduler_save_node_schedule($node->nid, $node->vid, (array) $node->workbench_schedule);
  }
}

/**
 * Implements hook_node_delete().
 */
function workbench_scheduler_node_delete($node) {

  // Delete any schedule data for this node.
  workbench_scheduler_delete_node_schedule($node->nid);
}

/**
 * Implements hook_node_type_delete().
 */
function workbench_scheduler_node_type_delete($info) {
  $delete = db_delete('workbench_scheduler_types');
  $delete
    ->condition('type', $info->type);
  $delete
    ->execute();
}

/**
 * Implements hook_node_revision_delete().
 */
function workbench_scheduler_node_revision_delete($node) {

  // Delete the schedule for this revision.
  workbench_scheduler_delete_node_schedule($node->nid, $node->vid);
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function workbench_scheduler_form_node_form_alter(&$form, &$form_state, $form_id) {

  // Does the user have permission to set schedules?
  $user_permission = user_access('set workbench schedule');

  // And are there any schedules for this node type?
  $type_schedules = workbench_scheduler_load_type_schedules($form['#node']->type);
  if ($user_permission && $type_schedules) {

    // Add a scheduler section.
    $form['workbench_scheduler'] = array(
      '#type' => 'fieldset',
      '#title' => t('Workbench Schedule'),
      '#description' => t('Select a schedule for changing moderation states.'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#group' => 'additional_settings',
      '#access' => user_access('set workbench schedule'),
      '#attached' => array(
        'js' => array(
          drupal_get_path('module', 'workbench_scheduler') . '/js/workbench_scheduler.js',
        ),
      ),
    );

    // Build an options array for which schedules to choose.
    $schedule_options = array();

    // Add default option of no schedule.
    $schedule_options[] = array(
      'label' => t('No Schedule'),
      'start_state' => '',
      'end_state' => '',
    );

    // Retrieve a list of human safe moderation state names.
    $moderation_states = workbench_scheduler_state_labels();

    // Add each type schedule to the options array.
    foreach ($type_schedules as $schedule) {
      if (user_access('set any workbench schedule') || user_access('set workbench schedule for ' . $schedule->name)) {
        $schedule_options[$schedule->sid] = array(
          'label' => $schedule->label,
          'start_state' => !empty($schedule->start_state) ? $moderation_states[$schedule->start_state] : '',
          'end_state' => !empty($schedule->end_state) ? $moderation_states[$schedule->end_state] : '',
        );
      }
    }

    // Add schedules to JavaScript settings.
    $form['workbench_scheduler']['#attached']['js'][] = array(
      'data' => array(
        'workbench_scheduler' => array(
          'schedules' => $schedule_options,
        ),
      ),
      'type' => 'setting',
    );

    // Create table select for selecting schedule.
    $form['workbench_scheduler']['workbench_scheduler_sid'] = array(
      '#type' => 'tableselect',
      '#title' => t('Select Schedule'),
      '#description' => t('Select the schedule to use for this node.'),
      '#options' => $schedule_options,
      '#multiple' => FALSE,
      '#header' => array(
        'label' => t('Name'),
        'start_state' => t('Start State'),
        'end_state' => t('End State'),
      ),
    );

    // Fieldset for collecting schedule dates.
    $form['workbench_scheduler']['dates'] = array(
      '#type' => 'fieldset',
      '#title' => t('Schedule Dates'),
      '#description' => t('Select the start and/or end dates to trigger states changes for this node.'),
      '#states' => array(
        'invisible' => array(
          ':input[name=workbench_scheduler_sid]' => array(
            'value' => '0',
          ),
        ),
      ),
    );

    // Start date.
    $form['workbench_scheduler']['dates']['workbench_scheduler_start_date'] = array(
      '#type' => 'date_popup',
      '#date_format' => 'Y-m-d H:i',
      '#title' => t('Start date'),
      '#description' => t('Select the date to switch this node to the scheduled "start state"'),
    );

    // End date.
    $form['workbench_scheduler']['dates']['workbench_scheduler_end_date'] = array(
      '#type' => 'date_popup',
      '#date_format' => 'Y-m-d H:i',
      '#title' => t('End date'),
      '#description' => t('Select the date to switch this node to the scheduled "end state"'),
    );
    $node_schedule = isset($form['#node']->workbench_schedule) ? $form['#node']->workbench_schedule : FALSE;

    // Have a node schedule(editing a node)? Use for defaults.
    if ($node_schedule) {
      $form['workbench_scheduler']['workbench_scheduler_sid']['#default_value'] = $node_schedule->sid;
      if ($node_schedule->start_date) {

        // Format date for field.
        $start_date = format_date($node_schedule->start_date, 'custom', 'Y-m-d H:i:s');
        $form['workbench_scheduler']['dates']['workbench_scheduler_start_date']['#default_value'] = $start_date;
      }
      if ($node_schedule->end_date) {

        // Format date for field.
        $end_date = format_date($node_schedule->end_date, 'custom', 'Y-m-d H:i:s');
        $form['workbench_scheduler']['dates']['workbench_scheduler_end_date']['#default_value'] = $end_date;
      }
    }

    // Display message depending on schedule settings.
    $msg = '';
    $type_settings = variable_get('workbench_scheduler_' . $form['#node']->type, array());

    // Check if only process latest revision.
    if (in_array('workbench_scheduler_limit_current_revision', $type_settings)) {
      $msg = t('Only the schedule for the most recent revision will be run.');
    }
    elseif (isset($form['#node']->nid)) {

      // Check if there are existing active schedules.
      $active_schedules = workbench_scheduler_count_node_schedules($form['#node']->nid, 'active');

      // Schedules set?
      if ($active_schedules > 0) {

        // Display message about active schedules.
        $msg = format_plural($active_schedules, 'There is still 1 active schedule for a previous revision. !link', 'There are still @count active schedules for previous revisions. !link', array(
          '!link' => l(t('Review Schedules'), 'node/' . $form['#node']->nid . '/manage_schedules'),
        ));
      }
    }
    if ($msg) {
      $form['workbench_scheduler']['note'] = array(
        '#type' => 'item',
        '#title' => t('Note'),
        '#markup' => $msg,
      );
    }

    // Add custom validation and submission hooks.
    $form['#validate'][] = 'workbench_scheduler_node_form_validate';

    // Add to submit button action so that will have access to the new,
    // Nid & vid values.
    $form['actions']['submit']['#submit'][] = 'workbench_scheduler_node_form_submit';
  }
}

/**
 * Validation function for editing a scheduled node.
 *
 * @param array $form
 *   The form array.
 * @param array $form_state
 *   The form state array.
 */
function workbench_scheduler_node_form_validate($form, &$form_state) {
  if (!empty($form_state['values']['workbench_scheduler_start_date'])) {
    $start_date = $form_state['values']['workbench_scheduler_start_date'];
  }
  else {
    $start_date = '';
  }
  if (!empty($form_state['values']['workbench_scheduler_end_date'])) {
    $end_date = $form_state['values']['workbench_scheduler_end_date'];
  }
  else {
    $end_date = '';
  }

  // Retrieve schedule data.
  $sid = $form_state['values']['workbench_scheduler_sid'];

  // Was a schedule selected?
  if ($sid && $sid > 0) {

    // Both a start date and an end date, start should be before end.
    if ($start_date && $end_date && $start_date >= $end_date) {
      form_set_error('workbench_scheduler_end_date', t('End date must be after start date.'));
    }
    elseif ($start_date || $end_date) {

      // All good.
    }
    else {

      // Only other option, are no dates. throw an error.
      form_set_error('workbench_scheduler_start_date', t('Must provide either a start date or end date for workbench schedule'));
    }
  }
  elseif ($start_date || $end_date) {
    if ($form_state['values']['workbench_scheduler_sid'] != 0) {

      // Need to select a schedule for the dates unless none were selected.
      form_set_error('workbench_scheduler_sid', t('Must select a workbench schedule to apply to the provided date(s)'));
    }
  }
}

/**
 * Submit function for editing a scheduled node.
 *
 * @param array $form
 *   The form array.
 * @param array $form_state
 *   The form state array.
 */
function workbench_scheduler_node_form_submit($form, &$form_state) {
  if (!empty($form_state['values']['workbench_scheduler_start_date'])) {
    $start_date = $form_state['values']['workbench_scheduler_start_date'];
  }
  else {
    $start_date = '';
  }
  if (!empty($form_state['values']['workbench_scheduler_end_date'])) {
    $end_date = $form_state['values']['workbench_scheduler_end_date'];
  }
  else {
    $end_date = '';
  }

  // Retrieve schedule data.
  $schedule_data = array(
    'sid' => $form_state['values']['workbench_scheduler_sid'],
    // Format to timestamp for storage.
    'start_date' => strtotime($start_date),
    // Format to timestamp for storage.
    'end_date' => strtotime($end_date),
  );

  // Get the node data from the form state.
  $nid = $form_state['node']->nid;
  $vid = $form_state['node']->vid;

  // Have a schedule selected? sid of 0 for removing a schedule.
  if ($schedule_data['sid'] || $schedule_data['sid'] == 0) {

    // Attempt to save the schedule for this node.
    if (workbench_scheduler_save_node_schedule($nid, $vid, $schedule_data)) {

      // Success.
    }
    else {
      drupal_set_message(t('Error saving workbench schedule for node'), 'error', FALSE);
    }
  }
}

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

  // Call the run function to process schedules.
  if ($count = _workbench_scheduler_run()) {
    watchdog('workbench_scheduler', '@count Workbench Schedules were processed.', array(
      '@count' => $count,
    ));
  }
}

/**
 * Process the different workbench scheduler schedules.
 */
function _workbench_scheduler_run() {
  $count = 0;

  // Only run if there are schedules.
  if (workbench_scheduler_schedules_exist()) {

    // Run scheduler.
    $curr_time = time();

    // Process the start date schedules.
    $count += workbench_scheduler_process_start_dates($curr_time);

    // Process the end date schedules.
    $count += workbench_scheduler_process_end_dates($curr_time);
  }
  return $count;
}

/**
 * Implements hook_views_api().
 */
function workbench_scheduler_views_api() {
  return array(
    'api' => 2.0,
  );
}

/**
 * Retrieve a list of all the content types associated to schedules.
 *
 * @return mixed
 *   Returns a boolean FALSE or array of associated types.
 */
function workbench_scheduler_get_types() {
  $types =& drupal_static(__FUNCTION__);
  if (!isset($types)) {

    // Build select query to retrieve all distinct content types,
    // In the workbench_scheduler_types table.
    $types_query = db_select('workbench_scheduler_types', 'wst')
      ->fields('wst', array(
      'type',
    ))
      ->distinct()
      ->execute();

    // If query returns results, build an array.
    if ($types_query
      ->rowCount()) {
      foreach ($types_query as $type) {
        $types[] = $type->type;
      }
    }
  }
  return $types;
}

/**
 * Returns the number of results for schedules with given machine name(s).
 *
 * @param mixed $names
 *   Machine name of schedule(s).
 *
 * @return int
 *   The number of results.
 */
function _workbench_schedule_check_machine_name_exists($names) {
  if (!is_array($names)) {
    $names = array(
      $names,
    );
  }

  // Turn into an associate array.
  $names = array_flip($names);
  if ($schedule_names = workbench_scheduler_schedule_names()) {

    // Find the intersection.
    $intersect = array_intersect_key($names, $schedule_names);
    return count($intersect);
  }

  // No schedules, so return 0.
  return 0;
}

/**
 * Generate an array of moderation states.
 *
 * @return array
 *   Array of moderation states.
 */
function workbench_scheduler_state_labels() {
  $states =& drupal_static(__FUNCTION__);
  if (!isset($states)) {

    // Retrieve the moderation states from the workbench_moderation module.
    module_load_include('module', 'workbench_moderation');
    $states = workbench_moderation_state_labels();

    // Add unpublished to list since it is not actually a moderation state.
    $states['unpublished'] = t('Unpublished');
  }
  return $states;
}

/**
 * Generate an array of schedule machine names.
 *
 * @return array
 *   An array of schedule machine names.
 */
function workbench_scheduler_schedule_names() {
  $names =& drupal_static(__FUNCTION__);
  if (!isset($names)) {

    // Fetch list of all machine_names for schedules from the DB.
    $names = db_select('workbench_scheduler_schedules', 'wss')
      ->fields('wss', array(
      'name',
      'label',
    ))
      ->execute()
      ->fetchAllAssoc('name');

    // Simplify the assoc array.
    foreach ($names as &$name) {
      $name = $name->label;
    }
  }
  return $names;
}

/**
 * Return an array of counts of schedules for a specific node.
 *
 * @param int $nid
 *   Node id.
 * @param bool|string $status
 *   Which status to return counts.
 *
 * @return int
 *   Return count of schedules.
 */
function workbench_scheduler_count_node_schedules($nid, $status = FALSE) {
  $counts = array(
    'active' => 0,
    'completed' => 0,
  );

  // Select from the workbench_scheduler_nodes table.
  $node_schedules = db_select('workbench_scheduler_nodes', 'wsn')
    ->fields('wsn', array(
    'nid',
  ));

  // Retrieve count of completed.
  $node_schedules
    ->addExpression('SUM(wsn.completed)', 'completed_schedules');

  // Retrieve count of active (count of completed - sum).
  $node_schedules
    ->addExpression('(COUNT(wsn.completed) - SUM(wsn.completed))', 'active_schedules');

  // WHERE node id matches this node id.
  $node_schedules
    ->condition('wsn.nid', $nid)
    ->range(0, 1);

  // Execute.
  $node_schedules = $node_schedules
    ->execute();

  // Returned results?
  if ($node_schedules
    ->rowCount()) {

    // Gather data from first result.
    $result = $node_schedules
      ->fetchObject();
    $counts['completed'] = $result->completed_schedules;
    $counts['active'] = $result->active_schedules;
  }

  // Only want count for a specific status?
  if ($status) {
    if (isset($counts[$status])) {
      return $counts[$status];
    }
    else {
      return 0;
    }
  }
  return array_sum($counts);
}

/**
 * Retrieves schedule data for a given node revision.
 *
 * @param int $nid
 *   Node id.
 * @param int $vid
 *   Revision id.
 *
 * @return mixed
 *   Return an array of scheduler data for the node, or boolean TRUE
 *    if there is no schedule for this node.
 */
function workbench_scheduler_load_node_schedule($nid, $vid) {

  // Select from the workbench_scheduler_nodes table.
  $node_schedule_query = db_select('workbench_scheduler_nodes', 'wsn');

  // Join the workbench_scheduler_schedule table on the schedule id.
  $node_schedule_query
    ->innerjoin('workbench_scheduler_schedules', 'wss', 'wsn.sid = wss.sid');

  // Retrieve the node start and end date.
  $node_schedule_query
    ->fields('wsn', array(
    'start_date',
    'end_date',
    'completed',
  ))
    ->fields('wss');

  // WHERE node id matches this node id.
  $node_schedule_query
    ->condition('wsn.nid', $nid)
    ->condition('wsn.vid', $vid)
    ->range(0, 1);
  $node_schedule = $node_schedule_query
    ->execute();

  // Was data successfully retrieve?
  if ($node_schedule
    ->rowCount()) {
    return $node_schedule
      ->fetchObject();
  }

  // Return a boolean false if nothing is retrieved.
  return FALSE;
}

/**
 * Saves the schedule data for a given node revision.
 *
 * @param int $nid
 *   The node id.
 * @param int $vid
 *   The node revision id.
 * @param array $schedule_data
 *   Array of schedule data to set/update.
 *
 * @return bool
 *   Boolean TRUE/TRUE on success of query.
 */
function workbench_scheduler_save_node_schedule($nid, $vid, $schedule_data) {

  // Need to have the sid and start date  or end date data.
  if (isset($schedule_data['sid']) && ($schedule_data['sid'] == 0 || $schedule_data['start_date'] || $schedule_data['end_date'])) {
    if ($schedule_data['sid'] != 0) {

      // Run a merge query to insert or update the row.
      $merge = db_merge('workbench_scheduler_nodes')
        ->key(array(
        'nid' => $nid,
        'vid' => $vid,
      ))
        ->fields(array(
        'sid' => $schedule_data['sid'],
        // Start date if it was passed.
        'start_date' => $schedule_data['start_date'] ? $schedule_data['start_date'] : 0,
        // End if it was passed.
        'end_date' => $schedule_data['end_date'] ? $schedule_data['end_date'] : 0,
      ))
        ->execute();

      // Was the merge successful?
      if ($merge) {

        // Apply update hooks.
        module_invoke_all('workbench_scheduler_post_save_node_schedule', $nid, $vid, $schedule_data);
        return TRUE;
      }
    }
    elseif ($schedule_data['sid'] == 0) {

      // Make sure the this revision has no schedule.
      workbench_scheduler_delete_node_schedule($nid, $vid);

      // Schedule_data removed.
      return TRUE;
    }
  }

  // Return boolean false if missing fields or failed query.
  return FALSE;
}

/**
 * Delete the schedule(s) for a node.
 *
 * @param int $nid
 *   The node_id.
 * @param int $vid
 *   Optional node revision id.
 * @param int $sid
 *   Optional schedule id.
 *
 * @return bool
 *   Boolean result of delete query.
 */
function workbench_scheduler_delete_node_schedule($nid, $vid = 0, $sid = 0) {

  // Build the delete query.
  $delete = db_delete('workbench_scheduler_nodes')
    ->condition('nid', $nid);

  // Deleting for a specific revision?
  if ($vid) {
    $delete
      ->condition('vid', $vid);
  }

  // Deleting for a specific schedule.
  if ($sid) {
    $delete
      ->condition('sid', $sid);
  }
  $delete = $delete
    ->execute();
  if ($delete) {
    module_invoke_all('workbench_scheduler_post_delete_node_schedule', $nid, $vid, $sid);
    return $delete;
  }
}

/**
 * Mark that a node schedule has been completed.
 *
 * @param int $nid
 *   The node id.
 * @param int $vid
 *   The node revision id.
 * @param int $sid
 *   The schedule id.
 */
function workbench_scheduler_node_set_complete($nid, $vid, $sid) {
  db_update('workbench_scheduler_nodes')
    ->fields(array(
    'completed' => 1,
  ))
    ->condition('nid', $nid)
    ->condition('vid', $vid)
    ->condition('sid', $sid)
    ->execute();
  module_invoke_all('workbench_scheduler_post_node_set_complete', $nid, $vid, $sid);
}

/**
 * Return a list of the schedules for a given content type.
 *
 * @param mixed $types
 *   A string value of a content type or an array of content types.
 *
 * @return mixed
 *   An array of schedule objects or boolean false.
 */
function workbench_scheduler_load_type_schedules($types) {
  $schedules =& drupal_static(__FUNCTION__);

  // Check to see if only retrieving for a single type.
  $get_single = FALSE;
  if ($schedules) {

    // Create an array of the type schedules already retrieved.
    $retrieved_types = array_keys($schedules);
  }

  // Passed a single content type?
  if (!is_array($types)) {

    // Retrieving a single.
    $get_single = $types;

    // Make it an array.
    $types = array(
      $types,
    );
  }

  // Find out which of the types have not already been retrieved.
  if (isset($retrieved_types)) {
    $missing_types = array_diff($types, $retrieved_types);
  }
  else {
    $missing_types = $types;
  }

  // Only need to perform query if no schedules or missing types.
  if (!$schedules || count($schedules) < 1 || count($missing_types) > 0) {

    // Retrieve machine name of schedules for types.
    $schedules_query = db_select('workbench_scheduler_types', 'wst')
      ->fields('wst')
      ->condition('wst.type', $missing_types, 'IN')
      ->execute();

    // Retrieved results?
    if ($schedules_query
      ->rowCount()) {

      // Add the schedules to the array.
      foreach ($schedules_query as $type_schedule) {
        $schedule = workbench_scheduler_load_schedules($type_schedule->name);
        $schedules[$type_schedule->type][$schedule->sid] = $schedule;
      }
    }
  }
  if ($schedules && count($schedules) > 0) {

    // Flip types to make it assoc.
    $types = array_flip($types);

    // Take only a subsection of what was retrieved.
    $return_schedules = array_intersect_key($schedules, $types);

    // Able to get they types?
    if (count($return_schedules) > 0) {

      // Only want a single one?
      if ($get_single) {

        // And that type exists in the array?
        if (isset($return_schedules[$get_single])) {

          // Only return schedules for that type.
          $return_schedules = $return_schedules[$get_single];
        }
      }
      return $return_schedules;
    }
  }
  return FALSE;
}

/**
 * Retrieve an array of schedule data.
 *
 * @param mixed $names
 *   Machine name of a schedule or array of machine names (optional).
 *
 * @return mixed
 *   An array of schedule data or boolean FALSE.
 */
function workbench_scheduler_load_schedules($names = FALSE) {
  $schedules =& drupal_static(__FUNCTION__);

  // Boolean to check if only retrieving a single schedule.
  $get_single = FALSE;
  if ($schedules) {

    // Create an array of the schedules names already retrieved.
    $retrieved_names = array_keys($schedules);
  }

  // Passed machine name(s) of schedules to retrieve?
  if ($names) {

    // Passed a single schedule name?
    if (!is_array($names)) {

      // Retrieving a single.
      $get_single = $names;

      // Make it an array.
      $names = array(
        $names,
      );
    }

    // Find out which of the schedules have not already been retrieved.
    if (isset($retrieved_names)) {
      $missing_names = array_diff($names, $retrieved_names);
    }
    else {
      $missing_names = $names;
    }
  }

  // Only need to query the DB if noe schedules, no names or missing names.
  if (!$schedules || count($schedules) < 1 || !isset($missing_names) || count($missing_names) > 0) {

    // Build query to retrieve schedules.
    $schedules_query = db_select('workbench_scheduler_schedules', 'wss');

    // Left join the types table to get related content types based on.
    // Schedule machine names.
    $schedules_query
      ->leftjoin('workbench_scheduler_types', 'wst', 'wss.name = wst.name');

    // Retrieve all the fields for a schedule.
    $schedules_query
      ->fields('wss')
      ->fields('wst', array(
      'type',
    ));

    // Have missing names we want to only retrieve?
    if (isset($missing_names)) {

      // Add condition to only retrieve these schedules.
      $schedules_query
        ->condition('wss.name', $missing_names, 'IN');
    }

    // Have previously retrieved schedules?
    if (isset($retrieved_names)) {

      // Add condition not to retrieve these.
      $schedules_query
        ->condition('wss.name', $retrieved_names, 'NOT IN');
    }

    // Perform the query.
    $retrieved_schedules = $schedules_query
      ->execute();

    // Were results returned?
    if ($retrieved_schedules
      ->rowCount()) {

      // Loop through the results.
      foreach ($retrieved_schedules as $schedule) {

        // Already retrieved the schedule?
        if (isset($schedules[$schedule->name])) {

          // Yes, then just add this content type to it.
          $schedules[$schedule->name]->types[] = $schedule->type;
        }
        else {

          // No, add to schedules array.
          $schedules[$schedule->name] = $schedule;

          // Add an array for the content types.
          $schedules[$schedule->name]->types = array(
            $schedule->type,
          );

          // Do not need the type attribute.
          unset($schedules[$schedule->name]->type);
        }
      }
    }
  }

  // Have schedules to return?
  if ($schedules && count($schedules) > 0) {

    // Have a specific sub set of schedules ?
    if ($names) {

      // Flip names to make it assoc.
      $names = array_flip($names);

      // Take only a sub section of the returned schedules.
      $return_schedules = array_intersect_key($schedules, $names);

      // Only want a single schedule?
      if ($get_single) {

        // Is it present in the array?
        if (isset($return_schedules[$get_single])) {

          // Return just that schedule.
          return $return_schedules[$get_single];
        }
      }
      elseif (count($return_schedules) > 0) {
        return $return_schedules;
      }
    }
    else {

      // Return all of the retrieved schedules.
      return $schedules;
    }
  }

  // No schedules fetched, return boolean FALSE.
  return FALSE;
}

/**
 * Inserts/updates a schedule.
 *
 * @param string $name
 *   The machine name of the schedule.
 * @param array $schedule_data
 *   An array of other schedule data.
 *
 * @return bool
 *   boolean TRUE/FALSE if the insert/update was a success
 */
function workbench_scheduler_save_schedule($name, $schedule_data) {

  // Need to have the label, start_state and end_state.
  if (isset($schedule_data['label']) && isset($schedule_data['start_state']) && isset($schedule_data['end_state'])) {

    // Run a merge query to insert or update the row.
    $merge = db_merge('workbench_scheduler_schedules')
      ->key(array(
      'name' => $name,
    ))
      ->fields(array(
      'label' => trim($schedule_data['label']),
      'start_state' => trim($schedule_data['start_state']),
      'end_state' => trim($schedule_data['end_state']),
    ))
      ->execute();
    if ($merge) {

      // Core schedule data was updated, need to update types?
      if (isset($schedule_data['types'])) {

        // Delete the old types set.
        db_delete('workbench_scheduler_types')
          ->condition('name', $name)
          ->execute();

        // Build insert query for new types.
        $insert_query = db_insert('workbench_scheduler_types')
          ->fields(array(
          'name',
          'type',
        ));
        foreach ($schedule_data['types'] as $content_type) {
          $insert_query
            ->values(array(
            'name' => $name,
            'type' => $content_type,
          ));
        }
        $insert_query
          ->execute();
      }

      // Reset the statics.
      drupal_static_reset('workbench_scheduler_load_schedules');
      drupal_static_reset('workbench_scheduler_load_type_schedules');
      return $merge;
    }
  }

  // Return boolean FALSE if missing fields or failed query.
  return FALSE;
}

/**
 * Delete schedule(s) add associated data from the database.
 *
 * @param mixed $names
 *   Machine name(s) of schedule(s) to be deleted.
 *
 * @return bool
 *   True or false is successful.
 */
function workbench_scheduler_delete_schedules($names) {

  // If was not passed an array of machine names.
  if (!is_array($names)) {

    // Make machine name an array with one element.
    $names = array(
      $names,
    );
  }

  // Get all sid's for the schedules going to be delete.
  $sids_query = db_select('workbench_scheduler_schedules', 'wss')
    ->fields('wss', array(
    'sid',
  ))
    ->condition('wss.name', $names, 'IN')
    ->execute();
  $sids = array();
  if ($sids_query
    ->rowCount()) {
    foreach ($sids_query as $sid) {
      $sids[] = $sid->sid;
    }
  }

  // Schedules nodes to delete?
  if (count($sids) > 0) {

    // Delete all nodes for these schedules.
    db_delete('workbench_scheduler_nodes')
      ->condition('sid', $sids, 'IN')
      ->execute();
  }

  // Delete all types associated to these schedules.
  db_delete('workbench_scheduler_types')
    ->condition('name', $names, 'IN')
    ->execute();

  // Delete the schedules themselves.
  db_delete('workbench_scheduler_schedules')
    ->condition('name', $names, 'IN')
    ->execute();

  // Check to see if machine name still exists as validation.
  if (_workbench_schedule_check_machine_name_exists($names)) {

    // Failed to delete, return false.
    return FALSE;
  }
  else {

    // Reset the statics.
    drupal_static_reset('workbench_scheduler_load_schedules');
    drupal_static_reset('workbench_scheduler_load_type_schedules');
    return TRUE;
  }
}

/**
 * Check to see if schedules exist in the db.
 *
 * @return bool
 *   Boolean TRUE/FALSE if at least one schedule exists.
 */
function workbench_scheduler_schedules_exist() {

  // Run query to see if an sid can be turned.
  // Should return a boolean TRUE/FALSE.
  $count = db_select('workbench_scheduler_schedules', 'wss')
    ->fields('wss', array(
    'sid',
  ))
    ->execute()
    ->rowCount();
  return $count > 0;
}

/**
 * Run schedules for start times / states.
 *
 * @param int $timestamp
 *   Timestamp to check against.
 *
 * @return int|bool
 *   Count of nodes that were processed or boolean FALSE.
 */
function workbench_scheduler_process_start_dates($timestamp) {

  // Fetch all nodes that need to have their states changed to the 'start_date'.
  // State before NOW, and have not already done so.
  // Instantiate a counter for number of nodes processed.
  $count = 0;

  // Select from the workbench_scheduler_nodes table.
  $schedule_query = db_select('workbench_scheduler_nodes', 'wsn');

  // Join node table to get 'type'.
  $schedule_query
    ->innerjoin('node', 'node', 'node.nid = wsn.nid');

  // Inner join on workbench_scheduler_schedules table on schedule id.
  $schedule_query
    ->innerjoin('workbench_scheduler_schedules', 'wss', 'wsn.sid = wss.sid');

  // Inner join on workbench_moderation_node_history table on nid, revision id.
  // And where the current state does not equal the start state.
  $schedule_query
    ->innerjoin('workbench_moderation_node_history', 'wmnh', 'wsn.nid = wmnh.nid AND wsn.vid = wmnh.vid AND wss.start_state != wmnh.state');

  // Join the node history table and grab highest vid of the highest hid.
  $schedule_query
    ->leftJoin('workbench_moderation_node_history', 'wmnh2', 'wmnh.nid = wmnh2.nid AND wmnh.vid = wmnh2.vid AND wmnh.hid < wmnh2.hid');

  // Retrieve the nid, vid from the workbench_scheduler_nodes table.
  $schedule_query
    ->fields('wsn')
    ->fields('wss', array(
    'start_state',
  ))
    ->fields('node', array(
    'type',
  ))
    ->condition('wsn.completed', 0)
    ->condition('wsn.start_date', 0, '!=')
    ->condition('wsn.start_date', $timestamp, '<=');

  // Only grab vid with highest hid.
  $schedule_query
    ->isNull('wmnh2.hid');

  // Create an or condition.
  $db_or = db_or();

  // Where no end date.
  $db_or
    ->condition('wsn.end_date', 0);

  // Or end date is after now.
  $db_or
    ->condition('wsn.end_date', $timestamp, '>');

  // Add or condition to the query.
  $schedule_query
    ->condition($db_or);

  // Order by vid to return the highest vid of each node.
  $schedule_query
    ->orderBy('wmnh.vid', 'DESC');

  // Execute the query.
  $schedule_nodes = $schedule_query
    ->execute();

  // If scheduled nodes are returned from the query.
  if ($schedule_nodes
    ->rowCount()) {

    // Include workbench moderation.
    module_load_include('module', 'workbench_moderation');

    // Loop through each scheduled node.
    foreach ($schedule_nodes as $node_schedule) {
      $do_process = FALSE;
      $type_settings = variable_get('workbench_scheduler_' . $node_schedule->type, array());

      // Only process latest revision.
      if (in_array('workbench_scheduler_limit_current_revision', $type_settings)) {

        // Getting the latest revision.
        $rev_list = node_revision_list($node_schedule);
        $latest_vid = max(array_keys($rev_list));
        if ($node_schedule->vid == $latest_vid) {
          $do_process = TRUE;
        }
      }
      else {
        $do_process = TRUE;
      }

      // Load and process the node.
      if ($do_process && ($node = node_load($node_schedule->nid, $node_schedule->vid))) {

        // If moderation state is 'unpublished' follow a different workflow.
        if ($node_schedule->start_state == 'unpublished' && $node->status) {
          workbench_scheduler_moderate_unpublish($node);
        }
        else {
          workbench_moderation_moderate($node, $node_schedule->start_state);
        }

        // Does this schedule have an end date as well?
        if ($node_schedule->end_date) {
        }
        else {

          // No end state so mark schedule as complete.
          workbench_scheduler_node_set_complete($node_schedule->nid, $node_schedule->vid, $node_schedule->sid);
        }

        // Update count of run schedules.
        $count++;
        module_invoke_all('workbench_scheduler_post_process_start_dates', $node_schedule);
      }
    }
  }

  // Return int if nodes that were moderated, if none boolean FALSE.
  return $count ?: FALSE;
}

/**
 * Run schedules for end times / states.
 *
 * @param int $timestamp
 *   Timestamp to check against.
 *
 * @return int|bool
 *   Count of nodes that were processed or boolean FALSE.
 */
function workbench_scheduler_process_end_dates($timestamp) {

  // Fetch all nodes that need to have their states changed to the 'end_date'.
  // State before NOW, and have not already done so.
  // Instantiate a counter for number of nodes processed.
  $count = 0;

  // Select from the workbench_scheduler_nodes table.
  $schedule_query = db_select('workbench_scheduler_nodes', 'wsn');

  // Join node table to get 'type'.
  $schedule_query
    ->innerjoin('node', 'node', 'node.nid = wsn.nid');

  // Inner join on workbench_scheduler_schedules table on schedule id.
  $schedule_query
    ->innerjoin('workbench_scheduler_schedules', 'wss', 'wsn.sid = wss.sid');

  // Inner join on workbench_moderation_node_history table on nid, revision id.
  // And where the current state does not equal the end state.
  $schedule_query
    ->innerjoin('workbench_moderation_node_history', 'wmnh', 'wsn.nid = wmnh.nid AND wsn.vid = wmnh.vid AND wss.end_state != wmnh.state');

  // Join the node history table and grab highest vid of the highest hid.
  $schedule_query
    ->leftJoin('workbench_moderation_node_history', 'wmnh2', 'wmnh.nid = wmnh2.nid AND wmnh.vid = wmnh2.vid AND wmnh.hid < wmnh2.hid');

  // Retrieve the nid, vid from the workbench_scheduler_nodes table.
  $schedule_query
    ->fields('wsn')
    ->fields('wss', array(
    'end_state',
  ))
    ->fields('node', array(
    'type',
  ))
    ->condition('wsn.completed', 0)
    ->condition('wsn.end_date', 0, '!=')
    ->condition('wsn.end_date', $timestamp, '<=');
  $schedule_query
    ->isNull('wmnh2.hid');

  // Order by vid to return the highest vid of each node.
  $schedule_query
    ->orderBy('wmnh.vid', 'DESC');

  // Execute the query.
  $scheduled_nodes = $schedule_query
    ->execute();

  // If scheduled nodes are returned from the query.
  if ($scheduled_nodes
    ->rowCount()) {

    // Include workbench moderation.
    module_load_include('module', 'workbench_moderation');

    // Loop through each scheduled node.
    foreach ($scheduled_nodes as $node_schedule) {
      $do_process = FALSE;
      $type_settings = variable_get('workbench_scheduler_' . $node_schedule->type, array());

      // Only process latest revision.
      if (in_array('workbench_scheduler_limit_current_revision', $type_settings)) {

        // Getting the latest revision.
        $rev_list = node_revision_list($node_schedule);
        $latest_vid = max(array_keys($rev_list));
        if ($node_schedule->vid == $latest_vid) {
          $do_process = TRUE;
        }
      }
      else {
        $do_process = TRUE;
      }

      // Need to load the node.
      if ($do_process && ($node = node_load($node_schedule->nid, $node_schedule->vid))) {

        // Only moderate if state is not 'unpublish'.
        // If moderation state is 'unpublished' follow a different workflow.
        if ($node_schedule->end_state == 'unpublished' && $node->status) {
          workbench_scheduler_moderate_unpublish($node);
        }
        else {
          workbench_moderation_moderate($node, $node_schedule->end_state);
        }

        // Mark schedule as completed.
        workbench_scheduler_node_set_complete($node_schedule->nid, $node_schedule->vid, $node_schedule->sid);

        // Update count of run schedules.
        $count++;
        module_invoke_all('workbench_scheduler_post_process_end_dates', $node_schedule);
      }
    }
  }

  // Return int if nodes that were moderated, if none boolean FALSE.
  return $count ?: FALSE;
}

/**
 * Unpublish a node following proper moderation workflow.
 *
 * @param object $node
 *   The node to unpublish.
 */
function workbench_scheduler_moderate_unpublish($node) {
  module_load_include('module', 'workbench_moderation');
  module_load_include('inc', 'workbench_moderation', 'workbench_moderation.node');

  // Copied from workbench_moderation_node_unpublish_form_submit.
  // In workbench_moderation.node.inc.
  // Remove the moderation record's "published" flag.
  db_update('workbench_moderation_node_history')
    ->condition('hid', $node->workbench_moderation['published']->hid)
    ->fields(array(
    'published' => 0,
  ))
    ->execute();

  // Moderate the revision.
  workbench_moderation_moderate($node, workbench_moderation_state_none());

  // Make sure the 'current' revision is the 'live' revision.
  // I.e, the revision in {node}.
  $live_revision = workbench_moderation_node_current_load($node);
  $live_revision->status = 0;
  $live_revision->revision = 0;
  $live_revision->workbench_moderation['updating_live_revision'] = TRUE;

  // @TODO: do we trust node_save() here?
  node_save($live_revision);
}

/**
 * Content Type Edit Form.
 */
function workbench_scheduler_form_node_type_form_alter(&$form, &$form_state, $form_id) {

  // Add Workbench Scheduler options.
  $form['workflow']['workbench_scheduler'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Workbench Scheduler'),
    '#options' => array(
      'workbench_scheduler_limit_current_revision' => t('Allow active schedule for latest revisions only'),
    ),
    '#states' => array(
      'visible' => array(
        ':input[name="node_options[moderation]"]' => array(
          'checked' => TRUE,
        ),
      ),
    ),
    '#weight' => 1,
    '#default_value' => variable_get('workbench_scheduler_' . $form['#node_type']->type, array()),
  );
}

Functions

Namesort descending Description
workbench_scheduler_action_info Implements hook_action_info().
workbench_scheduler_admin_paths Implements hook_admin_paths().
workbench_scheduler_count_node_schedules Return an array of counts of schedules for a specific node.
workbench_scheduler_cron Implements hook_cron().
workbench_scheduler_delete_node_schedule Delete the schedule(s) for a node.
workbench_scheduler_delete_schedules Delete schedule(s) add associated data from the database.
workbench_scheduler_form_node_form_alter Implements hook_form_FORM_ID_alter().
workbench_scheduler_form_node_type_form_alter Content Type Edit Form.
workbench_scheduler_get_types Retrieve a list of all the content types associated to schedules.
workbench_scheduler_load_node_schedule Retrieves schedule data for a given node revision.
workbench_scheduler_load_schedules Retrieve an array of schedule data.
workbench_scheduler_load_type_schedules Return a list of the schedules for a given content type.
workbench_scheduler_manage_schedules_access_check Access callback to see if current node type allows schedules.
workbench_scheduler_menu Implements hook_menu().
workbench_scheduler_moderate_unpublish Unpublish a node following proper moderation workflow.
workbench_scheduler_node_delete Implements hook_node_delete().
workbench_scheduler_node_form_submit Submit function for editing a scheduled node.
workbench_scheduler_node_form_validate Validation function for editing a scheduled node.
workbench_scheduler_node_insert Implements hook_node_insert().
workbench_scheduler_node_load Implements hook_node_load().
workbench_scheduler_node_revision_delete Implements hook_node_revision_delete().
workbench_scheduler_node_set_complete Mark that a node schedule has been completed.
workbench_scheduler_node_type_delete Implements hook_node_type_delete().
workbench_scheduler_node_update Implements hook_node_update().
workbench_scheduler_permission Implements hook_permission().
workbench_scheduler_process_end_dates Run schedules for end times / states.
workbench_scheduler_process_start_dates Run schedules for start times / states.
workbench_scheduler_save_node_schedule Saves the schedule data for a given node revision.
workbench_scheduler_save_schedule Inserts/updates a schedule.
workbench_scheduler_schedules_exist Check to see if schedules exist in the db.
workbench_scheduler_schedule_names Generate an array of schedule machine names.
workbench_scheduler_state_labels Generate an array of moderation states.
workbench_scheduler_views_api Implements hook_views_api().
_workbench_scheduler_run Process the different workbench scheduler schedules.
_workbench_schedule_check_machine_name_exists Returns the number of results for schedules with given machine name(s).