diff options
Diffstat (limited to 'classes/feeds.php')
-rwxr-xr-x | classes/feeds.php | 444 |
1 files changed, 310 insertions, 134 deletions
diff --git a/classes/feeds.php b/classes/feeds.php index 42673ca95..a06486883 100755 --- a/classes/feeds.php +++ b/classes/feeds.php @@ -5,20 +5,25 @@ class Feeds extends Handler_Protected { const NEVER_GROUP_FEEDS = [ -6, 0 ]; const NEVER_GROUP_BY_DATE = [ -2, -1, -3 ]; - private $params; + /** @var int|float int on 64-bit, float on 32-bit */ + private $viewfeed_timestamp; - private $viewfeed_timestamp; - private $viewfeed_timestamp_last; + /** @var int|float int on 64-bit, float on 32-bit */ + private $viewfeed_timestamp_last; - function csrf_ignore($method) { + function csrf_ignore(string $method): bool { $csrf_ignored = array("index"); return array_search($method, $csrf_ignored) !== false; } - private function _format_headlines_list($feed, $method, $view_mode, $limit, $cat_view, - $offset, $override_order = false, $include_children = false, $check_first_id = false, - $skip_first_id_check = false, $order_by = false) { + /** + * @param string|int $feed + * @return array{0: array<int, int>, 1: int, 2: int, 3: bool, 4: array<string, mixed>} $topmost_article_ids, $headlines_count, $feed, $disable_cache, $reply + */ + private function _format_headlines_list($feed, string $method, string $view_mode, int $limit, bool $cat_view, + int $offset, string $override_order, bool $include_children, ?int $check_first_id = null, + ?bool $skip_first_id_check = false, ? string $order_by = ''): array { $disable_cache = false; @@ -65,6 +70,8 @@ class Feeds extends Handler_Protected { $qfh_ret = []; if (!$cat_view && is_numeric($feed) && $feed < PLUGIN_FEED_BASE_INDEX && $feed > LABEL_BASE_INDEX) { + + /** @var IVirtualFeed|false $handler */ $handler = PluginHost::getInstance()->get_feed_handler( PluginHost::feed_to_pfeed_id($feed)); @@ -126,7 +133,7 @@ class Feeds extends Handler_Protected { $reply['vfeed_group_enabled'] = $vfeed_group_enabled; $plugin_menu_items = ""; - PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_HEADLINE_TOOLBAR_SELECT_MENU_ITEM, + PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_HEADLINE_TOOLBAR_SELECT_MENU_ITEM2, function ($result) use (&$plugin_menu_items) { $plugin_menu_items .= $result; }, @@ -188,7 +195,11 @@ class Feeds extends Handler_Protected { // frontend doesn't expect pdo returning booleans as strings on mysql if (Config::get(Config::DB_TYPE) == "mysql") { foreach (["unread", "marked", "published"] as $k) { - $line[$k] = $line[$k] === "1"; + if (is_integer($line[$k])) { + $line[$k] = $line[$k] === 1; + } else { + $line[$k] = $line[$k] === "1"; + } } } @@ -230,24 +241,58 @@ 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); + } + } else if ($result) { + user_error(get_class($plugin) . + " plugin: content provided in HOOK_ARTICLE_LEFT_BUTTON is not valid XML: " . + Errors::libxml_last_error() . " $result", E_USER_WARNING); + } }, $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); + } + } else if ($result) { + user_error(get_class($plugin) . + " plugin: content provided in HOOK_ARTICLE_BUTTON is not valid XML: " . + Errors::libxml_last_error() . " $result", E_USER_WARNING); + } }, $line); $this->_mark_timestamp(" pre-sanitize"); $line["content"] = Sanitizer::sanitize($line["content"], - $line['hide_images'], false, $line["site_url"], $highlight_words, $line["id"]); + $line['hide_images'], null, $line["site_url"], $highlight_words, $line["id"]); $this->_mark_timestamp(" sanitize"); @@ -265,9 +310,9 @@ class Feeds extends Handler_Protected { if ($line["num_enclosures"] > 0) { $line["enclosures"] = Article::_format_enclosures($id, - $line["always_display_enclosures"], + sql_bool_to_bool($line["always_display_enclosures"]), $line["content"], - $line["hide_images"]); + sql_bool_to_bool($line["hide_images"])); } else { $line["enclosures"] = [ 'formatted' => '', 'entries' => [] ]; } @@ -275,7 +320,7 @@ class Feeds extends Handler_Protected { $this->_mark_timestamp(" enclosures"); $line["updated_long"] = TimeHelper::make_local_datetime($line["updated"],true); - $line["updated"] = TimeHelper::make_local_datetime($line["updated"], false, false, false, true); + $line["updated"] = TimeHelper::make_local_datetime($line["updated"], false, null, false, true); $line['imported'] = T_sprintf("Imported at %s", TimeHelper::make_local_datetime($line["date_entered"], false)); @@ -409,7 +454,7 @@ class Feeds extends Handler_Protected { return array($topmost_article_ids, $headlines_count, $feed, $disable_cache, $reply); } - function catchupAll() { + function catchupAll(): void { $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET last_read = NOW(), unread = false WHERE unread = true AND owner_uid = ?"); $sth->execute([$_SESSION['uid']]); @@ -417,16 +462,16 @@ class Feeds extends Handler_Protected { print json_encode(array("message" => "UPDATE_COUNTERS")); } - function view() { + function view(): void { $reply = array(); $feed = $_REQUEST["feed"]; $method = $_REQUEST["m"] ?? ""; $view_mode = $_REQUEST["view_mode"] ?? ""; $limit = 30; - $cat_view = $_REQUEST["cat"] == "true"; + $cat_view = self::_param_to_bool($_REQUEST["cat"] ?? false); $next_unread_feed = $_REQUEST["nuf"] ?? 0; - $offset = $_REQUEST["skip"] ?? 0; + $offset = (int) ($_REQUEST["skip"] ?? 0); $order_by = $_REQUEST["order_by"] ?? ""; $check_first_id = $_REQUEST["fid"] ?? 0; @@ -514,7 +559,10 @@ class Feeds extends Handler_Protected { print json_encode($reply); } - private function _generate_dashboard_feed() { + /** + * @return array<string, array<string, mixed>> + */ + private function _generate_dashboard_feed(): array { $reply = array(); $reply['headlines']['id'] = -5; @@ -556,7 +604,10 @@ class Feeds extends Handler_Protected { return $reply; } - private function _generate_error_feed($error) { + /** + * @return array<string, mixed> + */ + private function _generate_error_feed(string $error): array { $reply = array(); $reply['headlines']['id'] = -7; @@ -572,13 +623,13 @@ class Feeds extends Handler_Protected { return $reply; } - function subscribeToFeed() { + function subscribeToFeed(): void { print json_encode([ "cat_select" => \Controls\select_feeds_cats("cat") ]); } - function search() { + function search(): void { print json_encode([ "show_language" => Config::get(Config::DB_TYPE) == "pgsql", "show_syntax_help" => count(PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH)) == 0, @@ -587,10 +638,31 @@ class Feeds extends Handler_Protected { ]); } - function updatedebugger() { + function opensite(): void { + $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(): void { header("Content-type: text/html"); - $xdebug = isset($_REQUEST["xdebug"]) ? (int)$_REQUEST["xdebug"] : 1; + $xdebug = isset($_REQUEST["xdebug"]) ? (int)$_REQUEST["xdebug"] : Debug::LOG_VERBOSE; + + if (!in_array($xdebug, Debug::ALL_LOG_LEVELS)) { + $xdebug = Debug::LOG_VERBOSE; + } Debug::set_enabled(true); Debug::set_loglevel($xdebug); @@ -603,9 +675,9 @@ class Feeds extends Handler_Protected { $sth->execute([$feed_id, $_SESSION['uid']]); if (!$sth->fetch()) { - print "Access denied."; - return; - } + print "Access denied."; + return; + } ?> <!DOCTYPE html> <html> @@ -658,7 +730,7 @@ class Feeds extends Handler_Protected { <fieldset> <label> <?= \Controls\select_hash("xdebug", $xdebug, - [Debug::$LOG_VERBOSE => "LOG_VERBOSE", Debug::$LOG_EXTENDED => "LOG_EXTENDED"]); + [Debug::LOG_VERBOSE => "LOG_VERBOSE", Debug::LOG_EXTENDED => "LOG_EXTENDED"]); ?></label> </fieldset> @@ -690,7 +762,10 @@ class Feeds extends Handler_Protected { } - static function _catchup($feed, $cat_view, $owner_uid = false, $mode = 'all', $search = false) { + /** + * @param array<int, string> $search + */ + static function _catchup(string $feed_id_or_tag_name, bool $cat_view, ?int $owner_uid = null, string $mode = 'all', ?array $search = null): void { if (!$owner_uid) $owner_uid = $_SESSION['uid']; @@ -744,14 +819,16 @@ class Feeds extends Handler_Protected { $date_qpart = "true"; } - if (is_numeric($feed)) { + if (is_numeric($feed_id_or_tag_name)) { + $feed_id = (int) $feed_id_or_tag_name; + if ($cat_view) { - if ($feed >= 0) { + if ($feed_id >= 0) { - if ($feed > 0) { - $children = self::_get_child_cats($feed, $owner_uid); - array_push($children, $feed); + if ($feed_id > 0) { + $children = self::_get_child_cats($feed_id, $owner_uid); + array_push($children, $feed_id); $children = array_map("intval", $children); $children = join(",", $children); @@ -769,7 +846,7 @@ class Feeds extends Handler_Protected { (SELECT id FROM ttrss_feeds WHERE $cat_qpart) AND $date_qpart AND $search_qpart) as tmp)"); $sth->execute([$owner_uid]); - } else if ($feed == -2) { + } else if ($feed_id == -2) { $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false,last_read = NOW() WHERE (SELECT COUNT(*) @@ -778,18 +855,18 @@ class Feeds extends Handler_Protected { $sth->execute([$owner_uid]); } - } else if ($feed > 0) { + } else if ($feed_id > 0) { $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false, last_read = NOW() WHERE ref_id IN (SELECT id FROM (SELECT DISTINCT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id AND owner_uid = ? AND unread = true AND feed_id = ? AND $date_qpart AND $search_qpart) as tmp)"); - $sth->execute([$owner_uid, $feed]); + $sth->execute([$owner_uid, $feed_id]); - } else if ($feed < 0 && $feed > LABEL_BASE_INDEX) { // special, like starred + } else if ($feed_id < 0 && $feed_id > LABEL_BASE_INDEX) { // special, like starred - if ($feed == -1) { + if ($feed_id == -1) { $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false, last_read = NOW() WHERE ref_id IN (SELECT id FROM @@ -798,7 +875,7 @@ class Feeds extends Handler_Protected { $sth->execute([$owner_uid]); } - if ($feed == -2) { + if ($feed_id == -2) { $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false, last_read = NOW() WHERE ref_id IN (SELECT id FROM @@ -807,7 +884,7 @@ class Feeds extends Handler_Protected { $sth->execute([$owner_uid]); } - if ($feed == -3) { + if ($feed_id == -3) { $intl = (int) get_pref(Prefs::FRESH_ARTICLE_MAX_AGE); @@ -826,7 +903,7 @@ class Feeds extends Handler_Protected { $sth->execute([$owner_uid]); } - if ($feed == -4) { + if ($feed_id == -4) { $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false, last_read = NOW() WHERE ref_id IN (SELECT id FROM @@ -834,10 +911,9 @@ class Feeds extends Handler_Protected { AND owner_uid = ? AND unread = true AND $date_qpart AND $search_qpart) as tmp)"); $sth->execute([$owner_uid]); } + } else if ($feed_id < LABEL_BASE_INDEX) { // label - } else if ($feed < LABEL_BASE_INDEX) { // label - - $label_id = Labels::feed_to_label_id($feed); + $label_id = Labels::feed_to_label_id($feed_id); $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false, last_read = NOW() WHERE ref_id IN @@ -846,23 +922,29 @@ class Feeds extends Handler_Protected { AND label_id = ? AND ref_id = article_id AND owner_uid = ? AND unread = true AND $date_qpart AND $search_qpart) as tmp)"); $sth->execute([$label_id, $owner_uid]); - } - } else { // tag + $tag_name = $feed_id_or_tag_name; + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false, last_read = NOW() WHERE ref_id IN (SELECT id FROM (SELECT DISTINCT ttrss_entries.id FROM ttrss_entries, ttrss_user_entries, ttrss_tags WHERE ref_id = ttrss_entries.id AND post_int_id = int_id AND tag_name = ? AND ttrss_user_entries.owner_uid = ? AND unread = true AND $date_qpart AND $search_qpart) as tmp)"); - $sth->execute([$feed, $owner_uid]); - + $sth->execute([$tag_name, $owner_uid]); } } - static function _get_counters($feed, $is_cat = false, $unread_only = false, - $owner_uid = false) { + /** + * @param int|string $feed feed id or tag name + * @param bool $is_cat + * @param bool $unread_only + * @param null|int $owner_uid + * @return int + * @throws PDOException + */ + static function _get_counters($feed, bool $is_cat = false, bool $unread_only = false, ?int $owner_uid = null): int { $n_feed = (int) $feed; $need_entries = false; @@ -883,6 +965,7 @@ class Feeds extends Handler_Protected { return self::_get_cat_unread($n_feed, $owner_uid); } else if ($n_feed == -6) { return 0; + // tags } else if ($feed != "0" && $n_feed == 0) { $sth = $pdo->prepare("SELECT SUM((SELECT COUNT(int_id) @@ -893,7 +976,8 @@ class Feeds extends Handler_Protected { $sth->execute([$owner_uid, $feed]); $row = $sth->fetch(); - return $row["count"]; + // Handle 'SUM()' returning null if there are no results + return $row["count"] ?? 0; } else if ($n_feed == -1) { $match_part = "marked = true"; @@ -961,9 +1045,9 @@ class Feeds extends Handler_Protected { } } - function add() { + function add(): void { $feed = clean($_REQUEST['feed']); - $cat = clean($_REQUEST['cat'] ?? ''); + $cat = (int) clean($_REQUEST['cat'] ?? ''); $need_auth = isset($_REQUEST['need_auth']); $login = $need_auth ? clean($_REQUEST['login']) : ''; $pass = $need_auth ? clean($_REQUEST['pass']) : ''; @@ -974,7 +1058,7 @@ class Feeds extends Handler_Protected { } /** - * @return array (code => Status code, message => error message if available) + * @return array<string, mixed> (code => Status code, message => error message if available) * * 0 - OK, Feed already exists * 1 - OK, Feed added @@ -986,9 +1070,15 @@ class Feeds extends Handler_Protected { * 5 - Couldn't download the URL content. * 6 - Content is an invalid XML. * 7 - Error while creating feed database entry. + * 8 - Permission denied (ACCESS_LEVEL_READONLY). */ - static function _subscribe($url, $cat_id = 0, - $auth_login = '', $auth_pass = '') : array { + static function _subscribe(string $url, int $cat_id = 0, string $auth_login = '', string $auth_pass = ''): array { + + $user = ORM::for_table("ttrss_users")->find_one($_SESSION['uid']); + + if ($user && $user->access_level == UserHelper::ACCESS_LEVEL_READONLY) { + return ["code" => 8]; + } $pdo = Db::pdo(); @@ -996,6 +1086,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, @@ -1054,15 +1151,18 @@ class Feeds extends Handler_Protected { } } - static function _get_icon_file($feed_id) { + static function _get_icon_file(int $feed_id): string { return Config::get(Config::ICONS_DIR) . "/$feed_id.ico"; } - static function _has_icon($id) { + static function _has_icon(int $id): bool { return is_file(Config::get(Config::ICONS_DIR) . "/$id.ico") && filesize(Config::get(Config::ICONS_DIR) . "/$id.ico") > 0; } - static function _get_icon($id) { + /** + * @return false|string false if the icon ID was unrecognized, otherwise, the icon identifier string + */ + static function _get_icon(int $id) { switch ($id) { case 0: return "archive"; @@ -1082,7 +1182,7 @@ class Feeds extends Handler_Protected { } else { $icon = self::_get_icon_file($id); - if ($icon && file_exists($icon)) { + if ($icon && file_exists($icon)) { return Config::get(Config::ICONS_URL) . "/" . basename($icon) . "?" . filemtime($icon); } } @@ -1092,6 +1192,9 @@ class Feeds extends Handler_Protected { return false; } + /** + * @return false|int false if the feed couldn't be found by URL+owner, otherwise the feed ID + */ static function _find_by_url(string $feed_url, int $owner_uid) { $feed = ORM::for_table('ttrss_feeds') ->where('owner_uid', $owner_uid) @@ -1105,8 +1208,39 @@ class Feeds extends Handler_Protected { } } - static function _get_title($id, bool $cat = false) { - $pdo = Db::pdo(); + /** + * $owner_uid defaults to $_SESSION['uid'] + * + * @return false|int false if the category/feed couldn't be found by title, otherwise its ID + */ + 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; + } + } + + /** + * @param string|int $id + */ + static function _get_title($id, bool $cat = false): string { + $pdo = Db::pdo(); if ($cat) { return self::_get_cat_title($id); @@ -1118,7 +1252,7 @@ class Feeds extends Handler_Protected { return __("Fresh articles"); } else if ($id == -4) { return __("All articles"); - } else if ($id === 0 || $id === "0") { + } else if ($id === 0) { return __("Archived articles"); } else if ($id == -6) { return __("Recently read"); @@ -1147,12 +1281,12 @@ class Feeds extends Handler_Protected { } } else { - return $id; + return "$id"; } } // only real cats - static function _get_cat_marked(int $cat, int $owner_uid = 0) { + static function _get_cat_marked(int $cat, int $owner_uid = 0): int { if (!$owner_uid) $owner_uid = $_SESSION["uid"]; @@ -1166,16 +1300,17 @@ class Feeds extends Handler_Protected { WHERE (cat_id = :cat OR (:cat IS NULL AND cat_id IS NULL)) AND owner_uid = :uid) AND owner_uid = :uid"); + $sth->execute(["cat" => $cat ? $cat : null, "uid" => $owner_uid]); - $row = $sth->fetch(); - return $row["marked"]; - } else { - return 0; + if ($row = $sth->fetch()) { + return (int) $row["marked"]; + } } + return 0; } - static function _get_cat_unread(int $cat, int $owner_uid = 0) { + static function _get_cat_unread(int $cat, int $owner_uid = 0): int { if (!$owner_uid) $owner_uid = $_SESSION["uid"]; @@ -1186,14 +1321,15 @@ class Feeds extends Handler_Protected { $sth = $pdo->prepare("SELECT SUM(CASE WHEN unread THEN 1 ELSE 0 END) AS unread FROM ttrss_user_entries WHERE feed_id IN (SELECT id FROM ttrss_feeds - WHERE (cat_id = :cat OR (:cat IS NULL AND cat_id IS NULL)) + WHERE (cat_id = :cat OR (:cat IS NULL AND cat_id IS NULL)) AND owner_uid = :uid) AND owner_uid = :uid"); - $sth->execute(["cat" => $cat ? $cat : null, "uid" => $owner_uid]); - $row = $sth->fetch(); - return $row["unread"]; + $sth->execute(["cat" => $cat ? $cat : null, "uid" => $owner_uid]); + if ($row = $sth->fetch()) { + return (int) $row["unread"]; + } } else if ($cat == -1) { return 0; } else if ($cat == -2) { @@ -1201,15 +1337,19 @@ class Feeds extends Handler_Protected { $sth = $pdo->prepare("SELECT COUNT(DISTINCT article_id) AS unread FROM ttrss_user_entries ue, ttrss_user_labels2 l WHERE article_id = ref_id AND unread IS true AND ue.owner_uid = :uid"); + $sth->execute(["uid" => $owner_uid]); - $row = $sth->fetch(); - return $row["unread"]; + if ($row = $sth->fetch()) { + return (int) $row["unread"]; + } } + + return 0; } // only accepts real cats (>= 0) - static function _get_cat_children_unread(int $cat, int $owner_uid = 0) { + static function _get_cat_children_unread(int $cat, int $owner_uid = 0): int { if (!$owner_uid) $owner_uid = $_SESSION["uid"]; $pdo = Db::pdo(); @@ -1228,7 +1368,7 @@ class Feeds extends Handler_Protected { return $unread; } - static function _get_global_unread(int $user_id = 0) { + static function _get_global_unread(int $user_id = 0): int { if (!$user_id) $user_id = $_SESSION["uid"]; @@ -1241,10 +1381,11 @@ class Feeds extends Handler_Protected { $sth->execute([$user_id]); $row = $sth->fetch(); - return $row["count"]; + // Handle 'SUM()' returning null if there are no articles/results (e.g. admin user with no feeds) + return $row["count"] ?? 0; } - static function _get_cat_title(int $cat_id) { + static function _get_cat_title(int $cat_id): string { switch ($cat_id) { case 0: return __("Uncategorized"); @@ -1264,7 +1405,7 @@ class Feeds extends Handler_Protected { } } - private static function _get_label_unread($label_id, int $owner_uid = 0) { + private static function _get_label_unread(int $label_id, ?int $owner_uid = null): int { if (!$owner_uid) $owner_uid = $_SESSION["uid"]; $pdo = Db::pdo(); @@ -1281,7 +1422,11 @@ class Feeds extends Handler_Protected { } } - static function _get_headlines($params) { + /** + * @param array<string, mixed> $params + * @return array<int, mixed> $result, $feed_title, $feed_site_url, $last_error, $last_updated, $highlight_words, $first_id, $is_vfeed, $query_error_override + */ + static function _get_headlines($params): array { $pdo = Db::pdo(); @@ -1362,7 +1507,7 @@ class Feeds extends Handler_Protected { $view_query_part = " "; } else if ($feed != -1) { - $unread = getFeedUnread($feed, $cat_view); + $unread = Feeds::_get_counters($feed, $cat_view, true); if ($cat_view && $feed > 0 && $include_children) $unread += self::_get_cat_children_unread($feed); @@ -1498,7 +1643,7 @@ class Feeds extends Handler_Protected { } else if ($feed <= LABEL_BASE_INDEX) { // labels $label_id = Labels::feed_to_label_id($feed); - $query_strategy_part = "label_id = ".$pdo->quote($label_id)." AND + $query_strategy_part = "label_id = $label_id AND ttrss_labels2.id = ttrss_user_labels2.label_id AND ttrss_user_labels2.article_id = ref_id"; @@ -1778,7 +1923,10 @@ class Feeds extends Handler_Protected { } - static function _get_parent_cats(int $cat, int $owner_uid) { + /** + * @return array<int, int> + */ + static function _get_parent_cats(int $cat, int $owner_uid): array { $rv = array(); $pdo = Db::pdo(); @@ -1795,7 +1943,10 @@ class Feeds extends Handler_Protected { return $rv; } - static function _get_child_cats(int $cat, int $owner_uid) { + /** + * @return array<int, int> + */ + static function _get_child_cats(int $cat, int $owner_uid): array { $rv = array(); $pdo = Db::pdo(); @@ -1812,7 +1963,11 @@ class Feeds extends Handler_Protected { return $rv; } - static function _cats_of(array $feeds, int $owner_uid, bool $with_parents = false) { + /** + * @param array<int, int> $feeds + * @return array<int, int> + */ + static function _cats_of(array $feeds, int $owner_uid, bool $with_parents = false): array { if (count($feeds) == 0) return []; @@ -1851,24 +2006,27 @@ class Feeds extends Handler_Protected { } } - private function _color_of($name) { - $colormap = [ "#1cd7d7","#d91111","#1212d7","#8e16e5","#7b7b7b", - "#39f110","#0bbea6","#ec0e0e","#1534f2","#b9e416", - "#479af2","#f36b14","#10c7e9","#1e8fe7","#e22727" ]; + private function _color_of(string $name): string { + $colormap = [ "#1cd7d7","#d91111","#1212d7","#8e16e5","#7b7b7b", + "#39f110","#0bbea6","#ec0e0e","#1534f2","#b9e416", + "#479af2","#f36b14","#10c7e9","#1e8fe7","#e22727" ]; - $sum = 0; + $sum = 0; - for ($i = 0; $i < strlen($name); $i++) { - $sum += ord($name[$i]); - } + for ($i = 0; $i < strlen($name); $i++) { + $sum += ord($name[$i]); + } - $sum %= count($colormap); + $sum %= count($colormap); - return $colormap[$sum]; + return $colormap[$sum]; } - private static function _get_feeds_from_html($url, $content) { - $url = UrlHelper::validate($url); + /** + * @return array<string, string> array of feed URL -> feed title + */ + private static function _get_feeds_from_html(string $url, string $content): array { + $url = UrlHelper::validate($url); $baseUrl = substr($url, 0, strrpos($url, '/') + 1); $feedUrls = []; @@ -1885,9 +2043,7 @@ class Feeds extends Handler_Protected { if ($title == '') { $title = $entry->getAttribute('type'); } - $feedUrl = rewrite_relative_url( - $baseUrl, $entry->getAttribute('href') - ); + $feedUrl = UrlHelper::rewrite_relative($baseUrl, $entry->getAttribute('href')); $feedUrls[$feedUrl] = $title; } } @@ -1895,11 +2051,11 @@ class Feeds extends Handler_Protected { return $feedUrls; } - static function _is_html($content) { + static function _is_html(string $content): bool { return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 8192)) !== 0; } - static function _remove_cat(int $id, int $owner_uid) { + static function _remove_cat(int $id, int $owner_uid): void { $cat = ORM::for_table('ttrss_feed_categories') ->where('owner_uid', $owner_uid) ->find_one($id); @@ -1908,7 +2064,7 @@ class Feeds extends Handler_Protected { $cat->delete(); } - static function _add_cat(string $title, int $owner_uid, int $parent_cat = null, int $order_id = 0) { + static function _add_cat(string $title, int $owner_uid, int $parent_cat = null, int $order_id = 0): bool { $cat = ORM::for_table('ttrss_feed_categories') ->where('owner_uid', $owner_uid) @@ -1932,13 +2088,18 @@ class Feeds extends Handler_Protected { return false; } - static function _clear_access_keys(int $owner_uid) { + static function _clear_access_keys(int $owner_uid): void { $key = ORM::for_table('ttrss_access_keys') ->where('owner_uid', $owner_uid) ->delete_many(); } - static function _update_access_key(string $feed_id, bool $is_cat, int $owner_uid) { + /** + * @param string $feed_id may be a feed ID or tag + * + * @see Handler_Public#generate_syndicated_feed() + */ + static function _update_access_key(string $feed_id, bool $is_cat, int $owner_uid): ?string { $key = ORM::for_table('ttrss_access_keys') ->where('owner_uid', $owner_uid) ->where('feed_id', $feed_id) @@ -1948,7 +2109,12 @@ class Feeds extends Handler_Protected { return self::_get_access_key($feed_id, $is_cat, $owner_uid); } - static function _get_access_key(string $feed_id, bool $is_cat, int $owner_uid) { + /** + * @param string $feed_id may be a feed ID or tag + * + * @see Handler_Public#generate_syndicated_feed() + */ + static function _get_access_key(string $feed_id, bool $is_cat, int $owner_uid): ?string { $key = ORM::for_table('ttrss_access_keys') ->where('owner_uid', $owner_uid) ->where('feed_id', $feed_id) @@ -1957,21 +2123,23 @@ class Feeds extends Handler_Protected { if ($key) { return $key->access_key; - } else { - $key = ORM::for_table('ttrss_access_keys')->create(); + } - $key->owner_uid = $owner_uid; - $key->feed_id = $feed_id; - $key->is_cat = $is_cat; - $key->access_key = uniqid_short(); + $key = ORM::for_table('ttrss_access_keys')->create(); - if ($key->save()) { - return $key->access_key; - } + $key->owner_uid = $owner_uid; + $key->feed_id = $feed_id; + $key->is_cat = $is_cat; + $key->access_key = uniqid_short(); + + if ($key->save()) { + return $key->access_key; } + + return null; } - static function _purge(int $feed_id, int $purge_interval) { + static function _purge(int $feed_id, int $purge_interval): ?int { if (!$purge_interval) $purge_interval = self::_get_purge_interval($feed_id); @@ -1987,7 +2155,7 @@ class Feeds extends Handler_Protected { $owner_uid = $row["owner_uid"]; if (Config::get(Config::FORCE_ARTICLE_PURGE) != 0) { - Debug::log("purge_feed: FORCE_ARTICLE_PURGE is set, overriding interval to " . Config::get(Config::FORCE_ARTICLE_PURGE), Debug::$LOG_VERBOSE); + Debug::log("purge_feed: FORCE_ARTICLE_PURGE is set, overriding interval to " . Config::get(Config::FORCE_ARTICLE_PURGE), Debug::LOG_VERBOSE); $purge_unread = true; $purge_interval = Config::get(Config::FORCE_ARTICLE_PURGE); } else { @@ -1996,11 +2164,11 @@ class Feeds extends Handler_Protected { $purge_interval = (int) $purge_interval; - Debug::log("purge_feed: interval $purge_interval days for feed $feed_id, owner: $owner_uid, purge unread: $purge_unread", Debug::$LOG_VERBOSE); + Debug::log("purge_feed: interval $purge_interval days for feed $feed_id, owner: $owner_uid, purge unread: $purge_unread", Debug::LOG_VERBOSE); if ($purge_interval <= 0) { - Debug::log("purge_feed: purging disabled for this feed, nothing to do.", Debug::$LOG_VERBOSE); - return; + Debug::log("purge_feed: purging disabled for this feed, nothing to do.", Debug::LOG_VERBOSE); + return null; } if (!$purge_unread) @@ -2032,16 +2200,16 @@ class Feeds extends Handler_Protected { $rows_deleted = $sth->rowCount(); - Debug::log("purge_feed: deleted $rows_deleted articles.", Debug::$LOG_VERBOSE); + Debug::log("purge_feed: deleted $rows_deleted articles.", Debug::LOG_VERBOSE); } else { - Debug::log("purge_feed: owner of $feed_id not found", Debug::$LOG_VERBOSE); + Debug::log("purge_feed: owner of $feed_id not found", Debug::LOG_VERBOSE); } return $rows_deleted; } - private static function _get_purge_interval(int $feed_id) { + private static function _get_purge_interval(int $feed_id): int { $feed = ORM::for_table('ttrss_feeds')->find_one($feed_id); if ($feed) { @@ -2054,7 +2222,10 @@ class Feeds extends Handler_Protected { } } - private static function _search_to_sql($search, $search_language, $owner_uid) { + /** + * @return array{0: string, 1: array<int, string>} [$search_query_part, $search_words] + */ + private static function _search_to_sql(string $search, string $search_language, int $owner_uid): array { $keywords = str_getcsv(preg_replace('/(-?\w+)\:"(\w+)/', '"${1}:${2}', trim($search)), ' '); $query_keywords = array(); $search_words = array(); @@ -2147,7 +2318,7 @@ class Feeds extends Handler_Protected { array_push($query_keywords, "($not (ttrss_entries.id IN ( SELECT article_id FROM ttrss_user_labels2 WHERE - label_id = ".$pdo->quote($label_id).")))"); + label_id = $label_id)))"); } else { array_push($query_keywords, "(false)"); } @@ -2221,7 +2392,10 @@ class Feeds extends Handler_Protected { return array($search_query_part, $search_words); } - static function _order_to_override_query($order) { + /** + * @return array{0: string, 1: bool} + */ + static function _order_to_override_query(string $order): array { $query = ""; $skip_first_id = false; @@ -2231,7 +2405,9 @@ class Feeds extends Handler_Protected { }, $order); - if ($query) return [$query, $skip_first_id]; + if (is_string($query) && $query !== "") { + return [$query, $skip_first_id]; + } switch ($order) { case "title": @@ -2249,7 +2425,7 @@ class Feeds extends Handler_Protected { return [$query, $skip_first_id]; } - private function _mark_timestamp($label) { + private function _mark_timestamp(string $label): void { if (empty($_REQUEST['timestamps'])) return; |