Commit | Line | Data |
---|---|---|
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 | 21 | class 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 = " |
111 | SELECT 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 = " | |
135 | SELECT 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 = " |
208 | SELECT 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 = " | |
240 | SELECT 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 ) | |
246 | ORDER 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 = " | |
268 | SELECT 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 = " | |
469 | SELECT 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 ) | |
475 | GROUP BY a.operation,a.object_id | |
476 | ORDER 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 | } |