Fix token subscriber to format the display of the custom tokens
[civicrm-core.git] / CRM / ACL / BAO / ACL.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * Access Control List
20 */
21 class CRM_ACL_BAO_ACL extends CRM_ACL_DAO_ACL {
22 /**
23 * @var string
24 */
25 public static $_entityTable = NULL;
26 public static $_objectTable = NULL;
27 public static $_operation = NULL;
28
29 public static $_fieldKeys = NULL;
30
31 /**
32 * Available operations for pseudoconstant.
33 *
34 * @return array
35 */
36 public static function operation() {
37 if (!self::$_operation) {
38 self::$_operation = [
39 'View' => ts('View'),
40 'Edit' => ts('Edit'),
41 'Create' => ts('Create'),
42 'Delete' => ts('Delete'),
43 'Search' => ts('Search'),
44 'All' => ts('All'),
45 ];
46 }
47 return self::$_operation;
48 }
49
50 /**
51 * Construct an associative array of an ACL rule's properties
52 *
53 * @param string $format
54 * Sprintf format for array.
55 * @param bool $hideEmpty
56 * Only return elements that have a value set.
57 *
58 * @return array
59 * Assoc. array of the ACL rule's properties
60 */
61 public function toArray($format = '%s', $hideEmpty = FALSE) {
62 $result = [];
63
64 if (!self::$_fieldKeys) {
65 $fields = CRM_ACL_DAO_ACL::fields();
66 self::$_fieldKeys = array_keys($fields);
67 }
68
69 foreach (self::$_fieldKeys as $field) {
70 $result[$field] = $this->$field;
71 }
72 return $result;
73 }
74
75 /**
76 * Get all of the ACLs through ACL groups.
77 *
78 * @param int $contact_id
79 * ID of a contact to search for.
80 *
81 * @return array
82 * Array of assoc. arrays of ACL rules
83 *
84 * @throws \CRM_Core_Exception
85 */
86 protected static function getACLRoles($contact_id = NULL) {
87 $contact_id = CRM_Utils_Type::escape($contact_id, 'Integer');
88
89 $query = 'SELECT acl.* FROM civicrm_acl acl';
90 $where = ['acl.entity_table = "civicrm_acl_role" AND acl.entity_id IN (' . implode(',', array_keys(CRM_Core_OptionGroup::values('acl_role'))) . ')'];
91
92 if (!empty($contact_id)) {
93 return [];
94 }
95
96 $results = [];
97
98 $rule = CRM_Core_DAO::executeQuery($query . ' WHERE ' . implode(' AND ', $where));
99
100 while ($rule->fetch()) {
101 $results[$rule->id] = $rule->toArray();
102 }
103
104 return $results;
105 }
106
107 /**
108 * Get all ACLs granted to a contact through all group memberships.
109 *
110 * @param int $contact_id
111 * The contact's ID.
112 * @param bool $aclRoles
113 * Include ACL Roles?.
114 *
115 * @return array
116 * Assoc array of ACL rules
117 * @throws \CRM_Core_Exception
118 */
119 protected static function getGroupACLs($contact_id, $aclRoles = FALSE) {
120 $contact_id = CRM_Utils_Type::escape($contact_id, 'Integer');
121
122 $results = [];
123
124 if ($contact_id) {
125 $query = "
126 SELECT acl.*
127 FROM civicrm_acl acl
128 INNER JOIN civicrm_group_contact group_contact
129 ON acl.entity_id = group_contact.group_id
130 WHERE acl.entity_table = 'civicrm_group'
131 AND group_contact.contact_id = $contact_id
132 AND group_contact.status = 'Added'";
133
134 $rule = CRM_Core_DAO::executeQuery($query);
135
136 while ($rule->fetch()) {
137 $results[$rule->id] = $rule->toArray();
138 }
139 }
140
141 if ($aclRoles) {
142 $results += self::getGroupACLRoles($contact_id);
143 }
144
145 return $results;
146 }
147
148 /**
149 * Get all of the ACLs for a contact through ACL groups owned by Contact.
150 * groups.
151 *
152 * @param int $contact_id
153 * ID of a contact to search for.
154 *
155 * @return array
156 * Array of assoc. arrays of ACL rules
157 * @throws \CRM_Core_Exception
158 */
159 protected static function getGroupACLRoles($contact_id) {
160 $contact_id = CRM_Utils_Type::escape($contact_id, 'Integer');
161
162 $query = " SELECT acl.*
163 FROM civicrm_acl acl
164 INNER JOIN civicrm_option_group og
165 ON og.name = 'acl_role'
166 INNER JOIN civicrm_option_value ov
167 ON acl.entity_table = 'civicrm_acl_role'
168 AND ov.option_group_id = og.id
169 AND acl.entity_id = ov.value
170 AND ov.is_active = 1
171 INNER JOIN civicrm_acl_entity_role acl_entity_role
172 ON acl_entity_role.acl_role_id = acl.entity_id
173 AND acl_entity_role.is_active = 1
174 INNER JOIN civicrm_group_contact group_contact
175 ON acl_entity_role.entity_id = group_contact.group_id
176 AND acl_entity_role.entity_table = 'civicrm_group'
177 WHERE acl.entity_table = 'civicrm_acl_role'
178 AND acl.is_active = 1
179 AND group_contact.contact_id = $contact_id
180 AND group_contact.status = 'Added'";
181
182 $results = [];
183
184 $rule = CRM_Core_DAO::executeQuery($query);
185
186 while ($rule->fetch()) {
187 $results[$rule->id] = $rule->toArray();
188 }
189
190 // also get all acls for "Any Role" case
191 // and authenticated User Role if present
192 $roles = "0";
193 $session = CRM_Core_Session::singleton();
194 if ($session->get('ufID') > 0) {
195 $roles .= ",2";
196 }
197
198 $query = "
199 SELECT acl.*
200 FROM civicrm_acl acl
201 WHERE acl.entity_id IN ( $roles )
202 AND acl.entity_table = 'civicrm_acl_role'
203 ";
204
205 $rule = CRM_Core_DAO::executeQuery($query);
206 while ($rule->fetch()) {
207 $results[$rule->id] = $rule->toArray();
208 }
209
210 return $results;
211 }
212
213 /**
214 * Get all ACLs owned by a given contact, including domain and group-level.
215 *
216 * @param int $contact_id
217 * The contact ID.
218 *
219 * @return array
220 * Assoc array of ACL rules
221 *
222 * @throws \CRM_Core_Exception
223 */
224 public static function getAllByContact($contact_id) {
225 $result = [];
226
227 /* First, the contact-specific ACLs, including ACL Roles */
228 if ($contact_id) {
229 $query = " SELECT acl.*
230 FROM civicrm_acl acl
231 WHERE acl.entity_table = 'civicrm_contact'
232 AND acl.entity_id = $contact_id";
233
234 $rule = CRM_Core_DAO::executeQuery($query);
235
236 while ($rule->fetch()) {
237 $result[$rule->id] = $rule->toArray();
238 }
239
240 $result += self::getACLRoles($contact_id);
241 }
242
243 /* Then, all ACLs granted through group membership */
244 $result += self::getGroupACLs($contact_id, TRUE);
245
246 return $result;
247 }
248
249 /**
250 * @param array $params
251 *
252 * @return CRM_ACL_DAO_ACL
253 */
254 public static function create($params) {
255 $dao = new CRM_ACL_DAO_ACL();
256 $dao->copyValues($params);
257 $dao->save();
258 return $dao;
259 }
260
261 /**
262 * @param array $params
263 * @param array $defaults
264 */
265 public static function retrieve(&$params, &$defaults) {
266 CRM_Core_DAO::commonRetrieve('CRM_ACL_DAO_ACL', $params, $defaults);
267 }
268
269 /**
270 * Update the is_active flag in the db.
271 *
272 * @param int $id
273 * Id of the database record.
274 * @param bool $is_active
275 * Value we want to set the is_active field.
276 *
277 * @return bool
278 * true if we found and updated the object, else false
279 */
280 public static function setIsActive($id, $is_active) {
281 Civi::cache('fields')->flush();
282 // reset ACL and system caches.
283 CRM_Core_BAO_Cache::resetCaches();
284
285 return CRM_Core_DAO::setFieldValue('CRM_ACL_DAO_ACL', $id, 'is_active', $is_active);
286 }
287
288 /**
289 * @param $str
290 * @param int $contactID
291 *
292 * @return bool
293 */
294 public static function check($str, $contactID) {
295
296 $acls = CRM_ACL_BAO_Cache::build($contactID);
297
298 $aclKeys = array_keys($acls);
299 $aclKeys = implode(',', $aclKeys);
300
301 if (empty($aclKeys)) {
302 return FALSE;
303 }
304
305 $query = "
306 SELECT count( a.id )
307 FROM civicrm_acl_cache c, civicrm_acl a
308 WHERE c.acl_id = a.id
309 AND a.is_active = 1
310 AND a.object_table = %1
311 AND a.id IN ( $aclKeys )
312 ";
313 $params = [1 => [$str, 'String']];
314
315 $count = CRM_Core_DAO::singleValueQuery($query, $params);
316 return (bool) $count;
317 }
318
319 /**
320 * @param $type
321 * @param $tables
322 * @param $whereTables
323 * @param int $contactID
324 *
325 * @return null|string
326 */
327 public static function whereClause($type, &$tables, &$whereTables, $contactID = NULL) {
328 $acls = CRM_ACL_BAO_Cache::build($contactID);
329
330 $whereClause = NULL;
331 $clauses = [];
332
333 if (!empty($acls)) {
334 $aclKeys = array_keys($acls);
335 $aclKeys = implode(',', $aclKeys);
336
337 $query = "
338 SELECT a.operation, a.object_id
339 FROM civicrm_acl_cache c, civicrm_acl a
340 WHERE c.acl_id = a.id
341 AND a.is_active = 1
342 AND a.object_table = 'civicrm_saved_search'
343 AND a.id IN ( $aclKeys )
344 ORDER BY a.object_id
345 ";
346
347 $dao = CRM_Core_DAO::executeQuery($query);
348
349 // do an or of all the where clauses u see
350 $ids = [];
351 while ($dao->fetch()) {
352 // make sure operation matches the type TODO
353 if (self::matchType($type, $dao->operation)) {
354 if (!$dao->object_id) {
355 $ids = [];
356 $whereClause = ' ( 1 ) ';
357 break;
358 }
359 $ids[] = $dao->object_id;
360 }
361 }
362
363 if (!empty($ids)) {
364 $ids = implode(',', $ids);
365 $query = "
366 SELECT g.*
367 FROM civicrm_group g
368 WHERE g.id IN ( $ids )
369 AND g.is_active = 1
370 ";
371 $dao = CRM_Core_DAO::executeQuery($query);
372 $groupIDs = [];
373 $groupContactCacheClause = FALSE;
374 while ($dao->fetch()) {
375 $groupIDs[] = $dao->id;
376
377 if (($dao->saved_search_id || $dao->children || $dao->parents)) {
378 if ($dao->cache_date == NULL) {
379 CRM_Contact_BAO_GroupContactCache::load($dao);
380 }
381 $groupContactCacheClause = " UNION SELECT contact_id FROM civicrm_group_contact_cache WHERE group_id IN (" . implode(', ', $groupIDs) . ")";
382 }
383
384 }
385
386 if ($groupIDs) {
387 $clauses[] = "(
388 `contact_a`.id IN (
389 SELECT contact_id FROM civicrm_group_contact WHERE group_id IN (" . implode(', ', $groupIDs) . ") AND status = 'Added'
390 $groupContactCacheClause
391 )
392 )";
393 }
394 }
395 }
396
397 if (!empty($clauses)) {
398 $whereClause = ' ( ' . implode(' OR ', $clauses) . ' ) ';
399 }
400
401 // call the hook to get additional whereClauses
402 CRM_Utils_Hook::aclWhereClause($type, $tables, $whereTables, $contactID, $whereClause);
403
404 if (empty($whereClause)) {
405 $whereClause = ' ( 0 ) ';
406 }
407
408 return $whereClause;
409 }
410
411 /**
412 * @param int $type
413 * @param int $contactID
414 * @param string $tableName
415 * @param null $allGroups
416 * @param null $includedGroups
417 *
418 * @return array
419 */
420 public static function group(
421 $type,
422 $contactID = NULL,
423 $tableName = 'civicrm_saved_search',
424 $allGroups = NULL,
425 $includedGroups = NULL
426 ) {
427 $userCacheKey = "{$contactID}_{$type}_{$tableName}_" . CRM_Core_Config::domainID() . '_' . md5(implode(',', array_merge((array) $allGroups, (array) $includedGroups)));
428 if (empty(Civi::$statics[__CLASS__]['permissioned_groups'])) {
429 Civi::$statics[__CLASS__]['permissioned_groups'] = [];
430 }
431 if (!empty(Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey])) {
432 return Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey];
433 }
434
435 if ($allGroups == NULL) {
436 $allGroups = CRM_Contact_BAO_Contact::buildOptions('group_id', 'get');
437 }
438
439 $acls = CRM_ACL_BAO_Cache::build($contactID);
440
441 $ids = [];
442 if (!empty($acls)) {
443 $aclKeys = array_keys($acls);
444 $aclKeys = implode(',', $aclKeys);
445
446 $cacheKey = CRM_Utils_Cache::cleanKey("$type-$tableName-$aclKeys");
447 $cache = CRM_Utils_Cache::singleton();
448 $ids = $cache->get($cacheKey);
449 if (!is_array($ids)) {
450 $ids = [];
451 $query = "
452 SELECT a.operation, a.object_id
453 FROM civicrm_acl_cache c, civicrm_acl a
454 WHERE c.acl_id = a.id
455 AND a.is_active = 1
456 AND a.object_table = %1
457 AND a.id IN ( $aclKeys )
458 GROUP BY a.operation,a.object_id
459 ORDER BY a.object_id
460 ";
461 $params = [1 => [$tableName, 'String']];
462 $dao = CRM_Core_DAO::executeQuery($query, $params);
463 while ($dao->fetch()) {
464 if ($dao->object_id) {
465 if (self::matchType($type, $dao->operation)) {
466 $ids[] = $dao->object_id;
467 }
468 }
469 else {
470 // this user has got the permission for all objects of this type
471 // check if the type matches
472 if (self::matchType($type, $dao->operation)) {
473 foreach ($allGroups as $id => $dontCare) {
474 $ids[] = $id;
475 }
476 }
477 break;
478 }
479 }
480 $cache->set($cacheKey, $ids);
481 }
482 }
483
484 if (empty($ids) && !empty($includedGroups) &&
485 is_array($includedGroups)
486 ) {
487 // This is pretty alarming - we 'sometimes' include all included groups
488 // seems problematic per https://lab.civicrm.org/dev/core/-/issues/1879
489 $ids = $includedGroups;
490 }
491 if ($contactID) {
492 $groupWhere = '';
493 if (!empty($allGroups)) {
494 $groupWhere = " AND id IN (" . implode(',', array_keys($allGroups)) . ")";
495 }
496 // Contacts create hidden groups from search results. They should be able to retrieve their own.
497 $ownHiddenGroupsList = CRM_Core_DAO::singleValueQuery("
498 SELECT GROUP_CONCAT(id) FROM civicrm_group WHERE is_hidden =1 AND created_id = $contactID
499 $groupWhere
500 ");
501 if ($ownHiddenGroupsList) {
502 $ownHiddenGroups = explode(',', $ownHiddenGroupsList);
503 $ids = array_merge((array) $ids, $ownHiddenGroups);
504 }
505
506 }
507
508 CRM_Utils_Hook::aclGroup($type, $contactID, $tableName, $allGroups, $ids);
509 Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey] = $ids;
510 return $ids;
511 }
512
513 /**
514 * @param int $type
515 * @param $operation
516 *
517 * @return bool
518 */
519 protected static function matchType($type, $operation) {
520 $typeCheck = FALSE;
521 switch ($operation) {
522 case 'All':
523 $typeCheck = TRUE;
524 break;
525
526 case 'View':
527 if ($type == CRM_ACL_API::VIEW) {
528 $typeCheck = TRUE;
529 }
530 break;
531
532 case 'Edit':
533 if ($type == CRM_ACL_API::VIEW || $type == CRM_ACL_API::EDIT) {
534 $typeCheck = TRUE;
535 }
536 break;
537
538 case 'Create':
539 if ($type == CRM_ACL_API::CREATE) {
540 $typeCheck = TRUE;
541 }
542 break;
543
544 case 'Delete':
545 if ($type == CRM_ACL_API::DELETE) {
546 $typeCheck = TRUE;
547 }
548 break;
549
550 case 'Search':
551 if ($type == CRM_ACL_API::SEARCH) {
552 $typeCheck = TRUE;
553 }
554 break;
555 }
556 return $typeCheck;
557 }
558
559 /**
560 * Delete ACL records.
561 *
562 * @param int $aclId
563 * ID of the ACL record to be deleted.
564 *
565 */
566 public static function del($aclId) {
567 // delete all entries from the acl cache
568 CRM_ACL_BAO_Cache::resetCache();
569
570 $acl = new CRM_ACL_DAO_ACL();
571 $acl->id = $aclId;
572 $acl->delete();
573 }
574
575 }