diff --git a/Civi/Api4/Generic/Traits/ArrayQueryActionTrait.php b/Civi/Api4/Generic/Traits/ArrayQueryActionTrait.php index fd515b9a4b71..f393739fc2a7 100644 --- a/Civi/Api4/Generic/Traits/ArrayQueryActionTrait.php +++ b/Civi/Api4/Generic/Traits/ArrayQueryActionTrait.php @@ -94,7 +94,7 @@ private function walkFilters($row, $filters) { return $result; default: - return $this->filterCompare($row, $filters); + return self::filterCompare($row, $filters); } } @@ -104,7 +104,7 @@ private function walkFilters($row, $filters) { * @return bool * @throws \Civi\API\Exception\NotImplementedException */ - private function filterCompare($row, $condition) { + public static function filterCompare($row, $condition) { if (!is_array($condition)) { throw new NotImplementedException('Unexpected where syntax; expecting array.'); } diff --git a/api/v3/utils.php b/api/v3/utils.php index f0db7003173a..6e1ca06719ea 100644 --- a/api/v3/utils.php +++ b/api/v3/utils.php @@ -2472,12 +2472,42 @@ function _civicrm_api3_field_value_check(&$params, $fieldName, $type = NULL) { */ function _civicrm_api3_basic_array_get($entity, $params, $records, $idCol, $filterableFields) { $options = _civicrm_api3_get_options_from_params($params, TRUE, $entity, 'get'); - // TODO // $sort = CRM_Utils_Array::value('sort', $options, NULL); $offset = $options['offset'] ?? NULL; $limit = $options['limit'] ?? NULL; + $sort = !empty($options['sort']) ? explode(', ', $options['sort']) : NULL; + if ($sort) { + usort($records, function($a, $b) use ($sort) { + foreach ($sort as $field) { + [$field, $dir] = array_pad(explode(' ', $field), 2, 'asc'); + $modifier = strtolower($dir) == 'asc' ? 1 : -1; + if (isset($a[$field]) && isset($b[$field])) { + if ($a[$field] == $b[$field]) { + continue; + } + return (strnatcasecmp($a[$field], $b[$field]) * $modifier); + } + elseif (isset($a[$field]) || isset($b[$field])) { + return ((isset($a[$field]) ? 1 : -1) * $modifier); + } + } + return 0; + }); + } + $matches = []; + $isMatch = function($record, $key, $searchValue) { + if (is_array($searchValue) && count($searchValue) === 1 && in_array(array_keys($searchValue)[0], CRM_Core_DAO::acceptedSQLOperators())) { + $operator = array_keys($searchValue)[0]; + $searchValue = array_values($searchValue)[0]; + return \Civi\Api4\Generic\Traits\ArrayQueryActionTrait::filterCompare($record, [$key, $operator, $searchValue]); + } + // Prior to supporting multiple search operators this function used the sloppy == comparison + // So for backward compatibility we'll continue to use it when no operator is explicitly given + return $searchValue == ($record[$key] ?? NULL); + }; + $currentOffset = 0; foreach ($records as $record) { if ($idCol != 'id') { @@ -2488,7 +2518,7 @@ function _civicrm_api3_basic_array_get($entity, $params, $records, $idCol, $filt if ($k == 'id') { $k = $idCol; } - if (in_array($k, $filterableFields) && $record[$k] != $v) { + if (in_array($k, $filterableFields) && !$isMatch($record, $k, $v)) { $match = FALSE; break; } diff --git a/tests/phpunit/api/v3/UtilsTest.php b/tests/phpunit/api/v3/UtilsTest.php index f472f3c0b778..e108f5be63ed 100644 --- a/tests/phpunit/api/v3/UtilsTest.php +++ b/tests/phpunit/api/v3/UtilsTest.php @@ -376,16 +376,34 @@ public function basicArrayCases() { $cases[] = [ $records, - ['version' => 3, 'cheese' => 'cheddar'], + ['version' => 3, 'cheese' => 'cheddar', 'options' => ['sort' => 'fruit desc']], ['b', 'c'], ]; + $cases[] = [ + $records, + ['version' => 3, 'cheese' => 'cheddar', 'options' => ['sort' => 'fruit']], + ['c', 'b'], + ]; + $cases[] = [ $records, ['version' => 3, 'id' => 'd'], ['d'], ]; + $cases[] = [ + $records, + ['version' => 3, 'fruit' => ['!=' => 'apple']], + ['b'], + ]; + + $cases[] = [ + $records, + ['version' => 3, 'cheese' => ['LIKE' => '%o%']], + ['d', 'e'], + ]; + return $cases; } @@ -423,7 +441,9 @@ public function testBasicArrayGet($records, $params, $resultIds) { $this->assertEquals($resultIds, array_values(CRM_Utils_Array::collect('snack_id', $r2['values']))); $this->assertEquals($resultIds, array_values(CRM_Utils_Array::collect('id', $r2['values']))); - $r3 = $kernel->runSafe('Widget', 'get', $params + ['options' => ['offset' => 1, 'limit' => 2]]); + $params['options']['offset'] = 1; + $params['options']['limit'] = 2; + $r3 = $kernel->runSafe('Widget', 'get', $params); $slice = array_slice($resultIds, 1, 2); $this->assertEquals(count($slice), $r3['count']); $this->assertEquals($slice, array_values(CRM_Utils_Array::collect('snack_id', $r3['values'])));