ts('Contact'), 'civicrm_acl_role' => ts('ACL Role'), ); } return self::$_entityTable; } /** * @return array|null */ public static function objectTable() { if (!self::$_objectTable) { self::$_objectTable = array( 'civicrm_contact' => ts('Contact'), 'civicrm_group' => ts('Group'), 'civicrm_saved_search' => ts('Contact Group'), 'civicrm_admin' => ts('Import'), ); } return self::$_objectTable; } /** * @return array|null */ public static function operation() { if (!self::$_operation) { self::$_operation = array( 'View' => ts('View'), 'Edit' => ts('Edit'), 'Create' => ts('Create'), 'Delete' => ts('Delete'), 'Search' => ts('Search'), 'All' => ts('All'), ); } return self::$_operation; } /** * Construct a WHERE clause to handle permissions to $object_* * * @deprecated * * @param array $tables * Any tables that may be needed in the FROM. * @param string $operation * The operation being attempted. * @param string $object_table * The table of the object in question. * @param int $object_id * The ID of the object in question. * @param int $acl_id * If it's a grant/revoke operation, the ACL ID. * @param bool $acl_role * For grant operations, this flag determines if we're granting a single acl (false) or an entire group. * * @return string * The WHERE clause, or 0 on failure */ public static function permissionClause( &$tables, $operation, $object_table = NULL, $object_id = NULL, $acl_id = NULL, $acl_role = FALSE ) { CRM_Core_Error::deprecatedFunctionWarning('unknown - this is really old & not used in core'); $dao = new CRM_ACL_DAO_ACL(); $t = array( 'ACL' => self::getTableName(), 'ACLRole' => 'civicrm_acl_role', 'ACLEntityRole' => CRM_ACL_DAO_EntityRole::getTableName(), 'Contact' => CRM_Contact_DAO_Contact::getTableName(), 'Group' => CRM_Contact_DAO_Group::getTableName(), 'GroupContact' => CRM_Contact_DAO_GroupContact::getTableName(), ); $contact_id = CRM_Core_Session::getLoggedInContactID(); $where = " {$t['ACL']}.operation = '" . CRM_Utils_Type::escape($operation, 'String') . "'"; /* Include clause if we're looking for a specific table/id permission */ if (!empty($object_table)) { $where .= " AND ( {$t['ACL']}.object_table IS null OR ({$t['ACL']}.object_table = '" . CRM_Utils_Type::escape($object_table, 'String') . "'"; if (!empty($object_id)) { $where .= " AND ({$t['ACL']}.object_id IS null OR {$t['ACL']}.object_id = " . CRM_Utils_Type::escape($object_id, 'Integer') . ')'; } $where .= '))'; } /* Include clause if we're granting an ACL or ACL Role */ if (!empty($acl_id)) { $where .= " AND ({$t['ACL']}.acl_id IS null OR {$t['ACL']}.acl_id = " . CRM_Utils_Type::escape($acl_id, 'Integer') . ')'; if ($acl_role) { $where .= " AND {$t['ACL']}.acl_table = '{$t['ACLRole']}'"; } else { $where .= " AND {$t['ACL']}.acl_table = '{$t['ACL']}'"; } } $query = array(); /* Query for permissions granted to all contacts in the domain */ $query[] = "SELECT {$t['ACL']}.*, 0 as override FROM {$t['ACL']} WHERE {$t['ACL']}.entity_table = '{$t['Domain']}' AND ($where)"; /* Query for permissions granted to all contacts through an ACL group */ $query[] = "SELECT {$t['ACL']}.*, 0 as override FROM {$t['ACL']} INNER JOIN {$t['ACLEntityRole']} ON ({$t['ACL']}.entity_table = '{$t['ACLRole']}' AND {$t['ACL']}.entity_id = {$t['ACLEntityRole']}.acl_role_id) INNER JOIN {$t['ACLRole']} ON {$t['ACL']}.entity_id = {$t['ACLRole']}.id WHERE {$t['ACLEntityRole']}.entity_table = '{$t['Domain']}' AND {$t['ACLRole']}.is_active = 1 AND ($where)"; /* Query for permissions granted directly to the contact */ $query[] = "SELECT {$t['ACL']}.*, 1 as override FROM {$t['ACL']} INNER JOIN {$t['Contact']} ON ({$t['ACL']}.entity_table = '{$t['Contact']}' AND {$t['ACL']}.entity_id = {$t['Contact']}.id) WHERE {$t['Contact']}.id = $contact_id AND ($where)"; /* Query for permissions granted to the contact through an ACL group */ $query[] = "SELECT {$t['ACL']}.*, 1 as override FROM {$t['ACL']} INNER JOIN {$t['ACLEntityRole']} ON ({$t['ACL']}.entity_table = '{$t['ACLRole']}' AND {$t['ACL']}.entity_id = {$t['ACLEntityRole']}.acl_role_id) INNER JOIN {$t['ACLRole']} ON {$t['ACL']}.entity_id = {$t['ACLRole']}.id WHERE {$t['ACLEntityRole']}.entity_table = '{$t['Contact']}' AND {$t['ACLRole']}.is_active = 1 AND {$t['ACLEntityRole']}.entity_id = $contact_id AND ($where)"; /* Query for permissions granted to the contact through a group */ $query[] = "SELECT {$t['ACL']}.*, 0 as override FROM {$t['ACL']} INNER JOIN {$t['GroupContact']} ON ({$t['ACL']}.entity_table = '{$t['Group']}' AND {$t['ACL']}.entity_id = {$t['GroupContact']}.group_id) WHERE ($where) AND {$t['GroupContact']}.contact_id = $contact_id AND {$t['GroupContact']}.status = 'Added')"; /* Query for permissions granted through an ACL group to a Contact * group */ $query[] = "SELECT {$t['ACL']}.*, 0 as override FROM {$t['ACL']} INNER JOIN {$t['ACLEntityRole']} ON ({$t['ACL']}.entity_table = '{$t['ACLRole']}' AND {$t['ACL']}.entity_id = {$t['ACLEntityRole']}.acl_role_id) INNER JOIN {$t['ACLRole']} ON {$t['ACL']}.entity_id = {$t['ACLRole']}.id INNER JOIN {$t['GroupContact']} ON ({$t['ACLEntityRole']}.entity_table = '{$t['Group']}' AND {$t['ACLEntityRole']}.entity_id = {$t['GroupContact']}.group_id) WHERE ($where) AND {$t['ACLRole']}.is_active = 1 AND {$t['GroupContact']}.contact_id = $contact_id AND {$t['GroupContact']}.status = 'Added'"; $union = '(' . implode(') UNION DISTINCT (', $query) . ')'; $dao->query($union); $allow = array(0); $deny = array(0); $override = array(); while ($dao->fetch()) { /* Instant bypass for the following cases: * 1) the rule governs all tables * 2) the rule governs all objects in the table in question * 3) the rule governs the specific object we want */ if (empty($dao->object_table) || ($dao->object_table == $object_table && (empty($dao->object_id) || $dao->object_id == $object_id ) ) ) { $clause = 1; } else { /* Otherwise try to generate a clause for this rule */ $clause = self::getClause( $dao->object_table, $dao->object_id, $tables ); /* If the clause returned is null, then the rule is a blanket * (id is null) on a table other than the one we're interested * in. So skip it. */ if (empty($clause)) { continue; } } /* Now we figure out if this is an allow or deny rule, and possibly * a contact-level override */ if ($dao->deny) { $deny[] = $clause; } else { $allow[] = $clause; if ($dao->override) { $override[] = $clause; } } } $allows = '(' . implode(' OR ', $allow) . ')'; $denies = '(' . implode(' OR ', $deny) . ')'; if (!empty($override)) { $denies = '(NOT (' . implode(' OR ', $override) . ") AND $denies)"; } return "($allows AND NOT $denies)"; } /** * Given a table and id pair, return the filter clause * * @param string $table * The table owning the object. * @param int $id * The ID of the object. * @param array $tables * Tables that will be needed in the FROM. * * @return string|null * WHERE-style clause to filter results, * or null if $table or $id is null */ public static function getClause($table, $id, &$tables) { $table = CRM_Utils_Type::escape($table, 'String'); $id = CRM_Utils_Type::escape($id, 'Integer'); $whereTables = array(); $ssTable = CRM_Contact_BAO_SavedSearch::getTableName(); if (empty($table)) { return NULL; } elseif ($table == $ssTable) { return CRM_Contact_BAO_SavedSearch::whereClause($id, $tables, $whereTables); } elseif (!empty($id)) { $tables[$table] = TRUE; return "$table.id = $id"; } return NULL; } /** * Construct an associative array of an ACL rule's properties * * @param string $format * Sprintf format for array. * @param bool $hideEmpty * Only return elements that have a value set. * * @return array * Assoc. array of the ACL rule's properties */ public function toArray($format = '%s', $hideEmpty = FALSE) { $result = array(); if (!self::$_fieldKeys) { $fields = CRM_ACL_DAO_ACL::fields(); self::$_fieldKeys = array_keys($fields); } foreach (self::$_fieldKeys as $field) { $result[$field] = $this->$field; } return $result; } /** * Retrieve ACLs for a contact or group. Note that including a contact id * without a group id will return those ACL rules which are granted * directly to the contact, but not those granted to the contact through * any/all of his group memberships. * * @param int $contact_id * ID of a contact to search for. * @param int $group_id * ID of a group to search for. * @param bool $aclRoles * Should we include ACL Roles. * * @return array * Array of assoc. arrays of ACL rules */ public static function &getACLs($contact_id = NULL, $group_id = NULL, $aclRoles = FALSE) { $results = array(); if (empty($contact_id)) { return $results; } $contact_id = CRM_Utils_Type::escape($contact_id, 'Integer'); if ($group_id) { $group_id = CRM_Utils_Type::escape($group_id, 'Integer'); } $rule = new CRM_ACL_BAO_ACL(); $acl = self::getTableName(); $contact = CRM_Contact_BAO_Contact::getTableName(); $c2g = CRM_Contact_BAO_GroupContact::getTableName(); $group = CRM_Contact_BAO_Group::getTableName(); $query = " SELECT $acl.* FROM $acl "; if (!empty($group_id)) { $query .= " INNER JOIN $c2g ON $acl.entity_id = $c2g.group_id WHERE $acl.entity_table = '$group' AND $acl.is_active = 1 AND $c2g.group_id = $group_id"; if (!empty($contact_id)) { $query .= " AND $c2g.contact_id = $contact_id AND $c2g.status = 'Added'"; } } else { if (!empty($contact_id)) { $query .= " WHERE $acl.entity_table = '$contact' AND $acl.entity_id = $contact_id"; } } $rule->query($query); while ($rule->fetch()) { $results[$rule->id] = $rule->toArray(); } if ($aclRoles) { $results += self::getACLRoles($contact_id, $group_id); } return $results; } /** * Get all of the ACLs through ACL groups. * * @param int $contact_id * ID of a contact to search for. * @param int $group_id * ID of a group to search for. * * @return array * Array of assoc. arrays of ACL rules */ public static function &getACLRoles($contact_id = NULL, $group_id = NULL) { $contact_id = CRM_Utils_Type::escape($contact_id, 'Integer'); if ($group_id) { $group_id = CRM_Utils_Type::escape($group_id, 'Integer'); } $rule = new CRM_ACL_BAO_ACL(); $acl = self::getTableName(); $aclRole = 'civicrm_acl_role'; $aclRoleJoin = CRM_ACL_DAO_EntityRole::getTableName(); $contact = CRM_Contact_BAO_Contact::getTableName(); $c2g = CRM_Contact_BAO_GroupContact::getTableName(); $group = CRM_Contact_BAO_Group::getTableName(); $query = " SELECT $acl.* FROM $acl INNER JOIN civicrm_option_group og ON og.name = 'acl_role' INNER JOIN civicrm_option_value ov ON $acl.entity_table = '$aclRole' AND ov.option_group_id = og.id AND $acl.entity_id = ov.value"; if (!empty($group_id)) { $query .= " INNER JOIN $c2g ON $acl.entity_id = $c2g.group_id WHERE $acl.entity_table = '$group' AND $acl.is_active = 1 AND $c2g.group_id = $group_id"; if (!empty($contact_id)) { $query .= " AND $c2g.contact_id = $contact_id AND $c2g.status = 'Added'"; } } else { if (!empty($contact_id)) { $query .= " WHERE $acl.entity_table = '$contact' AND $acl.is_active = 1 AND $acl.entity_id = $contact_id"; } } $results = array(); $rule->query($query); while ($rule->fetch()) { $results[$rule->id] = $rule->toArray(); } return $results; } /** * Get all ACLs granted to a contact through all group memberships. * * @param int $contact_id * The contact's ID. * @param bool $aclRoles * Include ACL Roles?. * * @return array * Assoc array of ACL rules */ public static function &getGroupACLs($contact_id, $aclRoles = FALSE) { $contact_id = CRM_Utils_Type::escape($contact_id, 'Integer'); $rule = new CRM_ACL_BAO_ACL(); $acl = self::getTableName(); $c2g = CRM_Contact_BAO_GroupContact::getTableName(); $group = CRM_Contact_BAO_Group::getTableName(); $results = array(); if ($contact_id) { $query = " SELECT $acl.* FROM $acl INNER JOIN $c2g ON $acl.entity_id = $c2g.group_id WHERE $acl.entity_table = '$group' AND $c2g.contact_id = $contact_id AND $c2g.status = 'Added'"; $rule->query($query); while ($rule->fetch()) { $results[$rule->id] = $rule->toArray(); } } if ($aclRoles) { $results += self::getGroupACLRoles($contact_id); } return $results; } /** * Get all of the ACLs for a contact through ACL groups owned by Contact. * groups. * * @param int $contact_id * ID of a contact to search for. * * @return array * Array of assoc. arrays of ACL rules */ public static function &getGroupACLRoles($contact_id) { $contact_id = CRM_Utils_Type::escape($contact_id, 'Integer'); $rule = new CRM_ACL_BAO_ACL(); $acl = self::getTableName(); $aclRole = 'civicrm_acl_role'; $aclER = CRM_ACL_DAO_EntityRole::getTableName(); $c2g = CRM_Contact_BAO_GroupContact::getTableName(); $group = CRM_Contact_BAO_Group::getTableName(); $query = " SELECT $acl.* FROM $acl INNER JOIN civicrm_option_group og ON og.name = 'acl_role' INNER JOIN civicrm_option_value ov ON $acl.entity_table = '$aclRole' AND ov.option_group_id = og.id AND $acl.entity_id = ov.value AND ov.is_active = 1 INNER JOIN $aclER ON $aclER.acl_role_id = $acl.entity_id AND $aclER.is_active = 1 INNER JOIN $c2g ON $aclER.entity_id = $c2g.group_id AND $aclER.entity_table = 'civicrm_group' WHERE $acl.entity_table = '$aclRole' AND $acl.is_active = 1 AND $c2g.contact_id = $contact_id AND $c2g.status = 'Added'"; $results = array(); $rule->query($query); while ($rule->fetch()) { $results[$rule->id] = $rule->toArray(); } // also get all acls for "Any Role" case // and authenticated User Role if present $roles = "0"; $session = CRM_Core_Session::singleton(); if ($session->get('ufID') > 0) { $roles .= ",2"; } $query = " SELECT $acl.* FROM $acl WHERE $acl.entity_id IN ( $roles ) AND $acl.entity_table = 'civicrm_acl_role' "; $rule->query($query); while ($rule->fetch()) { $results[$rule->id] = $rule->toArray(); } return $results; } /** * Get all ACLs owned by a given contact, including domain and group-level. * * @param int $contact_id * The contact ID. * * @return array * Assoc array of ACL rules */ public static function &getAllByContact($contact_id) { $result = array(); /* First, the contact-specific ACLs, including ACL Roles */ $result += self::getACLs($contact_id, NULL, TRUE); /* Then, all ACLs granted through group membership */ $result += self::getGroupACLs($contact_id, TRUE); return $result; } /** * @param array $params * * @return CRM_ACL_DAO_ACL */ public static function create(&$params) { $dao = new CRM_ACL_DAO_ACL(); $dao->copyValues($params); $dao->save(); return $dao; } /** * @param array $params * @param $defaults */ public static function retrieve(&$params, &$defaults) { CRM_Core_DAO::commonRetrieve('CRM_ACL_DAO_ACL', $params, $defaults); } /** * Update the is_active flag in the db. * * @param int $id * Id of the database record. * @param bool $is_active * Value we want to set the is_active field. * * @return bool * true if we found and updated the object, else false */ public static function setIsActive($id, $is_active) { // note this also resets any ACL cache CRM_Core_BAO_Cache::deleteGroup('contact fields'); return CRM_Core_DAO::setFieldValue('CRM_ACL_DAO_ACL', $id, 'is_active', $is_active); } /** * @param $str * @param int $contactID * * @return bool */ public static function check($str, $contactID) { $acls = CRM_ACL_BAO_Cache::build($contactID); $aclKeys = array_keys($acls); $aclKeys = implode(',', $aclKeys); if (empty($aclKeys)) { return FALSE; } $query = " SELECT count( a.id ) FROM civicrm_acl_cache c, civicrm_acl a WHERE c.acl_id = a.id AND a.is_active = 1 AND a.object_table = %1 AND a.id IN ( $aclKeys ) "; $params = array(1 => array($str, 'String')); $count = CRM_Core_DAO::singleValueQuery($query, $params); return ($count) ? TRUE : FALSE; } /** * @param $type * @param $tables * @param $whereTables * @param int $contactID * * @return null|string */ public static function whereClause($type, &$tables, &$whereTables, $contactID = NULL) { $acls = CRM_ACL_BAO_Cache::build($contactID); $whereClause = NULL; $clauses = array(); if (!empty($acls)) { $aclKeys = array_keys($acls); $aclKeys = implode(',', $aclKeys); $query = " SELECT a.operation, a.object_id FROM civicrm_acl_cache c, civicrm_acl a WHERE c.acl_id = a.id AND a.is_active = 1 AND a.object_table = 'civicrm_saved_search' AND a.id IN ( $aclKeys ) ORDER BY a.object_id "; $dao = CRM_Core_DAO::executeQuery($query); // do an or of all the where clauses u see $ids = array(); while ($dao->fetch()) { // make sure operation matches the type TODO if (self::matchType($type, $dao->operation)) { if (!$dao->object_id) { $ids = array(); $whereClause = ' ( 1 ) '; break; } $ids[] = $dao->object_id; } } if (!empty($ids)) { $ids = implode(',', $ids); $query = " SELECT g.* FROM civicrm_group g WHERE g.id IN ( $ids ) AND g.is_active = 1 "; $dao = CRM_Core_DAO::executeQuery($query); $groupIDs = []; $groupContactCacheClause = FALSE; while ($dao->fetch()) { $groupIDs[] = $dao->id; if (($dao->saved_search_id || $dao->children || $dao->parents)) { if ($dao->cache_date == NULL) { CRM_Contact_BAO_GroupContactCache::load($dao); } $groupContactCacheClause = " UNION SELECT contact_id FROM civicrm_group_contact_cache WHERE group_id IN (" . implode(', ', $groupIDs) . ")"; } } if ($groupIDs) { $clauses[] = "( `contact_a`.id IN ( SELECT contact_id FROM civicrm_group_contact WHERE group_id IN (" . implode(', ', $groupIDs) . ") AND status = 'Added' $groupContactCacheClause ) )"; } } } if (!empty($clauses)) { $whereClause = ' ( ' . implode(' OR ', $clauses) . ' ) '; } // call the hook to get additional whereClauses CRM_Utils_Hook::aclWhereClause($type, $tables, $whereTables, $contactID, $whereClause); if (empty($whereClause)) { $whereClause = ' ( 0 ) '; } return $whereClause; } /** * @param int $type * @param int $contactID * @param string $tableName * @param null $allGroups * @param null $includedGroups * * @return array */ public static function group( $type, $contactID = NULL, $tableName = 'civicrm_saved_search', $allGroups = NULL, $includedGroups = NULL ) { $userCacheKey = "{$contactID}_{$type}_{$tableName}_" . CRM_Core_Config::domainID() . '_' . md5(implode(',', array_merge((array) $allGroups, (array) $includedGroups))); if (empty(Civi::$statics[__CLASS__]['permissioned_groups'])) { Civi::$statics[__CLASS__]['permissioned_groups'] = array(); } if (!empty(Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey])) { return Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey]; } $acls = CRM_ACL_BAO_Cache::build($contactID); $ids = array(); if (!empty($acls)) { $aclKeys = array_keys($acls); $aclKeys = implode(',', $aclKeys); $cacheKey = CRM_Core_BAO_Cache::cleanKey("$tableName-$aclKeys"); $cache = CRM_Utils_Cache::singleton(); $ids = $cache->get($cacheKey); if (!$ids) { $ids = array(); $query = " SELECT a.operation, a.object_id FROM civicrm_acl_cache c, civicrm_acl a WHERE c.acl_id = a.id AND a.is_active = 1 AND a.object_table = %1 AND a.id IN ( $aclKeys ) GROUP BY a.operation,a.object_id ORDER BY a.object_id "; $params = array(1 => array($tableName, 'String')); $dao = CRM_Core_DAO::executeQuery($query, $params); while ($dao->fetch()) { if ($dao->object_id) { if (self::matchType($type, $dao->operation)) { $ids[] = $dao->object_id; } } else { // this user has got the permission for all objects of this type // check if the type matches if (self::matchType($type, $dao->operation)) { foreach ($allGroups as $id => $dontCare) { $ids[] = $id; } } break; } } $cache->set($cacheKey, $ids); } } if (empty($ids) && !empty($includedGroups) && is_array($includedGroups) ) { $ids = $includedGroups; } if ($contactID) { $groupWhere = ''; if (!empty($allGroups)) { $groupWhere = " AND id IN (" . implode(',', array_keys($allGroups)) . ")"; } // Contacts create hidden groups from search results. They should be able to retrieve their own. $ownHiddenGroupsList = CRM_Core_DAO::singleValueQuery(" SELECT GROUP_CONCAT(id) FROM civicrm_group WHERE is_hidden =1 AND created_id = $contactID $groupWhere "); if ($ownHiddenGroupsList) { $ownHiddenGroups = explode(',', $ownHiddenGroupsList); $ids = array_merge((array) $ids, $ownHiddenGroups); } } CRM_Utils_Hook::aclGroup($type, $contactID, $tableName, $allGroups, $ids); Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey] = $ids; return $ids; } /** * @param int $type * @param $operation * * @return bool */ public static function matchType($type, $operation) { $typeCheck = FALSE; switch ($operation) { case 'All': $typeCheck = TRUE; break; case 'View': if ($type == CRM_ACL_API::VIEW) { $typeCheck = TRUE; } break; case 'Edit': if ($type == CRM_ACL_API::VIEW || $type == CRM_ACL_API::EDIT) { $typeCheck = TRUE; } break; case 'Create': if ($type == CRM_ACL_API::CREATE) { $typeCheck = TRUE; } break; case 'Delete': if ($type == CRM_ACL_API::DELETE) { $typeCheck = TRUE; } break; case 'Search': if ($type == CRM_ACL_API::SEARCH) { $typeCheck = TRUE; } break; } return $typeCheck; } /** * Delete ACL records. * * @param int $aclId * ID of the ACL record to be deleted. * */ public static function del($aclId) { // delete all entries from the acl cache CRM_ACL_BAO_Cache::resetCache(); $acl = new CRM_ACL_DAO_ACL(); $acl->id = $aclId; $acl->delete(); } }