diff options
| author | 2026-01-03 16:52:33 +0100 | |
|---|---|---|
| committer | 2026-01-03 16:52:33 +0100 | |
| commit | 252703305758e2ed0e6257ae94fdb3b4b90f7184 (patch) | |
| tree | 058b449bda17aef10eefbaadd38741161b47cb2b /app/Models/BooleanSearch.php | |
| parent | f0769d6e557320c6af93f7f589f9dbf05a96ca47 (diff) | |
Fix unwanted expansion of user queries in some cases (#8395)
fix https://github.com/FreshRSS/FreshRSS/issues/8378
Diffstat (limited to 'app/Models/BooleanSearch.php')
| -rw-r--r-- | app/Models/BooleanSearch.php | 88 |
1 files changed, 56 insertions, 32 deletions
diff --git a/app/Models/BooleanSearch.php b/app/Models/BooleanSearch.php index 1c0d6c18c..263fa8a58 100644 --- a/app/Models/BooleanSearch.php +++ b/app/Models/BooleanSearch.php @@ -20,7 +20,8 @@ class FreshRSS_BooleanSearch implements \Stringable { string $input, int $level = 0, private readonly string $operator = 'AND', - bool $allowUserQueries = true + bool $allowUserQueries = true, + bool $expandUserQueries = true ) { $input = trim($input); if ($input === '') { @@ -30,8 +31,10 @@ class FreshRSS_BooleanSearch implements \Stringable { if ($level === 0) { $input = self::escapeLiterals($input); - $input = $this->parseUserQueryNames($input, $allowUserQueries); - $input = $this->parseUserQueryIds($input, $allowUserQueries); + if ($expandUserQueries || !$allowUserQueries) { + $input = $this->parseUserQueryNames($input, $allowUserQueries); + $input = $this->parseUserQueryIds($input, $allowUserQueries); + } $input = trim($input); } @@ -46,6 +49,8 @@ class FreshRSS_BooleanSearch implements \Stringable { foreach ($this->searches as $key => $search) { $this->searches[$key] = clone $search; } + $this->expanded = null; + $this->notExpanded = null; } /** @@ -76,13 +81,11 @@ class FreshRSS_BooleanSearch implements \Stringable { } for ($i = count($matches['search']) - 1; $i >= 0; $i--) { $name = trim($matches['search'][$i]); - if (!empty($queries[$name])) { - $fromS[] = $matches[0][$i]; - if ($allowUserQueries) { - $toS[] = '(' . self::escapeLiterals($queries[$name]) . ')'; - } else { - $toS[] = ''; - } + $fromS[] = $matches[0][$i]; + if ($allowUserQueries && !empty($queries[$name])) { + $toS[] = '(' . self::escapeLiterals($queries[$name]) . ')'; + } else { + $toS[] = ''; } } } @@ -124,12 +127,9 @@ class FreshRSS_BooleanSearch implements \Stringable { $matchedQueries[] = $queries[$id]; } } - if (empty($matchedQueries)) { - continue; - } $fromS[] = $matches[0][$i]; - if ($allowUserQueries) { + if ($allowUserQueries && !empty($matchedQueries)) { $escapedQueries = array_map(fn(string $query): string => self::escapeLiterals($query), $matchedQueries); $toS[] = '(' . implode(') OR (', $escapedQueries) . ')'; } else { @@ -447,6 +447,8 @@ class FreshRSS_BooleanSearch implements \Stringable { public function enforce(FreshRSS_Search $search): self { $result = clone $this; $result->raw_input = ''; + $result->expanded = null; + $result->notExpanded = null; if (count($result->searches) === 1 && $result->searches[0] instanceof FreshRSS_Search) { $result->searches[0] = $result->searches[0]->enforce($search); @@ -489,6 +491,8 @@ class FreshRSS_BooleanSearch implements \Stringable { public function remove(FreshRSS_Search $search): self { $result = clone $this; $result->raw_input = ''; + $result->expanded = null; + $result->notExpanded = null; if (count($result->searches) === 1 && $result->searches[0] instanceof FreshRSS_Search) { $result->searches[0] = $result->searches[0]->remove($search); @@ -511,33 +515,53 @@ class FreshRSS_BooleanSearch implements \Stringable { return $result; } + private ?string $expanded = null; + #[\Override] public function __toString(): string { - $result = ''; - foreach ($this->searches as $search) { - $part = $search->__toString(); - if ($part === '') { - continue; - } - $operator = $search instanceof FreshRSS_BooleanSearch ? $search->operator : 'OR'; + if ($this->expanded === null) { + $result = ''; + foreach ($this->searches as $search) { + $part = $search->__toString(); + if ($part === '') { + continue; + } + $operator = $search instanceof FreshRSS_BooleanSearch ? $search->operator : 'OR'; + + if ((str_contains($part, ' ') || str_starts_with($part, '-')) && (count($this->searches) > 1 || in_array($operator, ['OR NOT', 'AND NOT'], true))) { + $part = '(' . $part . ')'; + } - if ((str_contains($part, ' ') || str_starts_with($part, '-')) && (count($this->searches) > 1 || in_array($operator, ['OR NOT', 'AND NOT'], true))) { - $part = '(' . $part . ')'; + $result .= match ($operator) { + 'OR' => $result === '' ? '' : ' OR ', + 'OR NOT' => $result === '' ? '-' : ' OR -', + 'AND NOT' => $result === '' ? '-' : ' -', + 'AND' => $result === '' ? '' : ' ', + default => throw new InvalidArgumentException('Invalid operator: ' . $operator), + } . $part; } + $this->expanded = trim($result); + } + return $this->expanded; + } - $result .= match ($operator) { - 'OR' => $result === '' ? '' : ' OR ', - 'OR NOT' => $result === '' ? '-' : ' OR -', - 'AND NOT' => $result === '' ? '-' : ' -', - 'AND' => $result === '' ? '' : ' ', - default => throw new InvalidArgumentException('Invalid operator: ' . $operator), - } . $part; + private ?string $notExpanded = null; + + /** + * @param bool $expandUserQueries Whether to expand user queries (saved searches) or not + */ + public function toString(bool $expandUserQueries = true): string { + if ($expandUserQueries) { + return $this->__toString(); } - return trim($result); + if ($this->notExpanded === null) { + $this->notExpanded = (new FreshRSS_BooleanSearch($this->raw_input, expandUserQueries: false))->__toString(); + } + return $this->notExpanded; } /** @return string Plain text search query. Must be XML-encoded or URL-encoded depending on the situation */ - #[Deprecated('Use __tostring() instead')] + #[Deprecated('Use __toString(expanded: false) instead')] public function getRawInput(): string { return $this->raw_input; } |
