1
1
Fork 0
mirror of https://code.mensbeam.com/MensBeam/Arsse.git synced 2024-12-22 21:22:40 +00:00

Retrofits dates to use ranges

Article and edition ranges still need work
This commit is contained in:
J. King 2022-04-19 20:19:51 -04:00
parent 73497688fc
commit 2c2bb4a856
15 changed files with 161 additions and 188 deletions

View file

@ -164,39 +164,39 @@ trait ExclusionMethods {
return $this->act(__FUNCTION__, func_num_args(), $spec); return $this->act(__FUNCTION__, func_num_args(), $spec);
} }
public function latestArticle(int $spec = null) { public function articleRange(?int $start = null, ?int $end = null) {
if ($start === null && $end === null) {
$spec = null;
} else {
$spec = [$start, $end];
}
return $this->act(__FUNCTION__, func_num_args(), $spec); return $this->act(__FUNCTION__, func_num_args(), $spec);
} }
public function oldestArticle(int $spec = null) { public function editionRange(?int $start = null, ?int $end = null) {
if ($start === null && $end === null) {
$spec = null;
} else {
$spec = [$start, $end];
}
return $this->act(__FUNCTION__, func_num_args(), $spec); return $this->act(__FUNCTION__, func_num_args(), $spec);
} }
public function latestEdition(int $spec = null) { public function modifiedRange($start = null, $end = null) {
if ($start === null && $end === null) {
$spec = null;
} else {
$spec = [Date::normalize($start), Date::normalize($end)];
}
return $this->act(__FUNCTION__, func_num_args(), $spec); return $this->act(__FUNCTION__, func_num_args(), $spec);
} }
public function oldestEdition(int $spec = null) { public function markedRange($start = null, $end = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec); if ($start === null && $end === null) {
$spec = null;
} else {
$spec = [Date::normalize($start), Date::normalize($end)];
} }
public function modifiedSince($spec = null) {
$spec = Date::normalize($spec);
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function notModifiedSince($spec = null) {
$spec = Date::normalize($spec);
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function markedSince($spec = null) {
$spec = Date::normalize($spec);
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function notMarkedSince($spec = null) {
$spec = Date::normalize($spec);
return $this->act(__FUNCTION__, func_num_args(), $spec); return $this->act(__FUNCTION__, func_num_args(), $spec);
} }
} }

View file

@ -29,12 +29,8 @@ trait ExclusionProperties {
public $searchTerms = null; public $searchTerms = null;
public $titleTerms = null; public $titleTerms = null;
public $authorTerms = null; public $authorTerms = null;
public $oldestArticle = null; public $articleRange = [null, null];
public $latestArticle = null; public $editionRange = [null, null];
public $oldestEdition = null; public $modifiedRange = [null, null];
public $latestEdition = null; public $markedRange = [null, null];
public $modifiedSince = null;
public $notModifiedSince = null;
public $markedSince = null;
public $notMarkedSince = null;
} }

View file

@ -1556,31 +1556,30 @@ class Database {
$q->setLimit($context->limit, $context->offset); $q->setLimit($context->limit, $context->offset);
// handle the simple context options // handle the simple context options
$options = [ $options = [
// each context array consists of a column identifier (see $colDefs above), a comparison operator, a data type, and an option to pair with for BETWEEN evaluation // each context array consists of a column identifier (see $colDefs above), a comparison operator, and a data type; the "between" operator has special handling
"edition" => ["edition", "=", "int", ""], "edition" => ["edition", "=", "int"],
"editions" => ["edition", "in", "int", ""], "editions" => ["edition", "in", "int"],
"article" => ["id", "=", "int", ""], "article" => ["id", "=", "int"],
"articles" => ["id", "in", "int", ""], "articles" => ["id", "in", "int"],
"oldestArticle" => ["id", ">=", "int", "latestArticle"], "articleRange" => ["id", "between", "int"],
"latestArticle" => ["id", "<=", "int", "oldestArticle"], "editionRange" => ["edition", "between", "int"],
"oldestEdition" => ["edition", ">=", "int", "latestEdition"], "modifiedRange" => ["modified_date", "between", "datetime"],
"latestEdition" => ["edition", "<=", "int", "oldestEdition"], "markedRange" => ["marked_date", "between", "datetime"],
"modifiedSince" => ["modified_date", ">=", "datetime", "notModifiedSince"], "folderShallow" => ["folder", "=", "int"],
"notModifiedSince" => ["modified_date", "<=", "datetime", "modifiedSince"], "foldersShallow" => ["folder", "in", "int"],
"markedSince" => ["marked_date", ">=", "datetime", "notMarkedSince"], "subscription" => ["subscription", "=", "int"],
"notMarkedSince" => ["marked_date", "<=", "datetime", "markedSince"], "subscriptions" => ["subscription", "in", "int"],
"folderShallow" => ["folder", "=", "int", ""], "unread" => ["unread", "=", "bool"],
"foldersShallow" => ["folder", "in", "int", ""], "starred" => ["starred", "=", "bool"],
"subscription" => ["subscription", "=", "int", ""], "hidden" => ["hidden", "=", "bool"],
"subscriptions" => ["subscription", "in", "int", ""],
"unread" => ["unread", "=", "bool", ""],
"starred" => ["starred", "=", "bool", ""],
"hidden" => ["hidden", "=", "bool", ""],
]; ];
foreach ($options as $m => [$col, $op, $type, $pair]) { foreach ($options as $m => [$col, $op, $type]) {
if (!$context->$m()) { if (!$context->$m()) {
// context is not being used // context is not being used
continue; continue;
} elseif ($op === "between") {
// option is a range
$q->setWhereNot("{$colDefs[$col]} BETWEEN ? AND ?", [$type, $type], $context->$m);
} elseif (is_array($context->$m)) { } elseif (is_array($context->$m)) {
// context option is an array of values // context option is an array of values
if (!$context->$m) { if (!$context->$m) {
@ -1588,23 +1587,18 @@ class Database {
} }
[$clause, $types, $values] = $this->generateIn($context->$m, $type); [$clause, $types, $values] = $this->generateIn($context->$m, $type);
$q->setWhere("{$colDefs[$col]} $op ($clause)", $types, $values); $q->setWhere("{$colDefs[$col]} $op ($clause)", $types, $values);
} elseif ($pair && $context->$pair()) {
// option is paired with another which is also being used
if ($op === ">=") {
$q->setWhere("{$colDefs[$col]} BETWEEN ? AND ?", [$type, $type], [$context->$m, $context->$pair]);
} else {
// option has already been paired
continue;
}
} else { } else {
$q->setWhere("{$colDefs[$col]} $op ?", $type, $context->$m); $q->setWhere("{$colDefs[$col]} $op ?", $type, $context->$m);
} }
} }
// further handle exclusionary options if specified // further handle exclusionary options if specified
foreach ($options as $m => [$col, $op, $type, $pair]) { foreach ($options as $m => [$col, $op, $type]) {
if (!method_exists($context->not, $m) || !$context->not->$m()) { if (!method_exists($context->not, $m) || !$context->not->$m()) {
// context option is not being used // context option is not being used
continue; continue;
} elseif ($op === "between") {
// option is a range
$q->setWhereNot("{$colDefs[$col]} BETWEEN ? AND ?", [$type, $type], $context->not->$m);
} elseif (is_array($context->not->$m)) { } elseif (is_array($context->not->$m)) {
if (!$context->not->$m) { if (!$context->not->$m) {
// for exclusions we don't care if the array is empty // for exclusions we don't care if the array is empty
@ -1612,14 +1606,6 @@ class Database {
} }
[$clause, $types, $values] = $this->generateIn($context->not->$m, $type); [$clause, $types, $values] = $this->generateIn($context->not->$m, $type);
$q->setWhereNot("{$colDefs[$col]} $op ($clause)", $types, $values); $q->setWhereNot("{$colDefs[$col]} $op ($clause)", $types, $values);
} elseif ($pair && $context->not->$pair()) {
// option is paired with another which is also being used
if ($op === ">=") {
$q->setWhereNot("{$colDefs[$col]} BETWEEN ? AND ?", [$type, $type], [$context->not->$m, $context->not->$pair]);
} else {
// option has already been paired
continue;
}
} else { } else {
$q->setWhereNot("{$colDefs[$col]} $op ?", $type, $context->not->$m); $q->setWhereNot("{$colDefs[$col]} $op ?", $type, $context->not->$m);
} }

View file

@ -244,7 +244,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
$c = new Context; $c = new Context;
$id = $P['id']; $id = $P['id'];
if ($P['before']) { if ($P['before']) {
$c->notMarkedSince($P['before']); $c->markedRange(null, $P['before']);
} }
switch ($P['mark']) { switch ($P['mark']) {
case "item": case "item":
@ -310,7 +310,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
$c = (new Context)->hidden(false); $c = (new Context)->hidden(false);
$lastUnread = Date::normalize($lastUnread, "sql"); $lastUnread = Date::normalize($lastUnread, "sql");
$since = Date::sub("PT15S", $lastUnread); $since = Date::sub("PT15S", $lastUnread);
$c->unread(false)->markedSince($since); $c->unread(false)->markedRange($since, null);
Arsse::$db->articleMark(Arsse::$user->id, ['read' => false], $c); Arsse::$db->articleMark(Arsse::$user->id, ['read' => false], $c);
} }

View file

@ -893,8 +893,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
->limit($query['limit'] ?? self::DEFAULT_ENTRY_LIMIT) // NOTE: This does not honour user preferences ->limit($query['limit'] ?? self::DEFAULT_ENTRY_LIMIT) // NOTE: This does not honour user preferences
->offset($query['offset']) ->offset($query['offset'])
->starred($query['starred']) ->starred($query['starred'])
->modifiedSince($query['after']) // FIXME: This may not be the correct date field ->modifiedRange($query['after'], $query['before']) // FIXME: This may not be the correct date field
->notModifiedSince($query['before'])
->oldestArticle($query['after_entry_id'] ? $query['after_entry_id'] + 1 : null) // FIXME: This might be edition ->oldestArticle($query['after_entry_id'] ? $query['after_entry_id'] + 1 : null) // FIXME: This might be edition
->latestArticle($query['before_entry_id'] ? $query['before_entry_id'] - 1 : null) ->latestArticle($query['before_entry_id'] ? $query['before_entry_id'] - 1 : null)
->searchTerms(strlen($query['search'] ?? "") ? preg_split("/\s+/", $query['search']) : null); // NOTE: Miniflux matches only whole words; we match simple substrings ->searchTerms(strlen($query['search'] ?? "") ? preg_split("/\s+/", $query['search']) : null); // NOTE: Miniflux matches only whole words; we match simple substrings

View file

@ -556,7 +556,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
} }
// whether to return only updated items // whether to return only updated items
if ($data['lastModified']) { if ($data['lastModified']) {
$c->markedSince($data['lastModified']); $c->markedRange($data['lastModified'], null);
} }
// perform the fetch // perform the fetch
try { try {

View file

@ -256,7 +256,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
public function opGetCounters(array $data): array { public function opGetCounters(array $data): array {
$user = Arsse::$user->id; $user = Arsse::$user->id;
$starred = Arsse::$db->articleStarred($user); $starred = Arsse::$db->articleStarred($user);
$fresh = Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedSince(Date::sub("PT24H", $this->now()))->hidden(false)); $fresh = Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedRange(Date::sub("PT24H", $this->now()), null)->hidden(false));
$countAll = 0; $countAll = 0;
$countSubs = 0; $countSubs = 0;
$feeds = []; $feeds = [];
@ -361,7 +361,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
'id' => "FEED:".self::FEED_FRESH, 'id' => "FEED:".self::FEED_FRESH,
'bare_id' => self::FEED_FRESH, 'bare_id' => self::FEED_FRESH,
'icon' => "images/fresh.png", 'icon' => "images/fresh.png",
'unread' => Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedSince(Date::sub("PT24H", $this->now()))->hidden(false)), 'unread' => Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedRange(Date::sub("PT24H", $this->now()), null)->hidden(false)),
], $tSpecial), ], $tSpecial),
array_merge([ // Starred articles array_merge([ // Starred articles
'name' => Arsse::$lang->msg("API.TTRSS.Feed.Starred"), 'name' => Arsse::$lang->msg("API.TTRSS.Feed.Starred"),
@ -545,7 +545,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
// FIXME: this is pretty inefficient // FIXME: this is pretty inefficient
$f = $map[self::CAT_SPECIAL]; $f = $map[self::CAT_SPECIAL];
$cats[$f]['unread'] += Arsse::$db->articleStarred($user)['unread']; // starred $cats[$f]['unread'] += Arsse::$db->articleStarred($user)['unread']; // starred
$cats[$f]['unread'] += Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedSince(Date::sub("PT24H", $this->now()))->hidden(false)); // fresh $cats[$f]['unread'] += Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedRange(Date::sub("PT24H", $this->now()), null)->hidden(false)); // fresh
if (!$read) { if (!$read) {
// if we're only including unread entries, remove any categories with zero unread items (this will by definition also exclude empties) // if we're only including unread entries, remove any categories with zero unread items (this will by definition also exclude empties)
$count = sizeof($cats); $count = sizeof($cats);
@ -697,7 +697,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
if ($cat == self::CAT_ALL || $cat == self::CAT_SPECIAL) { if ($cat == self::CAT_ALL || $cat == self::CAT_SPECIAL) {
// gather some statistics // gather some statistics
$starred = Arsse::$db->articleStarred($user)['unread']; $starred = Arsse::$db->articleStarred($user)['unread'];
$fresh = Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedSince(Date::sub("PT24H", $this->now()))->hidden(false)); $fresh = Arsse::$db->articleCount($user, (new Context)->unread(true)->modifiedRange(Date::sub("PT24H", $this->now()), null)->hidden(false));
$global = Arsse::$db->articleCount($user, (new Context)->unread(true)->hidden(false)); $global = Arsse::$db->articleCount($user, (new Context)->unread(true)->hidden(false));
$published = 0; // TODO: if the Published feed is implemented, the getFeeds method needs to be adjusted accordingly $published = 0; // TODO: if the Published feed is implemented, the getFeeds method needs to be adjusted accordingly
$archived = 0; // the archived feed is non-functional in the TT-RSS protocol itself $archived = 0; // the archived feed is non-functional in the TT-RSS protocol itself
@ -1096,7 +1096,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
// TODO: if the Published feed is implemented, the catchup function needs to be modified accordingly // TODO: if the Published feed is implemented, the catchup function needs to be modified accordingly
return $out; return $out;
case self::FEED_FRESH: case self::FEED_FRESH:
$c->modifiedSince(Date::sub("PT24H", $this->now())); $c->modifiedRange(Date::sub("PT24H", $this->now()), null);
break; break;
case self::FEED_ALL: case self::FEED_ALL:
// no context needed here // no context needed here
@ -1112,13 +1112,13 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
} }
switch ($mode) { switch ($mode) {
case "2week": case "2week":
$c->notModifiedSince(Date::sub("P2W", $this->now())); $c->modifiedRange($c->modifiedRange[0], Date::sub("P2W", $this->now()));
break; break;
case "1week": case "1week":
$c->notModifiedSince(Date::sub("P1W", $this->now())); $c->modifiedRange($c->modifiedRange[0], Date::sub("P1W", $this->now()));
break; break;
case "1day": case "1day":
$c->notModifiedSince(Date::sub("PT24H", $this->now())); $c->modifiedRange($c->modifiedRange[0], Date::sub("PT24H", $this->now()));
} }
// perform the marking // perform the marking
try { try {
@ -1473,13 +1473,13 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
// TODO: if the Published feed is implemented, the headline function needs to be modified accordingly // TODO: if the Published feed is implemented, the headline function needs to be modified accordingly
return new ResultEmpty; return new ResultEmpty;
case self::FEED_FRESH: case self::FEED_FRESH:
$c->modifiedSince(Date::sub("PT24H", $this->now()))->unread(true); $c->modifiedRange(Date::sub("PT24H", $this->now()), null)->unread(true);
break; break;
case self::FEED_ALL: case self::FEED_ALL:
// no context needed here // no context needed here
break; break;
case self::FEED_READ: case self::FEED_READ:
$c->markedSince(Date::sub("PT24H", $this->now()))->unread(false); // FIXME: this selects any recently touched (read, starred, annotated) article which is read, not necessarily a recently read one $c->markedRange(Date::sub("PT24H", $this->now()), null)->unread(false); // FIXME: this selects any recently touched (read, starred, annotated) article which is read, not necessarily a recently read one
break; break;
default: default:
// any actual feed // any actual feed

View file

@ -320,16 +320,15 @@ class Search {
$end = $day."T23:59:59+00:00"; $end = $day."T23:59:59+00:00";
// if a date is already set, the same date is a no-op; anything else is a contradiction // if a date is already set, the same date is a no-op; anything else is a contradiction
$cc = $neg ? $c->not : $c; $cc = $neg ? $c->not : $c;
if ($cc->modifiedSince() || $cc->notModifiedSince()) { if ($cc->modifiedRange()) {
if (!$cc->modifiedSince() || !$cc->notModifiedSince() || $cc->modifiedSince->format("c") !== $start || $cc->notModifiedSince->format("c") !== $end) { if (!$cc->modifiedRange[0] || !$cc->modifiedRange[1] || $cc->modifiedRange[0]->format("c") !== $start || $cc->modifiedRange[1]->format("c") !== $end) {
// FIXME: multiple negative dates should be allowed, but the design of the Context class does not support this // FIXME: multiple negative dates should be allowed, but the design of the Context class does not support this
throw new Exception; throw new Exception;
} else { } else {
return $c; return $c;
} }
} }
$cc->modifiedSince($start); $cc->modifiedRange($start, $end);
$cc->notModifiedSince($end);
return $c; return $c;
} }

View file

@ -462,16 +462,16 @@ trait SeriesArticle {
'Not before edition 1001' => [(new Context)->subscription(5)->oldestEdition(1001), [20]], 'Not before edition 1001' => [(new Context)->subscription(5)->oldestEdition(1001), [20]],
'Not after article 3' => [(new Context)->latestArticle(3), [1,2,3]], 'Not after article 3' => [(new Context)->latestArticle(3), [1,2,3]],
'Not before article 19' => [(new Context)->oldestArticle(19), [19,20]], 'Not before article 19' => [(new Context)->oldestArticle(19), [19,20]],
'Modified by author since 2005' => [(new Context)->modifiedSince("2005-01-01T00:00:00Z"), [2,4,6,8,20]], 'Modified by author since 2005' => [(new Context)->modifiedRange("2005-01-01T00:00:00Z", null), [2,4,6,8,20]],
'Modified by author since 2010' => [(new Context)->modifiedSince("2010-01-01T00:00:00Z"), [2,4,6,8,20]], 'Modified by author since 2010' => [(new Context)->modifiedRange("2010-01-01T00:00:00Z", null), [2,4,6,8,20]],
'Not modified by author since 2005' => [(new Context)->notModifiedSince("2005-01-01T00:00:00Z"), [1,3,5,7,19]], 'Not modified by author since 2005' => [(new Context)->modifiedRange(null, "2005-01-01T00:00:00Z"), [1,3,5,7,19]],
'Not modified by author since 2000' => [(new Context)->notModifiedSince("2000-01-01T00:00:00Z"), [1,3,5,7,19]], 'Not modified by author since 2000' => [(new Context)->modifiedRange(null, "2000-01-01T00:00:00Z"), [1,3,5,7,19]],
'Marked or labelled since 2014' => [(new Context)->markedSince("2014-01-01T00:00:00Z"), [8,19]], 'Marked or labelled since 2014' => [(new Context)->markedRange("2014-01-01T00:00:00Z", null), [8,19]],
'Marked or labelled since 2010' => [(new Context)->markedSince("2010-01-01T00:00:00Z"), [2,4,6,8,19,20]], 'Marked or labelled since 2010' => [(new Context)->markedRange("2010-01-01T00:00:00Z", null), [2,4,6,8,19,20]],
'Not marked or labelled since 2014' => [(new Context)->notMarkedSince("2014-01-01T00:00:00Z"), [1,2,3,4,5,6,7,20]], 'Not marked or labelled since 2014' => [(new Context)->markedRange(null, "2014-01-01T00:00:00Z"), [1,2,3,4,5,6,7,20]],
'Not marked or labelled since 2005' => [(new Context)->notMarkedSince("2005-01-01T00:00:00Z"), [1,3,5,7]], 'Not marked or labelled since 2005' => [(new Context)->markedRange(null, "2005-01-01T00:00:00Z"), [1,3,5,7]],
'Marked or labelled between 2000 and 2015' => [(new Context)->markedSince("2000-01-01T00:00:00Z")->notMarkedSince("2015-12-31T23:59:59Z"), [1,2,3,4,5,6,7,8,20]], 'Marked or labelled between 2000 and 2015' => [(new Context)->markedRange("2000-01-01T00:00:00Z", "2015-12-31T23:59:59Z"), [1,2,3,4,5,6,7,8,20]],
'Marked or labelled in 2010' => [(new Context)->markedSince("2010-01-01T00:00:00Z")->notMarkedSince("2010-12-31T23:59:59Z"), [2,4,6,20]], 'Marked or labelled in 2010' => [(new Context)->markedRange("2010-01-01T00:00:00Z", "2010-12-31T23:59:59Z"), [2,4,6,20]],
'Paged results' => [(new Context)->limit(2)->oldestEdition(4), [4,5]], 'Paged results' => [(new Context)->limit(2)->oldestEdition(4), [4,5]],
'With label ID 1' => [(new Context)->label(1), [1,19]], 'With label ID 1' => [(new Context)->label(1), [1,19]],
'With label ID 2' => [(new Context)->label(2), [1,5,20]], 'With label ID 2' => [(new Context)->label(2), [1,5,20]],
@ -505,7 +505,7 @@ trait SeriesArticle {
'Folder tree 1 excluding subscription 4' => [(new Context)->not->subscription(4)->folder(1), [5,6]], 'Folder tree 1 excluding subscription 4' => [(new Context)->not->subscription(4)->folder(1), [5,6]],
'Folder tree 1 excluding articles 7 and 8' => [(new Context)->folder(1)->not->articles([7,8]), [5,6]], 'Folder tree 1 excluding articles 7 and 8' => [(new Context)->folder(1)->not->articles([7,8]), [5,6]],
'Folder tree 1 excluding no articles' => [(new Context)->folder(1)->not->articles([]), [5,6,7,8]], 'Folder tree 1 excluding no articles' => [(new Context)->folder(1)->not->articles([]), [5,6,7,8]],
'Marked or labelled between 2000 and 2015 excluding in 2010' => [(new Context)->markedSince("2000-01-01T00:00:00Z")->notMarkedSince("2015-12-31T23:59:59")->not->markedSince("2010-01-01T00:00:00Z")->not->notMarkedSince("2010-12-31T23:59:59Z"), [1,3,5,7,8]], 'Marked or labelled between 2000 and 2015 excluding in 2010' => [(new Context)->markedRange("2000-01-01T00:00:00Z", "2015-12-31T23:59:59")->not->markedRange("2010-01-01T00:00:00Z", "2010-12-31T23:59:59Z"), [1,3,5,7,8]],
'Search with exclusion' => [(new Context)->searchTerms(["Article"])->not->searchTerms(["one", "two"]), [3]], 'Search with exclusion' => [(new Context)->searchTerms(["Article"])->not->searchTerms(["one", "two"]), [3]],
'Excluded folder tree' => [(new Context)->not->folder(1), [1,2,3,4,19,20]], 'Excluded folder tree' => [(new Context)->not->folder(1), [1,2,3,4,19,20]],
'Excluding label ID 2' => [(new Context)->not->label(2), [2,3,4,6,7,8,19]], 'Excluding label ID 2' => [(new Context)->not->label(2), [2,3,4,6,7,8,19]],
@ -953,7 +953,7 @@ trait SeriesArticle {
} }
public function testMarkByLastMarked(): void { public function testMarkByLastMarked(): void {
Arsse::$db->articleMark($this->user, ['starred' => true], (new Context)->markedSince('2017-01-01T00:00:00Z')); Arsse::$db->articleMark($this->user, ['starred' => true], (new Context)->markedRange('2017-01-01T00:00:00Z', null));
$now = Date::transform(time(), "sql"); $now = Date::transform(time(), "sql");
$state = $this->primeExpectations($this->data, $this->checkTables); $state = $this->primeExpectations($this->data, $this->checkTables);
$state['arsse_marks']['rows'][8][3] = 1; $state['arsse_marks']['rows'][8][3] = 1;
@ -964,7 +964,7 @@ trait SeriesArticle {
} }
public function testMarkByNotLastMarked(): void { public function testMarkByNotLastMarked(): void {
Arsse::$db->articleMark($this->user, ['starred' => true], (new Context)->notMarkedSince('2000-01-01T00:00:00Z')); Arsse::$db->articleMark($this->user, ['starred' => true], (new Context)->markedRange(null, '2000-01-01T00:00:00Z'));
$now = Date::transform(time(), "sql"); $now = Date::transform(time(), "sql");
$state = $this->primeExpectations($this->data, $this->checkTables); $state = $this->primeExpectations($this->data, $this->checkTables);
$state['arsse_marks']['rows'][] = [13,5,0,1,$now,'',0]; $state['arsse_marks']['rows'][] = [13,5,0,1,$now,'',0];

View file

@ -47,10 +47,6 @@ class TestContext extends \JKingWeb\Arsse\Test\AbstractTest {
'unread' => true, 'unread' => true,
'starred' => true, 'starred' => true,
'hidden' => true, 'hidden' => true,
'modifiedSince' => new \DateTime(),
'notModifiedSince' => new \DateTime(),
'markedSince' => new \DateTime(),
'notMarkedSince' => new \DateTime(),
'editions' => [1,2], 'editions' => [1,2],
'articles' => [1,2], 'articles' => [1,2],
'label' => 2112, 'label' => 2112,
@ -65,21 +61,17 @@ class TestContext extends \JKingWeb\Arsse\Test\AbstractTest {
'authorTerms' => ["foo", "bar"], 'authorTerms' => ["foo", "bar"],
'not' => (new Context)->subscription(5), 'not' => (new Context)->subscription(5),
]; ];
$times = ['modifiedSince','notModifiedSince','markedSince','notMarkedSince']; $ranges = ['modifiedRange', 'markedRange', 'articleRange', 'editionRange'];
$c = new Context; $c = new Context;
foreach ((new \ReflectionObject($c))->getMethods(\ReflectionMethod::IS_PUBLIC) as $m) { foreach ((new \ReflectionObject($c))->getMethods(\ReflectionMethod::IS_PUBLIC) as $m) {
if ($m->isStatic() || strpos($m->name, "__") === 0) { if ($m->isStatic() || strpos($m->name, "__") === 0 || in_array($m->name, $ranges)) {
continue; continue;
} }
$method = $m->name; $method = $m->name;
$this->assertArrayHasKey($method, $v, "Context method $method not included in test"); $this->assertArrayHasKey($method, $v, "Context method $method not included in test");
$this->assertInstanceOf(Context::class, $c->$method($v[$method])); $this->assertInstanceOf(Context::class, $c->$method($v[$method]));
$this->assertTrue($c->$method()); $this->assertTrue($c->$method());
if (in_array($method, $times)) {
$this->assertTime($c->$method, $v[$method], "Context method $method did not return the expected results");
} else {
$this->assertSame($c->$method, $v[$method], "Context method $method did not return the expected results"); $this->assertSame($c->$method, $v[$method], "Context method $method did not return the expected results");
}
// clear the context option // clear the context option
$c->$method(null); $c->$method(null);
$this->assertFalse($c->$method()); $this->assertFalse($c->$method());

View file

@ -407,7 +407,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
["mark=group&as=unread&id=-1", (new Context)->not->folder(0), $markUnread, $listUnread], ["mark=group&as=unread&id=-1", (new Context)->not->folder(0), $markUnread, $listUnread],
["mark=group&as=saved&id=-1", (new Context)->not->folder(0), $markSaved, $listSaved], ["mark=group&as=saved&id=-1", (new Context)->not->folder(0), $markSaved, $listSaved],
["mark=group&as=unsaved&id=-1", (new Context)->not->folder(0), $markUnsaved, $listSaved], ["mark=group&as=unsaved&id=-1", (new Context)->not->folder(0), $markUnsaved, $listSaved],
["mark=group&as=read&id=-1&before=946684800", (new Context)->not->folder(0)->notMarkedSince("2000-01-01T00:00:00Z"), $markRead, $listUnread], ["mark=group&as=read&id=-1&before=946684800", (new Context)->not->folder(0)->markedRange(null, "2000-01-01T00:00:00Z"), $markRead, $listUnread],
["mark=item&as=unread", new Context, [], []], ["mark=item&as=unread", new Context, [], []],
["mark=item&id=6", new Context, [], []], ["mark=item&id=6", new Context, [], []],
["as=unread&id=6", new Context, [], []], ["as=unread&id=6", new Context, [], []],
@ -462,7 +462,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->articleMark->returns(0); $this->dbMock->articleMark->returns(0);
$exp = new JsonResponse($out); $exp = new JsonResponse($out);
$this->assertMessage($exp, $this->req("api", ['unread_recently_read' => 1])); $this->assertMessage($exp, $this->req("api", ['unread_recently_read' => 1]));
$this->dbMock->articleMark->calledWith($this->userId, ['read' => false], $this->equalTo((new Context)->unread(false)->markedSince("1999-12-31T23:59:45Z")->hidden(false))); $this->dbMock->articleMark->calledWith($this->userId, ['read' => false], $this->equalTo((new Context)->unread(false)->markedRange("1999-12-31T23:59:45Z", null)->hidden(false)));
$this->dbMock->articleList->with($this->userId, (new Context)->limit(1)->hidden(false), ["marked_date"], ["marked_date desc"])->returns(new Result([])); $this->dbMock->articleList->with($this->userId, (new Context)->limit(1)->hidden(false), ["marked_date"], ["marked_date desc"])->returns(new Result([]));
$this->assertMessage($exp, $this->req("api", ['unread_recently_read' => 1])); $this->assertMessage($exp, $this->req("api", ['unread_recently_read' => 1]));
$this->dbMock->articleMark->once()->called(); // only called one time, above $this->dbMock->articleMark->once()->called(); // only called one time, above

View file

@ -768,9 +768,10 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
["/entries?starred=", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?starred=", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?starred=true", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?starred=true", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?starred=false", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?starred=false", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?after=0", (clone $c)->modifiedSince(0), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?after=0", (clone $c)->modifiedRange(0, null), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?before=0", $c, $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?before=0", $c, $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?before=1", (clone $c)->notModifiedSince(1), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?before=1", (clone $c)->modifiedRange(null, 1), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?before=1&after=0", (clone $c)->modifiedRange(0, 1), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?after_entry_id=42", (clone $c)->oldestArticle(43), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?after_entry_id=42", (clone $c)->oldestArticle(43), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?before_entry_id=47", (clone $c)->latestArticle(46), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?before_entry_id=47", (clone $c)->latestArticle(46), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?search=alpha%20beta", (clone $c)->searchTerms(["alpha", "beta"]), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?search=alpha%20beta", (clone $c)->searchTerms(["alpha", "beta"]), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],

View file

@ -695,7 +695,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
["/items", ['type' => 3, 'id' => 0], clone $c, $out, $r200], ["/items", ['type' => 3, 'id' => 0], clone $c, $out, $r200],
["/items", ['getRead' => true], clone $c, $out, $r200], ["/items", ['getRead' => true], clone $c, $out, $r200],
["/items", ['getRead' => false], (clone $c)->unread(true), $out, $r200], ["/items", ['getRead' => false], (clone $c)->unread(true), $out, $r200],
["/items", ['lastModified' => $t->getTimestamp()], (clone $c)->markedSince($t), $out, $r200], ["/items", ['lastModified' => $t->getTimestamp()], (clone $c)->markedRange($t, null), $out, $r200],
["/items", ['oldestFirst' => true, 'batchSize' => 10, 'offset' => 5], (clone $c)->oldestEdition(6)->limit(10), $out, $r200], ["/items", ['oldestFirst' => true, 'batchSize' => 10, 'offset' => 5], (clone $c)->oldestEdition(6)->limit(10), $out, $r200],
["/items", ['oldestFirst' => false, 'batchSize' => 5, 'offset' => 5], (clone $c)->latestEdition(4)->limit(5), $out, $r200], ["/items", ['oldestFirst' => false, 'batchSize' => 5, 'offset' => 5], (clone $c)->latestEdition(4)->limit(5), $out, $r200],
["/items", ['oldestFirst' => false, 'batchSize' => 5, 'offset' => 0], (clone $c)->limit(5), $out, $r200], ["/items", ['oldestFirst' => false, 'batchSize' => 5, 'offset' => 0], (clone $c)->limit(5), $out, $r200],
@ -708,7 +708,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
["/items/updated", ['type' => 3, 'id' => 0], clone $c, $out, $r200], ["/items/updated", ['type' => 3, 'id' => 0], clone $c, $out, $r200],
["/items/updated", ['getRead' => true], clone $c, $out, $r200], ["/items/updated", ['getRead' => true], clone $c, $out, $r200],
["/items/updated", ['getRead' => false], (clone $c)->unread(true), $out, $r200], ["/items/updated", ['getRead' => false], (clone $c)->unread(true), $out, $r200],
["/items/updated", ['lastModified' => $t->getTimestamp()], (clone $c)->markedSince($t), $out, $r200], ["/items/updated", ['lastModified' => $t->getTimestamp()], (clone $c)->markedRange($t, null), $out, $r200],
["/items/updated", ['oldestFirst' => true, 'batchSize' => 10, 'offset' => 5], (clone $c)->oldestEdition(6)->limit(10), $out, $r200], ["/items/updated", ['oldestFirst' => true, 'batchSize' => 10, 'offset' => 5], (clone $c)->oldestEdition(6)->limit(10), $out, $r200],
["/items/updated", ['oldestFirst' => false, 'batchSize' => 5, 'offset' => 5], (clone $c)->latestEdition(4)->limit(5), $out, $r200], ["/items/updated", ['oldestFirst' => false, 'batchSize' => 5, 'offset' => 5], (clone $c)->latestEdition(4)->limit(5), $out, $r200],
["/items/updated", ['oldestFirst' => false, 'batchSize' => 5, 'offset' => 0], (clone $c)->limit(5), $out, $r200], ["/items/updated", ['oldestFirst' => false, 'batchSize' => 5, 'offset' => 0], (clone $c)->limit(5), $out, $r200],

View file

@ -959,7 +959,7 @@ LONG_STRING;
$this->dbMock->folderList->with("~", null, false)->returns(new Result($this->v($this->topFolders))); $this->dbMock->folderList->with("~", null, false)->returns(new Result($this->v($this->topFolders)));
$this->dbMock->subscriptionList->returns(new Result($this->v($this->subscriptions))); $this->dbMock->subscriptionList->returns(new Result($this->v($this->subscriptions)));
$this->dbMock->labelList->returns(new Result($this->v($this->labels))); $this->dbMock->labelList->returns(new Result($this->v($this->labels)));
$this->dbMock->articleCount->with("~", $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedSince(Date::sub("PT24H", self::NOW))))->returns(7); $this->dbMock->articleCount->with("~", $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedRange(Date::sub("PT24H", self::NOW), null)))->returns(7);
$this->dbMock->articleStarred->returns($this->v($this->starred)); $this->dbMock->articleStarred->returns($this->v($this->starred));
$this->assertMessage($exp, $this->req($in)); $this->assertMessage($exp, $this->req($in));
} }
@ -1060,7 +1060,7 @@ LONG_STRING;
['id' => -2, 'kind' => "cat", 'counter' => 6], ['id' => -2, 'kind' => "cat", 'counter' => 6],
]; ];
$this->assertMessage($this->respGood($exp), $this->req($in)); $this->assertMessage($this->respGood($exp), $this->req($in));
$this->dbMock->articleCount->calledWith($this->userId, $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedSince(Date::sub("PT24H", self::NOW)))); $this->dbMock->articleCount->calledWith($this->userId, $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedRange(Date::sub("PT24H", self::NOW), null)));
} }
/** @dataProvider provideLabelListings */ /** @dataProvider provideLabelListings */
@ -1152,7 +1152,7 @@ LONG_STRING;
$this->assertMessage($this->respGood($exp), $this->req($in[0])); $this->assertMessage($this->respGood($exp), $this->req($in[0]));
$exp = ['categories' => ['identifier' => 'id','label' => 'name','items' => [['name' => 'Special','id' => 'CAT:-1','bare_id' => -1,'type' => 'category','unread' => 0,'items' => [['name' => 'All articles','id' => 'FEED:-4','bare_id' => -4,'icon' => 'images/folder.png','unread' => 35,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Fresh articles','id' => 'FEED:-3','bare_id' => -3,'icon' => 'images/fresh.png','unread' => 7,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Starred articles','id' => 'FEED:-1','bare_id' => -1,'icon' => 'images/star.png','unread' => 4,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Published articles','id' => 'FEED:-2','bare_id' => -2,'icon' => 'images/feed.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Archived articles','id' => 'FEED:0','bare_id' => 0,'icon' => 'images/archive.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Recently read','id' => 'FEED:-6','bare_id' => -6,'icon' => 'images/time.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => '']]],['name' => 'Labels','id' => 'CAT:-2','bare_id' => -2,'type' => 'category','unread' => 6,'items' => [['name' => 'Fascinating','id' => 'FEED:-1027','bare_id' => -1027,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => ''],['name' => 'Interesting','id' => 'FEED:-1029','bare_id' => -1029,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => ''],['name' => 'Logical','id' => 'FEED:-1025','bare_id' => -1025,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => '']]],['name' => 'Politics','id' => 'CAT:3','bare_id' => 3,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(3 feeds)','items' => [['name' => 'Local','id' => 'CAT:5','bare_id' => 5,'parent_id' => 3,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(1 feed)','items' => [['name' => 'Toronto Star','id' => 'FEED:2','bare_id' => 2,'icon' => 'feed-icons/2.ico','error' => 'oops','param' => '2011-11-11T11:11:11Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'National','id' => 'CAT:6','bare_id' => 6,'parent_id' => 3,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(2 feeds)','items' => [['name' => 'CBC News','id' => 'FEED:4','bare_id' => 4,'icon' => 'feed-icons/4.ico','error' => '','param' => '2017-10-09T15:58:34Z','unread' => 0,'auxcounter' => 0,'checkbox' => false],['name' => 'Ottawa Citizen','id' => 'FEED:5','bare_id' => 5,'icon' => false,'error' => '','param' => '2017-07-07T17:07:17Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]]]],['name' => 'Science','id' => 'CAT:1','bare_id' => 1,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(2 feeds)','items' => [['name' => 'Rocketry','id' => 'CAT:2','bare_id' => 2,'parent_id' => 1,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(1 feed)','items' => [['name' => 'NASA JPL','id' => 'FEED:1','bare_id' => 1,'icon' => false,'error' => '','param' => '2017-09-15T22:54:16Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'Ars Technica','id' => 'FEED:3','bare_id' => 3,'icon' => 'feed-icons/3.ico','error' => 'argh','param' => '2016-05-23T06:40:02Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'Uncategorized','id' => 'CAT:0','bare_id' => 0,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'parent_id' => null,'param' => '(1 feed)','items' => [['name' => 'Eurogamer','id' => 'FEED:6','bare_id' => 6,'icon' => 'feed-icons/6.ico','error' => '','param' => '2010-02-12T20:08:47Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]]]]]; $exp = ['categories' => ['identifier' => 'id','label' => 'name','items' => [['name' => 'Special','id' => 'CAT:-1','bare_id' => -1,'type' => 'category','unread' => 0,'items' => [['name' => 'All articles','id' => 'FEED:-4','bare_id' => -4,'icon' => 'images/folder.png','unread' => 35,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Fresh articles','id' => 'FEED:-3','bare_id' => -3,'icon' => 'images/fresh.png','unread' => 7,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Starred articles','id' => 'FEED:-1','bare_id' => -1,'icon' => 'images/star.png','unread' => 4,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Published articles','id' => 'FEED:-2','bare_id' => -2,'icon' => 'images/feed.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Archived articles','id' => 'FEED:0','bare_id' => 0,'icon' => 'images/archive.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => ''],['name' => 'Recently read','id' => 'FEED:-6','bare_id' => -6,'icon' => 'images/time.png','unread' => 0,'type' => 'feed','auxcounter' => 0,'error' => '','updated' => '']]],['name' => 'Labels','id' => 'CAT:-2','bare_id' => -2,'type' => 'category','unread' => 6,'items' => [['name' => 'Fascinating','id' => 'FEED:-1027','bare_id' => -1027,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => ''],['name' => 'Interesting','id' => 'FEED:-1029','bare_id' => -1029,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => ''],['name' => 'Logical','id' => 'FEED:-1025','bare_id' => -1025,'unread' => 0,'icon' => 'images/label.png','type' => 'feed','auxcounter' => 0,'error' => '','updated' => '','fg_color' => '','bg_color' => '']]],['name' => 'Politics','id' => 'CAT:3','bare_id' => 3,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(3 feeds)','items' => [['name' => 'Local','id' => 'CAT:5','bare_id' => 5,'parent_id' => 3,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(1 feed)','items' => [['name' => 'Toronto Star','id' => 'FEED:2','bare_id' => 2,'icon' => 'feed-icons/2.ico','error' => 'oops','param' => '2011-11-11T11:11:11Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'National','id' => 'CAT:6','bare_id' => 6,'parent_id' => 3,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(2 feeds)','items' => [['name' => 'CBC News','id' => 'FEED:4','bare_id' => 4,'icon' => 'feed-icons/4.ico','error' => '','param' => '2017-10-09T15:58:34Z','unread' => 0,'auxcounter' => 0,'checkbox' => false],['name' => 'Ottawa Citizen','id' => 'FEED:5','bare_id' => 5,'icon' => false,'error' => '','param' => '2017-07-07T17:07:17Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]]]],['name' => 'Science','id' => 'CAT:1','bare_id' => 1,'parent_id' => null,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(2 feeds)','items' => [['name' => 'Rocketry','id' => 'CAT:2','bare_id' => 2,'parent_id' => 1,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'param' => '(1 feed)','items' => [['name' => 'NASA JPL','id' => 'FEED:1','bare_id' => 1,'icon' => false,'error' => '','param' => '2017-09-15T22:54:16Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'Ars Technica','id' => 'FEED:3','bare_id' => 3,'icon' => 'feed-icons/3.ico','error' => 'argh','param' => '2016-05-23T06:40:02Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]],['name' => 'Uncategorized','id' => 'CAT:0','bare_id' => 0,'type' => 'category','auxcounter' => 0,'unread' => 0,'child_unread' => 0,'checkbox' => false,'parent_id' => null,'param' => '(1 feed)','items' => [['name' => 'Eurogamer','id' => 'FEED:6','bare_id' => 6,'icon' => 'feed-icons/6.ico','error' => '','param' => '2010-02-12T20:08:47Z','unread' => 0,'auxcounter' => 0,'checkbox' => false]]]]]];
$this->assertMessage($this->respGood($exp), $this->req($in[1])); $this->assertMessage($this->respGood($exp), $this->req($in[1]));
$this->dbMock->articleCount->twice()->calledWith($this->userId, $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedSince(Date::sub("PT24H", self::NOW)))); $this->dbMock->articleCount->twice()->calledWith($this->userId, $this->equalTo((new Context)->hidden(false)->unread(true)->modifiedRange(Date::sub("PT24H", self::NOW), null)));
} }
/** @dataProvider provideMassMarkings */ /** @dataProvider provideMassMarkings */
@ -1180,8 +1180,8 @@ LONG_STRING;
[['feed_id' => 0, 'is_cat' => true, 'mode' => "bogus"], (clone $c)->folderShallow(0)], [['feed_id' => 0, 'is_cat' => true, 'mode' => "bogus"], (clone $c)->folderShallow(0)],
[['feed_id' => -1], (clone $c)->starred(true)], [['feed_id' => -1], (clone $c)->starred(true)],
[['feed_id' => -1, 'is_cat' => "t"], null], [['feed_id' => -1, 'is_cat' => "t"], null],
[['feed_id' => -3], (clone $c)->modifiedSince(Date::sub("PT24H", self::NOW))], [['feed_id' => -3], (clone $c)->modifiedRange(Date::sub("PT24H", self::NOW), null)],
[['feed_id' => -3, 'mode' => "1day"], (clone $c)->modifiedSince(Date::sub("PT24H", self::NOW))->notModifiedSince(Date::sub("PT24H", self::NOW))], // this is a nonsense query, but it's what TT-RSS appearsto do [['feed_id' => -3, 'mode' => "1day"], (clone $c)->modifiedRange(Date::sub("PT24H", self::NOW), Date::sub("PT24H", self::NOW))], // this is a nonsense query, but it's what TT-RSS appearsto do
[['feed_id' => -3, 'is_cat' => true], null], [['feed_id' => -3, 'is_cat' => true], null],
[['feed_id' => -2], null], [['feed_id' => -2], null],
[['feed_id' => -2, 'is_cat' => true], (clone $c)->labelled(true)], [['feed_id' => -2, 'is_cat' => true], (clone $c)->labelled(true)],
@ -1191,9 +1191,9 @@ LONG_STRING;
[['feed_id' => -6, 'is_cat' => "f"], null], [['feed_id' => -6, 'is_cat' => "f"], null],
[['feed_id' => -2112], (clone $c)->label(1088)], [['feed_id' => -2112], (clone $c)->label(1088)],
[['feed_id' => 42, 'is_cat' => true], (clone $c)->folder(42)], [['feed_id' => 42, 'is_cat' => true], (clone $c)->folder(42)],
[['feed_id' => 42, 'is_cat' => true, 'mode' => "1week"], (clone $c)->folder(42)->notModifiedSince(Date::sub("P1W", self::NOW))], [['feed_id' => 42, 'is_cat' => true, 'mode' => "1week"], (clone $c)->folder(42)->modifiedRange(null, Date::sub("P1W", self::NOW))],
[['feed_id' => 2112], (clone $c)->subscription(2112)], [['feed_id' => 2112], (clone $c)->subscription(2112)],
[['feed_id' => 2112, 'mode' => "2week"], (clone $c)->subscription(2112)->notModifiedSince(Date::sub("P2W", self::NOW))], [['feed_id' => 2112, 'mode' => "2week"], (clone $c)->subscription(2112)->modifiedRange(null, Date::sub("P2W", self::NOW))],
]; ];
} }
@ -1202,7 +1202,7 @@ LONG_STRING;
$in = array_merge(['op' => "getFeeds", 'sid' => "PriestsOfSyrinx"], $in); $in = array_merge(['op' => "getFeeds", 'sid' => "PriestsOfSyrinx"], $in);
// statistical mocks // statistical mocks
$this->dbMock->articleStarred->returns($this->v($this->starred)); $this->dbMock->articleStarred->returns($this->v($this->starred));
$this->dbMock->articleCount->with("~", $this->equalTo((new Context)->unread(true)->hidden(false)->modifiedSince(Date::sub("PT24H", self::NOW))))->returns(7); $this->dbMock->articleCount->with("~", $this->equalTo((new Context)->unread(true)->hidden(false)->modifiedRange(Date::sub("PT24H", self::NOW), null)))->returns(7);
$this->dbMock->articleCount->with("~", $this->equalTo((new Context)->unread(true)->hidden(false)))->returns(35); $this->dbMock->articleCount->with("~", $this->equalTo((new Context)->unread(true)->hidden(false)))->returns(35);
// label mocks // label mocks
$this->dbMock->labelList->returns(new Result($this->v($this->labels))); $this->dbMock->labelList->returns(new Result($this->v($this->labels)));
@ -1551,10 +1551,10 @@ LONG_STRING;
[true, ['feed_id' => -4, 'order_by' => "feed_dates"], $out, $c, $fields, $sort, $expFull], [true, ['feed_id' => -4, 'order_by' => "feed_dates"], $out, $c, $fields, $sort, $expFull],
[true, ['feed_id' => -4, 'order_by' => "date_reverse"], $out, $c, $fields, ["edited_date"], $expFull], [true, ['feed_id' => -4, 'order_by' => "date_reverse"], $out, $c, $fields, ["edited_date"], $expFull],
[true, ['feed_id' => 42, 'search' => "interesting"], $out, (clone $c)->subscription(42)->searchTerms(["interesting"]), $fields, $sort, $expFull], [true, ['feed_id' => 42, 'search' => "interesting"], $out, (clone $c)->subscription(42)->searchTerms(["interesting"]), $fields, $sort, $expFull],
[true, ['feed_id' => -6], $out, (clone $c)->unread(false)->markedSince(Date::sub("PT24H", $t)), $fields, ["marked_date desc"], $expFull], [true, ['feed_id' => -6], $out, (clone $c)->unread(false)->markedRange(Date::sub("PT24H", $t), null), $fields, ["marked_date desc"], $expFull],
[true, ['feed_id' => -6, 'view_mode' => "unread"], null, $c, $fields, $sort, $this->respGood([])], [true, ['feed_id' => -6, 'view_mode' => "unread"], null, $c, $fields, $sort, $this->respGood([])],
[true, ['feed_id' => -3], $out, (clone $c)->unread(true)->modifiedSince(Date::sub("PT24H", $t)), $fields, $sort, $expFull], [true, ['feed_id' => -3], $out, (clone $c)->unread(true)->modifiedRange(Date::sub("PT24H", $t), null), $fields, $sort, $expFull],
[true, ['feed_id' => -3, 'view_mode' => "marked"], $out, (clone $c)->unread(true)->starred(true)->modifiedSince(Date::sub("PT24H", $t)), $fields, $sort, $expFull], [true, ['feed_id' => -3, 'view_mode' => "marked"], $out, (clone $c)->unread(true)->starred(true)->modifiedRange(Date::sub("PT24H", $t), null), $fields, $sort, $expFull],
[false, [], null, (clone $c)->limit(null), [], [], $this->respErr("INCORRECT_USAGE")], [false, [], null, (clone $c)->limit(null), [], [], $this->respErr("INCORRECT_USAGE")],
[false, ['feed_id' => 0], null, (clone $c)->limit(null), [], [], $this->respGood([])], [false, ['feed_id' => 0], null, (clone $c)->limit(null), [], [], $this->respGood([])],
[false, ['feed_id' => -1], $comp, (clone $c)->limit(null)->starred(true), ["id"], ["marked_date desc"], $expComp], [false, ['feed_id' => -1], $comp, (clone $c)->limit(null)->starred(true), ["id"], ["marked_date desc"], $expComp],
@ -1572,10 +1572,10 @@ LONG_STRING;
[false, ['feed_id' => -4, 'skip' => 2], $comp, (clone $c)->limit(null)->offset(2), ["id"], $sort, $expComp], [false, ['feed_id' => -4, 'skip' => 2], $comp, (clone $c)->limit(null)->offset(2), ["id"], $sort, $expComp],
[false, ['feed_id' => -4, 'limit' => 5, 'skip' => 2], $comp, (clone $c)->limit(5)->offset(2), ["id"], $sort, $expComp], [false, ['feed_id' => -4, 'limit' => 5, 'skip' => 2], $comp, (clone $c)->limit(5)->offset(2), ["id"], $sort, $expComp],
[false, ['feed_id' => -4, 'since_id' => 47], $comp, (clone $c)->limit(null)->oldestArticle(48), ["id"], $sort, $expComp], [false, ['feed_id' => -4, 'since_id' => 47], $comp, (clone $c)->limit(null)->oldestArticle(48), ["id"], $sort, $expComp],
[false, ['feed_id' => -6], $comp, (clone $c)->limit(null)->unread(false)->markedSince(Date::sub("PT24H", $t)), ["id"], ["marked_date desc"], $expComp], [false, ['feed_id' => -6], $comp, (clone $c)->limit(null)->unread(false)->markedRange(Date::sub("PT24H", $t), null), ["id"], ["marked_date desc"], $expComp],
[false, ['feed_id' => -6, 'view_mode' => "unread"], null, (clone $c)->limit(null), ["id"], $sort, $this->respGood([])], [false, ['feed_id' => -6, 'view_mode' => "unread"], null, (clone $c)->limit(null), ["id"], $sort, $this->respGood([])],
[false, ['feed_id' => -3], $comp, (clone $c)->limit(null)->unread(true)->modifiedSince(Date::sub("PT24H", $t)), ["id"], $sort, $expComp], [false, ['feed_id' => -3], $comp, (clone $c)->limit(null)->unread(true)->modifiedRange(Date::sub("PT24H", $t), null), ["id"], $sort, $expComp],
[false, ['feed_id' => -3, 'view_mode' => "marked"], $comp, (clone $c)->limit(null)->unread(true)->starred(true)->modifiedSince(Date::sub("PT24H", $t)), ["id"], $sort, $expComp], [false, ['feed_id' => -3, 'view_mode' => "marked"], $comp, (clone $c)->limit(null)->unread(true)->starred(true)->modifiedRange(Date::sub("PT24H", $t), null), ["id"], $sort, $expComp],
]; ];
} }

View file

@ -101,10 +101,10 @@ class TestSearch extends \JKingWeb\Arsse\Test\AbstractTest {
'Doubled boolean' => ['unread:true unread:true', (new Context)->unread(true)], 'Doubled boolean' => ['unread:true unread:true', (new Context)->unread(true)],
'Bare blank date' => ['@', new Context], 'Bare blank date' => ['@', new Context],
'Quoted blank date' => ['"@"', new Context], 'Quoted blank date' => ['"@"', new Context],
'Bare ISO date' => ['@2019-03-01', (new Context)->modifiedSince("2019-03-01T00:00:00Z")->notModifiedSince("2019-03-01T23:59:59Z")], 'Bare ISO date' => ['@2019-03-01', (new Context)->modifiedRange("2019-03-01T00:00:00Z", "2019-03-01T23:59:59Z")],
'Quoted ISO date' => ['"@March 1st, 2019"', (new Context)->modifiedSince("2019-03-01T00:00:00Z")->notModifiedSince("2019-03-01T23:59:59Z")], 'Quoted ISO date' => ['"@March 1st, 2019"', (new Context)->modifiedRange("2019-03-01T00:00:00Z", "2019-03-01T23:59:59Z")],
'Bare negative ISO date' => ['-@2019-03-01', (new Context)->not->modifiedSince("2019-03-01T00:00:00Z")->not->notModifiedSince("2019-03-01T23:59:59Z")], 'Bare negative ISO date' => ['-@2019-03-01', (new Context)->not->modifiedRange("2019-03-01T00:00:00Z", "2019-03-01T23:59:59Z")],
'Quoted negative English date' => ['"-@March 1st, 2019"', (new Context)->not->modifiedSince("2019-03-01T00:00:00Z")->not->notModifiedSince("2019-03-01T23:59:59Z")], 'Quoted negative English date' => ['"-@March 1st, 2019"', (new Context)->not->modifiedRange("2019-03-01T00:00:00Z", "2019-03-01T23:59:59Z")],
'Invalid date' => ['@Bugaboo', new Context], 'Invalid date' => ['@Bugaboo', new Context],
'Escaped quoted date 1' => ['"@""Yesterday" and today', (new Context)->searchTerms(["and", "today"])], 'Escaped quoted date 1' => ['"@""Yesterday" and today', (new Context)->searchTerms(["and", "today"])],
'Escaped quoted date 2' => ['"@\\"Yesterday" and today', (new Context)->searchTerms(["and", "today"])], 'Escaped quoted date 2' => ['"@\\"Yesterday" and today', (new Context)->searchTerms(["and", "today"])],
@ -112,8 +112,8 @@ class TestSearch extends \JKingWeb\Arsse\Test\AbstractTest {
'Escaped quoted date 4' => ['"@Yesterday\\and today', new Context], 'Escaped quoted date 4' => ['"@Yesterday\\and today', new Context],
'Escaped quoted date 5' => ['"@Yesterday"and today', (new Context)->searchTerms(["today"])], 'Escaped quoted date 5' => ['"@Yesterday"and today', (new Context)->searchTerms(["today"])],
'Contradictory dates' => ['@Yesterday @Today', null], 'Contradictory dates' => ['@Yesterday @Today', null],
'Doubled date' => ['"@March 1st, 2019" @2019-03-01', (new Context)->modifiedSince("2019-03-01T00:00:00Z")->notModifiedSince("2019-03-01T23:59:59Z")], 'Doubled date' => ['"@March 1st, 2019" @2019-03-01', (new Context)->modifiedRange("2019-03-01T00:00:00Z", "2019-03-01T23:59:59Z")],
'Doubled negative date' => ['"-@March 1st, 2019" -@2019-03-01', (new Context)->not->modifiedSince("2019-03-01T00:00:00Z")->not->notModifiedSince("2019-03-01T23:59:59Z")], 'Doubled negative date' => ['"-@March 1st, 2019" -@2019-03-01', (new Context)->not->modifiedRange("2019-03-01T00:00:00Z", "2019-03-01T23:59:59Z")],
]; ];
} }