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
{
25 public static $_entityTable = NULL;
26 public static $_objectTable = NULL;
27 public static $_operation = NULL;
29 public static $_fieldKeys = NULL;
32 * Get ACL entity table.
36 public static function entityTable() {
37 CRM_Core_Error
::deprecatedFunctionWarning('unused function to be removed');
38 if (!self
::$_entityTable) {
39 self
::$_entityTable = [
40 'civicrm_contact' => ts('Contact'),
41 'civicrm_acl_role' => ts('ACL Role'),
44 return self
::$_entityTable;
51 public static function objectTable() {
52 CRM_Core_Error
::deprecatedFunctionWarning('unused function to be removed');
53 if (!self
::$_objectTable) {
54 self
::$_objectTable = [
55 'civicrm_contact' => ts('Contact'),
56 'civicrm_group' => ts('Group'),
57 'civicrm_saved_search' => ts('Contact Group'),
58 'civicrm_admin' => ts('Import'),
61 return self
::$_objectTable;
65 * Available operations for pseudoconstant.
69 public static function operation() {
70 if (!self
::$_operation) {
74 'Create' => ts('Create'),
75 'Delete' => ts('Delete'),
76 'Search' => ts('Search'),
80 return self
::$_operation;
84 * Given a table and id pair, return the filter clause
86 * @param string $table
87 * The table owning the object.
89 * The ID of the object.
90 * @param array $tables
91 * Tables that will be needed in the FROM.
95 * WHERE-style clause to filter results,
96 * or null if $table or $id is null
98 * @throws \CRM_Core_Exception
100 public static function getClause($table, $id, &$tables) {
101 CRM_Core_Error
::deprecatedFunctionWarning('unused function to be removed');
102 $table = CRM_Utils_Type
::escape($table, 'String');
103 $id = CRM_Utils_Type
::escape($id, 'Integer');
106 $ssTable = CRM_Contact_BAO_SavedSearch
::getTableName();
111 elseif ($table == $ssTable) {
112 return CRM_Contact_BAO_SavedSearch
::whereClause($id, $tables, $whereTables);
114 elseif (!empty($id)) {
115 $tables[$table] = TRUE;
116 return "$table.id = $id";
122 * Construct an associative array of an ACL rule's properties
124 * @param string $format
125 * Sprintf format for array.
126 * @param bool $hideEmpty
127 * Only return elements that have a value set.
130 * Assoc. array of the ACL rule's properties
132 public function toArray($format = '%s', $hideEmpty = FALSE) {
135 if (!self
::$_fieldKeys) {
136 $fields = CRM_ACL_DAO_ACL
::fields();
137 self
::$_fieldKeys = array_keys($fields);
140 foreach (self
::$_fieldKeys as $field) {
141 $result[$field] = $this->$field;
147 * Retrieve ACLs for a contact or group. Note that including a contact id
148 * without a group id will return those ACL rules which are granted
149 * directly to the contact, but not those granted to the contact through
150 * any/all of his group memberships.
152 * @param int $contact_id
153 * ID of a contact to search for.
156 * Array of assoc. arrays of ACL rules
158 * @throws \CRM_Core_Exception
160 protected static function getACLs(int $contact_id) {
163 $rule = new CRM_ACL_BAO_ACL();
165 $contact = CRM_Contact_BAO_Contact
::getTableName();
167 $query = " SELECT acl.*
169 WHERE acl.entity_table = '$contact'
170 AND acl.entity_id = $contact_id";
172 $rule->query($query);
174 while ($rule->fetch()) {
175 $results[$rule->id
] = $rule->toArray();
178 $results +
= self
::getACLRoles($contact_id);
184 * Get all of the ACLs through ACL groups.
186 * @param int $contact_id
187 * ID of a contact to search for.
190 * Array of assoc. arrays of ACL rules
192 * @throws \CRM_Core_Exception
194 protected static function getACLRoles($contact_id = NULL) {
195 $contact_id = CRM_Utils_Type
::escape($contact_id, 'Integer');
197 $rule = new CRM_ACL_BAO_ACL();
199 $contact = CRM_Contact_BAO_Contact
::getTableName();
201 $query = 'SELECT acl.* FROM civicrm_acl acl';
202 $where = ['acl.entity_table = "civicrm_acl_role" AND acl.entity_id IN (' . implode(',', array_keys(CRM_Core_OptionGroup
::values('acl_role'))) . ')'];
204 if (!empty($contact_id)) {
205 $where[] = " acl.entity_table = '$contact' AND acl.is_active = 1 AND acl.entity_id = $contact_id";
210 $rule->query($query . ' WHERE ' . implode(' AND ', $where));
212 while ($rule->fetch()) {
213 $results[$rule->id
] = $rule->toArray();
220 * Get all ACLs granted to a contact through all group memberships.
222 * @param int $contact_id
224 * @param bool $aclRoles
225 * Include ACL Roles?.
228 * Assoc array of ACL rules
229 * @throws \CRM_Core_Exception
231 protected static function getGroupACLs($contact_id, $aclRoles = FALSE) {
232 $contact_id = CRM_Utils_Type
::escape($contact_id, 'Integer');
234 $rule = new CRM_ACL_BAO_ACL();
236 $c2g = CRM_Contact_BAO_GroupContact
::getTableName();
237 $group = CRM_Contact_BAO_Group
::getTableName();
244 INNER JOIN $c2g group_contact
245 ON acl.entity_id = group_contact.group_id
246 WHERE acl.entity_table = '$group'
247 AND group_contact.contact_id = $contact_id
248 AND group_contact.status = 'Added'";
250 $rule->query($query);
252 while ($rule->fetch()) {
253 $results[$rule->id
] = $rule->toArray();
258 $results +
= self
::getGroupACLRoles($contact_id);
265 * Get all of the ACLs for a contact through ACL groups owned by Contact.
268 * @param int $contact_id
269 * ID of a contact to search for.
272 * Array of assoc. arrays of ACL rules
273 * @throws \CRM_Core_Exception
275 protected static function getGroupACLRoles($contact_id) {
276 $contact_id = CRM_Utils_Type
::escape($contact_id, 'Integer');
278 $rule = new CRM_ACL_BAO_ACL();
280 $aclRole = 'civicrm_acl_role';
282 $aclER = CRM_ACL_DAO_EntityRole
::getTableName();
283 $c2g = CRM_Contact_BAO_GroupContact
::getTableName();
285 $query = " SELECT acl.*
287 INNER JOIN civicrm_option_group og
288 ON og.name = 'acl_role'
289 INNER JOIN civicrm_option_value ov
290 ON acl.entity_table = '$aclRole'
291 AND ov.option_group_id = og.id
292 AND acl.entity_id = ov.value
295 ON $aclER.acl_role_id = acl.entity_id
296 AND $aclER.is_active = 1
298 ON $aclER.entity_id = $c2g.group_id
299 AND $aclER.entity_table = 'civicrm_group'
300 WHERE acl.entity_table = '$aclRole'
301 AND acl.is_active = 1
302 AND $c2g.contact_id = $contact_id
303 AND $c2g.status = 'Added'";
307 $rule->query($query);
309 while ($rule->fetch()) {
310 $results[$rule->id
] = $rule->toArray();
313 // also get all acls for "Any Role" case
314 // and authenticated User Role if present
316 $session = CRM_Core_Session
::singleton();
317 if ($session->get('ufID') > 0) {
324 WHERE acl.entity_id IN ( $roles )
325 AND acl.entity_table = 'civicrm_acl_role'
328 $rule->query($query);
329 while ($rule->fetch()) {
330 $results[$rule->id
] = $rule->toArray();
337 * Get all ACLs owned by a given contact, including domain and group-level.
339 * @param int $contact_id
343 * Assoc array of ACL rules
345 * @throws \CRM_Core_Exception
347 public static function getAllByContact($contact_id) {
350 /* First, the contact-specific ACLs, including ACL Roles */
352 $result +
= self
::getACLs((int) $contact_id);
355 /* Then, all ACLs granted through group membership */
356 $result +
= self
::getGroupACLs($contact_id, TRUE);
362 * @param array $params
364 * @return CRM_ACL_DAO_ACL
366 public static function create($params) {
367 $dao = new CRM_ACL_DAO_ACL();
368 $dao->copyValues($params);
374 * @param array $params
375 * @param array $defaults
377 public static function retrieve(&$params, &$defaults) {
378 CRM_Core_DAO
::commonRetrieve('CRM_ACL_DAO_ACL', $params, $defaults);
382 * Update the is_active flag in the db.
385 * Id of the database record.
386 * @param bool $is_active
387 * Value we want to set the is_active field.
390 * true if we found and updated the object, else false
392 public static function setIsActive($id, $is_active) {
393 Civi
::cache('fields')->flush();
394 // reset ACL and system caches.
395 CRM_Core_BAO_Cache
::resetCaches();
397 return CRM_Core_DAO
::setFieldValue('CRM_ACL_DAO_ACL', $id, 'is_active', $is_active);
402 * @param int $contactID
406 public static function check($str, $contactID) {
408 $acls = CRM_ACL_BAO_Cache
::build($contactID);
410 $aclKeys = array_keys($acls);
411 $aclKeys = implode(',', $aclKeys);
413 if (empty($aclKeys)) {
419 FROM civicrm_acl_cache c, civicrm_acl a
420 WHERE c.acl_id = a.id
422 AND a.object_table = %1
423 AND a.id IN ( $aclKeys )
425 $params = [1 => [$str, 'String']];
427 $count = CRM_Core_DAO
::singleValueQuery($query, $params);
428 return (bool) $count;
434 * @param $whereTables
435 * @param int $contactID
437 * @return null|string
439 public static function whereClause($type, &$tables, &$whereTables, $contactID = NULL) {
440 $acls = CRM_ACL_BAO_Cache
::build($contactID);
446 $aclKeys = array_keys($acls);
447 $aclKeys = implode(',', $aclKeys);
450 SELECT a.operation, a.object_id
451 FROM civicrm_acl_cache c, civicrm_acl a
452 WHERE c.acl_id = a.id
454 AND a.object_table = 'civicrm_saved_search'
455 AND a.id IN ( $aclKeys )
459 $dao = CRM_Core_DAO
::executeQuery($query);
461 // do an or of all the where clauses u see
463 while ($dao->fetch()) {
464 // make sure operation matches the type TODO
465 if (self
::matchType($type, $dao->operation
)) {
466 if (!$dao->object_id
) {
468 $whereClause = ' ( 1 ) ';
471 $ids[] = $dao->object_id
;
476 $ids = implode(',', $ids);
480 WHERE g.id IN ( $ids )
483 $dao = CRM_Core_DAO
::executeQuery($query);
485 $groupContactCacheClause = FALSE;
486 while ($dao->fetch()) {
487 $groupIDs[] = $dao->id
;
489 if (($dao->saved_search_id ||
$dao->children ||
$dao->parents
)) {
490 if ($dao->cache_date
== NULL) {
491 CRM_Contact_BAO_GroupContactCache
::load($dao);
493 $groupContactCacheClause = " UNION SELECT contact_id FROM civicrm_group_contact_cache WHERE group_id IN (" . implode(', ', $groupIDs) . ")";
501 SELECT contact_id FROM civicrm_group_contact WHERE group_id IN (" . implode(', ', $groupIDs) . ") AND status = 'Added'
502 $groupContactCacheClause
509 if (!empty($clauses)) {
510 $whereClause = ' ( ' . implode(' OR ', $clauses) . ' ) ';
513 // call the hook to get additional whereClauses
514 CRM_Utils_Hook
::aclWhereClause($type, $tables, $whereTables, $contactID, $whereClause);
516 if (empty($whereClause)) {
517 $whereClause = ' ( 0 ) ';
525 * @param int $contactID
526 * @param string $tableName
527 * @param null $allGroups
528 * @param null $includedGroups
532 public static function group(
535 $tableName = 'civicrm_saved_search',
537 $includedGroups = NULL
539 $userCacheKey = "{$contactID}_{$type}_{$tableName}_" . CRM_Core_Config
::domainID() . '_' . md5(implode(',', array_merge((array) $allGroups, (array) $includedGroups)));
540 if (empty(Civi
::$statics[__CLASS__
]['permissioned_groups'])) {
541 Civi
::$statics[__CLASS__
]['permissioned_groups'] = [];
543 if (!empty(Civi
::$statics[__CLASS__
]['permissioned_groups'][$userCacheKey])) {
544 return Civi
::$statics[__CLASS__
]['permissioned_groups'][$userCacheKey];
547 if ($allGroups == NULL) {
548 $allGroups = CRM_Contact_BAO_Contact
::buildOptions('group_id', 'get');
551 $acls = CRM_ACL_BAO_Cache
::build($contactID);
555 $aclKeys = array_keys($acls);
556 $aclKeys = implode(',', $aclKeys);
558 $cacheKey = CRM_Utils_Cache
::cleanKey("$tableName-$aclKeys");
559 $cache = CRM_Utils_Cache
::singleton();
560 $ids = $cache->get($cacheKey);
564 SELECT a.operation, a.object_id
565 FROM civicrm_acl_cache c, civicrm_acl a
566 WHERE c.acl_id = a.id
568 AND a.object_table = %1
569 AND a.id IN ( $aclKeys )
570 GROUP BY a.operation,a.object_id
573 $params = [1 => [$tableName, 'String']];
574 $dao = CRM_Core_DAO
::executeQuery($query, $params);
575 while ($dao->fetch()) {
576 if ($dao->object_id
) {
577 if (self
::matchType($type, $dao->operation
)) {
578 $ids[] = $dao->object_id
;
582 // this user has got the permission for all objects of this type
583 // check if the type matches
584 if (self
::matchType($type, $dao->operation
)) {
585 foreach ($allGroups as $id => $dontCare) {
592 $cache->set($cacheKey, $ids);
596 if (empty($ids) && !empty($includedGroups) &&
597 is_array($includedGroups)
599 $ids = $includedGroups;
603 if (!empty($allGroups)) {
604 $groupWhere = " AND id IN (" . implode(',', array_keys($allGroups)) . ")";
606 // Contacts create hidden groups from search results. They should be able to retrieve their own.
607 $ownHiddenGroupsList = CRM_Core_DAO
::singleValueQuery("
608 SELECT GROUP_CONCAT(id) FROM civicrm_group WHERE is_hidden =1 AND created_id = $contactID
611 if ($ownHiddenGroupsList) {
612 $ownHiddenGroups = explode(',', $ownHiddenGroupsList);
613 $ids = array_merge((array) $ids, $ownHiddenGroups);
618 CRM_Utils_Hook
::aclGroup($type, $contactID, $tableName, $allGroups, $ids);
619 Civi
::$statics[__CLASS__
]['permissioned_groups'][$userCacheKey] = $ids;
629 protected static function matchType($type, $operation) {
631 switch ($operation) {
637 if ($type == CRM_ACL_API
::VIEW
) {
643 if ($type == CRM_ACL_API
::VIEW ||
$type == CRM_ACL_API
::EDIT
) {
649 if ($type == CRM_ACL_API
::CREATE
) {
655 if ($type == CRM_ACL_API
::DELETE
) {
661 if ($type == CRM_ACL_API
::SEARCH
) {
670 * Delete ACL records.
673 * ID of the ACL record to be deleted.
676 public static function del($aclId) {
677 // delete all entries from the acl cache
678 CRM_ACL_BAO_Cache
::resetCache();
680 $acl = new CRM_ACL_DAO_ACL();