You are here

permissions_by_term.module in Permissions by Term 7

Same filename and directory in other branches
  1. 8.2 permissions_by_term.module
  2. 8 permissions_by_term.module

Allows access to terms in a vocabulary to be limited by user or role.

File

permissions_by_term.module
View source
<?php

/**
 * @file
 * Allows access to terms in a vocabulary to be limited by user or role.
 */

/**
 * Implements hook_help().
 */
function permissions_by_term_help($path, $arg) {
  switch ($path) {
    case 'admin/help#permissions_by_term':
      $output = '<p>' . t('This module allows taxonomy administrators the
        ability to restrict setting individual terms on nodes by user
        or role. Additionally it restricts the access to node view and removes
        the nodes from views according user and role access settings. If a user
        is unable to set any terms for a required vocabulary, they are blocked
        from adding or editing content with that vocabulary.') . '</p>';
      $output .= '<p>' . t('To add permissions for a term, go to
        Administer >> Content Management >> Taxonomy, and add or
        edit a term. If the permissions are left blank, the term is
        available to all users.') . '</p>';
      return $output;
  }
}

/**
 * Implements hook_permission().
 */
function permissions_by_term_permission() {
  return array(
    'show term permission form on term page' => array(
      'title' => t('Term permission form on term page'),
      'description' => t('Enables the specific role to edit
      the term permissions on the term edit page.'),
    ),
  );
}

/**
 * Implements hook_menu().
 */
function permissions_by_term_menu() {
  $items = array();
  $items['permissions-by-term/autocomplete'] = array(
    'title' => '',
    'page callback' => 'permissions_by_term_autocomplete_multiple',
    'access arguments' => array(
      'access user profiles',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['permissions-by-term/autocomplete_terms'] = array(
    'title' => '',
    'page callback' => 'permissions_by_term_autocomplete_terms',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function permissions_by_term_form_taxonomy_form_term_alter(&$form, $form_state, $form_id) {
  if (user_access('show term permission form on term page')) {

    // This is the add / edit term form from the taxonomy page.
    // Normally the term is an array, but when deleting terms it becomes an
    // object. So, we cast it to an object so we can consistently reference it.
    $term = (object) $form['#term'];
    $form['access'] = array(
      '#type' => 'fieldset',
      '#title' => t('Permissions'),
      '#description' => t("To limit access of this term and it's related nodes\n        (in node view and views) by user or roles, add users or roles to the\n        following lists. Leave empty to allow selection by all users."),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#attributes' => array(
        'id' => 'fieldset_term_access',
      ),
      '#weight' => -5,
      '#tree' => TRUE,
    );

    // Pull in any stored users in the database.
    $allowed_users = array();
    if (!empty($form['tid']['#value'])) {
      $allowed_users = db_query("SELECT u.name FROM {permissions_by_term_user} p\n                        JOIN {users} u ON p.uid = u.uid WHERE p.tid = :tid", array(
        ':tid' => $term->tid,
      ))
        ->fetchCol();
    }
    $allowed_users = drupal_implode_tags($allowed_users);

    // Note that the autocomplete widget will only enable for users with the
    // 'access profiles' permission. Other users will have to specify the name
    // manually.
    $form['access']['user'] = array(
      '#type' => 'textfield',
      '#title' => t('Allowed users'),
      '#description' => t('Enter a comma-seperated list of user names to ' . 'give them permission to use this term and access related nodes in ' . 'single node view and views listings.'),
      '#default_value' => $allowed_users,
      '#size' => 60,
      '#autocomplete_path' => 'permissions-by-term/autocomplete',
      '#weight' => -10,
    );
    $allowed_roles = array();
    if (!empty($form['tid']['#value'])) {
      $query = db_select('permissions_by_term_role', 'pbt')
        ->fields('pbt', array(
        'tid',
        'rid',
      ))
        ->condition('pbt.tid', $term->tid);

      // joins cannot be chained in Drupal oop db-queries.
      $query
        ->leftJoin('role', 'r', 'r.rid = pbt.rid');
      $query
        ->fields('r', array(
        'name',
      ));
      $allowed_roles = $query
        ->execute()
        ->fetchAllAssoc('name');
    }

    // firstly fetch all translated allowed role names.
    $arr__translated_allowed_role_names = array();
    foreach ($allowed_roles as $role) {
      $arr__translated_allowed_role_names[] = t($role->name);
    }

    // get all roles for the complete form and translate them.
    $arr__all_translated_user_roles = array();
    $array_key_counter = 1;
    foreach (user_roles() as $user_role_id => $user_role_name) {
      $arr__all_translated_user_roles[$user_role_id] = t($user_role_name);
      $array_key_counter++;
    }

    // generate the default values for the form.
    $allowed_roles_positions = array();
    if (!empty($arr__translated_allowed_role_names)) {
      foreach ($arr__translated_allowed_role_names as $role_name) {
        $pos = array_search($role_name, $arr__all_translated_user_roles);
        if (is_numeric($pos)) {
          $allowed_roles_positions[] = $pos;
        }
      }
    }

    // Now, lets do the Roles table.
    $form['access']['role'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Allowed roles'),
      '#description' => t('Select a role to allow all members of this role ' . 'to use this term and access related nodes in single node view and ' . 'views listings.'),
      '#default_value' => $allowed_roles_positions,
      '#options' => $arr__all_translated_user_roles,
      //'#options' => user_roles(),
      '#multiple' => FALSE,
      '#weight' => 5,
    );
    $form['#validate'][] = 'permissions_by_term_validate';
    $form['#submit'][] = 'permissions_by_term_submit';
  }
}

/**
 * Implements hook_form_alter().
 */
function permissions_by_term_form_alter(&$form, $form_state, $form_id) {

  // This is the node add / edit form. If a different selector is used from
  // another contributed module, we do nothing so as to not break the form.
  if (isset($form['type']) && isset($form['#node']) && !variable_get('taxonomy_override_selector', FALSE) && $form['type']['#value'] . '_node_form' == $form_id || isset($form['#entity_type']) && isset($form['#bundle'])) {

    // Field types we are looking for.
    $types = array(
      'taxonomy_term_reference',
    );
    foreach (element_children($form) as $field_name) {
      if (!($field_info = field_info_field($field_name))) {
        continue;
      }
      if (!in_array('#language', $form[$field_name])) {
        continue;
      }
      $options =& $form[$field_name][$form[$field_name]['#language']]['#options'];
      if (!in_array($field_info['type'], $types) || !isset($options)) {
        continue;
      }
      foreach ($options as $tid => $name) {
        if ($tid == "_none") {
          continue;
        }

        // Now we have the term ID, check to see if the current user has
        // access to the term.
        global $user;
        if (!permissions_by_term_allowed($tid, $user)) {
          unset($options[$tid]);
        }
      }
    }
  }
}

/**
 * Validation handler for permissions_by_term_form_alter().
 */
function permissions_by_term_validate($form, &$form_state) {
  if (!empty($form_state['values']['access']['user'])) {
    $allowed_users = drupal_explode_tags($form_state['values']['access']['user']);
    foreach ($allowed_users as $name) {
      $user = db_query("SELECT u.name FROM {users} u WHERE u.name = :uname", array(
        ':uname' => $name,
      ))
        ->fetchCol();
      if (empty($user)) {
        form_set_error('access][user', t('The user %user_name does not exist.', array(
          '%user_name' => $name,
        )));
      }
    }
  }
}

/**
 * Submit handler for permissions_by_term_form_alter().
 */
function permissions_by_term_submit($form, &$form_state) {

  // For each user, save the term ID and the user ID.
  db_delete('permissions_by_term_user')
    ->condition('tid', $form_state['values']['tid'])
    ->execute();
  if (!empty($form_state['values']['access']['user']) && $form_state['values']['op'] == t('Save')) {
    $allowed_users = drupal_explode_tags($form_state['values']['access']['user']);
    $query = db_insert('permissions_by_term_user')
      ->fields(array(
      'tid',
      'uid',
    ));
    foreach ($allowed_users as $name) {
      $user = db_query("SELECT u.uid FROM {users} u WHERE u.name = :uname", array(
        ':uname' => $name,
      ))
        ->fetchCol();
      $query
        ->values(array(
        'tid' => $form_state['values']['tid'],
        'uid' => $user['0'],
      ));
    }
    $query
      ->execute();
  }

  // For each role, save the term ID and the role ID.
  db_delete('permissions_by_term_role')
    ->condition('tid', $form_state['values']['tid'])
    ->execute();
  if (!empty($form_state['values']['access']['role']) && $form_state['values']['op'] == t('Save')) {
    foreach (array_keys(array_filter($form_state['values']['access']['role'])) as $rid) {
      db_insert('permissions_by_term_role')
        ->fields(array(
        'tid' => $form_state['values']['tid'],
        'rid' => $rid,
      ))
        ->execute();
    }
  }
}

/**
 * Implements hook_views_post_execute().
 *
 * Hides nodes in every view on the site.
 */
function permissions_by_term_views_post_execute(&$view) {
  global $user;
  $view_contains_nodes = FALSE;
  foreach ($view->result as $view_result) {
    if (array_key_exists('nid', $view_result) === TRUE) {
      $view_contains_nodes = TRUE;
    }
  }
  if ($view_contains_nodes === TRUE) {
    $arr__nodes_to_hide_in_view = array();
    foreach ($view->result as $v) {
      $node = node_load($v->nid);
      $secured_areas = field_get_items('node', $node, 'field_secured_areas');
      if (!empty($secured_areas)) {
        foreach ($secured_areas as $term) {
          if (isset($term['tid']) && !permissions_by_term_allowed($term['tid'], $user)) {
            $arr__nodes_to_hide_in_view[] = $node->nid;
          }
        }
      }
    }
    $arr__new_results = array();

    // Hide the nodes.
    foreach ($view->result as $v) {
      if (!in_array($v->nid, $arr__nodes_to_hide_in_view)) {
        $arr__new_results[] = $v;
      }
    }
    $view->result = $arr__new_results;
  }
}

/**
 * Implements hook_node_access().
 *
 * Forwards user by drupal_access_denied(); to an access denied page, if a
 * single restricted node is called.
 */
function permissions_by_term_node_access($node, $op, $account) {
  if (isset($node->nid) && $op == 'view') {
    $node = node_load($node->nid);
    $secured_areas = field_get_items('node', $node, 'field_secured_areas');
    if (!empty($secured_areas)) {
      global $user;
      foreach ($secured_areas as $term) {
        $term_secured_areas = $term;
        if (isset($term_secured_areas['tid']) && permissions_by_term_allowed($term_secured_areas['tid'], $user) === TRUE) {
          $user_is_allowed_to_view = TRUE;
        }
        if (!isset($user_is_allowed_to_view)) {
          $user_is_allowed_to_view = FALSE;
        }
      }
    }
    if (isset($user_is_allowed_to_view) && $user_is_allowed_to_view === FALSE) {
      return NODE_ACCESS_DENY;
    }
  }
}

/**
 * Implements permissions_by_term_allowed().
 */
function permissions_by_term_allowed($tid, $user) {
  if ($user->uid == 1) {
    return TRUE;
  }

  // Are permissions enabled on this term?
  if (!(db_query("SELECT COUNT(1) FROM {permissions_by_term_user} WHERE tid = :tid", array(
    ':tid' => $tid,
  ))
    ->fetchField() || db_query("SELECT COUNT(1) FROM {permissions_by_term_role} WHERE tid = :tid", array(
    ':tid' => $tid,
  ))
    ->fetchField())) {
    return TRUE;
  }

  /* Permissions are enabled, check to see if this user or one of their roles
    is allowed.
     */
  $user_roles = array_keys($user->roles);
  $i = 0;
  while (isset($user_roles[$i])) {
    if (db_query("SELECT uid FROM {permissions_by_term_user} WHERE tid = :tid AND uid = :uid", array(
      ':tid' => $tid,
      ':uid' => $user->uid,
    ))
      ->fetchField() || db_query("SELECT rid FROM {permissions_by_term_role} WHERE tid = :tid AND rid IN (:user_roles)", array(
      ':tid' => $tid,
      ':user_roles' => $user_roles[$i],
    ))
      ->fetchField()) {
      return TRUE;
    }
    $i++;
  }
  return FALSE;
}

/**
 * Page callback: Returns Json array for autocomplete fields.
 *
 * Supports multiple entries separated by a comma.
 *
 * @param string $string
 *   A comma-separated list of users.
 */
function permissions_by_term_autocomplete_multiple($string) {

  // The user enters a comma-separated list of users.
  // We only autocomplete the last user.
  $array = drupal_explode_tags($string);

  // Fetch last user.
  $last_string = trim(array_pop($array));
  $matches = array();
  $result = db_select('users')
    ->fields('users', array(
    'name',
  ))
    ->condition('name', db_like($last_string) . '%', 'LIKE')
    ->range(0, 10)
    ->execute();
  $prefix = count($array) ? implode(', ', $array) . ', ' : '';
  foreach ($result as $user) {
    $matches[$prefix . $user->name] = check_plain($user->name);
  }
  drupal_json_output($matches);
}

/**
 * Implements hook_form_views_exposed_form_alter().
 */
function permissions_by_term_form_views_exposed_form_alter(&$form, $form_state) {
  global $user;
  $filter_names = array();

  // Gather all filters.
  foreach ($form['#info'] as $filter) {
    $filter_names[] = $filter['value'];
  }

  // Remove not allowed options.
  foreach ($filter_names as $filter_name) {
    _permissions_by_term_remove_restricted_items_in_select_field($form, $filter_name, $user);

    // Modify the autocomplete path, to remove restricted taxonomy term items.
    if (!empty($form[$filter_name]['#autocomplete_path'])) {
      $vocabulary_id = explode('taxonomy/', $form[$filter_name]['#autocomplete_path']);
    }
    if (!empty($vocabulary_id['1'])) {
      $vocabulary_id = $vocabulary_id['1'];
      $form[$filter_name]['#autocomplete_path'] = 'permissions-by-term/' . 'autocomplete_terms/' . $vocabulary_id;
    }
  }
}

/**
 * Removes restricted items in select field.
 *
 * @param $form
 * @param $filter_name
 * @param $user
 *
 * @return null
 */
function _permissions_by_term_remove_restricted_items_in_select_field(&$form, $filter_name, $user) {
  foreach ($form[$filter_name] as $form_element) {
    if (is_array($form_element)) {
      foreach ($form_element as $k => $option) {
        if (is_object($option)) {
          $term = taxonomy_term_load($k);
        }
        else {
          $term = taxonomy_get_term_by_name($option);
        }
        if (is_array($term)) {
          foreach ($term as $term_info) {
            $term_id = $term_info->tid;
            if (!permissions_by_term_allowed($term_id, $user)) {
              unset($form[$filter_name]['#options'][$term_id]);
            }
          }
        }
      }
    }
  }
}

/**
 * Page callback for views taxonomy autocomplete.
 * This function has been copied from the Views module. It's nearly a duplicate
 * from views_ajax_autocomplete_taxonomy(). It has the function
 * _permissions_by_term_remove_disallowed_term_matches() implemented.
 *
 * @param $vid
 *   The vocabulary id of the tags which should be returned.
 *
 * @param $tags_typed
 *   The typed string of the user.
 *
 * @see taxonomy_autocomplete()
 */
function permissions_by_term_autocomplete_terms($vid, $tags_typed = '') {

  // The user enters a comma-separated list of tags. We only autocomplete
  // the last tag.
  $tags_typed = drupal_explode_tags($tags_typed);
  $tag_last = drupal_strtolower(array_pop($tags_typed));
  $matches = array();
  if ($tag_last != '') {
    $query = db_select('taxonomy_term_data', 't');
    $query
      ->addTag('translatable');
    $query
      ->addTag('term_access');

    // Do not select already entered terms.
    if (!empty($tags_typed)) {
      $query
        ->condition('t.name', $tags_typed, 'NOT IN');
    }

    // Select rows that match by term name.
    $tags_return = $query
      ->fields('t', array(
      'tid',
      'name',
    ))
      ->condition('t.vid', $vid)
      ->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE')
      ->range(0, 10)
      ->execute()
      ->fetchAllKeyed();
    $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : '';
    $term_matches = array();
    foreach ($tags_return as $tid => $name) {
      $n = $name;

      // Term names containing commas or quotes must be wrapped in quotes.
      if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
        $n = '"' . str_replace('"', '""', $name) . '"';
      }

      // Add term name to list of matches.
      $term_matches[$prefix . $n] = check_plain($name);
    }

    // Only difference to original function from the Views module.
    _permissions_by_term_remove_disallowed_term_matches($term_matches);
  }
  drupal_json_output($term_matches);
}

/**
 * Removes disallowed term matches by reference.
 *
 * @param array $term_matches The matched taxonomy terms.
 *
 * @return null
 */
function _permissions_by_term_remove_disallowed_term_matches(&$term_matches) {
  global $user;
  $updated_term_matches = array();
  foreach ($term_matches as $term_match) {
    $term = taxonomy_get_term_by_name($term_match);
    $term_id = key($term);
    if (!permissions_by_term_allowed($term_id, $user)) {
      unset($term_match);
    }
    else {
      $updated_term_matches[$term_match] = $term_match;
    }
  }
  $term_matches = $updated_term_matches;
}

Functions

Namesort descending Description
permissions_by_term_allowed Implements permissions_by_term_allowed().
permissions_by_term_autocomplete_multiple Page callback: Returns Json array for autocomplete fields.
permissions_by_term_autocomplete_terms Page callback for views taxonomy autocomplete. This function has been copied from the Views module. It's nearly a duplicate from views_ajax_autocomplete_taxonomy(). It has the function _permissions_by_term_remove_disallowed_term_matches()…
permissions_by_term_form_alter Implements hook_form_alter().
permissions_by_term_form_taxonomy_form_term_alter Implements hook_form_FORM_ID_alter().
permissions_by_term_form_views_exposed_form_alter Implements hook_form_views_exposed_form_alter().
permissions_by_term_help Implements hook_help().
permissions_by_term_menu Implements hook_menu().
permissions_by_term_node_access Implements hook_node_access().
permissions_by_term_permission Implements hook_permission().
permissions_by_term_submit Submit handler for permissions_by_term_form_alter().
permissions_by_term_validate Validation handler for permissions_by_term_form_alter().
permissions_by_term_views_post_execute Implements hook_views_post_execute().
_permissions_by_term_remove_disallowed_term_matches Removes disallowed term matches by reference.
_permissions_by_term_remove_restricted_items_in_select_field Removes restricted items in select field.