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