You are here

nodeaccess_userreference.module in Node access user reference 7.3

The Node access user reference module.

File

nodeaccess_userreference.module
View source
<?php

/**
 * @file
 * The Node access user reference module.
 */

/**
 * Implements hook_node_grants().
 */
function nodeaccess_userreference_node_grants($account, $op) {
  $grants = array();
  $grants['nodeaccess_userreference'][] = $account->uid;
  $grants['nodeaccess_userreference_author'][] = $account->uid;
  $grants['nodeaccess_userreference_all'][] = 1;
  return $grants;
}

/**
 * Implements hook_form-FORM-ID_alter().
 */
function nodeaccess_userreference_form_field_ui_field_edit_form_alter(&$form, $form_state) {
  if (($form['#field']['type'] == 'entityreference' && $form['#field']['settings']['target_type'] == 'user' || $form['#field']['type'] == 'user_reference') && $form['#instance']['entity_type'] == 'node') {
    $data = isset($form['#instance']['settings']['nodeaccess_userreference']) ? $form['#instance']['settings']['nodeaccess_userreference'] : array();
    $current = nodeaccess_userreference_field_settings($form['#instance']['bundle'], $form['#instance']['field_name']);
    $form['instance']['settings']['nodeaccess_userreference'] = array(
      '#type' => 'fieldset',
      '#title' => t('Node access user reference'),
      '#tree' => TRUE,
      '#collapsible' => TRUE,
      '#collapsed' => empty($current),
    );
    $referenced_states = array(
      array(
        '[name="instance[settings][nodeaccess_userreference][referenced][view]"]' => array(
          'checked' => TRUE,
        ),
      ),
      array(
        '[name="instance[settings][nodeaccess_userreference][referenced][update]"]' => array(
          'checked' => TRUE,
        ),
      ),
      array(
        '[name="instance[settings][nodeaccess_userreference][referenced][delete]"]' => array(
          'checked' => TRUE,
        ),
      ),
    );
    $form['instance']['settings']['nodeaccess_userreference']['referenced'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Grants for referenced users on the node'),
      '#default_value' => isset($data['referenced']) ? $data['referenced'] : array(),
      '#options' => array(
        'view' => t('View'),
        'update' => t('Update'),
        'delete' => t('Delete'),
        'deny_view' => t('Deny view'),
        'deny_update' => t('Deny update'),
        'deny_delete' => t('Deny delete'),
      ),
      '#description' => t('These content access permissions will be granted to users referenced in the field.  These permissions apply to the node on which the field values are set.  Do not check the <em>deny</em> options unless you want to forcibly deny access to referenced users.'),
    );
    $form['instance']['settings']['nodeaccess_userreference']['referenced_published'] = array(
      '#type' => 'select',
      '#title' => t('Unpublished nodes'),
      '#title_display' => 'none',
      '#default_value' => isset($data['referenced_published']) ? $data['referenced_published'] : 0,
      '#options' => array(
        0 => t('Give these grants for published nodes only'),
        1 => t('Give these grants for published and unpublished nodes'),
      ),
    );
    $form['instance']['settings']['nodeaccess_userreference']['create'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Grants for referenced users to create content'),
      '#default_value' => isset($data['create']) ? $data['create'] : array(),
      '#options' => node_type_get_names(),
      '#description' => t('These content access permissions will be granted to users referenced in the field.  These permissions apply to the content types selected.'),
    );
    $form['instance']['settings']['nodeaccess_userreference']['author'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Grants for author'),
      '#default_value' => isset($data['author']) ? $data['author'] : array(),
      '#options' => array(
        'view' => t('View'),
        'update' => t('Update'),
        'delete' => t('Delete'),
      ),
      '#description' => t('These content access permissions will be granted to the authors of nodes.'),
      '#states' => array(
        'visible' => $referenced_states,
      ),
    );
    $form['instance']['settings']['nodeaccess_userreference']['author_published'] = array(
      '#type' => 'select',
      '#title' => t('Unpublished nodes'),
      '#title_display' => 'none',
      '#default_value' => isset($data['author_published']) ? $data['author_published'] : 0,
      '#options' => array(
        0 => t('Give these grants for published nodes only'),
        1 => t('Give these grants for published and unpublished nodes'),
      ),
      '#states' => array(
        'visible' => $referenced_states,
      ),
    );
    $form['instance']['settings']['nodeaccess_userreference']['all'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Grants for all users'),
      '#default_value' => isset($data['all']) ? $data['all'] : array(),
      '#options' => array(
        'view' => t('View'),
      ),
      '#description' => t('These content access permissions will be granted to all users including %anon.', array(
        '%anon' => format_username(drupal_anonymous_user()),
      )),
      '#states' => array(
        'visible' => $referenced_states,
      ),
    );
    $form['instance']['settings']['nodeaccess_userreference']['all_published'] = array(
      '#type' => 'select',
      '#title' => t('Unpublished nodes'),
      '#title_display' => 'none',
      '#default_value' => isset($data['all_published']) ? $data['all_published'] : 0,
      '#options' => array(
        0 => t('Give these grants for published nodes only'),
        1 => t('Give these grants for published and unpublished nodes'),
      ),
      '#states' => array(
        'visible' => $referenced_states,
      ),
    );
    $form['instance']['settings']['nodeaccess_userreference']['unused'] = array(
      '#type' => 'radios',
      '#title' => t('When to set grants'),
      '#default_value' => isset($data['unused']) ? $data['unused'] : 0,
      '#options' => array(
        0 => t('When the user reference field is in use'),
        1 => t('Always'),
      ),
      '#description' => t('Determines whether to set grants when the field is not in use.'),
      '#states' => array(
        'visible' => $referenced_states,
      ),
    );
    if (module_exists('views')) {
      $form['instance']['settings']['nodeaccess_userreference']['views'] = array(
        '#type' => 'fieldset',
        '#title' => t('Views - Nodes to use'),
        '#collapsible' => TRUE,
        '#collapsed' => !isset($data['views']['view']) || $data['views']['view'] == '',
        '#description' => t('Node access user reference looks at user reference values from all nodes in ' . 'this content type using this user reference field.  You can, ' . 'however, choose to use values from only certain nodes by creating a views ' . 'display of nodes that match your criteria, and then selecting it ' . 'here.'),
      );
      $form['instance']['settings']['nodeaccess_userreference']['views']['view'] = array(
        '#type' => 'select',
        '#title' => t('View'),
        '#default_value' => isset($data['views']['view']) ? $data['views']['view'] : '',
        '#options' => nodeaccess_userreference_views_displays('node'),
      );
      $form['instance']['settings']['nodeaccess_userreference']['views']['view_args'] = array(
        '#type' => 'textfield',
        '#title' => t('View arguments'),
        '#default_value' => isset($data['views']['view_args']) ? $data['views']['view_args'] : '',
        '#description' => t('Provide a comma separated list of arguments to pass to the view.'),
      );
    }
    else {
      $form['instance']['settings']['nodeaccess_userreference']['views']['view'] = array(
        '#type' => 'value',
        '#value' => isset($data['views']['view']) ? $data['views']['view'] : '',
      );
      $form['instance']['settings']['nodeaccess_userreference']['views']['view_args'] = array(
        '#type' => 'value',
        '#value' => isset($data['views']['view_args']) ? $data['views']['view_args'] : '',
      );
    }
    $form['instance']['settings']['nodeaccess_userreference']['priority'] = array(
      '#type' => 'weight',
      '#title' => t('Priority'),
      '#default_value' => isset($data['priority']) ? $data['priority'] : 0,
      '#description' => t('It is recommended to always leave this set to 0.'),
      '#states' => array(
        'visible' => $referenced_states,
      ),
    );
  }
}

/**
 * Get an array of node views for use in select forms.
 */
function nodeaccess_userreference_views_displays($base_table) {
  $views = array(
    '' => '<' . t('none') . '>',
  );
  $all_views = views_get_all_views();
  foreach ($all_views as $view_name => $view) {

    // Only $base_table views that have fields will work for our purpose.
    if ($view->base_table == $base_table) {
      foreach ((array) $view->display as $display_key => $display) {
        $id = $view_name . ':' . $display_key;

        // Get display title.
        $display_title = nodeaccess_userreference_views_display_title($view_name, $view, $display_key);

        // Append $id to the title for disambiguation in lists.
        $display_title .= ' [' . $id . ']';
        if ($view->type == 'Default') {
          $views[t('Default views')][$id] = $display_title;
        }
        else {
          $views[t('Existing views')][$id] = $display_title;
        }
      }
    }
  }
  return $views;
}

/**
 * Set the display title for a views display.
 */
function nodeaccess_userreference_views_display_title($view_name, $view, $display_name) {
  $view
    ->set_display($display_name);
  $display_title = $view
    ->get_title();
  if (!$display_title) {

    // No title, we have to construct a title.
    $display_title = ucfirst($view_name) . ' ' . strtolower($view->display[$display_name]->display_title);
  }
  return $display_title;
}

/**
 * Implements hook_field_update_instance().
 */
function nodeaccess_userreference_field_update_instance($instance, $prior_instance) {
  $field_info = field_info_field($instance['field_name']);
  if (($field_info['type'] == 'entityreference' && $field_info['settings']['target_type'] == 'user' || $field_info['type'] == 'user_reference') && $instance['entity_type'] == 'node') {
    $old_settings = isset($prior_instance['settings']['nodeaccess_userreference']) ? nodeaccess_userreference_reduce_variable($prior_instance['settings']['nodeaccess_userreference']) : array();
    $new_settings = isset($instance['settings']['nodeaccess_userreference']) ? nodeaccess_userreference_reduce_variable($instance['settings']['nodeaccess_userreference']) : array();
    if ($old_settings != $new_settings) {
      nodeaccess_userreference_field_settings($instance['bundle'], $instance['field_name'], $new_settings);
      $nodes = db_query("SELECT 1 FROM {node} WHERE type = :type", array(
        ':type' => $instance['bundle'],
      ))
        ->fetchField();
      if ($nodes) {

        // Because the field settings have changed we should prompt for a full rebuild.
        node_access_needs_rebuild(TRUE);
      }
    }
  }
}

/**
 * Add node grants in a way that prevents overriding previous iterations.
 *
 * @param &$grants
 *  The grants array where the grant will be added.
 * @param $realm
 *  The realm of this grant.
 * @param $gid
 *  The grant ID.
 * @param $priority
 *  The grant priority.
 * @param $settings
 *  An settings array of boolean equivalent values with keys 'view', 'edit',
 *  and 'delete'.
 */
function nodeaccess_userreference_add_grant(&$grants, $realm, $gid, $priority, $settings) {
  $key = $realm . $gid;
  if (!isset($grants[$key])) {

    // Setup the record.
    $grants[$key] = array(
      'realm' => $realm,
      'gid' => $gid,
      'priority' => $priority,
      'grant_view' => 0,
      'grant_update' => 0,
      'grant_delete' => 0,
    );
  }

  // Add the grants needed, so as not to override previous iterations.
  if (!empty($settings['view'])) {
    $grants[$key]['grant_view'] = 1;
  }
  if (!empty($settings['update'])) {
    $grants[$key]['grant_update'] = 1;
  }
  if (!empty($settings['delete'])) {
    $grants[$key]['grant_delete'] = 1;
  }

  // Increase the priority if needed.
  if ($priority > $grants[$key]['priority']) {
    $grants[$key]['priority'] = $priority;
  }
}

/**
 * Implements hook_node_access_records().
 */
function nodeaccess_userreference_node_access_records($node) {
  $grants = array();
  $field_data = nodeaccess_userreference_field_settings($node->type);
  if (!empty($field_data)) {
    foreach ($field_data as $field_name => &$data) {
      if (!empty($data) && (empty($data['views']['view']) || nodeaccess_userreference_node_in_field_view($data, array(
        $node->nid,
      )))) {
        if (!isset($data['priority'])) {
          $data['priority'] = 0;
        }
        if (!empty($node->status) || !empty($data['referenced_published'])) {

          // Add referenced user grants.
          $items = field_get_items('node', $node, $field_name);
          if (!empty($items)) {
            foreach ($items as &$user_reference) {
              $uid = NULL;

              // user_reference
              if (isset($user_reference['uid'])) {
                $uid = $user_reference['uid'];
              }
              elseif (isset($user_reference['target_id'])) {
                $uid = $user_reference['target_id'];
              }
              if ($uid) {
                nodeaccess_userreference_add_grant($grants, 'nodeaccess_userreference', $uid, $data['priority'], $data['referenced']);
              }
            }
          }
        }
        if (!empty($data['unused']) && isset($data['referenced'])) {

          // Add a dummy grant for user 1 to block other users' access.
          nodeaccess_userreference_add_grant($grants, 'nodeaccess_userreference', 1, $data['priority'], $data['referenced']);
        }

        // If there are grants set, also add the author and view-all grants.
        // These will fire for each non-empty nodeaccess_userreference field,
        // but redundant calls will be correctly handled by the helper function:
        // nodeaccess_userreference_add_grant().
        if (!empty($grants)) {
          if (!empty($data['author']) && (!empty($node->status) || !empty($data['author_published']))) {

            // Add author grants.
            if ($node->uid > 0) {
              nodeaccess_userreference_add_grant($grants, 'nodeaccess_userreference_author', $node->uid, $data['priority'], $data['author']);
            }
          }
          if (!empty($data['all']) && (!empty($node->status) || !empty($data['all_published']))) {

            // Add all grants.
            nodeaccess_userreference_add_grant($grants, 'nodeaccess_userreference_all', 1, $data['priority'], $data['all']);
          }
        }
      }
    }
  }
  return $grants;
}

/**
 * Implements hook_node_access().
 */
function nodeaccess_userreference_node_access($node, $op, $account) {
  if ($op != 'create') {

    // 'deny' functionality.
    $field_data = nodeaccess_userreference_field_settings($node->type);
    if (!empty($field_data)) {
      foreach ($field_data as $field_name => &$data) {
        if (!empty($data->referenced['deny_' . $op]) && (empty($data['views']['view']) || nodeaccess_userreference_node_in_field_view($data, array(
          $node->nid,
        )))) {

          // Add referenced user grants.
          $items = field_get_items('node', $node, $field_name);
          if (!empty($items)) {
            foreach ($items as &$user_reference) {
              if ($user_reference['uid'] == $account->uid) {
                return NODE_ACCESS_DENY;
              }
            }
          }
        }
      }
    }
  }
  else {

    // $op == 'create'.
    if (is_object($node)) {
      $node = $node->type;
    }

    // Get list of content types.
    $types = node_type_get_types();
    foreach ($types as $type) {
      $bundle = $type->type;

      // Get nodeaccess_userreference settings for every content type.
      $field_data = nodeaccess_userreference_field_settings($bundle);
      if (!empty($field_data)) {

        // Content type has nodeaccess_userreference field.
        foreach ($field_data as $field_name => $data) {
          if (!empty($data['create'][$node])) {

            // nodeaccess_userreference provides "create" grant for the content type we are checking access on.
            $field_info = field_info_field($field_name);

            // Check field_type so we can support entityreference fields as well as user_reference fields.
            if ($field_info['type'] == 'user_reference') {
              $sql = 'SELECT DISTINCT fd.entity_id ' . 'FROM {field_data_' . $field_name . '} fd ' . 'INNER JOIN {node} n ON n.vid = fd.revision_id ' . 'WHERE fd.' . $field_name . '_uid = :uid ' . 'AND fd.bundle = :bundle';
            }
            elseif ($field_info['type'] == 'entityreference') {
              $sql = 'SELECT DISTINCT fd.entity_id ' . 'FROM {field_data_' . $field_name . '} fd ' . 'INNER JOIN {node} n ON n.vid = fd.revision_id ' . 'WHERE fd.' . $field_name . '_target_id = :uid ' . 'AND fd.bundle = :bundle';
            }
            $args = array(
              ':uid' => $account->uid,
              ':bundle' => $bundle,
            );
            $result = db_query($sql, $args);
            if (empty($data['views']['view']) && $result
              ->rowCount()) {

              // Simple case, a row exists, so we allow.
              return NODE_ACCESS_ALLOW;
            }
            else {

              // They are using views to restrict affected nodes.
              $nids = $result
                ->fetchCol();
              if (!empty($nids) && nodeaccess_userreference_node_in_field_view($data, $nids)) {

                // At least one of the nodes is in the view, so allow.
                return NODE_ACCESS_ALLOW;
              }
            }
          }
        }
      }
    }
  }
  return NODE_ACCESS_IGNORE;
}

/**
 * Set and get nodeaccess userreference field settings.
 *
 * @param $bundle_name
 *   The name of the bundle.
 * @param $field_name
 *   The name of the field.
 * @param $variable
 *   If set will update the value of the settings for this field.
 * @return
 *   The stored or updated value of the settings for this field, or array() if no settings found.
 */
function nodeaccess_userreference_field_settings($bundle_name, $field_name = NULL, $variable = NULL) {
  $data = variable_get('nodeaccess_userreference', array());
  if (!is_null($field_name)) {
    if (!is_null($variable)) {
      if (!empty($variable)) {
        $data[$bundle_name][$field_name] = $variable;
      }
      else {

        // Variable is empty.
        unset($data[$bundle_name][$field_name]);

        // Clear empty bundle too.
        if (empty($data[$bundle_name])) {
          unset($data[$bundle_name]);
        }
      }
      variable_set('nodeaccess_userreference', $data);
    }
    if (isset($data[$bundle_name][$field_name])) {
      return $data[$bundle_name][$field_name];
    }
  }
  elseif (isset($data[$bundle_name])) {
    return $data[$bundle_name];
  }

  // No data found
  return array();
}

/**
 * Reduce settings array to the bare minimum.
 *
 * @param $variable
 *  The settings array.
 * @return
 *  The minimum array or FALSE if there is no array left to save.
 */
function nodeaccess_userreference_reduce_variable($variable) {

  // Filter the variable.
  $variable = nodeaccess_userreference_recursive_filter($variable);

  // Create a test array without the volatile keys and filter again.
  $test = $variable;
  unset($test['referenced_published']);
  unset($test['author_published']);
  unset($test['all_published']);
  unset($test['unused']);
  unset($test['priority']);
  unset($test['field_type']);
  $test = nodeaccess_userreference_recursive_filter($test);

  // Remove major areas of the variable if they are empty in the test array.
  foreach ($variable as $key => $value) {
    if (is_array($value) && empty($test[$key])) {
      unset($variable[$key]);
    }
  }

  // If all of these are empty, then the variable can be considered empty.
  if (empty($variable['referenced']) && empty($variable['create'])) {
    return FALSE;
  }
  return $variable;
}

/**
 * Recursively filter an array.
 */
function nodeaccess_userreference_recursive_filter($array) {
  foreach ($array as $key => $value) {
    if (is_array($value)) {
      $array[$key] = nodeaccess_userreference_recursive_filter($value);
    }
  }
  return array_filter($array);
}

/**
 * Implements hook_node_access_explain().
 *
 * This gives the Devel module nice information to display when
 * debugging node grants.
 */
function nodeaccess_userreference_node_access_explain($row) {
  if (in_array($row->realm, array(
    'nodeaccess_userreference',
    'nodeaccess_userreference_author',
    'nodeaccess_userreference_all',
  ))) {
    $ops = array();
    foreach (array(
      'view',
      'update',
      'delete',
    ) as $op) {
      $gop = 'grant_' . $op;
      if (!empty($row->{$gop})) {
        $ops[] = $op;
      }
    }
    if (!empty($ops)) {
      $do = implode('/', $ops);
      switch ($row->realm) {
        case 'nodeaccess_userreference':
          $account = user_load($row->gid);
          return t('Referenced user %name may !do this node', array(
            '%name' => $account->name,
            '!do' => $do,
          ));
        case 'nodeaccess_userreference_author':
          $account = user_load($row->gid);
          return t('Node author %name may !do this node', array(
            '%name' => $account->name,
            '!do' => $do,
          ));
        case 'nodeaccess_userreference_all':
          return t('All users may !do this node', array(
            '!do' => $do,
          ));
      }
    }
  }
}

/**
 * Implements hook_field_delete_instance().
 */
function nodeaccess_userreference_field_delete_instance($instance) {
  nodeaccess_userreference_field_settings($instance['bundle'], $instance['field_name'], FALSE);
}

/**
 * Implements hook_views_api().
 */
function nodeaccess_userreference_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'nodeaccess_userreference') . '/includes',
  );
}

/**
 * Determine if any node falls into the view configured in the field settings.
 *
 * @param $data
 *  The node access user reference settings for the field.
 * @param $nids
 *  Array of node ids.
 * @return
 *  Boolean indicating if the entity is in the view defined by the field config.
 */
function nodeaccess_userreference_node_in_field_view($data, $nids) {
  $view_id = $data['views']['view'];
  list($view_name, $view_display) = explode(':', $view_id);
  if ($view = views_get_view($view_name)) {

    // We add a display, and let it derive from the 'default' display.
    $display = $view
      ->add_display('nodeaccess_userreference_views_plugin_display');
    $view
      ->set_display($display);

    // Get the options from the user supplied display.
    if ($view_display != 'default' && isset($view->display[$view_display]->display_options)) {
      $view->display[$display]->display_options = $view->display[$view_display]->display_options;
    }

    // TODO from merlinofchaos on IRC : arguments using summary view can defeat the style setting.
    // We might also need to check if there's an argument, and set *its* style_plugin as well.
    $view->display_handler
      ->set_option('style_plugin', 'nodeaccess_userreference_views_plugin_style');
    $view->display_handler
      ->set_option('row_plugin', 'fields');

    // Additional options to let node_reference_display::query()
    // narrow the results.
    $options = array(
      'table' => 'node',
      'field_id' => 'nid',
      'ids' => $nids,
    );
    $view->display_handler
      ->set_option('nodeaccess_userreference_options', $options);

    // TODO : for consistency, a fair amount of what's below
    // should be moved to node_reference_display
    // Limit to a single result.
    $view->display_handler
      ->set_option('items_per_page', 1);

    // Get arguments for the view.
    if (!empty($data['views']['view_args'])) {
      $view_args = array_map('trim', explode(',', $data['views']['view']));
    }
    else {
      $view_args = array();
    }

    // Make sure the query is not cached
    $view->is_cacheable = FALSE;

    // Get the results.
    $result = $view
      ->execute_display($display, $view_args);
  }
  else {
    $result = FALSE;
  }
  return $result;
}

Functions

Namesort descending Description
nodeaccess_userreference_add_grant Add node grants in a way that prevents overriding previous iterations.
nodeaccess_userreference_field_delete_instance Implements hook_field_delete_instance().
nodeaccess_userreference_field_settings Set and get nodeaccess userreference field settings.
nodeaccess_userreference_field_update_instance Implements hook_field_update_instance().
nodeaccess_userreference_form_field_ui_field_edit_form_alter Implements hook_form-FORM-ID_alter().
nodeaccess_userreference_node_access Implements hook_node_access().
nodeaccess_userreference_node_access_explain Implements hook_node_access_explain().
nodeaccess_userreference_node_access_records Implements hook_node_access_records().
nodeaccess_userreference_node_grants Implements hook_node_grants().
nodeaccess_userreference_node_in_field_view Determine if any node falls into the view configured in the field settings.
nodeaccess_userreference_recursive_filter Recursively filter an array.
nodeaccess_userreference_reduce_variable Reduce settings array to the bare minimum.
nodeaccess_userreference_views_api Implements hook_views_api().
nodeaccess_userreference_views_displays Get an array of node views for use in select forms.
nodeaccess_userreference_views_display_title Set the display title for a views display.