diff options
| -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 | ||||
| -rw-r--r-- | p/api/query.php | 10 | ||||
| -rw-r--r-- | tests/app/Models/SearchTest.php | 108 |
19 files changed, 184 insertions, 73 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> diff --git a/p/api/query.php b/p/api/query.php index 7534a9e85..0ee79567d 100644 --- a/p/api/query.php +++ b/p/api/query.php @@ -92,14 +92,14 @@ foreach (FreshRSS_Context::userConf()->queries as $raw_query) { } Minz_Request::_param('state', (string)$query->getState()); - $search = $query->getSearch()->__toString(); + $search = $query->getSearch()->toString(); // Note: we disallow references to user queries in public user search to avoid sniffing internal user queries $userSearch = new FreshRSS_BooleanSearch(Minz_Request::paramString('search', plaintext: true), 0, 'AND', allowUserQueries: false); - if ($userSearch->__toString() !== '') { + if ($userSearch->toString() !== '') { if ($search === '') { - $search = $userSearch->__toString(); + $search = $userSearch->toString(); } else { - $search .= ' (' . $userSearch->__toString() . ')'; + $search .= ' (' . $userSearch->toString() . ')'; } } Minz_Request::_param('search', $search); @@ -117,7 +117,7 @@ $view = new FreshRSS_View(); try { FreshRSS_Context::updateUsingRequest(false); - Minz_Request::_param('search', $userSearch->__toString()); // Restore user search + Minz_Request::_param('search', $userSearch->toString()); // Restore user search $view->entries = FreshRSS_index_Controller::listEntriesByContext(); } catch (Minz_Exception) { Minz_Error::error(400, 'Bad user query!'); diff --git a/tests/app/Models/SearchTest.php b/tests/app/Models/SearchTest.php index 9e107a79d..09db13205 100644 --- a/tests/app/Models/SearchTest.php +++ b/tests/app/Models/SearchTest.php @@ -243,11 +243,11 @@ final class SearchTest extends \PHPUnit\Framework\TestCase { } /** - * @param list<array{search:string}> $queries + * @param list<array{search:string,name?:string}> $queries * @param array{0:string,1:list<string|int>} $expectedResult */ - #[DataProvider('provideSavedQueryIdExpansion')] - public static function test__construct_whenInputContainsSavedQueryIds_expandsSavedSearches(array $queries, string $input, array $expectedResult): void { + #[DataProvider('provideSavedQueriesExpansion')] + public static function test__construct_whenInputContainsSavedQueries_expandsSavedSearches(array $queries, string $input, array $expectedResult): void { $previousUserConf = FreshRSS_Context::hasUserConf() ? FreshRSS_Context::userConf() : null; $newUserConf = $previousUserConf instanceof FreshRSS_UserConfiguration ? clone $previousUserConf : clone FreshRSS_UserConfiguration::default(); $newUserConf->queries = $queries; @@ -266,14 +266,36 @@ final class SearchTest extends \PHPUnit\Framework\TestCase { /** * @return array<string,array{0:list<array{search:string}>,1:string,2:array{0:string,1:list<string|int>}}> */ - public static function provideSavedQueryIdExpansion(): array { + public static function provideSavedQueriesExpansion(): array { return [ - 'expanded single group' => [ + 'not found ID' => [ [ ['search' => 'author:Alice'], ['search' => 'intitle:World'], ], - 'S:0,1', + 'S:3', + [ + '', + [], + ], + ], + 'not found name' => [ + [ + ['search' => 'author:Alice', 'name' => 'First'], + ['search' => 'intitle:World', 'name' => 'Second'], + ], + 'search:Third', + [ + '', + [], + ], + ], + 'expanded single group name' => [ + [ + ['search' => 'author:Alice', 'name' => 'First'], + ['search' => 'intitle:World', 'name' => 'Second'], + ], + 'search:First OR search:Second', [ '((e.author LIKE ? )) OR ((e.title LIKE ? ))', ['%Alice%', '%World%'], @@ -286,7 +308,7 @@ final class SearchTest extends \PHPUnit\Framework\TestCase { ['search' => 'inurl:Example'], ['search' => 'author:Bob'], ], - 'S:0,1 OR S:2,3', + 'S:0,1 OR S:2,3,5', [ '((e.author LIKE ? )) OR ((e.title LIKE ? )) OR ((e.link LIKE ? )) OR ((e.author LIKE ? ))', ['%Alice%', '%World%', '%Example%', '%Bob%'], @@ -970,9 +992,9 @@ final class SearchTest extends \PHPUnit\Framework\TestCase { } #[DataProvider('provideBooleanSearchToString')] - public static function testBooleanSearch__toString(string $input, string $expected): void { + public static function testBooleanSearchToString(string $input, string $expected): void { $search = new FreshRSS_BooleanSearch($input); - self::assertSame($expected, $search->__toString()); + self::assertSame($expected, $search->toString()); } /** @@ -1023,6 +1045,70 @@ final class SearchTest extends \PHPUnit\Framework\TestCase { ]; } + /** + * @param list<array{search:string,name?:string}> $queries + */ + #[DataProvider('provideBooleanSearchToStringExpansion')] + public static function testBooleanSearchToStringExpansion(array $queries, string $input, + string $expectedNotExpanded, string $expectedExpanded): void { + $previousUserConf = FreshRSS_Context::hasUserConf() ? FreshRSS_Context::userConf() : null; + $newUserConf = $previousUserConf instanceof FreshRSS_UserConfiguration ? clone $previousUserConf : clone FreshRSS_UserConfiguration::default(); + $newUserConf->queries = $queries; + FreshRSS_Context::setUserConf($newUserConf); + + try { + $booleanSearch = new FreshRSS_BooleanSearch($input); + self::assertSame($expectedNotExpanded, $booleanSearch->toString(expandUserQueries: false)); + self::assertSame($expectedExpanded, $booleanSearch->toString()); + } finally { + FreshRSS_Context::setUserConf($previousUserConf); + } + } + + /** + * @return array<string,array{0:list<array{search:string,name?:string}>,1:string,2:string,3:string}> + */ + public static function provideBooleanSearchToStringExpansion(): array { + return [ + 'Not found ID' => [ + [ + ['search' => 'author:Alice'], + ['search' => 'intitle:World'], + ], + 'S:3 S:4,5 ', + 'S:3 S:4,5', + '', + ], + 'Not found name' => [ + [ + ['search' => 'author:Alice', 'name' => 'First'], + ['search' => 'intitle:World', 'name' => 'Second'], + ], + 'search:Third ', + 'search:Third', + '', + ], + 'Found IDs' => [ + [ + ['search' => 'author:Alice', 'name' => 'First'], + ['search' => 'intitle:World', 'name' => 'Second'], + ], + 'S:0,1 ', + 'S:0,1', + 'author:Alice OR intitle:World', + ], + 'Found names' => [ + [ + ['search' => 'author:Alice', 'name' => 'First'], + ['search' => 'intitle:World', 'name' => 'Second'], + ], + 'search:First search:Second ', + 'search:First search:Second', + 'author:Alice intitle:World', + ], + ]; + } + #[DataProvider('provideHasSameOperators')] public function testHasSameOperators(string $input1, string $input2, bool $expected): void { $search1 = new FreshRSS_Search($input1); @@ -1047,7 +1133,7 @@ final class SearchTest extends \PHPUnit\Framework\TestCase { $searchToEnforce = new FreshRSS_Search($enforceInput); $newBooleanSearch = $booleanSearch->enforce($searchToEnforce); self::assertNotSame($booleanSearch, $newBooleanSearch); - self::assertSame($expectedOutput, $newBooleanSearch->__toString()); + self::assertSame($expectedOutput, $newBooleanSearch->toString()); } /** @@ -1081,7 +1167,7 @@ final class SearchTest extends \PHPUnit\Framework\TestCase { $searchToRemove = new FreshRSS_Search($removeInput); $newBooleanSearch = $booleanSearch->remove($searchToRemove); self::assertNotSame($booleanSearch, $newBooleanSearch); - self::assertSame($expectedOutput, $newBooleanSearch->__toString()); + self::assertSame($expectedOutput, $newBooleanSearch->toString()); } /** |
