aboutsummaryrefslogtreecommitdiff
path: root/app/Models/BooleanSearch.php
diff options
context:
space:
mode:
Diffstat (limited to 'app/Models/BooleanSearch.php')
-rw-r--r--app/Models/BooleanSearch.php88
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;
}