More updates to stop using dev or alpha or beta versions.
[yaffs-website] / web / modules / contrib / permissions_by_term / permissions_by_term.module
1 <?php
2
3 /**
4  * @file
5  * Allows access to terms in a vocabulary to be limited by user or role.
6  */
7
8 use Drupal\Core\Cache\Cache;
9 use Drupal\Core\Entity\EntityInterface;
10 use Drupal\Core\Form\FormState;
11 use Drupal\Core\Form\FormStateInterface;
12 use Drupal\Core\Routing\RouteMatchInterface;
13 use Drupal\Core\Session\AccountInterface;
14 use Drupal\node\NodeInterface;
15 use Drupal\taxonomy\Entity\Term;
16
17 /**
18  * Implements hook_help().
19  */
20 function permissions_by_term_help($route_name, RouteMatchInterface $arg) {
21   switch ($route_name) {
22     case 'help.page.permissions_by_term':
23       $output = '';
24       $output .= '<h3>' . t('About') . '</h3>';
25       $output .= '<p>' . t('The "Permissions by Term" (PbT) module allows taxonomy administrators the
26         ability to restrict setting individual terms on nodes by user
27         or role. If a user is unable to set any terms for a required
28         vocabulary, they are blocked from adding or editing content with
29         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>';
30       $output .= '<h3>' . t('Uses') . '</h3>';
31       $output .= '<dl>';
32       $output .= '<dt>' . t('General') . '</dt>';
33       $output .= '<dd>' . t('Use Permissions by Term to easily build access-restricted content areas on your websites.') . '</dd>';
34       $output .= '<dt>' . t('Lightweight Access Control') . '</dt>';
35       $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>';
36       $output .= '<dt>' . t('Example use cases') . '</dt>';
37       $output .= '<dd>' . t('A club or service site with premium- or member-only content.') . '</dd>';
38       $output .= '<dd>' . t('School websites with content intended for teachers only and content aimed at individual classes within the school.') . '</dd>';
39       $output .= '<dd>' . t('Company intranets with sensitive or proprietary content alongside non-restricted content.') . '</dd>';
40       $output .= '</dl>';
41
42       return $output;
43   }
44 }
45
46 /**
47  * Validation handler for permissions_by_term_form_alter().
48  */
49 function permissions_by_term_validate($form, FormState $oFormState) {
50   foreach ($form as $field) {
51     if (!is_object($field) && !empty($field['widget']['target_id']['#target_type']) && $field['widget']['target_id']['#target_type'] == 'taxonomy_term') {
52       $field_name = $field['widget']['#field_name'];
53       $terms = $oFormState->getValues()[$field_name]['target_id'];
54       $not_allowed_term_names = [];
55       if (!empty($terms)) {
56         foreach ($terms as $term) {
57           if (!empty($term['target_id'])) {
58             $term_id = $term['target_id'];
59             /* @var \Drupal\permissions_by_term\Service\AccessCheck $access_check_service */
60             $access_check_service = \Drupal::service('permissions_by_term.access_check');
61             if (!$access_check_service->isAccessAllowedByDatabase($term_id)) {
62               $term = Term::load($term_id);
63               $not_allowed_term_names[] = $term->getName();
64             }
65           }
66         }
67       }
68     }
69   }
70   if (!empty($not_allowed_term_names)) {
71     if (count($not_allowed_term_names) > 1) {
72       $term_names = implode(', ', $not_allowed_term_names);
73     }
74     else {
75       $term_names = $not_allowed_term_names['0'];
76     }
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]));
79   }
80 }
81
82 /**
83  * Submit handler for permissions_by_term_form_alter().
84  */
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);
90
91   // Check if we need to rebuild node_access by term id
92   $invalidate_cache_tag = false;
93
94   // Has anything has changed?
95   foreach($access_update as $values) {
96     if(!empty($values)) {
97       $invalidate_cache_tag = true;
98       break;
99     }
100   }
101
102   // Do we need to flush the cache?
103   if($invalidate_cache_tag === true) {
104     Cache::invalidateTags(['search_index:node_search']);
105   }
106 }
107
108 /**
109  * Implements hook_form_alter().
110  */
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();
114
115     /* @var \Drupal\permissions_by_term\Service\AccessStorage $access_storage */
116     $access_storage = \Drupal::service('permissions_by_term.access_storage');
117
118     $description = <<<EOT
119 To limit access to this term by user(s) or role(s), select users or roles above.
120 If left empty, all users will have access to content, related to this taxonomy term
121 and this taxonomy term itself.
122 EOT;
123
124     $form['access'] = [
125       '#type' => 'fieldset',
126       '#title' => t('Permissions'),
127       '#description' => t($description),
128       '#collapsible' => TRUE,
129       '#collapsed' => TRUE,
130       '#attributes' => ['id' => 'fieldset_term_access'],
131       '#weight' => -5,
132       '#tree' => TRUE,
133     ];
134
135     $aAllowedUsers = $access_storage->getAllowedUserIds($iTermId);
136     if (!empty($aAllowedUsers)) {
137       $aAllowedUsers = user_load_multiple($aAllowedUsers);
138       $sUserFormValue = $access_storage->getUserFormValue($aAllowedUsers);
139     }
140     else {
141       $sUserFormValue = NULL;
142     }
143
144     $description = <<<EOT
145 Enter a comma-separated list of user names who will be able to access content,
146 related to this taxonomy term.
147 EOT;
148
149     // Note that the autocomplete widget will only enable for users with the
150     // 'access profiles' permission. Other users will have to specify the name
151     // manually.
152     $form['access']['user'] = [
153       '#type' => 'entity_autocomplete',
154       '#target_type' => 'user',
155       '#title' => t('Allowed users'),
156       '#description' => t($description),
157       '#value' => $sUserFormValue,
158       '#size' => 60,
159       '#autocomplete_route_name' => 'permissions_by_term.autocomplete_multiple',
160       '#weight' => -10,
161     ];
162
163     $aAllowedRoles = $access_storage->getRoleTermPermissionsByTid($iTermId);
164
165     // Firstly fetch all translated allowed role names.
166     $aTranslatedAllowedRoleNames = [];
167     foreach ($aAllowedRoles as $role) {
168       $aTranslatedAllowedRoleNames[] = $role;
169     }
170
171     // Get all roles for the complete form and translate them.
172     $aTranslatedUserRoles = [];
173     $array_key_counter = 1;
174     foreach (user_roles() as $user_role_id => $user_role_name) {
175       $aTranslatedUserRoles[$user_role_id] = $user_role_name->label();
176       $array_key_counter++;
177     }
178
179     // Generate the default values for the form.
180     $aSetRoles = [];
181     if (!empty($aTranslatedAllowedRoleNames)) {
182       foreach ($aTranslatedAllowedRoleNames as $role_name) {
183         $aSetRoles[] = $role_name;
184       }
185     }
186
187     $description = <<<EOT
188 Select user roles who will be able to access content, related to this taxonomy term.
189 EOT;
190
191     // Now, lets do the Roles table.
192     $form['access']['role'] = [
193       '#type' => 'checkboxes',
194       '#title' => t('Allowed roles'),
195       '#description' => t($description),
196       '#default_value' => $aSetRoles,
197       '#options' => $aTranslatedUserRoles,
198       '#multiple' => FALSE,
199       '#weight' => 5,
200     ];
201
202     $form['#validate'][] = 'permissions_by_term_validate';
203     $form['actions']['submit']['#submit'][] = 'permissions_by_term_submit';
204   }
205 }
206
207 /**
208  * Implements hook_form_alter().
209  */
210 function permissions_by_term_form_alter(&$form, FormStateInterface $oFormState, $form_id) {
211   $form['#validate'][] = 'permissions_by_term_validate';
212   if (isNodeEditForm()) {
213     $form['permissions_by_term_info'] = [
214       '#type' => 'details',
215       '#group' => 'advanced',
216       '#title' => t('Permissions by Term'),
217       '#access' => \Drupal::currentUser()->hasPermission('show term permissions on node edit page'),
218     ];
219
220     $nid = null;
221     if (!empty($node = \Drupal::routeMatch()->getParameter('node'))) {
222       $nid = $node->id();
223     }
224
225     $viewFilePath = drupal_get_path('module', 'permissions_by_term') . '/src/View/node-details.html.twig';
226     /**
227      * @var \Drupal\permissions_by_term\Service\NodeEntityBundleInfo $nodeEntityBundleInfo
228      */
229     $nodeEntityBundleInfo = \Drupal::service('permissions_by_term.node_entity_bundle_info');
230
231     $form['permissions_by_term_info']['revision'] = array(
232       '#type' => 'item',
233       '#markup' => $nodeEntityBundleInfo->renderNodeDetails($viewFilePath, $nid),
234     );
235
236     $form['#attached']['library'][] = 'permissions_by_term/nodeForm';
237   }
238 }
239
240 function isNodeEditForm() {
241   $currentPath = \Drupal::service('path.current')->getPath();
242   if (is_numeric(strpos($currentPath, '/node/'))
243     && (is_numeric(strpos($currentPath, '/edit')) || is_numeric(strpos($currentPath, '/add')))) {
244     return TRUE;
245   }
246   return FALSE;
247 }
248
249 /**
250  * Implements hook_node_access().
251  *
252  * Forwards user by drupal_access_denied(); to an access denied page, if a
253  * single restricted node is called.
254  *
255  * This hook is not fired if admin is logged in. Users with the
256  * "bypass node access" permission may always view and edit content
257  * through the administrative interface.
258  */
259 function permissions_by_term_node_access(NodeInterface $node, $op, AccountInterface $account) {
260   /* @var \Drupal\permissions_by_term\Service\AccessCheck $accessCheck */
261   $accessCheck = \Drupal::service('permissions_by_term.access_check');
262
263   return $accessCheck->handleNode($node->id(), $node->language()->getId());
264 }
265
266 /**
267  * Implements hook_node_grants().
268  */
269 function permissions_by_term_node_grants(\Drupal\Core\Session\AccountInterface $account, $op)
270 {
271     if ($op == 'view') {
272       /**
273        * @var \Drupal\permissions_by_term\Service\AccessStorage $accessStorage
274        */
275       $accessStorage = \Drupal::service('permissions_by_term.access_storage');
276       $grants = $accessStorage->getGids(\Drupal::currentUser());
277
278       return $grants;
279     }
280 }
281
282 /**
283  * Implements hook_node_access_records().
284  *
285  * Permissions can be rebuild at /admin/reports/status/rebuild.
286  */
287 function permissions_by_term_node_access_records(\Drupal\node\NodeInterface $node) {
288   // Do not return any grants for nodes that this module doesn't manage.
289   if (!$node->isPublished()) {
290     return;
291   }
292   $has_term_access_restrictions = FALSE;
293   /* @var \Drupal\permissions_by_term\Service\AccessStorage $access_storage */
294   $access_storage = \Drupal::service('permissions_by_term.access_storage');
295   foreach ($access_storage->getTidsByNid($node->id()) as $tid) {
296     /* @var \Drupal\permissions_by_term\Service\AccessCheck $access_check_service */
297     $access_check_service = \Drupal::service('permissions_by_term.access_check');
298     if ($access_check_service->isAnyPermissionSetForTerm($tid, $node->language()->getId())) {
299       $has_term_access_restrictions = TRUE;
300       break;
301     }
302   }
303   if (!$has_term_access_restrictions) {
304     return;
305   }
306
307   /**
308    * @var \Drupal\permissions_by_term\Service\NodeAccess $nodeAccess
309    */
310   $nodeAccess = \Drupal::service('permissions_by_term.node_access');
311   $grantObject = $nodeAccess->createGrant($node->id(), $node->id());
312
313   $grants[] = [
314     'realm' => $grantObject->realm,
315     'gid' => $grantObject->gid,
316     'grant_view' => $grantObject->grant_view,
317     'grant_update' => $grantObject->grant_update,
318     'grant_delete' => $grantObject->grant_delete,
319     'nid' => $node->id(),
320   ];
321
322   return $grants;
323 }
324
325 /**
326  * Implements hook_user_insert().
327  */
328 function permissions_by_term_user_insert($user) {
329   Cache::invalidateTags(['search_index:node_search']);
330 }
331
332 /**
333  * Implements hook_user_update().
334  */
335 function permissions_by_term_user_update($user) {
336   if (\Drupal::currentUser()->hasPermission('administer permissions')) {
337     Cache::invalidateTags(['search_index:node_search']);
338   }
339 }
340
341 /**
342  * Implements hook_node_insert().
343  */
344 function permissions_by_term_node_insert($node) {
345   Cache::invalidateTags(['search_index:node_search']);
346 }
347
348 /**
349  * Implements hook_options_list_alter().
350  */
351 function permissions_by_term_options_list_alter(array &$options, array $context) {
352   $fieldDefinitionSettings = $context['fieldDefinition']->getFieldStorageDefinition()->getSettings();
353   if (!empty($fieldDefinitionSettings['target_type']) && $fieldDefinitionSettings['target_type'] == 'taxonomy_term') {
354     foreach ($options as $id => $names) {
355       if ($id !== '_none') {
356         /**
357          * @var \Drupal\permissions_by_term\Service\AccessCheck $accessCheck
358          */
359         $accessCheck = \Drupal::service('permissions_by_term.access_check');
360
361         if (is_array($names)) {
362           foreach ($names as $group_id => $name) {
363             if (!$accessCheck->isAccessAllowedByDatabase($group_id)) {
364               unset($options[$id]);
365             }
366           }
367         } elseif(is_string($names)) {
368           if (!$accessCheck->isAccessAllowedByDatabase($id)) {
369             unset($options[$id]);
370           }
371         }
372       }
373
374     }
375   }
376 }
377
378 /**
379  * Implements hook_user_cancel().
380  *
381  * Deletes all term permissions for a user when their account is cancelled.
382  */
383 function permissions_by_term_user_cancel($edit, $account, $method) {
384   $deleted_user_id = $account->id();
385
386   /* @var \Drupal\permissions_by_term\Service\AccessStorage $access_storage */
387   $access_storage = \Drupal::service('permissions_by_term.access_storage');
388   $access_storage->deleteAllTermPermissionsByUserId($deleted_user_id);
389 }
390
391 /**
392  * Implements hook_ENTITY_TYPE_delete().
393  *
394  * Deletes all term permissions from storage when a term is deleted.
395  */
396 function permissions_by_term_taxonomy_term_delete(EntityInterface $entity) {
397   /* @var \Drupal\permissions_by_term\Service\AccessStorage $access_storage */
398   $access_storage = \Drupal::service('permissions_by_term.access_storage');
399   $access_storage->deleteAllTermPermissionsByTid($entity->id());
400 }