Commit | Line | Data |
---|---|---|
9287a0b7 | 1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
f452d72c | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
9287a0b7 | 5 | | | |
f452d72c CW |
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 | | |
9287a0b7 | 9 | +--------------------------------------------------------------------+ |
10 | */ | |
11 | ||
12 | /** | |
9287a0b7 | 13 | * This class exists primarily for the purposes of supporting code clean up in the Merger class. |
14 | * | |
15 | * It is expected to be fast-moving and calling it outside the refactoring work is not advised. | |
f452d72c CW |
16 | * |
17 | * @package CRM | |
18 | * @copyright CiviCRM LLC https://civicrm.org/licensing | |
9287a0b7 | 19 | */ |
20 | class CRM_Dedupe_MergeHandler { | |
21 | ||
22 | /** | |
23 | * ID of contact to be kept. | |
24 | * | |
25 | * @var int | |
26 | */ | |
27 | protected $toKeepID; | |
28 | ||
29 | /** | |
30 | * ID of contact to be merged and deleted. | |
31 | * | |
32 | * @var int | |
33 | */ | |
34 | protected $toRemoveID; | |
35 | ||
4c54e0bd | 36 | /** |
37 | * Migration info array. | |
38 | * | |
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. | |
41 | * | |
42 | * @var array | |
43 | */ | |
44 | protected $migrationInfo = []; | |
45 | ||
46 | /** | |
47 | * @return array | |
48 | */ | |
49 | public function getMigrationInfo(): array { | |
50 | return $this->migrationInfo; | |
51 | } | |
52 | ||
53 | /** | |
54 | * @param array $migrationInfo | |
55 | */ | |
56 | public function setMigrationInfo(array $migrationInfo) { | |
57 | $this->migrationInfo = $migrationInfo; | |
58 | } | |
59 | ||
9287a0b7 | 60 | /** |
61 | * @return mixed | |
62 | */ | |
63 | public function getToKeepID() { | |
64 | return $this->toKeepID; | |
65 | } | |
66 | ||
67 | /** | |
68 | * @param mixed $toKeepID | |
69 | */ | |
70 | public function setToKeepID($toKeepID) { | |
71 | $this->toKeepID = $toKeepID; | |
72 | } | |
73 | ||
74 | /** | |
75 | * @return mixed | |
76 | */ | |
77 | public function getToRemoveID() { | |
78 | return $this->toRemoveID; | |
79 | } | |
80 | ||
81 | /** | |
82 | * @param mixed $toRemoveID | |
83 | */ | |
84 | public function setToRemoveID($toRemoveID) { | |
85 | $this->toRemoveID = $toRemoveID; | |
86 | } | |
87 | ||
88 | /** | |
89 | * CRM_Dedupe_MergeHandler constructor. | |
90 | * | |
91 | * @param int $toKeepID | |
92 | * ID of contact to be kept. | |
93 | * @param int $toRemoveID | |
94 | * ID of contact to be removed. | |
95 | */ | |
96 | public function __construct(int $toKeepID, int $toRemoveID) { | |
97 | $this->setToKeepID($toKeepID); | |
98 | $this->setToRemoveID($toRemoveID); | |
99 | } | |
100 | ||
101 | /** | |
102 | * Get an array of tables that relate to the contact entity and will need consideration in a merge. | |
103 | * | |
104 | * The list of potential tables is filtered by tables which have data for the relevant contacts. | |
105 | */ | |
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)) | |
113 | ) { | |
114 | unset($relTables[$name]); | |
115 | } | |
116 | } | |
117 | return $relTables; | |
118 | } | |
119 | ||
eca28463 | 120 | /** |
121 | * Get an array of tables that have a dynamic reference to the contact table. | |
122 | * | |
123 | * This would be the case when the table uses entity_table + entity_id rather than an FK. | |
124 | * | |
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. | |
127 | * | |
128 | * @return array | |
129 | */ | |
130 | public function getTablesDynamicallyRelatedToContactTable() { | |
131 | if (!isset(\Civi::$statics[__CLASS__]['dynamic'])) { | |
132 | \Civi::$statics[__CLASS__]['dynamic'] = []; | |
9811efd4 | 133 | foreach (CRM_Core_DAO::getDynamicReferencesToTable('civicrm_contact') as $tableName => $fields) { |
d035857f | 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. | |
139 | continue; | |
140 | } | |
9811efd4 JG |
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)"; | |
143 | } | |
eca28463 | 144 | } |
145 | $sqlString = implode(' UNION ', $sql); | |
146 | if ($sqlString) { | |
147 | $result = CRM_Core_DAO::executeQuery($sqlString); | |
148 | while ($result->fetch()) { | |
149 | \Civi::$statics[__CLASS__]['dynamic'][$result->civicrm_table] = $result->field_name; | |
150 | } | |
151 | } | |
152 | } | |
153 | return \Civi::$statics[__CLASS__]['dynamic']; | |
154 | } | |
155 | ||
4c54e0bd | 156 | /** |
157 | * Get the location blocks designated to be moved during the merge. | |
158 | * | |
159 | * Note this is a refactoring step and future refactors should develop a more coherent array | |
160 | * | |
161 | * @return 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. | |
165 | */ | |
166 | public function getLocationBlocksToMerge(): array { | |
167 | $locBlocks = []; | |
168 | foreach ($this->getMigrationInfo() as $key => $value) { | |
169 | $isLocationField = (substr($key, 0, 14) === 'move_location_' and $value != NULL); | |
170 | if (!$isLocationField) { | |
171 | continue; | |
172 | } | |
173 | $locField = explode('_', $key); | |
174 | $fieldName = $locField[2]; | |
175 | $fieldCount = $locField[3]; | |
176 | ||
177 | // Set up the operation type (add/overwrite) | |
178 | // Ignore operation for websites | |
179 | // @todo Tidy this up | |
180 | $operation = 0; | |
181 | if ($fieldName !== 'website') { | |
182 | $operation = $this->getMigrationInfo()['location_blocks'][$fieldName][$fieldCount]['operation'] ?? NULL; | |
183 | } | |
184 | // default operation is overwrite. | |
185 | if (!$operation) { | |
186 | $operation = 2; | |
187 | } | |
188 | $locBlocks[$fieldName][$fieldCount]['is_replace'] = $operation === 2; | |
189 | } | |
190 | return $locBlocks; | |
191 | } | |
192 | ||
1e809f0e | 193 | /** |
194 | * Copy the data to be moved to a new DAO object. | |
195 | * | |
196 | * This is intended as a refactoring step - not the long term function. Do not | |
197 | * call from any function other than the one it is taken from (Merger::mergeLocations). | |
198 | * | |
1e809f0e | 199 | * @param int $otherBlockId |
200 | * @param string $name | |
201 | * @param int $blkCount | |
202 | * | |
99d57de1 | 203 | * @return CRM_Core_DAO_Address|CRM_Core_DAO_Email|CRM_Core_DAO_IM|CRM_Core_DAO_Phone|CRM_Core_DAO_Website |
204 | * | |
205 | * @throws \CRM_Core_Exception | |
1e809f0e | 206 | */ |
99d57de1 | 207 | public function copyDataToNewBlockDAO($otherBlockId, $name, $blkCount) { |
1e809f0e | 208 | $locationBlocks = CRM_Dedupe_Merger::getLocationBlockInfo(); |
209 | $migrationInfo = $this->getMigrationInfo(); | |
210 | // For the block which belongs to other-contact, link the location block to main-contact | |
99d57de1 | 211 | $otherBlockDAO = $this->getDAOForLocationEntity($name); |
1e809f0e | 212 | $otherBlockDAO->contact_id = $this->getToKeepID(); |
213 | ||
214 | // Get the ID of this block on the 'other' contact, otherwise skip | |
215 | $otherBlockDAO->id = $otherBlockId; | |
216 | ||
217 | // Add/update location and type information from the form, if applicable | |
218 | if ($locationBlocks[$name]['hasLocation']) { | |
219 | $locTypeId = $migrationInfo['location_blocks'][$name][$blkCount]['locTypeId'] ?? NULL; | |
220 | $otherBlockDAO->location_type_id = $locTypeId; | |
221 | } | |
222 | if ($locationBlocks[$name]['hasType']) { | |
223 | $typeTypeId = $migrationInfo['location_blocks'][$name][$blkCount]['typeTypeId'] ?? NULL; | |
224 | $otherBlockDAO->{$locationBlocks[$name]['hasType']} = $typeTypeId; | |
225 | } | |
226 | return $otherBlockDAO; | |
227 | } | |
228 | ||
99d57de1 | 229 | /** |
230 | * Get the DAO object appropriate to the location entity. | |
231 | * | |
232 | * @param string $entity | |
233 | * | |
234 | * @return CRM_Core_DAO_Address|CRM_Core_DAO_Email|CRM_Core_DAO_IM|CRM_Core_DAO_Phone|CRM_Core_DAO_Website | |
235 | * @throws \CRM_Core_Exception | |
236 | */ | |
237 | public function getDAOForLocationEntity($entity) { | |
238 | switch ($entity) { | |
239 | case 'email': | |
240 | return new CRM_Core_DAO_Email(); | |
241 | ||
242 | case 'address': | |
243 | return new CRM_Core_DAO_Address(); | |
244 | ||
245 | case 'phone': | |
246 | return new CRM_Core_DAO_Phone(); | |
247 | ||
248 | case 'website': | |
249 | return new CRM_Core_DAO_Website(); | |
250 | ||
251 | case 'im': | |
252 | return new CRM_Core_DAO_IM(); | |
253 | ||
254 | default: | |
255 | // Mostly here, along with the switch over a more concise format, to help IDEs understand the possibilities. | |
256 | throw new CRM_Core_Exception('Unsupported entity'); | |
257 | } | |
258 | } | |
259 | ||
9287a0b7 | 260 | } |