Security update for permissions_by_term
[yaffs-website] / web / modules / contrib / permissions_by_term / src / Service / AccessStorage.php
1 <?php
2
3 namespace Drupal\permissions_by_term\Service;
4
5 use Drupal\Core\Database\Connection;
6 use Drupal\Component\Utility\Tags;
7 use Drupal\Core\Form\FormState;
8 use Drupal\Core\Session\AccountInterface;
9 use Drupal\user\Entity\User;
10
11 /**
12  * Class AccessStorage.
13  *
14  * @package Drupal\permissions_by_term
15  */
16 class AccessStorage {
17
18   /**
19    * The database connection.
20    *
21    * @var Connection
22    */
23   protected $database;
24
25   /**
26    * The term name for which the access is set.
27    *
28    * @var string
29    */
30   protected $sTermName;
31
32   /**
33    * The user ids which gain granted access.
34    *
35    * @var array
36    */
37   protected $aUserIdsGrantedAccess;
38
39   /**
40    * The roles with granted access.
41    *
42    * @var array
43    */
44   protected $aSubmittedRolesGrantedAccess;
45
46   /**
47    * @var Term
48    */
49   protected $term;
50
51   /**
52    * @var string
53    */
54   const NODE_ACCESS_REALM = 'permissions_by_term';
55
56   /**
57    * @var AccessCheck
58    */
59   protected $accessCheck;
60
61   /**
62    * AccessStorageService constructor.
63    *
64    * @param Connection  $database
65    * @param Term        $term
66    * @param AccessCheck $accessCheck
67    */
68   public function __construct(Connection $database, Term $term, AccessCheck $accessCheck) {
69     $this->database  = $database;
70     $this->term = $term;
71     $this->accessCheck = $accessCheck;
72   }
73
74   /**
75    * Gets submitted roles with granted access from form.
76    *
77    * @return array
78    *   An array with chosen roles.
79    */
80   public function getSubmittedRolesGrantedAccess(FormState $form_state) {
81     $aRoles       = $form_state->getValue('access')['role'];
82     $aChosenRoles = [];
83     foreach ($aRoles as $sRole) {
84       if ($sRole !== 0) {
85         $aChosenRoles[] = $sRole;
86       }
87     }
88     return $aChosenRoles;
89   }
90
91   /**
92    * @param FormState $form_state
93    */
94   public function checkIfUsersExists(FormState $form_state) {
95     $sAllowedUsers = $form_state->getValue('access')['user'];
96     $aAllowedUsers = Tags::explode($sAllowedUsers);
97     foreach ($aAllowedUsers as $sUserId) {
98       $aUserId = \Drupal::entityQuery('user')
99         ->condition('uid', $sUserId)
100         ->execute();
101       if (empty($aUserId)) {
102         $form_state->setErrorByName('access][user',
103           t('The user with ID %user_id does not exist.',
104           ['%user_id' => $sUserId]));
105       }
106     }
107   }
108
109   /**
110    * @param int $term_id
111    *
112    * @return array
113    */
114   public function getUserTermPermissionsByTid($term_id) {
115     return $this->database->select('permissions_by_term_user', 'pu')
116       ->condition('tid', $term_id)
117       ->fields('pu', ['uid'])
118       ->execute()
119       ->fetchCol();
120   }
121
122   /**
123    * @param int   $uid
124    * @param array $rids
125    *
126    * @return array
127    */
128   public function getPermittedTids($uid, $rids) {
129     $permittedTids = $this->database->select('permissions_by_term_user', 'pu')
130       ->condition('uid', $uid)
131       ->fields('pu', ['tid'])
132       ->execute()
133       ->fetchCol();
134
135     foreach ($rids as $rid) {
136       $permittedTidsByRid = $this->database->select('permissions_by_term_role', 'pr')
137         ->condition('rid', $rid)
138         ->fields('pr', ['tid'])
139         ->execute()
140         ->fetchCol();
141
142       $permittedTids = array_merge($permittedTidsByRid, $permittedTids);
143     }
144
145     return array_unique($permittedTids);
146   }
147
148   /**
149    * @param array $tids
150    *
151    * @return array
152    */
153   public function getUserTermPermissionsByTids($tids) {
154     $uids = [];
155
156     foreach ($tids as $tid) {
157       if (!empty($tmpUids = $this->getUserTermPermissionsByTid($tid))) {
158         foreach ($tmpUids as $tmpUid) {
159           $uids[] = $tmpUid;
160         }
161       }
162     }
163
164     return $uids;
165   }
166
167   /**
168    * @param int $term_id
169    *
170    * @return array
171    */
172   public function getRoleTermPermissionsByTid($term_id) {
173     return $this->database->select('permissions_by_term_role', 'pr')
174       ->condition('tid', $term_id)
175       ->fields('pr', ['rid'])
176       ->execute()
177       ->fetchCol();
178   }
179
180   /**
181    * @param array $tids
182    *
183    * @return array
184    */
185   public function getRoleTermPermissionsByTids($tids) {
186     $rids = [];
187
188     foreach ($tids as $tid) {
189       $tmpRids = $this->getRoleTermPermissionsByTid($tid);
190       if (!empty($tmpRids)) {
191         foreach ($tmpRids as $tmpRid) {
192           $rids[] = $tmpRid;
193         }
194       }
195     }
196
197     return $rids;
198   }
199
200   /**
201    * @param string $sUsername
202    *
203    * @return int
204    */
205   public function getUserIdByName($sUsername) {
206     return $this->database->select('users_field_data', 'ufd')
207       ->condition('name', $sUsername)
208       ->fields('ufd', ['uid'])
209       ->execute()
210       ->fetchAssoc();
211   }
212
213   /**
214    * @param array $aUserNames
215    *
216    * @return array
217    */
218   public function getUserIdsByNames($aUserNames) {
219     $aUserIds = [];
220     foreach ($aUserNames as $userName) {
221       $iUserId    = $this->getUserIdByName($userName)['uid'];
222       $aUserIds[] = $iUserId['uid'];
223     }
224     return $aUserIds;
225   }
226
227   /**
228    * @param int $term_id
229    *
230    * @return array
231    */
232   public function getAllowedUserIds($term_id) {
233     $query = $this->database->select('permissions_by_term_user', 'p')
234       ->fields('p', ['uid'])
235       ->condition('p.tid', $term_id);
236
237     // fetchCol() returns all results, fetchAssoc() only "one" result.
238     return $query->execute()
239       ->fetchCol();
240   }
241
242   /**
243    * @param array $aUserIdsAccessRemove
244    * @param int   $term_id
245    */
246   public function deleteTermPermissionsByUserIds($aUserIdsAccessRemove, $term_id) {
247     foreach ($aUserIdsAccessRemove as $iUserId) {
248       $this->database->delete('permissions_by_term_user')
249         ->condition('uid', $iUserId, '=')
250         ->condition('tid', $term_id, '=')
251         ->execute();
252     }
253   }
254
255   /**
256    * @param array $aRoleIdsAccessRemove
257    * @param int   $term_id
258    */
259   public function deleteTermPermissionsByRoleIds($aRoleIdsAccessRemove, $term_id) {
260     foreach ($aRoleIdsAccessRemove as $sRoleId) {
261       $this->database->delete('permissions_by_term_role')
262         ->condition('rid', $sRoleId, '=')
263         ->condition('tid', $term_id, '=')
264         ->execute();
265     }
266   }
267
268   /**
269    * @param int $userId
270    */
271   public function deleteAllTermPermissionsByUserId($userId) {
272     $this->database->delete('permissions_by_term_user')
273       ->condition('uid', $userId, '=')
274       ->execute();
275   }
276
277   /**
278    * @param array $aUserIdsGrantedAccess
279    * @param int   $term_id
280    *
281    * @throws \Exception
282    */
283   public function addTermPermissionsByUserIds($aUserIdsGrantedAccess, $term_id) {
284     foreach ($aUserIdsGrantedAccess as $iUserIdGrantedAccess) {
285       $this->database->insert('permissions_by_term_user')
286         ->fields(['tid', 'uid'], [$term_id, $iUserIdGrantedAccess])
287         ->execute();
288     }
289   }
290
291   /**
292    * @param array $aRoleIdsGrantedAccess
293    * @param int   $term_id
294    *
295    * @throws \Exception
296    */
297   public function addTermPermissionsByRoleIds($aRoleIdsGrantedAccess, $term_id) {
298     foreach ($aRoleIdsGrantedAccess as $sRoleIdGrantedAccess) {
299       $this->database->insert('permissions_by_term_role')
300         ->fields(['tid', 'rid'], [$term_id, $sRoleIdGrantedAccess])
301         ->execute();
302     }
303   }
304
305   /**
306    * Gets the user ids which have been submitted by form.
307    *
308    * Users which will gain granted access to taxonomy terms.
309    *
310    * @return array
311    *   The user ids which have been submitted.
312    */
313   public function getSubmittedUserIds() {
314     /* There's a $this->oFormState->getValues() method, but
315      * it is loosing multiple form values. Don't know why.
316      * So there're some custom lines on the $_REQUEST array. */
317     $sRawUsers = $_REQUEST['access']['user'];
318
319     if (empty($sRawUsers)) {
320       return [];
321     }
322
323     $aRawUsers = explode('),', $sRawUsers);
324     $aUserIds = [];
325     if (!empty($aRawUsers)) {
326       foreach ($aRawUsers as $sRawUser) {
327         $aTempRawUser = explode(' (', $sRawUser);
328         // We check the user id by user name. If we get null back, the user might
329         // be the Anonymous user. In that case we get null back and then we use
330         // this id, which is 0.
331         if (!empty($aTempRawUser[1])) {
332           $fallback_user_id = str_replace(')', '', $aTempRawUser[1]);
333           $fallback_user_id = intval($fallback_user_id);
334         }
335
336         $sRawUser = trim($aTempRawUser['0']);
337         $uid = $this->getUserIdByName($sRawUser)['uid'];
338         if ($uid == NULL && $fallback_user_id == 0) {
339           // We might want to give access to the Anonymous user.
340           $aUserIds[] = 0;
341         }
342         else {
343           $aUserIds[] = $this->getUserIdByName($sRawUser)['uid'];
344         }
345       }
346     }
347
348     return $aUserIds;
349   }
350
351   /**
352    * Saves term permissions by users.
353    *
354    * Opposite to save term permission by roles.
355    *
356    * @param FormState $form_state
357    * @param int       $term_id
358    *
359    * @return array
360    *   Data for database queries.
361    */
362   public function saveTermPermissions(FormState $form_state, $term_id) {
363     $aExistingUserPermissions       = $this->getUserTermPermissionsByTid($term_id);
364     $aSubmittedUserIdsGrantedAccess = $this->getSubmittedUserIds();
365
366     $aExistingRoleIdsGrantedAccess = $this->getRoleTermPermissionsByTid($term_id);
367     $aSubmittedRolesGrantedAccess  = $this->getSubmittedRolesGrantedAccess($form_state);
368
369     $aRet = $this->getPreparedDataForDatabaseQueries($aExistingUserPermissions,
370       $aSubmittedUserIdsGrantedAccess, $aExistingRoleIdsGrantedAccess,
371       $aSubmittedRolesGrantedAccess);
372
373     // Run the database queries.
374     $this->deleteTermPermissionsByUserIds($aRet['UserIdPermissionsToRemove'], $term_id);
375     $this->addTermPermissionsByUserIds($aRet['UserIdPermissionsToAdd'], $term_id);
376
377     $this->deleteTermPermissionsByRoleIds($aRet['UserRolePermissionsToRemove'], $term_id);
378     $this->addTermPermissionsByRoleIds($aRet['aRoleIdPermissionsToAdd'], $term_id);
379
380     return $aRet;
381   }
382
383   /**
384    * Get array items to remove.
385    *
386    * The array items which aren't in the new items array, but are in old items
387    * array, will be returned.
388    *
389    * @param array $aExistingItems
390    *   The existing array items.
391    * @param array|bool $aNewItems
392    *   Either false if there're no new items or an array with items.
393    *
394    * @return array
395    *   The array items to remove.
396    */
397   private function getArrayItemsToRemove($aExistingItems, $aNewItems) {
398     $aRet = [];
399
400     foreach ($aExistingItems as $existingItem) {
401       if (!in_array($existingItem, $aNewItems)) {
402         $aRet[] = $existingItem;
403       }
404     }
405
406     return $aRet;
407   }
408
409   /**
410    * Get the array items to add.
411    *
412    * The items in the new items array, which aren't in the existing items array,
413    * will be returned.
414    *
415    * @param array $aNewItems
416    *   The new array items.
417    * @param array $aExistingItems
418    *   The existing array items.
419    *
420    * @return array
421    *   The items which needs to be added.
422    */
423   private function getArrayItemsToAdd($aNewItems, $aExistingItems) {
424     $aRet = [];
425
426     foreach ($aNewItems as $newItem) {
427       if (!in_array($newItem, $aExistingItems)) {
428         $aRet[] = $newItem;
429       }
430     }
431
432     return $aRet;
433   }
434
435   /**
436    * Prepares the data which has to be applied to the database.
437    *
438    * @param array $aExistingUserPermissions
439    *   The permissions for existing user.
440    * @param array $aSubmittedUserIdsGrantedAccess
441    *   The user ids which get access.
442    * @param array $aExistingRoleIdsGrantedAccess
443    *   The existing role ids.
444    * @param array $aSubmittedRolesGrantedAccess
445    *   The user roles which get access.
446    *
447    * @return array
448    *   User ID and role data.
449    */
450   public function getPreparedDataForDatabaseQueries($aExistingUserPermissions,
451                                                     $aSubmittedUserIdsGrantedAccess,
452                                                     $aExistingRoleIdsGrantedAccess,
453                                                     $aSubmittedRolesGrantedAccess) {
454     // Fill array with user ids to remove permission.
455     $aRet['UserIdPermissionsToRemove'] =
456       $this->getArrayItemsToRemove($aExistingUserPermissions,
457         $aSubmittedUserIdsGrantedAccess);
458
459     // Fill array with user ids to add permission.
460     $aRet['UserIdPermissionsToAdd'] =
461       $this->getArrayItemsToAdd($aSubmittedUserIdsGrantedAccess,
462         $aExistingUserPermissions);
463
464     // Fill array with user roles to remove permission.
465     $aRet['UserRolePermissionsToRemove'] =
466       $this->getArrayItemsToRemove($aExistingRoleIdsGrantedAccess,
467         $aSubmittedRolesGrantedAccess);
468
469     // Fill array with user roles to add permission.
470     $aRet['aRoleIdPermissionsToAdd'] =
471       $this->getArrayItemsToAdd($aSubmittedRolesGrantedAccess,
472         $aExistingRoleIdsGrantedAccess);
473
474     return $aRet;
475   }
476
477   /**
478    * The form value for allowed users as string to be shown to the user.
479    *
480    * @param User[] $aAllowedUsers
481    *   An array with the allowed users.
482    *
483    * @return null|string
484    *   Either null or the user name.
485    */
486   public function getUserFormValue($aAllowedUsers) {
487
488     $sUserInfos = '';
489
490     if (!empty($aAllowedUsers)) {
491
492       foreach ($aAllowedUsers as $oUser) {
493         $iUid = intval($oUser->id());
494         if ($iUid !== 0) {
495           $sUsername = $oUser->getDisplayName();
496         }
497         else {
498           $sUsername = t('Anonymous User');
499         }
500
501         $sUserInfos .= "$sUsername ($iUid), ";
502       }
503
504       // Remove space and comma at the end of the string.
505       $sUserInfos = substr($sUserInfos, 0, -2);
506     }
507
508     return $sUserInfos;
509   }
510
511   /**
512    * @return array
513    */
514   public function getAllNids()
515   {
516     $query = $this->database->select('node', 'n')
517         ->fields('n', ['nid']);
518
519     return $query->execute()
520         ->fetchCol();
521   }
522
523   /**
524    * @param $nid
525    *
526    * @return array
527    */
528   public function getTidsByNid($nid)
529   {
530     $node = \Drupal::entityTypeManager()->getStorage('node')->load($nid);
531     $tids = [];
532
533     foreach ($node->getFields() as $field) {
534       if ($field->getFieldDefinition()->getType() == 'entity_reference' && $field->getFieldDefinition()->getSetting('target_type') == 'taxonomy_term') {
535         $aReferencedTaxonomyTerms = $field->getValue();
536         if (!empty($aReferencedTaxonomyTerms)) {
537           foreach ($aReferencedTaxonomyTerms as $aReferencedTerm) {
538             if (isset($aReferencedTerm['target_id'])) {
539               $tids[] = $aReferencedTerm['target_id'];
540             }
541           }
542         }
543       }
544     }
545
546     return $tids;
547   }
548
549   /**
550    * @return array
551    */
552   public function getAllUids()
553   {
554     $nodes = \Drupal::entityQuery('user')
555       ->execute();
556
557     return array_values($nodes);
558   }
559
560   /**
561    * @param $nid
562    *
563    * @return array
564    */
565   public function getNodeType($nid)
566   {
567     $query = $this->database->select('node', 'n')
568       ->fields('n', ['type'])
569       ->condition('n.nid', $nid);
570
571     return $query->execute()
572       ->fetchAssoc()['type'];
573   }
574
575   /**
576    * @param $nid
577    *
578    * @return array
579    */
580   public function getLangCode($nid)
581   {
582     $query = $this->database->select('node', 'n')
583       ->fields('n', ['langcode'])
584       ->condition('n.nid', $nid);
585
586     return $query->execute()
587       ->fetchAssoc()['langcode'];
588   }
589
590   /**
591    * @param AccountInterface $user
592    *
593    * @return array
594    */
595   public function getGids(AccountInterface $user)
596   {
597     $grants = null;
598
599     if (!empty($permittedNids = $this->computePermittedTids($user))) {
600       $query = $this->database->select('node_access', 'na')
601         ->fields('na', ['gid'])
602         ->condition('na.nid', $permittedNids, 'IN')
603         ->condition('na.realm', self::NODE_ACCESS_REALM);
604
605       $gids = $query->execute()->fetchCol();
606
607       foreach ($gids as $gid) {
608         $grants[self::NODE_ACCESS_REALM][] = $gid;
609       }
610     }
611
612     return $grants;
613   }
614
615   private function computePermittedTids(AccountInterface $user)
616   {
617     $nidsWithNoTidRestriction = $this->getNidsWithNoTidRestriction();
618     $nidsByTids = $this->term->getNidsByTids($this->getPermittedTids($user->id(), $user->getRoles()));
619
620     if (\Drupal::config('permissions_by_term.settings.single_term_restriction')->get('value')) {
621       $permittedNids = [];
622       foreach ($nidsByTids as $nid) {
623         if($this->accessCheck->canUserAccessByNodeId($nid)) {
624           $permittedNids[] = $nid;
625         }
626       }
627       $nidsByTids = $permittedNids;
628     }
629
630     if (!empty($nidsByTids)) {
631       return array_merge(
632         $this->getNidsWithNoTidRestriction(),
633         $nidsByTids
634       );
635     }
636
637     return $nidsWithNoTidRestriction;
638   }
639
640   private function getNidsWithNoTidRestriction() {
641     $queryNidsNoRestriction = $this->database->select('node', 'n')
642       ->fields('n', ['nid']);
643
644     $queryNidsNoRestriction->leftJoin('taxonomy_index', 'ti', 'n.nid = ti.nid');
645     $queryNidsNoRestriction->leftJoin('permissions_by_term_user', 'ptu', 'ptu.tid = ti.tid');
646     $queryNidsNoRestriction->condition('ptu.tid', NULL, 'IS');
647     $queryNidsNoRestriction->leftJoin('permissions_by_term_role', 'ptr', 'ptr.tid = ti.tid');
648     $queryNidsNoRestriction->condition('ptr.tid', NULL, 'IS');
649
650      return $queryNidsNoRestriction
651       ->execute()
652       ->fetchCol();
653   }
654
655
656   /**
657    * @param $uid
658    *
659    * @return array
660    */
661   public function getAllNidsUserCanAccess($uid)
662   {
663     $query = $this->database->select('node_access', 'na')
664       ->fields('na', ['nid'])
665       ->condition('na.realm', 'permissions_by_term__uid_' . $uid);
666
667     return $query->execute()
668       ->fetchCol();
669   }
670
671   /**
672    * @param $tid
673    *
674    * @return array
675    */
676   public function getNidsByTid($tid)
677   {
678       $query = $this->database->select('taxonomy_index', 'ti')
679         ->fields('ti', ['nid'])
680         ->condition('ti.tid', $tid);
681
682       return $query->execute()->fetchCol();
683   }
684
685 }