Towards CRM-20155 remove duplicate calls to dedupe functions.
[civicrm-core.git] / api / v3 / Contact.php
index 0a2f686d700a256fce795d282a11fdb923fc0d82..e09fc91ee17bc544fc21bba3d3ea8d0c2ce0127a 100644 (file)
@@ -3,7 +3,7 @@
   +--------------------------------------------------------------------+
   | CiviCRM version 4.7                                                |
   +--------------------------------------------------------------------+
-  | Copyright CiviCRM LLC (c) 2004-2016                                |
+  | Copyright CiviCRM LLC (c) 2004-2017                                |
   +--------------------------------------------------------------------+
   | This file is a part of CiviCRM.                                    |
   |                                                                    |
@@ -55,8 +55,14 @@ function civicrm_api3_contact_create($params) {
     throw new \Civi\API\Exception\UnauthorizedException('Permission denied to modify contact record');
   }
 
-  $dupeCheck = CRM_Utils_Array::value('dupe_check', $params, FALSE);
-  $values = _civicrm_api3_contact_check_params($params, $dupeCheck);
+  if (!empty($params['dupe_check'])) {
+    $ids = CRM_Contact_BAO_Contact::getDuplicateContacts($params, $params['contact_type'], 'Unsupervised', array(), $params['check_permission']);
+    if (count($ids) > 0) {
+      throw new API_Exception("Found matching contacts: " . implode(',', $ids), "duplicate", array("ids" => $ids));
+    }
+  }
+
+  $values = _civicrm_api3_contact_check_params($params);
   if ($values) {
     return $values;
   }
@@ -376,6 +382,37 @@ function _civicrm_api3_contact_get_supportanomalies(&$params, &$options) {
     unset($params['filter.group_id']);
     $options['input_params']['group'] = $groups;
   }
+  if (isset($params['group'])) {
+    $groups = $params['group'];
+    $allGroups = CRM_Core_PseudoConstant::group();
+    if (is_array($groups) && in_array(key($groups), CRM_Core_DAO::acceptedSQLOperators(), TRUE)) {
+      // Get the groups array.
+      $groupsArray = $groups[key($groups)];
+      foreach ($groupsArray as &$group) {
+        if (!is_numeric($group) && array_search($group, $allGroups)) {
+          $group = array_search($group, $allGroups);
+        }
+      }
+      // Now reset the $groups array with the ids not the titles.
+      $groups[key($groups)] = $groupsArray;
+    }
+    // handle format like 'group' => array('title1', 'title2').
+    elseif (is_array($groups)) {
+      foreach ($groups as $k => &$group) {
+        if (!is_numeric($group) && array_search($group, $allGroups)) {
+          $group = array_search($group, $allGroups);
+        }
+        if (!is_numeric($k) && array_search($k, $allGroups)) {
+          unset($groups[$k]);
+          $groups[array_search($k, $allGroups)] = $group;
+        }
+      }
+    }
+    elseif (!is_numeric($groups) &&  array_search($groups, $allGroups)) {
+      $groups = array_search($groups, $allGroups);
+    }
+    $params['group'] = $groups;
+  }
 }
 
 /**
@@ -424,13 +461,12 @@ function civicrm_api3_contact_delete($params) {
  * This function is on it's way out.
  *
  * @param array $params
- * @param bool $dupeCheck
  *
  * @return null
  * @throws API_Exception
  * @throws CiviCRM_API3_Exception
  */
-function _civicrm_api3_contact_check_params(&$params, $dupeCheck) {
+function _civicrm_api3_contact_check_params(&$params) {
 
   switch (strtolower(CRM_Utils_Array::value('contact_type', $params))) {
     case 'household':
@@ -463,35 +499,13 @@ function _civicrm_api3_contact_check_params(&$params, $dupeCheck) {
     }
   }
 
-  if ($dupeCheck) {
-    // check for record already existing
-    $dedupeParams = CRM_Dedupe_Finder::formatParams($params, $params['contact_type']);
-
-    // CRM-6431
-    // setting 'check_permission' here means that the dedupe checking will be carried out even if the
-    // person does not have permission to carry out de-dupes
-    // this is similar to the front end form
-    if (isset($params['check_permission'])) {
-      $dedupeParams['check_permission'] = $params['check_permission'];
-    }
-
-    $ids = CRM_Dedupe_Finder::dupesByParams($dedupeParams, $params['contact_type'], 'Unsupervised', array());
-
-    if (count($ids) > 0) {
-      throw new API_Exception("Found matching contacts: " . implode(',', $ids), "duplicate", array("ids" => $ids));
-    }
-  }
-
   // The BAO no longer supports the legacy param "current_employer" so here is a shim for api backward-compatability
   if (!empty($params['current_employer'])) {
     $organizationParams = array(
       'organization_name' => $params['current_employer'],
     );
 
-    $dedupParams = CRM_Dedupe_Finder::formatParams($organizationParams, 'Organization');
-
-    $dedupParams['check_permission'] = FALSE;
-    $dupeIds = CRM_Dedupe_Finder::dupesByParams($dedupParams, 'Organization', 'Supervised');
+    $dupeIds = CRM_Contact_BAO_Contact::getDuplicateContacts($organizationParams, 'Organization', 'Supervised', array(), FALSE);
 
     // check for mismatch employer name and id
     if (!empty($params['employer_id']) && !in_array($params['employer_id'], $dupeIds)) {
@@ -806,6 +820,7 @@ function civicrm_api3_contact_getquick($params) {
   if ($aclWhere) {
     $where .= " AND $aclWhere ";
   }
+  $isPrependWildcard = \Civi::settings()->get('includeWildCardInName');
 
   if (!empty($params['org'])) {
     $where .= " AND contact_type = \"Organization\"";
@@ -818,7 +833,7 @@ function civicrm_api3_contact_getquick($params) {
           (int) $params['employee_id'],
           'employer_id'
         )) {
-        if ($config->includeWildCardInName) {
+        if ($isPrependWildcard) {
           $strSearch = "%$name%";
         }
         else {
@@ -865,7 +880,7 @@ function civicrm_api3_contact_getquick($params) {
     $rel      = CRM_Utils_Type::escape($relation[2], 'String');
   }
 
-  if ($config->includeWildCardInName) {
+  if ($isPrependWildcard) {
     $strSearch = "%$name%";
   }
   else {
@@ -880,15 +895,13 @@ function civicrm_api3_contact_getquick($params) {
   //CRM-10687
   if (!empty($params['field_name']) && !empty($params['table_name'])) {
     $whereClause = " WHERE ( $table_name.$field_name LIKE '$strSearch') {$where}";
-    $exactWhereClause = " WHERE ( $table_name.$field_name = '$name') {$where}";
     // Search by id should be exact
     if ($field_name == 'id' || $field_name == 'external_identifier') {
-      $whereClause = $exactWhereClause;
+      $whereClause = " WHERE ( $table_name.$field_name = '$name') {$where}";
     }
   }
   else {
     $whereClause = " WHERE ( sort_name LIKE '$strSearch' $includeNickName ) {$where} ";
-    $exactWhereClause = " WHERE ( sort_name LIKE '$name' $exactIncludeNickName ) {$where} ";
     if ($config->includeEmailInName) {
       if (!in_array('email', $list)) {
         $includeEmailFrom = "LEFT JOIN civicrm_email eml ON ( cc.id = eml.contact_id AND eml.is_primary = 1 )";
@@ -913,12 +926,7 @@ function civicrm_api3_contact_getquick($params) {
       INNER JOIN civicrm_uf_match um ON (um.contact_id=cc.id)
       ";
   }
-
-  $orderByInner = $orderByOuter = "ORDER BY exactFirst";
-  if ($config->includeOrderByClause) {
-    $orderByInner = "ORDER BY exactFirst, sort_name";
-    $orderByOuter .= ", sort_name";
-  }
+  $orderBy = _civicrm_api3_quicksearch_get_order_by($name, $isPrependWildcard, $field_name);
 
   //CRM-5954
   $query = "
@@ -932,7 +940,7 @@ function civicrm_api3_contact_getquick($params) {
     {$aclFrom}
     {$additionalFrom}
     {$whereClause}
-    {$orderByInner}
+    {$orderBy}
     LIMIT 0, {$limit} )
     ";
 
@@ -947,13 +955,13 @@ function civicrm_api3_contact_getquick($params) {
         {$aclFrom}
         {$additionalFrom} {$includeEmailFrom}
         {$emailWhere} AND cc.is_deleted = 0 " . ($aclWhere ? " AND $aclWhere " : '') . "
-        {$orderByInner}
+        {$orderBy}
       LIMIT 0, {$limit}
       )
     ";
   }
   $query .= ") t
-    {$orderByOuter}
+    {$orderBy}
     LIMIT    0, {$limit}
   ";
 
@@ -1008,6 +1016,58 @@ function civicrm_api3_contact_getquick($params) {
   return civicrm_api3_create_success($contactList, $params, 'Contact', 'getquick');
 }
 
+/**
+ * Get the order by string for the quicksearch query.
+ *
+ * Get the order by string. The string might be
+ *  - sort name if there is no search value provided and the site is configured
+ *    to search by sort name
+ *  - empty if there is no search value provided and the site is not configured
+ *    to search by sort name
+ *  - exactFirst and then sort name if a search value is provided and the site is configured
+ *    to search by sort name
+ *  - exactFirst if a search value is provided and the site is not configured
+ *    to search by sort name
+ *
+ * exactFirst means 'yes if the search value exactly matches the searched field. else no'.
+ * It is intended to prioritise exact matches for the entered string so on a first name search
+ * for 'kath' contacts with a first name of exactly Kath rise to the top.
+ *
+ * On short strings it is expensive. Per CRM-19547 there is still an open question
+ * as to whether we should only do exactMatch on a minimum length or on certain fields.
+ *
+ * However, we have mitigated this somewhat by not doing an exact match search on
+ * empty strings, non-wildcard sort-name searches and email searches where there is
+ * no @ after the first character.
+ *
+ * For the user it is further mitigated by the fact they just don't know the
+ * slower queries are firing. If they type 'smit' slowly enough 4 queries will trigger
+ * but if the first 3 are slow the first result they see may be off the 4th query.
+ *
+ * @param string $name
+ * @param bool $isPrependWildcard
+ * @param string $field_name
+ *
+ * @return string
+ */
+function _civicrm_api3_quicksearch_get_order_by($name, $isPrependWildcard, $field_name) {
+  $skipExactMatch = ($name === '%');
+  if ($field_name === 'email' && !strpos('@', $name)) {
+    $skipExactMatch = TRUE;
+  }
+
+  if (!\Civi::settings()->get('includeOrderByClause')) {
+    return $skipExactMatch ? '' : "ORDER BY exactFirst";
+  }
+  if ($skipExactMatch || (!$isPrependWildcard && $field_name === 'sort_name')) {
+    // If there is no wildcard then sorting by exactFirst would have the same
+    // effect as just a sort_name search, but slower.
+    return "ORDER BY sort_name";
+  }
+
+  return "ORDER BY exactFirst, sort_name";
+}
+
 /**
  * Declare deprecated api functions.
  *
@@ -1027,11 +1087,10 @@ function _civicrm_api3_contact_deprecation() {
  *   -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
+ * @throws API_Exception
  */
 function civicrm_api3_contact_merge($params) {
   if (($result = CRM_Dedupe_Merger::merge(array(
@@ -1039,10 +1098,10 @@ function civicrm_api3_contact_merge($params) {
         'srcID' => $params['to_remove_id'],
         'dstID' => $params['to_keep_id'],
       ),
-    ), array(), $params['mode'], $params['auto_flip'])) != FALSE) {
+    ), array(), $params['mode'])) != FALSE) {
     return civicrm_api3_create_success($result, $params);
   }
-  throw new CiviCRM_API3_Exception('Merge failed');
+  throw new API_Exception('Merge failed');
 }
 
 /**
@@ -1065,10 +1124,6 @@ function _civicrm_api3_contact_merge_spec(&$params) {
     'type' => CRM_Utils_Type::T_INT,
     'api.aliases' => array('other_id'),
   );
-  $params['auto_flip'] = array(
-    'title' => 'Swap destination and source to retain lowest id?',
-    'api.default' => TRUE,
-  );
   $params['mode'] = array(
     // @todo need more detail on what this means.
     'title' => 'Dedupe mode',
@@ -1260,17 +1315,14 @@ function _civicrm_api3_contact_getlist_output($result, $request) {
  *   API formatted array
  */
 function civicrm_api3_contact_duplicatecheck($params) {
-  $dedupeParams = CRM_Dedupe_Finder::formatParams($params['match'], $params['match']['contact_type']);
-
-  // CRM-6431
-  // setting 'check_permission' here means that the dedupe checking will be carried out even if the
-  // person does not have permission to carry out de-dupes
-  // this is similar to the front end form
-  if (isset($params['check_permission'])) {
-    $dedupeParams['check_permission'] = $params['check_permission'];
-  }
-
-  $dupes = CRM_Dedupe_Finder::dupesByParams($dedupeParams, $params['match']['contact_type'], 'Unsupervised', array(), CRM_Utils_Array::value('dedupe_rule_id', $params));
+  $dupes = CRM_Contact_BAO_Contact::getDuplicateContacts(
+    $params['match'],
+    $params['match']['contact_type'],
+    'Unsupervised',
+    array(),
+    CRM_Utils_Array::value('check_permissions', $params),
+    CRM_Utils_Array::value('dedupe_rule_id', $params)
+  );
   $values = empty($dupes) ? array() : array_fill_keys($dupes, array());
   return civicrm_api3_create_success($values, $params, 'Contact', 'duplicatecheck');
 }