}
}
self::appendCustomTablesExtendingContacts($contactReferences);
+ self::appendCustomContactReferenceFields($contactReferences);
// FixME for time being adding below line statically as no Foreign key constraint defined for table 'civicrm_entity_tag'
$contactReferences['civicrm_entity_tag'][] = 'entity_id';
$customValueTables = CRM_Core_BAO_CustomGroup::getAllCustomGroupsByBaseEntity('Contact');
$customValueTables->find();
while ($customValueTables->fetch()) {
- $cidRefs[$customValueTables->table_name] = ['entity_id'];
+ $cidRefs[$customValueTables->table_name][] = 'entity_id';
+ }
+ }
+
+ /**
+ * Add custom ContactReference fields to the list of contact references
+ *
+ * This includes active and inactive fields/groups
+ *
+ * @param array $cidRefs
+ *
+ * @throws \CiviCRM_API3_Exception
+ */
+ public static function appendCustomContactReferenceFields(&$cidRefs) {
+ $fields = civicrm_api3('CustomField', 'get', [
+ 'return' => ['column_name', 'custom_group_id.table_name'],
+ 'data_type' => 'ContactReference',
+ ])['values'];
+ foreach ($fields as $field) {
+ $cidRefs[$field['custom_group_id.table_name']][] = $field['column_name'];
}
}
if ($customTableToCopyFrom !== NULL) {
// @todo this duplicates cidRefs?
CRM_Core_DAO::appendCustomTablesExtendingContacts($customTables);
+ CRM_Core_DAO::appendCustomContactReferenceFields($customTables);
$customTables = array_keys($customTables);
}
// skipping non selected single-value custom table's value migration
if (!in_array($table, $multi_value_tables)) {
if ($customTableToCopyFrom !== NULL && in_array($table, $customTables) && !in_array($table, $customTableToCopyFrom)) {
- continue;
+ if (isset($cidRefs[$table]) && ($delCol = array_search('entity_id', $cidRefs[$table])) !== FALSE) {
+ // remove entity_id from the field list
+ unset($cidRefs[$table][$delCol]);
+ }
}
}
$preOperationSqls = self::operationSql($mainId, $otherId, $table, $tableOperations);
$sqls = array_merge($sqls, $preOperationSqls);
- if ($customTableToCopyFrom !== NULL && in_array($table, $customTableToCopyFrom) && !self::customRecordExists($mainId, $table, $field)) {
+ if ($customTableToCopyFrom !== NULL && in_array($table, $customTableToCopyFrom) && !self::customRecordExists($mainId, $table, $field) && $field == 'entity_id') {
+ // this is the entity_id column of a custom field group where:
+ // - the custom table should be copied as indicated by $customTableToCopyFrom
+ // e.g. because a field in the group was selected in a form
+ // - AND no record exists yet for the $mainId contact
+ // we only do this for column "entity_id" as we wouldn't want to
+ // run this INSERT for ContactReference fields
$sqls[] = "INSERT INTO $table ($field) VALUES ($mainId)";
}
$sqls[] = "UPDATE IGNORE $table SET $field = $mainId WHERE $field = $otherId";
$this->callAPISuccess('CustomGroup', 'delete', ['id' => $multiGroup['id']]);
}
+ /**
+ * Test that ContactReference fields are updated to point to the main contact
+ * after a merge is performed and the duplicate contact is deleted.
+ */
+ public function testMigrationOfContactReferenceCustomField() {
+ // Create Custom Fields
+ $contactGroup = $this->setupCustomGroupForIndividual();
+ $activityGroup = $this->customGroupCreate([
+ 'name' => 'test_group_activity',
+ 'extends' => 'Activity',
+ ]);
+ $refFieldContact = $this->customFieldCreate([
+ 'custom_group_id' => $contactGroup['id'],
+ 'label' => 'field_1' . $contactGroup['id'],
+ 'data_type' => 'ContactReference',
+ 'default_value' => NULL,
+ ]);
+ $refFieldActivity = $this->customFieldCreate([
+ 'custom_group_id' => $activityGroup['id'],
+ 'label' => 'field_1' . $activityGroup['id'],
+ 'data_type' => 'ContactReference',
+ 'default_value' => NULL,
+ ]);
+
+ // Contacts setup
+ $this->setupMatchData();
+ $originalContactID = $this->contacts[0]['id'];
+ $duplicateContactID = $this->contacts[1]['id'];
+
+ // create a contact that won't be merged but has a ContactReference field
+ // pointing to the duplicate (to be deleted) contact
+ $unrelatedContact = $this->individualCreate([
+ 'first_name' => 'Unrelated',
+ 'first_name' => 'Contact',
+ 'email' => 'unrelated@example.com',
+ "custom_{$refFieldContact['id']}" => $duplicateContactID,
+ ]);
+ // also create an activity with a ContactReference custom field
+ $activity = $this->activityCreate([
+ 'target_contact_id' => $unrelatedContact,
+ "custom_{$refFieldActivity['id']}" => $duplicateContactID,
+ ]);
+
+ // verify that the fields were set
+ $this->assertCustomFieldValue($unrelatedContact, $duplicateContactID, "custom_{$refFieldContact['id']}");
+ $this->assertEntityCustomFieldValue('Activity', $activity['id'], $duplicateContactID, "custom_{$refFieldActivity['id']}_id");
+
+ // Perform merge
+ $this->mergeContacts($originalContactID, $duplicateContactID, []);
+
+ // verify that the ContactReference fields were updated to point to the surviving contact post-merge
+ $this->assertCustomFieldValue($unrelatedContact, $originalContactID, "custom_{$refFieldContact['id']}");
+ $this->assertEntityCustomFieldValue('Activity', $activity['id'], $originalContactID, "custom_{$refFieldActivity['id']}_id");
+
+ // cleanup created custom set
+ $this->callAPISuccess('CustomField', 'delete', ['id' => $refFieldContact['id']]);
+ $this->callAPISuccess('CustomGroup', 'delete', ['id' => $contactGroup['id']]);
+ $this->callAPISuccess('CustomField', 'delete', ['id' => $refFieldActivity['id']]);
+ $this->callAPISuccess('CustomGroup', 'delete', ['id' => $activityGroup['id']]);
+ }
+
/**
* Calls merge method on given contacts, with values given in $params array.
*
* @param $customFieldName
*/
private function assertCustomFieldValue($contactID, $expectedValue, $customFieldName) {
- $data = $this->callAPISuccess('Contact', 'getsingle', [
- 'id' => $contactID,
+ $this->assertEntityCustomFieldValue('Contact', $contactID, $expectedValue, $customFieldName);
+ }
+
+ /**
+ * Check if the custom field of the given field and entity id matches the
+ * expected value
+ *
+ * @param $entity
+ * @param $id
+ * @param $expectedValue
+ * @param $customFieldName
+ */
+ private function assertEntityCustomFieldValue($entity, $id, $expectedValue, $customFieldName) {
+ $data = $this->callAPISuccess($entity, 'getsingle', [
+ 'id' => $id,
'return' => [$customFieldName],
]);
foreach ($fixtures as $fixture) {
$contactID = $this->individualCreate($fixture);
$this->contacts[] = array_merge($fixture, ['id' => $contactID]);
- sleep(5);
+ sleep(2);
}
$organizationFixtures = [
[