diff --git a/src/DataTables/Filters/PartSearchFilter.php b/src/DataTables/Filters/PartSearchFilter.php index aa8c20f4..9b30cd87 100644 --- a/src/DataTables/Filters/PartSearchFilter.php +++ b/src/DataTables/Filters/PartSearchFilter.php @@ -66,11 +66,16 @@ class PartSearchFilter implements FilterInterface /** @var bool Use Internal Part number for searching */ protected bool $ipn = true; + /** @var int Helper variable for hacky array_map variable injection */ + protected int $it = 0; + public function __construct( /** @var string The string to query for */ protected string $keyword ) { + // Transform keyword and trim excess spaces + $keyword = trim(str_replace('+', ' ', $keyword)); } protected function getFieldsToSearch(): array @@ -126,27 +131,53 @@ public function apply(QueryBuilder $queryBuilder): void return; } - //Convert the fields to search to a list of expressions - $expressions = array_map(function (string $field): string { - if ($this->regex) { + if($this->regex) { + //Convert the fields to search to a list of expressions + $expressions = array_map(function (string $field): string { return sprintf("REGEXP(%s, :search_query) = TRUE", $field); - } + }, $fields_to_search); - return sprintf("ILIKE(%s, :search_query) = TRUE", $field); - }, $fields_to_search); + //Add Or concatenation of the expressions to our query + $queryBuilder->andWhere( + $queryBuilder->expr()->orX(...$expressions) + ); - //Add Or concatenation of the expressions to our query - $queryBuilder->andWhere( - $queryBuilder->expr()->orX(...$expressions) - ); - - //For regex, we pass the query as is, for like we add % to the start and end as wildcards - if ($this->regex) { + //For regex, we pass the query as is, save html special chars $queryBuilder->setParameter('search_query', $this->keyword); + return; } else { //Escape % and _ characters in the keyword $this->keyword = str_replace(['%', '_'], ['\%', '\_'], $this->keyword); - $queryBuilder->setParameter('search_query', '%' . $this->keyword . '%'); + + //Split keyword on spaces, but limit token count + $tokens = explode(' ', $this->keyword, 5); + + $params = new \Doctrine\Common\Collections\ArrayCollection(); + + //Perform search of every single token in every selected field + //AND-combine the results (all tokens must be present in any result, but the order does not matter) + for ($i = 0; $i < sizeof($tokens); $i++) { + $this->it = $i; + $tokens[$i] = trim($tokens[$i]); + + //Skip empty words (e.g. because of multiple spaces) + if ($tokens[$i] === '') { + continue; + } + //Convert the fields to search to a list of expressions + $expressions = array_map(function (string $field): string { + return sprintf("ILIKE(%s, :search_query%u) = TRUE", $field, $this->it); + }, $fields_to_search); + + //Aggregate the parameters for consolidated commission + $params[] = new \Doctrine\ORM\Query\Parameter('search_query' . $i, '%' . $tokens[$i] . '%'); + + //Add Or concatenation of the expressions to our query + $queryBuilder->andWhere( + $queryBuilder->expr()->orX(...$expressions) + ); + } + $queryBuilder->setParameters($params); } } @@ -304,5 +335,4 @@ public function setComment(bool $comment): PartSearchFilter return $this; } - }