diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/Controllers/indexController.php | 6 | ||||
| -rw-r--r-- | app/Controllers/subscriptionController.php | 4 | ||||
| -rw-r--r-- | app/Models/BooleanSearch.php | 88 | ||||
| -rw-r--r-- | app/Models/FilterAction.php | 2 | ||||
| -rw-r--r-- | app/Models/FilterActionsTrait.php | 4 | ||||
| -rw-r--r-- | app/Models/Search.php | 2 | ||||
| -rw-r--r-- | app/Models/UserQuery.php | 4 | ||||
| -rw-r--r-- | app/layout/header.phtml | 2 | ||||
| -rw-r--r-- | app/layout/nav_menu.phtml | 6 | ||||
| -rw-r--r-- | app/views/configure/queries.phtml | 4 | ||||
| -rw-r--r-- | app/views/configure/reading.phtml | 4 | ||||
| -rw-r--r-- | app/views/helpers/category/update.phtml | 2 | ||||
| -rw-r--r-- | app/views/helpers/configure/query.phtml | 3 | ||||
| -rw-r--r-- | app/views/helpers/export/opml.phtml | 2 | ||||
| -rw-r--r-- | app/views/helpers/feed/update.phtml | 2 | ||||
| -rw-r--r-- | app/views/helpers/stream-footer.phtml | 2 | ||||
| -rw-r--r-- | app/views/tag/update.phtml | 2 |
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> |
