Determine name of DAO when needed rather than passing a variable around
[civicrm-core.git] / CRM / Dedupe / MergeHandler.php
CommitLineData
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 */
20class 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}