From 5f282bcabac02a84b0fd3dde6030df68bd1bc5f8 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Sun, 25 Apr 2021 14:58:00 -0400 Subject: [PATCH] Fix hard crash when deduping if any multiValued ContactRef fields exist Fixes https://lab.civicrm.org/dev/core/-/issues/2561 --- CRM/Dedupe/Merger.php | 32 +++++++++++++++++++++++++++- tests/phpunit/api/v3/ContactTest.php | 26 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/CRM/Dedupe/Merger.php b/CRM/Dedupe/Merger.php index 8c19989164..2205d938e7 100644 --- a/CRM/Dedupe/Merger.php +++ b/CRM/Dedupe/Merger.php @@ -503,6 +503,7 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m $cpTables = self::cpTables(); $paymentTables = self::paymentTables(); self::filterRowBasedCustomDataFromCustomTables($cidRefs); + $multiValueCidRefs = self::getMultiValueCidRefs(); $affected = array_merge(array_keys($cidRefs), array_keys($eidRefs)); @@ -581,7 +582,14 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m $preOperationSqls = self::operationSql($mainId, $otherId, $table, $tableOperations); $sqls = array_merge($sqls, $preOperationSqls); - $sqls[] = "UPDATE $table SET $field = $mainId WHERE $field = $otherId"; + + if (!empty($multiValueCidRefs[$table][$field])) { + $sep = CRM_Core_DAO::VALUE_SEPARATOR; + $sqls[] = "UPDATE $table SET $field = REPLACE($field, '$sep$otherId$sep', '$sep$mainId$sep') WHERE $field LIKE '%$sep$otherId$sep%'"; + } + else { + $sqls[] = "UPDATE $table SET $field = $mainId WHERE $field = $otherId"; + } } } @@ -637,6 +645,28 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m } } + /** + * Return an array of tables & fields which hold serialized arrays of contact ids + * + * Return format is ['table_name' => ['field_name' => SERIALIZE_METHOD]] + * + * For now, only custom fields can be serialized and the only + * method used is CRM_Core_DAO::SERIALIZE_SEPARATOR_BOOKEND. + */ + protected static function getMultiValueCidRefs() { + $fields = \Civi\Api4\CustomField::get(FALSE) + ->addSelect('custom_group.table_name', 'column_name', 'serialize') + ->addWhere('data_type', '=', 'ContactReference') + ->addWhere('serialize', 'IS NOT EMPTY') + ->execute(); + + $map = []; + foreach ($fields as $field) { + $map[$field['custom_group.table_name']][$field['column_name']] = $field['serialize']; + } + return $map; + } + /** * Update the contact with the new parameters. * diff --git a/tests/phpunit/api/v3/ContactTest.php b/tests/phpunit/api/v3/ContactTest.php index 43942a9342..ed31c8db80 100644 --- a/tests/phpunit/api/v3/ContactTest.php +++ b/tests/phpunit/api/v3/ContactTest.php @@ -4146,6 +4146,32 @@ class api_v3_ContactTest extends CiviUnitTestCase { $this->assertEquals($contact1, $this->callAPISuccessGetValue('Contact', ['id' => $contact3, 'return' => $this->getCustomFieldName('contact_reference')])); } + /** + * Test merging a contact that is the target of a contact reference field on another contact. + * + * @throws \API_Exception + * @throws \CRM_Core_Exception + * @throws \Civi\API\Exception\UnauthorizedException + */ + public function testMergeMultiContactReferenceCustomFieldTarget() { + $this->createCustomGroupWithFieldOfType([], 'contact_reference', NULL, ['serialize' => 1]); + $fieldName = $this->getCustomFieldName('contact_reference'); + $contact1 = $this->individualCreate(); + $refA = $this->individualCreate(); + $refB = $this->individualCreate(); + $contact2 = $this->individualCreate([$fieldName => [$refA, $refB]]); + $contact3 = $this->individualCreate([$fieldName => $refA]); + $this->callAPISuccess('contact', 'merge', [ + 'to_keep_id' => $contact1, + 'to_remove_id' => $refA, + 'auto_flip' => FALSE, + ]); + $result2 = $this->callAPISuccessGetValue('Contact', ['id' => $contact2, 'return' => $fieldName]); + $this->assertEquals([$contact1, $refB], $result2); + $result3 = $this->callAPISuccessGetValue('Contact', ['id' => $contact3, 'return' => $fieldName]); + $this->assertEquals([$contact1], $result3); + } + /** * Test merging when a multiple record set is in use. * -- 2.25.1