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