Ejemplo n.º 1
0
 /**
  * A function which uses various rules / algorithms for choosing which contact to bias to
  * when there's a conflict (to handle "gotchas"). Plus the safest route to merge.
  *
  * @param int $mainId
  *   Main contact with whom merge has to happen.
  * @param int $otherId
  *   Duplicate contact which would be deleted after merge operation.
  * @param array $migrationInfo
  *   Array of information about which elements to merge.
  * @param string $mode
  *   Helps decide how to behave when there are conflicts.
  *                                 A 'safe' value skips the merge if there are any un-resolved conflicts.
  *                                 Does a force merge otherwise (aggressive mode).
  *
  * @return bool
  */
 public static function skipMerge($mainId, $otherId, &$migrationInfo, $mode = 'safe')
 {
     $conflicts = array();
     $migrationData = array('old_migration_info' => $migrationInfo, 'mode' => $mode);
     $allLocationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
     foreach ($migrationInfo as $key => $val) {
         if ($val === "null") {
             // Rule: no overwriting with empty values in any mode
             unset($migrationInfo[$key]);
             continue;
         } elseif ((in_array(substr($key, 5), CRM_Dedupe_Merger::getContactFields()) or substr($key, 0, 12) == 'move_custom_') and $val != NULL) {
             // Rule: if both main-contact has other-contact, let $mode decide if to merge a
             // particular field or not
             if (!empty($migrationInfo['rows'][$key]['main'])) {
                 // if main also has a value its a conflict
                 if ($mode == 'safe') {
                     // note it down & lets wait for response from the hook.
                     // For no response skip this merge
                     $conflicts[$key] = NULL;
                 } elseif ($mode == 'aggressive') {
                     // let the main-field be overwritten
                     continue;
                 }
             }
         } elseif (substr($key, 0, 14) == 'move_location_' and $val != NULL) {
             $locField = explode('_', $key);
             $fieldName = $locField[2];
             $fieldCount = $locField[3];
             // Rule: resolve address conflict if any -
             if ($fieldName == 'address') {
                 $mainNewLocTypeId = $migrationInfo['location'][$fieldName][$fieldCount]['locTypeId'];
                 if (!empty($migrationInfo['main_loc_block']) && array_key_exists("main_address{$mainNewLocTypeId}", $migrationInfo['main_loc_block'])) {
                     // main loc already has some address for the loc-type. Its a overwrite situation.
                     // look for next available loc-type
                     $newTypeId = NULL;
                     foreach ($allLocationTypes as $typeId => $typeLabel) {
                         if (!array_key_exists("main_address{$typeId}", $migrationInfo['main_loc_block'])) {
                             $newTypeId = $typeId;
                         }
                     }
                     if ($newTypeId) {
                         // try insert address at new available loc-type
                         $migrationInfo['location'][$fieldName][$fieldCount]['locTypeId'] = $newTypeId;
                     } elseif ($mode == 'safe') {
                         // note it down & lets wait for response from the hook.
                         // For no response skip this merge
                         $conflicts[$key] = NULL;
                     } elseif ($mode == 'aggressive') {
                         // let the loc-type-id be same as that of other-contact & go ahead
                         // with merge assuming aggressive mode
                         continue;
                     }
                 }
             } elseif ($migrationInfo['rows'][$key]['main'] == $migrationInfo['rows'][$key]['other']) {
                 // for loc blocks other than address like email, phone .. if values are same no point in merging
                 // and adding redundant value
                 unset($migrationInfo[$key]);
             }
         }
     }
     // A hook to implement other algorithms for choosing which contact to bias to when
     // there's a conflict (to handle "gotchas"). fields_in_conflict could be modified here
     // merge happens with new values filled in here. For a particular field / row not to be merged
     // field should be unset from fields_in_conflict.
     $migrationData['fields_in_conflict'] = $conflicts;
     CRM_Utils_Hook::merge('batch', $migrationData, $mainId, $otherId);
     $conflicts = $migrationData['fields_in_conflict'];
     if (!empty($conflicts)) {
         foreach ($conflicts as $key => $val) {
             if ($val === NULL and $mode == 'safe') {
                 // un-resolved conflicts still present. Lets skip this merge.
                 return TRUE;
             } else {
                 // copy over the resolved values
                 $migrationInfo[$key] = $val;
             }
         }
     }
     return FALSE;
 }
Ejemplo n.º 2
0
 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);
 }
Ejemplo n.º 3
0
 /**
  * Based on the provided two contact_ids and a set of tables, move the 
  * belongings of the other contact to the main one.
  */
 function moveContactBelongings($mainId, $otherId, $tables = false, $tableOperations = array())
 {
     $cidRefs = self::cidRefs();
     $eidRefs = self::eidRefs();
     $cpTables = self::cpTables();
     $paymentTables = self::paymentTables();
     $affected = array_merge(array_keys($cidRefs), array_keys($eidRefs));
     if ($tables !== false) {
         // if there are specific tables, sanitize the list
         $affected = array_unique(array_intersect($affected, $tables));
     } else {
         // if there aren't any specific tables, don't affect the ones handled by relTables()
         $relTables =& self::relTables();
         $handled = array();
         foreach ($relTables as $params) {
             $handled = array_merge($handled, $params['tables']);
         }
         $affected = array_diff($affected, $handled);
     }
     $mainId = (int) $mainId;
     $otherId = (int) $otherId;
     // use UPDATE IGNORE + DELETE query pair to skip on situations when
     // there's a UNIQUE restriction on ($field, some_other_field) pair
     $sqls = array();
     foreach ($affected as $table) {
         //here we require custom processing.
         if (array_key_exists($table, $cpTables)) {
             $path = CRM_Utils_Array::value('path', $cpTables[$table]);
             $fName = CRM_Utils_Array::value('function', $cpTables[$table]);
             if ($path && $fName) {
                 require_once str_replace('_', DIRECTORY_SEPARATOR, $path) . ".php";
                 eval("{$path}::{$fName}( {$mainId}, null, {$otherId} );");
             }
             continue;
         }
         if (isset($cidRefs[$table])) {
             foreach ($cidRefs[$table] as $field) {
                 // carry related contributions CRM-5359
                 if (in_array($table, $paymentTables)) {
                     $payOprSqls = self::operationSql($mainId, $otherId, $table, $tableOperations, 'payment');
                     $sqls = array_merge($sqls, $payOprSqls);
                     $paymentSqls = self::paymentSql($table, $mainId, $otherId);
                     $sqls = array_merge($sqls, $paymentSqls);
                 }
                 $preOperationSqls = self::operationSql($mainId, $otherId, $table, $tableOperations);
                 $sqls = array_merge($sqls, $preOperationSqls);
                 $sqls[] = "UPDATE IGNORE {$table} SET {$field} = {$mainId} WHERE {$field} = {$otherId}";
                 $sqls[] = "DELETE FROM {$table} WHERE {$field} = {$otherId}";
             }
         }
         if (isset($eidRefs[$table])) {
             foreach ($eidRefs[$table] as $entityTable => $entityId) {
                 $sqls[] = "UPDATE IGNORE {$table} SET {$entityId} = {$mainId} WHERE {$entityId} = {$otherId} AND {$entityTable} = 'civicrm_contact'";
                 $sqls[] = "DELETE FROM {$table} WHERE {$entityId} = {$otherId} AND {$entityTable} = 'civicrm_contact'";
             }
         }
     }
     // CRM-6184: if we’re moving relationships, update civicrm_contact.employer_id
     if (is_array($tables) and in_array('civicrm_relationship', $tables)) {
         $sqls[] = "UPDATE IGNORE civicrm_contact SET employer_id = {$mainId} WHERE employer_id = {$otherId}";
     }
     // Allow hook_civicrm_merge() to add SQL statements for the merge operation.
     CRM_Utils_Hook::merge('sqls', $sqls, $mainId, $otherId, $tables);
     // call the SQL queries in one transaction
     require_once 'CRM/Core/Transaction.php';
     $transaction = new CRM_Core_Transaction();
     foreach ($sqls as $sql) {
         CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray, true, null, true);
     }
     $transaction->commit();
 }
Ejemplo n.º 4
0
 /**
  * Merge two tags: tag B into tag A.
  */
 public function mergeTags($tagAId, $tagBId)
 {
     $queryParams = array(1 => array($tagBId, 'Integer'), 2 => array($tagAId, 'Integer'));
     // re-compute used_for field
     $query = "SELECT id, name, used_for FROM civicrm_tag WHERE id IN (%1, %2)";
     $dao = CRM_Core_DAO::executeQuery($query, $queryParams);
     $tags = array();
     while ($dao->fetch()) {
         $label = $dao->id == $tagAId ? 'tagA' : 'tagB';
         $tags[$label] = $dao->name;
         $tags["{$label}_used_for"] = $dao->used_for ? explode(",", $dao->used_for) : array();
     }
     $usedFor = array_merge($tags["tagA_used_for"], $tags["tagB_used_for"]);
     $usedFor = implode(',', array_unique($usedFor));
     $tags["tagB_used_for"] = explode(",", $usedFor);
     // get all merge queries together
     $sqls = array("UPDATE IGNORE civicrm_entity_tag SET tag_id = %1 WHERE tag_id = %2", "UPDATE civicrm_tag SET used_for = '{$usedFor}' WHERE id = %1", "DELETE FROM civicrm_tag WHERE id = %2", "DELETE et2.* from civicrm_entity_tag et1 INNER JOIN civicrm_entity_tag et2 ON et1.entity_table = et2.entity_table AND et1.entity_id = et2.entity_id AND et1.tag_id = et2.tag_id WHERE et1.id < et2.id", "DELETE FROM civicrm_entity_tag WHERE tag_id = %2");
     $tables = array('civicrm_entity_tag', 'civicrm_tag');
     // Allow hook_civicrm_merge() to add SQL statements for the merge operation AND / OR
     // perform any other actions like logging
     CRM_Utils_Hook::merge('sqls', $sqls, $tagAId, $tagBId, $tables);
     // call the SQL queries in one transaction
     $transaction = new CRM_Core_Transaction();
     foreach ($sqls as $sql) {
         CRM_Core_DAO::executeQuery($sql, $queryParams, TRUE, NULL, TRUE);
     }
     $transaction->commit();
     $tags['status'] = TRUE;
     return $tags;
 }
Ejemplo n.º 5
0
 /**
  * A function which uses various rules / algorithms for choosing which contact to bias to
  * when there's a conflict (to handle "gotchas"). Plus the safest route to merge.
  *
  * @param int $mainId
  *   Main contact with whom merge has to happen.
  * @param int $otherId
  *   Duplicate contact which would be deleted after merge operation.
  * @param array $migrationInfo
  *   Array of information about which elements to merge.
  * @param string $mode
  *   Helps decide how to behave when there are conflicts.
  *   -  A 'safe' value skips the merge if there are any un-resolved conflicts.
  *   -  Does a force merge otherwise (aggressive mode).
  *
  * @param array $conflicts
  *
  * @return bool
  */
 public static function skipMerge($mainId, $otherId, &$migrationInfo, $mode = 'safe', &$conflicts = array())
 {
     $originalMigrationInfo = $migrationInfo;
     foreach ($migrationInfo as $key => $val) {
         if ($val === "null") {
             // Rule: Never overwrite with an empty value (in any mode)
             unset($migrationInfo[$key]);
             continue;
         } elseif ((in_array(substr($key, 5), CRM_Dedupe_Merger::getContactFields()) or substr($key, 0, 12) == 'move_custom_') and $val != NULL) {
             // Rule: If both main-contact, and other-contact have a field with a
             // different value, then let $mode decide if to merge it or not
             if ((!empty($migrationInfo['rows'][$key]['main']) || $migrationInfo['rows'][$key]['main'] === '0' && substr($key, 0, 12) == 'move_custom_') && $migrationInfo['rows'][$key]['main'] != $migrationInfo['rows'][$key]['other']) {
                 // note it down & lets wait for response from the hook.
                 // For no response $mode will decide if to skip this merge
                 $conflicts[$key] = NULL;
             }
         } elseif (substr($key, 0, 14) == 'move_location_' and $val != NULL) {
             $locField = explode('_', $key);
             $fieldName = $locField[2];
             $fieldCount = $locField[3];
             // Rule: Catch address conflicts (same address type on both contacts)
             if (isset($migrationInfo['main_details']['location_blocks'][$fieldName]) && !empty($migrationInfo['main_details']['location_blocks'][$fieldName])) {
                 // Load the address we're inspecting from the 'other' contact
                 $addressRecord = $migrationInfo['other_details']['location_blocks'][$fieldName][$fieldCount];
                 $addressRecordLocTypeId = CRM_Utils_Array::value('location_type_id', $addressRecord);
                 // If it exists on the 'main' contact already, skip it. Otherwise
                 // if the location type exists already, log a conflict.
                 foreach ($migrationInfo['main_details']['location_blocks'][$fieldName] as $mainAddressKey => $mainAddressRecord) {
                     if (self::locationIsSame($addressRecord, $mainAddressRecord)) {
                         unset($migrationInfo[$key]);
                         break;
                     } elseif ($addressRecordLocTypeId == $mainAddressRecord['location_type_id']) {
                         $conflicts[$key] = NULL;
                         break;
                     }
                 }
             } elseif (CRM_Utils_Array::value('main', $migrationInfo['rows'][$key]) == $migrationInfo['rows'][$key]['other']) {
                 unset($migrationInfo[$key]);
             }
         }
     }
     // A hook to implement other algorithms for choosing which contact to bias to when
     // there's a conflict (to handle "gotchas"). fields_in_conflict could be modified here
     // merge happens with new values filled in here. For a particular field / row not to be merged
     // field should be unset from fields_in_conflict.
     $migrationData = array('old_migration_info' => $originalMigrationInfo, 'mode' => $mode, 'fields_in_conflict' => $conflicts, 'merge_mode' => $mode, 'migration_info' => $migrationInfo);
     CRM_Utils_Hook::merge('batch', $migrationData, $mainId, $otherId);
     $conflicts = $migrationData['fields_in_conflict'];
     // allow hook to override / manipulate migrationInfo as well
     $migrationInfo = $migrationData['migration_info'];
     if (!empty($conflicts)) {
         foreach ($conflicts as $key => $val) {
             if ($val === NULL and $mode == 'safe') {
                 // un-resolved conflicts still present. Lets skip this merge after saving the conflict / reason.
                 return TRUE;
             } else {
                 // copy over the resolved values
                 $migrationInfo[$key] = $val;
             }
         }
         // if there are conflicts and mode is aggressive, allow hooks to decide if to skip merges
         if (array_key_exists('skip_merge', $migrationData)) {
             return (bool) $migrationData['skip_merge'];
         }
     }
     return FALSE;
 }