3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
13 * This class exists primarily for the purposes of supporting code clean up in the Merger class.
15 * It is expected to be fast-moving and calling it outside the refactoring work is not advised.
18 * @copyright CiviCRM LLC https://civicrm.org/licensing
20 class CRM_Dedupe_MergeHandler
{
23 * ID of contact to be kept.
30 * ID of contact to be merged and deleted.
34 protected $toRemoveID;
37 * Migration info array.
39 * This is a nasty bunch of data used in mysterious ways. We want to work to make it more
40 * sensible but for now we store it.
44 protected $migrationInfo = [];
49 public function getMigrationInfo(): array {
50 return $this->migrationInfo
;
54 * @param array $migrationInfo
56 public function setMigrationInfo(array $migrationInfo) {
57 $this->migrationInfo
= $migrationInfo;
63 public function getToKeepID() {
64 return $this->toKeepID
;
68 * @param mixed $toKeepID
70 public function setToKeepID($toKeepID) {
71 $this->toKeepID
= $toKeepID;
77 public function getToRemoveID() {
78 return $this->toRemoveID
;
82 * @param mixed $toRemoveID
84 public function setToRemoveID($toRemoveID) {
85 $this->toRemoveID
= $toRemoveID;
89 * CRM_Dedupe_MergeHandler constructor.
91 * @param int $toKeepID
92 * ID of contact to be kept.
93 * @param int $toRemoveID
94 * ID of contact to be removed.
96 public function __construct(int $toKeepID, int $toRemoveID) {
97 $this->setToKeepID($toKeepID);
98 $this->setToRemoveID($toRemoveID);
102 * Get an array of tables that relate to the contact entity and will need consideration in a merge.
104 * The list of potential tables is filtered by tables which have data for the relevant contacts.
106 public function getTablesRelatedToTheMergePair() {
107 $relTables = CRM_Dedupe_Merger
::relTables();
108 $activeRelTables = CRM_Dedupe_Merger
::getActiveRelTables($this->toRemoveID
);
109 $activeMainRelTables = CRM_Dedupe_Merger
::getActiveRelTables($this->toKeepID
);
110 foreach ($relTables as $name => $null) {
111 if (!in_array($name, $activeRelTables, TRUE) &&
112 !(($name === 'rel_table_users') && in_array($name, $activeMainRelTables, TRUE))
114 unset($relTables[$name]);
121 * Get an array of tables that have a dynamic reference to the contact table.
123 * This would be the case when the table uses entity_table + entity_id rather than an FK.
125 * There are a number of tables that 'could' but don't have contact related data so we
126 * do a cached check for this, ensuring the query is only done once per batch run.
130 public function getTablesDynamicallyRelatedToContactTable() {
131 if (!isset(\Civi
::$statics[__CLASS__
]['dynamic'])) {
132 \Civi
::$statics[__CLASS__
]['dynamic'] = [];
133 foreach (CRM_Core_DAO
::getDynamicReferencesToTable('civicrm_contact') as $tableName => $fields) {
134 if ($tableName === 'civicrm_financial_item') {
135 // It turns out that civicrm_financial_item does not have an index on entity_table (only as the latter
136 // part of a entity_id/entity_table index which probably is not adding any value over & above entity_id
137 // only. This means this is a slow query. The correct fix is probably to add a whitelist to
138 // values for entity_table in the schema.
141 foreach ($fields as $field) {
142 $sql[] = "(SELECT '$tableName' as civicrm_table, '{$field[0]}' as field_name FROM $tableName WHERE {$field[1]} = 'civicrm_contact' LIMIT 1)";
145 $sqlString = implode(' UNION ', $sql);
147 $result = CRM_Core_DAO
::executeQuery($sqlString);
148 while ($result->fetch()) {
149 \Civi
::$statics[__CLASS__
]['dynamic'][$result->civicrm_table
] = $result->field_name
;
153 return \Civi
::$statics[__CLASS__
]['dynamic'];
157 * Get the location blocks designated to be moved during the merge.
159 * Note this is a refactoring step and future refactors should develop a more coherent array
162 * The format is ['address' => [0 => ['is_replace' => TRUE]], 'email' => [0...],[1....]
163 * where the entity is address, the internal indexing for the addresses on both contact is 1
164 * and the intent to replace the existing address is TRUE.
166 public function getLocationBlocksToMerge(): array {
168 foreach ($this->getMigrationInfo() as $key => $value) {
169 $isLocationField = (substr($key, 0, 14) === 'move_location_' and $value != NULL);
170 if (!$isLocationField) {
173 $locField = explode('_', $key);
174 $fieldName = $locField[2];
175 $fieldCount = $locField[3];
177 // Set up the operation type (add/overwrite)
178 // Ignore operation for websites
179 // @todo Tidy this up
181 if ($fieldName !== 'website') {
182 $operation = $this->getMigrationInfo()['location_blocks'][$fieldName][$fieldCount]['operation'] ??
NULL;
184 // default operation is overwrite.
188 $locBlocks[$fieldName][$fieldCount]['is_replace'] = $operation === 2;