You are here

workbench_moderation.module in Workbench Moderation 7.2

File

workbench_moderation.module
View source
<?php

/**
 * @file workbench_moderation.module
 */

/**
 * Implements hook_views_api().
 */
function workbench_moderation_views_api() {

  // @todo, Should this be 3.0?
  return array(
    'api' => 2.0,
  );
}

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

  // View the current draft of a node.
  $items["node/%workbench_moderation_node_current_draft/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,
    'tab_parent' => 'node/%',
    'tab_root' => 'node/%',
  );
  return $items;
}

/**
 * Replacement menu loader for viewing a current draft node.
 *
 * This ensures that page.tpl gets the correct version of the node.
 *
 * @param int $nid
 *   The node ID.
 *
 * @return object
 *   The current node according to moderation.
 */
function workbench_moderation_node_current_draft_load($nid) {
  $node = node_load($nid);

  // Get draft node.
  $draft_node = state_flow_entity_get_active_revision($node, 'node');
  return $draft_node;
}

/**
 * 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().
 * The user has access if the loaded node is the active revision and the active
 * revision isn't the published one.
 *
 * @param int $node
 *   The node being acted upon.
 *
 * @return bool
 *   Booelan TRUE or FALSE.
 */
function _workbench_moderation_access_current_draft($node) {
  $active_id = state_flow_entity_get_active_revision_id($node, 'node');
  $machine = state_flow_entity_load_state_machine($node, 'node');

  // Empty active ID means that it is a node that never before was handled by
  // moderation so it's most likely an published version and not a draft.
  // If the loaded node has't the same revision id as the active revision id we
  // skip further checks too.
  if (empty($active_id) || $machine
    ->isActivePublishedRevision()) {
    return FALSE;
  }
  return state_flow_menu_node_access($node);
}

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

  // Hide Workflow tab provided by State Flow.
  $items['node/%node/workflow']['access callback'] = FALSE;

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

  // 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';
  $items['node/%node/revisions/%/view']['access callback'] = 'workbench_moderation_revision_access';

  // Use our access control for the entity_translation path handling if set.
  if (isset($items['node/%node/revisions/%state_flow_revision_node'])) {
    $items['node/%node/revisions/%state_flow_revision_node']['access callback'] = 'workbench_moderation_revision_access';
  }

  // Redirect node/%node/workflow
  if (!empty($items['node/%node/workflow']) && $items['node/%node/workflow']['module'] === 'state_flow') {

    // @todo, Uncomment this when the moderation tab is ready.
    // $items['node/%node/workflow']['page callback'] = 'workbench_moderation_node_revisions_redirect';
    // $items['node/%node/workflow']['page arguments'] = array(1);
    // $items['node/%node/workflow']['type'] = MENU_NORMAL_ITEM;
  }

  /**
  * @todo, revisit these alters from version 1.x. See link below:
  * @see https://www.drupal.org/node/1279290
    // 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'),
   );
    }
  *
  *
  */
}

/**
 * Implements hook_permission().
 *
 * Provides permissions for each state to state change.
 */
function workbench_moderation_permission() {
  $permissions = array();
  $permissions['view moderation messages'] = array(
    'title' => t('View the moderation messages on a node'),
  );
  $permissions['view all unpublished content'] = array(
    'title' => t('View all unpublished content'),
  );
  $permissions['bypass workbench moderation'] = array(
    'title' => t('Bypass moderation restrictions'),
    'restrict access' => TRUE,
  );
  $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'),
  );

  // Add permissions for every state.
  $states = workbench_workflows_load_all();
  foreach ($states as $state => $info) {
    $permission = 'view all content in state ' . $state;
    $permissions[$permission] = array(
      'title' => t('View all content in state "!state"', array(
        '!state' => $info->title,
      )),
    );
    if ($info->entity_state_change < 1) {
      $permissions[$permission]['warning'] = t('This permission will give users access to probably unpublished content.');
    }
  }
  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') {
    if (!$node->status && user_access('view all unpublished content', $account)) {
      return NODE_ACCESS_ALLOW;
    }
    $machine = state_flow_entity_load_state_machine($node, 'node');
    if ($machine && !$machine
      ->ignore()) {

      // Check if user has specific access to the workflow state the node has.
      if (($state = $machine
        ->get_current_state()) && user_access('view all content in state ' . $state, $account)) {
        return NODE_ACCESS_ALLOW;
      }

      // Check if this is a formerly published version - if so user doesn't have
      // access if no explicit state access above is given.
      if ($node->status && !$machine
        ->isActivePublishedRevision()) {
        return NODE_ACCESS_DENY;
      }
    }
  }
  return NODE_ACCESS_IGNORE;
}

/**
 * Access callback used to check if a revision is accessible.
 *
 * This re-uses the node_access() as well as state_flow_menu_node_access().
 * Our implementation of hook_node_access() allows fine grained access control
 * based on the workflow state of a node.
 */
function workbench_moderation_revision_access($node, $op = 'view', $account = NULL) {

  // Normal behavior for unmoderated nodes.
  if (state_flow_entity_load_state_machine($node, 'node')
    ->ignore()) {
    return _node_revision_access($node, $op);
  }

  // View access is controlled in detail by our hook_node_access implementation.
  if ($op == 'view') {
    return node_access('view', $node, $account);
  }

  // Run access check by state_flow.
  return state_flow_menu_node_access($node, $account);
}

/**
 * Implements hook_state_flow_menu_node_access_alter().
 */
function workbench_moderation_state_flow_menu_node_access_alter(&$access, $node, $account) {

  // Simplify state flow access down to match node edit access.
  $access = node_access('update', $node, $account);
}

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function workbench_moderation_form_node_form_alter(&$form, &$form_state, $form_id) {
  if (!empty($form['options']['state_flow'])) {

    // Hide Published checkbox.
    $form['options']['status']['#access'] = FALSE;

    // Disable revision checkbox on new nodes. This is also enforced in
    // workbench_moderation_node_presave().
    if (!isset($form['#node']->nid) && isset($form['revision_information']['revision'])) {
      $form['revision_information']['revision']['#default_value'] = TRUE;
      $form['revision_information']['revision']['#disabled'] = TRUE;
      $form['revision_information']['revision']['#description'] = t('A new revision must always be created when Workbench Moderation and State Flow are enabled.');
    }

    // Allow access to revision info.
    $form['revision_information']['#access'] = TRUE;

    // Info block messages.
    $node = $form_state['node'];
    workbench_moderation_messages('edit', $node);

    // Note: Our JS relies on functionality from core that may or may not be on
    // the page yet.
    drupal_add_library('system', 'drupal.form');

    // Add the javascript for vertical tabs.
    drupal_add_js(drupal_get_path('module', 'workbench_moderation') . '/js/workbench_moderation.js', array(
      'weight' => 90,
    ));

    // Change the title of core options from "Publishing Options" to "Additional
    // Options because the event the publish checkbox is hidden.
    if (!empty($form['options']['#title'])) {
      $form['options']['#title'] = t('Additional options');
    }
  }
}

/**
 * Implements hook_node_presave().
 */
function workbench_moderation_node_presave($node) {

  // Enforce a revision for new nodes, even when programmatically created.
  if ($node->is_new) {
    $node->revision = 1;
  }
}

/**
 * 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) {
  drupal_goto('node/' . $node->nid . '/moderation');

  /**
  * @todo Revisit this additional logic from version 1.x
    // 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 {

   if (module_exists('diff')) {
     return diff_diffs_overview($node);
   }
   else {
     return node_revision_overview($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_block_view_workbench_block().
 *
 * Show the editorial status of this node.
 */
function workbench_moderation_workbench_block() {
  $output = array();
  foreach (workbench_moderation_set_message() as $message) {
    $output[] = t('!label: <em>!message</em>', array(
      '!label' => $message['label'],
      '!message' => $message['message'],
    ));
  }
  return $output;
}

/**
 * Stores status messages for delivery.
 *
 * This function stores up moderation messages to be passed on to workbench_moderation_workbench_block().
 *
 * This function uses a static variable so that function can be called more than
 * once and the array built up.
 *
 * @see workbench_moderation_workbench_block()
 * @see workbench_moderation_messages()
 *
 * @param $new_messages
 *   An array of messages to be added to the block.
 *
 * @return
 *   An array of messages to be added to the block.
 */
function workbench_moderation_set_message($new_messages = array()) {
  static $messages = array();
  $messages = array_merge($messages, $new_messages);
  return $messages;
}

/**
 * Sets status messages for a node.
 *
 * Note that these status messages aren't relevant to the session, only the
 * current page view.
 *
 * @see workbench_moderation_set_message()
 *
 * @param string $context
 *   A string, either 'view' or 'edit'.
 * @param object $node
 *   A node object. The current menu object will be used if it is a node and
 *   this variable was not set.
 */
function workbench_moderation_messages($context, $node = NULL) {
  $info_block_messages = array();
  if (!user_access('view moderation messages') || !$node && !($node = menu_get_object())) {
    return;
  }
  $msgid = 'node:' . (isset($node->nid) ? $node->nid : $node->created);
  if (($state_machine = state_flow_entity_load_state_machine($node, 'node')) && !empty($node->current_state)) {
    $any_published_vid = !empty($node->published_revision_id);
    $published = $any_published_vid && $node->published_revision_id == $node->vid && $node->status == '1';
    $active_revision_id = $state_machine
      ->get_active_revision();
    $info_block_messages[$msgid . ':state:' . $node->current_state] = array(
      'label' => t('Revision state'),
      'message' => check_plain(workbench_workflows_load_label('states', $node->current_state)),
    );
    $info_block_messages[$msgid . ':active_revision'] = array(
      'label' => t('Active revision'),
      'message' => $active_revision_id == $node->vid ? t('Yes') : t('No'),
    );
    $info_block_messages[$msgid . ':published:' . (int) $published] = array(
      'label' => t('Public'),
      'message' => $published ? t('Yes') : t('No'),
    );

    // If there is a published vid, that's something else, create a link.
    if ($any_published_vid && !$published) {
      $info_block_messages[$msgid . ':public_rev'] = array(
        'label' => t('Public revision'),
        'message' => l(t('Public version'), 'node/' . $node->nid),
      );
    }
    if (!$published) {
      $info_block_messages[$msgid . ':publish'] = array(
        'label' => t('Moderate'),
        'message' => l(t('Change state'), 'node/' . $node->nid . '/revisions-state-flow-states'),
      );
    }

    // Send the info block array to a static variable.
    workbench_moderation_set_message($info_block_messages);
  }
}

/**
 * Prepare variables for the state_flow_history_entity template.
 */
function workbench_moderation_preprocess_state_flow_history_entity(&$variables) {
  $states = workbench_workflows_options('states');

  // Rewrite state machine names into labels.
  if (!empty($variables['state']) && array_key_exists($variables['state'], $states)) {
    $variables['state'] = $states[$variables['state']];
  }
  if (!empty($variables['from_state']) && array_key_exists($variables['from_state'], $states)) {
    $variables['from_state'] = $states[$variables['from_state']];
  }
}

Functions

Namesort descending Description
workbench_moderation_form_node_form_alter Implements hook_form_BASE_FORM_ID_alter().
workbench_moderation_menu Implements hook_menu().
workbench_moderation_menu_alter Implements hook_menu_alter().
workbench_moderation_messages Sets status messages for a node.
workbench_moderation_node_access Implements hook_node_access().
workbench_moderation_node_current_draft_load Replacement menu loader for viewing a current draft node.
workbench_moderation_node_presave Implements hook_node_presave().
workbench_moderation_node_revisions_redirect Redirects 'node/%node/revisions' to node/%node/moderation
workbench_moderation_node_view Implements hook_node_view().
workbench_moderation_permission Implements hook_permission().
workbench_moderation_preprocess_state_flow_history_entity Prepare variables for the state_flow_history_entity template.
workbench_moderation_revision_access Access callback used to check if a revision is accessible.
workbench_moderation_set_message Stores status messages for delivery.
workbench_moderation_state_flow_menu_node_access_alter Implements hook_state_flow_menu_node_access_alter().
workbench_moderation_views_api Implements hook_views_api().
workbench_moderation_workbench_block Implements hook_block_view_workbench_block().
_workbench_moderation_access_current_draft Checks if the user can view the current node revision.