Forked from
UNL Information Services / UNL-CMS
611 commits behind the upstream repository.
-
Eric Rasmussen authored
git-svn-id: file:///tmp/wdn_thm_drupal/branches/drupal-7.x/staging@1395 20a16fea-79d4-4915-8869-1ea9d5ebf173
Eric Rasmussen authoredgit-svn-id: file:///tmp/wdn_thm_drupal/branches/drupal-7.x/staging@1395 20a16fea-79d4-4915-8869-1ea9d5ebf173
workbench_moderation.module 63.21 KiB
<?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) {
global $user;
// 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();
// Fetch the most recent revision from the {node_revision} table. This is the
// current revision ("head").
$query = db_select('node_revision', 'r');
$query->addJoin('LEFT OUTER', 'workbench_moderation_node_history', 'm', 'r.vid = %alias.vid');
$query->condition('r.nid', $node->nid)
->orderBy('r.vid', 'DESC')
->orderBy('m.hid', 'DESC')
->fields('m')
->fields('r', array('title', 'timestamp'));
$current = $query->execute()->fetchObject();
if (!$current) {
$current = (object) $defaults;
}
else {
// Fill in any defaults that are missing from the database record. We need
// to maintain false-y values except for NULL, so array_filter() +
// array_merge() wouldn't work here.
foreach (array_keys($defaults) as $key) {
if (is_null($current->$key)) {
$current->$key = $defaults[$key];
}
}
}
$current->current = 1;
$node->workbench_moderation['current'] = $current;
// Fetch the published revision. There may not be a workbench_moderation
// record for some nodes, but in those cases if the node is published,
// $current->published will be TRUE.
if ($current->published) {
$published = $current;
}
else {
// Fetch the most recent published revision.
$query = db_select('node', 'n');
$query->addJoin('INNER', 'node_revision', 'r', 'n.vid = r.vid');
$query->addJoin('LEFT OUTER', 'workbench_moderation_node_history', 'm', 'r.vid = m.vid');
$query->condition('n.nid', $node->nid)
->condition('n.status', 1)
->orderBy('m.hid', 'DESC')
->fields('r', array('nid', 'vid', 'title', 'timestamp'))
->fields('m');
$published = $query->execute()->fetchObject();
}
// If we have a published copy, add that to the array.
if ($published) {
$published->state = workbench_moderation_state_published();
$node->workbench_moderation['published'] = $published;
}
// Fetch the workbench_moderation record for this node object's revision. If
// it is either the current or published revision of the node, that data will
// be used.
if ($node->vid == $current->vid) {
$my_revision = $current;
}
elseif ($published && $node->vid == $published->vid) {
$my_revision = $published;
}
else {
$query = db_select('node_revision', 'r');
$query->addJoin('LEFT OUTER', 'workbench_moderation_node_history', 'm', 'r.vid = m.vid');
$query->condition('m.vid', $node->vid)
->orderBy('m.hid', 'DESC')
->fields('m')
->fields('r', array('nid', 'vid', 'title', 'timestamp'));
$my_revision = $query->execute()->fetchObject();
// This might happen if you're turning workbench_moderation on and off, but
// it should be really rare. Workbench_moderation must have recorded a
// current revision, and then the node table must contain a different and
// unpublished revision.
if (!$my_revision) {
$my_revision = (object) array(
'hid' => NULL,
'nid' => $node->nid,
'vid' => $node->vid,
'from_state' => workbench_moderation_state_none(),
'state' => workbench_moderation_state_none(),
'uid' => $node->uid,
'stamp' => $node->changed,
'published' => 0,
'current' => 0,
);
}
}
// Add my revision to the array.
$node->workbench_moderation['my_revision'] = $my_revision;
}
/**
* Utility function to load the current revision of a node.
*
* @param $node
* The node being acted upon.
*
* @return
* The current node according to moderation.
*/
function workbench_moderation_node_current_load($node) {
// Is there a current revision?
if (isset($node->workbench_moderation['current']->vid)) {
// Ensure that we will return the current revision
if ($node->vid != $node->workbench_moderation['current']->vid) {
$node = node_load($node->nid, $node->workbench_moderation['current']->vid);
}
}
return $node;
}
/**
* Utility function to load the live revision of a node.
*
* This is encapsulated so that changes to how the moderation data is stored
* will not impact the API.
*
* @param $node
* The node being acted upon.
*
* @return
* The node object of the live revision.
*/
function workbench_moderation_node_live_load($node) {
// Is there a live revision of this node?
if (isset($node->workbench_moderation['published']->vid)) {
// If the live revision is not this revision, we need to load that revision
if ($node->vid != $node->workbench_moderation['published']->vid) {
return node_load($node->nid, $node->workbench_moderation['published']->vid, TRUE);
}
// This is the live node, return it
return $node;
}
}
/**
* Utility function to determine if this node is in the live state.
*
* @param $node
* The node being acted upon.
*
* @return
* Boolean TRUE if this is the current revision. FALSE if not.
*/
function workbench_moderation_node_is_current($node) {
if (isset($node->workbench_moderation['published']->vid) && isset($node->workbench_moderation['current']->vid)) {
if ($node->workbench_moderation['published']->vid == $node->workbench_moderation['current']->vid) {
return TRUE;
}
return FALSE;
}
// If not set, then TRUE.
return TRUE;
}
/**
* Get a list of all moderation states.
*
* @return
* An array of state objects, keyed by state name and ordered by weight. Each
* state object has name, description, and weight properties.
*/
function workbench_moderation_states() {
static $states;
if (!isset($states)) {
$states = db_select('workbench_moderation_states', 'states')
->fields('states', array('name', 'label', 'description', 'weight'))
->orderBy('states.weight', 'ASC')
->execute()
->fetchAllAssoc('name');
}
return $states;
}
/**
* Generate an array of moderation states suitable for use as Form API #options.
*
* @return
* An array of states with machine names as keys and labels as values.
*/
function workbench_moderation_state_labels() {
$labels = &drupal_static(__FUNCTION__);
if (!isset($labels)) {
$labels = array();
foreach (workbench_moderation_states() as $machine_name => $state) {
$labels[$machine_name] = $state->label;
}
}
return $labels;
}
/**
* Get the label for a state based on its machine name.
*
* @param type $machine_name
* The machine name of the state.
* @return
* An unsanitized label or an empty string if the state does not exist.
*/
function workbench_moderation_state_label($machine_name) {
$labels = workbench_moderation_state_labels();
return isset($labels[$machine_name]) ? $labels[$machine_name] : '';
}
/**
* Get information about a single moderation state.
*
* @param type $machine_name
* The machine name of the state.
* @return
* An object of information about the state or FALSE if the state does not exist.
*/
function workbench_moderation_state_load($machine_name) {
$states = workbench_moderation_states();
if (isset($states[$machine_name])) {
return $states[$machine_name];
}
return FALSE;
}
/**
* Save a new or existing moderation state.
*
* Moderation state names must be unique, so saving a state object with a
* non-unique name updates the existing state.
*
* @TODO: add a hook here.
*
* @param $state
* An object with name, description, and weight properties.
*/
function workbench_moderation_state_save($state) {
return db_merge('workbench_moderation_states')
->key(array('name' => $state->name))
->fields((array) $state)
->execute();
}
/**
* Delete a moderation state.
*
* This function also deletes any transitions that reference the deleted
* moderation state.
*
* @TODO: add a hook here.
*
* @param $state
* An object with at least a name property.
*
*/
function workbench_moderation_state_delete($state) {
db_delete('workbench_moderation_states')
->condition('name', $state->name)
->execute();
db_delete('workbench_moderation_transitions')
->condition(db_or()->condition('from_name', $state->name)->condition('to_name', $state->name))
->execute();
}
/**
* Get a list of all moderation state transitions.
*
* @return
* An array of transition objects, each with from_name and to_name properties
* that reference moderation states. The array is ordered by the weight of the
* 'from' states, then by the weight of the 'to' states.
*/
function workbench_moderation_transitions() {
static $transitions;
if (!isset($transitions)) {
$query = db_select('workbench_moderation_transitions', 't')
->fields('t', array('from_name', 'to_name'));
$alias_from = $query->addJoin('INNER', 'workbench_moderation_states', NULL, 't.from_name = %alias.name');
$alias_to = $query->addJoin('INNER', 'workbench_moderation_states', NULL, 't.to_name = %alias.name');
$query
->orderBy("$alias_from.weight", 'ASC')
->orderBy("$alias_to.weight", 'ASC');
$transitions = $query->execute()->fetchAll();
}
return $transitions;
}
/**
* Saves a moderation state transition.
*
* @TODO: add a hook here.
*
* @param $transition
* An object with from_name and to_name properties that reference moderation
* states.
*/
function workbench_moderation_transition_save($transition) {
return db_merge('workbench_moderation_transitions')
->key(array('from_name' => $transition->from_name, 'to_name' => $transition->to_name))
->fields((array) $transition)
->execute();
}
/**
* Deletes a moderation state transition.
*
* @TODO: add a hook here.
*
* @param $transition
* An object with from_name and to_name properties that reference moderation
* states.
*/
function workbench_moderation_transition_delete($transition) {
db_delete('workbench_moderation_transitions')
->condition('from_name', $transition->from_name)
->condition('to_name', $transition->to_name)
->execute();
}
/**
* Provides a list of possible next states for this node.
*
* This function is used in permissions checks, so it should never return
* disallowed transitions.
*
* @param $current_state
* The current moderation state.
* @param $account
* The user object being checked.
* @param $node_type
* The node type being acted upon.
*
* @return
* If the user may moderate a change, return an array of possible state
* changes. Otherwise, return FALSE.
*/
function workbench_moderation_states_next($current_state, $account = NULL, $node_type) {
// Make sure we have a current state.
if (!$current_state) {
$current_state = workbench_moderation_state_none();
}
if (empty($account)) {
$account = $GLOBALS['user'];
}
if (user_access('bypass workbench moderation', $account)) {
// Some functions expect an array of $state => $state pairs.
$states = workbench_moderation_state_labels();
unset($states[$current_state]);
return $states;
}
else {
// Get a list of possible transitions.
$select = db_select('workbench_moderation_transitions', 'transitions')
->condition('transitions.from_name', $current_state)
->fields('transitions', array('to_name'))
->fields('states', array('label'));
$select->join('workbench_moderation_states', 'states', 'transitions.to_name = states.name');
$states = $select->execute()->fetchAllKeyed();
// Checks whether the user has permission to make each transition. The
// 'bypass workbench moderation' permission is accounted for in
// workbench_moderation_state_allowed().
if ($states) {
foreach ($states as $machine_name => $label) {
if (!workbench_moderation_state_allowed($account, $current_state, $machine_name, $node_type)) {
unset($states[$machine_name]);
}
}
return $states;
}
}
return FALSE;
}
/**
* Provide quick moderation of nodes.
*
* Access is controlled by the menu router to these pseudo-form callbacks.
* This function is also abstracted so that it can be called from any node
* context.
*
* @see _workbench_moderation_moderate_access()
* @see workbench_moderation_menu()
* @see workbench_moderation_node_update()
*
* @param $node
* The node being acted upon.
* @param $state
* The new moderation state requested.
*/
function workbench_moderation_moderate($node, $state) {
global $user;
$old_revision = $node->workbench_moderation['my_revision'];
// Get the number of revisions for this node with vids greater than $node->vid
$vid_count = db_select('node_revision', 'r')
->condition('r.nid', $node->nid)
->condition('r.vid', $node->vid, '>')
->countQuery()->execute()->fetchField();
// If the number of greater vids is 0, then this is the most current revision
$current = ($vid_count == 0);
// Build a history record.
$new_revision = (object) array(
'from_state' => $old_revision->state,
'state' => $state,
'nid' => $node->nid,
'vid' => $node->vid,
'uid' => $user->uid,
'current' => $current,
'published' => ($state == workbench_moderation_state_published()),
'stamp' => $_SERVER['REQUEST_TIME'],
);
// If this is the new 'current' moderation record, it should be the only one
// flagged 'current' in {workbench_moderation_node_history}.
if ($new_revision->current) {
$query = db_update('workbench_moderation_node_history')
->condition('nid', $node->nid)
->fields(array('current' => 0))
->execute();
}
// If this revision is to be published, the new moderation record should be
// the only one flagged 'published' in both
// {workbench_moderation_node_history} AND {node_revision}
if ($new_revision->published) {
$query = db_update('workbench_moderation_node_history')
->condition('nid', $node->nid)
->fields(array('published' => 0))
->execute();
$query = db_update('node_revision')
->condition('nid', $node->nid)
->fields(array('status' => 0))
->execute();
}
// Save the node history record.
drupal_write_record('workbench_moderation_node_history', $new_revision);
// Update the node's content_moderation information so that we can publish it
// if necessary.
$node->workbench_moderation['my_revision'] = $new_revision;
if ($new_revision->current) {
$node->workbench_moderation['current'] = $new_revision;
}
// Handle the published revision.
if ($new_revision->published) {
// If we're moderating a revision to the published state, mark the new
// revision as the published revision.
$node->workbench_moderation['published'] = $new_revision;
}
elseif (isset($node->workbench_moderation['published']) && $new_revision->vid == $node->workbench_moderation['published']->vid && $new_revision->from_state == workbench_moderation_state_published()) {
// If we're moderating the published revision to a non-published state,
// remove the workbench moderation 'published' property.
unset($node->workbench_moderation['published']);
}
// If we're moderating an unpublished revision and there is an existing
// published revision, make sure that the published revision is live.
// We do this in a shutdown function to avoid race conditions when
// running node_save() from within a node submission.
if (!empty($node->workbench_moderation['published'])) {
drupal_register_shutdown_function('workbench_moderation_store', $node);
}
}
/**
* Shutdown callback for saving a node revision.
*
* This function is called by drupal_register_shutdown_function().
* The purpose is to delay a node_save() call so that a live revision
* is not called during hook_node_update().
*
* Instead, we delay the update until the new revision is saved. This way,
* we can more safely call the revision and pick up changes to items
* that are not revisioned (such as menu and path assignments).
*
* @see workbench_moderation_moderate()
*
* @param $node
* The node being saved.
*/
function workbench_moderation_store($node) {
if (!isset($node->nid)) {
watchdog('Workbench moderation', 'Failed to save node revision: node not passed to shutdown function.', array(), WATCHDOG_NOTICE);
return;
}
watchdog('Workbench moderation', 'Saved node revision: %node as live version for node %live.', array('%node' => $node->vid, '%live' => $node->nid), WATCHDOG_NOTICE, l($node->title, 'node/' . $node->nid));
$live_revision = workbench_moderation_node_live_load($node);
// Make sure we're published.
$live_revision->status = 1;
// Don't create a new revision.
$live_revision->revision = 0;
// Prevent another moderation record from being written.
$live_revision->workbench_moderation['updating_live_revision'] = TRUE;
// Reset flag from taxonomy_field_update() so that {taxonomy_index} values aren't written twice.
$taxonomy_index_flag = &drupal_static('taxonomy_field_update', array());
unset($taxonomy_index_flag[$node->nid]);
// Save the node.
node_save($live_revision);
}
/**
* Helper function to redirect after a state change submission.
*
* @param $node
* The node being acted upon.
* @param $state
* The new moderation state requested.
*/
function workbench_moderation_moderate_callback($node, $state) {
if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], "{$node->nid}:{$node->vid}:$state")) {
return MENU_ACCESS_DENIED;
}
workbench_moderation_moderate($node, $state);
drupal_goto(isset($_GET['destination']) ? $_GET['destination'] : 'node/' . $node->nid . '/moderation');
}
/**
* Generates a list of links to available moderation actions.
*
* @param $node
* The node being acted upon.
* @param $url_options
* An array of options to pass, following the url() function syntax.
*
* @return
* A list of links to display with the revision.
*/
function workbench_moderation_get_moderation_links($node, $url_options = array()) {
// Make sure that this node type is moderated.
if (!workbench_moderation_node_type_moderated($node->type)) {
return;
}
// Build links to available moderation states.
$links = array();
$my_revision = $node->workbench_moderation['my_revision'];
if ($my_revision->vid == $node->workbench_moderation['current']->vid
&& $next_states = workbench_moderation_states_next($my_revision->state, NULL, $node->type)) {
foreach ($next_states as $state => $label) {
$link = array_merge($url_options, array(
'title' => workbench_moderation_state_label($state),
'href' => "node/{$node->nid}/moderation/{$node->vid}/change-state/{$state}",
));
$link['query']['token'] = drupal_get_token("{$node->nid}:{$node->vid}:{$state}");
$links[] = $link;
}
}
return $links;
}
/**
* Generates a moderation form for a node.
*
* The caller of this form needs to check whether the node is in moderation.
*
* @param $node
* The node being acted upon.
*
* @return $form
* A Drupal Forms API array.
*/
function workbench_moderation_moderate_form($form, &$form_state, $node, $destination = NULL) {
global $user;
$form = array();
// Build links to available moderation states.
$links = array();
$my_revision = $node->workbench_moderation['my_revision'];
if ($my_revision->vid == $node->workbench_moderation['current']->vid
&& $next_states = workbench_moderation_states_next($my_revision->state, $user, $node->type)) {
$form['#destination'] = $destination;
$form['node'] = array(
'#type' => 'value',
'#value' => $node,
);
$form['#attributes']['class'][] = 'workbench-moderation-moderate-form';
$form['#attached']['css'][] = drupal_get_path('module', 'workbench_moderation') . '/css/workbench_moderation.css';
$form['state'] = array(
'#type' => 'select',
'#options' => $next_states,
'#default_value' => _workbench_moderation_default_next_state($my_revision->state, $next_states),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Apply'),
);
// Cache the form on first load to preserve the node for validation.
// Otherwise, the node would be reloaded on submit, and there would be no
// way to detect if the current revision has been changed.
$form_state['cache'] = TRUE;
}
else {
$form['#access'] = FALSE;
}
return $form;
}
function _workbench_moderation_default_next_state($current_state, $next_states) {
$states = workbench_moderation_states();
foreach ($states as $state_name => $state) {
if ($state->weight > $states[$current_state]->weight && isset($next_states[$state_name])) {
return $state_name;
}
}
}
function workbench_moderation_moderate_form_validate($form, &$form_state) {
// Make sure that the revision that was shown to the user is still the current
// revision before changing the current revision's state.
$moderated_node = $form_state['values']['node'];
$current_node = workbench_moderation_node_current_load(node_load($moderated_node->nid));
if ($moderated_node->vid != $current_node->vid) {
form_set_error('', t('The moderation state could not be changed because the draft has been updated by another user. Please review the current draft.'));
// Redirect the form so that it rebuilds with the current revision.
drupal_redirect_form($form_state);
}
}
function workbench_moderation_moderate_form_submit($form, $form_state) {
if (_workbench_moderation_moderate_access($form_state['values']['node'], $form_state['values']['state'])) {
workbench_moderation_moderate($form_state['values']['node'], $form_state['values']['state']);
}
// This is not ideal, but if the form is invoked from a node's draft tab and
// used to publish the node, the draft tab will not be available after
// publishing, and Drupal's will throw an access denied error before it is
// able to redirect to the published revision.
if (!empty($form['#destination'])) {
if ($form_state['values']['state'] == workbench_moderation_state_published()) {
if ($uri = entity_uri('node', $form['node']['#value'])) {
$form_state['redirect'] = array($uri['path'], $uri['options']);
}
}
else {
$form_state['redirect'] = $form['#destination'];
}
drupal_redirect_form($form_state);
}
}
/**
* 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 $context
* A string, either 'view' or 'edit'.
* @param $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) {
global $user;
if (!user_access('view moderation messages')
|| (!$node && !($node = menu_get_object()))
|| !workbench_moderation_node_type_moderated($node->type)) {
return;
}
$node_published = FALSE;
$revision_published = FALSE;
$revision_current = FALSE;
// For new content, this property will not be set.
if (isset($node->workbench_moderation)) {
$state = $node->workbench_moderation;
if (!empty($state['published'])) {
$node_published = TRUE;
}
if ($state['my_revision']->published) {
$revision_published = TRUE;
}
if ($state['my_revision']->vid == $state['current']->vid) {
$revision_current = TRUE;
}
}
// An array of messages to add to the general workbench block.
$info_block_messages = array();
if ($context == 'view') {
$info_block_messages[] = array(
'label' => t('Revision state'),
'message' => check_plain(workbench_moderation_state_label($state['my_revision']->state)),
);
$info_block_messages[] = array(
'label' => t('Current draft'),
'message' => !empty($revision_current) ? t('Yes') : t('No'),
);
// Check node access.
drupal_static('_node_revision_access', array(), TRUE);
// Add a moderation form.
if ($revision_current && !$revision_published && _workbench_moderation_access('update', $node) && $moderate_form = drupal_get_form('workbench_moderation_moderate_form', $node, "node/{$node->nid}/current-revision")) {
if ($moderate_form = drupal_render($moderate_form)) {
$info_block_messages[] = array(
'label' => t('Moderate'),
'message' => $moderate_form,
);
}
}
// Add an unpublish link.
$next_states = workbench_moderation_states_next(workbench_moderation_state_published(), $user, $node->type);
if ($revision_published && !empty($next_states) && $link = workbench_moderation_access_link(t('Unpublish this revision'), "node/{$node->nid}/moderation/{$node->vid}/unpublish")) {
$info_block_messages[] = array(
'label' => t('Actions'),
'message' => $link,
);
}
// Revision navigation links. This is disabled for the time being, since
// node tabs are lost when navigating through old revisions.
// @TODO remove this entirely?
if (variable_get('workbench_moderation_show_revision_navigation', FALSE) && user_access('view revisions')) {
$links = array();
// Get previous and next revision ids.
$args = array(':nid' => $node->nid, ':vid' => $node->vid);
if ($prev_vid = db_query_range("SELECT nr.vid FROM {node_revision} nr WHERE nr.nid = :nid AND nr.vid < :vid ORDER BY nr.vid DESC", 0, 1, $args)->fetchField()) {
$links[$prev_vid] = array('title' => t('Previous revision'), 'href' => "node/{$node->nid}/revisions/{$prev_vid}/view");
}
if ($next_vid = db_query_range("SELECT nr.vid FROM {node_revision} nr WHERE nr.nid = :nid AND nr.vid > :vid ORDER BY nr.vid ASC", 0, 1, $args)->fetchField()) {
$links[$next_vid] = array('title' => t('Next revision'), 'href' => "node/{$node->nid}/revisions/{$next_vid}/view");
}
// If the current revision is next or previous, use the "node/%node/current-revision" path.
if (($current = $state['current']->vid) && isset($links[$current])) {
$links[$current]['href'] = "node/{$node->nid}/current-revision";
}
// If the published revision is next or previous, use the "node/%node" path.
if (isset($state['published']) && ($published = $state['published']->vid) && isset($links[$published])) {
$links[$published]['href'] = "node/{$node->nid}";
}
// Link it up, with access checks.
foreach ($links as $key => $args) {
$links[$key] = call_user_func_array('workbench_moderation_access_link', $args);
}
// Post the links in a non-repeating message.
if (!empty($links)) {
$info_block_messages[] = array(
'label' => t('View'),
'message' => implode(', ', $links),
);
}
}
}
// @TODO: Clean these up.
elseif ($context == 'edit') {
if ($node_published && $revision_published) {
$info_block_messages[] = array(
'label' => t('Status'),
'message' => t('New draft of live content.'),
);
}
elseif ($node_published && !$revision_published) {
$info_block_messages[] = array(
'label' => t('Status'),
'message' => t('New draft from current revision'),
);
$link = workbench_moderation_access_link(t('Create a new draft from the published revision.'), "node/{$node->nid}/revisions/{$state['published']->vid}/revert");
$info_block_messages[] = array(
'label' => t('Actions'),
'message' => $link,
);
}
else {
// New content.
$info_block_messages[] = array(
'label' => t('New content'),
'message' => t('Your draft will be placed in moderation.'),
);
}
}
// Send the info block array to a static variable.
workbench_moderation_set_message($info_block_messages);
}
/**
* Builds a link for use in messages.
*
* @see workbench_moderation_messages()
*
* @param $text
* The link text to use.
* @param $internal_path
* The Drupal path for the link.
* @param $options
* Link options, following the format of url().
*
* @return
* A drupal-formatted HTML link.
*/
function workbench_moderation_access_link($text, $internal_path, $options = array()) {
if (($item = menu_get_item($internal_path)) && !empty($item['access'])) {
return l($text, $internal_path, $options);
}
}
/**
* 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;
}
/**
* 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;
}
/**
* Implements hook_ctools_plugin_directory() to let the system know
* where our task and task_handler plugins are.
*/
function workbench_moderation_ctools_plugin_directory($owner, $plugin_type) {
if ($owner == 'page_manager') {
return 'plugins/page_manager/' . $plugin_type;
}
}
/**
* Implements hook_ctools_plugin_api().
*/
function workbench_moderation_ctools_plugin_api($module, $api) {
if ($module == 'page_manager' && $api == 'pages_default') {
return array('version' => 1);
}
}