diff --git a/lib/Database.php b/lib/Database.php index 5e44d384..d60a0281 100644 --- a/lib/Database.php +++ b/lib/Database.php @@ -1118,8 +1118,9 @@ class Database { $icon = $this->db->prepare("INSERT INTO arsse_icons(url, type, data) values(?, ?, ?)", "str", "str", "blob")->run($feed->iconUrl, $feed->iconType, $feed->iconData)->lastId(); } } - // actually perform updates - foreach ($feed->newItems as $article) { + $articleMap = []; + // actually perform updates, starting with inserting new articles + foreach ($feed->newItems as $k => $article) { $articleID = $qInsertArticle->run( $article->url, $article->title, @@ -1133,14 +1134,20 @@ class Database { $article->titleContentHash, $feedID )->lastId(); + // note the new ID for later use + $articleMap[$k] = $articleID; + // insert any enclosures if ($article->enclosureUrl) { $qInsertEnclosure->run($articleID, $article->enclosureUrl, $article->enclosureType); } + // insert any categories foreach ($article->categories as $c) { $qInsertCategory->run($articleID, $c); } + // assign a new edition ID to the article $qInsertEdition->run($articleID); } + // next update existing artricles which have been edited foreach ($feed->changedItems as $articleID => $article) { $qUpdateArticle->run( $article->url, @@ -1155,6 +1162,7 @@ class Database { $article->titleContentHash, $articleID ); + // delete all enclosures and categories and re-insert them $qDeleteEnclosures->run($articleID); $qDeleteCategories->run($articleID); if ($article->enclosureUrl) { @@ -1163,9 +1171,33 @@ class Database { foreach ($article->categories as $c) { $qInsertCategory->run($articleID, $c); } + // assign a new edition ID to this version of the article $qInsertEdition->run($articleID); $qClearReadMarks->run($articleID); } + // hide or unhide any filtered articles + foreach ($feed->filteredItems as $user => $filterData) { + $hide = []; + $unhide = []; + foreach ($filterData['new'] as $index => $keep) { + if (!$keep) { + $hide[] = $articleMap[$index]; + } + } + foreach ($filterData['changed'] as $article => $keep) { + if (!$keep) { + $hide[] = $article; + } else { + $unhide[] = $article; + } + } + if ($hide) { + $this->articleMark($user, ['hidden' => true], (new Context)->articles($hide), false); + } + if ($unhide) { + $this->articleMark($user, ['hidden' => false], (new Context)->articles($unhide), false); + } + } // lastly update the feed database itself with updated information. $this->db->prepareArray( "UPDATE arsse_feeds SET title = ?, source = ?, updated = CURRENT_TIMESTAMP, modified = ?, etag = ?, err_count = 0, err_msg = '', next_fetch = ?, size = ?, icon = ? WHERE id = ?", @@ -1693,8 +1725,9 @@ class Database { * @param string $user The user who owns the articles to be modified * @param array $data An associative array of properties to modify. Anything not specified will remain unchanged * @param Context $context The query context to match articles against + * @param bool $updateTimestamp Whether to also update the timestamp. This should only be false if a mark is changed as a result of an automated action not taken by the user */ - public function articleMark(string $user, array $data, Context $context = null): int { + public function articleMark(string $user, array $data, Context $context = null, bool $updateTimestamp = true): int { $data = [ 'read' => $data['read'] ?? null, 'starred' => $data['starred'] ?? null, @@ -1743,7 +1776,11 @@ class Database { $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues()); } // finally set the modification date for all touched marks and return the number of affected marks - $out = $this->db->query("UPDATE arsse_marks set modified = CURRENT_TIMESTAMP, touched = 0 where touched = 1")->changes(); + if ($updateTimestamp) { + $out = $this->db->query("UPDATE arsse_marks set modified = CURRENT_TIMESTAMP, touched = 0 where touched = 1")->changes(); + } else { + $out = $this->db->query("UPDATE arsse_marks set touched = 0 where touched = 1")->changes(); + } } else { if (!isset($data['read']) && ($context->edition() || $context->editions())) { // get the articles associated with the requested editions @@ -1763,7 +1800,10 @@ class Database { return isset($v); }); [$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); + if ($updateTimestamp) { + $set .= ", modified = CURRENT_TIMESTAMP"; + } + $q->setBody("UPDATE arsse_marks set $set 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(); } $tr->commit(); diff --git a/tests/cases/Database/SeriesFeed.php b/tests/cases/Database/SeriesFeed.php index 65a2931f..5cc0d84c 100644 --- a/tests/cases/Database/SeriesFeed.php +++ b/tests/cases/Database/SeriesFeed.php @@ -80,7 +80,7 @@ trait SeriesFeed { [3,'john.doe@example.com',3,'\w+',null], [4,'john.doe@example.com',4,'\w+',"["], // invalid rule leads to both rules being ignored [5,'john.doe@example.com',5,null,'and/or'], - [6,'jane.doe@example.com',1,'^(?i)[a-z]+','bluberry'], + [6,'jane.doe@example.com',1,'^(?i)[a-z]+','3|6'], ], ], 'arsse_articles' => [ @@ -129,19 +129,20 @@ trait SeriesFeed { 'subscription' => "int", 'read' => "bool", 'starred' => "bool", + 'hidden' => "bool", 'modified' => "datetime", ], 'rows' => [ // Jane's marks - [1,6,1,0,$past], - [2,6,1,0,$past], - [3,6,1,1,$past], - [4,6,1,0,$past], - [5,6,1,1,$past], + [1,6,1,0,0,$past], + [2,6,1,0,0,$past], + [3,6,1,1,0,$past], + [4,6,1,0,1,$past], + [5,6,1,1,0,$past], // John's marks - [1,1,1,0,$past], - [3,1,1,0,$past], - [4,1,0,1,$past], + [1,1,1,0,0,$past], + [3,1,1,0,0,$past], + [4,1,0,1,0,$past], ], ], 'arsse_enclosures' => [ @@ -210,7 +211,7 @@ trait SeriesFeed { public function provideFilterRules(): iterable { return [ - [1, ['jane.doe@example.com' => ['keep' => "`^(?i)[a-z]+`u", 'block' => "`bluberry`u"], 'john.doe@example.com' => ['keep' => "", 'block' => "`^Sport$`u"]]], + [1, ['jane.doe@example.com' => ['keep' => "`^(?i)[a-z]+`u", 'block' => "`3|6`u"], 'john.doe@example.com' => ['keep' => "", 'block' => "`^Sport$`u"]]], [2, []], [3, ['john.doe@example.com' => ['keep' => '`\w+`u', 'block' => ""]]], [4, []], @@ -225,7 +226,7 @@ trait SeriesFeed { $state = $this->primeExpectations($this->data, [ 'arsse_articles' => ["id", "feed","url","title","author","published","edited","content","guid","url_title_hash","url_content_hash","title_content_hash","modified"], 'arsse_editions' => ["id","article","modified"], - 'arsse_marks' => ["subscription","article","read","starred","modified"], + 'arsse_marks' => ["subscription","article","read","starred","hidden","modified"], 'arsse_feeds' => ["id","size"], ]); $state['arsse_articles']['rows'][2] = [3,1,'http://example.com/3','Article title 3 (updated)','','2000-01-03 00:00:00','2000-01-03 00:00:00','
Article content 3
','31a6594500a48b59fcc8a075ce82b946c9c3c782460d088bd7b8ef3ede97ad92','6cc99be662ef3486fef35a890123f18d74c29a32d714802d743c5b4ef713315a','b278380e984cefe63f0e412b88ffc9cb0befdfa06fdc00bace1da99a8daff406','d5faccc13bf8267850a1e8e61f95950a0f34167df2c8c58011c0aaa6367026ac',$now]; @@ -236,9 +237,10 @@ trait SeriesFeed { [7,3,$now], [8,4,$now], ]); - $state['arsse_marks']['rows'][2] = [6,3,0,1,$now]; - $state['arsse_marks']['rows'][3] = [6,4,0,0,$now]; - $state['arsse_marks']['rows'][6] = [1,3,0,0,$now]; + $state['arsse_marks']['rows'][2] = [6,3,0,1,1,$now]; + $state['arsse_marks']['rows'][3] = [6,4,0,0,0,$now]; + $state['arsse_marks']['rows'][6] = [1,3,0,0,0,$now]; + $state['arsse_marks']['rows'][] = [6,8,0,0,1,null]; $state['arsse_feeds']['rows'][0] = [1,6]; $this->compareExpectations(static::$drv, $state); // update a valid feed which previously had an error