Merge pull request #7950 from JKingsnorth/CRM-18196
[civicrm-core.git] / CRM / Contact / Page / DedupeFind.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2016 |
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-2016
32 */
33 class CRM_Contact_Page_DedupeFind extends CRM_Core_Page_Basic {
34 protected $_cid = NULL;
35 protected $_rgid;
36 protected $_mainContacts;
37 protected $_gid;
38
39 /**
40 * Get BAO Name.
41 *
42 * @return string
43 * Classname of BAO.
44 */
45 public function getBAOName() {
46 return 'CRM_Dedupe_BAO_RuleGroup';
47 }
48
49 /**
50 * Get action Links.
51 */
52 public function &links() {
53 }
54
55 /**
56 * Browse all rule groups.
57 */
58 public function run() {
59 $gid = CRM_Utils_Request::retrieve('gid', 'Positive', $this, FALSE, 0);
60 $action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 0);
61 $context = CRM_Utils_Request::retrieve('context', 'String', $this);
62
63 $session = CRM_Core_Session::singleton();
64 $contactIds = $session->get('selectedSearchContactIds');
65 if ($context == 'search' || !empty($contactIds)) {
66 $context = 'search';
67 $this->assign('backURL', $session->readUserContext());
68 }
69
70 if ($action & CRM_Core_Action::RENEW) {
71 // empty cache
72 $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive', $this, FALSE, 0);
73
74 if ($rgid) {
75 $contactType = CRM_Core_DAO::getFieldValue('CRM_Dedupe_DAO_RuleGroup', $rgid, 'contact_type');
76 $cacheKeyString = "merge $contactType";
77 $cacheKeyString .= $rgid ? "_{$rgid}" : '_0';
78 $cacheKeyString .= $gid ? "_{$gid}" : '_0';
79 CRM_Core_BAO_PrevNextCache::deleteItem(NULL, $cacheKeyString);
80 }
81 $urlQry = "reset=1&action=update&rgid={$rgid}";
82 if ($gid) {
83 $urlQry .= "&gid={$gid}";
84 }
85 CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry));
86 }
87 elseif ($action & CRM_Core_Action::MAP) {
88 // do a batch merge if requested
89 $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive', $this, FALSE, 0);
90 $result = CRM_Dedupe_Merger::batchMerge($rgid, $gid, 'safe', TRUE, 75);
91
92 $skippedCount = CRM_Utils_Request::retrieve('skipped', 'Positive', $this, FALSE, 0);
93 $skippedCount = $skippedCount + count($result['skipped']);
94 $mergedCount = CRM_Utils_Request::retrieve('merged', 'Positive', $this, FALSE, 0);
95 $mergedCount = $mergedCount + count($result['merged']);
96
97 if (empty($result['merged']) && empty($result['skipped'])) {
98 $message = '';
99 if ($mergedCount >= 1) {
100 $message = ts("%1 pairs of duplicates were merged", array(1 => $mergedCount));
101 }
102 if ($skippedCount >= 1) {
103 $message = $message ? "{$message} and " : '';
104 $message .= ts("%1 pairs of duplicates were skipped due to conflict",
105 array(1 => $skippedCount)
106 );
107 }
108 $message .= ts(" during the batch merge process with safe mode.");
109 CRM_Core_Session::setStatus($message, ts('Merge Complete'), 'success');
110
111 $urlQry = "reset=1&action=update&rgid={$rgid}";
112 if ($gid) {
113 $urlQry .= "&gid={$gid}";
114 }
115 CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry));
116 }
117 else {
118 $urlQry = "reset=1&action=map&rgid={$rgid}";
119 if ($gid) {
120 $urlQry .= "&gid={$gid}";
121 }
122 $urlQry .= "&skipped={$skippedCount}&merged={$mergedCount}";
123 CRM_Utils_System::jsRedirect(
124 CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry),
125 ts('Batch Merge Task in progress'),
126 ts('The batch merge task is still in progress. This page will be refreshed automatically.')
127 );
128 }
129 }
130
131 if ($action & CRM_Core_Action::UPDATE ||
132 $action & CRM_Core_Action::BROWSE
133 ) {
134 $cid = CRM_Utils_Request::retrieve('cid', 'Positive', $this, FALSE, 0);
135 $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive', $this, FALSE, 0);
136 $this->action = CRM_Core_Action::UPDATE;
137
138 //calculate the $contactType
139 if ($rgid) {
140 $contactType = CRM_Core_DAO::getFieldValue('CRM_Dedupe_DAO_RuleGroup',
141 $rgid,
142 'contact_type'
143 );
144 }
145
146 $sourceParams = 'snippet=4';
147 if ($gid) {
148 $sourceParams .= "&gid={$gid}";
149 }
150 if ($rgid) {
151 $sourceParams .= "&rgid={$rgid}";
152 }
153 if ($context == 'conflicts') {
154 $sourceParams .= "&selected=1";
155 }
156
157 $this->assign('sourceUrl', CRM_Utils_System::url('civicrm/ajax/dedupefind', $sourceParams, FALSE, NULL, FALSE));
158
159 //reload from cache table
160 $cacheKeyString = "merge $contactType";
161 $cacheKeyString .= $rgid ? "_{$rgid}" : '_0';
162 $cacheKeyString .= $gid ? "_{$gid}" : '_0';
163
164 $stats = CRM_Dedupe_Merger::getMergeStatsMsg($cacheKeyString);
165 if ($stats) {
166 CRM_Core_Session::setStatus($stats);
167 // reset so we not displaying same message again
168 CRM_Dedupe_Merger::resetMergeStats($cacheKeyString);
169 }
170 $join = "LEFT JOIN civicrm_dedupe_exception de ON ( pn.entity_id1 = de.contact_id1 AND
171 pn.entity_id2 = de.contact_id2 )";
172 $where = "de.id IS NULL";
173 if ($context == 'conflicts') {
174 $where .= " AND pn.is_selected = 1";
175 }
176 $this->_mainContacts = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, $join, $where);
177 if (empty($this->_mainContacts)) {
178 if ($context == 'conflicts') {
179 // if the current screen was intended to list only selected contacts, move back to full dupe list
180 $sourceParams = 'reset=1&action=update';
181 if ($gid) {
182 $sourceParams .= "&gid={$gid}";
183 }
184 if ($rgid) {
185 $sourceParams .= "&rgid={$rgid}";
186 }
187 CRM_Utils_System::redirect(CRM_Utils_System::url(CRM_Utils_System::currentPath(), $sourceParams));
188 }
189 if ($gid) {
190 $foundDupes = $this->get("dedupe_dupes_$gid");
191 if (!$foundDupes) {
192 $foundDupes = CRM_Dedupe_Finder::dupesInGroup($rgid, $gid);
193 }
194 $this->set("dedupe_dupes_$gid", $foundDupes);
195 }
196 elseif (!empty($contactIds)) {
197 $foundDupes = $this->get("search_dedupe_dupes_$gid");
198 if (!$foundDupes) {
199 $foundDupes = CRM_Dedupe_Finder::dupes($rgid, $contactIds);
200 }
201 $this->get("search_dedupe_dupes_$gid", $foundDupes);
202 }
203 else {
204 $foundDupes = $this->get('dedupe_dupes');
205 if (!$foundDupes) {
206 $foundDupes = CRM_Dedupe_Finder::dupes($rgid);
207 }
208 $this->set('dedupe_dupes', $foundDupes);
209 }
210 if (!$foundDupes) {
211 $ruleGroup = new CRM_Dedupe_BAO_RuleGroup();
212 $ruleGroup->id = $rgid;
213 $ruleGroup->find(TRUE);
214
215 $session = CRM_Core_Session::singleton();
216 $session->setStatus(ts('No possible duplicates were found using %1 rule.', array(1 => $ruleGroup->name)), ts('None Found'), 'info');
217 $url = CRM_Utils_System::url('civicrm/contact/deduperules', 'reset=1');
218 if ($context == 'search') {
219 $url = $session->readUserContext();
220 }
221 CRM_Utils_System::redirect($url);
222 }
223 else {
224 $cids = array();
225 foreach ($foundDupes as $dupe) {
226 $cids[$dupe[0]] = 1;
227 $cids[$dupe[1]] = 1;
228 }
229 $cidString = implode(', ', array_keys($cids));
230 $sql = "SELECT id, display_name FROM civicrm_contact WHERE id IN ($cidString) ORDER BY sort_name";
231 $dao = new CRM_Core_DAO();
232 $dao->query($sql);
233 $displayNames = array();
234 while ($dao->fetch()) {
235 $displayNames[$dao->id] = $dao->display_name;
236 }
237
238 // FIXME: sort the contacts; $displayName
239 // is already sort_name-sorted, so use that
240 // (also, consider sorting by dupe count first)
241 // lobo - change the sort to by threshold value
242 // so the more likely dupes are sorted first
243 $session = CRM_Core_Session::singleton();
244 $userId = $session->get('userID');
245 $mainContacts = $permission = array();
246
247 foreach ($foundDupes as $dupes) {
248 $srcID = $dupes[0];
249 $dstID = $dupes[1];
250 if ($dstID == $userId) {
251 $srcID = $dupes[1];
252 $dstID = $dupes[0];
253 }
254
255 /***
256 * Eliminate this since it introduces 3 queries PER merge row
257 * and hence is very expensive
258 * CRM-8822
259 * if ( !array_key_exists( $srcID, $permission ) ) {
260 * $permission[$srcID] = CRM_Contact_BAO_Contact_Permission::allow( $srcID, CRM_Core_Permission::EDIT );
261 * }
262 * if ( !array_key_exists( $dstID, $permission ) ) {
263 * $permission[$dstID] = CRM_Contact_BAO_Contact_Permission::allow( $dstID, CRM_Core_Permission::EDIT );
264 * }
265 *
266 * $canMerge = ( $permission[$dstID] && $permission[$srcID] );
267 *
268 */
269
270 // we'll do permission checking during the merge process
271 $canMerge = TRUE;
272
273 $mainContacts[] = $row = array(
274 'srcID' => $srcID,
275 'srcName' => $displayNames[$srcID],
276 'dstID' => $dstID,
277 'dstName' => $displayNames[$dstID],
278 'weight' => $dupes[2],
279 'canMerge' => $canMerge,
280 );
281
282 $data = CRM_Core_DAO::escapeString(serialize($row));
283 $values[] = " ( 'civicrm_contact', $srcID, $dstID, '$cacheKeyString', '$data' ) ";
284 }
285 if ($cid) {
286 $this->_cid = $cid;
287 }
288 if ($gid) {
289 $this->_gid = $gid;
290 }
291 $this->_rgid = $rgid;
292 $this->_mainContacts = $mainContacts;
293
294 CRM_Core_BAO_PrevNextCache::setItem($values);
295 $session = CRM_Core_Session::singleton();
296 if ($this->_cid) {
297 $session->pushUserContext(CRM_Utils_System::url('civicrm/contact/deduperules',
298 "action=update&rgid={$this->_rgid}&gid={$this->_gid}&cid={$this->_cid}"
299 ));
300 }
301 else {
302 $session->pushUserContext(CRM_Utils_System::url('civicrm/contact/dedupefind',
303 "reset=1&action=update&rgid={$this->_rgid}"
304 ));
305 }
306 }
307 }
308 else {
309 if ($cid) {
310 $this->_cid = $cid;
311 }
312 if ($gid) {
313 $this->_gid = $gid;
314 }
315 $this->_rgid = $rgid;
316 }
317
318 $this->assign('action', $this->action);
319 $this->browse();
320 }
321 else {
322 $this->action = CRM_Core_Action::UPDATE;
323 $this->edit($this->action);
324 $this->assign('action', $this->action);
325 }
326 $this->assign('context', $context);
327
328 // parent run
329 return parent::run();
330 }
331
332 /**
333 * Browse all rule groups.
334 */
335 public function browse() {
336 $this->assign('main_contacts', $this->_mainContacts);
337
338 if ($this->_cid) {
339 $this->assign('cid', $this->_cid);
340 }
341 if (isset($this->_gid) || $this->_gid) {
342 $this->assign('gid', $this->_gid);
343 }
344 $this->assign('rgid', $this->_rgid);
345 }
346
347 /**
348 * Get name of edit form.
349 *
350 * @return string
351 * classname of edit form
352 */
353 public function editForm() {
354 return 'CRM_Contact_Form_DedupeFind';
355 }
356
357 /**
358 * Get edit form name.
359 *
360 * @return string
361 * name of this page
362 */
363 public function editName() {
364 return 'DedupeFind';
365 }
366
367 /**
368 * Get user context.
369 *
370 * @param null $mode
371 *
372 * @return string
373 * user context
374 */
375 public function userContext($mode = NULL) {
376 return 'civicrm/contact/dedupefind';
377 }
378
379 }