Преглед изворни кода

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
Andrew Dolgov пре 7 година
родитељ
комит
aeb1abedb2
9 измењених фајлова са 1902 додато и 1956 уклоњено
  1. 1 10
      classes/api.php
  2. 47 0
      classes/article.php
  3. 441 10
      classes/feeds.php
  4. 2 17
      classes/handler/public.php
  5. 2 2
      classes/rpc.php
  6. 1 1
      include/digest.php
  7. 1407 2
      include/functions.php
  8. 0 1895
      include/functions2.php
  9. 1 19
      plugins/vf_shared/init.php

+ 1 - 10
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];

+ 47 - 0
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;
+		}
+	}
 
 }

+ 441 - 10
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;
+	}
+
 }
 

+ 2 - 17
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]);

+ 2 - 2
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));
 	}

+ 1 - 1
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");

+ 1407 - 2
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 = '<head>
+				<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+			</head>';
+
+		$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 <body>...</body> */
+
+		$res_frag = array();
+		if (preg_match('/<body>(.*)<\/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("<!-- CP[$n] %.4f seconds -->\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("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
+	}
+
+	function url_is_html($url, $login = false, $pass = false) {
+		return is_html(fetch_file_contents($url, false, $login, $pass));
+	}
+
+	function build_url($parts) {
+		return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
+	}
+
+	function cleanup_url_path($path) {
+		$path = str_replace("/./", "/", $path);
+		$path = str_replace("//", "/", $path);
+
+		return $path;
+	}
+
+	/**
+	 * Converts a (possibly) relative URL to a absolute one.
+	 *
+	 * @param string $url     Base URL (i.e. from where the document is)
+	 * @param string $rel_url Possibly relative URL in the document
+	 *
+	 * @return string Absolute URL
+	 */
+	function rewrite_relative_url($url, $rel_url) {
+		if (strpos($rel_url, "://") !== false) {
+			return $rel_url;
+		} else if (strpos($rel_url, "//") === 0) {
+			# protocol-relative URL (rare but they exist)
+			return $rel_url;
+		} else if (preg_match("/^[a-z]+:/i", $rel_url)) {
+			# magnet:, feed:, etc
+			return $rel_url;
+		} else if (strpos($rel_url, "/") === 0) {
+			$parts = parse_url($url);
+			$parts['path'] = $rel_url;
+			$parts['path'] = cleanup_url_path($parts['path']);
+
+			return build_url($parts);
+
+		} else {
+			$parts = parse_url($url);
+			if (!isset($parts['path'])) {
+				$parts['path'] = '/';
+			}
+			$dir = $parts['path'];
+			if (substr($dir, -1) !== '/') {
+				$dir = dirname($parts['path']);
+				$dir !== '/' && $dir .= '/';
+			}
+			$parts['path'] = $dir . $rel_url;
+			$parts['path'] = cleanup_url_path($parts['path']);
+
+			return build_url($parts);
+		}
+	}
+
+	function cleanup_tags($days = 14, $limit = 1000) {
+
+		if (DB_TYPE == "pgsql") {
+			$interval_query = "date_updated < NOW() - INTERVAL '$days days'";
+		} else if (DB_TYPE == "mysql") {
+			$interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
+		}
+
+		$tags_deleted = 0;
+
+		while ($limit > 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 "<style type=\"text/css\">";
+			print str_replace("<br/>", "\n", $value);
+			print "</style>";
+		}
+
+	}
+
+	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 "";
+		}
+	}
+

+ 0 - 1895
include/functions2.php

@@ -1,1895 +0,0 @@
-<?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'] = 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 = '<head>
-			<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-		</head>';
-
-		$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 <body>...</body> */
-
-		$res_frag = array();
-		if (preg_match('/<body>(.*)<\/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("<!-- CP[$n] %.4f seconds -->\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("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
-	}
-
-	function url_is_html($url, $login = false, $pass = false) {
-		return is_html(fetch_file_contents($url, false, $login, $pass));
-	}
-
-	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;
-		}
-	}
-
-	function build_url($parts) {
-		return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
-	}
-
-	function cleanup_url_path($path) {
-		$path = str_replace("/./", "/", $path);
-		$path = str_replace("//", "/", $path);
-
-		return $path;
-	}
-
-	/**
-	 * Converts a (possibly) relative URL to a absolute one.
-	 *
-	 * @param string $url     Base URL (i.e. from where the document is)
-	 * @param string $rel_url Possibly relative URL in the document
-	 *
-	 * @return string Absolute URL
-	 */
-	function rewrite_relative_url($url, $rel_url) {
-		if (strpos($rel_url, "://") !== false) {
-			return $rel_url;
-		} else if (strpos($rel_url, "//") === 0) {
-			# protocol-relative URL (rare but they exist)
-			return $rel_url;
-		} else if (preg_match("/^[a-z]+:/i", $rel_url)) {
-			# magnet:, feed:, etc
-			return $rel_url;
-		} else if (strpos($rel_url, "/") === 0) {
-			$parts = parse_url($url);
-			$parts['path'] = $rel_url;
-			$parts['path'] = cleanup_url_path($parts['path']);
-
-			return build_url($parts);
-
-		} else {
-			$parts = parse_url($url);
-			if (!isset($parts['path'])) {
-				$parts['path'] = '/';
-			}
-			$dir = $parts['path'];
-			if (substr($dir, -1) !== '/') {
-				$dir = dirname($parts['path']);
-				$dir !== '/' && $dir .= '/';
-			}
-			$parts['path'] = $dir . $rel_url;
-			$parts['path'] = cleanup_url_path($parts['path']);
-
-			return build_url($parts);
-		}
-	}
-
-	function cleanup_tags($days = 14, $limit = 1000) {
-
-		if (DB_TYPE == "pgsql") {
-			$interval_query = "date_updated < NOW() - INTERVAL '$days days'";
-		} else if (DB_TYPE == "mysql") {
-			$interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
-		}
-
-		$tags_deleted = 0;
-
-		while ($limit > 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 "<style type=\"text/css\">";
-			print str_replace("<br/>", "\n", $value);
-			print "</style>";
-		}
-
-	}
-
-	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 "";
-		}
-	}
-

+ 1 - 19
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;