Index: modules/node/node.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.admin.inc,v
retrieving revision 1.74
diff -u -r1.74 node.admin.inc
--- modules/node/node.admin.inc	27 Oct 2009 04:06:44 -0000	1.74
+++ modules/node/node.admin.inc	30 Oct 2009 12:24:12 -0000
@@ -409,15 +409,27 @@
   $header['operations'] = array('data' => t('Operations'));
   
   $query = db_select('node', 'n')->extend('PagerDefault')->extend('TableSort');
-  $query->join('users', 'u', 'n.uid = u.uid');
   node_build_filter_query($query);
-
-  $result = $query
-    ->fields('n')
-    ->fields('u', array('name'))
+  if (!user_access('administer nodes') && !user_access('bypass node access')) {
+    // If the user is able to view their own unpublished nodes, allow them
+    // to see these in addition to published nodes. Check that they actually
+    // have some unpublished nodes to view before adding the condition.
+    if (user_access('view own unpublished content') && $own_unpublished = db_query('SELECT nid FROM {node} WHERE uid = :uid AND status = 0', array(':uid' => $GLOBALS['user']->uid))->fetchCol()) {
+      $query->condition(db_or()->condition('n.status', 1)->condition('n.nid', $own_unpublished, 'IN'));
+    }
+    else {
+      // If not, restrict the query to published nodes unless the user has
+      // administrative permissions.
+      $query->condition('n.status', 1);
+    }
+  }
+  $nids = $query
+    ->fields('n',array('nid'))
     ->limit(50)
     ->orderByHeader($header)
-    ->execute();
+    ->execute()
+    ->fetchCol();
+  $nodes = node_load_multiple($nids);
 
   // Build the 'Update options' form.
   $form['options'] = array(
@@ -425,6 +437,7 @@
     '#title' => t('Update options'),
     '#prefix' => '<div class="container-inline">',
     '#suffix' => '</div>',
+    '#access' => user_access('administer nodes') && user_access('bypass node access'),
   );
   $options = array();
   foreach (module_invoke_all('node_operations') as $operation => $array) {
@@ -443,13 +456,13 @@
     '#validate' => array('node_admin_nodes_validate'),
   );
 
+  // Prepare the list of nodes.
   $languages = language_list();
   $destination = drupal_get_destination();
-  $nodes = array();
-  foreach ($result as $node) {
+  foreach ($nodes as $node) {
     $l_options = empty($node->language) ? array() : array('language' => $languages[$node->language]);
     $options[$node->nid] = array(
-      'title' => l($node->title, 'node/' . $node->nid, $l_options) . ' ' . theme('mark', array('type' => node_mark($node->nid, $node->changed))),
+      'title' => l($node->title[FIELD_LANGUAGE_NONE][0]['value'], 'node/' . $node->nid, $l_options) . ' ' . theme('mark', array('type' => node_mark($node->nid, $node->changed))),
       'type' =>  check_plain(node_type_get_name($node)),
       'author' => theme('username', array('account' => $node)),
       'status' =>  $node->status ? t('published') : t('not published'),
@@ -458,14 +471,61 @@
     if ($multilanguage) {
       $options[$node->nid]['language'] = empty($node->language) ? t('Language neutral') : t($languages[$node->language]->name);
     }
-    $options[$node->nid]['operations'] = l(t('edit'), 'node/' . $node->nid . '/edit', array('query' => $destination));
+    
+    // Build a list of all the accessible operations for the current node.
+    $operations = array();
+    if (node_access('update', $node)) {
+      $operations[] = l(t('edit'), 'node/' . $node->nid . '/edit', array('query' => $destination));
+    }
+    if (node_access('delete', $node)) {
+      $operations[] = l(t('delete'), 'node/' . $node->nid . '/delete', array('query' => $destination));
+    }
+    if (count($operations) > 1) {
+      // Render an unordered list of operations links.
+      $options[$node->nid]['operations'] = theme('item_list', array(
+        'items' => $operations,
+        'type' => 'ul',
+        'attributes' => array('class' => array('links', 'inline')),
+      ));
+    }
+    elseif (!empty($operations)) {
+      // Render the first and only operation as a link.
+      $options[$node->nid]['operations'] = $operations[0];
+    }
   }
-  $form['nodes'] = array(
-    '#type' => 'tableselect',
-    '#header' => $header,
-    '#options' => $options,
-    '#empty' => t('No content available.'),
-  );
+  
+  // Tableselect ability is only made available when the user has access
+  // to update operations, otherwise a simple table is used.
+  if (user_access('administer nodes') && user_access('bypass node access')) {
+    $form['nodes'] = array(
+      '#type' => 'tableselect',
+      '#header' => $header,
+      '#options' => $options,
+      '#empty' => t('No content available.'),
+    );
+  }
+  else {
+    $form['nodes'] = array(
+      '#theme' => 'table',
+      '#header' => $header,
+    );
+    if (!empty($options)) {
+      $form['nodes']['#rows'] = $options;
+    }
+    else {
+      // Display an empty message as it is done with tableselect when there 
+      // is no node to display.
+      $form['nodes']['#rows'] = array(
+        array(
+          array(
+            'data' => t('No content available.'),
+            'colspan' => count($header),
+          ),
+        ),
+      );
+    }
+  }
+
   $form['pager'] = array('#markup' => theme('pager', array('tags' => NULL)));
   return $form;
 }
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1154
diff -u -r1.1154 node.module
--- modules/node/node.module	23 Oct 2009 22:24:16 -0000	1.1154
+++ modules/node/node.module	30 Oct 2009 08:08:16 -0000
@@ -1392,6 +1392,10 @@
       'title' => t('Bypass node access'),
       'description' => t('View, edit and delete all site content. Users with this permission will bypass any content-related access control. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))),
     ),
+    'access content overview' => array(
+      'title' => t('Access content overview'),
+      'description' => t('Access the content overview page.'),
+    ),
     'view revisions' => array(
       'title' => t('View revisions'),
       'description' => t('View content revisions.'),
@@ -1738,7 +1742,7 @@
     'description' => 'Find and manage content.',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('node_admin_content'),
-    'access arguments' => array('administer nodes'),
+    'access arguments' => array('access content overview'),
     'weight' => -10,
     'file' => 'node.admin.inc',
   );
Index: modules/node/node.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.test,v
retrieving revision 1.50
diff -u -r1.50 node.test
--- modules/node/node.test	24 Oct 2009 21:39:40 -0000	1.50
+++ modules/node/node.test	30 Oct 2009 08:21:34 -0000
@@ -392,6 +392,111 @@
   }
 }
 
+/**
+ * Test the functionality of the administer content page.
+ */
+class ContentAdminTestCase extends DrupalWebTestCase {
+  public static function getInfo() {
+     return array(
+      'name' => t('Content administration overview'),
+      'description' => t('Create users and content to test the content admin pages functionality.'),
+      'group' => t('Node'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+    // Remove the "view own unpublished content" permission which is set
+    // by default for authenticated users so we can test this permission
+    // correctly.
+    db_delete('role_permission')
+      ->condition('rid', DRUPAL_AUTHENTICATED_RID)
+      ->condition('permission', 'view own unpublished content')
+      ->execute();
+    $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'access content overview', 'administer nodes', 'bypass node access'));
+    $this->base_user_1 = $this->drupalCreateUser(array('access content overview'));
+    $this->base_user_2 = $this->drupalCreateUser(array('access content overview', 'view own unpublished content'));
+    $this->base_user_3 = $this->drupalCreateUser(array('access content overview', 'bypass node access'));
+  }
+
+  /**
+   * Ensure that both administrative users and basic content editors have
+   * access to the appropriate functionality on the node administration page.
+   */
+  function testContentAdminPages() {
+    $this->drupalLogin($this->admin_user);
+    
+    $nodes['published_page'] = $this->drupalCreateNode(array('type' => 'page'));
+    $nodes['unpublished_page_1'] = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->base_user_1->uid, 'status' => 0));
+    $nodes['unpublished_page_2'] = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->base_user_2->uid, 'status' => 0));
+    $nodes['published_article'] = $this->drupalCreateNode(array('type' => 'article'));
+
+    $nodes_count = count($nodes);
+
+    // Ensure the admin user can edit and delete any content.
+    $this->drupalGet('admin/content/node');
+    $this->assertResponse(200);
+    $content_plain = filter_xss($this->drupalGetContent(), array());
+    $this->assertTrue(substr_count($content_plain, 'edit') == $nodes_count, t('Admin user has edit link.'));
+    $this->assertTrue(substr_count($content_plain, 'delete') == $nodes_count, t('Admin user has delete link.'));
+    // Ensure the admin user can view an other user unpublished node.
+    $this->assertText($nodes['unpublished_page_1']->title[FIELD_LANGUAGE_NONE][0]['value'], t('Admin user can see unpublished nodes.'));
+
+    // Ensure the admin user can filter nodes by status.
+    $edit = array(
+      'filter' => 'status',
+      'status' => 'status-1',
+    );
+    $this->drupalPost('admin/content', $edit, t('Filter'));
+    $this->assertRaw(t('<strong>%type</strong> is <strong>%value</strong>', array('%type' => t('status'), '%value' => t('published'))), t('The content overview is filtered by status.'));
+    $this->assertText($nodes['published_page']->title[FIELD_LANGUAGE_NONE][0]['value'], t('Published nodes can be seen on content overview when filter is set to show only published nodes.'));
+    $this->assertNoText($nodes['unpublished_page_1']->title[FIELD_LANGUAGE_NONE][0]['value'], t('Unpublished nodes cannot be seen on content overview when filter is set to see only published nodes.'));
+
+    // Ensure the admin user can filter nodes by status and by content type.
+    $edit = array(
+      'filter' => 'type',
+      'type' => 'page',
+    );
+    $this->drupalPost('admin/content', $edit, t('Refine'));
+    $this->assertRaw(t('<strong>%type</strong> is <strong>%value</strong>', array('%type' => t('status'), '%value' => t('published'))), t('The content overview is filtered by status.'));
+    $this->assertRaw(t('<strong>%type</strong> is <strong>%value</strong>', array('%type' => t('type'), '%value' => 'Page')), t('The content overview is filtered by content type.'));
+    $this->assertText($nodes['published_page']->title[FIELD_LANGUAGE_NONE][0]['value'], t('Page nodes can be seen on content overview when filter is set to show only published page nodes.'));
+    $this->assertNoText($nodes['published_article']->title[FIELD_LANGUAGE_NONE][0]['value'], t('Article node cannot be seen on content overview when filter is set to show only published page nodes.'));
+    $this->drupalLogout();
+
+    // Ensure users don't see operation links for content they don't have
+    // sufficient permissions.
+    $this->drupalLogin($this->base_user_1);
+    $this->drupalGet('admin/content/node');
+    $this->assertResponse(200);
+    $this->assertNoText('edit', t('Edit links do not show up for users without rights.'));
+    $this->assertNoText('delete', t('Delete links do not show up for users without rights.'));
+    // Ensure users do not see their own unpublished nodes when they don't have
+    // the permission.
+    $this->assertNoText($nodes['unpublished_page_1']->title[FIELD_LANGUAGE_NONE][0]['value'], t('Unpublished nodes do not show up for users without rights.'));
+    $this->drupalLogout();
+    
+    // Ensure users see their own unpublished when they have the permission.
+    $this->drupalLogin($this->base_user_2);
+    $this->drupalGet('admin/content/node');
+    $this->assertResponse(200);
+    $this->assertText($nodes['unpublished_page_2']->title[FIELD_LANGUAGE_NONE][0]['value'], t('Own unpublished nodes show up for users with rights.'));
+    // Ensure users do no see unpublished nodes of other users when they are
+    // allowed to see their own.
+    $this->assertNoText($nodes['unpublished_page_1']->title[FIELD_LANGUAGE_NONE][0]['value'], t('Unpublished nodes of other users do not show up for users with rights.'));
+    $this->drupalLogout();
+    
+    // Ensure users can edit and delete all nodes when they are allowed to 
+    // bypass the node access.
+    $this->drupalLogin($this->base_user_3);
+    $this->drupalGet('admin/content/node');
+    $this->assertResponse(200);
+    $content_plain = filter_xss($this->drupalGetContent(), array());
+    $this->assertTrue(substr_count($content_plain, 'edit') == $nodes_count, t('User who can bypass node access has edit link.'));
+    $this->assertTrue(substr_count($content_plain, 'delete') == $nodes_count, t('User who can bypass node access has delete link.'));
+  }
+}
+
 class SummaryLengthTestCase extends DrupalWebTestCase {
   public static function getInfo() {
     return array(
@@ -944,66 +1049,6 @@
 }
 
 /**
- * Test node administration page functionality.
- */
-class NodeAdminTestCase extends DrupalWebTestCase {
-  protected $admin_user;
-
-  public static function getInfo() {
-    return array(
-      'name' => 'Node administration',
-      'description' => 'Test node administration page functionality.',
-      'group' => 'Node'
-    );
-  }
-
-  function setUp() {
-    parent::setUp();
-    $this->admin_user = $this->drupalCreateUser(array('administer nodes', 'create article content', 'create page content'));
-    $this->drupalLogin($this->admin_user);
-  }
-
-  /**
-   * Create 3 nodes and test if they are listed on the node admistration page.
-   */
-  function testNodeAdmin() {
-    $this->drupalPost('admin/content', array(), t('Update'));
-    $this->assertText(t('No items selected.'), t('Clicking update with no nodes displays error message on the node administration listing.'));
-
-    $node1 = $this->drupalCreateNode(array('type' => 'article', 'status' => 1));
-    $node2 = $this->drupalCreateNode(array('type' => 'article', 'status' => 0));
-    $node3 = $this->drupalCreateNode(array('type' => 'page'));
-
-    $this->drupalGet('admin/content');
-    $this->assertText($node1->title[FIELD_LANGUAGE_NONE][0]['value'], t('Node appears on the node administration listing.'));
-
-    $this->drupalPost('admin/content', array(), t('Update'));
-    $this->assertText(t('No items selected.'), t('Clicking update with no selected nodes displays error message on the node administration listing.'));
-
-    // Filter the node listing by status.
-    $edit = array(
-      'filter' => 'status',
-      'status' => 'status-1',
-    );
-    $this->drupalPost('admin/content', $edit, t('Filter'));
-    $this->assertRaw(t('<strong>%type</strong> is <strong>%value</strong>', array('%type' => t('status'), '%value' => t('published'))), t('The node administration listing is filtered by status.'));
-    $this->assertText($node1->title[FIELD_LANGUAGE_NONE][0]['value'], t('Published node appears on the node administration listing.'));
-    $this->assertNoText($node2->title[FIELD_LANGUAGE_NONE][0]['value'], t('Unpublished node does not appear on the node administration listing.'));
-
-    // Filter the node listing by content type.
-    $edit = array(
-      'filter' => 'type',
-      'type' => 'article',
-    );
-    $this->drupalPost('admin/content', $edit, t('Refine'));
-    $this->assertRaw(t('<strong>%type</strong> is <strong>%value</strong>', array('%type' => t('status'), '%value' => t('published'))), t('The node administration listing is filtered by status.'));
-    $this->assertRaw(t('<strong>%type</strong> is <strong>%value</strong>', array('%type' => t('type'), '%value' => 'Article')), t('The node administration listing is filtered by content type.'));
-    $this->assertText($node1->title[FIELD_LANGUAGE_NONE][0]['value'], t('Article node appears on the node administration listing.'));
-    $this->assertNoText($node3->title[FIELD_LANGUAGE_NONE][0]['value'], t('Page node does not appear on the node administration listing.'));
-  }
-}
-
-/**
  * Test node title.
  */
 class NodeTitleTestCase extends DrupalWebTestCase {
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.415
diff -u -r1.415 system.install
--- modules/system/system.install	24 Oct 2009 23:19:21 -0000	1.415
+++ modules/system/system.install	30 Oct 2009 08:50:31 -0000
@@ -2784,6 +2784,18 @@
 }
 
 /**
+ * Split the 'administer nodes' permission from 'access content overview'.
+ */
+function system_update_7046() {
+  $ret = array();
+  $roles = user_roles(FALSE, 'administer nodes');
+  foreach ($roles as $rid => $role) {
+    user_role_grant_permissions($rid, array('access content overview'));
+  }
+  return $ret;
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
Index: modules/tracker/tracker.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/tracker/tracker.test,v
retrieving revision 1.12
diff -u -r1.12 tracker.test
--- modules/tracker/tracker.test	11 Oct 2009 03:07:20 -0000	1.12
+++ modules/tracker/tracker.test	30 Oct 2009 08:08:16 -0000
@@ -213,7 +213,7 @@
    * Test that publish/unpublish works at admin/content/node
    */
   function testTrackerAdminUnpublish() {
-    $admin_user = $this->drupalCreateUser(array('administer nodes'));
+    $admin_user = $this->drupalCreateUser(array('access content overview', 'administer nodes', 'bypass node access'));
     $this->drupalLogin($admin_user);
 
     $node = $this->drupalCreateNode(array(
