Skip to content
Snippets Groups Projects
workbench_moderation.module 65.8 KiB
Newer Older
<?php

/**
 * @file
 * Content moderation for Workbench.
 *
 * Based on content_moderation.module by eugenmayer.
 * Base version 1.12.2.17 2010/04/18 11:31:29.
 */

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

  // Display a node's moderation history
  $items["node/%node/moderation"] = array(
    'title' => 'Moderate',
    'description' => 'Show the content moderation history.',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'workbench_moderation_node_history_view',
    'page arguments' => array(1),
    'access callback' => '_workbench_moderation_access',
    'access arguments' => array('view history', 1),
    'file' => 'workbench_moderation.node.inc'
  );

  // Unpublishing a live revision.
  $items["node/%node/moderation/%/unpublish"] = array(
    'title' => 'Unpublish revision',
    'description' => 'Unpublish the current live revision.',
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array('workbench_moderation_node_unpublish_form', 1),
    'load arguments' => array(3),
    'access callback' => '_workbench_moderation_access',
    'access arguments' => array('unpublish', 1),
    'file' => 'workbench_moderation.node.inc'
  );

  // Change the moderation state of a node.
  // Used in workbench_moderation_get_moderation_links()
  $items["node/%node/moderation/%/change-state/%"] = array(
    'title' => 'Change Moderation State',
    'page callback' => 'workbench_moderation_moderate_callback',
    'page arguments' => array(1, 5),
    'load arguments' => array(3),
    'access callback' => '_workbench_moderation_moderate_access',
    'access arguments' => array(1, 5),
    'type' => MENU_CALLBACK,
  );

  // View the current revision of a node. Redirects to node/%node if the current revision is
  // published, and to node/%node/draft if the current revision is a draft.
  $items["node/%node/current-revision"] = array(
    'page callback' => 'workbench_moderation_node_current_view',
    'page arguments' => array(1),
    'access arguments' => array('view revisions'),
    'file' => 'workbench_moderation.node.inc',
  );

  // View the current draft of a node.
  $items["node/%node/draft"] = array(
    'title' => 'View draft',
    'page callback' => 'workbench_moderation_node_view_draft',
    'page arguments' => array(1),
    'access callback' => '_workbench_moderation_access_current_draft',
    'access arguments' => array(1),
    'file' => 'workbench_moderation.node.inc',
    'type' => MENU_LOCAL_TASK,
    'weight' => -9,
  );

  // Module settings.
  $items["admin/config/workbench/moderation"] = array(
    'title' => 'Workbench Moderation',
    'description' => 'Configure content moderation.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('workbench_moderation_admin_states_form'),
    'access arguments' => array('administer workbench moderation'),
    'file' => 'workbench_moderation.admin.inc',
  );
  $items['admin/config/workbench/moderation/general'] = array(
    'title' => 'States',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -1,
  );
  $items['admin/config/workbench/moderation/transitions'] = array(
    'title' => 'Transitions',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array('workbench_moderation_admin_transitions_form'),
    'access arguments' => array('administer workbench moderation'),
    'file' => 'workbench_moderation.admin.inc',
  );
  $items['admin/config/workbench/moderation/check-permissions'] = array(
    'title' => 'Check Permissions',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array('workbench_moderation_admin_check_role_form'),
    'access arguments' => array('administer workbench moderation'),
    'file' => 'workbench_moderation.admin.inc',
    'weight' => 10,
  );

  return $items;
}

/**
 * Implements hook_menu_alter().
 */
function workbench_moderation_menu_alter(&$items) {
  // Hijack the node/X/edit page to ensure that the right revision (most current) is displayed.
  $items['node/%node/edit']['page callback'] = 'workbench_moderation_node_edit_page_override';

  // Override the node edit menu item title.
  $items['node/%node/edit']['title callback'] = 'workbench_moderation_edit_tab_title';
  $items['node/%node/edit']['title arguments'] = array(1);

  // Override the node view menu item title.
  $items['node/%node/view']['title callback'] = 'workbench_moderation_view_tab_title';
  $items['node/%node/view']['title arguments'] = array(1);

  // Redirect node/%node/revisions
  $items['node/%node/revisions']['page callback'] = 'workbench_moderation_node_revisions_redirect';
  $items['node/%node/revisions']['page arguments'] = array(1);

  // Override the node revision view callback.
 $items['node/%node/revisions/%/view']['page callback'] = 'workbench_moderation_node_view_revision';
 $items['node/%node/revisions/%/view']['file path'] = drupal_get_path('module', 'workbench_moderation');
 $items['node/%node/revisions/%/view']['file'] = 'workbench_moderation.node.inc';


  // For revert and delete operations, use our own access check.
  $items['node/%node/revisions/%/revert']['access callback'] = '_workbench_moderation_revision_access';
  $items['node/%node/revisions/%/delete']['access callback'] = '_workbench_moderation_revision_access';

  // Provide a container administration menu item, if one doesn't already exist.
  if (!isset($items['admin/config/workbench'])) {
    $items['admin/config/workbench'] = array(
      'title' => 'Workbench',
      'description' => 'Workbench',
      'page callback' => 'system_admin_menu_block_page',
      'access arguments' => array('administer site configuration'),
      'position' => 'right',
      'file' => 'system.admin.inc',
      'file path' => drupal_get_path('module', 'system'),
    );
  }
}

/**
 * Redirects 'node/%node/revisions' to node/%node/moderation
 *
 * workbench_moderation_menu_alter() changes the page callback
 * for 'node/%node/revisions' to this function
 *
 * @param $node
 *   The node being acted upon.
 */
function workbench_moderation_node_revisions_redirect($node) {
  // Redirect node types subject to moderation.
  if (workbench_moderation_node_type_moderated($node->type) === TRUE) {
    drupal_goto('node/' . $node->nid . '/moderation');
  }
  // Return the normal node revisions page for unmoderated types.
  else {
    return node_revision_overview($node);
  }
}

/**
 * Implements hook_menu_local_tasks_alter().
 *
 * Hide the node revisions tab conditionally.
 *
 * Check if the node type is subject to moderation. If so, unset the revision
 * tab. This step is necessary because hook_menu_alter cannot change the menu
 * item type on a node type by node type basis for node/%node/revision.
 *
 * Additionally, workbench_menu_alter() is used to change the page callback
 * for node/%node/revisions so that this URL redirects to node/%node/moderation
 * for node types subject to moderation.
 */
function workbench_moderation_menu_local_tasks_alter(&$data, $router_item, $root_path) {
  // Do we need to bother doing anything?
  if (empty($data['tabs'][0]['output']) || empty($router_item['page_arguments'][0])) {
    return;
  }

  // Check the path.
  $arg = arg(0, $root_path);
  $arg1 = arg(1, $root_path);
  if ($arg != 'node' || $arg1 != '%') {
    return;
  }

  // Devel module passes node as the first parameter, but we support it.
  if (count($router_item['page_arguments']) > 1 && $router_item['page_arguments'][0] == 'node') {
    array_shift($router_item['page_arguments']);
  }
  // Ensure that we have a proper node.
  if (!is_object($router_item['page_arguments'][0])) {
    return;
  }
  $node = $router_item['page_arguments'][0];

  // Here is the reason this hook implementation exists:
  // If this is a node type that gets moderated, don't show 'node/%/revisions'
  if (workbench_moderation_node_type_moderated($node->type) === TRUE) {
    foreach ($data['tabs'][0]['output'] as $key => $value) {
      if (!empty($value['#link']['path']) && $value['#link']['path'] == 'node/%/revisions') {
        unset($data['tabs'][0]['output'][$key]);
        break;
      }
    }
  }
}

/**
 * Change the name of the node edit tab, conditionally.
 *
 * - Don't change the title if the content is not under moderation.
 *
 * - If a piece of content has a published revision and the published revision
 *   is also the current moderation revision, the "Edit" tab should be titled
 *   "Create draft".
 *
 * - If a piece of content has a published revision and the current moderation
 *   revision is a newer, or if the content has no published revision, the
 *   "Edit" tab should be titled "Edit draft".
 *
 * @param $node
 *   The node being acted upon.
 *
 * @return
 *   The title for the tab.
 */
function workbench_moderation_edit_tab_title($node) {
  // Use the normal tab title if the node type is not under moderation.
  if (!workbench_moderation_node_type_moderated($node->type)) {
    return t('Edit');
  }

  // Is the latest draft published?
  $state = $node->workbench_moderation;
  if (!empty($state['published']) && $state['published']->vid == $state['current']->vid) {
    return t('New draft');
  }

  // The latest draft is not published.
  return t('Edit draft');
}

/**
 * Change the name of the node view tab, conditionally.
 *
 * - Don't change the title if the content is not under moderation.
 *
 * - If a piece of content has a published revision, the "View" tab should be
 *   titled "View published".
 *
 * - Otherwise, it should be titled "View draft".
 *
 * @param $node
 *   The node being acted upon.
 *
 * @return
 *   The title for the tab.
 */
function workbench_moderation_view_tab_title($node) {
  // Use the normal tab title if the node type is not under moderation.
  if (!workbench_moderation_node_type_moderated($node->type)) {
    return t('View');
  }

  // Is there a published revision?
  $state = $node->workbench_moderation;
  if (!empty($state['published'])) {
    return t('View published');
  }
  return t('View draft');
}


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

/**
 * Implements hook_theme().
 */
function workbench_moderation_theme() {
  return array(
    'workbench_moderation_admin_states_form' => array(
      'file' => 'workbench_moderation.admin.inc',
      'render element' => 'form',
    ),
    'workbench_moderation_admin_transitions_form' => array(
      'file' => 'workbench_moderation.admin.inc',
      'render element' => 'form',
    ),
  );
}

/**
 * Implements hook_permission().
 *
 * Provides permissions for each state to state change.
 */
function workbench_moderation_permission() {
  $permissions = array();
  $permissions['view all unpublished content'] = array(
    'title' => t('View all unpublished content'),
  );
  $permissions['administer workbench moderation'] = array(
    'title' => t('Administer Workbench Moderation'),
  );
  $permissions['bypass workbench moderation'] = array(
    'title' => t('Bypass moderation restrictions'),
    'restrict access' => TRUE,
  );
  $permissions['view moderation history'] = array(
    'title' => t('View moderation history'),
  );
  $permissions['view moderation messages'] = array(
    'title' => t('View the moderation messages on a node')
  );
  $permissions['use workbench_moderation my drafts tab'] = array(
    'title' => t('Use "My Drafts" workbench tab')
  );
  $permissions['use workbench_moderation needs review tab'] = array(
    'title' => t('Use "Needs Review" workbench tab')
  );

  // Per-node-type, per-transition permissions. Used by workbench_moderation_state_allowed().
  $node_types = workbench_moderation_moderate_node_types();
  $transitions = workbench_moderation_transitions();

  foreach ($transitions as $transition) {
    $from_state = $transition->from_name;
    $to_state = $transition->to_name;

    // Always set a permission to perform all moderation states.
    $permissions["moderate content from $from_state to $to_state"] = array(
      'title' => t('Moderate all content from @from_state to @to_state', array('@from_state' => workbench_moderation_state_label($from_state), '@to_state' => workbench_moderation_state_label($to_state))),
    );
    // Per-node type permissions are very complex, and should only be used if
    // absolutely needed. For right now, this is hardcoded to OFF. To enable it,
    // Add this line to settings.php and then reset permissions.
    //   $conf['workbench_moderation_per_node_type'] = TRUE;
    if (variable_get('workbench_moderation_per_node_type', FALSE)) {
      foreach ($node_types as $node_type) {
        $permissions["moderate $node_type state from $from_state to $to_state"] = array(
          'title' => t('Moderate @node_type state from @from_state to @to_state', array('@node_type' => node_type_get_name($node_type), '@from_state' => workbench_moderation_state_label($from_state), '@to_state' => workbench_moderation_state_label($to_state))),
        );
      }
    }
  }
  return $permissions;
}

/**
 * Implements hook_node_access().
 *
 * Allows users with the 'view all unpublished content' permission to do so.
 */
function workbench_moderation_node_access($node, $op, $account) {
  if ($op == 'view' && !$node->status && user_access('view all unpublished content', $account)) {
    return NODE_ACCESS_ALLOW;
  }
  return NODE_ACCESS_IGNORE;
}

/**
 * Custom access handler for node operations.
 *
 * @param $op
 *   The operation being requested.
 * @param $node
 *   The node being acted upon.
 *
 * @return
 *   Boolean TRUE or FALSE.
 */
function _workbench_moderation_access($op, $node) {
  // If we do not control this node type, deny access.
  if (workbench_moderation_node_type_moderated($node->type) === FALSE) {
    return FALSE;
  }

  $access = TRUE;

  // The user must be able to view the moderation history.
  $access &= user_access('view moderation history');

  // The user must be able to edit this node.
  $access &= node_access('update', $node);

  if ($op == 'unpublish') {
    // workbench_moderation_states_next() checks transition permissions.
    $next_states = workbench_moderation_states_next(workbench_moderation_state_published(), $user, $node->type);
    $access &= !empty($next_states);
  }

  // Allow other modules to change our rule set.
  drupal_alter('workbench_moderation_access', $access, $op, $node);

  return $access;
}

/**
 * Wrapper for the 'revert' and 'delete' operations of _node_revision_access().
 *
 * Drupal core's "current revision" of a node is the version in {node}; for
 * Workbench Moderation, latest revision in {node_revision} is the current
 * revision. For nodes with a published revision, Workbench Moderation keeps
 * that revision in {node}, whether or not it is the current revision.
 */
function _workbench_moderation_revision_access($node, $op) {
  // Normal behavior for unmoderated nodes.
  if (!workbench_moderation_node_type_moderated($node->type)) {
    return _node_revision_access($node, $op);
  }

  // Prevent reverting to or deleting the current revision.
  if ($node->workbench_moderation['current']->vid == $node->workbench_moderation['my_revision']->vid) {
    return FALSE;
  }

  // Temporarily give the node an impossible revision.
  // _node_revision_access() keeps access check results in a static variable
  // indexed by revision only, not by op. Thus, subsequent checks on the same
  // vid for different ops yield the same result, regardless of permissions.
  // Setting a fake vid here allows us to store different static results per op.
  $tmp = $node->vid;
  switch ($op) {
    case 'update':
      $node->vid = -1;
      break;
    case 'delete':
      $node->vid = -2;
      break;
  }

  // Check access.
  $access = _node_revision_access($node, $op);

  // Restore the original revision id.
  $node->vid = $tmp;
  return $access;
}

/**
 * Checks if a user may make a particular transition on a node.
 *
 * @param $node
 *   The node being acted upon.
 * @param $state
 *   The new moderation state.
 *
 * @return
 *   Booelan TRUE or FALSE.
 */
function _workbench_moderation_moderate_access($node, $state) {
  global $user;

  $my_revision = $node->workbench_moderation['my_revision'];
  $next_states = workbench_moderation_states_next($my_revision->state, $user, $node->type);
  $access = node_access('update', $node, $user)       // the user can edit the node
          && $my_revision->current                    // this is the current revision (no branching the revision history)
          && (!empty($next_states))                   // there are next states the user may transition to
          && isset($next_states[$state]);             // this state is in the available next states

  // Allow other modules to change our rule set.
  $op = 'moderate';
  drupal_alter('workbench_moderation_access', $access, $op, $node);

  return $access;
}

/**
 * Checks if the user can view the current node revision.
 *
 * This is the access callback for node/%node/draft as defined in
 * workbench_moderation_menu().
 *
 * @param $node
 *   The node being acted upon.
 *
 * @return
 *   Booelan TRUE or FALSE.
 */
function _workbench_moderation_access_current_draft($node) {

  // This tab should only appear for node types under moderation
  if (!workbench_moderation_node_type_moderated($node->type)) {
    return FALSE;
  }

  $state = $node->workbench_moderation;
  return (_workbench_moderation_access('view revisions', $node)
      && !empty($state['published'])
      && $state['published']->vid != $state['current']->vid);
}

/**
 * Implements hook_help().
 */
function workbench_moderation_help($path, $arg) {
  switch ($path) {
    case 'admin/help#workbench_moderation':
      return '<p>' . t("Enables you to control node display with a moderation
        workflow. You can have a 'Live revision' for all visitors and pending
        revisions which need to be approved to become the new 'Live Revision.'") . '</p>';

    case 'admin/config/workbench/moderation':
      return '<p>' . t("These are the states through which a node passes in order to become published. By
        default, the Workbench Moderation module provides the states 'Draft,' 'Needs Review,' and 'Published.'
        On this screen you may create, delete and re-order states. Additional states might include
        'Legal Review,' 'PR Review,' or any or state your site may need.") . '</p>';

    case 'admin/config/workbench/moderation/transitions':
      return '<p>' . t("The Workbench Moderation module keeeps track of when a node moves from one state to
        another. By default, nodes begin in the %draft state and end in the %published state. The transitions
        on this page control how nodes move from state to state.", array('%draft' => workbench_moderation_state_label(workbench_moderation_state_none()),
        '%published' => workbench_moderation_state_label(workbench_moderation_state_published()))) . ' ' . l(t('Permission to perform these transitions
        is controlled on a role-by-role basis.'), 'admin/people/permissions', array('fragment' => 'module-workbench_moderation')) . '</p>';

    case 'admin/config/workbench/moderation/check-permissions':
      return '<p>' . t("In order to participate in the moderation process,
        Drupal users must be granted several node- and moderation- related
        permissions. This page can help check whether roles have the correct
        permissions to author, edit, moderate, and publish moderated content.") . '</p>';
  }
}

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

/**
 * Implements hook_views_default_views().
 */
function workbench_moderation_views_default_views() {
  $module = 'workbench_moderation';
  $directory = 'views';
  $extension = 'view.inc';
  $name = 'view';

  // From workbench_load_all_exports().
  $return = array();
  // Find all the files in the directory with the correct extension.
  $files = file_scan_directory(drupal_get_path('module', $module) . "/$directory", "/.$extension/");
  foreach ($files as $path => $file) {
    require $path;
    if (isset($$name)) {
      $return[$$name->name] = $$name;
    }
  }

  return $return;
}


/**
 * Implements hook_node_presave().
 *
 * Ensure that a node in moderation has the proper publication status.
 * We set $node->status = 0 (unpublished) if this is a new node which has not
 * been marked as published, or if the node has no published revision.
 */
function workbench_moderation_node_presave($node) {
  global $user;
  if (isset($node->workbench_moderation_state_new)) {
    // If the new moderation state is published, or if this revision is the
    // published revision, set the node status to published.
    if ($node->workbench_moderation_state_new == workbench_moderation_state_published() || (!empty($node->workbench_moderation['published']) && $node->vid == $node->workbench_moderation['published']->vid)) {
      $node->status = 1;
    }
    else {
      $node->status = 0;
    }
  }
}

/**
 * Implements hook_node_insert().
 *
 * Wrapper call to the update hook.
 */
function workbench_moderation_node_insert($node) {
  workbench_moderation_node_update($node);
}

/**
 * Implements hook_node_update().
 *
 * Handles the submit of the node form moderation information
 */
function workbench_moderation_node_update($node) {
  global $user;

  // Don't proceed if moderation is not enabled on this content type, or if
  // we're replacing an already-published revision.
  if (!workbench_moderation_node_type_moderated($node->type) ||
      !empty($node->workbench_moderation['updating_live_revision'])) {
    return;
  }

  // Set default moderation state values.
  if (!isset($node->workbench_moderation_state_current)) {
    $node->workbench_moderation_state_current = ($node->status ? workbench_moderation_state_published() : workbench_moderation_state_none());
  };
  if (!isset($node->workbench_moderation_state_new)) {
    $node->workbench_moderation_state_new = variable_get('workbench_moderation_default_state_' . $node->type, workbench_moderation_state_none());
  };

  // If this is a new node, give it some information about 'my revision'.
  if (!isset($node->workbench_moderation)) {
    $node->workbench_moderation = array();
    $node->workbench_moderation['my_revision'] = $node->workbench_moderation['current'] = (object) array(
      'from_state' => workbench_moderation_state_none(),
      'state' => workbench_moderation_state_none(),
      'nid' => $node->nid,
      'vid' => $node->vid,
      'uid' => $user->uid,
      'current' => TRUE,
      'published' => FALSE,
      'stamp' => $node->changed,
    );
  }

  // Apply moderation changes if this is a new revision or if the moderation
  // state has changed.
  if (!empty($node->revision) || $node->workbench_moderation_state_current != $node->workbench_moderation_state_new) {
    // Update attached fields.
    field_attach_update('node', $node);
    // Moderate the node.
    workbench_moderation_moderate($node, $node->workbench_moderation_state_new);
  }

  return;
}

/**
 * Implements hook_node_load().
 *
 * Load moderation history and status on a node.
 */
function workbench_moderation_node_load($nodes, $types) {
  foreach ($nodes as $node) {
    // Add the node history
    workbench_moderation_node_data($node);
  }
}

/**
 * Implements hook_node_view().
 *
 * Display messages about the node's moderation state.
 */
function workbench_moderation_node_view($node, $view_mode = 'full') {
  // Show moderation state messages if we're on a node page.
  if (node_is_page($node) && $view_mode == 'full' && empty($node->in_preview)) {
    workbench_moderation_messages('view', $node);
  }
}

/**
 * Implements hook_node_delete().
 */
function workbench_moderation_node_delete($node) {
  // Delete node history when it is deleted.
  db_delete('workbench_moderation_node_history')
    ->condition('nid', $node->nid)
    ->execute();
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Add moderation rules to node types.
 */
function workbench_moderation_form_node_type_form_alter(&$form, $form_state) {
  // Get a list of moderation states.
  $options = workbench_moderation_state_labels();

  // Disable the 'revision' checkbox when the 'moderation' checkbox is checked, so that moderation
  // can not be enabled unless revisions are enabled.
  $form['workflow']['node_options']['revision']['#states'] = array(
    'disabled' => array(':input[name="node_options[moderation]"]' => array('checked' => TRUE)),
  );

  // Disable the 'moderation' checkbox when the 'revision' checkbox is not checked, so that
  // revisions can not be turned off without also turning off moderation.
  $form['workflow']['node_options']['#options']['moderation'] = t('Enable moderation of revisions');
  $form['workflow']['node_options']['moderation']['#description'] = t('Revisions must be enabled in order to use moderation.');
  $form['workflow']['node_options']['moderation']['#states'] = array(
    'disabled' => array(':input[name="node_options[revision]"]' => array('checked' => FALSE)),
  );

  // This select element is hidden when moderation is not enabled.
  $form['workflow']['workbench_moderation_default_state'] = array(
    '#title' => t('Default moderation state'),
    '#type' => 'select',
    '#options' => $options,
    '#default_value' => variable_get('workbench_moderation_default_state_' . $form['#node_type']->type),
    '#description' => t('Set the default moderation state for this content type. Users with additional moderation permissions will be able to set the moderation state when creating or editing nodes.'),
    '#states' => array(
      'visible' => array(':input[name="node_options[moderation]"]' => array('checked' => TRUE)),
    ),
  );
  $form['#validate'][] = 'workbench_moderation_node_type_form_validate';
}

/**
 * Validate the content type form.
 */
function workbench_moderation_node_type_form_validate($from, &$form_state) {
  // Ensure that revisions are enabled if moderation is.
  if ($form_state['values']['node_options']['moderation']) {
    $form_state['values']['node_options']['status'] = 0;
    $form_state['values']['node_options']['revision'] = 1;
  }
}

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 *
 * Forcing new reversion and publishing.
 */
function workbench_moderation_form_node_form_alter(&$form, $form_state) {
  global $user;

  // This must be a node form and a type that has moderation enabled
  if (!workbench_moderation_node_type_moderated($form['type']['#value'])) {
    return;
  }
  // Set a moderation state even if there is not one defined
  if (isset($form['#node']->workbench_moderation['current']->state)) {
    $moderation_state = $form['#node']->workbench_moderation['current']->state;
  }
  else {
    $moderation_state = variable_get('workbench_moderation_default_state_' . $form['type']['#value'], workbench_moderation_state_none());
  }

  // Store the current moderation state
  $form['workbench_moderation_state_current'] = array(
    '#type' => 'value',
    '#value' => $moderation_state
  );
  // We have a use case where a live node is being edited. This will always
  // revert back to the original node status.
  if ($moderation_state == workbench_moderation_state_published()) {
    $moderation_state = workbench_moderation_state_none();
  }
  // Get all the states *this* user can access. If states is false, this user
  // can not change the moderation state
  if ($states = workbench_moderation_states_next($moderation_state, $user, $form['type']['#value'])) {
    $current = array($moderation_state => t('Current: @state', array('@state' => workbench_moderation_state_label($moderation_state))));
    $states = array_merge($current, $states);
    $form['options']['workbench_moderation_state_new'] = array(
      '#title' => t('Moderation state'),
      '#type' => 'select',
      '#options' => $states,
      '#default_value' => $moderation_state,
      '#description' => t('Set the moderation state for this content.'),
      '#access' => $states ? TRUE: FALSE,
    );
  }
  else {
    // Store the current moderation state
    $form['workbench_moderation_state_new'] = array(
      '#type' => 'value',
      '#value' => $moderation_state
    );
  }

  // Always create new revisions for nodes that are moderated
  $form['revision_information']['revision'] = array(
    '#type' => 'value',
    '#value' => TRUE,
  );
  // Set a default revision log message.
  $logged_name = (user_is_anonymous() ? variable_get('anonymous', t('Anonymous')) : $user->name);
  $logged_action = !empty($form['#node']->nid) ? t('Edited') : t('Created');
  $form['revision_information']['log']['#default_value'] = t('!action by @user.', array('!action' => $logged_action, '@user' => $logged_name));
  // Move the revision log into the publishing options to make things pretty.
  if ($form['options']['#access']) {
    $form['options']['log'] = $form['revision_information']['log'];
    $form['options']['log']['#title'] = t('Moderation notes');

    // Unset the old placement of the Revision log.
    unset($form['revision_information']['log']);
    // The revision information section should now be empty.
    $form['revision_information']['#access'] = FALSE;
  }

  // Users can not choose to publish content; content can only be published by
  // setting the content's moderation state to "Published".
  $form['options']['status']['#access'] = FALSE;
  $form['actions']['submit']['#submit'][] = 'workbench_moderation_node_form_submit';
  workbench_moderation_messages('edit', $form['#node']);
}

/**
 * Redirect to the current revision of a node after editing.
 */
function workbench_moderation_node_form_submit($form, &$form_state) {
  $form_state['redirect'] = array('node/' . $form_state['node']->nid . '/current-revision');
}

/**
 * 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 workbench_moderation_node_edit_page_override($node) {
  // Check to see if this is an existing node
  if (isset($node->nid)) {
    if (workbench_moderation_node_type_moderated($node->type)) {
      // Load the node moderation data
      workbench_moderation_node_data($node);
      // We ONLY edit the current revision
      $node = workbench_moderation_node_current_load($node);
    }
  }
  // Ensure we have the editing code.
  module_load_include('inc', 'node', 'node.pages');
  return node_page_edit($node);
}

/**
 * Returns the key which represents the live revision.
 *
 * @TODO: make this configurable.
 */
function workbench_moderation_state_published() {
  return 'published';
}

/**
 * Returns the key which represents the neutral non moderated revision.
 *
 * @TODO: make this configurable.
 */
function workbench_moderation_state_none() {
  return 'draft';
}

/**
 * Determines if this content type is set to be moderated
 *
 * @param $type
 *   String, content type name
 *
 * @return boolean
 */
function workbench_moderation_node_type_moderated($type) {
  // Is this content even in moderatation?
  $options = variable_get("node_options_$type", array());
  if (in_array('revision', $options) && in_array('moderation', $options)) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Lists content types that are moderated
 */
function workbench_moderation_moderate_node_types() {
  $types = node_type_get_types();
  $result = array();
  foreach ($types as $type) {
    // Is this content even in moderatation?
    if (workbench_moderation_node_type_moderated($type->type)) {
      $result[] = $type->type;
    }
  }
  return $result;
}

/**
 * Checks if a user may change the state of a node.
 *
 * This check is based on transition and node type. Users
 * with the 'bypass workbench moderation' permission may make any state
 * transition.
 *
 * @see workbench_moderation_permission()
 *
 * Note that we do not use content-type specific moderation by default. To
 * enable that, see the instructions in workbench_moderation_permission().
 *
 * @param $account
 *   The user account being checked.
 * @param $from_state
 *   The original moderation state.
 * @param $to_state
 *   The new moderation state.
 *
 * @return
 *   Bollean TRUE or FALSE.
 */
function workbench_moderation_state_allowed($account, $from_state, $to_state, $node_type) {
  // Allow super-users to moderate all content.
  if (user_access("bypass workbench moderation", $account)) {
    return TRUE;
  }

  // Can this user moderate all content for this transition?
  if (user_access("moderate content from $from_state to $to_state", $account)) {
    return TRUE;
  }

  // Are we using complex node type rules for this transition?
  if (variable_get('workbench_moderation_per_node_type', FALSE) && user_access("moderate $node_type state from $from_state to $to_state", $account)) {
    return TRUE;
  }

  // Default return.
  return FALSE;
}

/**
 * Adds current and live revision data to a node.
 *
 * @param $node
 *   The node being acted upon.
 */
function workbench_moderation_node_data($node) {
  // Make sure that this node type is moderated.
  if (!workbench_moderation_node_type_moderated($node->type)) {
    return;
  }

  // Path module is stupid and doesn't load its data in node_load.
  if (module_exists('path') && isset($node->nid)) {
    $path = array();
    $conditions = array('source' => 'node/' . $node->nid);
    if ($node->language != LANGUAGE_NONE) {
      $conditions['language'] = $node->language;
    }
    $path = path_load($conditions);
    if ($path === FALSE) {
      $path = array();
    }
    if (isset($node->path)) {
      $path += $node->path;
    }
    $path += array(
      'pid' => NULL,
      'source' => 'node/' . $node->nid,
      'alias' => '',
      'language' => isset($node->language) ? $node->language : LANGUAGE_NONE,
    );

    $node->path = $path;
  }

  // Build a default 'current' moderation record. Nodes will lack a
  // workbench_moderation record if moderation was not enabled for their node
  // type when they were created. In that case, assume the live node is at the
  // current revision.
  $defaults = array(
    'hid' => NULL,
    'nid' => $node->nid,
    'vid' => $node->vid,
    'from_state' => workbench_moderation_state_none(),
    'state' => ($node->status ? workbench_moderation_state_published() : workbench_moderation_state_none()),
    'uid' => $node->uid,
    'stamp' => $node->changed,
    'published' => $node->status,
    'current' => 1,
  );

  // We'll store moderation state information in an array on the node.
  $node->workbench_moderation = array();