'Pending revisions', 'page callback' => 'revision_moderation_pending_revisions_admin', 'access arguments' => array('administer nodes'), 'type' => MENU_LOCAL_TASK, ); // Admin menu $items['admin/config/content/revision_moderation'] = array( 'title' => 'Revision moderation', 'page callback' => 'drupal_get_form', 'page arguments' => array('revision_moderation_settings'), 'description' => 'Configure revision publishing options.', 'access arguments' => array('administer nodes'), ); // Callback to allow users to edit revisions. $items['node/%node/revisions/%/edit'] = array( 'title' => 'Edit revision', 'load arguments' => array(3), 'page callback' => 'revision_moderation_edit', 'page arguments' => array(1), 'access callback' => '_node_revision_access', 'access arguments' => array(1, 'update'), 'file' => 'node.pages.inc', 'file path' => drupal_get_path('module', 'node'), 'type' => MENU_CALLBACK, ); // Callback to allow users to publish revisions directly. $items['node/%node/revisions/%/publish'] = array( 'title' => 'Publish revision', 'load arguments' => array(3), 'page callback' => 'drupal_get_form', 'page arguments' => array('revision_moderation_publish_confirm', 1), 'access callback' => '_node_revision_access', 'access arguments' => array(1, 'update'), 'type' => MENU_CALLBACK, ); return $items; } /** * Menu permission callback. */ function revision_moderation_admin_permisson($nid) { $node = node_load($nid); $access = user_access('administer nodes') || (user_access('view revisions') && node_access('update', $node)); return $access; } /** * Menu callback; admin settings page. */ function revision_moderation_settings() { $form['revision_moderation_exempt'] = array( '#type' => 'checkbox', '#title' => t('Exempt administrators from revision moderation'), '#default_value' => variable_get('revision_moderation_exempt', 1), '#description' => t('With this option enabled, users with the "administer nodes" privilege will bypass the moderation system, and their revisions will be published immediately.'), ); return system_settings_form($form); } /** * Implement hook_form_alter(). */ function revision_moderation_form_alter(&$form, $form_state, $form_id) { // On node edit forms, add in the "New revisions in moderation" option. if (!empty($form['#node_edit_form'])) { $default_value = in_array('revision_moderation', variable_get("node_options_{$form['type']['#value']}", array('status', 'promote'))); if ($form['nid']['#value']) { $result = db_query_range('SELECT revision_moderation FROM {revision_moderation} WHERE nid = :nid', 0, 1, array(':nid' => $form['nid']['#value'], ))->fetchField(); if ($result !== FALSE) { $default_value = $result; } } // Only show the checkbox if user has 'administer nodes' privileges. if (!empty($node->revision) || user_access('administer nodes')) { $form['revision_information']['revision_moderation'] = array( '#type' => 'checkbox', '#title' => t('New revisions in moderation'), '#default_value' => $default_value, ); } else { $form['revision_moderation'] = array( '#type' => 'value', '#value' => $default_value, ); } } // Also add option to node settings form elseif ($form_id == 'node_type_form') { $form['workflow']['node_options']['#options']['revision_moderation'] = t('New revisions in moderation'); } } /** * Implement hook_node_insert(). */ function revision_moderation_node_insert($node) { // Store revision moderation setting of this node. drupal_write_record('revision_moderation', $node); } /** * Implement hook_node_update(). */ function revision_moderation_node_update($node) { // Update revision moderation setting of this node. drupal_write_record('revision_moderation', $node, 'nid'); if ($node->nid && $node->revision_moderation == 1 && arg(2) != 'revisions' && (!user_access('administer nodes') || !variable_get('revision_moderation_exempt', 1))) { if (isset($node->original_node)) { // Update node table's vid to the original value. db_update('node') -> fields(array('vid' => $node->original_node->vid, 'title' => $node->original_node->title['zxx'][0]['value'], 'status' => $node->original_node->status, 'moderate' => $node->original_node->moderate, )) ->condition('nid', $node->nid) ->execute(); drupal_set_message(t('Your changes have been submitted for moderation.')); } } } /** * Implement hook_node_delete(). */ function revision_moderation_node_delete($node) { // Delete record from revision_moderation table when node is deleted. db_delete('revision_moderation') ->condition('nid', $node->nid) ->execute(); } /** * Implement hook_node_load(). */ function revision_moderation_node_load($nodes, $types) { // Set a revision_moderation property which can be checked later. $results = db_query('SELECT revision_moderation, nid FROM {revision_moderation} WHERE nid IN(:nids)', array(':nids' => array_keys($nodes), )); foreach ($results as $record) { $nodes[$record->nid]->revision_moderation = $record->revision_moderation; } } /** * Implement hook_node_view(). */ function revision_moderation_node_view($node) { // Cannot use _node_revision_access() here, it's static cached with 1 op $access_update = user_access('revert revisions'); $access_delete = user_access('delete revisions'); // Display more descriptive message at the top of node revision views, including operations // that the current user has available to them. $current_vid = db_query_range('SELECT vid FROM {node} WHERE nid = :nid', 0, 1, array(':nid' => $node->nid, ))->fetchField(); if ($node->vid != $current_vid) { $links = array(); // Array of links to show along with the message. if ($access_update) { // Add a link directly to the diff if we have Diff module installed. if (module_exists('diff')) { if ($node->vid > $current_vid) { $difflink = "node/$node->nid/revisions/view/$current_vid/$node->vid"; } else { $difflink = "node/$node->nid/revisions/view/$node->vid/$current_vid"; } $links[] = l(t('Compare revisions'), $difflink); } $links[] = l(t('Edit revision'), "node/$node->nid/revisions/$node->vid/edit"); // If this revision is old, show an option to revert to it. // Otherwise, show an option to publish it. if ($node->vid < $current_vid) { $links[] = l(t('Revert to revision'), "node/$node->nid/revisions/$node->vid/revert"); } else { $links[] = l(t('Publish revision'), "node/$node->nid/revisions/$node->vid/publish"); } } if ($access_delete) { $links[] = l(t('Delete revision'), "node/$node->nid/revisions/$node->vid/delete"); } // Get username for the revision rather than the original node. $author = user_load($node->revision_uid); $revision_author = array('account' => $author, 'name' => check_plain($author->name), ); drupal_set_message(t('You are currently viewing a revision of this post created on @date by !author.', array('@date' => format_date($node->revision_timestamp, 'small'), '!author' => theme('username', $revision_author))) . theme('item_list', $links)); } elseif (isset($node->revision_moderation) && $node->revision_moderation == 1 && $node->build_mode != 'teaser') { // Notify admin if a node has pending revisions. if ($access_update && arg(2) != 'revisions' && revision_moderation_get_node_pending_revisions($node->nid)) { drupal_set_message(t('This post has one or more pending revisions: view list of revisions.', array('@list' => url("node/$node->nid/revisions")))); } } } /** * Implement hook_node_prepare(). */ function revision_moderation_node_prepare($node) { if (isset($node->nid)) { if (isset($node->revision_moderation) && $node->nid && $node->revision_moderation == 1 && arg(2) != 'revisions' && (!user_access('administer nodes') || !variable_get('revision_moderation_exempt', 1))) { // If user has a pending revision for this node, load the latest version of // it instead. if ($revisions = revision_moderation_get_node_pending_revisions($node->nid)) { global $user; foreach ($revisions as $revision) { if ($revision->uid == $user->uid) { drupal_set_message(t('Editing your latest revision, which is still pending moderation.')); $node = node_load($node->nid, $revision->vid); } } } } } } /** * Implement hook_node_presave(). */ function revision_moderation_node_presave($node) { if ($node->nid && $node->revision_moderation == 1 && arg(2) != 'revisions' && (!user_access('administer nodes') || !variable_get('revision_moderation_exempt', 1))) { $current_vid = db_query_range('SELECT vid FROM {node} WHERE nid = :nid', 0, 1, array(':nid' => $node->nid, ))->fetchField(); $node->original_node = node_load($node->nid, $current_vid); } } /** * Implement hook_block(). */ function revision_moderation_block($op = 'list', $delta = 0, $edit = array()) { if ($op == 'list') { $blocks[0]['info'] = t('Pending revisions'); return $blocks; } elseif ($op == 'view') { $block = array(); if (user_access('administer nodes')) { $output = ''; $list = array(); $nodes = revision_moderation_get_all_pending_revisions(10); if (count($nodes)) { foreach ($nodes as $node) { $list[] = l($node->title, "node/$node->nid/revisions/$node->vid/view"); } $output .= theme('item_list', $list); $output .= '

' . l(t('View all pending revisions'), 'admin/content/node/revisions') . '

'; } else { $output .= t('No pending revisions found.'); } $block['subject'] = t('Pending revisions'); $block['content'] = $output; } return $block; } } /** * Menu callback to display list of nodes with pending revisions. */ function revision_moderation_pending_revisions_admin() { return theme('revision_moderation_pending_revisions_admin'); } /** * Implement hook_theme(). */ function revision_moderation_theme() { return array( 'revision_moderation_pending_revisions_admin' => array( 'arguments' => array(), ), ); } /** * Displays list of nodes with pending revisions. */ function theme_revision_moderation_pending_revisions_admin() { $nodes = revision_moderation_get_all_pending_revisions(50); if (count($nodes)) { $header = array( t('Title', array(), array('context' => 'Node title', )), t('Type', array(), array('context' => 'Node types', )), t('Updated by'), t('Last updated'), ); $rows = array(); foreach ($nodes as $node) { $rows[] = array( l($node->title['zxx'][0]['value'], "node/$node->nid/revisions"), check_plain(node_get_type_name($node)), theme('username', user_load(array('uid' => $node->uid))), format_date($node->timestamp), ); } return theme('table', array('header' => $header, 'rows' => $rows)); } else { return '

' . t('No pending revisions found.') . '

'; } } /** * Retrieve list of all pending revisions. * * @param $limit * The number of pending revisions to retrieve. */ function revision_moderation_get_all_pending_revisions($limit) { // Obtain a list of nodes with revisions higher than current published revision. $sql = "SELECT n.nid, r.vid, n.type, r.title, r.body, r.uid, r.timestamp FROM {node} n INNER JOIN {node_revisions} r ON n.nid = r.nid WHERE r.vid > n.vid ORDER BY r.vid DESC LIMIT :limit"; $result = db_query($sql, array(':limit' => $limit, )); $revisions = array(); foreach ($result as $revision) { $revisions[$revision->nid] = $revision; } return $revisions; } /** * Retrieve list of all pending revisions for a given node. * * @param $nid * The node ID to retrieve. */ function revision_moderation_get_node_pending_revisions($nid) { // Obtain a list of revisions higher than current published revision for a given node. $sql = "SELECT n.nid, r.vid, r.uid FROM {node} n INNER JOIN {node_revisions} r ON n.nid = r.nid WHERE r.vid > n.vid AND n.nid = :nid ORDER BY r.vid DESC"; $result = db_query($sql, array(':nid' => $nid, )); $revisions = array(); while ($revision = db_fetch_object($result)) { $revisions[$revision->vid] = $revision; } return $revisions; } /** * Menu callback; edit revision. */ function revision_moderation_edit($node) { // Get username for the revision rather than the original node. $revision_author = user_load($node->revision_uid); drupal_set_message(t('You are currently editing a revision of this post created on @date by !author.', array('@date' => format_date($node->revision_timestamp, 'small'), '!author' => theme('username', $revision_author)))); return drupal_get_form($node->type . '_node_form', $node); } /** * Returns a confirmation page for publishing a revision. * * @param $node * The node object for which revision is to be published. */ function revision_moderation_publish_confirm($form_state, $node) { $form['node_id'] = array('#type' => 'value', '#value' => $node->nid); $form['title'] = array('#type' => 'value', '#value' => $node->title); $form['revision'] = array('#type' => 'value', '#value' => $node->vid); $form['type'] = array('#type' => 'value', '#value' => $node->type); return confirm_form($form, t('Are you sure you want to publish this revision for %title?', array('%title' => $node->title)), 'node/' . $node->nid . '/revisions/' . $node->vid, t('Publishing this revision will make it public for all users.'), t('Publish'), t('Cancel')); } /** * Submission handler for the publish confirm form. * Publishes a revision directly. */ function revision_moderation_publish_confirm_submit($form, &$form_state) { $nid = $form_state['values']['node_id']; $title = $form_state['values']['title']; $vid = $form_state['values']['revision']; $type = $form_state['values']['type']; db_update('node') ->fields(array('vid' => $vid, 'title' => $title, )) ->condition(array('nid' => $nid)) ->execute(); // Clear the cache so an anonymous poster can see the changes cache_clear_all(); drupal_set_message(t('The selected revision has been published.')); watchdog('content', '@type: published %title revision %revision', array('@type' => t($type), '%title' => $title, '%revision' => $vid), WATCHDOG_NOTICE, l(t('view'), "node/$nid/revisions/$vid/view")); $form_state['redirect'] = 'node/' . $nid; } function revision_moderation_schema_alter(&$schema) { $schema['node']['fields']['moderate'] = array( 'description' => 'A boolean indicating whether the node "in moderation" used by Revision Moderation.', 'type' => 'int', 'not null' => TRUE, 'default' => 0, ); }