/** * Carry out batch merges. */ public static function callBatchMerge(CRM_Queue_TaskContext $ctx, $rgid, $gid = NULL, $mode = 'safe', $autoFlip = TRUE, $batchLimit = 1, $isSelected = 2) { $result = CRM_Dedupe_Merger::batchMerge($rgid, $gid, $mode, $autoFlip, $batchLimit, $isSelected); return CRM_Queue_Task::TASK_SUCCESS; }
/** * Merges given pair of duplicate contacts. * * @param array $params * Allowed array keys are: * -int main_id: main contact id with whom merge has to happen * -int other_id: duplicate contact which would be deleted after merge operation * -string mode: "safe" skips the merge if there are no conflicts. Does a force merge otherwise. * -boolean auto_flip: whether to let api decide which contact to retain and which to delete. * * @return array * API Result Array */ function civicrm_api3_contact_merge($params) { $mode = CRM_Utils_Array::value('mode', $params, 'safe'); $autoFlip = CRM_Utils_Array::value('auto_flip', $params, TRUE); $dupePairs = array(array('srcID' => CRM_Utils_Array::value('main_id', $params), 'dstID' => CRM_Utils_Array::value('other_id', $params))); $result = CRM_Dedupe_Merger::merge($dupePairs, array(), $mode, $autoFlip); if ($result['is_error'] == 0) { return civicrm_api3_create_success(); } else { return civicrm_api3_create_error($result['messages']); } }
/** * Browse all rule groups. */ public function run() { $gid = CRM_Utils_Request::retrieve('gid', 'Positive', $this, FALSE, 0); $action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 0); $context = CRM_Utils_Request::retrieve('context', 'String', $this); $limit = CRM_Utils_Request::retrieve('limit', 'Integer', $this); $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive'); $urlQry = "reset=1&rgid={$rgid}&gid={$gid}&limit={$limit}"; $this->assign('urlQuery', $urlQry); $session = CRM_Core_Session::singleton(); $contactIds = $session->get('selectedSearchContactIds'); if ($context == 'search' || !empty($contactIds)) { $context = 'search'; $this->assign('backURL', $session->readUserContext()); } if ($action & CRM_Core_Action::RENEW) { // empty cache if ($rgid) { CRM_Core_BAO_PrevNextCache::deleteItem(NULL, CRM_Dedupe_Merger::getMergeCacheKeyString($rgid, $gid)); } CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry . "&action=update")); } elseif ($action & CRM_Core_Action::MAP) { // do a batch merge if requested $result = CRM_Dedupe_Merger::batchMerge($rgid, $gid, 'safe', TRUE, 75); $skippedCount = CRM_Utils_Request::retrieve('skipped', 'Positive', $this, FALSE, 0); $skippedCount = $skippedCount + count($result['skipped']); $mergedCount = CRM_Utils_Request::retrieve('merged', 'Positive', $this, FALSE, 0); $mergedCount = $mergedCount + count($result['merged']); if (empty($result['merged']) && empty($result['skipped'])) { $message = ''; if ($mergedCount >= 1) { $message = ts("%1 pairs of duplicates were merged", array(1 => $mergedCount)); } if ($skippedCount >= 1) { $message = $message ? "{$message} and " : ''; $message .= ts("%1 pairs of duplicates were skipped due to conflict", array(1 => $skippedCount)); } $message .= ts(" during the batch merge process with safe mode."); CRM_Core_Session::setStatus($message, ts('Merge Complete'), 'success'); CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry . "&action=update")); } else { $urlQry .= "&action=map&skipped={$skippedCount}&merged={$mergedCount}"; CRM_Utils_System::jsRedirect(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry), ts('Batch Merge Task in progress'), ts('The batch merge task is still in progress. This page will be refreshed automatically.')); } } if ($action & CRM_Core_Action::UPDATE || $action & CRM_Core_Action::BROWSE) { $cid = CRM_Utils_Request::retrieve('cid', 'Positive', $this, FALSE, 0); $this->action = CRM_Core_Action::UPDATE; $urlQry .= '&snippet=4'; if ($context == 'conflicts') { $urlQry .= "&selected=1"; } $this->assign('sourceUrl', CRM_Utils_System::url('civicrm/ajax/dedupefind', $urlQry, FALSE, NULL, FALSE)); //reload from cache table $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($rgid, $gid); $stats = CRM_Dedupe_Merger::getMergeStatsMsg($cacheKeyString); if ($stats) { CRM_Core_Session::setStatus($stats); // reset so we not displaying same message again CRM_Dedupe_Merger::resetMergeStats($cacheKeyString); } $join = CRM_Dedupe_Merger::getJoinOnDedupeTable(); $where = "de.id IS NULL"; if ($context == 'conflicts') { $where .= " AND pn.is_selected = 1"; } $this->_mainContacts = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, $join, $where); if (empty($this->_mainContacts)) { if ($context == 'conflicts') { // if the current screen was intended to list only selected contacts, move back to full dupe list CRM_Utils_System::redirect(CRM_Utils_System::url(CRM_Utils_System::currentPath(), $urlQry . '&action=update')); } if ($gid) { $foundDupes = $this->get("dedupe_dupes_{$gid}"); if (!$foundDupes) { $foundDupes = CRM_Dedupe_Finder::dupesInGroup($rgid, $gid, $limit); } $this->set("dedupe_dupes_{$gid}", $foundDupes); } elseif (!empty($contactIds)) { $foundDupes = $this->get("search_dedupe_dupes_{$gid}"); if (!$foundDupes) { $foundDupes = CRM_Dedupe_Finder::dupes($rgid, $contactIds); } $this->set("search_dedupe_dupes_{$gid}", $foundDupes); } else { $foundDupes = $this->get('dedupe_dupes'); if (!$foundDupes) { $foundDupes = CRM_Dedupe_Finder::dupes($rgid, array(), TRUE, $limit); } $this->set('dedupe_dupes', $foundDupes); } if (!$foundDupes) { $ruleGroup = new CRM_Dedupe_BAO_RuleGroup(); $ruleGroup->id = $rgid; $ruleGroup->find(TRUE); $session = CRM_Core_Session::singleton(); $session->setStatus(ts('No possible duplicates were found using %1 rule.', array(1 => $ruleGroup->name)), ts('None Found'), 'info'); $url = CRM_Utils_System::url('civicrm/contact/deduperules', 'reset=1'); if ($context == 'search') { $url = $session->readUserContext(); } CRM_Utils_System::redirect($url); } else { $mainContacts = CRM_Dedupe_Finder::parseAndStoreDupePairs($foundDupes, $cacheKeyString); if ($cid) { $this->_cid = $cid; } if ($gid) { $this->_gid = $gid; } $this->_rgid = $rgid; $this->_mainContacts = $mainContacts; $session = CRM_Core_Session::singleton(); if ($this->_cid) { $session->pushUserContext(CRM_Utils_System::url('civicrm/contact/deduperules', $urlQry . "&action=update&cid={$this->_cid}")); } else { $session->pushUserContext(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry . "&action=update")); } } } else { if ($cid) { $this->_cid = $cid; } if ($gid) { $this->_gid = $gid; } $this->_rgid = $rgid; } $this->assign('action', $this->action); $this->browse(); } else { $this->action = CRM_Core_Action::UPDATE; $this->edit($this->action); $this->assign('action', $this->action); } $this->assign('context', $context); // parent run return parent::run(); }
public function postProcess() { $formValues = $this->exportValues(); // reset all selected contact ids from session // when we came from search context, CRM-3526 $session = CRM_Core_Session::singleton(); if ($session->get('selectedSearchContactIds')) { $session->resetScope('selectedSearchContactIds'); } $formValues['main_details'] = $this->_mainDetails; $formValues['other_details'] = $this->_otherDetails; CRM_Dedupe_Merger::moveAllBelongings($this->_cid, $this->_oid, $formValues); $name = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_cid, 'display_name'); $message = '<ul><li>' . ts('%1 has been updated.', array(1 => $name)) . '</li><li>' . ts('Contact ID %1 has been deleted.', array(1 => $this->_oid)) . '</li></ul>'; CRM_Core_Session::setStatus($message, ts('Contacts Merged'), 'success'); //create activity for merge //To do: this should be refactored into BAO layer at some point. $messageActivity = ts('Contact ID %1 has been merged and deleted.', array(1 => $this->_oid)); $activityParams = array('subject' => $messageActivity, 'source_contact_id' => $session->get('userID'), 'target_contact_id' => $this->_cid, 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Contact Merged'), 'status_id' => 'Completed', 'priority_id' => 'Normal', 'activity_date_time' => date('YmdHis')); civicrm_api3('activity', 'create', $activityParams); $url = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$this->_cid}"); if (!empty($formValues['_qf_Merge_submit'])) { $listParamsURL = "reset=1&action=update&rgid={$this->_rgid}"; if ($this->_gid) { $listParamsURL .= "&gid={$this->_gid}"; } $lisitingURL = CRM_Utils_System::url('civicrm/contact/dedupefind', $listParamsURL); CRM_Utils_System::redirect($lisitingURL); } if (!empty($formValues['_qf_Merge_done'])) { CRM_Utils_System::redirect($url); } if ($this->next && $this->_mergeId) { $cacheKey = "merge {$this->_contactType}"; $cacheKey .= $this->_rgid ? "_{$this->_rgid}" : '_0'; $cacheKey .= $this->_gid ? "_{$this->_gid}" : '_0'; $join = "LEFT JOIN civicrm_dedupe_exception de ON ( pn.entity_id1 = de.contact_id1 AND\n pn.entity_id2 = de.contact_id2 )"; $where = "de.id IS NULL"; $pos = CRM_Core_BAO_PrevNextCache::getPositions($cacheKey, NULL, NULL, $this->_mergeId, $join, $where); if (!empty($pos) && $pos['next']['id1'] && $pos['next']['id2']) { $urlParam = "reset=1&cid={$pos['next']['id1']}&oid={$pos['next']['id2']}&mergeId={$pos['next']['mergeId']}&action=update"; if ($this->_rgid) { $urlParam .= "&rgid={$this->_rgid}"; } if ($this->_gid) { $urlParam .= "&gid={$this->_gid}"; } $url = CRM_Utils_System::url('civicrm/contact/merge', $urlParam); } } CRM_Utils_System::redirect($url); }
public function postProcess() { $formValues = $this->exportValues(); // reset all selected contact ids from session // when we came from search context, CRM-3526 $session = CRM_Core_Session::singleton(); if ($session->get('selectedSearchContactIds')) { $session->resetScope('selectedSearchContactIds'); } $formValues['main_details'] = $formValues['other_details'] = array(); $formValues['main_details']['contact_type'] = $this->_contactType; $formValues['main_details']['loc_block_ids'] = $this->_locBlockIds['main']; $formValues['other_details']['loc_block_ids'] = $this->_locBlockIds['other']; CRM_Dedupe_Merger::moveAllBelongings($this->_cid, $this->_oid, $formValues); CRM_Core_Session::setStatus(ts('Contact id %1 has been updated and contact id %2 has been deleted.', array(1 => $this->_cid, 2 => $this->_oid)), ts('Contacts Merged'), 'success'); $url = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$this->_cid}"); if (CRM_Utils_Array::value('_qf_Merge_submit', $formValues)) { $listParamsURL = "reset=1&action=update&rgid={$this->_rgid}"; if ($this->_gid) { $listParamsURL .= "&gid={$this->_gid}"; } $lisitingURL = CRM_Utils_System::url('civicrm/contact/dedupefind', $listParamsURL); CRM_Utils_System::redirect($lisitingURL); } if (CRM_Utils_Array::value('_qf_Merge_done', $formValues)) { CRM_Utils_System::redirect($url); } if ($this->next && $this->_mergeId) { $cacheKey = "merge {$this->_contactType}"; $cacheKey .= $this->_rgid ? "_{$this->_rgid}" : '_0'; $cacheKey .= $this->_gid ? "_{$this->_gid}" : '_0'; $join = "LEFT JOIN civicrm_dedupe_exception de ON ( pn.entity_id1 = de.contact_id1 AND\n pn.entity_id2 = de.contact_id2 )"; $where = "de.id IS NULL"; $pos = CRM_Core_BAO_PrevNextCache::getPositions($cacheKey, NULL, NULL, $this->_mergeId, $join, $where); if (!empty($pos) && $pos['next']['id1'] && $pos['next']['id2']) { $urlParam = "reset=1&cid={$pos['next']['id1']}&oid={$pos['next']['id2']}&mergeId={$pos['next']['mergeId']}&action=update"; if ($this->_rgid) { $urlParam .= "&rgid={$this->_rgid}"; } if ($this->_gid) { $urlParam .= "&gid={$this->_gid}"; } $url = CRM_Utils_System::url('civicrm/contact/merge', $urlParam); } } CRM_Utils_System::redirect($url); }
/** * Page will try and merge as many contacts as possible into the household, * and give an overview of the current status * * @param (via URL) hid household ID * @param (via URL) oids other contact IDs, comma separated, to be merged int hid */ public function run() { CRM_Utils_System::setTitle(ts('Merge Contacts into Household', array('domain' => 'de.systopia.householdmerge'))); // extract IDs $household_id = (int) CRM_Utils_Array::value('hid', $_REQUEST); $other_ids = array(); $oids = preg_split('#,#', CRM_Utils_Array::value('oids', $_REQUEST, "")); foreach ($oids as $oid) { $oid = (int) $oid; if ($oid) { $other_ids[] = $oid; } } // verify parameters if (empty($household_id) || empty($other_ids)) { CRM_Core_Session::setStatus(ts('Household-Merge page cannot be called without "hid" or "oids" parameter.', array('domain' => 'de.systopia.householdmerge')), ts('Error', array('domain' => 'de.systopia.householdmerge')), 'error'); CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/dashboard')); return; } // NOW: load all contacts $household = civicrm_api3('Contact', 'getsingle', array('id' => $household_id)); $other_contacts = array(); foreach ($other_ids as $other_id) { $other_contact = civicrm_api3('Contact', 'getsingle', array('id' => $other_id)); $other_contact['was_merged'] = (bool) (!empty($other_contact['contact_is_deleted'])); $other_contacts[] = $other_contact; } // AND: try to merge the (remaining) contacts $merge_controller = new CRM_Householdmerge_MergeController(); $merge_controller->registerHHMerge($household_id, $other_ids); $merge_complete = TRUE; foreach ($other_contacts as &$other_contact) { if ($other_contact['was_merged']) { continue; } $cacheParams = array(); $mode = 'safe'; $dupePairs = array(); $dupePairs[] = array('srcID' => $other_contact['id'], 'dstID' => $household_id); $result = CRM_Dedupe_Merger::merge($dupePairs, $cacheParams, $mode, FALSE); // process result if (!empty($result['skipped'])) { $other_contact['was_merged'] = FALSE; $merge_complete = FALSE; } else { $other_contact['was_merged'] = TRUE; } } // set the conflict counts $hhmerge_controller = new CRM_Householdmerge_MergeController(); foreach ($other_contacts as &$other_contact) { $other_contact['conflict_count'] = $hhmerge_controller->getConflictCount($household_id, $other_contact['id']); } $this->assign('household', $household); $this->assign('other', $other_contacts); $this->assign('merge_complete', $merge_complete); if ($merge_complete) { $merge_controller->unregisterHHMerge($household_id); } parent::run(); }
/** * Test function that gets duplicate pairs. * * It turns out there are 2 code paths retrieving this data so my initial focus is on ensuring * they match. */ public function testGetMatchesInGroup() { $this->setupMatchData(); $groupID = $this->groupCreate(array('title' => 'she-mice')); $this->callAPISuccess('GroupContact', 'create', array('group_id' => $groupID, 'contact_id' => $this->contacts[3]['id'])); $pairs = CRM_Dedupe_Merger::getDuplicatePairs(1, $groupID, TRUE, 25, FALSE); $this->assertEquals(array(0 => array('srcID' => $this->contacts[3]['id'], 'srcName' => 'Mr. Minnie Mouse II', 'dstID' => $this->contacts[2]['id'], 'dstName' => 'Mr. Minnie Mouse II', 'weight' => 20, 'canMerge' => TRUE)), $pairs); }
/** * Merges given pair of duplicate contacts. * * @param array $params * Allowed array keys are: * -int main_id: main contact id with whom merge has to happen * -int other_id: duplicate contact which would be deleted after merge operation * -string mode: "safe" skips the merge if there are no conflicts. Does a force merge otherwise. * -boolean auto_flip: whether to let api decide which contact to retain and which to delete. * * @return array * API Result Array * @throws CiviCRM_API3_Exception */ function civicrm_api3_contact_merge($params) { if (($result = CRM_Dedupe_Merger::merge(array(array('srcID' => $params['to_remove_id'], 'dstID' => $params['to_keep_id'])), array(), $params['mode'], $params['auto_flip'])) != FALSE) { return civicrm_api3_create_success($result, $params); } throw new CiviCRM_API3_Exception('Merge failed'); }
/** * The goal of this function is to test that all required tables are returned. */ public function testGetCidRefs() { $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, 'Contacts'); $this->assertEquals(array_merge($this->getStaticCIDRefs(), $this->getHackedInCIDRef()), CRM_Dedupe_Merger::cidRefs()); $this->assertEquals(array_merge($this->getCalculatedCIDRefs(), $this->getHackedInCIDRef()), CRM_Dedupe_Merger::cidRefs()); }
/** * Merges given pair of duplicate contacts. * * @param array $params * Allowed array keys are: * -int main_id: main contact id with whom merge has to happen * -int other_id: duplicate contact which would be deleted after merge operation * -string mode: "safe" skips the merge if there are no conflicts. Does a force merge otherwise. * -boolean auto_flip: whether to let api decide which contact to retain and which to delete. * * @return array * API Result Array * @throws CiviCRM_API3_Exception */ function civicrm_api3_contact_merge($params) { $mode = CRM_Utils_Array::value('mode', $params, 'safe'); $autoFlip = CRM_Utils_Array::value('auto_flip', $params, TRUE); $dupePairs = array(array('srcID' => CRM_Utils_Array::value('main_id', $params), 'dstID' => CRM_Utils_Array::value('other_id', $params))); if (($result = CRM_Dedupe_Merger::merge($dupePairs, array(), $mode, $autoFlip)) != FALSE) { return civicrm_api3_create_success($result, $params); } throw new CiviCRM_API3_Exception('Merge failed'); }
/** * Dedupe a pair of contacts. * * @param array $migrationInfo * @param array $resultStats * @param array $deletedContacts * @param string $mode * @param bool $checkPermissions * @param int $mainId * @param int $otherId * @param string $cacheKeyString */ protected static function dedupePair(&$migrationInfo, &$resultStats, &$deletedContacts, $mode, $checkPermissions, $mainId, $otherId, $cacheKeyString) { // go ahead with merge if there is no conflict $conflicts = array(); if (!CRM_Dedupe_Merger::skipMerge($mainId, $otherId, $migrationInfo, $mode, $conflicts)) { CRM_Dedupe_Merger::moveAllBelongings($mainId, $otherId, $migrationInfo, $checkPermissions); $resultStats['merged'][] = array('main_id' => $mainId, 'other_id' => $otherId); $deletedContacts[] = $otherId; } else { $resultStats['skipped'][] = array('main_id' => $mainId, 'other_id' => $otherId); } // store any conflicts if (!empty($conflicts)) { foreach ($conflicts as $key => $dnc) { $conflicts[$key] = "{$migrationInfo['rows'][$key]['title']}: '{$migrationInfo['rows'][$key]['main']}' vs. '{$migrationInfo['rows'][$key]['other']}'"; } CRM_Core_BAO_PrevNextCache::markConflict($mainId, $otherId, $cacheKeyString, $conflicts); } else { // delete entry from PrevNextCache table so we don't consider the pair next time // pair may have been flipped, so make sure we delete using both orders CRM_Core_BAO_PrevNextCache::deletePair($mainId, $otherId, $cacheKeyString, TRUE); } CRM_Core_DAO::freeResult(); }
/** * Merges given pair of duplicate contacts. * * @param array $params * Input parameters. * * @return array * API Result Array * @throws \CiviCRM_API3_Exception */ function civicrm_api3_job_process_batch_merge($params) { $rule_group_id = CRM_Utils_Array::value('rule_group_id', $params); if (!$rule_group_id) { $rule_group_id = civicrm_api3('RuleGroup', 'getvalue', array('contact_type' => 'Individual', 'used' => 'Unsupervised', 'return' => 'id', 'options' => array('limit' => 1))); } $gid = CRM_Utils_Array::value('gid', $params); $mode = CRM_Utils_Array::value('mode', $params, 'safe'); $autoFlip = CRM_Utils_Array::value('auto_flip', $params, TRUE); $result = CRM_Dedupe_Merger::batchMerge($rule_group_id, $gid, $mode, $autoFlip, 1, 2, CRM_Utils_Array::value('criteria', $params, array()), CRM_Utils_Array::value('check_permissions', $params)); return civicrm_api3_create_success($result, $params); }
/** * Repopulate the cache of merge prospects. * * @param int $rgid * @param int $gid * @param NULL $cacheKeyString * @param array $criteria * Additional criteria to filter by. * * @param bool $checkPermissions * Respect logged in user's permissions. * * @return bool * @throws \CRM_Core_Exception * @throws \CiviCRM_API3_Exception */ public static function refillCache($rgid, $gid, $cacheKeyString, $criteria, $checkPermissions) { if (!$cacheKeyString && $rgid) { $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($rgid, $gid, $criteria, $checkPermissions); } if (!$cacheKeyString) { return FALSE; } // 1. Clear cache if any $sql = "DELETE FROM civicrm_prevnext_cache WHERE cacheKey LIKE %1"; CRM_Core_DAO::executeQuery($sql, array(1 => array("{$cacheKeyString}%", 'String'))); // FIXME: we need to start using temp tables / queries here instead of arrays. // And cleanup code in CRM/Contact/Page/DedupeFind.php // 2. FILL cache $foundDupes = array(); if ($rgid && $gid) { $foundDupes = CRM_Dedupe_Finder::dupesInGroup($rgid, $gid); } elseif ($rgid) { $contactIDs = array(); if (!empty($criteria)) { $contacts = civicrm_api3('Contact', 'get', array_merge(array('options' => array('limit' => 0), 'return' => 'id'), $criteria['contact'])); $contactIDs = array_keys($contacts['values']); } $foundDupes = CRM_Dedupe_Finder::dupes($rgid, $contactIDs, $checkPermissions); } if (!empty($foundDupes)) { CRM_Dedupe_Finder::parseAndStoreDupePairs($foundDupes, $cacheKeyString); } }
/** * Browse all rule groups. */ public function run() { $gid = CRM_Utils_Request::retrieve('gid', 'Positive', $this, FALSE, 0); $action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 0); $context = CRM_Utils_Request::retrieve('context', 'String', $this); $session = CRM_Core_Session::singleton(); $contactIds = $session->get('selectedSearchContactIds'); if ($context == 'search' || !empty($contactIds)) { $context = 'search'; $this->assign('backURL', $session->readUserContext()); } if ($action & CRM_Core_Action::RENEW) { // empty cache $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive', $this, FALSE, 0); if ($rgid) { $contactType = CRM_Core_DAO::getFieldValue('CRM_Dedupe_DAO_RuleGroup', $rgid, 'contact_type'); $cacheKeyString = "merge {$contactType}"; $cacheKeyString .= $rgid ? "_{$rgid}" : '_0'; $cacheKeyString .= $gid ? "_{$gid}" : '_0'; CRM_Core_BAO_PrevNextCache::deleteItem(NULL, $cacheKeyString); } $urlQry = "reset=1&action=update&rgid={$rgid}"; if ($gid) { $urlQry .= "&gid={$gid}"; } CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry)); } elseif ($action & CRM_Core_Action::MAP) { // do a batch merge if requested $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive', $this, FALSE, 0); $result = CRM_Dedupe_Merger::batchMerge($rgid, $gid, 'safe', TRUE, 75); $skippedCount = CRM_Utils_Request::retrieve('skipped', 'Positive', $this, FALSE, 0); $skippedCount = $skippedCount + count($result['skipped']); $mergedCount = CRM_Utils_Request::retrieve('merged', 'Positive', $this, FALSE, 0); $mergedCount = $mergedCount + count($result['merged']); if (empty($result['merged']) && empty($result['skipped'])) { $message = ''; if ($mergedCount >= 1) { $message = ts("%1 pairs of duplicates were merged", array(1 => $mergedCount)); } if ($skippedCount >= 1) { $message = $message ? "{$message} and " : ''; $message .= ts("%1 pairs of duplicates were skipped due to conflict", array(1 => $skippedCount)); } $message .= ts(" during the batch merge process with safe mode."); CRM_Core_Session::setStatus($message, ts('Merge Complete'), 'success'); $urlQry = "reset=1&action=update&rgid={$rgid}"; if ($gid) { $urlQry .= "&gid={$gid}"; } CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry)); } else { $urlQry = "reset=1&action=map&rgid={$rgid}"; if ($gid) { $urlQry .= "&gid={$gid}"; } $urlQry .= "&skipped={$skippedCount}&merged={$mergedCount}"; CRM_Utils_System::jsRedirect(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry), ts('Batch Merge Task in progress'), ts('The batch merge task is still in progress. This page will be refreshed automatically.')); } } if ($action & CRM_Core_Action::UPDATE || $action & CRM_Core_Action::BROWSE) { $cid = CRM_Utils_Request::retrieve('cid', 'Positive', $this, FALSE, 0); $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive', $this, FALSE, 0); $this->action = CRM_Core_Action::UPDATE; //calculate the $contactType if ($rgid) { $contactType = CRM_Core_DAO::getFieldValue('CRM_Dedupe_DAO_RuleGroup', $rgid, 'contact_type'); } $sourceParams = 'snippet=4'; if ($gid) { $sourceParams .= "&gid={$gid}"; } if ($rgid) { $sourceParams .= "&rgid={$rgid}"; } if ($context == 'conflicts') { $sourceParams .= "&selected=1"; } $this->assign('sourceUrl', CRM_Utils_System::url('civicrm/ajax/dedupefind', $sourceParams, FALSE, NULL, FALSE)); //reload from cache table $cacheKeyString = "merge {$contactType}"; $cacheKeyString .= $rgid ? "_{$rgid}" : '_0'; $cacheKeyString .= $gid ? "_{$gid}" : '_0'; $stats = CRM_Dedupe_Merger::getMergeStatsMsg($cacheKeyString); if ($stats) { CRM_Core_Session::setStatus($stats); // reset so we not displaying same message again CRM_Dedupe_Merger::resetMergeStats($cacheKeyString); } $join = "LEFT JOIN civicrm_dedupe_exception de ON ( pn.entity_id1 = de.contact_id1 AND\n pn.entity_id2 = de.contact_id2 )"; $where = "de.id IS NULL"; if ($context == 'conflicts') { $where .= " AND pn.is_selected = 1"; } $this->_mainContacts = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, $join, $where); if (empty($this->_mainContacts)) { if ($context == 'conflicts') { // if the current screen was intended to list only selected contacts, move back to full dupe list $sourceParams = 'reset=1&action=update'; if ($gid) { $sourceParams .= "&gid={$gid}"; } if ($rgid) { $sourceParams .= "&rgid={$rgid}"; } CRM_Utils_System::redirect(CRM_Utils_System::url(CRM_Utils_System::currentPath(), $sourceParams)); } if ($gid) { $foundDupes = $this->get("dedupe_dupes_{$gid}"); if (!$foundDupes) { $foundDupes = CRM_Dedupe_Finder::dupesInGroup($rgid, $gid); } $this->set("dedupe_dupes_{$gid}", $foundDupes); } elseif (!empty($contactIds)) { $foundDupes = $this->get("search_dedupe_dupes_{$gid}"); if (!$foundDupes) { $foundDupes = CRM_Dedupe_Finder::dupes($rgid, $contactIds); } $this->get("search_dedupe_dupes_{$gid}", $foundDupes); } else { $foundDupes = $this->get('dedupe_dupes'); if (!$foundDupes) { $foundDupes = CRM_Dedupe_Finder::dupes($rgid); } $this->set('dedupe_dupes', $foundDupes); } if (!$foundDupes) { $ruleGroup = new CRM_Dedupe_BAO_RuleGroup(); $ruleGroup->id = $rgid; $ruleGroup->find(TRUE); $session = CRM_Core_Session::singleton(); $session->setStatus(ts('No possible duplicates were found using %1 rule.', array(1 => $ruleGroup->name)), ts('None Found'), 'info'); $url = CRM_Utils_System::url('civicrm/contact/deduperules', 'reset=1'); if ($context == 'search') { $url = $session->readUserContext(); } CRM_Utils_System::redirect($url); } else { $cids = array(); foreach ($foundDupes as $dupe) { $cids[$dupe[0]] = 1; $cids[$dupe[1]] = 1; } $cidString = implode(', ', array_keys($cids)); $sql = "SELECT id, display_name FROM civicrm_contact WHERE id IN ({$cidString}) ORDER BY sort_name"; $dao = new CRM_Core_DAO(); $dao->query($sql); $displayNames = array(); while ($dao->fetch()) { $displayNames[$dao->id] = $dao->display_name; } // FIXME: sort the contacts; $displayName // is already sort_name-sorted, so use that // (also, consider sorting by dupe count first) // lobo - change the sort to by threshold value // so the more likely dupes are sorted first $session = CRM_Core_Session::singleton(); $userId = $session->get('userID'); $mainContacts = $permission = array(); foreach ($foundDupes as $dupes) { $srcID = $dupes[0]; $dstID = $dupes[1]; if ($dstID == $userId) { $srcID = $dupes[1]; $dstID = $dupes[0]; } /*** * Eliminate this since it introduces 3 queries PER merge row * and hence is very expensive * CRM-8822 * if ( !array_key_exists( $srcID, $permission ) ) { * $permission[$srcID] = CRM_Contact_BAO_Contact_Permission::allow( $srcID, CRM_Core_Permission::EDIT ); * } * if ( !array_key_exists( $dstID, $permission ) ) { * $permission[$dstID] = CRM_Contact_BAO_Contact_Permission::allow( $dstID, CRM_Core_Permission::EDIT ); * } * * $canMerge = ( $permission[$dstID] && $permission[$srcID] ); * */ // we'll do permission checking during the merge process $canMerge = TRUE; $mainContacts[] = $row = array('srcID' => $srcID, 'srcName' => $displayNames[$srcID], 'dstID' => $dstID, 'dstName' => $displayNames[$dstID], 'weight' => $dupes[2], 'canMerge' => $canMerge); $data = CRM_Core_DAO::escapeString(serialize($row)); $values[] = " ( 'civicrm_contact', {$srcID}, {$dstID}, '{$cacheKeyString}', '{$data}' ) "; } if ($cid) { $this->_cid = $cid; } if ($gid) { $this->_gid = $gid; } $this->_rgid = $rgid; $this->_mainContacts = $mainContacts; CRM_Core_BAO_PrevNextCache::setItem($values); $session = CRM_Core_Session::singleton(); if ($this->_cid) { $session->pushUserContext(CRM_Utils_System::url('civicrm/contact/deduperules', "action=update&rgid={$this->_rgid}&gid={$this->_gid}&cid={$this->_cid}")); } else { $session->pushUserContext(CRM_Utils_System::url('civicrm/contact/dedupefind', "reset=1&action=update&rgid={$this->_rgid}")); } } } else { if ($cid) { $this->_cid = $cid; } if ($gid) { $this->_gid = $gid; } $this->_rgid = $rgid; } $this->assign('action', $this->action); $this->browse(); } else { $this->action = CRM_Core_Action::UPDATE; $this->edit($this->action); $this->assign('action', $this->action); } $this->assign('context', $context); // parent run return parent::run(); }
public function postProcess() { $formValues = $this->exportValues(); // user can't choose to move cases without activities (CRM-3778) if ($formValues['move_rel_table_cases'] == '1' && array_key_exists('move_rel_table_activities', $formValues)) { $formValues['move_rel_table_activities'] = '1'; } // reset all selected contact ids from session // when we came from search context, CRM-3526 $session =& CRM_Core_Session::singleton(); if ($session->get('selectedSearchContactIds')) { $session->resetScope('selectedSearchContactIds'); } $relTables =& CRM_Dedupe_Merger::relTables(); $moveTables = $locBlocks = array(); foreach ($formValues as $key => $value) { if ($value == $this->_qfZeroBug) { $value = '0'; } if ((in_array(substr($key, 5), CRM_Dedupe_Merger::$validFields) or substr($key, 0, 12) == 'move_custom_') and $value != null) { $submitted[substr($key, 5)] = $value; } elseif (substr($key, 0, 14) == 'move_location_' and $value != null) { $locField = explode('_', $key); $fieldName = $locField[2]; $fieldCount = $locField[3]; $operation = CRM_Utils_Array::value('operation', $formValues['location'][$fieldName][$fieldCount]); // default operation is overwrite. if (!$operation) { $operation = 2; } $locBlocks[$fieldName][$fieldCount]['operation'] = $operation; $locBlocks[$fieldName][$fieldCount]['locTypeId'] = CRM_Utils_Array::value('locTypeId', $formValues['location'][$fieldName][$fieldCount]); } elseif (substr($key, 0, 15) == 'move_rel_table_' and $value == '1') { $moveTables = array_merge($moveTables, $relTables[substr($key, 5)]['tables']); } } // process location blocks. if (!empty($locBlocks)) { $locComponent = array('email' => 'Email', 'phone' => 'Phone', 'im' => 'IM', 'openid' => 'OpenID', 'address' => 'Address'); require_once 'CRM/Contact/BAO/Contact.php'; $primaryBlockIds = CRM_Contact_BAO_Contact::getLocBlockIds($this->_cid, array('is_primary' => 1)); $billingBlockIds = CRM_Contact_BAO_Contact::getLocBlockIds($this->_cid, array('is_billing' => 1)); foreach ($locBlocks as $name => $block) { if (!is_array($block) || CRM_Utils_System::isNull($block)) { continue; } $daoName = $locComponent[$name]; $primaryDAOId = array_key_exists($name, $primaryBlockIds) ? array_pop($primaryBlockIds[$name]) : null; $billingDAOId = array_key_exists($name, $billingBlockIds) ? array_pop($billingBlockIds[$name]) : null; foreach ($block as $blkCount => $values) { $locTypeId = CRM_Utils_Array::value('locTypeId', $values, 1); $operation = CRM_Utils_Array::value('operation', $values, 2); $updateBlockId = CRM_Utils_Array::value($blkCount, $this->_locBlockIds['other'][$name]); // keep 1-1 mapping for address - loc type. $idKey = $blkCount; if ($name == 'address') { $idKey = $locTypeId; } $deleteBlockId = CRM_Utils_Array::value($idKey, $this->_locBlockIds['main'][$name]); if (!$updateBlockId) { continue; } require_once "CRM/Core/DAO/{$daoName}.php"; eval("\$updateDAO =& new CRM_Core_DAO_{$daoName}();"); $updateDAO->id = $updateBlockId; $updateDAO->contact_id = $this->_cid; $updateDAO->location_type_id = $locTypeId; // contact having primary block. if ($primaryDAOId) { $updateDAO->is_primary = 0; } if ($billingDAOId) { $updateDAO->is_billing = 0; } // overwrite - need to delete block from main contact. if ($deleteBlockId && $operation == 2) { eval("\$deleteDAO =& new CRM_Core_DAO_{$daoName}();"); $deleteDAO->id = $deleteBlockId; $deleteDAO->find(true); // since we overwrite primary block. if ($primaryDAOId && $primaryDAOId == $deleteDAO->id) { $updateDAO->is_primary = 1; } if ($billingDAOId && $billingDAOId == $deleteDAO->id) { $updateDAO->is_billing = 1; } $deleteDAO->delete(); $deleteDAO->free(); } $updateDAO->update(); $updateDAO->free(); } } } // FIXME: fix gender, prefix and postfix, so they're edible by createProfileContact() $names['gender'] = array('newName' => 'gender_id', 'groupName' => 'gender'); $names['individual_prefix'] = array('newName' => 'prefix_id', 'groupName' => 'individual_prefix'); $names['individual_suffix'] = array('newName' => 'suffix_id', 'groupName' => 'individual_suffix'); $names['addressee'] = array('newName' => 'addressee_id', 'groupName' => 'addressee'); $names['email_greeting'] = array('newName' => 'email_greeting_id', 'groupName' => 'email_greeting'); $names['postal_greeting'] = array('newName' => 'postal_greeting_id', 'groupName' => 'postal_greeting'); CRM_Core_OptionGroup::lookupValues($submitted, $names, true); // FIXME: fix custom fields so they're edible by createProfileContact() $cgTree =& CRM_Core_BAO_CustomGroup::getTree($this->_contactType, $this, null, -1); foreach ($cgTree as $key => $group) { if (!isset($group['fields'])) { continue; } foreach ($group['fields'] as $fid => $field) { $cFields[$fid]['attributes'] = $field; } } if (!isset($submitted)) { $submitted = array(); } foreach ($submitted as $key => $value) { if (substr($key, 0, 7) == 'custom_') { $fid = (int) substr($key, 7); $htmlType = $cFields[$fid]['attributes']['html_type']; switch ($htmlType) { case 'File': $customFiles[] = $fid; unset($submitted["custom_{$fid}"]); break; case 'Select Country': case 'Select State/Province': $submitted[$key] = CRM_Core_BAO_CustomField::getDisplayValue($value, $fid, $cFields); break; case 'CheckBox': case 'AdvMulti-Select': case 'Multi-Select': case 'Multi-Select Country': case 'Multi-Select State/Province': // Merge values from both contacts for multivalue fields, CRM-4385 // get the existing custom values from db. require_once 'CRM/Core/BAO/CustomValueTable.php'; $customParams = array('entityID' => $this->_cid, $key => true); $customfieldValues = CRM_Core_BAO_CustomValueTable::getValues($customParams); if (CRM_Utils_array::value($key, $customfieldValues)) { $existingValue = explode(CRM_Core_DAO::VALUE_SEPARATOR, $customfieldValues[$key]); if (is_array($existingValue) && !empty($existingValue)) { $mergeValue = $submmtedCustomValue = array(); if ($value) { $submmtedCustomValue = explode(CRM_Core_DAO::VALUE_SEPARATOR, $value); } //hack to remove null and duplicate values from array. foreach (array_merge($submmtedCustomValue, $existingValue) as $k => $v) { if ($v != '' && !in_array($v, $mergeValue)) { $mergeValue[] = $v; } } //keep state and country as array format. //for checkbox and m-select format w/ VALUE_SEPERATOR if (in_array($htmlType, array('CheckBox', 'Multi-Select', 'AdvMulti-Select'))) { $submitted[$key] = CRM_Core_BAO_CustomOption::VALUE_SEPERATOR . implode(CRM_Core_BAO_CustomOption::VALUE_SEPERATOR, $mergeValue) . CRM_Core_BAO_CustomOption::VALUE_SEPERATOR; } else { $submitted[$key] = $mergeValue; } } } else { if (in_array($htmlType, array('Multi-Select Country', 'Multi-Select State/Province'))) { //we require submitted values should be in array format if ($value) { $mergeValueArray = explode(CRM_Core_DAO::VALUE_SEPARATOR, $value); //hack to remove null values from array. $mergeValue = array(); foreach ($mergeValueArray as $k => $v) { if ($v != '') { $mergeValue[] = $v; } } $submitted[$key] = $mergeValue; } } } break; default: break; } } } // handle the related tables if (isset($moveTables)) { CRM_Dedupe_Merger::moveContactBelongings($this->_cid, $this->_oid, $moveTables); } // move file custom fields // FIXME: move this someplace else (one of the BAOs) after discussing // where to, and whether CRM_Core_BAO_File::delete() shouldn't actually, // like, delete a file... require_once 'CRM/Core/BAO/File.php'; require_once 'CRM/Core/DAO/CustomField.php'; require_once 'CRM/Core/DAO/CustomGroup.php'; require_once 'CRM/Core/DAO/EntityFile.php'; require_once 'CRM/Core/Config.php'; if (!isset($customFiles)) { $customFiles = array(); } foreach ($customFiles as $customId) { list($tableName, $columnName, $groupID) = CRM_Core_BAO_CustomField::getTableColumnGroup($customId); // get the contact_id -> file_id mapping $fileIds = array(); $sql = "SELECT entity_id, {$columnName} AS file_id FROM {$tableName} WHERE entity_id IN ({$this->_cid}, {$this->_oid})"; $dao =& CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray); while ($dao->fetch()) { $fileIds[$dao->entity_id] = $dao->file_id; } $dao->free(); // delete the main contact's file CRM_Core_BAO_File::delete($fileIds[$this->_cid], $this->_cid, $customId); // move the other contact's file to main contact $sql = "UPDATE {$tableName} SET {$columnName} = {$fileIds[$this->_oid]} WHERE entity_id = {$this->_cid}"; CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray); $sql = "UPDATE civicrm_entity_file SET entity_id = {$this->_cid} WHERE entity_table = '{$tableName}' AND file_id = {$fileIds[$this->_oid]}"; CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray); } // move other's belongings and delete the other contact CRM_Dedupe_Merger::moveContactBelongings($this->_cid, $this->_oid); $otherParams = array('contact_id' => $this->_oid); if (CRM_Core_Permission::check('delete contacts')) { civicrm_contact_delete($otherParams); } else { CRM_Core_Session::setStatus(ts('Do not have sufficient permission to delete duplicate contact.')); } if (isset($submitted)) { $submitted['contact_id'] = $this->_cid; CRM_Contact_BAO_Contact::createProfileContact($submitted, CRM_Core_DAO::$_nullArray, $this->_cid); } CRM_Core_Session::setStatus(ts('The contacts have been merged.')); $url = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$this->_cid}"); CRM_Utils_System::redirect($url); }
public function postProcess() { $formValues = $this->exportValues(); // reset all selected contact ids from session // when we came from search context, CRM-3526 $session = CRM_Core_Session::singleton(); if ($session->get('selectedSearchContactIds')) { $session->resetScope('selectedSearchContactIds'); } $formValues['main_details'] = $this->_mainDetails; $formValues['other_details'] = $this->_otherDetails; $migrationData = array('migration_info' => $formValues); CRM_Utils_Hook::merge('form', $migrationData, $this->_cid, $this->_oid); CRM_Dedupe_Merger::moveAllBelongings($this->_cid, $this->_oid, $migrationData['migration_info']); $name = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_cid, 'display_name'); $message = '<ul><li>' . ts('%1 has been updated.', array(1 => $name)) . '</li><li>' . ts('Contact ID %1 has been deleted.', array(1 => $this->_oid)) . '</li></ul>'; CRM_Core_Session::setStatus($message, ts('Contacts Merged'), 'success'); $url = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$this->_cid}"); $urlParams = "reset=1&gid={$this->_gid}&rgid={$this->_rgid}&limit={$this->limit}"; if (!empty($formValues['_qf_Merge_submit'])) { $urlParams .= "&action=update"; $lisitingURL = CRM_Utils_System::url('civicrm/contact/dedupefind', $urlParams); CRM_Utils_System::redirect($lisitingURL); } if (!empty($formValues['_qf_Merge_done'])) { CRM_Utils_System::redirect($url); } if ($this->next && $this->_mergeId) { $cacheKey = CRM_Dedupe_Merger::getMergeCacheKeyString($this->_rgid, $this->_gid); $join = CRM_Dedupe_Merger::getJoinOnDedupeTable(); $where = "de.id IS NULL"; $pos = CRM_Core_BAO_PrevNextCache::getPositions($cacheKey, NULL, NULL, $this->_mergeId, $join, $where); if (!empty($pos) && $pos['next']['id1'] && $pos['next']['id2']) { $urlParams .= "&cid={$pos['next']['id1']}&oid={$pos['next']['id2']}&mergeId={$pos['next']['mergeId']}&action=update"; $url = CRM_Utils_System::url('civicrm/contact/merge', $urlParams); } } CRM_Utils_System::redirect($url); }
public function testBatchMergeAllDuplicates() { $this->createDupeContacts(); // verify that all contacts have been created separately $this->assertEquals(count($this->_contactIds), 9, 'Check for number of contacts.'); $dao = new CRM_Dedupe_DAO_RuleGroup(); $dao->contact_type = 'Individual'; $dao->name = 'IndividualSupervised'; $dao->is_default = 1; $dao->find(TRUE); $foundDupes = CRM_Dedupe_Finder::dupesInGroup($dao->id, $this->_groupId); // ------------------------------------------------------------------------- // Name and Email (reserved) Matches ( 3 pairs ) // -------------------------------------------------------------------------- // robin - hood - robin@example.com // robin - hood - robin@example.com // little - dale - dale@example.com // little - dale - dale@example.com // will - dale - will@example.com // will - dale - will@example.com // so 3 pairs for - first + last + mail $this->assertEquals(count($foundDupes), 3, 'Check Individual-Supervised dupe rule for dupesInGroup().'); // Run dedupe finder as the browser would $_SERVER['REQUEST_METHOD'] = 'GET'; //avoid invalid key error $object = new CRM_Contact_Page_DedupeFind(); $object->set('gid', $this->_groupId); $object->set('rgid', $dao->id); $object->set('action', CRM_Core_Action::UPDATE); @$object->run(); // Retrieve pairs from prev next cache table $select = array('pn.is_selected' => 'is_selected'); $cacheKeyString = "merge Individual_{$dao->id}_{$this->_groupId}"; $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select); $this->assertEquals(count($foundDupes), count($pnDupePairs), 'Check number of dupe pairs in prev next cache.'); // batch merge all dupes $result = CRM_Dedupe_Merger::batchMerge($dao->id, $this->_groupId, 'safe', TRUE, 5, 2); $this->assertEquals(count($result['merged']), 3, 'Check number of merged pairs.'); // retrieve pairs from prev next cache table $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select); $this->assertEquals(count($pnDupePairs), 0, 'Check number of remaining dupe pairs in prev next cache.'); $this->deleteDupeContacts(); }
/** * Based on the provided two contact_ids and a set of tables, move the belongings of the * other contact to the main one - be it Location / CustomFields or Contact .. related info. * A superset of moveContactBelongings() function. * * @param int $mainId * Main contact with whom merge has to happen. * @param int $otherId * Duplicate contact which would be deleted after merge operation. * * @param $migrationInfo * * @return bool */ public static function moveAllBelongings($mainId, $otherId, $migrationInfo) { if (empty($migrationInfo)) { return FALSE; } $qfZeroBug = 'e8cddb72-a257-11dc-b9cc-0016d3330ee9'; $relTables = CRM_Dedupe_Merger::relTables(); $moveTables = $locBlocks = $tableOperations = array(); foreach ($migrationInfo as $key => $value) { if ($value == $qfZeroBug) { $value = '0'; } if ((in_array(substr($key, 5), CRM_Dedupe_Merger::getContactFields()) || substr($key, 0, 12) == 'move_custom_') && $value != NULL) { $submitted[substr($key, 5)] = $value; } elseif (substr($key, 0, 14) == 'move_location_' and $value != NULL) { $locField = explode('_', $key); $fieldName = $locField[2]; $fieldCount = $locField[3]; $operation = CRM_Utils_Array::value('operation', $migrationInfo['location'][$fieldName][$fieldCount]); // default operation is overwrite. if (!$operation) { $operation = 2; } $locBlocks[$fieldName][$fieldCount]['operation'] = $operation; $locBlocks[$fieldName][$fieldCount]['locTypeId'] = CRM_Utils_Array::value('locTypeId', $migrationInfo['location'][$fieldName][$fieldCount]); } elseif (substr($key, 0, 15) == 'move_rel_table_' and $value == '1') { $moveTables = array_merge($moveTables, $relTables[substr($key, 5)]['tables']); if (array_key_exists('operation', $migrationInfo)) { foreach ($relTables[substr($key, 5)]['tables'] as $table) { if (array_key_exists($key, $migrationInfo['operation'])) { $tableOperations[$table] = $migrationInfo['operation'][$key]; } } } } } // **** Do location related migration: if (!empty($locBlocks)) { $locComponent = array('email' => 'Email', 'phone' => 'Phone', 'im' => 'IM', 'openid' => 'OpenID', 'address' => 'Address'); $primaryBlockIds = CRM_Contact_BAO_Contact::getLocBlockIds($mainId, array('is_primary' => 1)); $billingBlockIds = CRM_Contact_BAO_Contact::getLocBlockIds($mainId, array('is_billing' => 1)); foreach ($locBlocks as $name => $block) { if (!is_array($block) || CRM_Utils_System::isNull($block)) { continue; } $daoName = 'CRM_Core_DAO_' . $locComponent[$name]; $primaryDAOId = array_key_exists($name, $primaryBlockIds) ? array_pop($primaryBlockIds[$name]) : NULL; $billingDAOId = array_key_exists($name, $billingBlockIds) ? array_pop($billingBlockIds[$name]) : NULL; foreach ($block as $blkCount => $values) { $locTypeId = CRM_Utils_Array::value('locTypeId', $values, 1); $operation = CRM_Utils_Array::value('operation', $values, 2); $otherBlockId = CRM_Utils_Array::value($blkCount, $migrationInfo['other_details']['loc_block_ids'][$name]); // keep 1-1 mapping for address - loc type. $idKey = $blkCount; if (array_key_exists($name, $locComponent)) { $idKey = $locTypeId; } if (isset($migrationInfo['main_details']['loc_block_ids'][$name])) { $mainBlockId = CRM_Utils_Array::value($idKey, $migrationInfo['main_details']['loc_block_ids'][$name]); } if (!$otherBlockId) { continue; } // for the block which belongs to other-contact, link the contact to main-contact $otherBlockDAO = new $daoName(); $otherBlockDAO->id = $otherBlockId; $otherBlockDAO->contact_id = $mainId; $otherBlockDAO->location_type_id = $locTypeId; // if main contact already has primary & billing, set the flags to 0. if ($primaryDAOId) { $otherBlockDAO->is_primary = 0; } if ($billingDAOId) { $otherBlockDAO->is_billing = 0; } // overwrite - need to delete block which belongs to main-contact. if (isset($mainBlockId) && $mainBlockId && $operation == 2) { $deleteDAO = new $daoName(); $deleteDAO->id = $mainBlockId; $deleteDAO->find(TRUE); // if we about to delete a primary / billing block, set the flags for new block // that we going to assign to main-contact if ($primaryDAOId && $primaryDAOId == $deleteDAO->id) { $otherBlockDAO->is_primary = 1; } if ($billingDAOId && $billingDAOId == $deleteDAO->id) { $otherBlockDAO->is_billing = 1; } $deleteDAO->delete(); $deleteDAO->free(); } $otherBlockDAO->update(); $otherBlockDAO->free(); } } } // **** Do tables related migrations if (!empty($moveTables)) { CRM_Dedupe_Merger::moveContactBelongings($mainId, $otherId, $moveTables, $tableOperations); unset($moveTables, $tableOperations); } // **** Do contact related migrations CRM_Dedupe_Merger::moveContactBelongings($mainId, $otherId); // FIXME: fix gender, prefix and postfix, so they're edible by createProfileContact() $names['gender'] = array('newName' => 'gender_id', 'groupName' => 'gender'); $names['individual_prefix'] = array('newName' => 'prefix_id', 'groupName' => 'individual_prefix'); $names['individual_suffix'] = array('newName' => 'suffix_id', 'groupName' => 'individual_suffix'); $names['communication_style'] = array('newName' => 'communication_style_id', 'groupName' => 'communication_style'); $names['addressee'] = array('newName' => 'addressee_id', 'groupName' => 'addressee'); $names['email_greeting'] = array('newName' => 'email_greeting_id', 'groupName' => 'email_greeting'); $names['postal_greeting'] = array('newName' => 'postal_greeting_id', 'groupName' => 'postal_greeting'); CRM_Core_OptionGroup::lookupValues($submitted, $names, TRUE); // fix custom fields so they're edible by createProfileContact() static $treeCache = array(); if (!array_key_exists($migrationInfo['main_details']['contact_type'], $treeCache)) { $treeCache[$migrationInfo['main_details']['contact_type']] = CRM_Core_BAO_CustomGroup::getTree($migrationInfo['main_details']['contact_type'], CRM_Core_DAO::$_nullObject, NULL, -1); } $cgTree =& $treeCache[$migrationInfo['main_details']['contact_type']]; $cFields = array(); foreach ($cgTree as $key => $group) { if (!isset($group['fields'])) { continue; } foreach ($group['fields'] as $fid => $field) { $cFields[$fid]['attributes'] = $field; } } if (!isset($submitted)) { $submitted = array(); } foreach ($submitted as $key => $value) { if (substr($key, 0, 7) == 'custom_') { $fid = (int) substr($key, 7); if (empty($cFields[$fid])) { continue; } $htmlType = $cFields[$fid]['attributes']['html_type']; switch ($htmlType) { case 'File': $customFiles[] = $fid; unset($submitted["custom_{$fid}"]); break; case 'Select Country': case 'Select State/Province': $submitted[$key] = CRM_Core_BAO_CustomField::getDisplayValue($value, $fid, $cFields); break; case 'CheckBox': case 'AdvMulti-Select': case 'Multi-Select': case 'Multi-Select Country': case 'Multi-Select State/Province': // Merge values from both contacts for multivalue fields, CRM-4385 // get the existing custom values from db. $customParams = array('entityID' => $mainId, $key => TRUE); $customfieldValues = CRM_Core_BAO_CustomValueTable::getValues($customParams); if (!empty($customfieldValues[$key])) { $existingValue = explode(CRM_Core_DAO::VALUE_SEPARATOR, $customfieldValues[$key]); if (is_array($existingValue) && !empty($existingValue)) { $mergeValue = $submmtedCustomValue = array(); if ($value) { $submmtedCustomValue = explode(CRM_Core_DAO::VALUE_SEPARATOR, $value); } //hack to remove null and duplicate values from array. foreach (array_merge($submmtedCustomValue, $existingValue) as $k => $v) { if ($v != '' && !in_array($v, $mergeValue)) { $mergeValue[] = $v; } } //keep state and country as array format. //for checkbox and m-select format w/ VALUE_SEPARATOR if (in_array($htmlType, array('CheckBox', 'Multi-Select', 'AdvMulti-Select'))) { $submitted[$key] = CRM_Core_DAO::VALUE_SEPARATOR . implode(CRM_Core_DAO::VALUE_SEPARATOR, $mergeValue) . CRM_Core_DAO::VALUE_SEPARATOR; } else { $submitted[$key] = $mergeValue; } } } elseif (in_array($htmlType, array('Multi-Select Country', 'Multi-Select State/Province'))) { //we require submitted values should be in array format if ($value) { $mergeValueArray = explode(CRM_Core_DAO::VALUE_SEPARATOR, $value); //hack to remove null values from array. $mergeValue = array(); foreach ($mergeValueArray as $k => $v) { if ($v != '') { $mergeValue[] = $v; } } $submitted[$key] = $mergeValue; } } break; default: break; } } } // **** Do file custom fields related migrations // FIXME: move this someplace else (one of the BAOs) after discussing // where to, and whether CRM_Core_BAO_File::deleteFileReferences() shouldn't actually, // like, delete a file... if (!isset($customFiles)) { $customFiles = array(); } foreach ($customFiles as $customId) { list($tableName, $columnName, $groupID) = CRM_Core_BAO_CustomField::getTableColumnGroup($customId); // get the contact_id -> file_id mapping $fileIds = array(); $sql = "SELECT entity_id, {$columnName} AS file_id FROM {$tableName} WHERE entity_id IN ({$mainId}, {$otherId})"; $dao = CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray); while ($dao->fetch()) { $fileIds[$dao->entity_id] = $dao->file_id; } $dao->free(); // delete the main contact's file if (!empty($fileIds[$mainId])) { CRM_Core_BAO_File::deleteFileReferences($fileIds[$mainId], $mainId, $customId); } // move the other contact's file to main contact //NYSS need to INSERT or UPDATE depending on whether main contact has an existing record if (CRM_Core_DAO::singleValueQuery("SELECT id FROM {$tableName} WHERE entity_id = {$mainId}")) { $sql = "UPDATE {$tableName} SET {$columnName} = {$fileIds[$otherId]} WHERE entity_id = {$mainId}"; } else { $sql = "INSERT INTO {$tableName} ( entity_id, {$columnName} ) VALUES ( {$mainId}, {$fileIds[$otherId]} )"; } CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray); if (CRM_Core_DAO::singleValueQuery("\n SELECT id\n FROM civicrm_entity_file\n WHERE entity_table = '{$tableName}' AND file_id = {$fileIds[$otherId]}")) { $sql = "\n UPDATE civicrm_entity_file\n SET entity_id = {$mainId}\n WHERE entity_table = '{$tableName}' AND file_id = {$fileIds[$otherId]}"; } else { $sql = "\n INSERT INTO civicrm_entity_file ( entity_table, entity_id, file_id )\n VALUES ( '{$tableName}', {$mainId}, {$fileIds[$otherId]} )"; } CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray); } // move view only custom fields CRM-5362 $viewOnlyCustomFields = array(); foreach ($submitted as $key => $value) { $fid = (int) substr($key, 7); if (array_key_exists($fid, $cFields) && !empty($cFields[$fid]['attributes']['is_view'])) { $viewOnlyCustomFields[$key] = $value; } } // special case to set values for view only, CRM-5362 if (!empty($viewOnlyCustomFields)) { $viewOnlyCustomFields['entityID'] = $mainId; CRM_Core_BAO_CustomValueTable::setValues($viewOnlyCustomFields); } // **** Delete other contact & update prev-next caching $otherParams = array('contact_id' => $otherId, 'id' => $otherId, 'version' => 3); if (CRM_Core_Permission::check('merge duplicate contacts') && CRM_Core_Permission::check('delete contacts')) { // if ext id is submitted then set it null for contact to be deleted if (!empty($submitted['external_identifier'])) { $query = "UPDATE civicrm_contact SET external_identifier = null WHERE id = {$otherId}"; CRM_Core_DAO::executeQuery($query); } civicrm_api('contact', 'delete', $otherParams); CRM_Core_BAO_PrevNextCache::deleteItem($otherId); } // FIXME: else part /* else { */ /* CRM_Core_Session::setStatus( ts('Do not have sufficient permission to delete duplicate contact.') ); */ /* } */ // CRM-15681 merge sub_types if ($other_sub_types = CRM_Utils_array::value('contact_sub_type', $migrationInfo['other_details'])) { if ($main_sub_types = CRM_Utils_array::value('contact_sub_type', $migrationInfo['main_details'])) { $submitted['contact_sub_type'] = array_unique(array_merge($main_sub_types, $other_sub_types)); } else { $submitted['contact_sub_type'] = $other_sub_types; } } // **** Update contact related info for the main contact if (!empty($submitted)) { $submitted['contact_id'] = $mainId; //update current employer field if ($currentEmloyerId = CRM_Utils_Array::value('current_employer_id', $submitted)) { if (!CRM_Utils_System::isNull($currentEmloyerId)) { $submitted['current_employer'] = $submitted['current_employer_id']; } else { $submitted['current_employer'] = ''; } unset($submitted['current_employer_id']); } //CRM-14312 include prefix/suffix from mainId if not overridden for proper construction of display/sort name if (!isset($submitted['prefix_id']) && !empty($migrationInfo['main_details']['prefix_id'])) { $submitted['prefix_id'] = $migrationInfo['main_details']['prefix_id']; } if (!isset($submitted['suffix_id']) && !empty($migrationInfo['main_details']['suffix_id'])) { $submitted['suffix_id'] = $migrationInfo['main_details']['suffix_id']; } CRM_Contact_BAO_Contact::createProfileContact($submitted, CRM_Core_DAO::$_nullArray, $mainId); unset($submitted); } CRM_Utils_Hook::post('merge', 'Contact', $mainId, CRM_Core_DAO::$_nullObject); return TRUE; }
/** * Merges given pair of duplicate contacts. * * @param array $params * Input parameters. * * @return array * API Result Array */ function civicrm_api3_job_process_batch_merge($params) { $rgid = CRM_Utils_Array::value('rgid', $params); $gid = CRM_Utils_Array::value('gid', $params); $mode = CRM_Utils_Array::value('mode', $params, 'safe'); $autoFlip = CRM_Utils_Array::value('auto_flip', $params, TRUE); $result = CRM_Dedupe_Merger::batchMerge($rgid, $gid, $mode, $autoFlip); if ($result['is_error'] == 0) { return civicrm_api3_create_success(); } else { return civicrm_api3_create_error($result['messages']); } }
/** * Test hook allowing modification of the data calculated for merging locations. * * We are testing a nuanced real life situation where the address data of the * most recent donor gets priority - resulting in the primary address being set * to the primary address of the most recent donor and address data on a per * location type basis also being set to the most recent donor. Hook also excludes * a fully matching address with a different location. * * This has been added to the test suite to ensure the code supports more this * type of intervention. * * @param array $blocksDAO * Array of location DAO to be saved. These are arrays in 2 keys 'update' & 'delete'. * @param int $mainId * Contact_id of the contact that survives the merge. * @param int $otherId * Contact_id of the contact that will be absorbed and deleted. * @param array $migrationInfo * Calculated migration info, informational only. * * @return mixed */ public function hookMostRecentDonor(&$blocksDAO, $mainId, $otherId, $migrationInfo) { $lastDonorID = $this->callAPISuccessGetValue('Contribution', array('return' => 'contact_id', 'contact_id' => array('IN' => array($mainId, $otherId)), 'options' => array('sort' => 'receive_date DESC', 'limit' => 1))); // Since the last donor is not the main ID we are prioritising info from the last donor. // In the test this should always be true - but keep the check in case // something changes that we need to detect. if ($lastDonorID != $mainId) { foreach ($migrationInfo['other_details']['location_blocks'] as $blockType => $blocks) { foreach ($blocks as $block) { if ($block['is_primary']) { $primaryAddressID = $block['id']; if (!empty($migrationInfo['main_details']['location_blocks'][$blockType])) { foreach ($migrationInfo['main_details']['location_blocks'][$blockType] as $mainBlock) { if (empty($blocksDAO[$blockType]['update'][$block['id']]) && $mainBlock['location_type_id'] == $block['location_type_id']) { // This was an address match - we just need to check the is_primary // is true on the matching kept address. $primaryAddressID = $mainBlock['id']; $blocksDAO[$blockType]['update'][$primaryAddressID] = _civicrm_api3_load_DAO($blockType); $blocksDAO[$blockType]['update'][$primaryAddressID]->id = $primaryAddressID; } $mainLocationTypeID = $mainBlock['location_type_id']; // We also want to be more ruthless about removing matching addresses. unset($mainBlock['location_type_id']); if (CRM_Dedupe_Merger::locationIsSame($block, $mainBlock) && (!isset($blocksDAO[$blockType]['update']) || !isset($blocksDAO[$blockType]['update'][$mainBlock['id']])) && (!isset($blocksDAO[$blockType]['delete']) || !isset($blocksDAO[$blockType]['delete'][$mainBlock['id']]))) { $blocksDAO[$blockType]['delete'][$mainBlock['id']] = _civicrm_api3_load_DAO($blockType); $blocksDAO[$blockType]['delete'][$mainBlock['id']]->id = $mainBlock['id']; } elseif ($mainBlock['is_primary'] && $mainLocationTypeID != $block['location_type_id']) { $blocksDAO['address']['update'][$mainBlock['id']] = _civicrm_api3_load_DAO($blockType); $blocksDAO['address']['update'][$mainBlock['id']]->is_primary = 0; $blocksDAO['address']['update'][$mainBlock['id']]->id = $mainBlock['id']; } } $blocksDAO[$blockType]['update'][$primaryAddressID]->is_primary = 1; } } } } } }
/** * Get all the log tables that reference civicrm_contact. * * Note that it might make sense to wrap this in a getLogTablesForEntity * but this is the only entity currently available... */ public function getLogTablesForContact() { $tables = array_keys(CRM_Dedupe_Merger::cidRefs()); return array_intersect($tables, $this->tables); }
/** * Mark dupe pairs as selected from un-selected state or vice-versa, in dupe cache table. */ public static function toggleDedupeSelect() { $rgid = CRM_Utils_Type::escape($_REQUEST['rgid'], 'Integer'); $gid = CRM_Utils_Type::escape($_REQUEST['gid'], 'Integer'); $pnid = $_REQUEST['pnid']; $isSelected = CRM_Utils_Type::escape($_REQUEST['is_selected'], 'Boolean'); $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($rgid, $gid); $params = array(1 => array($isSelected, 'Boolean'), 3 => array("{$cacheKeyString}%", 'String')); //check pnid is_array or integer $whereClause = NULL; if (is_array($pnid) && !CRM_Utils_Array::crmIsEmptyArray($pnid)) { CRM_Utils_Type::escapeAll($pnid, 'Positive'); $pnid = implode(', ', $pnid); $whereClause = " id IN ( {$pnid} ) "; } else { $pnid = CRM_Utils_Type::escape($pnid, 'Integer'); $whereClause = " id = %2"; $params[2] = array($pnid, 'Integer'); } $sql = "UPDATE civicrm_prevnext_cache SET is_selected = %1 WHERE {$whereClause} AND cacheKey LIKE %3"; CRM_Core_DAO::executeQuery($sql, $params); CRM_Utils_System::civiExit(); }
/** * Based on the provided two contact_ids and a set of tables, move the belongings of the * other contact to the main one - be it Location / CustomFields or Contact .. related info. * A superset of moveContactBelongings() function. * * @param int $mainId * Main contact with whom merge has to happen. * @param int $otherId * Duplicate contact which would be deleted after merge operation. * * @param $migrationInfo * * @param bool $checkPermissions * Respect logged in user permissions. * * @return bool */ public static function moveAllBelongings($mainId, $otherId, $migrationInfo, $checkPermissions = TRUE) { if (empty($migrationInfo)) { return FALSE; } $qfZeroBug = 'e8cddb72-a257-11dc-b9cc-0016d3330ee9'; $relTables = CRM_Dedupe_Merger::relTables(); $moveTables = $locationMigrationInfo = $tableOperations = array(); foreach ($migrationInfo as $key => $value) { if ($value == $qfZeroBug) { $value = '0'; } if ((in_array(substr($key, 5), CRM_Dedupe_Merger::getContactFields()) || substr($key, 0, 12) == 'move_custom_') && $value != NULL) { $submitted[substr($key, 5)] = $value; } elseif (substr($key, 0, 14) == 'move_location_' and $value != NULL) { $locationMigrationInfo[$key] = $value; } elseif (substr($key, 0, 15) == 'move_rel_table_' and $value == '1') { $moveTables = array_merge($moveTables, $relTables[substr($key, 5)]['tables']); if (array_key_exists('operation', $migrationInfo)) { foreach ($relTables[substr($key, 5)]['tables'] as $table) { if (array_key_exists($key, $migrationInfo['operation'])) { $tableOperations[$table] = $migrationInfo['operation'][$key]; } } } } } self::mergeLocations($mainId, $otherId, $locationMigrationInfo, $migrationInfo); // **** Do tables related migrations if (!empty($moveTables)) { CRM_Dedupe_Merger::moveContactBelongings($mainId, $otherId, $moveTables, $tableOperations); unset($moveTables, $tableOperations); } // **** Do contact related migrations CRM_Dedupe_Merger::moveContactBelongings($mainId, $otherId); // FIXME: fix gender, prefix and postfix, so they're edible by createProfileContact() $names['gender'] = array('newName' => 'gender_id', 'groupName' => 'gender'); $names['individual_prefix'] = array('newName' => 'prefix_id', 'groupName' => 'individual_prefix'); $names['individual_suffix'] = array('newName' => 'suffix_id', 'groupName' => 'individual_suffix'); $names['communication_style'] = array('newName' => 'communication_style_id', 'groupName' => 'communication_style'); $names['addressee'] = array('newName' => 'addressee_id', 'groupName' => 'addressee'); $names['email_greeting'] = array('newName' => 'email_greeting_id', 'groupName' => 'email_greeting'); $names['postal_greeting'] = array('newName' => 'postal_greeting_id', 'groupName' => 'postal_greeting'); CRM_Core_OptionGroup::lookupValues($submitted, $names, TRUE); // fix custom fields so they're edible by createProfileContact() static $treeCache = array(); if (!array_key_exists($migrationInfo['main_details']['contact_type'], $treeCache)) { $treeCache[$migrationInfo['main_details']['contact_type']] = CRM_Core_BAO_CustomGroup::getTree($migrationInfo['main_details']['contact_type'], CRM_Core_DAO::$_nullObject, NULL, -1); } $cgTree =& $treeCache[$migrationInfo['main_details']['contact_type']]; $cFields = array(); foreach ($cgTree as $key => $group) { if (!isset($group['fields'])) { continue; } foreach ($group['fields'] as $fid => $field) { $cFields[$fid]['attributes'] = $field; } } if (!isset($submitted)) { $submitted = array(); } foreach ($submitted as $key => $value) { if (substr($key, 0, 7) == 'custom_') { $fid = (int) substr($key, 7); if (empty($cFields[$fid])) { continue; } $htmlType = $cFields[$fid]['attributes']['html_type']; switch ($htmlType) { case 'File': $customFiles[] = $fid; unset($submitted["custom_{$fid}"]); break; case 'Select Country': case 'Select State/Province': $submitted[$key] = CRM_Core_BAO_CustomField::displayValue($value, $fid); break; case 'Select Date': if ($cFields[$fid]['attributes']['is_view']) { $submitted[$key] = date('YmdHis', strtotime($submitted[$key])); } break; case 'CheckBox': case 'AdvMulti-Select': case 'Multi-Select': case 'Multi-Select Country': case 'Multi-Select State/Province': // Merge values from both contacts for multivalue fields, CRM-4385 // get the existing custom values from db. $customParams = array('entityID' => $mainId, $key => TRUE); $customfieldValues = CRM_Core_BAO_CustomValueTable::getValues($customParams); if (!empty($customfieldValues[$key])) { $existingValue = explode(CRM_Core_DAO::VALUE_SEPARATOR, $customfieldValues[$key]); if (is_array($existingValue) && !empty($existingValue)) { $mergeValue = $submmtedCustomValue = array(); if ($value == 'null') { // CRM-19074 if someone has deliberately chosen to overwrite with 'null', respect it. $submitted[$key] = $value; } else { if ($value) { $submmtedCustomValue = explode(CRM_Core_DAO::VALUE_SEPARATOR, $value); } //hack to remove null and duplicate values from array. foreach (array_merge($submmtedCustomValue, $existingValue) as $k => $v) { if ($v != '' && !in_array($v, $mergeValue)) { $mergeValue[] = $v; } } //keep state and country as array format. //for checkbox and m-select format w/ VALUE_SEPARATOR if (in_array($htmlType, array('CheckBox', 'Multi-Select', 'AdvMulti-Select'))) { $submitted[$key] = CRM_Core_DAO::VALUE_SEPARATOR . implode(CRM_Core_DAO::VALUE_SEPARATOR, $mergeValue) . CRM_Core_DAO::VALUE_SEPARATOR; } else { $submitted[$key] = $mergeValue; } } } } elseif (in_array($htmlType, array('Multi-Select Country', 'Multi-Select State/Province'))) { //we require submitted values should be in array format if ($value) { $mergeValueArray = explode(CRM_Core_DAO::VALUE_SEPARATOR, $value); //hack to remove null values from array. $mergeValue = array(); foreach ($mergeValueArray as $k => $v) { if ($v != '') { $mergeValue[] = $v; } } $submitted[$key] = $mergeValue; } } break; default: break; } } } // **** Do file custom fields related migrations // FIXME: move this someplace else (one of the BAOs) after discussing // where to, and whether CRM_Core_BAO_File::deleteFileReferences() shouldn't actually, // like, delete a file... if (!isset($customFiles)) { $customFiles = array(); } foreach ($customFiles as $customId) { list($tableName, $columnName, $groupID) = CRM_Core_BAO_CustomField::getTableColumnGroup($customId); // get the contact_id -> file_id mapping $fileIds = array(); $sql = "SELECT entity_id, {$columnName} AS file_id FROM {$tableName} WHERE entity_id IN ({$mainId}, {$otherId})"; $dao = CRM_Core_DAO::executeQuery($sql); while ($dao->fetch()) { $fileIds[$dao->entity_id] = $dao->file_id; } $dao->free(); // delete the main contact's file if (!empty($fileIds[$mainId])) { CRM_Core_BAO_File::deleteFileReferences($fileIds[$mainId], $mainId, $customId); } // move the other contact's file to main contact //NYSS need to INSERT or UPDATE depending on whether main contact has an existing record if (CRM_Core_DAO::singleValueQuery("SELECT id FROM {$tableName} WHERE entity_id = {$mainId}")) { $sql = "UPDATE {$tableName} SET {$columnName} = {$fileIds[$otherId]} WHERE entity_id = {$mainId}"; } else { $sql = "INSERT INTO {$tableName} ( entity_id, {$columnName} ) VALUES ( {$mainId}, {$fileIds[$otherId]} )"; } CRM_Core_DAO::executeQuery($sql); if (CRM_Core_DAO::singleValueQuery("\n SELECT id\n FROM civicrm_entity_file\n WHERE entity_table = '{$tableName}' AND file_id = {$fileIds[$otherId]}")) { $sql = "\n UPDATE civicrm_entity_file\n SET entity_id = {$mainId}\n WHERE entity_table = '{$tableName}' AND file_id = {$fileIds[$otherId]}"; } else { $sql = "\n INSERT INTO civicrm_entity_file ( entity_table, entity_id, file_id )\n VALUES ( '{$tableName}', {$mainId}, {$fileIds[$otherId]} )"; } CRM_Core_DAO::executeQuery($sql); } // move view only custom fields CRM-5362 $viewOnlyCustomFields = array(); foreach ($submitted as $key => $value) { $fid = (int) substr($key, 7); if (array_key_exists($fid, $cFields) && !empty($cFields[$fid]['attributes']['is_view'])) { $viewOnlyCustomFields[$key] = $value; } } // special case to set values for view only, CRM-5362 if (!empty($viewOnlyCustomFields)) { $viewOnlyCustomFields['entityID'] = $mainId; CRM_Core_BAO_CustomValueTable::setValues($viewOnlyCustomFields); } if (!$checkPermissions || CRM_Core_Permission::check('merge duplicate contacts') && CRM_Core_Permission::check('delete contacts')) { // if ext id is submitted then set it null for contact to be deleted if (!empty($submitted['external_identifier'])) { $query = "UPDATE civicrm_contact SET external_identifier = null WHERE id = {$otherId}"; CRM_Core_DAO::executeQuery($query); } civicrm_api3('contact', 'delete', array('id' => $otherId)); } // CRM-15681 merge sub_types if ($other_sub_types = CRM_Utils_Array::value('contact_sub_type', $migrationInfo['other_details'])) { if ($main_sub_types = CRM_Utils_Array::value('contact_sub_type', $migrationInfo['main_details'])) { $submitted['contact_sub_type'] = array_unique(array_merge($main_sub_types, $other_sub_types)); } else { $submitted['contact_sub_type'] = $other_sub_types; } } // **** Update contact related info for the main contact if (!empty($submitted)) { $submitted['contact_id'] = $mainId; //update current employer field if ($currentEmloyerId = CRM_Utils_Array::value('current_employer_id', $submitted)) { if (!CRM_Utils_System::isNull($currentEmloyerId)) { $submitted['current_employer'] = $submitted['current_employer_id']; } else { $submitted['current_employer'] = ''; } unset($submitted['current_employer_id']); } //CRM-14312 include prefix/suffix from mainId if not overridden for proper construction of display/sort name if (!isset($submitted['prefix_id']) && !empty($migrationInfo['main_details']['prefix_id'])) { $submitted['prefix_id'] = $migrationInfo['main_details']['prefix_id']; } if (!isset($submitted['suffix_id']) && !empty($migrationInfo['main_details']['suffix_id'])) { $submitted['suffix_id'] = $migrationInfo['main_details']['suffix_id']; } CRM_Contact_BAO_Contact::createProfileContact($submitted, CRM_Core_DAO::$_nullArray, $mainId); } CRM_Utils_Hook::post('merge', 'Contact', $mainId, CRM_Core_DAO::$_nullObject); self::createMergeActivities($mainId, $otherId); return TRUE; }