+--------------------------------------------------------------------+
*/
+use Civi\Api4\CustomGroup;
+
/**
*
* @package CRM
*
* @param int $mainId
* @param int $otherId
- * @param bool $tables
+ * @param array $tables
* @param array $tableOperations
- * @param array $customTableToCopyFrom
*
+ * @throws \API_Exception
* @throws \CiviCRM_API3_Exception
+ * @throws \Civi\API\Exception\UnauthorizedException
*/
- public static function moveContactBelongings($mainId, $otherId, $tables, $tableOperations, array $customTableToCopyFrom) {
+ public static function moveContactBelongings($mainId, $otherId, $tables, $tableOperations) {
$cidRefs = self::cidRefs();
$eidRefs = self::eidRefs();
$cpTables = self::cpTables();
$paymentTables = self::paymentTables();
-
- // getting all custom tables
- $customTables = [];
- // @todo this duplicates cidRefs?
- CRM_Core_DAO::appendCustomTablesExtendingContacts($customTables);
- CRM_Core_DAO::appendCustomContactReferenceFields($customTables);
- $customTables = array_keys($customTables);
+ self::filterRowBasedCustomDataFromCustomTables($cidRefs);
$affected = array_merge(array_keys($cidRefs), array_keys($eidRefs));
$mainId = (int) $mainId;
$otherId = (int) $otherId;
- $multi_value_tables = array_keys(CRM_Dedupe_Merger::getMultiValueCustomSets('cidRefs'));
$sqls = [];
foreach ($affected as $table) {
- // skipping non selected single-value custom table's value migration
- if (!in_array($table, $multi_value_tables)) {
- if (in_array($table, $customTables) && !in_array($table, $customTableToCopyFrom)) {
- if (isset($cidRefs[$table]) && ($delCol = array_search('entity_id', $cidRefs[$table])) !== FALSE) {
- // remove entity_id from the field list
- unset($cidRefs[$table][$delCol]);
- }
- }
- }
-
// Call custom processing function for objects that require it
if (isset($cpTables[$table])) {
foreach ($cpTables[$table] as $className => $fnName) {
$preOperationSqls = self::operationSql($mainId, $otherId, $table, $tableOperations);
$sqls = array_merge($sqls, $preOperationSqls);
-
- if (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";
$sqls[] = "DELETE FROM $table WHERE $field = $otherId";
}
CRM_Dedupe_Merger::addMembershipToRealtedContacts($mainId);
}
+ /**
+ * Filter out custom tables from cidRefs unless they are there due to a contact reference or are a multiple set.
+ *
+ * The only fields where we want to move the data by sql is where entity reference fields
+ * on another contact refer to the contact being merged, or it is a multiple record set.
+ * The transference of custom data from one contact to another is done in 2 other places in the dedupe process but should
+ * not be done in moveAllContactData.
+ *
+ * Note it's a bit silly the way we build & then cull cidRefs - however, poor hook placement means that
+ * until we fully deprecate calling the hook from cidRefs we are stuck.
+ *
+ * It was deprecated in code (via deprecation notices if people altered it) in Mar 2019 but in docs only in Apri 2020.
+ *
+ * @param array $cidRefs
+ *
+ * @throws \API_Exception
+ * @throws \Civi\API\Exception\UnauthorizedException
+ */
+ protected static function filterRowBasedCustomDataFromCustomTables(array &$cidRefs) {
+ $customTables = (array) CustomGroup::get()
+ ->setCheckPermissions(FALSE)
+ ->setSelect(['table_name'])
+ ->addWhere('is_multiple', '=', 0)
+ ->addWhere('extends', 'IN', CRM_Contact_BAO_ContactType::contactTypes())
+ ->execute()
+ ->indexBy('table_name');
+ foreach (array_intersect_key($cidRefs, $customTables) as $tableName => $cidSpec) {
+ if (in_array('entity_id', $cidSpec, TRUE)) {
+ unset($cidRefs[$tableName][array_search('entity_id', $cidSpec, TRUE)]);
+ }
+ if (empty($cidRefs[$tableName])) {
+ unset($cidRefs[$tableName]);
+ }
+ }
+ }
+
/**
* Given a contact ID, will check if a record exists in given table.
*
* Respect logged in user permissions.
*
* @return bool
+ *
+ * @throws \API_Exception
+ * @throws \CRM_Core_Exception
* @throws \CiviCRM_API3_Exception
+ * @throws \Civi\API\Exception\UnauthorizedException
*/
public static function moveAllBelongings($mainId, $otherId, $migrationInfo, $checkPermissions = TRUE) {
if (empty($migrationInfo)) {
self::mergeLocations($mainId, $otherId, $migrationInfo);
// **** Do contact related migrations
- $customTablesToCopyValues = self::getAffectedCustomTables($submittedCustomFields);
// @todo - move all custom field processing to the move class & eventually have an
// overridable DAO class for it.
$customFieldBAO = new CRM_Core_BAO_CustomField();
$customFieldBAO->move($otherId, $mainId, $submittedCustomFields);
- CRM_Dedupe_Merger::moveContactBelongings($mainId, $otherId, $moveTables, $tableOperations, $customTablesToCopyValues);
+ CRM_Dedupe_Merger::moveContactBelongings($mainId, $otherId, $moveTables, $tableOperations);
unset($moveTables, $tableOperations);
// **** Do table related removals
return TRUE;
}
- /**
- * Builds an Array of Custom tables for given custom field ID's.
- *
- * @param $customFieldIDs
- *
- * @return array
- * Array of custom table names
- */
- private static function getAffectedCustomTables($customFieldIDs) {
- $customTableToCopyValues = [];
-
- foreach ($customFieldIDs as $fieldID) {
- if (!empty($fieldID)) {
- $customField = civicrm_api3('custom_field', 'getsingle', [
- 'id' => $fieldID,
- 'is_active' => TRUE,
- ]);
- if (!civicrm_error($customField) && !empty($customField['custom_group_id'])) {
- $customGroup = civicrm_api3('custom_group', 'getsingle', [
- 'id' => $customField['custom_group_id'],
- 'is_active' => TRUE,
- ]);
-
- if (!civicrm_error($customGroup) && !empty($customGroup['table_name'])) {
- $customTableToCopyValues[] = $customGroup['table_name'];
- }
- }
- }
- }
-
- return $customTableToCopyValues;
- }
-
/**
* Get fields in the contact table suitable for merging.
*
parent::tearDown();
}
+ /**
+ * @throws \CRM_Core_Exception
+ */
public function createDupeContacts() {
// create a group to hold contacts, so that dupe checks don't consider any other contacts in the DB
$params = [
* for a merge, only those values are merged, while all other fields of the
* custom group retain their original value, specifically for a contact with
* no records on the custom group table.
+ *
+ * @throws \CRM_Core_Exception
*/
public function testMigrationOfSomeCustomDataOnEmptyCustomRecord() {
// Create Custom Fields
/**
* Test that ContactReference fields are updated to point to the main contact
* after a merge is performed and the duplicate contact is deleted.
+ *
+ * @throws \API_Exception
+ * @throws \CRM_Core_Exception
+ * @throws \CiviCRM_API3_Exception
+ * @throws \Civi\API\Exception\UnauthorizedException
*/
public function testMigrationOfContactReferenceCustomField() {
// Create Custom Fields
// pointing to the duplicate (to be deleted) contact
$unrelatedContact = $this->individualCreate([
'first_name' => 'Unrelated',
- 'first_name' => 'Contact',
+ 'last_name' => 'Contact',
'email' => 'unrelated@example.com',
"custom_{$refFieldContact['id']}" => $duplicateContactID,
]);
* Array of fields to be merged from source into target contact, of the form
* ['move_<fieldName>' => <fieldValue>]
*
+ * @throws \API_Exception
* @throws \CRM_Core_Exception
* @throws \CiviCRM_API3_Exception
+ * @throws \Civi\API\Exception\UnauthorizedException
*/
private function mergeContacts($originalContactID, $duplicateContactID, $params) {
$rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($originalContactID, $duplicateContactID);
* @param $contactID
* @param $expectedValue
* @param $customFieldName
+ *
+ * @throws \CRM_Core_Exception
*/
private function assertCustomFieldValue($contactID, $expectedValue, $customFieldName) {
$this->assertEntityCustomFieldValue('Contact', $contactID, $expectedValue, $customFieldName);
* @param $id
* @param $expectedValue
* @param $customFieldName
+ *
+ * @throws \CRM_Core_Exception
*/
private function assertEntityCustomFieldValue($entity, $id, $expectedValue, $customFieldName) {
$data = $this->callAPISuccess($entity, 'getsingle', [
*
* @return array
* Data for the created custom group record
+ * @throws \CRM_Core_Exception
*/
private function setupCustomGroupForIndividual() {
$customGroup = $this->callAPISuccess('custom_group', 'get', [
* Creates a custom field on the provided custom group with the given field
* label.
*
- * @param $fieldLabel
- * @param $createGroup
+ * @param string $fieldLabel
+ * @param array $createGroup
*
* @return array
* Data for the created custom field record
+ * @throws \CRM_Core_Exception
*/
private function setupCustomField($fieldLabel, $createGroup) {
return $this->callAPISuccess('custom_field', 'create', [
/**
* Set up some contacts for our matching.
+ *
+ * @throws \CRM_Core_Exception
*/
public function setupMatchData() {
$fixtures = [