From 931a6b3bbd66d75dcbe87d1bee48c8c4100dcae2 Mon Sep 17 00:00:00 2001 From: Mehmet <101359750+KoseceMehmet@users.noreply.github.com> Date: Mon, 9 Nov 2026 00:47:48 +0300 Subject: [PATCH] Shared queries: combine user search structurally (#9544) Follow-up to #9541. In `p/api/query.php`, the optional `search` parameter of a shared user query was appended as a string to the user query's own search, and the result was parsed again. This keeps both searches as parsed objects and combines them with a structural AND via `FreshRSS_BooleanSearch::add()`, avoiding the serialise/re-parse round trip. Adds a test for combining Boolean searches with `add()`. --- p/api/query.php | 22 +++++++++++++--------- tests/app/Models/SearchTest.php | 9 +++++++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/p/api/query.php b/p/api/query.php index 73d755e8d..55e9b0207 100644 --- a/p/api/query.php +++ b/p/api/query.php @@ -101,7 +101,6 @@ foreach (FreshRSS_Context::userConf()->queries as $raw_query) { } Minz_Request::_param('state', (string)$query->getState()); - $search = $query->getSearch()->toString(); // Note: we disallow references to user queries in public user search to avoid sniffing internal user queries try { $userSearch = new FreshRSS_BooleanSearch(Minz_Request::paramString('search', plaintext: true), 0, 'AND', allowUserQueries: false); @@ -110,14 +109,8 @@ foreach (FreshRSS_Context::userConf()->queries as $raw_query) { header('Content-Type: text/plain; charset=UTF-8'); die($e->getMessage()); } - if ($userSearch->toString() !== '') { - if ($search === '') { - $search = $userSearch->toString(); - } else { - $search .= ' (' . $userSearch->toString() . ')'; - } - } - Minz_Request::_param('search', $search); + // Only the search of the user query is passed as a string; the user search is combined structurally below + Minz_Request::_param('search', $query->getSearch()->toString()); break; } } @@ -132,6 +125,17 @@ $view = new FreshRSS_View(); try { FreshRSS_Context::updateUsingRequest(false); + if ($userSearch->toString() !== '') { + if (FreshRSS_Context::$search->toString() === '') { + FreshRSS_Context::$search = $userSearch; + } else { + // Combine with a structural AND, so that the user search cannot alter the search of the user query + $combinedSearch = new FreshRSS_BooleanSearch(''); + $combinedSearch->add(FreshRSS_Context::$search); + $combinedSearch->add($userSearch); + FreshRSS_Context::$search = $combinedSearch; + } + } $view->entries = FreshRSS_index_Controller::listEntriesByContext(); if (!$view->entries->valid()) { // Init the generator to consume the aggregated search and catch potential exceptions $view->entries = new EmptyIterator(); diff --git a/tests/app/Models/SearchTest.php b/tests/app/Models/SearchTest.php index e3f06d4a2..6a377295b 100644 --- a/tests/app/Models/SearchTest.php +++ b/tests/app/Models/SearchTest.php @@ -757,6 +757,15 @@ final class SearchTest extends \PHPUnit\Framework\TestCase { self::assertSame('(e.id >= ?) OR (e.`lastModified` >= ?)', $filterSearch); self::assertSame([$startTime . '000000', $startTime], $filterValues); } + public function test__add_boolean_searches_combines_with_and(): void { + $combined = new FreshRSS_BooleanSearch(''); + $combined->add(new FreshRSS_BooleanSearch('ab')); + $combined->add(new FreshRSS_BooleanSearch('cd OR ef', 0, 'AND', allowUserQueries: false)); + self::assertSame('ab (cd OR ef)', $combined->toString()); + + [$filterValues] = FreshRSS_EntryDAOPGSQL::sqlBooleanSearch('e.', $combined); + self::assertSame(['%ab%', '%ab%', '%cd%', '%cd%', '%ef%', '%ef%'], $filterValues); + } /** * @param array $values