From d5cd5b6a17503c5774d45a9c0ebe92dc5f220ea9 Mon Sep 17 00:00:00 2001 From: "J. King" Date: Tue, 15 Dec 2020 13:20:03 -0500 Subject: [PATCH] Implement hidden marks Tests are still needed --- lib/Context/Context.php | 5 +++++ lib/Database.php | 33 +++++++++++++++++++++++--------- sql/MySQL/6.sql | 1 + sql/PostgreSQL/6.sql | 1 + sql/SQLite3/6.sql | 4 +++- tests/cases/Misc/TestContext.php | 1 + 6 files changed, 35 insertions(+), 10 deletions(-) diff --git a/lib/Context/Context.php b/lib/Context/Context.php index fb1236a..8e1b699 100644 --- a/lib/Context/Context.php +++ b/lib/Context/Context.php @@ -13,6 +13,7 @@ class Context extends ExclusionContext { public $offset = 0; public $unread; public $starred; + public $hidden; public $labelled; public $annotated; @@ -46,6 +47,10 @@ class Context extends ExclusionContext { return $this->act(__FUNCTION__, func_num_args(), $spec); } + public function hidden(bool $spec = null) { + return $this->act(__FUNCTION__, func_num_args(), $spec); + } + public function labelled(bool $spec = null) { return $this->act(__FUNCTION__, func_num_args(), $spec); } diff --git a/lib/Database.php b/lib/Database.php index 799968f..30a126f 100644 --- a/lib/Database.php +++ b/lib/Database.php @@ -741,7 +741,7 @@ class Database { "SELECT s.id as id, s.feed as feed, - f.url,source,folder,pinned,err_count,err_msg,order_type,added, + f.url,source,folder,pinned,err_count,err_msg,order_type,added,keep_rule,block_rule, f.updated as updated, f.modified as edited, s.modified as modified, @@ -762,8 +762,8 @@ class Database { // topmost folders belonging to the user $q->setCTE("topmost(f_id,top)", "SELECT id,id from arsse_folders where owner = ? and parent is null union all select id,top from arsse_folders join topmost on parent=f_id", ["str"], [$user]); if ($id) { - // this condition facilitates the implementation of subscriptionPropertiesGet, which would otherwise have to duplicate the complex query; it takes precedence over a specified folder // if an ID is specified, add a suitable WHERE condition and bindings + // this condition facilitates the implementation of subscriptionPropertiesGet, which would otherwise have to duplicate the complex query; it takes precedence over a specified folder $q->setWhere("s.id = ?", "int", $id); } elseif ($folder && $recursive) { // if a folder is specified and we're listing recursively, add a common table expression to list it and its children so that we select from the entire subtree @@ -1194,6 +1194,19 @@ class Database { return $out; } + /** Retrieves the set of filters users have applied to a given feed + * + * Each record includes the following keys: + * + * - "owner": The user for whom to apply the filters + * - "sub": The subscription ID which ties the user to the feed + * - "keep": The "keep" rule; any articles which fail to match this rule are hidden + * - "block": The block rule; any article which matches this rule are hidden + */ + public function feedRulesGet(int $feedID): Db\Result { + return $this->db->prepare("SELECT owner, id as sub, keep_rule as keep, block_rule as block from arsse_subscriptions where feed = ? and (coalesce(keep_rule, '') || coalesce(block_rule, '')) <> ''", "int")->run($feedID); + } + /** Retrieves various identifiers for the latest $count articles in the given newsfeed. The identifiers are: * * - "id": The database record key for the article @@ -1652,6 +1665,7 @@ class Database { * * - "read": Whether the article should be marked as read (true) or unread (false) * - "starred": Whether the article should (true) or should not (false) be marked as starred/favourite + * - "hidden": Whether the article should (true) or should not (false) be suppressed from normal listings; this is normally set by the system rather than the user directly * - "note": A string containing a freeform plain-text note for the article * * @param string $user The user who owns the articles to be modified @@ -1662,22 +1676,23 @@ class Database { $data = [ 'read' => $data['read'] ?? null, 'starred' => $data['starred'] ?? null, + 'hidden' => $data['hidden'] ?? null, 'note' => $data['note'] ?? null, ]; - if (!isset($data['read']) && !isset($data['starred']) && !isset($data['note'])) { + if (!isset($data['read']) && !isset($data['starred']) && !isset($data['hidden']) && !isset($data['note'])) { return 0; } $context = $context ?? new Context; $tr = $this->begin(); $out = 0; - if ($data['read'] || $data['starred'] || strlen($data['note'] ?? "")) { + if ($data['read'] || $data['starred'] || $data['hidden'] || strlen($data['note'] ?? "")) { // first prepare a query to insert any missing marks rows for the articles we want to mark // but only insert new mark records if we're setting at least one "positive" mark $q = $this->articleQuery($user, $context, ["id", "subscription", "note"]); - $q->setWhere("arsse_marks.starred is null"); // null means there is no marks row for the article + $q->setWhere("arsse_marks.starred is null"); // null means there is no marks row for the article, because the column is defined not-null $this->db->prepare("INSERT INTO arsse_marks(article,subscription,note) ".$q->getQuery(), $q->getTypes())->run($q->getValues()); } - if (isset($data['read']) && (isset($data['starred']) || isset($data['note'])) && ($context->edition() || $context->editions())) { + if (isset($data['read']) && (isset($data['starred']) || isset($data['hidden']) || isset($data['note'])) && ($context->edition() || $context->editions())) { // if marking by edition both read and something else, do separate marks for starred and note than for read // marking as read is ignored if the edition is not the latest, but the same is not true of the other two marks $this->db->query("UPDATE arsse_marks set touched = 0 where touched <> 0"); @@ -1693,7 +1708,7 @@ class Database { } else { $context->articles($this->editionArticle(...$context->editions))->editions(null); } - // set starred and/or note marks (unless all requested editions actually do not exist) + // set starred, hidden, and/or note marks (unless all requested editions actually do not exist) if ($context->article || $context->articles) { $q = $this->articleQuery($user, $context, ["id", "subscription"]); $q->setWhere("(arsse_marks.note <> coalesce(?,arsse_marks.note) or arsse_marks.starred <> coalesce(?,arsse_marks.starred))", ["str", "bool"], [$data['note'], $data['starred']]); @@ -1701,7 +1716,7 @@ class Database { $data = array_filter($data, function($v) { return isset($v); }); - [$set, $setTypes, $setValues] = $this->generateSet($data, ['starred' => "bool", 'note' => "str"]); + [$set, $setTypes, $setValues] = $this->generateSet($data, ['starred' => "bool", 'hidden' => "bool", 'note' => "str"]); $q->setBody("UPDATE arsse_marks set touched = 1, $set where article in(select article from target_articles) and subscription in(select distinct subscription from target_articles)", $setTypes, $setValues); $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues()); } @@ -1725,7 +1740,7 @@ class Database { $data = array_filter($data, function($v) { return isset($v); }); - [$set, $setTypes, $setValues] = $this->generateSet($data, ['read' => "bool", 'starred' => "bool", 'note' => "str"]); + [$set, $setTypes, $setValues] = $this->generateSet($data, ['read' => "bool", 'starred' => "bool", 'hidden' => "bool", 'note' => "str"]); $q->setBody("UPDATE arsse_marks set $set, modified = CURRENT_TIMESTAMP where article in(select article from target_articles) and subscription in(select distinct subscription from target_articles)", $setTypes, $setValues); $out = $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues())->changes(); } diff --git a/sql/MySQL/6.sql b/sql/MySQL/6.sql index 9370e27..c2f8b53 100644 --- a/sql/MySQL/6.sql +++ b/sql/MySQL/6.sql @@ -8,6 +8,7 @@ alter table arsse_tokens add column data longtext default null; alter table arsse_subscriptions add column keep_rule longtext default null; alter table arsse_subscriptions add column block_rule longtext default null; +alter table arsse_marks add column hidden boolean not null default 0; alter table arsse_users add column num bigint unsigned unique; alter table arsse_users add column admin boolean not null default 0; diff --git a/sql/PostgreSQL/6.sql b/sql/PostgreSQL/6.sql index f936b87..a27b87a 100644 --- a/sql/PostgreSQL/6.sql +++ b/sql/PostgreSQL/6.sql @@ -8,6 +8,7 @@ alter table arsse_tokens add column data text default null; alter table arsse_subscriptions add column keep_rule text default null; alter table arsse_subscriptions add column block_rule text default null; +alter table arsse_marks add column hidden smallint not null default 0; alter table arsse_users add column num bigint unique; alter table arsse_users add column admin smallint not null default 0; diff --git a/sql/SQLite3/6.sql b/sql/SQLite3/6.sql index 752c056..3c5f358 100644 --- a/sql/SQLite3/6.sql +++ b/sql/SQLite3/6.sql @@ -6,9 +6,11 @@ -- This is a speculative addition to support OAuth login in the future alter table arsse_tokens add column data text default null; --- Add columns to subscriptions to store "keep" and "block" filtering rules from Miniflux +-- Add columns to subscriptions to store "keep" and "block" filtering rules from Miniflux, +-- as well as a column to mark articles as hidden for users alter table arsse_subscriptions add column keep_rule text default null; alter table arsse_subscriptions add column block_rule text default null; +alter table arsse_marks add column hidden boolean not null default 0; -- Add numeric identifier and admin columns to the users table create table arsse_users_new( diff --git a/tests/cases/Misc/TestContext.php b/tests/cases/Misc/TestContext.php index 037ca8e..46ecaaf 100644 --- a/tests/cases/Misc/TestContext.php +++ b/tests/cases/Misc/TestContext.php @@ -46,6 +46,7 @@ class TestContext extends \JKingWeb\Arsse\Test\AbstractTest { 'oldestEdition' => 1337, 'unread' => true, 'starred' => true, + 'hidden' => true, 'modifiedSince' => new \DateTime(), 'notModifiedSince' => new \DateTime(), 'markedSince' => new \DateTime(),