2 +--------------------------------------------------------------------+
3 | Copyright CiviCRM LLC. All rights reserved. |
5 | This work is published under the GNU AGPLv3 license with some |
6 | permitted exceptions and without any warranty. For full license |
7 | and copyright information, see https://civicrm.org/licensing |
8 +--------------------------------------------------------------------+
10 <div class="crm-block crm-form-block crm-contact-merge-form-block">
12 {ts}Click <strong>Merge</strong> to move data from the Duplicate Contact on the left into the Main Contact. In addition to the contact data (address, phone, email...), you may choose to move all or some of the related activity records (groups, contributions, memberships, etc.).{/ts} {help id="intro"}
15 <div class="message status">
16 {icon icon="fa-info-circle"}{/icon}
17 <strong>{ts}WARNING: The duplicate contact record WILL BE DELETED after the merge is complete.{/ts}</strong>
21 <div class="message status">
22 {icon icon="fa-info-circle"}{/icon}
23 <strong>{ts 1=$config->userFramework}WARNING: There are %1 user accounts associated with both the original and duplicate contacts. Ensure that the %1 user you want to retain is on the right - if necessary use the 'Flip between original and duplicate contacts.' option at top to swap the positions of the two records before doing the merge.
24 The user record associated with the duplicate contact will not be deleted, but will be unlinked from the associated contact record (which will be deleted).
25 You will need to manually delete that user (click on the link to open the %1 user account in new screen). You may need to give thought to how you handle any content or contents associated with that user.{/ts}</strong>
29 <div class="crm-submit-buttons">
30 {include file="CRM/common/formButtons.tpl" location="top"}
33 <div class="action-link">
34 {if $prev}<a href="{$prev}" class="crm-hover-button action-item"><i class="crm-i fa-chevron-left" aria-hidden="true"></i> {ts}Previous{/ts}</a>{/if}
35 {if $next}<a href="{$next}" class="crm-hover-button action-item">{ts}Next{/ts} <i class="crm-i fa-chevron-right" aria-hidden="true"></i></a>{/if}
36 <a href="{$flip}" class="action-item crm-hover-button">
37 <i class="crm-i fa-random" aria-hidden="true"></i>
38 {ts}Flip between original and duplicate contacts.{/ts}
42 <div class="action-link">
43 <a href="#" class="action-item crm-hover-button crm-notDuplicate" title={ts}Mark this pair as not a duplicate.{/ts} onClick="processDupes( {$main_cid|escape}, {$other_cid|escape}, 'dupe-nondupe', 'merge-contact', '{$browseUrl}' );return false;">
44 <i class="crm-i fa-times-circle" aria-hidden="true"></i>
45 {ts}Mark this pair as not a duplicate.{/ts}
49 <div class="action-link">
50 <a href="javascript:void(0);" class="action-item crm-hover-button toggle_equal_rows">
51 <i class="crm-i fa-eye-slash" aria-hidden="true"></i>
52 {ts}Show/hide rows with the same data on each contact record.{/ts}
56 <table class="row-highlight">
57 <tr class="columnheader">
59 <th>{$otherContactTypeIcon} <a href="{crmURL p='civicrm/contact/view' q="reset=1&cid=$other_cid"}">{$other_name|escape}</a> ({ts}duplicate{/ts})</th>
60 <th>{ts}Mark All{/ts}<br />=={$form.toggleSelect.html} ==></th>
61 <th>{$mainContactTypeIcon}<a href="{crmURL p='civicrm/contact/view' q="reset=1&cid=$main_cid"}">{$main_name|escape}</a></th>
62 <th width="300">{ts}Add/overwrite?{/ts}</th>
66 {foreach from=$summary_rows item=summaryRow}
68 <td>{$summaryRow.label}</td>
69 <td>{$summaryRow.other_contact_value}</td>
71 <td>{$summaryRow.main_contact_value}</td>
76 {foreach from=$rows item=row key=field}
78 {if !$row.main && !$row.other}
79 <tr style="background-color: #fff !important; border-bottom:1px solid #ccc !important;" class="no-data">
81 <strong>{$row.title|escape}</strong>
84 {if $row.main eq $row.other}
85 <tr class="merge-row-equal crm-row-ok {cycle values="odd-row,even-row"}">
87 <tr class="crm-row-error {cycle values="odd-row,even-row"}">
94 {assign var=position value=$field|strrpos:'_'}
95 {assign var=blockId value=$field|substr:$position+1}
96 {assign var=blockName value=$field|substr:14:$position-14}
99 {* @TODO check if this is ever an array or a fileName? *}
100 {if $row.location_entity == "email" OR
101 $row.location_entity == "address"}
102 <span style="white-space: pre">
106 {if !is_array($row.other)}
108 {elseif $row.other.fileName}
109 {$row.other.fileName|escape}
111 {', '|implode:$row.other}
116 <td style='white-space: nowrap'>
117 {if $form.$field}=={$form.$field.html|crmAddClass:"select-row"}==>{/if}
120 {* For location blocks *}
121 {if $row.location_entity}
125 {if $row.location_entity == "email" OR
126 $row.location_entity == "address"}
127 <span style="white-space: pre" id="main_{$blockName|escape}_{$blockId|escape}">
129 <span id="main_{$blockName|escape}_{$blockId|escape}">
131 {* @TODO check if this is ever an array or a fileName? *}
132 {if !is_array($row.main)}
134 {elseif $row.main.fileName}
135 {$row.main.fileName|escape}
137 {', '|implode:$row.main}
144 {* Display location for fields with locations *}
145 {if $blockName eq 'email' || $blockName eq 'phone' || $blockName eq 'address' || $blockName eq 'im' }
146 {$form.location_blocks.$blockName.$blockId.locTypeId.html}
149 {* Display other_type_id for websites, ims and phones *}
150 {if $blockName eq 'website' || $blockName eq 'im' || $blockName eq 'phone' }
151 {$form.location_blocks.$blockName.$blockId.typeTypeId.html}
154 {* Display the overwrite/add/add new label *}
155 <span id="main_{$blockName}_{$blockId}_overwrite" class="location_block_controls">
157 <span class="location_primary">
158 {if $row.main && $row.main_is_primary == "1"}{ts}Primary{/ts}{/if}
161 <span class="location_block_controls_options">
162 <span class="location_operation_description">
163 {if $row.main}({ts}overwrite{/ts}){else}({ts}add{/ts}){/if}
165 <span style="display: block" class="location_operation_checkbox">
166 {if $row.main && ($blockName eq 'email' || $blockName eq 'phone')}
167 {$form.location_blocks.$blockName.$blockId.operation.html}
170 <span style="display: block" class="location_set_other_primary">
171 {if $blockName neq 'website' && (($row.main && $row.main_is_primary != "1") || !$row.main)}
172 {$form.location_blocks.$blockName.$blockId.set_other_primary.html}
180 {* For non-location blocks *}
185 {if !is_array($row.main)}
187 {elseif $row.main.fileName}
188 {$row.main.fileName|escape}
190 {', '|implode:$row.main}
196 {if $row.main || $row.other}
198 {if $row.main == $row.other}
199 <span class="action_label">({ts}match{/ts})</span><br />
201 <span class="action_label">({ts}overwrite{/ts})</span><br />
203 <span class="action_label">({ts}add{/ts})</span>
214 {foreach from=$rel_tables item=params key=paramName}
215 {if $paramName eq 'move_rel_table_users'}
216 <tr class="{cycle values="even-row,odd-row"}">
217 <td><strong>{ts}Move related...{/ts}</strong></td><td>{if $otherUfId}<a target="_blank" href="{$params.other_url}">{$otherUfName}</a></td><td style='white-space: nowrap'>=={$form.$paramName.html|crmAddClass:"select-row"}==>{else}<td style='white-space: nowrap'></td>{/if}</td><td>{if $mainUfId}<a target="_blank" href="{$params.main_url}">{$mainUfName}</a>{/if}</td>
218 <td>({ts}migrate{/ts})</td>
221 <tr class="{cycle values="even-row,odd-row"}">
222 <td><strong>{ts}Move related...{/ts}</strong></td><td><a href="{$params.other_url}">{$params.title}</a></td><td style='white-space: nowrap'>=={$form.$paramName.html|crmAddClass:"select-row"}==></td><td><a href="{$params.main_url}">{$params.title}</a>{if $form.operation.$paramName.add.html} {$form.operation.$paramName.add.html}{/if}</td>
223 <td>({ts}migrate{/ts})</td>
229 <div class="crm-submit-buttons">
230 {include file="CRM/common/formButtons.tpl" location="bottom"}
235 <script type="text/javascript">
237 var locationBlockInfo = {/literal}{$locationBlockInfo}{literal};
238 var allBlock = {/literal}{$mainLocBlock}{literal};
241 * Triggered when a 'location' or 'type' destination is changed, and when
242 * the operation or 'set primary' checkboxes are changed.
244 * Check to see if the 'main' contact record has a corresponding location
245 * block when the destination of a field is changed. Allow existing location
246 * fields to be overwritten with data from the 'other' contact.
248 * @param blockName string
249 * The name of the entity.
251 * The block ID being affected.
252 * @param event object
253 * The event that triggered the update.
255 function updateMainLocationBlock(blockName, blockId, event) {
257 // Get type of select list that's been changed (location or type)
258 var locTypeId = CRM.$('select#location_blocks_' + blockName + '_' + blockId + '_locTypeId').val();
259 var typeTypeId = CRM.$('select#location_blocks_' + blockName + '_' + blockId + '_typeTypeId').val();
261 // @todo Fix this 'special handling' for websites (no location id)
266 // Look for a matching block on the main contact
268 var mainBlockDisplay = '';
269 var mainBlock = findBlock(blockName, locTypeId, typeTypeId);
270 if (mainBlock != false) {
271 mainBlockDisplay = mainBlock['display'];
272 mainBlockId = mainBlock['id'];
275 // Update main location display and id
276 CRM.$("input[name='location_blocks[" + blockName + "][" + blockId + "][mainContactBlockId]']").val(mainBlockId);
277 CRM.$("#main_" + blockName + "_" + blockId).html(mainBlockDisplay);
279 // Update controls area
281 // Get the parent block once for speed
282 var this_controls = CRM.$("#main_" + blockName + "_" + blockId + "_overwrite");
284 // Update primary label
285 if (mainBlock != false && mainBlock['is_primary'] == '1') {
286 this_controls.find(".location_primary").text('{/literal}{ts escape='js'}Primary{/ts}{literal}');
289 this_controls.find(".location_primary").text('');
292 // Update operation description
293 var operation_description = "{/literal}{ts escape='js'}add{/ts}{literal}";
294 var add_new_check_length = this_controls.find(".location_operation_checkbox input:checked").length;
295 if (mainBlock != false) {
296 if (add_new_check_length > 0) {
297 operation_description = "{/literal}{ts escape='js'}add new{/ts}{literal}";
300 operation_description = "{/literal}{ts escape='js'}overwrite{/ts}{literal}";
303 this_controls.find(".location_operation_description").text("(" + operation_description + ")");
305 // Skip if the 'add new' or 'set primary' checkboxes were clicked
306 if (event.target.id.match(/(operation|set_other_primary)/) === null) {
307 // Display 'Add new' checkbox if there is a main block, and this is an
308 // email or phone type.
309 if (mainBlock != false && (blockName == 'email' || blockName == 'phone')) {
310 var op_id = 'location_blocks[' + blockName + '][' + blockId + '][operation]';
311 this_controls.find(".location_operation_checkbox").html(
312 '<input id="' + op_id + '" name="' + op_id + '" type="checkbox" value="1" class="crm-form-checkbox"><label for="' + op_id + '">{/literal}{ts escape='js'}Add new{/ts}{literal}</label>'
316 this_controls.find(".location_operation_checkbox").html('');
320 // Skip if 'set primary' was clicked
321 if (event.target.id.match(/(set_other_primary)/) === null) {
322 // Display 'Set primary' checkbox if applicable
323 if (blockName != 'website' && (mainBlock == false || mainBlock['is_primary'] != "1" || add_new_check_length > 0)) {
324 var prim_id = 'location_blocks[' + blockName + '][' + blockId + '][set_other_primary]';
325 this_controls.find(".location_set_other_primary").html(
326 '<input id="' + prim_id + '" name="' + prim_id + '" type="checkbox" value="1" class="crm-form-checkbox"><label for="' + prim_id + '">{/literal}{ts escape='js'}Set as primary{/ts}{literal}</label>'
330 this_controls.find(".location_set_other_primary").html('');
337 * Look for a matching 'main' contact location block by entity, location and
340 * @param entName string
341 * The entity name to lookup.
342 * @param locationID int
343 * The location ID to lookup.
345 * The type ID to lookup.
347 * @returns boolean|object
348 * Returns false if no match, otherwise an object with the location ID and
351 function findBlock(entName, locationID, typeID) {
352 var entityArray = allBlock[entName];
354 for (var i = 0; i < entityArray.length; i++) {
355 // Match based on location and type ID, depending on the entity info
356 if (locationBlockInfo[entName]['hasLocation'] == false || locationID == entityArray[i]['location_type_id']) {
357 if (locationBlockInfo[entName]['hasType'] == false || typeID == entityArray[i][locationBlockInfo[entName]['hasType']]) {
359 display: entityArray[i][locationBlockInfo[entName]['displayField']],
360 id: entityArray[i]['id'],
361 is_primary: entityArray[i]['is_primary']
371 * Called when a 'set primary' checkbox is clicked in order to disable any
372 * other 'set primary' checkboxes for blocks of the same entity. So don't let
373 * users try to set two different phone numbers as primary on the form.
375 * @param event object
376 * The event that triggered the update
378 function updateSetPrimaries(event) {
379 var nameSplit = event.target.name.split('[');
380 var blockName = nameSplit[1].slice(0, -1);
381 var controls = CRM.$('span.location_block_controls[id^="main_' + blockName + '"]');
384 controls.find('input[id$="[set_other_primary]"]:not(:checked)').removeAttr("disabled");
386 // If one is checked, disable the others
387 if (controls.find('input[id$="[set_other_primary]"]:checked').length > 0) {
388 controls.find('input[id$="[set_other_primary]"]:not(:checked)').attr("disabled", "disabled");
393 * Toggle the location type and the is_primary on & off depending on whether the merge box is ticked.
397 function toggleRelatedLocationFields(element) {
398 relatedElements = CRM.$(element).parent().siblings('td').find('input,select,label,hidden');
399 if (CRM.$(element).is(':checked')) {
400 relatedElements.removeClass('disabled').attr('disabled', false);
404 relatedElements.addClass('disabled').attr('disabled', true);
410 $('input.crm-form-checkbox[data-is_location]').on('click', function(){
411 toggleRelatedLocationFields(this)
414 // Show/hide matching data rows
415 $('.toggle_equal_rows').click(function() {
416 $('tr.merge-row-equal').toggle();
419 // Call mergeBlock whenever a location type is changed
420 // (This is applied to the body because the inputs can be added dynamically
421 // to the form, and we need to catch when they change.)
422 $('body').on('change', 'select[id$="locTypeId"],select[id$="typeTypeId"],input[id$="[operation]"],input[id$="[set_other_primary]"]', function(event){
424 // All the information we need is held in the id, separated by underscores
425 var nameSplit = this.name.split('[');
427 // Lookup the main value, if any are available
428 if (allBlock[nameSplit[1].slice(0, -1)] != undefined) {
429 updateMainLocationBlock(nameSplit[1].slice(0, -1), nameSplit[2].slice(0, -1), event);
432 // Update all 'set primary' checkboxes
433 updateSetPrimaries(event);
442 {* process the dupe contacts *}
443 {include file="CRM/common/dedupe.tpl"}