$out = $this->db->prepare("SELECT id,created,expires,user from arsse_sessions where id is ? and expires > CURRENT_TIMESTAMP and created > ?", "str", "datetime")->run($id, $maxAge)->getRow();
$out = $this->db->prepare("SELECT id,created,expires,user from arsse_sessions where id = ? and expires > CURRENT_TIMESTAMP and created > ?", "str", "datetime")->run($id, $maxAge)->getRow();
// if the session does not exist or is expired, throw an exception
// if the session does not exist or is expired, throw an exception
if (!$out) {
if (!$out) {
throw new User\ExceptionSession("invalid", $id);
throw new User\ExceptionSession("invalid", $id);
@ -276,7 +276,7 @@ class Database {
// if we're more than half-way from the session expiring, renew it
// if we're more than half-way from the session expiring, renew it
if ($this->sessionExpiringSoon(Date::normalize($out['expires'], "sql"))) {
if ($this->sessionExpiringSoon(Date::normalize($out['expires'], "sql"))) {
$q->setCTE("folders", "SELECT id from arsse_folders where owner is ? and parent is ? union select arsse_folders.id from arsse_folders join folders on arsse_folders.parent=folders.id", ["str", "int"], [$user, $parent]);
$q->setCTE("folders", "SELECT id from arsse_folders where owner = ? and coalesce(parent,0) = ? union select arsse_folders.id from arsse_folders join folders on arsse_folders.parent=folders.id", ["str", "strict int"], [$user, $parent]);
return (bool) $this->db->prepare("UPDATE arsse_folders set $setClause, modified = CURRENT_TIMESTAMP where owner is ? and id is ?", $setTypes, "str", "int")->run($setValues, $user, $id)->changes();
return (bool) $this->db->prepare("UPDATE arsse_folders set $setClause, modified = CURRENT_TIMESTAMP where owner = ? and id = ?", $setTypes, "str", "int")->run($setValues, $user, $id)->changes();
target as (select ? as user, ? as source, ? as dest, ? as rename),
target as (select ? as user, ? as source, ? as dest, ? as rename),
folders as (SELECT id from arsse_folders join target on owner is user and parent is source union select arsse_folders.id as id from arsse_folders join folders on arsse_folders.parent=folders.id)
folders as (SELECT id from arsse_folders join target on owner = user and coalesce(parent,0) = source union select arsse_folders.id as id from arsse_folders join folders on arsse_folders.parent=folders.id)
".
".
"SELECT
"SELECT
((select dest from target) is null or exists(select id from arsse_folders join target on owner is user and id is dest)) as extant,
((select dest from target) is null or exists(select id from arsse_folders join target on owner = user and coalesce(id,0) = coalesce(dest,0))) as extant,
not exists(select id from folders where id is (select dest from target)) as valid,
not exists(select id from folders where id = coalesce((select dest from target),0)) as valid,
not exists(select id from arsse_folders join target on parent is dest and name is coalesce((select rename from target),(select name from arsse_folders join target on id is source))) as available
not exists(select id from arsse_folders join target on coalesce(parent,0) = coalesce(dest,0) and name = coalesce((select rename from target),(select name from arsse_folders join target on id = source))) as available
", "str", "int", "int", "str"
", "str", "strict int", "int", "str"
)->run($user, $id, $parent, $name)->getRow();
)->run($user, $id, $parent, $name)->getRow();
if (!$p['extant']) {
if (!$p['extant']) {
// if the parent doesn't exist or doesn't below to the user, throw an exception
// if the parent doesn't exist or doesn't below to the user, throw an exception
@ -475,7 +475,7 @@ class Database {
// make sure that a folder with the same prospective name and parent does not already exist: if the parent is null,
// make sure that a folder with the same prospective name and parent does not already exist: if the parent is null,
// SQL will happily accept duplicates (null is not unique), so we must do this check ourselves
// SQL will happily accept duplicates (null is not unique), so we must do this check ourselves
$parent = $parent ? $parent : null;
$parent = $parent ? $parent : null;
if ($this->db->prepare("SELECT exists(select id from arsse_folders where parent is ? and name is ?)", "int", "str")->run($parent, $name)->getValue()) {
if ($this->db->prepare("SELECT exists(select id from arsse_folders where coalesce(parent,0) = ? and name = ?)", "strict int", "str")->run($parent, $name)->getValue()) {
throw new Db\ExceptionInput("constraintViolation", ["action" => $this->caller(), "field" => "name"]);
throw new Db\ExceptionInput("constraintViolation", ["action" => $this->caller(), "field" => "name"]);
}
}
return true;
return true;
@ -489,7 +489,7 @@ class Database {
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
}
}
// check to see if the feed exists
// check to see if the feed exists
$check = $this->db->prepare("SELECT id from arsse_feeds where url is ? and username is ? and password is ?", "str", "str", "str");
$check = $this->db->prepare("SELECT id from arsse_feeds where url = ? and username = ? and password = ?", "str", "str", "str");
// if the feed doesn't exist, first perform discovery if requested and check for the existence of that URL
// if the feed doesn't exist, first perform discovery if requested and check for the existence of that URL
@ -504,7 +504,7 @@ class Database {
$this->feedUpdate($feedID, true);
$this->feedUpdate($feedID, true);
} catch (\Throwable $e) {
} catch (\Throwable $e) {
// if the update fails, delete the feed we just added
// if the update fails, delete the feed we just added
$this->db->prepare('DELETE from arsse_feeds where id is ?', 'int')->run($feedID);
$this->db->prepare('DELETE from arsse_feeds where id = ?', 'int')->run($feedID);
throw $e;
throw $e;
}
}
}
}
@ -526,9 +526,9 @@ class Database {
arsse_feeds.updated as updated,
arsse_feeds.updated as updated,
topmost.top as top_folder,
topmost.top as top_folder,
coalesce(arsse_subscriptions.title, arsse_feeds.title) as title,
coalesce(arsse_subscriptions.title, arsse_feeds.title) as title,
(SELECT count(*) from arsse_articles where feed is arsse_subscriptions.feed) - (SELECT count(*) from arsse_marks where subscription is arsse_subscriptions.id and read is 1) as unread
(SELECT count(*) from arsse_articles where feed = arsse_subscriptions.feed) - (SELECT count(*) from arsse_marks where subscription = arsse_subscriptions.id and read = 1) as unread
from arsse_subscriptions
from arsse_subscriptions
join user on user is owner
join user on user = owner
join arsse_feeds on feed = arsse_feeds.id
join arsse_feeds on feed = arsse_feeds.id
left join topmost on folder=f_id"
left join topmost on folder=f_id"
);
);
@ -536,19 +536,19 @@ class Database {
// define common table expressions
// define common table expressions
$q->setCTE("user(user)", "SELECT ?", "str", $user); // the subject user; this way we only have to pass it to prepare() once
$q->setCTE("user(user)", "SELECT ?", "str", $user); // the subject user; this way we only have to pass it to prepare() once
// topmost folders belonging to the user
// topmost folders belonging to the user
$q->setCTE("topmost(f_id,top)", "SELECT id,id from arsse_folders join user on owner is user where parent is null union select id,top from arsse_folders join topmost on parent=f_id");
$q->setCTE("topmost(f_id,top)", "SELECT id,id from arsse_folders join user on owner = user where parent is null union select id,top from arsse_folders join topmost on parent=f_id");
if ($id) {
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
// 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
// if an ID is specified, add a suitable WHERE condition and bindings
$q->setWhere("arsse_subscriptions.id is ?", "int", $id);
// 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
// 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
$q->setCTE("folders(folder)", "SELECT ? union select id from arsse_folders join folders on parent is folder", "int", $folder);
$q->setCTE("folders(folder)", "SELECT ? union select id from arsse_folders join folders on parent = folder", "int", $folder);
// add a suitable WHERE condition
// add a suitable WHERE condition
$q->setWhere("folder in (select folder from folders)");
$q->setWhere("folder in (select folder from folders)");
} elseif (!$recursive) {
} elseif (!$recursive) {
// if we're not listing recursively, match against only the specified folder (even if it is null)
// if we're not listing recursively, match against only the specified folder (even if it is null)
// if no changes would actually be applied, just return
// if no changes would actually be applied, just return
return false;
return false;
}
}
$out = (bool) $this->db->prepare("UPDATE arsse_subscriptions set $setClause, modified = CURRENT_TIMESTAMP where owner is ? and id is ?", $setTypes, "str", "int")->run($setValues, $user, $id)->changes();
$out = (bool) $this->db->prepare("UPDATE arsse_subscriptions set $setClause, modified = CURRENT_TIMESTAMP where owner = ? and id = ?", $setTypes, "str", "int")->run($setValues, $user, $id)->changes();
$tr->commit();
$tr->commit();
return $out;
return $out;
}
}
public function subscriptionFavicon(int $id): string {
public function subscriptionFavicon(int $id): string {
return (string) $this->db->prepare("SELECT favicon from arsse_feeds join arsse_subscriptions on feed is arsse_feeds.id where arsse_subscriptions.id is ?", "int")->run($id)->getValue();
return (string) $this->db->prepare("SELECT favicon from arsse_feeds join arsse_subscriptions on feed = arsse_feeds.id where arsse_subscriptions.id = ?", "int")->run($id)->getValue();
$f = $this->db->prepare("SELECT url, username, password, modified, etag, err_count, scrape FROM arsse_feeds where id is ?", "int")->run($feedID)->getRow();
$f = $this->db->prepare("SELECT url, username, password, modified, etag, err_count, scrape FROM arsse_feeds where id = ?", "int")->run($feedID)->getRow();
// if the feed hasn't changed, just compute the next fetch time and record it
// if the feed hasn't changed, just compute the next fetch time and record it
$this->db->prepare("UPDATE arsse_feeds SET updated = CURRENT_TIMESTAMP, next_fetch = ? WHERE id is ?", 'datetime', 'int')->run($feed->nextFetch, $feedID);
$this->db->prepare("UPDATE arsse_feeds SET updated = CURRENT_TIMESTAMP, next_fetch = ? WHERE id = ?", 'datetime', 'int')->run($feed->nextFetch, $feedID);
return false;
return false;
}
}
} catch (Feed\Exception $e) {
} catch (Feed\Exception $e) {
// update the database with the resultant error and the next fetch time, incrementing the error count
// update the database with the resultant error and the next fetch time, incrementing the error count
$this->db->prepare(
$this->db->prepare(
"UPDATE arsse_feeds SET updated = CURRENT_TIMESTAMP, next_fetch = ?, err_count = err_count + 1, err_msg = ? WHERE id is ?",
"UPDATE arsse_feeds SET updated = CURRENT_TIMESTAMP, next_fetch = ?, err_count = err_count + 1, err_msg = ? WHERE id = ?",
// lastly update the feed database itself with updated information.
// lastly update the feed database itself with updated information.
$this->db->prepare(
$this->db->prepare(
"UPDATE arsse_feeds SET url = ?, title = ?, favicon = ?, source = ?, updated = CURRENT_TIMESTAMP, modified = ?, etag = ?, err_count = 0, err_msg = '', next_fetch = ?, size = ? WHERE id is ?",
"UPDATE arsse_feeds SET url = ?, title = ?, favicon = ?, source = ?, updated = CURRENT_TIMESTAMP, modified = ?, etag = ?, err_count = 0, err_msg = '', next_fetch = ?, size = ? WHERE id = ?",
'str', 'str', 'str', 'str', 'datetime', 'str', 'datetime', 'int', 'int'
'str', 'str', 'str', 'str', 'datetime', 'str', 'datetime', 'int', 'int'
)->run(
)->run(
$feed->data->feedUrl,
$feed->data->feedUrl,
@ -786,9 +786,9 @@ class Database {
public function feedCleanup(): bool {
public function feedCleanup(): bool {
$tr = $this->begin();
$tr = $this->begin();
// first unmark any feeds which are no longer orphaned
// first unmark any feeds which are no longer orphaned
$this->db->query("UPDATE arsse_feeds set orphaned = null where exists(SELECT id from arsse_subscriptions where feed is arsse_feeds.id)");
$this->db->query("UPDATE arsse_feeds set orphaned = null where exists(SELECT id from arsse_subscriptions where feed = arsse_feeds.id)");
// next mark any newly orphaned feeds with the current date and time
// next mark any newly orphaned feeds with the current date and time
$this->db->query("UPDATE arsse_feeds set orphaned = CURRENT_TIMESTAMP where orphaned is null and not exists(SELECT id from arsse_subscriptions where feed is arsse_feeds.id)");
$this->db->query("UPDATE arsse_feeds set orphaned = CURRENT_TIMESTAMP where orphaned is null and not exists(SELECT id from arsse_subscriptions where feed = arsse_feeds.id)");
// finally delete feeds that have been orphaned longer than the retention period
// finally delete feeds that have been orphaned longer than the retention period
$limit = Date::normalize("now");
$limit = Date::normalize("now");
if (Arsse::$conf->purgeFeeds) {
if (Arsse::$conf->purgeFeeds) {
@ -803,7 +803,7 @@ class Database {
public function feedMatchLatest(int $feedID, int $count): Db\Result {
public function feedMatchLatest(int $feedID, int $count): Db\Result {
return $this->db->prepare(
return $this->db->prepare(
"SELECT id, edited, guid, url_title_hash, url_content_hash, title_content_hash FROM arsse_articles WHERE feed is ? ORDER BY modified desc, id desc limit ?",
"SELECT id, edited, guid, url_title_hash, url_content_hash, title_content_hash FROM arsse_articles WHERE feed = ? ORDER BY modified desc, id desc limit ?",
"SELECT id, edited, guid, url_title_hash, url_content_hash, title_content_hash FROM arsse_articles WHERE feed is ? and (guid in($cId) or url_title_hash in($cHashUT) or url_content_hash in($cHashUC) or title_content_hash in($cHashTC))",
"SELECT id, edited, guid, url_title_hash, url_content_hash, title_content_hash FROM arsse_articles WHERE feed = ? and (guid in($cId) or url_title_hash in($cHashUT) or url_content_hash in($cHashUC) or title_content_hash in($cHashTC))",
coalesce((select modified from arsse_marks where article is arsse_articles.id and subscription in (select sub from subscribed_feeds)),''),
coalesce((select modified from arsse_marks where article = arsse_articles.id and subscription in (select sub from subscribed_feeds)),''),
coalesce((select modified from arsse_label_members where article is arsse_articles.id and subscription in (select sub from subscribed_feeds)),'')
coalesce((select modified from arsse_label_members where article = arsse_articles.id and subscription in (select sub from subscribed_feeds)),'')
) as marked_date,
) as marked_date,
NOT (select count(*) from arsse_marks where article is arsse_articles.id and read is 1 and subscription in (select sub from subscribed_feeds)) as unread,
NOT (select count(*) from arsse_marks where article = arsse_articles.id and read = 1 and subscription in (select sub from subscribed_feeds)) as unread,
(select count(*) from arsse_marks where article is arsse_articles.id and starred is 1 and subscription in (select sub from subscribed_feeds)) as starred,
(select count(*) from arsse_marks where article = arsse_articles.id and starred = 1 and subscription in (select sub from subscribed_feeds)) as starred,
(select max(id) from arsse_editions where article is arsse_articles.id) as edition,
(select max(id) from arsse_editions where article = arsse_articles.id) as edition,
subscribed_feeds.sub as subscription
subscribed_feeds.sub as subscription
FROM arsse_articles"
FROM arsse_articles"
);
);
@ -849,29 +849,29 @@ class Database {
// if a subscription is specified, make sure it exists
// if a subscription is specified, make sure it exists
// if it does exist, add a common table expression to list it and its children so that we select from the entire subtree
// if it does exist, add a common table expression to list it 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 is folder", "int", $context->folder);
$q->setCTE("folders(folder)", "SELECT ? union select id from arsse_folders join folders on parent = folder", "int", $context->folder);
// add another CTE for the subscriptions within the folder
// add another CTE for the subscriptions within the folder
$q->setCTE("subscribed_feeds(id,sub)", "SELECT feed,id from arsse_subscriptions join user on user is owner join folders on arsse_subscriptions.folder is folders.folder", [], [], "join subscribed_feeds on feed is subscribed_feeds.id");
$q->setCTE("subscribed_feeds(id,sub)", "SELECT feed,id from arsse_subscriptions join user on user = owner join folders on arsse_subscriptions.folder = folders.folder", [], [], "join subscribed_feeds on feed = subscribed_feeds.id");
} elseif ($context->folderShallow()) {
} elseif ($context->folderShallow()) {
// if a shallow folder is specified, make sure it exists
// if a shallow folder is specified, make sure it exists
// if it does exist, add a CTE with only its subscriptions (and not those of its descendents)
// if it does exist, add a CTE with only its subscriptions (and not those of its descendents)
$q->setCTE("subscribed_feeds(id,sub)", "SELECT feed,id from arsse_subscriptions join user on user is owner and coalesce(folder,0) is ?", "strict int", $context->folderShallow, "join subscribed_feeds on feed is subscribed_feeds.id");
$q->setCTE("subscribed_feeds(id,sub)", "SELECT feed,id from arsse_subscriptions join user on user = owner and coalesce(folder,0) = ?", "strict int", $context->folderShallow, "join subscribed_feeds on feed = subscribed_feeds.id");
} else {
} else {
// otherwise add a CTE for all the user's subscriptions
// otherwise add a CTE for all the user's subscriptions
$q->setCTE("subscribed_feeds(id,sub)", "SELECT feed,id from arsse_subscriptions join user on user is owner", [], [], "join subscribed_feeds on feed is subscribed_feeds.id");
$q->setCTE("subscribed_feeds(id,sub)", "SELECT feed,id from arsse_subscriptions join user on user = owner", [], [], "join subscribed_feeds on feed = subscribed_feeds.id");
}
}
if ($context->edition()) {
if ($context->edition()) {
// if an edition is specified, filter for its previously identified article
// if an edition is specified, filter for its previously identified article
$q->setWhere("arsse_articles.id is (select article from arsse_editions where id is ?)", "int", $context->edition);
$q->setWhere("arsse_articles.id = (select article from arsse_editions where id = ?)", "int", $context->edition);
} elseif ($context->article()) {
} elseif ($context->article()) {
// if an article is specified, filter for it (it has already been validated above)
// if an article is specified, filter for it (it has already been validated above)
$q->setWhere("arsse_articles.id is ?", "int", $context->article);
"SELECT id,(select max(id) from arsse_editions where article is arsse_articles.id) as edition from arsse_articles where arsse_articles.id in ($inParams)",
"SELECT id,(select max(id) from arsse_editions where article = arsse_articles.id) as edition from arsse_articles where arsse_articles.id in ($inParams)",
$inTypes,
$inTypes,
$context->articles
$context->articles
);
);
$q->setWhere("arsse_articles.id in (select id from requested_articles)");
$q->setWhere("arsse_articles.id in (select id from requested_articles)");
} else {
} else {
// if neither list is specified, mock an empty table
// if neither list is specified, mock an empty table
$q->setCTE("requested_articles(id,edition)", "SELECT 'empty','table' where 1 is 0");
$q->setCTE("requested_articles(id,edition)", "SELECT 'empty','table' where 1 = 0");
}
}
// filter based on label by ID or name
// filter based on label by ID or name
if ($context->labelled()) {
if ($context->labelled()) {
// any label (true) or no label (false)
// any label (true) or no label (false)
$q->setWhere((!$context->labelled ? "not " : "")."exists(select article from arsse_label_members where assigned is 1 and article is arsse_articles.id and subscription in (select sub from subscribed_feeds))");
$q->setWhere((!$context->labelled ? "not " : "")."exists(select article from arsse_label_members where assigned = 1 and article = arsse_articles.id and subscription in (select sub from subscribed_feeds))");
$q->setWhere((!$context->annotated ? "not " : "")."exists(select modified from arsse_marks where article is arsse_articles.id and note <> '' and subscription in (select sub from subscribed_feeds))");
$q->setWhere((!$context->annotated ? "not " : "")."exists(select modified from arsse_marks where article = arsse_articles.id and note <> '' and subscription in (select sub from subscribed_feeds))");
}
}
// return the query
// return the query
return $q;
return $q;
@ -1003,7 +1003,7 @@ class Database {
// NOTE: the cases all cascade into each other: a given verbosity level is always a superset of the previous one
// NOTE: the cases all cascade into each other: a given verbosity level is always a superset of the previous one
case self::LIST_FULL: // everything
case self::LIST_FULL: // everything
$columns = array_merge($columns, [
$columns = array_merge($columns, [
"(select note from arsse_marks where article is arsse_articles.id and subscription in (select sub from subscribed_feeds)) as note",
"(select note from arsse_marks where article = arsse_articles.id and subscription in (select sub from subscribed_feeds)) as note",
]);
]);
case self::LIST_TYPICAL: // conservative, plus content
case self::LIST_TYPICAL: // conservative, plus content
$columns = array_merge($columns, [
$columns = array_merge($columns, [
@ -1015,7 +1015,7 @@ class Database {
$columns = array_merge($columns, [
$columns = array_merge($columns, [
"arsse_articles.url as url",
"arsse_articles.url as url",
"arsse_articles.title as title",
"arsse_articles.title as title",
"(select coalesce(arsse_subscriptions.title,arsse_feeds.title) from arsse_feeds join arsse_subscriptions on arsse_subscriptions.feed is arsse_feeds.id where arsse_feeds.id is arsse_articles.feed) as subscription_title",
"(select coalesce(arsse_subscriptions.title,arsse_feeds.title) from arsse_feeds join arsse_subscriptions on arsse_subscriptions.feed = arsse_feeds.id where arsse_feeds.id = arsse_articles.feed) as subscription_title",
read = case when (select honour_read from target_articles where target_articles.id is article) is 1 then (select read from target_values) else read end,
read = case when (select honour_read from target_articles where target_articles.id = article) = 1 then (select read from target_values) else read end,
starred = coalesce((select starred from target_values),starred),
starred = coalesce((select starred from target_values),starred),
note = coalesce((select note from target_values),note),
note = coalesce((select note from target_values),note),
modified = CURRENT_TIMESTAMP
modified = CURRENT_TIMESTAMP
WHERE
WHERE
subscription in (select sub from subscribed_feeds)
subscription in (select sub from subscribed_feeds)
and article in (select id from target_articles where to_insert is 0 and (honour_read is 1 or honour_star is 1 or (select note from target_values) is not null))",
and article in (select id from target_articles where to_insert = 0 and (honour_read = 1 or honour_star = 1 or (select note from target_values) is not null))",
"INSERT INTO arsse_marks(subscription,article,read,starred,note)
"INSERT INTO arsse_marks(subscription,article,read,starred,note)
select
select
(select id from arsse_subscriptions join user on user is owner where arsse_subscriptions.feed is target_articles.feed),
(select id from arsse_subscriptions join user on user = owner where arsse_subscriptions.feed = target_articles.feed),
id,
id,
coalesce((select read from target_values) * honour_read,0),
coalesce((select read from target_values) * honour_read,0),
coalesce((select starred from target_values),0),
coalesce((select starred from target_values),0),
coalesce((select note from target_values),'')
coalesce((select note from target_values),'')
from target_articles where to_insert is 1 and (honour_read is 1 or honour_star is 1 or coalesce((select note from target_values),'') <> '')"
from target_articles where to_insert = 1 and (honour_read = 1 or honour_star = 1 or coalesce((select note from target_values),'') <> '')"
];
];
$out = 0;
$out = 0;
// wrap this UPDATE and INSERT together into a transaction
// wrap this UPDATE and INSERT together into a transaction
@ -1122,9 +1122,9 @@ class Database {
foreach ($queries as $query) {
foreach ($queries as $query) {
// first build the query which will select the target articles; we will later turn this into a CTE for the actual query that manipulates the articles
// first build the query which will select the target articles; we will later turn this into a CTE for the actual query that manipulates the articles
$q = $this->articleQuery($user, $context, [
$q = $this->articleQuery($user, $context, [
"(not exists(select article from arsse_marks where article is arsse_articles.id and subscription in (select sub from subscribed_feeds))) as to_insert",
"(not exists(select article from arsse_marks where article = arsse_articles.id and subscription in (select sub from subscribed_feeds))) as to_insert",
"((select read from target_values) is not null and (select read from target_values) is not (coalesce((select read from arsse_marks where article is arsse_articles.id and subscription in (select sub from subscribed_feeds)),0)) and (not exists(select * from requested_articles) or (select max(id) from arsse_editions where article is arsse_articles.id) in (select edition from requested_articles))) as honour_read",
"((select read from target_values) is not null and (select read from target_values) <> (coalesce((select read from arsse_marks where article = arsse_articles.id and subscription in (select sub from subscribed_feeds)),0)) and (not exists(select * from requested_articles) or (select max(id) from arsse_editions where article = arsse_articles.id) in (select edition from requested_articles))) as honour_read",
"((select starred from target_values) is not null and (select starred from target_values) is not (coalesce((select starred from arsse_marks where article is arsse_articles.id and subscription in (select sub from subscribed_feeds)),0))) as honour_star",
"((select starred from target_values) is not null and (select starred from target_values) <> (coalesce((select starred from arsse_marks where article = arsse_articles.id and subscription in (select sub from subscribed_feeds)),0))) as honour_star",
$out = $this->db->prepare("SELECT id,name from arsse_labels where owner is ? and exists(select id from arsse_label_members where article is ? and label is arsse_labels.id and assigned is 1)", "str", "int")->run($user, $id)->getAll();
$out = $this->db->prepare("SELECT id,name from arsse_labels where owner = ? and exists(select id from arsse_label_members where article = ? and label = arsse_labels.id and assigned = 1)", "str", "int")->run($user, $id)->getAll();
if (!$out) {
if (!$out) {
return $out;
return $out;
} else {
} else {
@ -1173,7 +1173,7 @@ class Database {
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
$out = $this->db->prepare("SELECT name from arsse_categories where article is ? order by name", "int")->run($id)->getAll();
$out = $this->db->prepare("SELECT name from arsse_categories where article = ? order by name", "int")->run($id)->getAll();
if (!$out) {
if (!$out) {
return $out;
return $out;
} else {
} else {
@ -1186,22 +1186,22 @@ class Database {
$query = $this->db->prepare(
$query = $this->db->prepare(
"WITH target_feed(id,subs) as (".
"WITH target_feed(id,subs) as (".
"SELECT
"SELECT
id, (select count(*) from arsse_subscriptions where feed is arsse_feeds.id) as subs
id, (select count(*) from arsse_subscriptions where feed = arsse_feeds.id) as subs
from arsse_feeds where id is ?".
from arsse_feeds where id = ?".
"), excepted_articles(id,edition) as (".
"), excepted_articles(id,edition) as (".
"SELECT
"SELECT
arsse_articles.id, (select max(id) from arsse_editions where article is arsse_articles.id) as edition
arsse_articles.id, (select max(id) from arsse_editions where article = arsse_articles.id) as edition
from arsse_articles
from arsse_articles
join target_feed on arsse_articles.feed is target_feed.id
join target_feed on arsse_articles.feed = target_feed.id
order by edition desc limit ?".
order by edition desc limit ?".
") ".
") ".
"DELETE from arsse_articles where
"DELETE from arsse_articles where
feed is (select max(id) from target_feed)
feed = (select max(id) from target_feed)
and id not in (select id from excepted_articles)
and id not in (select id from excepted_articles)
and (select count(*) from arsse_marks where article is arsse_articles.id and starred is 1) is 0
and (select count(*) from arsse_marks where article = arsse_articles.id and starred = 1) = 0
and (
and (
coalesce((select max(modified) from arsse_marks where article is arsse_articles.id),modified) <= ?
coalesce((select max(modified) from arsse_marks where article = arsse_articles.id),modified) <= ?
or ((select max(subs) from target_feed) is (select count(*) from arsse_marks where article is arsse_articles.id and read is 1) and coalesce((select max(modified) from arsse_marks where article is arsse_articles.id),modified) <= ?)
or ((select max(subs) from target_feed) = (select count(*) from arsse_marks where article = arsse_articles.id and read = 1) and coalesce((select max(modified) from arsse_marks where article = arsse_articles.id),modified) <= ?)
)
)
", "int", "int", "datetime", "datetime"
", "int", "int", "datetime", "datetime"
);
);
@ -1227,12 +1227,12 @@ class Database {
$out = $this->db->prepare(
$out = $this->db->prepare(
"SELECT
"SELECT
arsse_articles.id as article,
arsse_articles.id as article,
(select max(id) from arsse_editions where article is arsse_articles.id) as edition
(select max(id) from arsse_editions where article = arsse_articles.id) as edition
FROM arsse_articles
FROM arsse_articles
join arsse_feeds on arsse_feeds.id is arsse_articles.feed
join arsse_feeds on arsse_feeds.id = arsse_articles.feed
join arsse_subscriptions on arsse_subscriptions.feed is arsse_feeds.id
join arsse_subscriptions on arsse_subscriptions.feed = arsse_feeds.id
WHERE
WHERE
arsse_articles.id is ? and arsse_subscriptions.owner is ?",
arsse_articles.id = ? and arsse_subscriptions.owner = ?",
"int", "str"
"int", "str"
)->run($id, $user)->getRow();
)->run($id, $user)->getRow();
if (!$out) {
if (!$out) {
@ -1249,13 +1249,13 @@ class Database {
"SELECT
"SELECT
arsse_editions.id as edition,
arsse_editions.id as edition,
arsse_editions.article as article,
arsse_editions.article as article,
(arsse_editions.id is (select max(id) from arsse_editions where article is arsse_editions.article)) as current
(arsse_editions.id = (select max(id) from arsse_editions where article = arsse_editions.article)) as current
FROM arsse_editions
FROM arsse_editions
join arsse_articles on arsse_editions.article is arsse_articles.id
join arsse_articles on arsse_editions.article = arsse_articles.id
join arsse_feeds on arsse_feeds.id is arsse_articles.feed
join arsse_feeds on arsse_feeds.id = arsse_articles.feed
join arsse_subscriptions on arsse_subscriptions.feed is arsse_feeds.id
join arsse_subscriptions on arsse_subscriptions.feed = arsse_feeds.id
WHERE
WHERE
edition is ? and arsse_subscriptions.owner is ?",
edition = ? and arsse_subscriptions.owner = ?",
"int", "str"
"int", "str"
)->run($id, $user)->getRow();
)->run($id, $user)->getRow();
if (!$out) {
if (!$out) {
@ -1269,15 +1269,15 @@ class Database {
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
}
}
$context = $context ?? new Context;
$context = $context ?? new Context;
$q = new Query("SELECT max(arsse_editions.id) from arsse_editions left join arsse_articles on article is arsse_articles.id left join arsse_feeds on arsse_articles.feed is arsse_feeds.id");
$q = new Query("SELECT max(arsse_editions.id) from arsse_editions left join arsse_articles on article = arsse_articles.id left join arsse_feeds on arsse_articles.feed = arsse_feeds.id");
if ($context->subscription()) {
if ($context->subscription()) {
// if a subscription is specified, make sure it exists
// if a subscription is specified, make sure it exists
$q->setCTE("feeds(feed)", "SELECT feed from arsse_subscriptions join user on user is owner", [], [], "join feeds on arsse_articles.feed is feeds.feed");
$q->setCTE("feeds(feed)", "SELECT feed from arsse_subscriptions join user on user = owner", [], [], "join feeds on arsse_articles.feed = feeds.feed");
(select count(*) from arsse_label_members where label is id and assigned is 1) as articles,
(select count(*) from arsse_label_members where label = id and assigned = 1) as articles,
(select count(*) from arsse_label_members
(select count(*) from arsse_label_members
join arsse_marks on arsse_label_members.article is arsse_marks.article and arsse_label_members.subscription is arsse_marks.subscription
join arsse_marks on arsse_label_members.article = arsse_marks.article and arsse_label_members.subscription = arsse_marks.subscription
where label is id and assigned is 1 and read is 1
where label = id and assigned = 1 and read = 1
) as read
) as read
FROM arsse_labels where $field is ? and owner is ?
FROM arsse_labels where $field = ? and owner = ?
", $type, "str"
", $type, "str"
)->run($id, $user)->getRow();
)->run($id, $user)->getRow();
if (!$out) {
if (!$out) {
@ -1368,7 +1368,7 @@ class Database {
// if no changes would actually be applied, just return
// if no changes would actually be applied, just return
return false;
return false;
}
}
$out = (bool) $this->db->prepare("UPDATE arsse_labels set $setClause, modified = CURRENT_TIMESTAMP where owner is ? and $field is ?", $setTypes, "str", $type)->run($setValues, $user, $id)->changes();
$out = (bool) $this->db->prepare("UPDATE arsse_labels set $setClause, modified = CURRENT_TIMESTAMP where owner = ? and $field = ?", $setTypes, "str", $type)->run($setValues, $user, $id)->changes();
$out = $this->db->prepare("SELECT article from arsse_label_members join arsse_labels on label is id where assigned is 1 and $field is ? and owner is ?", $type, "str")->run($id, $user)->getAll();
$out = $this->db->prepare("SELECT article from arsse_label_members join arsse_labels on label = id where assigned = 1 and $field = ? and owner = ?", $type, "str")->run($id, $user)->getAll();
if (!$out) {
if (!$out) {
// if no results were returned, do a full validation on the label ID
// if no results were returned, do a full validation on the label ID
// first update any existing entries with the removal or re-addition of their association
// first update any existing entries with the removal or re-addition of their association
$q = $this->articleQuery($user, $context);
$q = $this->articleQuery($user, $context);
$q->setWhere("exists(select article from arsse_label_members where label is ? and article is arsse_articles.id)", "int", $id);
$q->setWhere("exists(select article from arsse_label_members where label = ? and article = arsse_articles.id)", "int", $id);
$q->pushCTE("target_articles");
$q->pushCTE("target_articles");
$q->setBody(
$q->setBody(
"UPDATE arsse_label_members set assigned = ?, modified = CURRENT_TIMESTAMP where label is ? and assigned is not ? and article in (select id from target_articles)",
"UPDATE arsse_label_members set assigned = ?, modified = CURRENT_TIMESTAMP where label = ? and assigned = not ? and article in (select id from target_articles)",
["bool","int","bool"],
["bool","int","bool"],
[!$remove, $id, !$remove]
[!$remove, $id, !$remove]
);
);
@ -1418,14 +1418,14 @@ class Database {
// next, if we're not removing, add any new entries that need to be added
// next, if we're not removing, add any new entries that need to be added
if (!$remove) {
if (!$remove) {
$q = $this->articleQuery($user, $context);
$q = $this->articleQuery($user, $context);
$q->setWhere("not exists(select article from arsse_label_members where label is ? and article is arsse_articles.id)", "int", $id);
$q->setWhere("not exists(select article from arsse_label_members where label = ? and article = arsse_articles.id)", "int", $id);
$q->pushCTE("target_articles");
$q->pushCTE("target_articles");
$q->setBody(
$q->setBody(
"INSERT INTO
"INSERT INTO
arsse_label_members(label,article,subscription)
arsse_label_members(label,article,subscription)
SELECT
SELECT
?,id,
?,id,
(select id from arsse_subscriptions join user on user is owner where arsse_subscriptions.feed is target_articles.feed)
(select id from arsse_subscriptions join user on user = owner where arsse_subscriptions.feed = target_articles.feed)
FROM target_articles",
FROM target_articles",
"int", $id
"int", $id
);
);
@ -1446,7 +1446,7 @@ class Database {
} elseif ($checkDb) {
} elseif ($checkDb) {
$field = !$byName ? "id" : "name";
$field = !$byName ? "id" : "name";
$type = !$byName ? "int" : "str";
$type = !$byName ? "int" : "str";
$l = $this->db->prepare("SELECT id,name from arsse_labels where $field is ? and owner is ?", $type, "str")->run($id, $user)->getRow();
$l = $this->db->prepare("SELECT id,name from arsse_labels where $field = ? and owner = ?", $type, "str")->run($id, $user)->getRow();