You are here

permissions_by_term.module in Permissions by Term 8.2

Same filename and directory in other branches
  1. 8 permissions_by_term.module
  2. 7 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.
 */
use Drupal\Core\Access\AccessResultForbidden;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
use Drupal\permissions_by_term\Service\AccessCheck;
use Drupal\permissions_by_term\Service\AccessStorage;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\TermInterface;

/**
 * Implements hook_help().
 */
function permissions_by_term_help($route_name, RouteMatchInterface $arg) {
  switch ($route_name) {
    case 'help.page.permissions_by_term':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The "Permissions by Term" (PbT) module allows taxonomy administrators the
        ability to restrict setting individual terms on nodes by user
        or role. If a user is unable to set any terms for a required
        vocabulary, they are blocked from adding or editing content with
        that vocabulary. For more information, see the online documentation for <a href=":PbT-documentation" target="_blan" title="Online Documentation">Permissions by Term</a>.', [
        ':PbT-documentation' => 'https://www.drupal.org/docs/8/modules/permissions-by-term',
      ]) . '</p>';
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('General') . '</dt>';
      $output .= '<dd>' . t('Use Permissions by Term to easily build access-restricted content areas on your websites.') . '</dd>';
      $output .= '<dt>' . t('Lightweight Access Control') . '</dt>';
      $output .= '<dd>' . t('Permissions by Term restricts user access to specified Drupal nodes based on taxonomy terms - a core part of Drupal’s functionality. PbT lets you restrict content access while relying on very little contributed code.') . '</dd>';
      $output .= '<dt>' . t('Example use cases') . '</dt>';
      $output .= '<dd>' . t('A club or service site with premium- or member-only content.') . '</dd>';
      $output .= '<dd>' . t('School websites with content intended for teachers only and content aimed at individual classes within the school.') . '</dd>';
      $output .= '<dd>' . t('Company intranets with sensitive or proprietary content alongside non-restricted content.') . '</dd>';
      $output .= '</dl>';
      return $output;
  }
}

/**
 * Validation handler for permissions_by_term_form_alter().
 */
function permissions_by_term_validate($form, FormState $oFormState) {
  foreach ($form as $field) {
    if (!is_object($field) && !empty($field['widget']['target_id']['#target_type']) && $field['widget']['target_id']['#target_type'] == 'taxonomy_term') {
      $field_name = $field['widget']['#field_name'];
      $terms = $oFormState
        ->getValues()[$field_name]['target_id'];
      $not_allowed_term_names = [];
      if (!empty($terms)) {
        foreach ($terms as $term) {
          if (!empty($term['target_id'])) {
            $term_id = $term['target_id'];

            /* @var \Drupal\permissions_by_term\Service\AccessCheck $access_check_service */
            $access_check_service = \Drupal::service('permissions_by_term.access_check');
            if (!$access_check_service
              ->isAccessAllowedByDatabase($term_id)) {
              $term = Term::load($term_id);
              $not_allowed_term_names[] = $term
                ->getName();
            }
          }
        }
      }
    }
  }
  if (!empty($not_allowed_term_names)) {
    if (count($not_allowed_term_names) > 1) {
      $term_names = implode(', ', $not_allowed_term_names);
    }
    else {
      $term_names = $not_allowed_term_names['0'];
    }
    $oFormState
      ->setErrorByName('field_tags', t('You are not allowed to use taxonomy terms like: "@termNames". Remove the restricted taxonomy terms from the form field and try again.', [
      '@termNames' => $term_names,
    ]));
  }
}

/**
 * Submit handler for permissions_by_term_form_alter().
 */
function permissions_by_term_submit($form, FormState $formState) {
  $termId = $formState
    ->getFormObject()
    ->getEntity()
    ->id();

  /* @var \Drupal\permissions_by_term\Service\AccessStorage $access_storage */
  $access_storage = \Drupal::service('permissions_by_term.access_storage');
  $access_update = $access_storage
    ->saveTermPermissions($formState, $termId);

  // Check if we need to rebuild cache and node_access
  $rebuild_cache_and_node_access = false;

  // Has anything has changed?
  foreach ($access_update as $values) {
    if (!empty($values)) {
      $rebuild_cache_and_node_access = true;
      break;
    }
  }
  if (\Drupal::configFactory()
    ->getEditable('permissions_by_term.settings')
    ->get('disable_node_access_records')) {
    $rebuild_cache_and_node_access = false;
  }

  // Do we need to flush the cache and the node access records?
  if ($rebuild_cache_and_node_access === true) {

    /**
     * @var \Drupal\permissions_by_term\Service\NodeAccess $nodeAccess
     */
    $nodeAccess = \Drupal::service('permissions_by_term.node_access');
    $nodeAccess
      ->rebuildAccess();

    /**
     * @var \Drupal\permissions_by_term\Cache\CacheInvalidator $cacheInvalidator
     */
    $cacheInvalidator = \Drupal::service('permissions_by_term.cache_invalidator');
    $cacheInvalidator
      ->invalidate();
  }
}

/**
 * Implements hook_form_alter().
 */
function permissions_by_term_form_taxonomy_term_form_alter(&$form, FormStateInterface $formState, $form_id) {
  if (\Drupal::currentUser()
    ->hasPermission('show term permission form on term page')) {
    $term = $formState
      ->getFormObject()
      ->getEntity();
    $settings = \Drupal::config('permissions_by_term.settings');
    $target_bundles = $settings
      ->get('target_bundles');

    // Check if permissions can be managed for this taxonomy term bundle.
    if ($target_bundles && !in_array($term
      ->bundle(), $target_bundles)) {
      return;
    }
    $termId = $term
      ->id();
    $settings = \Drupal::config('permissions_by_term.settings');
    $target_bundles = $settings
      ->get('target_bundles');

    // Check if permissions can be managed for this taxonomy term bundle.
    if ($target_bundles && !in_array($term
      ->bundle(), $target_bundles)) {
      return;
    }

    /* @var \Drupal\permissions_by_term\Service\AccessStorage $access_storage */
    $access_storage = \Drupal::service('permissions_by_term.access_storage');
    $form['access'] = [
      '#type' => 'details',
      '#title' => t('Permissions'),
      '#description' => t('To limit access to this term by user(s) or role(s), select users or roles below. If left empty, all users will have access to content, related to this taxonomy term and this taxonomy term itself.'),
      '#attributes' => [
        'id' => 'fieldset_term_access',
      ],
      '#weight' => -5,
      '#tree' => TRUE,
    ];
    $langcode = \Drupal::languageManager()
      ->getCurrentLanguage()
      ->getId();
    if (!empty($formState
      ->getValue('langcode'))) {
      $langcode = $formState
        ->getValue('langcode')['0']['value'];
    }
    $aAllowedUsers = $access_storage
      ->getAllowedUserIds($termId, $langcode);
    if (!empty($aAllowedUsers)) {
      $aAllowedUsers = user_load_multiple($aAllowedUsers);
      $sUserFormValue = $access_storage
        ->getUserFormValue($aAllowedUsers);
    }
    else {
      $sUserFormValue = NULL;
    }

    // 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'] = [
      '#type' => 'entity_autocomplete',
      '#target_type' => 'user',
      '#title' => t('Allowed users'),
      '#description' => t('Enter a comma-separated list of user names who will be able to access content, related to this taxonomy term.'),
      '#value' => $sUserFormValue,
      '#size' => 60,
      '#maxlength' => FALSE,
      '#autocomplete_route_name' => 'permissions_by_term.autocomplete_multiple',
      '#weight' => -10,
    ];
    $aAllowedRoles = $access_storage
      ->getRoleTermPermissionsByTid($termId, $langcode);

    // Firstly fetch all translated allowed role names.
    $aTranslatedAllowedRoleNames = [];
    foreach ($aAllowedRoles as $role) {
      $aTranslatedAllowedRoleNames[] = $role;
    }

    // Get all roles for the complete form and translate them.
    $aTranslatedUserRoles = [];
    $array_key_counter = 1;
    foreach (user_roles() as $user_role_id => $user_role_name) {
      $aTranslatedUserRoles[$user_role_id] = $user_role_name
        ->label();
      $array_key_counter++;
    }

    // Generate the default values for the form.
    $aSetRoles = [];
    if (!empty($aTranslatedAllowedRoleNames)) {
      foreach ($aTranslatedAllowedRoleNames as $role_name) {
        $aSetRoles[] = $role_name;
      }
    }

    // Now, lets do the Roles table.
    $form['access']['role'] = [
      '#type' => 'checkboxes',
      '#title' => t('Allowed roles'),
      '#description' => t('Select user roles who will be able to access content, related to this taxonomy term.'),
      '#default_value' => $aSetRoles,
      '#options' => $aTranslatedUserRoles,
      '#multiple' => FALSE,
      '#weight' => 5,
    ];
    $form['#validate'][] = 'permissions_by_term_validate';
    $form['actions']['submit']['#submit'][] = 'permissions_by_term_submit';
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for 'user_form'.
 */
function permissions_by_term_form_user_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  // Nothing to do if the current user does not have requested permission.
  if (!\Drupal::currentUser()
    ->hasPermission('show term permissions on user edit page')) {
    return;
  }
  $form['access'] = [
    '#type' => 'details',
    '#title' => t('Permissions'),
    '#description' => t('Extend content access by giving access to the related taxonomy terms.'),
    '#open' => TRUE,
    '#weight' => -10,
  ];
  $term_storage = \Drupal::entityTypeManager()
    ->getStorage('taxonomy_term');
  $settings = \Drupal::config('permissions_by_term.settings');
  $target_bundles = $settings
    ->get('target_bundles');
  $terms = $target_bundles ? $term_storage
    ->loadByProperties([
    'vid' => $target_bundles,
  ]) : $term_storage
    ->loadMultiple();
  $form['access']['terms'] = [
    '#type' => 'select',
    '#multiple' => TRUE,
    '#options' => array_map(function (TermInterface $term) {
      return $term
        ->label();
    }, $terms),
    '#title' => t('Allowed terms'),
    '#description' => t('Choose a list of taxonomy terms. The access to the content, related to chosen taxonomy terms will be granted to this user.'),
  ];

  /** @var \Drupal\permissions_by_term\Service\AccessStorage $access_storage */
  $access_storage = \Drupal::service('permissions_by_term.access_storage');

  /** @var \Drupal\user\UserInterface $user */
  $user = $form_state
    ->getFormObject()
    ->getEntity();
  if (!$user
    ->isNew() && ($permitted_tids = $access_storage
    ->getPermittedTids($user
    ->id(), $user
    ->getRoles()))) {
    $form['access']['terms']['#default_value'] = array_values($permitted_tids);
  }
  $form['actions']['submit']['#submit'][] = 'permissions_by_term_user_form_submit';
}

/**
 * Form submission handler for permissions_by_term_form_user_form_alter.
 *
 * @param array $form
 *   The complete form array.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The current state of the form.
 */
function permissions_by_term_user_form_submit(array &$form, FormStateInterface $form_state) {

  /** @var \Drupal\permissions_by_term\Service\AccessStorage $access_storage */
  $access_storage = \Drupal::service('permissions_by_term.access_storage');

  /** @var \Drupal\user\UserInterface $user */
  $user = $form_state
    ->getFormObject()
    ->getEntity();
  $uid = $user
    ->id();
  $origPerms = $access_storage
    ->getAllTermPermissionsByUserId($uid);

  // First, we delete existing values from the db.
  $access_storage
    ->deleteAllTermPermissionsByUserId($uid);
  if ($tids = $form_state
    ->getValue('terms')) {

    // For term permissions use user preferred language.
    $langcode = $user
      ->getPreferredLangcode();

    // Second, we insert updated values.
    foreach ($tids as $tid) {
      $access_storage
        ->addTermPermissionsByUserIds([
        $uid,
      ], $tid, $langcode);
    }
  }
  $newPerms = $access_storage
    ->getAllTermPermissionsByUserId($uid);

  // Rebuild permissions for nodes if needed.
  if (!\Drupal::config('permissions_by_term.settings')
    ->get('disable_node_access_records') && !empty(array_diff($origPerms, $newPerms))) {
    \Drupal::service('permissions_by_term.node_access')
      ->rebuildAccess($uid);
  }
}

/**
 * Implements hook_form_alter().
 */
function permissions_by_term_form_alter(&$form, FormStateInterface $formState, $form_id) {
  $form['#validate'][] = 'permissions_by_term_validate';
  if (isNodeEditForm()) {
    $form['permissions_by_term_info'] = [
      '#type' => 'details',
      '#group' => 'advanced',
      '#title' => t('Permissions by Term'),
      '#access' => \Drupal::currentUser()
        ->hasPermission('show term permissions on node edit page'),
    ];
    $langcode = \Drupal::languageManager()
      ->getCurrentLanguage()
      ->getId();
    if (!empty($formState
      ->getUserInput()['langcode']['0']['value'])) {
      $langcode = $formState
        ->getUserInput()['langcode']['0']['value'];
    }
    $nid = null;
    if (!empty($node = \Drupal::routeMatch()
      ->getParameter('node')) && $node instanceof Node) {
      $nid = $node
        ->id();

      /**
       * @var AccessCheck $accessCheck
       */
      $accessCheck = \Drupal::service('permissions_by_term.access_check');
      $accessResult = $accessCheck
        ->handleNode($node, $langcode);
      if ($accessResult instanceof AccessResultForbidden) {
        return $accessResult;
      }
    }
    $viewFilePath = drupal_get_path('module', 'permissions_by_term') . '/src/View/node-details.html.twig';

    /**
     * @var \Drupal\permissions_by_term\Service\NodeEntityBundleInfo $nodeEntityBundleInfo
     */
    $nodeEntityBundleInfo = \Drupal::service('permissions_by_term.node_entity_bundle_info');
    $form['permissions_by_term_info']['revision'] = array(
      '#type' => 'item',
      '#markup' => $nodeEntityBundleInfo
        ->renderNodeDetails($viewFilePath, $langcode, $nid),
    );
    $form['#attached']['library'][] = 'permissions_by_term/nodeForm';
  }
}
function isNodeEditForm() {
  $currentPath = \Drupal::service('path.current')
    ->getPath();
  if (is_numeric(strpos($currentPath, '/node/')) && (is_numeric(strpos($currentPath, '/edit')) || is_numeric(strpos($currentPath, '/add')))) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implements hook_node_grants().
 */
function permissions_by_term_node_grants(\Drupal\Core\Session\AccountInterface $account, $op) {
  $grants = [];
  if ($op == 'view' && !\Drupal::configFactory()
    ->getEditable('permissions_by_term.settings')
    ->get('disable_node_access_records')) {

    /**
     * @var \Drupal\permissions_by_term\Service\AccessStorage $accessStorage
     */
    $accessStorage = \Drupal::service('permissions_by_term.access_storage');
    $grants = $accessStorage
      ->getGids($account);
  }
  return $grants;
}

/**
 * Implements hook_node_access().
 *
 * Forwards user by drupal_access_denied(); to an access denied page, if a
 * single restricted node is called.
 *
 * This hook is not fired if admin is logged in. Users with the
 * "bypass node access" permission may always view and edit content
 * through the administrative interface.
 */
function permissions_by_term_node_access(NodeInterface $node, $op, AccountInterface $account) {

  /* @var \Drupal\permissions_by_term\Service\AccessCheck $accessCheck */
  $accessCheck = \Drupal::service('permissions_by_term.access_check');
  $accessCheck
    ->dispatchDeniedEventOnRestricedAccess($node, $node
    ->language()
    ->getId());
}

/**
 * Implements hook_node_access_records().
 *
 * Permissions can be rebuild at /admin/reports/status/rebuild.
 */
function permissions_by_term_node_access_records(NodeInterface $node) {

  /**
   * @var AccessCheck $accessCheckService
   */
  $accessCheckService = \Drupal::service('permissions_by_term.access_check');

  // Do not return any grants for nodes that this module doesn't manage.
  if (\Drupal::configFactory()
    ->getEditable('permissions_by_term.settings')
    ->get('disable_node_access_records')) {
    return NULL;
  }
  if (!$accessCheckService
    ->isAnyTaxonomyTermFieldDefinedInNodeType($node
    ->getType())) {
    return NULL;
  }

  /**
   * @var \Drupal\permissions_by_term\Service\TermHandler $termHandler
   */
  $termHandler = \Drupal::service('permissions_by_term.term_handler');
  if (!\Drupal::config('permissions_by_term.settings')
    ->get('permission_mode') && empty($termHandler
    ->getTidsByNid($node
    ->id(), $node))) {
    return NULL;
  }
  $isAnyPermissionSetForTerm = FALSE;
  $tids = $termHandler
    ->getTidsByNid($node
    ->id());
  if (!empty($tids)) {
    foreach ($tids as $tid) {

      /* @var \Drupal\permissions_by_term\Service\AccessCheck $access_check_service */
      $access_check_service = \Drupal::service('permissions_by_term.access_check');
      if ($node
        ->language()
        ->getId() == 'und') {

        // Current system default language
        $language = \Drupal::languageManager()
          ->getCurrentLanguage()
          ->getId();
      }
      else {
        $language = $node
          ->language()
          ->getId();
      }
      if ($access_check_service
        ->isAnyPermissionSetForTerm($tid, $language)) {
        $isAnyPermissionSetForTerm = TRUE;
        break;
      }
    }
  }
  if (!$isAnyPermissionSetForTerm && !\Drupal::config('permissions_by_term.settings')
    ->get('permission_mode')) {
    return null;
  }

  /**
   * @var \Drupal\permissions_by_term\Service\NodeAccess $nodeAccess
   */
  $nodeAccess = \Drupal::service('permissions_by_term.node_access');
  $grantObject = $nodeAccess
    ->createGrant($node
    ->id(), $node
    ->id());
  $grants[] = [
    'realm' => $grantObject->realm,
    'gid' => $grantObject->gid,
    'grant_view' => $grantObject->grant_view,
    'grant_update' => $grantObject->grant_update,
    'grant_delete' => $grantObject->grant_delete,
    'nid' => $node
      ->id(),
  ];
  return $grants;
}

/**
 * Implements hook_user_insert().
 */
function permissions_by_term_user_insert($user) {

  /**
   * @var \Drupal\permissions_by_term\Cache\CacheInvalidator $cacheInvalidator
   */
  $cacheInvalidator = \Drupal::service('permissions_by_term.cache_invalidator');
  $cacheInvalidator
    ->invalidate();
}

/**
 * Implements hook_user_update().
 */
function permissions_by_term_user_update($user) {
  if (\Drupal::currentUser()
    ->hasPermission('administer permissions')) {

    /**
     * @var \Drupal\permissions_by_term\Cache\CacheInvalidator $cacheInvalidator
     */
    $cacheInvalidator = \Drupal::service('permissions_by_term.cache_invalidator');
    $cacheInvalidator
      ->invalidate();
  }
}

/**
 * Implements hook_node_insert().
 */
function permissions_by_term_node_insert($node) {

  /**
   * @var \Drupal\permissions_by_term\Cache\CacheInvalidator $cacheInvalidator
   */
  $cacheInvalidator = \Drupal::service('permissions_by_term.cache_invalidator');
  $cacheInvalidator
    ->invalidate();
}

/**
 * Implements hook_options_list_alter().
 */
function permissions_by_term_options_list_alter(array &$options, array $context) {
  $fieldDefinitionSettings = $context['fieldDefinition']
    ->getFieldStorageDefinition()
    ->getSettings();
  if (!empty($fieldDefinitionSettings['target_type']) && $fieldDefinitionSettings['target_type'] == 'taxonomy_term') {
    foreach ($options as $id => $names) {
      if ($id !== '_none') {

        /**
         * @var AccessCheck $accessCheck
         */
        $accessCheck = \Drupal::service('permissions_by_term.access_check');
        if (is_array($names)) {
          foreach ($names as $group_id => $name) {
            if (!$accessCheck
              ->isAccessAllowedByDatabase($group_id)) {
              unset($options[$id]);
            }
          }
        }
        elseif (is_string($names)) {
          if (!$accessCheck
            ->isAccessAllowedByDatabase($id)) {
            unset($options[$id]);
          }
        }
      }
    }
  }
}

/**
 * Implements hook_user_cancel().
 *
 * Deletes all term permissions for a user when their account is cancelled.
 */
function permissions_by_term_user_cancel($edit, $account, $method) {
  $deleted_user_id = $account
    ->id();

  /* @var AccessStorage $access_storage */
  $access_storage = \Drupal::service('permissions_by_term.access_storage');
  $access_storage
    ->deleteAllTermPermissionsByUserId($deleted_user_id);

  /**
   * @var \Drupal\permissions_by_term\Cache\CacheInvalidator $cacheInvalidator
   */
  $cacheInvalidator = \Drupal::service('permissions_by_term.cache_invalidator');
  $cacheInvalidator
    ->invalidate();
}

/**
 * Implements hook_ENTITY_TYPE_delete().
 *
 * Deletes all term permissions from storage when a term is deleted.
 */
function permissions_by_term_taxonomy_term_delete(EntityInterface $entity) {

  /* @var AccessStorage $access_storage */
  $access_storage = \Drupal::service('permissions_by_term.access_storage');
  $access_storage
    ->deleteAllTermPermissionsByTid($entity
    ->id());

  /**
   * @var \Drupal\permissions_by_term\Cache\CacheInvalidator $cacheInvalidator
   */
  $cacheInvalidator = \Drupal::service('permissions_by_term.cache_invalidator');
  $cacheInvalidator
    ->invalidate();
}