<?php
/*
+--------------------------------------------------------------------+
- | CiviCRM version 5 |
- +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC (c) 2004-2019 |
- +--------------------------------------------------------------------+
- | This file is a part of CiviCRM. |
- | |
- | CiviCRM is free software; you can copy, modify, and distribute it |
- | under the terms of the GNU Affero General Public License |
- | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
+ | Copyright CiviCRM LLC. All rights reserved. |
| |
- | CiviCRM is distributed in the hope that it will be useful, but |
- | WITHOUT ANY WARRANTY; without even the implied warranty of |
- | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
- | See the GNU Affero General Public License for more details. |
- | |
- | You should have received a copy of the GNU Affero General Public |
- | License and the CiviCRM Licensing Exception along |
- | with this program; if not, contact CiviCRM LLC |
- | at info[AT]civicrm[DOT]org. If you have questions about the |
- | GNU Affero General Public License or the licensing of CiviCRM, |
- | see the CiviCRM license FAQ at http://civicrm.org/licensing |
+ | This work is published under the GNU AGPLv3 license with some |
+ | permitted exceptions and without any warranty. For full license |
+ | and copyright information, see https://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
/**
*
* @package CRM
- * @copyright CiviCRM LLC (c) 2004-2019
+ * @copyright CiviCRM LLC https://civicrm.org/licensing
*/
class CRM_Dedupe_Merger {
* Respect logged in user permissions.
*
* @return array|bool
+ *
+ * @throws \API_Exception
+ * @throws \CRM_Core_Exception
+ * @throws \CiviCRM_API3_Exception
*/
public static function merge($dupePairs = [], $cacheParams = [], $mode = 'safe',
$redirectForPerformance = FALSE, $checkPermissions = TRUE
unset($dupePairs[$index]);
continue;
}
- CRM_Utils_Hook::merge('flip', $dupes, $dupes['dstID'], $dupes['srcID']);
- $mainId = $dupes['dstID'];
- $otherId = $dupes['srcID'];
-
- if (!$mainId || !$otherId) {
- // return error
- return FALSE;
+ if (($result = self::dedupePair($dupes, $mode, $checkPermissions, $cacheKeyString)) === FALSE) {
+ unset($dupePairs[$index]);
+ continue;
+ }
+ if (!empty($result['merged'])) {
+ $deletedContacts[] = $result['merged'][0]['other_id'];
+ $resultStats['merged'][] = ($result['merged'][0]);
+ }
+ else {
+ $resultStats['skipped'][] = ($result['skipped'][0]);
}
-
- self::dedupePair($resultStats, $deletedContacts, $mode, $checkPermissions, $mainId, $otherId, $cacheKeyString);
}
if ($cacheKeyString && !$redirectForPerformance) {
// CRM-15681 don't display sub-types in UI
continue;
}
- foreach (['main' => $main, 'other' => $other] as $moniker => $contact) {
- $value = $label = CRM_Utils_Array::value($field, $contact);
- $fieldSpec = $fields[$field];
- if (!empty($fieldSpec['serialize']) && is_array($value)) {
- // In practice this only applies to preferred_communication_method as the sub types are skipped above
- // and no others are serialized.
- $labels = [];
- foreach ($value as $individualValue) {
- $labels[] = CRM_Core_PseudoConstant::getLabel('CRM_Contact_BAO_Contact', $field, $individualValue);
- }
- $label = implode(', ', $labels);
- // We serialize this due to historic handling but it's likely that if we just left it as an
- // array all would be well & we would have less code.
- $value = CRM_Core_DAO::serializeField($value, $fieldSpec['serialize']);
- }
- elseif (!empty($fieldSpec['type']) && $fieldSpec['type'] == CRM_Utils_Type::T_DATE) {
- if ($value) {
- $value = str_replace('-', '', $value);
- $label = CRM_Utils_Date::customFormat($label);
- }
- else {
- $value = "null";
- }
- }
- elseif (!empty($fields[$field]['type']) && $fields[$field]['type'] == CRM_Utils_Type::T_BOOLEAN) {
- if ($label === '0') {
- $label = ts('[ ]');
- }
- if ($label === '1') {
- $label = ts('[x]');
- }
- }
- elseif (!empty($fieldSpec['pseudoconstant'])) {
- $label = CRM_Core_PseudoConstant::getLabel('CRM_Contact_BAO_Contact', $field, $value);
- }
- elseif ($field == 'current_employer_id' && !empty($value)) {
- $label = "$value (" . CRM_Contact_BAO_Contact::displayName($value) . ")";
- }
- $rows["move_$field"][$moniker] = $label;
- if ($moniker == 'other') {
- //CRM-14334
- if ($value === NULL || $value == '') {
- $value = 'null';
- }
- if ($value === 0 or $value === '0') {
- $value = $qfZeroBug;
- }
- if (is_array($value) && empty($value[1])) {
- $value[1] = NULL;
- }
+ $rows["move_$field"] = [
+ 'main' => self::getFieldValueAndLabel($field, $main)['label'],
+ 'other' => self::getFieldValueAndLabel($field, $other)['label'],
+ 'title' => $fields[$field]['title'],
+ ];
- // Display a checkbox to migrate, only if the values are different
- if ($value != $main[$field]) {
- $elements[] = [
- 'advcheckbox',
- "move_$field",
- NULL,
- NULL,
- NULL,
- $value,
- ];
- }
+ $value = self::getFieldValueAndLabel($field, $other)['value'];
+ //CRM-14334
+ if ($value === NULL || $value == '') {
+ $value = 'null';
+ }
+ if ($value === 0 or $value === '0') {
+ $value = $qfZeroBug;
+ }
+ if (is_array($value) && empty($value[1])) {
+ $value[1] = NULL;
+ }
- $migrationInfo["move_$field"] = $value;
- }
+ // Display a checkbox to migrate, only if the values are different
+ if ($value != $main[$field]) {
+ $elements[] = [
+ 0 => 'advcheckbox',
+ 1 => "move_$field",
+ 2 => NULL,
+ 3 => NULL,
+ 4 => NULL,
+ 5 => $value,
+ 'is_checked' => (!isset($main[$field]) || $main[$field] === ''),
+ ];
}
- $rows["move_$field"]['title'] = $fields[$field]['title'];
+
+ $migrationInfo["move_$field"] = $value;
}
// Handle location blocks.
// Set up useful information about the location blocks
$locationBlocks = self::getLocationBlockInfo();
- $locations = [
- 'main' => [],
- 'other' => [],
- ];
-
- // @todo This could probably be defined and used earlier
- $mergeTargets = [
- 'main' => $mainId,
- 'other' => $otherId,
- ];
+ $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
- foreach ($mergeTargets as $moniker => $cid) {
- $searchParams = [
- 'contact_id' => $cid,
- // CRM-17556 Order by field-specific criteria
- 'options' => [
- 'sort' => $blockInfo['sortString'],
- ],
- ];
- $values = civicrm_api3($blockName, 'get', $searchParams);
- if ($values['count']) {
- $cnt = 0;
- foreach ($values['values'] as $value) {
- $locations[$moniker][$blockName][$cnt] = $value;
- // Fix address display
- if ($blockName == 'address') {
- // For performance avoid geocoding while merging https://issues.civicrm.org/jira/browse/CRM-21786
- // we can expect existing geocode values to be retained.
- $value['skip_geocode'] = TRUE;
- CRM_Core_BAO_Address::fixAddress($value);
- unset($value['skip_geocode']);
- $locations[$moniker][$blockName][$cnt]['display'] = CRM_Utils_Address::format($value);
- }
- // Fix email display
- elseif ($blockName == 'email') {
- $locations[$moniker][$blockName][$cnt]['display'] = CRM_Utils_Mail::format($value);
- }
-
- $cnt++;
- }
- }
- }
+ $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
/**
* Dedupe a pair of contacts.
*
- * @param array $resultStats
- * @param array $deletedContacts
+ * @param array $dupes
* @param string $mode
* @param bool $checkPermissions
- * @param int $mainId
- * @param int $otherId
* @param string $cacheKeyString
*
+ * @return bool|array
* @throws \CRM_Core_Exception
* @throws \CiviCRM_API3_Exception
* @throws \API_Exception
*/
- protected static function dedupePair(&$resultStats, &$deletedContacts, $mode, $checkPermissions, $mainId, $otherId, $cacheKeyString) {
-
+ protected static function dedupePair($dupes, $mode = 'safe', $checkPermissions = TRUE, $cacheKeyString = NULL) {
+ CRM_Utils_Hook::merge('flip', $dupes, $dupes['dstID'], $dupes['srcID']);
+ $mainId = $dupes['dstID'];
+ $otherId = $dupes['srcID'];
+ $resultStats = [];
+
+ if (!$mainId || !$otherId) {
+ // return error
+ return FALSE;
+ }
$migrationInfo = [];
$conflicts = [];
if (!CRM_Dedupe_Merger::skipMerge($mainId, $otherId, $migrationInfo, $mode, $conflicts)) {
'main_id' => $mainId,
'other_id' => $otherId,
];
- $deletedContacts[] = $otherId;
}
else {
$resultStats['skipped'][] = [
else {
CRM_Core_BAO_PrevNextCache::deletePair($mainId, $otherId, $cacheKeyString);
}
+ return $resultStats;
}
/**
return $keysToIgnore;
}
+ /**
+ * Get the field value & label for the given field.
+ *
+ * @param $field
+ * @param $contact
+ *
+ * @return array
+ * @throws \Exception
+ */
+ private static function getFieldValueAndLabel($field, $contact): array {
+ $fields = self::getMergeFieldsMetadata();
+ $value = $label = CRM_Utils_Array::value($field, $contact);
+ $fieldSpec = $fields[$field];
+ if (!empty($fieldSpec['serialize']) && is_array($value)) {
+ // In practice this only applies to preferred_communication_method as the sub types are skipped above
+ // and no others are serialized.
+ $labels = [];
+ foreach ($value as $individualValue) {
+ $labels[] = CRM_Core_PseudoConstant::getLabel('CRM_Contact_BAO_Contact', $field, $individualValue);
+ }
+ $label = implode(', ', $labels);
+ // We serialize this due to historic handling but it's likely that if we just left it as an
+ // array all would be well & we would have less code.
+ $value = CRM_Core_DAO::serializeField($value, $fieldSpec['serialize']);
+ }
+ elseif (!empty($fieldSpec['type']) && $fieldSpec['type'] == CRM_Utils_Type::T_DATE) {
+ if ($value) {
+ $value = str_replace('-', '', $value);
+ $label = CRM_Utils_Date::customFormat($label);
+ }
+ else {
+ $value = "null";
+ }
+ }
+ elseif (!empty($fields[$field]['type']) && $fields[$field]['type'] == CRM_Utils_Type::T_BOOLEAN) {
+ if ($label === '0') {
+ $label = ts('[ ]');
+ }
+ if ($label === '1') {
+ $label = ts('[x]');
+ }
+ }
+ elseif (!empty($fieldSpec['pseudoconstant'])) {
+ $label = CRM_Core_PseudoConstant::getLabel('CRM_Contact_BAO_Contact', $field, $value);
+ }
+ elseif ($field == 'current_employer_id' && !empty($value)) {
+ $label = "$value (" . CRM_Contact_BAO_Contact::displayName($value) . ")";
+ }
+ return ['label' => $label, 'value' => $value];
+ }
+
+ /**
+ * Build up the location block for the contact in dedupe-screen display format.
+ *
+ * @param integer $cid
+ * @param array $blockInfo
+ * @param string $blockName
+ *
+ * @return array
+ *
+ * @throws \CiviCRM_API3_Exception
+ */
+ private static function buildLocationBlockForContact($cid, $blockInfo, $blockName): array {
+ $searchParams = [
+ 'contact_id' => $cid,
+ // CRM-17556 Order by field-specific criteria
+ 'options' => [
+ 'sort' => $blockInfo['sortString'],
+ ],
+ ];
+ $locationBlock = [];
+ $values = civicrm_api3($blockName, 'get', $searchParams);
+ if ($values['count']) {
+ $cnt = 0;
+ foreach ($values['values'] as $value) {
+ $locationBlock[$cnt] = $value;
+ // Fix address display
+ if ($blockName == 'address') {
+ // For performance avoid geocoding while merging https://issues.civicrm.org/jira/browse/CRM-21786
+ // we can expect existing geocode values to be retained.
+ $value['skip_geocode'] = TRUE;
+ CRM_Core_BAO_Address::fixAddress($value);
+ unset($value['skip_geocode']);
+ $locationBlock[$cnt]['display'] = CRM_Utils_Address::format($value);
+ }
+ // Fix email display
+ elseif ($blockName == 'email') {
+ $locationBlock[$cnt]['display'] = CRM_Utils_Mail::format($value);
+ }
+
+ $cnt++;
+ }
+ }
+ return $locationBlock;
+ }
+
}