Index: modules/aggregator/aggregator.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.test,v
retrieving revision 1.28
diff -u -r1.28 aggregator.test
--- modules/aggregator/aggregator.test	30 Jul 2009 19:24:20 -0000	1.28
+++ modules/aggregator/aggregator.test	15 Aug 2009 12:49:06 -0000
@@ -253,7 +253,7 @@
       $edit = array();
       $edit['title'] = $this->randomName();
       $edit['body[0][value]'] = $this->randomName();
-      $this->drupalPost('node/add/article', $edit, t('Save'));
+      $this->drupalPost('node/add/article', $edit, t('Publish'));
     }
   }
 }
Index: modules/blog/blog.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/blog/blog.test,v
retrieving revision 1.17
diff -u -r1.17 blog.test
--- modules/blog/blog.test	3 Aug 2009 03:04:33 -0000	1.17
+++ modules/blog/blog.test	15 Aug 2009 12:49:06 -0000
@@ -153,7 +153,7 @@
       $edit = array();
       $edit['title'] = 'node/' . $node->nid;
       $edit['body[0][value]'] = $this->randomName(256);
-      $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+      $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Publish'));
       $this->assertRaw(t('Blog entry %title has been updated.', array('%title' => $edit['title'])), t('Blog node was edited'));
 
       // Delete blog node.
Index: modules/book/book.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/book/book.test,v
retrieving revision 1.13
diff -u -r1.13 book.test
--- modules/book/book.test	20 Jul 2009 18:51:32 -0000	1.13
+++ modules/book/book.test	15 Aug 2009 12:49:06 -0000
@@ -65,7 +65,7 @@
     $other_book = $this->createBookNode('new');
     $node = $this->createBookNode($book->nid);
     $edit = array('book[bid]' => $other_book->nid);
-    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Publish'));
 
     $this->drupalLogout();
     $this->drupalLogin($web_user);
@@ -180,10 +180,10 @@
       $this->drupalPost('node/add/book', $edit, t('Change book (update list of parents)'));
 
       $edit['book[plid]'] = $parent;
-      $this->drupalPost(NULL, $edit, t('Save'));
+      $this->drupalPost(NULL, $edit, t('Publish'));
     }
     else {
-      $this->drupalPost('node/add/book', $edit, t('Save'));
+      $this->drupalPost('node/add/book', $edit, t('Publish'));
     }
 
     // Check to make sure the book node was created.
Index: modules/dblog/dblog.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/dblog/dblog.test,v
retrieving revision 1.26
diff -u -r1.26 dblog.test
--- modules/dblog/dblog.test	14 Aug 2009 14:00:14 -0000	1.26
+++ modules/dblog/dblog.test	15 Aug 2009 12:49:06 -0000
@@ -262,14 +262,14 @@
     // Create node using form to generate add content event (which is not triggered by drupalCreateNode).
     $edit = $this->getContent($type);
     $title = $edit['title'];
-    $this->drupalPost('node/add/' . $type, $edit, t('Save'));
+    $this->drupalPost('node/add/' . $type, $edit, t('Publish'));
     $this->assertResponse(200);
     // Retrieve node object.
     $node = $this->drupalGetNodeByTitle($title);
     $this->assertTrue($node != NULL, t('Node @title was loaded', array('@title' => $title)));
     // Edit node.
     $edit = $this->getContentUpdate($type);
-    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Publish'));
     $this->assertResponse(200);
     // Delete node.
     $this->drupalPost('node/' . $node->nid . '/delete', array(), t('Delete'));
Index: modules/filter/filter.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.test,v
retrieving revision 1.30
diff -u -r1.30 filter.test
--- modules/filter/filter.test	15 Aug 2009 06:45:31 -0000	1.30
+++ modules/filter/filter.test	15 Aug 2009 12:49:07 -0000
@@ -112,7 +112,7 @@
     $edit['title'] = $this->randomName();
     $edit['body[0][value]'] = $body . '<random>' . $extra_text . '</random>';
     $edit['body[0][value_format]'] = $filtered;
-    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $this->drupalPost('node/add/page', $edit, t('Publish'));
     $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Filtered node created.'));
 
     $node = $this->drupalGetNodeByTitle($edit['title']);
Index: modules/forum/forum.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/forum/forum.test,v
retrieving revision 1.27
diff -u -r1.27 forum.test
--- modules/forum/forum.test	3 Aug 2009 03:04:33 -0000	1.27
+++ modules/forum/forum.test	15 Aug 2009 12:49:07 -0000
@@ -243,8 +243,7 @@
     // Instead, the post variables seem to pick up the first non-blank value in the select list.
 
     // Create forum topic.
-//    $this->drupalPost('node/add/forum/' . $forum['tid'], $edit, t('Save'));
-    $this->drupalPost('node/add/forum/', $edit, t('Save'));
+    $this->drupalPost('node/add/forum/', $edit, t('Publish'));
     $type = t('Forum topic');
     if ($container) {
       $this->assertNoRaw(t('@type %title has been created.', array('@type' => $type, '%title' => $title)), t('Forum topic was not created'));
@@ -324,7 +323,7 @@
       $edit['body[0][value]'] = $this->randomName(256);
       $edit['taxonomy[1]'] = $this->root_forum['tid']; // Assumes the topic is initially associated with $forum.
       $edit['shadow'] = TRUE;
-      $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+      $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Publish'));
       $this->assertRaw(t('Forum topic %title has been updated.', array('%title' => $edit['title'])), t('Forum node was edited'));
 
       // Verify topic was moved to a different forum.
Index: modules/locale/locale.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v
retrieving revision 1.35
diff -u -r1.35 locale.test
--- modules/locale/locale.test	11 Aug 2009 11:52:46 -0000	1.35
+++ modules/locale/locale.test	15 Aug 2009 12:49:07 -0000
@@ -1401,7 +1401,7 @@
     $edit = array(
       'language' => 'en',
     );
-    $this->drupalPost($path, $edit, t('Save'));
+    $this->drupalPost($path, $edit, t('Publish'));
     $this->assertRaw(t('Page %title has been updated.', array('%title' => $node_title)), t('Page updated.'));
 
     $this->drupalLogout();
Index: modules/node/node.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.admin.inc,v
retrieving revision 1.60
diff -u -r1.60 node.admin.inc
--- modules/node/node.admin.inc	30 Jul 2009 19:24:21 -0000	1.60
+++ modules/node/node.admin.inc	15 Aug 2009 12:49:07 -0000
@@ -75,6 +75,7 @@
     'options' => array(
       'status-1' => t('published'),
       'status-0' => t('not published'),
+      'draft-1' => t('draft'),
       'promote-1' => t('promoted'),
       'promote-0' => t('not promoted'),
       'sticky-1' => t('sticky'),
@@ -124,6 +125,13 @@
       case 'status':
         // Note: no exploitable hole as $key/$value have already been checked when submitted
         list($key, $value) = explode('-', $value, 2);
+        if ($key == 'draft') {
+          $query->innerJoin('node_revision', 'r', "n.nid = r.nid AND r.draft = 1");
+        }
+        else {
+          $query->condition('n.' . $key, $value);
+        }
+        break;
       case 'type':
       case 'language':
         $query->condition('n.' . $key, $value);
Index: modules/node/node.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.install,v
retrieving revision 1.25
diff -u -r1.25 node.install
--- modules/node/node.install	27 Jul 2009 19:26:31 -0000	1.25
+++ modules/node/node.install	15 Aug 2009 12:49:07 -0000
@@ -213,6 +213,12 @@
         'not null' => TRUE,
         'default' => '',
       ),
+      'draft' => array(
+        'description' => 'A boolean indicating if the revision is a draft.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
       'log' => array(
         'description' => 'The log entry explaining the changes in this version.',
         'type' => 'text',
@@ -229,6 +235,7 @@
     'indexes' => array(
       'nid' => array('nid'),
       'uid' => array('uid'),
+      'draft' => array('draft'),
     ),
     'primary key' => array('vid'),
     'foreign keys' => array(
@@ -525,7 +532,22 @@
   return $ret;
 }
 
+/**
+ * Add new column draft to the node_revision table.
+ */
+function node_update_7007() {
+  $ret = array();
+  $specification = array(
+    'description' => 'A boolean indicating if the revision is a draft.',
+    'type' => 'int',
+    'not null' => TRUE,
+    'default' => 0,
+  );
 
+  db_add_field($ret, 'node_revision', 'draft', $specification);
+  db_add_index($ret, 'node_revision', 'draft', array('draft'));
+  return $ret;
+}
 
 /**
  * @} End of "defgroup updates-6.x-to-7.x"
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1099
diff -u -r1.1099 node.module
--- modules/node/node.module	14 Aug 2009 13:53:01 -0000	1.1099
+++ modules/node/node.module	15 Aug 2009 12:53:47 -0000
@@ -952,10 +952,14 @@
   if (empty($node->created)) {
     $node->created = REQUEST_TIME;
   }
-  // The changed timestamp is always updated for bookkeeping purposes (revisions, searching, ...)
-  $node->changed = REQUEST_TIME;
-
-  $node->timestamp = REQUEST_TIME;
+  // The changed timestamp is always updated for bookkeeping purposes.
+  if (empty($node->timestamp_changed)) {
+    $node->changed = REQUEST_TIME;
+  }
+  // Set the revision timestamp.
+  if (empty($node->timestamp_revision)) {
+    $node->timestamp = REQUEST_TIME;
+  }
   $update_node = TRUE;
 
   // Generate the node table query and the node_revisions table query.
@@ -1075,6 +1079,40 @@
 }
 
 /**
+ * Delete a node revision.
+ *
+ * This function should only be used to delete inactive revisions. Using it to
+ * delete the current revision will cause data inconsistencies in the node
+ * table.
+ *
+ * @param $nid
+ *   The node ID.
+ * @param $vid
+ *   The revision ID.
+ */
+function node_delete_revision($nid, $vid) {
+  $node_revision = node_load($nid, $vid);
+  db_delete('node_revision')
+    ->condition('nid', $node_revision->nid)
+    ->condition('vid', $node_revision->vid)
+    ->execute();
+  module_invoke_all('node_delete_revision', $node_revision);
+  field_attach_delete_revision('node', $node_revision);
+}
+
+/**
+ * Determine if a node has any pending drafts.
+ *
+ * @param $nid
+ *  The node ID.
+ * @return
+ *  TRUE if the node has pending drafts, FALSE if not.
+ */
+function node_has_pending_draft($nid) {
+  return (bool) db_query_range('SELECT vid FROM {node_revision} WHERE draft = 1 AND nid = :nid', array(':nid' => $nid), 0, 1)->fetchField();
+}
+
+/**
  * Generate an array for rendering the given node.
  *
  * @param $node
@@ -1180,10 +1218,15 @@
   if ($message) {
     drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp))), PASS_THROUGH);
   }
-
   // Update the history table, stating that this user viewed this node.
   node_tag_new($node->nid);
 
+  // Display a message if the node is published, the user has access to view
+  // revisions and edit the node, and there are pending drafts.
+  if ($node->status && user_access('view revisions') && node_access('update', $node) && node_has_pending_draft($node->nid)) {
+    drupal_set_message(t('There are pending drafts for this content. <a href="@revisions">Review</a> available revisions.', array('@revisions' => url('node/' . $node->nid . '/revisions'))));
+  }
+
   // For markup consistency with other pages, use node_build_multiple() rather than node_build().
   return node_build_multiple(array($node), 'full');
 }
@@ -1289,10 +1332,6 @@
       'title' => t('View revisions'),
       'description' => t('View content revisions.'),
     ),
-    'revert revisions' => array(
-      'title' => t('Revert revisions'),
-      'description' => t('Replace content with an older revision.'),
-    ),
     'delete revisions' => array(
       'title' => t('Delete revisions'),
       'description' => t('Delete content revisions.'),
@@ -1611,7 +1650,7 @@
       $access[$node->vid] = TRUE;
     }
     else {
-      $map = array('view' => 'view revisions', 'update' => 'revert revisions', 'delete' => 'delete revisions');
+      $map = array('view' => 'view revisions', 'update' => "publish any $node->type content", 'delete' => 'delete revisions');
       // First check the user permission, second check the access to the
       // current revision and finally, if the node passed in is not the current
       // revision then access to that, too.
@@ -1753,6 +1792,14 @@
     'weight' => 1,
     'type' => MENU_LOCAL_TASK,
   );
+  $items['node/%/edit/revision/%'] = array(
+    'title' => 'Edit',
+    'page callback' => 'node_revision_page_edit',
+    'page arguments' => array(1, 4),
+    'access callback' => 'node_access',
+    'access arguments' => array('update', 1),
+    'type' => MENU_CALLBACK,
+  );
   $items['node/%node/delete'] = array(
     'title' => 'Delete',
     'page callback' => 'drupal_get_form',
@@ -2935,6 +2982,7 @@
  */
 function node_publish_action($node, $context = array()) {
   $node->status = 1;
+  $node->draft = 0;
   watchdog('action', 'Set @type %title to published.', array('@type' => node_type_get_name($node), '%title' => $node->title));
 }
 
@@ -2944,6 +2992,7 @@
  */
 function node_unpublish_action($node, $context = array()) {
   $node->status = 0;
+  $node->draft = 0;
   watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_type_get_name($node), '%title' => $node->title));
 }
 
@@ -3120,6 +3169,10 @@
       'title' => t('Delete any %type_name content', array('%type_name' => $info->name)),
       'description' => t('Delete any %type_name content, regardless of its author.', array('%type_name' => $info->name)),
     ),
+    "publish any $type content" => array(
+      'title' => t('Change publishing status for %type_name content', array('%type_name' => $info->name)),
+      'description' => t('Change publishing status for %type_name content, regardless of its author.', array('%type_name' => $info->name)),
+    ),
   );
 
   return $perms;
Index: modules/node/node.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.pages.inc,v
retrieving revision 1.73
diff -u -r1.73 node.pages.inc
--- modules/node/node.pages.inc	4 Aug 2009 06:44:48 -0000	1.73
+++ modules/node/node.pages.inc	15 Aug 2009 12:49:07 -0000
@@ -13,6 +13,22 @@
 function node_page_edit($node) {
   $type_name = node_type_get_name($node);
   drupal_set_title(t('<em>Edit @type</em> @title', array('@type' => $type_name, '@title' => $node->title)), PASS_THROUGH);
+  // If the node is published and has pending drafts, inform the user, since the
+  // drafts will be more up to date than the current published version.
+  if ($node->status && user_access('view revisions') && node_has_pending_draft($node->nid)) {
+    drupal_set_message(t('There are pending drafts for this content. <a href="@revisions">Review</a> available revisions.', array('@revisions' => url('node/' . $node->nid . '/revisions'))));
+  }
+
+  return drupal_get_form($node->type . '_node_form', $node);
+}
+
+/**
+ * Menu callback to present the node editing form for a revision.
+ */
+function node_revision_page_edit($nid, $vid) {
+  $node = node_load($nid, $vid);
+  $type_name = node_type_get_name($node);
+  drupal_set_title(t('<em>Edit @type</em> @title', array('@type' => $type_name, '@title' => $node->title)), PASS_THROUGH);
   return drupal_get_form($node->type . '_node_form', $node);
 }
 
@@ -135,7 +151,7 @@
 
   // Basic node information.
   // These elements are just values so they are not even sent to the client.
-  foreach (array('nid', 'vid', 'uid', 'created', 'type', 'language') as $key) {
+  foreach (array('nid', 'vid', 'uid', 'created', 'type', 'language', 'draft') as $key) {
     $form[$key] = array(
       '#type' => 'value',
       '#value' => isset($node->$key) ? $node->$key : NULL,
@@ -232,9 +248,8 @@
     '#weight' => 95,
   );
   $form['options']['status'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Published'),
-    '#default_value' => $node->status,
+    '#type' => 'value',
+    '#value' => $node->status,
   );
   $form['options']['promote'] = array(
     '#type' => 'checkbox',
@@ -258,12 +273,18 @@
   // Add the buttons.
   $form['buttons'] = array();
   $form['buttons']['#weight'] = 100;
-  $form['buttons']['submit'] = array(
+  $form['buttons']['publish'] = array(
     '#type' => 'submit',
-    '#access' => variable_get('node_preview_' . $node->type, 1) != DRUPAL_REQUIRED || (!form_get_errors() && isset($form_state['node_preview'])),
-    '#value' => t('Save'),
+    '#access' => variable_get('node_preview_' . $node->type, 1) != DRUPAL_REQUIRED || (!form_get_errors() && isset($form_state['node_preview'])) && (user_access('publish any $node->type content') || user_access('administer nodes') || (!empty($node->nid) && $node->status)),
+    '#value' => t('Publish'),
     '#weight' => 5,
-    '#submit' => array('node_form_submit'),
+    '#submit' => array('node_form_publish_submit'),
+  );
+  $form['buttons']['draft'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save as draft'),
+    '#weight' => 6,
+    '#submit' => array('node_form_draft_submit'),
   );
   $form['buttons']['preview'] = array(
     '#access' => variable_get('node_preview_' . $node->type, 1) != DRUPAL_DISABLED,
@@ -272,14 +293,20 @@
     '#weight' => 10,
     '#submit' => array('node_form_build_preview'),
   );
-  if (!empty($node->nid) && node_access('delete', $node)) {
-    $form['buttons']['delete'] = array(
-      '#type' => 'submit',
-      '#value' => t('Delete'),
-      '#weight' => 15,
-      '#submit' => array('node_form_delete_submit'),
-    );
-  }
+  $form['buttons']['unpublish'] = array(
+    '#access' => !empty($node->nid) && $node->status && (user_access('administer nodes') || user_access('publish any $node->type content')),
+    '#type' => 'submit',
+    '#value' => t('Unpublish'),
+    '#weight' => 11,
+    '#submit' => array('node_form_unpublish_submit'),
+  );
+  $form['buttons']['delete'] = array(
+    '#type' => 'submit',
+    '#value' => t('Delete'),
+    '#weight' => 15,
+    '#submit' => array('node_form_delete_submit'),
+    '#access' => !empty($node->nid) && node_access('delete', $node),
+  );
   $form['#validate'][] = 'node_form_validate';
   $form['#theme'] = array($node->type . '_node_form', 'node_form');
 
@@ -395,6 +422,74 @@
   return $output;
 }
 
+/**
+ * Submit handler for saving as draft.
+ */
+function node_form_draft_submit($form, &$form_state) {
+  $node = node_form_submit_build_node($form, $form_state);
+
+  // Always create a new revision if saving as a draft.
+  $node->revision = TRUE;
+  $node->draft = 1;
+  if (empty($node->nid)) {
+    $node->status = FALSE;
+  }
+  else {
+    // Set $node->changed to the current value so that this draft does not
+    // affect the 'updated' mark on node listings.
+    $original = node_load($node->nid);
+    $node->timestamp_changed = $original->changed;
+  }
+  node_save($node);
+
+  // If the node is published, and there is already an existing revision
+  // we need to ensure that the data from the current active revision is used
+  // in both the {node} and field API tables.
+  if ($node->status && $node->old_vid) {
+    // Since we have already saved a new revision, load the original again.
+    $current = node_load($node->nid, $node->old_vid);
+
+    // Set taxonomy terms to tids to resolve inconsistencies between loaded
+    // and saved data.
+    if (module_exists('taxonomy')) {
+      $current->taxonomy = array_keys($current->taxonomy);
+    }
+    $current->revision = TRUE;
+
+    // Set both the updated and revision timestamps to those of the revision
+    // we are re-saving to ensure it is listed correctly on the revisions tab.
+    $current->timestamp_updated = $current->timestamp_revision = $original->changed;
+    node_save($current);
+
+    // We now have a duplicate copy of this revision, so delete the old one.
+    node_delete_revision($original->nid, $original->vid);
+  }
+
+  if ($node->nid) {
+    unset($form_state['rebuild']);
+    $form_state['nid'] = $node->nid;
+    $form_state['redirect'] = 'node/' . $node->nid;
+  }
+}
+
+/**
+ * Submit handler for saving as published.
+ */
+function node_form_publish_submit($form, &$form_state) {
+  $form_state['values']['status'] = 1;
+  $form_state['values']['draft'] = 0;
+  node_form_submit($form, $form_state);
+}
+
+/**
+ * Submit handler for saving as unpublished.
+ */
+function node_form_unpublish_submit($form, &$form_state) {
+  $form_state['values']['status'] = 0;
+  $form_state['values']['draft'] = 0;
+  node_form_submit($form, $form_state);
+}
+
 function node_form_submit($form, &$form_state) {
   global $user;
 
@@ -480,19 +575,23 @@
 function node_revision_overview($node) {
   drupal_set_title(t('Revisions for %title', array('%title' => $node->title)), PASS_THROUGH);
 
-  $header = array(t('Revision'), array('data' => t('Operations'), 'colspan' => 2));
+  $header = array(t('Revision'), array('data' => t('Operations'), 'colspan' => 3));
 
   $revisions = node_revision_list($node);
 
   $rows = array();
-  $revert_permission = FALSE;
-  if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) {
-    $revert_permission = TRUE;
+  $edit_permission = FALSE;
+  if (node_access('update', $node)) {
+    $edit_permission = TRUE;
   }
   $delete_permission = FALSE;
-  if ((user_access('delete revisions') || user_access('administer nodes')) && node_access('delete', $node)) {
+  if (user_access('delete revisions') || node_access('delete', $node)) {
     $delete_permission = TRUE;
   }
+  $publish_permission = FALSE;
+  if ((user_access("publish any $node->type content") || user_access('administer nodes')) && node_access('update', $node)) {
+    $publish_permission = TRUE;
+  }
   foreach ($revisions as $revision) {
     $row = array();
     $operations = array();
@@ -501,21 +600,24 @@
       $row[] = array('data' => t('!date by !username', array('!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid"), '!username' => theme('username', $revision)))
                                . (($revision->log != '') ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : ''),
                      'class' => 'revision-current');
-      $operations[] = array('data' => theme('placeholder', t('current revision')), 'class' => 'revision-current', 'colspan' => 2);
+      $operations[] = array('data' => theme('placeholder', t('current revision')), 'class' => 'revision-current', 'colspan' => 3);
     }
     else {
       $row[] = t('!date by !username', array('!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid/revisions/$revision->vid/view"), '!username' => theme('username', $revision)))
                . (($revision->log != '') ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : '');
-      if ($revert_permission) {
-        $operations[] = l(t('revert'), "node/$node->nid/revisions/$revision->vid/revert");
+      if ($publish_permission) {
+        $operations[] = l(t('publish'), "node/$node->nid/revisions/$revision->vid/revert");
       }
       if ($delete_permission) {
         $operations[] = l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete");
       }
+      if ($edit_permission) {
+        $operations[] = l(t('edit'), "node/$node->nid/edit/revision/$revision->vid");
+      }
     }
     $rows[] = array_merge($row, $operations);
   }
-  
+
   $build['node_revisions_table'] = array(
     '#theme' => 'table',
     '#rows' => $rows,
@@ -530,12 +632,13 @@
  */
 function node_revision_revert_confirm($form_state, $node_revision) {
   $form['#node_revision'] = $node_revision;
-  return confirm_form($form, t('Are you sure you want to revert to the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/' . $node_revision->nid . '/revisions', '', t('Revert'), t('Cancel'));
+  return confirm_form($form, t('Are you sure you want to publish the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/' . $node_revision->nid . '/revisions', '', t('Publish'), t('Cancel'));
 }
 
 function node_revision_revert_confirm_submit($form, &$form_state) {
   $node_revision = $form['#node_revision'];
   $node_revision->revision = 1;
+  $node_revision->draft = 0;
   $node_revision->log = t('Copy of the revision from %date.', array('%date' => format_date($node_revision->revision_timestamp)));
   if (module_exists('taxonomy')) {
     $node_revision->taxonomy = array_keys($node_revision->taxonomy);
@@ -555,12 +658,7 @@
 
 function node_revision_delete_confirm_submit($form, &$form_state) {
   $node_revision = $form['#node_revision'];
-  db_delete('node_revision')
-    ->condition('nid', $node_revision->nid)
-    ->condition('vid', $node_revision->vid)
-    ->execute();
-  module_invoke_all('node_delete_revision', $node_revision);
-  field_attach_delete_revision('node', $node_revision);
+  node_delete_revision($node_revision->nid, $node_revision->vid);
   watchdog('content', '@type: deleted %title revision %revision.', array('@type' => $node_revision->type, '%title' => $node_revision->title, '%revision' => $node_revision->vid));
   drupal_set_message(t('Revision from %revision-date of @type %title has been deleted.', array('%revision-date' => format_date($node_revision->revision_timestamp), '@type' => node_type_get_name($node_revision), '%title' => $node_revision->title)));
   $form_state['redirect'] = 'node/' . $node_revision->nid;
Index: modules/node/node.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.test,v
retrieving revision 1.38
diff -u -r1.38 node.test
--- modules/node/node.test	28 Jul 2009 19:18:06 -0000	1.38
+++ modules/node/node.test	15 Aug 2009 12:49:07 -0000
@@ -97,8 +97,7 @@
     parent::setUp();
 
     // Create and login user.
-    $web_user = $this->drupalCreateUser(array('view revisions', 'revert revisions', 'edit any page content',
-                                               'delete revisions', 'delete any page content'));
+    $web_user = $this->drupalCreateUser(array('view revisions', 'publish any page content', 'edit any page content', 'delete revisions', 'delete any page content'));
     $this->drupalLogin($web_user);
 
     // Create initial node.
@@ -150,7 +149,7 @@
     }
 
     // Confirm that revisions revert properly.
-    $this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/revert", array(), t('Revert'));
+    $this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/revert", array(), t('Publish'));
     $this->assertRaw(t('@type %title has been reverted back to the revision from %revision-date.',
                         array('@type' => 'Page', '%title' => $nodes[1]->title,
                               '%revision-date' => format_date($nodes[1]->revision_timestamp))), t('Revision reverted.'));
@@ -191,7 +190,7 @@
     $edit = array();
     $edit['title'] = $this->randomName(8);
     $edit[$body_key] = $this->randomName(16);
-    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $this->drupalPost('node/add/page', $edit, t('Publish'));
 
     // Check that the node exists in the database.
     $node = $this->drupalGetNodeByTitle($edit['title']);
@@ -213,7 +212,7 @@
     $edit['title'] = $this->randomName(8);
     $edit[$body_key] = $this->randomName(16);
     // Stay on the current page, without reloading.
-    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->drupalPost(NULL, $edit, t('Publish'));
 
     // Check that the title and body fields are displayed with the updated values.
     $this->assertText($edit['title'], t('Title displayed.'));
@@ -312,7 +311,7 @@
     $edit = array();
     $edit['title'] = $this->randomName(8);
     $edit['body[0][value]'] = $this->randomName(16);
-    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $this->drupalPost('node/add/page', $edit, t('Publish'));
 
     // Check that the page has been created.
     $this->assertRaw(t('!post %title has been created.', array('!post' => 'Page', '%title' => $edit['title'])), t('Page created.'));
@@ -501,7 +500,7 @@
     $edit = array();
     $edit['title'] = $this->randomName(8);
     $edit['body[0][value]'] = $this->randomName(16);
-    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $this->drupalPost('node/add/page', $edit, t('Publish'));
 
     // Check that the post information is displayed.
     $node = $this->drupalGetNodeByTitle($edit['title']);
@@ -522,7 +521,7 @@
     $edit = array();
     $edit['title'] = $this->randomName(8);
     $edit['body[0][value]'] = $this->randomName(16);
-    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $this->drupalPost('node/add/page', $edit, t('Publish'));
 
     // Check that the post information is displayed.
     $node = $this->drupalGetNodeByTitle($edit['title']);
Index: modules/path/path.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/path/path.test,v
retrieving revision 1.17
diff -u -r1.17 path.test
--- modules/path/path.test	11 Aug 2009 11:52:46 -0000	1.17
+++ modules/path/path.test	15 Aug 2009 12:49:07 -0000
@@ -113,7 +113,7 @@
     // Create alias.
     $edit = array();
     $edit['path'] = $this->randomName(8);
-    $this->drupalPost('node/' . $node1->nid . '/edit', $edit, t('Save'));
+    $this->drupalPost('node/' . $node1->nid . '/edit', $edit, t('Publish'));
 
     // Confirm that the alias works.
     $this->drupalGet($edit['path']);
@@ -122,7 +122,7 @@
     // Change alias.
     $previous = $edit['path'];
     $edit['path'] = $this->randomName(8);
-    $this->drupalPost('node/' . $node1->nid . '/edit', $edit, t('Save'));
+    $this->drupalPost('node/' . $node1->nid . '/edit', $edit, t('Publish'));
 
     // Confirm that the alias works.
     $this->drupalGet($edit['path']);
@@ -138,13 +138,13 @@
 
     // Set alias to second test node.
     // Leave $edit['path'] the same.
-    $this->drupalPost('node/' . $node2->nid . '/edit', $edit, t('Save'));
+    $this->drupalPost('node/' . $node2->nid . '/edit', $edit, t('Publish'));
 
     // Confirm that the alias didn't make a duplicate.
     $this->assertText(t('The path is already in use.'), 'Attempt to moved alias was rejected.');
 
     // Delete alias.
-    $this->drupalPost('node/' . $node1->nid . '/edit', array('path' => ''), t('Save'));
+    $this->drupalPost('node/' . $node1->nid . '/edit', array('path' => ''), t('Publish'));
 
     // Confirm that the alias no longer works.
     $this->drupalGet($edit['path']);
@@ -201,7 +201,7 @@
     $edit = array();
     $edit['language'] = 'en';
     $edit['path'] = $this->randomName();
-    $this->drupalPost('node/' . $english_node->nid . '/edit', $edit, t('Save'));
+    $this->drupalPost('node/' . $english_node->nid . '/edit', $edit, t('Publish'));
 
     // Confirm that the alias works.
     $this->drupalGet($edit['path']);
@@ -214,7 +214,7 @@
     $edit['title'] = $this->randomName();
     $edit['body[0][value]'] = $this->randomName();
     $edit['path'] = $this->randomName();
-    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->drupalPost(NULL, $edit, t('Publish'));
 
     // Clear the path lookup cache.
     drupal_lookup_path('wipe');
Index: modules/php/php.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/php/php.test,v
retrieving revision 1.14
diff -u -r1.14 php.test
--- modules/php/php.test	13 Jul 2009 21:51:11 -0000	1.14
+++ modules/php/php.test	15 Aug 2009 12:49:07 -0000
@@ -61,7 +61,7 @@
     // Change filter to PHP filter and see that PHP code is evaluated.
     $edit = array();
     $edit['body[0][value_format]'] = 3;
-    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Publish'));
     $this->assertRaw(t('Page %title has been updated.', array('%title' => $node->title)), t('PHP code filter turned on.'));
 
     // Make sure that the PHP code shows up as text.
Index: modules/poll/poll.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/poll/poll.test,v
retrieving revision 1.21
diff -u -r1.21 poll.test
--- modules/poll/poll.test	20 Jul 2009 18:51:33 -0000	1.21
+++ modules/poll/poll.test	15 Aug 2009 12:49:07 -0000
@@ -44,7 +44,7 @@
       list($edit, $index) = $this->_pollGenerateEdit($title, $choices, $index);
     }
 
-    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->drupalPost(NULL, $edit, t('Publish'));
     $node = $this->drupalGetNodeByTitle($title);
     $this->assertText(t('@type @title has been created.', array('@type' => node_type_get_name('poll'), '@title' => $title)), 'Poll has been created.');
     $this->assertTrue($node->nid, t('Poll has been found in the database.'));
@@ -79,7 +79,7 @@
 
   function pollUpdate($nid, $title, $edit) {
     // Edit the poll node.
-    $this->drupalPost('node/' . $nid . '/edit', $edit, t('Save'));
+    $this->drupalPost('node/' . $nid . '/edit', $edit, t('Publish'));
     $this->assertText(t('@type @title has been updated.', array('@type' => node_type_get_name('poll'), '@title' => $title)), 'Poll has been updated.');
   }
 }
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.367
diff -u -r1.367 system.install
--- modules/system/system.install	12 Aug 2009 23:51:19 -0000	1.367
+++ modules/system/system.install	15 Aug 2009 12:49:07 -0000
@@ -1444,6 +1444,8 @@
     $renamed_permission = preg_replace('/(?<=^|,\ )delete\ own\ forum\ topics(?=,|$)/', 'delete own forum content', $role->perm);
     $renamed_permission = preg_replace('/(?<=^|,\ )edit\ any\ forum\ topic(?=,|$)/', 'edit any forum content', $role->perm);
     $renamed_permission = preg_replace('/(?<=^|,\ )edit\ own\ forum\ topics(?=,|$)/', 'edit own forum content', $role->perm);
+    // Also remove the 'revert revisions' permission.
+    $renamed_permission = preg_replace('/(?<=^|,\ )revert\ revisions(?=, |$)/', '', $role->perm);
 
     if ($renamed_permission != $role->perm) {
       $ret[] = update_sql("UPDATE {permission} SET perm = '$renamed_permission' WHERE rid = $role->rid");
Index: modules/system/system.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.test,v
retrieving revision 1.63
diff -u -r1.63 system.test
--- modules/system/system.test	12 Aug 2009 23:51:19 -0000	1.63
+++ modules/system/system.test	15 Aug 2009 12:49:07 -0000
@@ -701,7 +701,7 @@
      'body[0][value]' => '!SimpleTest! test body' . $this->randomName(200),
     );
     // Create the node with HTML in the title.
-    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $this->drupalPost('node/add/page', $edit, t('Publish'));
 
     $node = $this->drupalGetNodeByTitle($edit['title']);
     $this->assertNotNull($node, 'Node created and found in database');
Index: modules/taxonomy/taxonomy.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.test,v
retrieving revision 1.43
diff -u -r1.43 taxonomy.test
--- modules/taxonomy/taxonomy.test	4 Aug 2009 06:50:07 -0000	1.43
+++ modules/taxonomy/taxonomy.test	15 Aug 2009 12:49:07 -0000
@@ -455,7 +455,7 @@
     $edit['title'] = $this->randomName();
     $edit['body[0][value]'] = $this->randomName();
     $edit['taxonomy[' . $this->vocabulary->vid . ']'] = $term1->tid;
-    $this->drupalPost('node/add/article', $edit, t('Save'));
+    $this->drupalPost('node/add/article', $edit, t('Publish'));
 
     // Check that the term is displayed when the node is viewed.
     $node = $this->drupalGetNodeByTitle($edit['title']);
@@ -464,7 +464,7 @@
 
     // Edit the node with a different term.
     $edit['taxonomy[' . $this->vocabulary->vid . ']'] = $term2->tid;
-    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Publish'));
 
     $this->drupalGet('node/' . $node->nid);
     $this->assertText($term2->name, t('Term is displayed when viewing the node.'));
@@ -496,7 +496,7 @@
     // free-tagging field created by the default profile.
     $edit['taxonomy[tags][' . $this->vocabulary->vid . ']'] =  implode(', ', $terms);
     $edit['body[0][value]'] = $this->randomName();
-    $this->drupalPost('node/add/article', $edit, t('Save'));
+    $this->drupalPost('node/add/article', $edit, t('Publish'));
     $this->assertRaw(t('@type %title has been created.', array('@type' => t('Article'), '%title' => $edit['title'])), t('The node was created successfully'));
     foreach ($terms as $term) {
       $this->assertText($term, t('The term was saved and appears on the node page'));
Index: modules/translation/translation.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/translation/translation.test,v
retrieving revision 1.15
diff -u -r1.15 translation.test
--- modules/translation/translation.test	11 Aug 2009 11:52:46 -0000	1.15
+++ modules/translation/translation.test	15 Aug 2009 12:49:07 -0000
@@ -61,7 +61,7 @@
     $edit = array();
     $edit['title'] = $this->randomName();
     $edit['body[0][value]'] = $this->randomName();
-    $this->drupalPost('node/add/page', $edit, t('Save'), array('query' => array('translation' => $node->nid, 'language' => 'es')));
+    $this->drupalPost('node/add/page', $edit, t('Publish'), array('query' => array('translation' => $node->nid, 'language' => 'es')));
     $duplicate = $this->drupalGetNodeByTitle($edit['title']);
     $this->assertEqual($duplicate->tnid, 0, t('The node does not have a tnid.'));
 
@@ -69,7 +69,7 @@
     $edit = array();
     $edit['body[0][value]'] = $this->randomName();
     $edit['translation[retranslate]'] = TRUE;
-    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Publish'));
     $this->assertRaw(t('Page %title has been updated.', array('%title' => $node_title)), t('Original node updated.'));
 
     // Check to make sure that interface shows translation as outdated
@@ -80,7 +80,7 @@
     $edit = array();
     $edit['body[0][value]'] = $this->randomName();
     $edit['translation[status]'] = FALSE;
-    $this->drupalPost('node/' . $node_translation->nid . '/edit', $edit, t('Save'));
+    $this->drupalPost('node/' . $node_translation->nid . '/edit', $edit, t('Publish'));
     $this->assertRaw(t('Page %title has been updated.', array('%title' => $node_translation_title)), t('Translated node updated.'));
   }
 
@@ -130,7 +130,7 @@
     $edit['title'] = $title;
     $edit['body[0][value]'] = $body;
     $edit['language'] = $language;
-    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $this->drupalPost('node/add/page', $edit, t('Publish'));
     $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Page created.'));
 
     // Check to make sure the node was created.
@@ -154,7 +154,7 @@
     $edit = array();
     $edit['title'] = $title;
     $edit['body[0][value]'] = $body;
-    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->drupalPost(NULL, $edit, t('Publish'));
     $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Translation created.'));
 
     // Check to make sure that translation was successful.
Index: modules/trigger/trigger.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/trigger/trigger.test,v
retrieving revision 1.15
diff -u -r1.15 trigger.test
--- modules/trigger/trigger.test	28 Jul 2009 19:18:08 -0000	1.15
+++ modules/trigger/trigger.test	15 Aug 2009 12:49:07 -0000
@@ -33,18 +33,30 @@
       $this->drupalLogin($test_user);
       $edit = array('aid' => $hash);
       $this->drupalPost('admin/structure/trigger/node', $edit, t('Assign'));
-      // Create an unpublished node.
-      $web_user = $this->drupalCreateUser(array('create page content', 'access content', 'administer nodes'));
+      // Create a node.
+      $web_user = $this->drupalCreateUser(array('create page content', 'edit any page content', 'access content', 'administer nodes'));
       $this->drupalLogin($web_user);
       $edit = array();
       $edit['title'] = '!SimpleTest test node! ' . $this->randomName(10);
       $edit['body[0][value]'] = '!SimpleTest test body! ' . $this->randomName(32) . ' ' . $this->randomName(32);
-      $edit[$info['property']] = !$info['expected'];
-      $this->drupalPost('node/add/page', $edit, t('Save'));
-      // Make sure the text we want appears.
-      $this->assertRaw(t('!post %title has been created.', array('!post' => 'Page', '%title' => $edit['title'])), t('Make sure the page has actually been created'));
+
+      // Publish and unpublish are buttons rather than checkboxes.
+      if ($action == 'node_publish_action') {
+        $this->drupalPost('node/add/page', $edit, t('Publish'));
+      }
+      elseif ($action == 'node_unpublish_action') {
+        // The 'Unpublish' button does not appear on the node/add page
+        // so use 'save as draft' instead.
+        $this->drupalPost('node/add/page', $edit, t('Save as draft'));
+      }
+      else {
+        $edit[$info['property']] = !$info['expected'];
+        $this->drupalPost('node/add/page', $edit, t('Publish'));
+        // Make sure the text we want appears.
+        $this->assertRaw(t('!post %title has been created.', array('!post' => 'Page', '%title' => $edit['title'])), t('Make sure the page has actually been created'));
+      }
       // Action should have been fired.
-      $loaded_node = $this->drupalGetNodeByTitle($edit['title']);;
+      $loaded_node = $this->drupalGetNodeByTitle($edit['title']);
       $this->assertTrue($loaded_node->$info['property'] == $info['expected'], t('Make sure the @action action fired.', array('@action' => $info['name'])));
       // Leave action assigned for next test
 
Index: modules/upload/upload.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/upload/upload.test,v
retrieving revision 1.22
diff -u -r1.22 upload.test
--- modules/upload/upload.test	28 Jul 2009 19:18:08 -0000	1.22
+++ modules/upload/upload.test	15 Aug 2009 12:49:07 -0000
@@ -73,7 +73,7 @@
       // Rename file.
       $edit = array();
       $edit['files[' . $upload->fid . '][description]'] = $new_name = substr($upload->description, 1);
-      $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+      $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Publish'));
       $this->assertRaw(t('Page %title has been updated.', array('%title' => $node->title)), 'File renamed successfully.');
 
       $this->assertText($new_name, $new_name . ' found on node.');
@@ -82,7 +82,7 @@
       // Delete a file.
       $edit = array();
       $edit['files[' . $upload->fid . '][remove]'] = TRUE;
-      $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+      $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Publish'));
       $this->assertRaw(t('Page %title has been updated.', array('%title' => $node->title)), 'File deleted successfully.');
 
       $this->assertNoText($new_name, $new_name . ' not found on node.');
@@ -193,7 +193,7 @@
   function uploadFile($node, $filename, $assert = TRUE) {
     $edit = array();
     $edit['files[upload]'] = $filename; //edit-upload
-    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Publish'));
     if ($assert) {
       $this->assertRaw(t('Page %title has been updated.', array('%title' => $node->title)), 'File attached successfully.');
     }

