Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CRM-20565 - Improve ajax dedupe lookups on contact add form #10341

Merged
merged 3 commits into from Jun 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 10 additions & 1 deletion CRM/Admin/Form/Preferences/Display.php
Expand Up @@ -77,9 +77,9 @@ public function preProcess() {
'weight' => 7,
),
'contact_ajax_check_similar' => array(
'html_type' => 'checkbox',
'title' => ts('Check for Similar Contacts'),
'weight' => 8,
'html_type' => NULL,
),
'user_dashboard_options' => array(
'html_type' => 'checkboxes',
Expand Down Expand Up @@ -150,6 +150,12 @@ public function buildQuickForm() {
$this->addElement('select', 'editor_id', ts('WYSIWYG Editor'), $wysiwyg_options, $extra);
$this->addElement('submit', 'ckeditor_config', ts('Configure CKEditor'));

$this->addRadio('contact_ajax_check_similar', ts('Check for Similar Contacts'), array(
'1' => ts('While Typing'),
'0' => ts('When Saving'),
'2' => ts('Never'),
Copy link
Member Author

@colemanw colemanw May 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: In all existing installs this preference is already set to either '1' or '0'. These new options keep the original meaning of 1 and 0 so no upgrade script is needed.

));

$editOptions = CRM_Core_OptionGroup::values('contact_edit_options', FALSE, FALSE, FALSE, 'AND v.filter = 0');
$this->assign('editOptions', $editOptions);

Expand Down Expand Up @@ -192,6 +198,9 @@ public function postProcess() {

$this->postProcessCommon();

// Fixme - shouldn't be needed
Civi::settings()->set('contact_ajax_check_similar', $this->_params['contact_ajax_check_similar']);

// If "Configure CKEditor" button was clicked
if (!empty($this->_params['ckeditor_config'])) {
// Suppress the "Saved" status message and redirect to the CKEditor Config page
Expand Down
15 changes: 14 additions & 1 deletion CRM/Contact/Form/Contact.php
Expand Up @@ -590,7 +590,7 @@ public function addRules() {
* @return bool
* email/openId
*/
public static function formRule($fields, &$errors, $contactId = NULL) {
public static function formRule($fields, &$errors, $contactId, $contactType) {
$config = CRM_Core_Config::singleton();

// validations.
Expand Down Expand Up @@ -713,6 +713,11 @@ public static function formRule($fields, &$errors, $contactId = NULL) {
}
}

// Check for duplicate contact if it wasn't already handled by ajax or disabled
if (!Civi::settings()->get('contact_ajax_check_similar')) {
self::checkDuplicateContacts($fields, $errors, $contactId, $contactType);
}

return $primaryID;
}

Expand Down Expand Up @@ -754,6 +759,14 @@ public function buildQuickForm() {
$className = 'CRM_Contact_Form_Edit_' . $this->_contactType;
$className::buildQuickForm($this);

// Ajax duplicate checking
$checkSimilar = $this->_action == CRM_Core_Action::ADD && Civi::settings()->get('contact_ajax_check_similar');
$this->assign('checkSimilar', $checkSimilar);
if ($checkSimilar == 1) {
$ruleParams = array('used' => 'Supervised', 'contact_type' => $this->_contactType);
$this->assign('ruleFields', CRM_Dedupe_BAO_Rule::dedupeRuleFields($ruleParams));
}

// build Custom data if Custom data present in edit option
$buildCustomData = 'noCustomDataPresent';
if (array_key_exists('CustomData', $this->_editOptions)) {
Expand Down
5 changes: 1 addition & 4 deletions CRM/Contact/Form/Edit/Household.php
Expand Up @@ -85,16 +85,13 @@ public static function buildQuickForm(&$form, $inlineEditMode = NULL) {
*/
public static function formRule($fields, $files, $contactID = NULL) {
$errors = array();
$primaryID = CRM_Contact_Form_Contact::formRule($fields, $errors, $contactID);
$primaryID = CRM_Contact_Form_Contact::formRule($fields, $errors, $contactID, 'Household');

// make sure that household name is set
if (empty($fields['household_name'])) {
$errors['household_name'] = 'Household Name should be set.';
}

//check for duplicate - dedupe rules
CRM_Contact_Form_Contact::checkDuplicateContacts($fields, $errors, $contactID, 'Household');

return empty($errors) ? TRUE : $errors;
}

Expand Down
12 changes: 1 addition & 11 deletions CRM/Contact/Form/Edit/Individual.php
Expand Up @@ -96,13 +96,6 @@ public static function buildQuickForm(&$form, $inlineEditMode = NULL) {
}

if (!$inlineEditMode) {
$checkSimilar = Civi::settings()->get('contact_ajax_check_similar');

if ($checkSimilar == NULL) {
$checkSimilar = 0;
}
$form->assign('checkSimilar', $checkSimilar);

//External Identifier Element
$form->addField('external_identifier', array('label' => 'External ID'));

Expand All @@ -129,16 +122,13 @@ public static function buildQuickForm(&$form, $inlineEditMode = NULL) {
*/
public static function formRule($fields, $files, $contactID = NULL) {
$errors = array();
$primaryID = CRM_Contact_Form_Contact::formRule($fields, $errors, $contactID);
$primaryID = CRM_Contact_Form_Contact::formRule($fields, $errors, $contactID, 'Individual');

// make sure that firstName and lastName or a primary OpenID is set
if (!$primaryID && (empty($fields['first_name']) || empty($fields['last_name']))) {
$errors['_qf_default'] = ts('First Name and Last Name OR an email OR an OpenID in the Primary Location should be set.');
}

//check for duplicate - dedupe rules
CRM_Contact_Form_Contact::checkDuplicateContacts($fields, $errors, $contactID, 'Individual');

return empty($errors) ? TRUE : $errors;
}

Expand Down
5 changes: 1 addition & 4 deletions CRM/Contact/Form/Edit/Organization.php
Expand Up @@ -86,16 +86,13 @@ public static function buildQuickForm(&$form, $inlineEditMode = NULL) {
*/
public static function formRule($fields, $files, $contactID = NULL) {
$errors = array();
$primaryID = CRM_Contact_Form_Contact::formRule($fields, $errors, $contactID);
$primaryID = CRM_Contact_Form_Contact::formRule($fields, $errors, $contactID, 'Organization');

// make sure that organization name is set
if (empty($fields['organization_name'])) {
$errors['organization_name'] = 'Organization Name should be set.';
}

//check for duplicate - dedupe rules
CRM_Contact_Form_Contact::checkDuplicateContacts($fields, $errors, $contactID, 'Organization');

// add code to make sure that the uniqueness criteria is satisfied
return empty($errors) ? TRUE : $errors;
}
Expand Down
22 changes: 20 additions & 2 deletions api/v3/Contact.php
Expand Up @@ -1359,12 +1359,24 @@ function civicrm_api3_contact_duplicatecheck($params) {
$dupes = CRM_Contact_BAO_Contact::getDuplicateContacts(
$params['match'],
$params['match']['contact_type'],
'Unsupervised',
$params['rule_type'],
array(),
CRM_Utils_Array::value('check_permissions', $params),
CRM_Utils_Array::value('dedupe_rule_id', $params)
);
$values = empty($dupes) ? array() : array_fill_keys($dupes, array());
$values = array();
if ($dupes && !empty($params['return'])) {
return civicrm_api3('Contact', 'get', array(
'return' => $params['return'],
'id' => array('IN' => $dupes),
'options' => CRM_Utils_Array::value('options', $params),
'sequential' => CRM_Utils_Array::value('sequential', $params),
'check_permissions' => CRM_Utils_Array::value('check_permissions', $params),
));
}
foreach ($dupes as $dupe) {
$values[$dupe] = array('id' => $dupe);
}
return civicrm_api3_create_success($values, $params, 'Contact', 'duplicatecheck');
}

Expand All @@ -1379,5 +1391,11 @@ function _civicrm_api3_contact_duplicatecheck_spec(&$params) {
'description' => 'This will default to the built in unsupervised rule',
'type' => CRM_Utils_Type::T_INT,
);
$params['rule_type'] = array(
'title' => 'Dedupe Rule Type',
'description' => 'If no rule id specified, pass "Unsupervised" or "Supervised"',
'type' => CRM_Utils_Type::T_STRING,
'api.default' => 'Unsupervised',
);
// @todo declare 'match' parameter. We don't have a standard for type = array yet.
}
4 changes: 2 additions & 2 deletions settings/Core.setting.php
Expand Up @@ -211,10 +211,10 @@
'group' => 'core',
'name' => 'contact_ajax_check_similar',
'type' => 'String',
'html_type' => 'Text',
'html_type' => 'radio',
'default' => '1',
'add' => '4.1',
'title' => 'Ajax Check Similar',
'title' => 'Check for Similar Contacts',
'is_domain' => 1,
'is_contact' => 0,
'description' => NULL,
Expand Down
7 changes: 4 additions & 3 deletions templates/CRM/Admin/Form/Preferences/Display.tpl
Expand Up @@ -114,12 +114,13 @@
</td>
</tr>
<tr class="crm-preferences-display-form-block-contact_ajax_check_similar">
<td class="label"></td>
<td>{$form.contact_ajax_check_similar.html} {$form.contact_ajax_check_similar.label}</td>
<td class="label">{$form.contact_ajax_check_similar.label}</td>
<td>{$form.contact_ajax_check_similar.html}</td>
</tr>
<tr class="crm-preferences-display-form-block-description">
<td>&nbsp;</td>
<td class="description">{ts}When enabled, checks for contacts with similar names as the user types values into the contact form name fields.{/ts}
{capture assign=dedupeRules}href="{crmURL p='civicrm/contact/deduperules' q='reset=1'}"{/capture}
<td class="description">{ts 1=$dedupeRules}When enabled, checks for possible matches on the "New Contact" form using the Supervised <a %1>matching rule specified in your system</a>.{/ts}
</td>
</tr>
<tr class="crm-preferences-display-form-block-activity_assignee_notification">
Expand Down
2 changes: 1 addition & 1 deletion templates/CRM/Admin/Page/APIExplorer.js
Expand Up @@ -760,7 +760,7 @@
alert(ts('Select an entity.'));
return;
}
if (!_.includes(action, 'get') && action != 'check') {
if (!_.includes(action, 'get') && !_.includes(action, 'check')) {
var msg = action === 'delete' ? ts('This will delete data from CiviCRM. Are you sure?') : ts('This will write to the database. Continue?');
CRM.confirm({title: ts('Confirm %1', {1: action}), message: msg}).on('crmConfirm:yes', execute);
} else {
Expand Down
89 changes: 87 additions & 2 deletions templates/CRM/Contact/Form/Contact.tpl
Expand Up @@ -111,8 +111,9 @@

<script type="text/javascript" >
CRM.$(function($) {
var $form = $("form.{/literal}{$form.formClass}{literal}");
var action = "{/literal}{$action}{literal}";
var $form = $("form.{/literal}{$form.formClass}{literal}"),
action = "{/literal}{$action}{literal}",
_ = CRM._;

$('.crm-accordion-body').each( function() {
//remove tab which doesn't have any element
Expand Down Expand Up @@ -258,6 +259,90 @@
$('div' + addClass).last().show();
});
});

{/literal}{* Ajax check for matching contacts *}
{if $checkSimilar == 1}
var contactType = {$contactType|@json_encode},
rules = {*$ruleFields|@json_encode*}{literal}[
'first_name',
'last_name',
'nick_name',
'household_name',
'organization_name',
'email'
],
ruleFields = {},
$ruleElements = $(),
matchMessage,
runningCheck = 0;
$.each(rules, function(i, field) {
// Match regular fields
var $el = $('#' + field + ', #' + field + '_1_' + field, $form).filter(':input');
// Match custom fields
if (!$el.length && field.lastIndexOf('_') > 0) {
var pieces = field.split('_');
field = 'custom_' + pieces[pieces.length-1];
$el = $('#' + field + ', [name=' + field + '_-1]', $form).filter(':input');
}
if ($el.length) {
ruleFields[field] = $el;
$ruleElements = $ruleElements.add($el);
}
});
$ruleElements.on('change', checkMatches);
function checkMatches() {
if ($(this).is('input[type=text]') && $(this).val().length < 3) {
return;
}
var match = {contact_type: contactType},
checkNum = ++runningCheck;
$.each(ruleFields, function(fieldName, ruleField) {
if (ruleField.length > 1) {
match[fieldName] = ruleField.filter(':checked').val();
} else if (ruleField.is('input[type=text]')) {
if (ruleField.val().length > 2) {
match[fieldName] = ruleField.val() + '%'; // Todo: remove wildcard when switching to contact.match api
}
} else {
match[fieldName] = ruleField.val();
}
});
// CRM-20565 - Need a good default matching rule before using the dedupe engine for this. Using contact.get for now.
// CRM.api3('contact', 'duplicatecheck', {
// match: match,
// rule_type: 'Supervised',
// options: {sort: 'sort_name'},
// return: ['display_name', 'email']
// }).done(function(data) {
CRM.api3('contact', 'get', _.extend({
options: {sort: 'sort_name'},
return: ['display_name', 'email']
}, match)).done(function(data) {
// If a new request has started running, cancel this one.
if (checkNum < runningCheck) {
return;
}
// Close msg if it exists
matchMessage && matchMessage.close && matchMessage.close();
var title = data.count == 1 ? {/literal}"{ts escape='js'}Similar Contact Found{/ts}" : "{ts escape='js'}Similar Contacts Found{/ts}"{literal},
msg = "<em>{/literal}{ts escape='js'}If the contact you were trying to add is listed below, click their name to view or edit their record{/ts}{literal}:</em>";
if (data.is_error == 1 || data.count == 0) {
return;
}
msg += '<ul class="matching-contacts-actions">';
$.each(data.values, function(i, contact) {
contact.email = contact.email || '';
msg += '<li><a href="'+ CRM.url('civicrm/contact/view', {reset: 1, cid: contact.id}) + '">'+ contact.display_name +'</a> '+contact.email+'</li>';
});
msg += '</ul>';
matchMessage = CRM.alert(msg, title);
$('.matching-contacts-actions a').click(function() {
// No confirmation dialog on click
$('[data-warn-changes=true]').attr('data-warn-changes', 'false');
});
});
}
{/literal}{/if}{literal}
});

</script>
Expand Down
36 changes: 0 additions & 36 deletions templates/CRM/Contact/Form/Edit/Individual.tpl
Expand Up @@ -27,45 +27,9 @@
<script type="text/javascript">
{literal}
CRM.$(function($) {
{/literal}
var cid = "{$contactId}",
viewIndividual = "{crmURL p='civicrm/contact/view' q='reset=1&cid=' h=0}",
checkSimilar = {$checkSimilar},
lastnameMsg;
{literal}
if ($('#contact_sub_type *').length == 0) {//if they aren't any subtype we don't offer the option
$('#contact_sub_type').parent().hide();
}
if (cid.length || !checkSimilar) {
return;//no dupe check if this is a modif or if checkSimilar is disabled (contact_ajax_check_similar in civicrm_setting table)
}
$('#last_name').change(function() {
// Close msg if it exists
lastnameMsg && lastnameMsg.close && lastnameMsg.close();
if (this.value == '') return;
CRM.api3('contact', 'get', {
sort_name: $('#last_name').val(),
contact_type: 'Individual',
'return': 'display_name,sort_name,email'
}).done(function(data) {
var title = data.count == 1 ? {/literal}"{ts escape='js'}Similar Contact Found{/ts}" : "{ts escape='js'}Similar Contacts Found{/ts}"{literal},
msg = "<em>{/literal}{ts escape='js'}If the person you were trying to add is listed below, click their name to view or edit their record{/ts}{literal}:</em>";
if (data.is_error == 1 || data.count == 0) {
return;
}
msg += '<ul class="matching-contacts-actions">';
$.each(data.values, function(i, contact) {
contact.email = contact.email || '';
msg += '<li><a href="'+viewIndividual+contact.id+'">'+ contact.display_name +'</a> '+contact.email+'</li>';
});
msg += '</ul>';
lastnameMsg = CRM.alert(msg, title);
$('.matching-contacts-actions a').click(function() {
// No confirmation dialog on click
$('[data-warn-changes=true]').attr('data-warn-changes', 'false');
});
});
});
});
</script>
{/literal}
Expand Down