From bb4187d757af9abb09d6a97b20ccdd62e1b72c62 Mon Sep 17 00:00:00 2001 From: Monish Deb Date: Tue, 1 Jun 2021 16:22:26 +0530 Subject: [PATCH] Add DedupeRule, DedupeRuleGroup and DedupeException APIv4 entities --- CRM/Core/DAO/AllCoreTables.data.php | 18 +- CRM/Dedupe/BAO/DedupeException.php | 56 ++ CRM/Dedupe/BAO/DedupeRule.php | 252 +++++++++ CRM/Dedupe/BAO/DedupeRuleGroup.php | 517 ++++++++++++++++++ CRM/Dedupe/BAO/Exception.php | 37 +- CRM/Dedupe/BAO/Rule.php | 232 +------- CRM/Dedupe/BAO/RuleGroup.php | 497 +---------------- CRM/Dedupe/DAO/DedupeException.php | 231 ++++++++ CRM/Dedupe/DAO/DedupeRule.php | 284 ++++++++++ CRM/Dedupe/DAO/DedupeRuleGroup.php | 304 ++++++++++ CRM/Dedupe/DAO/Exception.php | 218 +------- CRM/Dedupe/DAO/Rule.php | 276 +--------- CRM/Dedupe/DAO/RuleGroup.php | 296 +--------- CRM/Event/DAO/Event.php | 4 +- Civi/Api4/DedupeRule.php | 32 ++ api/v3/utils.php | 12 + .../{Exception.xml => DedupeException.xml} | 2 +- .../Dedupe/{Rule.xml => DedupeRule.xml} | 2 +- .../{RuleGroup.xml => DedupeRuleGroup.xml} | 2 +- xml/schema/Dedupe/files.xml | 6 +- 20 files changed, 1713 insertions(+), 1565 deletions(-) create mode 100644 CRM/Dedupe/BAO/DedupeException.php create mode 100644 CRM/Dedupe/BAO/DedupeRule.php create mode 100644 CRM/Dedupe/BAO/DedupeRuleGroup.php create mode 100644 CRM/Dedupe/DAO/DedupeException.php create mode 100644 CRM/Dedupe/DAO/DedupeRule.php create mode 100644 CRM/Dedupe/DAO/DedupeRuleGroup.php create mode 100644 Civi/Api4/DedupeRule.php rename xml/schema/Dedupe/{Exception.xml => DedupeException.xml} (97%) rename xml/schema/Dedupe/{Rule.xml => DedupeRule.xml} (98%) rename xml/schema/Dedupe/{RuleGroup.xml => DedupeRuleGroup.xml} (98%) diff --git a/CRM/Core/DAO/AllCoreTables.data.php b/CRM/Core/DAO/AllCoreTables.data.php index fdbda3a704..b29ef2728c 100644 --- a/CRM/Core/DAO/AllCoreTables.data.php +++ b/CRM/Core/DAO/AllCoreTables.data.php @@ -207,19 +207,19 @@ return [ 'class' => 'CRM_Event_Cart_DAO_Cart', 'table' => 'civicrm_event_carts', ], - 'CRM_Dedupe_DAO_RuleGroup' => [ - 'name' => 'RuleGroup', - 'class' => 'CRM_Dedupe_DAO_RuleGroup', + 'CRM_Dedupe_DAO_DedupeRuleGroup' => [ + 'name' => 'DedupeRuleGroup', + 'class' => 'CRM_Dedupe_DAO_DedupeRuleGroup', 'table' => 'civicrm_dedupe_rule_group', ], - 'CRM_Dedupe_DAO_Rule' => [ - 'name' => 'Rule', - 'class' => 'CRM_Dedupe_DAO_Rule', + 'CRM_Dedupe_DAO_DedupeRule' => [ + 'name' => 'DedupeRule', + 'class' => 'CRM_Dedupe_DAO_DedupeRule', 'table' => 'civicrm_dedupe_rule', ], - 'CRM_Dedupe_DAO_Exception' => [ - 'name' => 'Exception', - 'class' => 'CRM_Dedupe_DAO_Exception', + 'CRM_Dedupe_DAO_DedupeException' => [ + 'name' => 'DedupeException', + 'class' => 'CRM_Dedupe_DAO_DedupeException', 'table' => 'civicrm_dedupe_exception', ], 'CRM_Case_DAO_CaseType' => [ diff --git a/CRM/Dedupe/BAO/DedupeException.php b/CRM/Dedupe/BAO/DedupeException.php new file mode 100644 index 0000000000..eeaea3369c --- /dev/null +++ b/CRM/Dedupe/BAO/DedupeException.php @@ -0,0 +1,56 @@ +copyValues($params); + if ($contact1 && $contact2) { + CRM_Core_DAO::singleValueQuery(" + DELETE FROM civicrm_prevnext_cache + WHERE (entity_id1 = %1 AND entity_id2 = %2) + OR (entity_id1 = %2 AND entity_id2 = %2)", + [1 => [$contact1, 'Integer'], 2 => [$contact2, 'Integer']] + ); + if ($contact2 < $contact1) { + // These are expected to be saved lowest first. + $dao->contact_id1 = $contact2; + $dao->contact_id2 = $contact1; + } + } + $dao->save(); + + CRM_Utils_Hook::post($hook, 'Exception', $dao->id, $dao); + return $dao; + } + +} diff --git a/CRM/Dedupe/BAO/DedupeRule.php b/CRM/Dedupe/BAO/DedupeRule.php new file mode 100644 index 0000000000..af7e794aa3 --- /dev/null +++ b/CRM/Dedupe/BAO/DedupeRule.php @@ -0,0 +1,252 @@ +. + */ +class CRM_Dedupe_BAO_DedupeRule extends CRM_Dedupe_DAO_DedupeRule { + + /** + * Ids of the contacts to limit the SQL queries (whole-database queries otherwise) + * @var array + */ + public $contactIds = []; + + /** + * Params to dedupe against (queries against the whole contact set otherwise) + * @var array + */ + public $params = []; + + /** + * Return the SQL query for the given rule - either for finding matching + * pairs of contacts, or for matching against the $params variable (if set). + * + * @return string + * SQL query performing the search + * + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception + */ + public function sql() { + if ($this->params && + (!array_key_exists($this->rule_table, $this->params) || + !array_key_exists($this->rule_field, $this->params[$this->rule_table]) + ) + ) { + // if params is present and doesn't have an entry for a field, don't construct the clause. + return NULL; + } + + // we need to initialise WHERE, ON and USING here, as some table types + // extend them; $where is an array of required conditions, $on and + // $using are arrays of required field matchings (for substring and + // full matches, respectively) + $where = []; + $on = ["SUBSTR(t1.{$this->rule_field}, 1, {$this->rule_length}) = SUBSTR(t2.{$this->rule_field}, 1, {$this->rule_length})"]; + + $innerJoinClauses = [ + "t1.{$this->rule_field} IS NOT NULL", + "t2.{$this->rule_field} IS NOT NULL", + "t1.{$this->rule_field} = t2.{$this->rule_field}", + ]; + + if (in_array($this->getFieldType($this->rule_field), CRM_Utils_Type::getTextTypes(), TRUE)) { + $innerJoinClauses[] = "t1.{$this->rule_field} <> ''"; + $innerJoinClauses[] = "t2.{$this->rule_field} <> ''"; + } + + switch ($this->rule_table) { + case 'civicrm_contact': + $id = 'id'; + //we should restrict by contact type in the first step + $sql = "SELECT contact_type FROM civicrm_dedupe_rule_group WHERE id = {$this->dedupe_rule_group_id};"; + $ct = CRM_Core_DAO::singleValueQuery($sql); + if ($this->params) { + $where[] = "t1.contact_type = '{$ct}'"; + } + else { + $where[] = "t1.contact_type = '{$ct}'"; + $where[] = "t2.contact_type = '{$ct}'"; + } + break; + + case 'civicrm_address': + case 'civicrm_email': + case 'civicrm_im': + case 'civicrm_openid': + case 'civicrm_phone': + $id = 'contact_id'; + break; + + case 'civicrm_note': + $id = 'entity_id'; + if ($this->params) { + $where[] = "t1.entity_table = 'civicrm_contact'"; + } + else { + $where[] = "t1.entity_table = 'civicrm_contact'"; + $where[] = "t2.entity_table = 'civicrm_contact'"; + } + break; + + default: + // custom data tables + if (preg_match('/^civicrm_value_/', $this->rule_table) || preg_match('/^custom_value_/', $this->rule_table)) { + $id = 'entity_id'; + } + else { + throw new CRM_Core_Exception("Unsupported rule_table for civicrm_dedupe_rule.id of {$this->id}"); + } + break; + } + + // build SELECT based on the field names containing contact ids + // if there are params provided, id1 should be 0 + if ($this->params) { + $select = "t1.$id id1, {$this->rule_weight} weight"; + $subSelect = 'id1, weight'; + } + else { + $select = "t1.$id id1, t2.$id id2, {$this->rule_weight} weight"; + $subSelect = 'id1, id2, weight'; + } + + // build FROM (and WHERE, if it's a parametrised search) + // based on whether the rule is about substrings or not + if ($this->params) { + $from = "{$this->rule_table} t1"; + $str = 'NULL'; + if (isset($this->params[$this->rule_table][$this->rule_field])) { + $str = trim(CRM_Utils_Type::escape($this->params[$this->rule_table][$this->rule_field], 'String')); + } + if ($this->rule_length) { + $where[] = "SUBSTR(t1.{$this->rule_field}, 1, {$this->rule_length}) = SUBSTR('$str', 1, {$this->rule_length})"; + $where[] = "t1.{$this->rule_field} IS NOT NULL"; + } + else { + $where[] = "t1.{$this->rule_field} = '$str'"; + } + } + else { + if ($this->rule_length) { + $from = "{$this->rule_table} t1 JOIN {$this->rule_table} t2 ON (" . implode(' AND ', $on) . ")"; + } + else { + $from = "{$this->rule_table} t1 INNER JOIN {$this->rule_table} t2 ON (" . implode(' AND ', $innerJoinClauses) . ")"; + } + } + + // finish building WHERE, also limit the results if requested + if (!$this->params) { + $where[] = "t1.$id < t2.$id"; + } + $query = "SELECT $select FROM $from WHERE " . implode(' AND ', $where); + if ($this->contactIds) { + $cids = []; + foreach ($this->contactIds as $cid) { + $cids[] = CRM_Utils_Type::escape($cid, 'Integer'); + } + if (count($cids) == 1) { + $query .= " AND (t1.$id = {$cids[0]}) UNION $query AND t2.$id = {$cids[0]}"; + } + else { + $query .= " AND t1.$id IN (" . implode(',', $cids) . ") + UNION $query AND t2.$id IN (" . implode(',', $cids) . ")"; + } + // The `weight` is ambiguous in the context of the union; put the whole + // thing in a subquery. + $query = "SELECT $subSelect FROM ($query) subunion"; + } + + return $query; + } + + /** + * find fields related to a rule group. + * + * @param array $params contains the rule group property to identify rule group + * + * @return array + * rule fields array associated to rule group + */ + public static function dedupeRuleFields($params) { + $rgBao = new CRM_Dedupe_BAO_RuleGroup(); + $rgBao->used = $params['used']; + $rgBao->contact_type = $params['contact_type']; + $rgBao->find(TRUE); + + $ruleBao = new CRM_Dedupe_BAO_Rule(); + $ruleBao->dedupe_rule_group_id = $rgBao->id; + $ruleBao->find(); + $ruleFields = []; + while ($ruleBao->fetch()) { + $field_name = $ruleBao->rule_field; + if ($field_name == 'phone_numeric') { + $field_name = 'phone'; + } + $ruleFields[] = $field_name; + } + return $ruleFields; + } + + /** + * @param int $cid + * @param int $oid + * + * @return bool + */ + public static function validateContacts($cid, $oid) { + if (!$cid || !$oid) { + return NULL; + } + $exception = new CRM_Dedupe_DAO_Exception(); + $exception->contact_id1 = $cid; + $exception->contact_id2 = $oid; + //make sure contact2 > contact1. + if ($cid > $oid) { + $exception->contact_id1 = $oid; + $exception->contact_id2 = $cid; + } + + return !$exception->find(TRUE); + } + + /** + * Get the specification for the given field. + * + * @param string $fieldName + * + * @return array + * @throws \CiviCRM_API3_Exception + */ + public function getFieldType($fieldName) { + $entity = CRM_Core_DAO_AllCoreTables::getBriefName(CRM_Core_DAO_AllCoreTables::getClassForTable($this->rule_table)); + if (!$entity) { + // This means we have stored a custom field rather than an entity name in rule_table, figure out the entity. + $entity = civicrm_api3('CustomGroup', 'getvalue', ['table_name' => $this->rule_table, 'return' => 'extends']); + if (in_array($entity, ['Individual', 'Household', 'Organization'])) { + $entity = 'Contact'; + } + $fieldName = 'custom_' . civicrm_api3('CustomField', 'getvalue', ['column_name' => $fieldName, 'return' => 'id']); + } + $fields = civicrm_api3($entity, 'getfields', ['action' => 'create'])['values']; + return $fields[$fieldName]['type']; + } + +} diff --git a/CRM/Dedupe/BAO/DedupeRuleGroup.php b/CRM/Dedupe/BAO/DedupeRuleGroup.php new file mode 100644 index 0000000000..35a2902fdf --- /dev/null +++ b/CRM/Dedupe/BAO/DedupeRuleGroup.php @@ -0,0 +1,517 @@ +. + */ +class CRM_Dedupe_BAO_DedupeRuleGroup extends CRM_Dedupe_DAO_DedupeRuleGroup { + + /** + * Ids of the contacts to limit the SQL queries (whole-database queries otherwise) + * @var array + */ + public $contactIds = []; + + /** + * Set the contact IDs to restrict the dedupe to. + * + * @param array $contactIds + */ + public function setContactIds($contactIds) { + $this->contactIds = $contactIds; + } + + /** + * Params to dedupe against (queries against the whole contact set otherwise) + * @var array + */ + public $params = []; + + /** + * If there are no rules in rule group. + * @var bool + */ + public $noRules = FALSE; + + protected $temporaryTables = []; + + /** + * Return a structure holding the supported tables, fields and their titles + * + * @param string $requestedType + * The requested contact type. + * + * @return array + * a table-keyed array of field-keyed arrays holding supported fields' titles + */ + public static function supportedFields($requestedType) { + static $fields = NULL; + if (!$fields) { + // this is needed, as we're piggy-backing importableFields() below + $replacements = [ + 'civicrm_country.name' => 'civicrm_address.country_id', + 'civicrm_county.name' => 'civicrm_address.county_id', + 'civicrm_state_province.name' => 'civicrm_address.state_province_id', + 'gender.label' => 'civicrm_contact.gender_id', + 'individual_prefix.label' => 'civicrm_contact.prefix_id', + 'individual_suffix.label' => 'civicrm_contact.suffix_id', + 'addressee.label' => 'civicrm_contact.addressee_id', + 'email_greeting.label' => 'civicrm_contact.email_greeting_id', + 'postal_greeting.label' => 'civicrm_contact.postal_greeting_id', + 'civicrm_phone.phone' => 'civicrm_phone.phone_numeric', + ]; + // the table names we support in dedupe rules - a filter for importableFields() + $supportedTables = [ + 'civicrm_address', + 'civicrm_contact', + 'civicrm_email', + 'civicrm_im', + 'civicrm_note', + 'civicrm_openid', + 'civicrm_phone', + ]; + + foreach (['Individual', 'Organization', 'Household'] as $ctype) { + // take the table.field pairs and their titles from importableFields() if the table is supported + foreach (CRM_Contact_BAO_Contact::importableFields($ctype) as $iField) { + if (isset($iField['where'])) { + $where = $iField['where']; + if (isset($replacements[$where])) { + $where = $replacements[$where]; + } + list($table, $field) = explode('.', $where); + if (!in_array($table, $supportedTables)) { + continue; + } + $fields[$ctype][$table][$field] = $iField['title']; + } + } + // Note that most of the fields available come from 'importable fields' - + // I thought about making this field 'importable' but it felt like there might be unknown consequences + // so I opted for just adding it in & securing it with a unit test. + /// Example usage of sort_name - It is possible to alter sort name via hook so 2 organization names might differ as in + // Justice League vs The Justice League but these could have the same sort_name if 'the the' + // exension is installed (https://github.com/eileenmcnaughton/org.wikimedia.thethe) + $fields[$ctype]['civicrm_contact']['sort_name'] = ts('Sort Name'); + // add custom data fields + foreach (CRM_Core_BAO_CustomGroup::getTree($ctype, NULL, NULL, -1) as $key => $cg) { + if (!is_int($key)) { + continue; + } + foreach ($cg['fields'] as $cf) { + $fields[$ctype][$cg['table_name']][$cf['column_name']] = $cf['label']; + } + } + } + } + CRM_Utils_Hook::dupeQuery(CRM_Core_DAO::$_nullObject, 'supportedFields', $fields); + return !empty($fields[$requestedType]) ? $fields[$requestedType] : []; + } + + /** + * Return the SQL query for dropping the temporary table. + */ + public function tableDropQuery() { + return 'DROP TEMPORARY TABLE IF EXISTS dedupe'; + } + + /** + * Return a set of SQL queries whose cummulative weights will mark matched + * records for the RuleGroup::threasholdQuery() to retrieve. + */ + public function tableQuery() { + // make sure we've got a fetched dbrecord, not sure if this is enforced + if (!$this->name == NULL || $this->is_reserved == NULL) { + $this->find(TRUE); + } + + // Reserved Rule Groups can optionally get special treatment by + // implementing an optimization class and returning a query array. + if ($this->is_reserved && + CRM_Utils_File::isIncludable("CRM/Dedupe/BAO/QueryBuilder/{$this->name}.php") + ) { + $command = empty($this->params) ? 'internal' : 'record'; + $queries = call_user_func(["CRM_Dedupe_BAO_QueryBuilder_{$this->name}", $command], $this); + } + else { + // All other rule groups have queries generated by the member dedupe + // rules defined in the administrative interface. + + // Find all rules contained by this script sorted by weight so that + // their execution can be short circuited on RuleGroup::fillTable() + $bao = new CRM_Dedupe_BAO_Rule(); + $bao->dedupe_rule_group_id = $this->id; + $bao->orderBy('rule_weight DESC'); + $bao->find(); + + // Generate a SQL query for each rule in the rule group that is + // tailored to respect the param and contactId options provided. + $queries = []; + while ($bao->fetch()) { + $bao->contactIds = $this->contactIds; + $bao->params = $this->params; + + // Skipping empty rules? Empty rules shouldn't exist; why check? + if ($query = $bao->sql()) { + $queries["{$bao->rule_table}.{$bao->rule_field}.{$bao->rule_weight}"] = $query; + } + } + } + + // if there are no rules in this rule group + // add an empty query fulfilling the pattern + if (!$queries) { + $this->noRules = TRUE; + return []; + } + + return $queries; + } + + public function fillTable() { + // get the list of queries handy + $tableQueries = $this->tableQuery(); + + if ($this->params && !$this->noRules) { + $this->temporaryTables['dedupe'] = CRM_Utils_SQL_TempTable::build() + ->setCategory('dedupe') + ->createWithColumns("id1 int, weight int, UNIQUE UI_id1 (id1)")->getName(); + $dedupeCopyTemporaryTableObject = CRM_Utils_SQL_TempTable::build() + ->setCategory('dedupe'); + $this->temporaryTables['dedupe_copy'] = $dedupeCopyTemporaryTableObject->getName(); + $insertClause = "INSERT INTO {$this->temporaryTables['dedupe']} (id1, weight)"; + $groupByClause = "GROUP BY id1, weight"; + $dupeCopyJoin = " JOIN {$this->temporaryTables['dedupe_copy']} ON {$this->temporaryTables['dedupe_copy']}.id1 = t1.column WHERE "; + } + else { + $this->temporaryTables['dedupe'] = CRM_Utils_SQL_TempTable::build() + ->setCategory('dedupe') + ->createWithColumns("id1 int, id2 int, weight int, UNIQUE UI_id1_id2 (id1, id2)")->getName(); + $dedupeCopyTemporaryTableObject = CRM_Utils_SQL_TempTable::build() + ->setCategory('dedupe'); + $this->temporaryTables['dedupe_copy'] = $dedupeCopyTemporaryTableObject->getName(); + $insertClause = "INSERT INTO {$this->temporaryTables['dedupe']} (id1, id2, weight)"; + $groupByClause = "GROUP BY id1, id2, weight"; + $dupeCopyJoin = " JOIN {$this->temporaryTables['dedupe_copy']} ON {$this->temporaryTables['dedupe_copy']}.id1 = t1.column AND {$this->temporaryTables['dedupe_copy']}.id2 = t2.column WHERE "; + } + $patternColumn = '/t1.(\w+)/'; + $exclWeightSum = []; + + CRM_Utils_Hook::dupeQuery($this, 'table', $tableQueries); + + while (!empty($tableQueries)) { + list($isInclusive, $isDie) = self::isQuerySetInclusive($tableQueries, $this->threshold, $exclWeightSum); + + if ($isInclusive) { + // order queries by table count + self::orderByTableCount($tableQueries); + + $weightSum = array_sum($exclWeightSum); + $searchWithinDupes = !empty($exclWeightSum) ? 1 : 0; + + while (!empty($tableQueries)) { + // extract the next query ( and weight ) to be executed + $fieldWeight = array_keys($tableQueries); + $fieldWeight = $fieldWeight[0]; + $query = array_shift($tableQueries); + + if ($searchWithinDupes) { + // drop dedupe_copy table just in case if its already there. + $dedupeCopyTemporaryTableObject->drop(); + // get prepared to search within already found dupes if $searchWithinDupes flag is set + $dedupeCopyTemporaryTableObject->createWithQuery("SELECT * FROM {$this->temporaryTables['dedupe']} WHERE weight >= {$weightSum}"); + + preg_match($patternColumn, $query, $matches); + $query = str_replace(' WHERE ', str_replace('column', $matches[1], $dupeCopyJoin), $query); + + // CRM-19612: If there's a union, there will be two WHEREs, and you + // can't use the temp table twice. + if (preg_match('/' . $this->temporaryTables['dedupe_copy'] . '[\S\s]*(union)[\S\s]*' . $this->temporaryTables['dedupe_copy'] . '/i', $query, $matches, PREG_OFFSET_CAPTURE)) { + // Make a second temp table: + $this->temporaryTables['dedupe_copy_2'] = CRM_Utils_SQL_TempTable::build() + ->setCategory('dedupe') + ->createWithQuery("SELECT * FROM {$this->temporaryTables['dedupe']} WHERE weight >= {$weightSum}") + ->getName(); + // After the union, use that new temp table: + $part1 = substr($query, 0, $matches[1][1]); + $query = $part1 . str_replace($this->temporaryTables['dedupe_copy'], $this->temporaryTables['dedupe_copy_2'], substr($query, $matches[1][1])); + } + } + $searchWithinDupes = 1; + + // construct and execute the intermediate query + $query = "{$insertClause} {$query} {$groupByClause} ON DUPLICATE KEY UPDATE weight = weight + VALUES(weight)"; + $dao = CRM_Core_DAO::executeQuery($query); + + // FIXME: we need to be more acurate with affected rows, especially for insert vs duplicate insert. + // And that will help optimize further. + $affectedRows = $dao->affectedRows(); + + // In an inclusive situation, failure of any query means no further processing - + if ($affectedRows == 0) { + // reset to make sure no further execution is done. + $tableQueries = []; + break; + } + $weightSum = substr($fieldWeight, strrpos($fieldWeight, '.') + 1) + $weightSum; + } + // An exclusive situation - + } + elseif (!$isDie) { + // since queries are already sorted by weights, we can continue as is + $fieldWeight = array_keys($tableQueries); + $fieldWeight = $fieldWeight[0]; + $query = array_shift($tableQueries); + $query = "{$insertClause} {$query} {$groupByClause} ON DUPLICATE KEY UPDATE weight = weight + VALUES(weight)"; + $dao = CRM_Core_DAO::executeQuery($query); + if ($dao->affectedRows() >= 1) { + $exclWeightSum[] = substr($fieldWeight, strrpos($fieldWeight, '.') + 1); + } + } + else { + // its a die situation + break; + } + } + } + + /** + * Function to determine if a given query set contains inclusive or exclusive set of weights. + * The function assumes that the query set is already ordered by weight in desc order. + * @param $tableQueries + * @param $threshold + * @param array $exclWeightSum + * + * @return array + */ + public static function isQuerySetInclusive($tableQueries, $threshold, $exclWeightSum = []) { + $input = []; + foreach ($tableQueries as $key => $query) { + $input[] = substr($key, strrpos($key, '.') + 1); + } + + if (!empty($exclWeightSum)) { + $input = array_merge($input, $exclWeightSum); + rsort($input); + } + + if (count($input) == 1) { + return [FALSE, $input[0] < $threshold]; + } + + $totalCombinations = 0; + for ($i = 0; $i < count($input); $i++) { + $combination = [$input[$i]]; + if (array_sum($combination) >= $threshold) { + $totalCombinations++; + continue; + } + for ($j = $i + 1; $j < count($input); $j++) { + $combination[] = $input[$j]; + if (array_sum($combination) >= $threshold) { + $totalCombinations++; + } + } + } + return [$totalCombinations == 1, $totalCombinations <= 0]; + } + + /** + * sort queries by number of records for the table associated with them. + * @param $tableQueries + */ + public static function orderByTableCount(&$tableQueries) { + static $tableCount = []; + + $tempArray = []; + foreach ($tableQueries as $key => $query) { + $table = explode(".", $key); + $table = $table[0]; + if (!array_key_exists($table, $tableCount)) { + $query = "SELECT COUNT(*) FROM {$table}"; + $tableCount[$table] = CRM_Core_DAO::singleValueQuery($query); + } + $tempArray[$key] = $tableCount[$table]; + } + + asort($tempArray); + foreach ($tempArray as $key => $count) { + $tempArray[$key] = $tableQueries[$key]; + } + $tableQueries = $tempArray; + } + + /** + * Return the SQL query for getting only the interesting results out of the dedupe table. + * + * @$checkPermission boolean $params a flag to indicate if permission should be considered. + * default is to always check permissioning but public pages for example might not want + * permission to be checked for anonymous users. Refer CRM-6211. We might be beaking + * Multi-Site dedupe for public pages. + * + * @param bool $checkPermission + * + * @return string + */ + public function thresholdQuery($checkPermission = TRUE) { + $this->_aclFrom = ''; + $aclWhere = ''; + + if ($this->params && !$this->noRules) { + if ($checkPermission) { + list($this->_aclFrom, $aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause('civicrm_contact'); + $aclWhere = $aclWhere ? "AND {$aclWhere}" : ''; + } + $query = "SELECT {$this->temporaryTables['dedupe']}.id1 as id + FROM {$this->temporaryTables['dedupe']} JOIN civicrm_contact ON {$this->temporaryTables['dedupe']}.id1 = civicrm_contact.id {$this->_aclFrom} + WHERE contact_type = '{$this->contact_type}' AND is_deleted = 0 $aclWhere + AND weight >= {$this->threshold}"; + } + else { + $aclWhere = ''; + if ($checkPermission) { + list($this->_aclFrom, $aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause(['c1', 'c2']); + $aclWhere = $aclWhere ? "AND {$aclWhere}" : ''; + } + $query = "SELECT IF({$this->temporaryTables['dedupe']}.id1 < {$this->temporaryTables['dedupe']}.id2, {$this->temporaryTables['dedupe']}.id1, {$this->temporaryTables['dedupe']}.id2) as id1, + IF({$this->temporaryTables['dedupe']}.id1 < {$this->temporaryTables['dedupe']}.id2, {$this->temporaryTables['dedupe']}.id2, {$this->temporaryTables['dedupe']}.id1) as id2, {$this->temporaryTables['dedupe']}.weight + FROM {$this->temporaryTables['dedupe']} JOIN civicrm_contact c1 ON {$this->temporaryTables['dedupe']}.id1 = c1.id + JOIN civicrm_contact c2 ON {$this->temporaryTables['dedupe']}.id2 = c2.id {$this->_aclFrom} + LEFT JOIN civicrm_dedupe_exception exc ON {$this->temporaryTables['dedupe']}.id1 = exc.contact_id1 AND {$this->temporaryTables['dedupe']}.id2 = exc.contact_id2 + WHERE c1.contact_type = '{$this->contact_type}' AND + c2.contact_type = '{$this->contact_type}' + AND c1.is_deleted = 0 AND c2.is_deleted = 0 + {$aclWhere} + AND weight >= {$this->threshold} AND exc.contact_id1 IS NULL"; + } + + CRM_Utils_Hook::dupeQuery($this, 'threshold', $query); + return $query; + } + + /** + * find fields related to a rule group. + * + * @param array $params + * + * @return array + * (rule field => weight) array and threshold associated to rule group + */ + public static function dedupeRuleFieldsWeight($params) { + $rgBao = new CRM_Dedupe_BAO_RuleGroup(); + $rgBao->contact_type = $params['contact_type']; + if (!empty($params['id'])) { + // accept an ID if provided + $rgBao->id = $params['id']; + } + else { + $rgBao->used = $params['used']; + } + $rgBao->find(TRUE); + + $ruleBao = new CRM_Dedupe_BAO_Rule(); + $ruleBao->dedupe_rule_group_id = $rgBao->id; + $ruleBao->find(); + $ruleFields = []; + while ($ruleBao->fetch()) { + $field_name = $ruleBao->rule_field; + if ($field_name == 'phone_numeric') { + $field_name = 'phone'; + } + $ruleFields[$field_name] = $ruleBao->rule_weight; + } + + return [$ruleFields, $rgBao->threshold]; + } + + /** + * Get all of the combinations of fields that would work with a rule. + * + * @param array $rgFields + * @param int $threshold + * @param array $combos + * @param array $running + */ + public static function combos($rgFields, $threshold, &$combos, $running = []) { + foreach ($rgFields as $rgField => $weight) { + unset($rgFields[$rgField]); + $diff = $threshold - $weight; + $runningnow = $running; + $runningnow[] = $rgField; + if ($diff > 0) { + self::combos($rgFields, $diff, $combos, $runningnow); + } + else { + $combos[] = $runningnow; + } + } + } + + /** + * Get an array of rule group id to rule group name + * for all th groups for that contactType. If contactType + * not specified, do it for all + * + * @param string $contactType + * Individual, Household or Organization. + * + * + * @return array + * id => "nice name" of rule group + */ + public static function getByType($contactType = NULL) { + $dao = new CRM_Dedupe_DAO_RuleGroup(); + + if ($contactType) { + $dao->contact_type = $contactType; + } + + $dao->find(); + $result = []; + while ($dao->fetch()) { + $title = !empty($dao->title) ? $dao->title : (!empty($dao->name) ? $dao->name : $dao->contact_type); + + $name = "$title - {$dao->used}"; + $result[$dao->id] = $name; + } + return $result; + } + + /** + * Get the cached contact type for a particular rule group. + * + * @param int $rule_group_id + * + * @return string + */ + public static function getContactTypeForRuleGroup($rule_group_id) { + if (!isset(\Civi::$statics[__CLASS__]) || !isset(\Civi::$statics[__CLASS__]['rule_groups'])) { + \Civi::$statics[__CLASS__]['rule_groups'] = []; + } + if (empty(\Civi::$statics[__CLASS__]['rule_groups'][$rule_group_id])) { + \Civi::$statics[__CLASS__]['rule_groups'][$rule_group_id]['contact_type'] = CRM_Core_DAO::getFieldValue( + 'CRM_Dedupe_DAO_RuleGroup', + $rule_group_id, + 'contact_type' + ); + } + + return \Civi::$statics[__CLASS__]['rule_groups'][$rule_group_id]['contact_type']; + } + +} diff --git a/CRM/Dedupe/BAO/Exception.php b/CRM/Dedupe/BAO/Exception.php index 6097887055..e3777e7fe6 100644 --- a/CRM/Dedupe/BAO/Exception.php +++ b/CRM/Dedupe/BAO/Exception.php @@ -18,39 +18,4 @@ /** * Manages dedupe exceptions - ie pairs marked as non-duplicates. */ -class CRM_Dedupe_BAO_Exception extends CRM_Dedupe_DAO_Exception { - - /** - * Create a dedupe exception record. - * - * @param array $params - * - * @return \CRM_Dedupe_BAO_Exception - */ - public static function create($params) { - $hook = empty($params['id']) ? 'create' : 'edit'; - CRM_Utils_Hook::pre($hook, 'Exception', CRM_Utils_Array::value('id', $params), $params); - $contact1 = $params['contact_id1'] ?? NULL; - $contact2 = $params['contact_id2'] ?? NULL; - $dao = new CRM_Dedupe_BAO_Exception(); - $dao->copyValues($params); - if ($contact1 && $contact2) { - CRM_Core_DAO::singleValueQuery(" - DELETE FROM civicrm_prevnext_cache - WHERE (entity_id1 = %1 AND entity_id2 = %2) - OR (entity_id1 = %2 AND entity_id2 = %2)", - [1 => [$contact1, 'Integer'], 2 => [$contact2, 'Integer']] - ); - if ($contact2 < $contact1) { - // These are expected to be saved lowest first. - $dao->contact_id1 = $contact2; - $dao->contact_id2 = $contact1; - } - } - $dao->save(); - - CRM_Utils_Hook::post($hook, 'Exception', $dao->id, $dao); - return $dao; - } - -} +class CRM_Dedupe_BAO_Exception extends CRM_Dedupe_BAO_DedupeException {} diff --git a/CRM/Dedupe/BAO/Rule.php b/CRM/Dedupe/BAO/Rule.php index 6b7f63be7b..0e4d31f948 100644 --- a/CRM/Dedupe/BAO/Rule.php +++ b/CRM/Dedupe/BAO/Rule.php @@ -19,234 +19,4 @@ * The CiviCRM duplicate discovery engine is based on an * algorithm designed by David Strauss . */ -class CRM_Dedupe_BAO_Rule extends CRM_Dedupe_DAO_Rule { - - /** - * Ids of the contacts to limit the SQL queries (whole-database queries otherwise) - * @var array - */ - public $contactIds = []; - - /** - * Params to dedupe against (queries against the whole contact set otherwise) - * @var array - */ - public $params = []; - - /** - * Return the SQL query for the given rule - either for finding matching - * pairs of contacts, or for matching against the $params variable (if set). - * - * @return string - * SQL query performing the search - * - * @throws \CRM_Core_Exception - * @throws \CiviCRM_API3_Exception - */ - public function sql() { - if ($this->params && - (!array_key_exists($this->rule_table, $this->params) || - !array_key_exists($this->rule_field, $this->params[$this->rule_table]) - ) - ) { - // if params is present and doesn't have an entry for a field, don't construct the clause. - return NULL; - } - - // we need to initialise WHERE, ON and USING here, as some table types - // extend them; $where is an array of required conditions, $on and - // $using are arrays of required field matchings (for substring and - // full matches, respectively) - $where = []; - $on = ["SUBSTR(t1.{$this->rule_field}, 1, {$this->rule_length}) = SUBSTR(t2.{$this->rule_field}, 1, {$this->rule_length})"]; - - $innerJoinClauses = [ - "t1.{$this->rule_field} IS NOT NULL", - "t2.{$this->rule_field} IS NOT NULL", - "t1.{$this->rule_field} = t2.{$this->rule_field}", - ]; - - if (in_array($this->getFieldType($this->rule_field), CRM_Utils_Type::getTextTypes(), TRUE)) { - $innerJoinClauses[] = "t1.{$this->rule_field} <> ''"; - $innerJoinClauses[] = "t2.{$this->rule_field} <> ''"; - } - - switch ($this->rule_table) { - case 'civicrm_contact': - $id = 'id'; - //we should restrict by contact type in the first step - $sql = "SELECT contact_type FROM civicrm_dedupe_rule_group WHERE id = {$this->dedupe_rule_group_id};"; - $ct = CRM_Core_DAO::singleValueQuery($sql); - if ($this->params) { - $where[] = "t1.contact_type = '{$ct}'"; - } - else { - $where[] = "t1.contact_type = '{$ct}'"; - $where[] = "t2.contact_type = '{$ct}'"; - } - break; - - case 'civicrm_address': - case 'civicrm_email': - case 'civicrm_im': - case 'civicrm_openid': - case 'civicrm_phone': - $id = 'contact_id'; - break; - - case 'civicrm_note': - $id = 'entity_id'; - if ($this->params) { - $where[] = "t1.entity_table = 'civicrm_contact'"; - } - else { - $where[] = "t1.entity_table = 'civicrm_contact'"; - $where[] = "t2.entity_table = 'civicrm_contact'"; - } - break; - - default: - // custom data tables - if (preg_match('/^civicrm_value_/', $this->rule_table) || preg_match('/^custom_value_/', $this->rule_table)) { - $id = 'entity_id'; - } - else { - throw new CRM_Core_Exception("Unsupported rule_table for civicrm_dedupe_rule.id of {$this->id}"); - } - break; - } - - // build SELECT based on the field names containing contact ids - // if there are params provided, id1 should be 0 - if ($this->params) { - $select = "t1.$id id1, {$this->rule_weight} weight"; - $subSelect = 'id1, weight'; - } - else { - $select = "t1.$id id1, t2.$id id2, {$this->rule_weight} weight"; - $subSelect = 'id1, id2, weight'; - } - - // build FROM (and WHERE, if it's a parametrised search) - // based on whether the rule is about substrings or not - if ($this->params) { - $from = "{$this->rule_table} t1"; - $str = 'NULL'; - if (isset($this->params[$this->rule_table][$this->rule_field])) { - $str = trim(CRM_Utils_Type::escape($this->params[$this->rule_table][$this->rule_field], 'String')); - } - if ($this->rule_length) { - $where[] = "SUBSTR(t1.{$this->rule_field}, 1, {$this->rule_length}) = SUBSTR('$str', 1, {$this->rule_length})"; - $where[] = "t1.{$this->rule_field} IS NOT NULL"; - } - else { - $where[] = "t1.{$this->rule_field} = '$str'"; - } - } - else { - if ($this->rule_length) { - $from = "{$this->rule_table} t1 JOIN {$this->rule_table} t2 ON (" . implode(' AND ', $on) . ")"; - } - else { - $from = "{$this->rule_table} t1 INNER JOIN {$this->rule_table} t2 ON (" . implode(' AND ', $innerJoinClauses) . ")"; - } - } - - // finish building WHERE, also limit the results if requested - if (!$this->params) { - $where[] = "t1.$id < t2.$id"; - } - $query = "SELECT $select FROM $from WHERE " . implode(' AND ', $where); - if ($this->contactIds) { - $cids = []; - foreach ($this->contactIds as $cid) { - $cids[] = CRM_Utils_Type::escape($cid, 'Integer'); - } - if (count($cids) == 1) { - $query .= " AND (t1.$id = {$cids[0]}) UNION $query AND t2.$id = {$cids[0]}"; - } - else { - $query .= " AND t1.$id IN (" . implode(',', $cids) . ") - UNION $query AND t2.$id IN (" . implode(',', $cids) . ")"; - } - // The `weight` is ambiguous in the context of the union; put the whole - // thing in a subquery. - $query = "SELECT $subSelect FROM ($query) subunion"; - } - - return $query; - } - - /** - * find fields related to a rule group. - * - * @param array $params contains the rule group property to identify rule group - * - * @return array - * rule fields array associated to rule group - */ - public static function dedupeRuleFields($params) { - $rgBao = new CRM_Dedupe_BAO_RuleGroup(); - $rgBao->used = $params['used']; - $rgBao->contact_type = $params['contact_type']; - $rgBao->find(TRUE); - - $ruleBao = new CRM_Dedupe_BAO_Rule(); - $ruleBao->dedupe_rule_group_id = $rgBao->id; - $ruleBao->find(); - $ruleFields = []; - while ($ruleBao->fetch()) { - $field_name = $ruleBao->rule_field; - if ($field_name == 'phone_numeric') { - $field_name = 'phone'; - } - $ruleFields[] = $field_name; - } - return $ruleFields; - } - - /** - * @param int $cid - * @param int $oid - * - * @return bool - */ - public static function validateContacts($cid, $oid) { - if (!$cid || !$oid) { - return NULL; - } - $exception = new CRM_Dedupe_DAO_Exception(); - $exception->contact_id1 = $cid; - $exception->contact_id2 = $oid; - //make sure contact2 > contact1. - if ($cid > $oid) { - $exception->contact_id1 = $oid; - $exception->contact_id2 = $cid; - } - - return !$exception->find(TRUE); - } - - /** - * Get the specification for the given field. - * - * @param string $fieldName - * - * @return array - * @throws \CiviCRM_API3_Exception - */ - public function getFieldType($fieldName) { - $entity = CRM_Core_DAO_AllCoreTables::getBriefName(CRM_Core_DAO_AllCoreTables::getClassForTable($this->rule_table)); - if (!$entity) { - // This means we have stored a custom field rather than an entity name in rule_table, figure out the entity. - $entity = civicrm_api3('CustomGroup', 'getvalue', ['table_name' => $this->rule_table, 'return' => 'extends']); - if (in_array($entity, ['Individual', 'Household', 'Organization'])) { - $entity = 'Contact'; - } - $fieldName = 'custom_' . civicrm_api3('CustomField', 'getvalue', ['column_name' => $fieldName, 'return' => 'id']); - } - $fields = civicrm_api3($entity, 'getfields', ['action' => 'create'])['values']; - return $fields[$fieldName]['type']; - } - -} +class CRM_Dedupe_BAO_Rule extends CRM_Dedupe_BAO_DedupeRule {} diff --git a/CRM/Dedupe/BAO/RuleGroup.php b/CRM/Dedupe/BAO/RuleGroup.php index 6cbb407c84..1b70dcbffe 100644 --- a/CRM/Dedupe/BAO/RuleGroup.php +++ b/CRM/Dedupe/BAO/RuleGroup.php @@ -19,499 +19,4 @@ * The CiviCRM duplicate discovery engine is based on an * algorithm designed by David Strauss . */ -class CRM_Dedupe_BAO_RuleGroup extends CRM_Dedupe_DAO_RuleGroup { - - /** - * Ids of the contacts to limit the SQL queries (whole-database queries otherwise) - * @var array - */ - public $contactIds = []; - - /** - * Set the contact IDs to restrict the dedupe to. - * - * @param array $contactIds - */ - public function setContactIds($contactIds) { - $this->contactIds = $contactIds; - } - - /** - * Params to dedupe against (queries against the whole contact set otherwise) - * @var array - */ - public $params = []; - - /** - * If there are no rules in rule group. - * @var bool - */ - public $noRules = FALSE; - - protected $temporaryTables = []; - - /** - * Return a structure holding the supported tables, fields and their titles - * - * @param string $requestedType - * The requested contact type. - * - * @return array - * a table-keyed array of field-keyed arrays holding supported fields' titles - */ - public static function supportedFields($requestedType) { - static $fields = NULL; - if (!$fields) { - // this is needed, as we're piggy-backing importableFields() below - $replacements = [ - 'civicrm_country.name' => 'civicrm_address.country_id', - 'civicrm_county.name' => 'civicrm_address.county_id', - 'civicrm_state_province.name' => 'civicrm_address.state_province_id', - 'gender.label' => 'civicrm_contact.gender_id', - 'individual_prefix.label' => 'civicrm_contact.prefix_id', - 'individual_suffix.label' => 'civicrm_contact.suffix_id', - 'addressee.label' => 'civicrm_contact.addressee_id', - 'email_greeting.label' => 'civicrm_contact.email_greeting_id', - 'postal_greeting.label' => 'civicrm_contact.postal_greeting_id', - 'civicrm_phone.phone' => 'civicrm_phone.phone_numeric', - ]; - // the table names we support in dedupe rules - a filter for importableFields() - $supportedTables = [ - 'civicrm_address', - 'civicrm_contact', - 'civicrm_email', - 'civicrm_im', - 'civicrm_note', - 'civicrm_openid', - 'civicrm_phone', - ]; - - foreach (['Individual', 'Organization', 'Household'] as $ctype) { - // take the table.field pairs and their titles from importableFields() if the table is supported - foreach (CRM_Contact_BAO_Contact::importableFields($ctype) as $iField) { - if (isset($iField['where'])) { - $where = $iField['where']; - if (isset($replacements[$where])) { - $where = $replacements[$where]; - } - list($table, $field) = explode('.', $where); - if (!in_array($table, $supportedTables)) { - continue; - } - $fields[$ctype][$table][$field] = $iField['title']; - } - } - // Note that most of the fields available come from 'importable fields' - - // I thought about making this field 'importable' but it felt like there might be unknown consequences - // so I opted for just adding it in & securing it with a unit test. - /// Example usage of sort_name - It is possible to alter sort name via hook so 2 organization names might differ as in - // Justice League vs The Justice League but these could have the same sort_name if 'the the' - // exension is installed (https://github.com/eileenmcnaughton/org.wikimedia.thethe) - $fields[$ctype]['civicrm_contact']['sort_name'] = ts('Sort Name'); - // add custom data fields - foreach (CRM_Core_BAO_CustomGroup::getTree($ctype, NULL, NULL, -1) as $key => $cg) { - if (!is_int($key)) { - continue; - } - foreach ($cg['fields'] as $cf) { - $fields[$ctype][$cg['table_name']][$cf['column_name']] = $cf['label']; - } - } - } - } - CRM_Utils_Hook::dupeQuery(CRM_Core_DAO::$_nullObject, 'supportedFields', $fields); - return !empty($fields[$requestedType]) ? $fields[$requestedType] : []; - } - - /** - * Return the SQL query for dropping the temporary table. - */ - public function tableDropQuery() { - return 'DROP TEMPORARY TABLE IF EXISTS dedupe'; - } - - /** - * Return a set of SQL queries whose cummulative weights will mark matched - * records for the RuleGroup::threasholdQuery() to retrieve. - */ - public function tableQuery() { - // make sure we've got a fetched dbrecord, not sure if this is enforced - if (!$this->name == NULL || $this->is_reserved == NULL) { - $this->find(TRUE); - } - - // Reserved Rule Groups can optionally get special treatment by - // implementing an optimization class and returning a query array. - if ($this->is_reserved && - CRM_Utils_File::isIncludable("CRM/Dedupe/BAO/QueryBuilder/{$this->name}.php") - ) { - $command = empty($this->params) ? 'internal' : 'record'; - $queries = call_user_func(["CRM_Dedupe_BAO_QueryBuilder_{$this->name}", $command], $this); - } - else { - // All other rule groups have queries generated by the member dedupe - // rules defined in the administrative interface. - - // Find all rules contained by this script sorted by weight so that - // their execution can be short circuited on RuleGroup::fillTable() - $bao = new CRM_Dedupe_BAO_Rule(); - $bao->dedupe_rule_group_id = $this->id; - $bao->orderBy('rule_weight DESC'); - $bao->find(); - - // Generate a SQL query for each rule in the rule group that is - // tailored to respect the param and contactId options provided. - $queries = []; - while ($bao->fetch()) { - $bao->contactIds = $this->contactIds; - $bao->params = $this->params; - - // Skipping empty rules? Empty rules shouldn't exist; why check? - if ($query = $bao->sql()) { - $queries["{$bao->rule_table}.{$bao->rule_field}.{$bao->rule_weight}"] = $query; - } - } - } - - // if there are no rules in this rule group - // add an empty query fulfilling the pattern - if (!$queries) { - $this->noRules = TRUE; - return []; - } - - return $queries; - } - - public function fillTable() { - // get the list of queries handy - $tableQueries = $this->tableQuery(); - - if ($this->params && !$this->noRules) { - $this->temporaryTables['dedupe'] = CRM_Utils_SQL_TempTable::build() - ->setCategory('dedupe') - ->createWithColumns("id1 int, weight int, UNIQUE UI_id1 (id1)")->getName(); - $dedupeCopyTemporaryTableObject = CRM_Utils_SQL_TempTable::build() - ->setCategory('dedupe'); - $this->temporaryTables['dedupe_copy'] = $dedupeCopyTemporaryTableObject->getName(); - $insertClause = "INSERT INTO {$this->temporaryTables['dedupe']} (id1, weight)"; - $groupByClause = "GROUP BY id1, weight"; - $dupeCopyJoin = " JOIN {$this->temporaryTables['dedupe_copy']} ON {$this->temporaryTables['dedupe_copy']}.id1 = t1.column WHERE "; - } - else { - $this->temporaryTables['dedupe'] = CRM_Utils_SQL_TempTable::build() - ->setCategory('dedupe') - ->createWithColumns("id1 int, id2 int, weight int, UNIQUE UI_id1_id2 (id1, id2)")->getName(); - $dedupeCopyTemporaryTableObject = CRM_Utils_SQL_TempTable::build() - ->setCategory('dedupe'); - $this->temporaryTables['dedupe_copy'] = $dedupeCopyTemporaryTableObject->getName(); - $insertClause = "INSERT INTO {$this->temporaryTables['dedupe']} (id1, id2, weight)"; - $groupByClause = "GROUP BY id1, id2, weight"; - $dupeCopyJoin = " JOIN {$this->temporaryTables['dedupe_copy']} ON {$this->temporaryTables['dedupe_copy']}.id1 = t1.column AND {$this->temporaryTables['dedupe_copy']}.id2 = t2.column WHERE "; - } - $patternColumn = '/t1.(\w+)/'; - $exclWeightSum = []; - - CRM_Utils_Hook::dupeQuery($this, 'table', $tableQueries); - - while (!empty($tableQueries)) { - list($isInclusive, $isDie) = self::isQuerySetInclusive($tableQueries, $this->threshold, $exclWeightSum); - - if ($isInclusive) { - // order queries by table count - self::orderByTableCount($tableQueries); - - $weightSum = array_sum($exclWeightSum); - $searchWithinDupes = !empty($exclWeightSum) ? 1 : 0; - - while (!empty($tableQueries)) { - // extract the next query ( and weight ) to be executed - $fieldWeight = array_keys($tableQueries); - $fieldWeight = $fieldWeight[0]; - $query = array_shift($tableQueries); - - if ($searchWithinDupes) { - // drop dedupe_copy table just in case if its already there. - $dedupeCopyTemporaryTableObject->drop(); - // get prepared to search within already found dupes if $searchWithinDupes flag is set - $dedupeCopyTemporaryTableObject->createWithQuery("SELECT * FROM {$this->temporaryTables['dedupe']} WHERE weight >= {$weightSum}"); - - preg_match($patternColumn, $query, $matches); - $query = str_replace(' WHERE ', str_replace('column', $matches[1], $dupeCopyJoin), $query); - - // CRM-19612: If there's a union, there will be two WHEREs, and you - // can't use the temp table twice. - if (preg_match('/' . $this->temporaryTables['dedupe_copy'] . '[\S\s]*(union)[\S\s]*' . $this->temporaryTables['dedupe_copy'] . '/i', $query, $matches, PREG_OFFSET_CAPTURE)) { - // Make a second temp table: - $this->temporaryTables['dedupe_copy_2'] = CRM_Utils_SQL_TempTable::build() - ->setCategory('dedupe') - ->createWithQuery("SELECT * FROM {$this->temporaryTables['dedupe']} WHERE weight >= {$weightSum}") - ->getName(); - // After the union, use that new temp table: - $part1 = substr($query, 0, $matches[1][1]); - $query = $part1 . str_replace($this->temporaryTables['dedupe_copy'], $this->temporaryTables['dedupe_copy_2'], substr($query, $matches[1][1])); - } - } - $searchWithinDupes = 1; - - // construct and execute the intermediate query - $query = "{$insertClause} {$query} {$groupByClause} ON DUPLICATE KEY UPDATE weight = weight + VALUES(weight)"; - $dao = CRM_Core_DAO::executeQuery($query); - - // FIXME: we need to be more acurate with affected rows, especially for insert vs duplicate insert. - // And that will help optimize further. - $affectedRows = $dao->affectedRows(); - - // In an inclusive situation, failure of any query means no further processing - - if ($affectedRows == 0) { - // reset to make sure no further execution is done. - $tableQueries = []; - break; - } - $weightSum = substr($fieldWeight, strrpos($fieldWeight, '.') + 1) + $weightSum; - } - // An exclusive situation - - } - elseif (!$isDie) { - // since queries are already sorted by weights, we can continue as is - $fieldWeight = array_keys($tableQueries); - $fieldWeight = $fieldWeight[0]; - $query = array_shift($tableQueries); - $query = "{$insertClause} {$query} {$groupByClause} ON DUPLICATE KEY UPDATE weight = weight + VALUES(weight)"; - $dao = CRM_Core_DAO::executeQuery($query); - if ($dao->affectedRows() >= 1) { - $exclWeightSum[] = substr($fieldWeight, strrpos($fieldWeight, '.') + 1); - } - } - else { - // its a die situation - break; - } - } - } - - /** - * Function to determine if a given query set contains inclusive or exclusive set of weights. - * The function assumes that the query set is already ordered by weight in desc order. - * @param $tableQueries - * @param $threshold - * @param array $exclWeightSum - * - * @return array - */ - public static function isQuerySetInclusive($tableQueries, $threshold, $exclWeightSum = []) { - $input = []; - foreach ($tableQueries as $key => $query) { - $input[] = substr($key, strrpos($key, '.') + 1); - } - - if (!empty($exclWeightSum)) { - $input = array_merge($input, $exclWeightSum); - rsort($input); - } - - if (count($input) == 1) { - return [FALSE, $input[0] < $threshold]; - } - - $totalCombinations = 0; - for ($i = 0; $i < count($input); $i++) { - $combination = [$input[$i]]; - if (array_sum($combination) >= $threshold) { - $totalCombinations++; - continue; - } - for ($j = $i + 1; $j < count($input); $j++) { - $combination[] = $input[$j]; - if (array_sum($combination) >= $threshold) { - $totalCombinations++; - } - } - } - return [$totalCombinations == 1, $totalCombinations <= 0]; - } - - /** - * sort queries by number of records for the table associated with them. - * @param $tableQueries - */ - public static function orderByTableCount(&$tableQueries) { - static $tableCount = []; - - $tempArray = []; - foreach ($tableQueries as $key => $query) { - $table = explode(".", $key); - $table = $table[0]; - if (!array_key_exists($table, $tableCount)) { - $query = "SELECT COUNT(*) FROM {$table}"; - $tableCount[$table] = CRM_Core_DAO::singleValueQuery($query); - } - $tempArray[$key] = $tableCount[$table]; - } - - asort($tempArray); - foreach ($tempArray as $key => $count) { - $tempArray[$key] = $tableQueries[$key]; - } - $tableQueries = $tempArray; - } - - /** - * Return the SQL query for getting only the interesting results out of the dedupe table. - * - * @$checkPermission boolean $params a flag to indicate if permission should be considered. - * default is to always check permissioning but public pages for example might not want - * permission to be checked for anonymous users. Refer CRM-6211. We might be beaking - * Multi-Site dedupe for public pages. - * - * @param bool $checkPermission - * - * @return string - */ - public function thresholdQuery($checkPermission = TRUE) { - $this->_aclFrom = ''; - $aclWhere = ''; - - if ($this->params && !$this->noRules) { - if ($checkPermission) { - list($this->_aclFrom, $aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause('civicrm_contact'); - $aclWhere = $aclWhere ? "AND {$aclWhere}" : ''; - } - $query = "SELECT {$this->temporaryTables['dedupe']}.id1 as id - FROM {$this->temporaryTables['dedupe']} JOIN civicrm_contact ON {$this->temporaryTables['dedupe']}.id1 = civicrm_contact.id {$this->_aclFrom} - WHERE contact_type = '{$this->contact_type}' AND is_deleted = 0 $aclWhere - AND weight >= {$this->threshold}"; - } - else { - $aclWhere = ''; - if ($checkPermission) { - list($this->_aclFrom, $aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause(['c1', 'c2']); - $aclWhere = $aclWhere ? "AND {$aclWhere}" : ''; - } - $query = "SELECT IF({$this->temporaryTables['dedupe']}.id1 < {$this->temporaryTables['dedupe']}.id2, {$this->temporaryTables['dedupe']}.id1, {$this->temporaryTables['dedupe']}.id2) as id1, - IF({$this->temporaryTables['dedupe']}.id1 < {$this->temporaryTables['dedupe']}.id2, {$this->temporaryTables['dedupe']}.id2, {$this->temporaryTables['dedupe']}.id1) as id2, {$this->temporaryTables['dedupe']}.weight - FROM {$this->temporaryTables['dedupe']} JOIN civicrm_contact c1 ON {$this->temporaryTables['dedupe']}.id1 = c1.id - JOIN civicrm_contact c2 ON {$this->temporaryTables['dedupe']}.id2 = c2.id {$this->_aclFrom} - LEFT JOIN civicrm_dedupe_exception exc ON {$this->temporaryTables['dedupe']}.id1 = exc.contact_id1 AND {$this->temporaryTables['dedupe']}.id2 = exc.contact_id2 - WHERE c1.contact_type = '{$this->contact_type}' AND - c2.contact_type = '{$this->contact_type}' - AND c1.is_deleted = 0 AND c2.is_deleted = 0 - {$aclWhere} - AND weight >= {$this->threshold} AND exc.contact_id1 IS NULL"; - } - - CRM_Utils_Hook::dupeQuery($this, 'threshold', $query); - return $query; - } - - /** - * find fields related to a rule group. - * - * @param array $params - * - * @return array - * (rule field => weight) array and threshold associated to rule group - */ - public static function dedupeRuleFieldsWeight($params) { - $rgBao = new CRM_Dedupe_BAO_RuleGroup(); - $rgBao->contact_type = $params['contact_type']; - if (!empty($params['id'])) { - // accept an ID if provided - $rgBao->id = $params['id']; - } - else { - $rgBao->used = $params['used']; - } - $rgBao->find(TRUE); - - $ruleBao = new CRM_Dedupe_BAO_Rule(); - $ruleBao->dedupe_rule_group_id = $rgBao->id; - $ruleBao->find(); - $ruleFields = []; - while ($ruleBao->fetch()) { - $field_name = $ruleBao->rule_field; - if ($field_name == 'phone_numeric') { - $field_name = 'phone'; - } - $ruleFields[$field_name] = $ruleBao->rule_weight; - } - - return [$ruleFields, $rgBao->threshold]; - } - - /** - * Get all of the combinations of fields that would work with a rule. - * - * @param array $rgFields - * @param int $threshold - * @param array $combos - * @param array $running - */ - public static function combos($rgFields, $threshold, &$combos, $running = []) { - foreach ($rgFields as $rgField => $weight) { - unset($rgFields[$rgField]); - $diff = $threshold - $weight; - $runningnow = $running; - $runningnow[] = $rgField; - if ($diff > 0) { - self::combos($rgFields, $diff, $combos, $runningnow); - } - else { - $combos[] = $runningnow; - } - } - } - - /** - * Get an array of rule group id to rule group name - * for all th groups for that contactType. If contactType - * not specified, do it for all - * - * @param string $contactType - * Individual, Household or Organization. - * - * - * @return array - * id => "nice name" of rule group - */ - public static function getByType($contactType = NULL) { - $dao = new CRM_Dedupe_DAO_RuleGroup(); - - if ($contactType) { - $dao->contact_type = $contactType; - } - - $dao->find(); - $result = []; - while ($dao->fetch()) { - $title = !empty($dao->title) ? $dao->title : (!empty($dao->name) ? $dao->name : $dao->contact_type); - - $name = "$title - {$dao->used}"; - $result[$dao->id] = $name; - } - return $result; - } - - /** - * Get the cached contact type for a particular rule group. - * - * @param int $rule_group_id - * - * @return string - */ - public static function getContactTypeForRuleGroup($rule_group_id) { - if (!isset(\Civi::$statics[__CLASS__]) || !isset(\Civi::$statics[__CLASS__]['rule_groups'])) { - \Civi::$statics[__CLASS__]['rule_groups'] = []; - } - if (empty(\Civi::$statics[__CLASS__]['rule_groups'][$rule_group_id])) { - \Civi::$statics[__CLASS__]['rule_groups'][$rule_group_id]['contact_type'] = CRM_Core_DAO::getFieldValue( - 'CRM_Dedupe_DAO_RuleGroup', - $rule_group_id, - 'contact_type' - ); - } - - return \Civi::$statics[__CLASS__]['rule_groups'][$rule_group_id]['contact_type']; - } - -} +class CRM_Dedupe_BAO_RuleGroup extends CRM_Dedupe_BAO_DedupeRuleGroup {} diff --git a/CRM/Dedupe/DAO/DedupeException.php b/CRM/Dedupe/DAO/DedupeException.php new file mode 100644 index 0000000000..2a73b7a7ef --- /dev/null +++ b/CRM/Dedupe/DAO/DedupeException.php @@ -0,0 +1,231 @@ +__table = 'civicrm_dedupe_exception'; + parent::__construct(); + } + + /** + * Returns localized title of this entity. + * + * @param bool $plural + * Whether to return the plural version of the title. + */ + public static function getEntityTitle($plural = FALSE) { + return $plural ? ts('Dedupe Exceptions') : ts('Dedupe Exception'); + } + + /** + * Returns foreign keys and entity references. + * + * @return array + * [CRM_Core_Reference_Interface] + */ + public static function getReferenceColumns() { + if (!isset(Civi::$statics[__CLASS__]['links'])) { + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id1', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id2', 'civicrm_contact', 'id'); + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); + } + return Civi::$statics[__CLASS__]['links']; + } + + /** + * Returns all the column names of this table + * + * @return array + */ + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ + 'name' => 'id', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('Dedupe Exception ID'), + 'description' => ts('Unique dedupe exception id'), + 'required' => TRUE, + 'where' => 'civicrm_dedupe_exception.id', + 'table_name' => 'civicrm_dedupe_exception', + 'entity' => 'DedupeException', + 'bao' => 'CRM_Dedupe_DAO_DedupeException', + 'localizable' => 0, + 'html' => [ + 'type' => 'Number', + ], + 'readonly' => TRUE, + 'add' => '3.3', + ], + 'contact_id1' => [ + 'name' => 'contact_id1', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('First Dupe Contact ID'), + 'description' => ts('FK to Contact ID'), + 'required' => TRUE, + 'where' => 'civicrm_dedupe_exception.contact_id1', + 'table_name' => 'civicrm_dedupe_exception', + 'entity' => 'DedupeException', + 'bao' => 'CRM_Dedupe_DAO_DedupeException', + 'localizable' => 0, + 'FKClassName' => 'CRM_Contact_DAO_Contact', + 'html' => [ + 'label' => ts("First Dupe Contact"), + ], + 'add' => '3.3', + ], + 'contact_id2' => [ + 'name' => 'contact_id2', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('Second Dupe Contact ID'), + 'description' => ts('FK to Contact ID'), + 'required' => TRUE, + 'where' => 'civicrm_dedupe_exception.contact_id2', + 'table_name' => 'civicrm_dedupe_exception', + 'entity' => 'DedupeException', + 'bao' => 'CRM_Dedupe_DAO_DedupeException', + 'localizable' => 0, + 'FKClassName' => 'CRM_Contact_DAO_Contact', + 'html' => [ + 'label' => ts("Second Dupe Contact"), + ], + 'add' => '3.3', + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); + } + return Civi::$statics[__CLASS__]['fields']; + } + + /** + * Return a mapping from field-name to the corresponding key (as used in fields()). + * + * @return array + * Array(string $name => string $uniqueName). + */ + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); + } + return Civi::$statics[__CLASS__]['fieldKeys']; + } + + /** + * Returns the names of this table + * + * @return string + */ + public static function getTableName() { + return self::$_tableName; + } + + /** + * Returns if this table needs to be logged + * + * @return bool + */ + public function getLog() { + return self::$_log; + } + + /** + * Returns the list of fields that can be imported + * + * @param bool $prefix + * + * @return array + */ + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'dedupe_exception', $prefix, []); + return $r; + } + + /** + * Returns the list of fields that can be exported + * + * @param bool $prefix + * + * @return array + */ + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'dedupe_exception', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = [ + 'UI_contact_id1_contact_id2' => [ + 'name' => 'UI_contact_id1_contact_id2', + 'field' => [ + 0 => 'contact_id1', + 1 => 'contact_id2', + ], + 'localizable' => FALSE, + 'unique' => TRUE, + 'sig' => 'civicrm_dedupe_exception::1::contact_id1::contact_id2', + ], + ]; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; + } + +} diff --git a/CRM/Dedupe/DAO/DedupeRule.php b/CRM/Dedupe/DAO/DedupeRule.php new file mode 100644 index 0000000000..7dc912053c --- /dev/null +++ b/CRM/Dedupe/DAO/DedupeRule.php @@ -0,0 +1,284 @@ +__table = 'civicrm_dedupe_rule'; + parent::__construct(); + } + + /** + * Returns localized title of this entity. + * + * @param bool $plural + * Whether to return the plural version of the title. + */ + public static function getEntityTitle($plural = FALSE) { + return $plural ? ts('Dedupe Rules') : ts('Dedupe Rule'); + } + + /** + * Returns foreign keys and entity references. + * + * @return array + * [CRM_Core_Reference_Interface] + */ + public static function getReferenceColumns() { + if (!isset(Civi::$statics[__CLASS__]['links'])) { + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'dedupe_rule_group_id', 'civicrm_dedupe_rule_group', 'id'); + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); + } + return Civi::$statics[__CLASS__]['links']; + } + + /** + * Returns all the column names of this table + * + * @return array + */ + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ + 'name' => 'id', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('Dedupe Rule ID'), + 'description' => ts('Unique dedupe rule id'), + 'required' => TRUE, + 'where' => 'civicrm_dedupe_rule.id', + 'table_name' => 'civicrm_dedupe_rule', + 'entity' => 'DedupeRule', + 'bao' => 'CRM_Dedupe_BAO_DedupeRule', + 'localizable' => 0, + 'html' => [ + 'type' => 'Number', + ], + 'readonly' => TRUE, + 'add' => '1.8', + ], + 'dedupe_rule_group_id' => [ + 'name' => 'dedupe_rule_group_id', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('Group ID'), + 'description' => ts('The id of the rule group this rule belongs to'), + 'required' => TRUE, + 'where' => 'civicrm_dedupe_rule.dedupe_rule_group_id', + 'table_name' => 'civicrm_dedupe_rule', + 'entity' => 'DedupeRule', + 'bao' => 'CRM_Dedupe_BAO_DedupeRule', + 'localizable' => 0, + 'FKClassName' => 'CRM_Dedupe_DAO_DedupeRuleGroup', + 'html' => [ + 'label' => ts("Group"), + ], + 'add' => '1.8', + ], + 'rule_table' => [ + 'name' => 'rule_table', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => ts('Rule Table'), + 'description' => ts('The name of the table this rule is about'), + 'required' => TRUE, + 'maxlength' => 64, + 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_dedupe_rule.rule_table', + 'table_name' => 'civicrm_dedupe_rule', + 'entity' => 'DedupeRule', + 'bao' => 'CRM_Dedupe_BAO_DedupeRule', + 'localizable' => 0, + 'add' => '1.8', + ], + 'rule_field' => [ + 'name' => 'rule_field', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => ts('Rule Field'), + 'description' => ts('The name of the field of the table referenced in rule_table'), + 'required' => TRUE, + 'maxlength' => 64, + 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_dedupe_rule.rule_field', + 'table_name' => 'civicrm_dedupe_rule', + 'entity' => 'DedupeRule', + 'bao' => 'CRM_Dedupe_BAO_DedupeRule', + 'localizable' => 0, + 'add' => '1.8', + ], + 'rule_length' => [ + 'name' => 'rule_length', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('Rule Length'), + 'description' => ts('The length of the matching substring'), + 'where' => 'civicrm_dedupe_rule.rule_length', + 'table_name' => 'civicrm_dedupe_rule', + 'entity' => 'DedupeRule', + 'bao' => 'CRM_Dedupe_BAO_DedupeRule', + 'localizable' => 0, + 'html' => [ + 'type' => 'Text', + ], + 'add' => '1.8', + ], + 'rule_weight' => [ + 'name' => 'rule_weight', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('Order'), + 'description' => ts('The weight of the rule'), + 'required' => TRUE, + 'where' => 'civicrm_dedupe_rule.rule_weight', + 'table_name' => 'civicrm_dedupe_rule', + 'entity' => 'DedupeRule', + 'bao' => 'CRM_Dedupe_BAO_DedupeRule', + 'localizable' => 0, + 'html' => [ + 'type' => 'Text', + ], + 'add' => '1.8', + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); + } + return Civi::$statics[__CLASS__]['fields']; + } + + /** + * Return a mapping from field-name to the corresponding key (as used in fields()). + * + * @return array + * Array(string $name => string $uniqueName). + */ + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); + } + return Civi::$statics[__CLASS__]['fieldKeys']; + } + + /** + * Returns the names of this table + * + * @return string + */ + public static function getTableName() { + return self::$_tableName; + } + + /** + * Returns if this table needs to be logged + * + * @return bool + */ + public function getLog() { + return self::$_log; + } + + /** + * Returns the list of fields that can be imported + * + * @param bool $prefix + * + * @return array + */ + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'dedupe_rule', $prefix, []); + return $r; + } + + /** + * Returns the list of fields that can be exported + * + * @param bool $prefix + * + * @return array + */ + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'dedupe_rule', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = []; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; + } + +} diff --git a/CRM/Dedupe/DAO/DedupeRuleGroup.php b/CRM/Dedupe/DAO/DedupeRuleGroup.php new file mode 100644 index 0000000000..55daa8ae2d --- /dev/null +++ b/CRM/Dedupe/DAO/DedupeRuleGroup.php @@ -0,0 +1,304 @@ +__table = 'civicrm_dedupe_rule_group'; + parent::__construct(); + } + + /** + * Returns localized title of this entity. + * + * @param bool $plural + * Whether to return the plural version of the title. + */ + public static function getEntityTitle($plural = FALSE) { + return $plural ? ts('Dedupe Rule Groups') : ts('Dedupe Rule Group'); + } + + /** + * Returns all the column names of this table + * + * @return array + */ + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ + 'name' => 'id', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('Rule Group ID'), + 'description' => ts('Unique dedupe rule group id'), + 'required' => TRUE, + 'where' => 'civicrm_dedupe_rule_group.id', + 'table_name' => 'civicrm_dedupe_rule_group', + 'entity' => 'DedupeRuleGroup', + 'bao' => 'CRM_Dedupe_BAO_DedupeRuleGroup', + 'localizable' => 0, + 'html' => [ + 'type' => 'Number', + ], + 'readonly' => TRUE, + 'add' => '1.8', + ], + 'contact_type' => [ + 'name' => 'contact_type', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => ts('Contact Type'), + 'description' => ts('The type of contacts this group applies to'), + 'maxlength' => 12, + 'size' => CRM_Utils_Type::TWELVE, + 'where' => 'civicrm_dedupe_rule_group.contact_type', + 'table_name' => 'civicrm_dedupe_rule_group', + 'entity' => 'DedupeRuleGroup', + 'bao' => 'CRM_Dedupe_BAO_DedupeRuleGroup', + 'localizable' => 0, + 'html' => [ + 'type' => 'Select', + ], + 'pseudoconstant' => [ + 'table' => 'civicrm_contact_type', + 'keyColumn' => 'name', + 'labelColumn' => 'label', + 'condition' => 'parent_id IS NULL', + ], + 'add' => '1.8', + ], + 'threshold' => [ + 'name' => 'threshold', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('Threshold'), + 'description' => ts('The weight threshold the sum of the rule weights has to cross to consider two contacts the same'), + 'required' => TRUE, + 'where' => 'civicrm_dedupe_rule_group.threshold', + 'table_name' => 'civicrm_dedupe_rule_group', + 'entity' => 'DedupeRuleGroup', + 'bao' => 'CRM_Dedupe_BAO_DedupeRuleGroup', + 'localizable' => 0, + 'html' => [ + 'type' => 'Text', + ], + 'add' => '1.8', + ], + 'used' => [ + 'name' => 'used', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => ts('Length'), + 'description' => ts('Whether the rule should be used for cases where usage is Unsupervised, Supervised OR General(programatically)'), + 'required' => TRUE, + 'maxlength' => 12, + 'size' => CRM_Utils_Type::TWELVE, + 'where' => 'civicrm_dedupe_rule_group.used', + 'table_name' => 'civicrm_dedupe_rule_group', + 'entity' => 'DedupeRuleGroup', + 'bao' => 'CRM_Dedupe_BAO_DedupeRuleGroup', + 'localizable' => 0, + 'html' => [ + 'type' => 'Radio', + ], + 'pseudoconstant' => [ + 'callback' => 'CRM_Core_SelectValues::getDedupeRuleTypes', + ], + 'add' => '4.3', + ], + 'name' => [ + 'name' => 'name', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => ts('Name'), + 'description' => ts('Name of the rule group'), + 'maxlength' => 64, + 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_dedupe_rule_group.name', + 'table_name' => 'civicrm_dedupe_rule_group', + 'entity' => 'DedupeRuleGroup', + 'bao' => 'CRM_Dedupe_BAO_DedupeRuleGroup', + 'localizable' => 0, + 'add' => '2.1', + ], + 'title' => [ + 'name' => 'title', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => ts('Title'), + 'description' => ts('Label of the rule group'), + 'maxlength' => 255, + 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_dedupe_rule_group.title', + 'table_name' => 'civicrm_dedupe_rule_group', + 'entity' => 'DedupeRuleGroup', + 'bao' => 'CRM_Dedupe_BAO_DedupeRuleGroup', + 'localizable' => 0, + 'html' => [ + 'type' => 'Text', + ], + 'add' => '4.1', + ], + 'is_reserved' => [ + 'name' => 'is_reserved', + 'type' => CRM_Utils_Type::T_BOOLEAN, + 'title' => ts('Reserved?'), + 'description' => ts('Is this a reserved rule - a rule group that has been optimized and cannot be changed by the admin'), + 'where' => 'civicrm_dedupe_rule_group.is_reserved', + 'table_name' => 'civicrm_dedupe_rule_group', + 'entity' => 'DedupeRuleGroup', + 'bao' => 'CRM_Dedupe_BAO_DedupeRuleGroup', + 'localizable' => 0, + 'html' => [ + 'type' => 'CheckBox', + ], + 'add' => '4.1', + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); + } + return Civi::$statics[__CLASS__]['fields']; + } + + /** + * Return a mapping from field-name to the corresponding key (as used in fields()). + * + * @return array + * Array(string $name => string $uniqueName). + */ + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); + } + return Civi::$statics[__CLASS__]['fieldKeys']; + } + + /** + * Returns the names of this table + * + * @return string + */ + public static function getTableName() { + return self::$_tableName; + } + + /** + * Returns if this table needs to be logged + * + * @return bool + */ + public function getLog() { + return self::$_log; + } + + /** + * Returns the list of fields that can be imported + * + * @param bool $prefix + * + * @return array + */ + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'dedupe_rule_group', $prefix, []); + return $r; + } + + /** + * Returns the list of fields that can be exported + * + * @param bool $prefix + * + * @return array + */ + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'dedupe_rule_group', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = []; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; + } + +} diff --git a/CRM/Dedupe/DAO/Exception.php b/CRM/Dedupe/DAO/Exception.php index 32be00eaa0..fae8f0bae4 100644 --- a/CRM/Dedupe/DAO/Exception.php +++ b/CRM/Dedupe/DAO/Exception.php @@ -12,220 +12,4 @@ /** * Database access object for the Exception entity. */ -class CRM_Dedupe_DAO_Exception extends CRM_Core_DAO { - const EXT = 'civicrm'; - const TABLE_ADDED = '3.3'; - - /** - * Static instance to hold the table name. - * - * @var string - */ - public static $_tableName = 'civicrm_dedupe_exception'; - - /** - * Should CiviCRM log any modifications to this table in the civicrm_log table. - * - * @var bool - */ - public static $_log = FALSE; - - /** - * Unique dedupe exception id - * - * @var int - */ - public $id; - - /** - * FK to Contact ID - * - * @var int - */ - public $contact_id1; - - /** - * FK to Contact ID - * - * @var int - */ - public $contact_id2; - - /** - * Class constructor. - */ - public function __construct() { - $this->__table = 'civicrm_dedupe_exception'; - parent::__construct(); - } - - /** - * Returns localized title of this entity. - * - * @param bool $plural - * Whether to return the plural version of the title. - */ - public static function getEntityTitle($plural = FALSE) { - return $plural ? ts('Exceptions') : ts('Exception'); - } - - /** - * Returns foreign keys and entity references. - * - * @return array - * [CRM_Core_Reference_Interface] - */ - public static function getReferenceColumns() { - if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id1', 'civicrm_contact', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id2', 'civicrm_contact', 'id'); - CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); - } - return Civi::$statics[__CLASS__]['links']; - } - - /** - * Returns all the column names of this table - * - * @return array - */ - public static function &fields() { - if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = [ - 'id' => [ - 'name' => 'id', - 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Dedupe Exception ID'), - 'description' => ts('Unique dedupe exception id'), - 'required' => TRUE, - 'where' => 'civicrm_dedupe_exception.id', - 'table_name' => 'civicrm_dedupe_exception', - 'entity' => 'Exception', - 'bao' => 'CRM_Dedupe_BAO_Exception', - 'localizable' => 0, - 'html' => [ - 'type' => 'Number', - ], - 'readonly' => TRUE, - 'add' => '3.3', - ], - 'contact_id1' => [ - 'name' => 'contact_id1', - 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('First Dupe Contact ID'), - 'description' => ts('FK to Contact ID'), - 'required' => TRUE, - 'where' => 'civicrm_dedupe_exception.contact_id1', - 'table_name' => 'civicrm_dedupe_exception', - 'entity' => 'Exception', - 'bao' => 'CRM_Dedupe_BAO_Exception', - 'localizable' => 0, - 'FKClassName' => 'CRM_Contact_DAO_Contact', - 'html' => [ - 'label' => ts("First Dupe Contact"), - ], - 'add' => '3.3', - ], - 'contact_id2' => [ - 'name' => 'contact_id2', - 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Second Dupe Contact ID'), - 'description' => ts('FK to Contact ID'), - 'required' => TRUE, - 'where' => 'civicrm_dedupe_exception.contact_id2', - 'table_name' => 'civicrm_dedupe_exception', - 'entity' => 'Exception', - 'bao' => 'CRM_Dedupe_BAO_Exception', - 'localizable' => 0, - 'FKClassName' => 'CRM_Contact_DAO_Contact', - 'html' => [ - 'label' => ts("Second Dupe Contact"), - ], - 'add' => '3.3', - ], - ]; - CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); - } - return Civi::$statics[__CLASS__]['fields']; - } - - /** - * Return a mapping from field-name to the corresponding key (as used in fields()). - * - * @return array - * Array(string $name => string $uniqueName). - */ - public static function &fieldKeys() { - if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { - Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); - } - return Civi::$statics[__CLASS__]['fieldKeys']; - } - - /** - * Returns the names of this table - * - * @return string - */ - public static function getTableName() { - return self::$_tableName; - } - - /** - * Returns if this table needs to be logged - * - * @return bool - */ - public function getLog() { - return self::$_log; - } - - /** - * Returns the list of fields that can be imported - * - * @param bool $prefix - * - * @return array - */ - public static function &import($prefix = FALSE) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'dedupe_exception', $prefix, []); - return $r; - } - - /** - * Returns the list of fields that can be exported - * - * @param bool $prefix - * - * @return array - */ - public static function &export($prefix = FALSE) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'dedupe_exception', $prefix, []); - return $r; - } - - /** - * Returns the list of indices - * - * @param bool $localize - * - * @return array - */ - public static function indices($localize = TRUE) { - $indices = [ - 'UI_contact_id1_contact_id2' => [ - 'name' => 'UI_contact_id1_contact_id2', - 'field' => [ - 0 => 'contact_id1', - 1 => 'contact_id2', - ], - 'localizable' => FALSE, - 'unique' => TRUE, - 'sig' => 'civicrm_dedupe_exception::1::contact_id1::contact_id2', - ], - ]; - return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; - } - -} +class CRM_Dedupe_DAO_Exception extends CRM_Dedupe_DAO_DedupeException {} diff --git a/CRM/Dedupe/DAO/Rule.php b/CRM/Dedupe/DAO/Rule.php index 03780fcbba..b1d4686fb8 100644 --- a/CRM/Dedupe/DAO/Rule.php +++ b/CRM/Dedupe/DAO/Rule.php @@ -4,281 +4,9 @@ * @package CRM * @copyright CiviCRM LLC https://civicrm.org/licensing * - * Generated from xml/schema/CRM/Dedupe/Rule.xml - * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:2860c5a16ead4f23809027d8a2812265) */ /** - * Database access object for the Rule entity. + * Database access object for the DedupeRule entity. */ -class CRM_Dedupe_DAO_Rule extends CRM_Core_DAO { - const EXT = 'civicrm'; - const TABLE_ADDED = '1.8'; - - /** - * Static instance to hold the table name. - * - * @var string - */ - public static $_tableName = 'civicrm_dedupe_rule'; - - /** - * Should CiviCRM log any modifications to this table in the civicrm_log table. - * - * @var bool - */ - public static $_log = FALSE; - - /** - * Unique dedupe rule id - * - * @var int - */ - public $id; - - /** - * The id of the rule group this rule belongs to - * - * @var int - */ - public $dedupe_rule_group_id; - - /** - * The name of the table this rule is about - * - * @var string - */ - public $rule_table; - - /** - * The name of the field of the table referenced in rule_table - * - * @var string - */ - public $rule_field; - - /** - * The length of the matching substring - * - * @var int - */ - public $rule_length; - - /** - * The weight of the rule - * - * @var int - */ - public $rule_weight; - - /** - * Class constructor. - */ - public function __construct() { - $this->__table = 'civicrm_dedupe_rule'; - parent::__construct(); - } - - /** - * Returns localized title of this entity. - * - * @param bool $plural - * Whether to return the plural version of the title. - */ - public static function getEntityTitle($plural = FALSE) { - return $plural ? ts('Rules') : ts('Rule'); - } - - /** - * Returns foreign keys and entity references. - * - * @return array - * [CRM_Core_Reference_Interface] - */ - public static function getReferenceColumns() { - if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'dedupe_rule_group_id', 'civicrm_dedupe_rule_group', 'id'); - CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); - } - return Civi::$statics[__CLASS__]['links']; - } - - /** - * Returns all the column names of this table - * - * @return array - */ - public static function &fields() { - if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = [ - 'id' => [ - 'name' => 'id', - 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Dedupe Rule ID'), - 'description' => ts('Unique dedupe rule id'), - 'required' => TRUE, - 'where' => 'civicrm_dedupe_rule.id', - 'table_name' => 'civicrm_dedupe_rule', - 'entity' => 'Rule', - 'bao' => 'CRM_Dedupe_BAO_Rule', - 'localizable' => 0, - 'html' => [ - 'type' => 'Number', - ], - 'readonly' => TRUE, - 'add' => '1.8', - ], - 'dedupe_rule_group_id' => [ - 'name' => 'dedupe_rule_group_id', - 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Group ID'), - 'description' => ts('The id of the rule group this rule belongs to'), - 'required' => TRUE, - 'where' => 'civicrm_dedupe_rule.dedupe_rule_group_id', - 'table_name' => 'civicrm_dedupe_rule', - 'entity' => 'Rule', - 'bao' => 'CRM_Dedupe_BAO_Rule', - 'localizable' => 0, - 'FKClassName' => 'CRM_Dedupe_DAO_RuleGroup', - 'html' => [ - 'label' => ts("Group"), - ], - 'add' => '1.8', - ], - 'rule_table' => [ - 'name' => 'rule_table', - 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Rule Table'), - 'description' => ts('The name of the table this rule is about'), - 'required' => TRUE, - 'maxlength' => 64, - 'size' => CRM_Utils_Type::BIG, - 'where' => 'civicrm_dedupe_rule.rule_table', - 'table_name' => 'civicrm_dedupe_rule', - 'entity' => 'Rule', - 'bao' => 'CRM_Dedupe_BAO_Rule', - 'localizable' => 0, - 'add' => '1.8', - ], - 'rule_field' => [ - 'name' => 'rule_field', - 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Rule Field'), - 'description' => ts('The name of the field of the table referenced in rule_table'), - 'required' => TRUE, - 'maxlength' => 64, - 'size' => CRM_Utils_Type::BIG, - 'where' => 'civicrm_dedupe_rule.rule_field', - 'table_name' => 'civicrm_dedupe_rule', - 'entity' => 'Rule', - 'bao' => 'CRM_Dedupe_BAO_Rule', - 'localizable' => 0, - 'add' => '1.8', - ], - 'rule_length' => [ - 'name' => 'rule_length', - 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Rule Length'), - 'description' => ts('The length of the matching substring'), - 'where' => 'civicrm_dedupe_rule.rule_length', - 'table_name' => 'civicrm_dedupe_rule', - 'entity' => 'Rule', - 'bao' => 'CRM_Dedupe_BAO_Rule', - 'localizable' => 0, - 'html' => [ - 'type' => 'Text', - ], - 'add' => '1.8', - ], - 'rule_weight' => [ - 'name' => 'rule_weight', - 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Order'), - 'description' => ts('The weight of the rule'), - 'required' => TRUE, - 'where' => 'civicrm_dedupe_rule.rule_weight', - 'table_name' => 'civicrm_dedupe_rule', - 'entity' => 'Rule', - 'bao' => 'CRM_Dedupe_BAO_Rule', - 'localizable' => 0, - 'html' => [ - 'type' => 'Text', - ], - 'add' => '1.8', - ], - ]; - CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); - } - return Civi::$statics[__CLASS__]['fields']; - } - - /** - * Return a mapping from field-name to the corresponding key (as used in fields()). - * - * @return array - * Array(string $name => string $uniqueName). - */ - public static function &fieldKeys() { - if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { - Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); - } - return Civi::$statics[__CLASS__]['fieldKeys']; - } - - /** - * Returns the names of this table - * - * @return string - */ - public static function getTableName() { - return self::$_tableName; - } - - /** - * Returns if this table needs to be logged - * - * @return bool - */ - public function getLog() { - return self::$_log; - } - - /** - * Returns the list of fields that can be imported - * - * @param bool $prefix - * - * @return array - */ - public static function &import($prefix = FALSE) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'dedupe_rule', $prefix, []); - return $r; - } - - /** - * Returns the list of fields that can be exported - * - * @param bool $prefix - * - * @return array - */ - public static function &export($prefix = FALSE) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'dedupe_rule', $prefix, []); - return $r; - } - - /** - * Returns the list of indices - * - * @param bool $localize - * - * @return array - */ - public static function indices($localize = TRUE) { - $indices = []; - return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; - } - -} +class CRM_Dedupe_DAO_Rule extends CRM_Dedupe_DAO_DedupeRule {} diff --git a/CRM/Dedupe/DAO/RuleGroup.php b/CRM/Dedupe/DAO/RuleGroup.php index 901c580612..b471cf40ef 100644 --- a/CRM/Dedupe/DAO/RuleGroup.php +++ b/CRM/Dedupe/DAO/RuleGroup.php @@ -4,301 +4,9 @@ * @package CRM * @copyright CiviCRM LLC https://civicrm.org/licensing * - * Generated from xml/schema/CRM/Dedupe/RuleGroup.xml - * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:114051c4c9bd7fd1e7854de7fc110884) */ /** - * Database access object for the RuleGroup entity. + * Database access object for the DedupeRuleGroup entity. */ -class CRM_Dedupe_DAO_RuleGroup extends CRM_Core_DAO { - const EXT = 'civicrm'; - const TABLE_ADDED = '1.8'; - - /** - * Static instance to hold the table name. - * - * @var string - */ - public static $_tableName = 'civicrm_dedupe_rule_group'; - - /** - * Should CiviCRM log any modifications to this table in the civicrm_log table. - * - * @var bool - */ - public static $_log = FALSE; - - /** - * Unique dedupe rule group id - * - * @var int - */ - public $id; - - /** - * The type of contacts this group applies to - * - * @var string - */ - public $contact_type; - - /** - * The weight threshold the sum of the rule weights has to cross to consider two contacts the same - * - * @var int - */ - public $threshold; - - /** - * Whether the rule should be used for cases where usage is Unsupervised, Supervised OR General(programatically) - * - * @var string - */ - public $used; - - /** - * Name of the rule group - * - * @var string - */ - public $name; - - /** - * Label of the rule group - * - * @var string - */ - public $title; - - /** - * Is this a reserved rule - a rule group that has been optimized and cannot be changed by the admin - * - * @var bool - */ - public $is_reserved; - - /** - * Class constructor. - */ - public function __construct() { - $this->__table = 'civicrm_dedupe_rule_group'; - parent::__construct(); - } - - /** - * Returns localized title of this entity. - * - * @param bool $plural - * Whether to return the plural version of the title. - */ - public static function getEntityTitle($plural = FALSE) { - return $plural ? ts('Rule Groups') : ts('Rule Group'); - } - - /** - * Returns all the column names of this table - * - * @return array - */ - public static function &fields() { - if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = [ - 'id' => [ - 'name' => 'id', - 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Rule Group ID'), - 'description' => ts('Unique dedupe rule group id'), - 'required' => TRUE, - 'where' => 'civicrm_dedupe_rule_group.id', - 'table_name' => 'civicrm_dedupe_rule_group', - 'entity' => 'RuleGroup', - 'bao' => 'CRM_Dedupe_BAO_RuleGroup', - 'localizable' => 0, - 'html' => [ - 'type' => 'Number', - ], - 'readonly' => TRUE, - 'add' => '1.8', - ], - 'contact_type' => [ - 'name' => 'contact_type', - 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Contact Type'), - 'description' => ts('The type of contacts this group applies to'), - 'maxlength' => 12, - 'size' => CRM_Utils_Type::TWELVE, - 'where' => 'civicrm_dedupe_rule_group.contact_type', - 'table_name' => 'civicrm_dedupe_rule_group', - 'entity' => 'RuleGroup', - 'bao' => 'CRM_Dedupe_BAO_RuleGroup', - 'localizable' => 0, - 'html' => [ - 'type' => 'Select', - ], - 'pseudoconstant' => [ - 'table' => 'civicrm_contact_type', - 'keyColumn' => 'name', - 'labelColumn' => 'label', - 'condition' => 'parent_id IS NULL', - ], - 'add' => '1.8', - ], - 'threshold' => [ - 'name' => 'threshold', - 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Threshold'), - 'description' => ts('The weight threshold the sum of the rule weights has to cross to consider two contacts the same'), - 'required' => TRUE, - 'where' => 'civicrm_dedupe_rule_group.threshold', - 'table_name' => 'civicrm_dedupe_rule_group', - 'entity' => 'RuleGroup', - 'bao' => 'CRM_Dedupe_BAO_RuleGroup', - 'localizable' => 0, - 'html' => [ - 'type' => 'Text', - ], - 'add' => '1.8', - ], - 'used' => [ - 'name' => 'used', - 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Length'), - 'description' => ts('Whether the rule should be used for cases where usage is Unsupervised, Supervised OR General(programatically)'), - 'required' => TRUE, - 'maxlength' => 12, - 'size' => CRM_Utils_Type::TWELVE, - 'where' => 'civicrm_dedupe_rule_group.used', - 'table_name' => 'civicrm_dedupe_rule_group', - 'entity' => 'RuleGroup', - 'bao' => 'CRM_Dedupe_BAO_RuleGroup', - 'localizable' => 0, - 'html' => [ - 'type' => 'Radio', - ], - 'pseudoconstant' => [ - 'callback' => 'CRM_Core_SelectValues::getDedupeRuleTypes', - ], - 'add' => '4.3', - ], - 'name' => [ - 'name' => 'name', - 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Name'), - 'description' => ts('Name of the rule group'), - 'maxlength' => 64, - 'size' => CRM_Utils_Type::BIG, - 'where' => 'civicrm_dedupe_rule_group.name', - 'table_name' => 'civicrm_dedupe_rule_group', - 'entity' => 'RuleGroup', - 'bao' => 'CRM_Dedupe_BAO_RuleGroup', - 'localizable' => 0, - 'add' => '2.1', - ], - 'title' => [ - 'name' => 'title', - 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Title'), - 'description' => ts('Label of the rule group'), - 'maxlength' => 255, - 'size' => CRM_Utils_Type::HUGE, - 'where' => 'civicrm_dedupe_rule_group.title', - 'table_name' => 'civicrm_dedupe_rule_group', - 'entity' => 'RuleGroup', - 'bao' => 'CRM_Dedupe_BAO_RuleGroup', - 'localizable' => 0, - 'html' => [ - 'type' => 'Text', - ], - 'add' => '4.1', - ], - 'is_reserved' => [ - 'name' => 'is_reserved', - 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Reserved?'), - 'description' => ts('Is this a reserved rule - a rule group that has been optimized and cannot be changed by the admin'), - 'where' => 'civicrm_dedupe_rule_group.is_reserved', - 'table_name' => 'civicrm_dedupe_rule_group', - 'entity' => 'RuleGroup', - 'bao' => 'CRM_Dedupe_BAO_RuleGroup', - 'localizable' => 0, - 'html' => [ - 'type' => 'CheckBox', - ], - 'add' => '4.1', - ], - ]; - CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); - } - return Civi::$statics[__CLASS__]['fields']; - } - - /** - * Return a mapping from field-name to the corresponding key (as used in fields()). - * - * @return array - * Array(string $name => string $uniqueName). - */ - public static function &fieldKeys() { - if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { - Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); - } - return Civi::$statics[__CLASS__]['fieldKeys']; - } - - /** - * Returns the names of this table - * - * @return string - */ - public static function getTableName() { - return self::$_tableName; - } - - /** - * Returns if this table needs to be logged - * - * @return bool - */ - public function getLog() { - return self::$_log; - } - - /** - * Returns the list of fields that can be imported - * - * @param bool $prefix - * - * @return array - */ - public static function &import($prefix = FALSE) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'dedupe_rule_group', $prefix, []); - return $r; - } - - /** - * Returns the list of fields that can be exported - * - * @param bool $prefix - * - * @return array - */ - public static function &export($prefix = FALSE) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'dedupe_rule_group', $prefix, []); - return $r; - } - - /** - * Returns the list of indices - * - * @param bool $localize - * - * @return array - */ - public static function indices($localize = TRUE) { - $indices = []; - return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; - } - -} +class CRM_Dedupe_DAO_RuleGroup extends CRM_Dedupe_DAO_DedupeRuleGroup {} diff --git a/CRM/Event/DAO/Event.php b/CRM/Event/DAO/Event.php index 7b6f30f952..5403c25a2b 100644 --- a/CRM/Event/DAO/Event.php +++ b/CRM/Event/DAO/Event.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Event/Event.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:c5779073cea9c6b4a959070ec0e95b6e) + * (GenCodeChecksum:e0bee3952ac39a53952b6a7979a79e13) */ /** @@ -1736,7 +1736,7 @@ class CRM_Event_DAO_Event extends CRM_Core_DAO { 'entity' => 'Event', 'bao' => 'CRM_Event_BAO_Event', 'localizable' => 0, - 'FKClassName' => 'CRM_Dedupe_DAO_RuleGroup', + 'FKClassName' => 'CRM_Dedupe_DAO_DedupeRuleGroup', 'html' => [ 'type' => 'Select', 'label' => ts("Dedupe Rule"), diff --git a/Civi/Api4/DedupeRule.php b/Civi/Api4/DedupeRule.php new file mode 100644 index 0000000000..8ef4cc3373 --- /dev/null +++ b/Civi/Api4/DedupeRule.php @@ -0,0 +1,32 @@ + CRM/Dedupe - Exception + DedupeExceptioncivicrm_dedupe_exceptionDedupe exceptions3.3 diff --git a/xml/schema/Dedupe/Rule.xml b/xml/schema/Dedupe/DedupeRule.xml similarity index 98% rename from xml/schema/Dedupe/Rule.xml rename to xml/schema/Dedupe/DedupeRule.xml index 0b1649796a..396714df40 100644 --- a/xml/schema/Dedupe/Rule.xml +++ b/xml/schema/Dedupe/DedupeRule.xml @@ -1,7 +1,7 @@
CRM/Dedupe - Rule + DedupeRulecivicrm_dedupe_ruleDedupe rules for use by rule groups1.8 diff --git a/xml/schema/Dedupe/RuleGroup.xml b/xml/schema/Dedupe/DedupeRuleGroup.xml similarity index 98% rename from xml/schema/Dedupe/RuleGroup.xml rename to xml/schema/Dedupe/DedupeRuleGroup.xml index 26fe7e63f8..1a8722da33 100644 --- a/xml/schema/Dedupe/RuleGroup.xml +++ b/xml/schema/Dedupe/DedupeRuleGroup.xml @@ -1,7 +1,7 @@
CRM/Dedupe - RuleGroup + DedupeRuleGroupcivicrm_dedupe_rule_groupDedupe rule groups1.8 diff --git a/xml/schema/Dedupe/files.xml b/xml/schema/Dedupe/files.xml index 7321e4ce9a..1e0dd1de39 100644 --- a/xml/schema/Dedupe/files.xml +++ b/xml/schema/Dedupe/files.xml @@ -2,7 +2,7 @@ - - - + + + -- 2.25.1