Revisioning project page',
array('@revisioning' => url('http://drupal.org/project/revisioning')));
break;
case 'node/%/revisions':
$s = t('To edit, publish or delete one of the revisions below, click on its saved date.');
break;
case 'admin/build/trigger/revisioning':
$s = t("Below you can assign actions to run when certain publication-related events happen. For example, you could send an e-mail to an author when their content is pubished.");
break;
case 'accessible-content/i-created/pending':
$s = t('Showing all pending content you created and still have at least view access to.');
break;
case 'accessible-content/i-last-modified/pending':
$s = t('Showing all pending content you last modified and still have at least view access to.');
break;
case 'accessible-content/i-can-edit/pending':
$s = t('Showing all pending content you can edit.');
break;
case 'accessible-content/i-can-view/pending':
$s = t('Showing all pending content you have at least view access to.');
break;
}
return empty($s) ? '' : '
'. $s .'
';
}
/**
* Implementation of hook_perm().
*
* Revisioning permissions. Note that permissions to view, revert and delete
* revisions already exist in node.module.
*/
function revisioning_perm() {
$perms = module_exists('module_grants_monitor') ? array('access Pending tab') : array();
$perms = array_merge($perms, array('view revision status messages',
'edit revisions', 'publish revisions', 'unpublish current revision'));
// Add per node-type view perms in same way as edit perms of node module.
// TOOD only do this for content types that have the "Create new revision" ticked
foreach (node_get_types() as $type) {
$name = check_plain($type->type);
$perms[] = 'view revisions of own '. $name .' content';
$perms[] = 'view revisions of any '. $name .' content';
$perms[] = 'publish revisions of own '. $name .' content';
$perms[] = 'publish revisions of any '. $name .' content';
}
return $perms;
}
/**
* Implementation of hook_menu().
*
* Define new menu items.
* Existing menu items are modified through hook_menu_alter().
*/
function revisioning_menu() {
$items = array();
if (module_exists('module_grants_monitor')) {
// Add a tab to the 'I created' tab (defined in module_grants_monitor.module)
$items['accessible-content/i-created/pending'] = array(
'title' => 'In draft/Pending publication',
'page callback' => '_revisioning_show_pending_nodes',
'page arguments' => array('view', I_CREATED),
'access callback' => 'revisioning_user_all_access',
'access arguments' => array(array('access I Created tab', 'access Pending tab')),
'type' => MENU_LOCAL_TASK,
'weight' => -1
);
// Add a tab to the 'I last modified' tab
$items['accessible-content/i-last-modified/pending'] = array(
'title' => 'In draft/Pending publication',
'page callback' => '_revisioning_show_pending_nodes',
'page arguments' => array('view', I_LAST_MODIFIED),
'access callback' => 'revisioning_user_all_access',
'access arguments' => array(array('access I Last Modified tab', 'access Pending tab')),
'type' => MENU_LOCAL_TASK,
'weight' => -1
);
// Add a tab to the 'I can edit' tab
$items['accessible-content/i-can-edit/pending'] = array(
'title' => 'In draft/Pending publication',
'page callback' => '_revisioning_show_pending_nodes',
'page arguments' => array('update'),
'access callback' => 'revisioning_user_all_access',
'access arguments' => array(array('access I Can Edit tab', 'access Pending tab')),
'type' => MENU_LOCAL_TASK,
'weight' => -1
);
// Add a tab to the 'I can view' tab (defined in module_grants.module)
$items['accessible-content/i-can-view/pending'] = array(
'title' => 'In draft/Pending publication',
'page callback' => '_revisioning_show_pending_nodes',
'page arguments' => array('view'),
'access callback' => 'revisioning_user_all_access',
'access arguments' => array(array('access I Can View tab', 'access Pending tab')),
'type' => MENU_LOCAL_TASK,
'weight' => -1
);
}
// Callback (not a tab) to allow users to unpublish a node.
// Note that is a node operation more so than a revision operation, but
// we let _revisioning_node_revision_access() handle access anyway.
$items['node/%node/unpublish'] = array(
//'title' => t(Unpublish current revision'),
'page callback' => 'drupal_get_form',
'page arguments' => array('revisioning_unpublish_confirm', 1),
'access callback' => '_revisioning_node_revision_access', // _revisioning_node_access ?
'access arguments' => array('unpublish current revision', 1),
'type' => MENU_CALLBACK,
);
// Revision tab local subtasks (i.e. secondary tabs), up to 7 of them:
// view, edit, publish, unpublish, revert, delete and compare.
// All revision operations 'node/%node/revisions/%vid/' are defined as
// local tasks (tabs) secondary to the primary 'node/%node/revisions' local
// task (tab).
// The tricky part is to always set "tab_parent", core does NOT figure this
// out based on the URL. %vid is optional, see vid_to_arg().
// Note: the MENU_DEFAULT_LOCAL_TASK for 'node/%node/revisions' is defined in
// function revisioning_menu_alter()
// View revision local subtask
$items['node/%node/revisions/%vid/view'] = array(
'title' => 'View',
'load arguments' => array(3),
'page callback' => '_revisioning_view_revision',
'page arguments' => array(1),
'access callback' => '_revisioning_node_revision_access',
'access arguments' => array('view revisions', 1),
'type' => MENU_LOCAL_TASK,
'weight' => -10,
'tab_parent' => 'node/%/revisions',
);
// Edit revision local subtask
$items['node/%node/revisions/%vid/edit'] = array(
'title' => 'Edit',
'load arguments' => array(3),
'page callback' => '_revisioning_edit_revision',
'page arguments' => array(1),
'access callback' => '_revisioning_node_revision_access',
'access arguments' => array('edit revisions', 1),
'file' => 'node.pages.inc',
'file path' => drupal_get_path('module', 'node'),
'type' => MENU_LOCAL_TASK,
'weight' => -7,
'tab_parent' => 'node/%/revisions',
);
// Publish revision local subtask
$items['node/%node/revisions/%vid/publish'] = array(
'title' => 'Publish this',
'load arguments' => array(3),
'page callback' => 'drupal_get_form',
'page arguments' => array('revisioning_publish_confirm', 1),
'access callback' => '_revisioning_node_revision_access',
'access arguments' => array('publish revisions', 1),
'type' => MENU_LOCAL_TASK,
'weight' => -4,
'tab_parent' => 'node/%/revisions',
);
// Unpublish node local subtask
$items['node/%node/revisions/%vid/unpublish'] = array(
'title' => 'Unpublish this',
'load arguments' => array(3),
'page callback' => 'drupal_get_form',
'page arguments' => array('revisioning_unpublish_confirm', 1),
'access callback' => '_revisioning_node_revision_access',
'access arguments' => array('unpublish current revision', 1),
'type' => MENU_LOCAL_TASK,
'weight' => -3,
'tab_parent' => 'node/%/revisions',
);
// Revert to revision local subtask.
// Difference from core version is %vid that's served by vid_to_arg() function.
$items['node/%node/revisions/%vid/revert'] = array(
'title' => 'Revert to this',
'load arguments' => array(3),
'page callback' => 'drupal_get_form',
'page arguments' => array('node_revision_revert_confirm', 1),
'access callback' => '_revisioning_node_revision_access',
'access arguments' => array('revert revisions', 1),
'file' => 'node.pages.inc',
'file path' => drupal_get_path('module', 'node'),
'type' => MENU_LOCAL_TASK,
'weight' => -2,
'tab_parent' => 'node/%/revisions',
);
// Delete revision local subtask.
// Difference from core version is %vid that's served by vid_to_arg() function.
$items['node/%node/revisions/%vid/delete'] = array(
'title' => 'Delete',
'load arguments' => array(3),
'page callback' => 'drupal_get_form',
'page arguments' => array('node_revision_delete_confirm', 1),
'access callback' => '_revisioning_node_revision_access',
'access arguments' => array('delete revisions', 1),
'file' => 'node.pages.inc',
'file path' => drupal_get_path('module', 'node'),
'type' => MENU_LOCAL_TASK,
'weight' => 10,
'tab_parent' => 'node/%/revisions',
);
// If Diff module is enabled, provide a compare local subtask
if (module_exists('diff')) {
$items['node/%node/revisions/%vid/compare'] = array(
'title' => 'Compare to current',
'load arguments' => array(3),
'page callback' => '_revisioning_compare_to_current_revision',
'page arguments' => array(1),
'access callback' => '_revisioning_node_revision_access',
'access arguments' => array('compare to current', 1),
'type' => MENU_LOCAL_TASK,
'weight' => 0,
'tab_parent' => 'node/%/revisions',
);
}
// Finally, the Revisioning configuration menu item
$items['admin/settings/revisioning'] = array(
'title' => 'Revisioning',
'description' => 'Configure how links to view and edit content behave.',
'page callback' => 'drupal_get_form',
'page arguments' => array('revisioning_admin_configure'),
'access arguments' => array('administer site configuration'),
'file' => 'revisioning.admin.inc'
);
return $items;
}
/**
* Implementation of hook_menu_alter().
*
* Modify menu items defined in other modules (in particular the Node and
* Module Grants modules).
*/
function revisioning_menu_alter(&$items) {
// Primary tabs for 'node/%node': View tab, Edit tab, Revisions tab ...
// View tab can be either 'View current' or 'View latest'.
// It should be suppressed when the 'Revisions' tab shows the same revision,
// so we need a special access callback for this, which expands on the
// callback defined in Module Grants.
$items['node/%node']['access callback'] = $items['node/%node/view']['access callback'] = '_revisioning_view_edit_access_callback';
$items['node/%node']['access arguments']= $items['node/%node/view']['access arguments']= array('view', 1);
$items['node/%node']['page callback'] = $items['node/%node/view']['page callback'] = '_revisioning_view';
$items['node/%node']['page arguments'] = $items['node/%node/view']['page arguments'] = array(1);
// Not applying title callback to 'node/%node', see #782316
$items['node/%node/view']['title callback'] = '_revisioning_title_for_tab';
$items['node/%node/view']['title arguments'] = array(1, FALSE);
// Edit tab can be either 'Edit current' or 'Edit latest'.
// It should be suppressed when the 'Revisions' tab shows the same revision,
// so we need a special access callback for this, which expands on the
// callback defined in Module Grants.
$items['node/%node/edit']['access callback'] = '_revisioning_view_edit_access_callback';
$items['node/%node/edit']['access arguments']= array('edit', 1);
$items['node/%node/edit']['page callback'] = '_revisioning_edit';
$items['node/%node/edit']['title callback'] = '_revisioning_title_for_tab';
$items['node/%node/edit']['title arguments'] = array(1, TRUE);
// 'Revisions' tab remains but points to new page callback, allowing users to
// pick the revision to view, edit, publish, revert, unpublish, delete.
// Need to override _node_revision_access() call back as it disallows access
// to the 'Revisions' tab when there's only one revision, which will prevent
// users from getting to the publish/unpublish links.
$items['node/%node/revisions']['access callback'] = '_revisioning_node_revision_access';
$items['node/%node/revisions']['access arguments'] = array('view revision list', 1);
$items['node/%node/revisions']['page callback'] = '_revisioning_present_node';
$items['node/%node/revisions']['page arguments'] = array(1);
// Unset old menu items defined in node.module (or module_grants.module), as
// these are replaced by ones that use the %vid wildcard instead of % and
// come with the appropriate callbacks.
unset($items['node/%node/revisions/%/view']);
unset($items['node/%node/revisions/%/revert']);
unset($items['node/%node/revisions/%/delete']);
if (module_exists('diff')) {
// If Diff module is enabled, make sure it uses correct access callback
$items['node/%node/revisions/view/%/%']['access callback'] = '_revisioning_node_revision_access';
$items['node/%node/revisions/view/%/%']['access arguments'] = array('view revisions', 1);
}
// This is here rather than in revisioning_menu() as Diff may redefine
// the node/%node/revisions/list item.
$items['node/%node/revisions/list'] = array(
'title' => t('List all revisions'),
'access callback' => '_revisioning_node_revision_access',
'access arguments' => array('view revision list', 1),
'file' => 'node.pages.inc',
'module' => 'node',
//'file path' => drupal_get_path('module', 'node'),
'type' => MENU_LOCAL_TASK, // was: MENU_DEFAULT_LOCAL_TASK; changed for Smart tabs
'weight' => -20,
);
$items['node/%node/revisions/delete-archived'] = array(
'title' => t('Delete archived revisions'),
'page callback' => 'drupal_get_form',
'page arguments' => array('revisioning_delete_archived_confirm', 1),
'access callback' => '_revisioning_node_revision_access',
'access arguments' => array('delete archived revisions', 1),
'type' => MENU_CALLBACK,
);
// Apart from administrators, allow those that pass the 'trigger_access_check'
// to configure the revisioning triggers. This means that users must have at
// least 'administer actions' and 'access administration pages' (the latter is
// to allow them to navigate to the trigger page via the menu).
if (module_exists('trigger')) {
$items['admin/build/trigger/revisioning']['access callback'] = 'trigger_access_check';
}
// [#1024864]: Allow other modules to make further alterations
drupal_alter('revisioning_menu', $items);
}
/**
* Implementation of hook_nodeapi().
*
* This function is called serveral times during the node's life cycle, with
* different node operations passed in.
*
* Typically when loading a node for viewing, the order is:
* 'load', 'view', 'alter'
*
* When creating new content:
* Before displaying the creation form: 'prepare'
* When saving: 'validate', 'presave', 'insert'
*
* When editing an existing node:
* Before displaying the edit form: 'load', 'prepare'
* When saving: 'load', 'validate', 'presave', 'update'
*
* The same $op may be requested multiple times during the same HTTP request,
* especially 'load'.
*/
function revisioning_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
if (!empty($node->bypass_nodeapi)) {
return;
}
switch ($op) {
case 'load': // called at the end of node_load()
// The revision_moderation flag may be overridden on the node edit form
// by users with the "administer nodes" permission
$node->revision_moderation = node_tools_content_is_moderated($node->type);
$node->is_pending = _revisioning_node_is_pending($node);
// Could use following, but this seems old style, i.e. when $node is not a &ref
//$node_extras['revision_moderation'] = $node->revision_moderation;
//$node_extras['is_pending'] = $node->is_pending;
break;
case 'view': // called from called from node_view() before $node is fully built
break;
case 'alter': // called from node_view() after $node is fully built for display
if (!$teaser && $node->nid == arg(1) && // don't show msg on page with many nodes
!empty($node->revision_moderation) && user_access('view revision status messages')) {
drupal_set_message(_revisioning_node_info_msg($node));
}
break;
case 'prepare': // presenting edit form
// Check that we're dealing with the current node, not its translation source
$current_nid = arg(1);
if (is_numeric($current_nid) && $current_nid == $node->nid) {
_revisioning_prepare_msg($node);
}
break;
case 'presave': // for edits, called from node_save(), prior to _node_save_revision()
if (!empty($node->revision_moderation)) { // Tick-box on edit form
$node->is_pending = _revisioning_node_is_pending($node);
if ($node->revision && $node->is_pending && variable_get('new_revisions_'. $node->type, NEW_REVISION_WHEN_NOT_PENDING) == NEW_REVISION_WHEN_NOT_PENDING) {
drupal_set_message(t('Updating existing copy, not creating new revision as this one is still pending.'));
// Update the $node object just before it is saved to the db
$node->revision = FALSE;
}
// Save title of current revision to restore at $op=='update' time
$node->current_title = db_result(db_query('SELECT title FROM {node} WHERE nid=%d', $node->nid));
if (variable_get('revisioning_auto_publish_'. $node->type, FALSE)) {
if (user_access('publish revisions') ||
user_access("publish revisions of any $node->type content") ||
(user_access("publish revisions of own $node->type content") && $node->revision_uid == $user->uid)) {
if (!$node->status) {
// Fix for [#751092] (thanks naquah and mirgorod_a).
// If the Publish box has just been unticked, do not auto-publish.
if (isset($node->nid)) {
// Existing published node for which Publish box was unticked.
if (db_result(db_query("SELECT status FROM {node} WHERE nid = %d", $node->nid))) {
break;
}
}
else {
$node_options = variable_get('node_options_'. $node->type, array());
if (in_array('status', $node_options)) {
// New node for which Publish box is ticked by default but was
// unticked on the edit form.
break;
}
}
}
drupal_set_message(t('Auto-publishing this revision.'));
$node->status = TRUE;
// Make sure the 'update' does NOT reset vid, so that new revision becomes current
unset($node->current_revision_id);
}
}
}
break;
case 'insert': // new node, called from node_save(), after _node_save_revision()
if (!empty($node->revision_moderation)) {
_revisioning_insert_msg($node);
}
break;
case 'update': // edited node, called from node_save(), after _node_save_revision()
if (!empty($node->revision_moderation) && $node->current_revision_id // i.e. set and not 0
&& $node->current_revision_id != $node->vid) {
// Resetting title and vid back to their originial values, thus creating pending revision.
db_query("UPDATE {node} SET vid=%d, title='%s' WHERE nid=%d", $node->current_revision_id, $node->current_title, $node->nid);
//$node->is_pending = TRUE;
}
break;
case 'delete revision':
module_invoke_all('revisionapi', 'post delete', $node);
break;
}
}
/**
* Implementation of hook_block().
*
* A block that may be placed on all or selected pages, alerting the user
* (moderator) when new content has been submitted for review. Shows titles
* of pending revisions as a series of links (max. number configurable).
* Clicking a link takes the moderator straight to the revision in question.
*/
function revisioning_block($op = 'list', $delta = 0, $edit = array()) {
switch ($op) {
case 'list':
// Set up the defaults for the Site configuration>>Blocks page
// Return a list of (1) block(s) and the default values
$blocks[0]['info'] = t('Pending revisions');
$blocks[0]['cache'] = BLOCK_NO_CACHE;
$blocks[0]['weight'] = -10; // top of whatever region is chosen
$blocks[0]['custom'] = FALSE; // block is implemented by this module;
return $blocks;
case 'configure':
$form['revisioning_block_num_pending'] = array(
'#type' => 'textfield',
'#title' => t('Maximum number of pending revisions displayed'),
'#default_value' => variable_get('revisioning_block_num_pending', 5),
'#description' => t('Note: the title of this block mentions the total number of revisions pending, which may be greater than the number of revisions displayed.')
);
$form['revisioning_block_order'] = array(
'#type' => 'radios',
'#title' => t('Order in which pending revisions are displayed'),
'#options' => array(
REVISIONS_BLOCK_OLDEST_AT_TOP => t('Oldest at top'),
REVISIONS_BLOCK_NEWEST_AT_TOP => t('Newest at top')),
'#default_value' => variable_get('revisioning_block_order', REVISIONS_BLOCK_OLDEST_AT_TOP),
'#description' => t('Note: order is based on revision timestamps.')
);
$form['revisioning_content_summary_page'] = array(
'#type' => 'textfield',
'#title' => t('Page to go to when the block title is clicked'),
'#default_value' => variable_get('revisioning_content_summary_page', ''),
'#description' => t('When left blank this will default to %accessible_content, provided Module Grants Montior is enabled and the user has sufficient permissions. Otherwise %admin_content is used, subject to permissions. For any of this to work the above Block title field must be left blank.',
array('%accessible_content' => 'accessible-content', '%admin_content' => 'admin/content/node'))
);
return $form;
case 'save':
variable_set('revisioning_block_num_pending', (int)$edit['revisioning_block_num_pending']);
variable_set('revisioning_block_order', (int)$edit['revisioning_block_order']);
variable_set('revisioning_content_summary_page', $edit['revisioning_content_summary_page']);
break;
case 'view':
$max_nodes = variable_get('revisioning_block_num_pending', 100);
$order = variable_get('revisioning_block_order', REVISIONS_BLOCK_OLDEST_AT_TOP) == REVISIONS_BLOCK_OLDEST_AT_TOP ? 'ASC' : 'DESC';
$nodes = node_tools_get_nodes('update', NO_FILTER, NO_FILTER, NO_FILTER, TRUE, TRUE, $max_nodes, 'timestamp '. $order);
if (!empty($nodes)) {
return _theme_revisions_pending_block($nodes);
}
}
}
/**
* Implementation of hook_views_api().
*/
function revisioning_views_api() {
return array(
'api' => views_api_version(),
'path' => drupal_get_path('module', 'revisioning') .'/views'
);
}
/**
* Perform path manipulations for menu items containing %vid wildcard. $map
* contains what arg() function returns, eg. $map[0]=='node', $map[1]=='123'.
*
* When vid is absent, return $map as empty array. This seems to disable menu
* items which require a vid context to work. So on the page
* "node/123/revisions" we won't see tasks like "node/123/revisions/456/edit".
*
* An alternative implementation would be to substitute an empty vid with
* current revision id. In that case we should also change the tab titles
* (via title callbacks) for an enhanced user experience. For example: we'd
* change "Edit" to "Edit current".
*
* See http://drupal.org/node/500864
*/
function vid_to_arg($arg, &$map, $index) {
if (empty($arg)) {
//return node_tools_get_current_node_revision_id($nid = $map[1]);
$map = array();
return '';
}
return $arg;
}
/**
* Implementation of hook_user_node_access().
*
* @see module_grants_node_revision_access()
*
* @param $revision_op
* node or revision operation e.g. 'view revisions'
* @param $node
* @return the associated node operation required for this revision_op, or
* FALSE if access to the node is to be denied.
* Valid node operations to return are 'view', 'update', 'delete'.
*/
function revisioning_user_node_access($revision_op, $node) {
global $user;
$type = check_plain($node->type);
switch ($revision_op) {
case 'view current':
break;
case 'compare to current':
case 'view revisions':
case 'view revision list':
if (user_access('view revisions', $user)) { // node.module
break;
}
if (user_access('view revisions of any '. $type .' content', $user)) {
break;
}
if (($node->uid == $user->uid) && user_access('view revisions of own '. $type .' content', $user)) {
break;
}
return FALSE;
case 'edit current':
return 'update';
case 'edit revisions':
case 'revert revisions':
return user_access($revision_op, $user) ? 'update' : FALSE;
case 'publish revisions':
if (user_access('publish revisions of any '. $type .' content', $user)) {
break;
}
if (($node->uid == $user->uid) && user_access('publish revisions of own '. $type .' content', $user)) {
break;
}
case 'unpublish current revision':
return user_access($revision_op, $user) ? 'view' : FALSE;
case 'delete revisions':
case 'delete archived revisions':
if (!user_access('delete revisions', $user)) {
return FALSE;
}
case 'delete node':
return 'delete';
default:
drupal_set_message(t("Unknown Revisioning operation '%op'. Treating as 'view'.", array('%op' => $revision_op)), 'warning', FALSE);
}
return 'view';
}
/**
* Test whether the supplied revision operation is appropriate for the node.
* This is irrespective of user permissions, e.g. even for an administrator it
* doesn't make sense to publish a node that is already published or to
* "revert" to the current revision.
*
* @param $revision_op
* @param $node
* @return TRUE if the operation is appropriate for this node at this point
*/
function _revisioning_operation_appropriate($revision_op, $node) {
switch ($revision_op) {
case 'compare to current':
// Can't compare against itself
case 'delete revisions':
// If the revision is the current one, suppress the delete operation
// @TODO ...unless it's the only revision, in which case delete the
// entire node; however this requires a different URL.
return !$node->is_current;
case 'delete archived revisions':
break;
case 'view revision list': // i.e. node revisions summary
if ($node->num_revisions == 1 && !$node->revision_moderation
/* && (module_exists('module_grants') ? !module_grants_node_access('delete', $node) : !node_access('delete', $node))*/) {
// Suppress Revisions tab when when there's only 1 revision -- consistent with core.
// However, when content is moderated (i.e. "New revision in draft,
// pending moderation" is ticked) we want to be able to get to the
// 'Unpublish current' link on this page and the 'Publish this' tab on
// the next. Also when user has permission to delete node, we need to
// present the Delete link, unless we assume that this privilege
// assumes the 'edit' permission.
return FALSE;
}
break;
case 'publish revisions':
// If the node isn't meant to be moderated and the user is not an admin,
// or the revision is not either pending or current but not published,
// then disallow publication.
if ((!$node->revision_moderation && !user_access('administer nodes'))
|| !($node->is_pending || ($node->is_current && !$node->status))) {
return FALSE;
}
break;
case 'unpublish current revision':
// If the node isn't meant to be moderated and the user is not an admin,
// or it is unpublished already or we're not looking at the current
// revision, then unpublication is not an option.
if ((!$node->revision_moderation && !user_access('administer nodes'))
|| !$node->status || !$node->is_current) {
return FALSE;
}
break;
case 'revert revisions':
// If this revision is pending or current, suppress the reversion
if ($node->is_pending || $node->is_current) {
return FALSE;
}
break;
}
return TRUE;
}
/**
* Determine whether the supplied revision operation is permitted on the node.
* This requires getting through three levels of defence:
* o Is the operation appropriate for this node at this time, e.g. a node must
* not be published if it already is or if it isn't under moderation control
* o Does the user have permissions to operations of this kind in general?
* o Does the user have the node access rights (view/update/delete) required
* for this operation?
*
* @param $revision_op
* For instance 'publish revisions', 'delete revisions'
* @param $node
* @return bool
*/
function _revisioning_node_revision_access($revision_op, $node) {
if (!isset($node->num_revisions) || !isset($node->is_current)) {
drupal_set_message(t('Node object data incomplete -- have you enabled the Node Tools submodule?'), 'warning', FALSE);
}
if (!_revisioning_operation_appropriate($revision_op, $node)) {
return FALSE;
}
if (module_exists('module_grants')) {
$access = module_grants_node_revision_access($revision_op, $node);
}
else {
// Fall back to core to assess permissions (even though they suck)
$access = ($node_op = revisioning_user_node_access($revision_op, $node)) &&
node_access($node_op, $node);
}
return $access;
}
/**
* Access callback for 'node/%', 'node/%/view' and 'node/%/edit' links that
* may appear anywhere on the site.
* At the time that this function is called the CURRENT revision will already
* have been loaded by the system. However depending on the value of the
* 'revisioning_view_callback' and 'revisioning_edit_callback' variables (as
* set on the admin/settings/revisioning page), this may not be the desired
* revision.
* If these variables state that the LATEST revision should be loaded, we need
* to check at this point whether the user has permission to view this revision.
*
* The 'View current' and/or 'Edit current' tabs are suppressed when the current
* revision is already displayed via one of the Revisions subtabs.
* The 'View latest' and/or 'Edit latest' tabs are suppressed when the latest
* revision is already displayed via one of the Revisions subtabs.
*
* @param op, must be one of 'view' or 'edit'
* @param $node
* @return FALSE if access to the desired revision is denied
*
*/
function _revisioning_view_edit_access_callback($op, $node) {
$load_op = _revisioning_load_op($node, $op);
$vid = arg(3);
if (/*!empty($node->revision_moderation) && */is_numeric($vid)) {
// The View, Edit primary tabs are requested indirectly, in the context of
// the secondary tabs under Revisions, e.g. node/%/revisions/%
if ($load_op == REVISIONING_LOAD_CURRENT && $vid == $node->current_revision_id) {
// Suppress 'View current' and 'Edit current' primary tabs when viewing current
return FALSE;
}
if ($load_op == REVISIONING_LOAD_LATEST && $vid == revisioning_get_latest_revision_id($node->nid)) {
// Suppress 'View latest' and 'Edit latest' primary tabs when viewing latest
return FALSE;
}
}
if ($load_op == REVISIONING_LOAD_LATEST) {
// _revisioning_load_op has already checked permission to view latest
return TRUE;
}
$revision_op = ($op == 'view') ? 'view current' : 'edit current';
return _revisioning_node_revision_access($revision_op, $node);
}
function _revisioning_load_op($node, $op, $check_access = TRUE) {
if ($node->revision_moderation) {
$view_mode = (int)variable_get('revisioning_view_callback', REVISIONING_LOAD_CURRENT);
$edit_mode = (int)variable_get('revisioning_edit_callback', REVISIONING_LOAD_CURRENT);
$load_op = ($op == 'edit') ? $edit_mode : $view_mode;
if ($load_op == REVISIONING_LOAD_LATEST) {
// Site is configured to load latest revision, but we'll only do this if
// the latest isn't loaded already and the user has the permission to do so.
$latest_vid = revisioning_get_latest_revision_id($node->nid);
if ($latest_vid != $node->current_revision_id) {
if (!$check_access) {
return REVISIONING_LOAD_LATEST;
}
$original_vid = $node->vid;
$node->vid = $latest_vid;
$node->is_current = node_tools_revision_is_current($node);
$revision_op = ($op == 'view') ? 'view revisions' : 'edit revisions';
$access = _revisioning_node_revision_access($revision_op, $node);
// Restore $node (even though called by value), so that it remains consistent
$node->vid = $original_vid;
$node->is_current = node_tools_revision_is_current($node);
if ($access) {
return REVISIONING_LOAD_LATEST;
}
}
}
}
return REVISIONING_LOAD_CURRENT;
}
function _revisioning_prepare_msg($node) {
if (!$node->nid) { // new node
return;
}
$count = _revisioning_get_number_of_revisions_newer_than($node->vid, $node->nid);
if ($count == 1) {
drupal_set_message(t('Please note there is one revision more recent than the one you are about to edit.'), 'warning');
}
elseif ($count > 1) {
drupal_set_message(t('Please note there are !count revisions more recent than the one you are about to edit.', array('!count' => $count)), 'warning');
}
}
function _revisioning_insert_msg($node) {
if ($node->status) {
drupal_set_message(t('Initial revision created and published.'));
}
else {
drupal_set_message(t('Initial draft created, pending publication.'));
}
}
/**
* Display all revisions of the supplied node in a themed table with links for
* the permitted operations above it.
*/
function _revisioning_present_node($node, $op = 'any') {
return ($op == 'edit' && !$node->revision_moderation) ? node_page_edit($node) :_theme_revisions_summary($node);
}
/**
* Menu callback for the primary View tab.
*/
function _revisioning_view($node) {
if (_revisioning_load_op($node, 'view') == REVISIONING_LOAD_LATEST) {
$vid_to_load = revisioning_get_latest_revision_id($node->nid);
$node = node_load($node->nid, $vid_to_load);
}
// In node.module, node_page_view() is used to display the current, while
// node_show() is used for any other revision. The difference between the
// two is that node_page_view() surpresses the message that tells us we're
// viewing a revision. That's what we use here because we have our own
// configurable message.
return node_page_view($node);
}
/**
* Callback for the primary Edit tab.
*/
function _revisioning_edit($node) {
// Use the admin theme if the user specified this for node edit pages
if (variable_get('node_admin_theme', FALSE)) {
global $theme, $custom_theme;
$custom_theme = variable_get('admin_theme', $theme);
}
if (_revisioning_load_op($node, 'edit') == REVISIONING_LOAD_LATEST) {
$vid_to_load = revisioning_get_latest_revision_id($node->nid);
$node = node_load($node->nid, $vid_to_load);
}
// Following is the same as node_page_edit().
drupal_set_title(check_plain($node->title));
return drupal_get_form($node->type .'_node_form', $node);
}
/**
* Callback for the primary View and Edit tabs.
* @param $node
* @param $edit, bool
* @return string
*/
function _revisioning_title_for_tab($node, $edit = FALSE) {
if ($node->num_revisions <= 1 || !$node->revision_moderation) {
return $edit ? t('Edit') : t('View');
}
if (_revisioning_load_op($node, $edit ? 'edit' : 'view') == REVISIONING_LOAD_LATEST) {
return $edit ? t('Edit latest') : t('View latest');
}
return $edit ? t('Edit current') : t('View current');
}
/**
* Callback to view a particular revision.
*/
function _revisioning_view_revision($node) {
if (isset($node->nid)) {
$router_item = menu_get_item('node/' . $node->nid);
if (!empty($router_item['file'])) {
$path = $_SERVER['DOCUMENT_ROOT'] . base_path();
require_once $path . $router_item['file'];
}
// Call whatever function is assigned to the main node path but pass the
// current node as an argument. This approach allows for the reuse of Panel
// definition acting on node/%node. See [#1567880].
if (isset($router_item['page_callback'])) {
return $router_item['page_callback']($node);
}
}
return node_page_view($node);
}
/**
* Callback to edit a particular revision.
*/
function _revisioning_edit_revision($node) {
// Use the admin theme if the user specified this for node edit pages
if (variable_get('node_admin_theme', FALSE)) {
global $theme, $custom_theme;
$custom_theme = variable_get('admin_theme', $theme);
}
drupal_set_title(check_plain($node->title));
return drupal_get_form($node->type .'_node_form', $node);
}
/**
* Use diff's compare callback to compare specific revision to the current one
*/
if (module_exists('diff')) {
function _revisioning_compare_to_current_revision($node) {
module_load_include('inc', 'diff', 'diff.pages'); // for diff_diffs_show()
// Make sure that latest of the two revisions is on the right
if ($node->is_pending) {
return diff_diffs_show($node, $node->current_revision_id, $node->vid);
}
return diff_diffs_show($node, $node->vid, $node->current_revision_id);
}
}