diff options
author | Andrew Dolgov <[email protected]> | 2021-02-24 21:56:52 +0300 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2021-02-24 21:56:52 +0300 |
commit | 93940d2a9f80d9e1dac49b5eb7db23230d31c5f6 (patch) | |
tree | 71016661f6017918d0934eb462bd9552018d557a /classes/feeds.php | |
parent | 8b022c2bfb356d7dddaf334bc931d6dec77086fb (diff) | |
parent | 1adacd057230aea4ede29dab510385bf01cf99a3 (diff) |
Merge branch 'master' of git.fakecake.org:fox/tt-rss into weblate-integration
Diffstat (limited to 'classes/feeds.php')
-rwxr-xr-x | classes/feeds.php | 601 |
1 files changed, 222 insertions, 379 deletions
diff --git a/classes/feeds.php b/classes/feeds.php index 031a671ae..ba2719f48 100755 --- a/classes/feeds.php +++ b/classes/feeds.php @@ -16,110 +16,13 @@ class Feeds extends Handler_Protected { return array_search($method, $csrf_ignored) !== false; } - private function format_headline_subtoolbar($feed_site_url, $feed_title, - $feed_id, $is_cat, $search, - $error, $feed_last_updated) { - - $cat_q = $is_cat ? "&is_cat=$is_cat" : ""; - - if ($search) { - $search_q = "&q=$search"; - } else { - $search_q = ""; - } - - $reply = ""; - - $rss_link = htmlspecialchars(get_self_url_prefix() . - "/public.php?op=rss&id=${feed_id}${cat_q}${search_q}"); - - $reply .= "<span class='left'>"; - - $reply .= "<a href=\"#\" - title=\"".__("Show as feed")."\" - onclick='CommonDialogs.generatedFeed(\"$feed_id\", \"$is_cat\", \"$rss_link\")'> - <i class='icon-syndicate material-icons'>rss_feed</i></a>"; - - $reply .= "<span id='feed_title'>"; - - if ($feed_site_url) { - $last_updated = T_sprintf("Last updated: %s", $feed_last_updated); - - $reply .= "<a title=\"$last_updated\" target='_blank' href=\"$feed_site_url\">". - truncate_string(strip_tags($feed_title), 30)."</a>"; - } else { - $reply .= strip_tags($feed_title); - } - - if ($error) - $reply .= " <i title=\"" . htmlspecialchars($error) . "\" class='material-icons icon-error'>error</i>"; - - $reply .= "</span>"; - $reply .= "<span id='feed_current_unread' style='display: none'></span>"; - $reply .= "</span>"; - - $reply .= "<span class=\"right\">"; - $reply .= "<span id='selected_prompt'></span>"; - $reply .= " "; - - $reply .= "<div dojoType='fox.form.DropDownButton' title='".__('Select articles')."'> - <span>".__("Select...")."</span> - <div dojoType='dijit.Menu' style='display: none;'> - <div dojoType='dijit.MenuItem' onclick='Headlines.select(\"all\")'>".__('All')."</div> - <div dojoType='dijit.MenuItem' onclick='Headlines.select(\"unread\")'>".__('Unread')."</div> - <div dojoType='dijit.MenuItem' onclick='Headlines.select(\"invert\")'>".__('Invert')."</div> - <div dojoType='dijit.MenuItem' onclick='Headlines.select(\"none\")'>".__('None')."</div> - <div dojoType='dijit.MenuSeparator'></div> - <div dojoType='dijit.MenuItem' onclick='Headlines.selectionToggleUnread()'>".__('Toggle unread')."</div> - <div dojoType='dijit.MenuItem' onclick='Headlines.selectionToggleMarked()'>".__('Toggle starred')."</div> - <div dojoType='dijit.MenuItem' onclick='Headlines.selectionTogglePublished()'>".__('Toggle published')."</div> - <div dojoType='dijit.MenuSeparator'></div> - <div dojoType='dijit.MenuItem' onclick='Headlines.catchupSelection()'>".__('Mark as read')."</div> - <div dojoType='dijit.MenuItem' onclick='Article.selectionSetScore()'>".__('Set score')."</div>"; - - // TODO: move to mail plugin - if (PluginHost::getInstance()->get_plugin("mail")) { - $reply .= "<div dojoType='dijit.MenuItem' value='Plugins.Mail.send()'>".__('Forward by email')."</div>"; - } - - // TODO: move to mailto plugin - if (PluginHost::getInstance()->get_plugin("mailto")) { - $reply .= "<div dojoType='dijit.MenuItem' value='Plugins.Mailto.send()'>".__('Forward by email')."</div>"; - } - - PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_HEADLINE_TOOLBAR_SELECT_MENU_ITEM, - function ($result) use (&$reply) { - $reply .= $result; - }, - $feed_id, $is_cat); - - if ($feed_id == 0 && !$is_cat) { - $reply .= "<div dojoType='dijit.MenuSeparator'></div> - <div dojoType='dijit.MenuItem' class='text-error' onclick='Headlines.deleteSelection()'>".__('Delete permanently')."</div>"; - } - - $reply .= "</div>"; /* menu */ - - $reply .= "</div>"; /* dropdown */ - - PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_HEADLINE_TOOLBAR_BUTTON, - function ($result) use (&$reply) { - $reply .= $result; - }, - $feed_id, $is_cat); - - $reply .= "</span>"; - - return $reply; - } - - private function format_headlines_list($feed, $method, $view_mode, $limit, $cat_view, + 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) { $disable_cache = false; - $this->mark_timestamp("init"); + $this->_mark_timestamp("init"); $reply = []; $rgba_cache = []; @@ -138,7 +41,7 @@ class Feeds extends Handler_Protected { } if ($method_split[0] == "MarkAllReadGR") { - $this->catchup_feed($method_split[1], false); + $this->_catchup($method_split[1], false); } // FIXME: might break tag display? @@ -200,10 +103,10 @@ class Feeds extends Handler_Protected { "order_by" => $order_by ); - $qfh_ret = $this->queryFeedHeadlines($params); + $qfh_ret = $this->_get_headlines($params); } - $this->mark_timestamp("db query"); + $this->_mark_timestamp("db query"); $vfeed_group_enabled = get_pref("VFEED_GROUP_BY_FEED") && !(in_array($feed, self::NEVER_GROUP_FEEDS) && !$cat_view); @@ -222,10 +125,28 @@ class Feeds extends Handler_Protected { $reply['search_query'] = [$search, $search_language]; $reply['vfeed_group_enabled'] = $vfeed_group_enabled; - $reply['toolbar'] = $this->format_headline_subtoolbar($feed_site_url, - $feed_title, - $feed, $cat_view, $search, - $last_error, $last_updated); + $plugin_menu_items = ""; + PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_HEADLINE_TOOLBAR_SELECT_MENU_ITEM, + function ($result) use (&$plugin_menu_items) { + $plugin_menu_items .= $result; + }, + $feed, $cat_view); + + $plugin_buttons = ""; + PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_HEADLINE_TOOLBAR_BUTTON, + function ($result) use (&$plugin_buttons) { + $plugin_buttons .= $result; + }, + $feed, $cat_view); + + $reply['toolbar'] = [ + 'site_url' => $feed_site_url, + 'title' => strip_tags($feed_title), + 'error' => $last_error, + 'last_updated' => $last_updated, + 'plugin_menu_items' => $plugin_menu_items, + 'plugin_buttons' => $plugin_buttons, + ]; $reply['content'] = []; @@ -236,13 +157,13 @@ class Feeds extends Handler_Protected { }, $feed, $cat_view, $qfh_ret); - $this->mark_timestamp("object header"); + $this->_mark_timestamp("object header"); $headlines_count = 0; if ($result instanceof PDOStatement) { while ($line = $result->fetch(PDO::FETCH_ASSOC)) { - $this->mark_timestamp("article start: " . $line["id"] . " " . $line["title"]); + $this->_mark_timestamp("article start: " . $line["id"] . " " . $line["title"]); ++$headlines_count; @@ -260,12 +181,12 @@ class Feeds extends Handler_Protected { $line, $max_excerpt_length); } - $this->mark_timestamp(" hook_query_headlines"); + $this->_mark_timestamp(" hook_query_headlines"); $id = $line["id"]; // frontend doesn't expect pdo returning booleans as strings on mysql - if (DB_TYPE == "mysql") { + if (Config::get(Config::DB_TYPE) == "mysql") { foreach (["unread", "marked", "published"] as $k) { $line[$k] = $line[$k] === "1"; } @@ -293,19 +214,15 @@ class Feeds extends Handler_Protected { } } - if (!is_array($labels)) $labels = Article::get_article_labels($id); - - $labels_str = "<span class=\"HLLCTR-$id\">"; - $labels_str .= Article::format_article_labels($labels); - $labels_str .= "</span>"; + if (!is_array($labels)) $labels = Article::_get_labels($id); - $line["labels"] = $labels_str; + $line["labels"] = Article::_get_labels($id); if (count($topmost_article_ids) < 3) { array_push($topmost_article_ids, $id); } - $this->mark_timestamp(" labels"); + $this->_mark_timestamp(" labels"); $line["feed_title"] = $line["feed_title"] ?? ""; @@ -323,32 +240,27 @@ class Feeds extends Handler_Protected { }, $line); - $this->mark_timestamp(" pre-sanitize"); + $this->_mark_timestamp(" pre-sanitize"); $line["content"] = Sanitizer::sanitize($line["content"], $line['hide_images'], false, $line["site_url"], $highlight_words, $line["id"]); - $this->mark_timestamp(" sanitize"); + $this->_mark_timestamp(" sanitize"); PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_RENDER_ARTICLE_CDM, function ($result, $plugin) use (&$line) { $line = $result; - $this->mark_timestamp(" hook_render_cdm: " . get_class($plugin)); + $this->_mark_timestamp(" hook_render_cdm: " . get_class($plugin)); }, $line); - $this->mark_timestamp(" hook_render_cdm"); + $this->_mark_timestamp(" hook_render_cdm"); - $line['content'] = DiskCache::rewriteUrls($line['content']); + $line['content'] = DiskCache::rewrite_urls($line['content']); - $this->mark_timestamp(" disk_cache_rewrite"); + $this->_mark_timestamp(" disk_cache_rewrite"); - if ($line['note']) - $line['note'] = Article::format_article_note($id, $line['note']); - else - $line['note'] = ""; - - $this->mark_timestamp(" note"); + $this->_mark_timestamp(" note"); if (!get_pref("CDM_EXPANDED")) { $line["cdm_excerpt"] = "<span class='collapse'> @@ -360,12 +272,14 @@ class Feeds extends Handler_Protected { } } - $this->mark_timestamp(" pre-enclosures"); + $this->_mark_timestamp(" pre-enclosures"); - $line["enclosures"] = Article::format_article_enclosures($id, $line["always_display_enclosures"], - $line["content"], $line["hide_images"]); + $line["enclosures"] = Article::_format_enclosures($id, + $line["always_display_enclosures"], + $line["content"], + $line["hide_images"]); - $this->mark_timestamp(" enclosures"); + $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); @@ -373,35 +287,31 @@ class Feeds extends Handler_Protected { $line['imported'] = T_sprintf("Imported at %s", TimeHelper::make_local_datetime($line["date_entered"], false)); - $this->mark_timestamp(" local-datetime"); + $this->_mark_timestamp(" local-datetime"); if ($line["tag_cache"]) $tags = explode(",", $line["tag_cache"]); else $tags = false; - $line["tags_str"] = Article::format_tags_string($tags); + $line["tags"] = Article::_get_tags($line["id"], false, $line["tag_cache"]); - $this->mark_timestamp(" tags"); + $this->_mark_timestamp(" tags"); - if (self::feedHasIcon($feed_id)) { - $line['feed_icon'] = "<img class=\"icon\" src=\"".ICONS_URL."/$feed_id.ico\" alt=\"\">"; - } else { - $line['feed_icon'] = "<i class='icon-no-feed material-icons'>rss_feed</i>"; - } + $line['has_icon'] = self::_has_icon($feed_id); //setting feed headline background color, needs to change text color based on dark/light $fav_color = $line['favicon_avg_color'] ?? false; - $this->mark_timestamp(" pre-color"); + $this->_mark_timestamp(" pre-color"); require_once "colors.php"; if (!isset($rgba_cache[$feed_id])) { if ($fav_color && $fav_color != 'fail') { - $rgba_cache[$feed_id] = _color_unpack($fav_color); + $rgba_cache[$feed_id] = \Colors\_color_unpack($fav_color); } else { - $rgba_cache[$feed_id] = _color_unpack($this->color_of($line['feed_title'])); + $rgba_cache[$feed_id] = \Colors\_color_unpack($this->_color_of($line['feed_title'])); } } @@ -409,7 +319,7 @@ class Feeds extends Handler_Protected { $line['feed_bg_color'] = 'rgba(' . implode(",", $rgba_cache[$feed_id]) . ',0.3)'; } - $this->mark_timestamp(" color"); + $this->_mark_timestamp(" color"); /* we don't need those */ @@ -419,11 +329,11 @@ class Feeds extends Handler_Protected { array_push($reply['content'], $line); - $this->mark_timestamp("article end"); + $this->_mark_timestamp("article end"); } } - $this->mark_timestamp("end of articles"); + $this->_mark_timestamp("end of articles"); if (!$headlines_count) { @@ -485,7 +395,7 @@ class Feeds extends Handler_Protected { } } - $this->mark_timestamp("end"); + $this->_mark_timestamp("end"); return array($topmost_article_ids, $headlines_count, $feed, $disable_cache, $reply); } @@ -494,6 +404,8 @@ class Feeds extends Handler_Protected { $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET last_read = NOW(), unread = false WHERE unread = true AND owner_uid = ?"); $sth->execute([$_SESSION['uid']]); + + print json_encode(array("message" => "UPDATE_COUNTERS")); } function view() { @@ -506,7 +418,7 @@ class Feeds extends Handler_Protected { $cat_view = $_REQUEST["cat"] == "true"; $next_unread_feed = $_REQUEST["nuf"] ?? 0; $offset = $_REQUEST["skip"] ?? 0; - $order_by = $_REQUEST["order_by"]; + $order_by = $_REQUEST["order_by"] ?? ""; $check_first_id = $_REQUEST["fid"] ?? 0; if (is_numeric($feed)) $feed = (int) $feed; @@ -515,7 +427,7 @@ class Feeds extends Handler_Protected { * when there's nothing to load - e.g. no stuff in fresh feed */ if ($feed == -5) { - print json_encode($this->generate_dashboard_feed()); + print json_encode($this->_generate_dashboard_feed()); return; } @@ -543,7 +455,7 @@ class Feeds extends Handler_Protected { } if ($sth && !$sth->fetch()) { - print json_encode($this->generate_error_feed(__("Feed not found."))); + print json_encode($this->_generate_error_feed(__("Feed not found."))); return; } @@ -566,9 +478,9 @@ class Feeds extends Handler_Protected { $reply['headlines'] = []; - list($override_order, $skip_first_id_check) = self::order_to_override_query($order_by); + list($override_order, $skip_first_id_check) = self::_order_to_override_query($order_by); - $ret = $this->format_headlines_list($feed, $method, + $ret = $this->_format_headlines_list($feed, $method, $view_mode, $limit, $cat_view, $offset, $override_order, true, $check_first_id, $skip_first_id_check, $order_by); @@ -589,18 +501,10 @@ class Feeds extends Handler_Protected { // this is parsed by handleRpcJson() on first viewfeed() to set cdm expanded, etc $reply['runtime-info'] = RPC::make_runtime_info(); - $reply_json = json_encode($reply); - - if (!$reply_json) { - $reply_json = json_encode(["error" => ["code" => 15, - "message" => json_last_error_msg()]]); - } - - print $reply_json; - + print json_encode($reply); } - private function generate_dashboard_feed() { + private function _generate_dashboard_feed() { $reply = array(); $reply['headlines']['id'] = -5; @@ -642,7 +546,7 @@ class Feeds extends Handler_Protected { return $reply; } - private function generate_error_feed($error) { + private function _generate_error_feed($error) { $reply = array(); $reply['headlines']['id'] = -7; @@ -658,131 +562,22 @@ class Feeds extends Handler_Protected { return $reply; } - function quickAddFeed() { - print "<form onsubmit='return false'>"; - - print_hidden("op", "rpc"); - print_hidden("method", "addfeed"); - - print "<div id='fadd_error_message' style='display : none' class='alert alert-danger'></div>"; - - print "<div id='fadd_multiple_notify' style='display : none'>"; - print_notice("Provided URL is a HTML page referencing multiple feeds, please select required feed from the dropdown menu below."); - print "<p></div>"; - - print "<section>"; - - print "<fieldset>"; - print "<div style='float : right'><img style='display : none' id='feed_add_spinner' src='images/indicator_white.gif'></div>"; - print "<input style='font-size : 16px; width : 500px;' - placeHolder=\"".__("Feed or site URL")."\" - dojoType='dijit.form.ValidationTextBox' required='1' name='feed' id='feedDlg_feedUrl'>"; - - print "</fieldset>"; - - print "<fieldset>"; - - if (get_pref('ENABLE_FEED_CATS')) { - print "<label class='inline'>" . __('Place in category:') . "</label> "; - print_feed_cat_select("cat", false, 'dojoType="fox.form.Select"'); - } - - print "</fieldset>"; - - print "</section>"; - - print '<div id="feedDlg_feedsContainer" style="display : none"> - <header>' . __('Available feeds') . '</header> - <section> - <fieldset> - <select id="feedDlg_feedContainerSelect" - dojoType="fox.form.Select" size="3"> - <script type="dojo/method" event="onChange" args="value"> - dijit.byId("feedDlg_feedUrl").attr("value", value); - </script> - </select> - </fieldset> - </section> - </div>'; - - print "<div id='feedDlg_loginContainer' style='display : none'> - <section> - <fieldset> - <input dojoType=\"dijit.form.TextBox\" name='login'\" - placeHolder=\"".__("Login")."\" - autocomplete=\"new-password\" - style=\"width : 10em;\"> - <input - placeHolder=\"".__("Password")."\" - dojoType=\"dijit.form.TextBox\" type='password' - autocomplete=\"new-password\" - style=\"width : 10em;\" name='pass'\"> - </fieldset> - </section> - </div>"; - - print "<section>"; - print "<label> - <label class='checkbox'><input type='checkbox' name='need_auth' dojoType='dijit.form.CheckBox' id='feedDlg_loginCheck' - onclick='App.displayIfChecked(this, \"feedDlg_loginContainer\")'> - ".__('This feed requires authentication.')."</label>"; - print "</section>"; - - print "<footer>"; - print "<button dojoType='dijit.form.Button' class='alt-primary' type='submit' - onclick='App.dialogOf(this).execute()'>".__('Subscribe')."</button>"; - - print "<button dojoType='dijit.form.Button' onclick='App.dialogOf(this).hide()'>". - __('Cancel')."</button>"; - print "</footer>"; - - print "</form>"; + function subscribeToFeed() { + print json_encode([ + "cat_select" => \Controls\select_feeds_cats("cat") + ]); } function search() { - $this->params = explode(":", $_REQUEST["param"], 2); - - $active_feed_id = sprintf("%d", $this->params[0]); - $is_cat = $this->params[1] != "false"; - - print "<form onsubmit='return false'>"; - - print "<section>"; - - print "<fieldset>"; - print "<input dojoType='dijit.form.ValidationTextBox' id='search_query' - style='font-size : 16px; width : 540px;' - placeHolder=\"".T_sprintf("Search %s...", $this->getFeedTitle($active_feed_id, $is_cat))."\" - name='query' type='search' value=''>"; - print "</fieldset>"; - - if (DB_TYPE == "pgsql") { - print "<fieldset>"; - print "<label class='inline'>" . __("Language:") . "</label>"; - print_select("search_language", get_pref('DEFAULT_SEARCH_LANGUAGE'), Pref_Feeds::get_ts_languages(), - "dojoType='fox.form.Select' title=\"".__('Used for word stemming')."\""); - print "</fieldset>"; - } - - print "</section>"; - - print "<footer>"; - - if (count(PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH)) == 0) { - print "<button dojoType='dijit.form.Button' style='float : left' class='alt-info' onclick='window.open(\"https://tt-rss.org/wiki/SearchSyntax\")'> - <i class='material-icons'>help</i> ".__("Search syntax")."</button>"; - } - - print "<button dojoType='dijit.form.Button' class='alt-primary' - type='submit' onclick='App.dialogOf(this).execute()'>".__('Search')."</button> - <button dojoType='dijit.form.Button' onclick='App.dialogOf(this).hide()'>".__('Cancel')."</button>"; - - print "</footer>"; - - print "</form>"; + print json_encode([ + "show_language" => Config::get(Config::DB_TYPE) == "pgsql", + "show_syntax_help" => count(PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH)) == 0, + "all_languages" => Pref_Feeds::get_ts_languages(), + "default_language" => get_pref('DEFAULT_SEARCH_LANGUAGE') + ]); } - function update_debugger() { + function updatedebugger() { header("Content-type: text/html"); $xdebug = isset($_REQUEST["xdebug"]) ? (int)$_REQUEST["xdebug"] : 1; @@ -801,10 +596,6 @@ class Feeds extends Handler_Protected { print "Access denied."; return; } - - $refetch_checked = isset($_REQUEST["force_refetch"]) ? "checked" : ""; - $rehash_checked = isset($_REQUEST["force_rehash"]) ? "checked" : ""; - ?> <!DOCTYPE html> <html> @@ -820,16 +611,23 @@ class Feeds extends Handler_Protected { display : none; } </style> - <?php - echo javascript_tag("lib/prototype.js"); - echo javascript_tag("js/utility.js"); - echo javascript_tag("lib/dojo/dojo.js"); - echo javascript_tag("lib/dojo/tt-rss-layer.js"); - ?> + <script> + dojoConfig = { + async: true, + cacheBust: "<?= get_scripts_timestamp(); ?>", + packages: [ + { name: "fox", location: "../../js" }, + ] + }; + </script> + <?= javascript_tag("js/utility.js") ?> + <?= javascript_tag("js/common.js") ?> + <?= javascript_tag("lib/dojo/dojo.js") ?> + <?= javascript_tag("lib/dojo/tt-rss-layer.js") ?> </head> <body class="flat ttrss_utility feed_debugger css_loading"> <script type="text/javascript"> - require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox', 'dijit/form/Select', 'dijit/form/Form', + require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox', 'fox/form/Select', 'dijit/form/Form', 'dijit/form/Select','dijit/form/TextBox','dijit/form/ValidationTextBox'],function(parser, ready){ ready(function() { parser.parse(); @@ -838,32 +636,31 @@ class Feeds extends Handler_Protected { </script> <div class="container"> - <h1>Feed Debugger: <?php echo "$feed_id: " . $this->getFeedTitle($feed_id) ?></h1> + <h1>Feed Debugger: <?= "$feed_id: " . $this->_get_title($feed_id) ?></h1> <div class="content"> - <form method="post" action=""> - <input type="hidden" name="op" value="feeds"> - <input type="hidden" name="method" value="update_debugger"> - <input type="hidden" name="csrf_token" value="<?php echo $csrf_token ?>"> - <input type="hidden" name="action" value="do_update"> - <input type="hidden" name="feed_id" value="<?php echo $feed_id ?>"> + <form method="post" action="" dojoType="dijit.form.Form"> + <?= \Controls\hidden_tag("op", "feeds") ?> + <?= \Controls\hidden_tag("method", "updatedebugger") ?> + <?= \Controls\hidden_tag("csrf_token", $csrf_token) ?> + <?= \Controls\hidden_tag("action", "do_update") ?> + <?= \Controls\hidden_tag("feed_id", (string)$feed_id) ?> <fieldset> <label> - <?php print_select_hash("xdebug", $xdebug, - [Debug::$LOG_VERBOSE => "LOG_VERBOSE", Debug::$LOG_EXTENDED => "LOG_EXTENDED"], - 'dojoType="dijit.form.Select"'); + <?= \Controls\select_hash("xdebug", $xdebug, + [Debug::$LOG_VERBOSE => "LOG_VERBOSE", Debug::$LOG_EXTENDED => "LOG_EXTENDED"]); ?></label> </fieldset> <fieldset> - <label class="checkbox"><input dojoType="dijit.form.CheckBox" type="checkbox" name="force_refetch" value="1" <?php echo $refetch_checked ?>> Force refetch</label> + <label class="checkbox"><?= \Controls\checkbox_tag("force_refetch", isset($_REQUEST["force_refetch"])) ?> Force refetch</label> </fieldset> <fieldset class="narrow"> - <label class="checkbox"><input dojoType="dijit.form.CheckBox" type="checkbox" name="force_rehash" value="1" <?php echo $rehash_checked ?>> Force rehash</label> + <label class="checkbox"><?= \Controls\checkbox_tag("force_rehash", isset($_REQUEST["force_rehash"])) ?> Force rehash</label> </fieldset> - <button type="submit" dojoType="dijit.form.Button" class="alt-primary">Continue</button> + <?= \Controls\submit_tag("Continue") ?> </form> <hr> @@ -883,7 +680,7 @@ class Feeds extends Handler_Protected { } - static function catchup_feed($feed, $cat_view, $owner_uid = false, $mode = 'all', $search = false) { + static function _catchup($feed, $cat_view, $owner_uid = false, $mode = 'all', $search = false) { if (!$owner_uid) $owner_uid = $_SESSION['uid']; @@ -903,7 +700,7 @@ class Feeds extends Handler_Protected { // fall back in case of no plugins if (empty($search_qpart)) { - list($search_qpart, $search_words) = self::search_to_sql($search[0], $search[1], $owner_uid); + list($search_qpart, $search_words) = self::_search_to_sql($search[0], $search[1], $owner_uid); } } else { $search_qpart = "true"; @@ -913,21 +710,21 @@ class Feeds extends Handler_Protected { switch ($mode) { case "1day": - if (DB_TYPE == "pgsql") { + if (Config::get(Config::DB_TYPE) == "pgsql") { $date_qpart = "date_entered < NOW() - INTERVAL '1 day' "; } else { $date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 1 DAY) "; } break; case "1week": - if (DB_TYPE == "pgsql") { + if (Config::get(Config::DB_TYPE) == "pgsql") { $date_qpart = "date_entered < NOW() - INTERVAL '1 week' "; } else { $date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 1 WEEK) "; } break; case "2week": - if (DB_TYPE == "pgsql") { + if (Config::get(Config::DB_TYPE) == "pgsql") { $date_qpart = "date_entered < NOW() - INTERVAL '2 week' "; } else { $date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 2 WEEK) "; @@ -943,7 +740,7 @@ class Feeds extends Handler_Protected { if ($feed >= 0) { if ($feed > 0) { - $children = self::getChildCategories($feed, $owner_uid); + $children = self::_get_child_cats($feed, $owner_uid); array_push($children, $feed); $children = array_map("intval", $children); @@ -1004,7 +801,7 @@ class Feeds extends Handler_Protected { $intl = (int) get_pref("FRESH_ARTICLE_MAX_AGE"); - if (DB_TYPE == "pgsql") { + if (Config::get(Config::DB_TYPE) == "pgsql") { $match_part = "date_entered > NOW() - INTERVAL '$intl hour' "; } else { $match_part = "date_entered > DATE_SUB(NOW(), @@ -1054,7 +851,7 @@ class Feeds extends Handler_Protected { } } - static function getFeedArticles($feed, $is_cat = false, $unread_only = false, + static function _get_counters($feed, $is_cat = false, $unread_only = false, $owner_uid = false) { $n_feed = (int) $feed; @@ -1073,7 +870,7 @@ class Feeds extends Handler_Protected { $match_part = ""; if ($is_cat) { - return self::getCategoryUnread($n_feed, $owner_uid); + return self::_get_cat_unread($n_feed, $owner_uid); } else if ($n_feed == -6) { return 0; } else if ($feed != "0" && $n_feed == 0) { @@ -1097,7 +894,7 @@ class Feeds extends Handler_Protected { $intl = (int) get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid); - if (DB_TYPE == "pgsql") { + if (Config::get(Config::DB_TYPE) == "pgsql") { $match_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' "; } else { $match_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) "; @@ -1119,7 +916,7 @@ class Feeds extends Handler_Protected { $label_id = Labels::feed_to_label_id($feed); - return self::getLabelUnread($label_id, $owner_uid); + return self::_get_label_unread($label_id, $owner_uid); } if ($match_part) { @@ -1154,6 +951,18 @@ class Feeds extends Handler_Protected { } } + function add() { + $feed = clean($_REQUEST['feed']); + $cat = clean($_REQUEST['cat']); + $need_auth = isset($_REQUEST['need_auth']); + $login = $need_auth ? clean($_REQUEST['login']) : ''; + $pass = $need_auth ? clean($_REQUEST['pass']) : ''; + + $rc = Feeds::_subscribe($feed, $cat, $login, $pass); + + print json_encode(array("result" => $rc)); + } + /** * @return array (code => Status code, message => error message if available) * @@ -1167,7 +976,7 @@ class Feeds extends Handler_Protected { * 5 - Couldn't download the URL content. * 6 - Content is an invalid XML. */ - static function subscribe_to_feed($url, $cat_id = 0, + static function _subscribe($url, $cat_id = 0, $auth_login = '', $auth_pass = '') { global $fetch_last_error; @@ -1196,8 +1005,8 @@ class Feeds extends Handler_Protected { return array("code" => 5, "message" => $fetch_last_error); } - if (mb_strpos($fetch_last_content_type, "html") !== false && self::is_html($contents)) { - $feedUrls = self::get_feeds_from_html($url, $contents); + if (mb_strpos($fetch_last_content_type, "html") !== false && self::_is_html($contents)) { + $feedUrls = self::_get_feeds_from_html($url, $contents); if (count($feedUrls) == 0) { return array("code" => 3); @@ -1240,42 +1049,36 @@ class Feeds extends Handler_Protected { } } - static function getIconFile($feed_id) { - return ICONS_DIR . "/$feed_id.ico"; + static function _get_icon_file($feed_id) { + return Config::get(Config::ICONS_DIR) . "/$feed_id.ico"; } - static function feedHasIcon($id) { - return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0; + static function _has_icon($id) { + return is_file(Config::get(Config::ICONS_DIR) . "/$id.ico") && filesize(Config::get(Config::ICONS_DIR) . "/$id.ico") > 0; } - static function getFeedIcon($id) { + static function _get_icon($id) { switch ($id) { case 0: return "archive"; - break; case -1: return "star"; - break; case -2: return "rss_feed"; - break; case -3: return "whatshot"; - break; case -4: return "inbox"; - break; case -6: return "restore"; - break; default: if ($id < LABEL_BASE_INDEX) { return "label"; } else { - $icon = self::getIconFile($id); + $icon = self::_get_icon_file($id); if ($icon && file_exists($icon)) { - return ICONS_URL . "/" . basename($icon) . "?" . filemtime($icon); + return Config::get(Config::ICONS_URL) . "/" . basename($icon) . "?" . filemtime($icon); } } break; @@ -1284,11 +1087,23 @@ class Feeds extends Handler_Protected { return false; } - static function getFeedTitle($id, $cat = false) { + static function _find_by_url($feed_url, $owner_uid) { + $sth = Db::pdo()->prepare("SELECT id FROM ttrss_feeds WHERE + feed_url = ? AND owner_uid = ?"); + $sth->execute([$feed_url, $owner_uid]); + + if ($row = $sth->fetch()) { + return $row["id"]; + } + + return false; + } + + static function _get_title($id, $cat = false) { $pdo = Db::pdo(); if ($cat) { - return self::getCategoryTitle($id); + return self::_get_cat_title($id); } else if ($id == -1) { return __("Starred articles"); } else if ($id == -2) { @@ -1331,7 +1146,7 @@ class Feeds extends Handler_Protected { } // only real cats - static function getCategoryMarked($cat, $owner_uid = false) { + static function _get_cat_marked($cat, $owner_uid = false) { if (!$owner_uid) $owner_uid = $_SESSION["uid"]; @@ -1354,7 +1169,7 @@ class Feeds extends Handler_Protected { } } - static function getCategoryUnread($cat, $owner_uid = false) { + static function _get_cat_unread($cat, $owner_uid = false) { if (!$owner_uid) $owner_uid = $_SESSION["uid"]; @@ -1388,7 +1203,7 @@ class Feeds extends Handler_Protected { } // only accepts real cats (>= 0) - static function getCategoryChildrenUnread($cat, $owner_uid = false) { + static function _get_cat_children_unread($cat, $owner_uid = false) { if (!$owner_uid) $owner_uid = $_SESSION["uid"]; $pdo = Db::pdo(); @@ -1400,14 +1215,14 @@ class Feeds extends Handler_Protected { $unread = 0; while ($line = $sth->fetch()) { - $unread += self::getCategoryUnread($line["id"], $owner_uid); - $unread += self::getCategoryChildrenUnread($line["id"], $owner_uid); + $unread += self::_get_cat_unread($line["id"], $owner_uid); + $unread += self::_get_cat_children_unread($line["id"], $owner_uid); } return $unread; } - static function getGlobalUnread($user_id = false) { + static function _get_global_unread($user_id = false) { if (!$user_id) $user_id = $_SESSION["uid"]; @@ -1423,7 +1238,7 @@ class Feeds extends Handler_Protected { return $row["count"]; } - static function getCategoryTitle($cat_id) { + static function _get_cat_title($cat_id) { if ($cat_id == -1) { return __("Special"); @@ -1445,7 +1260,7 @@ class Feeds extends Handler_Protected { } } - static function getLabelUnread($label_id, $owner_uid = false) { + private static function _get_label_unread($label_id, $owner_uid = false) { if (!$owner_uid) $owner_uid = $_SESSION["uid"]; $pdo = Db::pdo(); @@ -1462,7 +1277,7 @@ class Feeds extends Handler_Protected { } } - static function queryFeedHeadlines($params) { + static function _get_headlines($params) { $pdo = Db::pdo(); @@ -1508,10 +1323,10 @@ class Feeds extends Handler_Protected { // fall back in case of no plugins if (!$search_query_part) { - list($search_query_part, $search_words) = self::search_to_sql($search, $search_language, $owner_uid); + list($search_query_part, $search_words) = self::_search_to_sql($search, $search_language, $owner_uid); } - if (DB_TYPE == "pgsql") { + if (Config::get(Config::DB_TYPE) == "pgsql") { $test_sth = $pdo->prepare("select $search_query_part FROM ttrss_entries, ttrss_user_entries WHERE id = ref_id limit 1"); @@ -1546,7 +1361,7 @@ class Feeds extends Handler_Protected { $unread = getFeedUnread($feed, $cat_view); if ($cat_view && $feed > 0 && $include_children) - $unread += self::getCategoryChildrenUnread($feed); + $unread += self::_get_cat_children_unread($feed); if ($unread > 0) { $view_query_part = " unread = true AND "; @@ -1590,7 +1405,7 @@ class Feeds extends Handler_Protected { if ($feed > 0) { if ($include_children) { # sub-cats - $subcats = self::getChildCategories($feed, $owner_uid); + $subcats = self::_get_child_cats($feed, $owner_uid); array_push($subcats, $feed); $subcats = array_map("intval", $subcats); @@ -1648,7 +1463,7 @@ class Feeds extends Handler_Protected { } else if ($feed == -6) { // recently read $query_strategy_part = "unread = false AND last_read IS NOT NULL"; - if (DB_TYPE == "pgsql") { + if (Config::get(Config::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) "; @@ -1665,7 +1480,7 @@ class Feeds extends Handler_Protected { $intl = (int) get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid); - if (DB_TYPE == "pgsql") { + if (Config::get(Config::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) "; @@ -1714,7 +1529,7 @@ class Feeds extends Handler_Protected { $feed_title = T_sprintf("Search results: %s", $search); } else { if ($cat_view) { - $feed_title = self::getCategoryTitle($feed); + $feed_title = self::_get_cat_title($feed); } else { if (is_numeric($feed) && $feed > 0) { $ssth = $pdo->prepare("SELECT title,site_url,last_error,last_updated @@ -1727,7 +1542,7 @@ class Feeds extends Handler_Protected { $last_error = $row["last_error"]; $last_updated = $row["last_updated"]; } else { - $feed_title = self::getFeedTitle($feed); + $feed_title = self::_get_title($feed); } } } @@ -1784,7 +1599,7 @@ class Feeds extends Handler_Protected { if ($feed == -3) $first_id_query_strategy_part = "true"; - if (DB_TYPE == "pgsql") { + if (Config::get(Config::DB_TYPE) == "pgsql") { $sanity_interval_qpart = "date_entered >= NOW() - INTERVAL '1 hour' AND"; $yyiw_qpart = "to_char(date_entered, 'IYYY-IW') AS yyiw"; @@ -1884,7 +1699,7 @@ class Feeds extends Handler_Protected { } else { // browsing by tag - if (DB_TYPE == "pgsql") { + if (Config::get(Config::DB_TYPE) == "pgsql") { $distinct_columns = str_replace("desc", "", strtolower($order_by)); $distinct_qpart = "DISTINCT ON (id, $distinct_columns)"; } else { @@ -1942,7 +1757,7 @@ class Feeds extends Handler_Protected { } - static function getParentCategories($cat, $owner_uid) { + static function _get_parent_cats($cat, $owner_uid) { $rv = array(); $pdo = Db::pdo(); @@ -1952,14 +1767,14 @@ class Feeds extends Handler_Protected { $sth->execute([$cat, $owner_uid]); while ($line = $sth->fetch()) { - array_push($rv, $line["parent_cat"]); - $rv = array_merge($rv, self::getParentCategories($line["parent_cat"], $owner_uid)); + array_push($rv, (int)$line["parent_cat"]); + $rv = array_merge($rv, self::_get_parent_cats($line["parent_cat"], $owner_uid)); } return $rv; } - static function getChildCategories($cat, $owner_uid) { + static function _get_child_cats($cat, $owner_uid) { $rv = array(); $pdo = Db::pdo(); @@ -1970,13 +1785,41 @@ class Feeds extends Handler_Protected { while ($line = $sth->fetch()) { array_push($rv, $line["id"]); - $rv = array_merge($rv, self::getChildCategories($line["id"], $owner_uid)); + $rv = array_merge($rv, self::_get_child_cats($line["id"], $owner_uid)); + } + + return $rv; + } + + static function _cats_of(array $feeds, int $owner_uid, bool $with_parents = false) { + if (count($feeds) == 0) + return []; + + $pdo = Db::pdo(); + + $feeds_qmarks = arr_qmarks($feeds); + + $sth = $pdo->prepare("SELECT DISTINCT cat_id, fc.parent_cat FROM ttrss_feeds f LEFT JOIN ttrss_feed_categories fc + ON (fc.id = f.cat_id) + WHERE f.owner_uid = ? AND f.id IN ($feeds_qmarks)"); + $sth->execute(array_merge([$owner_uid], $feeds)); + + $rv = []; + + if ($row = $sth->fetch()) { + array_push($rv, (int)$row["cat_id"]); + + if ($with_parents && $row["parent_cat"]) + $rv = array_merge($rv, + self::_get_parent_cats($row["cat_id"], $owner_uid)); } + $rv = array_unique($rv); + return $rv; } - static function getFeedCategory($feed) { + static function _cat_of_feed($feed) { $pdo = Db::pdo(); $sth = $pdo->prepare("SELECT cat_id FROM ttrss_feeds @@ -1991,7 +1834,7 @@ class Feeds extends Handler_Protected { } - function color_of($name) { + private function _color_of($name) { $colormap = [ "#1cd7d7","#d91111","#1212d7","#8e16e5","#7b7b7b", "#39f110","#0bbea6","#ec0e0e","#1534f2","#b9e416", "#479af2","#f36b14","#10c7e9","#1e8fe7","#e22727" ]; @@ -2007,7 +1850,7 @@ class Feeds extends Handler_Protected { return $colormap[$sum]; } - static function get_feeds_from_html($url, $content) { + private static function _get_feeds_from_html($url, $content) { $url = UrlHelper::validate($url); $baseUrl = substr($url, 0, strrpos($url, '/') + 1); @@ -2035,11 +1878,11 @@ class Feeds extends Handler_Protected { return $feedUrls; } - static function is_html($content) { + static function _is_html($content) { return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 8192)) !== 0; } - static function add_feed_category($feed_cat, $parent_cat_id = false, $order_id = 0) { + static function _add_cat($feed_cat, $parent_cat_id = false, $order_id = 0) { if (!$feed_cat) return false; @@ -2076,7 +1919,7 @@ class Feeds extends Handler_Protected { return false; } - static function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) { + static function _get_access_key($feed_id, $is_cat, $owner_uid = false) { if (!$owner_uid) $owner_uid = $_SESSION["uid"]; @@ -2112,9 +1955,9 @@ class Feeds extends Handler_Protected { * @access public * @return mixed */ - static function purge_feed($feed_id, $purge_interval) { + static function _purge($feed_id, $purge_interval) { - if (!$purge_interval) $purge_interval = self::feed_purge_interval($feed_id); + if (!$purge_interval) $purge_interval = self::_get_purge_interval($feed_id); $pdo = Db::pdo(); @@ -2127,10 +1970,10 @@ class Feeds extends Handler_Protected { if ($row = $sth->fetch()) { $owner_uid = $row["owner_uid"]; - if (FORCE_ARTICLE_PURGE != 0) { - Debug::log("purge_feed: FORCE_ARTICLE_PURGE is set, overriding interval to " . FORCE_ARTICLE_PURGE, Debug::$LOG_VERBOSE); + 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); $purge_unread = true; - $purge_interval = FORCE_ARTICLE_PURGE; + $purge_interval = Config::get(Config::FORCE_ARTICLE_PURGE); } else { $purge_unread = get_pref("PURGE_UNREAD_ARTICLES", $owner_uid, false); } @@ -2149,7 +1992,7 @@ class Feeds extends Handler_Protected { else $query_limit = ""; - if (DB_TYPE == "pgsql") { + if (Config::get(Config::DB_TYPE) == "pgsql") { $sth = $pdo->prepare("DELETE FROM ttrss_user_entries USING ttrss_entries WHERE ttrss_entries.id = ref_id AND @@ -2182,7 +2025,7 @@ class Feeds extends Handler_Protected { return $rows_deleted; } - static function feed_purge_interval($feed_id) { + private static function _get_purge_interval($feed_id) { $pdo = Db::pdo(); @@ -2203,7 +2046,7 @@ class Feeds extends Handler_Protected { } } - static function search_to_sql($search, $search_language, $owner_uid) { + private static function _search_to_sql($search, $search_language, $owner_uid) { $keywords = str_getcsv(trim($search), " "); $query_keywords = array(); @@ -2332,7 +2175,7 @@ class Feeds extends Handler_Protected { array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')"); } else { - if (DB_TYPE == "pgsql") { + if (Config::get(Config::DB_TYPE) == "pgsql") { $k = mb_strtolower($k); array_push($search_query_leftover, $not ? "!$k" : $k); } else { @@ -2347,7 +2190,7 @@ class Feeds extends Handler_Protected { if (count($search_query_leftover) > 0) { - if (DB_TYPE == "pgsql") { + if (Config::get(Config::DB_TYPE) == "pgsql") { // if there's no joiners consider this a "simple" search and // concatenate everything with &, otherwise don't try to mess with tsquery syntax @@ -2371,7 +2214,7 @@ class Feeds extends Handler_Protected { return array($search_query_part, $search_words); } - static function order_to_override_query($order) { + static function _order_to_override_query($order) { $query = ""; $skip_first_id = false; @@ -2399,7 +2242,7 @@ class Feeds extends Handler_Protected { return [$query, $skip_first_id]; } - function mark_timestamp($label) { + private function _mark_timestamp($label) { if (empty($_REQUEST['timestamps'])) return; |