Security update for permissions_by_term
[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\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;
18
19 /**
20  * Implements hook_help().
21  */
22 function permissions_by_term_help($route_name, RouteMatchInterface $arg) {
23   switch ($route_name) {
24     case 'help.page.permissions_by_term':
25       $output = '';
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>';
33       $output .= '<dl>';
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>';
42       $output .= '</dl>';
43
44       return $output;
45   }
46 }
47
48 /**
49  * Validation handler for permissions_by_term_form_alter().
50  */
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 = [];
57       if (!empty($terms)) {
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();
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     $form['access'] = [
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'],
128       '#weight' => -5,
129       '#tree' => TRUE,
130     ];
131
132     $aAllowedUsers = $access_storage->getAllowedUserIds($iTermId);
133     if (!empty($aAllowedUsers)) {
134       $aAllowedUsers = user_load_multiple($aAllowedUsers);
135       $sUserFormValue = $access_storage->getUserFormValue($aAllowedUsers);
136     }
137     else {
138       $sUserFormValue = NULL;
139     }
140
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
143     // manually.
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,
152       '#size' => 60,
153       '#autocomplete_route_name' => 'permissions_by_term.autocomplete_multiple',
154       '#weight' => -10,
155     ];
156
157     $aAllowedRoles = $access_storage->getRoleTermPermissionsByTid($iTermId);
158
159     // Firstly fetch all translated allowed role names.
160     $aTranslatedAllowedRoleNames = [];
161     foreach ($aAllowedRoles as $role) {
162       $aTranslatedAllowedRoleNames[] = $role;
163     }
164
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++;
171     }
172
173     // Generate the default values for the form.
174     $aSetRoles = [];
175     if (!empty($aTranslatedAllowedRoleNames)) {
176       foreach ($aTranslatedAllowedRoleNames as $role_name) {
177         $aSetRoles[] = $role_name;
178       }
179     }
180
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
187         listings.'),
188       '#default_value' => $aSetRoles,
189       '#options' => $aTranslatedUserRoles,
190       '#multiple' => FALSE,
191       '#weight' => 5,
192     ];
193
194     $form['#validate'][] = 'permissions_by_term_validate';
195     $form['actions']['submit']['#submit'][] = 'permissions_by_term_submit';
196   }
197 }
198
199 /**
200  * Implements hook_form_alter().
201  */
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'),
210     ];
211
212     $nid = null;
213     if (!empty($node = \Drupal::routeMatch()->getParameter('node'))) {
214       $nid = $node->id();
215     }
216
217     $viewFilePath = drupal_get_path('module', 'permissions_by_term') . '/src/View/node-details.html.twig';
218     /**
219      * @var \Drupal\permissions_by_term\Service\NodeEntityBundleInfo $nodeEntityBundleInfo
220      */
221     $nodeEntityBundleInfo = \Drupal::service('permissions_by_term.node_entity_bundle_info');
222
223     $form['permissions_by_term_info']['revision'] = array(
224       '#type' => 'item',
225       '#markup' => $nodeEntityBundleInfo->renderNodeDetails($viewFilePath, $nid),
226     );
227
228     $form['#attached']['library'][] = 'permissions_by_term/nodeForm';
229   }
230 }
231
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')))) {
236     return TRUE;
237   }
238   return FALSE;
239 }
240
241 /**
242  * Implements hook_node_access().
243  *
244  * Forwards user by drupal_access_denied(); to an access denied page, if a
245  * single restricted node is called.
246  *
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.
250  */
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);
257
258       return AccessResult::forbidden();
259     }
260
261     /* @var \Drupal\permissions_by_term\Service\AccessCheck $accessCheck */
262     $accessCheck = \Drupal::service('permissions_by_term.access_check');
263
264     return $accessCheck->handleNode($node->id());
265   }
266 }
267
268 /**
269  * Implements hook_node_grants().
270  */
271 function permissions_by_term_node_grants(\Drupal\Core\Session\AccountInterface $account, $op)
272 {
273     if ($op == 'view') {
274       /**
275        * @var \Drupal\permissions_by_term\Service\AccessStorage $accessStorage
276        */
277       $accessStorage = \Drupal::service('permissions_by_term.access_storage');
278       $grants = $accessStorage->getGids(\Drupal::currentUser());
279
280       return $grants;
281     }
282 }
283
284 /**
285  * Implements hook_node_access_records().
286  *
287  * Permissions can be rebuild at /admin/reports/status/rebuild.
288  */
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()) {
292     return;
293   }
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;
302       break;
303     }
304   }
305   if (!$has_term_access_restrictions) {
306     return;
307   }
308
309   /**
310    * @var \Drupal\permissions_by_term\Service\NodeAccess $nodeAccess
311    */
312   $nodeAccess = \Drupal::service('permissions_by_term.node_access');
313   $grantObject = $nodeAccess->createGrant($node->id(), $node->id());
314
315   $grants[] = [
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,
322     'fallback' => 1,
323     'nid' => $node->id(),
324   ];
325
326   return $grants;
327 }
328
329 /**
330  * Implements hook_user_insert().
331  */
332 function permissions_by_term_user_insert($user) {
333   Cache::invalidateTags(['search_index:node_search']);
334 }
335
336 /**
337  * Implements hook_user_update().
338  */
339 function permissions_by_term_user_update($user) {
340   if (\Drupal::currentUser()->hasPermission('administer permissions')) {
341     Cache::invalidateTags(['search_index:node_search']);
342   }
343 }
344
345 /**
346  * Implements hook_node_insert().
347  */
348 function permissions_by_term_node_insert($node) {
349   Cache::invalidateTags(['search_index:node_search']);
350 }
351
352 /**
353  * Implements hook_options_list_alter().
354  */
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') {
360         /**
361          * @var \Drupal\permissions_by_term\Service\Term $term
362          */
363         $term = \Drupal::service('permissions_by_term.term');
364
365         /**
366          * @var \Drupal\permissions_by_term\Service\AccessCheck $accessCheck
367          */
368         $accessCheck = \Drupal::service('permissions_by_term.access_check');
369
370         if (is_array($names)) {
371           foreach ($names as $name) {
372             if (!$accessCheck->isAccessAllowedByDatabase($term->getTermIdByName($name))) {
373               unset($options[$id]);
374             }
375           }
376         } elseif(is_string($names)) {
377           if (!$accessCheck->isAccessAllowedByDatabase($term->getTermIdByName($names))) {
378             unset($options[$id]);
379           }
380         }
381       }
382
383     }
384   }
385 }
386
387 /**
388  * Implements hook_user_cancel().
389  *
390  * Deletes all term permissions for a user when their account is cancelled.
391  */
392 function permissions_by_term_user_cancel($edit, $account, $method) {
393   $deleted_user_id = $account->id();
394
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);
398 }