Skip to content

Commit

Permalink
APIv3 - Improve array-based apis to support sorting and multiple oper…
Browse files Browse the repository at this point in the history
…ators

This backports some APIv4 code to v3, for the purpose of supporting
entityRef widgets for Afform.
  • Loading branch information
colemanw committed Feb 28, 2021
1 parent fdb8caa commit bffbcb2
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 6 deletions.
4 changes: 2 additions & 2 deletions Civi/Api4/Generic/Traits/ArrayQueryActionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ private function walkFilters($row, $filters) {
return $result;

default:
return $this->filterCompare($row, $filters);
return self::filterCompare($row, $filters);
}
}

Expand All @@ -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.');
}
Expand Down
34 changes: 32 additions & 2 deletions api/v3/utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand All @@ -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;
}
Expand Down
24 changes: 22 additions & 2 deletions tests/phpunit/api/v3/UtilsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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'])));
Expand Down

0 comments on commit bffbcb2

Please sign in to comment.