From 7e72033859f60f529d4d985da1e9cac0a691b2dc Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 15 Oct 2025 00:12:19 +0200 Subject: Filter on last user modified (#8093) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Example: `userdate:PT1H` to select only articles modified by user during the last hour Fix https://github.com/FreshRSS/FreshRSS/issues/4280#issuecomment-3393078024 Useful for instance to bulk mark as unread recently marked articles by error: 1. Click on the toggle button to show the read articles (making sure the toggle for the unread articles is off) 2. Sort by *User modified 9→1* 3. Filter by *user modified date*, for instance to the last 3 hours by typing `userdate:PT3H` 4. Click in the drop-down menu *Mark selection as unread* P.S.: I have added at the same time a bunch of unit tests for date-related logic --- tests/app/Models/SearchTest.php | 142 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 136 insertions(+), 6 deletions(-) (limited to 'tests/app') diff --git a/tests/app/Models/SearchTest.php b/tests/app/Models/SearchTest.php index 19279fa65..90aca6a24 100644 --- a/tests/app/Models/SearchTest.php +++ b/tests/app/Models/SearchTest.php @@ -167,9 +167,9 @@ final class SearchTest extends \PHPUnit\Framework\TestCase { */ public static function provideDateSearch(): array { return [ - ['date:2007-03-01T13:00:00Z/2008-05-11T15:30:00Z', 1172754000, 1210519800], - ['date:2007-03-01T13:00:00Z/P1Y2M10DT2H30M', 1172754000, 1210519799], - ['date:P1Y2M10DT2H30M/2008-05-11T15:30:00Z', 1172754001, 1210519800], + ['date:2007-03-01T13:00:00Z/2008-05-11T15:30:00Z', strtotime('2007-03-01T13:00:00Z'), strtotime('2008-05-11T15:30:00Z')], + ['date:2007-03-01T13:00:00Z/P1Y2M10DT2H30M', strtotime('2007-03-01T13:00:00Z'), strtotime('2008-05-11T15:29:59Z')], + ['date:P1Y2M10DT2H30M/2008-05-11T15:30:00Z', strtotime('2007-03-01T13:00:01Z'), strtotime('2008-05-11T15:30:00Z')], ['date:2007-03-01/2008-05-11', strtotime('2007-03-01'), strtotime('2008-05-12') - 1], ['date:2007-03-01/', strtotime('2007-03-01'), null], ['date:/2008-05-11', null, strtotime('2008-05-12') - 1], @@ -188,15 +188,32 @@ final class SearchTest extends \PHPUnit\Framework\TestCase { */ public static function providePubdateSearch(): array { return [ - ['pubdate:2007-03-01T13:00:00Z/2008-05-11T15:30:00Z', 1172754000, 1210519800], - ['pubdate:2007-03-01T13:00:00Z/P1Y2M10DT2H30M', 1172754000, 1210519799], - ['pubdate:P1Y2M10DT2H30M/2008-05-11T15:30:00Z', 1172754001, 1210519800], + ['pubdate:2007-03-01T13:00:00Z/2008-05-11T15:30:00Z', strtotime('2007-03-01T13:00:00Z'), strtotime('2008-05-11T15:30:00Z')], + ['pubdate:2007-03-01T13:00:00Z/P1Y2M10DT2H30M', strtotime('2007-03-01T13:00:00Z'), strtotime('2008-05-11T15:29:59Z')], + ['pubdate:P1Y2M10DT2H30M/2008-05-11T15:30:00Z', strtotime('2007-03-01T13:00:01Z'), strtotime('2008-05-11T15:30:00Z')], ['pubdate:2007-03-01/2008-05-11', strtotime('2007-03-01'), strtotime('2008-05-12') - 1], ['pubdate:2007-03-01/', strtotime('2007-03-01'), null], ['pubdate:/2008-05-11', null, strtotime('2008-05-12') - 1], ]; } + #[DataProvider('provideUserdateSearch')] + public static function test__construct_whenInputContainsUserdate(string $input, ?int $min_userdate_value, ?int $max_userdate_value): void { + $search = new FreshRSS_Search($input); + self::assertSame($min_userdate_value, $search->getMinUserdate()); + self::assertSame($max_userdate_value, $search->getMaxUserdate()); + } + + /** + * @return list> + */ + public static function provideUserdateSearch(): array { + return [ + ['userdate:2007-03-01T13:00:00Z/2008-05-11T15:30:00Z', strtotime('2007-03-01T13:00:00Z'), strtotime('2008-05-11T15:30:00Z')], + ['userdate:/2008-05-11', null, strtotime('2008-05-12') - 1], + ]; + } + /** * @param array|null $tags_value * @param array|null $search_value @@ -583,6 +600,119 @@ final class SearchTest extends \PHPUnit\Framework\TestCase { ]; } + /** + * @param array $values + */ + #[DataProvider('provideDateOperators')] + public function test__date_operators(string $input, string $sql, array $values): void { + [$filterValues, $filterSearch] = FreshRSS_EntryDAOPGSQL::sqlBooleanSearch('e.', new FreshRSS_BooleanSearch($input)); + self::assertSame(trim($sql), trim($filterSearch)); + self::assertSame($values, $filterValues); + } + + /** @return list> */ + public static function provideDateOperators(): array { + return [ + // Basic date operator tests + [ + 'date:2007-03-01/2008-05-11', + '(e.id >= ? AND e.id <= ? )', + [strtotime('2007-03-01T00:00:00Z') . '000000', strtotime('2008-05-11T23:59:59Z') . '000000'], + ], + [ + 'date:2007-03-01/', + '(e.id >= ? )', + [strtotime('2007-03-01T00:00:00Z') . '000000'], + ], + [ + 'date:/2008-05-11', + '(e.id <= ? )', + [strtotime('2008-05-11T23:59:59Z') . '000000'], + ], + // Basic pubdate operator tests + [ + 'pubdate:2007-03-01T13:00:00Z/2008-05-11T15:30:00Z', + '(e.date >= ? AND e.date <= ? )', + [strtotime('2007-03-01T13:00:00Z'), strtotime('2008-05-11T15:30:00Z')], + ], + [ + 'pubdate:2007-03-01/', + '(e.date >= ? )', + [strtotime('2007-03-01T00:00:00Z')], + ], + [ + 'pubdate:/2008-05-11', + '(e.date <= ? )', + [strtotime('2008-05-11T23:59:59Z')], + ], + // Basic userdate operator tests + [ + 'userdate:2007-03-01T13:00:00Z/2008-05-11T15:30:00Z', + '(e.`lastUserModified` >= ? AND e.`lastUserModified` <= ? )', + [strtotime('2007-03-01T13:00:00Z'), strtotime('2008-05-11T15:30:00Z')], + ], + [ + 'userdate:2007-03-01/', + '(e.`lastUserModified` >= ? )', + [strtotime('2007-03-01T00:00:00Z')], + ], + [ + 'userdate:/2008-05-11', + '(e.`lastUserModified` <= ? )', + [strtotime('2008-05-11T23:59:59Z')], + ], + // Negative date operator tests + [ + '-date:2007-03-01/2008-05-11', + '((e.id < ? OR e.id > ?) )', + [strtotime('2007-03-01T00:00:00Z') . '000000', strtotime('2008-05-11T23:59:59Z') . '000000'], + ], + [ + '!pubdate:2007-03-01T13:00:00Z/2008-05-11T15:30:00Z', + '((e.date < ? OR e.date > ?) )', + [strtotime('2007-03-01T13:00:00Z'), strtotime('2008-05-11T15:30:00Z')], + ], + [ + '!userdate:2007-03-01T13:00:00Z/2008-05-11T15:30:00Z', + '((e.`lastUserModified` < ? OR e.`lastUserModified` > ?) )', + [strtotime('2007-03-01T13:00:00Z'), strtotime('2008-05-11T15:30:00Z')], + ], + // Combined date operators + [ + 'date:2007-03-01/ pubdate:/2008-05-11', + '(e.id >= ? AND e.date <= ? )', + [strtotime('2007-03-01T00:00:00Z') . '000000', strtotime('2008-05-11T23:59:59Z')], + ], + [ + 'pubdate:2007-03-01/ userdate:/2008-05-11', + '(e.date >= ? AND e.`lastUserModified` <= ? )', + [strtotime('2007-03-01T00:00:00Z'), strtotime('2008-05-11T23:59:59Z')], + ], + [ + 'date:2007-03-01/ userdate:2007-06-01/', + '(e.id >= ? AND e.`lastUserModified` >= ? )', + [strtotime('2007-03-01T00:00:00Z') . '000000', strtotime('2007-06-01T00:00:00Z')], + ], + // Complex combinations with other operators + [ + 'intitle:test date:2007-03-01/ pubdate:/2008-05-11', + '(e.id >= ? AND e.date <= ? AND e.title LIKE ? )', + [strtotime('2007-03-01T00:00:00Z') . '000000', strtotime('2008-05-11T23:59:59Z'), '%test%'], + ], + [ + 'author:john userdate:2007-03-01/2008-05-11', + '(e.`lastUserModified` >= ? AND e.`lastUserModified` <= ? AND e.author LIKE ? )', + [strtotime('2007-03-01T00:00:00Z'), strtotime('2008-05-11T23:59:59Z'), '%john%'], + ], + // Mixed positive and negative date operators + [ + 'date:2007-03-01/ !pubdate:2008-01-01/2008-05-11', + '(e.id >= ? AND (e.date < ? OR e.date > ?) )', + [strtotime('2007-03-01T00:00:00Z') . '000000', strtotime('2008-01-01T00:00:00Z'), strtotime('2008-05-11T23:59:59Z')], + ], + ]; + } + /** * @dataProvider provideRegexPostreSQL * @param array $values -- cgit v1.2.3