Merge pull request #11724 from lemacarl/CRM-21779
[civicrm-core.git] / CRM / Contact / Form / Merge.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
6a488035 5 +--------------------------------------------------------------------+
8c9251b3 6 | Copyright CiviCRM LLC (c) 2004-2018 |
6a488035
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
8c9251b3 31 * @copyright CiviCRM LLC (c) 2004-2018
6a488035
TO
32 */
33
86538308 34/**
5a409b50 35 * Class CRM_Contact_Form_Merge.
86538308 36 */
6a488035 37class CRM_Contact_Form_Merge extends CRM_Core_Form {
5af77f03 38 // The id of the contact that there's a duplicate for; this one will
39 // possibly inherit some of $_oid's properties and remain in the system.
6a488035
TO
40 var $_cid = NULL;
41
5af77f03 42 // The id of the other contact - the duplicate one that will get deleted.
6a488035
TO
43 var $_oid = NULL;
44
45 var $_contactType = NULL;
46
9f54f049 47 /**
48 * @var array
49 */
50 public $criteria = array();
51
dc6285d5 52 /**
53 * Query limit to be retained in the urls.
54 *
55 * @var int
56 */
57 var $limit;
58
5af77f03 59 /**
60 * String for quickform bug handling.
61 *
62 * FIXME: QuickForm can't create advcheckboxes with value set to 0 or '0' :(
63 * see HTML_QuickForm_advcheckbox::setValues() - but patching that doesn't
64 * help, as QF doesn't put the 0-value elements in exportValues() anyway...
65 * to side-step this, we use the below UUID as a (re)placeholder
66 *
67 * @var string
68 */
8ef12e64 69 var $_qfZeroBug = 'e8cddb72-a257-11dc-b9cc-0016d3330ee9';
70
00be9182 71 public function preProcess() {
fcc3d8ee 72 try {
6a488035 73
fcc3d8ee 74 $this->_cid = CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE);
75 $this->_oid = CRM_Utils_Request::retrieve('oid', 'Positive', $this, TRUE);
76 $flip = CRM_Utils_Request::retrieve('flip', 'Positive', $this, FALSE);
6a488035 77
fcc3d8ee 78 $this->_rgid = CRM_Utils_Request::retrieve('rgid', 'Positive', $this, FALSE);
79 $this->_gid = $gid = CRM_Utils_Request::retrieve('gid', 'Positive', $this, FALSE);
80 $this->_mergeId = CRM_Utils_Request::retrieve('mergeId', 'Positive', $this, FALSE);
81 $this->limit = CRM_Utils_Request::retrieve('limit', 'Positive', $this, FALSE);
9f54f049 82 $this->criteria = CRM_Utils_Request::retrieve('criteria', 'Json', $this, FALSE, '{}');
c0cc2ad4 83
9f54f049 84 $urlParams = ['reset' => 1, 'rgid' => $this->_rgid, 'gid' => $this->_gid, 'limit' => $this->limit, 'criteria' => $this->criteria];
6d5a3f21 85
fcc3d8ee 86 $this->bounceIfInvalid($this->_cid, $this->_oid);
186e5c44 87
fcc3d8ee 88 $this->_contactType = civicrm_api3('Contact', 'getvalue', array(
89 'id' => $this->_cid,
90 'return' => 'contact_type',
e9299e88 91 ));
6a488035 92
c0cc2ad4 93 $browseUrl = CRM_Utils_System::url('civicrm/contact/dedupefind', array_merge($urlParams, ['action' => 'browse']));
2ae26001 94
fcc3d8ee 95 if (!$this->_rgid) {
96 // Unset browse URL as we have come from the search screen.
97 $browseUrl = '';
98 $this->_rgid = civicrm_api3('RuleGroup', 'getvalue', array(
99 'contact_type' => $this->_contactType,
100 'used' => 'Supervised',
101 'return' => 'id',
102 ));
103 }
104 $this->assign('browseUrl', $browseUrl);
105 if ($browseUrl) {
106 CRM_Core_Session::singleton()->pushUserContext($browseUrl);
107 }
6a488035 108
9f54f049 109 $cacheKey = CRM_Dedupe_Merger::getMergeCacheKeyString($this->_rgid, $gid, json_decode($this->criteria, TRUE));
6a488035 110
fcc3d8ee 111 $join = CRM_Dedupe_Merger::getJoinOnDedupeTable();
112 $where = "de.id IS NULL";
6a488035 113
fcc3d8ee 114 $pos = CRM_Core_BAO_PrevNextCache::getPositions($cacheKey, $this->_cid, $this->_oid, $this->_mergeId, $join, $where, $flip);
6a488035 115
fcc3d8ee 116 // get user info of main contact.
117 $config = CRM_Core_Config::singleton();
0626851e 118 CRM_Core_Config::setPermitCacheFlushMode(FALSE);
6a488035 119
fcc3d8ee 120 $mainUfId = CRM_Core_BAO_UFMatch::getUFId($this->_cid);
121 $mainUser = NULL;
122 if ($mainUfId) {
cff0c9aa 123 $mainUser = $config->userSystem->getUser($this->_cid);
fcc3d8ee 124 $this->assign('mainUfId', $mainUfId);
cff0c9aa 125 $this->assign('mainUfName', $mainUser ? $mainUser['name'] : NULL);
fcc3d8ee 126 }
c0cc2ad4 127 $flipParams = array_merge($urlParams, ['action' => 'update', 'cid' => $this->_oid, 'oid' => $this->_cid]);
fcc3d8ee 128 if (!$flip) {
c0cc2ad4 129 $flipParams['flip'] = '1';
6a488035 130 }
c0cc2ad4 131 $flipUrl = CRM_Utils_System::url('civicrm/contact/merge',
132 $flipParams
133 );
fcc3d8ee 134 $this->assign('flip', $flipUrl);
135
136 $this->prev = $this->next = NULL;
137 foreach (array(
138 'prev',
139 'next',
140 ) as $position) {
141 if (!empty($pos[$position])) {
142 if ($pos[$position]['id1'] && $pos[$position]['id2']) {
c0cc2ad4 143 $rowParams = array_merge($urlParams, [
144 'action' => 'update',
145 'cid' => $pos[$position]['id1'],
146 'oid' => $pos[$position]['id2'],
147 'mergeId' => $pos[$position]['mergeId'],
148 ]);
149 $this->$position = CRM_Utils_System::url('civicrm/contact/merge', $rowParams);
fcc3d8ee 150 $this->assign($position, $this->$position);
151 }
152 }
6a488035
TO
153 }
154
fcc3d8ee 155 // get user info of other contact.
156 $otherUfId = CRM_Core_BAO_UFMatch::getUFId($this->_oid);
157 $otherUser = NULL;
6a488035 158
fcc3d8ee 159 if ($otherUfId) {
cff0c9aa 160 $otherUser = $config->userSystem->getUser($this->_oid);
fcc3d8ee 161 $this->assign('otherUfId', $otherUfId);
cff0c9aa 162 $this->assign('otherUfName', $otherUser ? $otherUser['name'] : NULL);
fcc3d8ee 163 }
6a488035 164
fcc3d8ee 165 $cmsUser = ($mainUfId && $otherUfId) ? TRUE : FALSE;
166 $this->assign('user', $cmsUser);
167
168 $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($this->_cid, $this->_oid);
169 $main = $this->_mainDetails = $rowsElementsAndInfo['main_details'];
170 $other = $this->_otherDetails = $rowsElementsAndInfo['other_details'];
171
172 $this->assign('contact_type', $main['contact_type']);
173 $this->assign('main_name', $main['display_name']);
174 $this->assign('other_name', $other['display_name']);
175 $this->assign('main_cid', $main['contact_id']);
176 $this->assign('other_cid', $other['contact_id']);
177 $this->assign('rgid', $this->_rgid);
178
179 $this->addElement('checkbox', 'toggleSelect', NULL, NULL, array('class' => 'select-rows'));
180
181 $this->assign('mainLocBlock', json_encode($rowsElementsAndInfo['main_details']['location_blocks']));
182 $this->assign('locationBlockInfo', json_encode(CRM_Dedupe_Merger::getLocationBlockInfo()));
183 $this->assign('rows', $rowsElementsAndInfo['rows']);
184
185 // add elements
186 foreach ($rowsElementsAndInfo['elements'] as $element) {
187 // We could push this down to the getRowsElementsAndInfo function but it's
188 // already so overloaded - let's start moving towards doing form-things
189 // on the form.
190 if (substr($element[1], 0, 13) === 'move_location') {
191 $element[4] = array_merge(
192 (array) CRM_Utils_Array::value(4, $element, array()),
193 array(
194 'data-location' => substr($element[1], 14),
195 'data-is_location' => TRUE,
196 ));
197 }
198 if (substr($element[1], 0, 15) === 'location_blocks') {
199 // @todo We could add some data elements here to make jquery manipulation more straight-forward
200 // @todo consider enabling if it is an add & defaulting to true.
201 $element[4] = array_merge((array) CRM_Utils_Array::value(4, $element, array()), array('disabled' => TRUE));
202 }
203 $this->addElement($element[0],
204 $element[1],
205 array_key_exists('2', $element) ? $element[2] : NULL,
206 array_key_exists('3', $element) ? $element[3] : NULL,
207 array_key_exists('4', $element) ? $element[4] : NULL,
208 array_key_exists('5', $element) ? $element[5] : NULL
209 );
972e23db 210 }
fcc3d8ee 211
212 // add related table elements
213 foreach ($rowsElementsAndInfo['rel_table_elements'] as $relTableElement) {
214 $element = $this->addElement($relTableElement[0], $relTableElement[1]);
215 $element->setChecked(TRUE);
972e23db 216 }
6a488035 217
fcc3d8ee 218 $this->assign('rel_tables', $rowsElementsAndInfo['rel_tables']);
219 $this->assign('userContextURL', CRM_Core_Session::singleton()
220 ->readUserContext());
221 }
222 catch (CRM_Core_Exception $e) {
223 CRM_Core_Error::statusBounce(ts($e->getMessage()));
6a488035 224 }
6a488035
TO
225 }
226
6ea503d4
TO
227 public function addRules() {
228 }
6a488035
TO
229
230 public function buildQuickForm() {
d45c349e 231 $this->unsavedChangesWarn = FALSE;
d5be719d 232 CRM_Utils_System::setTitle(ts('Merge %1 contacts', array(1 => $this->_contactType)));
fd66df85
CW
233 $buttons = array();
234
235 $buttons[] = array(
236 'type' => 'next',
3709190a 237 'name' => $this->next ? ts('Merge and go to Next Pair') : ts('Merge'),
fd66df85 238 'isDefault' => TRUE,
5db72c32 239 'icon' => $this->next ? 'fa-play-circle' : 'check',
fd66df85 240 );
6a488035
TO
241
242 if ($this->next || $this->prev) {
fd66df85
CW
243 $buttons[] = array(
244 'type' => 'submit',
3709190a 245 'name' => ts('Merge and go to Listing'),
6a488035 246 );
fd66df85
CW
247 $buttons[] = array(
248 'type' => 'done',
249 'name' => ts('Merge and View Result'),
0291a521 250 'icon' => 'fa-check-circle',
6a488035
TO
251 );
252 }
253
fd66df85
CW
254 $buttons[] = array(
255 'type' => 'cancel',
256 'name' => ts('Cancel'),
257 );
258
259 $this->addButtons($buttons);
4b87bd02 260 $this->addFormRule(array('CRM_Contact_Form_Merge', 'formRule'), $this);
261 }
262
86538308
EM
263 /**
264 * @param $fields
265 * @param $files
266 * @param $self
267 *
268 * @return array
269 */
00be9182 270 public static function formRule($fields, $files, $self) {
4b87bd02 271 $errors = array();
0653585f 272 $link = CRM_Utils_System::href(ts('Flip between the original and duplicate contacts.'),
b74201e4 273 'civicrm/contact/merge',
274 'reset=1&action=update&cid=' . $self->_oid . '&oid=' . $self->_cid . '&rgid=' . $self->_rgid . '&flip=1'
275 );
4b87bd02 276 if (CRM_Contact_BAO_Contact::checkDomainContact($self->_oid)) {
0653585f 277 $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", array(1 => $link));
4b87bd02 278 }
279 return $errors;
6a488035
TO
280 }
281
282 public function postProcess() {
283 $formValues = $this->exportValues();
8ef12e64 284
471ec338
BS
285 $formValues['main_details'] = $this->_mainDetails;
286 $formValues['other_details'] = $this->_otherDetails;
b54f692d 287 $migrationData = array('migration_info' => $formValues);
288 CRM_Utils_Hook::merge('form', $migrationData, $this->_cid, $this->_oid);
289 CRM_Dedupe_Merger::moveAllBelongings($this->_cid, $this->_oid, $migrationData['migration_info']);
6a488035 290
7b99ead3
CW
291 $name = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_cid, 'display_name');
292 $message = '<ul><li>' . ts('%1 has been updated.', array(1 => $name)) . '</li><li>' . ts('Contact ID %1 has been deleted.', array(1 => $this->_oid)) . '</li></ul>';
293 CRM_Core_Session::setStatus($message, ts('Contacts Merged'), 'success');
294
9f54f049 295 $urlParams = ['reset' => 1, 'cid' => $this->_cid, 'rgid' => $this->_rgid, 'gid' => $this->_gid, 'limit' => $this->limit, 'criteria' => $this->criteria];
c0cc2ad4 296 $contactViewUrl = CRM_Utils_System::url('civicrm/contact/view', ['reset' => 1, 'cid' => $this->_cid]);
dc6285d5 297
a7488080 298 if (!empty($formValues['_qf_Merge_submit'])) {
c0cc2ad4 299 $urlParams['action'] = "update";
300 CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/dedupefind',
dc6285d5 301 $urlParams
c0cc2ad4 302 ));
6a488035 303 }
353ffa53 304 if (!empty($formValues['_qf_Merge_done'])) {
c0cc2ad4 305 CRM_Utils_System::redirect($contactViewUrl);
6a488035
TO
306 }
307
308 if ($this->next && $this->_mergeId) {
9f54f049 309 $cacheKey = CRM_Dedupe_Merger::getMergeCacheKeyString($this->_rgid, $this->_gid, json_decode($this->criteria, TRUE));
6a488035 310
2ae26001 311 $join = CRM_Dedupe_Merger::getJoinOnDedupeTable();
6a488035
TO
312 $where = "de.id IS NULL";
313
314 $pos = CRM_Core_BAO_PrevNextCache::getPositions($cacheKey, NULL, NULL, $this->_mergeId, $join, $where);
315
316 if (!empty($pos) &&
317 $pos['next']['id1'] &&
318 $pos['next']['id2']
319 ) {
320
c0cc2ad4 321 $urlParams['cid'] = $pos['next']['id1'];
322 $urlParams['oid'] = $pos['next']['id2'];
323 $urlParams['mergeId'] = $pos['next']['mergeId'];
324 $urlParams['action'] = 'update';
325 CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/merge', $urlParams));
6a488035
TO
326 }
327 }
328
c0cc2ad4 329 // Perhaps never reached.
330 CRM_Utils_System::redirect($contactViewUrl);
6a488035 331 }
96025800 332
fa74cac4 333 /**
334 * Bounce if the merge action is invalid.
335 *
336 * We don't allow the merge if it is nonsensical, marked as a duplicate
337 * or outside the user's permission.
338 *
339 * @param int $cid
340 * Contact ID to retain
341 * @param int $oid
342 * Contact ID to delete.
343 */
344 public function bounceIfInvalid($cid, $oid) {
345 if ($cid == $oid) {
346 CRM_Core_Error::statusBounce(ts('Cannot merge a contact with itself.'));
347 }
348
349 if (!CRM_Dedupe_BAO_Rule::validateContacts($cid, $oid)) {
350 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.', array(1 => CRM_Utils_System::url('civicrm/dedupe/exception', 'reset=1'))));
351 }
352
353 if (!(CRM_Contact_BAO_Contact_Permission::allow($cid, CRM_Core_Permission::EDIT) &&
354 CRM_Contact_BAO_Contact_Permission::allow($oid, CRM_Core_Permission::EDIT)
355 )
356 ) {
357 CRM_Utils_System::permissionDenied();
358 }
359 // ensure that oid is not the current user, if so refuse to do the merge
360 if (CRM_Core_Session::singleton()->getLoggedInContactID() == $oid) {
361 $message = ts('The contact record which is linked to the currently logged in user account - \'%1\' - cannot be deleted.',
362 array(1 => CRM_Core_Session::singleton()->getLoggedInContactDisplayName())
363 );
364 CRM_Core_Error::statusBounce($message);
365 }
366 }
367
6a488035 368}