2 +--------------------------------------------------------------------+
3 | CiviCRM version 4.7 |
4 +--------------------------------------------------------------------+
5 | Copyright CiviCRM LLC (c) 2004-2018 |
6 +--------------------------------------------------------------------+
7 | This file is a part of CiviCRM. |
9 | CiviCRM is free software; you can copy, modify, and distribute it |
10 | under the terms of the GNU Affero General Public License |
11 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | CiviCRM is distributed in the hope that it will be useful, but |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
16 | See the GNU Affero General Public License for more details. |
18 | You should have received a copy of the GNU Affero General Public |
19 | License and the CiviCRM Licensing Exception along |
20 | with this program; if not, contact CiviCRM LLC |
21 | at info[AT]civicrm[DOT]org. If you have questions about the |
22 | GNU Affero General Public License or the licensing of CiviCRM, |
23 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
24 +--------------------------------------------------------------------+
26 <div class="crm-block crm-form-block crm-contact-merge-form-block">
28 {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"}
31 <div class="message status">
32 <div class="icon inform-icon"></div>
33 <strong>{ts}WARNING: The duplicate contact record WILL BE DELETED after the merge is complete.{/ts}</strong>
37 <div class="message status">
38 <div class="icon inform-icon"></div>
39 <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.
40 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).
41 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>
45 <div class="crm-submit-buttons">
46 {include file="CRM/common/formButtons.tpl" location="top"}
49 <div class="action-link">
50 {if $prev}<a href="{$prev}" class="crm-hover-button action-item"><i class="crm-i fa-chevron-left"></i> {ts}Previous{/ts}</a>{/if}
51 {if $next}<a href="{$next}" class="crm-hover-button action-item">{ts}Next{/ts} <i class="crm-i fa-chevron-right"></i></a>{/if}
52 <a href="{$flip}" class="action-item crm-hover-button">
53 <i class="crm-i fa-random"></i>
54 {ts}Flip between original and duplicate contacts.{/ts}
58 <div class="action-link">
59 <a href="#" class="action-item crm-hover-button crm-notDuplicate" title={ts}Mark this pair as not a duplicate.{/ts} onClick="processDupes( {$main_cid}, {$other_cid}, 'dupe-nondupe', 'merge-contact', '{$browseUrl}' );return false;">
60 <i class="crm-i fa-times-circle"></i>
61 {ts}Mark this pair as not a duplicate.{/ts}
65 <div class="action-link">
66 <a href="javascript:void(0);" class="action-item crm-hover-button toggle_equal_rows">
67 <i class="crm-i fa-eye-slash"></i>
68 {ts}Show/hide rows with the same data on each contact record.{/ts}
72 <table class="row-highlight">
73 <tr class="columnheader">
75 <th><a href="{crmURL p='civicrm/contact/view' q="reset=1&cid=$other_cid"}">{$other_name}</a> ({ts}duplicate{/ts})</th>
76 <th>{ts}Mark All{/ts}<br />=={$form.toggleSelect.html} ==></th>
77 <th><a href="{crmURL p='civicrm/contact/view' q="reset=1&cid=$main_cid"}">{$main_name}</a></th>
78 <th width="300">Add/overwrite?</th>
81 {crmAPI var='other_result' entity='Contact' action='get' return="modified_date" id=$other_cid}
83 {crmAPI var='main_result' entity='Contact' action='get' return="modified_date" id=$main_cid}
86 <td>Last modified</td>
87 <td>{$other_result.values.0.modified_date|crmDate} {if $other_result.values.0.modified_date gt $main_result.values.0.modified_date} (Most recent) {/if}</td>
89 <td>{$main_result.values.0.modified_date|crmDate} {if $main_result.values.0.modified_date gt $other_result.values.0.modified_date} (Most recent) {/if}</td>
93 {foreach from=$rows item=row key=field}
95 {if !isset($row.main) && !isset($row.other)}
96 <tr style="background-color: #fff !important; border-bottom:1px solid #ccc !important;" class="no-data">
98 <strong>{$row.title}</strong>
101 {if $row.main eq $row.other}
102 <tr class="merge-row-equal crm-row-ok {cycle values="odd-row,even-row"}">
104 <tr class="crm-row-error {cycle values="odd-row,even-row"}">
111 {assign var=position value=$field|strrpos:'_'}
112 {assign var=blockId value=$field|substr:$position+1}
113 {assign var=blockName value=$field|substr:14:$position-14}
116 {* @TODO check if this is ever an array or a fileName? *}
117 {* This is on one long line for address formatting *}
118 {if $row.title|substr:0:7 == "Address"}<span style="white-space: pre">{else}<span>{/if}{if !is_array($row.other)}{$row.other}{elseif $row.other.fileName}{$row.other.fileName}{else}{', '|implode:$row.other}{/if}</span>
121 <td style='white-space: nowrap'>
122 {if $form.$field}=={$form.$field.html|crmAddClass:"select-row"}==>{/if}
125 {* For location blocks *}
126 {if $row.title|substr:0:5 == "Email" OR
127 $row.title|substr:0:7 == "Address" OR
128 $row.title|substr:0:2 == "IM" OR
129 $row.title|substr:0:7 == "Website" OR
130 $row.title|substr:0:5 == "Phone"}
134 {if $row.title|substr:0:7 == "Address"}
135 <span style="white-space: pre" id="main_{$blockName}_{$blockId}">
137 <span id="main_{$blockName}_{$blockId}">
139 {* @TODO check if this is ever an array or a fileName? *}
140 {if !is_array($row.main)}
142 {elseif $row.main.fileName}
145 {', '|implode:$row.main}
152 {* Display location for fields with locations *}
153 {if $blockName eq 'email' || $blockName eq 'phone' || $blockName eq 'address' || $blockName eq 'im' }
154 {$form.location_blocks.$blockName.$blockId.locTypeId.html}
157 {* Display other_type_id for websites, ims and phones *}
158 {if $blockName eq 'website' || $blockName eq 'im' || $blockName eq 'phone' }
159 {$form.location_blocks.$blockName.$blockId.typeTypeId.html}
162 {* Display the overwrite/add/add new label *}
163 <span id="main_{$blockName}_{$blockId}_overwrite" class="location_block_controls">
165 <span class="location_primary">
166 {if $row.main && $row.main_is_primary == "1"}Primary{/if}
169 <span class="location_block_controls_options">
170 <span class="location_operation_description">
171 {if $row.main}({ts}overwrite{/ts}){else}({ts}add{/ts}){/if}
173 <span style="display: block" class="location_operation_checkbox">
174 {if $row.main && ($blockName eq 'email' || $blockName eq 'phone')}
175 {$form.location_blocks.$blockName.$blockId.operation.html}
178 <span style="display: block" class="location_set_other_primary">
179 {if $blockName neq 'website' && (($row.main && $row.main_is_primary != "1") || !$row.main)}
180 {$form.location_blocks.$blockName.$blockId.set_other_primary.html}
188 {* For non-location blocks *}
193 {if !is_array($row.main)}
195 {elseif $row.main.fileName}
198 {', '|implode:$row.main}
204 {if isset($row.main) || isset($row.other)}
206 {if $row.main == $row.other}
207 <span class="action_label">({ts}match{/ts})</span><br />
209 <span class="action_label">({ts}overwrite{/ts})</span><br />
211 <span class="action_label">({ts}add{/ts})</span>
222 {foreach from=$rel_tables item=params key=paramName}
223 {if $paramName eq 'move_rel_table_users'}
224 <tr class="{cycle values="even-row,odd-row"}">
225 <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>
226 <td>({ts}migrate{/ts})</td>
229 <tr class="{cycle values="even-row,odd-row"}">
230 <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>
231 <td>({ts}migrate{/ts})</td>
237 <div class="crm-submit-buttons">
238 {include file="CRM/common/formButtons.tpl" location="bottom"}
243 <script type="text/javascript">
245 var locationBlockInfo = {/literal}{$locationBlockInfo}{literal};
246 var allBlock = {/literal}{$mainLocBlock}{literal};
249 * Triggered when a 'location' or 'type' destination is changed, and when
250 * the operation or 'set primary' checkboxes are changed.
252 * Check to see if the 'main' contact record has a corresponding location
253 * block when the destination of a field is changed. Allow existing location
254 * fields to be overwritten with data from the 'other' contact.
256 * @param blockName string
257 * The name of the entity.
259 * The block ID being affected.
260 * @param event object
261 * The event that triggered the update.
263 function updateMainLocationBlock(blockName, blockId, event) {
265 // Get type of select list that's been changed (location or type)
266 var locTypeId = CRM.$('select#location_blocks_' + blockName + '_' + blockId + '_locTypeId').val();
267 var typeTypeId = CRM.$('select#location_blocks_' + blockName + '_' + blockId + '_typeTypeId').val();
269 // @todo Fix this 'special handling' for websites (no location id)
274 // Look for a matching block on the main contact
276 var mainBlockDisplay = '';
277 var mainBlock = findBlock(blockName, locTypeId, typeTypeId);
278 if (mainBlock != false) {
279 mainBlockDisplay = mainBlock['display'];
280 mainBlockId = mainBlock['id'];
283 // Update main location display and id
284 CRM.$("input[name='location_blocks[" + blockName + "][" + blockId + "][mainContactBlockId]']").val(mainBlockId);
285 CRM.$("#main_" + blockName + "_" + blockId).html(mainBlockDisplay);
287 // Update controls area
289 // Get the parent block once for speed
290 var this_controls = CRM.$("#main_" + blockName + "_" + blockId + "_overwrite");
292 // Update primary label
293 if (mainBlock != false && mainBlock['is_primary'] == '1') {
294 this_controls.find(".location_primary").text('Primary');
297 this_controls.find(".location_primary").text('');
300 // Update operation description
301 var operation_description = "{/literal}{ts}add{/ts}{literal}";
302 var add_new_check_length = this_controls.find(".location_operation_checkbox input:checked").length;
303 if (mainBlock != false) {
304 if (add_new_check_length > 0) {
305 operation_description = "{/literal}{ts}add new{/ts}{literal}";
308 operation_description = "{/literal}{ts}overwrite{/ts}{literal}";
311 this_controls.find(".location_operation_description").text("(" + operation_description + ")");
313 // Skip if the 'add new' or 'set primary' checkboxes were clicked
314 if (event.target.id.match(/(operation|set_other_primary)/) === null) {
315 // Display 'Add new' checkbox if there is a main block, and this is an
316 // email or phone type.
317 if (mainBlock != false && (blockName == 'email' || blockName == 'phone')) {
318 var op_id = 'location_blocks[' + blockName + '][' + blockId + '][operation]';
319 this_controls.find(".location_operation_checkbox").html(
320 '<input id="' + op_id + '" name="' + op_id + '" type="checkbox" value="1" class="crm-form-checkbox"><label for="' + op_id + '">{/literal}{ts}Add new{/ts}{literal}</label>'
324 this_controls.find(".location_operation_checkbox").html('');
328 // Skip if 'set primary' was clicked
329 if (event.target.id.match(/(set_other_primary)/) === null) {
330 // Display 'Set primary' checkbox if applicable
331 if (blockName != 'website' && (mainBlock == false || mainBlock['is_primary'] != "1" || add_new_check_length > 0)) {
332 var prim_id = 'location_blocks[' + blockName + '][' + blockId + '][set_other_primary]';
333 this_controls.find(".location_set_other_primary").html(
334 '<input id="' + prim_id + '" name="' + prim_id + '" type="checkbox" value="1" class="crm-form-checkbox"><label for="' + prim_id + '">{/literal}{ts}Set as primary{/ts}{literal}</label>'
338 this_controls.find(".location_set_other_primary").html('');
345 * Look for a matching 'main' contact location block by entity, location and
348 * @param entName string
349 * The entity name to lookup.
350 * @param locationID int
351 * The location ID to lookup.
353 * The type ID to lookup.
355 * @returns boolean|object
356 * Returns false if no match, otherwise an object with the location ID and
359 function findBlock(entName, locationID, typeID) {
360 var entityArray = allBlock[entName];
362 for (var i = 0; i < entityArray.length; i++) {
363 // Match based on location and type ID, depending on the entity info
364 if (locationBlockInfo[entName]['hasLocation'] == false || locationID == entityArray[i]['location_type_id']) {
365 if (locationBlockInfo[entName]['hasType'] == false || typeID == entityArray[i][locationBlockInfo[entName]['hasType']]) {
367 display: entityArray[i][locationBlockInfo[entName]['displayField']],
368 id: entityArray[i]['id'],
369 is_primary: entityArray[i]['is_primary']
379 * Called when a 'set primary' checkbox is clicked in order to disable any
380 * other 'set primary' checkboxes for blocks of the same entity. So don't let
381 * users try to set two different phone numbers as primary on the form.
383 * @param event object
384 * The event that triggered the update
386 function updateSetPrimaries(event) {
387 var nameSplit = event.target.name.split('[');
388 var blockName = nameSplit[1].slice(0, -1);
389 var controls = CRM.$('span.location_block_controls[id^="main_' + blockName + '"]');
392 controls.find('input[id$="[set_other_primary]"]:not(:checked)').removeAttr("disabled");
394 // If one is checked, disable the others
395 if (controls.find('input[id$="[set_other_primary]"]:checked').length > 0) {
396 controls.find('input[id$="[set_other_primary]"]:not(:checked)').attr("disabled", "disabled");
401 * Toggle the location type and the is_primary on & off depending on whether the merge box is ticked.
405 function toggleRelatedLocationFields(element) {
406 relatedElements = CRM.$(element).parent().siblings('td').find('input,select,label,hidden');
407 if (CRM.$(element).is(':checked')) {
408 relatedElements.removeClass('disabled').attr('disabled', false);
412 relatedElements.addClass('disabled').attr('disabled', true);
418 $('input.crm-form-checkbox[data-is_location]').on('click', function(){
419 toggleRelatedLocationFields(this)
422 // Show/hide matching data rows
423 $('.toggle_equal_rows').click(function() {
424 $('tr.merge-row-equal').toggle();
427 // Call mergeBlock whenever a location type is changed
428 // (This is applied to the body because the inputs can be added dynamically
429 // to the form, and we need to catch when they change.)
430 $('body').on('change', 'select[id$="locTypeId"],select[id$="typeTypeId"],input[id$="[operation]"],input[id$="[set_other_primary]"]', function(event){
432 // All the information we need is held in the id, separated by underscores
433 var nameSplit = this.name.split('[');
435 // Lookup the main value, if any are available
436 if (allBlock[nameSplit[1].slice(0, -1)] != undefined) {
437 updateMainLocationBlock(nameSplit[1].slice(0, -1), nameSplit[2].slice(0, -1), event);
440 // Update all 'set primary' checkboxes
441 updateSetPrimaries(event);
450 {* process the dupe contacts *}
451 {include file="CRM/common/dedupe.tpl"}