Merge pull request #22267 from eileenmcnaughton/cont_test2
[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 */
42f0bf16 21class CRM_ACL_BAO_ACL extends CRM_ACL_DAO_ACL implements \Civi\Test\HookInterface {
6a488035 22
28518c90 23 /**
9bd90abd 24 * Available operations for pseudoconstant.
25 *
26 * @return array
28518c90 27 */
07422810 28 public static function operation(): array {
29 return [
30 'View' => ts('View'),
31 'Edit' => ts('Edit'),
32 'Create' => ts('Create'),
33 'Delete' => ts('Delete'),
34 'Search' => ts('Search'),
35 'All' => ts('All'),
36 ];
6a488035
TO
37 }
38
6a488035 39 /**
d2e5d2ce 40 * Get all of the ACLs for a contact through ACL groups owned by Contact.
6a488035
TO
41 * groups.
42 *
b758c7d5
TO
43 * @param int $contact_id
44 * ID of a contact to search for.
6a488035 45 *
a6c01b45
CW
46 * @return array
47 * Array of assoc. arrays of ACL rules
03149bb2 48 * @throws \CRM_Core_Exception
6a488035 49 */
59b8c224 50 protected static function getGroupACLRoles(int $contact_id) {
6a488035 51
20095057 52 $query = " SELECT acl.*
d340e675 53 FROM civicrm_acl acl
6a488035
TO
54 INNER JOIN civicrm_option_group og
55 ON og.name = 'acl_role'
56 INNER JOIN civicrm_option_value ov
a4757413 57 ON acl.entity_table = 'civicrm_acl_role'
6a488035 58 AND ov.option_group_id = og.id
20095057 59 AND acl.entity_id = ov.value
6a488035 60 AND ov.is_active = 1
a4757413 61 INNER JOIN civicrm_acl_entity_role acl_entity_role
62 ON acl_entity_role.acl_role_id = acl.entity_id
63 AND acl_entity_role.is_active = 1
64 INNER JOIN civicrm_group_contact group_contact
65 ON acl_entity_role.entity_id = group_contact.group_id
66 AND acl_entity_role.entity_table = 'civicrm_group'
67 WHERE acl.entity_table = 'civicrm_acl_role'
20095057 68 AND acl.is_active = 1
a4757413 69 AND group_contact.contact_id = $contact_id
70 AND group_contact.status = 'Added'";
6a488035 71
cf0d1c08 72 $results = [];
6a488035 73
3e8437f3 74 $rule = CRM_Core_DAO::executeQuery($query);
6a488035
TO
75
76 while ($rule->fetch()) {
39eb89f4 77 $results[$rule->id] = $rule->toArray();
6a488035
TO
78 }
79
6a488035
TO
80 return $results;
81 }
82
83 /**
84 * Get all ACLs owned by a given contact, including domain and group-level.
85 *
e9b3684c 86 * @param int|null $contact_id
b758c7d5 87 * The contact ID.
6a488035 88 *
a6c01b45
CW
89 * @return array
90 * Assoc array of ACL rules
03149bb2 91 *
92 * @throws \CRM_Core_Exception
6a488035 93 */
581a9d2e 94 public static function getAllByContact(int $contact_id): array {
cf0d1c08 95 $result = [];
6a488035
TO
96
97 /* First, the contact-specific ACLs, including ACL Roles */
581a9d2e
EM
98 // 0 would be the anonymous contact.
99 if ($contact_id > 0) {
02e01272 100 $query = " SELECT acl.*
101 FROM civicrm_acl acl
102 WHERE acl.entity_table = 'civicrm_contact'
103 AND acl.entity_id = $contact_id";
104
105 $rule = CRM_Core_DAO::executeQuery($query);
106
107 while ($rule->fetch()) {
108 $result[$rule->id] = $rule->toArray();
109 }
e9b3684c 110 $query = "
111SELECT acl.*
112 FROM civicrm_acl acl
113 INNER JOIN civicrm_group_contact group_contact
114 ON acl.entity_id = group_contact.group_id
115 WHERE acl.entity_table = 'civicrm_group'
116 AND group_contact.contact_id = $contact_id
117 AND group_contact.status = 'Added'";
6a488035 118
e9b3684c 119 $rule = CRM_Core_DAO::executeQuery($query);
6a488035 120
e9b3684c 121 while ($rule->fetch()) {
122 $result[$rule->id] = $rule->toArray();
123 }
59b8c224 124 $result += self::getGroupACLRoles($contact_id);
125 }
126 // also get all acls for "Any Role" case
127 // and authenticated User Role if present
128 $roles = '0';
129 $session = CRM_Core_Session::singleton();
130 if ($session->get('ufID') > 0) {
131 $roles .= ',2';
132 }
133
134 $query = "
135SELECT acl.*
136 FROM civicrm_acl acl
137 WHERE acl.entity_id IN ( $roles )
138 AND acl.entity_table = 'civicrm_acl_role'
139";
140
141 $rule = CRM_Core_DAO::executeQuery($query);
142 while ($rule->fetch()) {
143 $result[$rule->id] = $rule->toArray();
e9b3684c 144 }
6a488035
TO
145 return $result;
146 }
147
28518c90 148 /**
c490a46a 149 * @param array $params
28518c90
EM
150 *
151 * @return CRM_ACL_DAO_ACL
152 */
03149bb2 153 public static function create($params) {
6a488035
TO
154 $dao = new CRM_ACL_DAO_ACL();
155 $dao->copyValues($params);
156 $dao->save();
1fe97a01 157 return $dao;
6a488035
TO
158 }
159
28518c90 160 /**
c490a46a 161 * @param array $params
03149bb2 162 * @param array $defaults
28518c90 163 */
00be9182 164 public static function retrieve(&$params, &$defaults) {
6a488035
TO
165 CRM_Core_DAO::commonRetrieve('CRM_ACL_DAO_ACL', $params, $defaults);
166 }
167
168 /**
fe482240 169 * Update the is_active flag in the db.
6a488035 170 *
b758c7d5
TO
171 * @param int $id
172 * Id of the database record.
173 * @param bool $is_active
174 * Value we want to set the is_active field.
6a488035 175 *
8a4fede3 176 * @return bool
177 * true if we found and updated the object, else false
6a488035 178 */
00be9182 179 public static function setIsActive($id, $is_active) {
9cdf85c1 180 Civi::cache('fields')->flush();
5e601882
SL
181 // reset ACL and system caches.
182 CRM_Core_BAO_Cache::resetCaches();
6a488035
TO
183
184 return CRM_Core_DAO::setFieldValue('CRM_ACL_DAO_ACL', $id, 'is_active', $is_active);
185 }
186
28518c90
EM
187 /**
188 * @param $str
100fef9d 189 * @param int $contactID
28518c90
EM
190 *
191 * @return bool
f87a8e30
MW
192 *
193 * @deprecated
28518c90 194 */
00be9182 195 public static function check($str, $contactID) {
a963992d 196 \CRM_Core_Error::deprecatedWarning(__CLASS__ . '::' . __FUNCTION__ . ' is deprecated.');
6a488035
TO
197
198 $acls = CRM_ACL_BAO_Cache::build($contactID);
199
200 $aclKeys = array_keys($acls);
201 $aclKeys = implode(',', $aclKeys);
202
203 if (empty($aclKeys)) {
204 return FALSE;
205 }
206
6a488035
TO
207 $query = "
208SELECT count( a.id )
209 FROM civicrm_acl_cache c, civicrm_acl a
210 WHERE c.acl_id = a.id
211 AND a.is_active = 1
212 AND a.object_table = %1
213 AND a.id IN ( $aclKeys )
214";
cf0d1c08 215 $params = [1 => [$str, 'String']];
6a488035
TO
216
217 $count = CRM_Core_DAO::singleValueQuery($query, $params);
1699214f 218 return (bool) $count;
6a488035
TO
219 }
220
28518c90
EM
221 /**
222 * @param $type
223 * @param $tables
224 * @param $whereTables
100fef9d 225 * @param int $contactID
28518c90
EM
226 *
227 * @return null|string
228 */
199761b4 229 public static function whereClause($type, &$tables, &$whereTables, $contactID = NULL) {
6a488035 230 $acls = CRM_ACL_BAO_Cache::build($contactID);
6a488035
TO
231
232 $whereClause = NULL;
cf0d1c08 233 $clauses = [];
6a488035
TO
234
235 if (!empty($acls)) {
236 $aclKeys = array_keys($acls);
237 $aclKeys = implode(',', $aclKeys);
238
239 $query = "
240SELECT a.operation, a.object_id
241 FROM civicrm_acl_cache c, civicrm_acl a
242 WHERE c.acl_id = a.id
243 AND a.is_active = 1
244 AND a.object_table = 'civicrm_saved_search'
245 AND a.id IN ( $aclKeys )
246ORDER BY a.object_id
247";
248
249 $dao = CRM_Core_DAO::executeQuery($query);
250
251 // do an or of all the where clauses u see
cf0d1c08 252 $ids = [];
6a488035
TO
253 while ($dao->fetch()) {
254 // make sure operation matches the type TODO
255 if (self::matchType($type, $dao->operation)) {
256 if (!$dao->object_id) {
cf0d1c08 257 $ids = [];
6a488035
TO
258 $whereClause = ' ( 1 ) ';
259 break;
260 }
261 $ids[] = $dao->object_id;
262 }
263 }
264
265 if (!empty($ids)) {
266 $ids = implode(',', $ids);
267 $query = "
268SELECT g.*
269 FROM civicrm_group g
270 WHERE g.id IN ( $ids )
271 AND g.is_active = 1
272";
353ffa53 273 $dao = CRM_Core_DAO::executeQuery($query);
1bcdee33 274 $groupIDs = [];
275 $groupContactCacheClause = FALSE;
6a488035 276 while ($dao->fetch()) {
1bcdee33 277 $groupIDs[] = $dao->id;
6a488035 278
1d902030 279 if (($dao->saved_search_id || $dao->children || $dao->parents)) {
280 if ($dao->cache_date == NULL) {
281 CRM_Contact_BAO_GroupContactCache::load($dao);
282 }
1bcdee33 283 $groupContactCacheClause = " UNION SELECT contact_id FROM civicrm_group_contact_cache WHERE group_id IN (" . implode(', ', $groupIDs) . ")";
6a488035 284 }
6a488035 285
6a488035
TO
286 }
287
1bcdee33 288 if ($groupIDs) {
289 $clauses[] = "(
290 `contact_a`.id IN (
291 SELECT contact_id FROM civicrm_group_contact WHERE group_id IN (" . implode(', ', $groupIDs) . ") AND status = 'Added'
292 $groupContactCacheClause
293 )
294 )";
6a488035
TO
295 }
296 }
297 }
298
299 if (!empty($clauses)) {
300 $whereClause = ' ( ' . implode(' OR ', $clauses) . ' ) ';
301 }
302
303 // call the hook to get additional whereClauses
304 CRM_Utils_Hook::aclWhereClause($type, $tables, $whereTables, $contactID, $whereClause);
305
199761b4 306 if (empty($whereClause)) {
6a488035
TO
307 $whereClause = ' ( 0 ) ';
308 }
309
310 return $whereClause;
311 }
312
28518c90 313 /**
c490a46a 314 * @param int $type
100fef9d 315 * @param int $contactID
28518c90
EM
316 * @param string $tableName
317 * @param null $allGroups
318 * @param null $includedGroups
319 *
320 * @return array
321 */
e6a83034
TO
322 public static function group(
323 $type,
100b0ec6
TO
324 $contactID = NULL,
325 $tableName = 'civicrm_saved_search',
326 $allGroups = NULL,
6a488035
TO
327 $includedGroups = NULL
328 ) {
e3ad0182 329 $userCacheKey = "{$contactID}_{$type}_{$tableName}_" . CRM_Core_Config::domainID() . '_' . md5(implode(',', array_merge((array) $allGroups, (array) $includedGroups)));
330 if (empty(Civi::$statics[__CLASS__]['permissioned_groups'])) {
cf0d1c08 331 Civi::$statics[__CLASS__]['permissioned_groups'] = [];
e3ad0182 332 }
333 if (!empty(Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey])) {
334 return Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey];
335 }
6a488035 336
8aace6af 337 if ($allGroups == NULL) {
39868387 338 $allGroups = CRM_Contact_BAO_Contact::buildOptions('group_id', 'get');
8aace6af
JG
339 }
340
6a488035
TO
341 $acls = CRM_ACL_BAO_Cache::build($contactID);
342
cf0d1c08 343 $ids = [];
6a488035
TO
344 if (!empty($acls)) {
345 $aclKeys = array_keys($acls);
346 $aclKeys = implode(',', $aclKeys);
347
d4761b7f 348 $cacheKey = CRM_Utils_Cache::cleanKey("$type-$tableName-$aclKeys");
557f8c17
ARW
349 $cache = CRM_Utils_Cache::singleton();
350 $ids = $cache->get($cacheKey);
69fb41da 351 if (!is_array($ids)) {
92f38c2a 352 $ids = self::loadPermittedIDs((int) $contactID, $tableName, $type, $allGroups);
557f8c17 353 $cache->set($cacheKey, $ids);
6a488035
TO
354 }
355 }
356
b1f4e637
RN
357 if (empty($ids) && !empty($includedGroups) &&
358 is_array($includedGroups)
359 ) {
7ad3182b 360 // This is pretty alarming - we 'sometimes' include all included groups
361 // seems problematic per https://lab.civicrm.org/dev/core/-/issues/1879
b1f4e637
RN
362 $ids = $includedGroups;
363 }
e3ad0182 364 if ($contactID) {
54d93c06 365 $groupWhere = '';
366 if (!empty($allGroups)) {
92f38c2a 367 $groupWhere = ' AND id IN (' . implode(',', array_keys($allGroups)) . ")";
54d93c06 368 }
e3ad0182 369 // Contacts create hidden groups from search results. They should be able to retrieve their own.
370 $ownHiddenGroupsList = CRM_Core_DAO::singleValueQuery("
371 SELECT GROUP_CONCAT(id) FROM civicrm_group WHERE is_hidden =1 AND created_id = $contactID
54d93c06 372 $groupWhere
e3ad0182 373 ");
374 if ($ownHiddenGroupsList) {
375 $ownHiddenGroups = explode(',', $ownHiddenGroupsList);
54d93c06 376 $ids = array_merge((array) $ids, $ownHiddenGroups);
e3ad0182 377 }
378
379 }
b1f4e637 380
6a488035 381 CRM_Utils_Hook::aclGroup($type, $contactID, $tableName, $allGroups, $ids);
e3ad0182 382 Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey] = $ids;
6a488035
TO
383 return $ids;
384 }
385
28518c90 386 /**
c490a46a 387 * @param int $type
28518c90
EM
388 * @param $operation
389 *
390 * @return bool
391 */
9bd90abd 392 protected static function matchType($type, $operation) {
6a488035
TO
393 $typeCheck = FALSE;
394 switch ($operation) {
395 case 'All':
396 $typeCheck = TRUE;
397 break;
398
399 case 'View':
400 if ($type == CRM_ACL_API::VIEW) {
401 $typeCheck = TRUE;
402 }
403 break;
404
405 case 'Edit':
406 if ($type == CRM_ACL_API::VIEW || $type == CRM_ACL_API::EDIT) {
407 $typeCheck = TRUE;
408 }
409 break;
410
411 case 'Create':
412 if ($type == CRM_ACL_API::CREATE) {
413 $typeCheck = TRUE;
414 }
415 break;
416
417 case 'Delete':
418 if ($type == CRM_ACL_API::DELETE) {
419 $typeCheck = TRUE;
420 }
421 break;
422
423 case 'Search':
424 if ($type == CRM_ACL_API::SEARCH) {
425 $typeCheck = TRUE;
426 }
427 break;
428 }
429 return $typeCheck;
430 }
431
432 /**
d2e5d2ce 433 * Delete ACL records.
6a488035 434 *
b758c7d5 435 * @param int $aclId
42f0bf16 436 * @deprecated
6a488035 437 */
00be9182 438 public static function del($aclId) {
42f0bf16
CW
439 self::deleteRecord(['id' => $aclId]);
440 }
6a488035 441
42f0bf16
CW
442 /**
443 * Event fired before an action is taken on an ACL record.
444 * @param \Civi\Core\Event\PreEvent $event
445 */
446 public static function self_hook_civicrm_pre(\Civi\Core\Event\PreEvent $event) {
447 // Reset cache when deleting an ACL record
448 if ($event->action === 'delete') {
449 CRM_ACL_BAO_Cache::resetCache();
450 }
6a488035 451 }
96025800 452
92f38c2a
EM
453 /**
454 * Load permitted acl IDs.
455 *
456 * @param int $contactID
457 * @param string $tableName
458 * @param int $type
459 * @param $allGroups
460 *
461 * @return array
462 */
463 protected static function loadPermittedIDs(int $contactID, string $tableName, int $type, $allGroups): array {
464 $ids = [];
465 $acls = CRM_ACL_BAO_Cache::build($contactID);
466 $aclKeys = array_keys($acls);
467 $aclKeys = implode(',', $aclKeys);
468 $query = "
469SELECT a.operation, a.object_id
470 FROM civicrm_acl_cache c, civicrm_acl a
471 WHERE c.acl_id = a.id
472 AND a.is_active = 1
473 AND a.object_table = %1
474 AND a.id IN ( $aclKeys )
475GROUP BY a.operation,a.object_id
476ORDER BY a.object_id
477";
478 $params = [1 => [$tableName, 'String']];
479 $dao = CRM_Core_DAO::executeQuery($query, $params);
480 while ($dao->fetch()) {
481 if ($dao->object_id) {
482 if (self::matchType($type, $dao->operation)) {
483 $ids[] = $dao->object_id;
484 }
485 }
486 else {
487 // this user has got the permission for all objects of this type
488 // check if the type matches
489 if (self::matchType($type, $dao->operation)) {
490 foreach ($allGroups as $id => $dontCare) {
491 $ids[] = $id;
492 }
493 }
494 break;
495 }
496 }
497 return $ids;
498 }
499
6a488035 500}