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
This commit is contained in:
Alexandre Alapetite
2025-12-17 10:07:52 +01:00
committed by GitHub
parent 6952a13958
commit 4bd5035914
20 changed files with 40 additions and 33 deletions

View File

@@ -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);

View File

@@ -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();

View File

@@ -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(),
]));
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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,
];
}

View File

@@ -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);
}

View File

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

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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()) { ?>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>

View File

@@ -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,

View File

@@ -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>

View File

@@ -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!');

View File

@@ -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'],