protected function articleFilter(Context $context, QueryFilter $q = null) {
/** Transforms a selection context for articles into a set of terms for an SQL "where" clause */
protected function articleFilter(Context $context, QueryFilter $q = null): QueryFilter {
$q = $q ?? new QueryFilter;
$colDefs = $this->articleColumns();
// handle the simple context options
@ -1919,11 +1920,10 @@ 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 RootContext $context The query context to match articles against
* @param Context|UnionContext $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, RootContext $context = null, bool $updateTimestamp = true): int {
// normalize requested marks
$data = [
'read' => $data['read'] ?? null,
'starred' => $data['starred'] ?? null,
@ -1931,94 +1931,102 @@ class Database {
'note' => $data['note'] ?? null,
];
if (!isset($data['read']) && !isset($data['starred']) && !isset($data['hidden']) && !isset($data['note'])) {
// no changes were requested
return 0;
}
// begin a transaction
$tr = $this->begin();
$context = $context ?? new Context;
if ($context instanceof UnionContext) {
$out = 0;
// if we were provided a union context, mark each context in series;
// this is atomic (due to the transaction already begun), but may
// result in multiple timestamps as well as an inaccurate output
// integer as articles may be in multiple contexts
// TODO: The above quirks could be fixed by resolving the union
// context to a single list of article IDs noting which are
// valid read marks (if editions were selected in any of the child
// contexts); this functionality is not needed yet, however
// prepare the subquery which selects the articles to act on
$subq = $this->articleQuery($user, $context);
$subq->setWhere("(arsse_articles.note <> coalesce(?,arsse_articles.note) or arsse_articles.starred <> coalesce(?,arsse_articles.starred) or arsse_articles.read <> coalesce(?,arsse_articles.read) or arsse_articles.hidden <> coalesce(?,arsse_articles.hidden))", ["str", "bool", "bool", "bool"], [$data['note'], $data['starred'], $data['read'], $data['hidden']]);
// if we're marking as read/unread by edition, we have to use a different query so that we only mark read/unread if the edition is the latest for the article
if (isset($data['read']) && ($context->edition() || $context->editions())) {
// set up the "SET" clause for the update
$setData = array_filter($data, function($v, $k) {
// filter out anyhing with a value of null (no change), as well as the "rea" key as it required special handling
url_title_hashtextnotnull,-- hash of URL + title; used when checking for updates and for identification if there is no guid
url_content_hashtextnotnull,-- hash of URL + content + enclosure URL + enclosure content type; used when checking for updates and for identification if there is no guid
title_content_hashtextnotnull,-- hash of title + content + enclosure URL + enclosure content type; used when checking for updates and for identification if there is no guid
touchedintnotnulldefault0,-- field used internally while marking; should normally be left as 0
notetextnotnulldefault''-- Tiny Tiny RSS freeform user note