Bläddra i källkod

Rework encoding of search filters (#8324)

Rework:
* https://github.com/FreshRSS/FreshRSS/pull/8222

now that we have:
* https://github.com/FreshRSS/FreshRSS/pull/8293

Follow-up of:
* https://github.com/FreshRSS/FreshRSS/pull/8311

* More simplification

* Deprecate getRawInput
Alexandre Alapetite 3 månader sedan
förälder
incheckning
4bd5035914

+ 1 - 1
app/Controllers/categoryController.php

@@ -110,7 +110,7 @@ class FreshRSS_category_Controller extends FreshRSS_ActionController {
 				$category->_attribute('read_when_same_title_in_category', null);
 			}
 
-			$category->_filtersAction('read', Minz_Request::paramTextToArray('filteractions_read'));	// Keep as HTML
+			$category->_filtersAction('read', Minz_Request::paramTextToArray('filteractions_read', plaintext: true));
 
 			if (Minz_Request::paramBoolean('use_default_purge_options')) {
 				$category->_attribute('archiving', null);

+ 2 - 2
app/Controllers/configureController.php

@@ -164,8 +164,8 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
 				'site' => Minz_Request::paramBoolean('mark_open_site'),
 				'focus' => Minz_Request::paramBoolean('mark_focus'),
 			];
-			FreshRSS_Context::userConf()->_filtersAction('read', Minz_Request::paramTextToArray('filteractions_read'));	// Keep as HTML
-			FreshRSS_Context::userConf()->_filtersAction('star', Minz_Request::paramTextToArray('filteractions_star'));	// Keep as HTML
+			FreshRSS_Context::userConf()->_filtersAction('read', Minz_Request::paramTextToArray('filteractions_read', plaintext: true));
+			FreshRSS_Context::userConf()->_filtersAction('star', Minz_Request::paramTextToArray('filteractions_star', plaintext: true));
 			FreshRSS_Context::userConf()->save();
 			invalidateHttpCache();
 

+ 1 - 1
app/Controllers/indexController.php

@@ -94,7 +94,7 @@ 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->getRawInput() === '' ? $searchString :
+			'search' => FreshRSS_Context::$search->__toString() === '' ? $searchString :
 				FreshRSS_Context::$search->enforce(new FreshRSS_Search($searchString))->__toString(),
 			]));
 	}

+ 11 - 6
app/Controllers/subscriptionController.php

@@ -247,7 +247,7 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
 				]);
 			}
 
-			$feed->_filtersAction('read', Minz_Request::paramTextToArray('filteractions_read'));	// Keep as HTML
+			$feed->_filtersAction('read', Minz_Request::paramTextToArray('filteractions_read', plaintext: true));
 
 			$feed->_kind(Minz_Request::paramInt('feed_kind') ?: FreshRSS_Feed::KIND_RSS);
 			if ($feed->kind() === FreshRSS_Feed::KIND_HTML_XPATH || $feed->kind() === FreshRSS_Feed::KIND_XML_XPATH) {
@@ -418,17 +418,22 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
 		$filteractions = Minz_Request::paramTextToArray('filteractions_read', plaintext: true);
 		$filteractions = array_map(fn(string $action): string => trim($action), $filteractions);
 		$filteractions = array_filter($filteractions, fn(string $action): bool => $action !== '');
-		$search = "f:$id (";
+		$actionsSearch = new FreshRSS_BooleanSearch('', operator: 'AND');
 		foreach ($filteractions as $action) {
-			$search .= "($action) OR ";
+			$actionSearch = new FreshRSS_BooleanSearch($action, operator: 'OR');
+			if ($actionSearch->__toString() === '') {
+				continue;
+			}
+			$actionsSearch->add($actionSearch);
 		}
-		$search = preg_replace('/ OR $/', '', $search);
-		$search .= ')';
+		$search = new FreshRSS_BooleanSearch('');
+		$search->add(new FreshRSS_Search("f:$id"));
+		$search->add($actionsSearch);
 		Minz_Request::forward([
 			'c' => 'index',
 			'a' => 'index',
 			'params' => [
-				'search' => $search,
+				'search' => $search->__toString(),
 			],
 		], redirect: true);
 	}

+ 1 - 0
app/Models/BooleanSearch.php

@@ -525,6 +525,7 @@ class FreshRSS_BooleanSearch implements \Stringable {
 	}
 
 	/** @return string Plain text search query. Must be XML-encoded or URL-encoded depending on the situation */
+	#[Deprecated('Use __tostring() instead')]
 	public function getRawInput(): string {
 		return $this->raw_input;
 	}

+ 2 - 2
app/Models/FilterAction.php

@@ -29,11 +29,11 @@ class FreshRSS_FilterAction {
 		}
 	}
 
-	/** @return array{'search'?:string,'actions'?:array<string>} */
+	/** @return array{search?:string,actions?:array<string>} */
 	public function toJSON(): array {
 		if (is_array($this->actions) && $this->booleanSearch != null) {
 			return [
-				'search' => $this->booleanSearch->getRawInput(),
+				'search' => $this->booleanSearch->__toString(),
 				'actions' => $this->actions,
 			];
 		}

+ 2 - 3
app/Models/FilterActionsTrait.php

@@ -71,8 +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() == null || trim($filterAction->booleanSearch()->getRawInput()) == '') {
+			if ($filterAction === null || !is_array($filterAction->actions()) || $filterAction->booleanSearch()->__toString() === '') {
 				array_splice($filterActions, $i, 1);
 				continue;
 			}
@@ -86,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()->getRawInput()) {
+				if ($filter === $filterAction->booleanSearch()->__toString()) {
 					$actions[] = $action;
 					array_splice($filters, $k, 1);
 				}

+ 1 - 0
app/Models/Search.php

@@ -452,6 +452,7 @@ class FreshRSS_Search implements \Stringable {
 		return trim($result);
 	}
 
+	#[Deprecated('Use __tostring() instead')]
 	public function getRawInput(): string {
 		return $this->raw_input;
 	}

+ 2 - 2
app/Models/UserQuery.php

@@ -123,7 +123,7 @@ class FreshRSS_UserQuery {
 			'get' => $this->get,
 			'name' => $this->name,
 			'order' => $this->order,
-			'search' => $this->search->getRawInput(),
+			'search' => $this->search->__toString(),
 			'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->getRawInput() !== '';
+		return $this->search->__toString() !== '';
 	}
 
 	public function getGet(): string {

+ 3 - 3
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<?= (strlen(FreshRSS_Context::$search->getRawInput()) > 0) ? ' 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(htmlspecialchars_decode(FreshRSS_Context::$search->getRawInput(), ENT_QUOTES), 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' => htmlspecialchars_decode(FreshRSS_Context::$search->getRawInput(), ENT_QUOTES),
+				'search' => FreshRSS_Context::$search->__toString(),
 				'state' => FreshRSS_Context::$state,
 				'sort' => FreshRSS_Context::$sort,
 				'order' => FreshRSS_Context::$order,

+ 2 - 2
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()->getRawInput()) ?>"/>
+							<input type="hidden" id="queries_<?= $key ?>_search" name="queries[<?= $key ?>][search]" value="<?= urlencode($query->getSearch()->__toString()) ?>"/>
 							<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()->getRawInput(), ENT_NOQUOTES, 'UTF-8')) ?></li>
+							<li class="item"><?= _t('conf.query.search', htmlspecialchars($query->getSearch()->__toString(), ENT_NOQUOTES, 'UTF-8')) ?></li>
 							<?php } ?>
 
 							<?php if ($query->getState()) { ?>

+ 2 - 2
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 $filterRead->getRawInput(), PHP_EOL;
+							echo htmlspecialchars($filterRead->__toString(), 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 $filterStar->getRawInput(), PHP_EOL;
+							echo htmlspecialchars($filterStar->__toString(), ENT_NOQUOTES, 'UTF-8'), PHP_EOL;
 						}
 					?></textarea>
 					<p class="help"><?= _i('help') ?> <?= _t('sub.feed.filteractions.help') ?></p>

+ 1 - 1
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 $filterRead->getRawInput(), PHP_EOL;
+							echo htmlspecialchars($filterRead->__toString(), ENT_NOQUOTES, 'UTF-8'), PHP_EOL;
 						}
 					?></textarea>
 					<p class="help"><?= _i('help') ?> <?= _t('sub.feed.filteractions.help') ?></p>

+ 1 - 1
app/views/helpers/configure/query.phtml

@@ -92,7 +92,7 @@
 			<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()->getRawInput(), ENT_COMPAT, 'UTF-8') ?>"/>
+					<input type="text" class="w100" id="query_search" name="query[search]" value="<?= htmlspecialchars($this->query->getSearch()->__toString(), ENT_COMPAT, 'UTF-8') ?>"/>
 					<p class="help"><?= _i('help') ?> <?= _t('gen.menu.search_help') ?></a></p>
 				</div>
 			</div>

+ 1 - 1
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->getRawInput() . "\n";
+				$filters .= $filterRead->__toString() . "\n";
 			}
 			$filters = trim($filters);
 			$outline['frss:filtersActionRead'] = $filters;

+ 1 - 1
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 $filterRead->getRawInput(), PHP_EOL;
+							echo htmlspecialchars($filterRead->__toString(), ENT_NOQUOTES, 'UTF-8'), PHP_EOL;
 						}
 					?></textarea>
 					<p class="help"><?= _i('help') ?> <?= _t('sub.feed.filteractions.help') ?></p>

+ 1 - 1
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' => htmlspecialchars_decode(FreshRSS_Context::$search->getRawInput(), ENT_QUOTES),
+			'search' => FreshRSS_Context::$search->__toString(),
 			'state' => FreshRSS_Context::$state,
 			'sort' => FreshRSS_Context::$sort,
 			'order' => FreshRSS_Context::$order,

+ 1 - 1
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 $filterRead->getRawInput(), PHP_EOL;
+							echo htmlspecialchars($filterRead->__toString(), ENT_NOQUOTES, 'UTF-8') . PHP_EOL;
 						}
 					?></textarea>
 					<p class="help"><?= _i('help') ?> <?= _t('sub.feed.filteractions.help') ?></p>

+ 1 - 1
p/api/query.php

@@ -117,7 +117,7 @@ $view = new FreshRSS_View();
 
 try {
 	FreshRSS_Context::updateUsingRequest(false);
-	Minz_Request::_param('search', $userSearch->getRawInput());	// 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!');

+ 3 - 2
tests/app/Models/SearchTest.php

@@ -10,7 +10,7 @@ final class SearchTest extends \PHPUnit\Framework\TestCase {
 	#[DataProvider('provideEmptyInput')]
 	public static function test__construct_whenInputIsEmpty_getsOnlyNullValues(string $input): void {
 		$search = new FreshRSS_Search($input);
-		self::assertSame('', $search->getRawInput());
+		self::assertSame('', $search->__toString());
 		self::assertNull($search->getIntitle());
 		self::assertNull($search->getMinDate());
 		self::assertNull($search->getMaxDate());
@@ -327,7 +327,6 @@ final class SearchTest extends \PHPUnit\Framework\TestCase {
 		self::assertSame($max_pubdate_value, $search->getMaxPubdate());
 		self::assertSame($tags_value, $search->getTags());
 		self::assertSame($search_value, $search->getSearch());
-		self::assertSame($input, $search->getRawInput());
 	}
 
 	/** @return list<list<mixed>> */
@@ -1033,7 +1032,9 @@ final class SearchTest extends \PHPUnit\Framework\TestCase {
 	 */
 	public static function provideBooleanSearchEnforce(): array {
 		return [
+			['', '', ''],
 			['', 'intitle:b', 'intitle:b'],
+			['intitle:a', '', 'intitle:a'],
 			['intitle:a', 'intitle:b', 'intitle:b'],
 			['a', 'intitle:b', 'intitle:b a'],
 			['intitle:a intext:a', 'intitle:b', 'intitle:b intext:a'],