CRM-20565 - Improve ajax dedupe lookups on contact add form
authorColeman Watts <coleman@civicrm.org>
Fri, 12 May 2017 17:38:23 +0000 (12:38 -0500)
committerColeman Watts <coleman@civicrm.org>
Thu, 31 May 2018 01:21:03 +0000 (21:21 -0400)
CRM/Admin/Form/Preferences/Display.php
CRM/Contact/Form/Contact.php
CRM/Contact/Form/Edit/Household.php
CRM/Contact/Form/Edit/Individual.php
CRM/Contact/Form/Edit/Organization.php
api/v3/Contact.php
settings/Core.setting.php
templates/CRM/Admin/Form/Preferences/Display.tpl
templates/CRM/Admin/Page/APIExplorer.js
templates/CRM/Contact/Form/Contact.tpl
templates/CRM/Contact/Form/Edit/Individual.tpl

index 16e0c193ffedf87e721d361d507524e6d9649ee8..f8cb9af81828fcdb2e7a8a62ad583efd1991511f 100644 (file)
@@ -77,9 +77,9 @@ class CRM_Admin_Form_Preferences_Display extends CRM_Admin_Form_Preferences {
           '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 @@ class CRM_Admin_Form_Preferences_Display extends CRM_Admin_Form_Preferences {
     $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 @@ class CRM_Admin_Form_Preferences_Display extends CRM_Admin_Form_Preferences {
 
     $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
index 9f5db2222f253ccf61ea6048909b5aec1e125715..b78eb31383ade9393323b9c6a1e62657ea00b101 100644 (file)
@@ -590,7 +590,7 @@ class CRM_Contact_Form_Contact extends CRM_Core_Form {
    * @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 @@ class CRM_Contact_Form_Contact extends CRM_Core_Form {
       }
     }
 
+    // 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 @@ class CRM_Contact_Form_Contact extends CRM_Core_Form {
     $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)) {
index 98f70a3197b9b7dc71b81093561f01232cefeb6c..dab3278c417262b511a474391d7da2522ecef626 100644 (file)
@@ -85,16 +85,13 @@ class CRM_Contact_Form_Edit_Household {
    */
   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;
   }
 
index b189500f45ddd35c5fcde8a4b81b9013c22e9146..a0e76082df749d987f16c3c370107923f065b3a5 100644 (file)
@@ -96,13 +96,6 @@ class CRM_Contact_Form_Edit_Individual {
     }
 
     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 @@ class CRM_Contact_Form_Edit_Individual {
    */
   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;
   }
 
index 914aac66525f33ca7dceb612dd121884c5952078..12b31183b4c616e5a7b6d0cff9d1f022ea8ca5f7 100644 (file)
@@ -86,16 +86,13 @@ class CRM_Contact_Form_Edit_Organization {
    */
   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;
   }
index bf9d748f2802c52297f8b97034b5ae6c99883eea..5d3ecc7af1cdd6145a74723f76bfab4f9572178f 100644 (file)
@@ -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.
 }
index 0efc087dac7997ce85808a62e8cf56e1d55a5ff0..76253535f33650811040b8cb10a5f5ab34709d6d 100644 (file)
@@ -211,10 +211,10 @@ return array(
     '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,
index 81a4fd73aaca409e045b43d5048c10713a95a780..3405f878e13eb442af8534d6e887cd5825f8b4e0 100644 (file)
       </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">
index b562e133a108ef1f1514ddfe9d3213f53260a208..0951b924aae01333753642c29ae0134ad12f4c07 100644 (file)
       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 {
index a72929461a29ef630fa4367ad048c899a014bbec..b0d2cb0e15a977e5d3b5c607dd870e4b2cb7a73b 100644 (file)
           $('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 = "<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>
index 25cfef12ff3de182ae65143e0def5ccb2a40c276..71d1e2d0e340db3fb4e5840826985d6ad35f956f 100644 (file)
 <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}