Using field level permissions to allow 'view in list' but not 'view full node' in Drupal

tanc's picture

As Drupal’s view permission doesn’t differentiate between a view in a list (output by Views) and a full node view it can be hard to meet the requirements of some clients who want to ‘tease’ their users by showing lists of titles without giving full access to the node.

Stella Power wrote about one way to do this which made me write up my method in this blog post.

What I’ve done is used field level permissions in CCK to do my access checks in hook_field_access(). I have a quite complex requirement from the client in that they want certain items (content type called ‘file’) to be listed in some Views listings but for certain users (based on OG membership) to not be able to view the full node. As Drupal basically doesn’t differentiate between ‘view’ and ‘view in list’ I had to implement some permission checking in hook_field_access() and use the result in the rendering of the content.

Here is some access checking code I use to determine if a user is a member of an organic group which has a post in the group that links to this node:

/**
 * Implementation of hook_field_access().
 *
 * @see content_access()
 */
function custom_perms_field_access($op, $field, $account = NULL, $node = NULL) {
  // We do not want to check anything other than 'view' access.
  if ($op == 'view') {
    // We only want to check file content types
    if ($node->type == 'file') {
      // We only want to run this check on file field
      if ($field['field_name'] == 'field_file_file') {
        // in this example there is a field called 'privacy'
        // which is a cck widget allowing the author to choose
        // the privacy options of this node.
        $privacy = $node->field_file_privacy[0]['value'];
        if ($privacy == 'committee') {
          // do all your custom permission checking here and
          // return a TRUE or FALSE
          return FALSE; // just an example
        }
        else if ($privacy == 'public') {
          // do no permission checks
          return TRUE;
        }
      }
    }
  }
}

For the results of a faceted search (see lightboxed image below) I use this to check the field permissions:

function custom_perms_node_perm_check($node) {
  module_load_include('inc', 'content', 'includes/content.crud');
  if ($fields = content_field_instance_read(array('type_name' => $node->type))) {
    foreach ($fields as $field) {
      $checks[] = content_access('view', $field, NULL, $node);
    }
  }
  if (is_array($checks) && in_array(FALSE, $checks)) {
    // we have a permission denied on a field so return FALSE.
    return FALSE;
  }
  else {
    // permissions are all positive, we have access.
    return TRUE;
  }
}

That checks all the fields for permission denied and returns TRUE or FALSE depending on the outcome. Then when I render the results I can add the necessary class to the html with something like this in theme function:

    // part of the theme function that renders a single search result:
    // do some access checking
    if (custom_perms_node_perm_check($item['node']) == FALSE) {
      $attr = 'locked';
      $output = ' <dt class="title ' . $attr . $fileclass . '">'. check_plain($item['title']) . '</dt>';
    }
    else {
      $attr = 'unlocked';
      $output = ' <dt class="title ' . $attr . $fileclass . '"><a href="'. check_url($item['link']) . '">' . check_plain($item['title']) . '</a></dt>';
    }

This then allows me to theme the Views list like this (notice the greyed out item. This user can’t click through to the full node):

screenshot demonstrating example

The second item in the list in the image above has failed an access check for that user (file is marked as ‘committee only’ and user is not a member of a committee that has linked to this file).

This is still a work in progress and any advice or ideas about how to better implement this are always welcome.