? 309007-grants-alter-alt.patch
? 309007-grants-alter-final.patch
? 309007-grants-alter-named.patch
? 309007-grants-alter.patch
? node_access_records_alter_6.patch
? nodeapi7.1.patch
? sites/default/files
Index: modules/node/node.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.api.php,v
retrieving revision 1.20
diff -u -p -r1.20 node.api.php
--- modules/node/node.api.php	12 May 2009 23:19:13 -0000	1.20
+++ modules/node/node.api.php	17 May 2009 20:53:27 -0000
@@ -109,6 +109,113 @@ function hook_node_access_records($node)
 }
 
 /**
+ * Alter permissions for a node before it is written to the database.
+ *
+ * Node access modules establish rules for user access to content. Node access
+ * records are stored in the {node_access} table and define which permissions
+ * are required to access a node. This hook is invoked after node access modules
+ * returned their requirements via hook_node_access_records(); doing so allows
+ * modules to modify the $grants array by reference before it is stored, so
+ * custom or advanced business logic can be applied.
+ *
+ * @see hook_node_access_records()
+ *
+ * Upon viewing, editing or deleting a node, hook_node_grants() builds a
+ * permissions array that is compared against the stored access records. The
+ * user must have one or more matching permissions in order to complete the
+ * requested operation.
+ *
+ * @see hook_node_grants()
+ * @see hook_node_grants_alter()
+ *
+ * @param &$grants
+ *   The $grants array returned by hook_node_access_records().
+ * @param $node
+ *   The node for which the grants were acquired.
+ *
+ * The preferred use of this hook is in a module that bridges multiple node
+ * access modules with a configurable behavior, as shown in the example
+ * by the variable 'example_preview_terms'. This variable would
+ * be a configuration setting for your module.
+ *
+ * @ingroup node_access
+ */
+function hook_node_access_records_alter(&$grants, $node) {
+  // Our module allows editors to tag specific articles as 'preview'
+  // content using the taxonomy system. If the node being saved
+  // contains one of the preview terms defined in our variable
+  // 'example_preview_terms', then only our grants are retained,
+  // and other grants are removed. Doing so ensures that our rules
+  // are enforced no matter what priority other grants are given.
+  $preview = variable_get('example_preview_terms', array());
+  // Check to see if we have enabled complex behavior.
+  if (!empty($preview)) {
+    foreach ($preview as $term_id) {
+      if (isset($node->taxonomy[$term_id])) {
+        // Our module grants are set in $grants['example'].
+        $temp = $grants['example'];
+        // Now remove all module grants but our own.
+        $grants = array('example' => $temp);
+        // No need to check additonal terms.
+        break;
+      }
+     }
+  }
+}
+
+/**
+ * Alter user access rules when trying to view, edit or delete a node.
+ *
+ * Node access modules establish rules for user access to content.
+ * hook_node_grants() builds a permissions array for a user to view, edit or
+ * delete nodes by building a $grants array that indicates the permissions
+ * assigned to the user by each node access module. This hook is called to allow
+ * modules to modify the $grants array by reference, so the interaction of
+ * multiple node access modules can be altered or advanced business logic can be
+ * applied.
+ *
+ * @see hook_node_grants()
+ *
+ * The resulting grants are then checked against the records stored in the
+ * {node_access} table to determine if the operation may be completed.
+ *
+ * @see hook_node_access_records()
+ * @see hook_node_access_records_alter()
+ *
+ * @param &$grants
+ *   The $grants array returned by hook_node_grants().
+ * @param $account
+ *   The user account requesting access to content.
+ * @param $op
+ *   The operation being performed, 'view', 'update' or 'delete'.
+ *
+ * Developers may use this hook to either add additional grants to a user
+ * or to remove existing grants. These rules are typically based on either the
+ * permissions assigned to a user role, or specific attributes of a user
+ * account.
+ *
+ * @ingroup node_access
+ */
+function hook_node_grants_alter(&$grants, $account, $op) {
+  // Our sample module never allows certain roles to edit or delete
+  // content. Since some other node access modules might allow this
+  // permission, we expressly remove it by returning an empty $grants
+  // array for roles specified in our variable setting.
+
+  // Get our list of banned roles.
+  $restricted = variable_get('example_restricted_roles', array());
+  
+  if ($op != 'view' && !empty($restricted)) {
+    // Now check the roles for this account against the restrictions.
+    foreach ($restricted as $role_id) {
+      if (isset($user->roles[$role_id])) {
+        $grants = array();
+      }
+    }
+  }
+}
+
+/**
  * Add mass node operations.
  *
  * This hook enables modules to inject custom operations into the mass operations
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1048
diff -u -p -r1.1048 node.module
--- modules/node/node.module	16 May 2009 15:23:16 -0000	1.1048
+++ modules/node/node.module	17 May 2009 20:53:31 -0000
@@ -2428,6 +2428,10 @@ function _node_access_where_sql($op = 'v
  * access module should implement hook_node_grants() to provide a grant
  * list for the user.
  *
+ * After the default grants have been loaded, we allow modules to alter
+ * the grants array by reference. This hook allows for complex business
+ * logic to be applied when integrating multiple node access modules.
+ *
  * @param $op
  *   The operation that the user is trying to perform.
  * @param $account
@@ -2443,7 +2447,12 @@ function node_access_grants($op, $accoun
     $account = $GLOBALS['user'];
   }
 
-  return array_merge(array('all' => array(0)), module_invoke_all('node_grants', $account, $op));
+  // Fetch node access grants from other modules.
+  $grants = module_invoke_all('node_grants', $account, $op);
+  // Allow modules to alter the assigned grants.
+  drupal_alter('node_grants', $grants, $account, $op);
+
+  return array_merge(array('all' => array(0)), $grants);
 }
 
 /**
@@ -2467,7 +2476,7 @@ function node_access_view_all_nodes() {
       $grants = db_or();
       foreach (node_access_grants('view') as $realm => $gids) {
         foreach ($gids as $gid) {
-          $or->condition(db_and()
+          $grants->condition(db_and()
             ->condition('gid', $gid)
             ->condition('realm', $realm)
           );
@@ -2539,6 +2548,12 @@ function node_query_node_access_alter(Qu
  * called by modules whenever something other than a node_save causes
  * the permissions on a node to change.
  *
+ * After the default grants have been loaded, we allow modules to alter
+ * the grants array by reference. This hook allows for complex business
+ * logic to be applied when integrating multiple node access modules.
+ *
+ * @see hook_node_access_records()
+ *
  * This function is the only function that should write to the node_access
  * table.
  *
@@ -2547,11 +2562,14 @@ function node_query_node_access_alter(Qu
  */
 function node_access_acquire_grants($node) {
   $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)) {
     $grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0);
   }
   else {
-    // retain grants by highest priority
+    // Retain grants by highest priority.
     $grant_by_priority = array();
     foreach ($grants as $g) {
       $grant_by_priority[intval($g['priority'])][] = $g;
Index: modules/node/node.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.test,v
retrieving revision 1.24
diff -u -p -r1.24 node.test
--- modules/node/node.test	6 May 2009 19:56:21 -0000	1.24
+++ modules/node/node.test	17 May 2009 20:53:33 -0000
@@ -685,6 +685,70 @@ class NodeRSSContentTestCase extends Dru
 }
 
 /**
+ * Test case to verify hook_node_access_records_alter functionality.
+ */
+class NodeAccessRecordsAlterUnitTest extends DrupalWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => t('Node access records alter'),
+      'description' => t('Test hook_node_access_records_alter when acquiring grants.'),
+      'group' => t('Node'),
+    );
+  }
+
+  function setUp() {
+    // Enable dummy module that implements hook_node_grants, hook_node_access_records, 
+    // hook_node_grants_alter and hook_node_access_records_alter.
+    parent::setUp('node_test');
+  }
+
+  /**
+   * Create a node and test the creation of node access rules.
+   */
+  function testGrantAlter() {
+    // Create an article node.
+    $node1 = $this->drupalCreateNode(array('type' => 'article'));
+    $this->assertTrue(node_load($node1->nid), t('Article node created.'));
+
+    // Check to see if grants added by node_test_node_access_records made it in.
+    $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = %d', $node1->nid)->fetchAll();
+    $this->assertEqual(count($records), 1, t('Returned the correct number of rows.'));
+    $this->assertEqual($records[0]->realm, 'test_article_realm', t('Grant with article_realm acquired for node without alteration.'));
+    $this->assertEqual($records[0]->gid, 1, t('Grant with gid = 1 acquired for node without alteration.'));
+
+    // Create an unpromoted page node.
+    $node2 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 0));
+    $this->assertTrue(node_load($node1->nid), t('Unpromoted page node created.'));
+
+    // Check to see if grants added by node_test_node_access_records made it in.
+    $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = %d', $node2->nid)->fetchAll();
+    $this->assertEqual(count($records), 1, t('Returned the correct number of rows.'));
+    $this->assertEqual($records[0]->realm, 'test_page_realm', t('Grant with page_realm acquired for node without alteration.'));
+    $this->assertEqual($records[0]->gid, 1, t('Grant with gid = 1 acquired for node without alteration.'));
+    
+    // Create a promoted page node.
+    $node3 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 1));
+    $this->assertTrue(node_load($node3->nid), t('Promoted page node created.'));
+
+    // Check to see if grant added by node_test_node_access_records was altered by node_test_node_access_records_alter.
+    $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = %d', $node3->nid)->fetchAll();
+    $this->assertEqual(count($records), 1, t('Returned the correct number of rows.'));
+    $this->assertEqual($records[0]->realm, 'test_alter_realm', t('Altered grant with alter_realm acquired for node.'));
+    $this->assertEqual($records[0]->gid, 2, t('Altered grant with gid = 2 acquired for node.'));
+
+    // Check to see if we can alter grants with hook_node_grants_alter().
+    $operations = array('view', 'update', 'delete');
+    // Create a user that is allowed to access content.
+    $web_user = $this->drupalCreateUser(array('access content'));
+    foreach ($operations as $op) {
+      $grants = node_test_node_grants($op, $web_user);
+      $altered_grants = drupal_alter($grants, $web_user, $op);
+      $this->assertNotEqual($grants, $altered_grants, t('Altered the %op grant for a user.', array('%op' => $op)));
+    }
+  }
+}
+
+/**
  * Test case to check node save related functionality, including import-save
  */
 class NodeSaveTestCase extends DrupalWebTestCase {
Index: modules/node/tests/node_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/tests/node_test.module,v
retrieving revision 1.2
diff -u -p -r1.2 node_test.module
--- modules/node/tests/node_test.module	3 May 2009 10:11:34 -0000	1.2
+++ modules/node/tests/node_test.module	17 May 2009 20:53:33 -0000
@@ -33,3 +33,70 @@ function node_test_node_view($node, $tea
     );
   }
 }
+
+/**
+ * Implementation of hook_node_grants().
+ */
+function node_test_node_grants($account, $op) {
+  // Give everyone full grants so we don't break other node tests.
+  // Our node access tests asserts three realms of access.
+  // @see testGrantAlter()
+  return array(
+    'test_article_realm' => array(1),
+    'test_page_realm' => array(1),
+    'test_alter_realm' => array(2),
+  );
+}
+
+/**
+ * Implementation of hook_node_access_records().
+ */
+function node_test_node_access_records($node) {
+  $grants = array();
+  if ($node->type == 'article') {
+    // Create grant in arbitrary article_realm for article nodes.
+    $grants[] = array(
+      'realm' => 'test_article_realm',
+      'gid' => 1,
+      'grant_view' => 1,
+      'grant_update' => 0,
+      'grant_delete' => 0,
+      'priority' => 0,
+    );
+  }
+  elseif ($node->type == 'page') {
+    // Create grant in arbitrary page_realm for page nodes.
+    $grants[] = array(
+      'realm' => 'test_page_realm',
+      'gid' => 1,
+      'grant_view' => 1,
+      'grant_update' => 0,
+      'grant_delete' => 0,
+      'priority' => 0,
+    );
+  }
+  return $grants;
+}
+
+/**
+ * Implementation of hook_node_access_records_alter().
+ */
+function node_test_node_access_records_alter(&$grants, $node) {
+  if (!empty($grants)) {
+    foreach ($grants as $key => $grant) {
+      // Alter grant from test_page_realm to test_alter_realm and modify the gid.
+      if ($grant['realm'] == 'test_page_realm' && $node->promote) {
+        $grants[$key]['realm'] = 'test_alter_realm';
+        $grants[$key]['gid'] = 2;
+      }
+    }
+  }
+}
+
+/**
+ * Implementation of hook_node_grants_alter().
+ */
+function node_test_node_grants_alter(&$grants, $account, $op) {
+  // Return an empty array of grants to prove that we can alter by reference.
+  $grants = array();
+}
