Просмотр исходного кода

Preserve explicit sort overrides when searching (#9467)

* Preserve explicit sort overrides when searching

Closes #9466

Changes proposed in this pull request:

- Keep an explicit sort override when submitting the header search form, including `c.name`, `f.name` and `lastUserModified`.
- Continue using the already validated `FreshRSS_Context::$sort` value; omit the hidden field when no non-empty override was requested.
- Add header-rendering regression tests for all nine supported sorts, absent/empty overrides and invalid input.

* A bit more fixing

* Fix line mistake

* Forgotten plaintext

* Fix test

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
spa77k 13 часов назад
Родитель
Сommit
c5dd38b0d5
3 измененных файлов с 58 добавлено и 6 удалено
  1. 2 2
      app/layout/aside_feed.phtml
  2. 4 4
      app/layout/header.phtml
  3. 52 0
      tests/app/Views/headerTest.php

+ 2 - 2
app/layout/aside_feed.phtml

@@ -14,10 +14,10 @@
 		$state_filter_manual .= '&state=' . $s;
 	}
 	if (FreshRSS_Context::userConf()->sticky_sort) {
-		if (($s = Minz_Request::paramString('sort', plaintext: true)) !== '' && preg_match('/^[a-z.]+$/', $s)) {
+		if (($s = Minz_Request::paramString('sort', plaintext: true)) !== '' && preg_match('/^[a-zA-Z.]{2,16}$/', $s)) {
 			$state_filter_manual .= '&sort=' . $s;
 		}
-		if (($s = Minz_Request::paramString('order', plaintext: true)) !== '' && ctype_alpha($s)) {
+		if (($s = Minz_Request::paramString('order', plaintext: true)) !== '' && ctype_upper($s)) {
 			$state_filter_manual .= '&order=' . $s;
 		}
 	}

+ 4 - 4
app/layout/header.phtml

@@ -32,13 +32,13 @@
 			<?php endif; ?>
 			<?php if (Minz_Request::paramString('user') !== '') { ?>
 			<input type="hidden" name="user" value="<?= Minz_User::name() ?>" />
-			<?php } if (ctype_alnum(Minz_Request::paramString('t'))) { ?>
+			<?php } if (ctype_alnum(Minz_Request::paramString('t', plaintext: true))) { ?>
 			<input type="hidden" name="t" value="<?= Minz_Request::paramString('t') ?>" />
-			<?php } if (ctype_lower(Minz_Request::paramString('sort'))) { ?>
+			<?php } if (Minz_Request::paramString('sort', plaintext: true) !== '' && preg_match('/^[a-zA-Z.]{2,16}$/', Minz_Request::paramString('sort', plaintext: true))) { ?>
 			<input type="hidden" name="sort" value="<?= FreshRSS_Context::$sort ?>" />
-			<?php } if (ctype_upper(Minz_Request::paramString('order'))) { ?>
+			<?php } if (ctype_upper(Minz_Request::paramString('order', plaintext: true))) { ?>
 			<input type="hidden" name="order" value="<?= FreshRSS_Context::$order ?>" />
-			<?php } if (ctype_lower(Minz_Request::paramString('f'))) { ?>
+			<?php } if (ctype_lower(Minz_Request::paramString('f', plaintext: true))) { // Format ?>
 			<input type="hidden" name="f" value="<?= Minz_Request::paramString('f') ?>" />
 			<?php } ?>
 			<div class="stick">

+ 52 - 0
tests/app/Views/headerTest.php

@@ -0,0 +1,52 @@
+<?php
+declare(strict_types=1);
+
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\PreserveGlobalState;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+
+#[RunTestsInSeparateProcesses]
+#[PreserveGlobalState(false)]
+final class headerTest extends \PHPUnit\Framework\TestCase {
+
+	/** @return Traversable<string,array{?string,?string}> */
+	public static function provideSearchSorts(): Traversable {
+		foreach (['id', 'c.name', 'date', 'f.name', 'lastUserModified', 'length', 'link', 'title', 'rand'] as $sort) {
+			yield $sort => [$sort, $sort];
+		}
+		yield 'no override' => [null, null];
+		yield 'empty override' => ['', null];
+		yield 'invalid override' => ['" onfocus="alert(1)', null];
+	}
+
+	#[DataProvider('provideSearchSorts')]
+	public function testSearchPreservesSort(?string $requestedSort, ?string $expectedSort): void {
+		FreshRSS_Context::initSystem();
+		FreshRSS_Context::systemConf()->allow_anonymous = true;
+		Minz_User::change(FreshRSS_Context::systemConf()->default_user);
+		FreshRSS_Context::setUserConf(clone FreshRSS_UserConfiguration::default());
+		Minz_Translate::init('en');
+		Minz_Request::_params($requestedSort === null ? [] : ['sort' => $requestedSort]);
+		// The context has already validated the requested sort before rendering the header.
+		FreshRSS_Context::$sort = match ($expectedSort) {
+			'c.name', 'date', 'f.name', 'lastUserModified', 'length', 'link', 'title', 'rand' => $expectedSort,
+			default => 'id',
+		};
+		FreshRSS_Context::$search = new FreshRSS_BooleanSearch('needle');
+
+		$view = new FreshRSS_View();
+		$view->_path('../layout/header.phtml');
+		$view->html_url = 'https://example.net/';
+		$html = new DOMDocument();
+		self::assertTrue($html->loadHTML($view->renderToString(), LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING));
+		$inputs = (new DOMXPath($html))->query('//form[.//input[@name="search"]]/input[@name="sort"]');
+		self::assertNotFalse($inputs);
+		self::assertSame($expectedSort === null ? 0 : 1, $inputs->length);
+		if ($expectedSort !== null) {
+			$input = $inputs->item(0);
+			self::assertInstanceOf(DOMElement::class, $input);
+			self::assertSame('hidden', $input->getAttribute('type'));
+			self::assertSame($expectedSort, $input->getAttribute('value'));
+		}
+	}
+}