Merge pull request #6444 from PalanteJon/CRM-16981
[civicrm-core.git] / CRM / Contact / Form / Merge.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
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 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2015
32 * $Id$
33 *
34 */
35
36 require_once 'api/api.php';
37
38 /**
39 * Class CRM_Contact_Form_Merge
40 */
41 class CRM_Contact_Form_Merge extends CRM_Core_Form {
42 // the id of the contact that tere's a duplicate for; this one will
43 // possibly inherit some of $_oid's properties and remain in the system
44 var $_cid = NULL;
45
46 // the id of the other contact - the duplicate one that will get deleted
47 var $_oid = NULL;
48
49 var $_contactType = NULL;
50
51 // variable to keep all location block ids.
52 protected $_locBlockIds = array();
53
54 // FIXME: QuickForm can't create advcheckboxes with value set to 0 or '0' :(
55 // see HTML_QuickForm_advcheckbox::setValues() - but patching that doesn't
56 // help, as QF doesn't put the 0-value elements in exportValues() anyway...
57 // to side-step this, we use the below UUID as a (re)placeholder
58 var $_qfZeroBug = 'e8cddb72-a257-11dc-b9cc-0016d3330ee9';
59
60 public function preProcess() {
61 if (!CRM_Core_Permission::check('merge duplicate contacts')) {
62 CRM_Core_Error::fatal(ts('You do not have access to this page'));
63 }
64
65 $rows = array();
66 $cid = CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE);
67 $oid = CRM_Utils_Request::retrieve('oid', 'Positive', $this, TRUE);
68 $flip = CRM_Utils_Request::retrieve('flip', 'Positive', $this, FALSE);
69
70 $this->_rgid = $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive', $this, FALSE);
71 $this->_gid = $gid = CRM_Utils_Request::retrieve('gid', 'Positive', $this, FALSE);
72 $this->_mergeId = CRM_Utils_Request::retrieve('mergeId', 'Positive', $this, FALSE);
73
74 // Sanity check
75 if ($cid == $oid) {
76 CRM_Core_Error::statusBounce(ts('Cannot merge a contact with itself.'));
77 }
78
79 if (!CRM_Dedupe_BAO_Rule::validateContacts($cid, $oid)) {
80 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'))));
81 }
82
83 //load cache mechanism
84 $contactType = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $cid, 'contact_type');
85 $cacheKey = "merge $contactType";
86 $cacheKey .= $rgid ? "_{$rgid}" : '_0';
87 $cacheKey .= $gid ? "_{$gid}" : '_0';
88
89 $join = "LEFT JOIN civicrm_dedupe_exception de ON ( pn.entity_id1 = de.contact_id1 AND
90 pn.entity_id2 = de.contact_id2 )";
91 $where = "de.id IS NULL";
92
93 $pos = CRM_Core_BAO_PrevNextCache::getPositions($cacheKey, $cid, $oid, $this->_mergeId, $join, $where, $flip);
94
95 // Block access if user does not have EDIT permissions for both contacts.
96 if (!(CRM_Contact_BAO_Contact_Permission::allow($cid, CRM_Core_Permission::EDIT) &&
97 CRM_Contact_BAO_Contact_Permission::allow($oid, CRM_Core_Permission::EDIT)
98 )
99 ) {
100 CRM_Utils_System::permissionDenied();
101 }
102
103 // get user info of main contact.
104 $config = CRM_Core_Config::singleton();
105 $config->doNotResetCache = 1;
106
107 $viewUser = CRM_Core_Permission::check('access user profiles');
108 $mainUfId = CRM_Core_BAO_UFMatch::getUFId($cid);
109 $mainUser = NULL;
110 if ($mainUfId) {
111 // d6 compatible
112 if ($config->userSystem->is_drupal == '1') {
113 $mainUser = user_load($mainUfId);
114 }
115 elseif ($config->userFramework == 'Joomla') {
116 $mainUser = JFactory::getUser($mainUfId);
117 }
118
119 $this->assign('mainUfId', $mainUfId);
120 $this->assign('mainUfName', $mainUser ? $mainUser->name : NULL);
121 }
122
123 $flipUrl = CRM_Utils_System::url('civicrm/contact/merge',
124 "reset=1&action=update&cid={$oid}&oid={$cid}&rgid={$rgid}&gid={$gid}"
125 );
126 if (!$flip) {
127 $flipUrl .= '&flip=1';
128 }
129 $this->assign('flip', $flipUrl);
130
131 $this->prev = $this->next = NULL;
132 foreach (array(
133 'prev',
134 'next',
135 ) as $position) {
136 if (!empty($pos[$position])) {
137 if ($pos[$position]['id1'] && $pos[$position]['id2']) {
138 $urlParam = "reset=1&cid={$pos[$position]['id1']}&oid={$pos[$position]['id2']}&mergeId={$pos[$position]['mergeId']}&action=update";
139
140 if ($rgid) {
141 $urlParam .= "&rgid={$rgid}";
142 }
143 if ($gid) {
144 $urlParam .= "&gid={$gid}";
145 }
146
147 $this->$position = CRM_Utils_System::url('civicrm/contact/merge', $urlParam);
148 $this->assign($position, $this->$position);
149 }
150 }
151 }
152
153 // get user info of other contact.
154 $otherUfId = CRM_Core_BAO_UFMatch::getUFId($oid);
155 $otherUser = NULL;
156
157 if ($otherUfId) {
158 // d6 compatible
159 if ($config->userSystem->is_drupal == '1') {
160 $otherUser = user_load($otherUfId);
161 }
162 elseif ($config->userFramework == 'Joomla') {
163 $otherUser = JFactory::getUser($otherUfId);
164 }
165
166 $this->assign('otherUfId', $otherUfId);
167 $this->assign('otherUfName', $otherUser ? $otherUser->name : NULL);
168 }
169
170 $cmsUser = ($mainUfId && $otherUfId) ? TRUE : FALSE;
171 $this->assign('user', $cmsUser);
172
173 $session = CRM_Core_Session::singleton();
174
175 // context fixed.
176 if ($rgid) {
177 $urlParam = "reset=1&action=browse&rgid={$rgid}";
178 if ($gid) {
179 $urlParam .= "&gid={$gid}";
180 }
181 $session->pushUserContext(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlParam));
182 }
183
184 // ensure that oid is not the current user, if so refuse to do the merge
185 if ($session->get('userID') == $oid) {
186 $display_name = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $oid, 'display_name');
187 $message = ts('The contact record which is linked to the currently logged in user account - \'%1\' - cannot be deleted.',
188 array(1 => $display_name)
189 );
190 CRM_Core_Error::statusBounce($message);
191 }
192
193 $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($cid, $oid);
194 $main = $this->_mainDetails = &$rowsElementsAndInfo['main_details'];
195 $other = $this->_otherDetails = &$rowsElementsAndInfo['other_details'];
196
197 if ($main['contact_id'] != $cid) {
198 CRM_Core_Error::fatal(ts('The main contact record does not exist'));
199 }
200
201 if ($other['contact_id'] != $oid) {
202 CRM_Core_Error::fatal(ts('The other contact record does not exist'));
203 }
204
205 $this->assign('contact_type', $main['contact_type']);
206 $this->assign('main_name', $main['display_name']);
207 $this->assign('other_name', $other['display_name']);
208 $this->assign('main_cid', $main['contact_id']);
209 $this->assign('other_cid', $other['contact_id']);
210 $this->assign('rgid', $rgid);
211
212 $this->_cid = $cid;
213 $this->_oid = $oid;
214 $this->_rgid = $rgid;
215 $this->_contactType = $main['contact_type'];
216 $this->addElement('checkbox', 'toggleSelect', NULL, NULL, array('class' => 'select-rows'));
217
218 $this->assign('mainLocBlock', json_encode($rowsElementsAndInfo['main_loc_block']));
219 $this->assign('rows', $rowsElementsAndInfo['rows']);
220
221 $this->_locBlockIds = array(
222 'main' => $rowsElementsAndInfo['main_details']['loc_block_ids'],
223 'other' => $rowsElementsAndInfo['other_details']['loc_block_ids'],
224 );
225
226 // add elements
227 foreach ($rowsElementsAndInfo['elements'] as $element) {
228 $this->addElement($element[0],
229 $element[1],
230 array_key_exists('2', $element) ? $element[2] : NULL,
231 array_key_exists('3', $element) ? $element[3] : NULL,
232 array_key_exists('4', $element) ? $element[4] : NULL,
233 array_key_exists('5', $element) ? $element[5] : NULL
234 );
235 }
236
237 // add related table elements
238 foreach ($rowsElementsAndInfo['rel_table_elements'] as $relTableElement) {
239 $element = $this->addElement($relTableElement[0], $relTableElement[1]);
240 $element->setChecked(TRUE);
241 }
242
243 $this->assign('rel_tables', $rowsElementsAndInfo['rel_tables']);
244 $this->assign('userContextURL', $session->readUserContext());
245 }
246
247 /**
248 * This virtual function is used to set the default values of
249 * various form elements
250 *
251 * access public
252 *
253 * @return array
254 * reference to the array of default values
255 */
256 /**
257 * @return array
258 */
259 public function setDefaultValues() {
260 return array('deleteOther' => 1);
261 }
262
263 public function addRules() {
264 }
265
266 public function buildQuickForm() {
267 CRM_Utils_System::setTitle(ts('Merge %1 contacts', array(1 => $this->_contactType)));
268 $buttons = array();
269
270 $buttons[] = array(
271 'type' => 'next',
272 'name' => $this->next ? ts('Merge and Goto Next Pair') : ts('Merge'),
273 'isDefault' => TRUE,
274 'icon' => $this->next ? 'circle-triangle-e' : 'check',
275 );
276
277 if ($this->next || $this->prev) {
278 $buttons[] = array(
279 'type' => 'submit',
280 'name' => ts('Merge and Goto Listing'),
281 );
282 $buttons[] = array(
283 'type' => 'done',
284 'name' => ts('Merge and View Result'),
285 'icon' => 'circle-check',
286 );
287 }
288
289 $buttons[] = array(
290 'type' => 'cancel',
291 'name' => ts('Cancel'),
292 );
293
294 $this->addButtons($buttons);
295 $this->addFormRule(array('CRM_Contact_Form_Merge', 'formRule'), $this);
296 }
297
298 /**
299 * @param $fields
300 * @param $files
301 * @param $self
302 *
303 * @return array
304 */
305 public static function formRule($fields, $files, $self) {
306 $errors = array();
307 $link = CRM_Utils_System::href(ts('Flip between the original and duplicate contacts.'),
308 'civicrm/contact/merge',
309 'reset=1&action=update&cid=' . $self->_oid . '&oid=' . $self->_cid . '&rgid=' . $self->_rgid . '&flip=1'
310 );
311 if (CRM_Contact_BAO_Contact::checkDomainContact($self->_oid)) {
312 $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));
313 }
314 return $errors;
315 }
316
317 public function postProcess() {
318 $formValues = $this->exportValues();
319
320 // reset all selected contact ids from session
321 // when we came from search context, CRM-3526
322 $session = CRM_Core_Session::singleton();
323 if ($session->get('selectedSearchContactIds')) {
324 $session->resetScope('selectedSearchContactIds');
325 }
326
327 $formValues['main_details'] = $this->_mainDetails;
328 $formValues['other_details'] = $this->_otherDetails;
329
330 CRM_Dedupe_Merger::moveAllBelongings($this->_cid, $this->_oid, $formValues);
331
332 $name = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_cid, 'display_name');
333 $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>';
334 CRM_Core_Session::setStatus($message, ts('Contacts Merged'), 'success');
335
336 //create activity for merge
337 //To do: this should be refactored into BAO layer at some point.
338 $messageActivity = ts('Contact ID %1 has been merged and deleted.', array(1 => $this->_oid));
339 $activityParams = array(
340 'subject' => $messageActivity,
341 'source_contact_id' => $session->get('userID'),
342 'target_contact_id' => $this->_cid,
343 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Contact Merged'),
344 'status_id' => 'Completed',
345 'priority_id' => 'Normal',
346 'activity_date_time' => date('YmdHis'),
347 );
348 civicrm_api3('activity', 'create', $activityParams);
349
350 $url = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$this->_cid}");
351 if (!empty($formValues['_qf_Merge_submit'])) {
352 $listParamsURL = "reset=1&action=update&rgid={$this->_rgid}";
353 if ($this->_gid) {
354 $listParamsURL .= "&gid={$this->_gid}";
355 }
356 $lisitingURL = CRM_Utils_System::url('civicrm/contact/dedupefind',
357 $listParamsURL
358 );
359 CRM_Utils_System::redirect($lisitingURL);
360 }
361 if (!empty($formValues['_qf_Merge_done'])) {
362 CRM_Utils_System::redirect($url);
363 }
364
365 if ($this->next && $this->_mergeId) {
366 $cacheKey = "merge {$this->_contactType}";
367 $cacheKey .= $this->_rgid ? "_{$this->_rgid}" : '_0';
368 $cacheKey .= $this->_gid ? "_{$this->_gid}" : '_0';
369
370 $join = "LEFT JOIN civicrm_dedupe_exception de ON ( pn.entity_id1 = de.contact_id1 AND
371 pn.entity_id2 = de.contact_id2 )";
372 $where = "de.id IS NULL";
373
374 $pos = CRM_Core_BAO_PrevNextCache::getPositions($cacheKey, NULL, NULL, $this->_mergeId, $join, $where);
375
376 if (!empty($pos) &&
377 $pos['next']['id1'] &&
378 $pos['next']['id2']
379 ) {
380
381 $urlParam = "reset=1&cid={$pos['next']['id1']}&oid={$pos['next']['id2']}&mergeId={$pos['next']['mergeId']}&action=update";
382 if ($this->_rgid) {
383 $urlParam .= "&rgid={$this->_rgid}";
384 }
385 if ($this->_gid) {
386 $urlParam .= "&gid={$this->_gid}";
387 }
388
389 $url = CRM_Utils_System::url('civicrm/contact/merge', $urlParam);
390 }
391 }
392
393 CRM_Utils_System::redirect($url);
394 }
395
396 }