Merge pull request #15854 from eileenmcnaughton/test_fix
[civicrm-core.git] / CRM / Contact / Form / Merge.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
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 |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17
86538308 18/**
5a409b50 19 * Class CRM_Contact_Form_Merge.
86538308 20 */
6a488035 21class CRM_Contact_Form_Merge extends CRM_Core_Form {
5af77f03 22 // The id of the contact that there's a duplicate for; this one will
69078420
SL
23 /**
24 * possibly inherit some of $_oid's properties and remain in the system.
25 * @var int
26 */
27 public $_cid = NULL;
6a488035 28
69078420
SL
29 /**
30 * The id of the other contact - the duplicate one that will get deleted.
31 * @var int
32 */
33 public $_oid = NULL;
6a488035 34
69078420 35 public $_contactType = NULL;
6a488035 36
9f54f049 37 /**
38 * @var array
39 */
be2fb01f 40 public $criteria = [];
9f54f049 41
dc6285d5 42 /**
43 * Query limit to be retained in the urls.
44 *
45 * @var int
46 */
69078420 47 public $limit;
dc6285d5 48
5af77f03 49 /**
50 * String for quickform bug handling.
51 *
52 * FIXME: QuickForm can't create advcheckboxes with value set to 0 or '0' :(
53 * see HTML_QuickForm_advcheckbox::setValues() - but patching that doesn't
54 * help, as QF doesn't put the 0-value elements in exportValues() anyway...
55 * to side-step this, we use the below UUID as a (re)placeholder
56 *
57 * @var string
58 */
69078420 59 public $_qfZeroBug = 'e8cddb72-a257-11dc-b9cc-0016d3330ee9';
8ef12e64 60
00be9182 61 public function preProcess() {
fcc3d8ee 62 try {
6a488035 63
fcc3d8ee 64 $this->_cid = CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE);
65 $this->_oid = CRM_Utils_Request::retrieve('oid', 'Positive', $this, TRUE);
66 $flip = CRM_Utils_Request::retrieve('flip', 'Positive', $this, FALSE);
6a488035 67
fcc3d8ee 68 $this->_rgid = CRM_Utils_Request::retrieve('rgid', 'Positive', $this, FALSE);
69 $this->_gid = $gid = CRM_Utils_Request::retrieve('gid', 'Positive', $this, FALSE);
70 $this->_mergeId = CRM_Utils_Request::retrieve('mergeId', 'Positive', $this, FALSE);
71 $this->limit = CRM_Utils_Request::retrieve('limit', 'Positive', $this, FALSE);
9f54f049 72 $this->criteria = CRM_Utils_Request::retrieve('criteria', 'Json', $this, FALSE, '{}');
c0cc2ad4 73
9f54f049 74 $urlParams = ['reset' => 1, 'rgid' => $this->_rgid, 'gid' => $this->_gid, 'limit' => $this->limit, 'criteria' => $this->criteria];
6d5a3f21 75
fcc3d8ee 76 $this->bounceIfInvalid($this->_cid, $this->_oid);
186e5c44 77
c8e6c437 78 $contacts = civicrm_api3('Contact', 'get', [
79 'id' => ['IN' => [$this->_cid, $this->_oid]],
58edb71b 80 'return' => ['contact_type', 'modified_date', 'created_date', 'contact_sub_type'],
c8e6c437 81 ])['values'];
82
83 $this->_contactType = $contacts[$this->_cid]['contact_type'];
6a488035 84
c0cc2ad4 85 $browseUrl = CRM_Utils_System::url('civicrm/contact/dedupefind', array_merge($urlParams, ['action' => 'browse']));
2ae26001 86
fcc3d8ee 87 if (!$this->_rgid) {
88 // Unset browse URL as we have come from the search screen.
89 $browseUrl = '';
be2fb01f 90 $this->_rgid = civicrm_api3('RuleGroup', 'getvalue', [
fcc3d8ee 91 'contact_type' => $this->_contactType,
92 'used' => 'Supervised',
93 'return' => 'id',
be2fb01f 94 ]);
fcc3d8ee 95 }
96 $this->assign('browseUrl', $browseUrl);
97 if ($browseUrl) {
98 CRM_Core_Session::singleton()->pushUserContext($browseUrl);
99 }
6a488035 100
997a03fe 101 $cacheKey = CRM_Dedupe_Merger::getMergeCacheKeyString($this->_rgid, $gid, json_decode($this->criteria, TRUE), TRUE, $this->limit);
6a488035 102
fcc3d8ee 103 $join = CRM_Dedupe_Merger::getJoinOnDedupeTable();
104 $where = "de.id IS NULL";
6a488035 105
fcc3d8ee 106 $pos = CRM_Core_BAO_PrevNextCache::getPositions($cacheKey, $this->_cid, $this->_oid, $this->_mergeId, $join, $where, $flip);
6a488035 107
fcc3d8ee 108 // get user info of main contact.
109 $config = CRM_Core_Config::singleton();
0626851e 110 CRM_Core_Config::setPermitCacheFlushMode(FALSE);
6a488035 111
fcc3d8ee 112 $mainUfId = CRM_Core_BAO_UFMatch::getUFId($this->_cid);
113 $mainUser = NULL;
114 if ($mainUfId) {
cff0c9aa 115 $mainUser = $config->userSystem->getUser($this->_cid);
fcc3d8ee 116 $this->assign('mainUfId', $mainUfId);
cff0c9aa 117 $this->assign('mainUfName', $mainUser ? $mainUser['name'] : NULL);
fcc3d8ee 118 }
c0cc2ad4 119 $flipParams = array_merge($urlParams, ['action' => 'update', 'cid' => $this->_oid, 'oid' => $this->_cid]);
fcc3d8ee 120 if (!$flip) {
c0cc2ad4 121 $flipParams['flip'] = '1';
6a488035 122 }
c0cc2ad4 123 $flipUrl = CRM_Utils_System::url('civicrm/contact/merge',
124 $flipParams
125 );
fcc3d8ee 126 $this->assign('flip', $flipUrl);
127
128 $this->prev = $this->next = NULL;
be2fb01f 129 foreach ([
69078420
SL
130 'prev',
131 'next',
132 ] as $position) {
fcc3d8ee 133 if (!empty($pos[$position])) {
134 if ($pos[$position]['id1'] && $pos[$position]['id2']) {
c0cc2ad4 135 $rowParams = array_merge($urlParams, [
136 'action' => 'update',
137 'cid' => $pos[$position]['id1'],
138 'oid' => $pos[$position]['id2'],
139 'mergeId' => $pos[$position]['mergeId'],
140 ]);
141 $this->$position = CRM_Utils_System::url('civicrm/contact/merge', $rowParams);
fcc3d8ee 142 $this->assign($position, $this->$position);
143 }
144 }
6a488035
TO
145 }
146
fcc3d8ee 147 // get user info of other contact.
148 $otherUfId = CRM_Core_BAO_UFMatch::getUFId($this->_oid);
149 $otherUser = NULL;
6a488035 150
fcc3d8ee 151 if ($otherUfId) {
cff0c9aa 152 $otherUser = $config->userSystem->getUser($this->_oid);
fcc3d8ee 153 $this->assign('otherUfId', $otherUfId);
cff0c9aa 154 $this->assign('otherUfName', $otherUser ? $otherUser['name'] : NULL);
fcc3d8ee 155 }
6a488035 156
fcc3d8ee 157 $cmsUser = ($mainUfId && $otherUfId) ? TRUE : FALSE;
158 $this->assign('user', $cmsUser);
159
160 $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($this->_cid, $this->_oid);
161 $main = $this->_mainDetails = $rowsElementsAndInfo['main_details'];
162 $other = $this->_otherDetails = $rowsElementsAndInfo['other_details'];
163
164 $this->assign('contact_type', $main['contact_type']);
165 $this->assign('main_name', $main['display_name']);
166 $this->assign('other_name', $other['display_name']);
167 $this->assign('main_cid', $main['contact_id']);
168 $this->assign('other_cid', $other['contact_id']);
169 $this->assign('rgid', $this->_rgid);
c8e6c437 170 $this->assignSummaryRowsToTemplate($contacts);
fcc3d8ee 171
be2fb01f 172 $this->addElement('checkbox', 'toggleSelect', NULL, NULL, ['class' => 'select-rows']);
fcc3d8ee 173
174 $this->assign('mainLocBlock', json_encode($rowsElementsAndInfo['main_details']['location_blocks']));
175 $this->assign('locationBlockInfo', json_encode(CRM_Dedupe_Merger::getLocationBlockInfo()));
58edb71b 176 $this->assign('mainContactTypeIcon', CRM_Contact_BAO_Contact_Utils::getImage($contacts[$this->_cid]['contact_sub_type'] ? $contacts[$this->_cid]['contact_sub_type'] : $contacts[$this->_cid]['contact_type'],
177 FALSE,
178 $this->_cid
179 ));
180 $this->assign('otherContactTypeIcon', CRM_Contact_BAO_Contact_Utils::getImage($contacts[$this->_oid]['contact_sub_type'] ? $contacts[$this->_oid]['contact_sub_type'] : $contacts[$this->_oid]['contact_type'],
181 FALSE,
182 $this->_oid
183 ));
184
185 if (isset($rowsElementsAndInfo['rows']['move_contact_type'])) {
186 // We don't permit merging contacts of different types so this is just clutter - putting
187 // the icon next to the contact name is consistent with elsewhere and permits hover-info
188 // https://lab.civicrm.org/dev/core/issues/824
189 unset($rowsElementsAndInfo['rows']['move_contact_type']);
190 }
191
fcc3d8ee 192 $this->assign('rows', $rowsElementsAndInfo['rows']);
193
194 // add elements
195 foreach ($rowsElementsAndInfo['elements'] as $element) {
196 // We could push this down to the getRowsElementsAndInfo function but it's
197 // already so overloaded - let's start moving towards doing form-things
198 // on the form.
199 if (substr($element[1], 0, 13) === 'move_location') {
200 $element[4] = array_merge(
be2fb01f
CW
201 (array) CRM_Utils_Array::value(4, $element, []),
202 [
fcc3d8ee 203 'data-location' => substr($element[1], 14),
204 'data-is_location' => TRUE,
be2fb01f 205 ]);
fcc3d8ee 206 }
207 if (substr($element[1], 0, 15) === 'location_blocks') {
208 // @todo We could add some data elements here to make jquery manipulation more straight-forward
209 // @todo consider enabling if it is an add & defaulting to true.
be2fb01f 210 $element[4] = array_merge((array) CRM_Utils_Array::value(4, $element, []), ['disabled' => TRUE]);
fcc3d8ee 211 }
3d1d395a 212 $newCheckBox = $this->addElement($element[0],
fcc3d8ee 213 $element[1],
214 array_key_exists('2', $element) ? $element[2] : NULL,
215 array_key_exists('3', $element) ? $element[3] : NULL,
216 array_key_exists('4', $element) ? $element[4] : NULL,
217 array_key_exists('5', $element) ? $element[5] : NULL
218 );
3d1d395a 219 if (!empty($element['is_checked'])) {
220 $newCheckBox->setChecked(TRUE);
221 }
972e23db 222 }
fcc3d8ee 223
224 // add related table elements
225 foreach ($rowsElementsAndInfo['rel_table_elements'] as $relTableElement) {
226 $element = $this->addElement($relTableElement[0], $relTableElement[1]);
227 $element->setChecked(TRUE);
972e23db 228 }
6a488035 229
fcc3d8ee 230 $this->assign('rel_tables', $rowsElementsAndInfo['rel_tables']);
231 $this->assign('userContextURL', CRM_Core_Session::singleton()
232 ->readUserContext());
233 }
234 catch (CRM_Core_Exception $e) {
1836ab9e 235 CRM_Core_Error::statusBounce($e->getMessage());
6a488035 236 }
6a488035
TO
237 }
238
6ea503d4
TO
239 public function addRules() {
240 }
6a488035
TO
241
242 public function buildQuickForm() {
d45c349e 243 $this->unsavedChangesWarn = FALSE;
be2fb01f
CW
244 CRM_Utils_System::setTitle(ts('Merge %1 contacts', [1 => $this->_contactType]));
245 $buttons = [];
fd66df85 246
be2fb01f 247 $buttons[] = [
fd66df85 248 'type' => 'next',
3709190a 249 'name' => $this->next ? ts('Merge and go to Next Pair') : ts('Merge'),
fd66df85 250 'isDefault' => TRUE,
5db72c32 251 'icon' => $this->next ? 'fa-play-circle' : 'check',
be2fb01f 252 ];
6a488035
TO
253
254 if ($this->next || $this->prev) {
be2fb01f 255 $buttons[] = [
fd66df85 256 'type' => 'submit',
3709190a 257 'name' => ts('Merge and go to Listing'),
be2fb01f
CW
258 ];
259 $buttons[] = [
fd66df85
CW
260 'type' => 'done',
261 'name' => ts('Merge and View Result'),
0291a521 262 'icon' => 'fa-check-circle',
be2fb01f 263 ];
6a488035
TO
264 }
265
be2fb01f 266 $buttons[] = [
fd66df85
CW
267 'type' => 'cancel',
268 'name' => ts('Cancel'),
be2fb01f 269 ];
fd66df85
CW
270
271 $this->addButtons($buttons);
be2fb01f 272 $this->addFormRule(['CRM_Contact_Form_Merge', 'formRule'], $this);
4b87bd02 273 }
274
86538308
EM
275 /**
276 * @param $fields
277 * @param $files
278 * @param $self
279 *
280 * @return array
281 */
00be9182 282 public static function formRule($fields, $files, $self) {
be2fb01f 283 $errors = [];
0653585f 284 $link = CRM_Utils_System::href(ts('Flip between the original and duplicate contacts.'),
b74201e4 285 'civicrm/contact/merge',
286 'reset=1&action=update&cid=' . $self->_oid . '&oid=' . $self->_cid . '&rgid=' . $self->_rgid . '&flip=1'
287 );
4b87bd02 288 if (CRM_Contact_BAO_Contact::checkDomainContact($self->_oid)) {
be2fb01f 289 $errors['_qf_default'] = ts("The Default Organization contact cannot be merged into another contact record. It is associated with the CiviCRM installation for this domain and contains information used for system functions. If you want to merge these records, you can: %1", [1 => $link]);
4b87bd02 290 }
291 return $errors;
6a488035
TO
292 }
293
294 public function postProcess() {
295 $formValues = $this->exportValues();
8ef12e64 296
471ec338
BS
297 $formValues['main_details'] = $this->_mainDetails;
298 $formValues['other_details'] = $this->_otherDetails;
be2fb01f 299 $migrationData = ['migration_info' => $formValues];
b54f692d 300 CRM_Utils_Hook::merge('form', $migrationData, $this->_cid, $this->_oid);
301 CRM_Dedupe_Merger::moveAllBelongings($this->_cid, $this->_oid, $migrationData['migration_info']);
6a488035 302
7b99ead3 303 $name = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_cid, 'display_name');
be2fb01f 304 $message = '<ul><li>' . ts('%1 has been updated.', [1 => $name]) . '</li><li>' . ts('Contact ID %1 has been deleted.', [1 => $this->_oid]) . '</li></ul>';
7b99ead3
CW
305 CRM_Core_Session::setStatus($message, ts('Contacts Merged'), 'success');
306
9f54f049 307 $urlParams = ['reset' => 1, 'cid' => $this->_cid, 'rgid' => $this->_rgid, 'gid' => $this->_gid, 'limit' => $this->limit, 'criteria' => $this->criteria];
c0cc2ad4 308 $contactViewUrl = CRM_Utils_System::url('civicrm/contact/view', ['reset' => 1, 'cid' => $this->_cid]);
dc6285d5 309
a7488080 310 if (!empty($formValues['_qf_Merge_submit'])) {
c0cc2ad4 311 $urlParams['action'] = "update";
312 CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/dedupefind',
dc6285d5 313 $urlParams
c0cc2ad4 314 ));
6a488035 315 }
353ffa53 316 if (!empty($formValues['_qf_Merge_done'])) {
c0cc2ad4 317 CRM_Utils_System::redirect($contactViewUrl);
6a488035
TO
318 }
319
320 if ($this->next && $this->_mergeId) {
997a03fe 321 $cacheKey = CRM_Dedupe_Merger::getMergeCacheKeyString($this->_rgid, $this->_gid, json_decode($this->criteria, TRUE), TRUE, $this->limit);
6a488035 322
2ae26001 323 $join = CRM_Dedupe_Merger::getJoinOnDedupeTable();
6a488035
TO
324 $where = "de.id IS NULL";
325
326 $pos = CRM_Core_BAO_PrevNextCache::getPositions($cacheKey, NULL, NULL, $this->_mergeId, $join, $where);
327
328 if (!empty($pos) &&
329 $pos['next']['id1'] &&
330 $pos['next']['id2']
331 ) {
332
c0cc2ad4 333 $urlParams['cid'] = $pos['next']['id1'];
334 $urlParams['oid'] = $pos['next']['id2'];
335 $urlParams['mergeId'] = $pos['next']['mergeId'];
336 $urlParams['action'] = 'update';
337 CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/merge', $urlParams));
6a488035
TO
338 }
339 }
340
c0cc2ad4 341 // Perhaps never reached.
342 CRM_Utils_System::redirect($contactViewUrl);
6a488035 343 }
96025800 344
fa74cac4 345 /**
346 * Bounce if the merge action is invalid.
347 *
348 * We don't allow the merge if it is nonsensical, marked as a duplicate
349 * or outside the user's permission.
350 *
351 * @param int $cid
352 * Contact ID to retain
353 * @param int $oid
354 * Contact ID to delete.
355 */
356 public function bounceIfInvalid($cid, $oid) {
357 if ($cid == $oid) {
358 CRM_Core_Error::statusBounce(ts('Cannot merge a contact with itself.'));
359 }
360
361 if (!CRM_Dedupe_BAO_Rule::validateContacts($cid, $oid)) {
be2fb01f 362 CRM_Core_Error::statusBounce(ts('The selected pair of contacts are marked as non duplicates. If these records should be merged, you can remove this exception on the <a href="%1">Dedupe Exceptions</a> page.', [1 => CRM_Utils_System::url('civicrm/dedupe/exception', 'reset=1')]));
fa74cac4 363 }
364
365 if (!(CRM_Contact_BAO_Contact_Permission::allow($cid, CRM_Core_Permission::EDIT) &&
366 CRM_Contact_BAO_Contact_Permission::allow($oid, CRM_Core_Permission::EDIT)
367 )
368 ) {
369 CRM_Utils_System::permissionDenied();
370 }
371 // ensure that oid is not the current user, if so refuse to do the merge
372 if (CRM_Core_Session::singleton()->getLoggedInContactID() == $oid) {
373 $message = ts('The contact record which is linked to the currently logged in user account - \'%1\' - cannot be deleted.',
be2fb01f 374 [1 => CRM_Core_Session::singleton()->getLoggedInContactDisplayName()]
fa74cac4 375 );
376 CRM_Core_Error::statusBounce($message);
377 }
378 }
379
c8e6c437 380 /**
381 * Assign the summary_rows variable to the tpl.
382 *
383 * This adds rows to the beginning of the block that will help in making merge choices.
384 *
385 * It can be modified by a hook by altering what is assigned. Although not technically supported this
386 * is an easy tweak with no earth-shattering impacts if later changes stop if from working.
387 *
388 * https://lab.civicrm.org/dev/core/issues/824
389 *
390 * @param array $contacts
391 */
392 protected function assignSummaryRowsToTemplate($contacts) {
393 $mostRecent = ($contacts[$this->_cid]['modified_date'] < $contacts[$this->_oid]['modified_date']) ? $this->_oid : $this->_cid;
394 $this->assign('summary_rows', [
395 [
396 'name' => 'created_date',
397 'label' => ts('Created'),
398 'main_contact_value' => CRM_Utils_Date::customFormat($contacts[$this->_cid]['created_date']),
399 'other_contact_value' => CRM_Utils_Date::customFormat($contacts[$this->_oid]['created_date']),
400 ],
401 [
402 'name' => 'modified_date',
403 'label' => ts('Last Modified'),
404 'main_contact_value' => CRM_Utils_Date::customFormat($contacts[$this->_cid]['modified_date']) . ($mostRecent == $this->_cid ? ' (' . ts('Most Recent') . ')' : ''),
405 'other_contact_value' => CRM_Utils_Date::customFormat($contacts[$this->_oid]['modified_date']) . ($mostRecent == $this->_oid ? ' (' . ts('Most Recent') . ')' : ''),
406 ],
407 ]);
408 }
409
6a488035 410}