t('Pending revisions'), 'page callback' => 'revision_moderation_pending_revisions_admin', 'access arguments' => array('administer nodes'), 'type' => MENU_LOCAL_TASK, ); // Admin menu $items['admin/settings/revision_moderation'] = array( 'title' => t('Revision moderation'), 'page callback' => 'drupal_get_form', 'page arguments' => array('revision_moderation_settings'), 'description' => t('Configure revision publishing options.'), 'access arguments' => array('administer nodes'), ); // Callback to allow users to edit revisions. $items['node/%node/revisions/%/edit'] = array( 'title' => t('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' => t('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_perm($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); } /** * Implementation of 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 (isset($form['#id']) && $form['#id'] == 'node-form') { $default_value = in_array('revision_moderation', variable_get("node_options_{$form['type']['#value']}", array('status', 'promote'))); if ($form['nid']['#value']) { $result = db_result(db_query('SELECT revision_moderation FROM {revision_moderation} WHERE nid = %d', $form['nid']['#value'])); 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'); } } /** * Implementation of hook_nodeapi(). */ function revision_moderation_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) { $args = arg(); switch ($op) { case 'insert': // Store revision moderation setting of this node. drupal_write_record('revision_moderation', $node); break; case 'update': // Update revision moderation setting of this node. drupal_write_record('revision_moderation', $node, 'nid'); break; case 'delete': // Delete record from revision_moderation table when node is deleted. db_query('DELETE FROM {revision_moderation} WHERE nid = %d', $node->nid); break; case 'load': // Set a revision_moderation property which can be checked later. $node->revision_moderation = db_result(db_query('SELECT revision_moderation FROM {revision_moderation} WHERE nid = %d', $node->nid)); break; case 'view': // 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_result(db_query('SELECT vid FROM {node} WHERE nid = %d', $node->nid)); 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. $revision_author = user_load($node->revision_uid); 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 ($node->revision_moderation == 1 && !$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")))); } } break; } // Only do this logic for non-admin users on nodes with revision moderation // turned on. // And not editing a chose revision if ($node->nid && $node->revision_moderation == 1 && arg(2) != 'revisions' && (!user_access('administer nodes') || !variable_get('revision_moderation_exempt', 1))) { switch ($op) { case 'prepare': // 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); break; } } } break; case 'presave': $current_vid = db_result(db_query('SELECT vid FROM {node} WHERE nid = %d', $node->nid)); $node->original_node = node_load($node->nid, $current_vid); break; case 'update': if (isset($node->original_node)) { // Update node table's vid to the original value. db_query("UPDATE {node} SET vid = %d, title = '%s', status = %d, moderate = %d WHERE nid = %d", $node->original_node->vid, $node->original_node->title, $node->original_node->status, $node->original_node->moderate, $node->nid); // If node doesn't exist in revision_moderation table, add it. $in_db = db_result(db_query("SELECT revision_moderation FROM {revision_moderation} WHERE nid = %d", $node->nid)); if ($in_db === FALSE) { db_query("INSERT INTO {revision_moderation} (nid, revision_moderation) VALUES(%d, 1)", $node->nid); } drupal_set_message(t('Your changes have been submitted for moderation.')); } break; } } else if ($node->nid && $node->revision_moderation == 1 && end($args) == 'edit') { switch ($op) { case 'prepare': $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)))); break; case 'presave': $current_vid = db_result(db_query('SELECT vid FROM {node} WHERE nid = %d', $node->nid)); $node->original_node = node_load($node->nid, $current_vid); break; case 'update': if (isset($node->original_node)) { // Update node table's vid to the original value. db_query("UPDATE {node} SET vid = %d, title = '%s', status = %d, moderate = %d WHERE nid = %d", $node->original_node->vid, $node->original_node->title, $node->original_node->status, $node->original_node->moderate, $node->nid); drupal_set_message(t('Your changes have been submitted for moderation.')); } break; } } } /** * Implementation of 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'); } /** * Implementation of 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'), t('Type'), t('Updated by'), t('Last updated'), ); $rows = array(); foreach ($nodes as $node) { $rows[] = array( l($node->title, "node/$node->nid/revisions"), check_plain(node_get_types('name', $node)), theme('username', user_load(array('uid' => $node->uid))), format_date($node->timestamp), ); } return theme('table', $header, $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 %d"; $result = db_query($sql, $limit); $revisions = array(); while ($revision = db_fetch_object($result)) { $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 = %d ORDER BY r.vid DESC"; $result = db_query($sql, $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_query("UPDATE {node} SET vid = %d, title = '%s' WHERE nid = %d", $vid, $title, $nid); // Clear the cache so an anonymous poster can see the changes cache_clear_all(); drupal_set_message('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; }