5 * Allows access to terms in a vocabulary to be limited by user or role.
8 use Drupal\Core\Access\AccessResult;
9 use Drupal\Core\Form\FormState;
10 use Drupal\permissions_by_term\Controller\PermissionsByTermController;
11 use Drupal\Core\Form\FormStateInterface;
12 use Drupal\node\NodeInterface;
13 use Drupal\Core\Session\AccountInterface;
14 use Drupal\permissions_by_term\Event\PermissionsByTermDeniedEvent;
15 use Drupal\taxonomy\Entity\Term;
16 use Drupal\Core\Routing\RouteMatchInterface;
17 use Drupal\Core\Cache\Cache;
20 * Implements hook_help().
22 function permissions_by_term_help($route_name, RouteMatchInterface $arg) {
23 switch ($route_name) {
24 case 'help.page.permissions_by_term':
26 $output .= '<h3>' . t('About') . '</h3>';
27 $output .= '<p>' . t('The "Permissions by Term" (PbT) module allows taxonomy administrators the
28 ability to restrict setting individual terms on nodes by user
29 or role. If a user is unable to set any terms for a required
30 vocabulary, they are blocked from adding or editing content with
31 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>';
32 $output .= '<h3>' . t('Uses') . '</h3>';
34 $output .= '<dt>' . t('General') . '</dt>';
35 $output .= '<dd>' . t('Use Permissions by Term to easily build access-restricted content areas on your websites.') . '</dd>';
36 $output .= '<dt>' . t('Lightweight Access Control') . '</dt>';
37 $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>';
38 $output .= '<dt>' . t('Example use cases') . '</dt>';
39 $output .= '<dd>' . t('A club or service site with premium- or member-only content.') . '</dd>';
40 $output .= '<dd>' . t('School websites with content intended for teachers only and content aimed at individual classes within the school.') . '</dd>';
41 $output .= '<dd>' . t('Company intranets with sensitive or proprietary content alongside non-restricted content.') . '</dd>';
49 * Validation handler for permissions_by_term_form_alter().
51 function permissions_by_term_validate($form, FormState $oFormState) {
52 foreach ($form as $field) {
53 if (!is_object($field) && !empty($field['widget']['target_id']['#target_type']) && $field['widget']['target_id']['#target_type'] == 'taxonomy_term') {
54 $field_name = $field['widget']['#field_name'];
55 $terms = $oFormState->getValues()[$field_name]['target_id'];
56 $not_allowed_term_names = [];
58 foreach ($terms as $term) {
59 $term_id = $term['target_id'];
60 /* @var \Drupal\permissions_by_term\Service\AccessCheck $access_check_service */
61 $access_check_service = \Drupal::service('permissions_by_term.access_check');
62 if (!$access_check_service->isAccessAllowedByDatabase($term_id)) {
63 $term = Term::load($term_id);
64 $not_allowed_term_names[] = $term->getName();
70 if (!empty($not_allowed_term_names)) {
71 if (count($not_allowed_term_names) > 1) {
72 $term_names = implode(', ', $not_allowed_term_names);
75 $term_names = $not_allowed_term_names['0'];
77 $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.',
78 ['@termNames' => $term_names]));
83 * Submit handler for permissions_by_term_form_alter().
85 function permissions_by_term_submit($form, FormState $formState) {
86 $termId = $formState->getFormObject()->getEntity()->id();
87 /* @var \Drupal\permissions_by_term\Service\AccessStorage $access_storage */
88 $access_storage = \Drupal::service('permissions_by_term.access_storage');
89 $access_update = $access_storage->saveTermPermissions($formState, $termId);
91 // Check if we need to rebuild node_access by term id
92 $invalidate_cache_tag = false;
94 // Has anything has changed?
95 foreach($access_update as $values) {
97 $invalidate_cache_tag = true;
102 // Do we need to flush the cache?
103 if($invalidate_cache_tag === true) {
104 Cache::invalidateTags(['search_index:node_search']);
109 * Implements hook_form_alter().
111 function permissions_by_term_form_taxonomy_term_form_alter(&$form, FormStateInterface $oFormState, $form_id) {
112 if (\Drupal::currentUser()->hasPermission('show term permission form on term page')) {
113 $iTermId = $oFormState->getFormObject()->getEntity()->id();
115 /* @var \Drupal\permissions_by_term\Service\AccessStorage $access_storage */
116 $access_storage = \Drupal::service('permissions_by_term.access_storage');
119 '#type' => 'fieldset',
120 '#title' => t('Permissions'),
121 '#description' => t('To limit access to this term by user or roles,
122 add users or roles to the following lists. Leave empty to allow
123 node access by single node view, node listing in views and taxonomy
124 term selection by all users.'),
125 '#collapsible' => TRUE,
126 '#collapsed' => TRUE,
127 '#attributes' => ['id' => 'fieldset_term_access'],
132 $aAllowedUsers = $access_storage->getAllowedUserIds($iTermId);
133 if (!empty($aAllowedUsers)) {
134 $aAllowedUsers = user_load_multiple($aAllowedUsers);
135 $sUserFormValue = $access_storage->getUserFormValue($aAllowedUsers);
138 $sUserFormValue = NULL;
141 // Note that the autocomplete widget will only enable for users with the
142 // 'access profiles' permission. Other users will have to specify the name
144 $form['access']['user'] = [
145 '#type' => 'entity_autocomplete',
146 '#target_type' => 'user',
147 '#title' => t('Allowed users'),
148 '#description' => t('Enter a comma-seperated list of user names to give') . ' ' .
149 t('them permission to use this term and access related nodes in single node view
150 and views listings.'),
151 '#value' => $sUserFormValue,
153 '#autocomplete_route_name' => 'permissions_by_term.autocomplete_multiple',
157 $aAllowedRoles = $access_storage->getRoleTermPermissionsByTid($iTermId);
159 // Firstly fetch all translated allowed role names.
160 $aTranslatedAllowedRoleNames = [];
161 foreach ($aAllowedRoles as $role) {
162 $aTranslatedAllowedRoleNames[] = $role;
165 // Get all roles for the complete form and translate them.
166 $aTranslatedUserRoles = [];
167 $array_key_counter = 1;
168 foreach (user_roles() as $user_role_id => $user_role_name) {
169 $aTranslatedUserRoles[$user_role_id] = $user_role_name->label();
170 $array_key_counter++;
173 // Generate the default values for the form.
175 if (!empty($aTranslatedAllowedRoleNames)) {
176 foreach ($aTranslatedAllowedRoleNames as $role_name) {
177 $aSetRoles[] = $role_name;
181 // Now, lets do the Roles table.
182 $form['access']['role'] = [
183 '#type' => 'checkboxes',
184 '#title' => t('Allowed roles'),
185 '#description' => t('Select a role to allow all members of this role to
186 use this term and access related nodes in single node view and views
188 '#default_value' => $aSetRoles,
189 '#options' => $aTranslatedUserRoles,
190 '#multiple' => FALSE,
194 $form['#validate'][] = 'permissions_by_term_validate';
195 $form['actions']['submit']['#submit'][] = 'permissions_by_term_submit';
200 * Implements hook_form_alter().
202 function permissions_by_term_form_alter(&$form, FormStateInterface $oFormState, $form_id) {
203 $form['#validate'][] = 'permissions_by_term_validate';
204 if (isNodeEditForm()) {
205 $form['permissions_by_term_info'] = [
206 '#type' => 'details',
207 '#group' => 'advanced',
208 '#title' => t('Permissions by Term'),
209 '#access' => \Drupal::currentUser()->hasPermission('show term permissions on node edit page'),
213 if (!empty($node = \Drupal::routeMatch()->getParameter('node'))) {
217 $viewFilePath = drupal_get_path('module', 'permissions_by_term') . '/src/View/node-details.html.twig';
219 * @var \Drupal\permissions_by_term\Service\NodeEntityBundleInfo $nodeEntityBundleInfo
221 $nodeEntityBundleInfo = \Drupal::service('permissions_by_term.node_entity_bundle_info');
223 $form['permissions_by_term_info']['revision'] = array(
225 '#markup' => $nodeEntityBundleInfo->renderNodeDetails($viewFilePath, $nid),
228 $form['#attached']['library'][] = 'permissions_by_term/nodeForm';
232 function isNodeEditForm() {
233 $currentPath = \Drupal::service('path.current')->getPath();
234 if (is_numeric(strpos($currentPath, '/node/'))
235 && (is_numeric(strpos($currentPath, '/edit')) || is_numeric(strpos($currentPath, '/add')))) {
242 * Implements hook_node_access().
244 * Forwards user by drupal_access_denied(); to an access denied page, if a
245 * single restricted node is called.
247 * This hook is not fired if admin is logged in. Users with the
248 * "bypass node access" permission may always view and edit content
249 * through the administrative interface.
251 function permissions_by_term_node_access(NodeInterface $node, $op, AccountInterface $account) {
252 if (method_exists($node, 'id') && ($op == 'view' OR $op == 'update' OR $op == 'delete')) {
253 if (!$node->isPublished() && !$account->hasPermission('Bypass content access control', $account)) {
254 $eventDispatcher = \Drupal::service('event_dispatcher');
255 $accessDeniedEvent = new PermissionsByTermDeniedEvent($node->id());
256 $eventDispatcher->dispatch(PermissionsByTermDeniedEvent::NAME, $accessDeniedEvent);
258 return AccessResult::forbidden();
261 /* @var \Drupal\permissions_by_term\Service\AccessCheck $accessCheck */
262 $accessCheck = \Drupal::service('permissions_by_term.access_check');
264 return $accessCheck->handleNode($node->id());
269 * Implements hook_node_grants().
271 function permissions_by_term_node_grants(\Drupal\Core\Session\AccountInterface $account, $op)
275 * @var \Drupal\permissions_by_term\Service\AccessStorage $accessStorage
277 $accessStorage = \Drupal::service('permissions_by_term.access_storage');
278 $grants = $accessStorage->getGids(\Drupal::currentUser());
285 * Implements hook_node_access_records().
287 * Permissions can be rebuild at /admin/reports/status/rebuild.
289 function permissions_by_term_node_access_records(\Drupal\node\NodeInterface $node) {
290 // Do not return any grants for nodes that this module doesn't manage.
291 if (!$node->isPublished()) {
294 $has_term_access_restrictions = FALSE;
295 /* @var \Drupal\permissions_by_term\Service\AccessStorage $access_storage */
296 $access_storage = \Drupal::service('permissions_by_term.access_storage');
297 foreach ($access_storage->getTidsByNid($node->id()) as $tid) {
298 /* @var \Drupal\permissions_by_term\Service\AccessCheck $access_check_service */
299 $access_check_service = \Drupal::service('permissions_by_term.access_check');
300 if ($access_check_service->isAnyPermissionSetForTerm($tid)) {
301 $has_term_access_restrictions = TRUE;
305 if (!$has_term_access_restrictions) {
310 * @var \Drupal\permissions_by_term\Service\NodeAccess $nodeAccess
312 $nodeAccess = \Drupal::service('permissions_by_term.node_access');
313 $grantObject = $nodeAccess->createGrant($node->id(), $node->id());
316 'realm' => $grantObject->realm,
317 'gid' => $grantObject->gid,
318 'grant_view' => $grantObject->grant_view,
319 'grant_update' => $grantObject->grant_update,
320 'grant_delete' => $grantObject->grant_delete,
321 'langcode' => $grantObject->langcode,
323 'nid' => $node->id(),
330 * Implements hook_user_insert().
332 function permissions_by_term_user_insert($user) {
333 Cache::invalidateTags(['search_index:node_search']);
337 * Implements hook_user_update().
339 function permissions_by_term_user_update($user) {
340 if (\Drupal::currentUser()->hasPermission('administer permissions')) {
341 Cache::invalidateTags(['search_index:node_search']);
346 * Implements hook_node_insert().
348 function permissions_by_term_node_insert($node) {
349 Cache::invalidateTags(['search_index:node_search']);
353 * Implements hook_options_list_alter().
355 function permissions_by_term_options_list_alter(array &$options, array $context) {
356 $fieldDefinitionSettings = $context['fieldDefinition']->getFieldStorageDefinition()->getSettings();
357 if (!empty($fieldDefinitionSettings['target_type']) && $fieldDefinitionSettings['target_type'] == 'taxonomy_term') {
358 foreach ($options as $id => $names) {
359 if ($id !== '_none') {
361 * @var \Drupal\permissions_by_term\Service\Term $term
363 $term = \Drupal::service('permissions_by_term.term');
366 * @var \Drupal\permissions_by_term\Service\AccessCheck $accessCheck
368 $accessCheck = \Drupal::service('permissions_by_term.access_check');
370 if (is_array($names)) {
371 foreach ($names as $name) {
372 if (!$accessCheck->isAccessAllowedByDatabase($term->getTermIdByName($name))) {
373 unset($options[$id]);
376 } elseif(is_string($names)) {
377 if (!$accessCheck->isAccessAllowedByDatabase($term->getTermIdByName($names))) {
378 unset($options[$id]);
388 * Implements hook_user_cancel().
390 * Deletes all term permissions for a user when their account is cancelled.
392 function permissions_by_term_user_cancel($edit, $account, $method) {
393 $deleted_user_id = $account->id();
395 /* @var \Drupal\permissions_by_term\Service\AccessStorage $access_storage */
396 $access_storage = \Drupal::service('permissions_by_term.access_storage');
397 $access_storage->deleteAllTermPermissionsByUserId($deleted_user_id);