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 Mar 1, 2021
1 parent fdb8caa commit 1ad334d
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 4 deletions.
74 changes: 72 additions & 2 deletions api/v3/utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -2472,12 +2472,82 @@ 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 = [];

$filterCompare = function($value, $operator, $expected) {
switch ($operator) {
case '=':
case '!=':
case '<>':
return ($value == $expected) == ($operator == '=');

case 'IS NULL':
case 'IS NOT NULL':
return is_null($value) == ($operator == 'IS NULL');

case '>':
return $value > $expected;

case '>=':
return $value >= $expected;

case '<':
return $value < $expected;

case '<=':
return $value <= $expected;

case 'BETWEEN':
case 'NOT BETWEEN':
$between = ($value >= $expected[0] && $value <= $expected[1]);
return $between == ($operator == 'BETWEEN');

case 'LIKE':
case 'NOT LIKE':
$pattern = '/^' . str_replace('%', '.*', preg_quote($expected, '/')) . '$/i';
return !preg_match($pattern, $value) == ($operator != 'LIKE');

case 'IN':
case 'NOT IN':
return in_array($value, $expected) == ($operator == 'IN');

default:
throw new API_Exception("Unsupported operator: '$operator' cannot be used with array data");
}
};

$isMatch = function($recordVal, $searchValue) use ($filterCompare) {
$operator = '=';
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 $filterCompare($recordVal, $operator, $searchValue);
};

$currentOffset = 0;
foreach ($records as $record) {
if ($idCol != 'id') {
Expand All @@ -2488,7 +2558,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] ?? NULL, $v)) {
$match = FALSE;
break;
}
Expand Down
36 changes: 34 additions & 2 deletions tests/phpunit/api/v3/UtilsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -376,16 +376,46 @@ 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, 'cheese' => ['IS NOT NULL' => 1], 'options' => ['sort' => 'fruit, cheese']],
['c', 'd', 'e', 'a', 'b'],
];

$cases[] = [
$records,
['version' => 3, 'id' => 'd'],
['d'],
];

$cases[] = [
$records,
['version' => 3, 'fruit' => ['!=' => 'apple']],
['b'],
];

$cases[] = [
$records,
['version' => 3, 'cheese' => ['LIKE' => '%o%']],
['d', 'e'],
];

$cases[] = [
$records,
['version' => 3, 'cheese' => ['IN' => ['swiss', 'cheddar', 'gouda']]],
['a', 'b', 'c', 'd'],
];

return $cases;
}

Expand Down Expand Up @@ -423,7 +453,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 1ad334d

Please sign in to comment.