CRM-18673 fix merge handling of identical addresses
authoreileen <emcnaughton@wikimedia.org>
Wed, 25 May 2016 00:21:28 +0000 (12:21 +1200)
committereileen <emcnaughton@wikimedia.org>
Wed, 25 May 2016 01:02:37 +0000 (13:02 +1200)
CRM/Dedupe/Merger.php
tests/phpunit/api/v3/JobTest.php

index 46ac4761923d9a9da4143bf67546df11ba378a41..0bc09a6f4d19dcfedaf2e5f2c3dce3228f82a871 100644 (file)
@@ -868,20 +868,33 @@ INNER JOIN  civicrm_membership membership2 ON membership1.membership_type_id = m
 
         // Rule: Catch address conflicts (same address type on both contacts)
         if ($fieldName == 'address') {
-          $mainNewLocTypeId = $migrationInfo['location_blocks'][$fieldName][$fieldCount]['locTypeId'];
           if (
             isset($migrationInfo['main_details']['location_blocks']['address']) &&
             !empty($migrationInfo['main_details']['location_blocks']['address'])
           ) {
+            $otherAddresses = $migrationInfo['other_details']['location_blocks']['address'];
+            $otherAddressLookup = array();
+            foreach ($otherAddresses as $otherAddressIndex => $otherAddress) {
+              $otherAddressLookup[$otherAddress['location_type_id']] = $otherAddressIndex;
+            }
             // Look for this LocTypeId in the results
             // @todo This can be streamlined using array_column() in PHP 5.5+
             foreach ($migrationInfo['main_details']['location_blocks']['address'] as $addressKey => $addressRecord) {
-              if ($addressRecord['location_type_id'] == $mainNewLocTypeId) {
+              $otherAddressIndex = CRM_Utils_Array::value($addressRecord['location_type_id'], $otherAddressLookup);
+              $hasMatchingAddress = FALSE;
+              foreach ($otherAddresses as $otherAddress) {
+                if (self::addressIsSame($addressRecord, $otherAddress)) {
+                  $hasMatchingAddress = TRUE;
+                }
+              }
+              if ($hasMatchingAddress) {
+                unset($migrationInfo[$key]);
+              }
+              elseif (!empty($otherAddresses[$otherAddressIndex]) && !self::addressIsSame($addressRecord, $otherAddresses[$otherAddressIndex])) {
                 $conflicts[$key] = NULL;
                 break;
               }
             }
-
           }
         }
         // For other locations, don't merge/add if the values are the same
@@ -926,6 +939,27 @@ INNER JOIN  civicrm_membership membership2 ON membership1.membership_type_id = m
     return FALSE;
   }
 
+  /**
+   * Compare 2 addresses to see if they are the same.
+   *
+   * @param array $mainAddress
+   * @param array $comparisonAddress
+   *
+   * @return bool
+   */
+  static protected function addressIsSame($mainAddress, $comparisonAddress) {
+    $keysToIgnore = array('id', 'is_primary', 'is_billing', 'manual_geo_code', 'contact_id');
+    foreach ($comparisonAddress as $field => $value) {
+      if (in_array($field, $keysToIgnore)) {
+        continue;
+      }
+      if (!empty($value) && $mainAddress[$field] != $value) {
+        return FALSE;
+      }
+    }
+    return TRUE;
+  }
+
   /**
    * A function to build an array of information about location blocks that is
    * required when merging location fields
index 63de71320322f5f3abe9567b98f5109c7c6149f3..dfe3648dcfc245c9f7644785c2021c14f5f93978 100644 (file)
@@ -353,6 +353,58 @@ class api_v3_JobTest extends CiviUnitTestCase {
     ), 4);
   }
 
+  /**
+   * Test the batch merge does not create duplicate emails.
+   *
+   * Test CRM-18546, a 4.7 regression whereby a merged contact gets duplicate emails.
+   */
+  public function testBatchMergeMatchingAddress() {
+    for ($x = 0; $x <= 2; $x++) {
+      $this->individualCreate(array(
+        'api.address.create' => array(
+          'location_type_id' => 'Home',
+          'street_address' => 'Appt 115, The Batcave',
+          'city' => 'Gotham',
+          'postal_code' => 'Nananananana',
+        ),
+      ));
+    }
+    // Different location type, still merge, identical.
+    $this->individualCreate(array(
+      'api.address.create' => array(
+        'location_type_id' => 'Main',
+        'street_address' => 'Appt 115, The Batcave',
+        'city' => 'Gotham',
+        'postal_code' => 'Nananananana',
+      ),
+    ));
+
+    $this->individualCreate(array(
+      'api.address.create' => array(
+        'location_type_id' => 'Home',
+        'street_address' => 'Appt 115, The Batcave',
+        'city' => 'Gotham',
+        'postal_code' => 'Batman',
+      ),
+    ));
+
+    $result = $this->callAPISuccess('Job', 'process_batch_merge', array());
+    $this->assertEquals(3, count($result['values']['merged']));
+    $this->assertEquals(1, count($result['values']['skipped']));
+    $this->callAPISuccessGetCount('Contact', array('street_address' => 'Appt 115, The Batcave'), 2);
+    $contacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 0));
+    $deletedContacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 1));
+    $this->callAPISuccessGetCount('Address', array(
+      'street_address' => 'Appt 115, The Batcave',
+      'contact_id' => array('IN' => array_keys($contacts['values'])),
+    ), 3);
+
+    $this->callAPISuccessGetCount('Address', array(
+      'street_address' => 'Appt 115, The Batcave',
+      'contact_id' => array('IN' => array_keys($deletedContacts['values'])),
+    ), 2);
+  }
+
   /**
    * Test the batch merge by id range.
    *