From aeb1abedb2cb5051c2a94e287f114bdbdaa467ff Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Thu, 4 May 2017 15:13:02 +0300 Subject: [PATCH] move a bunch of functions into Feeds/Article namespaces + static function catchupArticlesById($ids, $cmode, $owner_uid = false) { + static function getLastArticleId() { + static function queryFeedHeadlines($params) { + static function getParentCategories($cat, $owner_uid) { + static function getChildCategories($cat, $owner_uid) { move the rest of functions2.php back to functions.php as it is of more manageable size, remove the former --- classes/api.php | 11 +- classes/article.php | 47 + classes/feeds.php | 451 ++++++++- classes/handler/public.php | 19 +- classes/rpc.php | 4 +- include/digest.php | 2 +- include/functions.php | 1409 ++++++++++++++++++++++++++- include/functions2.php | 1895 ------------------------------------ plugins/vf_shared/init.php | 20 +- 9 files changed, 1902 insertions(+), 1956 deletions(-) delete mode 100644 include/functions2.php diff --git a/classes/api.php b/classes/api.php index 2dee790b3..6c7823a8f 100644 --- a/classes/api.php +++ b/classes/api.php @@ -687,15 +687,6 @@ class API extends Handler { } } - /*$qfh_ret = queryFeedHeadlines($feed_id, $limit, - $view_mode, $is_cat, $search, false, - $order, $offset, 0, false, $since_id, $include_nested);*/ - - //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_id, "limit" => $limit, @@ -710,7 +701,7 @@ class API extends Handler { "skip_first_id_check" => $skip_first_id_check ); - $qfh_ret = queryFeedHeadlines($params); + $qfh_ret = Feeds::queryFeedHeadlines($params); $result = $qfh_ret[0]; $feed_title = $qfh_ret[1]; diff --git a/classes/article.php b/classes/article.php index 64c04e140..7924ded35 100644 --- a/classes/article.php +++ b/classes/article.php @@ -876,5 +876,52 @@ class Article extends Handler_Protected { } } + static function catchupArticlesById($ids, $cmode, $owner_uid = false) { + + if (!$owner_uid) $owner_uid = $_SESSION["uid"]; + if (count($ids) == 0) return; + + $tmp_ids = array(); + + foreach ($ids as $id) { + array_push($tmp_ids, "ref_id = '$id'"); + } + + $ids_qpart = join(" OR ", $tmp_ids); + + if ($cmode == 0) { + db_query("UPDATE ttrss_user_entries SET + unread = false,last_read = NOW() + WHERE ($ids_qpart) AND owner_uid = $owner_uid"); + } else if ($cmode == 1) { + db_query("UPDATE ttrss_user_entries SET + unread = true + WHERE ($ids_qpart) AND owner_uid = $owner_uid"); + } else { + db_query("UPDATE ttrss_user_entries SET + unread = NOT unread,last_read = NOW() + WHERE ($ids_qpart) AND owner_uid = $owner_uid"); + } + + /* update ccache */ + + $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries + WHERE ($ids_qpart) AND owner_uid = $owner_uid"); + + while ($line = db_fetch_assoc($result)) { + ccache_update($line["feed_id"], $owner_uid); + } + } + + static function getLastArticleId() { + $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries + WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1"); + + if (db_num_rows($result) == 1) { + return db_fetch_result($result, 0, "id"); + } else { + return -1; + } + } } diff --git a/classes/feeds.php b/classes/feeds.php index 8f57f059e..0b326d2bf 100755 --- a/classes/feeds.php +++ b/classes/feeds.php @@ -262,14 +262,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,7 +277,7 @@ 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; @@ -1283,7 +1275,7 @@ class Feeds extends Handler_Protected { if ($feed >= 0) { if ($feed > 0) { - $children = getChildCategories($feed, $owner_uid); + $children = Feeds::getChildCategories($feed, $owner_uid); array_push($children, $feed); $children = join(",", $children); @@ -1753,5 +1745,444 @@ class Feeds extends Handler_Protected { } } + static function queryFeedHeadlines($params) { + + $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 = ""; + $query_strategy_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 > $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 " . $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); + $query_strategy_part = "cat_id IN (". + implode(",", $subcats).")"; + + } else { + $query_strategy_part = "cat_id = '$feed'"; + } + + } else { + $query_strategy_part = "cat_id IS NULL"; + } + + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + + } else { + $query_strategy_part = "feed_id = '$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 = 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 = feed_to_label_id($feed); + + $query_strategy_part = "label_id = '$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; + } + + $feed_title = ""; + + 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) { + $result = db_query("SELECT title,site_url,last_error,last_updated + FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid"); + + $feed_title = db_fetch_result($result, 0, "title"); + $feed_site_url = db_fetch_result($result, 0, "site_url"); + $last_error = db_fetch_result($result, 0, "last_error"); + $last_updated = db_fetch_result($result, 0, "last_updated"); + } else { + $feed_title = Feeds::getFeedTitle($feed); + } + } + } + + + $content_query_part = "content, "; + + if ($limit_query_part) { + $offset_query_part = "OFFSET $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 = '$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; + } + + $result = db_query($query); + if ($result && db_num_rows($result) > 0) { + $first_id = (int)db_fetch_result($result, 0, "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 = '$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; + + $result = db_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 = $owner_uid AND + post_int_id = int_id AND + tag_name = '$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; + + $result = db_query($query); + } + + return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id); + + } + + static function getParentCategories($cat, $owner_uid) { + $rv = array(); + + $result = db_query("SELECT parent_cat FROM ttrss_feed_categories + WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid"); + + while ($line = db_fetch_assoc($result)) { + 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(); + + $result = db_query("SELECT id FROM ttrss_feed_categories + WHERE parent_cat = '$cat' AND owner_uid = $owner_uid"); + + while ($line = db_fetch_assoc($result)) { + array_push($rv, $line["id"]); + $rv = array_merge($rv, Feeds::getChildCategories($line["id"], $owner_uid)); + } + + return $rv; + } + } diff --git a/classes/handler/public.php b/classes/handler/public.php index 0f1ae21eb..ec0c0fc60 100644 --- a/classes/handler/public.php +++ b/classes/handler/public.php @@ -37,16 +37,6 @@ class Handler_Public extends Handler { break; } - /*$qfh_ret = queryFeedHeadlines($feed, - 1, $view_mode, $is_cat, $search, false, - $date_sort_field, $offset, $owner_uid, - false, 0, true, true, false, false, $start_ts);*/ - - //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( "owner_uid" => $owner_uid, "feed" => $feed, @@ -61,7 +51,7 @@ class Handler_Public extends Handler { "start_ts" => $start_ts ); - $qfh_ret = queryFeedHeadlines($params); + $qfh_ret = Feeds::queryFeedHeadlines($params); $result = $qfh_ret[0]; @@ -79,11 +69,6 @@ class Handler_Public extends Handler { header("Last-Modified: $last_modified", true); } - /*$qfh_ret = queryFeedHeadlines($feed, - $limit, $view_mode, $is_cat, $search, false, - $date_sort_field, $offset, $owner_uid, - false, 0, true, true, false, false, $start_ts);*/ - $params = array( "owner_uid" => $owner_uid, "feed" => $feed, @@ -98,7 +83,7 @@ class Handler_Public extends Handler { "start_ts" => $start_ts ); - $qfh_ret = queryFeedHeadlines($params); + $qfh_ret = Feeds::queryFeedHeadlines($params); $result = $qfh_ret[0]; $feed_title = htmlspecialchars($qfh_ret[1]); diff --git a/classes/rpc.php b/classes/rpc.php index 949a6177a..4c4e52b7a 100755 --- a/classes/rpc.php +++ b/classes/rpc.php @@ -297,7 +297,7 @@ class RPC extends Handler_Protected { if (!empty($_REQUEST['seq'])) $reply['seq'] = (int) $_REQUEST['seq']; - if ($last_article_id != getLastArticleId()) { + if ($last_article_id != Article::getLastArticleId()) { $reply['counters'] = getAllCounters(); } @@ -311,7 +311,7 @@ class RPC extends Handler_Protected { $ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"])); $cmode = sprintf("%d", $_REQUEST["cmode"]); - catchupArticlesById($ids, $cmode); + Article::catchupArticlesById($ids, $cmode); print json_encode(array("message" => "UPDATE_COUNTERS", "ids" => $ids)); } diff --git a/include/digest.php b/include/digest.php index ff5d77f10..2c7f81cd9 100644 --- a/include/digest.php +++ b/include/digest.php @@ -60,7 +60,7 @@ if ($rc && $do_catchup) { if ($debug) _debug("Marking affected articles as read..."); - catchupArticlesById($affected_ids, 0, $line["id"]); + Article::catchupArticlesById($affected_ids, 0, $line["id"]); } } else { if ($debug) _debug("No headlines"); diff --git a/include/functions.php b/include/functions.php index d5a75843f..987e0c3f8 100644 --- a/include/functions.php +++ b/include/functions.php @@ -1268,5 +1268,1410 @@ return uniqid(base_convert(rand(), 10, 36)); } - // TODO: less dumb splitting - require_once "functions2.php"; + function make_init_params() { + $params = array(); + + foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS", + "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP", + "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE", + "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) { + + $params[strtolower($param)] = (int) get_pref($param); + } + + $params["icons_url"] = ICONS_URL; + $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME; + $params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE"); + $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT"); + $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY"); + $params["bw_limit"] = (int) $_SESSION["bw_limit"]; + $params["label_base_index"] = (int) LABEL_BASE_INDEX; + + $theme = get_pref( "USER_CSS_THEME", false, false); + $params["theme"] = theme_valid("$theme") ? $theme : ""; + + $params["plugins"] = implode(", ", PluginHost::getInstance()->get_plugin_names()); + + $params["php_platform"] = PHP_OS; + $params["php_version"] = PHP_VERSION; + + $params["sanity_checksum"] = sha1(file_get_contents("include/sanity_check.php")); + + $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM + ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]); + + $max_feed_id = db_fetch_result($result, 0, "mid"); + $num_feeds = db_fetch_result($result, 0, "nf"); + + $params["max_feed_id"] = (int) $max_feed_id; + $params["num_feeds"] = (int) $num_feeds; + + $params["hotkeys"] = get_hotkeys_map(); + + $params["csrf_token"] = $_SESSION["csrf_token"]; + $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"]; + + $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE; + + $params["icon_alert"] = base64_img("images/alert.png"); + $params["icon_information"] = base64_img("images/information.png"); + $params["icon_cross"] = base64_img("images/cross.png"); + $params["icon_indicator_white"] = base64_img("images/indicator_white.gif"); + + return $params; + } + + function get_hotkeys_info() { + $hotkeys = array( + __("Navigation") => array( + "next_feed" => __("Open next feed"), + "prev_feed" => __("Open previous feed"), + "next_article" => __("Open next article"), + "prev_article" => __("Open previous article"), + "next_article_noscroll" => __("Open next article (don't scroll long articles)"), + "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"), + "next_article_noexpand" => __("Move to next article (don't expand or mark read)"), + "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"), + "search_dialog" => __("Show search dialog")), + __("Article") => array( + "toggle_mark" => __("Toggle starred"), + "toggle_publ" => __("Toggle published"), + "toggle_unread" => __("Toggle unread"), + "edit_tags" => __("Edit tags"), + "open_in_new_window" => __("Open in new window"), + "catchup_below" => __("Mark below as read"), + "catchup_above" => __("Mark above as read"), + "article_scroll_down" => __("Scroll down"), + "article_scroll_up" => __("Scroll up"), + "select_article_cursor" => __("Select article under cursor"), + "email_article" => __("Email article"), + "close_article" => __("Close/collapse article"), + "toggle_expand" => __("Toggle article expansion (combined mode)"), + "toggle_widescreen" => __("Toggle widescreen mode"), + "toggle_embed_original" => __("Toggle embed original")), + __("Article selection") => array( + "select_all" => __("Select all articles"), + "select_unread" => __("Select unread"), + "select_marked" => __("Select starred"), + "select_published" => __("Select published"), + "select_invert" => __("Invert selection"), + "select_none" => __("Deselect everything")), + __("Feed") => array( + "feed_refresh" => __("Refresh current feed"), + "feed_unhide_read" => __("Un/hide read feeds"), + "feed_subscribe" => __("Subscribe to feed"), + "feed_edit" => __("Edit feed"), + "feed_catchup" => __("Mark as read"), + "feed_reverse" => __("Reverse headlines"), + "feed_toggle_vgroup" => __("Toggle headline grouping"), + "feed_debug_update" => __("Debug feed update"), + "feed_debug_viewfeed" => __("Debug viewfeed()"), + "catchup_all" => __("Mark all feeds as read"), + "cat_toggle_collapse" => __("Un/collapse current category"), + "toggle_combined_mode" => __("Toggle combined mode"), + "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")), + __("Go to") => array( + "goto_all" => __("All articles"), + "goto_fresh" => __("Fresh"), + "goto_marked" => __("Starred"), + "goto_published" => __("Published"), + "goto_tagcloud" => __("Tag cloud"), + "goto_prefs" => __("Preferences")), + __("Other") => array( + "create_label" => __("Create label"), + "create_filter" => __("Create filter"), + "collapse_sidebar" => __("Un/collapse sidebar"), + "help_dialog" => __("Show help dialog")) + ); + + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_INFO) as $plugin) { + $hotkeys = $plugin->hook_hotkey_info($hotkeys); + } + + return $hotkeys; + } + + function get_hotkeys_map() { + $hotkeys = array( + // "navigation" => array( + "k" => "next_feed", + "j" => "prev_feed", + "n" => "next_article", + "p" => "prev_article", + "(38)|up" => "prev_article", + "(40)|down" => "next_article", + // "^(38)|Ctrl-up" => "prev_article_noscroll", + // "^(40)|Ctrl-down" => "next_article_noscroll", + "(191)|/" => "search_dialog", + // "article" => array( + "s" => "toggle_mark", + "*s" => "toggle_publ", + "u" => "toggle_unread", + "*t" => "edit_tags", + "o" => "open_in_new_window", + "c p" => "catchup_below", + "c n" => "catchup_above", + "*n" => "article_scroll_down", + "*p" => "article_scroll_up", + "*(38)|Shift+up" => "article_scroll_up", + "*(40)|Shift+down" => "article_scroll_down", + "a *w" => "toggle_widescreen", + "a e" => "toggle_embed_original", + "e" => "email_article", + "a q" => "close_article", + // "article_selection" => array( + "a a" => "select_all", + "a u" => "select_unread", + "a *u" => "select_marked", + "a p" => "select_published", + "a i" => "select_invert", + "a n" => "select_none", + // "feed" => array( + "f r" => "feed_refresh", + "f a" => "feed_unhide_read", + "f s" => "feed_subscribe", + "f e" => "feed_edit", + "f q" => "feed_catchup", + "f x" => "feed_reverse", + "f g" => "feed_toggle_vgroup", + "f *d" => "feed_debug_update", + "f *g" => "feed_debug_viewfeed", + "f *c" => "toggle_combined_mode", + "f c" => "toggle_cdm_expanded", + "*q" => "catchup_all", + "x" => "cat_toggle_collapse", + // "goto" => array( + "g a" => "goto_all", + "g f" => "goto_fresh", + "g s" => "goto_marked", + "g p" => "goto_published", + "g t" => "goto_tagcloud", + "g *p" => "goto_prefs", + // "other" => array( + "(9)|Tab" => "select_article_cursor", // tab + "c l" => "create_label", + "c f" => "create_filter", + "c s" => "collapse_sidebar", + "^(191)|Ctrl+/" => "help_dialog", + ); + + if (get_pref('COMBINED_DISPLAY_MODE')) { + $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll"; + $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll"; + } + + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) { + $hotkeys = $plugin->hook_hotkey_map($hotkeys); + } + + $prefixes = array(); + + foreach (array_keys($hotkeys) as $hotkey) { + $pair = explode(" ", $hotkey, 2); + + if (count($pair) > 1 && !in_array($pair[0], $prefixes)) { + array_push($prefixes, $pair[0]); + } + } + + return array($prefixes, $hotkeys); + } + + function check_for_update() { + if (defined("GIT_VERSION_TIMESTAMP")) { + $content = @fetch_file_contents(array("url" => "http://tt-rss.org/version.json", "timeout" => 5)); + + if ($content) { + $content = json_decode($content, true); + + if ($content && isset($content["changeset"])) { + if ((int)GIT_VERSION_TIMESTAMP < (int)$content["changeset"]["timestamp"] && + GIT_VERSION_HEAD != $content["changeset"]["id"]) { + + return $content["changeset"]["id"]; + } + } + } + } + + return ""; + } + + function make_runtime_info($disable_update_check = false) { + $data = array(); + + $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM + ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]); + + $max_feed_id = db_fetch_result($result, 0, "mid"); + $num_feeds = db_fetch_result($result, 0, "nf"); + + $data["max_feed_id"] = (int) $max_feed_id; + $data["num_feeds"] = (int) $num_feeds; + + $data['last_article_id'] = Article::getLastArticleId(); + $data['cdm_expanded'] = get_pref('CDM_EXPANDED'); + + $data['dep_ts'] = calculate_dep_timestamp(); + $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE'); + + + if (CHECK_FOR_UPDATES && !$disable_update_check && $_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) { + $update_result = @check_for_update(); + + $data["update_result"] = $update_result; + + $_SESSION["last_version_check"] = time(); + } + + if (file_exists(LOCK_DIRECTORY . "/update_daemon.lock")) { + + $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock"); + + if (time() - $_SESSION["daemon_stamp_check"] > 30) { + + $stamp = (int) @file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp"); + + if ($stamp) { + $stamp_delta = time() - $stamp; + + if ($stamp_delta > 1800) { + $stamp_check = 0; + } else { + $stamp_check = 1; + $_SESSION["daemon_stamp_check"] = time(); + } + + $data['daemon_stamp_ok'] = $stamp_check; + + $stamp_fmt = date("Y.m.d, G:i", $stamp); + + $data['daemon_stamp'] = $stamp_fmt; + } + } + } + + return $data; + } + + function search_to_sql($search, $search_language) { + + $keywords = str_getcsv(trim($search), " "); + $query_keywords = array(); + $search_words = array(); + $search_query_leftover = array(); + + if ($search_language) + $search_language = db_escape_string(mb_strtolower($search_language)); + else + $search_language = "english"; + + foreach ($keywords as $k) { + if (strpos($k, "-") === 0) { + $k = substr($k, 1); + $not = "NOT"; + } else { + $not = ""; + } + + $commandpair = explode(":", mb_strtolower($k), 2); + + switch ($commandpair[0]) { + case "title": + if ($commandpair[1]) { + array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%". + db_escape_string(mb_strtolower($commandpair[1]))."%'))"); + } else { + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') + OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + array_push($search_words, $k); + } + break; + case "author": + if ($commandpair[1]) { + array_push($query_keywords, "($not (LOWER(author) LIKE '%". + db_escape_string(mb_strtolower($commandpair[1]))."%'))"); + } else { + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') + OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + array_push($search_words, $k); + } + break; + case "note": + if ($commandpair[1]) { + if ($commandpair[1] == "true") + array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))"); + else if ($commandpair[1] == "false") + array_push($query_keywords, "($not (note IS NULL OR note = ''))"); + else + array_push($query_keywords, "($not (LOWER(note) LIKE '%". + db_escape_string(mb_strtolower($commandpair[1]))."%'))"); + } else { + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') + OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + if (!$not) array_push($search_words, $k); + } + break; + case "star": + + if ($commandpair[1]) { + if ($commandpair[1] == "true") + array_push($query_keywords, "($not (marked = true))"); + else + array_push($query_keywords, "($not (marked = false))"); + } else { + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') + OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + if (!$not) array_push($search_words, $k); + } + break; + case "pub": + if ($commandpair[1]) { + if ($commandpair[1] == "true") + array_push($query_keywords, "($not (published = true))"); + else + array_push($query_keywords, "($not (published = false))"); + + } else { + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') + OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + if (!$not) array_push($search_words, $k); + } + break; + case "unread": + if ($commandpair[1]) { + if ($commandpair[1] == "true") + array_push($query_keywords, "($not (unread = true))"); + else + array_push($query_keywords, "($not (unread = false))"); + + } else { + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') + OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + if (!$not) array_push($search_words, $k); + } + break; + default: + if (strpos($k, "@") === 0) { + + $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']); + $orig_ts = strtotime(substr($k, 1)); + $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC')); + + //$k = date("Y-m-d", strtotime(substr($k, 1))); + + array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')"); + } else { + + if (DB_TYPE == "pgsql") { + $k = mb_strtolower($k); + array_push($search_query_leftover, $not ? "!$k" : $k); + } else { + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') + OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + } + + if (!$not) array_push($search_words, $k); + } + } + } + + if (count($search_query_leftover) > 0) { + $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover)); + + if (DB_TYPE == "pgsql") { + array_push($query_keywords, + "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))"); + } + + } + + $search_query_part = implode("AND", $query_keywords); + + return array($search_query_part, $search_words); + } + + function iframe_whitelisted($entry) { + $whitelist = array("youtube.com", "youtu.be", "vimeo.com", "player.vimeo.com"); + + @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST); + + if ($src) { + foreach ($whitelist as $w) { + if ($src == $w || $src == "www.$w") + return true; + } + } + + return false; + } + + function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) { + if (!$owner) $owner = $_SESSION["uid"]; + + $res = trim($str); if (!$res) return ''; + + $charset_hack = ' + + '; + + $res = trim($res); if (!$res) return ''; + + libxml_use_internal_errors(true); + + $doc = new DOMDocument(); + $doc->loadHTML($charset_hack . $res); + $xpath = new DOMXPath($doc); + + $ttrss_uses_https = parse_url(get_self_url_prefix(), PHP_URL_SCHEME) === 'https'; + $rewrite_base_url = $site_url ? $site_url : SELF_URL_PATH; + + $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src]|//audio/source[@src])'); + + foreach ($entries as $entry) { + + if ($entry->hasAttribute('href')) { + $entry->setAttribute('href', + rewrite_relative_url($rewrite_base_url, $entry->getAttribute('href'))); + + $entry->setAttribute('rel', 'noopener noreferrer'); + } + + if ($entry->hasAttribute('src')) { + $src = rewrite_relative_url($rewrite_base_url, $entry->getAttribute('src')); + $cached_filename = CACHE_DIR . '/images/' . sha1($src); + + if (file_exists($cached_filename)) { + + // this is strictly cosmetic + if ($entry->tagName == 'img') { + $suffix = ".png"; + } else if ($entry->parentNode && $entry->parentNode->tagName == "video") { + $suffix = ".mp4"; + } else if ($entry->parentNode && $entry->parentNode->tagName == "audio") { + $suffix = ".ogg"; + } else { + $suffix = ""; + } + + $src = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($src) . $suffix; + + if ($entry->hasAttribute('srcset')) { + $entry->removeAttribute('srcset'); + } + + if ($entry->hasAttribute('sizes')) { + $entry->removeAttribute('sizes'); + } + } + + $entry->setAttribute('src', $src); + } + + if ($entry->nodeName == 'img') { + + if ($entry->hasAttribute('src')) { + $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME) === 'https'; + + if ($ttrss_uses_https && !$is_https_url) { + + if ($entry->hasAttribute('srcset')) { + $entry->removeAttribute('srcset'); + } + + if ($entry->hasAttribute('sizes')) { + $entry->removeAttribute('sizes'); + } + } + } + + if (($owner && get_pref("STRIP_IMAGES", $owner)) || + $force_remove_images || $_SESSION["bw_limit"]) { + + $p = $doc->createElement('p'); + + $a = $doc->createElement('a'); + $a->setAttribute('href', $entry->getAttribute('src')); + + $a->appendChild(new DOMText($entry->getAttribute('src'))); + $a->setAttribute('target', '_blank'); + $a->setAttribute('rel', 'noopener noreferrer'); + + $p->appendChild($a); + + $entry->parentNode->replaceChild($p, $entry); + } + } + + if (strtolower($entry->nodeName) == "a") { + $entry->setAttribute("target", "_blank"); + $entry->setAttribute("rel", "noopener noreferrer"); + } + } + + $entries = $xpath->query('//iframe'); + foreach ($entries as $entry) { + if (!iframe_whitelisted($entry)) { + $entry->setAttribute('sandbox', 'allow-scripts'); + } else { + if ($_SERVER['HTTPS'] == "on") { + $entry->setAttribute("src", + str_replace("http://", "https://", + $entry->getAttribute("src"))); + } + } + } + + $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside', + 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', + 'caption', 'cite', 'center', 'code', 'col', 'colgroup', + 'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font', + 'dt', 'em', 'footer', 'figure', 'figcaption', + 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i', + 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript', + 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section', + 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary', + 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time', + 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' ); + + if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe'; + + $disallowed_attributes = array('id', 'style', 'class'); + + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) { + $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id); + if (is_array($retval)) { + $doc = $retval[0]; + $allowed_elements = $retval[1]; + $disallowed_attributes = $retval[2]; + } else { + $doc = $retval; + } + } + + $doc->removeChild($doc->firstChild); //remove doctype + $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes); + + if ($highlight_words) { + foreach ($highlight_words as $word) { + + // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph + + $elements = $xpath->query("//*/text()"); + + foreach ($elements as $child) { + + $fragment = $doc->createDocumentFragment(); + $text = $child->textContent; + + while (($pos = mb_stripos($text, $word)) !== false) { + $fragment->appendChild(new DomText(mb_substr($text, 0, $pos))); + $word = mb_substr($text, $pos, mb_strlen($word)); + $highlight = $doc->createElement('span'); + $highlight->appendChild(new DomText($word)); + $highlight->setAttribute('class', 'highlight'); + $fragment->appendChild($highlight); + $text = mb_substr($text, $pos + mb_strlen($word)); + } + + if (!empty($text)) $fragment->appendChild(new DomText($text)); + + $child->parentNode->replaceChild($fragment, $child); + } + } + } + + $res = $doc->saveHTML(); + + /* strip everything outside of ... */ + + $res_frag = array(); + if (preg_match('/(.*)<\/body>/is', $res, $res_frag)) { + return $res_frag[1]; + } else { + return $res; + } + } + + function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) { + $xpath = new DOMXPath($doc); + $entries = $xpath->query('//*'); + + foreach ($entries as $entry) { + if (!in_array($entry->nodeName, $allowed_elements)) { + $entry->parentNode->removeChild($entry); + } + + if ($entry->hasAttributes()) { + $attrs_to_remove = array(); + + foreach ($entry->attributes as $attr) { + + if (strpos($attr->nodeName, 'on') === 0) { + array_push($attrs_to_remove, $attr); + } + + if ($attr->nodeName == 'href' && stripos($attr->value, 'javascript:') === 0) { + array_push($attrs_to_remove, $attr); + } + + if (in_array($attr->nodeName, $disallowed_attributes)) { + array_push($attrs_to_remove, $attr); + } + } + + foreach ($attrs_to_remove as $attr) { + $entry->removeAttributeNode($attr); + } + } + } + + return $doc; + } + + function trim_array($array) { + $tmp = $array; + array_walk($tmp, 'trim'); + return $tmp; + } + + function tag_is_valid($tag) { + if ($tag == '') return false; + if (is_numeric($tag)) return false; + if (mb_strlen($tag) > 250) return false; + + if (!$tag) return false; + + return true; + } + + function render_login_form() { + header('Cache-Control: public'); + + require_once "login_form.php"; + exit; + } + + function T_sprintf() { + $args = func_get_args(); + return vsprintf(__(array_shift($args)), $args); + } + + function print_checkpoint($n, $s) { + $ts = microtime(true); + echo sprintf("\n", $ts - $s); + return $ts; + } + + function sanitize_tag($tag) { + $tag = trim($tag); + + $tag = mb_strtolower($tag, 'utf-8'); + + $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag); + + if (DB_TYPE == "mysql") { + $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag); + } + + return $tag; + } + + function get_self_url_prefix() { + if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) { + return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1); + } else { + return SELF_URL_PATH; + } + } + + /** + * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI. + * + * @return string The Mozilla Firefox feed adding URL. + */ + function add_feed_url() { + //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH); + + $url_path = get_self_url_prefix() . + "/public.php?op=subscribe&feed_url=%s"; + return $url_path; + } // function add_feed_url + + function encrypt_password($pass, $salt = '', $mode2 = false) { + if ($salt && $mode2) { + return "MODE2:" . hash('sha256', $salt . $pass); + } else if ($salt) { + return "SHA1X:" . sha1("$salt:$pass"); + } else { + return "SHA1:" . sha1($pass); + } + } // function encrypt_password + + function load_filters($feed_id, $owner_uid) { + $filters = array(); + + $cat_id = (int)getFeedCategory($feed_id); + + if ($cat_id == 0) + $null_cat_qpart = "cat_id IS NULL OR"; + else + $null_cat_qpart = ""; + + $result = db_query("SELECT * FROM ttrss_filters2 WHERE + owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title"); + + $check_cats = join(",", array_merge( + Feeds::getParentCategories($cat_id, $owner_uid), + array($cat_id))); + + while ($line = db_fetch_assoc($result)) { + $filter_id = $line["id"]; + + $result2 = db_query("SELECT + r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name + FROM ttrss_filters2_rules AS r, + ttrss_filter_types AS t + WHERE + ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND + (feed_id IS NULL OR feed_id = '$feed_id') AND + filter_type = t.id AND filter_id = '$filter_id'"); + + $rules = array(); + $actions = array(); + + while ($rule_line = db_fetch_assoc($result2)) { + # print_r($rule_line); + + $rule = array(); + $rule["reg_exp"] = $rule_line["reg_exp"]; + $rule["type"] = $rule_line["type_name"]; + $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]); + + array_push($rules, $rule); + } + + $result2 = db_query("SELECT a.action_param,t.name AS type_name + FROM ttrss_filters2_actions AS a, + ttrss_filter_actions AS t + WHERE + action_id = t.id AND filter_id = '$filter_id'"); + + while ($action_line = db_fetch_assoc($result2)) { + # print_r($action_line); + + $action = array(); + $action["type"] = $action_line["type_name"]; + $action["param"] = $action_line["action_param"]; + + array_push($actions, $action); + } + + + $filter = array(); + $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]); + $filter["inverse"] = sql_bool_to_bool($line["inverse"]); + $filter["rules"] = $rules; + $filter["actions"] = $actions; + + if (count($rules) > 0 && count($actions) > 0) { + array_push($filters, $filter); + } + } + + return $filters; + } + + function get_score_pic($score) { + if ($score > 100) { + return "score_high.png"; + } else if ($score > 0) { + return "score_half_high.png"; + } else if ($score < -100) { + return "score_low.png"; + } else if ($score < 0) { + return "score_half_low.png"; + } else { + return "score_neutral.png"; + } + } + + function feed_has_icon($id) { + return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0; + } + + function init_plugins() { + PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL); + + return true; + } + + function add_feed_category($feed_cat, $parent_cat_id = false) { + + if (!$feed_cat) return false; + + db_query("BEGIN"); + + if ($parent_cat_id) { + $parent_qpart = "parent_cat = '$parent_cat_id'"; + $parent_insert = "'$parent_cat_id'"; + } else { + $parent_qpart = "parent_cat IS NULL"; + $parent_insert = "NULL"; + } + + $feed_cat = mb_substr($feed_cat, 0, 250); + + $result = db_query( + "SELECT id FROM ttrss_feed_categories + WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]); + + if (db_num_rows($result) == 0) { + + $result = db_query( + "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat) + VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)"); + + db_query("COMMIT"); + + return true; + } + + return false; + } + + /** + * Fixes incomplete URLs by prepending "http://". + * Also replaces feed:// with http://, and + * prepends a trailing slash if the url is a domain name only. + * + * @param string $url Possibly incomplete URL + * + * @return string Fixed URL. + */ + function fix_url($url) { + + // support schema-less urls + if (strpos($url, '//') === 0) { + $url = 'https:' . $url; + } + + if (strpos($url, '://') === false) { + $url = 'http://' . $url; + } else if (substr($url, 0, 5) == 'feed:') { + $url = 'http:' . substr($url, 5); + } + + //prepend slash if the URL has no slash in it + // "http://www.example" -> "http://www.example/" + if (strpos($url, '/', strpos($url, ':') + 3) === false) { + $url .= '/'; + } + + //convert IDNA hostname to punycode if possible + if (function_exists("idn_to_ascii")) { + $parts = parse_url($url); + if (mb_detect_encoding($parts['host']) != 'ASCII') + { + $parts['host'] = idn_to_ascii($parts['host']); + $url = build_url($parts); + } + } + + if ($url != "http:///") + return $url; + else + return ''; + } + + function validate_feed_url($url) { + $parts = parse_url($url); + + return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https'); + + } + + /* function save_email_address($email) { + // FIXME: implement persistent storage of emails + + if (!$_SESSION['stored_emails']) + $_SESSION['stored_emails'] = array(); + + if (!in_array($email, $_SESSION['stored_emails'])) + array_push($_SESSION['stored_emails'], $email); + } */ + + + function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) { + + if (!$owner_uid) $owner_uid = $_SESSION["uid"]; + + $sql_is_cat = bool_to_sql_bool($is_cat); + + $result = db_query("SELECT access_key FROM ttrss_access_keys + WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat + AND owner_uid = " . $owner_uid); + + if (db_num_rows($result) == 1) { + return db_fetch_result($result, 0, "access_key"); + } else { + $key = db_escape_string(uniqid_short()); + + $result = db_query("INSERT INTO ttrss_access_keys + (access_key, feed_id, is_cat, owner_uid) + VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')"); + + return $key; + } + return false; + } + + function get_feeds_from_html($url, $content) + { + $url = fix_url($url); + $baseUrl = substr($url, 0, strrpos($url, '/') + 1); + + libxml_use_internal_errors(true); + + $doc = new DOMDocument(); + $doc->loadHTML($content); + $xpath = new DOMXPath($doc); + $entries = $xpath->query('/html/head/link[@rel="alternate" and '. + '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]'); + $feedUrls = array(); + foreach ($entries as $entry) { + if ($entry->hasAttribute('href')) { + $title = $entry->getAttribute('title'); + if ($title == '') { + $title = $entry->getAttribute('type'); + } + $feedUrl = rewrite_relative_url( + $baseUrl, $entry->getAttribute('href') + ); + $feedUrls[$feedUrl] = $title; + } + } + return $feedUrls; + } + + function is_html($content) { + return preg_match("/ 0) { + $limit_part = 500; + + $query = "SELECT ttrss_tags.id AS id + FROM ttrss_tags, ttrss_user_entries, ttrss_entries + WHERE post_int_id = int_id AND $interval_query AND + ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part"; + + $result = db_query($query); + + $ids = array(); + + while ($line = db_fetch_assoc($result)) { + array_push($ids, $line['id']); + } + + if (count($ids) > 0) { + $ids = join(",", $ids); + + $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)"); + $tags_deleted += db_affected_rows($tmp_result); + } else { + break; + } + + $limit -= $limit_part; + } + + return $tags_deleted; + } + + function print_user_stylesheet() { + $value = get_pref('USER_STYLESHEET'); + + if ($value) { + print ""; + } + + } + + function filter_to_sql($filter, $owner_uid) { + $query = array(); + + if (DB_TYPE == "pgsql") + $reg_qpart = "~"; + else + $reg_qpart = "REGEXP"; + + foreach ($filter["rules"] AS $rule) { + $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]); + $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/', + $rule['reg_exp']) !== FALSE; + + if ($regexp_valid) { + + $rule['reg_exp'] = db_escape_string($rule['reg_exp']); + + switch ($rule["type"]) { + case "title": + $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('". + $rule['reg_exp'] . "')"; + break; + case "content": + $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('". + $rule['reg_exp'] . "')"; + break; + case "both": + $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('". + $rule['reg_exp'] . "') OR LOWER(" . + "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')"; + break; + case "tag": + $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('". + $rule['reg_exp'] . "')"; + break; + case "link": + $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('". + $rule['reg_exp'] . "')"; + break; + case "author": + $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('". + $rule['reg_exp'] . "')"; + break; + } + + if (isset($rule['inverse'])) $qpart = "NOT ($qpart)"; + + if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) { + $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]); + } + + if (isset($rule["cat_id"])) { + + if ($rule["cat_id"] > 0) { + $children = Feeds::getChildCategories($rule["cat_id"], $owner_uid); + array_push($children, $rule["cat_id"]); + + $children = join(",", $children); + + $cat_qpart = "cat_id IN ($children)"; + } else { + $cat_qpart = "cat_id IS NULL"; + } + + $qpart .= " AND $cat_qpart"; + } + + $qpart .= " AND feed_id IS NOT NULL"; + + array_push($query, "($qpart)"); + + } + } + + if (count($query) > 0) { + $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")"; + } else { + $fullquery = "(false)"; + } + + if ($filter['inverse']) $fullquery = "(NOT $fullquery)"; + + return $fullquery; + } + + if (!function_exists('gzdecode')) { + function gzdecode($string) { // no support for 2nd argument + return file_get_contents('compress.zlib://data:who/cares;base64,'. + base64_encode($string)); + } + } + + function get_random_bytes($length) { + if (function_exists('openssl_random_pseudo_bytes')) { + return openssl_random_pseudo_bytes($length); + } else { + $output = ""; + + for ($i = 0; $i < $length; $i++) + $output .= chr(mt_rand(0, 255)); + + return $output; + } + } + + function read_stdin() { + $fp = fopen("php://stdin", "r"); + + if ($fp) { + $line = trim(fgets($fp)); + fclose($fp); + return $line; + } + + return null; + } + + function getFeedCategory($feed) { + $result = db_query("SELECT cat_id FROM ttrss_feeds + WHERE id = '$feed'"); + + if (db_num_rows($result) > 0) { + return db_fetch_result($result, 0, "cat_id"); + } else { + return false; + } + + } + + function implements_interface($class, $interface) { + return in_array($interface, class_implements($class)); + } + + function get_minified_js($files) { + require_once 'lib/jshrink/Minifier.php'; + + $rv = ''; + + foreach ($files as $js) { + if (!isset($_GET['debug'])) { + $cached_file = CACHE_DIR . "/js/".basename($js).".js"; + + if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) { + + list($header, $contents) = explode("\n", file_get_contents($cached_file), 2); + + if ($header && $contents) { + list($htag, $hversion) = explode(":", $header); + + if ($htag == "tt-rss" && $hversion == VERSION) { + $rv .= $contents; + continue; + } + } + } + + $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js")); + file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified); + $rv .= $minified; + + } else { + $rv .= file_get_contents("js/$js.js"); // no cache in debug mode + } + } + + return $rv; + } + + function calculate_dep_timestamp() { + $files = array_merge(glob("js/*.js"), glob("css/*.css")); + + $max_ts = -1; + + foreach ($files as $file) { + if (filemtime($file) > $max_ts) $max_ts = filemtime($file); + } + + return $max_ts; + } + + function T_js_decl($s1, $s2) { + if ($s1 && $s2) { + $s1 = preg_replace("/\n/", "", $s1); + $s2 = preg_replace("/\n/", "", $s2); + + $s1 = preg_replace("/\"/", "\\\"", $s1); + $s2 = preg_replace("/\"/", "\\\"", $s2); + + return "T_messages[\"$s1\"] = \"$s2\";\n"; + } + } + + function init_js_translations() { + + print 'var T_messages = new Object(); + + function __(msg) { + if (T_messages[msg]) { + return T_messages[msg]; + } else { + return msg; + } + } + + function ngettext(msg1, msg2, n) { + return __((parseInt(n) > 1) ? msg2 : msg1); + }'; + + $l10n = _get_reader(); + + for ($i = 0; $i < $l10n->total; $i++) { + $orig = $l10n->get_original_string($i); + if(strpos($orig, "\000") !== FALSE) { // Plural forms + $key = explode(chr(0), $orig); + print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular + print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural + } else { + $translation = __($orig); + print T_js_decl($orig, $translation); + } + } + } + + function label_to_feed_id($label) { + return LABEL_BASE_INDEX - 1 - abs($label); + } + + function feed_to_label_id($feed) { + return LABEL_BASE_INDEX - 1 + abs($feed); + } + + function get_theme_path($theme) { + $check = "themes/$theme"; + if (file_exists($check)) return $check; + + $check = "themes.local/$theme"; + if (file_exists($check)) return $check; + } + + function theme_valid($theme) { + $bundled_themes = [ "default.php", "night.css", "compact.css" ]; + + if (in_array($theme, $bundled_themes)) return true; + + $file = "themes/" . basename($theme); + + if (!file_exists($file)) $file = "themes.local/" . basename($theme); + + if (file_exists($file) && is_readable($file)) { + $fh = fopen($file, "r"); + + if ($fh) { + $header = fgets($fh); + fclose($fh); + + return strpos($header, "supports-version:" . VERSION_STATIC) !== FALSE; + } + } + + return false; + } + + /** + * @SuppressWarnings(unused) + */ + function error_json($code) { + require_once "errors.php"; + + @$message = $ERRORS[$code]; + + return json_encode(array("error" => + array("code" => $code, "message" => $message))); + + } + + function abs_to_rel_path($dir) { + $tmp = str_replace(dirname(__DIR__), "", $dir); + + if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1); + + return $tmp; + } + + function get_upload_error_message($code) { + + $errors = array( + 0 => __('There is no error, the file uploaded with success'), + 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'), + 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'), + 3 => __('The uploaded file was only partially uploaded'), + 4 => __('No file was uploaded'), + 6 => __('Missing a temporary folder'), + 7 => __('Failed to write file to disk.'), + 8 => __('A PHP extension stopped the file upload.'), + ); + + return $errors[$code]; + } + + function base64_img($filename) { + if (file_exists($filename)) { + $ext = pathinfo($filename, PATHINFO_EXTENSION); + + return "data:image/$ext;base64," . base64_encode(file_get_contents($filename)); + } else { + return ""; + } + } + diff --git a/include/functions2.php b/include/functions2.php deleted file mode 100644 index a47e9c463..000000000 --- a/include/functions2.php +++ /dev/null @@ -1,1895 +0,0 @@ -get_plugin_names()); - - $params["php_platform"] = PHP_OS; - $params["php_version"] = PHP_VERSION; - - $params["sanity_checksum"] = sha1(file_get_contents("include/sanity_check.php")); - - $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM - ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]); - - $max_feed_id = db_fetch_result($result, 0, "mid"); - $num_feeds = db_fetch_result($result, 0, "nf"); - - $params["max_feed_id"] = (int) $max_feed_id; - $params["num_feeds"] = (int) $num_feeds; - - $params["hotkeys"] = get_hotkeys_map(); - - $params["csrf_token"] = $_SESSION["csrf_token"]; - $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"]; - - $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE; - - $params["icon_alert"] = base64_img("images/alert.png"); - $params["icon_information"] = base64_img("images/information.png"); - $params["icon_cross"] = base64_img("images/cross.png"); - $params["icon_indicator_white"] = base64_img("images/indicator_white.gif"); - - return $params; - } - - function get_hotkeys_info() { - $hotkeys = array( - __("Navigation") => array( - "next_feed" => __("Open next feed"), - "prev_feed" => __("Open previous feed"), - "next_article" => __("Open next article"), - "prev_article" => __("Open previous article"), - "next_article_noscroll" => __("Open next article (don't scroll long articles)"), - "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"), - "next_article_noexpand" => __("Move to next article (don't expand or mark read)"), - "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"), - "search_dialog" => __("Show search dialog")), - __("Article") => array( - "toggle_mark" => __("Toggle starred"), - "toggle_publ" => __("Toggle published"), - "toggle_unread" => __("Toggle unread"), - "edit_tags" => __("Edit tags"), - "open_in_new_window" => __("Open in new window"), - "catchup_below" => __("Mark below as read"), - "catchup_above" => __("Mark above as read"), - "article_scroll_down" => __("Scroll down"), - "article_scroll_up" => __("Scroll up"), - "select_article_cursor" => __("Select article under cursor"), - "email_article" => __("Email article"), - "close_article" => __("Close/collapse article"), - "toggle_expand" => __("Toggle article expansion (combined mode)"), - "toggle_widescreen" => __("Toggle widescreen mode"), - "toggle_embed_original" => __("Toggle embed original")), - __("Article selection") => array( - "select_all" => __("Select all articles"), - "select_unread" => __("Select unread"), - "select_marked" => __("Select starred"), - "select_published" => __("Select published"), - "select_invert" => __("Invert selection"), - "select_none" => __("Deselect everything")), - __("Feed") => array( - "feed_refresh" => __("Refresh current feed"), - "feed_unhide_read" => __("Un/hide read feeds"), - "feed_subscribe" => __("Subscribe to feed"), - "feed_edit" => __("Edit feed"), - "feed_catchup" => __("Mark as read"), - "feed_reverse" => __("Reverse headlines"), - "feed_toggle_vgroup" => __("Toggle headline grouping"), - "feed_debug_update" => __("Debug feed update"), - "feed_debug_viewfeed" => __("Debug viewfeed()"), - "catchup_all" => __("Mark all feeds as read"), - "cat_toggle_collapse" => __("Un/collapse current category"), - "toggle_combined_mode" => __("Toggle combined mode"), - "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")), - __("Go to") => array( - "goto_all" => __("All articles"), - "goto_fresh" => __("Fresh"), - "goto_marked" => __("Starred"), - "goto_published" => __("Published"), - "goto_tagcloud" => __("Tag cloud"), - "goto_prefs" => __("Preferences")), - __("Other") => array( - "create_label" => __("Create label"), - "create_filter" => __("Create filter"), - "collapse_sidebar" => __("Un/collapse sidebar"), - "help_dialog" => __("Show help dialog")) - ); - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_INFO) as $plugin) { - $hotkeys = $plugin->hook_hotkey_info($hotkeys); - } - - return $hotkeys; - } - - function get_hotkeys_map() { - $hotkeys = array( -// "navigation" => array( - "k" => "next_feed", - "j" => "prev_feed", - "n" => "next_article", - "p" => "prev_article", - "(38)|up" => "prev_article", - "(40)|down" => "next_article", -// "^(38)|Ctrl-up" => "prev_article_noscroll", -// "^(40)|Ctrl-down" => "next_article_noscroll", - "(191)|/" => "search_dialog", -// "article" => array( - "s" => "toggle_mark", - "*s" => "toggle_publ", - "u" => "toggle_unread", - "*t" => "edit_tags", - "o" => "open_in_new_window", - "c p" => "catchup_below", - "c n" => "catchup_above", - "*n" => "article_scroll_down", - "*p" => "article_scroll_up", - "*(38)|Shift+up" => "article_scroll_up", - "*(40)|Shift+down" => "article_scroll_down", - "a *w" => "toggle_widescreen", - "a e" => "toggle_embed_original", - "e" => "email_article", - "a q" => "close_article", -// "article_selection" => array( - "a a" => "select_all", - "a u" => "select_unread", - "a *u" => "select_marked", - "a p" => "select_published", - "a i" => "select_invert", - "a n" => "select_none", -// "feed" => array( - "f r" => "feed_refresh", - "f a" => "feed_unhide_read", - "f s" => "feed_subscribe", - "f e" => "feed_edit", - "f q" => "feed_catchup", - "f x" => "feed_reverse", - "f g" => "feed_toggle_vgroup", - "f *d" => "feed_debug_update", - "f *g" => "feed_debug_viewfeed", - "f *c" => "toggle_combined_mode", - "f c" => "toggle_cdm_expanded", - "*q" => "catchup_all", - "x" => "cat_toggle_collapse", -// "goto" => array( - "g a" => "goto_all", - "g f" => "goto_fresh", - "g s" => "goto_marked", - "g p" => "goto_published", - "g t" => "goto_tagcloud", - "g *p" => "goto_prefs", -// "other" => array( - "(9)|Tab" => "select_article_cursor", // tab - "c l" => "create_label", - "c f" => "create_filter", - "c s" => "collapse_sidebar", - "^(191)|Ctrl+/" => "help_dialog", - ); - - if (get_pref('COMBINED_DISPLAY_MODE')) { - $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll"; - $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll"; - } - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) { - $hotkeys = $plugin->hook_hotkey_map($hotkeys); - } - - $prefixes = array(); - - foreach (array_keys($hotkeys) as $hotkey) { - $pair = explode(" ", $hotkey, 2); - - if (count($pair) > 1 && !in_array($pair[0], $prefixes)) { - array_push($prefixes, $pair[0]); - } - } - - return array($prefixes, $hotkeys); - } - - function check_for_update() { - if (defined("GIT_VERSION_TIMESTAMP")) { - $content = @fetch_file_contents(array("url" => "http://tt-rss.org/version.json", "timeout" => 5)); - - if ($content) { - $content = json_decode($content, true); - - if ($content && isset($content["changeset"])) { - if ((int)GIT_VERSION_TIMESTAMP < (int)$content["changeset"]["timestamp"] && - GIT_VERSION_HEAD != $content["changeset"]["id"]) { - - return $content["changeset"]["id"]; - } - } - } - } - - return ""; - } - - function make_runtime_info($disable_update_check = false) { - $data = array(); - - $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM - ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]); - - $max_feed_id = db_fetch_result($result, 0, "mid"); - $num_feeds = db_fetch_result($result, 0, "nf"); - - $data["max_feed_id"] = (int) $max_feed_id; - $data["num_feeds"] = (int) $num_feeds; - - $data['last_article_id'] = getLastArticleId(); - $data['cdm_expanded'] = get_pref('CDM_EXPANDED'); - - $data['dep_ts'] = calculate_dep_timestamp(); - $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE'); - - - if (CHECK_FOR_UPDATES && !$disable_update_check && $_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) { - $update_result = @check_for_update(); - - $data["update_result"] = $update_result; - - $_SESSION["last_version_check"] = time(); - } - - if (file_exists(LOCK_DIRECTORY . "/update_daemon.lock")) { - - $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock"); - - if (time() - $_SESSION["daemon_stamp_check"] > 30) { - - $stamp = (int) @file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp"); - - if ($stamp) { - $stamp_delta = time() - $stamp; - - if ($stamp_delta > 1800) { - $stamp_check = 0; - } else { - $stamp_check = 1; - $_SESSION["daemon_stamp_check"] = time(); - } - - $data['daemon_stamp_ok'] = $stamp_check; - - $stamp_fmt = date("Y.m.d, G:i", $stamp); - - $data['daemon_stamp'] = $stamp_fmt; - } - } - } - - return $data; - } - - function search_to_sql($search, $search_language) { - - $keywords = str_getcsv(trim($search), " "); - $query_keywords = array(); - $search_words = array(); - $search_query_leftover = array(); - - if ($search_language) - $search_language = db_escape_string(mb_strtolower($search_language)); - else - $search_language = "english"; - - foreach ($keywords as $k) { - if (strpos($k, "-") === 0) { - $k = substr($k, 1); - $not = "NOT"; - } else { - $not = ""; - } - - $commandpair = explode(":", mb_strtolower($k), 2); - - switch ($commandpair[0]) { - case "title": - if ($commandpair[1]) { - array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%". - db_escape_string(mb_strtolower($commandpair[1]))."%'))"); - } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); - array_push($search_words, $k); - } - break; - case "author": - if ($commandpair[1]) { - array_push($query_keywords, "($not (LOWER(author) LIKE '%". - db_escape_string(mb_strtolower($commandpair[1]))."%'))"); - } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); - array_push($search_words, $k); - } - break; - case "note": - if ($commandpair[1]) { - if ($commandpair[1] == "true") - array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))"); - else if ($commandpair[1] == "false") - array_push($query_keywords, "($not (note IS NULL OR note = ''))"); - else - array_push($query_keywords, "($not (LOWER(note) LIKE '%". - db_escape_string(mb_strtolower($commandpair[1]))."%'))"); - } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); - if (!$not) array_push($search_words, $k); - } - break; - case "star": - - if ($commandpair[1]) { - if ($commandpair[1] == "true") - array_push($query_keywords, "($not (marked = true))"); - else - array_push($query_keywords, "($not (marked = false))"); - } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); - if (!$not) array_push($search_words, $k); - } - break; - case "pub": - if ($commandpair[1]) { - if ($commandpair[1] == "true") - array_push($query_keywords, "($not (published = true))"); - else - array_push($query_keywords, "($not (published = false))"); - - } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); - if (!$not) array_push($search_words, $k); - } - break; - case "unread": - if ($commandpair[1]) { - if ($commandpair[1] == "true") - array_push($query_keywords, "($not (unread = true))"); - else - array_push($query_keywords, "($not (unread = false))"); - - } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); - if (!$not) array_push($search_words, $k); - } - break; - default: - if (strpos($k, "@") === 0) { - - $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']); - $orig_ts = strtotime(substr($k, 1)); - $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC')); - - //$k = date("Y-m-d", strtotime(substr($k, 1))); - - array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')"); - } else { - - if (DB_TYPE == "pgsql") { - $k = mb_strtolower($k); - array_push($search_query_leftover, $not ? "!$k" : $k); - } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); - } - - if (!$not) array_push($search_words, $k); - } - } - } - - if (count($search_query_leftover) > 0) { - $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover)); - - if (DB_TYPE == "pgsql") { - array_push($query_keywords, - "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))"); - } - - } - - $search_query_part = implode("AND", $query_keywords); - - return array($search_query_part, $search_words); - } - - function getParentCategories($cat, $owner_uid) { - $rv = array(); - - $result = db_query("SELECT parent_cat FROM ttrss_feed_categories - WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid"); - - while ($line = db_fetch_assoc($result)) { - array_push($rv, $line["parent_cat"]); - $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid)); - } - - return $rv; - } - - function getChildCategories($cat, $owner_uid) { - $rv = array(); - - $result = db_query("SELECT id FROM ttrss_feed_categories - WHERE parent_cat = '$cat' AND owner_uid = $owner_uid"); - - while ($line = db_fetch_assoc($result)) { - array_push($rv, $line["id"]); - $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid)); - } - - return $rv; - } - - function queryFeedHeadlines($params) { - - $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 = ""; - $query_strategy_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 > $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 " . $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 = getChildCategories($feed, $owner_uid); - - array_push($subcats, $feed); - $query_strategy_part = "cat_id IN (". - implode(",", $subcats).")"; - - } else { - $query_strategy_part = "cat_id = '$feed'"; - } - - } else { - $query_strategy_part = "cat_id IS NULL"; - } - - $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; - - } else { - $query_strategy_part = "feed_id = '$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 = 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 = feed_to_label_id($feed); - - $query_strategy_part = "label_id = '$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; - } - - $feed_title = ""; - - 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) { - $result = db_query("SELECT title,site_url,last_error,last_updated - FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid"); - - $feed_title = db_fetch_result($result, 0, "title"); - $feed_site_url = db_fetch_result($result, 0, "site_url"); - $last_error = db_fetch_result($result, 0, "last_error"); - $last_updated = db_fetch_result($result, 0, "last_updated"); - } else { - $feed_title = Feeds::getFeedTitle($feed); - } - } - } - - - $content_query_part = "content, "; - - if ($limit_query_part) { - $offset_query_part = "OFFSET $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 = '$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; - } - - $result = db_query($query); - if ($result && db_num_rows($result) > 0) { - $first_id = (int)db_fetch_result($result, 0, "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 = '$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; - - $result = db_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 = $owner_uid AND - post_int_id = int_id AND - tag_name = '$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; - - $result = db_query($query); - } - - return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id); - - } - - function iframe_whitelisted($entry) { - $whitelist = array("youtube.com", "youtu.be", "vimeo.com", "player.vimeo.com"); - - @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST); - - if ($src) { - foreach ($whitelist as $w) { - if ($src == $w || $src == "www.$w") - return true; - } - } - - return false; - } - - function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) { - if (!$owner) $owner = $_SESSION["uid"]; - - $res = trim($str); if (!$res) return ''; - - $charset_hack = ' - - '; - - $res = trim($res); if (!$res) return ''; - - libxml_use_internal_errors(true); - - $doc = new DOMDocument(); - $doc->loadHTML($charset_hack . $res); - $xpath = new DOMXPath($doc); - - $ttrss_uses_https = parse_url(get_self_url_prefix(), PHP_URL_SCHEME) === 'https'; - $rewrite_base_url = $site_url ? $site_url : SELF_URL_PATH; - - $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src]|//audio/source[@src])'); - - foreach ($entries as $entry) { - - if ($entry->hasAttribute('href')) { - $entry->setAttribute('href', - rewrite_relative_url($rewrite_base_url, $entry->getAttribute('href'))); - - $entry->setAttribute('rel', 'noopener noreferrer'); - } - - if ($entry->hasAttribute('src')) { - $src = rewrite_relative_url($rewrite_base_url, $entry->getAttribute('src')); - $cached_filename = CACHE_DIR . '/images/' . sha1($src); - - if (file_exists($cached_filename)) { - - // this is strictly cosmetic - if ($entry->tagName == 'img') { - $suffix = ".png"; - } else if ($entry->parentNode && $entry->parentNode->tagName == "video") { - $suffix = ".mp4"; - } else if ($entry->parentNode && $entry->parentNode->tagName == "audio") { - $suffix = ".ogg"; - } else { - $suffix = ""; - } - - $src = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($src) . $suffix; - - if ($entry->hasAttribute('srcset')) { - $entry->removeAttribute('srcset'); - } - - if ($entry->hasAttribute('sizes')) { - $entry->removeAttribute('sizes'); - } - } - - $entry->setAttribute('src', $src); - } - - if ($entry->nodeName == 'img') { - - if ($entry->hasAttribute('src')) { - $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME) === 'https'; - - if ($ttrss_uses_https && !$is_https_url) { - - if ($entry->hasAttribute('srcset')) { - $entry->removeAttribute('srcset'); - } - - if ($entry->hasAttribute('sizes')) { - $entry->removeAttribute('sizes'); - } - } - } - - if (($owner && get_pref("STRIP_IMAGES", $owner)) || - $force_remove_images || $_SESSION["bw_limit"]) { - - $p = $doc->createElement('p'); - - $a = $doc->createElement('a'); - $a->setAttribute('href', $entry->getAttribute('src')); - - $a->appendChild(new DOMText($entry->getAttribute('src'))); - $a->setAttribute('target', '_blank'); - $a->setAttribute('rel', 'noopener noreferrer'); - - $p->appendChild($a); - - $entry->parentNode->replaceChild($p, $entry); - } - } - - if (strtolower($entry->nodeName) == "a") { - $entry->setAttribute("target", "_blank"); - $entry->setAttribute("rel", "noopener noreferrer"); - } - } - - $entries = $xpath->query('//iframe'); - foreach ($entries as $entry) { - if (!iframe_whitelisted($entry)) { - $entry->setAttribute('sandbox', 'allow-scripts'); - } else { - if ($_SERVER['HTTPS'] == "on") { - $entry->setAttribute("src", - str_replace("http://", "https://", - $entry->getAttribute("src"))); - } - } - } - - $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside', - 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', - 'caption', 'cite', 'center', 'code', 'col', 'colgroup', - 'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font', - 'dt', 'em', 'footer', 'figure', 'figcaption', - 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i', - 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript', - 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section', - 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary', - 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time', - 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' ); - - if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe'; - - $disallowed_attributes = array('id', 'style', 'class'); - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) { - $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id); - if (is_array($retval)) { - $doc = $retval[0]; - $allowed_elements = $retval[1]; - $disallowed_attributes = $retval[2]; - } else { - $doc = $retval; - } - } - - $doc->removeChild($doc->firstChild); //remove doctype - $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes); - - if ($highlight_words) { - foreach ($highlight_words as $word) { - - // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph - - $elements = $xpath->query("//*/text()"); - - foreach ($elements as $child) { - - $fragment = $doc->createDocumentFragment(); - $text = $child->textContent; - - while (($pos = mb_stripos($text, $word)) !== false) { - $fragment->appendChild(new DomText(mb_substr($text, 0, $pos))); - $word = mb_substr($text, $pos, mb_strlen($word)); - $highlight = $doc->createElement('span'); - $highlight->appendChild(new DomText($word)); - $highlight->setAttribute('class', 'highlight'); - $fragment->appendChild($highlight); - $text = mb_substr($text, $pos + mb_strlen($word)); - } - - if (!empty($text)) $fragment->appendChild(new DomText($text)); - - $child->parentNode->replaceChild($fragment, $child); - } - } - } - - $res = $doc->saveHTML(); - - /* strip everything outside of ... */ - - $res_frag = array(); - if (preg_match('/(.*)<\/body>/is', $res, $res_frag)) { - return $res_frag[1]; - } else { - return $res; - } - } - - function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) { - $xpath = new DOMXPath($doc); - $entries = $xpath->query('//*'); - - foreach ($entries as $entry) { - if (!in_array($entry->nodeName, $allowed_elements)) { - $entry->parentNode->removeChild($entry); - } - - if ($entry->hasAttributes()) { - $attrs_to_remove = array(); - - foreach ($entry->attributes as $attr) { - - if (strpos($attr->nodeName, 'on') === 0) { - array_push($attrs_to_remove, $attr); - } - - if ($attr->nodeName == 'href' && stripos($attr->value, 'javascript:') === 0) { - array_push($attrs_to_remove, $attr); - } - - if (in_array($attr->nodeName, $disallowed_attributes)) { - array_push($attrs_to_remove, $attr); - } - } - - foreach ($attrs_to_remove as $attr) { - $entry->removeAttributeNode($attr); - } - } - } - - return $doc; - } - - function catchupArticlesById($ids, $cmode, $owner_uid = false) { - - if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - if (count($ids) == 0) return; - - $tmp_ids = array(); - - foreach ($ids as $id) { - array_push($tmp_ids, "ref_id = '$id'"); - } - - $ids_qpart = join(" OR ", $tmp_ids); - - if ($cmode == 0) { - db_query("UPDATE ttrss_user_entries SET - unread = false,last_read = NOW() - WHERE ($ids_qpart) AND owner_uid = $owner_uid"); - } else if ($cmode == 1) { - db_query("UPDATE ttrss_user_entries SET - unread = true - WHERE ($ids_qpart) AND owner_uid = $owner_uid"); - } else { - db_query("UPDATE ttrss_user_entries SET - unread = NOT unread,last_read = NOW() - WHERE ($ids_qpart) AND owner_uid = $owner_uid"); - } - - /* update ccache */ - - $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries - WHERE ($ids_qpart) AND owner_uid = $owner_uid"); - - while ($line = db_fetch_assoc($result)) { - ccache_update($line["feed_id"], $owner_uid); - } - } - - function trim_array($array) { - $tmp = $array; - array_walk($tmp, 'trim'); - return $tmp; - } - - function tag_is_valid($tag) { - if ($tag == '') return false; - if (is_numeric($tag)) return false; - if (mb_strlen($tag) > 250) return false; - - if (!$tag) return false; - - return true; - } - - function render_login_form() { - header('Cache-Control: public'); - - require_once "login_form.php"; - exit; - } - - function T_sprintf() { - $args = func_get_args(); - return vsprintf(__(array_shift($args)), $args); - } - - function print_checkpoint($n, $s) { - $ts = microtime(true); - echo sprintf("\n", $ts - $s); - return $ts; - } - - function sanitize_tag($tag) { - $tag = trim($tag); - - $tag = mb_strtolower($tag, 'utf-8'); - - $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag); - - if (DB_TYPE == "mysql") { - $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag); - } - - return $tag; - } - - function get_self_url_prefix() { - if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) { - return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1); - } else { - return SELF_URL_PATH; - } - } - - /** - * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI. - * - * @return string The Mozilla Firefox feed adding URL. - */ - function add_feed_url() { - //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH); - - $url_path = get_self_url_prefix() . - "/public.php?op=subscribe&feed_url=%s"; - return $url_path; - } // function add_feed_url - - function encrypt_password($pass, $salt = '', $mode2 = false) { - if ($salt && $mode2) { - return "MODE2:" . hash('sha256', $salt . $pass); - } else if ($salt) { - return "SHA1X:" . sha1("$salt:$pass"); - } else { - return "SHA1:" . sha1($pass); - } - } // function encrypt_password - - function load_filters($feed_id, $owner_uid) { - $filters = array(); - - $cat_id = (int)getFeedCategory($feed_id); - - if ($cat_id == 0) - $null_cat_qpart = "cat_id IS NULL OR"; - else - $null_cat_qpart = ""; - - $result = db_query("SELECT * FROM ttrss_filters2 WHERE - owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title"); - - $check_cats = join(",", array_merge( - getParentCategories($cat_id, $owner_uid), - array($cat_id))); - - while ($line = db_fetch_assoc($result)) { - $filter_id = $line["id"]; - - $result2 = db_query("SELECT - r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name - FROM ttrss_filters2_rules AS r, - ttrss_filter_types AS t - WHERE - ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND - (feed_id IS NULL OR feed_id = '$feed_id') AND - filter_type = t.id AND filter_id = '$filter_id'"); - - $rules = array(); - $actions = array(); - - while ($rule_line = db_fetch_assoc($result2)) { -# print_r($rule_line); - - $rule = array(); - $rule["reg_exp"] = $rule_line["reg_exp"]; - $rule["type"] = $rule_line["type_name"]; - $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]); - - array_push($rules, $rule); - } - - $result2 = db_query("SELECT a.action_param,t.name AS type_name - FROM ttrss_filters2_actions AS a, - ttrss_filter_actions AS t - WHERE - action_id = t.id AND filter_id = '$filter_id'"); - - while ($action_line = db_fetch_assoc($result2)) { -# print_r($action_line); - - $action = array(); - $action["type"] = $action_line["type_name"]; - $action["param"] = $action_line["action_param"]; - - array_push($actions, $action); - } - - - $filter = array(); - $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]); - $filter["inverse"] = sql_bool_to_bool($line["inverse"]); - $filter["rules"] = $rules; - $filter["actions"] = $actions; - - if (count($rules) > 0 && count($actions) > 0) { - array_push($filters, $filter); - } - } - - return $filters; - } - - function get_score_pic($score) { - if ($score > 100) { - return "score_high.png"; - } else if ($score > 0) { - return "score_half_high.png"; - } else if ($score < -100) { - return "score_low.png"; - } else if ($score < 0) { - return "score_half_low.png"; - } else { - return "score_neutral.png"; - } - } - - function feed_has_icon($id) { - return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0; - } - - function init_plugins() { - PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL); - - return true; - } - - function add_feed_category($feed_cat, $parent_cat_id = false) { - - if (!$feed_cat) return false; - - db_query("BEGIN"); - - if ($parent_cat_id) { - $parent_qpart = "parent_cat = '$parent_cat_id'"; - $parent_insert = "'$parent_cat_id'"; - } else { - $parent_qpart = "parent_cat IS NULL"; - $parent_insert = "NULL"; - } - - $feed_cat = mb_substr($feed_cat, 0, 250); - - $result = db_query( - "SELECT id FROM ttrss_feed_categories - WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]); - - if (db_num_rows($result) == 0) { - - $result = db_query( - "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat) - VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)"); - - db_query("COMMIT"); - - return true; - } - - return false; - } - - /** - * Fixes incomplete URLs by prepending "http://". - * Also replaces feed:// with http://, and - * prepends a trailing slash if the url is a domain name only. - * - * @param string $url Possibly incomplete URL - * - * @return string Fixed URL. - */ - function fix_url($url) { - - // support schema-less urls - if (strpos($url, '//') === 0) { - $url = 'https:' . $url; - } - - if (strpos($url, '://') === false) { - $url = 'http://' . $url; - } else if (substr($url, 0, 5) == 'feed:') { - $url = 'http:' . substr($url, 5); - } - - //prepend slash if the URL has no slash in it - // "http://www.example" -> "http://www.example/" - if (strpos($url, '/', strpos($url, ':') + 3) === false) { - $url .= '/'; - } - - //convert IDNA hostname to punycode if possible - if (function_exists("idn_to_ascii")) { - $parts = parse_url($url); - if (mb_detect_encoding($parts['host']) != 'ASCII') - { - $parts['host'] = idn_to_ascii($parts['host']); - $url = build_url($parts); - } - } - - if ($url != "http:///") - return $url; - else - return ''; - } - - function validate_feed_url($url) { - $parts = parse_url($url); - - return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https'); - - } - - /* function save_email_address($email) { - // FIXME: implement persistent storage of emails - - if (!$_SESSION['stored_emails']) - $_SESSION['stored_emails'] = array(); - - if (!in_array($email, $_SESSION['stored_emails'])) - array_push($_SESSION['stored_emails'], $email); - } */ - - - function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) { - - if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - - $sql_is_cat = bool_to_sql_bool($is_cat); - - $result = db_query("SELECT access_key FROM ttrss_access_keys - WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat - AND owner_uid = " . $owner_uid); - - if (db_num_rows($result) == 1) { - return db_fetch_result($result, 0, "access_key"); - } else { - $key = db_escape_string(uniqid_short()); - - $result = db_query("INSERT INTO ttrss_access_keys - (access_key, feed_id, is_cat, owner_uid) - VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')"); - - return $key; - } - return false; - } - - function get_feeds_from_html($url, $content) - { - $url = fix_url($url); - $baseUrl = substr($url, 0, strrpos($url, '/') + 1); - - libxml_use_internal_errors(true); - - $doc = new DOMDocument(); - $doc->loadHTML($content); - $xpath = new DOMXPath($doc); - $entries = $xpath->query('/html/head/link[@rel="alternate" and '. - '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]'); - $feedUrls = array(); - foreach ($entries as $entry) { - if ($entry->hasAttribute('href')) { - $title = $entry->getAttribute('title'); - if ($title == '') { - $title = $entry->getAttribute('type'); - } - $feedUrl = rewrite_relative_url( - $baseUrl, $entry->getAttribute('href') - ); - $feedUrls[$feedUrl] = $title; - } - } - return $feedUrls; - } - - function is_html($content) { - return preg_match("/ 0) { - $limit_part = 500; - - $query = "SELECT ttrss_tags.id AS id - FROM ttrss_tags, ttrss_user_entries, ttrss_entries - WHERE post_int_id = int_id AND $interval_query AND - ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part"; - - $result = db_query($query); - - $ids = array(); - - while ($line = db_fetch_assoc($result)) { - array_push($ids, $line['id']); - } - - if (count($ids) > 0) { - $ids = join(",", $ids); - - $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)"); - $tags_deleted += db_affected_rows($tmp_result); - } else { - break; - } - - $limit -= $limit_part; - } - - return $tags_deleted; - } - - function print_user_stylesheet() { - $value = get_pref('USER_STYLESHEET'); - - if ($value) { - print ""; - } - - } - - function filter_to_sql($filter, $owner_uid) { - $query = array(); - - if (DB_TYPE == "pgsql") - $reg_qpart = "~"; - else - $reg_qpart = "REGEXP"; - - foreach ($filter["rules"] AS $rule) { - $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]); - $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/', - $rule['reg_exp']) !== FALSE; - - if ($regexp_valid) { - - $rule['reg_exp'] = db_escape_string($rule['reg_exp']); - - switch ($rule["type"]) { - case "title": - $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('". - $rule['reg_exp'] . "')"; - break; - case "content": - $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('". - $rule['reg_exp'] . "')"; - break; - case "both": - $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('". - $rule['reg_exp'] . "') OR LOWER(" . - "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')"; - break; - case "tag": - $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('". - $rule['reg_exp'] . "')"; - break; - case "link": - $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('". - $rule['reg_exp'] . "')"; - break; - case "author": - $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('". - $rule['reg_exp'] . "')"; - break; - } - - if (isset($rule['inverse'])) $qpart = "NOT ($qpart)"; - - if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) { - $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]); - } - - if (isset($rule["cat_id"])) { - - if ($rule["cat_id"] > 0) { - $children = getChildCategories($rule["cat_id"], $owner_uid); - array_push($children, $rule["cat_id"]); - - $children = join(",", $children); - - $cat_qpart = "cat_id IN ($children)"; - } else { - $cat_qpart = "cat_id IS NULL"; - } - - $qpart .= " AND $cat_qpart"; - } - - $qpart .= " AND feed_id IS NOT NULL"; - - array_push($query, "($qpart)"); - - } - } - - if (count($query) > 0) { - $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")"; - } else { - $fullquery = "(false)"; - } - - if ($filter['inverse']) $fullquery = "(NOT $fullquery)"; - - return $fullquery; - } - - if (!function_exists('gzdecode')) { - function gzdecode($string) { // no support for 2nd argument - return file_get_contents('compress.zlib://data:who/cares;base64,'. - base64_encode($string)); - } - } - - function get_random_bytes($length) { - if (function_exists('openssl_random_pseudo_bytes')) { - return openssl_random_pseudo_bytes($length); - } else { - $output = ""; - - for ($i = 0; $i < $length; $i++) - $output .= chr(mt_rand(0, 255)); - - return $output; - } - } - - function read_stdin() { - $fp = fopen("php://stdin", "r"); - - if ($fp) { - $line = trim(fgets($fp)); - fclose($fp); - return $line; - } - - return null; - } - - function getFeedCategory($feed) { - $result = db_query("SELECT cat_id FROM ttrss_feeds - WHERE id = '$feed'"); - - if (db_num_rows($result) > 0) { - return db_fetch_result($result, 0, "cat_id"); - } else { - return false; - } - - } - - function implements_interface($class, $interface) { - return in_array($interface, class_implements($class)); - } - - function get_minified_js($files) { - require_once 'lib/jshrink/Minifier.php'; - - $rv = ''; - - foreach ($files as $js) { - if (!isset($_GET['debug'])) { - $cached_file = CACHE_DIR . "/js/".basename($js).".js"; - - if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) { - - list($header, $contents) = explode("\n", file_get_contents($cached_file), 2); - - if ($header && $contents) { - list($htag, $hversion) = explode(":", $header); - - if ($htag == "tt-rss" && $hversion == VERSION) { - $rv .= $contents; - continue; - } - } - } - - $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js")); - file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified); - $rv .= $minified; - - } else { - $rv .= file_get_contents("js/$js.js"); // no cache in debug mode - } - } - - return $rv; - } - - function calculate_dep_timestamp() { - $files = array_merge(glob("js/*.js"), glob("css/*.css")); - - $max_ts = -1; - - foreach ($files as $file) { - if (filemtime($file) > $max_ts) $max_ts = filemtime($file); - } - - return $max_ts; - } - - function T_js_decl($s1, $s2) { - if ($s1 && $s2) { - $s1 = preg_replace("/\n/", "", $s1); - $s2 = preg_replace("/\n/", "", $s2); - - $s1 = preg_replace("/\"/", "\\\"", $s1); - $s2 = preg_replace("/\"/", "\\\"", $s2); - - return "T_messages[\"$s1\"] = \"$s2\";\n"; - } - } - - function init_js_translations() { - - print 'var T_messages = new Object(); - - function __(msg) { - if (T_messages[msg]) { - return T_messages[msg]; - } else { - return msg; - } - } - - function ngettext(msg1, msg2, n) { - return __((parseInt(n) > 1) ? msg2 : msg1); - }'; - - $l10n = _get_reader(); - - for ($i = 0; $i < $l10n->total; $i++) { - $orig = $l10n->get_original_string($i); - if(strpos($orig, "\000") !== FALSE) { // Plural forms - $key = explode(chr(0), $orig); - print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular - print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural - } else { - $translation = __($orig); - print T_js_decl($orig, $translation); - } - } - } - - function label_to_feed_id($label) { - return LABEL_BASE_INDEX - 1 - abs($label); - } - - function feed_to_label_id($feed) { - return LABEL_BASE_INDEX - 1 + abs($feed); - } - - function get_theme_path($theme) { - $check = "themes/$theme"; - if (file_exists($check)) return $check; - - $check = "themes.local/$theme"; - if (file_exists($check)) return $check; - } - - function theme_valid($theme) { - $bundled_themes = [ "default.php", "night.css", "compact.css" ]; - - if (in_array($theme, $bundled_themes)) return true; - - $file = "themes/" . basename($theme); - - if (!file_exists($file)) $file = "themes.local/" . basename($theme); - - if (file_exists($file) && is_readable($file)) { - $fh = fopen($file, "r"); - - if ($fh) { - $header = fgets($fh); - fclose($fh); - - return strpos($header, "supports-version:" . VERSION_STATIC) !== FALSE; - } - } - - return false; - } - - /** - * @SuppressWarnings(unused) - */ - function error_json($code) { - require_once "errors.php"; - - @$message = $ERRORS[$code]; - - return json_encode(array("error" => - array("code" => $code, "message" => $message))); - - } - - function abs_to_rel_path($dir) { - $tmp = str_replace(dirname(__DIR__), "", $dir); - - if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1); - - return $tmp; - } - - function get_upload_error_message($code) { - - $errors = array( - 0 => __('There is no error, the file uploaded with success'), - 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'), - 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'), - 3 => __('The uploaded file was only partially uploaded'), - 4 => __('No file was uploaded'), - 6 => __('Missing a temporary folder'), - 7 => __('Failed to write file to disk.'), - 8 => __('A PHP extension stopped the file upload.'), - ); - - return $errors[$code]; - } - - function base64_img($filename) { - if (file_exists($filename)) { - $ext = pathinfo($filename, PATHINFO_EXTENSION); - - return "data:image/$ext;base64," . base64_encode(file_get_contents($filename)); - } else { - return ""; - } - } - diff --git a/plugins/vf_shared/init.php b/plugins/vf_shared/init.php index 9caf1093e..ce18f92d5 100644 --- a/plugins/vf_shared/init.php +++ b/plugins/vf_shared/init.php @@ -38,28 +38,10 @@ class VF_Shared extends Plugin { return db_fetch_result($result, 0, "count"); } - //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) { - /** * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ function get_headlines($feed_id, $options) { - /*$qfh_ret = queryFeedHeadlines(-4, - $options['limit'], - $this->get_unread(-1) > 0 ? "adaptive" : "all_articles", - false, - $options['search'], - $options['search_mode'], - $options['override_order'], - $options['offset'], - $options['owner_uid'], - $options['filter'], - $options['since_id'], - $options['include_children'], - false, - "uuid != ''", - "ttrss_feeds.title AS feed_title,"); */ - $params = array( "feed" => -4, "limit" => $options["limit"], @@ -74,7 +56,7 @@ class VF_Shared extends Plugin { "override_vfeed" => "ttrss_feeds.title AS feed_title," ); - $qfh_ret = queryFeedHeadlines($params); + $qfh_ret = Feeds::queryFeedHeadlines($params); $qfh_ret[1] = __("Shared articles"); return $qfh_ret;