From d1833d7b8ddc2fbcd1d8d833285fa9f657d929ca Mon Sep 17 00:00:00 2001 From: James Collins Date: Sat, 11 Mar 2023 21:38:36 +1000 Subject: [PATCH] added raw "filter" support as well as <> between --- app/Conductors/Conductor.php | 188 ++++++++++++++++++++++++++++------- 1 file changed, 150 insertions(+), 38 deletions(-) diff --git a/app/Conductors/Conductor.php b/app/Conductors/Conductor.php index 9a8c2c4..0581c56 100644 --- a/app/Conductors/Conductor.php +++ b/app/Conductors/Conductor.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; class Conductor @@ -102,6 +103,9 @@ class Conductor $params = $request->all(); $filterFields = array_intersect_key($params, array_flip($fields)); $conductor->filter($filterFields); + if ($request->has('filter') === true) { + $conductor->filterRaw($request->input('filter', ''), $fields); + } // Sort request $conductor->sort($request->input('sort', $conductor->sort)); @@ -176,7 +180,7 @@ class Conductor return $model; } - final public function filterField(Builder $builder, string $field, mixed $value) + private function filterFieldWithBuilder(Builder $builder, string $field, mixed $value, string $boolean = 'AND') { $values = []; @@ -190,53 +194,56 @@ class Conductor } // Add each AND check to the query - $this->query->where(function ($query) use ($field, $values) { + $builder->where(function ($query) use ($field, $values) { foreach ($values as $value) { $value = trim($value); + $prefix = ''; // Check if value has a prefix and remove it if it's a number if (preg_match('/^([<>!=]=?)(\d+\.?\d*)$/', $value, $matches) > 0) { $prefix = $matches[1]; $value = $matches[2]; - } else { - $prefix = ''; - } - - // If the value starts with '=', exact match - - if (strpos($value, '=') === 0) { - $query->orWhere($field, '=', substr($value, 1)); - } elseif (strpos($value, '!=') === 0) { - $query->orWhere($field, '<>', substr($value, 2)); - } elseif (strpos($value, '!') === 0) { - $query->orWhere($field, 'NOT LIKE', '%' . substr($value, 1) . '%'); - } else { - $query->orWhere($field, 'LIKE', "%$value%"); } // Apply the prefix to the query if the value is a number - if (is_numeric($value) === true) { - switch ($prefix) { - case '>': - $query->orWhere($field, '>', $value); - break; - case '<': - $query->orWhere($field, '<', $value); - break; - case '>=': - $query->orWhere($field, '>=', $value); - break; - case '<=': - $query->orWhere($field, '<=', $value); - break; - case '!=': - case '<>': - $query->orWhere($field, '<>', $value); - break; - } - } + switch ($prefix) { + case '=': + $query->orWhere($field, '=', $value); + break; + case '!': + $query->orWhere($field, 'NOT LIKE', "%$value%"); + break; + case '>': + $query->orWhere($field, '>', $value); + break; + case '<': + $query->orWhere($field, '<', $value); + break; + case '>=': + $query->orWhere($field, '>=', $value); + break; + case '<=': + $query->orWhere($field, '<=', $value); + break; + case '!=': + case '<>': + $query->orWhere($field, '!=', $value); + break; + default: + $betweenPos = strpos($value, '<>'); + if ($betweenPos !== false) { + $query->orWhereBetween($field, [substr($value, 0, $betweenPos), substr($value, ($betweenPos + 2))]); + } else { + $query->orWhere($field, 'LIKE', "%$value%"); + } + }//end switch }//end foreach - }); + }, null, null, $boolean); + } + + final public function filterField(string $field, mixed $value, string $boolean = 'AND') + { + $this->filterFieldWithBuilder($this->query, $field, $value, $boolean); } final public function collection(Collection $collection = null) @@ -287,7 +294,7 @@ class Conductor final public function filter(array $filters) { foreach ($filters as $param => $value) { - $this->filterField($this->query, $param, $value); + $this->filterField($param, $value); } } @@ -371,4 +378,109 @@ class Conductor { return true; } + + public function filterRaw($filterString, $limitFields = null) + { + if (is_array($limitFields) === false || empty($limitFields) === true) { + $limitFields = null; + } else { + $limitFields = array_map('strtolower', $limitFields); + } + + $tokens = preg_split('/([()]|,OR,|,AND,|,)/', $filterString, -1, (PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)); + + $parseTokens = function ($tokenList, $level, $index, $groupBoolean = null) use ($limitFields, &$parseTokens) { + $tokenGroup = []; + $firstToken = false; + $tokenGroupBoolean = 'AND'; + + if ($groupBoolean !== null) { + $firstToken = true; + $tokenGroupBoolean = $groupBoolean; + } + + while ($index < count($tokenList)) { + $token = $tokenList[$index]; + + ++$index; + if ($token === '(') { + // next group + $nextGroupBoolean = null; + if (count($tokenGroup) > 0 && strlen($tokenGroup[(count($tokenGroup) - 1)]['field']) === 0) { + $nextGroupBoolean = $tokenGroup[(count($tokenGroup) - 1)]['boolean']; + unset($tokenGroup[(count($tokenGroup) - 1)]); + } + + $index = $parseTokens($tokenList, $level + 1, $index, $nextGroupBoolean); + } elseif ($token === ')') { + // end group + break; + } elseif (in_array(strtoupper($token), [',AND,', ',OR,']) === true) { + // update boolean + $boolean = trim(strtoupper($token), ','); + + if ($firstToken === false && $level > 0) { + $tokenGroupBoolean = $boolean; + } else { + $firstToken = true; + $tokenGroup[] = [ + 'field' => '', + 'value' => '', + 'boolean' => $boolean + ]; + } + } elseif (strpos($token, ':') !== false) { + // set tokenGroup + $firstToken = true; + $field = substr($token, 0, strpos($token, ':')); + $value = substr($token, (strpos($token, ':') + 1)); + $boolean = 'AND'; + + if (count($tokenGroup) > 0 && strlen($tokenGroup[(count($tokenGroup) - 1)]['field']) === 0) { + $tokenGroup[(count($tokenGroup) - 1)]['field'] = $field; + $tokenGroup[(count($tokenGroup) - 1)]['value'] = $value; + $boolean = $tokenGroup[(count($tokenGroup) - 1)]['boolean']; + } else { + $tokenGroup[] = [ + 'field' => $field, + 'value' => $value, + 'boolean' => 'AND' + ]; + } + + if ($limitFields === null || in_array(strtolower($field), $limitFields) !== true) { + unset($tokenGroup[(count($tokenGroup) - 1)]); + } + + if ($level === 0) { + $this->filterFieldWithBuilder($this->query, $field, $value, $boolean); + } + }//end if + }//end while + + if ($level > 0) { + if ($tokenGroupBoolean === 'OR') { + $this->query->orWhere(function ($query) use ($tokenGroup) { + foreach ($tokenGroup as $tokenItem) { + if (strlen($tokenItem['field']) > 0) { + $this->filterFieldWithBuilder($query, $tokenItem['field'], $tokenItem['value'], $tokenItem['boolean']); + } + } + }); + } else { + $this->query->where(function ($query) use ($tokenGroup) { + foreach ($tokenGroup as $tokenItem) { + if (strlen($tokenItem['field']) > 0) { + $this->filterFieldWithBuilder($query, $tokenItem['field'], $tokenItem['value'], $tokenItem['boolean']); + } + } + }); + } + }//end if + + return $index; + }; + + $parseTokens($tokens, 0, 0); + } }