X-Git-Url: https://git.wh0rd.org/?a=blobdiff_plain;f=classes%2Ffeeds.php;h=2bd9b0e6573f7afc0b6c3d4551b287bd262fdf56;hb=b9585004e68fdd7718e042083a4d9fb2dc351e0c;hp=6b96d8364d914c13f6e31e31009f31869c86b64a;hpb=ba2853caac636d2ae596d74561fa0233567242d4;p=tt-rss.git diff --git a/classes/feeds.php b/classes/feeds.php index 6b96d836..2bd9b0e6 100755 --- a/classes/feeds.php +++ b/classes/feeds.php @@ -13,7 +13,7 @@ class Feeds extends Handler_Protected { private function format_headline_subtoolbar($feed_site_url, $feed_title, $feed_id, $is_cat, $search, - $view_mode, $error, $feed_last_updated) { + $error, $feed_last_updated) { $catchup_sel_link = "catchupSelection()"; @@ -39,7 +39,7 @@ class Feeds extends Handler_Protected { $search_q = ""; } - $reply .= ""; + $reply = ""; $rss_link = htmlspecialchars(get_self_url_prefix() . "/public.php?op=rss&id=$feed_id$cat_q$search_q"); @@ -64,7 +64,7 @@ class Feeds extends Handler_Protected { $target = "target=\"_blank\""; $reply .= "". - truncate_string($feed_title, 30).""; + truncate_string(strip_tags($feed_title), 30).""; if ($error) { $error = htmlspecialchars($error); @@ -72,7 +72,7 @@ class Feeds extends Handler_Protected { } } else { - $reply .= $feed_title; + $reply .= strip_tags($feed_title); } $reply .= ""; @@ -153,7 +153,7 @@ class Feeds extends Handler_Protected { } private function format_headlines_list($feed, $method, $view_mode, $limit, $cat_view, - $next_unread_feed, $offset, $vgr_last_feed = false, + $offset, $vgr_last_feed = false, $override_order = false, $include_children = false, $check_first_id = false, $skip_first_id_check = false) { @@ -173,65 +173,29 @@ class Feeds extends Handler_Protected { $method_split = explode(":", $method); if ($method == "ForceUpdate" && $feed > 0 && is_numeric($feed)) { - // Update the feed if required with some basic flood control - - $any_needs_curl = false; - - if (ini_get("open_basedir")) { - $pluginhost = PluginHost::getInstance(); - foreach ($pluginhost->get_plugins() as $plugin) { - $flags = $plugin->flags(); - - if (isset($flags["needs_curl"]) && $flags["needs_curl"]) { - $any_needs_curl = true; - break; - } - } - } - - //if ($_REQUEST["debug"]) print ""; - - if (!$any_needs_curl) { - - $result = $this->dbh->query( - "SELECT cache_images," . SUBSTRING_FOR_DATE . "(last_updated,1,19) AS last_updated - FROM ttrss_feeds WHERE id = '$feed'"); - - if ($this->dbh->num_rows($result) != 0) { - $last_updated = strtotime($this->dbh->fetch_result($result, 0, "last_updated")); - $cache_images = sql_bool_to_bool($this->dbh->fetch_result($result, 0, "cache_images")); - - if (!$cache_images && time() - $last_updated > 120) { - include "rssfuncs.php"; - update_rss_feed($feed, true, true); - } else { - $this->dbh->query("UPDATE ttrss_feeds SET last_updated = '1970-01-01', last_update_started = '1970-01-01' - WHERE id = '$feed'"); - } - } - } else { - $this->dbh->query("UPDATE ttrss_feeds SET last_updated = '1970-01-01', last_update_started = '1970-01-01' - WHERE id = '$feed'"); - } + $sth = $this->pdo->prepare("UPDATE ttrss_feeds + SET last_updated = '1970-01-01', last_update_started = '1970-01-01' + WHERE id = ?"); + $sth->execute([$feed]); } if ($method_split[0] == "MarkAllReadGR") { - catchup_feed($method_split[1], false); + $this->catchup_feed($method_split[1], false); } // FIXME: might break tag display? if (is_numeric($feed) && $feed > 0 && !$cat_view) { - $result = $this->dbh->query( - "SELECT id FROM ttrss_feeds WHERE id = '$feed' LIMIT 1"); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE id = ? LIMIT 1"); + $sth->execute([$feed]); - if ($this->dbh->num_rows($result) == 0) { + if (!$sth->fetch()) { $reply['content'] = "
".__('Feed not found.')."
"; } } - @$search = $this->dbh->escape_string($_REQUEST["query"]); - @$search_language = $this->dbh->escape_string($_REQUEST["search_language"]); // PGSQL only + @$search = $_REQUEST["query"]; + @$search_language = $_REQUEST["search_language"]; // PGSQL only if ($search) { $disable_cache = true; @@ -239,7 +203,6 @@ class Feeds extends Handler_Protected { if ($_REQUEST["debug"]) $timing_info = print_checkpoint("H0", $timing_info); - if (!$cat_view && is_numeric($feed) && $feed < PLUGIN_FEED_BASE_INDEX && $feed > LABEL_BASE_INDEX) { $handler = PluginHost::getInstance()->get_feed_handler( PluginHost::feed_to_pfeed_id($feed)); @@ -262,14 +225,6 @@ class Feeds extends Handler_Protected { } } else { - /*$qfh_ret = queryFeedHeadlines($feed, $limit, $view_mode, $cat_view, - $search, false, $override_order, $offset, 0, - false, 0, $include_children, $topid);*/ - - //function queryFeedHeadlines($feed, $limit, - // $view_mode, $cat_view, $search, $search_mode, - // $override_order = false, $offset = 0, $owner_uid = 0, $filter = false, $since_id = 0, $include_children = false, - // $ignore_vfeed_group = false, $override_strategy = false, $override_vfeed = false, $start_ts = false, $check_top_id = false) { $params = array( "feed" => $feed, @@ -285,14 +240,14 @@ class Feeds extends Handler_Protected { "skip_first_id_check" => $skip_first_id_check ); - $qfh_ret = queryFeedHeadlines($params); + $qfh_ret = $this->queryFeedHeadlines($params); } $vfeed_group_enabled = get_pref("VFEED_GROUP_BY_FEED") && $feed != -6; if ($_REQUEST["debug"]) $timing_info = print_checkpoint("H1", $timing_info); - $result = $qfh_ret[0]; + $result = $qfh_ret[0]; // this could be either a PDO query result or a -1 if first id changed $feed_title = $qfh_ret[1]; $feed_site_url = $qfh_ret[2]; $last_error = $qfh_ret[3]; @@ -300,16 +255,15 @@ class Feeds extends Handler_Protected { make_local_datetime($qfh_ret[4], false) : __("Never"); $highlight_words = $qfh_ret[5]; $reply['first_id'] = $qfh_ret[6]; + $reply['search_query'] = [$search, $search_language]; $vgroup_last_feed = $vgr_last_feed; $reply['toolbar'] = $this->format_headline_subtoolbar($feed_site_url, $feed_title, - $feed, $cat_view, $search, $view_mode, + $feed, $cat_view, $search, $last_error, $last_updated); - $headlines_count = is_numeric($result) ? 0 : $this->dbh->num_rows($result); - if ($offset == 0) { foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HEADLINES_BEFORE) as $p) { $reply['content'] .= $p->hook_headlines_before($feed, $cat_view, $qfh_ret); @@ -318,18 +272,17 @@ class Feeds extends Handler_Protected { $reply['content'] = ''; - if ($headlines_count > 0) { - - $lnum = $offset; + $headlines_count = 0; - $num_unread = 0; - $cur_feed_title = ''; + $lnum = $offset; + $num_unread = 0; + if ($_REQUEST["debug"]) $timing_info = print_checkpoint("PS", $timing_info); - if ($_REQUEST["debug"]) $timing_info = print_checkpoint("PS", $timing_info); + if (is_object($result)) { - $expand_cdm = get_pref('CDM_EXPANDED'); + while ($line = $result->fetch()) { - while ($line = $this->dbh->fetch_assoc($result)) { + ++$headlines_count; $line["content_preview"] = "— " . truncate_string(strip_tags($line["content"]), 250); @@ -346,6 +299,8 @@ class Feeds extends Handler_Protected { $label_cache = $line["label_cache"]; $labels = false; + $mouseover_attrs = "onmouseover='postMouseIn(event, $id)' onmouseout='postMouseOut($id)'"; + if ($label_cache) { $label_cache = json_decode($label_cache, true); @@ -357,10 +312,10 @@ class Feeds extends Handler_Protected { } } - if (!is_array($labels)) $labels = get_article_labels($id); + if (!is_array($labels)) $labels = Article::get_article_labels($id); $labels_str = ""; - $labels_str .= format_article_labels($labels, $id); + $labels_str .= Article::format_article_labels($labels); $labels_str .= ""; if (count($topmost_article_ids) < 3) { @@ -369,45 +324,18 @@ class Feeds extends Handler_Protected { $class = ""; - if (sql_bool_to_bool($line["unread"])) { + if ($line["unread"]) { $class .= " Unread"; ++$num_unread; } - if (sql_bool_to_bool($line["marked"])) { - $marked_pic = "\"Unstar"; - $class .= " marked"; - } else { - $marked_pic = "\"Star"; - } - - if (sql_bool_to_bool($line["published"])) { - $published_pic = "\"Unpublish"; - $class .= " published"; - } else { - $published_pic = "\"Publish"; - } - -# $content_link = "" . -# $line["title"] . ""; + $marked_pic_src = $line["marked"] ? "mark_set.png" : "mark_unset.png"; + $class .= $line["marked"] ? " marked" : ""; + $marked_pic = ""; -# $content_link = "" . -# $line["title"] . ""; - -# $content_link = "" . -# $line["title"] . ""; + $published_pic_src = $line["published"] ? "pub_set.png" : "pub_unset.png"; + $class .= $line["published"] ? " published" : ""; + $published_pic = ""; $updated_fmt = make_local_datetime($line["updated"], false, false, false, true); $date_entered_fmt = T_sprintf("Imported at %s", @@ -417,12 +345,8 @@ class Feeds extends Handler_Protected { $score_pic = "images/" . get_score_pic($score); -/* $score_title = __("(Click to change)"); - $score_pic = ""; */ - $score_pic = ""; + title=\"$score\">"; if ($score > 500) { $hlc_suffix = "high"; @@ -438,7 +362,7 @@ class Feeds extends Handler_Protected { $entry_author = " — $entry_author"; } - $has_feed_icon = feed_has_icon($feed_id); + $has_feed_icon = feeds::feedHasIcon($feed_id); if ($has_feed_icon) { $feed_icon_img = "\"\""; @@ -464,33 +388,27 @@ class Feeds extends Handler_Protected { if ($vfeed_group_enabled) { if ($feed_id != $vgroup_last_feed && $line["feed_title"]) { - $cur_feed_title = $line["feed_title"]; $vgroup_last_feed = $feed_id; - $cur_feed_title = htmlspecialchars($cur_feed_title); - $vf_catchup_link = "".__('mark feed as read').""; $reply['content'] .= "
". "
$feed_icon_img
". "". $line["feed_title"]." - $vf_catchup_link
"; + $vf_catchup_link"; } } - $mouseover_attrs = "onmouseover='postMouseIn(event, $id)' - onmouseout='postMouseOut($id)'"; - - $reply['content'] .= "
"; + $reply['content'] .= "
"; $reply['content'] .= "
"; $reply['content'] .= ""; + type=\"checkbox\" onclick=\"toggleSelectRow2(this)\" + class='rchk'>"; $reply['content'] .= "$marked_pic"; $reply['content'] .= "$published_pic"; @@ -498,14 +416,14 @@ class Feeds extends Handler_Protected { $reply['content'] .= "
"; $reply['content'] .= "
"; + class=\"hlTitle\">"; $reply['content'] .= "" . + href=\"" . htmlspecialchars($line["link"]) . "\" + onclick=\"\">" . truncate_string($line["title"], 200); if (get_pref('SHOW_CONTENT_PREVIEW')) { - $reply['content'] .= "" . $line["content_preview"] . ""; + $reply['content'] .= "" . $line["content_preview"] . ""; } $reply['content'] .= ""; @@ -527,7 +445,7 @@ class Feeds extends Handler_Protected { $reply['content'] .= ""; $reply['content'] .= "
$updated_fmt
-
"; +
"; $reply['content'] .= "
"; @@ -536,9 +454,9 @@ class Feeds extends Handler_Protected { if ($line["feed_title"] && !$vfeed_group_enabled) { $reply['content'] .= " - $feed_icon_img"; + style=\"cursor : pointer\" + title=\"".htmlspecialchars($line['feed_title'])."\"> + $feed_icon_img"; } $reply['content'] .= "
"; @@ -552,29 +470,23 @@ class Feeds extends Handler_Protected { $tags = false; $line["content"] = sanitize($line["content"], - sql_bool_to_bool($line['hide_images']), false, $entry_site_url, $highlight_words, $line["id"]); + $line['hide_images'], false, $entry_site_url, $highlight_words, $line["id"]); foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_CDM) as $p) { $line = $p->hook_render_article_cdm($line); } + $line['content'] = rewrite_cached_urls($line['content']); + if ($vfeed_group_enabled && $line["feed_title"]) { if ($feed_id != $vgroup_last_feed) { - $cur_feed_title = $line["feed_title"]; $vgroup_last_feed = $feed_id; - $cur_feed_title = htmlspecialchars($cur_feed_title); - $vf_catchup_link = "".__('mark feed as read').""; - $has_feed_icon = feed_has_icon($feed_id); - - if ($has_feed_icon) { - $feed_icon_img = "\"\""; - } else { - //$feed_icon_img = "\"\""; - } + $feed_icon_src = Feeds::getFeedIcon($feed_id); + $feed_icon_img = ""; $reply['content'] .= "
". "
$feed_icon_img
". @@ -584,157 +496,136 @@ class Feeds extends Handler_Protected { } } - $mouseover_attrs = "onmouseover='postMouseIn(event, $id)' - onmouseout='postMouseOut($id)'"; + $content_encoded = htmlspecialchars($line["content"]); - $expanded_class = $expand_cdm ? "expanded" : "expandable"; + $tmp_content = "
"; - $reply['content'] .= "
"; + $tmp_content .= "
"; + $tmp_content .= "
"; - $reply['content'] .= "
"; - $reply['content'] .= "
"; + $tmp_content .= ""; - $reply['content'] .= ""; + $tmp_content .= "$marked_pic"; + $tmp_content .= "$published_pic"; - $reply['content'] .= "$marked_pic"; - $reply['content'] .= "$published_pic"; + $tmp_content .= "
"; - $reply['content'] .= "
"; - - if ($highlight_words && count($highlight_words > 0)) { + if ($highlight_words && count($highlight_words) > 0) { foreach ($highlight_words as $word) { - $line["title"] = preg_replace("/(\Q$word\E)/i", + $word = preg_quote($word, "/"); + + $line["title"] = preg_replace("/($word)/i", "$1", $line["title"]); } } - $reply['content'] .= " - + ". $line["title"] . " $entry_author"; - $reply['content'] .= $labels_str; + $tmp_content .= $labels_str; - $reply['content'] .= ""; - - if (!$expand_cdm) - $content_hidden = "style=\"display : none\""; - else - $excerpt_hidden = "style=\"display : none\""; - - $reply['content'] .= "" . $content_preview . ""; - - $reply['content'] .= ""; + $tmp_content .= ""; if (!$vfeed_group_enabled) { if (@$line["feed_title"]) { $rgba = @$rgba_cache[$feed_id]; - $reply['content'] .= ""; } } - $reply['content'] .= " - $updated_fmt"; + $tmp_content .= "$updated_fmt"; - $reply['content'] .= "
"; - $reply['content'] .= "$score_pic"; + $tmp_content .= "
"; + $tmp_content .= "$score_pic"; if (!get_pref("VFEED_GROUP_BY_FEED") && $line["feed_title"]) { - $reply['content'] .= "$feed_icon_img"; + $tmp_content .= "$feed_icon_img"; } - $reply['content'] .= "
"; + $tmp_content .= "
"; //scoreWrap - $reply['content'] .= "
"; + $tmp_content .= "
"; //cdmHeader - $reply['content'] .= "
"; + $tmp_content .= "
"; - $reply['content'] .= "
"; + $tmp_content .= "
"; if ($line['note']) { - $reply['content'] .= format_article_note($id, $line['note']); + $tmp_content .= Article::format_article_note($id, $line['note']); } - $reply['content'] .= "
"; + $tmp_content .= "
"; //POSTNOTE if (!$line['lang']) $line['lang'] = 'en'; - $reply['content'] .= "
"; + // this is filled from RROW data-content + $tmp_content .= "
"; - if ($line["orig_feed_id"]) { + if ($line["orig_feed_id"]) { - $tmp_result = $this->dbh->query("SELECT * FROM ttrss_archived_feeds - WHERE id = ".$line["orig_feed_id"] . " AND owner_uid = " . $_SESSION["uid"]); + $ofgh = $this->pdo->prepare("SELECT * FROM ttrss_archived_feeds + WHERE id = ? AND owner_uid = ?"); + $ofgh->execute([$line["orig_feed_id"], $_SESSION['uid']]); - if ($this->dbh->num_rows($tmp_result) != 0) { + if ($tmp_line = $ofgh->fetch()) { - $reply['content'] .= "
"; - $reply['content'] .= __("Originally from:"); + $tmp_content .= "
"; + $tmp_content .= __("Originally from:"); - $reply['content'] .= " "; + $tmp_content .= " "; - $tmp_line = $this->dbh->fetch_assoc($tmp_result); - - $reply['content'] .= "" . + $tmp_content .= "" . $tmp_line['title'] . ""; - $reply['content'] .= " "; + $tmp_content .= " "; - $reply['content'] .= ""; - $reply['content'] .= ""; + $tmp_content .= ""; + $tmp_content .= ""; - $reply['content'] .= "
"; + $tmp_content .= "
"; } } - $reply['content'] .= ""; - - $reply['content'] .= ""; - $reply['content'] .= htmlspecialchars($line["content"]); - $reply['content'] .= ""; - - $reply['content'] .= ""; - - $reply['content'] .= "
"; - - $reply['content'] .= "
"; + $tmp_content .= "
"; //cdmContentInner + $tmp_content .= "
"; - $always_display_enclosures = sql_bool_to_bool($line["always_display_enclosures"]); - $reply['content'] .= format_article_enclosures($id, $always_display_enclosures, $line["content"], sql_bool_to_bool($line["hide_images"])); + $always_display_enclosures = $line["always_display_enclosures"]; + $tmp_content .= Article::format_article_enclosures($id, $always_display_enclosures, + $line["content"], $line["hide_images"]); - $reply['content'] .= "
"; + $tmp_content .= "
"; // cdmIntermediate - $reply['content'] .= "
"; + $tmp_content .= "
"; foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) { - $reply['content'] .= $p->hook_article_left_button($line); + $tmp_content .= $p->hook_article_left_button($line); } - $tags_str = format_tags_string($tags, $id); + $tags_str = Article::format_tags_string($tags, $id); - $reply['content'] .= ""; + $tmp_content .= ""; - $reply['content'] .= "Tags - $tags_str - (+)"; + $tmp_content .= "Tags + $tags_str + (+)"; $num_comments = (int) $line["num_comments"]; $entry_comments = ""; @@ -746,7 +637,7 @@ class Feeds extends Handler_Protected { $comments_url = htmlspecialchars($line["link"]); } $entry_comments = "$num_comments ". + target='_blank' rel='noopener noreferrer' href=\"$comments_url\">$num_comments ". _ngettext("comment", "comments", $num_comments).""; } else { @@ -755,81 +646,88 @@ class Feeds extends Handler_Protected { } } - if ($entry_comments) $reply['content'] .= " ($entry_comments)"; - - $reply['content'] .= ""; - $reply['content'] .= "
"; + if ($entry_comments) $tmp_content .= " ($entry_comments)"; -// $reply['content'] .= "$marked_pic"; -// $reply['content'] .= "$published_pic"; + $tmp_content .= ""; + $tmp_content .= "
"; foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) { - $reply['content'] .= $p->hook_article_button($line); + $tmp_content .= $p->hook_article_button($line); } - $reply['content'] .= "
"; - $reply['content'] .= "
"; + $tmp_content .= "
"; // buttons - $reply['content'] .= "
"; + $tmp_content .= "
"; // cdmFooter + $tmp_content .= "
"; // cdmContent + $tmp_content .= "
"; // RROW.cdm - $reply['content'] .= "
"; + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ARTICLE_CDM) as $p) { + $tmp_content = $p->hook_format_article_cdm($tmp_content, $line); + } + $reply['content'] .= $tmp_content; } ++$lnum; } + } - if ($_REQUEST["debug"]) $timing_info = print_checkpoint("PE", $timing_info); - - } else if (!is_numeric($result)) { - $message = ""; - - switch ($view_mode) { - case "unread": - $message = __("No unread articles found to display."); - break; - case "updated": - $message = __("No updated articles found to display."); - break; - case "marked": - $message = __("No starred articles found to display."); - break; - default: - if ($feed < LABEL_BASE_INDEX) { - $message = __("No articles found to display. You can assign articles to labels manually from article header context menu (applies to all selected articles) or use a filter."); - } else { - $message = __("No articles found to display."); - } - } + if ($_REQUEST["debug"]) $timing_info = print_checkpoint("PE", $timing_info); + + if (!$headlines_count) { + + if (!is_numeric($result)) { + + switch ($view_mode) { + case "unread": + $message = __("No unread articles found to display."); + break; + case "updated": + $message = __("No updated articles found to display."); + break; + case "marked": + $message = __("No starred articles found to display."); + break; + default: + if ($feed < LABEL_BASE_INDEX) { + $message = __("No articles found to display. You can assign articles to labels manually from article header context menu (applies to all selected articles) or use a filter."); + } else { + $message = __("No articles found to display."); + } + } - if (!$offset && $message) { - $reply['content'] = "
$message"; + if (!$offset && $message) { + $reply['content'] = "
$message"; - $reply['content'] .= "

"; + $reply['content'] .= "

"; - $result = $this->dbh->query("SELECT ".SUBSTRING_FOR_DATE."(MAX(last_updated), 1, 19) AS last_updated FROM ttrss_feeds - WHERE owner_uid = " . $_SESSION['uid']); + $sth = $this->pdo->prepare("SELECT " . SUBSTRING_FOR_DATE . "(MAX(last_updated), 1, 19) AS last_updated FROM ttrss_feeds + WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + $row = $sth->fetch(); - $last_updated = $this->dbh->fetch_result($result, 0, "last_updated"); - $last_updated = make_local_datetime($last_updated, false); + $last_updated = make_local_datetime($row["last_updated"], false); - $reply['content'] .= sprintf(__("Feeds last updated at %s"), $last_updated); + $reply['content'] .= sprintf(__("Feeds last updated at %s"), $last_updated); - $result = $this->dbh->query("SELECT COUNT(id) AS num_errors - FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ".$_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT COUNT(id) AS num_errors + FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + $row = $sth->fetch(); - $num_errors = $this->dbh->fetch_result($result, 0, "num_errors"); + $num_errors = $row["num_errors"]; - if ($num_errors > 0) { - $reply['content'] .= "
"; - $reply['content'] .= "". - __('Some feeds have update errors (click for details)').""; - } - $reply['content'] .= "

"; + if ($num_errors > 0) { + $reply['content'] .= "
"; + $reply['content'] .= "" . + __('Some feeds have update errors (click for details)') . ""; + } + $reply['content'] .= "

"; + } + } else if (is_numeric($result) && $result == -1) { + $reply['first_id_changed'] = true; } - } else if (is_numeric($result) && $result == -1) { - $reply['first_id_changed'] = true; } if ($_REQUEST["debug"]) $timing_info = print_checkpoint("H2", $timing_info); @@ -839,9 +737,11 @@ class Feeds extends Handler_Protected { } function catchupAll() { - $this->dbh->query("UPDATE ttrss_user_entries SET - last_read = NOW(), unread = false WHERE unread = true AND owner_uid = " . $_SESSION["uid"]); - ccache_zero_all($_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET + last_read = NOW(), unread = false WHERE unread = true AND owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + + CCache::zero_all($_SESSION["uid"]); } function view() { @@ -851,16 +751,16 @@ class Feeds extends Handler_Protected { if ($_REQUEST["debug"]) $timing_info = print_checkpoint("0", $timing_info); - $feed = $this->dbh->escape_string($_REQUEST["feed"]); - $method = $this->dbh->escape_string($_REQUEST["m"]); - $view_mode = $this->dbh->escape_string($_REQUEST["view_mode"]); + $feed = $_REQUEST["feed"]; + $method = $_REQUEST["m"]; + $view_mode = $_REQUEST["view_mode"]; $limit = 30; @$cat_view = $_REQUEST["cat"] == "true"; - @$next_unread_feed = $this->dbh->escape_string($_REQUEST["nuf"]); - @$offset = $this->dbh->escape_string($_REQUEST["skip"]); - @$vgroup_last_feed = $this->dbh->escape_string($_REQUEST["vgrlf"]); - $order_by = $this->dbh->escape_string($_REQUEST["order_by"]); - $check_first_id = $this->dbh->escape_string($_REQUEST["fid"]); + @$next_unread_feed = $_REQUEST["nuf"]; + @$offset = $_REQUEST["skip"]; + @$vgroup_last_feed = $_REQUEST["vgrlf"]; + $order_by = $_REQUEST["order_by"]; + $check_first_id = $_REQUEST["fid"]; if (is_numeric($feed)) $feed = (int) $feed; @@ -872,21 +772,30 @@ class Feeds extends Handler_Protected { return; } - $result = false; - + $sth = false; if ($feed < LABEL_BASE_INDEX) { - $label_feed = feed_to_label_id($feed); - $result = $this->dbh->query("SELECT id FROM ttrss_labels2 WHERE - id = '$label_feed' AND owner_uid = " . $_SESSION['uid']); + + $label_feed = Labels::feed_to_label_id($feed); + + $sth = $this->pdo->prepare("SELECT id FROM ttrss_labels2 WHERE + id = ? AND owner_uid = ?"); + $sth->execute([$label_feed, $_SESSION['uid']]); + } else if (!$cat_view && is_numeric($feed) && $feed > 0) { - $result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE - id = '$feed' AND owner_uid = " . $_SESSION['uid']); + + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE + id = ? AND owner_uid = ?"); + $sth->execute([$feed, $_SESSION['uid']]); + } else if ($cat_view && is_numeric($feed) && $feed > 0) { - $result = $this->dbh->query("SELECT id FROM ttrss_feed_categories WHERE - id = '$feed' AND owner_uid = " . $_SESSION['uid']); + + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feed_categories WHERE + id = ? AND owner_uid = ?"); + + $sth->execute([$feed, $_SESSION['uid']]); } - if ($result && $this->dbh->num_rows($result) == 0) { + if ($sth && !$sth->fetch()) { print json_encode($this->generate_error_feed(__("Feed not found."))); return; } @@ -895,7 +804,7 @@ class Feeds extends Handler_Protected { * so for performance reasons we don't do that here */ if ($feed >= 0) { - ccache_update($feed, $_SESSION["uid"], $cat_view); + CCache::update($feed, $_SESSION["uid"], $cat_view); } set_pref("_DEFAULT_VIEW_MODE", $view_mode); @@ -903,14 +812,16 @@ class Feeds extends Handler_Protected { /* bump login timestamp if needed */ if (time() - $_SESSION["last_login_update"] > 3600) { - $this->dbh->query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " . - $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_users SET last_login = NOW() WHERE id = ?"); + $sth->execute([$_SESSION['uid']]); + $_SESSION["last_login_update"] = time(); } if (!$cat_view && is_numeric($feed) && $feed > 0) { - $this->dbh->query("UPDATE ttrss_feeds SET last_viewed = NOW() - WHERE id = '$feed' AND owner_uid = ".$_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET last_viewed = NOW() + WHERE id = ? AND owner_uid = ?"); + $sth->execute([$feed, $_SESSION['uid']]); } $reply['headlines'] = array(); @@ -920,7 +831,7 @@ class Feeds extends Handler_Protected { switch ($order_by) { case "title": - $override_order = "ttrss_entries.title"; + $override_order = "ttrss_entries.title, date_entered, updated"; break; case "date_reverse": $override_order = "score DESC, date_entered, updated"; @@ -934,7 +845,7 @@ class Feeds extends Handler_Protected { if ($_REQUEST["debug"]) $timing_info = print_checkpoint("04", $timing_info); $ret = $this->format_headlines_list($feed, $method, - $view_mode, $limit, $cat_view, $next_unread_feed, $offset, + $view_mode, $limit, $cat_view, $offset, $vgroup_last_feed, $override_order, true, $check_first_id, $skip_first_id_check); //$topmost_article_ids = $ret[0]; @@ -981,18 +892,21 @@ class Feeds extends Handler_Protected { $reply['headlines']['content'] .= "

"; - $result = $this->dbh->query("SELECT ".SUBSTRING_FOR_DATE."(MAX(last_updated), 1, 19) AS last_updated FROM ttrss_feeds - WHERE owner_uid = " . $_SESSION['uid']); + $sth = $this->pdo->prepare("SELECT ".SUBSTRING_FOR_DATE."(MAX(last_updated), 1, 19) AS last_updated FROM ttrss_feeds + WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + $row = $sth->fetch(); - $last_updated = $this->dbh->fetch_result($result, 0, "last_updated"); - $last_updated = make_local_datetime($last_updated, false); + $last_updated = make_local_datetime($row["last_updated"], false); $reply['headlines']['content'] .= sprintf(__("Feeds last updated at %s"), $last_updated); - $result = $this->dbh->query("SELECT COUNT(id) AS num_errors - FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ".$_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT COUNT(id) AS num_errors + FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + $row = $sth->fetch(); - $num_errors = $this->dbh->fetch_result($result, 0, "num_errors"); + $num_errors = $row["num_errors"]; if ($num_errors > 0) { $reply['headlines']['content'] .= "
"; @@ -1027,6 +941,8 @@ class Feeds extends Handler_Protected { } function quickAddFeed() { + print "

"; + print_hidden("op", "rpc"); print_hidden("method", "addfeed"); @@ -1091,10 +1007,8 @@ class Feeds extends Handler_Protected {
"; - print ""; - print "
- "; + "; if (!(defined('_DISABLE_FEED_BROWSER') && _DISABLE_FEED_BROWSER)) { print ""; @@ -1103,13 +1017,15 @@ class Feeds extends Handler_Protected { print "
"; + print ""; + //return; } function feedBrowser() { if (defined('_DISABLE_FEED_BROWSER') && _DISABLE_FEED_BROWSER) return; - $browser_search = $this->dbh->escape_string($_REQUEST["search"]); + $browser_search = $_REQUEST["search"]; print_hidden("op", "rpc"); print_hidden("method", "updateFeedBrowser"); @@ -1155,11 +1071,13 @@ class Feeds extends Handler_Protected { } function search() { - $this->params = explode(":", $this->dbh->escape_string($_REQUEST["param"]), 2); + $this->params = explode(":", $_REQUEST["param"], 2); $active_feed_id = sprintf("%d", $this->params[0]); $is_cat = $this->params[1] != "false"; + print "
"; + print "
".__('Look for')."
"; print "
"; @@ -1168,7 +1086,7 @@ class Feeds extends Handler_Protected { style=\"font-size : 16px; width : 20em;\" required=\"1\" name=\"query\" type=\"search\" value=''>"; - print "
".T_sprintf('in %s', getFeedTitle($active_feed_id, $is_cat)).""; + print "
".T_sprintf('in %s', $this->getFeedTitle($active_feed_id, $is_cat)).""; if (DB_TYPE == "pgsql") { print "
"; @@ -1186,29 +1104,42 @@ class Feeds extends Handler_Protected {
"; } - print " + print "
"; + + print ""; } function update_debugger() { header("Content-type: text/html"); + Debug::set_enabled(true); + Debug::set_loglevel($_REQUEST["xdebug"]); + $feed_id = (int)$_REQUEST["feed_id"]; @$do_update = $_REQUEST["action"] == "do_update"; $csrf_token = $_REQUEST["csrf_token"]; + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE id = ? AND owner_uid = ?"); + $sth->execute([$feed_id, $_SESSION['uid']]); + + if (!$sth->fetch()) { + print "Access denied."; + return; + } + $refetch_checked = isset($_REQUEST["force_refetch"]) ? "checked" : ""; $rehash_checked = isset($_REQUEST["force_rehash"]) ? "checked" : ""; ?> - + Feed Debugger - -

Feed Debugger:

+ +

Feed Debugger: getFeedTitle($feed_id) ?>

@@ -1227,8 +1158,7 @@ class Feeds extends Handler_Protected {
@@ -1238,5 +1168,1028 @@ class Feeds extends Handler_Protected { = 0) { + + if ($feed > 0) { + $children = Feeds::getChildCategories($feed, $owner_uid); + array_push($children, $feed); + $children = array_map("intval", $children); + + $children = join(",", $children); + + $cat_qpart = "cat_id IN ($children)"; + } else { + $cat_qpart = "cat_id IS NULL"; + } + + $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 IN + (SELECT id FROM ttrss_feeds WHERE $cat_qpart) AND $date_qpart AND $search_qpart) as tmp)"); + $sth->execute([$owner_uid]); + + } else if ($feed == -2) { + + $sth = $pdo->prepare("UPDATE ttrss_user_entries + SET unread = false,last_read = NOW() WHERE (SELECT COUNT(*) + FROM ttrss_user_labels2, ttrss_entries WHERE article_id = ref_id AND id = ref_id AND $date_qpart AND $search_qpart) > 0 + AND unread = true AND owner_uid = ?"); + $sth->execute([$owner_uid]); + } + + } else if ($feed > 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]); + + } else if ($feed < 0 && $feed > LABEL_BASE_INDEX) { // special, like starred + + if ($feed == -1) { + $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 marked = true AND $date_qpart AND $search_qpart) as tmp)"); + $sth->execute([$owner_uid]); + } + + if ($feed == -2) { + $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 published = true AND $date_qpart AND $search_qpart) as tmp)"); + $sth->execute([$owner_uid]); + } + + if ($feed == -3) { + + $intl = (int) get_pref("FRESH_ARTICLE_MAX_AGE"); + + if (DB_TYPE == "pgsql") { + $match_part = "date_entered > NOW() - INTERVAL '$intl hour' "; + } else { + $match_part = "date_entered > DATE_SUB(NOW(), + INTERVAL $intl HOUR) "; + } + + $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 score >= 0 AND unread = true AND $date_qpart AND $match_part AND $search_qpart) as tmp)"); + $sth->execute([$owner_uid]); + } + + if ($feed == -4) { + $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 $date_qpart AND $search_qpart) as tmp)"); + $sth->execute([$owner_uid]); + } + + } else if ($feed < LABEL_BASE_INDEX) { // label + + $label_id = Labels::feed_to_label_id($feed); + + $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_user_labels2 WHERE ref_id = id + 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]); + + } + + CCache::update($feed, $owner_uid, $cat_view); + + } else { // tag + $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]); + + } + } + + static function getFeedArticles($feed, $is_cat = false, $unread_only = false, + $owner_uid = false) { + + $n_feed = (int) $feed; + $need_entries = false; + + $pdo = Db::pdo(); + + if (!$owner_uid) $owner_uid = $_SESSION["uid"]; + + if ($unread_only) { + $unread_qpart = "unread = true"; + } else { + $unread_qpart = "true"; + } + + $match_part = ""; + + if ($is_cat) { + return Feeds::getCategoryUnread($n_feed, $owner_uid); + } else if ($n_feed == -6) { + return 0; + } else if ($feed != "0" && $n_feed == 0) { + + $sth = $pdo->prepare("SELECT SUM((SELECT COUNT(int_id) + FROM ttrss_user_entries,ttrss_entries WHERE int_id = post_int_id + AND ref_id = id AND $unread_qpart)) AS count FROM ttrss_tags + WHERE owner_uid = ? AND tag_name = ?"); + + $sth->execute([$owner_uid, $feed]); + $row = $sth->fetch(); + + return $row["count"]; + + } else if ($n_feed == -1) { + $match_part = "marked = true"; + } else if ($n_feed == -2) { + $match_part = "published = true"; + } else if ($n_feed == -3) { + $match_part = "unread = true AND score >= 0"; + + $intl = (int) get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid); + + if (DB_TYPE == "pgsql") { + $match_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' "; + } else { + $match_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) "; + } + + $need_entries = true; + + } else if ($n_feed == -4) { + $match_part = "true"; + } else if ($n_feed >= 0) { + + if ($n_feed != 0) { + $match_part = "feed_id = " . (int)$n_feed; + } else { + $match_part = "feed_id IS NULL"; + } + + } else if ($feed < LABEL_BASE_INDEX) { + + $label_id = Labels::feed_to_label_id($feed); + + return Feeds::getLabelUnread($label_id, $owner_uid); + } + + if ($match_part) { + + if ($need_entries) { + $from_qpart = "ttrss_user_entries,ttrss_entries"; + $from_where = "ttrss_entries.id = ttrss_user_entries.ref_id AND"; + } else { + $from_qpart = "ttrss_user_entries"; + $from_where = ""; + } + + $sth = $pdo->prepare("SELECT count(int_id) AS unread + FROM $from_qpart WHERE + $unread_qpart AND $from_where ($match_part) AND ttrss_user_entries.owner_uid = ?"); + $sth->execute([$owner_uid]); + $row = $sth->fetch(); + + return $row["unread"]; + + } else { + + $sth = $pdo->prepare("SELECT COUNT(post_int_id) AS unread + FROM ttrss_tags,ttrss_user_entries,ttrss_entries + WHERE tag_name = ? AND post_int_id = int_id AND ref_id = ttrss_entries.id + AND $unread_qpart AND ttrss_tags.owner_uid = ,"); + + $sth->execute([$feed, $owner_uid]); + $row = $sth->fetch(); + + return $row["unread"]; + } + } + + /** + * @return array (code => Status code, message => error message if available) + * + * 0 - OK, Feed already exists + * 1 - OK, Feed added + * 2 - Invalid URL + * 3 - URL content is HTML, no feeds available + * 4 - URL content is HTML which contains multiple feeds. + * Here you should call extractfeedurls in rpc-backend + * to get all possible feeds. + * 5 - Couldn't download the URL content. + * 6 - Content is an invalid XML. + */ + static function subscribe_to_feed($url, $cat_id = 0, + $auth_login = '', $auth_pass = '') { + + global $fetch_last_error; + global $fetch_last_error_content; + + $pdo = Db::pdo(); + + $url = fix_url($url); + + if (!$url || !validate_feed_url($url)) return array("code" => 2); + + $contents = @fetch_file_contents($url, false, $auth_login, $auth_pass); + + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SUBSCRIBE_FEED) as $plugin) { + $contents = $plugin->hook_subscribe_feed($contents, $url, $auth_login, $auth_pass); + } + + if (!$contents) { + if (preg_match("/cloudflare\.com/", $fetch_last_error_content)) { + $fetch_last_error .= " (feed behind Cloudflare)"; + } + + return array("code" => 5, "message" => $fetch_last_error); + } + + if (is_html($contents)) { + $feedUrls = get_feeds_from_html($url, $contents); + + if (count($feedUrls) == 0) { + return array("code" => 3); + } else if (count($feedUrls) > 1) { + return array("code" => 4, "feeds" => $feedUrls); + } + //use feed url as new URL + $url = key($feedUrls); + } + + if (!$cat_id) $cat_id = null; + + $sth = $pdo->prepare("SELECT id FROM ttrss_feeds + WHERE feed_url = ? AND owner_uid = ?"); + $sth->execute([$url, $_SESSION['uid']]); + + if ($row = $sth->fetch()) { + return array("code" => 0, "feed_id" => (int) $row["id"]); + } else { + $sth = $pdo->prepare( + "INSERT INTO ttrss_feeds + (owner_uid,feed_url,title,cat_id, auth_login,auth_pass,update_method,auth_pass_encrypted) + VALUES (?, ?, ?, ?, ?, ?, 0, false)"); + + $sth->execute([$_SESSION['uid'], $url, "[Unknown]", $cat_id, (string)$auth_login, (string)$auth_pass]); + + $sth = $pdo->prepare("SELECT id FROM ttrss_feeds WHERE feed_url = ? + AND owner_uid = ?"); + $sth->execute([$url, $_SESSION['uid']]); + $row = $sth->fetch(); + + $feed_id = $row["id"]; + + if ($feed_id) { + RSSUtils::set_basic_feed_info($feed_id); + } + + return array("code" => 1, "feed_id" => (int) $feed_id); + + } + } + + static function getIconFile($feed_id) { + return ICONS_DIR . "/$feed_id.ico"; + } + + static function feedHasIcon($id) { + return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0; + } + + static function getFeedIcon($id) { + switch ($id) { + case 0: + return "images/archive.png"; + break; + case -1: + return "images/star.png"; + break; + case -2: + return "images/feed.png"; + break; + case -3: + return "images/fresh.png"; + break; + case -4: + return "images/folder.png"; + break; + case -6: + return "images/time.png"; + break; + default: + if ($id < LABEL_BASE_INDEX) { + return "images/label.png"; + } else { + $icon = self::getIconFile($id); + + if ($icon && file_exists($icon)) { + return ICONS_URL . "/" . basename($icon) . "?" . filemtime($icon); + } + } + break; + } + + return false; + } + + static function getFeedTitle($id, $cat = false) { + $pdo = Db::pdo(); + + if ($cat) { + return Feeds::getCategoryTitle($id); + } else if ($id == -1) { + return __("Starred articles"); + } else if ($id == -2) { + return __("Published articles"); + } else if ($id == -3) { + return __("Fresh articles"); + } else if ($id == -4) { + return __("All articles"); + } else if ($id === 0 || $id === "0") { + return __("Archived articles"); + } else if ($id == -6) { + return __("Recently read"); + } else if ($id < LABEL_BASE_INDEX) { + + $label_id = Labels::feed_to_label_id($id); + + $sth = $pdo->prepare("SELECT caption FROM ttrss_labels2 WHERE id = ?"); + $sth->execute([$label_id]); + + if ($row = $sth->fetch()) { + return $row["caption"]; + } else { + return "Unknown label ($label_id)"; + } + + } else if (is_numeric($id) && $id > 0) { + + $sth = $pdo->prepare("SELECT title FROM ttrss_feeds WHERE id = ?"); + $sth->execute([$id]); + + if ($row = $sth->fetch()) { + return $row["title"]; + } else { + return "Unknown feed ($id)"; + } + + } else { + return $id; + } + } + + static function getCategoryUnread($cat, $owner_uid = false) { + + if (!$owner_uid) $owner_uid = $_SESSION["uid"]; + + $pdo = Db::pdo(); + + if ($cat >= 0) { + + if (!$cat) $cat = null; + + $sth = $pdo->prepare("SELECT id FROM ttrss_feeds + WHERE (cat_id = :cat OR (:cat IS NULL AND cat_id IS NULL)) + AND owner_uid = :uid"); + + $sth->execute([":cat" => $cat, ":uid" => $owner_uid]); + + $cat_feeds = array(); + while ($line = $sth->fetch()) { + array_push($cat_feeds, "feed_id = " . (int)$line["id"]); + } + + if (count($cat_feeds) == 0) return 0; + + $match_part = implode(" OR ", $cat_feeds); + + $sth = $pdo->prepare("SELECT COUNT(int_id) AS unread + FROM ttrss_user_entries + WHERE unread = true AND ($match_part) + AND owner_uid = ?"); + $sth->execute([$owner_uid]); + + $unread = 0; + + # this needs to be rewritten + while ($line = $sth->fetch()) { + $unread += $line["unread"]; + } + + return $unread; + } else if ($cat == -1) { + return getFeedUnread(-1) + getFeedUnread(-2) + getFeedUnread(-3) + getFeedUnread(0); + } else if ($cat == -2) { + + $sth = $pdo->prepare("SELECT COUNT(unread) AS unread FROM + ttrss_user_entries, ttrss_user_labels2 + WHERE article_id = ref_id AND unread = true + AND ttrss_user_entries.owner_uid = ?"); + $sth->execute([$owner_uid]); + $row = $sth->fetch(); + + return $row["unread"]; + } + } + + // only accepts real cats (>= 0) + static function getCategoryChildrenUnread($cat, $owner_uid = false) { + if (!$owner_uid) $owner_uid = $_SESSION["uid"]; + + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT id FROM ttrss_feed_categories WHERE parent_cat = ? + AND owner_uid = ?"); + $sth->execute([$cat, $owner_uid]); + + $unread = 0; + + while ($line = $sth->fetch()) { + $unread += Feeds::getCategoryUnread($line["id"], $owner_uid); + $unread += Feeds::getCategoryChildrenUnread($line["id"], $owner_uid); + } + + return $unread; + } + + static function getGlobalUnread($user_id = false) { + + if (!$user_id) $user_id = $_SESSION["uid"]; + + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT SUM(value) AS c_id FROM ttrss_counters_cache + WHERE owner_uid = ? AND feed_id > 0"); + $sth->execute([$user_id]); + $row = $sth->fetch(); + + return $row["c_id"]; + } + + static function getCategoryTitle($cat_id) { + + if ($cat_id == -1) { + return __("Special"); + } else if ($cat_id == -2) { + return __("Labels"); + } else { + + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT title FROM ttrss_feed_categories WHERE + id = ?"); + $sth->execute([$cat_id]); + + if ($row = $sth->fetch()) { + return $row["title"]; + } else { + return __("Uncategorized"); + } + } + } + + static function getLabelUnread($label_id, $owner_uid = false) { + if (!$owner_uid) $owner_uid = $_SESSION["uid"]; + + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT COUNT(ref_id) AS unread FROM ttrss_user_entries, ttrss_user_labels2 + WHERE owner_uid = ? AND unread = true AND label_id = ? AND article_id = ref_id"); + + $sth->execute([$owner_uid, $label_id]); + + if ($row = $sth->fetch()) { + return $row["unread"]; + } else { + return 0; + } + } + + static function queryFeedHeadlines($params) { + + $pdo = Db::pdo(); + + // WARNING: due to highly dynamic nature of this query its going to quote parameters + // right before adding them to SQL part + + $feed = $params["feed"]; + $limit = isset($params["limit"]) ? $params["limit"] : 30; + $view_mode = $params["view_mode"]; + $cat_view = isset($params["cat_view"]) ? $params["cat_view"] : false; + $search = isset($params["search"]) ? $params["search"] : false; + $search_language = isset($params["search_language"]) ? $params["search_language"] : ""; + $override_order = isset($params["override_order"]) ? $params["override_order"] : false; + $offset = isset($params["offset"]) ? $params["offset"] : 0; + $owner_uid = isset($params["owner_uid"]) ? $params["owner_uid"] : $_SESSION["uid"]; + $since_id = isset($params["since_id"]) ? $params["since_id"] : 0; + $include_children = isset($params["include_children"]) ? $params["include_children"] : false; + $ignore_vfeed_group = isset($params["ignore_vfeed_group"]) ? $params["ignore_vfeed_group"] : false; + $override_strategy = isset($params["override_strategy"]) ? $params["override_strategy"] : false; + $override_vfeed = isset($params["override_vfeed"]) ? $params["override_vfeed"] : false; + $start_ts = isset($params["start_ts"]) ? $params["start_ts"] : false; + $check_first_id = isset($params["check_first_id"]) ? $params["check_first_id"] : false; + $skip_first_id_check = isset($params["skip_first_id_check"]) ? $params["skip_first_id_check"] : false; + + $ext_tables_part = ""; + $limit_query_part = ""; + + $search_words = array(); + + if ($search) { + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH) as $plugin) { + list($search_query_part, $search_words) = $plugin->hook_search($search); + break; + } + + // fall back in case of no plugins + if (!$search_query_part) { + list($search_query_part, $search_words) = search_to_sql($search, $search_language); + } + $search_query_part .= " AND "; + } else { + $search_query_part = ""; + } + + if ($since_id) { + $since_id_part = "ttrss_entries.id > ".$pdo->quote($since_id)." AND "; + } else { + $since_id_part = ""; + } + + $view_query_part = ""; + + if ($view_mode == "adaptive") { + if ($search) { + $view_query_part = " "; + } else if ($feed != -1) { + + $unread = getFeedUnread($feed, $cat_view); + + if ($cat_view && $feed > 0 && $include_children) + $unread += Feeds::getCategoryChildrenUnread($feed); + + if ($unread > 0) { + $view_query_part = " unread = true AND "; + } + } + } + + if ($view_mode == "marked") { + $view_query_part = " marked = true AND "; + } + + if ($view_mode == "has_note") { + $view_query_part = " (note IS NOT NULL AND note != '') AND "; + } + + if ($view_mode == "published") { + $view_query_part = " published = true AND "; + } + + if ($view_mode == "unread" && $feed != -6) { + $view_query_part = " unread = true AND "; + } + + if ($limit > 0) { + $limit_query_part = "LIMIT " . (int)$limit; + } + + $allow_archived = false; + + $vfeed_query_part = ""; + + /* tags */ + if (!is_numeric($feed)) { + $query_strategy_part = "true"; + $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE + id = feed_id) as feed_title,"; + } else if ($feed > 0) { + + if ($cat_view) { + + if ($feed > 0) { + if ($include_children) { + # sub-cats + $subcats = Feeds::getChildCategories($feed, $owner_uid); + array_push($subcats, $feed); + $subcats = array_map("intval", $subcats); + + $query_strategy_part = "cat_id IN (". + implode(",", $subcats).")"; + + } else { + $query_strategy_part = "cat_id = " . $pdo->quote($feed); + } + + } else { + $query_strategy_part = "cat_id IS NULL"; + } + + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + + } else { + $query_strategy_part = "feed_id = " . $pdo->quote($feed); + } + } else if ($feed == 0 && !$cat_view) { // archive virtual feed + $query_strategy_part = "feed_id IS NULL"; + $allow_archived = true; + } else if ($feed == 0 && $cat_view) { // uncategorized + $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL"; + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + } else if ($feed == -1) { // starred virtual feed + $query_strategy_part = "marked = true"; + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + $allow_archived = true; + + if (!$override_order) { + $override_order = "last_marked DESC, date_entered DESC, updated DESC"; + } + + } else if ($feed == -2) { // published virtual feed OR labels category + + if (!$cat_view) { + $query_strategy_part = "published = true"; + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + $allow_archived = true; + + if (!$override_order) { + $override_order = "last_published DESC, date_entered DESC, updated DESC"; + } + + } else { + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + + $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,"; + + $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND + ttrss_user_labels2.article_id = ref_id"; + + } + } else if ($feed == -6) { // recently read + $query_strategy_part = "unread = false AND last_read IS NOT NULL"; + + if (DB_TYPE == "pgsql") { + $query_strategy_part .= " AND last_read > NOW() - INTERVAL '1 DAY' "; + } else { + $query_strategy_part .= " AND last_read > DATE_SUB(NOW(), INTERVAL 1 DAY) "; + } + + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + $allow_archived = true; + $ignore_vfeed_group = true; + + if (!$override_order) $override_order = "last_read DESC"; + + } else if ($feed == -3) { // fresh virtual feed + $query_strategy_part = "unread = true AND score >= 0"; + + $intl = (int) get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid); + + if (DB_TYPE == "pgsql") { + $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' "; + } else { + $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) "; + } + + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + } else if ($feed == -4) { // all articles virtual feed + $allow_archived = true; + $query_strategy_part = "true"; + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + } 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 + ttrss_labels2.id = ttrss_user_labels2.label_id AND + ttrss_user_labels2.article_id = ref_id"; + + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,"; + $allow_archived = true; + + } else { + $query_strategy_part = "true"; + } + + $order_by = "score DESC, date_entered DESC, updated DESC"; + + if ($override_order) { + $order_by = $override_order; + } + + if ($override_strategy) { + $query_strategy_part = $override_strategy; + } + + if ($override_vfeed) { + $vfeed_query_part = $override_vfeed; + } + + if ($search) { + $feed_title = T_sprintf("Search results: %s", $search); + } else { + if ($cat_view) { + $feed_title = Feeds::getCategoryTitle($feed); + } else { + if (is_numeric($feed) && $feed > 0) { + $ssth = $pdo->prepare("SELECT title,site_url,last_error,last_updated + FROM ttrss_feeds WHERE id = ? AND owner_uid = ?"); + $ssth->execute([$feed, $owner_uid]); + $row = $ssth->fetch(); + + $feed_title = $row["title"]; + $feed_site_url = $row["site_url"]; + $last_error = $row["last_error"]; + $last_updated = $row["last_updated"]; + } else { + $feed_title = Feeds::getFeedTitle($feed); + } + } + } + + $content_query_part = "content, "; + + if ($limit_query_part) { + $offset_query_part = "OFFSET " . (int)$offset; + } else { + $offset_query_part = ""; + } + + if (is_numeric($feed)) { + // proper override_order applied above + if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) { + if (!$override_order) { + $order_by = "ttrss_feeds.title, ".$order_by; + } else { + $order_by = "ttrss_feeds.title, ".$override_order; + } + } + + if (!$allow_archived) { + $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id),ttrss_feeds"; + $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND"; + + } else { + $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id) + LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)"; + } + + if ($vfeed_query_part) $vfeed_query_part .= "favicon_avg_color,"; + + if ($start_ts) { + $start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts)); + $start_ts_query_part = "date_entered >= '$start_ts_formatted' AND"; + } else { + $start_ts_query_part = ""; + } + + $first_id = 0; + $first_id_query_strategy_part = $query_strategy_part; + + if ($feed == -3) + $first_id_query_strategy_part = "true"; + + if (DB_TYPE == "pgsql") { + $sanity_interval_qpart = "date_entered >= NOW() - INTERVAL '1 hour' AND"; + } else { + $sanity_interval_qpart = "date_entered >= DATE_SUB(NOW(), INTERVAL 1 hour) AND"; + } + + if (!$search && !$skip_first_id_check) { + // if previous topmost article id changed that means our current pagination is no longer valid + $query = "SELECT DISTINCT + ttrss_feeds.title, + date_entered, + guid, + ttrss_entries.id, + ttrss_entries.title, + updated, + score, + marked, + published, + last_marked, + last_published, + last_read + FROM + $from_qpart + WHERE + $feed_check_qpart + ttrss_user_entries.owner_uid = ".$pdo->quote($owner_uid)." AND + $search_query_part + $start_ts_query_part + $since_id_part + $sanity_interval_qpart + $first_id_query_strategy_part ORDER BY $order_by LIMIT 1"; + + /*if ($_REQUEST["debug"]) { + print $query; + }*/ + + $res = $pdo->query($query); + + if ($row = $res->fetch()) { + $first_id = (int)$row["id"]; + + if ($offset > 0 && $first_id && $check_first_id && $first_id != $check_first_id) { + return array(-1, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id); + } + } + } + + $query = "SELECT DISTINCT + date_entered, + guid, + ttrss_entries.id,ttrss_entries.title, + updated, + label_cache, + tag_cache, + always_display_enclosures, + site_url, + note, + num_comments, + comments, + int_id, + uuid, + lang, + hide_images, + unread,feed_id,marked,published,link,last_read,orig_feed_id, + last_marked, last_published, + $vfeed_query_part + $content_query_part + author,score + FROM + $from_qpart + WHERE + $feed_check_qpart + ttrss_user_entries.owner_uid = ".$pdo->quote($owner_uid)." AND + $search_query_part + $start_ts_query_part + $view_query_part + $since_id_part + $query_strategy_part ORDER BY $order_by + $limit_query_part $offset_query_part"; + + //if ($_REQUEST["debug"]) print $query; + + $res = $pdo->query($query); + + } else { + // browsing by tag + + $query = "SELECT DISTINCT + date_entered, + guid, + note, + ttrss_entries.id as id, + title, + updated, + unread, + feed_id, + orig_feed_id, + marked, + num_comments, + comments, + int_id, + tag_cache, + label_cache, + link, + lang, + uuid, + last_read, + (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images, + last_marked, last_published, + $since_id_part + $vfeed_query_part + $content_query_part + author, score + FROM ttrss_entries, ttrss_user_entries, ttrss_tags + WHERE + ref_id = ttrss_entries.id AND + ttrss_user_entries.owner_uid = ".$pdo->quote($owner_uid)." AND + post_int_id = int_id AND + tag_name = ".$pdo->quote($feed)." AND + $view_query_part + $search_query_part + $query_strategy_part ORDER BY $order_by + $limit_query_part $offset_query_part"; + + if ($_REQUEST["debug"]) print $query; + + $res = $pdo->query($query); + } + + return array($res, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id); + + } + + static function getParentCategories($cat, $owner_uid) { + $rv = array(); + + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT parent_cat FROM ttrss_feed_categories + WHERE id = ? AND parent_cat IS NOT NULL AND owner_uid = ?"); + $sth->execute([$cat, $owner_uid]); + + while ($line = $sth->fetch()) { + array_push($rv, $line["parent_cat"]); + $rv = array_merge($rv, Feeds::getParentCategories($line["parent_cat"], $owner_uid)); + } + + return $rv; + } + + static function getChildCategories($cat, $owner_uid) { + $rv = array(); + + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT id FROM ttrss_feed_categories + WHERE parent_cat = ? AND owner_uid = ?"); + $sth->execute([$cat, $owner_uid]); + + while ($line = $sth->fetch()) { + array_push($rv, $line["id"]); + $rv = array_merge($rv, Feeds::getChildCategories($line["id"], $owner_uid)); + } + + return $rv; + } + + static function getFeedCategory($feed) { + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT cat_id FROM ttrss_feeds + WHERE id = ?"); + $sth->execute([$feed]); + + if ($row = $sth->fetch()) { + return $row["cat_id"]; + } else { + return false; + } + + } + + } -?> +