From 63ef778e5b40866ad4a89140a31ccba5553ab729 Mon Sep 17 00:00:00 2001 From: deepak-srivastava Date: Thu, 14 May 2015 11:01:42 +0100 Subject: [PATCH 01/14] Applying patch from Civi v4.5 repo, dedupe-workflow-45 branch, till 97e53e0c63c9d0d4d4712787e6adcc17dcbf188d (re-order columns and disable sorting for icon column) --- CRM/Contact/Page/AJAX.php | 234 +++++++++++++++--- CRM/Contact/Page/DedupeFind.php | 25 +- CRM/Contact/Page/DedupeMerge.php | 129 ++++++++++ CRM/Core/BAO/PrevNextCache.php | 89 ++++++- CRM/Core/xml/Menu/Contact.xml | 11 + CRM/Dedupe/Merger.php | 97 +++++++- CRM/Utils/JSON.php | 33 +++ templates/CRM/Contact/Page/DedupeFind.tpl | 262 ++++++++++++++++++++- templates/CRM/Contact/Page/DedupeMerge.tpl | 25 ++ 9 files changed, 843 insertions(+), 62 deletions(-) create mode 100644 CRM/Contact/Page/DedupeMerge.php create mode 100644 templates/CRM/Contact/Page/DedupeMerge.tpl diff --git a/CRM/Contact/Page/AJAX.php b/CRM/Contact/Page/AJAX.php index a2a9b3bd9c54..e597802c87a4 100644 --- a/CRM/Contact/Page/AJAX.php +++ b/CRM/Contact/Page/AJAX.php @@ -668,53 +668,194 @@ public static function processDupes() { CRM_Utils_JSON::output(array('status' => ($status) ? $oper : $status)); } - public static function getDedupes() { - - $sEcho = CRM_Utils_Type::escape($_REQUEST['sEcho'], 'Integer'); - $offset = isset($_REQUEST['iDisplayStart']) ? CRM_Utils_Type::escape($_REQUEST['iDisplayStart'], 'Integer') : 0; - $rowCount = isset($_REQUEST['iDisplayLength']) ? CRM_Utils_Type::escape($_REQUEST['iDisplayLength'], 'Integer') : 25; - $sort = 'sort_name'; - $sortOrder = isset($_REQUEST['sSortDir_0']) ? CRM_Utils_Type::escape($_REQUEST['sSortDir_0'], 'String') : 'asc'; - - $gid = isset($_REQUEST['gid']) ? CRM_Utils_Type::escape($_REQUEST['gid'], 'Integer') : 0; - $rgid = isset($_REQUEST['rgid']) ? CRM_Utils_Type::escape($_REQUEST['rgid'], 'Integer') : 0; + static function getDedupes() { + $offset = isset($_REQUEST['start']) ? CRM_Utils_Type::escape($_REQUEST['start'], 'Integer') : 0; + $rowCount = isset($_REQUEST['length']) ? CRM_Utils_Type::escape($_REQUEST['length'], 'Integer') : 25; + + $gid = isset($_REQUEST['gid']) ? CRM_Utils_Type::escape($_REQUEST['gid'], 'Integer') : 0; + $rgid = isset($_REQUEST['rgid']) ? CRM_Utils_Type::escape($_REQUEST['rgid'], 'Integer') : 0; + $selected = isset($_REQUEST['selected']) ? CRM_Utils_Type::escape($_REQUEST['selected'], 'Integer') : 0; + if ($rowCount < 0) { + $rowCount = 0; + } $contactType = ''; if ($rgid) { $contactType = CRM_Core_DAO::getFieldValue('CRM_Dedupe_DAO_RuleGroup', $rgid, 'contact_type'); } - $cacheKeyString = "merge {$contactType}_{$rgid}_{$gid}"; - $searchRows = array(); - $selectorElements = array('src', 'dst', 'weight', 'actions'); + $cacheKeyString = "merge {$contactType}_{$rgid}_{$gid}"; + $searchRows = array(); + $selectorElements = array('is_selected', 'is_selected_input', 'src_image', 'src', 'src_email', 'src_street', 'src_postcode', 'dst_image', 'dst', 'dst_email', 'dst_street', 'dst_postcode', 'conflicts', 'weight', 'actions'); - $join = "LEFT JOIN civicrm_dedupe_exception de ON ( pn.entity_id1 = de.contact_id1 AND - pn.entity_id2 = de.contact_id2 )"; - $where = "de.id IS NULL"; + foreach ($_REQUEST['columns'] as $columnInfo) { + if (!empty($columnInfo['search']['value'])) { + ${$columnInfo['data']} = CRM_Utils_Type::escape($columnInfo['search']['value'], 'String'); + } + } + $join = ''; + $where = array(); + $searchData = CRM_Utils_Array::value('search', $_REQUEST); + if ($src || !empty($searchData['value']) ) { + $src = $src ? $src : $searchData['value']; + $where[] = " cc1.display_name LIKE '%{$src}%'"; + } + if ($dst || !empty($searchData['value'])) { + $dst = $dst ? $dst : $searchData['value']; + $where[] = " cc2.display_name LIKE '%{$dst}%'"; + } + if ($src_email || !empty($searchData['value'])) { + $src_email = $src_email ? $src_email : $searchData['value']; + $where[] = " (ce1.is_primary = 1 AND ce1.email LIKE '%{$src_email}%')"; + } + if ($dst_email || !empty($searchData['value'])) { + $dst_email = $dst_email ? $dst_email : $searchData['value']; + $where[] = " (ce2.is_primary = 1 AND ce2.email LIKE '%{$dst_email}%')"; + } + if ($src_postcode || !empty($searchData['value'])) { + $src_postcode = $src_postcode ? $src_postcode : $searchData['value']; + $where[] = " (ca1.is_primary = 1 AND ca1.postal_code LIKE '%{$src_postcode}%')"; + } + if ($dst_postcode || !empty($searchData['value'])) { + $dst_postcode = $dst_postcode ? $dst_postcode : $searchData['value']; + $where[] = " (ca2.is_primary = 1 AND ca2.postal_code LIKE '%{$dst_postcode}%')"; + } + if ($src_street || !empty($searchData['value'])) { + $src_street = $src_street ? $src_street : $searchData['value']; + $where[] = " (ca1.is_primary = 1 AND ca1.street_address LIKE '%{$src_street}%')"; + } + if ($dst_street || !empty($searchData['value'])) { + $dst_street = $dst_street ? $dst_street : $searchData['value']; + $where[] = " (ca2.is_primary = 1 AND ca2.street_address LIKE '%{$dst_street}%')"; + } + if (!empty($searchData['value'])) { + $whereClause = ' ( '.implode(' OR ', $where).' ) '; + } + else { + if (!empty($where)) { + $whereClause = implode(' AND ', $where); + } + } + $whereClause .= $whereClause ? ' AND de.id IS NULL' : ' de.id IS NULL'; - $iFilteredTotal = $iTotal = CRM_Core_BAO_PrevNextCache::getCount($cacheKeyString, $join, $where); - $mainContacts = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, $join, $where, $offset, $rowCount); + if ($selected) { + $whereClause .= ' AND pn.is_selected = 1'; + } + $join .= " LEFT JOIN civicrm_dedupe_exception de ON ( pn.entity_id1 = de.contact_id1 AND pn.entity_id2 = de.contact_id2 )"; + + + $select = array( + 'cc1.contact_type' => 'src_contact_type', + 'cc1.display_name' => 'src_display_name', + 'cc1.contact_sub_type'=> 'src_contact_sub_type', + 'cc2.contact_type' => 'dst_contact_type', + 'cc2.display_name' => 'dst_display_name', + 'cc2.contact_sub_type'=> 'dst_contact_sub_type', + 'ce1.email' => 'src_email', + 'ce2.email' => 'dst_email', + 'ca1.postal_code' => 'src_postcode', + 'ca2.postal_code' => 'dst_postcode', + 'ca1.street_address' => 'src_street', + 'ca2.street_address' => 'dst_street' + ); + + if($select) { + $join .= " INNER JOIN civicrm_contact cc1 ON cc1.id = pn.entity_id1"; + $join .= " INNER JOIN civicrm_contact cc2 ON cc2.id = pn.entity_id2"; + $join .= " LEFT JOIN civicrm_email ce1 ON (ce1.contact_id = pn.entity_id1 AND ce1.is_primary = 1 )"; + $join .= " LEFT JOIN civicrm_email ce2 ON (ce2.contact_id = pn.entity_id2 AND ce2.is_primary = 1 )"; + $join .= " LEFT JOIN civicrm_address ca1 ON (ca1.contact_id = pn.entity_id1 AND ca1.is_primary = 1 )"; + $join .= " LEFT JOIN civicrm_address ca2 ON (ca2.contact_id = pn.entity_id2 AND ca2.is_primary = 1 )"; + } + $iTotal = CRM_Core_BAO_PrevNextCache::getCount($cacheKeyString, $join, $whereClause); + foreach ($_REQUEST['order'] as $orderInfo) { + if (!empty($orderInfo['column'])) { + $orderColumnNumber = $orderInfo['column']; + $dir = $orderInfo['dir']; + } + } + $columnDetails = CRM_Utils_Array::value($orderColumnNumber, $_REQUEST['columns']); + if(!empty($columnDetails)) { + switch ($columnDetails['data']) { + case 'src': + $whereClause .= " ORDER BY cc1.display_name {$dir}"; + break; + case 'src_email': + $whereClause .= " ORDER BY ce1.email {$dir}"; + break; + case 'src_street': + $whereClause .= " ORDER BY ca1.street_address {$dir}"; + break; + case 'src_postcode': + $whereClause .= " ORDER BY ca1.postal_code {$dir}"; + break; + case 'dst': + $whereClause .= " ORDER BY cc2.display_name {$dir}"; + break; + case 'dst_email': + $whereClause .= " ORDER BY ce2.email {$dir}"; + break; + case 'dst_street': + $whereClause .= " ORDER BY ca2.street_address {$dir}"; + break; + case 'dst_postcode': + $whereClause .= " ORDER BY ca2.postal_code {$dir}"; + break; + default: + break; + } + } - foreach ($mainContacts as $mainId => $main) { - $searchRows[$mainId]['src'] = CRM_Utils_System::href($main['srcName'], 'civicrm/contact/view', "reset=1&cid={$main['srcID']}"); - $searchRows[$mainId]['dst'] = CRM_Utils_System::href($main['dstName'], 'civicrm/contact/view', "reset=1&cid={$main['dstID']}"); - $searchRows[$mainId]['weight'] = CRM_Utils_Array::value('weight', $main); + $dupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, $join, $whereClause, $offset, $rowCount, $select); + $iFilteredTotal = CRM_Core_DAO::singleValueQuery("SELECT FOUND_ROWS()"); + + $count = 0; + foreach ($dupePairs as $key => $pairInfo) { + $pair =& $pairInfo['data']; + $srcContactSubType = CRM_Utils_Array::value('src_contact_sub_type', $pairInfo); + $dstContactSubType = CRM_Utils_Array::value('dst_contact_sub_type', $pairInfo); + $srcTypeImage = CRM_Contact_BAO_Contact_Utils::getImage($srcContactSubType ? + $srcContactSubType : $pairInfo['src_contact_type'], + FALSE, + $pair['srcID'] + ); + $dstTypeImage = CRM_Contact_BAO_Contact_Utils::getImage($dstContactSubType ? + $dstContactSubType : $pairInfo['dst_contact_type'], + FALSE, + $pair['dstID'] + ); - if (!empty($main['canMerge'])) { - $mergeParams = "reset=1&cid={$main['srcID']}&oid={$main['dstID']}&action=update&rgid={$rgid}"; + $searchRows[$count]['is_selected'] = $pairInfo['is_selected']; + $searchRows[$count]['is_selected_input'] = ""; + $searchRows[$count]['src_image'] = $srcTypeImage; + $searchRows[$count]['src'] = CRM_Utils_System::href($pair['srcName'], 'civicrm/contact/view', "reset=1&cid={$pair['srcID']}"); + $searchRows[$count]['src_email'] = CRM_Utils_Array::value('src_email', $pairInfo); + $searchRows[$count]['src_street'] = CRM_Utils_Array::value('src_street', $pairInfo); + $searchRows[$count]['src_postcode'] = CRM_Utils_Array::value('src_postcode', $pairInfo); + $searchRows[$count]['dst_image'] = $dstTypeImage; + $searchRows[$count]['dst'] = CRM_Utils_System::href($pair['dstName'], 'civicrm/contact/view', "reset=1&cid={$pair['dstID']}"); + $searchRows[$count]['dst_email'] = CRM_Utils_Array::value('dst_email', $pairInfo); + $searchRows[$count]['dst_street'] = CRM_Utils_Array::value('dst_street', $pairInfo); + $searchRows[$count]['dst_postcode'] = CRM_Utils_Array::value('dst_postcode', $pairInfo); + $searchRows[$count]['conflicts'] = CRM_Utils_Array::value('conflicts', $pair); + $searchRows[$count]['weight'] = CRM_Utils_Array::value('weight', $pair); + + if (!empty($pair['canMerge'])) { + $mergeParams = "reset=1&cid={$pair['srcID']}&oid={$pair['dstID']}&action=update&rgid={$rgid}"; if ($gid) { $mergeParams .= "&gid={$gid}"; } - $searchRows[$mainId]['actions'] = '' . ts('merge') . ''; - $searchRows[$mainId]['actions'] .= "" . ts('not a duplicate') . ""; + $searchRows[$count]['actions'] = CRM_Utils_System::href(ts('merge'), 'civicrm/contact/merge', $mergeParams); + $searchRows[$count]['actions'] .= " | " . ts('not a duplicate') . ""; } else { - $searchRows[$mainId]['actions'] = '' . ts('Insufficient access rights - cannot merge') . ''; + $searchRows[$count]['actions'] = '' . ts('Insufficient access rights - cannot merge') . ''; } + $count++; } - CRM_Utils_System::setHttpHeader('Content-Type', 'application/json'); - echo CRM_Utils_JSON::encodeDataTableSelector($searchRows, $sEcho, $iTotal, $iFilteredTotal, $selectorElements); + header('Content-Type: application/json'); + echo CRM_Utils_JSON::encodeDataTable($searchRows, $iTotal, $iFilteredTotal, $selectorElements); CRM_Utils_System::civiExit(); } @@ -806,6 +947,41 @@ public static function getAddressDisplay() { CRM_Utils_JSON::output($addressVal); } + static function toggleDedupeSelect() { + $rgid = CRM_Utils_Type::escape($_REQUEST['rgid'], 'Integer'); + $gid = CRM_Utils_Type::escape($_REQUEST['gid'], 'Integer'); + $pnid = $_REQUEST['pnid']; + $isSelected = CRM_Utils_Type::escape($_REQUEST['is_selected'], 'Boolean'); + + $contactType = CRM_Core_DAO::getFieldValue('CRM_Dedupe_DAO_RuleGroup', $rgid, 'contact_type'); + $cacheKeyString = "merge $contactType"; + $cacheKeyString .= $rgid ? "_{$rgid}" : '_0'; + $cacheKeyString .= $gid ? "_{$gid}" : '_0'; + + $params = array( + 1 => array($isSelected, 'Boolean'), + 3 => array("$cacheKeyString%", 'String') // using % to address rows with conflicts as well + ); + + //check pnid is_array or integer + $whereClause = NULL; + if (is_array($pnid) && !CRM_Utils_Array::crmIsEmptyArray($pnid)) { + $pnid = implode(', ', $pnid); + $pnid = CRM_Utils_Type::escape($pnid, 'String'); + $whereClause = " id IN ( {$pnid} ) "; + } + else { + $pnid = CRM_Utils_Type::escape($pnid, 'Integer'); + $whereClause = " id = %2"; + $params[2] = array($pnid, 'Integer'); + } + + $sql = "UPDATE civicrm_prevnext_cache SET is_selected = %1 WHERE {$whereClause} AND cacheKey LIKE %3"; + CRM_Core_DAO::executeQuery($sql, $params); + + CRM_Utils_System::civiExit(); + } + /** * Retrieve contact relationships. */ diff --git a/CRM/Contact/Page/DedupeFind.php b/CRM/Contact/Page/DedupeFind.php index f401b0140579..0129be19d57f 100644 --- a/CRM/Contact/Page/DedupeFind.php +++ b/CRM/Contact/Page/DedupeFind.php @@ -94,7 +94,7 @@ public function run() { elseif ($action & CRM_Core_Action::MAP) { // do a batch merge if requested $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive', $this, FALSE, 0); - $result = CRM_Dedupe_Merger::batchMerge($rgid, $gid, 'safe', TRUE, TRUE); + $result = CRM_Dedupe_Merger::batchMerge($rgid, $gid, 'safe', TRUE, 75); $skippedCount = CRM_Utils_Request::retrieve('skipped', 'Positive', $this, FALSE, 0); $skippedCount = $skippedCount + count($result['skipped']); @@ -157,6 +157,9 @@ public function run() { if ($rgid) { $sourceParams .= "&rgid={$rgid}"; } + if ($context == 'conflicts') { + $sourceParams .= "&selected=1"; + } $this->assign('sourceUrl', CRM_Utils_System::url('civicrm/ajax/dedupefind', $sourceParams, FALSE, NULL, FALSE)); @@ -165,11 +168,31 @@ public function run() { $cacheKeyString .= $rgid ? "_{$rgid}" : '_0'; $cacheKeyString .= $gid ? "_{$gid}" : '_0'; + $stats = CRM_Dedupe_Merger::getMergeStatsMsg($cacheKeyString); + if ($stats) { + CRM_Core_Session::setStatus($stats); + // reset so we not displaying same message again + CRM_Dedupe_Merger::resetMergeStats($cacheKeyString); + } $join = "LEFT JOIN civicrm_dedupe_exception de ON ( pn.entity_id1 = de.contact_id1 AND pn.entity_id2 = de.contact_id2 )"; $where = "de.id IS NULL"; + if ($context == 'conflicts') { + $where .= " AND pn.is_selected = 1"; + } $this->_mainContacts = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, $join, $where); if (empty($this->_mainContacts)) { + if ($context == 'conflicts') { + // if the current screen was intended to list only selected contacts, move back to full dupe list + $sourceParams = 'reset=1&action=update'; + if ($gid) { + $sourceParams .= "&gid={$gid}"; + } + if ($rgid) { + $sourceParams .= "&rgid={$rgid}"; + } + CRM_Utils_System::redirect(CRM_Utils_System::url(CRM_Utils_System::currentPath(), $sourceParams)); + } if ($gid) { $foundDupes = $this->get("dedupe_dupes_$gid"); if (!$foundDupes) { diff --git a/CRM/Contact/Page/DedupeMerge.php b/CRM/Contact/Page/DedupeMerge.php new file mode 100644 index 000000000000..a3e393f66ac2 --- /dev/null +++ b/CRM/Contact/Page/DedupeMerge.php @@ -0,0 +1,129 @@ +runAllViaWeb(); + } else { + CRM_Core_Session::setStatus(ts('Nothing to merge.')); + } + + // parent run + return parent::run(); + } + + static function getRunner() { + $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive', $this, FALSE, 0); + $gid = CRM_Utils_Request::retrieve('gid', 'Positive', $this, FALSE, 0); + $action = CRM_Utils_Request::retrieve('action', 'String', CRM_Core_DAO::$_nullObject); + $mode = CRM_Utils_Request::retrieve('mode', 'String', CRM_Core_DAO::$_nullObject, FALSE, 'safe'); + + $contactType = CRM_Core_DAO::getFieldValue('CRM_Dedupe_DAO_RuleGroup', $rgid, 'contact_type'); + $cacheKeyString = "merge {$contactType}"; + $cacheKeyString .= $rgid ? "_{$rgid}" : '_0'; + $cacheKeyString .= $gid ? "_{$gid}" : '_0'; + + // Setup the Queue + $queue = CRM_Queue_Service::singleton()->create(array( + 'name' => $cacheKeyString, + 'type' => 'Sql', + 'reset' => TRUE, + )); + + $where = NULL; + if ($action == CRM_Core_Action::MAP) { + $where = "pn.is_selected = 1"; + $isSelected = 1; + } else { + // else merge all (2) + $isSelected = 2; + } + + $urlQry = "reset=1&action=update&rgid={$rgid}"; + $urlQry = $gid ? ($urlQry . "&gid={$gid}") : $urlQry; + + $total = CRM_Core_BAO_PrevNextCache::getCount($cacheKeyString, NULL, $where); + if ($total <= 0) { + // Nothing to do. + CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry)); + } + + // reset merge stats, so we compute new stats + CRM_Dedupe_Merger::resetMergeStats($cacheKeyString); + + for ($i = 1; $i <= ceil($total/self::BATCHLIMIT); $i++) { + $task = new CRM_Queue_Task( + array ('CRM_Contact_Page_DedupeMerge', 'callBatchMerge'), + array($rgid, $gid, $mode, TRUE, self::BATCHLIMIT, $isSelected), + "Processed " . $i*self::BATCHLIMIT . " pair of duplicates out of " . $total + ); + + // Add the Task to the Queue + $queue->createItem($task); + } + + // Setup the Runner + $urlQry .= "&context=conflicts"; + $runner = new CRM_Queue_Runner(array( + 'title' => ts('Merging Duplicates..'), + 'queue' => $queue, + 'errorMode'=> CRM_Queue_Runner::ERROR_ABORT, + 'onEndUrl' => CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry, TRUE, NULL, FALSE), + )); + + return $runner; + } + + /** + * Collect Mailchimp data into temporary working table. + */ + static function callBatchMerge(CRM_Queue_TaskContext $ctx, $rgid, $gid = NULL, $mode = 'safe', $autoFlip = TRUE, $batchLimit = 1, $isSelected = 2) { + $result = CRM_Dedupe_Merger::batchMerge($rgid, $gid, $mode, $autoFlip, $batchLimit, $isSelected); + + return CRM_Queue_Task::TASK_SUCCESS; + } +} + diff --git a/CRM/Core/BAO/PrevNextCache.php b/CRM/Core/BAO/PrevNextCache.php index b9a3e8ea1474..90bc832f07b1 100644 --- a/CRM/Core/BAO/PrevNextCache.php +++ b/CRM/Core/BAO/PrevNextCache.php @@ -153,12 +153,48 @@ public static function deletePair($id1, $id2, $cacheKey = NULL, $isViceVersa = F if (isset($cacheKey)) { $sql .= " AND cacheKey LIKE %4"; - $params[4] = array("{$cacheKey}%", 'String'); + $params[4] = array("{$cacheKey}%", 'String'); // used % to address any row with conflict-cacheKey e.g "merge Individual_8_0_conflicts" } CRM_Core_DAO::executeQuery($sql, $params); } + static function markConflict($id1, $id2, $cacheKey, $conflicts) { + if (empty($cacheKey) || empty($conflicts)) { + return FALSE; + } + + $sql = "SELECT pn.* + FROM civicrm_prevnext_cache pn + WHERE + ((pn.entity_id1 = %1 AND pn.entity_id2 = %2) OR (pn.entity_id1 = %2 AND pn.entity_id2 = %1)) AND + (cacheKey = %3 OR cacheKey = %4)"; + $params = array( + 1 => array($id1, 'Integer'), + 2 => array($id2, 'Integer'), + 3 => array("{$cacheKey}", 'String'), + 4 => array("{$cacheKey}_conflicts", 'String'), + ); + $pncFind = CRM_Core_DAO::executeQuery($sql, $params); + + while ($pncFind->fetch()) { + $data = $pncFind->data; + if (!empty($data)) { + $data = unserialize($data); + $data['conflicts'] = implode(",", array_keys($conflicts)); + + $pncUp = new CRM_Core_DAO_PrevNextCache(); + $pncUp->id = $pncFind->id; + if ($pncUp->find(TRUE)) { + $pncUp->data = serialize($data); + $pncUp->cacheKey = "{$cacheKey}_conflicts"; + $pncUp->save(); + } + } + } + return TRUE; + } + /** * @param $cacheKey * @param NULL $join @@ -168,14 +204,25 @@ public static function deletePair($id1, $id2, $cacheKey = NULL, $isViceVersa = F * * @return array */ - public static function retrieve($cacheKey, $join = NULL, $where = NULL, $offset = 0, $rowCount = 0) { + public static function retrieve($cacheKey, $join = NULL, $where = NULL, $offset = 0, $rowCount = 0, $select = array()) { + $selectString = 'pn.*'; + if(!empty($select)) { + $aliasArray = array(); + foreach ($select as $column => $alias) { + $aliasArray[] = $column.' as '.$alias; + } + $selectString .= " , ".implode(' , ', $aliasArray); + } $query = " -SELECT data +SELECT SQL_CALC_FOUND_ROWS {$selectString} FROM civicrm_prevnext_cache pn {$join} -WHERE cacheKey = %1 +WHERE (pn.cacheKey = %1 OR pn.cacheKey = %2) "; - $params = array(1 => array($cacheKey, 'String')); + $params = array( + 1 => array($cacheKey, 'String'), + 2 => array("{$cacheKey}_conflicts", 'String') + ); if ($where) { $query .= " AND {$where}"; @@ -190,19 +237,36 @@ public static function retrieve($cacheKey, $join = NULL, $where = NULL, $offset $dao = CRM_Core_DAO::executeQuery($query, $params); - $main = array(); + $main = array(); + $count = 0; while ($dao->fetch()) { if (self::is_serialized($dao->data)) { - $main[] = unserialize($dao->data); + $main[$count] = unserialize($dao->data); } else { - $main[] = $dao->data; + $main[$count] = $dao->data; + } + + if (!empty($select)) { + $extraData = array(); + foreach ($select as $dfield => $sfield) { + $extraData[$sfield] = $dao->$sfield; + } + $main[$count] = array( + 'prevnext_id' => $dao->id, + 'is_selected' => $dao->is_selected, + 'entity_id1' => $dao->entity_id1, + 'entity_id2' => $dao->entity_id2, + 'data' => $main[$count], + ); + $main[$count] = array_merge($main[$count], $extraData); } + $count++; } return $main; } - + /** * @param $string * @@ -235,13 +299,16 @@ public static function getCount($cacheKey, $join = NULL, $where = NULL, $op = "= $query = " SELECT COUNT(*) FROM civicrm_prevnext_cache pn {$join} -WHERE cacheKey $op %1 +WHERE (pn.cacheKey $op %1 OR pn.cacheKey $op %2) "; if ($where) { $query .= " AND {$where}"; } - $params = array(1 => array($cacheKey, 'String')); + $params = array( + 1 => array($cacheKey, 'String'), + 2 => array("{$cacheKey}_conflicts", 'String') + ); return (int) CRM_Core_DAO::singleValueQuery($query, $params, TRUE, FALSE); } diff --git a/CRM/Core/xml/Menu/Contact.xml b/CRM/Core/xml/Menu/Contact.xml index 729452ed1ace..b1ddb7d330ec 100644 --- a/CRM/Core/xml/Menu/Contact.xml +++ b/CRM/Core/xml/Menu/Contact.xml @@ -355,6 +355,12 @@ CRM_Contact_Page_AJAX::getDedupes merge duplicate contacts + + civicrm/contact/dedupemerge + Batch Merge Duplicate Contacts + CRM_Contact_Page_DedupeMerge + merge duplicate contacts + civicrm/dedupe/exception Dedupe Exceptions @@ -378,6 +384,11 @@ CRM_Contact_Page_AJAX::selectUnselectContacts access CiviCRM + + civicrm/ajax/toggleDedupeSelect + CRM_Contact_Page_AJAX::toggleDedupeSelect + merge duplicate contacts + civicrm/activity/sms/add action=add diff --git a/CRM/Dedupe/Merger.php b/CRM/Dedupe/Merger.php index 119164cbbb17..1d25f0841038 100644 --- a/CRM/Dedupe/Merger.php +++ b/CRM/Dedupe/Merger.php @@ -569,7 +569,7 @@ public static function findDifferences($main, $other) { * * @return array|bool */ - public static function batchMerge($rgid, $gid = NULL, $mode = 'safe', $autoFlip = TRUE, $redirectForPerformance = FALSE) { + public static function batchMerge($rgid, $gid = NULL, $mode = 'safe', $autoFlip = TRUE, $batchLimit = 1, $isSelected = 2) { $contactType = CRM_Core_DAO::getFieldValue('CRM_Dedupe_DAO_RuleGroup', $rgid, 'contact_type'); $cacheKeyString = "merge {$contactType}"; $cacheKeyString .= $rgid ? "_{$rgid}" : '_0'; @@ -577,11 +577,16 @@ public static function batchMerge($rgid, $gid = NULL, $mode = 'safe', $autoFlip $join = "LEFT JOIN civicrm_dedupe_exception de ON ( pn.entity_id1 = de.contact_id1 AND pn.entity_id2 = de.contact_id2 )"; - $limit = $redirectForPerformance ? 75 : 1; - $where = "de.id IS NULL LIMIT {$limit}"; + $where = "de.id IS NULL"; + if ($isSelected === 0 || $isSelected === 1) { + $where .= " AND pn.is_selected = {$isSelected}"; + }// else consider all dupe pairs + $where .= " LIMIT {$batchLimit}"; + + $redirectForPerformance = ($batchLimit > 1) ? TRUE : FALSE; $dupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, $join, $where); - if (empty($dupePairs) && !$redirectForPerformance) { + if (empty($dupePairs) && !$redirectForPerformance && $isSelected == 2) { // If we haven't found any dupes, probably cache is empty. // Try filling cache and give another try. CRM_Core_BAO_PrevNextCache::refillCache($rgid, $gid, $cacheKeyString); @@ -596,6 +601,65 @@ public static function batchMerge($rgid, $gid = NULL, $mode = 'safe', $autoFlip return CRM_Dedupe_Merger::merge($dupePairs, $cacheParams, $mode, $autoFlip, $redirectForPerformance); } + static function updateMergeStats($cacheKeyString, $result = array()) { + // gather latest stats + $merged = count($result['merged']); + $skipped = count($result['skipped']); + + if ($merged <= 0 && $skipped <= 0) { + return; + } + + // get previous stats + $previousStats = CRM_Core_BAO_PrevNextCache::retrieve("{$cacheKeyString}_stats"); + if (!empty($previousStats)) { + if ($previousStats[0]['merged']) { + $merged = $merged + $previousStats[0]['merged']; + } + if ($previousStats[0]['skipped']) { + $skipped = $skipped + $previousStats[0]['skipped']; + } + } + + // delete old stats + CRM_Dedupe_Merger::resetMergeStats($cacheKeyString); + + // store the updated stats + $data = array( + 'merged' => $merged, + 'skipped' => $skipped, + ); + $data = CRM_Core_DAO::escapeString(serialize($data)); + + $values = array(); + $values[] = " ( 'civicrm_contact', 0, 0, '{$cacheKeyString}_stats', '$data' ) "; + CRM_Core_BAO_PrevNextCache::setItem($values); + } + + static function resetMergeStats($cacheKeyString) { + return CRM_Core_BAO_PrevNextCache::deleteItem(NULL, "{$cacheKeyString}_stats"); + } + + static function getMergeStats($cacheKeyString) { + $stats = CRM_Core_BAO_PrevNextCache::retrieve("{$cacheKeyString}_stats"); + if (!empty($stats)) { + $stats = $stats[0]; + } + return $stats; + } + + static function getMergeStatsMsg($cacheKeyString) { + $msg = ''; + $stats = CRM_Dedupe_Merger::getMergeStats($cacheKeyString); + if (!empty($stats['merged'])) { + $msg = "{$stats['merged']} " . ts(' Contact(s) were merged. '); + } + if (!empty($stats['skipped'])) { + $msg .= $stats['skipped'] . ts(' Contact(s) were skipped.'); + } + return $msg; + } + /** * Merge given set of contacts. Performs core operation. * @@ -656,7 +720,8 @@ public static function merge($dupePairs = array(), $cacheParams = array(), $mode $migrationInfo['rows'] = &$rowsElementsAndInfo['rows']; // go ahead with merge if there is no conflict - if (!CRM_Dedupe_Merger::skipMerge($mainId, $otherId, $migrationInfo, $mode)) { + $conflicts = array(); + if (!CRM_Dedupe_Merger::skipMerge($mainId, $otherId, $migrationInfo, $mode, $conflicts)) { CRM_Dedupe_Merger::moveAllBelongings($mainId, $otherId, $migrationInfo); $resultStats['merged'][] = array('main_id' => $mainId, 'other_id' => $otherId); } @@ -664,10 +729,14 @@ public static function merge($dupePairs = array(), $cacheParams = array(), $mode $resultStats['skipped'][] = array('main_id' => $mainId, 'other_id' => $otherId); } - // delete entry from PrevNextCache table so we don't consider the pair next time - // pair may have been flipped, so make sure we delete using both orders - CRM_Core_BAO_PrevNextCache::deletePair($mainId, $otherId, $cacheKeyString); - CRM_Core_BAO_PrevNextCache::deletePair($otherId, $mainId, $cacheKeyString); + // store any conflicts + if (!empty($conflicts)) { + CRM_Core_BAO_PrevNextCache::markConflict($mainId, $otherId, $cacheKeyString, $conflicts); + } else { + // delete entry from PrevNextCache table so we don't consider the pair next time + // pair may have been flipped, so make sure we delete using both orders + CRM_Core_BAO_PrevNextCache::deletePair($mainId, $otherId, $cacheKeyString, TRUE); + } CRM_Core_DAO::freeResult(); unset($rowsElementsAndInfo, $migrationInfo); @@ -685,6 +754,8 @@ public static function merge($dupePairs = array(), $cacheParams = array(), $mode unset($dupePairs); } } + + CRM_Dedupe_Merger::updateMergeStats($cacheKeyString, $resultStats); return $resultStats; } @@ -705,8 +776,8 @@ public static function merge($dupePairs = array(), $cacheParams = array(), $mode * * @return bool */ - public static function skipMerge($mainId, $otherId, &$migrationInfo, $mode = 'safe') { - $conflicts = array(); + public static function skipMerge($mainId, $otherId, &$migrationInfo, $mode = 'safe', &$conflicts = array()) { + //$conflicts = array(); $migrationData = array( 'old_migration_info' => $migrationInfo, 'mode' => $mode, @@ -787,11 +858,13 @@ public static function skipMerge($mainId, $otherId, &$migrationInfo, $mode = 'sa $migrationData['fields_in_conflict'] = $conflicts; CRM_Utils_Hook::merge('batch', $migrationData, $mainId, $otherId); $conflicts = $migrationData['fields_in_conflict']; + // allow hook to override / manipulate migrationInfo as well + $migrationInfo = $migrationData['old_migration_info']; if (!empty($conflicts)) { foreach ($conflicts as $key => $val) { if ($val === NULL and $mode == 'safe') { - // un-resolved conflicts still present. Lets skip this merge. + // un-resolved conflicts still present. Lets skip this merge after saving the conflict / reason. return TRUE; } else { diff --git a/CRM/Utils/JSON.php b/CRM/Utils/JSON.php index 01e0b60bbf32..169ac6292443 100644 --- a/CRM/Utils/JSON.php +++ b/CRM/Utils/JSON.php @@ -94,4 +94,37 @@ public static function encodeDataTableSelector($params, $sEcho, $iTotal, $iFilte return $sOutput; } + /** + * This function is used to encode data for new dataTable plugin v1.10 and greater + * @return string + * + */ + static function encodeDataTable($params, $iTotal, $iFilteredTotal, $selectorElements) { + $sOutput = '{'; + $sOutput .= '"recordsTotal": ' . $iTotal . ', '; + $sOutput .= '"recordsFiltered": ' . $iFilteredTotal . ', '; + $sOutput .= '"data": [ '; + foreach ($params as $key => $value) { + $addcomma = FALSE; + $sOutput .= "{"; + foreach ($selectorElements as $element) { + if ($addcomma) { + $sOutput .= ","; + } + //CRM-7130 --lets addslashes to only double quotes, + //since we are using it to quote the field value. + //str_replace helps to provide a break for new-line + $sOutput .= '"' . $element . '":' . '"' . addcslashes(str_replace(array("\r\n", "\n", "\r"), '
', $value[$element]), '"\\') . '"'; + + //remove extra spaces and tab character that breaks dataTable CRM-12551 + $sOutput = preg_replace("/\s+/", " ", $sOutput); + $addcomma = TRUE; + } + $sOutput .= "},"; + } + $sOutput = substr_replace($sOutput, "", -1); + $sOutput .= '] }'; + + return $sOutput; + } } diff --git a/templates/CRM/Contact/Page/DedupeFind.tpl b/templates/CRM/Contact/Page/DedupeFind.tpl index bd0735931398..76f380bdbb52 100644 --- a/templates/CRM/Contact/Page/DedupeFind.tpl +++ b/templates/CRM/Contact/Page/DedupeFind.tpl @@ -25,12 +25,88 @@ *} {if $action eq 2 || $action eq 16}
- +
+
+ {ts}Filter Contacts{/ts} +
+
+
+ + + + + + + + + + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ Show / Hide columns: + + + + + + + + +

+ + + + + - + + + + + + + + + + + + + + + + + +
{ts}Contact{/ts} 1{ts}Contact{/ts} 2 ({ts}Duplicate{/ts}){ts}Threshold{/ts} 
 {ts}Contact{/ts} 1 {ts}Contact{/ts} 2 ({ts}Duplicate{/ts}){ts}Email{/ts} 1{ts}Email{/ts} 2 ({ts}Duplicate{/ts}){ts}Street Address{/ts} 1{ts}Street Address{/ts} 2 ({ts}Duplicate{/ts}){ts}Postcode{/ts} 1{ts}Postcode{/ts} 2 ({ts}Duplicate{/ts}){ts}Conflicts{/ts}{ts}Threshold{/ts} 
- {include file="CRM/common/jsortable.tpl" sourceUrl=$sourceUrl useAjax=1 } {if $cid} @@ -51,6 +127,27 @@ {if $context eq 'search'} {crmButton href=$backURL icon="close"}{ts}Done{/ts}{/crmButton} +{elseif $context eq 'conflicts'} + {if $gid} + {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&gid=`$gid`&action=map&mode=aggressive" a=1}{/capture} + {else} + {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&action=map&mode=aggressive" a=1}{/capture} + {/if} + {ts}Force Merge Selected Duplicates{/ts} + + {if $gid} + {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&gid=`$gid`&mode=aggressive" a=1}{/capture} + {else} + {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&mode=aggressive" a=1}{/capture} + {/if} + {ts}Force Merge All Duplicates{/ts} + + {if $gid} + {capture assign=backURL}{crmURL p="civicrm/contact/dedupefind" q="reset=1&action=update&rgid=`$rgid`&gid=`$gid`" a=1}{/capture} + {else} + {capture assign=backURL}{crmURL p="civicrm/contact/dedupefind" q="reset=1&action=update&rgid=`$rgid`" a=1}{/capture} + {/if} + {ts}List All Duplicates{/ts} {else} {if $gid} {capture assign=backURL}{crmURL p="civicrm/contact/dedupefind" q="reset=1&rgid=`$rgid`&gid=`$gid`&action=renew" a=1}{/capture} @@ -62,13 +159,18 @@ {if $gid} - {capture assign=backURL}{crmURL p="civicrm/contact/dedupefind" q="reset=1&rgid=`$rgid`&gid=`$gid`&action=map" a=1}{/capture} + {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&gid=`$gid`&action=map" a=1}{/capture} {else} - {capture assign=backURL}{crmURL p="civicrm/contact/dedupefind" q="reset=1&rgid=`$rgid`&action=map" a=1}{/capture} + {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&action=map" a=1}{/capture} {/if} - -
{ts}Batch Merge Duplicates{/ts}
-
+
{ts}Batch Merge Selected Duplicates{/ts}
+ + {if $gid} + {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&gid=`$gid`" a=1}{/capture} + {else} + {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`" a=1}{/capture} + {/if} +
{ts}Batch Merge All Duplicates{/ts}
{capture assign=backURL}{crmURL p="civicrm/contact/deduperules" q="reset=1" a=1}{/capture} @@ -84,6 +186,148 @@ {include file='CRM/common/dedupe.tpl'} {literal} {/literal} diff --git a/templates/CRM/Contact/Page/DedupeMerge.tpl b/templates/CRM/Contact/Page/DedupeMerge.tpl new file mode 100644 index 000000000000..a270e4f2d082 --- /dev/null +++ b/templates/CRM/Contact/Page/DedupeMerge.tpl @@ -0,0 +1,25 @@ +{* + +--------------------------------------------------------------------+ + | CiviCRM version 4.5 | + +--------------------------------------------------------------------+ + | Copyright CiviCRM LLC (c) 2004-2014 | + +--------------------------------------------------------------------+ + | This file is a part of CiviCRM. | + | | + | CiviCRM is free software; you can copy, modify, and distribute it | + | under the terms of the GNU Affero General Public License | + | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. | + | | + | CiviCRM is distributed in the hope that it will be useful, but | + | WITHOUT ANY WARRANTY; without even the implied warranty of | + | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | + | See the GNU Affero General Public License for more details. | + | | + | You should have received a copy of the GNU Affero General Public | + | License and the CiviCRM Licensing Exception along | + | with this program; if not, contact CiviCRM LLC | + | at info[AT]civicrm[DOT]org. If you have questions about the | + | GNU Affero General Public License or the licensing of CiviCRM, | + | see the CiviCRM license FAQ at http://civicrm.org/licensing | + +--------------------------------------------------------------------+ +*} From fd630ef95ece7c8f8005ddaf0e0e8cc391c1be43 Mon Sep 17 00:00:00 2001 From: deepak-srivastava Date: Tue, 21 Jul 2015 15:58:46 +0100 Subject: [PATCH 02/14] 1. flipping dupe pairs for a row or selections. 2. store meaningful - conflict labels & values in conflict. 3. layout for conflict column - new line after every conflict info. 4. 'vs' formatting. 5. Add permission for forced merges. 6. Fix for: 'One of parameters (value: null) is not of the type Money/Timestamp' errors during batch merges. 7. allow hooks to decide if to skip merges even in aggressive mode. 8. for conflicts screen provide option for safe merges as well --- CRM/Contact/Page/AJAX.php | 35 +++++++-- CRM/Contact/Page/DedupeMerge.php | 10 ++- CRM/Core/BAO/CustomValueTable.php | 12 ++- CRM/Core/BAO/PrevNextCache.php | 2 +- CRM/Core/Permission.php | 4 + CRM/Core/xml/Menu/Contact.xml | 5 ++ CRM/Dedupe/Merger.php | 30 ++++---- templates/CRM/Contact/Page/DedupeFind.tpl | 89 +++++++++++++++++++---- 8 files changed, 143 insertions(+), 44 deletions(-) diff --git a/CRM/Contact/Page/AJAX.php b/CRM/Contact/Page/AJAX.php index e597802c87a4..dc7ea9185156 100644 --- a/CRM/Contact/Page/AJAX.php +++ b/CRM/Contact/Page/AJAX.php @@ -816,37 +816,38 @@ static function getDedupes() { $srcTypeImage = CRM_Contact_BAO_Contact_Utils::getImage($srcContactSubType ? $srcContactSubType : $pairInfo['src_contact_type'], FALSE, - $pair['srcID'] + $pairInfo['entity_id1'] ); $dstTypeImage = CRM_Contact_BAO_Contact_Utils::getImage($dstContactSubType ? $dstContactSubType : $pairInfo['dst_contact_type'], FALSE, - $pair['dstID'] + $pairInfo['entity_id2'] ); $searchRows[$count]['is_selected'] = $pairInfo['is_selected']; $searchRows[$count]['is_selected_input'] = ""; $searchRows[$count]['src_image'] = $srcTypeImage; - $searchRows[$count]['src'] = CRM_Utils_System::href($pair['srcName'], 'civicrm/contact/view', "reset=1&cid={$pair['srcID']}"); + $searchRows[$count]['src'] = CRM_Utils_System::href($pair['srcName'], 'civicrm/contact/view', "reset=1&cid={$pairInfo['entity_id1']}"); $searchRows[$count]['src_email'] = CRM_Utils_Array::value('src_email', $pairInfo); $searchRows[$count]['src_street'] = CRM_Utils_Array::value('src_street', $pairInfo); $searchRows[$count]['src_postcode'] = CRM_Utils_Array::value('src_postcode', $pairInfo); $searchRows[$count]['dst_image'] = $dstTypeImage; - $searchRows[$count]['dst'] = CRM_Utils_System::href($pair['dstName'], 'civicrm/contact/view', "reset=1&cid={$pair['dstID']}"); + $searchRows[$count]['dst'] = CRM_Utils_System::href($pair['dstName'], 'civicrm/contact/view', "reset=1&cid={$pairInfo['entity_id2']}"); $searchRows[$count]['dst_email'] = CRM_Utils_Array::value('dst_email', $pairInfo); $searchRows[$count]['dst_street'] = CRM_Utils_Array::value('dst_street', $pairInfo); $searchRows[$count]['dst_postcode'] = CRM_Utils_Array::value('dst_postcode', $pairInfo); $searchRows[$count]['conflicts'] = CRM_Utils_Array::value('conflicts', $pair); $searchRows[$count]['weight'] = CRM_Utils_Array::value('weight', $pair); - if (!empty($pair['canMerge'])) { - $mergeParams = "reset=1&cid={$pair['srcID']}&oid={$pair['dstID']}&action=update&rgid={$rgid}"; + if (!empty($pairInfo['data']['canMerge'])) { + $mergeParams = "reset=1&cid={$pairInfo['entity_id1']}&oid={$pairInfo['entity_id2']}&action=update&rgid={$rgid}"; if ($gid) { $mergeParams .= "&gid={$gid}"; } + $searchRows[$count]['actions'] = "" . ts('flip') . " | "; $searchRows[$count]['actions'] = CRM_Utils_System::href(ts('merge'), 'civicrm/contact/merge', $mergeParams); - $searchRows[$count]['actions'] .= " | " . ts('not a duplicate') . ""; + $searchRows[$count]['actions'] .= " | " . ts('not a duplicate') . ""; } else { $searchRows[$count]['actions'] = '' . ts('Insufficient access rights - cannot merge') . ''; @@ -882,6 +883,26 @@ public static function paperSize() { CRM_Utils_JSON::output($paperSize); } + static function flipDupePairs($prevNextId = NULL) { + if (!$prevNextId) { + $prevNextId = $_REQUEST['pnid']; + } + $query = " + UPDATE civicrm_prevnext_cache cpc + INNER JOIN civicrm_prevnext_cache old on cpc.id = old.id + SET cpc.entity_id1 = cpc.entity_id2, cpc.entity_id2 = old.entity_id1 "; + if (is_array($prevNextId) && !CRM_Utils_Array::crmIsEmptyArray($prevNextId)) { + $prevNextId = implode(', ', $prevNextId); + $prevNextId = CRM_Utils_Type::escape($prevNextId, 'String'); + $query .= "WHERE cpc.id IN ({$prevNextId}) AND cpc.is_selected = 1"; + } else { + $prevNextId = CRM_Utils_Type::escape($prevNextId, 'Positive'); + $query .= "WHERE cpc.id = $prevNextId"; + } + CRM_Core_DAO::executeQuery($query); + CRM_Utils_JSON::output(); + } + /** * Used to store selected contacts across multiple pages in advanced search. */ diff --git a/CRM/Contact/Page/DedupeMerge.php b/CRM/Contact/Page/DedupeMerge.php index a3e393f66ac2..97970ea44a8f 100644 --- a/CRM/Contact/Page/DedupeMerge.php +++ b/CRM/Contact/Page/DedupeMerge.php @@ -66,6 +66,13 @@ static function getRunner() { $cacheKeyString .= $rgid ? "_{$rgid}" : '_0'; $cacheKeyString .= $gid ? "_{$gid}" : '_0'; + $urlQry = "reset=1&action=update&rgid={$rgid}"; + $urlQry = $gid ? ($urlQry . "&gid={$gid}") : $urlQry; + + if ($mode == 'aggressive' && !CRM_Core_Permission::check('force merge duplicate contacts')) { + CRM_Core_Session::setStatus(ts('You do not have permission to force merge duplicate contact records'), ts('Permission Denied'), 'error'); + CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry)); + } // Setup the Queue $queue = CRM_Queue_Service::singleton()->create(array( 'name' => $cacheKeyString, @@ -82,9 +89,6 @@ static function getRunner() { $isSelected = 2; } - $urlQry = "reset=1&action=update&rgid={$rgid}"; - $urlQry = $gid ? ($urlQry . "&gid={$gid}") : $urlQry; - $total = CRM_Core_BAO_PrevNextCache::getCount($cacheKeyString, NULL, $where); if ($total <= 0) { // Nothing to do. diff --git a/CRM/Core/BAO/CustomValueTable.php b/CRM/Core/BAO/CustomValueTable.php index 10c377872758..d2da71444e39 100644 --- a/CRM/Core/BAO/CustomValueTable.php +++ b/CRM/Core/BAO/CustomValueTable.php @@ -210,9 +210,15 @@ public static function create(&$customParams) { default: break; } - $set[$field['column_name']] = "%{$count}"; - $params[$count] = array($value, $type); - $count++; + if (strtolower($value) === "null") { + // when unsetting a value to null, we don't need to validate the type + // https://projectllr.atlassian.net/browse/VGQBMP-20 + $set[$field['column_name']] = $value; + } else { + $set[$field['column_name']] = "%{$count}"; + $params[$count] = array($value, $type); + $count++; + } } if (!empty($set)) { diff --git a/CRM/Core/BAO/PrevNextCache.php b/CRM/Core/BAO/PrevNextCache.php index 90bc832f07b1..74d9995a33f2 100644 --- a/CRM/Core/BAO/PrevNextCache.php +++ b/CRM/Core/BAO/PrevNextCache.php @@ -181,7 +181,7 @@ static function markConflict($id1, $id2, $cacheKey, $conflicts) { $data = $pncFind->data; if (!empty($data)) { $data = unserialize($data); - $data['conflicts'] = implode(",", array_keys($conflicts)); + $data['conflicts'] = implode(",", array_values($conflicts)); $pncUp = new CRM_Core_DAO_PrevNextCache(); $pncUp->id = $pncFind->id; diff --git a/CRM/Core/Permission.php b/CRM/Core/Permission.php index 9d983b921f05..0e99a8d1b6ee 100644 --- a/CRM/Core/Permission.php +++ b/CRM/Core/Permission.php @@ -771,6 +771,10 @@ public static function getCorePermissions($descriptions = FALSE) { $prefix . ts('merge duplicate contacts'), ts('Delete Contacts must also be granted in order for this to work.'), ), + 'force merge duplicate contacts' => array( + $prefix . ts('force merge duplicate contacts'), + ts('Delete Contacts must also be granted in order for this to work.'), + ), 'view debug output' => array( $prefix . ts('view debug output'), ts('View results of debug and backtrace'), diff --git a/CRM/Core/xml/Menu/Contact.xml b/CRM/Core/xml/Menu/Contact.xml index b1ddb7d330ec..3da74d907009 100644 --- a/CRM/Core/xml/Menu/Contact.xml +++ b/CRM/Core/xml/Menu/Contact.xml @@ -389,6 +389,11 @@ CRM_Contact_Page_AJAX::toggleDedupeSelectmerge duplicate contacts + + civicrm/ajax/flipDupePairs + CRM_Contact_Page_AJAX::flipDupePairs + merge duplicate contacts + civicrm/activity/sms/add action=add diff --git a/CRM/Dedupe/Merger.php b/CRM/Dedupe/Merger.php index 1d25f0841038..2f5e5d7de7d0 100644 --- a/CRM/Dedupe/Merger.php +++ b/CRM/Dedupe/Merger.php @@ -731,6 +731,9 @@ public static function merge($dupePairs = array(), $cacheParams = array(), $mode // store any conflicts if (!empty($conflicts)) { + foreach ($conflicts as $key => $dnc) { + $conflicts[$key] = "{$migrationInfo['rows'][$key]['title']}: '{$migrationInfo['rows'][$key]['main']}' vs. '{$migrationInfo['rows'][$key]['other']}'"; + } CRM_Core_BAO_PrevNextCache::markConflict($mainId, $otherId, $cacheKeyString, $conflicts); } else { // delete entry from PrevNextCache table so we don't consider the pair next time @@ -798,15 +801,10 @@ public static function skipMerge($mainId, $otherId, &$migrationInfo, $mode = 'sa // particular field or not if (!empty($migrationInfo['rows'][$key]['main'])) { // if main also has a value its a conflict - if ($mode == 'safe') { - // note it down & lets wait for response from the hook. - // For no response skip this merge - $conflicts[$key] = NULL; - } - elseif ($mode == 'aggressive') { - // let the main-field be overwritten - continue; - } + + // note it down & lets wait for response from the hook. + // For no response $mode will decide if to skip this merge + $conflicts[$key] = NULL; } } elseif (substr($key, 0, 14) == 'move_location_' and $val != NULL) { @@ -831,16 +829,11 @@ public static function skipMerge($mainId, $otherId, &$migrationInfo, $mode = 'sa // try insert address at new available loc-type $migrationInfo['location'][$fieldName][$fieldCount]['locTypeId'] = $newTypeId; } - elseif ($mode == 'safe') { + else { // note it down & lets wait for response from the hook. - // For no response skip this merge + // For no response $mode will decide if to skip this merge $conflicts[$key] = NULL; } - elseif ($mode == 'aggressive') { - // let the loc-type-id be same as that of other-contact & go ahead - // with merge assuming aggressive mode - continue; - } } } elseif ($migrationInfo['rows'][$key]['main'] == $migrationInfo['rows'][$key]['other']) { @@ -856,6 +849,7 @@ public static function skipMerge($mainId, $otherId, &$migrationInfo, $mode = 'sa // merge happens with new values filled in here. For a particular field / row not to be merged // field should be unset from fields_in_conflict. $migrationData['fields_in_conflict'] = $conflicts; + $migrationData['merge_mode'] = $mode; CRM_Utils_Hook::merge('batch', $migrationData, $mainId, $otherId); $conflicts = $migrationData['fields_in_conflict']; // allow hook to override / manipulate migrationInfo as well @@ -872,6 +866,10 @@ public static function skipMerge($mainId, $otherId, &$migrationInfo, $mode = 'sa $migrationInfo[$key] = $val; } } + // if there are conflicts and mode is aggressive, allow hooks to decide if to skip merges + if (array_key_exists('skip_merge', $migrationData)) { + return (bool) $migrationData['skip_merge']; + } } return FALSE; } diff --git a/templates/CRM/Contact/Page/DedupeFind.tpl b/templates/CRM/Contact/Page/DedupeFind.tpl index 76f380bdbb52..c3e11010c5ad 100644 --- a/templates/CRM/Contact/Page/DedupeFind.tpl +++ b/templates/CRM/Contact/Page/DedupeFind.tpl @@ -128,19 +128,21 @@ {if $context eq 'search'} {crmButton href=$backURL icon="close"}{ts}Done{/ts}{/crmButton} {elseif $context eq 'conflicts'} - {if $gid} - {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&gid=`$gid`&action=map&mode=aggressive" a=1}{/capture} - {else} - {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&action=map&mode=aggressive" a=1}{/capture} - {/if} - {ts}Force Merge Selected Duplicates{/ts} + {if call_user_func(array('CRM_Core_Permission','check'), 'force merge duplicate contacts')} + {if $gid} + {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&gid=`$gid`&action=map&mode=aggressive" a=1}{/capture} + {else} + {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&action=map&mode=aggressive" a=1}{/capture} + {/if} + {ts}Force Merge Selected Duplicates{/ts} - {if $gid} - {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&gid=`$gid`&mode=aggressive" a=1}{/capture} - {else} - {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&mode=aggressive" a=1}{/capture} + {if $gid} + {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&gid=`$gid`&action=map" a=1}{/capture} + {else} + {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&action=map" a=1}{/capture} + {/if} + {ts}Safe Merge Selected Duplicates{/ts} {/if} - {ts}Force Merge All Duplicates{/ts} {if $gid} {capture assign=backURL}{crmURL p="civicrm/contact/dedupefind" q="reset=1&action=update&rgid=`$rgid`&gid=`$gid`" a=1}{/capture} @@ -172,6 +174,8 @@ {/if}
{ts}Batch Merge All Duplicates{/ts}
+ {ts}Flip Selected Duplicates{/ts} + {capture assign=backURL}{crmURL p="civicrm/contact/deduperules" q="reset=1" a=1}{/capture}
{ts}Done{/ts}
@@ -208,7 +212,10 @@ CRM.$(function($) { {data: "dst_street"}, {data: "src_postcode"}, {data: "dst_postcode"}, - {data: "conflicts"}, + { + data: "conflicts", + className: "crm-pair-conflict" + }, {data: "weight"}, {data: "actions"}, ], @@ -231,6 +238,8 @@ CRM.$(function($) { } // for action column at the last, set nowrap $('td:last', row).attr('nowrap','nowrap'); + // for conflcts column + $('td.crm-pair-conflict', row).attr('nowrap','nowrap'); } }); @@ -246,13 +255,14 @@ CRM.$(function($) { $('#dupePairs_length_selection').appendTo('#dupePairs_length'); // apply selected class on click of a row - $('#dupePairs tbody').on('click', 'tr', function() { + $('#dupePairs tbody').on('click', 'tr', function(e) { $(this).toggleClass('crm-row-selected'); $('input.crm-dedupe-select', this).prop('checked', $(this).hasClass('crm-row-selected')); var ele = $('input.crm-dedupe-select', this); toggleDedupeSelect(ele, 0); }); - + + // when select-all checkbox is checked $('#dupePairs thead tr .crm-dedupe-selection').on('click', function() { var checked = $('.crm-dedupe-select-all').prop('checked'); if (checked) { @@ -291,17 +301,68 @@ CRM.$(function($) { var column = table.column( $(this).attr('data-column-main') ); column.visible( ! column.visible() ); + // nowrap to conflicts column is applied only during initial rendering + // for show / hide clicks we need to set it explicitly + $('#dupePairs tbody td.crm-pair-conflict').attr('nowrap', 'nowrap'); + if ($(this).attr('data-column-dupe')) { column = table.column( $(this).attr('data-column-dupe') ); column.visible( ! column.visible() ); } }); + // keep the conflicts checkbox checked when context is "conflicts" if(context == 'conflicts') { $('#conflicts').attr('checked', true); var column = table.column( $('#conflicts').attr('data-column-main') ); column.visible( ! column.visible() ); } + + // on click of flip link of a row + $('#dupePairs tbody').on('click', 'tr .crm-dedupe-flip', function(e) { + e.stopPropagation(); + var $el = $(this); + var $elTr = $(this).closest('tr'); + var postUrl = {/literal}"{crmURL p='civicrm/ajax/flipDupePairs' h=0 q='snippet=4'}"{literal}; + var request = $.post(postUrl, {pnid : $el.data('pnid')}); + request.done(function(dt) { + var mapper = {2:4, 5:6, 7:8, 9:10} + var idx = table.row($elTr).index(); + $.each(mapper, function(key, val) { + var v1 = table.cell(idx, key).data(); + var v2 = table.cell(idx, val).data(); + table.cell(idx, key).data(v2); + table.cell(idx, val).data(v1); + }); + // keep the checkbox checked if needed + $('input.crm-dedupe-select', $elTr).prop('checked', $elTr.hasClass('crm-row-selected')); + }); + }); + + $(".crm-dedupe-flip-selections").on('click', function(e) { + var ids = []; + $('.crm-row-selected').each(function() { + var ele = CRM.$('input.crm-dedupe-select', this); + ids.push(CRM.$(ele).attr('name').substr(5)); + }); + if (ids.length > 0) { + var dataUrl = {/literal}"{crmURL p='civicrm/ajax/flipDupePairs' h=0 q='snippet=4'}"{literal}; + CRM.$.post(dataUrl, {pnid: ids}, function (response) { + var mapper = {2:4, 5:6, 7:8, 9:10} + $('.crm-row-selected').each(function() { + var idx = table.row(this).index(); + $.each(mapper, function(key, val) { + var v1 = table.cell(idx, key).data(); + var v2 = table.cell(idx, val).data(); + table.cell(idx, key).data(v2); + table.cell(idx, val).data(v1); + }); + // keep the checkbox checked if needed + $('input.crm-dedupe-select', this).prop('checked', $(this).hasClass('crm-row-selected')); + }); + }, 'json'); + } + }); }); function toggleDedupeSelect(element, isMultiple) { From da3afd4af61b6b76f2844b00a8f5b057ec21ff14 Mon Sep 17 00:00:00 2001 From: deepak-srivastava Date: Wed, 22 Jul 2015 12:05:31 +0100 Subject: [PATCH 03/14] get conflict column disaply results with line breaks, and force enable searches for datatables --- CRM/Contact/Page/AJAX.php | 4 ++-- templates/CRM/Contact/Page/DedupeFind.tpl | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CRM/Contact/Page/AJAX.php b/CRM/Contact/Page/AJAX.php index dc7ea9185156..50e0d31ccea6 100644 --- a/CRM/Contact/Page/AJAX.php +++ b/CRM/Contact/Page/AJAX.php @@ -836,7 +836,7 @@ static function getDedupes() { $searchRows[$count]['dst_email'] = CRM_Utils_Array::value('dst_email', $pairInfo); $searchRows[$count]['dst_street'] = CRM_Utils_Array::value('dst_street', $pairInfo); $searchRows[$count]['dst_postcode'] = CRM_Utils_Array::value('dst_postcode', $pairInfo); - $searchRows[$count]['conflicts'] = CRM_Utils_Array::value('conflicts', $pair); + $searchRows[$count]['conflicts'] = str_replace("',", "',
", CRM_Utils_Array::value('conflicts', $pair)); $searchRows[$count]['weight'] = CRM_Utils_Array::value('weight', $pair); if (!empty($pairInfo['data']['canMerge'])) { @@ -846,7 +846,7 @@ static function getDedupes() { } $searchRows[$count]['actions'] = "
" . ts('flip') . " | "; - $searchRows[$count]['actions'] = CRM_Utils_System::href(ts('merge'), 'civicrm/contact/merge', $mergeParams); + $searchRows[$count]['actions'] .= CRM_Utils_System::href(ts('merge'), 'civicrm/contact/merge', $mergeParams); $searchRows[$count]['actions'] .= " | " . ts('not a duplicate') . ""; } else { diff --git a/templates/CRM/Contact/Page/DedupeFind.tpl b/templates/CRM/Contact/Page/DedupeFind.tpl index c3e11010c5ad..003f95b0490e 100644 --- a/templates/CRM/Contact/Page/DedupeFind.tpl +++ b/templates/CRM/Contact/Page/DedupeFind.tpl @@ -196,6 +196,7 @@ CRM.$(function($) { $('#dupePairs').DataTable({ //"scrollX": true, // doesn't work with hover popup for for icons "lengthMenu": [[10, 25, 50, 100, 1000, 2000, -1], [10, 25, 50, 100, 1000, 2000, "All"]], + "searching" : true, "dom": 'flrtip', "processing": true, "serverSide": true, From 579ea9bc3684eb59e46862a18193508820ae7aae Mon Sep 17 00:00:00 2001 From: deepak-srivastava Date: Wed, 22 Jul 2015 14:29:15 +0100 Subject: [PATCH 04/14] 1. escape grid wise search input, 2. correctify search column numbers --- CRM/Contact/Page/AJAX.php | 2 ++ templates/CRM/Contact/Page/DedupeFind.tpl | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CRM/Contact/Page/AJAX.php b/CRM/Contact/Page/AJAX.php index 50e0d31ccea6..fea8d422eeef 100644 --- a/CRM/Contact/Page/AJAX.php +++ b/CRM/Contact/Page/AJAX.php @@ -695,6 +695,8 @@ static function getDedupes() { $join = ''; $where = array(); $searchData = CRM_Utils_Array::value('search', $_REQUEST); + $searchData['value'] = CRM_Utils_Type::escape($searchData['value'], 'String'); + if ($src || !empty($searchData['value']) ) { $src = $src ? $src : $searchData['value']; $where[] = " cc1.display_name LIKE '%{$src}%'"; diff --git a/templates/CRM/Contact/Page/DedupeFind.tpl b/templates/CRM/Contact/Page/DedupeFind.tpl index 003f95b0490e..a24057f8a3e6 100644 --- a/templates/CRM/Contact/Page/DedupeFind.tpl +++ b/templates/CRM/Contact/Page/DedupeFind.tpl @@ -38,11 +38,11 @@
{ts 1=$main_contacts[$cid]}Merge %1 with{/ts}

- +

- +

@@ -85,7 +85,7 @@ - +
From f931b74cf85c797e77962eb01aee0c5a5755f806 Mon Sep 17 00:00:00 2001 From: deepak-srivastava Date: Mon, 27 Jul 2015 17:24:54 +0100 Subject: [PATCH 05/14] Code styling fixes --- CRM/Contact/Page/AJAX.php | 106 ++++++++++++++++-------------- CRM/Contact/Page/DedupeMerge.php | 34 +++++----- CRM/Core/BAO/CustomValueTable.php | 3 +- CRM/Core/BAO/PrevNextCache.php | 22 +++---- CRM/Dedupe/Merger.php | 14 ++-- CRM/Utils/JSON.php | 3 +- 6 files changed, 98 insertions(+), 84 deletions(-) diff --git a/CRM/Contact/Page/AJAX.php b/CRM/Contact/Page/AJAX.php index fea8d422eeef..f03bc0d640d5 100644 --- a/CRM/Contact/Page/AJAX.php +++ b/CRM/Contact/Page/AJAX.php @@ -668,7 +668,7 @@ public static function processDupes() { CRM_Utils_JSON::output(array('status' => ($status) ? $oper : $status)); } - static function getDedupes() { + public static function getDedupes() { $offset = isset($_REQUEST['start']) ? CRM_Utils_Type::escape($_REQUEST['start'], 'Integer') : 0; $rowCount = isset($_REQUEST['length']) ? CRM_Utils_Type::escape($_REQUEST['length'], 'Integer') : 25; @@ -697,7 +697,7 @@ static function getDedupes() { $searchData = CRM_Utils_Array::value('search', $_REQUEST); $searchData['value'] = CRM_Utils_Type::escape($searchData['value'], 'String'); - if ($src || !empty($searchData['value']) ) { + if ($src || !empty($searchData['value'])) { $src = $src ? $src : $searchData['value']; $where[] = " cc1.display_name LIKE '%{$src}%'"; } @@ -730,13 +730,13 @@ static function getDedupes() { $where[] = " (ca2.is_primary = 1 AND ca2.street_address LIKE '%{$dst_street}%')"; } if (!empty($searchData['value'])) { - $whereClause = ' ( '.implode(' OR ', $where).' ) '; + $whereClause = ' ( ' . implode(' OR ', $where) . ' ) '; } else { if (!empty($where)) { $whereClause = implode(' AND ', $where); } - } + } $whereClause .= $whereClause ? ' AND de.id IS NULL' : ' de.id IS NULL'; if ($selected) { @@ -744,23 +744,22 @@ static function getDedupes() { } $join .= " LEFT JOIN civicrm_dedupe_exception de ON ( pn.entity_id1 = de.contact_id1 AND pn.entity_id2 = de.contact_id2 )"; - $select = array( - 'cc1.contact_type' => 'src_contact_type', - 'cc1.display_name' => 'src_display_name', - 'cc1.contact_sub_type'=> 'src_contact_sub_type', - 'cc2.contact_type' => 'dst_contact_type', - 'cc2.display_name' => 'dst_display_name', - 'cc2.contact_sub_type'=> 'dst_contact_sub_type', - 'ce1.email' => 'src_email', - 'ce2.email' => 'dst_email', - 'ca1.postal_code' => 'src_postcode', - 'ca2.postal_code' => 'dst_postcode', - 'ca1.street_address' => 'src_street', - 'ca2.street_address' => 'dst_street' + 'cc1.contact_type' => 'src_contact_type', + 'cc1.display_name' => 'src_display_name', + 'cc1.contact_sub_type' => 'src_contact_sub_type', + 'cc2.contact_type' => 'dst_contact_type', + 'cc2.display_name' => 'dst_display_name', + 'cc2.contact_sub_type' => 'dst_contact_sub_type', + 'ce1.email' => 'src_email', + 'ce2.email' => 'dst_email', + 'ca1.postal_code' => 'src_postcode', + 'ca2.postal_code' => 'dst_postcode', + 'ca1.street_address' => 'src_street', + 'ca2.street_address' => 'dst_street', ); - if($select) { + if ($select) { $join .= " INNER JOIN civicrm_contact cc1 ON cc1.id = pn.entity_id1"; $join .= " INNER JOIN civicrm_contact cc2 ON cc2.id = pn.entity_id2"; $join .= " LEFT JOIN civicrm_email ce1 ON (ce1.contact_id = pn.entity_id1 AND ce1.is_primary = 1 )"; @@ -776,34 +775,42 @@ static function getDedupes() { } } $columnDetails = CRM_Utils_Array::value($orderColumnNumber, $_REQUEST['columns']); - if(!empty($columnDetails)) { + if (!empty($columnDetails)) { switch ($columnDetails['data']) { - case 'src': - $whereClause .= " ORDER BY cc1.display_name {$dir}"; - break; - case 'src_email': - $whereClause .= " ORDER BY ce1.email {$dir}"; - break; - case 'src_street': - $whereClause .= " ORDER BY ca1.street_address {$dir}"; - break; - case 'src_postcode': - $whereClause .= " ORDER BY ca1.postal_code {$dir}"; - break; - case 'dst': - $whereClause .= " ORDER BY cc2.display_name {$dir}"; - break; - case 'dst_email': - $whereClause .= " ORDER BY ce2.email {$dir}"; - break; - case 'dst_street': - $whereClause .= " ORDER BY ca2.street_address {$dir}"; - break; - case 'dst_postcode': - $whereClause .= " ORDER BY ca2.postal_code {$dir}"; - break; - default: - break; + case 'src': + $whereClause .= " ORDER BY cc1.display_name {$dir}"; + break; + + case 'src_email': + $whereClause .= " ORDER BY ce1.email {$dir}"; + break; + + case 'src_street': + $whereClause .= " ORDER BY ca1.street_address {$dir}"; + break; + + case 'src_postcode': + $whereClause .= " ORDER BY ca1.postal_code {$dir}"; + break; + + case 'dst': + $whereClause .= " ORDER BY cc2.display_name {$dir}"; + break; + + case 'dst_email': + $whereClause .= " ORDER BY ce2.email {$dir}"; + break; + + case 'dst_street': + $whereClause .= " ORDER BY ca2.street_address {$dir}"; + break; + + case 'dst_postcode': + $whereClause .= " ORDER BY ca2.postal_code {$dir}"; + break; + + default: + break; } } @@ -885,7 +892,7 @@ public static function paperSize() { CRM_Utils_JSON::output($paperSize); } - static function flipDupePairs($prevNextId = NULL) { + public static function flipDupePairs($prevNextId = NULL) { if (!$prevNextId) { $prevNextId = $_REQUEST['pnid']; } @@ -897,7 +904,8 @@ static function flipDupePairs($prevNextId = NULL) { $prevNextId = implode(', ', $prevNextId); $prevNextId = CRM_Utils_Type::escape($prevNextId, 'String'); $query .= "WHERE cpc.id IN ({$prevNextId}) AND cpc.is_selected = 1"; - } else { + } + else { $prevNextId = CRM_Utils_Type::escape($prevNextId, 'Positive'); $query .= "WHERE cpc.id = $prevNextId"; } @@ -970,7 +978,7 @@ public static function getAddressDisplay() { CRM_Utils_JSON::output($addressVal); } - static function toggleDedupeSelect() { + public static function toggleDedupeSelect() { $rgid = CRM_Utils_Type::escape($_REQUEST['rgid'], 'Integer'); $gid = CRM_Utils_Type::escape($_REQUEST['gid'], 'Integer'); $pnid = $_REQUEST['pnid']; @@ -983,7 +991,7 @@ static function toggleDedupeSelect() { $params = array( 1 => array($isSelected, 'Boolean'), - 3 => array("$cacheKeyString%", 'String') // using % to address rows with conflicts as well + 3 => array("$cacheKeyString%", 'String'), // using % to address rows with conflicts as well ); //check pnid is_array or integer diff --git a/CRM/Contact/Page/DedupeMerge.php b/CRM/Contact/Page/DedupeMerge.php index 97970ea44a8f..da762dda6ba8 100644 --- a/CRM/Contact/Page/DedupeMerge.php +++ b/CRM/Contact/Page/DedupeMerge.php @@ -23,7 +23,7 @@ | GNU Affero General Public License or the licensing of CiviCRM, | | see the CiviCRM license FAQ at http://civicrm.org/licensing | +--------------------------------------------------------------------+ -*/ + */ /** * @@ -32,7 +32,7 @@ * $Id$ * */ -class CRM_Contact_Page_DedupeMerge extends CRM_Core_Page{ +class CRM_Contact_Page_DedupeMerge extends CRM_Core_Page { const BATCHLIMIT = 2; @@ -42,12 +42,13 @@ class CRM_Contact_Page_DedupeMerge extends CRM_Core_Page{ * @return void * @access public */ - function run() { + public function run() { $runner = self::getRunner(); if ($runner) { // Run Everything in the Queue via the Web. $runner->runAllViaWeb(); - } else { + } + else { CRM_Core_Session::setStatus(ts('Nothing to merge.')); } @@ -55,7 +56,7 @@ function run() { return parent::run(); } - static function getRunner() { + public static function getRunner() { $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive', $this, FALSE, 0); $gid = CRM_Utils_Request::retrieve('gid', 'Positive', $this, FALSE, 0); $action = CRM_Utils_Request::retrieve('action', 'String', CRM_Core_DAO::$_nullObject); @@ -84,9 +85,10 @@ static function getRunner() { if ($action == CRM_Core_Action::MAP) { $where = "pn.is_selected = 1"; $isSelected = 1; - } else { + } + else { // else merge all (2) - $isSelected = 2; + $isSelected = 2; } $total = CRM_Core_BAO_PrevNextCache::getCount($cacheKeyString, NULL, $where); @@ -98,11 +100,11 @@ static function getRunner() { // reset merge stats, so we compute new stats CRM_Dedupe_Merger::resetMergeStats($cacheKeyString); - for ($i = 1; $i <= ceil($total/self::BATCHLIMIT); $i++) { + for ($i = 1; $i <= ceil($total / self::BATCHLIMIT); $i++) { $task = new CRM_Queue_Task( - array ('CRM_Contact_Page_DedupeMerge', 'callBatchMerge'), + array('CRM_Contact_Page_DedupeMerge', 'callBatchMerge'), array($rgid, $gid, $mode, TRUE, self::BATCHLIMIT, $isSelected), - "Processed " . $i*self::BATCHLIMIT . " pair of duplicates out of " . $total + "Processed " . $i * self::BATCHLIMIT . " pair of duplicates out of " . $total ); // Add the Task to the Queue @@ -112,10 +114,10 @@ static function getRunner() { // Setup the Runner $urlQry .= "&context=conflicts"; $runner = new CRM_Queue_Runner(array( - 'title' => ts('Merging Duplicates..'), - 'queue' => $queue, - 'errorMode'=> CRM_Queue_Runner::ERROR_ABORT, - 'onEndUrl' => CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry, TRUE, NULL, FALSE), + 'title' => ts('Merging Duplicates..'), + 'queue' => $queue, + 'errorMode' => CRM_Queue_Runner::ERROR_ABORT, + 'onEndUrl' => CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry, TRUE, NULL, FALSE), )); return $runner; @@ -124,10 +126,10 @@ static function getRunner() { /** * Collect Mailchimp data into temporary working table. */ - static function callBatchMerge(CRM_Queue_TaskContext $ctx, $rgid, $gid = NULL, $mode = 'safe', $autoFlip = TRUE, $batchLimit = 1, $isSelected = 2) { + public static function callBatchMerge(CRM_Queue_TaskContext $ctx, $rgid, $gid = NULL, $mode = 'safe', $autoFlip = TRUE, $batchLimit = 1, $isSelected = 2) { $result = CRM_Dedupe_Merger::batchMerge($rgid, $gid, $mode, $autoFlip, $batchLimit, $isSelected); return CRM_Queue_Task::TASK_SUCCESS; } -} +} diff --git a/CRM/Core/BAO/CustomValueTable.php b/CRM/Core/BAO/CustomValueTable.php index d2da71444e39..a86ffe6655ae 100644 --- a/CRM/Core/BAO/CustomValueTable.php +++ b/CRM/Core/BAO/CustomValueTable.php @@ -214,7 +214,8 @@ public static function create(&$customParams) { // when unsetting a value to null, we don't need to validate the type // https://projectllr.atlassian.net/browse/VGQBMP-20 $set[$field['column_name']] = $value; - } else { + } + else { $set[$field['column_name']] = "%{$count}"; $params[$count] = array($value, $type); $count++; diff --git a/CRM/Core/BAO/PrevNextCache.php b/CRM/Core/BAO/PrevNextCache.php index 74d9995a33f2..3550edb7b1d3 100644 --- a/CRM/Core/BAO/PrevNextCache.php +++ b/CRM/Core/BAO/PrevNextCache.php @@ -159,7 +159,7 @@ public static function deletePair($id1, $id2, $cacheKey = NULL, $isViceVersa = F CRM_Core_DAO::executeQuery($sql, $params); } - static function markConflict($id1, $id2, $cacheKey, $conflicts) { + public static function markConflict($id1, $id2, $cacheKey, $conflicts) { if (empty($cacheKey) || empty($conflicts)) { return FALSE; } @@ -206,12 +206,12 @@ static function markConflict($id1, $id2, $cacheKey, $conflicts) { */ public static function retrieve($cacheKey, $join = NULL, $where = NULL, $offset = 0, $rowCount = 0, $select = array()) { $selectString = 'pn.*'; - if(!empty($select)) { + if (!empty($select)) { $aliasArray = array(); foreach ($select as $column => $alias) { - $aliasArray[] = $column.' as '.$alias; + $aliasArray[] = $column . ' as ' . $alias; } - $selectString .= " , ".implode(' , ', $aliasArray); + $selectString .= " , " . implode(' , ', $aliasArray); } $query = " SELECT SQL_CALC_FOUND_ROWS {$selectString} @@ -221,7 +221,7 @@ public static function retrieve($cacheKey, $join = NULL, $where = NULL, $offset "; $params = array( 1 => array($cacheKey, 'String'), - 2 => array("{$cacheKey}_conflicts", 'String') + 2 => array("{$cacheKey}_conflicts", 'String'), ); if ($where) { @@ -253,10 +253,10 @@ public static function retrieve($cacheKey, $join = NULL, $where = NULL, $offset $extraData[$sfield] = $dao->$sfield; } $main[$count] = array( - 'prevnext_id' => $dao->id, - 'is_selected' => $dao->is_selected, - 'entity_id1' => $dao->entity_id1, - 'entity_id2' => $dao->entity_id2, + 'prevnext_id' => $dao->id, + 'is_selected' => $dao->is_selected, + 'entity_id1' => $dao->entity_id1, + 'entity_id2' => $dao->entity_id2, 'data' => $main[$count], ); $main[$count] = array_merge($main[$count], $extraData); @@ -266,7 +266,7 @@ public static function retrieve($cacheKey, $join = NULL, $where = NULL, $offset return $main; } - + /** * @param $string * @@ -307,7 +307,7 @@ public static function getCount($cacheKey, $join = NULL, $where = NULL, $op = "= $params = array( 1 => array($cacheKey, 'String'), - 2 => array("{$cacheKey}_conflicts", 'String') + 2 => array("{$cacheKey}_conflicts", 'String'), ); return (int) CRM_Core_DAO::singleValueQuery($query, $params, TRUE, FALSE); } diff --git a/CRM/Dedupe/Merger.php b/CRM/Dedupe/Merger.php index 2f5e5d7de7d0..e1e9ea9287c0 100644 --- a/CRM/Dedupe/Merger.php +++ b/CRM/Dedupe/Merger.php @@ -565,7 +565,8 @@ public static function findDifferences($main, $other) { * Does a force merge otherwise. * @param bool $autoFlip to let api decide which contact to retain and which to delete. * Wether to let api decide which contact to retain and which to delete. - * @param bool $redirectForPerformance + * @param int $batchLimit number of merges to carry out in one batch. + * @param int $isSelected if records with is_selected column needs to be processed. * * @return array|bool */ @@ -601,7 +602,7 @@ public static function batchMerge($rgid, $gid = NULL, $mode = 'safe', $autoFlip return CRM_Dedupe_Merger::merge($dupePairs, $cacheParams, $mode, $autoFlip, $redirectForPerformance); } - static function updateMergeStats($cacheKeyString, $result = array()) { + public static function updateMergeStats($cacheKeyString, $result = array()) { // gather latest stats $merged = count($result['merged']); $skipped = count($result['skipped']); @@ -636,11 +637,11 @@ static function updateMergeStats($cacheKeyString, $result = array()) { CRM_Core_BAO_PrevNextCache::setItem($values); } - static function resetMergeStats($cacheKeyString) { + public static function resetMergeStats($cacheKeyString) { return CRM_Core_BAO_PrevNextCache::deleteItem(NULL, "{$cacheKeyString}_stats"); } - static function getMergeStats($cacheKeyString) { + public static function getMergeStats($cacheKeyString) { $stats = CRM_Core_BAO_PrevNextCache::retrieve("{$cacheKeyString}_stats"); if (!empty($stats)) { $stats = $stats[0]; @@ -648,7 +649,7 @@ static function getMergeStats($cacheKeyString) { return $stats; } - static function getMergeStatsMsg($cacheKeyString) { + public static function getMergeStatsMsg($cacheKeyString) { $msg = ''; $stats = CRM_Dedupe_Merger::getMergeStats($cacheKeyString); if (!empty($stats['merged'])) { @@ -735,7 +736,8 @@ public static function merge($dupePairs = array(), $cacheParams = array(), $mode $conflicts[$key] = "{$migrationInfo['rows'][$key]['title']}: '{$migrationInfo['rows'][$key]['main']}' vs. '{$migrationInfo['rows'][$key]['other']}'"; } CRM_Core_BAO_PrevNextCache::markConflict($mainId, $otherId, $cacheKeyString, $conflicts); - } else { + } + else { // delete entry from PrevNextCache table so we don't consider the pair next time // pair may have been flipped, so make sure we delete using both orders CRM_Core_BAO_PrevNextCache::deletePair($mainId, $otherId, $cacheKeyString, TRUE); diff --git a/CRM/Utils/JSON.php b/CRM/Utils/JSON.php index 169ac6292443..6df05ada6eec 100644 --- a/CRM/Utils/JSON.php +++ b/CRM/Utils/JSON.php @@ -99,7 +99,7 @@ public static function encodeDataTableSelector($params, $sEcho, $iTotal, $iFilte * @return string * */ - static function encodeDataTable($params, $iTotal, $iFilteredTotal, $selectorElements) { + public static function encodeDataTable($params, $iTotal, $iFilteredTotal, $selectorElements) { $sOutput = '{'; $sOutput .= '"recordsTotal": ' . $iTotal . ', '; $sOutput .= '"recordsFiltered": ' . $iFilteredTotal . ', '; @@ -127,4 +127,5 @@ static function encodeDataTable($params, $iTotal, $iFilteredTotal, $selectorElem return $sOutput; } + } From 3bfb9d7eabf5b8ab7add068bb12a0bd17784f487 Mon Sep 17 00:00:00 2001 From: deepak-srivastava Date: Thu, 6 Aug 2015 11:35:34 +0100 Subject: [PATCH 06/14] button class icons --- templates/CRM/Contact/Page/DedupeFind.tpl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/CRM/Contact/Page/DedupeFind.tpl b/templates/CRM/Contact/Page/DedupeFind.tpl index a24057f8a3e6..e56e5085e1a9 100644 --- a/templates/CRM/Contact/Page/DedupeFind.tpl +++ b/templates/CRM/Contact/Page/DedupeFind.tpl @@ -134,14 +134,14 @@ {else} {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&action=map&mode=aggressive" a=1}{/capture} {/if} - {ts}Force Merge Selected Duplicates{/ts} +
{ts}Force Merge Selected Duplicates{/ts}
{if $gid} {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&gid=`$gid`&action=map" a=1}{/capture} {else} {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&action=map" a=1}{/capture} {/if} - {ts}Safe Merge Selected Duplicates{/ts} +
{ts}Safe Merge Selected Duplicates{/ts}
{/if} {if $gid} @@ -149,7 +149,7 @@ {else} {capture assign=backURL}{crmURL p="civicrm/contact/dedupefind" q="reset=1&action=update&rgid=`$rgid`" a=1}{/capture} {/if} - {ts}List All Duplicates{/ts} + {ts}List All Duplicates{/ts} {else} {if $gid} {capture assign=backURL}{crmURL p="civicrm/contact/dedupefind" q="reset=1&rgid=`$rgid`&gid=`$gid`&action=renew" a=1}{/capture} @@ -174,7 +174,7 @@ {/if}
{ts}Batch Merge All Duplicates{/ts}
- {ts}Flip Selected Duplicates{/ts} + {ts}Flip Selected Duplicates{/ts} {capture assign=backURL}{crmURL p="civicrm/contact/deduperules" q="reset=1" a=1}{/capture} From 22b232f3a1c35df528bf2c5b9c017350ae05a4e2 Mon Sep 17 00:00:00 2001 From: deepak-srivastava Date: Sat, 8 Aug 2015 01:14:00 +0100 Subject: [PATCH 07/14] use JSON::output instead of encodeDataTableSelector --- CRM/Contact/Page/AJAX.php | 10 ++++++---- CRM/Utils/JSON.php | 34 ---------------------------------- 2 files changed, 6 insertions(+), 38 deletions(-) diff --git a/CRM/Contact/Page/AJAX.php b/CRM/Contact/Page/AJAX.php index f03bc0d640d5..5d248649ab2f 100644 --- a/CRM/Contact/Page/AJAX.php +++ b/CRM/Contact/Page/AJAX.php @@ -864,10 +864,12 @@ public static function getDedupes() { $count++; } - header('Content-Type: application/json'); - echo CRM_Utils_JSON::encodeDataTable($searchRows, $iTotal, $iFilteredTotal, $selectorElements); - - CRM_Utils_System::civiExit(); + $dupePairs = array( + 'data' => $searchRows, + 'recordsTotal' => $iTotal, + 'recordsFiltered' => $iFilteredTotal, + ); + CRM_Utils_JSON::output($dupePairs); } /** diff --git a/CRM/Utils/JSON.php b/CRM/Utils/JSON.php index 6df05ada6eec..01e0b60bbf32 100644 --- a/CRM/Utils/JSON.php +++ b/CRM/Utils/JSON.php @@ -94,38 +94,4 @@ public static function encodeDataTableSelector($params, $sEcho, $iTotal, $iFilte return $sOutput; } - /** - * This function is used to encode data for new dataTable plugin v1.10 and greater - * @return string - * - */ - public static function encodeDataTable($params, $iTotal, $iFilteredTotal, $selectorElements) { - $sOutput = '{'; - $sOutput .= '"recordsTotal": ' . $iTotal . ', '; - $sOutput .= '"recordsFiltered": ' . $iFilteredTotal . ', '; - $sOutput .= '"data": [ '; - foreach ($params as $key => $value) { - $addcomma = FALSE; - $sOutput .= "{"; - foreach ($selectorElements as $element) { - if ($addcomma) { - $sOutput .= ","; - } - //CRM-7130 --lets addslashes to only double quotes, - //since we are using it to quote the field value. - //str_replace helps to provide a break for new-line - $sOutput .= '"' . $element . '":' . '"' . addcslashes(str_replace(array("\r\n", "\n", "\r"), '
', $value[$element]), '"\\') . '"'; - - //remove extra spaces and tab character that breaks dataTable CRM-12551 - $sOutput = preg_replace("/\s+/", " ", $sOutput); - $addcomma = TRUE; - } - $sOutput .= "},"; - } - $sOutput = substr_replace($sOutput, "", -1); - $sOutput .= '] }'; - - return $sOutput; - } - } From 183ec3304dddbfa82f2128c0652ac8a81cd17c54 Mon Sep 17 00:00:00 2001 From: deepak-srivastava Date: Sat, 8 Aug 2015 01:41:12 +0100 Subject: [PATCH 08/14] comments --- CRM/Contact/Page/AJAX.php | 9 +++++++++ CRM/Contact/Page/DedupeMerge.php | 7 +++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CRM/Contact/Page/AJAX.php b/CRM/Contact/Page/AJAX.php index 5d248649ab2f..7b5ef9b6f62f 100644 --- a/CRM/Contact/Page/AJAX.php +++ b/CRM/Contact/Page/AJAX.php @@ -668,6 +668,9 @@ public static function processDupes() { CRM_Utils_JSON::output(array('status' => ($status) ? $oper : $status)); } + /** + * Retrieve list of duplicate pairs from cache table. + */ public static function getDedupes() { $offset = isset($_REQUEST['start']) ? CRM_Utils_Type::escape($_REQUEST['start'], 'Integer') : 0; $rowCount = isset($_REQUEST['length']) ? CRM_Utils_Type::escape($_REQUEST['length'], 'Integer') : 25; @@ -894,6 +897,9 @@ public static function paperSize() { CRM_Utils_JSON::output($paperSize); } + /** + * Swap contacts in a dupe pair i.e main with duplicate contact. + */ public static function flipDupePairs($prevNextId = NULL) { if (!$prevNextId) { $prevNextId = $_REQUEST['pnid']; @@ -980,6 +986,9 @@ public static function getAddressDisplay() { CRM_Utils_JSON::output($addressVal); } + /** + * Mark dupe pairs as selected from un-selected state or vice-versa, in dupe cache table. + */ public static function toggleDedupeSelect() { $rgid = CRM_Utils_Type::escape($_REQUEST['rgid'], 'Integer'); $gid = CRM_Utils_Type::escape($_REQUEST['gid'], 'Integer'); diff --git a/CRM/Contact/Page/DedupeMerge.php b/CRM/Contact/Page/DedupeMerge.php index da762dda6ba8..4e1f17b410fd 100644 --- a/CRM/Contact/Page/DedupeMerge.php +++ b/CRM/Contact/Page/DedupeMerge.php @@ -37,7 +37,7 @@ class CRM_Contact_Page_DedupeMerge extends CRM_Core_Page { const BATCHLIMIT = 2; /** - * Browse all rule groups + * Browse batch merges. * * @return void * @access public @@ -56,6 +56,9 @@ public function run() { return parent::run(); } + /** + * Build a queue of tasks by dividing dupe pairs in batches. + */ public static function getRunner() { $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive', $this, FALSE, 0); $gid = CRM_Utils_Request::retrieve('gid', 'Positive', $this, FALSE, 0); @@ -124,7 +127,7 @@ public static function getRunner() { } /** - * Collect Mailchimp data into temporary working table. + * Carry out batch merges. */ public static function callBatchMerge(CRM_Queue_TaskContext $ctx, $rgid, $gid = NULL, $mode = 'safe', $autoFlip = TRUE, $batchLimit = 1, $isSelected = 2) { $result = CRM_Dedupe_Merger::batchMerge($rgid, $gid, $mode, $autoFlip, $batchLimit, $isSelected); From f2515ec6ad86452be420f17f59fb268731275f8b Mon Sep 17 00:00:00 2001 From: deepak-srivastava Date: Mon, 10 Aug 2015 20:40:49 +0100 Subject: [PATCH 09/14] towards using crmAjaxTable plugin --- templates/CRM/Contact/Page/DedupeFind.tpl | 356 ++++++++++------------ 1 file changed, 160 insertions(+), 196 deletions(-) diff --git a/templates/CRM/Contact/Page/DedupeFind.tpl b/templates/CRM/Contact/Page/DedupeFind.tpl index e56e5085e1a9..c5cea1436d2d 100644 --- a/templates/CRM/Contact/Page/DedupeFind.tpl +++ b/templates/CRM/Contact/Page/DedupeFind.tpl @@ -85,23 +85,32 @@ -
+ +
- - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -190,168 +199,99 @@ {include file='CRM/common/dedupe.tpl'} {literal} {/literal} From f826cbfc3d34054e12ca4d9301c044d0b98a3e0e Mon Sep 17 00:00:00 2001 From: deepak-srivastava Date: Wed, 12 Aug 2015 21:17:32 +0100 Subject: [PATCH 10/14] rowCallback fix --- templates/CRM/Contact/Page/DedupeFind.tpl | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/templates/CRM/Contact/Page/DedupeFind.tpl b/templates/CRM/Contact/Page/DedupeFind.tpl index c5cea1436d2d..cfb722dd592a 100644 --- a/templates/CRM/Contact/Page/DedupeFind.tpl +++ b/templates/CRM/Contact/Page/DedupeFind.tpl @@ -90,7 +90,6 @@ class="form-layout-compressed crm-ajax-table" cellspacing="0" width="100%" - data-length-menu='[[10, 25, 50, 100, 1000, 2000, -1], [10, 25, 50, 100, 1000, 2000, "All"]]', data-searching='true', data-dom='flrtip', data-order='[]', @@ -108,7 +107,7 @@ - + @@ -203,9 +202,23 @@ CRM.$('table#dupePairs').data({ "ajax": { "url": {/literal}'{$sourceUrl}'{literal} + }, + rowCallback: function (row, data) { + // Set the checked state of the checkbox in the table + $('input.crm-dedupe-select', row).prop('checked', data.is_selected == 1); + if (data.is_selected == 1) { + $(row).toggleClass('crm-row-selected'); + } + // for action column at the last, set nowrap + $('td:last', row).attr('nowrap','nowrap'); + // for conflcts column + $('td.crm-pair-conflict', row).attr('nowrap','nowrap'); } }); $(function($) { + var sourceUrl = {/literal}'{$sourceUrl}'{literal}; + var context = {/literal}'{$context}'{literal}; + // redraw datatable if searching within selected records $('#crm-dedupe-display-selection').on('click', function(){ reloadUrl = sourceUrl; @@ -275,7 +288,6 @@ }); // keep the conflicts checkbox checked when context is "conflicts" - var context = {/literal}'{$context}'{literal}; if(context == 'conflicts') { $('#conflicts').attr('checked', true); var column = table.column( $('#conflicts').attr('data-column-main') ); From 57db188afa86a9ca8281608482b3b7ccf8e2453d Mon Sep 17 00:00:00 2001 From: deepak-srivastava Date: Mon, 17 Aug 2015 17:59:43 +0100 Subject: [PATCH 11/14] get rid of crm-pair-conflict, and use index to catch cells to no-wrap --- templates/CRM/Contact/Page/DedupeFind.tpl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/templates/CRM/Contact/Page/DedupeFind.tpl b/templates/CRM/Contact/Page/DedupeFind.tpl index cfb722dd592a..9a7a8342bb9e 100644 --- a/templates/CRM/Contact/Page/DedupeFind.tpl +++ b/templates/CRM/Contact/Page/DedupeFind.tpl @@ -87,7 +87,7 @@
 {ts}Contact{/ts} 1 {ts}Contact{/ts} 2 ({ts}Duplicate{/ts}){ts}Email{/ts} 1{ts}Email{/ts} 2 ({ts}Duplicate{/ts}){ts}Street Address{/ts} 1{ts}Street Address{/ts} 2 ({ts}Duplicate{/ts}){ts}Postcode{/ts} 1{ts}Postcode{/ts} 2 ({ts}Duplicate{/ts}){ts}Conflicts{/ts}{ts}Threshold{/ts}  {ts}Contact{/ts} 1 {ts}Contact{/ts} 2 ({ts}Duplicate{/ts}){ts}Email{/ts} 1{ts}Email{/ts} 2 ({ts}Duplicate{/ts}){ts}Street Address{/ts} 1{ts}Street Address{/ts} 2 ({ts}Duplicate{/ts}){ts}Postcode{/ts} 1{ts}Postcode{/ts} 2 ({ts}Duplicate{/ts}){ts}Conflicts{/ts}{ts}Threshold{/ts} 
{ts}Street Address{/ts} 2 ({ts}Duplicate{/ts}) {ts}Postcode{/ts} 1 {ts}Postcode{/ts} 2 ({ts}Duplicate{/ts}){ts}Conflicts{/ts}{ts}Conflicts{/ts} {ts}Threshold{/ts}  
{ts}Street Address{/ts} 2 ({ts}Duplicate{/ts}) - + @@ -212,7 +212,8 @@ // for action column at the last, set nowrap $('td:last', row).attr('nowrap','nowrap'); // for conflcts column - $('td.crm-pair-conflict', row).attr('nowrap','nowrap'); + var col = CRM.$('table#dupePairs thead th.crm-contact-conflicts').index(); + $('td:eq(' + col + ')', row).attr('nowrap','nowrap'); } }); $(function($) { @@ -279,8 +280,11 @@ // nowrap to conflicts column is applied only during initial rendering // for show / hide clicks we need to set it explicitly - $('#dupePairs tbody td.crm-pair-conflict').attr('nowrap', 'nowrap'); - + var col = CRM.$('table#dupePairs thead th.crm-contact-conflicts').index() + 1; + if (col > 0) { + CRM.$('table#dupePairs tbody tr td:nth-child(' + col + ')').attr('nowrap','nowrap'); + } + if ($(this).attr('data-column-dupe')) { column = table.column( $(this).attr('data-column-dupe') ); column.visible( ! column.visible() ); From cc86f7ba54b1a9d6c36c09c1afdc58df96b89b8a Mon Sep 17 00:00:00 2001 From: deepak-srivastava Date: Mon, 17 Aug 2015 18:34:15 +0100 Subject: [PATCH 12/14] default page length and class --- templates/CRM/Contact/Page/DedupeFind.tpl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/CRM/Contact/Page/DedupeFind.tpl b/templates/CRM/Contact/Page/DedupeFind.tpl index 9a7a8342bb9e..daea1b89d9e8 100644 --- a/templates/CRM/Contact/Page/DedupeFind.tpl +++ b/templates/CRM/Contact/Page/DedupeFind.tpl @@ -87,9 +87,10 @@
{ts}Postcode{/ts} 1 {ts}Postcode{/ts} 2 ({ts}Duplicate{/ts}){ts}Conflicts{/ts}{ts}Conflicts{/ts} {ts}Threshold{/ts}  
Date: Tue, 25 Aug 2015 15:01:27 +0100 Subject: [PATCH 13/14] Make flip action also flip contact type icons --- templates/CRM/Contact/Page/DedupeFind.tpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/CRM/Contact/Page/DedupeFind.tpl b/templates/CRM/Contact/Page/DedupeFind.tpl index daea1b89d9e8..2a7d2d0ca27e 100644 --- a/templates/CRM/Contact/Page/DedupeFind.tpl +++ b/templates/CRM/Contact/Page/DedupeFind.tpl @@ -307,7 +307,7 @@ var postUrl = {/literal}"{crmURL p='civicrm/ajax/flipDupePairs' h=0 q='snippet=4'}"{literal}; var request = $.post(postUrl, {pnid : $el.data('pnid')}); request.done(function(dt) { - var mapper = {2:4, 5:6, 7:8, 9:10} + var mapper = {1:3, 2:4, 5:6, 7:8, 9:10} var idx = table.row($elTr).index(); $.each(mapper, function(key, val) { var v1 = table.cell(idx, key).data(); @@ -329,7 +329,7 @@ if (ids.length > 0) { var dataUrl = {/literal}"{crmURL p='civicrm/ajax/flipDupePairs' h=0 q='snippet=4'}"{literal}; CRM.$.post(dataUrl, {pnid: ids}, function (response) { - var mapper = {2:4, 5:6, 7:8, 9:10} + var mapper = {1:3, 2:4, 5:6, 7:8, 9:10} $('.crm-row-selected').each(function() { var idx = table.row(this).index(); $.each(mapper, function(key, val) { From 355927bf4e9e90b1eecbd4eeaa9fd0d57506783f Mon Sep 17 00:00:00 2001 From: deepak-srivastava Date: Tue, 25 Aug 2015 15:32:31 +0100 Subject: [PATCH 14/14] disable unsaved-cahnges warnings --- templates/CRM/Contact/Page/DedupeFind.tpl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/templates/CRM/Contact/Page/DedupeFind.tpl b/templates/CRM/Contact/Page/DedupeFind.tpl index 2a7d2d0ca27e..fee38211e7a6 100644 --- a/templates/CRM/Contact/Page/DedupeFind.tpl +++ b/templates/CRM/Contact/Page/DedupeFind.tpl @@ -218,6 +218,11 @@ } }); $(function($) { + $('.button').click(function() { + // no unsaved changes confirmation dialogs + $('[data-warn-changes=true]').attr('data-warn-changes', 'false'); + }); + var sourceUrl = {/literal}'{$sourceUrl}'{literal}; var context = {/literal}'{$context}'{literal};