Taxonomy Access Control Change Default Behavior From Taxonomy Access Control Documentation


  Users with multiple user roles: Allow/Ignore/Deny options are
  interpreted only within one user role. When a user belongs to multiple
  user roles, then the user gets access if any of his/her user roles
  have the access granted.
  In this case, permissions for the given user
  are calculated so that the permissions of ALL of his user roles are
  "OR-ed" together, which means that Allow in one role will take
  precedence over Deny in the other. This is different from how node
  access permissions (for multi-category nodes) are handled within one
  user role, as noted above.


This is a default behavior of the TAC. But is there a way to change this behavior so that the it is AND-ed instead of OR-ed or in other words a Deny in any of the roles would deny access to the content even if the other role has Allow permission. 

The use case is as follows. Let us Assume there are Two Taxonomy called Grade and Subject. And all the contents are tagged with with both the terms. 

A student logged in with a role of Grade 1 and Science should only be able to see the contents tagged with both Grade 1 and Science

The way to go about it should ideally be set allow permission to the Grade 1 role to access all with the grade 1 Term and deny for rest of the roles in the grade category such as grade 2 grade 3 etc.

Similarly Set allow permission for Science and Deny for the rest of the subjects. 

With the current setup if you give the role Grade 1 and Science. the student would be able to see all the subjects in Grade 1 and all Grades in science. 

Is there a way to accomplish this.
One option I found was to use the hook_node_access this function has three inputs 


$node The Node object on which the operation is done
$op The Operation being done on the Node could have three values view, update or delete 
$account the user object. 


The taxonomy access control stores the access information in taxonomy_access_term which has the following fields tid - taxonomy term ID, rid - the role ID, grant_view, grant_update, grant_delete, grant_delete, grant_create, grant_list

All the grant columns can have the following values


0=>Ignore 
1=>Allow 
2=>Deny.


Used the following function to override the hook_node_access

    function custom_node_access($node, $op, $account) {

        if ($op == 'view')
        {    
        // Get all the tids into the $tids array
            $tids = array();
            $tids[] = $node-&gt;field_class[$node-&gt;language][0]['tid'];
            $tids[] = $node-&gt;field_subject[$node-&gt;language][0]['tid'];

        // Get all the role ids into the $roles variable
            $roles = array_keys($account-&gt;roles);

        // Create the query to get the `grant_view` with a value 1 if all the   
        // roles have grant permission then the result would have 2 records. 

            $query = db_select('taxonomy_access_term' , 't');
            $query-&gt;condition('t.tid', $tids, 'IN');
            $query-&gt;condition('t.rid', $roles, 'IN');
            $query-&gt;condition('t.grant_view', 1);
            $query-&gt;fields('t', array('grant_view'));
            $result = $query-&gt;execute();

        // if the result is less than two then we deny access other wise we   
        // ignore it so that let the node_access control take over. 

        if ($result-&gt;rowCount() &lt; 2)
        {
                return NODE_ACCESS_DENY;
            }
      }
      return NODE_ACCESS_IGNORE;
    }


This works well node displays. But the problem would be that since the hook_node_access is bypassed by the list queries this will not work, Might have to add the node_access tag to all the queries which would become a performance issue. 

The second method was to use the hook_node_access_records_alter and hook_node_grants_alter

the hook_node_access_records_alter edits the grant realms before writing it to the node_access table when creating or editing a node. 

function custom_node_access_records_alter(&amp;$grants, $node) {

  // Store the grants to a temp variable


  // create a new varible to store the new gid
  $gid = "";

  // Loop through the grants  
  foreach ($temp as $key =&gt; $grant)
  {
    if ($grant['grant_view'])
    {
        // if the grant_view is set to 1 then append the gid 
        // after adding a padding 0 to the left so that we get 
        // a 2 digit number
        $gid .= str_pad($grant['gid'], 2, "0", STR_PAD_LEFT); 


    }

  } 

  // After completing this loop the new gid would look something like 040506
  // which means the role id 4, 5, and 6 has access to the page. and since we have 
  // added it together we can make sure that it is individualy not valid 
  // has to work together. 


  // Rewrite the grant values with the new gid.  
  if($gid_view != "")
  {
    $grants = array();
    $grants[0] = array();
    $grants[0]['realm'] = 'taxonomy_access_role';
    $grants[0]['gid'] = (int)$gid;
    $grants[0]['grant_view'] = 1;
    $grants[0]['grant_update'] = 0;
    $grants[0]['grant_delete'] = 0;
    $grants[0]['priority'] = 1;

  }

}


The next step would be to override the hook_node_grants_alter function.

function custom_node_grants_alter(&amp;$grants, $account, $op) {

  // we only want it to work with the view. 
  if($op == 'view')
  {    
    $gid = '';


    // loop through the roles of the user
    foreach($account-&gt;roles as $rid =&gt; $role)
    {
        // we do not want the role id 2 as it is the id of the authenticated user. 
        if ($rid &gt;2)
        {
            // generate the gid we created in the hook_node_access_records_alter
            // so if the user has all the roles we set to the node then we would 
            // get the same gid other wise the gid would be different and access 
            // would be denied
            $gid .= str_pad($rid, 2, "0", STR_PAD_LEFT); 
        }
    }

    // Over write the grants array. 
    $grants = array();

    $grants['taxonomy_access_role'][0] = (int)$gid;


  }
}


Note : There are few assumptions made here


We only have the taxonomy access control module as access control module other than the core access control system. If there are more then we have to check and make changes to the function accordingly.
We are only checking for the view grants and not bothered about the rest of the grants. 
Maximum number of roles that a user would have is 5. The gid is of the type int so it can accommodate a maximum value of 10 digit. 
Each role would have only one allow per taxonomy term and rest of it would be deny.