? 000-seven-form-css-sanity.patch
? 823428-url-entity.patch
? 920614-node-access-tests.patch
? file_download_access6.patch
? test.patch
? sites/default/files
? sites/default/private
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1227
diff -u -p -r1.1227 common.inc
--- includes/common.inc	25 Sep 2010 02:00:06 -0000	1.1227
+++ includes/common.inc	1 Oct 2010 14:15:44 -0000
@@ -2050,6 +2050,11 @@ function format_username($account) {
  *   A string containing a URL to the given path.
  */
 function url($path = NULL, array $options = array()) {
+  static $rewrite;
+  // Check for url rewrites, which can affect performance.
+  if (!isset($rewrite)) {
+    $rewrite = (bool) count(module_implements('url_outbound_alter'));
+  }
   // Merge in defaults.
   $options += array(
     'fragment' => '',
@@ -2072,8 +2077,27 @@ function url($path = NULL, array $option
   // Preserve the original path before altering or aliasing.
   $original_path = $path;
 
-  // Allow other modules to alter the outbound URL and options.
-  drupal_alter('url_outbound', $path, $options, $original_path);
+  // For performance, pass entity data to the alter hook.
+  if (!empty($rewrite)) {
+    if (!isset($options['entity_type'])) {
+      // No great hit here, as this is already cached.
+      $item = menu_get_item($path);
+      // There may be a better way to do this.
+      if (!empty($item['load_functions'])) {
+        $function = current($item['load_functions']);
+        if (substr_count($function, '_load') > 0) {
+          $type = str_replace('_load', '', $function);
+          $info = entity_get_info($type);
+          if (!empty($info)) {
+            $options['entity_type'] = $type;
+            $options['entity'] = current($item['page_arguments']);
+          }
+        }
+      }
+    }
+    // Allow other modules to alter the outbound URL and options.
+    drupal_alter('url_outbound', $path, $options, $original_path);
+  }
 
   if (isset($options['fragment']) && $options['fragment'] !== '') {
     $options['fragment'] = '#' . $options['fragment'];
Index: modules/node/node.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.api.php,v
retrieving revision 1.74
diff -u -p -r1.74 node.api.php
--- modules/node/node.api.php	25 Sep 2010 18:01:28 -0000	1.74
+++ modules/node/node.api.php	1 Oct 2010 14:15:44 -0000
@@ -138,7 +138,10 @@
  * an array of the list IDs that this user is a member of.
  *
  * A node access module may implement as many realms as necessary to
- * properly define the access privileges for the nodes.
+ * properly define the access privileges for the nodes. Note that the system
+ * makes no distinction between published and unpublished nodes. It is the
+ * module's responsibility to provide appropriate realms to limit access to
+ * unpublished content.
  *
  * @param $account
  *   The user object whose grants are requested.
@@ -169,6 +172,12 @@ function hook_node_grants($account, $op)
  * interested, it must respond with an array of permissions arrays for that
  * node.
  *
+ * Node access grants apply regardless of the published or unpublished status
+ * of the node. Implementations must make sure not to grant access to
+ * unpublished nodes if they don't want to change the standard access control
+ * behavior. Your module may need to create a separate access realm to handle
+ * access to unpublished nodes.
+ *
  * Note that the grant values in the return value from your hook must be
  * integers and not boolean TRUE and FALSE.
  *
@@ -177,7 +186,9 @@ function hook_node_grants($account, $op)
  *   hook_node_grants().
  * - 'gid': A 'grant ID' from hook_node_grants().
  * - 'grant_view': If set to 1 a user that has been identified as a member
- *   of this gid within this realm can view this node.
+ *   of this gid within this realm can view this node. This should usually be
+ *   set to $node->status. Failure to do so may expose unpublished content
+ *   to some users.
  * - 'grant_update': If set to 1 a user that has been identified as a member
  *   of this gid within this realm can edit this node.
  * - 'grant_delete': If set to 1 a user that has been identified as a member
@@ -187,6 +198,35 @@ function hook_node_grants($account, $op)
  *   priority will not be written. If there is any doubt, it is best to
  *   leave this 0.
  *
+ *
+ * When an implementation is interested in a node but want to deny access to
+ * everyone, it may return a "deny all" grant:
+ *
+ * @code
+ * $grants[] = array(
+ *   'realm' => 'all',
+ *   'gid' => 0,
+ *   'grant_view' => 0,
+ *   'grant_update' => 0,
+ *   'grant_delete' => 0,
+ *   'priority' => 1,
+ * );
+ * @endcode
+ *
+ * Setting the priority should cancel out other grants. In the case of a
+ * conflict between modules, it is safer to use hook_node_access_records_alter()
+ * to return only the deny grant.
+ *
+ * Note: a deny all grant is not written to the database; denies are implicit.
+ *
+ * @see node_access_write_grants()
+ *
+ * @param $node
+ *   The node that has just been saved.
+ *
+ * @return
+ *   An array of grants as defined above.
+ *
  * @ingroup node_access
  */
 function hook_node_access_records($node) {
@@ -194,17 +234,22 @@ function hook_node_access_records($node)
   // treated just like any other node and we completely ignore it.
   if ($node->private) {
     $grants = array();
-    $grants[] = array(
-      'realm' => 'example',
-      'gid' => 1,
-      'grant_view' => 1,
-      'grant_update' => 0,
-      'grant_delete' => 0,
-      'priority' => 0,
-    );
-
+    // Only published nodes should be viewable to all users. If we allow access
+    // blindly here, then all users could view an unpublished node.
+    if ($node->status) {
+      $grants[] = array(
+        'realm' => 'example',
+        'gid' => 1,
+        'grant_view' => 1,
+        'grant_update' => 0,
+        'grant_delete' => 0,
+        'priority' => 0,
+      );
+    }
     // For the example_author array, the GID is equivalent to a UID, which
-    // means there are many many groups of just 1 user.
+    // means there are many groups of just 1 user.
+    // Note that an author can always view his or her nodes, even if they
+    // have status unpublished.
     $grants[] = array(
       'realm' => 'example_author',
       'gid' => $node->uid,
@@ -213,6 +258,7 @@ function hook_node_access_records($node)
       'grant_delete' => 1,
       'priority' => 0,
     );
+
     return $grants;
   }
 }
@@ -234,6 +280,8 @@ function hook_node_access_records($node)
  * user must have one or more matching permissions in order to complete the
  * requested operation.
  *
+ * A module may deny all access to a node by setting $grants to an empty array.
+ *
  * @see hook_node_grants()
  * @see hook_node_grants_alter()
  *
@@ -277,6 +325,8 @@ function hook_node_access_records_alter(
  * The resulting grants are then checked against the records stored in the
  * {node_access} table to determine if the operation may be completed.
  *
+ * A module may deny all access to a user by setting $grants to an empty array.
+ *
  * @see hook_node_access_records()
  * @see hook_node_access_records_alter()
  *
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1302
diff -u -p -r1.1302 node.module
--- modules/node/node.module	24 Sep 2010 00:37:43 -0000	1.1302
+++ modules/node/node.module	1 Oct 2010 14:15:47 -0000
@@ -3156,8 +3156,8 @@ function node_access_acquire_grants($nod
   $grants = module_invoke_all('node_access_records', $node);
   // Let modules alter the grants.
   drupal_alter('node_access_records', $grants, $node);
-  // If no grants are set, then use the default grant.
-  if (empty($grants)) {
+  // If no grants are set and the node is published, then use the default grant.
+  if (empty($grants) && !empty($node->status)) {
     $grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0);
   }
   else {
@@ -3206,7 +3206,7 @@ function node_access_write_grants($node,
   }
 
   // Only perform work when node_access modules are active.
-  if (count(module_implements('node_grants'))) {
+  if (!empty($grants) && count(module_implements('node_grants'))) {
     $query = db_insert('node_access')->fields(array('nid', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete'));
     foreach ($grants as $grant) {
       if ($realm && $realm != $grant['realm']) {
Index: modules/node/node.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.test,v
retrieving revision 1.95
diff -u -p -r1.95 node.test
--- modules/node/node.test	17 Sep 2010 14:53:21 -0000	1.95
+++ modules/node/node.test	1 Oct 2010 14:15:49 -0000
@@ -770,7 +770,7 @@ class NodeRSSContentTestCase extends Dru
 
 /**
  * Test case to verify basic node_access functionality.
- * @todo Cover hook_access in a separate test class.
+ * @todo Cover hook_node_access in a separate test class.
  * hook_node_access_records is covered in another test class.
  */
 class NodeAccessUnitTest extends DrupalWebTestCase {
@@ -821,6 +821,9 @@ class NodeAccessUnitTest extends DrupalW
     $node3 = $this->drupalCreateNode(array('status' => 0, 'uid' => $web_user3->uid));
     $this->assertNodeAccess(array('view' => FALSE), $node3, $web_user3);
 
+    // User cannot create content without permission.
+    $this->assertNodeAccess(array('create' => FALSE), 'page', $web_user3);
+
     // User can 'view own unpublished content', but another user cannot.
     $web_user4 = $this->drupalCreateUser(array('access content', 'view own unpublished content'));
     $web_user5 = $this->drupalCreateUser(array('access content', 'view own unpublished content'));
@@ -830,7 +833,6 @@ class NodeAccessUnitTest extends DrupalW
 
     // Tests the default access provided for a published node.
     $node5 = $this->drupalCreateNode();
-    $this->assertNodeAccess(array('create' => FALSE), 'page', $web_user3);
     $this->assertNodeAccess(array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE), $node5, $web_user3);
   }
 }
@@ -909,6 +911,12 @@ class NodeAccessRecordsUnitTest extends 
       drupal_alter('node_grants', $altered_grants, $web_user, $op);
       $this->assertNotEqual($grants, $altered_grants, t('Altered the %op grant for a user.', array('%op' => $op)));
     }
+
+    // Check that core does not grant access to an unpublished node when an
+    // empty $grants array is returned.
+    $node6 = $this->drupalCreateNode(array('status' => 0, 'disable_node_access' => TRUE));
+    $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node6->nid))->fetchAll();
+    $this->assertEqual(count($records), 0, t('Returned no records for unpublished node.'));
   }
 }
 
Index: modules/node/tests/node_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/tests/node_test.module,v
retrieving revision 1.11
diff -u -p -r1.11 node_test.module
--- modules/node/tests/node_test.module	20 Apr 2010 09:30:21 -0000	1.11
+++ modules/node/tests/node_test.module	1 Oct 2010 14:15:49 -0000
@@ -52,6 +52,10 @@ function node_test_node_grants($account,
  * Implements hook_node_access_records().
  */
 function node_test_node_access_records($node) {
+  // Return nothing when testing for empty responses.
+  if (!empty($node->disable_node_access)) {
+    return;
+  }
   $grants = array();
   if ($node->type == 'article') {
     // Create grant in arbitrary article_realm for article nodes.
