4 +--------------------------------------------------------------------+
5 | Copyright CiviCRM LLC. All rights reserved. |
7 | This work is published under the GNU AGPLv3 license with some |
8 | permitted exceptions and without any warranty. For full license |
9 | and copyright information, see https://civicrm.org/licensing |
10 +--------------------------------------------------------------------+
16 * @copyright CiviCRM LLC https://civicrm.org/licensing
20 namespace Civi\Api4\Generic\Traits
;
22 use Civi\API\Exception\NotImplementedException
;
25 * Helper functions for performing api queries on arrays of data.
27 * @package Civi\Api4\Generic
29 trait ArrayQueryActionTrait
{
32 * @param array $values
33 * List of all rows to be filtered
34 * @param \Civi\Api4\Generic\Result $result
35 * Object to store result
37 protected function queryArray($values, $result) {
38 $values = $this->filterArray($values);
39 $values = $this->sortArray($values);
40 // Set total count before applying limit
41 $result->rowCount
= count($values);
42 $values = $this->limitArray($values);
43 $values = $this->selectArray($values);
44 $result->exchangeArray($values);
48 * @param array $values
51 protected function filterArray($values) {
52 if ($this->getWhere()) {
53 $values = array_filter($values, [$this, 'evaluateFilters']);
55 return array_values($values);
62 private function evaluateFilters($row) {
63 $where = $this->getWhere();
64 $allConditions = in_array($where[0], ['AND', 'OR', 'NOT']) ?
$where : ['AND', $where];
65 return $this->walkFilters($row, $allConditions);
70 * @param array $filters
72 * @throws \Civi\API\Exception\NotImplementedException
74 private function walkFilters($row, $filters) {
75 switch ($filters[0]) {
79 foreach ($filters[1] as $filter) {
80 if (!$this->walkFilters($row, $filter)) {
85 return $result == ($filters[0] == 'AND');
88 $result = !count($filters[1]);
89 foreach ($filters[1] as $filter) {
90 if ($this->walkFilters($row, $filter)) {
97 return $this->filterCompare($row, $filters);
103 * @param array $condition
105 * @throws \Civi\API\Exception\NotImplementedException
107 private function filterCompare($row, $condition) {
108 if (!is_array($condition)) {
109 throw new NotImplementedException('Unexpected where syntax; expecting array.');
111 $value = $row[$condition[0]] ??
NULL;
112 $operator = $condition[1];
113 $expected = $condition[2] ??
NULL;
118 $equal = $value == $expected;
119 // PHP is too imprecise about comparing the number 0
120 if ($expected === 0 ||
$expected === '0') {
121 $equal = ($value === 0 ||
$value === '0');
123 // PHP is too imprecise about comparing empty strings
124 if ($expected === '') {
125 $equal = ($value === '');
127 return $equal == ($operator == '=');
131 return is_null($value) == ($operator == 'IS NULL');
134 return $value > $expected;
137 return $value >= $expected;
140 return $value < $expected;
143 return $value <= $expected;
147 $between = ($value >= $expected[0] && $value <= $expected[1]);
148 return $between == ($operator == 'BETWEEN');
152 $pattern = '/^' . str_replace('%', '.*', preg_quote($expected, '/')) . '$/i';
153 return !preg_match($pattern, $value) == ($operator != 'LIKE');
156 return in_array($value, $expected);
159 return !in_array($value, $expected);
162 throw new NotImplementedException("Unsupported operator: '$operator' cannot be used with array data");
170 protected function sortArray($values) {
171 if ($this->getOrderBy()) {
172 usort($values, [$this, 'sortCompare']);
177 private function sortCompare($a, $b) {
178 foreach ($this->getOrderBy() as $field => $dir) {
179 $modifier = $dir == 'ASC' ?
1 : -1;
180 if (isset($a[$field]) && isset($b[$field])) {
181 if ($a[$field] == $b[$field]) {
184 return (strnatcasecmp($a[$field], $b[$field]) * $modifier);
186 elseif (isset($a[$field]) ||
isset($b[$field])) {
187 return ((isset($a[$field]) ?
1 : -1) * $modifier);
197 protected function selectArray($values) {
198 if ($this->getSelect() === ['row_count']) {
199 $values = [['row_count' => count($values)]];
201 elseif ($this->getSelect()) {
202 // Return only fields specified by SELECT
203 foreach ($values as &$value) {
204 $value = array_intersect_key($value, array_flip($this->getSelect()));
208 // With no SELECT specified, return all values that are keyed by plain field name; omit those with :pseudoconstant suffixes
209 foreach ($values as &$value) {
210 $value = array_filter($value, function($key) {
211 return strpos($key, ':') === FALSE;
212 }, ARRAY_FILTER_USE_KEY
);
222 protected function limitArray($values) {
223 if ($this->getOffset() ||
$this->getLimit()) {
224 $values = array_slice($values, $this->getOffset() ?
: 0, $this->getLimit() ?
: NULL);