Merge pull request #17384 from eileenmcnaughton/abort
[civicrm-core.git] / api / v3 / Generic / Getlist.php
index 1c9bd08af7f040bb6bc202e90d787c521f7d3ebf..f63948e27c2ce66b5e673aeb281d549dabbb2185 100644 (file)
@@ -1,27 +1,11 @@
 <?php
 /*
  +--------------------------------------------------------------------+
- | CiviCRM version 5                                                  |
- +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC (c) 2004-2019                                |
- +--------------------------------------------------------------------+
- | This file is a part of CiviCRM.                                    |
- |                                                                    |
- | CiviCRM is free software; you can copy, modify, and distribute it  |
- | under the terms of the GNU Affero General Public License           |
- | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
- |                                                                    |
- | CiviCRM is distributed in the hope that it will be useful, but     |
- | WITHOUT ANY WARRANTY; without even the implied warranty of         |
- | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
- | See the GNU Affero General Public License for more details.        |
+ | Copyright CiviCRM LLC. All rights reserved.                        |
  |                                                                    |
- | You should have received a copy of the GNU Affero General Public   |
- | License and the CiviCRM Licensing Exception along                  |
- | with this program; if not, contact CiviCRM LLC                     |
- | at info[AT]civicrm[DOT]org. If you have questions about the        |
- | GNU Affero General Public License or the licensing of CiviCRM,     |
- | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ | This work is published under the GNU AGPLv3 license with some      |
+ | permitted exceptions and without any warranty. For full license    |
+ | and copyright information, see https://civicrm.org/licensing       |
  +--------------------------------------------------------------------+
  */
 
  * @param array $apiRequest
  *
  * @return mixed
+ * @throws \CiviCRM_API3_Exception
  */
 function civicrm_api3_generic_getList($apiRequest) {
-  $entity = _civicrm_api_get_entity_name_from_camel($apiRequest['entity']);
+  $entity = CRM_Core_DAO_AllCoreTables::convertEntityNameToLower($apiRequest['entity']);
   $request = $apiRequest['params'];
-  $meta = civicrm_api3_generic_getfields(array('action' => 'get') + $apiRequest, FALSE);
+  $meta = civicrm_api3_generic_getfields(['action' => 'get'] + $apiRequest, FALSE);
 
   // Hey api, would you like to provide default values?
   $fnName = "_civicrm_api3_{$entity}_getlist_defaults";
-  $defaults = function_exists($fnName) ? $fnName($request) : array();
+  $defaults = function_exists($fnName) ? $fnName($request) : [];
   _civicrm_api3_generic_getList_defaults($entity, $request, $defaults, $meta['values']);
 
   // Hey api, would you like to format the search params?
@@ -53,6 +38,31 @@ function civicrm_api3_generic_getList($apiRequest) {
 
   $request['params']['check_permissions'] = !empty($apiRequest['params']['check_permissions']);
   $result = civicrm_api3($entity, 'get', $request['params']);
+  if (!empty($request['input']) && !empty($defaults['search_field_fallback']) && $result['count'] < $request['params']['options']['limit']) {
+    // We support a field fallback. Note we don't do this as an OR query because that could easily
+    // bypass an index & kill the server. We just 'pad' the results if needed with the second
+    // query - this is effectively the same as what the old Ajax::getContactEmail function did.
+    // Since these queries should be quick & often only one should be needed this is a simpler alternative
+    // to constructing a UNION via the api.
+    $request['params'][$defaults['search_field_fallback']] = $request['params'][$defaults['search_field']];
+    if ($request['params']['options']['sort'] === $defaults['search_field']) {
+      // The way indexing works here is that the order by field will be chosen in preference to the
+      // filter field. This can result in really bad performance so use the filter field for the sort.
+      // See https://github.com/civicrm/civicrm-core/pull/16993 for performance test results.
+      $request['params']['options']['sort'] = $defaults['search_field_fallback'];
+    }
+    // Exclude anything returned from the previous query since we are looking for additional rows in this
+    // second query.
+    $request['params'][$defaults['search_field']] = ['NOT LIKE' => $request['params'][$defaults['search_field_fallback']]['LIKE']];
+    $request['params']['options']['limit'] -= $result['count'];
+    $result2 = civicrm_api3($entity, 'get', $request['params']);
+    $result['values'] = array_merge($result['values'], $result2['values']);
+    $result['count'] = count($result['values']);
+  }
+  else {
+    // Re-index to sequential = 0.
+    $result['values'] = array_merge($result['values']);
+  }
 
   // Hey api, would you like to format the output?
   $fnName = "_civicrm_api3_{$entity}_getlist_output";
@@ -61,7 +71,7 @@ function civicrm_api3_generic_getList($apiRequest) {
 
   _civicrm_api3_generic_getlist_postprocess($result, $request, $values);
 
-  $output = array('page_num' => $request['page_num']);
+  $output = ['page_num' => $request['page_num']];
 
   // Limit is set for searching but not fetching by id
   if (!empty($request['params']['options']['limit'])) {
@@ -83,26 +93,26 @@ function civicrm_api3_generic_getList($apiRequest) {
  * @param array $fields
  */
 function _civicrm_api3_generic_getList_defaults($entity, &$request, $apiDefaults, $fields) {
-  $defaults = array(
+  $defaults = [
     'page_num' => 1,
     'input' => '',
     'image_field' => NULL,
     'color_field' => isset($fields['color']) ? 'color' : NULL,
     'id_field' => $entity == 'option_value' ? 'value' : 'id',
-    'description_field' => array(),
+    'description_field' => [],
     'add_wildcard' => Civi::settings()->get('includeWildCardInName'),
-    'params' => array(),
-    'extra' => array(),
-  );
+    'params' => [],
+    'extra' => [],
+  ];
   // Find main field from meta
-  foreach (array('sort_name', 'title', 'label', 'name', 'subject') as $field) {
+  foreach (['sort_name', 'title', 'label', 'name', 'subject'] as $field) {
     if (isset($fields[$field])) {
       $defaults['label_field'] = $defaults['search_field'] = $field;
       break;
     }
   }
   // Find fields to be used for the description
-  foreach (array('description') as $field) {
+  foreach (['description'] as $field) {
     if (isset($fields[$field])) {
       $defaults['description_field'][] = $field;
     }
@@ -113,13 +123,13 @@ function _civicrm_api3_generic_getList_defaults($entity, &$request, $apiDefaults
   }
   $request += $apiDefaults + $defaults;
   // Default api params
-  $params = array(
-    'sequential' => 1,
-    'options' => array(),
-  );
+  $params = [
+    'sequential' => 0,
+    'options' => [],
+  ];
   // When searching e.g. autocomplete
   if ($request['input']) {
-    $params[$request['search_field']] = array('LIKE' => ($request['add_wildcard'] ? '%' : '') . $request['input'] . '%');
+    $params[$request['search_field']] = ['LIKE' => ($request['add_wildcard'] ? '%' : '') . $request['input'] . '%'];
   }
   // When looking up a field e.g. displaying existing record
   if (!empty($request['id'])) {
@@ -129,18 +139,18 @@ function _civicrm_api3_generic_getList_defaults($entity, &$request, $apiDefaults
     // Don't run into search limits when prefilling selection
     $params['options']['limit'] = NULL;
     unset($params['options']['offset'], $request['params']['options']['limit'], $request['params']['options']['offset']);
-    $params[$request['id_field']] = is_array($request['id']) ? array('IN' => $request['id']) : $request['id'];
+    $params[$request['id_field']] = is_array($request['id']) ? ['IN' => $request['id']] : $request['id'];
   }
   $request['params'] += $params;
 
-  $request['params']['options'] += array(
+  $request['params']['options'] += [
     // Add pagination parameters
     'sort' => $request['label_field'],
     // Adding one extra result allows us to see if there are any more
     'limit' => $resultsPerPage + 1,
     // Because sql is zero-based
     'offset' => ($request['page_num'] - 1) * $resultsPerPage,
-  );
+  ];
 }
 
 /**
@@ -149,7 +159,7 @@ function _civicrm_api3_generic_getList_defaults($entity, &$request, $apiDefaults
  * @param array $request
  */
 function _civicrm_api3_generic_getlist_params(&$request) {
-  $fieldsToReturn = array($request['id_field'], $request['label_field']);
+  $fieldsToReturn = [$request['id_field'], $request['label_field']];
   if (!empty($request['image_field'])) {
     $fieldsToReturn[] = $request['image_field'];
   }
@@ -173,15 +183,15 @@ function _civicrm_api3_generic_getlist_params(&$request) {
  * @return array
  */
 function _civicrm_api3_generic_getlist_output($result, $request, $entity, $fields) {
-  $output = array();
+  $output = [];
   if (!empty($result['values'])) {
     foreach ($result['values'] as $row) {
-      $data = array(
+      $data = [
         'id' => $row[$request['id_field']],
         'label' => $row[$request['label_field']],
-      );
+      ];
       if (!empty($request['description_field'])) {
-        $data['description'] = array();
+        $data['description'] = [];
         foreach ((array) $request['description_field'] as $field) {
           if (!empty($row[$field])) {
             if (!isset($fields[$field]['pseudoconstant'])) {
@@ -198,7 +208,7 @@ function _civicrm_api3_generic_getlist_output($result, $request, $entity, $field
         }
       };
       if (!empty($request['image_field'])) {
-        $data['image'] = isset($row[$request['image_field']]) ? $row[$request['image_field']] : '';
+        $data['image'] = $row[$request['image_field']] ?? '';
       }
       if (isset($row[$request['color_field']])) {
         $data['color'] = $row[$request['color_field']];
@@ -217,7 +227,7 @@ function _civicrm_api3_generic_getlist_output($result, $request, $entity, $field
  * @param $values
  */
 function _civicrm_api3_generic_getlist_postprocess($result, $request, &$values) {
-  $chains = array();
+  $chains = [];
   foreach ($request['params'] as $field => $param) {
     if (substr($field, 0, 4) === 'api.') {
       $chains[] = $field;
@@ -226,10 +236,10 @@ function _civicrm_api3_generic_getlist_postprocess($result, $request, &$values)
   if (!empty($result['values'])) {
     foreach (array_values($result['values']) as $num => $row) {
       foreach ($request['extra'] as $field) {
-        $values[$num]['extra'][$field] = isset($row[$field]) ? $row[$field] : NULL;
+        $values[$num]['extra'][$field] = $row[$field] ?? NULL;
       }
       foreach ($chains as $chain) {
-        $values[$num][$chain] = isset($row[$chain]) ? $row[$chain] : NULL;
+        $values[$num][$chain] = $row[$chain] ?? NULL;
       }
     }
   }
@@ -242,49 +252,49 @@ function _civicrm_api3_generic_getlist_postprocess($result, $request, &$values)
  * @param array $apiRequest
  */
 function _civicrm_api3_generic_getlist_spec(&$params, $apiRequest) {
-  $params += array(
-    'page_num' => array(
+  $params += [
+    'page_num' => [
       'title' => 'Page Number',
       'description' => "Current page of a multi-page lookup",
       'type' => CRM_Utils_Type::T_INT,
-    ),
-    'input' => array(
+    ],
+    'input' => [
       'title' => 'Search Input',
       'description' => "String to search on",
       'type' => CRM_Utils_Type::T_TEXT,
-    ),
-    'params' => array(
+    ],
+    'params' => [
       'title' => 'API Params',
       'description' => "Additional filters to send to the {$apiRequest['entity']} API.",
-    ),
-    'extra' => array(
+    ],
+    'extra' => [
       'title' => 'Extra',
       'description' => 'Array of additional fields to return.',
-    ),
-    'image_field' => array(
+    ],
+    'image_field' => [
       'title' => 'Image Field',
       'description' => "Field that this entity uses to store icons (usually automatic)",
       'type' => CRM_Utils_Type::T_TEXT,
-    ),
-    'id_field' => array(
+    ],
+    'id_field' => [
       'title' => 'ID Field',
       'description' => "Field that uniquely identifies this entity (usually automatic)",
       'type' => CRM_Utils_Type::T_TEXT,
-    ),
-    'description_field' => array(
+    ],
+    'description_field' => [
       'title' => 'Description Field',
       'description' => "Field that this entity uses to store summary text (usually automatic)",
       'type' => CRM_Utils_Type::T_TEXT,
-    ),
-    'label_field' => array(
+    ],
+    'label_field' => [
       'title' => 'Label Field',
       'description' => "Field to display as title of results (usually automatic)",
       'type' => CRM_Utils_Type::T_TEXT,
-    ),
-    'search_field' => array(
+    ],
+    'search_field' => [
       'title' => 'Search Field',
       'description' => "Field to search on (assumed to be the same as label field unless otherwise specified)",
       'type' => CRM_Utils_Type::T_TEXT,
-    ),
-  );
+    ],
+  ];
 }