3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
21 class CRM_ACL_BAO_ACL
extends CRM_ACL_DAO_ACL
implements \Civi\Test\HookInterface
{
24 * Available operations for pseudoconstant.
28 public static function operation(): array {
32 'Create' => ts('Create'),
33 'Delete' => ts('Delete'),
34 'Search' => ts('Search'),
40 * Get all of the ACLs for a contact through ACL groups owned by Contact.
43 * @param int $contact_id
44 * ID of a contact to search for.
47 * Array of assoc. arrays of ACL rules
48 * @throws \CRM_Core_Exception
50 protected static function getGroupACLRoles(int $contact_id) {
52 $query = " SELECT acl.*
54 INNER JOIN civicrm_option_group og
55 ON og.name = 'acl_role'
56 INNER JOIN civicrm_option_value ov
57 ON acl.entity_table = 'civicrm_acl_role'
58 AND ov.option_group_id = og.id
59 AND acl.entity_id = ov.value
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'
69 AND group_contact.contact_id = $contact_id
70 AND group_contact.status = 'Added'";
74 $rule = CRM_Core_DAO
::executeQuery($query);
76 while ($rule->fetch()) {
77 $results[$rule->id
] = $rule->toArray();
84 * Get all ACLs owned by a given contact, including domain and group-level.
86 * @param int $contact_id
90 * Assoc array of ACL rules
92 * @throws \CRM_Core_Exception
94 public static function getAllByContact(int $contact_id): array {
97 /* First, the contact-specific ACLs, including ACL Roles */
98 // 0 would be the anonymous contact.
99 if ($contact_id > 0) {
100 $query = " SELECT acl.*
102 WHERE acl.entity_table = 'civicrm_contact'
103 AND acl.entity_id = $contact_id";
105 $rule = CRM_Core_DAO
::executeQuery($query);
107 while ($rule->fetch()) {
108 $result[$rule->id
] = $rule->toArray();
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'";
119 $rule = CRM_Core_DAO
::executeQuery($query);
121 while ($rule->fetch()) {
122 $result[$rule->id
] = $rule->toArray();
124 $result +
= self
::getGroupACLRoles($contact_id);
126 // also get all acls for "Any Role" case
127 // and authenticated User Role if present
129 $session = CRM_Core_Session
::singleton();
130 if ($session->get('ufID') > 0) {
137 WHERE acl.entity_id IN ( $roles )
138 AND acl.entity_table = 'civicrm_acl_role'
141 $rule = CRM_Core_DAO
::executeQuery($query);
142 while ($rule->fetch()) {
143 $result[$rule->id
] = $rule->toArray();
149 * @param array $params
151 * @return CRM_ACL_DAO_ACL
153 public static function create($params) {
154 $dao = new CRM_ACL_DAO_ACL();
155 $dao->copyValues($params);
161 * @param array $params
162 * @param array $defaults
164 public static function retrieve(&$params, &$defaults) {
165 CRM_Core_DAO
::commonRetrieve('CRM_ACL_DAO_ACL', $params, $defaults);
169 * Update the is_active flag in the db.
172 * Id of the database record.
173 * @param bool $is_active
174 * Value we want to set the is_active field.
177 * true if we found and updated the object, else false
179 public static function setIsActive($id, $is_active) {
180 Civi
::cache('fields')->flush();
181 // reset ACL and system caches.
182 CRM_Core_BAO_Cache
::resetCaches();
184 return CRM_Core_DAO
::setFieldValue('CRM_ACL_DAO_ACL', $id, 'is_active', $is_active);
189 * @param int $contactID
195 public static function check($str, $contactID) {
196 \CRM_Core_Error
::deprecatedWarning(__CLASS__
. '::' . __FUNCTION__
. ' is deprecated.');
198 $acls = CRM_ACL_BAO_Cache
::build($contactID);
200 $aclKeys = array_keys($acls);
201 $aclKeys = implode(',', $aclKeys);
203 if (empty($aclKeys)) {
209 FROM civicrm_acl_cache c, civicrm_acl a
210 WHERE c.acl_id = a.id
212 AND a.object_table = %1
213 AND a.id IN ( $aclKeys )
215 $params = [1 => [$str, 'String']];
217 $count = CRM_Core_DAO
::singleValueQuery($query, $params);
218 return (bool) $count;
223 * @param array $tables
224 * @param array $whereTables
225 * @param int $contactID
227 * @return null|string
229 public static function whereClause($type, &$tables, &$whereTables, $contactID = NULL) {
230 $acls = CRM_ACL_BAO_Cache
::build($contactID);
236 $aclKeys = array_keys($acls);
237 $aclKeys = implode(',', $aclKeys);
240 SELECT a.operation, a.object_id
241 FROM civicrm_acl_cache c, civicrm_acl a
242 WHERE c.acl_id = a.id
244 AND a.object_table = 'civicrm_saved_search'
245 AND a.id IN ( $aclKeys )
249 $dao = CRM_Core_DAO
::executeQuery($query);
251 // do an or of all the where clauses u see
253 while ($dao->fetch()) {
254 // make sure operation matches the type TODO
255 if (self
::matchType($type, $dao->operation
)) {
256 if (!$dao->object_id
) {
258 $whereClause = ' ( 1 ) ';
261 $ids[] = $dao->object_id
;
266 $ids = implode(',', $ids);
270 WHERE g.id IN ( $ids )
273 $dao = CRM_Core_DAO
::executeQuery($query);
275 $groupContactCacheClause = FALSE;
276 while ($dao->fetch()) {
277 $groupIDs[] = $dao->id
;
279 if (($dao->saved_search_id ||
$dao->children ||
$dao->parents
)) {
280 if ($dao->cache_date
== NULL) {
281 CRM_Contact_BAO_GroupContactCache
::load($dao);
283 $groupContactCacheClause = " UNION SELECT contact_id FROM civicrm_group_contact_cache WHERE group_id IN (" . implode(', ', $groupIDs) . ")";
291 SELECT contact_id FROM civicrm_group_contact WHERE group_id IN (" . implode(', ', $groupIDs) . ") AND status = 'Added'
292 $groupContactCacheClause
299 if (!empty($clauses)) {
300 $whereClause = ' ( ' . implode(' OR ', $clauses) . ' ) ';
303 // call the hook to get additional whereClauses
304 CRM_Utils_Hook
::aclWhereClause($type, $tables, $whereTables, $contactID, $whereClause);
306 if (empty($whereClause)) {
307 $whereClause = ' ( 0 ) ';
315 * @param int $contactID
316 * @param string $tableName
317 * @param array|null $allGroups
318 * @param array|null $includedGroups
322 public static function group(
325 $tableName = 'civicrm_saved_search',
327 $includedGroups = NULL
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'])) {
331 Civi
::$statics[__CLASS__
]['permissioned_groups'] = [];
333 if (!empty(Civi
::$statics[__CLASS__
]['permissioned_groups'][$userCacheKey])) {
334 return Civi
::$statics[__CLASS__
]['permissioned_groups'][$userCacheKey];
337 if ($allGroups == NULL) {
338 $allGroups = CRM_Contact_BAO_Contact
::buildOptions('group_id', 'get');
341 $acls = CRM_ACL_BAO_Cache
::build($contactID);
345 $aclKeys = array_keys($acls);
346 $aclKeys = implode(',', $aclKeys);
348 $cacheKey = CRM_Utils_Cache
::cleanKey("$type-$tableName-$aclKeys");
349 $cache = CRM_Utils_Cache
::singleton();
350 $ids = $cache->get($cacheKey);
351 if (!is_array($ids)) {
352 $ids = self
::loadPermittedIDs((int) $contactID, $tableName, $type, $allGroups);
353 $cache->set($cacheKey, $ids);
357 if (empty($ids) && !empty($includedGroups) &&
358 is_array($includedGroups)
360 // This is pretty alarming - we 'sometimes' include all included groups
361 // seems problematic per https://lab.civicrm.org/dev/core/-/issues/1879
362 $ids = $includedGroups;
366 if (!empty($allGroups)) {
367 $groupWhere = ' AND id IN (' . implode(',', array_keys($allGroups)) . ")";
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
374 if ($ownHiddenGroupsList) {
375 $ownHiddenGroups = explode(',', $ownHiddenGroupsList);
376 $ids = array_merge((array) $ids, $ownHiddenGroups);
381 CRM_Utils_Hook
::aclGroup($type, $contactID, $tableName, $allGroups, $ids);
382 Civi
::$statics[__CLASS__
]['permissioned_groups'][$userCacheKey] = $ids;
388 * @param string $operation
392 protected static function matchType($type, $operation) {
394 switch ($operation) {
400 if ($type == CRM_ACL_API
::VIEW
) {
406 if ($type == CRM_ACL_API
::VIEW ||
$type == CRM_ACL_API
::EDIT
) {
412 if ($type == CRM_ACL_API
::CREATE
) {
418 if ($type == CRM_ACL_API
::DELETE
) {
424 if ($type == CRM_ACL_API
::SEARCH
) {
433 * Delete ACL records.
438 public static function del($aclId) {
439 self
::deleteRecord(['id' => $aclId]);
443 * Event fired before an action is taken on an ACL record.
444 * @param \Civi\Core\Event\PreEvent $event
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();
454 * Load permitted acl IDs.
456 * @param int $contactID
457 * @param string $tableName
459 * @param array $allGroups
463 protected static function loadPermittedIDs(int $contactID, string $tableName, int $type, $allGroups): array {
465 $acls = CRM_ACL_BAO_Cache
::build($contactID);
466 $aclKeys = array_keys($acls);
467 $aclKeys = implode(',', $aclKeys);
469 SELECT a.operation, a.object_id
470 FROM civicrm_acl_cache c, civicrm_acl a
471 WHERE c.acl_id = a.id
473 AND a.object_table = %1
474 AND a.id IN ( $aclKeys )
475 GROUP BY a.operation,a.object_id
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
;
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) {