From d6def514cf3d5491091d4c6f81327f864015144a Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Fri, 12 May 2017 12:38:23 -0500 Subject: [PATCH 1/3] CRM-20565 - Improve ajax dedupe lookups on contact add form --- CRM/Admin/Form/Preferences/Display.php | 11 +++- CRM/Contact/Form/Contact.php | 15 ++++- CRM/Contact/Form/Edit/Household.php | 5 +- CRM/Contact/Form/Edit/Individual.php | 12 +--- CRM/Contact/Form/Edit/Organization.php | 5 +- api/v3/Contact.php | 22 ++++++- settings/Core.setting.php | 4 +- .../CRM/Admin/Form/Preferences/Display.tpl | 7 ++- templates/CRM/Admin/Page/APIExplorer.js | 2 +- templates/CRM/Contact/Form/Contact.tpl | 63 +++++++++++++++++++ .../CRM/Contact/Form/Edit/Individual.tpl | 36 ----------- 11 files changed, 117 insertions(+), 65 deletions(-) diff --git a/CRM/Admin/Form/Preferences/Display.php b/CRM/Admin/Form/Preferences/Display.php index 16e0c193ffed..f8cb9af81828 100644 --- a/CRM/Admin/Form/Preferences/Display.php +++ b/CRM/Admin/Form/Preferences/Display.php @@ -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', @@ -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'), + )); + $editOptions = CRM_Core_OptionGroup::values('contact_edit_options', FALSE, FALSE, FALSE, 'AND v.filter = 0'); $this->assign('editOptions', $editOptions); @@ -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 diff --git a/CRM/Contact/Form/Contact.php b/CRM/Contact/Form/Contact.php index 9f5db2222f25..b78eb31383ad 100644 --- a/CRM/Contact/Form/Contact.php +++ b/CRM/Contact/Form/Contact.php @@ -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. @@ -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; } @@ -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)) { diff --git a/CRM/Contact/Form/Edit/Household.php b/CRM/Contact/Form/Edit/Household.php index 98f70a3197b9..dab3278c4172 100644 --- a/CRM/Contact/Form/Edit/Household.php +++ b/CRM/Contact/Form/Edit/Household.php @@ -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; } diff --git a/CRM/Contact/Form/Edit/Individual.php b/CRM/Contact/Form/Edit/Individual.php index b189500f45dd..a0e76082df74 100644 --- a/CRM/Contact/Form/Edit/Individual.php +++ b/CRM/Contact/Form/Edit/Individual.php @@ -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')); @@ -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; } diff --git a/CRM/Contact/Form/Edit/Organization.php b/CRM/Contact/Form/Edit/Organization.php index 914aac66525f..12b31183b4c6 100644 --- a/CRM/Contact/Form/Edit/Organization.php +++ b/CRM/Contact/Form/Edit/Organization.php @@ -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; } diff --git a/api/v3/Contact.php b/api/v3/Contact.php index bf9d748f2802..5d3ecc7af1cd 100644 --- a/api/v3/Contact.php +++ b/api/v3/Contact.php @@ -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'); } @@ -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. } diff --git a/settings/Core.setting.php b/settings/Core.setting.php index 0efc087dac79..76253535f336 100644 --- a/settings/Core.setting.php +++ b/settings/Core.setting.php @@ -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, diff --git a/templates/CRM/Admin/Form/Preferences/Display.tpl b/templates/CRM/Admin/Form/Preferences/Display.tpl index 81a4fd73aaca..3405f878e13e 100644 --- a/templates/CRM/Admin/Form/Preferences/Display.tpl +++ b/templates/CRM/Admin/Form/Preferences/Display.tpl @@ -114,12 +114,13 @@ - - {$form.contact_ajax_check_similar.html} {$form.contact_ajax_check_similar.label} + {$form.contact_ajax_check_similar.label} + {$form.contact_ajax_check_similar.html}   - {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} + {ts 1=$dedupeRules}When enabled, checks for possible matches on the "New Contact" form using the Supervised matching rule specified in your system.{/ts} diff --git a/templates/CRM/Admin/Page/APIExplorer.js b/templates/CRM/Admin/Page/APIExplorer.js index b562e133a108..0951b924aae0 100644 --- a/templates/CRM/Admin/Page/APIExplorer.js +++ b/templates/CRM/Admin/Page/APIExplorer.js @@ -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 { diff --git a/templates/CRM/Contact/Form/Contact.tpl b/templates/CRM/Contact/Form/Contact.tpl index a72929461a29..b0d2cb0e15a9 100644 --- a/templates/CRM/Contact/Form/Contact.tpl +++ b/templates/CRM/Contact/Form/Contact.tpl @@ -258,6 +258,69 @@ $('div' + addClass).last().show(); }); }); + + {/literal}{* Ajax check for matching contacts *} + {if $checkSimilar == 1} + var contactType = {$contactType|@json_encode}, + rules = {$ruleFields|@json_encode}, + {literal} + ruleFields = {}, + $ruleElements = $(), + matchMessage; + $.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() { + // Close msg if it exists + matchMessage && matchMessage.close && matchMessage.close(); + if ($(this).is('input[type=text]') && $(this).val().length < 2) { + return; + } + var match = {contact_type: contactType}; + $.each(ruleFields, function(fieldName, ruleField) { + if (ruleField.length > 1) { + match[fieldName] = ruleField.filter(':checked').val(); + } else { + match[fieldName] = ruleField.val(); + } + }); + CRM.api3('contact', 'duplicatecheck', { + match: match, + rule_type: 'Supervised', + options: {sort: 'sort_name'}, + return: ['display_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 = "{/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}:"; + if (data.is_error == 1 || data.count == 0) { + return; + } + msg += ''; + 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} }); diff --git a/templates/CRM/Contact/Form/Edit/Individual.tpl b/templates/CRM/Contact/Form/Edit/Individual.tpl index 25cfef12ff3d..71d1e2d0e340 100644 --- a/templates/CRM/Contact/Form/Edit/Individual.tpl +++ b/templates/CRM/Contact/Form/Edit/Individual.tpl @@ -27,45 +27,9 @@ {/literal} From 6733671180a3c102fa7fd78556dac5820575105e Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Wed, 30 May 2018 22:40:58 -0400 Subject: [PATCH 2/3] CRM-20565 - Add throttling to prevent multiple dupe checks popping up --- templates/CRM/Contact/Form/Contact.tpl | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/templates/CRM/Contact/Form/Contact.tpl b/templates/CRM/Contact/Form/Contact.tpl index b0d2cb0e15a9..d0738ad6f795 100644 --- a/templates/CRM/Contact/Form/Contact.tpl +++ b/templates/CRM/Contact/Form/Contact.tpl @@ -266,7 +266,8 @@ {literal} ruleFields = {}, $ruleElements = $(), - matchMessage; + matchMessage, + runningCheck = 0; $.each(rules, function(i, field) { // Match regular fields var $el = $('#' + field + ', #' + field + '_1_' + field, $form).filter(':input'); @@ -283,12 +284,11 @@ }); $ruleElements.on('change', checkMatches); function checkMatches() { - // Close msg if it exists - matchMessage && matchMessage.close && matchMessage.close(); if ($(this).is('input[type=text]') && $(this).val().length < 2) { return; } - var match = {contact_type: contactType}; + var match = {contact_type: contactType}, + checkNum = ++runningCheck; $.each(ruleFields, function(fieldName, ruleField) { if (ruleField.length > 1) { match[fieldName] = ruleField.filter(':checked').val(); @@ -302,6 +302,12 @@ options: {sort: 'sort_name'}, return: ['display_name', 'email'] }).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 = "{/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}:"; if (data.is_error == 1 || data.count == 0) { From 01ee39a0165a6f3fdc8b105626abaa9cb951bb3f Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Wed, 30 May 2018 23:00:34 -0400 Subject: [PATCH 3/3] CRM-20565 - Use contact api for now Per discussion on https://github.com/civicrm/civicrm-core/pull/10341 Since the default dedupe rules are inadequate for this, we'll just use the contact api for now. --- templates/CRM/Contact/Form/Contact.tpl | 34 +++++++++++++++++++------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/templates/CRM/Contact/Form/Contact.tpl b/templates/CRM/Contact/Form/Contact.tpl index d0738ad6f795..b56ccc653e08 100644 --- a/templates/CRM/Contact/Form/Contact.tpl +++ b/templates/CRM/Contact/Form/Contact.tpl @@ -111,8 +111,9 @@