View source  
  <?php
require_once drupal_get_path('module', 'revisioning') . '/revisioning_triggers_actions.inc';
function revisioning_help($path, $arg) {
  switch ($path) {
    case 'node/%/revisions':
      return t('To edit, publish or delete one of the revisions below, click on its saved date.');
  }
}
function revisioning_menu() {
  $items = array();
  
  $items['content/pending'] = array(
    'title' => 'Pending',
    'page callback' => 'revisioning_pending_nodes',
    'access arguments' => array(
      'access content summary',
    ),
    'weight' => -20,
  );
  
  $items['node/%node/revisions/%/edit'] = array(
    
    'load arguments' => array(
      3,
    ),
    'page callback' => 'revisioning_edit',
    'page arguments' => array(
      1,
    ),
    'access callback' => 'module_grants_node_revision_access',
    'access arguments' => array(
      'edit revisions',
      1,
    ),
    'file' => 'node.pages.inc',
    'file path' => drupal_get_path('module', 'node'),
    'type' => MENU_CALL_BACK,
  );
  
  $items['node/%node/revisions/%/publish'] = array(
    
    'load arguments' => array(
      3,
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'revisioning_publish_confirm',
      1,
    ),
    'access callback' => 'module_grants_node_revision_access',
    'access arguments' => array(
      'publish revisions',
      1,
    ),
    'type' => MENU_CALLBACK,
  );
  
  $items['node/%node/unpublish'] = array(
    
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'revisioning_unpublish_confirm',
      1,
    ),
    'access callback' => 'module_grants_node_revision_access',
    'access arguments' => array(
      'unpublish current revision',
      1,
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}
function revisioning_menu_alter(&$items) {
  
  $items['content']['page callback'] = 'revisioning_pending_nodes';
  $items['content/editable']['type'] = MENU_LOCAL_TASK;
  $items['content/pending']['type'] = MENU_DEFAULT_LOCAL_TASK;
  
  $items['node/%node/view']['title'] = 'View current';
  
  $items['node/%node/edit']['page callback'] = 'revisioning_node_revisions';
  $items['node/%node/edit']['page arguments'] = array(
    1,
  );
  $items['node/%node/edit']['type'] = MENU_CALLBACK;
  if (!module_exists("diff")) {
    
    $items['node/%node/revisions']['page callback'] = 'revisioning_node_revisions';
    $items['node/%node/revisions']['page arguments'] = array(
      1,
    );
  }
  
  $items['node/%node/revisions/%/view']['page callback'] = 'node_page_view';
  
  $items['node/%node/revisions/%/view']['page arguments'] = array(
    1,
  );
  
  $items['node/%node/revisions/%/revert']['page callback'] = 'drupal_get_form';
  $items['node/%node/revisions/%/revert']['page arguments'] = array(
    'revisioning_revert_confirm',
    1,
  );
}
function revisioning_perm() {
  return array(
    'edit revisions',
    'publish revisions',
    'unpublish current revision',
  );
}
function revisioning_form_alter(&$form, $form_state, $form_id) {
  
  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 (!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,
      );
    }
  }
  elseif ($form_id == 'node_type_form') {
    $form['workflow']['node_options']['#options']['revision_moderation'] = t('New revisions in moderation');
  }
}
function revisioning_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
  $args = arg();
  
  if ($args[0] != 'node' || !is_numeric($args[1])) {
    return;
  }
  switch ($op) {
    case 'alter':
    case 'delete':
    case 'load':
    case 'validate':
      return;
    case 'insert':
      if ($node->status) {
        drupal_set_message(t('Initial revision created and published.'));
      }
      else {
        drupal_set_message(t('Initial revision created, pending publication.'));
      }
      return;
    case 'view':
      if (!$teaser) {
        
        _handle_view_op($node);
      }
      return;
  }
  if (end($args) == 'edit') {
    if ($op == 'prepare') {
      $count = _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');
      }
    }
    
    if ($node->revision_moderation) {
      switch ($op) {
        case 'presave':
          
          $current_revision = _get_current_revision($node->nid);
          
          $node->original_vid = $current_revision->vid;
          $node->original_revision = $node->revision;
          $pending = $node->vid > $current_revision->vid || $node->vid == $current_revision->vid && !$node->status;
          if ($node->revision && $pending) {
            
            $node->revision = FALSE;
          }
          break;
        case 'update':
          
          $node->revision = $node->original_revision;
          if (isset($node->original_vid)) {
            
            db_query("UPDATE {node} SET vid=%d WHERE nid=%d", $node->original_vid, $node->nid);
          }
          break;
      }
    }
    elseif ($op == 'update') {
      drupal_set_message(t('Your changes are now current as moderation is switched off for this content.'), 'warning');
    }
  }
}
function revisioning_edit($node) {
  drupal_set_title(check_plain($node->title));
  return drupal_get_form($node->type . '_node_form', $node);
}
function revisioning_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 of %title?', array(
    '%title' => $node->title,
  )), 'node/' . $node->nid . '/revisions', t('Publishing this revision will make it visible to the public.'), t('Publish'), t('Cancel'));
}
function revisioning_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'];
  _revisioning_publish_revision($nid, $vid, $title, $type);
  
  $form_state['redirect'] = "node/{$nid}/revisions";
}
function revisioning_unpublish_confirm($form_state, $node) {
  $form['node_id'] = array(
    '#type' => 'value',
    '#value' => $node->nid,
  );
  $form['title'] = array(
    '#type' => 'value',
    '#value' => $node->title,
  );
  $form['type'] = array(
    '#type' => 'value',
    '#value' => $node->type,
  );
  return confirm_form($form, t('Are you sure you want to unpublish %title?', array(
    '%title' => $node->title,
  )), "node/{$node->nid}/revisions", t('Unpublishing will remove this content from public view.'), t('Unpublish'), t('Cancel'));
}
function revisioning_unpublish_confirm_submit($form, &$form_state) {
  $nid = $form_state['values']['node_id'];
  $title = check_plain($form_state['values']['title']);
  $type = check_plain($form_state['values']['type']);
  db_query("UPDATE {node} SET status=0 WHERE nid=%d", $nid);
  cache_clear_all();
  drupal_set_message(t('%title has been unpublished.', array(
    '%title' => $title,
  )));
  watchdog('content', 'Unpublished @type %title', array(
    '@type' => $type,
    '%title' => $title,
  ), WATCHDOG_NOTICE, l(t('view'), "node/{$nid}"));
  
  $form_state['redirect'] = "node/{$nid}/revisions";
  
  module_invoke_all('revisioning', 'unpublish');
}
function _revisioning_publish_revision($nid, $vid, $title, $type) {
  
  db_query("UPDATE {node} SET vid=%d, title='%s', status=1 WHERE nid=%d", $vid, $title, $nid);
  cache_clear_all();
  drupal_set_message(t('Revision has been published.'));
  watchdog('content', 'Published rev #%revision of @type %title', array(
    '@type' => check_plain($type),
    '%title' => check_plain($title),
    '%revision' => $vid,
  ), WATCHDOG_NOTICE, l(t('view'), "node/{$nid}/revisions/{$vid}/view"));
  
  module_invoke_all('revisioning', 'publish');
}
function revisioning_publish_latest_revision($node) {
  
  $latest_pending = array_shift(_get_pending_revisions($node->nid));
  if (!$latest_pending) {
    $current_revision = _get_current_revision($node->nid);
    if ($node->vid == $current_revision->vid && !$node->status) {
      $latest_pending = $node;
    }
  }
  if ($latest_pending) {
    _revisioning_publish_revision($node->nid, $latest_pending->vid, $latest_pending->title, $latest_pending->type);
  }
  else {
    drupal_set_message(t('"!title" has no pending revision to be published.', array(
      '!title' => $node->title,
    )), 'warning');
  }
}
function revisioning_revert_confirm($form_state, $node) {
  if (_number_of_pending_revisions($node->nid) > 0) {
    drupal_set_message(t('There is a pending revision. Are you sure you want to revert to an older revision?'), 'warning');
  }
  return node_revision_revert_confirm($form_state, $node);
}
function revisioning_revert_confirm_submit($form, &$form_state) {
  $node = $form['#node_revision'];
  
  node_revision_revert_confirm_submit($form, $form_state);
  
  db_query("UPDATE {node} SET status=1 WHERE nid=%d", $node->nid);
  cache_clear_all();
  
  module_invoke_all('revisioning', 'revert');
}
function revisioning_pending_nodes() {
  return theme('revisioning_pending_nodes');
}
function revisioning_theme() {
  return array(
    'revisioning_pending_nodes' => array(
      'arguments' => array(),
    ),
  );
}
function theme_revisioning_pending_nodes() {
  $nodes = get_nodes('update', TRUE);
  return _theme_nodes($nodes);
}
function revisioning_node_revisions($node) {
  return _theme_node_revisions($node);
}
function _theme_node_revisions($node) {
  drupal_set_title(t('Revisions of %title', array(
    '%title' => $node->title,
  )));
  $show_taxonomy_terms = module_exists('taxonomy');
  $revisions = _get_all_revisions_for_node($node->nid, $show_taxonomy_terms);
  $message = format_plural(count($revisions), 'This content has only one revision', 'This content has @count revisions.');
  $links = array();
  if ($node->status && module_grants_node_revision_access('unpublish current revision', $node)) {
    $links[] = l(t('Unpublish current revision'), "node/{$node->nid}/unpublish");
  }
  if (module_grants_node_revision_access('delete revisions', $node)) {
    $links[] = l(t('Delete all revisions'), "node/{$node->nid}/delete");
  }
  drupal_set_message(empty($links) ? $message : $message . theme('item_list', $links));
  $header = array(
    t('Revision'),
  );
  if ($show_taxonomy_terms) {
    $header[] = t('Term');
  }
  $header[] = t('Status');
  $rows = array();
  foreach ($revisions as $revision) {
    $row = array();
    $base_url = "node/{$node->nid}/revisions/{$revision->vid}";
    $t = t(' Saved !date by !username', array(
      '!date' => l(format_date($revision->timestamp, 'small'), "{$base_url}/view"),
      '!username' => theme('username', $revision),
    )) . ($revision->log != '' ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : '');
    if ($revision->vid == $node->vid) {
      $row[] = array(
        'data' => $t,
        'class' => 'revision-current',
      );
      if ($show_taxonomy_terms) {
        $row[] = array(
          'data' => $revision->term,
          'class' => 'revision-current',
        );
      }
      $row[] = array(
        'data' => theme('placeholder', $revision->status ? t('current revision (published)') : t('current revision (unpublished)')),
        'class' => 'revision-current',
      );
    }
    else {
      $row[] = array(
        'data' => $t,
      );
      if ($show_taxonomy_terms) {
        $row[] = array(
          'data' => $revision->term,
        );
      }
      $row[] = array(
        'data' => $revision->vid > $node->vid ? t('pending moderation') : t('old'),
      );
    }
    $rows[] = $row;
  }
  return theme('table', $header, $rows);
}
function _number_of_revisions_newer_than($vid, $nid) {
  return db_result(db_query("SELECT COUNT(*) FROM {node} n INNER JOIN {node_revisions} r ON n.nid=r.nid WHERE (r.vid>%d AND n.nid=%d)", $vid, $nid));
}
function _number_of_pending_revisions($nid) {
  return db_result(db_query("SELECT COUNT(*) FROM {node} n INNER JOIN {node_revisions} r ON n.nid=r.nid WHERE (r.vid>n.vid AND n.nid=%d)", $nid));
}
function _get_pending_revisions($nid) {
  $sql = "SELECT r.vid, r.title, n.type 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;
}
function _get_all_revisions_for_node($nid, $include_taxonomy_terms = FALSE) {
  $sql_select = 'SELECT n.status, r.vid, r.title, r.log, r.uid, r.timestamp, u.name';
  $sql_from = ' FROM {node_revisions} r LEFT JOIN {node} n ON n.vid=r.vid INNER JOIN {users} u ON u.uid=r.uid';
  $sql_where = ' WHERE r.nid=%d ORDER BY r.vid DESC';
  if ($include_taxonomy_terms) {
    $sql_select .= ', td.name AS term';
    $sql_from .= ' LEFT JOIN {term_node} tn ON r.vid=tn.vid LEFT JOIN {term_data} td ON tn.tid=td.tid';
  }
  $sql = $sql_select . $sql_from . $sql_where;
  $result = db_query($sql, $nid);
  $revisions = array();
  while ($revision = db_fetch_object($result)) {
    if (empty($revisions[$revision->vid])) {
      $revisions[$revision->vid] = $revision;
    }
    elseif ($include_taxonomy_terms) {
      
      $existing_revision = $revisions[$revision->vid];
      $existing_revision->term .= '/' . $revision->term;
    }
  }
  return $revisions;
}
function _handle_view_op($node) {
  $args = arg();
  $access_view = module_grants_node_revision_access('view revisions', $node);
  $access_edit = module_grants_node_revision_access('edit revisions', $node);
  $access_delete = module_grants_node_revision_access('delete revisions', $node);
  
  $access_publish = $access_view && user_access('publish revisions');
  $access_revert = $access_view && user_access('revert revisions');
  $access_unpublish = $access_view && user_access('unpublish current revision');
  $current_revision = _get_current_revision($node->nid);
  $is_current = $node->vid == $current_revision->vid;
  
  $is_pending = $node->vid > $current_revision->vid;
  $links = array();
  
  if ($access_view) {
    
    $revision_author = user_load($node->revision_uid);
    $published = $node->status ? t('current, published') : t('current, unpublished');
    $placeholder_data = array(
      '%current' => $published,
      '%title' => check_plain($node->title),
      '!author' => theme('username', $revision_author),
      '@date' => format_date($node->revision_timestamp, 'small'),
    );
    
    if ($is_current) {
      $message = t('Displaying %current revision of %title, last modified by !author on @date', $placeholder_data);
    }
    else {
      $message = $is_pending ? t('Displaying pending revision of %title, last modified by !author on @date', $placeholder_data) : t('Displaying old revision of %title, last modified by !author on @date', $placeholder_data);
    }
    
    if (!$is_current && module_exists('diff')) {
      $comparison_url = "node/{$node->nid}/revisions/view/";
      
      if ($is_pending) {
        $comparison_url .= "{$current_revision->vid}/{$node->vid}";
      }
      else {
        $comparison_url .= "{$node->vid}/{$current_revision->vid}";
      }
      $links[] = l(t('Compare to current'), $comparison_url);
    }
  }
  $base_url = "node/{$node->nid}/revisions/{$node->vid}";
  if ($access_edit) {
    $links[] = l(t('Edit this revision'), "{$base_url}/edit");
  }
  
  if ($access_publish && ($is_pending || $is_current && !$node->status)) {
    $links[] = l(t('Publish this revision'), "{$base_url}/publish");
  }
  elseif ($access_revert && !$is_pending && !$is_current) {
    $links[] = l(t('Revert to this revision'), "{$base_url}/revert");
  }
  if ($access_unpublish && $is_current && $node->status) {
    $links[] = l(t('Unpublish this revision'), "node/{$node->nid}/unpublish");
  }
  if ($access_delete && !$is_current) {
    
    $links[] = l(t('Delete this revision'), "{$base_url}/delete");
  }
  if ($access_view && $args[2] == 'revisions') {
    
    $links[] = l(t('Show all revisions'), "node/{$node->nid}/revisions");
  }
  if ($message) {
    
    drupal_set_message($message . theme('item_list', $links));
  }
}
function _get_current_revision($nid) {
  return db_fetch_object(db_query('SELECT r.vid, r.timestamp FROM {node} n INNER JOIN {node_revisions} r ON n.vid=r.vid WHERE n.nid=%d', $nid));
}