? multiple_node_access.patch
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.947.2.15
diff -u -p -r1.947.2.15 node.module
--- modules/node/node.module	16 Feb 2009 14:39:40 -0000	1.947.2.15
+++ modules/node/node.module	31 May 2009 18:10:32 -0000
@@ -2032,22 +2032,19 @@ function node_access($op, $node, $accoun
 
   // If the module did not override the access rights, use those set in the
   // node_access table.
-  if ($op != 'create' && $node->nid && $node->status) {
-    $grants = array();
-    foreach (node_access_grants($op, $account) as $realm => $gids) {
-      foreach ($gids as $gid) {
-        $grants[] = "(gid = $gid AND realm = '$realm')";
+  if ($op != 'create' && $node->nid) {
+    $grants_sql = node_access_grants_sql($op, NULL, $account, $node->status);
+    // If the return value is FALSE, then the node status is unpublished and
+    // none of the grants requested an access check be run. In which case,
+    // we should fall through to the final 'if' statement.
+    if ($grants_sql !== FALSE) {
+      if (!empty($grants_sql)) {
+        $grants_sql .= ' AND';
       }
+      $sql = "SELECT COUNT(*) FROM {node_access} WHERE (nid = 0 OR nid = %d) $grants_sql grant_$op >= 1";
+      $result = db_query($sql, $node->nid);
+      return (db_result($result));
     }
-
-    $grants_sql = '';
-    if (count($grants)) {
-      $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
-    }
-
-    $sql = "SELECT COUNT(*) FROM {node_access} WHERE (nid = 0 OR nid = %d) $grants_sql AND grant_$op >= 1";
-    $result = db_query($sql, $node->nid);
-    return (db_result($result));
   }
 
   // Let authors view their own nodes.
@@ -2097,17 +2094,7 @@ function _node_access_where_sql($op = 'v
     return;
   }
 
-  $grants = array();
-  foreach (node_access_grants($op, $account) as $realm => $gids) {
-    foreach ($gids as $gid) {
-      $grants[] = "($node_access_alias.gid = $gid AND $node_access_alias.realm = '$realm')";
-    }
-  }
-
-  $grants_sql = '';
-  if (count($grants)) {
-    $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
-  }
+  $grants_sql = node_access_grants_sql($op, $node_access_alias, $account);
 
   $sql = "$node_access_alias.grant_$op >= 1 $grants_sql";
   return $sql;
@@ -2145,18 +2132,7 @@ function node_access_view_all_nodes() {
   static $access;
 
   if (!isset($access)) {
-    $grants = array();
-    foreach (node_access_grants('view') as $realm => $gids) {
-      foreach ($gids as $gid) {
-        $grants[] = "(gid = $gid AND realm = '$realm')";
-      }
-    }
-
-    $grants_sql = '';
-    if (count($grants)) {
-      $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
-    }
-
+    $grants_sql = node_access_grants_sql('view');
     $sql = "SELECT COUNT(*) FROM {node_access} WHERE nid = 0 $grants_sql AND grant_view >= 1";
     $result = db_query($sql);
     $access = db_result($result);
@@ -2165,6 +2141,92 @@ function node_access_view_all_nodes() {
   return $access;
 }
 
+ /**
+  * Check the logic of node grants and prepare the SQL statement.
+  *
+  * @param $op
+  *   The operation that the user is trying to perform.
+  * @param $account
+  *   The user object for the user performing the operation. If omitted, the
+  *   current user is used.
+  * @param $status
+  *   The publication status of the node being checked. Typically, node_access
+  *   checks are not run for unpublished nodes. However, some advanced uses
+  *   require that users can act on unpublished nodes.
+  * @return
+  *   There are three possible return values.
+  *   - A sql string containing the appropriate WHERE clauses, grouped together
+  *   according to the logic of hook_node_grants.
+  *   - An empty string, indicating that no extra rules are needed.
+  *   - Boolean FALSE, indicating that all nodes are unpublished and no module
+  *   has explicitly requested an access check.
+  */
+ function node_access_grants_sql($op = 'view', $node_access_alias = NULL, $account = NULL, $status = TRUE) {
+   // Define the grants array and variable strings.
+   $grants = array();
+   $alias = '';
+   $grants_sql = '';
+ 
+   // First, acquire all the grants as an array of rules.
+   $rules = node_access_grants($op, $account);
+ 
+   // Prepare the table alias, if needed.
+   if (!empty($node_access_alias)) {
+     $alias = $node_access_alias .'.';
+   }
+   // It may be that only the default rule is active. In that case, we can skip the
+   // process below.
+   if (count($rules) == 1 && key($rules) == 'all') {
+     $grants_sql .= "AND (". $alias ."realm = 'all' AND ". $alias ."gid = 0)";
+   }
+   else {
+     // Process the grants into logical groupings.
+     foreach ($rules as $realm => $rule) {
+       // Set the status flag.
+       if (isset($rule['check'])) {
+         $status += $rule['check'];
+       }
+       // Set the default clause group. Then check for options.
+       $group = 'all';
+       if (!empty($rule['group'])) {
+         $group = $rule['group'];
+       }
+       foreach ($rule as $gid) {
+         // Grants ids must be numeric, and we place them into logical groups.
+         if (is_numeric($gid)) {
+           $grants[$group][$realm][] = $gid;
+         }
+       }
+     }
+     // In most cases, node status must be set to TRUE. However, there are use-cases where
+     // we allow access to unpublished nodes. So we check the status to see if we need to
+     // continue with the logic or end this IF statement.
+     if (!$status) {
+       return FALSE;
+     }
+     // Run through the $grants, if needed, and generate the query SQL.
+     if (count($grants)) {
+       foreach ($grants as $group => $grant) {
+         $clauses = array();
+         foreach ($grant as $key => $value) {
+           foreach ($value as $gid) {
+             $clauses[] = "(". $alias ."realm = '$key' AND ". $alias ."gid = $gid)";
+           }
+         }
+         if ($group == 'all') {
+           $grants_sql .= 'AND ('. implode(' OR ', $clauses) .') ';
+         }
+         else {
+           // Required elements must go into a subquery.  Will not work prior to MySQL 4.1.x.
+           $subquery = "AND ". $alias ."nid IN (SELECT ". $alias ."nid FROM {node_access} ". $node_access_alias ." WHERE ";
+           $grants_sql .= $subquery .'('. implode(' OR ', $clauses) .')) ';
+         }
+       }
+     }
+   }
+   return $grants_sql;
+ }
+
 /**
  * Implementation of hook_db_rewrite_sql
  */
