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' => 'revision_moderation_publish', 'page arguments' => array(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.'), ); $form['revision_moderation_cron'] = array( '#type' => 'fieldset', '#title' => t('Cron job'), ); $form['revision_moderation_cron']['revision_moderation_cron_enable'] = array( '#type' => 'checkbox', '#title' => t('Enable this cron job'), '#default_value' => variable_get('revision_moderation_cron_enable', 0), '#description' => t('to delete revisions. This operation can be very slow.'), ); $form['revision_moderation_cron']['revision_moderation_cron_older_than'] = array( '#type' => 'textfield', '#title' => t('Delete revisions older than'), '#default_value' => variable_get('revision_moderation_cron_older_than', 0), '#size' => 6, '#maxlength' => 6, '#description' => t('in days.'), ); $form['revision_moderation_cron']['revision_moderation_cron_more_than'] = array( '#type' => 'textfield', '#title' => t('Delete revisions of nodes having more than this amount of revisions'), '#default_value' => variable_get('revision_moderation_cron_more_than', 0), '#size' => 6, '#maxlength' => 6, '#description' => t(''), ); $form['revision_moderation_cron']['revision_moderation_cron_amount'] = array( '#type' => 'textfield', '#title' => t('Total amount'), '#default_value' => variable_get('revision_moderation_cron_amount', 0), '#size' => 6, '#maxlength' => 6, '#description' => t('Amount of total revisions to be deleted.'), ); return system_settings_form($form); } function revision_moderation_settings_validate($form, &$form_state) { if (!empty($form_state['values']['revision_moderation_cron_enable'])) { if (!is_numeric($form_state['values']['revision_moderation_cron_older_than']) || $form_state['values']['revision_moderation_cron_older_than'] < 0) { form_set_error('revision_moderation_cron_older_than', t('Please input a number (bigger or equal than 0).')); } if (empty($form_state['values']['revision_moderation_cron_more_than'])) { form_set_error('revision_moderation_cron_more_than', t('Please input an amount.')); } elseif (!is_numeric($form_state['values']['revision_moderation_cron_more_than']) || $form_state['values']['revision_moderation_cron_more_than'] < 1) { form_set_error('revision_moderation_cron_more_than', t('Please input a number (bigger than 0).')); } if (empty($form_state['values']['revision_moderation_cron_amount'])) { form_set_error('revision_moderation_cron_amount', t('Please input an amount.')); } elseif (!is_numeric($form_state['values']['revision_moderation_cron_amount']) || $form_state['values']['revision_moderation_cron_amount'] < 1) { form_set_error('revision_moderation_cron_amount', t('Please input a number (bigger than 0).')); } } $form_state['values']['revision_moderation_cron_older_than'] = (int)$form_state['values']['revision_moderation_cron_older_than']; $form_state['values']['revision_moderation_cron_more_than'] = (int)$form_state['values']['revision_moderation_cron_more_than']; $form_state['values']['revision_moderation_cron_amount'] = (int)$form_state['values']['revision_moderation_cron_amount']; } /** * 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) { 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, status FROM {node} WHERE nid = %d', $node->nid)); if ($node->vid != $current_vid || $node->status == 0) { drupal_set_message(t('You are currently viewing a revision of this post created on @date by @author.', array('@date' => format_date($node->changed, 'small'), '@author' => $node->name))); if ($access_update) { drupal_set_message(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) { drupal_set_message(l(t('Revert to revision'), "node/$node->nid/revisions/$node->vid/revert")); } else { if (user_access('administer nodes') || user_access('publish revision')) { drupal_set_message(l(t('Publish revision'), "node/$node->nid/revisions/$node->vid/publish")); } } } if ($access_delete) { drupal_set_message(l(t('Delete revision'), "node/$node->nid/revisions/$node->vid/delete")); } } elseif ($node->revision_moderation == 1 && empty($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); 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; displays list of nodes with pending revisions. */ function 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/$node->vid/view"), $node->type, 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 or n.status = 0 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) { drupal_set_message(t('You are currently editing a revision of this post created on @date by @author.', array('@date' => format_date($node->changed, 'small'), '@author' => $node->name))); return drupal_get_form($node->type .'_node_form', $node); } /** * Menu callback; publish revision directly. */ function revision_moderation_publish($node) { db_query("UPDATE {node} SET status = 1, vid = %d, title = '%s' WHERE nid = %d", $node->vid, $node->title, $node->nid); drupal_set_message('The selected revision has been published.'); watchdog('content', '@type: published %title revision %revision', array('@type' => t($node->type), '%title' => $node->title, '%revision' => $node->vid), WATCHDOG_NOTICE, l(t('view'), "node/$node->nid/revisions/$node->vid/view")); drupal_goto("node/". $node->nid); } function revision_moderation_cron() { $enable = variable_get('revision_moderation_cron_enable', 0); $amount = variable_get('revision_moderation_cron_amount', 0); $older_than = variable_get('revision_moderation_cron_older_than', 0); $more_than = variable_get('revision_moderation_cron_more_than', 0); if (empty($enable) || empty($older_than) || empty($more_than) || empty($amount)) { return; } $timestamp = time() - (int)($older_than * 86400); $count_result = db_query("SELECT nid, COUNT(*) as count, MIN(timestamp) as timestamp FROM {node_revisions} GROUP BY nid HAVING timestamp < $timestamp AND count > $more_than"); while ($count_object = db_fetch_object($count_result)) { $limit = min(array($amount, $count_object->count - $more_than)); if ($limit < 1) { break; } $result = db_query("SELECT n.nid, r.vid FROM {node} n INNER JOIN {node_revisions} r ON n.nid = r.nid WHERE n.nid = %d AND r.vid != n.vid AND r.timestamp < $timestamp ORDER BY r.timestamp LIMIT %d", $count_object->nid, $limit); while ($object = db_fetch_object($result)) { $node = node_load($object->nid, $object->vid); db_query("DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $node->nid, $node->vid); node_invoke_nodeapi($node, 'delete revision'); watchdog('cron', '@type: deleted %title revision %revision.', array('@type' => $node->type, '%title' => $node->title, '%revision' => $node->vid)); $amount -= 1; } } }