You are here

state_flow.module in State Machine 7.3

An implementation of node revision workflow for Drupal based on the State Machine system.

File

modules/state_flow/state_flow.module
View source
<?php

/**
 * @file
 * An implementation of node revision workflow for Drupal based on the
 * State Machine system.
 */

/**
 * Implements hook_menu().
 */
function state_flow_menu() {
  $items = array();
  $items['node/%node/workflow'] = array(
    'title' => 'Workflow',
    'description' => 'Information about the workflow status of this content',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'state_flow_events',
    'page arguments' => array(
      1,
    ),
    'access callback' => 'state_flow_menu_node_access',
    'access arguments' => array(
      1,
    ),
    'weight' => 10,
    'file' => 'state_flow.pages.inc',
  );

  // This item required to have a valid path scheme for entity_translation.
  $items['node/%node/revisions/%state_flow_revision_node'] = array(
    'title' => 'Revision ',
    'title callback' => 'state_flow_revision_menu_item_title_callback',
    'title arguments' => array(
      3,
    ),
    'load arguments' => array(
      3,
    ),
    'page callback' => 'node_show',
    'page arguments' => array(
      1,
      TRUE,
    ),
    'access callback' => '_node_revision_access',
    'access arguments' => array(
      1,
    ),
  );
  $items['node/%node/revisions/%state_flow_revision_node/edit'] = array(
    'title' => 'Edit revision',
    'type' => MENU_LOCAL_TASK,
    'load arguments' => array(
      3,
    ),
    'page callback' => 'node_page_edit',
    'page arguments' => array(
      1,
      TRUE,
    ),
    'access callback' => 'node_access',
    'access arguments' => array(
      'update',
      1,
    ),
    'file' => 'node.pages.inc',
    'file path' => drupal_get_path('module', 'node'),
  );
  $items['node/%node/revisions/%state_flow_revision_node/workflow'] = array(
    'title' => 'Transition a revision to a new workflow state',
    'type' => MENU_CALLBACK,
    'load arguments' => array(
      3,
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'state_flow_entity_events_revision',
      1,
      'node',
      5,
      array(),
    ),
    'access callback' => 'state_flow_events_revisions_access',
    'access arguments' => array(
      1,
      5,
    ),
    'file' => 'state_flow_entity.forms.inc',
    'file path' => drupal_get_path('module', 'state_flow_entity'),
  );
  $items['admin/content/content-revisions'] = array(
    'title' => 'Content Revisions',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'state_flow_content_page',
    ),
    'access arguments' => array(
      'administer content revisions',
    ),
    'type' => MENU_NORMAL_ITEM | MENU_LOCAL_TASK,
    'file' => 'state_flow.admin.inc',
  );
  return $items;
}

/**
 * Menu argument loader.
 */
function state_flow_revision_node_load($vid) {
  $nid = arg(1);
  if (empty($vid) || empty($nid)) {
    return FALSE;
  }
  return node_load($nid, $vid, TRUE);
}

/**
 * Menu title callback loader.
 */
function state_flow_revision_menu_item_title_callback($node) {
  return t('Revision !vid', array(
    '!vid' => $node->vid,
  ));
}

/**
 * Implements hook_menu_alter().
 */
function state_flow_menu_alter(&$items) {

  // Hijack the node/X/edit page to ensure that the right revision  is displayed
  if (isset($items['node/%node/edit'])) {
    $items['node/%node/edit']['page callback'] = 'state_flow_node_edit_page_override';
    $items['node/%node/edit']['title'] = t('Edit Draft');
  }

  // Ensure viewing a revision is the default task for the revisions route.
  if (isset($items['node/%node/revisions/%/view'])) {
    $items['node/%node/revisions/%/view']['type'] = MENU_DEFAULT_LOCAL_TASK;
  }

  // Ensure our access callback is used.
  if (isset($items['node/%/revisions-state-flow-states'])) {
    $items['node/%/revisions-state-flow-states']['access callback'] = 'state_flow_revisions_node_tab_access';
  }
}

/**
 * Implements hook_module_implements_alter().
 *
 * Needed for state_flow_menu_alter() to reach Views-generated
 * menu entries.
 */
function state_flow_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'menu_alter') {
    $group = $implementations['state_flow'];
    unset($implementations['state_flow']);
    $implementations['state_flow'] = $group;
  }
}

/**
 * Disable Revisions tab if there are no state flow changes.
 *
 * From Views, it's not possible for performance reasons.
 * But here, with a lightweight query, we can predict if it would
 * have results or not.
 */
function state_flow_revisions_node_tab_access() {

  // The menu_get_object() would cause infinite recursion.
  $nid = (int) arg(1);
  $count = db_query("SELECT COUNT(*) FROM {state_flow_states} WHERE entity_type = 'node' and entity_id = :nid", array(
    ':nid' => $nid,
  ))
    ->fetchField();
  if ($count > 0) {
    $args = func_get_args();
    $callback = isset($args[0][0]) && is_callable($args[0][0]) ? $args[0][0] : 'views_check_perm';
    $params = isset($args[0][1]) ? $args[0][1] : array(
      'administer nodes',
    );
    return call_user_func_array($callback, $params);
  }
  return FALSE;
}

/**
 * Overrides the node/%/edit page to ensure the proper revision is shown.
 *
 * @param $node
 *   The node being acted upon.
 * @return
 *   A node editing form.
 */
function state_flow_node_edit_page_override($node) {

  // Check to see if this is an existing node.
  if (isset($node->nid)) {

    // Load the node moderation data.
    $machine = state_flow_entity_load_state_machine($node, 'node');
    $active_revision_id = $machine
      ->get_active_revision();
    if (!empty($active_revision_id)) {

      // We ONLY edit the active revision.
      $active_revision = node_load($node->nid, $machine->object->active_revision_id);
      if (!empty($active_revision)) {
        $node = $active_revision;
      }
      else {
        watchdog('state_flow', 'Node @title @nid has corrupted {state_flow_states} rows.', array(
          '@title' => $node->title,
          '@nid' => $node->nid,
        ), WATCHDOG_ERROR);
        drupal_set_message(t('Workflow data is corrupted, report this incident to the site administrators.'), 'error');
      }
    }
  }

  // Ensure we have the editing code.
  module_load_include('inc', 'node', 'node.pages');
  return node_page_edit($node);
}

/**
 * Implements hook_permission().
 */
function state_flow_permission() {
  return array(
    'manage content workflow' => array(
      'title' => t('Manage content workflow'),
      'description' => t('Manage the content workflow pages and operations.'),
    ),
    'administer content revisions' => array(
      'title' => t('Administer content revisions'),
    ),
  );
}

/**
 * Implements hook_admin_paths().
 */
function state_flow_admin_paths() {
  if (variable_get('node_admin_theme')) {
    $paths = array(
      'node/*/workflow' => TRUE,
      'node/*/revisions/*/edit' => TRUE,
      'node/*/revisions/*/workflow' => TRUE,
      'node/*/revisions/*/workflow/*' => TRUE,
      'node/*/state-flow-history' => TRUE,
      'node/*/revisions-state-flow-states' => TRUE,
    );
    return $paths;
  }
}

/**
 * Implements hook_entity_property_info_alter().
 *
 * Adds a "state" property on nodes that are configured with state flow.
 *
 * @see entity_api module.
 *
 * @todo, this should move to state_flow_entity, probably
 */
function state_flow_entity_property_info_alter(&$info) {
  if (!empty($info['node']['bundles'])) {
    foreach ($info['node']['bundles'] as $entity_type => $entity_info) {
      if (variable_get('state_flow_' . $entity_type, '')) {
        $info['node']['bundles'][$entity_type]['properties']['state'] = array(
          'label' => t('Workflow state'),
          'description' => t('The current workflow state for this node revision.'),
          'getter callback' => 'state_flow_entity_get_state',
        );
      }
    }
  }
}

/**
 * Implements hook_entity_info_alter().
 */
function state_flow_entity_info_alter(&$entity_info) {

  // Add state_flow_information to the node entity type.
  if (!empty($entity_info['node'])) {
    $entity_info['node']['state_flow_entity'] = array(
      // @todo, there will likely be more properties needed here.
      'revision_workflow_path' => 'node/%entity_id/revisions/%revision_id/workflow',
      'revision_delete_path' => 'node/%entity_id/revisions/%revision_id/delete',
      'revision_edit_path' => 'node/%entity_id/revisions/%revision_id/edit',
    );

    // Add revision handling path schemes for entity translation.
    if (module_exists('entity_translation')) {
      if (!isset($entity_info['node']['translation']['entity_translation'])) {
        $entity_info['node']['translation']['entity_translation'] = array();
      }
      $entity_info['node']['state_flow_entity']['revision_translate_path'] = 'node/%entity_id/revisions/%revision_id/translate';
      $et_info =& $entity_info['node']['translation']['entity_translation'];
      $et_info['path schemes']['state_flow'] = array(
        'admin theme' => 1,
        'base path' => 'node/%node/revisions/%state_flow_revision_node',
        'view  path' => 'node/%node/revisions/%/view',
        'edit path' => 'node/%node/revisions/%state_flow_revision_node/edit',
        'path wildcard' => '%node',
        'edit tabs' => TRUE,
      );
    }
  }
}

/**
 * Implements hook_state_flow_entity_plugins().
 */
function state_flow_state_flow_entity_plugins() {
  $info = array();
  $workflow_options = array(
    'states' => array(
      'draft' => array(
        'label' => t('Draft'),
      ),
      'published' => array(
        'label' => t('Published'),
        'on_enter' => 'on_enter_published',
        'on_exit' => 'on_exit_published',
      ),
      'unpublished' => array(
        'label' => t('Unpublished'),
        'on_enter' => 'on_enter_unpublished',
      ),
    ),
    'events' => array(
      'keep in draft' => array(
        'label' => t('Keep in Draft'),
        'origin' => 'draft',
        'target' => 'draft',
      ),
      'publish' => array(
        'label' => t('Publish'),
        'origin' => array(
          'draft',
          'published',
        ),
        'target' => 'published',
      ),
      'unpublish' => array(
        'label' => t('Unpublish'),
        'origin' => array(
          'draft',
          'published',
        ),
        'target' => 'unpublished',
        'permission' => 'publish and unpublish content',
      ),
      'to draft' => array(
        'label' => t('To Draft'),
        'origin' => array(
          'unpublished',
          'published',
        ),
        'target' => 'draft',
      ),
    ),
  );
  $info['state_flow_node'] = array(
    'handler' => array(
      'class' => 'StateFlowNode',
      'file' => 'state_flow_node.inc',
      'path' => drupal_get_path('module', 'state_flow') . '/plugins',
      'parent' => 'state_flow_entity',
      'workflow_options' => $workflow_options,
      'entity_type' => 'node',
      'event_form_options' => array(),
    ),
  );
  return $info;
}

/**
 * Implements hook_views_api().
 */
function state_flow_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'state_flow') . '/includes/views',
  );
}

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

  // Delete history and update active revision for this entity.
  $machine = state_flow_entity_load_state_machine($node, 'node');
  $machine
    ->delete_state_flow_revision($node->vid, 'node');
}

/**
 * Menu access callback for accessing the node workflow pages.
 */
function state_flow_menu_node_access($node, $account = NULL) {
  global $user;

  // If no user account is given, then use the current user.
  if (empty($account)) {
    $account = $user;
  }

  // If the user has the "manage content workflow" permission, then allow access
  // to workflow pages.
  $has_access = user_access('manage content workflow', $account);

  // Check to see if node type is ignored.
  $is_ignored = state_flow_entity_load_state_machine($node, 'node')
    ->ignore();
  if ($has_access && !$is_ignored) {
    $access = TRUE;
  }
  else {
    $access = FALSE;
  }

  // Allow other modules to alter this decision.
  drupal_alter('state_flow_menu_node_access', $access, $node, $account);
  return $access;
}

/**
 * Menu access callback for the node revision workflow transition page.
 */
function state_flow_events_revisions_access($node, $event_name = NULL) {
  return !empty($event_name) ? state_flow_access($node, $event_name) : state_flow_menu_node_access($node);
}

/**
 * Determine whether a user has permission to transition a node with an event.
 */
function state_flow_access($node, $event_name, $account = NULL) {
  global $user;

  // If no user account is given, then use the current user.
  if (empty($account)) {
    $account = $user;
  }

  // If the user cannot edit the node, then deny access to any events.
  if (!state_flow_menu_node_access($node, $account)) {
    return FALSE;
  }

  // Load the state machine for the node and test whether the event is allowed.
  $machine = state_flow_entity_load_state_machine($node, 'node');
  $state_event = $machine ? $machine
    ->get_event($event_name) : FALSE;
  return $state_event ? $state_event
    ->validate() : FALSE;
}

/**
 * Getter callback for the "state" property on node bundles using workflow.
 *
 * @see entity_api module.
 */
function state_flow_entity_get_state($data, $options, $name, $type, $info) {
  $machine = state_flow_entity_load_state_machine($data, 'node');
  return $machine
    ->get_current_state();
}

/**
 * Get all of the states for all content types.
 */
function state_flow_get_all_states() {
  static $states = array();
  if (empty($states)) {
    $i = 0;
    foreach (node_type_get_types() as $type_key => $type) {

      // Fake a node object.
      $node = new stdClass();
      $node->vid = $i;
      $i++;
      $node->type = $type_key;
      $machine = state_flow_entity_load_state_machine($node, 'node');
      $states += $machine
        ->get_states_options();
    }
  }
  return $states;
}

/**
 * Get all of the states for all content types as option list array.
 */
function state_flow_get_all_state_options() {
  $states = state_flow_get_all_states();
  array_walk($states, function (&$item) {
    $item = $item['label'];
  });
  return $states;
}

/**
 * Get all of the events for all content types.
 *
 * @todo, this will not play well at all with having more than one workflow available.
 */
function state_flow_get_all_events() {
  static $events = array();
  if (empty($events)) {
    $i = 0;
    foreach (node_type_get_types() as $type_key => $type) {

      // Fake a node object
      $node = new stdClass();
      $node->vid = $i;
      $i++;
      $node->type = $type_key;
      $machine = state_flow_entity_load_state_machine($node, 'node');
      $events += array_keys($machine
        ->get_all_events());
    }
  }
  return $events;
}

/**
 * Retrieve the states history for an entity.
 *
 * @param integer node id
 * @return array of objects
 */
function state_flow_get_all_history($entity_id) {
  $history = db_query('
    SELECT sfh.*, u.uid, u.name AS user_name
    FROM {state_flow_history} sfh
    LEFT JOIN {users} u ON u.uid = sfh.uid
    WHERE sfh.entity_id = :entity_id
    ORDER BY sfh.timestamp DESC', array(
    ':entity_id' => $entity_id,
  ))
    ->fetchAll();
  return $history;
}

/**
 * Implements hook_node_revision_filters()
 */
function state_flow_node_revision_filters() {
  $filters = array();
  $states = state_flow_get_all_states();
  $options = array_combine(array_keys($states), array_keys($states));
  array_unshift($options, '[any]');
  $filters['state'] = array(
    'form' => array(
      '#type' => 'select',
      '#title' => t('State'),
      '#options' => $options,
    ),
  );
  return $filters;
}

/**
 * Implements hook_query_node_revision_alter()
 */
function state_flow_query_node_revision_alter(QueryAlterableInterface $query) {

  // Get the filter form the session
  $filters = $query
    ->getMetaData('filters');
  if ($filter = isset($filters['state']) ? $filters['state'] : NULL) {
    $query
      ->join('state_flow_states', 'sfs', 'nr.vid = sfs.revision_id AND entity_type=\'node\'');
    $query
      ->condition('sfs.state', $filter);
  }
}

/**
 * Implements hook_node_revision_operations().
 */
function state_flow_node_revision_operations() {
  $operations = array();
  $events = state_flow_get_all_events();
  foreach ($events as $event) {
    $operations["change_state_{$event}"] = array(
      'label' => t('Transition Action: @event', array(
        '@event' => $event,
      )),
      'callback' => 'state_flow_node_revision_operation_change_state',
      'callback arguments' => array(
        'args' => array(
          'event' => $event,
        ),
      ),
    );
  }
  return $operations;
}

/**
 * Operation callback to change state of a node
 */
function state_flow_node_revision_operation_change_state($nodes, $args) {
  $event = $args['event'];

  // We use batch processing to prevent timeout when updating a large number
  // of nodes.
  if (count($nodes) > 10) {
    $batch = array(
      'operations' => array(
        array(
          'state_flow_node_revision_operation_change_state_batch_process',
          array(
            $nodes,
            $args,
          ),
        ),
      ),
      'finished' => 'state_flow_node_revision_operation_change_state_batch_finished',
      'title' => t('Processing'),
      // We use a single multi-pass operation, so the default
      // 'Remaining x of y operations' message will be confusing here.
      'progress_message' => '',
      'error_message' => t('The update has encountered an error.'),
    );
    batch_set($batch);
  }
  else {
    $message = array();
    foreach ($nodes as $info) {
      $messages[] = state_flow_operation_change_helper($info, $event);
    }
    $message = theme('item_list', array(
      'items' => $messages,
    ));
    $message = format_plural(count($nodes), '1 item successfully processed:', '@count items successfully processed:');
    drupal_set_message($message);
  }
}
function state_flow_operation_change_helper($info, $event) {
  global $user;
  $node = node_load($info['nid'], $info['vid']);
  $machine = state_flow_entity_load_state_machine($node, 'node');
  $allowed_events = $machine
    ->get_available_events();
  if (in_array($event, $allowed_events)) {
    $machine
      ->fire_event($event, $user->uid, 'State changed via Bulk Node Operation');
    return t('@title has been transitioned to @state', array(
      '@title' => $node->title,
      '@state' => $state_machine
        ->get_label_for_current_state(),
    ));
  }
}

/**
 * State Change Mass Update Batch operation
 */
function state_flow_node_revision_operation_change_state_batch_process($nodes, $updates, &$context) {
  if (!isset($context['sandbox']['progress'])) {
    $context['sandbox']['progress'] = 0;
    $context['sandbox']['max'] = count($nodes);
    $context['sandbox']['nodes'] = $nodes;
  }

  // Process nodes by groups of 5.
  $count = min(5, count($context['sandbox']['nodes']));
  for ($i = 1; $i <= $count; $i++) {

    // For each nid, load the node, reset the values, and save it.
    $info = array_shift($context['sandbox']['nodes']);

    // Store result for post-processing in the finished callback.
    $context['results'][] = state_flow_operation_change_helper($info, $updates);

    // Update our progress information.
    $context['sandbox']['progress']++;
  }

  // Inform the batch engine that we are not finished,
  // and provide an estimation of the completion level we reached.
  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
  }
}

/**
 * Node Mass Update Batch 'finished' callback.
 */
function state_flow_node_revision_operation_change_state_batch_finished($success, $results, $operations) {
  if ($success) {
    drupal_set_message(t('The update has been performed.'));
  }
  else {
    drupal_set_message(t('An error occurred and processing did not complete.'), 'error');
    $message = format_plural(count($results), '1 item successfully processed:', '@count items successfully processed:');
    $message .= theme('item_list', array(
      'items' => $results,
    ));
    drupal_set_message($message);
  }
}

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

  // Add the event form.
  // @todo, should this be added to all node forms? Should that be a setting
  // in the state flow plugin.
  $node = $form_state['node'];
  $machine = state_flow_entity_load_state_machine($node, 'node');

  // Only alter the form if this entity has a state machine and is not ignored.
  if (!empty($machine) && !$machine
    ->ignore()) {
    module_load_include('inc', 'state_flow_entity', 'state_flow_entity.forms');
    $form['options']['state_flow']['#type'] = 'fieldset';

    // Use a select list.
    $form_options = array(
      'event_element_type' => 'select',
    );
    $event_form = _state_flow_entity_events_revision(array(), $form_state, $node, 'node', NULL, $form_options);
    if (!empty($form['options']['#access'])) {
      $form['options']['state_flow'] += $event_form;
    }
    elseif (!empty($form['revision_information']['#access'])) {
      $form['revision_information']['state_flow'] = $event_form;
    }
    else {
      $form['state_flow'] = $event_form;
      $form['state_flow']['#weight'] = 99;
    }

    // Remove node revision log field.
    $form['revision_information']['log']['#access'] = FALSE;

    // If this is the published revision we create a new revision by default.
    $form['revision_information']['revision']['#default_value'] = !$machine
      ->object_is_new() && $machine
      ->get_active_revision() == $node->vid && !empty($node->status);

    // Hide Published checkbox.
    $form['options']['status']['#disabled'] = TRUE;
    if ($machine
      ->object_is_new()) {
      $form['options']['status']['#access'] = FALSE;
    }
  }
}

/**
 * Implements hook_node_validate().
 */
function state_flow_node_validate($node, $form, &$form_state) {
  if (!empty($form_state['values']['state_flow'])) {

    // Validate fields.
    $machine = $form_state['values']['state_flow'];
    $machine
      ->history_entity_form_field_validate($form, $form_state);

    // Validate event value.
    $event = $machine
      ->get_event($form_state['values']['event']);
    if (!is_object($event) || !method_exists($event, 'get_options')) {
      $event_item = NULL;
      if (isset($form['options']['state_flow']) && isset($form['options']['state_flow']['event'])) {
        $event_item = $form['options']['state_flow']['event'];
      }
      else {
        if (isset($form['revision_information']['state_flow']) && isset($form['revision_information']['state_flow']['event'])) {
          $event_item = $form['revision_information']['state_flow']['event'];
        }
        else {
          if (isset($form['state_flow']) && isset($form['state_flow']['event'])) {
            $event_item = $form['state_flow']['event'];
          }
        }
      }
      if (empty($event_item)) {
        form_set_error('event', t('@title value is unknown.', array(
          '@title' => $form_state['values']['event'],
        )));
      }
      else {
        form_set_error('event', t('@title value is invalid.', array(
          '@title' => $event_item['#title'],
        )));
      }
    }
  }
}

/**
 * Implements hook_node_submit().
 */
function state_flow_node_submit($node, $form, &$form_state) {
  if (!empty($form_state['values']['state_flow'])) {

    /** @var StateFlowNode $machine */
    $machine = $form_state['values']['state_flow'];

    /** @var StateMachine_Event $event */
    $event = $machine
      ->get_event($form_state['values']['event']);
    if (!is_object($event) || !method_exists($event, 'get_options')) {
      watchdog('state_flow', 'Invalid event value "@value" submitted in node form.', array(
        '@value' => $form_state['values']['event'],
      ), WATCHDOG_ERROR);
      return;
    }
    $machine
      ->history_entity_form_submit_build_entity($form, $form_state);

    // If this isn't the active published revision we redirect to the revision
    // view page - this should avoid confusion when working with revisions.
    if (!empty($node->nid) && !empty($node->vid) && !$machine
      ->isActivePublishedRevision() && empty($_GET['destination'])) {

      // We set the get param because we can't set $form_state['redirect'] - the
      // node form handler overwrites this value by default.
      $_GET['destination'] = 'node/' . $node->nid . '/revisions/' . $node->vid . '/view';
    }
  }
}

/**
 * Guard condition callback
 *
 * @param string $event
 * @return bool
 */
function state_flow_guard_schedule($event) {
  if (module_exists('state_flow_schedule')) {
    return state_flow_schedule_guard_permission($event, 'schedule content workflow');
  }
}

/**
 * Determine whether the workflow should be skipped on a node
 *
 * @param unknown_type $node
 */
function state_flow_skip_workflow($node) {
  if (!isset($node->stateflow_skip_workflow)) {
    return FALSE;
  }
  return $node->stateflow_skip_workflow;
}

/**
 * Implements hook_state_flow_entity_machine_type_alter().
 */
function state_flow_state_flow_entity_machine_type_alter(&$machine_type, $entity, $entity_type) {

  // If the entity is a node. Use the state_flow_node plugin.
  if ($entity_type === 'node') {
    $machine_type = 'state_flow_node';
  }
}

Functions

Namesort descending Description
state_flow_access Determine whether a user has permission to transition a node with an event.
state_flow_admin_paths Implements hook_admin_paths().
state_flow_entity_get_state Getter callback for the "state" property on node bundles using workflow.
state_flow_entity_info_alter Implements hook_entity_info_alter().
state_flow_entity_property_info_alter Implements hook_entity_property_info_alter().
state_flow_events_revisions_access Menu access callback for the node revision workflow transition page.
state_flow_form_node_form_alter Implements hook_form_FORM_ID_alter().
state_flow_get_all_events Get all of the events for all content types.
state_flow_get_all_history Retrieve the states history for an entity.
state_flow_get_all_states Get all of the states for all content types.
state_flow_get_all_state_options Get all of the states for all content types as option list array.
state_flow_guard_schedule Guard condition callback
state_flow_menu Implements hook_menu().
state_flow_menu_alter Implements hook_menu_alter().
state_flow_menu_node_access Menu access callback for accessing the node workflow pages.
state_flow_module_implements_alter Implements hook_module_implements_alter().
state_flow_node_edit_page_override Overrides the node/%/edit page to ensure the proper revision is shown.
state_flow_node_revision_delete Implements hook_node_revision_delete().
state_flow_node_revision_filters Implements hook_node_revision_filters()
state_flow_node_revision_operations Implements hook_node_revision_operations().
state_flow_node_revision_operation_change_state Operation callback to change state of a node
state_flow_node_revision_operation_change_state_batch_finished Node Mass Update Batch 'finished' callback.
state_flow_node_revision_operation_change_state_batch_process State Change Mass Update Batch operation
state_flow_node_submit Implements hook_node_submit().
state_flow_node_validate Implements hook_node_validate().
state_flow_operation_change_helper
state_flow_permission Implements hook_permission().
state_flow_query_node_revision_alter Implements hook_query_node_revision_alter()
state_flow_revisions_node_tab_access Disable Revisions tab if there are no state flow changes.
state_flow_revision_menu_item_title_callback Menu title callback loader.
state_flow_revision_node_load Menu argument loader.
state_flow_skip_workflow Determine whether the workflow should be skipped on a node
state_flow_state_flow_entity_machine_type_alter Implements hook_state_flow_entity_machine_type_alter().
state_flow_state_flow_entity_plugins Implements hook_state_flow_entity_plugins().
state_flow_views_api Implements hook_views_api().