$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'] = "<input type='checkbox' class='crm-dedupe-select' name='pnid_{$pairInfo['prevnext_id']}' value='{$pairInfo['is_selected']}' onclick='toggleDedupeSelect(this)'>";
$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'] = "<a class='crm-dedupe-flip' href='#' data-pnid={$pairInfo['prevnext_id']}>" . ts('flip') . "</a> | ";
$searchRows[$count]['actions'] = CRM_Utils_System::href(ts('merge'), 'civicrm/contact/merge', $mergeParams);
- $searchRows[$count]['actions'] .= " | <a id='notDuplicate' href='#' onClick=\"processDupes( {$pair['srcID']}, {$pair['dstID']}, 'dupe-nondupe', 'dupe-listing'); return false;\">" . ts('not a duplicate') . "</a>";
+ $searchRows[$count]['actions'] .= " | <a id='notDuplicate' href='#' onClick=\"processDupes( {$pairInfo['entity_id1']}, {$pairInfo['entity_id2']}, 'dupe-nondupe', 'dupe-listing'); return false;\">" . ts('not a duplicate') . "</a>";
}
else {
$searchRows[$count]['actions'] = '<em>' . ts('Insufficient access rights - cannot merge') . '</em>';
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.
*/
$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,
$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.
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)) {
$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;
$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'),
<page_callback>CRM_Contact_Page_AJAX::toggleDedupeSelect</page_callback>
<access_arguments>merge duplicate contacts</access_arguments>
</item>
+<item>
+ <path>civicrm/ajax/flipDupePairs</path>
+ <page_callback>CRM_Contact_Page_AJAX::flipDupePairs</page_callback>
+ <access_arguments>merge duplicate contacts</access_arguments>
+</item>
<item>
<path>civicrm/activity/sms/add</path>
<path_arguments>action=add</path_arguments>
// 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
// 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) {
// 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']) {
// 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
$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;
}
{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}
- <a href="{$backURL}" title="{ts}Batch Merge Duplicate Contacts{/ts}" onclick="return confirm('{ts escape="js"}This will run the batch merge process on the selected duplicates. The operation will run in force merge mode - all selected duplicates will be merged into main contacts even in case of any conflicts. Click OK to proceed if you are sure you wish to run this operation.{/ts}');" class="button"><span>{ts}Force Merge Selected Duplicates{/ts}</span></a>
+ {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}
+ <a href="{$backURL}" title="{ts}Batch Merge Duplicate Contacts{/ts}" onclick="return confirm('{ts escape="js"}This will run the batch merge process on the selected duplicates. The operation will run in force merge mode - all selected duplicates will be merged into main contacts even in case of any conflicts. Click OK to proceed if you are sure you wish to run this operation.{/ts}');" class="button"><span>{ts}Force Merge Selected Duplicates{/ts}</span></a>
- {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}
+ <a href="{$backURL}" title="{ts}Batch Merge Duplicate Contacts{/ts}" onclick="return confirm('{ts escape="js"}This will run the batch merge process on the selected duplicates. The operation will run in safe mode - only records with no direct data conflicts will be merged. Click OK to proceed if you are sure you wish to run this operation.{/ts}');" class="button"><span>{ts}Safe Merge Selected Duplicates{/ts}</span></a>
{/if}
- <a href="{$backURL}" title="{ts}Batch Merge Duplicate Contacts{/ts}" onclick="return confirm('{ts escape="js"}This will run the batch merge process on the listed duplicates. The operation will run in safe mode - only records with no direct data conflicts will be merged. Click OK to proceed if you are sure you wish to run this operation.{/ts}');" class="button"><span>{ts}Force Merge All Duplicates{/ts}</span></a>
{if $gid}
{capture assign=backURL}{crmURL p="civicrm/contact/dedupefind" q="reset=1&action=update&rgid=`$rgid`&gid=`$gid`" a=1}{/capture}
{/if}
<a href="{$backURL}" title="{ts}Batch Merge Duplicate Contacts{/ts}" onclick="return confirm('{ts escape="js"}This will run the batch merge process on the listed duplicates. The operation will run in safe mode - only records with no direct data conflicts will be merged. Click OK to proceed if you are sure you wish to run this operation.{/ts}');" class="button"><span><div class="icon ui-icon-script"></div>{ts}Batch Merge All Duplicates{/ts}</span></a>
+ <a href='#' title="{ts}Flip Selected Duplicates{/ts}" class="crm-dedupe-flip-selections button"><span>{ts}Flip Selected Duplicates{/ts}</span></a>
+
{capture assign=backURL}{crmURL p="civicrm/contact/deduperules" q="reset=1" a=1}{/capture}
<a href="{$backURL}" class="button crm-button-type-cancel">
<span><div class="icon ui-icon-close"></div> {ts}Done{/ts}</span>
{data: "dst_street"},
{data: "src_postcode"},
{data: "dst_postcode"},
- {data: "conflicts"},
+ {
+ data: "conflicts",
+ className: "crm-pair-conflict"
+ },
{data: "weight"},
{data: "actions"},
],
}
// 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');
}
});
$('#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) {
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) {