Merge pull request #11986 from eileenmcnaughton/test
[civicrm-core.git] / CRM / Contact / Form / Merge.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
7e9e8871 4 | CiviCRM version 4.7 |
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) {
123 // d6 compatible
124 if ($config->userSystem->is_drupal == '1') {
125 $mainUser = user_load($mainUfId);
126 }
127 elseif ($config->userFramework == 'Joomla') {
128 $mainUser = JFactory::getUser($mainUfId);
6a488035 129 }
6a488035 130
fcc3d8ee 131 $this->assign('mainUfId', $mainUfId);
132 $this->assign('mainUfName', $mainUser ? $mainUser->name : NULL);
133 }
c0cc2ad4 134 $flipParams = array_merge($urlParams, ['action' => 'update', 'cid' => $this->_oid, 'oid' => $this->_cid]);
fcc3d8ee 135 if (!$flip) {
c0cc2ad4 136 $flipParams['flip'] = '1';
6a488035 137 }
c0cc2ad4 138 $flipUrl = CRM_Utils_System::url('civicrm/contact/merge',
139 $flipParams
140 );
fcc3d8ee 141 $this->assign('flip', $flipUrl);
142
143 $this->prev = $this->next = NULL;
144 foreach (array(
145 'prev',
146 'next',
147 ) as $position) {
148 if (!empty($pos[$position])) {
149 if ($pos[$position]['id1'] && $pos[$position]['id2']) {
c0cc2ad4 150 $rowParams = array_merge($urlParams, [
151 'action' => 'update',
152 'cid' => $pos[$position]['id1'],
153 'oid' => $pos[$position]['id2'],
154 'mergeId' => $pos[$position]['mergeId'],
155 ]);
156 $this->$position = CRM_Utils_System::url('civicrm/contact/merge', $rowParams);
fcc3d8ee 157 $this->assign($position, $this->$position);
158 }
159 }
6a488035
TO
160 }
161
fcc3d8ee 162 // get user info of other contact.
163 $otherUfId = CRM_Core_BAO_UFMatch::getUFId($this->_oid);
164 $otherUser = NULL;
6a488035 165
fcc3d8ee 166 if ($otherUfId) {
167 // d6 compatible
168 if ($config->userSystem->is_drupal == '1') {
169 $otherUser = user_load($otherUfId);
170 }
171 elseif ($config->userFramework == 'Joomla') {
172 $otherUser = JFactory::getUser($otherUfId);
173 }
6a488035 174
fcc3d8ee 175 $this->assign('otherUfId', $otherUfId);
176 $this->assign('otherUfName', $otherUser ? $otherUser->name : NULL);
177 }
6a488035 178
fcc3d8ee 179 $cmsUser = ($mainUfId && $otherUfId) ? TRUE : FALSE;
180 $this->assign('user', $cmsUser);
181
182 $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($this->_cid, $this->_oid);
183 $main = $this->_mainDetails = $rowsElementsAndInfo['main_details'];
184 $other = $this->_otherDetails = $rowsElementsAndInfo['other_details'];
185
186 $this->assign('contact_type', $main['contact_type']);
187 $this->assign('main_name', $main['display_name']);
188 $this->assign('other_name', $other['display_name']);
189 $this->assign('main_cid', $main['contact_id']);
190 $this->assign('other_cid', $other['contact_id']);
191 $this->assign('rgid', $this->_rgid);
192
193 $this->addElement('checkbox', 'toggleSelect', NULL, NULL, array('class' => 'select-rows'));
194
195 $this->assign('mainLocBlock', json_encode($rowsElementsAndInfo['main_details']['location_blocks']));
196 $this->assign('locationBlockInfo', json_encode(CRM_Dedupe_Merger::getLocationBlockInfo()));
197 $this->assign('rows', $rowsElementsAndInfo['rows']);
198
199 // add elements
200 foreach ($rowsElementsAndInfo['elements'] as $element) {
201 // We could push this down to the getRowsElementsAndInfo function but it's
202 // already so overloaded - let's start moving towards doing form-things
203 // on the form.
204 if (substr($element[1], 0, 13) === 'move_location') {
205 $element[4] = array_merge(
206 (array) CRM_Utils_Array::value(4, $element, array()),
207 array(
208 'data-location' => substr($element[1], 14),
209 'data-is_location' => TRUE,
210 ));
211 }
212 if (substr($element[1], 0, 15) === 'location_blocks') {
213 // @todo We could add some data elements here to make jquery manipulation more straight-forward
214 // @todo consider enabling if it is an add & defaulting to true.
215 $element[4] = array_merge((array) CRM_Utils_Array::value(4, $element, array()), array('disabled' => TRUE));
216 }
217 $this->addElement($element[0],
218 $element[1],
219 array_key_exists('2', $element) ? $element[2] : NULL,
220 array_key_exists('3', $element) ? $element[3] : NULL,
221 array_key_exists('4', $element) ? $element[4] : NULL,
222 array_key_exists('5', $element) ? $element[5] : NULL
223 );
972e23db 224 }
fcc3d8ee 225
226 // add related table elements
227 foreach ($rowsElementsAndInfo['rel_table_elements'] as $relTableElement) {
228 $element = $this->addElement($relTableElement[0], $relTableElement[1]);
229 $element->setChecked(TRUE);
972e23db 230 }
6a488035 231
fcc3d8ee 232 $this->assign('rel_tables', $rowsElementsAndInfo['rel_tables']);
233 $this->assign('userContextURL', CRM_Core_Session::singleton()
234 ->readUserContext());
235 }
236 catch (CRM_Core_Exception $e) {
237 CRM_Core_Error::statusBounce(ts($e->getMessage()));
6a488035 238 }
6a488035
TO
239 }
240
6ea503d4
TO
241 public function addRules() {
242 }
6a488035
TO
243
244 public function buildQuickForm() {
d45c349e 245 $this->unsavedChangesWarn = FALSE;
d5be719d 246 CRM_Utils_System::setTitle(ts('Merge %1 contacts', array(1 => $this->_contactType)));
fd66df85
CW
247 $buttons = array();
248
249 $buttons[] = array(
250 'type' => 'next',
3709190a 251 'name' => $this->next ? ts('Merge and go to Next Pair') : ts('Merge'),
fd66df85 252 'isDefault' => TRUE,
5db72c32 253 'icon' => $this->next ? 'fa-play-circle' : 'check',
fd66df85 254 );
6a488035
TO
255
256 if ($this->next || $this->prev) {
fd66df85
CW
257 $buttons[] = array(
258 'type' => 'submit',
3709190a 259 'name' => ts('Merge and go to Listing'),
6a488035 260 );
fd66df85
CW
261 $buttons[] = array(
262 'type' => 'done',
263 'name' => ts('Merge and View Result'),
0291a521 264 'icon' => 'fa-check-circle',
6a488035
TO
265 );
266 }
267
fd66df85
CW
268 $buttons[] = array(
269 'type' => 'cancel',
270 'name' => ts('Cancel'),
271 );
272
273 $this->addButtons($buttons);
4b87bd02 274 $this->addFormRule(array('CRM_Contact_Form_Merge', 'formRule'), $this);
275 }
276
86538308
EM
277 /**
278 * @param $fields
279 * @param $files
280 * @param $self
281 *
282 * @return array
283 */
00be9182 284 public static function formRule($fields, $files, $self) {
4b87bd02 285 $errors = array();
0653585f 286 $link = CRM_Utils_System::href(ts('Flip between the original and duplicate contacts.'),
b74201e4 287 'civicrm/contact/merge',
288 'reset=1&action=update&cid=' . $self->_oid . '&oid=' . $self->_cid . '&rgid=' . $self->_rgid . '&flip=1'
289 );
4b87bd02 290 if (CRM_Contact_BAO_Contact::checkDomainContact($self->_oid)) {
0653585f 291 $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 292 }
293 return $errors;
6a488035
TO
294 }
295
296 public function postProcess() {
297 $formValues = $this->exportValues();
8ef12e64 298
471ec338
BS
299 $formValues['main_details'] = $this->_mainDetails;
300 $formValues['other_details'] = $this->_otherDetails;
b54f692d 301 $migrationData = array('migration_info' => $formValues);
302 CRM_Utils_Hook::merge('form', $migrationData, $this->_cid, $this->_oid);
303 CRM_Dedupe_Merger::moveAllBelongings($this->_cid, $this->_oid, $migrationData['migration_info']);
6a488035 304
7b99ead3
CW
305 $name = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_cid, 'display_name');
306 $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>';
307 CRM_Core_Session::setStatus($message, ts('Contacts Merged'), 'success');
308
9f54f049 309 $urlParams = ['reset' => 1, 'cid' => $this->_cid, 'rgid' => $this->_rgid, 'gid' => $this->_gid, 'limit' => $this->limit, 'criteria' => $this->criteria];
c0cc2ad4 310 $contactViewUrl = CRM_Utils_System::url('civicrm/contact/view', ['reset' => 1, 'cid' => $this->_cid]);
dc6285d5 311
a7488080 312 if (!empty($formValues['_qf_Merge_submit'])) {
c0cc2ad4 313 $urlParams['action'] = "update";
314 CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/dedupefind',
dc6285d5 315 $urlParams
c0cc2ad4 316 ));
6a488035 317 }
353ffa53 318 if (!empty($formValues['_qf_Merge_done'])) {
c0cc2ad4 319 CRM_Utils_System::redirect($contactViewUrl);
6a488035
TO
320 }
321
322 if ($this->next && $this->_mergeId) {
9f54f049 323 $cacheKey = CRM_Dedupe_Merger::getMergeCacheKeyString($this->_rgid, $this->_gid, json_decode($this->criteria, TRUE));
6a488035 324
2ae26001 325 $join = CRM_Dedupe_Merger::getJoinOnDedupeTable();
6a488035
TO
326 $where = "de.id IS NULL";
327
328 $pos = CRM_Core_BAO_PrevNextCache::getPositions($cacheKey, NULL, NULL, $this->_mergeId, $join, $where);
329
330 if (!empty($pos) &&
331 $pos['next']['id1'] &&
332 $pos['next']['id2']
333 ) {
334
c0cc2ad4 335 $urlParams['cid'] = $pos['next']['id1'];
336 $urlParams['oid'] = $pos['next']['id2'];
337 $urlParams['mergeId'] = $pos['next']['mergeId'];
338 $urlParams['action'] = 'update';
339 CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/merge', $urlParams));
6a488035
TO
340 }
341 }
342
c0cc2ad4 343 // Perhaps never reached.
344 CRM_Utils_System::redirect($contactViewUrl);
6a488035 345 }
96025800 346
fa74cac4 347 /**
348 * Bounce if the merge action is invalid.
349 *
350 * We don't allow the merge if it is nonsensical, marked as a duplicate
351 * or outside the user's permission.
352 *
353 * @param int $cid
354 * Contact ID to retain
355 * @param int $oid
356 * Contact ID to delete.
357 */
358 public function bounceIfInvalid($cid, $oid) {
359 if ($cid == $oid) {
360 CRM_Core_Error::statusBounce(ts('Cannot merge a contact with itself.'));
361 }
362
363 if (!CRM_Dedupe_BAO_Rule::validateContacts($cid, $oid)) {
364 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'))));
365 }
366
367 if (!(CRM_Contact_BAO_Contact_Permission::allow($cid, CRM_Core_Permission::EDIT) &&
368 CRM_Contact_BAO_Contact_Permission::allow($oid, CRM_Core_Permission::EDIT)
369 )
370 ) {
371 CRM_Utils_System::permissionDenied();
372 }
373 // ensure that oid is not the current user, if so refuse to do the merge
374 if (CRM_Core_Session::singleton()->getLoggedInContactID() == $oid) {
375 $message = ts('The contact record which is linked to the currently logged in user account - \'%1\' - cannot be deleted.',
376 array(1 => CRM_Core_Session::singleton()->getLoggedInContactDisplayName())
377 );
378 CRM_Core_Error::statusBounce($message);
379 }
380 }
381
6a488035 382}