Browse Source

Add article selection by tag

microsub
J. King 5 years ago
parent
commit
5de1844f6d
  1. 10
      lib/Context/ExclusionContext.php
  2. 15
      lib/Database.php
  3. 218
      tests/cases/Database/SeriesArticle.php
  4. 2
      tests/cases/Misc/TestContext.php

10
lib/Context/ExclusionContext.php

@ -12,6 +12,8 @@ use JKingWeb\Arsse\Misc\Date;
class ExclusionContext {
public $folder;
public $folderShallow;
public $tag;
public $tagName;
public $subscription;
public $edition;
public $article;
@ -101,6 +103,14 @@ class ExclusionContext {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function tag(int $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function tagName(string $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function subscription(int $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}

15
lib/Database.php

@ -1325,6 +1325,21 @@ class Database {
$q->setWhereNot("arsse_articles.id in (select article from labelled where label_name = ?)", "str", $context->not->labelName);
}
}
if ($context->tag() || $context->not->tag() || $context->tagName() || $context->not->tagName()) {
$q->setCTE("tagged(id,name,subscription)","SELECT arsse_tags.id, arsse_tags.name, arsse_tag_members.subscription FROM arsse_tag_members join arsse_tags on arsse_tags.id = arsse_tag_members.tag WHERE arsse_tags.owner = ? and assigned = 1", "str", $user);
if ($context->tag()) {
$q->setWhere("arsse_subscriptions.id in (select subscription from tagged where id = ?)", "int", $context->tag);
}
if ($context->not->tag()) {
$q->setWhereNot("arsse_subscriptions.id in (select subscription from tagged where id = ?)", "int", $context->not->tag);
}
if ($context->tagName()) {
$q->setWhere("arsse_subscriptions.id in (select subscription from tagged where name = ?)", "str", $context->tagName);
}
if ($context->not->tagName()) {
$q->setWhereNot("arsse_subscriptions.id in (select subscription from tagged where name = ?)", "str", $context->not->tagName);
}
}
if ($context->folder()) {
// add a common table expression to list the folder and its children so that we select from the entire subtree
$q->setCTE("folders(folder)", "SELECT ? union select id from arsse_folders join folders on parent = folder", "int", $context->folder);

218
tests/cases/Database/SeriesArticle.php

@ -28,6 +28,28 @@ trait SeriesArticle {
["john.doe@example.net", "", "John Doe"],
],
],
'arsse_feeds' => [
'columns' => [
'id' => "int",
'url' => "str",
'title' => "str",
],
'rows' => [
[1,"http://example.com/1", "Feed 1"],
[2,"http://example.com/2", "Feed 2"],
[3,"http://example.com/3", "Feed 3"],
[4,"http://example.com/4", "Feed 4"],
[5,"http://example.com/5", "Feed 5"],
[6,"http://example.com/6", "Feed 6"],
[7,"http://example.com/7", "Feed 7"],
[8,"http://example.com/8", "Feed 8"],
[9,"http://example.com/9", "Feed 9"],
[10,"http://example.com/10", "Feed 10"],
[11,"http://example.com/11", "Feed 11"],
[12,"http://example.com/12", "Feed 12"],
[13,"http://example.com/13", "Feed 13"],
]
],
'arsse_folders' => [
'columns' => [
'id' => "int",
@ -47,26 +69,21 @@ trait SeriesArticle {
[9, "john.doe@example.net", null, "Politics"],
]
],
'arsse_feeds' => [
'arsse_tags' => [
'columns' => [
'id' => "int",
'url' => "str",
'title' => "str",
'id' => "int",
'owner' => "str",
'name' => "str",
],
'rows' => [
[1,"http://example.com/1", "Feed 1"],
[2,"http://example.com/2", "Feed 2"],
[3,"http://example.com/3", "Feed 3"],
[4,"http://example.com/4", "Feed 4"],
[5,"http://example.com/5", "Feed 5"],
[6,"http://example.com/6", "Feed 6"],
[7,"http://example.com/7", "Feed 7"],
[8,"http://example.com/8", "Feed 8"],
[9,"http://example.com/9", "Feed 9"],
[10,"http://example.com/10", "Feed 10"],
[11,"http://example.com/11", "Feed 11"],
[12,"http://example.com/12", "Feed 12"],
[13,"http://example.com/13", "Feed 13"],
[1, "john.doe@example.com", "Technology"],
[2, "john.doe@example.com", "Software"],
[3, "john.doe@example.com", "Rocketry"],
[4, "jane.doe@example.com", "Politics"],
[5, "john.doe@example.com", "Politics"],
[6, "john.doe@example.net", "Technology"],
[7, "john.doe@example.net", "Software"],
[8, "john.doe@example.net", "Politics"],
]
],
'arsse_subscriptions' => [
@ -94,6 +111,25 @@ trait SeriesArticle {
[14,"john.doe@example.net",4, 7,null],
]
],
'arsse_tag_members' => [
'columns' => [
'tag' => "int",
'subscription' => "int",
'assigned' => "bool",
],
'rows' => [
[1,3,1],
[1,4,1],
[2,4,1],
[5,1,0],
[5,4,1],
[5,5,1],
[6,13,1],
[6,14,1],
[7,13,1],
[8,12,1],
],
],
'arsse_articles' => [
'columns' => [
'id' => "int",
@ -387,76 +423,84 @@ trait SeriesArticle {
public function provideContextMatches() {
return [
"Blank context" => [new Context, [1,2,3,4,5,6,7,8,19,20]],
"Folder tree" => [(new Context)->folder(1), [5,6,7,8]],
"Leaf folder" => [(new Context)->folder(6), [7,8]],
"Root folder only" => [(new Context)->folderShallow(0), [1,2,3,4]],
"Shallow folder" => [(new Context)->folderShallow(1), [5,6]],
"Subscription" => [(new Context)->subscription(5), [19,20]],
"Unread" => [(new Context)->subscription(5)->unread(true), [20]],
"Read" => [(new Context)->subscription(5)->unread(false), [19]],
"Starred" => [(new Context)->starred(true), [1,20]],
"Unstarred" => [(new Context)->starred(false), [2,3,4,5,6,7,8,19]],
"Starred and Read" => [(new Context)->starred(true)->unread(false), [1]],
"Starred and Read in subscription" => [(new Context)->starred(true)->unread(false)->subscription(5), []],
"Annotated" => [(new Context)->annotated(true), [2]],
"Not annotated" => [(new Context)->annotated(false), [1,3,4,5,6,7,8,19,20]],
"Labelled" => [(new Context)->labelled(true), [1,5,8,19,20]],
"Not labelled" => [(new Context)->labelled(false), [2,3,4,6,7]],
"Not after edition 999" => [(new Context)->subscription(5)->latestEdition(999), [19]],
"Not after edition 19" => [(new Context)->subscription(5)->latestEdition(19), [19]],
"Not before edition 999" => [(new Context)->subscription(5)->oldestEdition(999), [20]],
"Not before edition 1001" => [(new Context)->subscription(5)->oldestEdition(1001), [20]],
"Not after article 3" => [(new Context)->latestArticle(3), [1,2,3]],
"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 2010" => [(new Context)->modifiedSince("2010-01-01T00:00:00Z"), [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 2000" => [(new Context)->notModifiedSince("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 2010" => [(new Context)->markedSince("2010-01-01T00:00:00Z"), [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 2005" => [(new Context)->notMarkedSince("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 in 2010" => [(new Context)->markedSince("2010-01-01T00:00:00Z")->notMarkedSince("2010-12-31T23:59:59Z"), [2,4,6,20]],
"Paged results" => [(new Context)->limit(2)->oldestEdition(4), [4,5]],
"Reversed paged results" => [(new Context)->limit(2)->latestEdition(7)->reverse(true), [7,6]],
"With label ID 1" => [(new Context)->label(1), [1,19]],
"With label ID 2" => [(new Context)->label(2), [1,5,20]],
"With label 'Interesting'" => [(new Context)->labelName("Interesting"), [1,19]],
"With label 'Fascinating'" => [(new Context)->labelName("Fascinating"), [1,5,20]],
"Article ID 20" => [(new Context)->article(20), [20]],
"Edition ID 1001" => [(new Context)->edition(1001), [20]],
"Multiple articles" => [(new Context)->articles([1,20,50]), [1,20]],
"Multiple starred articles" => [(new Context)->articles([1,2,3])->starred(true), [1]],
"Multiple unstarred articles" => [(new Context)->articles([1,2,3])->starred(false), [2,3]],
"Multiple articles" => [(new Context)->articles([1,20,50]), [1,20]],
"Multiple editions" => [(new Context)->editions([1,1001,50]), [1,20]],
"150 articles" => [(new Context)->articles(range(1, Database::LIMIT_SET_SIZE * 3)), [1,2,3,4,5,6,7,8,19,20]],
"Search title or content 1" => [(new Context)->searchTerms(["Article"]), [1,2,3]],
"Search title or content 2" => [(new Context)->searchTerms(["one", "first"]), [1]],
"Search title or content 3" => [(new Context)->searchTerms(["one first"]), []],
"Search title 1" => [(new Context)->titleTerms(["two"]), [2]],
"Search title 2" => [(new Context)->titleTerms(["title two"]), [2]],
"Search title 3" => [(new Context)->titleTerms(["two", "title"]), [2]],
"Search title 4" => [(new Context)->titleTerms(["two title"]), []],
"Search note 1" => [(new Context)->annotationTerms(["some"]), [2]],
"Search note 2" => [(new Context)->annotationTerms(["some Note"]), [2]],
"Search note 3" => [(new Context)->annotationTerms(["note", "some"]), [2]],
"Search note 4" => [(new Context)->annotationTerms(["some", "sauce"]), []],
"Search author 1" => [(new Context)->authorTerms(["doe"]), [4,5,6,7]],
"Search author 2" => [(new Context)->authorTerms(["jane doe"]), [6,7]],
"Search author 3" => [(new Context)->authorTerms(["doe", "jane"]), [6,7]],
"Search author 4" => [(new Context)->authorTerms(["doe jane"]), []],
"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 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]],
"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]],
"Excluding label ID 2" => [(new Context)->not->label(2), [2,3,4,6,7,8,19]],
"Excluding label 'Fascinating'" => [(new Context)->not->labelName("Fascinating"), [2,3,4,6,7,8,19]],
"Search 501 terms" => [(new Context)->searchTerms(array_merge(range(1,500),[str_repeat("a", 1000)])), []],
'Blank context' => [new Context, [1,2,3,4,5,6,7,8,19,20]],
'Folder tree' => [(new Context)->folder(1), [5,6,7,8]],
'Leaf folder' => [(new Context)->folder(6), [7,8]],
'Root folder only' => [(new Context)->folderShallow(0), [1,2,3,4]],
'Shallow folder' => [(new Context)->folderShallow(1), [5,6]],
'Subscription' => [(new Context)->subscription(5), [19,20]],
'Unread' => [(new Context)->subscription(5)->unread(true), [20]],
'Read' => [(new Context)->subscription(5)->unread(false), [19]],
'Starred' => [(new Context)->starred(true), [1,20]],
'Unstarred' => [(new Context)->starred(false), [2,3,4,5,6,7,8,19]],
'Starred and Read' => [(new Context)->starred(true)->unread(false), [1]],
'Starred and Read in subscription' => [(new Context)->starred(true)->unread(false)->subscription(5), []],
'Annotated' => [(new Context)->annotated(true), [2]],
'Not annotated' => [(new Context)->annotated(false), [1,3,4,5,6,7,8,19,20]],
'Labelled' => [(new Context)->labelled(true), [1,5,8,19,20]],
'Not labelled' => [(new Context)->labelled(false), [2,3,4,6,7]],
'Not after edition 999' => [(new Context)->subscription(5)->latestEdition(999), [19]],
'Not after edition 19' => [(new Context)->subscription(5)->latestEdition(19), [19]],
'Not before edition 999' => [(new Context)->subscription(5)->oldestEdition(999), [20]],
'Not before edition 1001' => [(new Context)->subscription(5)->oldestEdition(1001), [20]],
'Not after article 3' => [(new Context)->latestArticle(3), [1,2,3]],
'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 2010' => [(new Context)->modifiedSince("2010-01-01T00:00:00Z"), [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 2000' => [(new Context)->notModifiedSince("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 2010' => [(new Context)->markedSince("2010-01-01T00:00:00Z"), [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 2005' => [(new Context)->notMarkedSince("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 in 2010' => [(new Context)->markedSince("2010-01-01T00:00:00Z")->notMarkedSince("2010-12-31T23:59:59Z"), [2,4,6,20]],
'Paged results' => [(new Context)->limit(2)->oldestEdition(4), [4,5]],
'Reversed paged results' => [(new Context)->limit(2)->latestEdition(7)->reverse(true), [7,6]],
'With label ID 1' => [(new Context)->label(1), [1,19]],
'With label ID 2' => [(new Context)->label(2), [1,5,20]],
'With label "Interesting"' => [(new Context)->labelName("Interesting"), [1,19]],
'With label "Fascinating"' => [(new Context)->labelName("Fascinating"), [1,5,20]],
'Article ID 20' => [(new Context)->article(20), [20]],
'Edition ID 1001' => [(new Context)->edition(1001), [20]],
'Multiple articles' => [(new Context)->articles([1,20,50]), [1,20]],
'Multiple starred articles' => [(new Context)->articles([1,2,3])->starred(true), [1]],
'Multiple unstarred articles' => [(new Context)->articles([1,2,3])->starred(false), [2,3]],
'Multiple articles' => [(new Context)->articles([1,20,50]), [1,20]],
'Multiple editions' => [(new Context)->editions([1,1001,50]), [1,20]],
'150 articles' => [(new Context)->articles(range(1, Database::LIMIT_SET_SIZE * 3)), [1,2,3,4,5,6,7,8,19,20]],
'Search title or content 1' => [(new Context)->searchTerms(["Article"]), [1,2,3]],
'Search title or content 2' => [(new Context)->searchTerms(["one", "first"]), [1]],
'Search title or content 3' => [(new Context)->searchTerms(["one first"]), []],
'Search title 1' => [(new Context)->titleTerms(["two"]), [2]],
'Search title 2' => [(new Context)->titleTerms(["title two"]), [2]],
'Search title 3' => [(new Context)->titleTerms(["two", "title"]), [2]],
'Search title 4' => [(new Context)->titleTerms(["two title"]), []],
'Search note 1' => [(new Context)->annotationTerms(["some"]), [2]],
'Search note 2' => [(new Context)->annotationTerms(["some Note"]), [2]],
'Search note 3' => [(new Context)->annotationTerms(["note", "some"]), [2]],
'Search note 4' => [(new Context)->annotationTerms(["some", "sauce"]), []],
'Search author 1' => [(new Context)->authorTerms(["doe"]), [4,5,6,7]],
'Search author 2' => [(new Context)->authorTerms(["jane doe"]), [6,7]],
'Search author 3' => [(new Context)->authorTerms(["doe", "jane"]), [6,7]],
'Search author 4' => [(new Context)->authorTerms(["doe jane"]), []],
'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 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]],
'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]],
'Excluding label ID 2' => [(new Context)->not->label(2), [2,3,4,6,7,8,19]],
'Excluding label "Fascinating"' => [(new Context)->not->labelName("Fascinating"), [2,3,4,6,7,8,19]],
'Search 501 terms' => [(new Context)->searchTerms(array_merge(range(1,500),[str_repeat("a", 1000)])), []],
'With tag ID 1' => [(new Context)->tag(1), [5,6,7,8]],
'With tag ID 5' => [(new Context)->tag(5), [7,8,19,20]],
'With tag "Technology"' => [(new Context)->tagName("Technology"), [5,6,7,8]],
'With tag "Politics"' => [(new Context)->tagName("Politics"), [7,8,19,20]],
'Excluding tag ID 1' => [(new Context)->not->tag(1), [1,2,3,4,19,20]],
'Excluding tag ID 5' => [(new Context)->not->tag(5), [1,2,3,4,5,6]],
'Excluding tag "Technology"' => [(new Context)->not->tagName("Technology"), [1,2,3,4,19,20]],
'Excluding tag "Politics"' => [(new Context)->not->tagName("Politics"), [1,2,3,4,5,6]],
];
}

2
tests/cases/Misc/TestContext.php

@ -30,6 +30,8 @@ class TestContext extends \JKingWeb\Arsse\Test\AbstractTest {
'offset' => 5,
'folder' => 42,
'folderShallow' => 42,
'tag' => 44,
'tagName' => "XLIV",
'subscription' => 2112,
'article' => 255,
'edition' => 65535,

Loading…
Cancel
Save