X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=CRM%2FDedupe%2FMerger.php;h=fb6a4eee8b0695111121fc2a6139533bc9551c89;hb=1fe557c22468db643ba14ead3a0e35ba86e8385e;hp=0bc09a6f4d19dcfedaf2e5f2c3dce3228f82a871;hpb=abb1959e9b161b33ab97b53154c600658b3f1aa7;p=civicrm-core.git diff --git a/CRM/Dedupe/Merger.php b/CRM/Dedupe/Merger.php index 0bc09a6f4d..fb6a4eee8b 100644 --- a/CRM/Dedupe/Merger.php +++ b/CRM/Dedupe/Merger.php @@ -593,21 +593,24 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m * @param array $criteria * Criteria to use in the filter. * + * @param bool $checkPermissions + * Respect logged in user permissions. + * * @return array|bool */ - public static function batchMerge($rgid, $gid = NULL, $mode = 'safe', $autoFlip = TRUE, $batchLimit = 1, $isSelected = 2, $criteria = array()) { + public static function batchMerge($rgid, $gid = NULL, $mode = 'safe', $autoFlip = TRUE, $batchLimit = 1, $isSelected = 2, $criteria = array(), $checkPermissions = TRUE) { $redirectForPerformance = ($batchLimit > 1) ? TRUE : FALSE; $reloadCacheIfEmpty = (!$redirectForPerformance && $isSelected == 2); - $dupePairs = self::getDuplicatePairs($rgid, $gid, $reloadCacheIfEmpty, $batchLimit, $isSelected, '', ($mode == 'aggressive'), $criteria); + $dupePairs = self::getDuplicatePairs($rgid, $gid, $reloadCacheIfEmpty, $batchLimit, $isSelected, '', ($mode == 'aggressive'), $criteria, $checkPermissions); $cacheParams = array( - 'cache_key_string' => self::getMergeCacheKeyString($rgid, $gid, $criteria), + 'cache_key_string' => self::getMergeCacheKeyString($rgid, $gid, $criteria, $checkPermissions), // @todo stop passing these parameters in & instead calculate them in the merge function based // on the 'real' params like $isRespectExclusions $batchLimit and $isSelected. 'join' => self::getJoinOnDedupeTable(), 'where' => self::getWhereString($batchLimit, $isSelected), ); - return CRM_Dedupe_Merger::merge($dupePairs, $cacheParams, $mode, $autoFlip, $redirectForPerformance); + return CRM_Dedupe_Merger::merge($dupePairs, $cacheParams, $mode, $autoFlip, $redirectForPerformance, $checkPermissions); } /** @@ -679,8 +682,13 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m CRM_Core_BAO_PrevNextCache::setItem($values); } + /** + * Delete information about merges for the given string. + * + * @param $cacheKeyString + */ public static function resetMergeStats($cacheKeyString) { - return CRM_Core_BAO_PrevNextCache::deleteItem(NULL, "{$cacheKeyString}_stats"); + CRM_Core_BAO_PrevNextCache::deleteItem(NULL, "{$cacheKeyString}_stats"); } public static function getMergeStats($cacheKeyString) { @@ -698,7 +706,7 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m $msg = "{$stats['merged']} " . ts('Contact(s) were merged.'); } if (!empty($stats['skipped'])) { - $msg .= $stats['skipped'] . ts('Contact(s) were skipped.'); + $msg .= $stats['skipped'] . ts(' Contact(s) were skipped.'); } return $msg; } @@ -716,15 +724,18 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m * A 'safe' value skips the merge if there are any un-resolved conflicts. * Does a force merge otherwise (aggressive mode). * @param bool $autoFlip to let api decide which contact to retain and which to delete. - * Wether to let api decide which contact to retain and which to delete. - * + * Whether to let api decide which contact to retain and which to delete. * * @param bool $redirectForPerformance + * Redirect to a url for batch processing. + * + * @param bool $checkPermissions + * Respect logged in user permissions. * * @return array|bool */ public static function merge($dupePairs = array(), $cacheParams = array(), $mode = 'safe', - $autoFlip = TRUE, $redirectForPerformance = FALSE + $autoFlip = TRUE, $redirectForPerformance = FALSE, $checkPermissions = TRUE ) { $cacheKeyString = CRM_Utils_Array::value('cache_key_string', $cacheParams); $resultStats = array('merged' => array(), 'skipped' => array()); @@ -757,7 +768,7 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m // Generate var $migrationInfo. The variable structure is exactly same as // $formValues submitted during a UI merge for a pair of contacts. - $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($mainId, $otherId); + $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($mainId, $otherId, $checkPermissions); $migrationInfo = &$rowsElementsAndInfo['migration_info']; @@ -769,7 +780,7 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m // 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); + CRM_Dedupe_Merger::moveAllBelongings($mainId, $otherId, $migrationInfo, $checkPermissions); $resultStats['merged'][] = array('main_id' => $mainId, 'other_id' => $otherId); $deletedContacts[] = $otherId; } @@ -852,7 +863,14 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m // 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']) + (!empty($migrationInfo['rows'][$key]['main']) + // For custom fields a 0 (e.g in an int field) could be a true conflict. This + // is probably true for other fields too - e.g. 'do_not_email' but + // leaving that investigation as a @todo - until tests can be written. + // Note the handling of this has test coverage - although the data-typing + // of '0' feels flakey we have insurance. + || ($migrationInfo['rows'][$key]['main'] === '0' && substr($key, 0, 12) == 'move_custom_') + ) && $migrationInfo['rows'][$key]['main'] != $migrationInfo['rows'][$key]['other'] ) { @@ -1014,6 +1032,14 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m * Main contact with whom merge has to happen. * @param int $otherId * Duplicate contact which would be deleted after merge operation. + * @param bool $checkPermissions + * Should the logged in user's permissions be ignore. Setting this to false is + * highly risky as it could cause data to be lost due to conflicts not showing up. + * OTOH there is a risk a merger might view custom data they do not have permission to. + * Hence for now only making this really explicit and making it reflect perms in + * an api call. + * + * @todo review permissions issue! * * @return array|bool|int * @@ -1044,8 +1070,11 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m * to move all fields from the 'other' contact to the 'main' contact, as * though the form had been submitted with those options. * + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception + * @throws \Exception */ - public static function getRowsElementsAndInfo($mainId, $otherId) { + public static function getRowsElementsAndInfo($mainId, $otherId, $checkPermissions = TRUE) { $qfZeroBug = 'e8cddb72-a257-11dc-b9cc-0016d3330ee9'; // Fetch contacts @@ -1218,11 +1247,9 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m foreach ($values['values'] as $index => $value) { $locations[$moniker][$blockName][$cnt] = $value; // Fix address display - $display = ''; if ($blockName == 'address') { CRM_Core_BAO_Address::fixAddress($value); - $display = CRM_Utils_Address::format($value); - $locations[$moniker][$blockName][$cnt]['display'] = $display; + $locations[$moniker][$blockName][$cnt]['display'] = CRM_Utils_Address::format($value); } $cnt++; @@ -1444,11 +1471,11 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m } // handle custom fields - $mainTree = CRM_Core_BAO_CustomGroup::getTree($main['contact_type'], CRM_Core_DAO::$_nullObject, $mainId, -1, - CRM_Utils_Array::value('contact_sub_type', $main) + $mainTree = CRM_Core_BAO_CustomGroup::getTree($main['contact_type'], NULL, $mainId, -1, + CRM_Utils_Array::value('contact_sub_type', $main), NULL, TRUE, NULL, TRUE, $checkPermissions ); $otherTree = CRM_Core_BAO_CustomGroup::getTree($main['contact_type'], CRM_Core_DAO::$_nullObject, $otherId, -1, - CRM_Utils_Array::value('contact_sub_type', $other) + CRM_Utils_Array::value('contact_sub_type', $other), NULL, TRUE, NULL, TRUE, $checkPermissions ); CRM_Core_DAO::freeResult(); @@ -1515,9 +1542,12 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m * * @param $migrationInfo * + * @param bool $checkPermissions + * Respect logged in user permissions. + * * @return bool */ - public static function moveAllBelongings($mainId, $otherId, $migrationInfo) { + public static function moveAllBelongings($mainId, $otherId, $migrationInfo, $checkPermissions = TRUE) { if (empty($migrationInfo)) { return FALSE; } @@ -1705,6 +1735,12 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m $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': @@ -1838,22 +1874,16 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m CRM_Core_BAO_CustomValueTable::setValues($viewOnlyCustomFields); } - if (CRM_Core_Permission::check('merge duplicate contacts') && - CRM_Core_Permission::check('delete contacts') + 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_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'])) { @@ -2019,24 +2049,27 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m * @param bool $reloadCacheIfEmpty * @param int $batchLimit * @param bool $isSelected - * @param array $orderByClause + * @param array|string $orderByClause * @param bool $includeConflicts * @param array $criteria * Additional criteria to narrow down the merge group. * + * @param bool $checkPermissions + * Respect logged in user permissions. + * * @return array - * Array of matches meeting the criteria. + * Array of matches meeting the criteria. */ - public static function getDuplicatePairs($rule_group_id, $group_id, $reloadCacheIfEmpty, $batchLimit, $isSelected, $orderByClause = '', $includeConflicts = TRUE, $criteria = array()) { + public static function getDuplicatePairs($rule_group_id, $group_id, $reloadCacheIfEmpty, $batchLimit, $isSelected, $orderByClause = '', $includeConflicts = TRUE, $criteria = array(), $checkPermissions = TRUE) { $where = self::getWhereString($batchLimit, $isSelected); - $cacheKeyString = self::getMergeCacheKeyString($rule_group_id, $group_id, $criteria); + $cacheKeyString = self::getMergeCacheKeyString($rule_group_id, $group_id, $criteria, $checkPermissions); $join = self::getJoinOnDedupeTable(); $dupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, $join, $where, 0, 0, array(), $orderByClause, $includeConflicts); if (empty($dupePairs) && $reloadCacheIfEmpty) { // If we haven't found any dupes, probably cache is empty. // Try filling cache and give another try. We don't need to specify include conflicts here are there will not be any // until we have done some processing. - CRM_Core_BAO_PrevNextCache::refillCache($rule_group_id, $group_id, $cacheKeyString, $criteria); + CRM_Core_BAO_PrevNextCache::refillCache($rule_group_id, $group_id, $cacheKeyString, $criteria, $checkPermissions); $dupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, $join, $where, 0, 0, array(), $orderByClause, $includeConflicts); return $dupePairs; } @@ -2052,14 +2085,28 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m * Additional criteria to narrow down the merge group. * Currently we are only supporting the key 'contact' within it. * + * @param bool $checkPermissions + * Respect the users permissions. + * * @return string */ - public static function getMergeCacheKeyString($rule_group_id, $group_id, $criteria = array()) { + public static function getMergeCacheKeyString($rule_group_id, $group_id, $criteria = array(), $checkPermissions = TRUE) { $contactType = CRM_Dedupe_BAO_RuleGroup::getContactTypeForRuleGroup($rule_group_id); $cacheKeyString = "merge {$contactType}"; $cacheKeyString .= $rule_group_id ? "_{$rule_group_id}" : '_0'; $cacheKeyString .= $group_id ? "_{$group_id}" : '_0'; $cacheKeyString .= !empty($criteria) ? serialize($criteria) : '_0'; + if ($checkPermissions) { + $contactID = CRM_Core_Session::getLoggedInContactID(); + if (!$contactID) { + // Distinguish between no permission check & no logged in user. + $contactID = 'null'; + } + $cacheKeyString .= '_' . $contactID; + } + else { + $cacheKeyString .= '_0'; + } return $cacheKeyString; }