ts('Contact'), 'civicrm_acl_role' => ts('ACL Role'), ); } return self::$_entityTable; } 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('Administer'), 'civicrm_admin' => ts('Import'), ); } return self::$_objectTable; } 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_* * * @param array ref $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 boolean $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 * @access public * @static */ public static function permissionClause(&$tables, $operation, $object_table = NULL, $object_id = NULL, $acl_id = NULL, $acl_role = FALSE ) { $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(), ); $session = CRM_Core_Session::singleton(); $contact_id = $session->get('userID'); $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 ref $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 * @access public * @static */ 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 sprintf format for array * @param bool empty only return elemnts that have a value set. * * @return array - Assoc. array of the ACL rule's properties * @access 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 boolean $aclRoles - Should we include ACL Roles * * @return array - Array of assoc. arrays of ACL rules * @access public * @static */ 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 * @access public * @static */ 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 boolean $aclRoles - Include ACL Roles? * * @return array - Assoc array of ACL rules * @access public * @static */ 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 * @access public * @static */ 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 * @access public * @static */ 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; } static function create(&$params) { $dao = new CRM_ACL_DAO_ACL(); $dao->copyValues($params); $dao->save(); } 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 boolean $is_active value we want to set the is_active field * * @return Object DAO object on sucess, null otherwise * @static */ 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); } 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; } public static function whereClause($type, &$tables, &$whereTables, $contactID = NULL) { $acls = CRM_ACL_BAO_Cache::build($contactID); //CRM_Core_Error::debug( "a: $contactID", $acls ); $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); $staticGroupIDs = array(); $cachedGroupIDs = array(); while ($dao->fetch()) { // currently operation is restrcited to VIEW/EDIT if ($dao->where_clause) { if ($dao->select_tables) { $tmpTables = array(); foreach (unserialize($dao->select_tables) as $tmpName => $tmpInfo) { if ($tmpName == '`civicrm_group_contact-' . $dao->id . '`') { $tmpName = '`civicrm_group_contact-ACL`'; $tmpInfo = str_replace('civicrm_group_contact-' . $dao->id, 'civicrm_group_contact-ACL', $tmpInfo); } elseif ($tmpName == '`civicrm_group_contact_cache_' . $dao->id . '`') { $tmpName = '`civicrm_group_contact_cache-ACL`'; $tmpInfo = str_replace('civicrm_group_contact_cache_' . $dao->id, 'civicrm_group_contact_cache-ACL', $tmpInfo); } $tmpTables[$tmpName] = $tmpInfo; } $tables = array_merge($tables, $tmpTables ); } if ($dao->where_tables) { $tmpTables = array(); foreach (unserialize($dao->where_tables) as $tmpName => $tmpInfo) { if ($tmpName == '`civicrm_group_contact-' . $dao->id . '`') { $tmpName = '`civicrm_group_contact-ACL`'; $tmpInfo = str_replace('civicrm_group_contact-' . $dao->id, 'civicrm_group_contact-ACL', $tmpInfo); $staticGroupIDs[] = $dao->id; } elseif ($tmpName == '`civicrm_group_contact_cache_' . $dao->id . '`') { $tmpName = '`civicrm_group_contact_cache-ACL`'; $tmpInfo = str_replace('civicrm_group_contact_cache_' . $dao->id, 'civicrm_group_contact_cache-ACL', $tmpInfo); $cachedGroupIDs[] = $dao->id; } $tmpTables[$tmpName] = $tmpInfo; } $whereTables = array_merge($whereTables, $tmpTables); } } if (($dao->saved_search_id || $dao->children || $dao->parents) && $dao->cache_date == NULL) { CRM_Contact_BAO_GroupContactCache::load($dao); } } if ($staticGroupIDs) { $clauses[] = '( `civicrm_group_contact-ACL`.group_id IN (' . join(', ', $staticGroupIDs) . ') AND `civicrm_group_contact-ACL`.status IN ("Added") )'; } if ($cachedGroupIDs) { $clauses[] = '`civicrm_group_contact_cache-ACL`.group_id IN (' . join(', ', $cachedGroupIDs) . ')'; } } } 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; } public static function group($type, $contactID = NULL, $tableName = 'civicrm_saved_search', $allGroups = NULL, $includedGroups = NULL ) { $acls = CRM_ACL_BAO_Cache::build($contactID); $ids = array(); if (!empty($acls)) { $aclKeys = array_keys($acls); $aclKeys = implode(',', $aclKeys); $cacheKey = "$tableName-$aclKeys"; $cache = CRM_Utils_Cache::singleton(); $ids = $cache->get($cacheKey); if (!$ids) { $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; } CRM_Utils_Hook::aclGroup($type, $contactID, $tableName, $allGroups, $ids); return $ids; } 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; } /** * Function to delete ACL records * * @param int $aclId ID of the ACL record to be deleted. * * @access public * @static */ 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(); } }