1

In some of mine content types I have checkbox field "Content protect". If checked that node should be visible only to registered user. If not checked - visible to everyone:

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

  // If $node is a string, the node has not yet been created. We don't care
  // about that case.
  if (is_string($node)) {
    return new AccessResultNeutral();
  }
  if ($node->hasField('field_content_protect')) {
    $protected = ($node->field_content_protect->value == 1) ? TRUE : FALSE;
    $userId = $account->id();
    if ($protected && $userId == 0) {
      return new AccessResultForbidden();
    }
  }
  return new AccessResultNeutral();
}

And this works well when I try to access node directly. If it's protected anonymous users can't access it.

Now, I have a view that lists some of those nodes. I want to apply same logic and to exclude protected nodes for anonymous users. So in my view I added filter:

Content access: Access

But it's not working as expected. For authenticated users content is there, but for anonymous users simply whole block content is missing?!? Nodes that don't have this "Content protect" field checked are also not listed?!? Why is that? This filter has no options/settings.

I do have other option, to duplicate view display, check directly the field and display first or second block view depending on user, but this solution with Content access filter looks more elegant.

0

1 Answer 1

3
+50

Lists like views will not be passed through hook_node_access(), as this would create too much overhead. Views does a query on the database to get the results. hook_node_access() is run in PHP, so these won't work (easily/well/efficently) together.

To deal with lists, you will need to create a grant system, using hook_node_access_records() and hook_node_grants(). This system writes to the {node_access} table in the database, and when a list is created, the database query is cross-referenced with that table.

hook_node_access_records() is called when a node is saved. Modules that implement this hook can set up various states by which the node is allowed to be accessed.

hook_node_grants() is then called when the node is viewed/updated/deleted. A set of potential grants is created based on the current environment, and these are compared with the states created in hook_node_access_records(). If there is a match, access is provided.

In your case, hook_node_access_records() should create a state whereby if the node has the field field_content_protect, then a state should be created upon which view access can be either granted or denied in hook_node_grants(). Example:

function MODULE_node_access_records(\Drupal\node\NodeInterface $node) {
  if ($node->hasField('field_content_protect')) {
    $grants = [];

    $grants[] = [
      'realm' => 'MODULE_protect_content',
      // This is set to 1. In hook_node_grants(), 
      // A value will be created based on the user's
      // logged in state, and compared to this value.
      'gid' => 1,
      'grant_view' => 1,
      'grant_update' => 0,
      'grant_delete' => 0,
    ];

    return $grants;
  }
}

The above sets up a state, whereby if a grant is created for the realm MODULE_protect_content that has a value of 1, the user should be granted view access. The next thing is to implement hook_node_grants() and create a grant based on the current user's status.

function MODULE_node_grants() {
  $current_user = \Drupal::currentUser();
  $grants = [];

  // This will set a value of 0 (zero) or 1 (one)
  // for the realm MODULE_protect_content. If the
  // value is 1 (authenticated user) the user will
  // be provided view access to the node. Note 
  // that an array of values must be passed for
  // realms.
  $status = (int) $current_user->isAuthenticated();
  $grants['MODULE_protect_content'] = [$status];

  return $grants.
}

For the above code, a grant is created based on the logged in status of the current user. If they are logged in, $status will equal 1, which is compared with the state provided in hook_node_access_records() providing access to the user.

You could actually alter the above hook_node_grants() to only return a grant if the user is logged in. I'm not sure which one would be more efficient. I suspect the code below may be:

function MODULE_node_grants() {
  $current_user = \Drupal::currentUser();

  if ($status = (int) $current_user->isAuthenticated()) {
    $grants = [];
    // This will set a value of 1 (one)
    // for the realm MODULE_protect_content, which
    // matches the state created in
    // hook_node_access_records(). Note that
    // an array of values must be passed for 
    // realms.
    $grants['MODULE_protect_content'] = [$status];

    return $grants.
  }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.