X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=CRM%2FDedupe%2FMerger.php;h=12ec5faaa539e8ebf1d0838d31e026a8750fc369;hb=20b47253347998c55797faa6b08c6131c22115ed;hp=f6a2e4749ea31897f854469ef07cbfb07ee997b5;hpb=d6e58a71b704cca9e86852a7284204b88ac6c1d3;p=civicrm-core.git diff --git a/CRM/Dedupe/Merger.php b/CRM/Dedupe/Merger.php index f6a2e4749e..12ec5faaa5 100644 --- a/CRM/Dedupe/Merger.php +++ b/CRM/Dedupe/Merger.php @@ -33,6 +33,7 @@ class CRM_Dedupe_Merger { $title = $userRecordUrl = ''; $config = CRM_Core_Config::singleton(); + // @todo - this user url stuff is only needed for the form layer - move to CRM_Contact_Form_Merge if ($config->userSystem->is_drupal) { $userRecordUrl = CRM_Utils_System::url('user/%ufid'); $title = ts('%1 User: %2; user id: %3', [ @@ -933,7 +934,7 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m */ public static function skipMerge($mainId, $otherId, &$migrationInfo, $mode = 'safe', &$conflicts = []) { - $conflicts = self::getConflicts($migrationInfo, $mainId, $otherId, $mode); + $conflicts = self::getConflicts($migrationInfo, $mainId, $otherId, $mode)['conflicts']; // A hook could have set skip_merge in order to alter merge behaviour. // This is a something we might ideally deprecate since they really 'should' // mess with the conflicts array instead. @@ -1116,189 +1117,10 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m $locations = ['main' => [], 'other' => []]; foreach ($locationBlocks as $blockName => $blockInfo) { - - // Collect existing fields from both 'main' and 'other' contacts first - // This allows us to match up location/types when building the table rows - $locations['main'][$blockName] = self::buildLocationBlockForContact($mainId, $blockInfo, $blockName); - $locations['other'][$blockName] = self::buildLocationBlockForContact($otherId, $blockInfo, $blockName); - - // Now, build the table rows appropriately, based off the information on - // the 'other' contact - if (!empty($locations['other']) && !empty($locations['other'][$blockName])) { - foreach ($locations['other'][$blockName] as $count => $value) { - - $displayValue = $value[$blockInfo['displayField']]; - - // Add this value to the table rows - $rows["move_location_{$blockName}_{$count}"]['other'] = $displayValue; - - // CRM-17556 Only display 'main' contact value if it's the same location + type - // Look it up from main values... - - $lookupLocation = FALSE; - if ($blockInfo['hasLocation']) { - $lookupLocation = $value['location_type_id']; - } - - $lookupType = FALSE; - if ($blockInfo['hasType']) { - $lookupType = CRM_Utils_Array::value($blockInfo['hasType'], $value); - } - - // Hold ID of main contact's matching block - $mainContactBlockId = 0; - - if (!empty($locations['main'][$blockName])) { - foreach ($locations['main'][$blockName] as $mainValueCheck) { - // No location/type, or matching location and type - if ( - (empty($lookupLocation) || $lookupLocation == $mainValueCheck['location_type_id']) - && (empty($lookupType) || $lookupType == $mainValueCheck[$blockInfo['hasType']]) - ) { - // Set this value as the default against the 'other' contact value - $rows["move_location_{$blockName}_{$count}"]['main'] = $mainValueCheck[$blockInfo['displayField']]; - $rows["move_location_{$blockName}_{$count}"]['main_is_primary'] = $mainValueCheck['is_primary']; - $rows["move_location_{$blockName}_{$count}"]['location_entity'] = $blockName; - $mainContactBlockId = $mainValueCheck['id']; - break; - } - } - } - - // Add checkbox to migrate data from 'other' to 'main' - $elements[] = ['advcheckbox', "move_location_{$blockName}_{$count}"]; - - // Add checkbox to set the 'other' location as primary - $elements[] = [ - 'advcheckbox', - "location_blocks[$blockName][$count][set_other_primary]", - NULL, - ts('Set as primary'), - ]; - - // Flag up this field to skipMerge function (@todo: do we need to?) - $migrationInfo["move_location_{$blockName}_{$count}"] = 1; - - // Add a hidden field to store the ID of the target main contact block - $elements[] = [ - 'hidden', - "location_blocks[$blockName][$count][mainContactBlockId]", - $mainContactBlockId, - ]; - - // Setup variables - $thisTypeId = FALSE; - $thisLocId = FALSE; - - // Provide a select drop-down for the location's location type - // eg: Home, Work... - - if ($blockInfo['hasLocation']) { - - // Load the location options for this entity - $locationOptions = civicrm_api3($blockName, 'getoptions', ['field' => 'location_type_id']); - - $thisLocId = $value['location_type_id']; - - // Put this field's location type at the top of the list - $tmpIdList = $locationOptions['values']; - $defaultLocId = [$thisLocId => $tmpIdList[$thisLocId]]; - unset($tmpIdList[$thisLocId]); - - // Add the element - $elements[] = [ - 'select', - "location_blocks[$blockName][$count][locTypeId]", - NULL, - $defaultLocId + $tmpIdList, - ]; - - // Add the relevant information to the $migrationInfo - // Keep location-type-id same as that of other-contact - // @todo Check this logic out - $migrationInfo['location_blocks'][$blockName][$count]['locTypeId'] = $thisLocId; - if ($blockName != 'address') { - $elements[] = [ - 'advcheckbox', - "location_blocks[{$blockName}][$count][operation]", - NULL, - ts('Add new'), - ]; - // always use add operation - $migrationInfo['location_blocks'][$blockName][$count]['operation'] = 1; - } - - } - - // Provide a select drop-down for the location's type/provider - // eg websites: Facebook... - - if ($blockInfo['hasType']) { - - // Load the type options for this entity - $typeOptions = civicrm_api3($blockName, 'getoptions', ['field' => $blockInfo['hasType']]); - - $thisTypeId = CRM_Utils_Array::value($blockInfo['hasType'], $value); - - // Put this field's location type at the top of the list - $tmpIdList = $typeOptions['values']; - $defaultTypeId = [$thisTypeId => CRM_Utils_Array::value($thisTypeId, $tmpIdList)]; - unset($tmpIdList[$thisTypeId]); - - // Add the element - $elements[] = [ - 'select', - "location_blocks[$blockName][$count][typeTypeId]", - NULL, - $defaultTypeId + $tmpIdList, - ]; - - // Add the information to the migrationInfo - $migrationInfo['location_blocks'][$blockName][$count]['typeTypeId'] = $thisTypeId; - - } - - // Set the label for this row - $rowTitle = $blockInfo['label'] . ' ' . ($count + 1); - if (!empty($thisLocId)) { - $rowTitle .= ' (' . $locationOptions['values'][$thisLocId] . ')'; - } - if (!empty($thisTypeId)) { - $rowTitle .= ' (' . $typeOptions['values'][$thisTypeId] . ')'; - } - $rows["move_location_{$blockName}_$count"]['title'] = $rowTitle; - - } // End loop through 'other' locations of this type - - } // End if 'other' location for this type exists - + list($locations, $rows, $elements, $migrationInfo) = self::addLocationFieldInfo($mainId, $otherId, $blockInfo, $blockName, $locations, $rows, $elements, $migrationInfo); } // End loop through each location block entity // add the related tables and unset the ones that don't sport any of the duplicate contact's info - $config = CRM_Core_Config::singleton(); - $mainUfId = CRM_Core_BAO_UFMatch::getUFId($mainId); - $mainUser = NULL; - if ($mainUfId) { - // d6 compatible - if ($config->userSystem->is_drupal == '1' && function_exists($mainUser)) { - $mainUser = user_load($mainUfId); - } - elseif ($config->userFramework == 'Joomla') { - $mainUser = JFactory::getUser($mainUfId); - } - } - $otherUfId = CRM_Core_BAO_UFMatch::getUFId($otherId); - $otherUser = NULL; - if ($otherUfId) { - // d6 compatible - if ($config->userSystem->is_drupal == '1' && function_exists($mainUser)) { - $otherUser = user_load($otherUfId); - } - elseif ($config->userFramework == 'Joomla') { - $otherUser = JFactory::getUser($otherUfId); - } - } - $mergeHandler = new CRM_Dedupe_MergeHandler((int) $mainId, (int) $otherId); $relTables = $mergeHandler->getTablesRelatedToTheMergePair(); foreach ($relTables as $name => $null) { @@ -1307,18 +1129,10 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m $relTables[$name]['main_url'] = str_replace('$cid', $mainId, $relTables[$name]['url']); $relTables[$name]['other_url'] = str_replace('$cid', $otherId, $relTables[$name]['url']); - if ($name == 'rel_table_users') { - $relTables[$name]['main_url'] = str_replace('%ufid', $mainUfId, $relTables[$name]['url']); - $relTables[$name]['other_url'] = str_replace('%ufid', $otherUfId, $relTables[$name]['url']); - $find = ['$ufid', '$ufname']; - if ($mainUser) { - $replace = [$mainUfId, $mainUser->name]; - $relTables[$name]['main_title'] = str_replace($find, $replace, $relTables[$name]['title']); - } - if ($otherUser) { - $replace = [$otherUfId, $otherUser->name]; - $relTables[$name]['other_title'] = str_replace($find, $replace, $relTables[$name]['title']); - } + if ($name === 'rel_table_users') { + // @todo - this user url stuff is only needed for the form layer - move to CRM_Contact_Form_Merge + $relTables[$name]['main_url'] = str_replace('%ufid', CRM_Core_BAO_UFMatch::getUFId($otherId), $relTables[$name]['url']); + $relTables[$name]['other_url'] = str_replace('%ufid', CRM_Core_BAO_UFMatch::getUFId($otherId), $relTables[$name]['url']); } if ($name == 'rel_table_memberships') { //Enable 'add new' checkbox if main contact does not contain any membership similar to duplicate contact. @@ -1354,25 +1168,24 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m ); foreach ($otherTree as $gid => $group) { - $foundField = FALSE; if (!isset($group['fields'])) { continue; } foreach ($group['fields'] as $fid => $field) { + $mainContactValue = $mainTree[$gid]['fields'][$fid]['customValue'] ?? NULL; + $otherContactValue = $otherTree[$gid]['fields'][$fid]['customValue'] ?? NULL; if (in_array($fid, $compareFields['custom'])) { - if (!$foundField) { - $rows["custom_group_$gid"]['title'] = $group['title']; - $foundField = TRUE; - } - if (!empty($mainTree[$gid]['fields'][$fid]['customValue'])) { - foreach ($mainTree[$gid]['fields'][$fid]['customValue'] as $valueId => $values) { + $rows["custom_group_$gid"]['title'] = $rows["custom_group_$gid"]['title'] ?? $group['title']; + + if ($mainContactValue) { + foreach ($mainContactValue as $valueId => $values) { $rows["move_custom_$fid"]['main'] = CRM_Core_BAO_CustomField::displayValue($values['data'], $fid); } } - $value = "null"; - if (!empty($otherTree[$gid]['fields'][$fid]['customValue'])) { - foreach ($otherTree[$gid]['fields'][$fid]['customValue'] as $valueId => $values) { + $value = 'null'; + if ($otherContactValue) { + foreach ($otherContactValue as $valueId => $values) { $rows["move_custom_$fid"]['other'] = CRM_Core_BAO_CustomField::displayValue($values['data'], $fid); if ($values['data'] === 0 || $values['data'] === '0') { $values['data'] = $qfZeroBug; @@ -1383,12 +1196,13 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m $rows["move_custom_$fid"]['title'] = $field['label']; $elements[] = [ - 'advcheckbox', - "move_custom_$fid", - NULL, - NULL, - NULL, - $value, + 0 => 'advcheckbox', + 1 => "move_custom_$fid", + 2 => NULL, + 3 => NULL, + 4 => NULL, + 5 => $value, + 'is_checked' => (!isset($rows["move_custom_$fid"]['main']) || $rows["move_custom_$fid"]['main'] === ''), ]; $migrationInfo["move_custom_$fid"] = $value; } @@ -2350,13 +2164,13 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m // allow hook to override / manipulate migrationInfo as well $migrationInfo = $migrationData['migration_info']; foreach ($conflicts as $key => $val) { - if ($val !== NULL || $mode !== 'safe') { - // copy over the resolved values - $migrationInfo[$key] = $val; - unset($conflicts[$key]); - } + // Copy over the resolved values. If we are in aggressive mode we update to null + // so as not to copy over. Why it's different to safe mode is a bit murky. + // Working theory is it doesn't matter what we do in safe mode here if $val is NULL. + // as the merge is not gonna happen if $val == NULL + $migrationInfo[$key] = $val ?? ($mode === 'safe' ? $migrationInfo[$key] : NULL); } - return self::formatConflictArray($conflicts, $migrationInfo['rows'], $migrationInfo['main_details']['location_blocks'], $migrationInfo['other_details']['location_blocks'], $mainId, $otherId); + return self::formatConflictArray($conflicts, $migrationInfo['rows'], $migrationInfo['main_details']['location_blocks'], $migrationInfo['other_details']['location_blocks'], $mainId, $otherId, $mode); } /** @@ -2366,12 +2180,29 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m * @param $toRemoveContactLocationBlocks * @param $toKeepID * @param $toRemoveID + * @param string $mode * * @return mixed * @throws \CRM_Core_Exception */ - protected static function formatConflictArray($conflicts, $migrationInfo, $toKeepContactLocationBlocks, $toRemoveContactLocationBlocks, $toKeepID, $toRemoveID) { + protected static function formatConflictArray($conflicts, $migrationInfo, $toKeepContactLocationBlocks, $toRemoveContactLocationBlocks, $toKeepID, $toRemoveID, $mode) { $return = []; + $resolved = []; + foreach ($conflicts as $key => $val) { + if ($val !== NULL) { + // copy over the resolved values + $resolved[$key] = $val; + unset($conflicts[$key]); + } + elseif ($mode === 'aggressive') { + unset($conflicts[$key]); + if (strpos($key, 'move_location_') !== 0) { + // @todo - just handling plain contact fields for now because I think I need a bigger refactor + // of the below to handle locations & will do as a follow up. + $resolved['contact'][substr($key, 5)] = $migrationInfo[$key]['main']; + } + } + } foreach (array_keys($conflicts) as $index) { if (substr($index, 0, 14) === 'move_location_') { $parts = explode('_', $index); @@ -2410,7 +2241,7 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m throw new CRM_Core_Exception(ts('Unknown parameter') . $index); } } - return $return; + return ['conflicts' => $return, 'resolved' => $resolved]; } /** @@ -2605,4 +2436,175 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m } } + /** + * @param $mainId + * @param $otherId + * @param $blockInfo + * @param $blockName + * @param array $locations + * @param array $rows + * @param array $elements + * @param array $migrationInfo + * + * @return array + * @throws \CiviCRM_API3_Exception + */ + protected static function addLocationFieldInfo($mainId, $otherId, $blockInfo, $blockName, array $locations, array $rows, array $elements, array $migrationInfo): array { + // Collect existing fields from both 'main' and 'other' contacts first + // This allows us to match up location/types when building the table rows + $locations['main'][$blockName] = self::buildLocationBlockForContact($mainId, $blockInfo, $blockName); + $locations['other'][$blockName] = self::buildLocationBlockForContact($otherId, $blockInfo, $blockName); + + // Now, build the table rows appropriately, based off the information on + // the 'other' contact + if (!empty($locations['other']) && !empty($locations['other'][$blockName])) { + foreach ($locations['other'][$blockName] as $count => $value) { + + $displayValue = $value[$blockInfo['displayField']]; + + // Add this value to the table rows + $rows["move_location_{$blockName}_{$count}"]['other'] = $displayValue; + + // CRM-17556 Only display 'main' contact value if it's the same location + type + // Look it up from main values... + + $lookupLocation = FALSE; + if ($blockInfo['hasLocation']) { + $lookupLocation = $value['location_type_id']; + } + + $lookupType = FALSE; + if ($blockInfo['hasType']) { + $lookupType = CRM_Utils_Array::value($blockInfo['hasType'], $value); + } + + // Hold ID of main contact's matching block + $mainContactBlockId = 0; + + if (!empty($locations['main'][$blockName])) { + foreach ($locations['main'][$blockName] as $mainValueCheck) { + // No location/type, or matching location and type + if ( + (empty($lookupLocation) || $lookupLocation == $mainValueCheck['location_type_id']) + && (empty($lookupType) || $lookupType == $mainValueCheck[$blockInfo['hasType']]) + ) { + // Set this value as the default against the 'other' contact value + $rows["move_location_{$blockName}_{$count}"]['main'] = $mainValueCheck[$blockInfo['displayField']]; + $rows["move_location_{$blockName}_{$count}"]['main_is_primary'] = $mainValueCheck['is_primary']; + $rows["move_location_{$blockName}_{$count}"]['location_entity'] = $blockName; + $mainContactBlockId = $mainValueCheck['id']; + break; + } + } + } + + // Add checkbox to migrate data from 'other' to 'main' + $elements[] = ['advcheckbox', "move_location_{$blockName}_{$count}"]; + + // Add checkbox to set the 'other' location as primary + $elements[] = [ + 'advcheckbox', + "location_blocks[$blockName][$count][set_other_primary]", + NULL, + ts('Set as primary'), + ]; + + // Flag up this field to skipMerge function (@todo: do we need to?) + $migrationInfo["move_location_{$blockName}_{$count}"] = 1; + + // Add a hidden field to store the ID of the target main contact block + $elements[] = [ + 'hidden', + "location_blocks[$blockName][$count][mainContactBlockId]", + $mainContactBlockId, + ]; + + // Setup variables + $thisTypeId = FALSE; + $thisLocId = FALSE; + + // Provide a select drop-down for the location's location type + // eg: Home, Work... + + if ($blockInfo['hasLocation']) { + + // Load the location options for this entity + $locationOptions = civicrm_api3($blockName, 'getoptions', ['field' => 'location_type_id']); + + $thisLocId = $value['location_type_id']; + + // Put this field's location type at the top of the list + $tmpIdList = $locationOptions['values']; + $defaultLocId = [$thisLocId => $tmpIdList[$thisLocId]]; + unset($tmpIdList[$thisLocId]); + + // Add the element + $elements[] = [ + 'select', + "location_blocks[$blockName][$count][locTypeId]", + NULL, + $defaultLocId + $tmpIdList, + ]; + + // Add the relevant information to the $migrationInfo + // Keep location-type-id same as that of other-contact + // @todo Check this logic out + $migrationInfo['location_blocks'][$blockName][$count]['locTypeId'] = $thisLocId; + if ($blockName != 'address') { + $elements[] = [ + 'advcheckbox', + "location_blocks[{$blockName}][$count][operation]", + NULL, + ts('Add new'), + ]; + // always use add operation + $migrationInfo['location_blocks'][$blockName][$count]['operation'] = 1; + } + + } + + // Provide a select drop-down for the location's type/provider + // eg websites: Facebook... + + if ($blockInfo['hasType']) { + + // Load the type options for this entity + $typeOptions = civicrm_api3($blockName, 'getoptions', ['field' => $blockInfo['hasType']]); + + $thisTypeId = CRM_Utils_Array::value($blockInfo['hasType'], $value); + + // Put this field's location type at the top of the list + $tmpIdList = $typeOptions['values']; + $defaultTypeId = [$thisTypeId => CRM_Utils_Array::value($thisTypeId, $tmpIdList)]; + unset($tmpIdList[$thisTypeId]); + + // Add the element + $elements[] = [ + 'select', + "location_blocks[$blockName][$count][typeTypeId]", + NULL, + $defaultTypeId + $tmpIdList, + ]; + + // Add the information to the migrationInfo + $migrationInfo['location_blocks'][$blockName][$count]['typeTypeId'] = $thisTypeId; + + } + + // Set the label for this row + $rowTitle = $blockInfo['label'] . ' ' . ($count + 1); + if (!empty($thisLocId)) { + $rowTitle .= ' (' . $locationOptions['values'][$thisLocId] . ')'; + } + if (!empty($thisTypeId)) { + $rowTitle .= ' (' . $typeOptions['values'][$thisTypeId] . ')'; + } + $rows["move_location_{$blockName}_$count"]['title'] = $rowTitle; + + } // End loop through 'other' locations of this type + + } + return [$locations, $rows, $elements, $migrationInfo]; + } + }