aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/Controllers/indexController.php6
-rw-r--r--app/Controllers/subscriptionController.php4
-rw-r--r--app/Models/BooleanSearch.php88
-rw-r--r--app/Models/FilterAction.php2
-rw-r--r--app/Models/FilterActionsTrait.php4
-rw-r--r--app/Models/Search.php2
-rw-r--r--app/Models/UserQuery.php4
-rw-r--r--app/layout/header.phtml2
-rw-r--r--app/layout/nav_menu.phtml6
-rw-r--r--app/views/configure/queries.phtml4
-rw-r--r--app/views/configure/reading.phtml4
-rw-r--r--app/views/helpers/category/update.phtml2
-rw-r--r--app/views/helpers/configure/query.phtml3
-rw-r--r--app/views/helpers/export/opml.phtml2
-rw-r--r--app/views/helpers/feed/update.phtml2
-rw-r--r--app/views/helpers/stream-footer.phtml2
-rw-r--r--app/views/tag/update.phtml2
17 files changed, 82 insertions, 57 deletions
diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php
index 0d704c496..ca88c9171 100644
--- a/app/Controllers/indexController.php
+++ b/app/Controllers/indexController.php
@@ -94,8 +94,8 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
};
$searchString = $operator . ':' . ($offset < 0 ? '/' : '') . date('Y-m-d', $timestamp + ($offset * 86400)) . ($offset > 0 ? '/' : '');
return Minz_Url::display(Minz_Request::modifiedCurrentRequest([
- 'search' => FreshRSS_Context::$search->__toString() === '' ? $searchString :
- FreshRSS_Context::$search->enforce(new FreshRSS_Search($searchString))->__toString(),
+ 'search' => FreshRSS_Context::$search->toString() === '' ? $searchString :
+ FreshRSS_Context::$search->enforce(new FreshRSS_Search($searchString))->toString(),
]));
}
@@ -135,7 +135,7 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
$this->view->rss_title = FreshRSS_Context::$name . ' | ' . FreshRSS_View::title();
$title = FreshRSS_Context::$name;
- $search = FreshRSS_Context::$search->__toString();
+ $search = FreshRSS_Context::$search->toString(expandUserQueries: false);
if ($search !== '') {
$title = '“' . htmlspecialchars($search, ENT_COMPAT, 'UTF-8') . '”';
}
diff --git a/app/Controllers/subscriptionController.php b/app/Controllers/subscriptionController.php
index 02942583f..eb9fbf58f 100644
--- a/app/Controllers/subscriptionController.php
+++ b/app/Controllers/subscriptionController.php
@@ -421,7 +421,7 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
$actionsSearch = new FreshRSS_BooleanSearch('', operator: 'AND');
foreach ($filteractions as $action) {
$actionSearch = new FreshRSS_BooleanSearch($action, operator: 'OR');
- if ($actionSearch->__toString() === '') {
+ if ($actionSearch->toString() === '') {
continue;
}
$actionsSearch->add($actionSearch);
@@ -433,7 +433,7 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
'c' => 'index',
'a' => 'index',
'params' => [
- 'search' => $search->__toString(),
+ 'search' => $search->toString(),
],
], redirect: true);
}
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;
}
diff --git a/app/Models/FilterAction.php b/app/Models/FilterAction.php
index 5d11d26db..9fc965733 100644
--- a/app/Models/FilterAction.php
+++ b/app/Models/FilterAction.php
@@ -33,7 +33,7 @@ class FreshRSS_FilterAction {
public function toJSON(): array {
if (is_array($this->actions) && $this->booleanSearch != null) {
return [
- 'search' => $this->booleanSearch->__toString(),
+ 'search' => $this->booleanSearch->toString(expandUserQueries: false),
'actions' => $this->actions,
];
}
diff --git a/app/Models/FilterActionsTrait.php b/app/Models/FilterActionsTrait.php
index 819f2d975..6f4eafd6e 100644
--- a/app/Models/FilterActionsTrait.php
+++ b/app/Models/FilterActionsTrait.php
@@ -71,7 +71,7 @@ trait FreshRSS_FilterActionsTrait {
//Check existing filters
for ($i = count($filterActions) - 1; $i >= 0; $i--) {
$filterAction = $filterActions[$i];
- if ($filterAction === null || !is_array($filterAction->actions()) || $filterAction->booleanSearch()->__toString() === '') {
+ if ($filterAction === null || !is_array($filterAction->actions()) || $filterAction->booleanSearch()->toString() === '') {
array_splice($filterActions, $i, 1);
continue;
}
@@ -85,7 +85,7 @@ trait FreshRSS_FilterActionsTrait {
//Update existing filter with new action
for ($k = count($filters) - 1; $k >= 0; $k--) {
$filter = $filters[$k];
- if ($filter === $filterAction->booleanSearch()->__toString()) {
+ if ($filter === $filterAction->booleanSearch()->toString()) {
$actions[] = $action;
array_splice($filters, $k, 1);
}
diff --git a/app/Models/Search.php b/app/Models/Search.php
index 80db5a58c..4d33f36b2 100644
--- a/app/Models/Search.php
+++ b/app/Models/Search.php
@@ -447,7 +447,7 @@ class FreshRSS_Search implements \Stringable {
return trim($result);
}
- #[Deprecated('Use __tostring() instead')]
+ #[Deprecated('Use __toString() instead')]
public function getRawInput(): string {
return $this->raw_input;
}
diff --git a/app/Models/UserQuery.php b/app/Models/UserQuery.php
index 154f4d92c..919330e64 100644
--- a/app/Models/UserQuery.php
+++ b/app/Models/UserQuery.php
@@ -123,7 +123,7 @@ class FreshRSS_UserQuery {
'get' => $this->get,
'name' => $this->name,
'order' => $this->order,
- 'search' => $this->search->__toString(),
+ 'search' => $this->search->toString(expandUserQueries: false),
'state' => $this->state,
'url' => $this->url,
'token' => $this->token,
@@ -221,7 +221,7 @@ class FreshRSS_UserQuery {
* Check if there is a search in the search object
*/
public function hasSearch(): bool {
- return $this->search->__toString() !== '';
+ return $this->search->toString() !== '';
}
public function getGet(): string {
diff --git a/app/layout/header.phtml b/app/layout/header.phtml
index 73dfb3119..56856f84c 100644
--- a/app/layout/header.phtml
+++ b/app/layout/header.phtml
@@ -40,7 +40,7 @@
<?php } ?>
<div class="stick">
<input type="search" name="search" id="search"
- value="<?= htmlspecialchars(FreshRSS_Context::$search->__toString(), ENT_COMPAT, 'UTF-8') ?>"
+ value="<?= htmlspecialchars(FreshRSS_Context::$search->toString(), ENT_COMPAT, 'UTF-8') ?>"
placeholder="<?= _t('gen.menu.search') ?>" />
<button class="btn" type="submit"><?= _i('search') ?></button>
</div>
diff --git a/app/layout/nav_menu.phtml b/app/layout/nav_menu.phtml
index 3c3a34243..debc06611 100644
--- a/app/layout/nav_menu.phtml
+++ b/app/layout/nav_menu.phtml
@@ -39,7 +39,7 @@
<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
<div id="dropdown-search" class="dropdown-target"></div>
- <a id="toggle-search" class="dropdown-toggle btn<?= FreshRSS_Context::$search->__toString() !== '' ? ' active' : ''; ?>" title="<?= _t('gen.menu.search') ?>"
+ <a id="toggle-search" class="dropdown-toggle btn<?= FreshRSS_Context::$search->toString() !== '' ? ' active' : ''; ?>" title="<?= _t('gen.menu.search') ?>"
href="#dropdown-search"><?= _i('search') ?></a>
<ul class="dropdown-menu">
<li class="item">
@@ -56,7 +56,7 @@
<?php } ?>
<div class="stick search">
<input type="search" name="search"
- value="<?= htmlspecialchars(FreshRSS_Context::$search->__toString(), ENT_COMPAT, 'UTF-8') ?>"
+ value="<?= htmlspecialchars(FreshRSS_Context::$search->toString(), ENT_COMPAT, 'UTF-8') ?>"
placeholder="<?= _t('gen.menu.search') ?>" title="<?= _t('gen.menu.search') ?>" /><button class="btn" type="submit" title="<?= _t('index.menu.search_short') ?>"><?= _i('search') ?></button>
</div>
<p class="help"><?= _i('help') ?> <a href="<?= _url('search', 'index') ?>"><?= _t('gen.menu.advanced_search') ?></a></p>
@@ -121,7 +121,7 @@
'get' => $get,
'nextGet' => FreshRSS_Context::$next_get,
'idMax' => FreshRSS_Context::$id_max,
- 'search' => FreshRSS_Context::$search->__toString(),
+ 'search' => FreshRSS_Context::$search->toString(),
'state' => FreshRSS_Context::$state,
'sort' => FreshRSS_Context::$sort,
'order' => FreshRSS_Context::$order,
diff --git a/app/views/configure/queries.phtml b/app/views/configure/queries.phtml
index 1b36c88bd..152e2746d 100644
--- a/app/views/configure/queries.phtml
+++ b/app/views/configure/queries.phtml
@@ -24,7 +24,7 @@
<input type="hidden" id="queries_<?= $key ?>_shareRss" name="queries[<?= $key ?>][shareRss]" value="<?= $query->shareRss() ? '1' : '0' ?>"/>
<input type="hidden" id="queries_<?= $key ?>_shareOpml" name="queries[<?= $key ?>][shareOpml]" value="<?= $query->shareOpml() ? '1' : '0' ?>"/>
<input type="hidden" id="queries_<?= $key ?>_url" name="queries[<?= $key ?>][url]" value="<?= $query->getUrl() ?>"/>
- <input type="hidden" id="queries_<?= $key ?>_search" name="queries[<?= $key ?>][search]" value="<?= urlencode($query->getSearch()->__toString()) ?>"/>
+ <input type="hidden" id="queries_<?= $key ?>_search" name="queries[<?= $key ?>][search]" value="<?= urlencode($query->getSearch()->toString(expandUserQueries: false)) ?>"/>
<input type="hidden" id="queries_<?= $key ?>_state" name="queries[<?= $key ?>][state]" value="<?= $query->getState() ?>"/>
<input type="hidden" id="queries_<?= $key ?>_order" name="queries[<?= $key ?>][order]" value="<?= $query->getOrder() ?>"/>
<input type="hidden" id="queries_<?= $key ?>_get" name="queries[<?= $key ?>][get]" value="<?= $query->getGet() ?>"/>
@@ -44,7 +44,7 @@
<?php } else { ?>
<ul class="box-content scrollbar-thin">
<?php if ($query->hasSearch()) { ?>
- <li class="item"><?= _t('conf.query.search', htmlspecialchars($query->getSearch()->__toString(), ENT_NOQUOTES, 'UTF-8')) ?></li>
+ <li class="item"><?= _t('conf.query.search', htmlspecialchars($query->getSearch()->toString(expandUserQueries: false), ENT_NOQUOTES, 'UTF-8')) ?></li>
<?php } ?>
<?php if ($query->getState()) { ?>
diff --git a/app/views/configure/reading.phtml b/app/views/configure/reading.phtml
index f761f7951..31daa7984 100644
--- a/app/views/configure/reading.phtml
+++ b/app/views/configure/reading.phtml
@@ -349,7 +349,7 @@
<div class="group-controls">
<textarea name="filteractions_read" id="filteractions_read" class="w100"><?php
foreach (FreshRSS_Context::userConf()->filtersAction('read') as $filterRead) {
- echo htmlspecialchars($filterRead->__toString(), ENT_NOQUOTES, 'UTF-8'), PHP_EOL;
+ echo htmlspecialchars($filterRead->toString(expandUserQueries: false), ENT_NOQUOTES, 'UTF-8'), PHP_EOL;
}
?></textarea>
<p class="help"><?= _i('help') ?> <?= _t('sub.feed.filteractions.help') ?></p>
@@ -366,7 +366,7 @@
<div class="group-controls">
<textarea name="filteractions_star" id="filteractions_star" class="w100"><?php
foreach (FreshRSS_Context::userConf()->filtersAction('star') as $filterStar) {
- echo htmlspecialchars($filterStar->__toString(), ENT_NOQUOTES, 'UTF-8'), PHP_EOL;
+ echo htmlspecialchars($filterStar->toString(expandUserQueries: false), ENT_NOQUOTES, 'UTF-8'), PHP_EOL;
}
?></textarea>
<p class="help"><?= _i('help') ?> <?= _t('sub.feed.filteractions.help') ?></p>
diff --git a/app/views/helpers/category/update.phtml b/app/views/helpers/category/update.phtml
index 77ee28f4e..b8e86662e 100644
--- a/app/views/helpers/category/update.phtml
+++ b/app/views/helpers/category/update.phtml
@@ -96,7 +96,7 @@
<div class="group-controls">
<textarea name="filteractions_read" id="filteractions_read" class="w100"><?php
foreach ($this->category->filtersAction('read') as $filterRead) {
- echo htmlspecialchars($filterRead->__toString(), ENT_NOQUOTES, 'UTF-8'), PHP_EOL;
+ echo htmlspecialchars($filterRead->toString(expandUserQueries: false), ENT_NOQUOTES, 'UTF-8'), PHP_EOL;
}
?></textarea>
<p class="help"><?= _i('help') ?> <?= _t('sub.feed.filteractions.help') ?></p>
diff --git a/app/views/helpers/configure/query.phtml b/app/views/helpers/configure/query.phtml
index fd3877bb6..192f656a7 100644
--- a/app/views/helpers/configure/query.phtml
+++ b/app/views/helpers/configure/query.phtml
@@ -92,7 +92,8 @@
<div class="form-group">
<label class="group-name" for=""><?= _t('conf.query.filter.search') ?></label>
<div class="group-controls">
- <input type="text" class="w100" id="query_search" name="query[search]" value="<?= htmlspecialchars($this->query->getSearch()->__toString(), ENT_COMPAT, 'UTF-8') ?>"/>
+ <input type="text" class="w100" id="query_search" name="query[search]" value="<?=
+ htmlspecialchars($this->query->getSearch()->toString(expandUserQueries: false), ENT_COMPAT, 'UTF-8') ?>"/>
<p class="help"><?= _i('help') ?> <?= _t('gen.menu.search_help') ?></a></p>
</div>
</div>
diff --git a/app/views/helpers/export/opml.phtml b/app/views/helpers/export/opml.phtml
index 2e67cd043..cc89edb49 100644
--- a/app/views/helpers/export/opml.phtml
+++ b/app/views/helpers/export/opml.phtml
@@ -89,7 +89,7 @@ function feedsToOutlines(array $feeds, bool $excludeMutedFeeds = false): array {
if (!empty($feed->filtersAction('read'))) {
$filters = '';
foreach ($feed->filtersAction('read') as $filterRead) {
- $filters .= $filterRead->__toString() . "\n";
+ $filters .= $filterRead->toString(expandUserQueries: false) . "\n";
}
$filters = trim($filters);
$outline['frss:filtersActionRead'] = $filters;
diff --git a/app/views/helpers/feed/update.phtml b/app/views/helpers/feed/update.phtml
index 73187cbab..3cdeb3f35 100644
--- a/app/views/helpers/feed/update.phtml
+++ b/app/views/helpers/feed/update.phtml
@@ -304,7 +304,7 @@
<textarea name="filteractions_read" id="filteractions_read" class="w100"
placeholder="<?= _t('gen.short.blank_to_disable') ?>"><?php
foreach ($this->feed->filtersAction('read') as $filterRead) {
- echo htmlspecialchars($filterRead->__toString(), ENT_NOQUOTES, 'UTF-8'), PHP_EOL;
+ echo htmlspecialchars($filterRead->toString(expandUserQueries: false), ENT_NOQUOTES, 'UTF-8'), PHP_EOL;
}
?></textarea>
<p class="help"><?= _i('help') ?> <?= _t('sub.feed.filteractions.help') ?></p>
diff --git a/app/views/helpers/stream-footer.phtml b/app/views/helpers/stream-footer.phtml
index 4fe93cf7d..d2334f2e8 100644
--- a/app/views/helpers/stream-footer.phtml
+++ b/app/views/helpers/stream-footer.phtml
@@ -18,7 +18,7 @@
'get' => FreshRSS_Context::currentGet(),
'nextGet' => FreshRSS_Context::$next_get,
'idMax' => FreshRSS_Context::$id_max,
- 'search' => FreshRSS_Context::$search->__toString(),
+ 'search' => FreshRSS_Context::$search->toString(),
'state' => FreshRSS_Context::$state,
'sort' => FreshRSS_Context::$sort,
'order' => FreshRSS_Context::$order,
diff --git a/app/views/tag/update.phtml b/app/views/tag/update.phtml
index b50d2d743..e0cb042f5 100644
--- a/app/views/tag/update.phtml
+++ b/app/views/tag/update.phtml
@@ -45,7 +45,7 @@
<div class="group-controls">
<textarea name="filteractions_label" id="filteractions_label" class="w100"><?php
foreach ($this->tag->filtersAction('label') as $filterRead) {
- echo htmlspecialchars($filterRead->__toString(), ENT_NOQUOTES, 'UTF-8') . PHP_EOL;
+ echo htmlspecialchars($filterRead->toString(expandUserQueries: false), ENT_NOQUOTES, 'UTF-8') . PHP_EOL;
}
?></textarea>
<p class="help"><?= _i('help') ?> <?= _t('sub.feed.filteractions.help') ?></p>