summaryrefslogtreecommitdiff
path: root/classes
diff options
context:
space:
mode:
Diffstat (limited to 'classes')
-rwxr-xr-xclasses/api.php2
-rw-r--r--classes/config.php9
-rwxr-xr-xclasses/feeditem/common.php1
-rwxr-xr-xclasses/feeds.php82
-rwxr-xr-xclasses/handler/public.php6
-rw-r--r--classes/opml.php334
-rwxr-xr-xclasses/pluginhost.php197
-rwxr-xr-xclasses/pref/feeds.php410
-rwxr-xr-xclasses/pref/filters.php2
-rw-r--r--classes/pref/labels.php2
-rwxr-xr-xclasses/rpc.php1
-rwxr-xr-xclasses/rssutils.php2
-rw-r--r--classes/sanitizer.php7
-rw-r--r--classes/urlhelper.php5
14 files changed, 643 insertions, 417 deletions
diff --git a/classes/api.php b/classes/api.php
index 5f825e551..033aa8654 100755
--- a/classes/api.php
+++ b/classes/api.php
@@ -307,6 +307,7 @@ class API extends Handler {
$article_ids = explode(',', clean($_REQUEST['article_id'] ?? ''));
$sanitize_content = self::_param_to_bool($_REQUEST['sanitize'] ?? true);
+ // @phpstan-ignore-next-line
if (count($article_ids)) {
$entries = ORM::for_table('ttrss_entries')
->table_alias('e')
@@ -369,7 +370,6 @@ class API extends Handler {
}
$this->_wrap(self::STATUS_OK, $articles);
- // @phpstan-ignore-next-line
} else {
$this->_wrap(self::STATUS_ERR, ['error' => self::E_INCORRECT_USAGE]);
}
diff --git a/classes/config.php b/classes/config.php
index 4ae4a2407..be4ecde36 100644
--- a/classes/config.php
+++ b/classes/config.php
@@ -170,6 +170,9 @@ class Config {
const AUTH_MIN_INTERVAL = "AUTH_MIN_INTERVAL";
// minimum amount of seconds required between authentication attempts
+ const HTTP_USER_AGENT = "HTTP_USER_AGENT";
+ // http user agent (changing this is not recommended)
+
// default values for all of the above:
private const _DEFAULTS = [
Config::DB_TYPE => [ "pgsql", Config::T_STRING ],
@@ -224,6 +227,8 @@ class Config {
Config::CHECK_FOR_PLUGIN_UPDATES => [ "true", Config::T_BOOL ],
Config::ENABLE_PLUGIN_INSTALLER => [ "true", Config::T_BOOL ],
Config::AUTH_MIN_INTERVAL => [ 5, Config::T_INT ],
+ Config::HTTP_USER_AGENT => [ 'Tiny Tiny RSS/%s (https://tt-rss.org/)',
+ Config::T_STRING ],
];
private static $instance;
@@ -632,4 +637,8 @@ class Config {
return $rv;
}
+
+ static function get_user_agent() {
+ return sprintf(self::get(self::HTTP_USER_AGENT), self::get_version());
+ }
}
diff --git a/classes/feeditem/common.php b/classes/feeditem/common.php
index 18afeaa94..b4774941f 100755
--- a/classes/feeditem/common.php
+++ b/classes/feeditem/common.php
@@ -190,6 +190,7 @@ abstract class FeedItem_Common extends FeedItem {
}, $tmp);
// remove empty values
+ // @phpstan-ignore-next-line
$tmp = array_filter($tmp, 'strlen');
asort($tmp);
diff --git a/classes/feeds.php b/classes/feeds.php
index 42673ca95..ea9158949 100755
--- a/classes/feeds.php
+++ b/classes/feeds.php
@@ -230,17 +230,43 @@ class Feeds extends Handler_Protected {
$line["feed_title"] = $line["feed_title"] ?? "";
+ $button_doc = new DOMDocument();
+
$line["buttons_left"] = "";
PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_ARTICLE_LEFT_BUTTON,
- function ($result) use (&$line) {
- $line["buttons_left"] .= $result;
+ function ($result, $plugin) use (&$line, &$button_doc) {
+ if ($result && $button_doc->loadXML($result)) {
+
+ /** @var DOMElement|null */
+ $child = $button_doc->firstChild;
+
+ if ($child) {
+ do {
+ $child->setAttribute('data-plugin-name', get_class($plugin));
+ } while ($child = $child->nextSibling);
+
+ $line["buttons_left"] .= $button_doc->saveXML($button_doc->firstChild);
+ }
+ }
},
$line);
$line["buttons"] = "";
PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_ARTICLE_BUTTON,
- function ($result) use (&$line) {
- $line["buttons"] .= $result;
+ function ($result, $plugin) use (&$line, &$button_doc) {
+ if ($result && $button_doc->loadXML($result)) {
+
+ /** @var DOMElement|null */
+ $child = $button_doc->firstChild;
+
+ if ($child) {
+ do {
+ $child->setAttribute('data-plugin-name', get_class($plugin));
+ } while ($child = $child->nextSibling);
+
+ $line["buttons"] .= $button_doc->saveXML($button_doc->firstChild);
+ }
+ }
},
$line);
@@ -587,6 +613,23 @@ class Feeds extends Handler_Protected {
]);
}
+ function opensite() {
+ $feed = ORM::for_table('ttrss_feeds')
+ ->find_one((int)$_REQUEST['feed_id']);
+
+ if ($feed) {
+ $site_url = UrlHelper::validate($feed->site_url);
+
+ if ($site_url) {
+ header("Location: $site_url");
+ return;
+ }
+ }
+
+ header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
+ print "Feed not found or has an empty site URL.";
+ }
+
function updatedebugger() {
header("Content-type: text/html");
@@ -996,6 +1039,13 @@ class Feeds extends Handler_Protected {
if (!$url) return ["code" => 2];
+ PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_PRE_SUBSCRIBE,
+ /** @phpstan-ignore-next-line */
+ function ($result) use (&$url, &$auth_login, &$auth_pass) {
+ // arguments are updated inside the hook (if needed)
+ },
+ $url, $auth_login, $auth_pass);
+
$contents = UrlHelper::fetch($url, false, $auth_login, $auth_pass);
PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_SUBSCRIBE_FEED,
@@ -1105,6 +1155,30 @@ class Feeds extends Handler_Protected {
}
}
+ /** $owner_uid defaults to $_SESSION['uid] */
+ static function _find_by_title(string $title, bool $cat = false, int $owner_uid = 0) {
+
+ $res = false;
+
+ if ($cat) {
+ $res = ORM::for_table('ttrss_feed_categories')
+ ->where('owner_uid', $owner_uid ? $owner_uid : $_SESSION['uid'])
+ ->where('title', $title)
+ ->find_one();
+ } else {
+ $res = ORM::for_table('ttrss_feeds')
+ ->where('owner_uid', $owner_uid ? $owner_uid : $_SESSION['uid'])
+ ->where('title', $title)
+ ->find_one();
+ }
+
+ if ($res) {
+ return $res->id;
+ } else {
+ return false;
+ }
+ }
+
static function _get_title($id, bool $cat = false) {
$pdo = Db::pdo();
diff --git a/classes/handler/public.php b/classes/handler/public.php
index 4da32e90d..14474d0bb 100755
--- a/classes/handler/public.php
+++ b/classes/handler/public.php
@@ -53,6 +53,10 @@ class Handler_Public extends Handler {
if ($handler) {
$qfh_ret = $handler->get_headlines(PluginHost::feed_to_pfeed_id((int)$feed), $params);
+ } else {
+ user_error("Failed to find handler for plugin feed ID: $feed", E_USER_ERROR);
+
+ return false;
}
} else {
@@ -448,7 +452,7 @@ class Handler_Public extends Handler {
if ($login) {
$user = ORM::for_table('ttrss_users')
- ->select('id', 'resetpass_token')
+ ->select_many('id', 'resetpass_token')
->where_raw('LOWER(login) = LOWER(?)', [$login])
->find_one();
diff --git a/classes/opml.php b/classes/opml.php
index 2cfc890fa..e0dbebcda 100644
--- a/classes/opml.php
+++ b/classes/opml.php
@@ -48,9 +48,7 @@ class OPML extends Handler_Protected {
// Export
- private function opml_export_category($owner_uid, $cat_id, $hide_private_feeds = false, $include_settings = true) {
-
- $cat_id = (int) $cat_id;
+ private function opml_export_category(int $owner_uid, int $cat_id, bool $hide_private_feeds = false, bool $include_settings = true) {
if ($hide_private_feeds)
$hide_qpart = "(private IS false AND auth_login = '' AND auth_pass = '')";
@@ -126,7 +124,7 @@ class OPML extends Handler_Protected {
return $out;
}
- function opml_export($filename, $owner_uid, $hide_private_feeds = false, $include_settings = true, $file_output = false) {
+ function opml_export(string $filename, int $owner_uid, bool $hide_private_feeds = false, bool $include_settings = true, bool $file_output = false) {
if (!$owner_uid) return;
if (!$file_output)
@@ -189,7 +187,7 @@ class OPML extends Handler_Protected {
WHERE owner_uid = ? ORDER BY id");
$sth->execute([$owner_uid]);
- while ($line = $sth->fetch()) {
+ while ($line = $sth->fetch(PDO::FETCH_ASSOC)) {
$line["rules"] = array();
$line["actions"] = array();
@@ -204,36 +202,36 @@ class OPML extends Handler_Protected {
$cat_filter = $tmp_line["cat_filter"];
if (!$tmp_line["match_on"]) {
- if ($cat_filter && $tmp_line["cat_id"] || $tmp_line["feed_id"]) {
- $tmp_line["feed"] = Feeds::_get_title(
- $cat_filter ? $tmp_line["cat_id"] : $tmp_line["feed_id"],
- $cat_filter);
- } else {
- $tmp_line["feed"] = "";
- }
- } else {
- $match = [];
- foreach (json_decode($tmp_line["match_on"], true) as $feed_id) {
-
- if (strpos($feed_id, "CAT:") === 0) {
- $feed_id = (int)substr($feed_id, 4);
- if ($feed_id) {
- array_push($match, [Feeds::_get_cat_title($feed_id), true, false]);
- } else {
- array_push($match, [0, true, true]);
- }
- } else {
- if ($feed_id) {
- array_push($match, [Feeds::_get_title((int)$feed_id), false, false]);
- } else {
- array_push($match, [0, false, true]);
- }
- }
- }
-
- $tmp_line["match"] = $match;
- unset($tmp_line["match_on"]);
- }
+ if ($cat_filter && $tmp_line["cat_id"] || $tmp_line["feed_id"]) {
+ $tmp_line["feed"] = Feeds::_get_title(
+ $cat_filter ? $tmp_line["cat_id"] : $tmp_line["feed_id"],
+ $cat_filter);
+ } else {
+ $tmp_line["feed"] = "";
+ }
+ } else {
+ $match = [];
+ foreach (json_decode($tmp_line["match_on"], true) as $feed_id) {
+
+ if (strpos($feed_id, "CAT:") === 0) {
+ $feed_id = (int)substr($feed_id, 4);
+ if ($feed_id) {
+ array_push($match, [Feeds::_get_cat_title($feed_id), true, false]);
+ } else {
+ array_push($match, [0, true, true]);
+ }
+ } else {
+ if ($feed_id) {
+ array_push($match, [Feeds::_get_title((int)$feed_id), false, false]);
+ } else {
+ array_push($match, [0, false, true]);
+ }
+ }
+ }
+
+ $tmp_line["match"] = $match;
+ unset($tmp_line["match_on"]);
+ }
unset($tmp_line["feed_id"]);
unset($tmp_line["cat_id"]);
@@ -298,7 +296,7 @@ class OPML extends Handler_Protected {
// Import
- private function opml_import_feed($node, $cat_id, $owner_uid) {
+ private function opml_import_feed(DOMNode $node, int $cat_id, int $owner_uid, int $nest) {
$attrs = $node->attributes;
$feed_title = mb_substr($attrs->getNamedItem('text')->nodeValue, 0, 250);
@@ -318,7 +316,7 @@ class OPML extends Handler_Protected {
if (!$sth->fetch()) {
#$this->opml_notice("[FEED] [$feed_title/$feed_url] dst_CAT=$cat_id");
- $this->opml_notice(T_sprintf("Adding feed: %s", $feed_title == '[Unknown]' ? $feed_url : $feed_title));
+ $this->opml_notice(T_sprintf("Adding feed: %s", $feed_title == '[Unknown]' ? $feed_url : $feed_title), $nest);
if (!$cat_id) $cat_id = null;
@@ -338,12 +336,12 @@ class OPML extends Handler_Protected {
$sth->execute([$feed_title, $feed_url, $owner_uid, $cat_id, $site_url, $order_id, $update_interval, $purge_interval]);
} else {
- $this->opml_notice(T_sprintf("Duplicate feed: %s", $feed_title == '[Unknown]' ? $feed_url : $feed_title));
+ $this->opml_notice(T_sprintf("Duplicate feed: %s", $feed_title == '[Unknown]' ? $feed_url : $feed_title), $nest);
}
}
}
- private function opml_import_label($node, $owner_uid) {
+ private function opml_import_label(DOMNode $node, int $owner_uid, int $nest) {
$attrs = $node->attributes;
$label_name = $attrs->getNamedItem('label-name')->nodeValue;
@@ -351,16 +349,16 @@ class OPML extends Handler_Protected {
$fg_color = $attrs->getNamedItem('label-fg-color')->nodeValue;
$bg_color = $attrs->getNamedItem('label-bg-color')->nodeValue;
- if (!Labels::find_id($label_name, $_SESSION['uid'])) {
- $this->opml_notice(T_sprintf("Adding label %s", htmlspecialchars($label_name)));
+ if (!Labels::find_id($label_name, $owner_uid)) {
+ $this->opml_notice(T_sprintf("Adding label %s", htmlspecialchars($label_name)), $nest);
Labels::create($label_name, $fg_color, $bg_color, $owner_uid);
} else {
- $this->opml_notice(T_sprintf("Duplicate label: %s", htmlspecialchars($label_name)));
+ $this->opml_notice(T_sprintf("Duplicate label: %s", htmlspecialchars($label_name)), $nest);
}
}
}
- private function opml_import_preference($node) {
+ private function opml_import_preference(DOMNode $node, int $owner_uid, int $nest) {
$attrs = $node->attributes;
$pref_name = $attrs->getNamedItem('pref-name')->nodeValue;
@@ -368,13 +366,13 @@ class OPML extends Handler_Protected {
$pref_value = $attrs->getNamedItem('value')->nodeValue;
$this->opml_notice(T_sprintf("Setting preference key %s to %s",
- $pref_name, $pref_value));
+ $pref_name, $pref_value), $nest);
- set_pref($pref_name, $pref_value);
+ set_pref($pref_name, $pref_value, $owner_uid);
}
}
- private function opml_import_filter($node) {
+ private function opml_import_filter(DOMNode $node, int $owner_uid, int $nest) {
$attrs = $node->attributes;
$filter_type = $attrs->getNamedItem('filter-type')->nodeValue;
@@ -393,47 +391,58 @@ class OPML extends Handler_Protected {
$sth = $this->pdo->prepare("INSERT INTO ttrss_filters2 (match_any_rule,enabled,inverse,title,owner_uid)
VALUES (?, ?, ?, ?, ?)");
- $sth->execute([$match_any_rule, $enabled, $inverse, $title, $_SESSION['uid']]);
+ $sth->execute([$match_any_rule, $enabled, $inverse, $title, $owner_uid]);
$sth = $this->pdo->prepare("SELECT MAX(id) AS id FROM ttrss_filters2 WHERE
owner_uid = ?");
- $sth->execute([$_SESSION['uid']]);
+ $sth->execute([$owner_uid]);
$row = $sth->fetch();
$filter_id = $row['id'];
if ($filter_id) {
- $this->opml_notice(T_sprintf("Adding filter %s...", $title));
+ $this->opml_notice(T_sprintf("Adding filter %s...", $title), $nest);
+ //$this->opml_notice(json_encode($filter));
foreach ($filter["rules"] as $rule) {
$feed_id = null;
$cat_id = null;
- if ($rule["match"]) {
+ if ($rule["match"] ?? false) {
+
+ $match_on = [];
- $match_on = [];
+ foreach ($rule["match"] as $match) {
+ list ($name, $is_cat, $is_id) = $match;
- foreach ($rule["match"] as $match) {
- list ($name, $is_cat, $is_id) = $match;
+ if ($is_id) {
+ array_push($match_on, ($is_cat ? "CAT:" : "") . $name);
+ } else {
- if ($is_id) {
- array_push($match_on, ($is_cat ? "CAT:" : "") . $name);
- } else {
+ $match_id = Feeds::_find_by_title($name, $is_cat, $owner_uid);
- if (!$is_cat) {
- $tsth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
- WHERE title = ? AND owner_uid = ?");
+ if ($match_id) {
+ if ($is_cat) {
+ array_push($match_on, "CAT:$match_id");
+ } else {
+ array_push($match_on, $match_id);
+ }
+ }
- $tsth->execute([$name, $_SESSION['uid']]);
+ /*if (!$is_cat) {
+ $tsth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
+ WHERE title = ? AND owner_uid = ?");
- if ($row = $tsth->fetch()) {
- $match_id = $row['id'];
+ $tsth->execute([$name, $_SESSION['uid']]);
+
+ if ($row = $tsth->fetch()) {
+ $match_id = $row['id'];
array_push($match_on, $match_id);
- }
- } else {
- $tsth = $this->pdo->prepare("SELECT id FROM ttrss_feed_categories
- WHERE title = ? AND owner_uid = ?");
+ }
+ } else {
+ $tsth = $this->pdo->prepare("SELECT id FROM ttrss_feed_categories
+ WHERE title = ? AND owner_uid = ?");
$tsth->execute([$name, $_SESSION['uid']]);
if ($row = $tsth->fetch()) {
@@ -441,54 +450,64 @@ class OPML extends Handler_Protected {
array_push($match_on, "CAT:$match_id");
}
- }
- }
- }
+ } */
+ }
+ }
- $reg_exp = $rule["reg_exp"];
- $filter_type = (int)$rule["filter_type"];
- $inverse = bool_to_sql_bool($rule["inverse"]);
- $match_on = json_encode($match_on);
+ $reg_exp = $rule["reg_exp"];
+ $filter_type = (int)$rule["filter_type"];
+ $inverse = bool_to_sql_bool($rule["inverse"]);
+ $match_on = json_encode($match_on);
- $usth = $this->pdo->prepare("INSERT INTO ttrss_filters2_rules
+ $usth = $this->pdo->prepare("INSERT INTO ttrss_filters2_rules
(feed_id,cat_id,match_on,filter_id,filter_type,reg_exp,cat_filter,inverse)
- VALUES
- (NULL, NULL, ?, ?, ?, ?, false, ?)");
- $usth->execute([$match_on, $filter_id, $filter_type, $reg_exp, $inverse]);
+ VALUES
+ (NULL, NULL, ?, ?, ?, ?, false, ?)");
+ $usth->execute([$match_on, $filter_id, $filter_type, $reg_exp, $inverse]);
- } else {
+ } else {
- if (!$rule["cat_filter"]) {
- $tsth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
- WHERE title = ? AND owner_uid = ?");
+ $match_id = Feeds::_find_by_title($rule['feed'] ?? "", $rule['cat_filter'], $owner_uid);
- $tsth->execute([$rule['feed'], $_SESSION['uid']]);
+ if ($match_id) {
+ if ($rule['cat_filter']) {
+ $cat_id = $match_id;
+ } else {
+ $feed_id = $match_id;
+ }
+ }
- if ($row = $tsth->fetch()) {
- $feed_id = $row['id'];
- }
- } else {
+ /*if (!$rule["cat_filter"]) {
+ $tsth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
+ WHERE title = ? AND owner_uid = ?");
+
+ $tsth->execute([$rule['feed'], $_SESSION['uid']]);
+
+ if ($row = $tsth->fetch()) {
+ $feed_id = $row['id'];
+ }
+ } else {
$tsth = $this->pdo->prepare("SELECT id FROM ttrss_feed_categories
- WHERE title = ? AND owner_uid = ?");
+ WHERE title = ? AND owner_uid = ?");
$tsth->execute([$rule['feed'], $_SESSION['uid']]);
if ($row = $tsth->fetch()) {
$feed_id = $row['id'];
}
- }
+ } */
- $cat_filter = bool_to_sql_bool($rule["cat_filter"]);
- $reg_exp = $rule["reg_exp"];
- $filter_type = (int)$rule["filter_type"];
- $inverse = bool_to_sql_bool($rule["inverse"]);
+ $cat_filter = bool_to_sql_bool($rule["cat_filter"]);
+ $reg_exp = $rule["reg_exp"];
+ $filter_type = (int)$rule["filter_type"];
+ $inverse = bool_to_sql_bool($rule["inverse"]);
- $usth = $this->pdo->prepare("INSERT INTO ttrss_filters2_rules
+ $usth = $this->pdo->prepare("INSERT INTO ttrss_filters2_rules
(feed_id,cat_id,filter_id,filter_type,reg_exp,cat_filter,inverse)
- VALUES
- (?, ?, ?, ?, ?, ?, ?)");
- $usth->execute([$feed_id, $cat_id, $filter_id, $filter_type, $reg_exp, $cat_filter, $inverse]);
- }
+ VALUES
+ (?, ?, ?, ?, ?, ?, ?)");
+ $usth->execute([$feed_id, $cat_id, $filter_id, $filter_type, $reg_exp, $cat_filter, $inverse]);
+ }
}
foreach ($filter["actions"] as $action) {
@@ -507,8 +526,8 @@ class OPML extends Handler_Protected {
}
}
- private function opml_import_category($doc, $root_node, $owner_uid, $parent_id) {
- $default_cat_id = (int) $this->get_feed_category('Imported feeds', false);
+ private function opml_import_category(DOMDocument $doc, ?DOMNode $root_node, int $owner_uid, int $parent_id, int $nest) {
+ $default_cat_id = (int) $this->get_feed_category('Imported feeds', $owner_uid, 0);
if ($root_node) {
$cat_title = mb_substr($root_node->attributes->getNamedItem('text')->nodeValue, 0, 250);
@@ -517,13 +536,13 @@ class OPML extends Handler_Protected {
$cat_title = mb_substr($root_node->attributes->getNamedItem('title')->nodeValue, 0, 250);
if (!in_array($cat_title, array("tt-rss-filters", "tt-rss-labels", "tt-rss-prefs"))) {
- $cat_id = $this->get_feed_category($cat_title, $parent_id);
+ $cat_id = $this->get_feed_category($cat_title, $owner_uid, $parent_id);
- if ($cat_id === false) {
+ if ($cat_id === 0) {
$order_id = (int) $root_node->attributes->getNamedItem('ttrssSortOrder')->nodeValue;
- Feeds::_add_cat($cat_title, $_SESSION['uid'], $parent_id ? $parent_id : null, (int)$order_id);
- $cat_id = $this->get_feed_category($cat_title, $parent_id);
+ Feeds::_add_cat($cat_title, $owner_uid, $parent_id ? $parent_id : null, (int)$order_id);
+ $cat_id = $this->get_feed_category($cat_title, $owner_uid, $parent_id);
}
} else {
@@ -540,21 +559,21 @@ class OPML extends Handler_Protected {
$cat_title = false;
}
- #$this->opml_notice("[CAT] $cat_title id: $cat_id P_id: $parent_id");
- $this->opml_notice(T_sprintf("Processing category: %s", $cat_title ? $cat_title : __("Uncategorized")));
+ //$this->opml_notice("[CAT] $cat_title id: $cat_id P_id: $parent_id");
+ $this->opml_notice(T_sprintf("Processing category: %s", $cat_title ? $cat_title : __("Uncategorized")), $nest);
foreach ($outlines as $node) {
if ($node->hasAttributes() && strtolower($node->tagName) == "outline") {
$attrs = $node->attributes;
- $node_cat_title = $attrs->getNamedItem('text')->nodeValue;
+ $node_cat_title = $attrs->getNamedItem('text') ? $attrs->getNamedItem('text')->nodeValue : false;
if (!$node_cat_title)
- $node_cat_title = $attrs->getNamedItem('title')->nodeValue;
+ $node_cat_title = $attrs->getNamedItem('title') ? $attrs->getNamedItem('title')->nodeValue : false;
- $node_feed_url = $attrs->getNamedItem('xmlUrl')->nodeValue;
+ $node_feed_url = $attrs->getNamedItem('xmlUrl') ? $attrs->getNamedItem('xmlUrl')->nodeValue : false;
if ($node_cat_title && !$node_feed_url) {
- $this->opml_import_category($doc, $node, $owner_uid, $cat_id);
+ $this->opml_import_category($doc, $node, $owner_uid, $cat_id, $nest+1);
} else {
if (!$cat_id) {
@@ -565,91 +584,112 @@ class OPML extends Handler_Protected {
switch ($cat_title) {
case "tt-rss-prefs":
- $this->opml_import_preference($node);
+ $this->opml_import_preference($node, $owner_uid, $nest+1);
break;
case "tt-rss-labels":
- $this->opml_import_label($node, $owner_uid);
+ $this->opml_import_label($node, $owner_uid, $nest+1);
break;
case "tt-rss-filters":
- $this->opml_import_filter($node);
+ $this->opml_import_filter($node, $owner_uid, $nest+1);
break;
default:
- $this->opml_import_feed($node, $dst_cat_id, $owner_uid);
+ $this->opml_import_feed($node, $dst_cat_id, $owner_uid, $nest+1);
}
}
}
}
}
- function opml_import($owner_uid) {
+ /** $filename is optional; assumes HTTP upload with $_FILES otherwise */
+ function opml_import(int $owner_uid, string $filename = "") {
if (!$owner_uid) return;
$doc = false;
- if ($_FILES['opml_file']['error'] != 0) {
- print_error(T_sprintf("Upload failed with error code %d",
- $_FILES['opml_file']['error']));
- return;
- }
+ if (!$filename) {
+ if ($_FILES['opml_file']['error'] != 0) {
+ print_error(T_sprintf("Upload failed with error code %d",
+ $_FILES['opml_file']['error']));
+ return false;
+ }
- if (is_uploaded_file($_FILES['opml_file']['tmp_name'])) {
- $tmp_file = (string)tempnam(Config::get(Config::CACHE_DIR) . '/upload', 'opml');
+ if (is_uploaded_file($_FILES['opml_file']['tmp_name'])) {
+ $tmp_file = (string)tempnam(Config::get(Config::CACHE_DIR) . '/upload', 'opml');
- $result = move_uploaded_file($_FILES['opml_file']['tmp_name'],
- $tmp_file);
+ $result = move_uploaded_file($_FILES['opml_file']['tmp_name'],
+ $tmp_file);
- if (!$result) {
- print_error(__("Unable to move uploaded file."));
- return;
+ if (!$result) {
+ print_error(__("Unable to move uploaded file."));
+ return false;
+ }
+ } else {
+ print_error(__('Error: please upload OPML file.'));
+ return false;
}
} else {
- print_error(__('Error: please upload OPML file.'));
- return;
+ $tmp_file = $filename;
+ }
+
+ if (!is_readable($tmp_file)) {
+ $this->opml_notice(T_sprintf("Error: file is not readable: %s", $filename));
+ return false;
}
$loaded = false;
- if (is_file($tmp_file)) {
- $doc = new DOMDocument();
+ $doc = new DOMDocument();
+
+ if (version_compare(PHP_VERSION, '8.0.0', '<')) {
libxml_disable_entity_loader(false);
- $loaded = $doc->load($tmp_file);
+ }
+
+ $loaded = $doc->load($tmp_file);
+
+ if (version_compare(PHP_VERSION, '8.0.0', '<')) {
libxml_disable_entity_loader(true);
- unlink($tmp_file);
- } else if (empty($doc)) {
- print_error(__('Error: unable to find moved OPML file.'));
- return;
}
+ // only remove temporary i.e. HTTP uploaded files
+ if (!$filename)
+ unlink($tmp_file);
+
if ($loaded) {
- $this->pdo->beginTransaction();
- $this->opml_import_category($doc, false, $owner_uid, false);
- $this->pdo->commit();
+ // we're using ORM while importing so we can't transaction-lock things anymore
+ //$this->pdo->beginTransaction();
+ $this->opml_import_category($doc, null, $owner_uid, 0, 0);
+ //$this->pdo->commit();
} else {
- print_error(__('Error while parsing document.'));
+ $this->opml_notice(__('Error while parsing document.'));
+ return false;
}
- }
- private function opml_notice($msg) {
- print "$msg<br/>";
+ return true;
}
- function get_feed_category($feed_cat, $parent_cat_id = false) {
+ private function opml_notice(string $msg, int $prefix_length = 0) {
+ if (php_sapi_name() == "cli") {
+ Debug::log(str_repeat(" ", $prefix_length) . $msg);
+ } else {
+ // TODO: use better separator i.e. CSS-defined span of certain width or something
+ print str_repeat("&nbsp;&nbsp;&nbsp;", $prefix_length) . $msg . "<br/>";
+ }
+ }
- $parent_cat_id = (int) $parent_cat_id;
+ function get_feed_category(string $feed_cat, int $owner_uid, int $parent_cat_id) : int {
$sth = $this->pdo->prepare("SELECT id FROM ttrss_feed_categories
WHERE title = :title
AND (parent_cat = :parent OR (:parent = 0 AND parent_cat IS NULL))
AND owner_uid = :uid");
- $sth->execute([':title' => $feed_cat, ':parent' => $parent_cat_id, ':uid' => $_SESSION['uid']]);
+ $sth->execute([':title' => $feed_cat, ':parent' => $parent_cat_id, ':uid' => $owner_uid]);
if ($row = $sth->fetch()) {
return $row['id'];
} else {
- return false;
+ return 0;
}
}
-
}
diff --git a/classes/pluginhost.php b/classes/pluginhost.php
index ee4107ae7..f067543bb 100755
--- a/classes/pluginhost.php
+++ b/classes/pluginhost.php
@@ -23,56 +23,153 @@ class PluginHost {
// Hooks marked with *1 are run in global context and available
// to plugins loaded in config.php only
- const HOOK_ARTICLE_BUTTON = "hook_article_button"; // hook_article_button($line)
- const HOOK_ARTICLE_FILTER = "hook_article_filter"; // hook_article_filter($article)
- const HOOK_PREFS_TAB = "hook_prefs_tab"; // hook_prefs_tab($tab)
- const HOOK_PREFS_TAB_SECTION = "hook_prefs_tab_section"; // hook_prefs_tab_section($section)
- const HOOK_PREFS_TABS = "hook_prefs_tabs"; // hook_prefs_tabs()
- const HOOK_FEED_PARSED = "hook_feed_parsed"; // hook_feed_parsed($parser, $feed_id)
- const HOOK_UPDATE_TASK = "hook_update_task"; //*1 // GLOBAL: hook_update_task($cli_options)
- const HOOK_AUTH_USER = "hook_auth_user"; // hook_auth_user($login, $password, $service) (byref)
- const HOOK_HOTKEY_MAP = "hook_hotkey_map"; // hook_hotkey_map($hotkeys) (byref)
- const HOOK_RENDER_ARTICLE = "hook_render_article"; // hook_render_article($article)
- const HOOK_RENDER_ARTICLE_CDM = "hook_render_article_cdm"; // hook_render_article_cdm($article)
- const HOOK_FEED_FETCHED = "hook_feed_fetched"; // hook_feed_fetched($feed_data, $fetch_url, $owner_uid, $feed) (byref)
- const HOOK_SANITIZE = "hook_sanitize"; // hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id) (byref)
- const HOOK_RENDER_ARTICLE_API = "hook_render_article_api"; // hook_render_article_api($params)
- const HOOK_TOOLBAR_BUTTON = "hook_toolbar_button"; // hook_toolbar_button()
- const HOOK_ACTION_ITEM = "hook_action_item"; // hook_action_item()
- const HOOK_HEADLINE_TOOLBAR_BUTTON = "hook_headline_toolbar_button"; // hook_headline_toolbar_button($feed_id, $is_cat)
- const HOOK_HOTKEY_INFO = "hook_hotkey_info"; // hook_hotkey_info($hotkeys) (byref)
- const HOOK_ARTICLE_LEFT_BUTTON = "hook_article_left_button"; // hook_article_left_button($row)
- const HOOK_PREFS_EDIT_FEED = "hook_prefs_edit_feed"; // hook_prefs_edit_feed($feed_id)
- const HOOK_PREFS_SAVE_FEED = "hook_prefs_save_feed"; // hook_prefs_save_feed($feed_id)
- const HOOK_FETCH_FEED = "hook_fetch_feed"; // hook_fetch_feed($feed_data, $fetch_url, $owner_uid, $feed, $last_article_timestamp, $auth_login, $auth_pass) (byref)
- const HOOK_QUERY_HEADLINES = "hook_query_headlines"; // hook_query_headlines($row) (byref)
- const HOOK_HOUSE_KEEPING = "hook_house_keeping"; //*1 // GLOBAL: hook_house_keeping()
- const HOOK_SEARCH = "hook_search"; // hook_search($query)
- const HOOK_FORMAT_ENCLOSURES = "hook_format_enclosures"; // hook__format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images) (byref)
- const HOOK_SUBSCRIBE_FEED = "hook_subscribe_feed"; // hook_subscribe_feed($contents, $url, $auth_login, $auth_pass) (byref)
- const HOOK_HEADLINES_BEFORE = "hook_headlines_before"; // hook_headlines_before($feed, $is_cat, $qfh_ret)
- const HOOK_RENDER_ENCLOSURE = "hook_render_enclosure"; // hook_render_enclosure($entry, $id, $rv)
- const HOOK_ARTICLE_FILTER_ACTION = "hook_article_filter_action"; // hook_article_filter_action($article, $action)
- const HOOK_ARTICLE_EXPORT_FEED = "hook_article_export_feed"; // hook_article_export_feed($line, $feed, $is_cat, $owner_uid) (byref)
- const HOOK_MAIN_TOOLBAR_BUTTON = "hook_main_toolbar_button"; // hook_main_toolbar_button()
- const HOOK_ENCLOSURE_ENTRY = "hook_enclosure_entry"; // hook_enclosure_entry($entry, $id, $rv) (byref)
- const HOOK_FORMAT_ARTICLE = "hook_format_article"; // hook_format_article($html, $row)
- const HOOK_FORMAT_ARTICLE_CDM = "hook_format_article_cdm"; /* RIP */
- const HOOK_FEED_BASIC_INFO = "hook_feed_basic_info"; // hook_feed_basic_info($basic_info, $fetch_url, $owner_uid, $feed_id, $auth_login, $auth_pass) (byref)
- const HOOK_SEND_LOCAL_FILE = "hook_send_local_file"; // hook_send_local_file($filename)
- const HOOK_UNSUBSCRIBE_FEED = "hook_unsubscribe_feed"; // hook_unsubscribe_feed($feed_id, $owner_uid)
- const HOOK_SEND_MAIL = "hook_send_mail"; // hook_send_mail(Mailer $mailer, $params)
- const HOOK_FILTER_TRIGGERED = "hook_filter_triggered"; // hook_filter_triggered($feed_id, $owner_uid, $article, $matched_filters, $matched_rules, $article_filters)
- const HOOK_GET_FULL_TEXT = "hook_get_full_text"; // hook_get_full_text($url)
- const HOOK_ARTICLE_IMAGE = "hook_article_image"; // hook_article_image($enclosures, $content, $site_url)
- const HOOK_FEED_TREE = "hook_feed_tree"; // hook_feed_tree()
- const HOOK_IFRAME_WHITELISTED = "hook_iframe_whitelisted"; // hook_iframe_whitelisted($url)
- const HOOK_ENCLOSURE_IMPORTED = "hook_enclosure_imported"; // hook_enclosure_imported($enclosure, $feed)
- const HOOK_HEADLINES_CUSTOM_SORT_MAP = "hook_headlines_custom_sort_map"; // hook_headlines_custom_sort_map()
+ /** hook_article_button($line) */
+ const HOOK_ARTICLE_BUTTON = "hook_article_button";
+
+ /** hook_article_filter($article) */
+ const HOOK_ARTICLE_FILTER = "hook_article_filter";
+
+ /** hook_prefs_tab($tab) */
+ const HOOK_PREFS_TAB = "hook_prefs_tab";
+
+ /** hook_prefs_tab_section($section) */
+ const HOOK_PREFS_TAB_SECTION = "hook_prefs_tab_section";
+
+ /** hook_prefs_tabs() */
+ const HOOK_PREFS_TABS = "hook_prefs_tabs";
+
+ /** hook_feed_parsed($parser, $feed_id) */
+ const HOOK_FEED_PARSED = "hook_feed_parsed";
+
+ /** GLOBAL: hook_update_task($cli_options) */
+ const HOOK_UPDATE_TASK = "hook_update_task"; //*1
+
+ /** hook_auth_user($login, $password, $service) (byref) */
+ const HOOK_AUTH_USER = "hook_auth_user";
+
+ /** hook_hotkey_map($hotkeys) (byref) */
+ const HOOK_HOTKEY_MAP = "hook_hotkey_map";
+
+ /** hook_render_article($article) */
+ const HOOK_RENDER_ARTICLE = "hook_render_article";
+
+ /** hook_render_article_cdm($article) */
+ const HOOK_RENDER_ARTICLE_CDM = "hook_render_article_cdm";
+
+ /** hook_feed_fetched($feed_data, $fetch_url, $owner_uid, $feed) (byref) */
+ const HOOK_FEED_FETCHED = "hook_feed_fetched";
+
+ /** hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id) (byref) */
+ const HOOK_SANITIZE = "hook_sanitize";
+
+ /** hook_render_article_api($params) */
+ const HOOK_RENDER_ARTICLE_API = "hook_render_article_api";
+
+ /** hook_toolbar_button() */
+ const HOOK_TOOLBAR_BUTTON = "hook_toolbar_button";
+
+ /** hook_action_item() */
+ const HOOK_ACTION_ITEM = "hook_action_item";
+
+ /** hook_headline_toolbar_button($feed_id, $is_cat) */
+ const HOOK_HEADLINE_TOOLBAR_BUTTON = "hook_headline_toolbar_button";
+
+ /** hook_hotkey_info($hotkeys) (byref) */
+ const HOOK_HOTKEY_INFO = "hook_hotkey_info";
+
+ /** hook_article_left_button($row) */
+ const HOOK_ARTICLE_LEFT_BUTTON = "hook_article_left_button";
+
+ /** hook_prefs_edit_feed($feed_id) */
+ const HOOK_PREFS_EDIT_FEED = "hook_prefs_edit_feed";
+
+ /** hook_prefs_save_feed($feed_id) */
+ const HOOK_PREFS_SAVE_FEED = "hook_prefs_save_feed";
+
+ /** hook_fetch_feed($feed_data, $fetch_url, $owner_uid, $feed, $last_article_timestamp, $auth_login, $auth_pass) (byref) */
+ const HOOK_FETCH_FEED = "hook_fetch_feed";
+
+ /** hook_query_headlines($row) (byref) */
+ const HOOK_QUERY_HEADLINES = "hook_query_headlines";
+
+ /** GLOBAL: hook_house_keeping() */
+ const HOOK_HOUSE_KEEPING = "hook_house_keeping"; //*1
+
+ /** hook_search($query) */
+ const HOOK_SEARCH = "hook_search";
+
+ /** hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images) (byref) */
+ const HOOK_FORMAT_ENCLOSURES = "hook_format_enclosures";
+
+ /** hook_subscribe_feed($contents, $url, $auth_login, $auth_pass) (byref) */
+ const HOOK_SUBSCRIBE_FEED = "hook_subscribe_feed";
+
+ /** hook_headlines_before($feed, $is_cat, $qfh_ret) */
+ const HOOK_HEADLINES_BEFORE = "hook_headlines_before";
+
+ /** hook_render_enclosure($entry, $id, $rv) */
+ const HOOK_RENDER_ENCLOSURE = "hook_render_enclosure";
+
+ /** hook_article_filter_action($article, $action) */
+ const HOOK_ARTICLE_FILTER_ACTION = "hook_article_filter_action";
+
+ /** hook_article_export_feed($line, $feed, $is_cat, $owner_uid) (byref) */
+ const HOOK_ARTICLE_EXPORT_FEED = "hook_article_export_feed";
+
+ /** hook_main_toolbar_button() */
+ const HOOK_MAIN_TOOLBAR_BUTTON = "hook_main_toolbar_button";
+
+ /** hook_enclosure_entry($entry, $id, $rv) (byref) */
+ const HOOK_ENCLOSURE_ENTRY = "hook_enclosure_entry";
+
+ /** hook_format_article($html, $row) */
+ const HOOK_FORMAT_ARTICLE = "hook_format_article";
+
+ /** @deprecated removed, do not use */
+ const HOOK_FORMAT_ARTICLE_CDM = "hook_format_article_cdm";
+
+ /** hook_feed_basic_info($basic_info, $fetch_url, $owner_uid, $feed_id, $auth_login, $auth_pass) (byref) */
+ const HOOK_FEED_BASIC_INFO = "hook_feed_basic_info";
+
+ /** hook_send_local_file($filename) */
+ const HOOK_SEND_LOCAL_FILE = "hook_send_local_file";
+
+ /** hook_unsubscribe_feed($feed_id, $owner_uid) */
+ const HOOK_UNSUBSCRIBE_FEED = "hook_unsubscribe_feed";
+
+ /** hook_send_mail(Mailer $mailer, $params) */
+ const HOOK_SEND_MAIL = "hook_send_mail";
+
+ /** hook_filter_triggered($feed_id, $owner_uid, $article, $matched_filters, $matched_rules, $article_filters) */
+ const HOOK_FILTER_TRIGGERED = "hook_filter_triggered";
+
+ /** hook_get_full_text($url) */
+ const HOOK_GET_FULL_TEXT = "hook_get_full_text";
+
+ /** hook_article_image($enclosures, $content, $site_url) */
+ const HOOK_ARTICLE_IMAGE = "hook_article_image";
+
+ /** hook_feed_tree() */
+ const HOOK_FEED_TREE = "hook_feed_tree";
+
+ /** hook_iframe_whitelisted($url) */
+ const HOOK_IFRAME_WHITELISTED = "hook_iframe_whitelisted";
+
+ /** hook_enclosure_imported($enclosure, $feed) */
+ const HOOK_ENCLOSURE_IMPORTED = "hook_enclosure_imported";
+
+ /** hook_headlines_custom_sort_map() */
+ const HOOK_HEADLINES_CUSTOM_SORT_MAP = "hook_headlines_custom_sort_map";
+
+ /** hook_headlines_custom_sort_override($order) */
const HOOK_HEADLINES_CUSTOM_SORT_OVERRIDE = "hook_headlines_custom_sort_override";
- // hook_headlines_custom_sort_override($order)
+
+ /** hook_headline_toolbar_select_menu_item($feed_id, $is_cat) */
const HOOK_HEADLINE_TOOLBAR_SELECT_MENU_ITEM = "hook_headline_toolbar_select_menu_item";
- // hook_headline_toolbar_select_menu_item($feed_id, $is_cat)
+
+
+ /** hook_pre_subscribe($url, $auth_login, $auth_pass) (byref) */
+ const HOOK_PRE_SUBSCRIBE = "hook_pre_subscribe";
const KIND_ALL = 1;
const KIND_SYSTEM = 2;
@@ -103,12 +200,12 @@ class PluginHost {
$this->plugins[$name] = $plugin;
}
- // needed for compatibility with API 1
+ /** needed for compatibility with API 1 */
function get_link() {
return false;
}
- // needed for compatibility with API 2 (?)
+ /** needed for compatibility with API 2 (?) */
function get_dbh() {
return false;
}
diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php
index 5f7635736..95bbcd190 100755
--- a/classes/pref/feeds.php
+++ b/classes/pref/feeds.php
@@ -12,19 +12,12 @@ class Pref_Feeds extends Handler_Protected {
}
public static function get_ts_languages() {
- $rv = [];
-
- if (Config::get(Config::DB_TYPE) == "pgsql") {
- $dbh = Db::pdo();
-
- $res = $dbh->query("SELECT cfgname FROM pg_ts_config");
-
- while ($row = $res->fetch()) {
- array_push($rv, ucfirst($row['cfgname']));
- }
+ if (Config::get(Config::DB_TYPE) == 'pgsql') {
+ return array_map('ucfirst',
+ array_column(ORM::for_table('pg_ts_config')->select('cfgname')->find_array(), 'cfgname'));
}
- return $rv;
+ return [];
}
function renameCat() {
@@ -51,61 +44,60 @@ class Pref_Feeds extends Handler_Protected {
$show_empty_cats = clean($_REQUEST['force_show_empty'] ?? false) ||
(clean($_REQUEST['mode'] ?? 0) != 2 && !$search);
- $items = array();
-
- $sth = $this->pdo->prepare("SELECT id, title FROM ttrss_feed_categories
- WHERE owner_uid = ? AND parent_cat = ? ORDER BY order_id, title");
- $sth->execute([$_SESSION['uid'], $cat_id]);
-
- while ($line = $sth->fetch()) {
-
- $cat = array();
- $cat['id'] = 'CAT:' . $line['id'];
- $cat['bare_id'] = (int)$line['id'];
- $cat['name'] = $line['title'];
- $cat['items'] = array();
- $cat['checkbox'] = false;
- $cat['type'] = 'category';
- $cat['unread'] = -1;
- $cat['child_unread'] = -1;
- $cat['auxcounter'] = -1;
- $cat['parent_id'] = $cat_id;
-
- $cat['items'] = $this->get_category_items($line['id']);
+ $items = [];
+
+ $feed_categories = ORM::for_table('ttrss_feed_categories')
+ ->select_many('id', 'title')
+ ->where(['owner_uid' => $_SESSION['uid'], 'parent_cat' => $cat_id])
+ ->order_by_asc('order_id')
+ ->order_by_asc('title')
+ ->find_many();
+
+ foreach ($feed_categories as $feed_category) {
+ $cat = [
+ 'id' => 'CAT:' . $feed_category->id,
+ 'bare_id' => (int)$feed_category->id,
+ 'name' => $feed_category->title,
+ 'items' => $this->get_category_items($feed_category->id),
+ 'checkbox' => false,
+ 'type' => 'category',
+ 'unread' => -1,
+ 'child_unread' => -1,
+ 'auxcounter' => -1,
+ 'parent_id' => $cat_id,
+ ];
$num_children = $this->calculate_children_count($cat);
$cat['param'] = sprintf(_ngettext('(%d feed)', '(%d feeds)', (int) $num_children), $num_children);
if ($num_children > 0 || $show_empty_cats)
array_push($items, $cat);
+ }
+
+ $feeds_obj = ORM::for_table('ttrss_feeds')
+ ->select_many('id', 'title', 'last_error', 'update_interval')
+ ->select_expr(SUBSTRING_FOR_DATE.'(last_updated,1,19)', 'last_updated')
+ ->where(['cat_id' => $cat_id, 'owner_uid' => $_SESSION['uid']])
+ ->order_by_asc('order_id')
+ ->order_by_asc('title');
+ if ($search) {
+ $feeds_obj->where_raw('(LOWER(title) LIKE ? OR LOWER(feed_url) LIKE LOWER(?))', ["%$search%", "%$search%"]);
}
- $fsth = $this->pdo->prepare("SELECT id, title, last_error,
- ".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated, update_interval
- FROM ttrss_feeds
- WHERE cat_id = :cat AND
- owner_uid = :uid AND
- (:search = '' OR (LOWER(title) LIKE :search OR LOWER(feed_url) LIKE :search))
- ORDER BY order_id, title");
-
- $fsth->execute([":cat" => $cat_id, ":uid" => $_SESSION['uid'], ":search" => $search ? "%$search%" : ""]);
-
- while ($feed_line = $fsth->fetch()) {
- $feed = array();
- $feed['id'] = 'FEED:' . $feed_line['id'];
- $feed['bare_id'] = (int)$feed_line['id'];
- $feed['auxcounter'] = -1;
- $feed['name'] = $feed_line['title'];
- $feed['checkbox'] = false;
- $feed['unread'] = -1;
- $feed['error'] = $feed_line['last_error'];
- $feed['icon'] = Feeds::_get_icon($feed_line['id']);
- $feed['param'] = TimeHelper::make_local_datetime(
- $feed_line['last_updated'], true);
- $feed['updates_disabled'] = (int)($feed_line['update_interval'] < 0);
-
- array_push($items, $feed);
+ foreach ($feeds_obj->find_many() as $feed) {
+ array_push($items, [
+ 'id' => 'FEED:' . $feed->id,
+ 'bare_id' => (int) $feed->id,
+ 'auxcounter' => -1,
+ 'name' => $feed->title,
+ 'checkbox' => false,
+ 'unread' => -1,
+ 'error' => $feed->last_error,
+ 'icon' => Feeds::_get_icon($feed->id),
+ 'param' => TimeHelper::make_local_datetime($feed->last_updated, true),
+ 'updates_disabled' => (int)($feed->update_interval < 0),
+ ]);
}
return $items;
@@ -181,24 +173,23 @@ class Pref_Feeds extends Handler_Protected {
if (get_pref(Prefs::ENABLE_FEED_CATS)) {
$cat = $this->feedlist_init_cat(-2);
} else {
- $cat['items'] = array();
+ $cat['items'] = [];
}
- $num_labels = 0;
- while ($line = $sth->fetch()) {
- ++$num_labels;
-
- $label_id = Labels::label_to_feed_id($line['id']);
-
- $feed = $this->feedlist_init_feed($label_id, false, 0);
-
- $feed['fg_color'] = $line['fg_color'];
- $feed['bg_color'] = $line['bg_color'];
-
- array_push($cat['items'], $feed);
- }
+ $labels = ORM::for_table('ttrss_labels2')
+ ->where('owner_uid', $_SESSION['uid'])
+ ->order_by_asc('caption')
+ ->find_many();
+
+ if (count($labels)) {
+ foreach ($labels as $label) {
+ $label_id = Labels::label_to_feed_id($label->id);
+ $feed = $this->feedlist_init_feed($label_id, false, 0);
+ $feed['fg_color'] = $label->fg_color;
+ $feed['bg_color'] = $label->bg_color;
+ array_push($cat['items'], $feed);
+ }
- if ($num_labels) {
if ($enable_cats) {
array_push($root['items'], $cat);
} else {
@@ -211,23 +202,26 @@ class Pref_Feeds extends Handler_Protected {
$show_empty_cats = clean($_REQUEST['force_show_empty'] ?? false) ||
(clean($_REQUEST['mode'] ?? 0) != 2 && !$search);
- $sth = $this->pdo->prepare("SELECT id, title FROM ttrss_feed_categories
- WHERE owner_uid = ? AND parent_cat IS NULL ORDER BY order_id, title");
- $sth->execute([$_SESSION['uid']]);
-
- while ($line = $sth->fetch()) {
- $cat = array();
- $cat['id'] = 'CAT:' . $line['id'];
- $cat['bare_id'] = (int)$line['id'];
- $cat['auxcounter'] = -1;
- $cat['name'] = $line['title'];
- $cat['items'] = array();
- $cat['checkbox'] = false;
- $cat['type'] = 'category';
- $cat['unread'] = -1;
- $cat['child_unread'] = -1;
-
- $cat['items'] = $this->get_category_items($line['id']);
+ $feed_categories = ORM::for_table('ttrss_feed_categories')
+ ->select_many('id', 'title')
+ ->where('owner_uid', $_SESSION['uid'])
+ ->where_null('parent_cat')
+ ->order_by_asc('order_id')
+ ->order_by_asc('title')
+ ->find_many();
+
+ foreach ($feed_categories as $feed_category) {
+ $cat = [
+ 'id' => 'CAT:' . $feed_category->id,
+ 'bare_id' => (int) $feed_category->id,
+ 'auxcounter' => -1,
+ 'name' => $feed_category->title,
+ 'items' => $this->get_category_items($feed_category->id),
+ 'checkbox' => false,
+ 'type' => 'category',
+ 'unread' => -1,
+ 'child_unread' => -1,
+ ];
$num_children = $this->calculate_children_count($cat);
$cat['param'] = sprintf(_ngettext('(%d feed)', '(%d feeds)', (int) $num_children), $num_children);
@@ -235,47 +229,48 @@ class Pref_Feeds extends Handler_Protected {
if ($num_children > 0 || $show_empty_cats)
array_push($root['items'], $cat);
- $root['param'] += count($cat['items']);
+ //$root['param'] += count($cat['items']);
}
/* Uncategorized is a special case */
+ $cat = [
+ 'id' => 'CAT:0',
+ 'bare_id' => 0,
+ 'auxcounter' => -1,
+ 'name' => __('Uncategorized'),
+ 'items' => [],
+ 'type' => 'category',
+ 'checkbox' => false,
+ 'unread' => -1,
+ 'child_unread' => -1,
+ ];
+
+ $feeds_obj = ORM::for_table('ttrss_feeds')
+ ->select_many('id', 'title', 'last_error', 'update_interval')
+ ->select_expr(SUBSTRING_FOR_DATE.'(last_updated,1,19)', 'last_updated')
+ ->where('owner_uid', $_SESSION['uid'])
+ ->where_null('cat_id')
+ ->order_by_asc('order_id')
+ ->order_by_asc('title');
- $cat = array();
- $cat['id'] = 'CAT:0';
- $cat['bare_id'] = 0;
- $cat['auxcounter'] = -1;
- $cat['name'] = __("Uncategorized");
- $cat['items'] = array();
- $cat['type'] = 'category';
- $cat['checkbox'] = false;
- $cat['unread'] = -1;
- $cat['child_unread'] = -1;
-
- $fsth = $this->pdo->prepare("SELECT id, title,last_error,
- ".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated, update_interval
- FROM ttrss_feeds
- WHERE cat_id IS NULL AND
- owner_uid = :uid AND
- (:search = '' OR (LOWER(title) LIKE :search OR LOWER(feed_url) LIKE :search))
- ORDER BY order_id, title");
- $fsth->execute([":uid" => $_SESSION['uid'], ":search" => $search ? "%$search%" : ""]);
-
- while ($feed_line = $fsth->fetch()) {
- $feed = array();
- $feed['id'] = 'FEED:' . $feed_line['id'];
- $feed['bare_id'] = (int)$feed_line['id'];
- $feed['auxcounter'] = -1;
- $feed['name'] = $feed_line['title'];
- $feed['checkbox'] = false;
- $feed['error'] = $feed_line['last_error'];
- $feed['icon'] = Feeds::_get_icon($feed_line['id']);
- $feed['param'] = TimeHelper::make_local_datetime(
- $feed_line['last_updated'], true);
- $feed['unread'] = -1;
- $feed['type'] = 'feed';
- $feed['updates_disabled'] = (int)($feed_line['update_interval'] < 0);
-
- array_push($cat['items'], $feed);
+ if ($search) {
+ $feeds_obj->where_raw('(LOWER(title) LIKE ? OR LOWER(feed_url) LIKE LOWER(?))', ["%$search%", "%$search%"]);
+ }
+
+ foreach ($feeds_obj->find_many() as $feed) {
+ array_push($cat['items'], [
+ 'id' => 'FEED:' . $feed->id,
+ 'bare_id' => (int) $feed->id,
+ 'auxcounter' => -1,
+ 'name' => $feed->title,
+ 'checkbox' => false,
+ 'error' => $feed->last_error,
+ 'icon' => Feeds::_get_icon($feed->id),
+ 'param' => TimeHelper::make_local_datetime($feed->last_updated, true),
+ 'unread' => -1,
+ 'type' => 'feed',
+ 'updates_disabled' => (int)($feed->update_interval < 0),
+ ]);
}
$cat['param'] = sprintf(_ngettext('(%d feed)', '(%d feeds)', count($cat['items'])), count($cat['items']));
@@ -287,46 +282,41 @@ class Pref_Feeds extends Handler_Protected {
$root['param'] = sprintf(_ngettext('(%d feed)', '(%d feeds)', (int) $num_children), $num_children);
} else {
- $fsth = $this->pdo->prepare("SELECT id, title, last_error,
- ".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated, update_interval
- FROM ttrss_feeds
- WHERE owner_uid = :uid AND
- (:search = '' OR (LOWER(title) LIKE :search OR LOWER(feed_url) LIKE :search))
- ORDER BY order_id, title");
- $fsth->execute([":uid" => $_SESSION['uid'], ":search" => $search ? "%$search%" : ""]);
-
- while ($feed_line = $fsth->fetch()) {
- $feed = array();
- $feed['id'] = 'FEED:' . $feed_line['id'];
- $feed['bare_id'] = (int)$feed_line['id'];
- $feed['auxcounter'] = -1;
- $feed['name'] = $feed_line['title'];
- $feed['checkbox'] = false;
- $feed['error'] = $feed_line['last_error'];
- $feed['icon'] = Feeds::_get_icon($feed_line['id']);
- $feed['param'] = TimeHelper::make_local_datetime(
- $feed_line['last_updated'], true);
- $feed['unread'] = -1;
- $feed['type'] = 'feed';
- $feed['updates_disabled'] = (int)($feed_line['update_interval'] < 0);
-
- array_push($root['items'], $feed);
- }
+ $feeds_obj = ORM::for_table('ttrss_feeds')
+ ->select_many('id', 'title', 'last_error', 'update_interval')
+ ->select_expr(SUBSTRING_FOR_DATE.'(last_updated,1,19)', 'last_updated')
+ ->where('owner_uid', $_SESSION['uid'])
+ ->order_by_asc('order_id')
+ ->order_by_asc('title');
- $root['param'] = sprintf(_ngettext('(%d feed)', '(%d feeds)', count($root['items'])), count($root['items']));
- }
+ if ($search) {
+ $feeds_obj->where_raw('(LOWER(title) LIKE ? OR LOWER(feed_url) LIKE LOWER(?))', ["%$search%", "%$search%"]);
+ }
- $fl = array();
- $fl['identifier'] = 'id';
- $fl['label'] = 'name';
+ foreach ($feeds_obj->find_many() as $feed) {
+ array_push($root['items'], [
+ 'id' => 'FEED:' . $feed->id,
+ 'bare_id' => (int) $feed->id,
+ 'auxcounter' => -1,
+ 'name' => $feed->title,
+ 'checkbox' => false,
+ 'error' => $feed->last_error,
+ 'icon' => Feeds::_get_icon($feed->id),
+ 'param' => TimeHelper::make_local_datetime($feed->last_updated, true),
+ 'unread' => -1,
+ 'type' => 'feed',
+ 'updates_disabled' => (int)($feed->update_interval < 0),
+ ]);
+ }
- if (clean($_REQUEST['mode'] ?? 0) != 2) {
- $fl['items'] = array($root);
- } else {
- $fl['items'] = $root['items'];
+ $root['param'] = sprintf(_ngettext('(%d feed)', '(%d feeds)', count($root['items'])), count($root['items']));
}
- return $fl;
+ return [
+ 'identifier' => 'id',
+ 'label' => 'name',
+ 'items' => clean($_REQUEST['mode'] ?? 0) != 2 ? [$root] : $root['items'],
+ ];
}
function catsortreset() {
@@ -359,10 +349,14 @@ class Pref_Feeds extends Handler_Protected {
$parent_qpart = null;
}
- $sth = $this->pdo->prepare("UPDATE ttrss_feed_categories
- SET parent_cat = ? WHERE id = ? AND
- owner_uid = ?");
- $sth->execute([$parent_qpart, $bare_item_id, $_SESSION['uid']]);
+ $feed_category = ORM::for_table('ttrss_feed_categories')
+ ->where('owner_uid', $_SESSION['uid'])
+ ->find_one($bare_item_id);
+
+ if ($feed_category) {
+ $feed_category->parent_cat = $parent_qpart;
+ $feed_category->save();
+ }
}
$order_id = 1;
@@ -380,22 +374,27 @@ class Pref_Feeds extends Handler_Protected {
if (strpos($id, "FEED") === 0) {
- $cat_id = ($item_id != "root") ? $bare_item_id : null;
-
- $sth = $this->pdo->prepare("UPDATE ttrss_feeds
- SET order_id = ?, cat_id = ?
- WHERE id = ? AND owner_uid = ?");
-
- $sth->execute([$order_id, $cat_id ? $cat_id : null, $bare_id, $_SESSION['uid']]);
+ $feed = ORM::for_table('ttrss_feeds')
+ ->where('owner_uid', $_SESSION['uid'])
+ ->find_one($bare_id);
+ if ($feed) {
+ $feed->order_id = $order_id;
+ $feed->cat_id = ($item_id != "root" && $bare_item_id) ? $bare_item_id : null;
+ $feed->save();
+ }
} else if (strpos($id, "CAT:") === 0) {
$this->process_category_order($data_map, $item['_reference'], $item_id,
$nest_level+1);
- $sth = $this->pdo->prepare("UPDATE ttrss_feed_categories
- SET order_id = ? WHERE id = ? AND
- owner_uid = ?");
- $sth->execute([$order_id, $bare_id, $_SESSION['uid']]);
+ $feed_category = ORM::for_table('ttrss_feed_categories')
+ ->where('owner_uid', $_SESSION['uid'])
+ ->find_one($bare_id);
+
+ if ($feed_category) {
+ $feed_category->order_id = $order_id;
+ $feed_category->save();
+ }
}
}
@@ -1120,41 +1119,38 @@ class Pref_Feeds extends Handler_Protected {
$interval_qpart = "DATE_SUB(NOW(), INTERVAL 3 MONTH)";
}
- $sth = $this->pdo->prepare("SELECT ttrss_feeds.title, ttrss_feeds.site_url,
- ttrss_feeds.feed_url, ttrss_feeds.id, MAX(updated) AS last_article
- FROM ttrss_feeds, ttrss_entries, ttrss_user_entries WHERE
- (SELECT MAX(updated) FROM ttrss_entries, ttrss_user_entries WHERE
- ttrss_entries.id = ref_id AND
- ttrss_user_entries.feed_id = ttrss_feeds.id) < $interval_qpart
- AND ttrss_feeds.owner_uid = ? AND
- ttrss_user_entries.feed_id = ttrss_feeds.id AND
- ttrss_entries.id = ref_id
- GROUP BY ttrss_feeds.title, ttrss_feeds.id, ttrss_feeds.site_url, ttrss_feeds.feed_url
- ORDER BY last_article");
- $sth->execute([$_SESSION['uid']]);
-
- $rv = [];
-
- while ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
- $row['last_article'] = TimeHelper::make_local_datetime($row['last_article'], false);
- array_push($rv, $row);
+ $inactive_feeds = ORM::for_table('ttrss_feeds')
+ ->table_alias('f')
+ ->select_many('f.id', 'f.title', 'f.site_url', 'f.feed_url')
+ ->select_expr('MAX(e.updated)', 'last_article')
+ ->join('ttrss_user_entries', [ 'ue.feed_id', '=', 'f.id'], 'ue')
+ ->join('ttrss_entries', ['e.id', '=', 'ue.ref_id'], 'e')
+ ->where('f.owner_uid', $_SESSION['uid'])
+ ->where_raw(
+ "(SELECT MAX(ttrss_entries.updated)
+ FROM ttrss_entries
+ JOIN ttrss_user_entries ON ttrss_entries.id = ttrss_user_entries.ref_id
+ WHERE ttrss_user_entries.feed_id = f.id) < $interval_qpart")
+ ->group_by('f.title')
+ ->group_by('f.id')
+ ->group_by('f.site_url')
+ ->group_by('f.feed_url')
+ ->order_by_asc('last_article')
+ ->find_array();
+
+ foreach ($inactive_feeds as $inactive_feed) {
+ $inactive_feed['last_article'] = TimeHelper::make_local_datetime($inactive_feed['last_article'], false);
}
- print json_encode($rv);
+ print json_encode($inactive_feeds);
}
function feedsWithErrors() {
- $sth = $this->pdo->prepare("SELECT id,title,feed_url,last_error,site_url
- FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?");
- $sth->execute([$_SESSION['uid']]);
-
- $rv = [];
-
- while ($row = $sth->fetch()) {
- array_push($rv, $row);
- }
-
- print json_encode($rv);
+ print json_encode(ORM::for_table('ttrss_feeds')
+ ->select_many('id', 'title', 'feed_url', 'last_error', 'site_url')
+ ->where_not_equal('last_error', '')
+ ->where('owner_uid', $_SESSION['uid'])
+ ->find_array());
}
static function remove_feed($id, $owner_uid) {
@@ -1272,7 +1268,7 @@ class Pref_Feeds extends Handler_Protected {
$c = 0;
foreach ($cat['items'] ?? [] as $child) {
- if ($child['type'] ?? '' == 'category') {
+ if (($child['type'] ?? '') == 'category') {
$c += $this->calculate_children_count($child);
} else {
$c += 1;
diff --git a/classes/pref/filters.php b/classes/pref/filters.php
index 29d309dbb..c4017e4ec 100755
--- a/classes/pref/filters.php
+++ b/classes/pref/filters.php
@@ -203,7 +203,7 @@ class Pref_Filters extends Handler_Protected {
} else {
$where = $line["cat_filter"] ?
- Feeds::_get_cat_title($line["cat_id"]) :
+ Feeds::_get_cat_title($line["cat_id"] ?? 0) :
($line["feed_id"] ?
Feeds::_get_title($line["feed_id"]) : __("All feeds"));
}
diff --git a/classes/pref/labels.php b/classes/pref/labels.php
index 2cdb919ce..0eb88ea36 100644
--- a/classes/pref/labels.php
+++ b/classes/pref/labels.php
@@ -160,7 +160,7 @@ class Pref_Labels extends Handler_Protected {
function add() {
$caption = clean($_REQUEST["caption"]);
- $output = clean($_REQUEST["output"]);
+ $output = clean($_REQUEST["output"] ?? false);
if ($caption) {
if (Labels::create($caption)) {
diff --git a/classes/rpc.php b/classes/rpc.php
index 23a45d951..b6c4a5fc9 100755
--- a/classes/rpc.php
+++ b/classes/rpc.php
@@ -522,6 +522,7 @@ class RPC extends Handler_Protected {
WHERE
errno NOT IN (".E_USER_NOTICE.", ".E_USER_DEPRECATED.") AND
$log_interval AND
+ errstr NOT LIKE '%Returning bool from comparison function is deprecated%' AND
errstr NOT LIKE '%imagecreatefromstring(): Data is not in a recognized format%'");
$sth->execute();
diff --git a/classes/rssutils.php b/classes/rssutils.php
index 216792a0e..87e52ba42 100755
--- a/classes/rssutils.php
+++ b/classes/rssutils.php
@@ -646,7 +646,7 @@ class RSSUtils {
$entry_title = strip_tags($item->get_title());
- $entry_link = rewrite_relative_url($site_url, clean($item->get_link()));
+ $entry_link = UrlHelper::rewrite_relative($site_url, clean($item->get_link()), "a", "href");
$entry_language = mb_substr(trim($item->get_language()), 0, 2);
diff --git a/classes/sanitizer.php b/classes/sanitizer.php
index 0a444a296..3f6e9504e 100644
--- a/classes/sanitizer.php
+++ b/classes/sanitizer.php
@@ -68,7 +68,7 @@ class Sanitizer {
// $rewrite_base_url = $site_url ? $site_url : Config::get_self_url();
$rewrite_base_url = $site_url ? $site_url : "http://domain.invalid/";
- $entries = $xpath->query('(//a[@href]|//img[@src]|//source[@srcset|@src])');
+ $entries = $xpath->query('(//a[@href]|//img[@src]|//source[@srcset|@src]|//video[@poster])');
foreach ($entries as $entry) {
@@ -100,6 +100,11 @@ class Sanitizer {
$entry->setAttribute("srcset", RSSUtils::encode_srcset($matches));
}
+ if ($entry->hasAttribute('poster')) {
+ $entry->setAttribute('poster',
+ UrlHelper::rewrite_relative($rewrite_base_url, $entry->getAttribute('poster'), $entry->tagName, "poster"));
+ }
+
if ($entry->hasAttribute('src') &&
($owner && get_pref(Prefs::STRIP_IMAGES, $owner)) || $force_remove_images || ($_SESSION["bw_limit"] ?? false)) {
diff --git a/classes/urlhelper.php b/classes/urlhelper.php
index b2c1331b6..0e4834b72 100644
--- a/classes/urlhelper.php
+++ b/classes/urlhelper.php
@@ -52,7 +52,7 @@ class UrlHelper {
$owner_attribute == "href") {
return $rel_url;
// allow limited subset of inline base64-encoded images for IMG elements
- } else if ($rel_parts["scheme"] ?? "" == "data" &&
+ } else if (($rel_parts["scheme"] ?? "") == "data" &&
preg_match('%^image/(webp|gif|jpg|png|svg);base64,%', $rel_parts["path"]) &&
$owner_element == "img" &&
$owner_attribute == "src") {
@@ -281,8 +281,7 @@ class UrlHelper {
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
- curl_setopt($ch, CURLOPT_USERAGENT, $useragent ? $useragent :
- SELF_USER_AGENT);
+ curl_setopt($ch, CURLOPT_USERAGENT, $useragent ? $useragent : Config::get_user_agent());
curl_setopt($ch, CURLOPT_ENCODING, "");
if ($http_referrer)