query("SELECT cfgname FROM pg_ts_config");
			while ($row = $res->fetch()) {
				array_push($rv, ucfirst($row['cfgname']));
			}
		}
		return $rv;
	}
	function batch_edit_cbox($elem, $label = false) {
		print "
        		';
			$title = htmlspecialchars($row["title"]);
			print_hidden("id", "$feed_id");
			print_hidden("op", "pref-feeds");
			print_hidden("method", "editSave");
			print "
";
			print "
";
			print "
";
			print "
";
			/* Update Interval */
			$update_interval = $row["update_interval"];
			print "";
			print "".__("Interval:")."  ";
			print_select_hash("update_interval", $update_interval, $update_intervals,
				'dojoType="dijit.form.Select"');
			print " ";
			/* Purge intl */
			$purge_interval = $row["purge_interval"];
			print "";
			print "" . __('Article purging:') . "  ";
			print_select_hash("purge_interval", $purge_interval, $purge_intervals,
				'dojoType="dijit.form.Select" ' .
				((FORCE_ARTICLE_PURGE == 0) ? "" : 'disabled="1"'));
			print " ";
			print " ";
			$auth_login = htmlspecialchars($row["auth_login"]);
			$auth_pass = htmlspecialchars($row["auth_pass"]);
			$auth_enabled = $auth_login !== '' || $auth_pass !== '';
			$auth_style = $auth_enabled ? '' : 'display: none';
			print "
";
			$auth_checked = $auth_enabled ? 'checked' : '';
			print "
				 ";
			print '
';
			print "
';
			/* Icon */
			print "
";
			print "
";
			print "";
			print '
';
			PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_EDIT_FEED,
				"hook_prefs_edit_feed", $feed_id);
			print "
 ";
			$title = htmlspecialchars($title, ENT_QUOTES);
			print "
				".
					__('Unsubscribe')." 
				".__('Save')." 
				".__('Cancel')." 
				 ";
		}
	}
	function editfeeds() {
		global $purge_intervals;
		global $update_intervals;
		$feed_ids = clean($_REQUEST["ids"]);
		print_notice("Enable the options you wish to apply using checkboxes on the right:");
		print "";
		print_hidden("ids", "$feed_ids");
		print_hidden("op", "pref-feeds");
		print_hidden("method", "batchEditSave");
		print "";
		print "";
		/* Category */
		if (get_pref('ENABLE_FEED_CATS')) {
			print "";
			print "" . __('Place in category:') . "  ";
			print_feed_cat_select("cat_id", false,
				'disabled="1" dojoType="dijit.form.Select"');
			$this->batch_edit_cbox("cat_id");
			print " ";
		}
		/* FTS Stemming Language */
		if (DB_TYPE == "pgsql") {
			print "";
			print "" . __('Language:') . "  ";
			print_select("feed_language", "", $this::get_ts_languages(),
				'disabled="1" dojoType="dijit.form.Select"');
			$this->batch_edit_cbox("feed_language");
			print " ";
		}
		print " ";
		print "";
		print "";
		/* Update Interval */
		print "";
		print "".__("Interval:")."  ";
		print_select_hash("update_interval", "", $update_intervals,
			'disabled="1" dojoType="dijit.form.Select"');
		$this->batch_edit_cbox("update_interval");
		print " ";
		/* Purge intl */
		if (FORCE_ARTICLE_PURGE == 0) {
			print "";
			print "" . __('Article purging:') . "  ";
			print_select_hash("purge_interval", "", $purge_intervals,
				'disabled="1" dojoType="dijit.form.Select"');
			$this->batch_edit_cbox("purge_interval");
			print " ";
		}
		print " ";
		print "";
		print "";
		print "";
		print "";
		print "
			".
				__('Save')." 
			".
				__('Cancel')." 
			 ";
		return;
	}
	function batchEditSave() {
		return $this->editsaveops(true);
	}
	function editSave() {
		return $this->editsaveops(false);
	}
	function editsaveops($batch) {
		$feed_title = trim(clean($_POST["title"]));
		$feed_url = trim(clean($_POST["feed_url"]));
		$site_url = trim(clean($_POST["site_url"]));
		$upd_intl = (int) clean($_POST["update_interval"]);
		$purge_intl = (int) clean($_POST["purge_interval"]);
		$feed_id = (int) clean($_POST["id"]); /* editSave */
		$feed_ids = explode(",", clean($_POST["ids"])); /* batchEditSave */
		$cat_id = (int) clean($_POST["cat_id"]);
		$auth_login = trim(clean($_POST["auth_login"]));
		$auth_pass = trim(clean($_POST["auth_pass"]));
		$private = checkbox_to_sql_bool(clean($_POST["private"]));
		$include_in_digest = checkbox_to_sql_bool(
			clean($_POST["include_in_digest"]));
		$cache_images = checkbox_to_sql_bool(
			clean($_POST["cache_images"]));
		$hide_images = checkbox_to_sql_bool(
			clean($_POST["hide_images"]));
		$always_display_enclosures = checkbox_to_sql_bool(
			clean($_POST["always_display_enclosures"]));
		$mark_unread_on_update = checkbox_to_sql_bool(
			clean($_POST["mark_unread_on_update"]));
		$feed_language = trim(clean($_POST["feed_language"]));
		if (!$batch) {
			if (clean($_POST["need_auth"]) !== 'on') {
				$auth_login = '';
				$auth_pass = '';
			}
			/* $sth = $this->pdo->prepare("SELECT feed_url FROM ttrss_feeds WHERE id = ?");
			$sth->execute([$feed_id]);
			$row = $sth->fetch();$orig_feed_url = $row["feed_url"];
			$reset_basic_info = $orig_feed_url != $feed_url; */
			$sth = $this->pdo->prepare("UPDATE ttrss_feeds SET
				cat_id = :cat_id,
				title = :title,
				feed_url = :feed_url,
				site_url = :site_url,
				update_interval = :upd_intl,
				purge_interval = :purge_intl,
				auth_login = :auth_login,
				auth_pass = :auth_pass,
				auth_pass_encrypted = false,
				private = :private,
				cache_images = :cache_images,
				hide_images = :hide_images,
				include_in_digest = :include_in_digest,
				always_display_enclosures = :always_display_enclosures,
				mark_unread_on_update = :mark_unread_on_update,
				feed_language = :feed_language
			WHERE id = :id AND owner_uid = :uid");
			$sth->execute([":title" => $feed_title,
					":cat_id" => $cat_id ? $cat_id : null,
					":feed_url" => $feed_url,
					":site_url" => $site_url,
					":upd_intl" => $upd_intl,
					":purge_intl" => $purge_intl,
					":auth_login" => $auth_login,
					":auth_pass" => $auth_pass,
					":private" => (int)$private,
					":cache_images" => (int)$cache_images,
					":hide_images" => (int)$hide_images,
					":include_in_digest" => (int)$include_in_digest,
					":always_display_enclosures" => (int)$always_display_enclosures,
					":mark_unread_on_update" => (int)$mark_unread_on_update,
					":feed_language" => $feed_language,
					":id" => $feed_id,
					":uid" => $_SESSION['uid']]);
/*			if ($reset_basic_info) {
				RSSUtils::set_basic_feed_info($feed_id);
			} */
			PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_SAVE_FEED,
				"hook_prefs_save_feed", $feed_id);
		} else {
			$feed_data = array();
			foreach (array_keys($_POST) as $k) {
				if ($k != "op" && $k != "method" && $k != "ids") {
					$feed_data[$k] = clean($_POST[$k]);
				}
			}
			$this->pdo->beginTransaction();
			$feed_ids_qmarks = arr_qmarks($feed_ids);
			foreach (array_keys($feed_data) as $k) {
				$qpart = "";
				switch ($k) {
					case "title":
						$qpart = "title = " . $this->pdo->quote($feed_title);
						break;
					case "feed_url":
						$qpart = "feed_url = " . $this->pdo->quote($feed_url);
						break;
					case "update_interval":
						$qpart = "update_interval = " . $this->pdo->quote($upd_intl);
						break;
					case "purge_interval":
						$qpart = "purge_interval =" . $this->pdo->quote($purge_intl);
						break;
					case "auth_login":
						$qpart = "auth_login = " . $this->pdo->quote($auth_login);
						break;
					case "auth_pass":
						$qpart = "auth_pass =" . $this->pdo->quote($auth_pass). ", auth_pass_encrypted = false";
						break;
					case "private":
						$qpart = "private = " . $this->pdo->quote($private);
						break;
					case "include_in_digest":
						$qpart = "include_in_digest = " . $this->pdo->quote($include_in_digest);
						break;
					case "always_display_enclosures":
						$qpart = "always_display_enclosures = " . $this->pdo->quote($always_display_enclosures);
						break;
					case "mark_unread_on_update":
						$qpart = "mark_unread_on_update = " . $this->pdo->quote($mark_unread_on_update);
						break;
					case "cache_images":
						$qpart = "cache_images = " . $this->pdo->quote($cache_images);
						break;
					case "hide_images":
						$qpart = "hide_images = " . $this->pdo->quote($hide_images);
						break;
					case "cat_id":
						if (get_pref('ENABLE_FEED_CATS')) {
							if ($cat_id) {
								$qpart = "cat_id = " . $this->pdo->quote($cat_id);
							} else {
								$qpart = 'cat_id = NULL';
							}
						} else {
							$qpart = "";
						}
						break;
					case "feed_language":
						$qpart = "feed_language = " . $this->pdo->quote($feed_language);
						break;
				}
				if ($qpart) {
					$sth = $this->pdo->prepare("UPDATE ttrss_feeds SET $qpart WHERE id IN ($feed_ids_qmarks)
						AND owner_uid = ?");
					$sth->execute(array_merge($feed_ids, [$_SESSION['uid']]));
				}
			}
			$this->pdo->commit();
		}
		return;
	}
	function remove() {
		$ids = explode(",", clean($_REQUEST["ids"]));
		foreach ($ids as $id) {
			Pref_Feeds::remove_feed($id, $_SESSION["uid"]);
		}
		return;
	}
	function removeCat() {
		$ids = explode(",", clean($_REQUEST["ids"]));
		foreach ($ids as $id) {
			$this->remove_feed_category($id, $_SESSION["uid"]);
		}
	}
	function addCat() {
		$feed_cat = trim(clean($_REQUEST["cat"]));
		add_feed_category($feed_cat);
	}
	function index() {
		print "
";
		print "
rss_feed ".__('Feeds')."\">";
		$sth = $this->pdo->prepare("SELECT COUNT(id) AS num_errors
			FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?");
		$sth->execute([$_SESSION['uid']]);
		if ($row = $sth->fetch()) {
			$num_errors = $row["num_errors"];
		} else {
			$num_errors = 0;
		}
		if ($num_errors > 0) {
			$error_button = "
" .
				__("Feeds with errors") . " ";
		}
		$inactive_button = "
" .
				__("Inactive feeds") . " ";
		$feed_search = clean($_REQUEST["search"]);
		if (array_key_exists("search", $_REQUEST)) {
			$_SESSION["prefs_feed_search"] = $feed_search;
		} else {
			$feed_search = $_SESSION["prefs_feed_search"];
		}
		print '
';
		print "
"; #toolbar
		print "
			".
				__('Search')." 
			
";
		print "
".
				"
" . __('Select')." ";
		print "
";
		print "
".__('All')."
";
		print "
".__('None')."
";
		print "
";
		print "
".
				"
" . __('Feeds')." ";
		print "
";
		print "
".__('Subscribe to feed')."
";
		print "
".__('Edit selected feeds')."
";
		print "
".__('Reset sort order')."
";
		print "
".__('Batch subscribe')."
";
		print "
"
			.__('Unsubscribe')."
 ";
		print "
";
		if (get_pref('ENABLE_FEED_CATS')) {
			print "
".
					"
" . __('Categories')." ";
			print "
";
			print "
".__('Add category')."
";
			print "
".__('Reset sort order')."
";
			print "
".__('Remove selected')."
";
			print "
";
		}
		print $error_button;
		print $inactive_button;
		print "
"; # toolbar
		//print '
';
		print '
';
		print "
		".
		 __("Loading, please wait...")."
 ";
		$auto_expand = $feed_search != "" ? "true" : "false";
		print "
		
		
		
		
		
		
		
";
#		print "
#			".__('Hint:  you can drag feeds and categories around.')."
#			
";
		print '
';
		print '
';
		print "
";
		print "
" . __("Using OPML you can export and import your feeds, filters, labels and Agriget settings.") . " ";
		print_notice("Only main settings profile can be migrated using OPML.");
		print "
";
		print "
";
		print "
";
		print "
";
		print "
" . __("Published OPML") . " ";
		print "
" . __('Your OPML can be published publicly and can be subscribed by anyone who knows the URL below.') .
			" " .
			__("Published OPML does not include your Agriget settings, feeds that require authentication or feeds hidden from Popular feeds.") . "
";
		print "
".
			__('Display published OPML URL')."  ";
		PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION,
			"hook_prefs_tab_section", "prefFeedsOPML");
		print "
share ".__('Published & shared articles / Generated feeds')."\">";
		print "
" . __('Published articles can be subscribed by anyone who knows the following URL:') . " ";
		$rss_url = '-2::' . htmlspecialchars(get_self_url_prefix() .
				"/public.php?op=rss&id=-2&view-mode=all_articles");;
		print "".
			__('Display URL')."  ";
		print "".
			__('Clear all generated URLs')."  ";
		PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION,
			"hook_prefs_tab_section", "prefFeedsPublishedGenerated");
		print ""; #pane
		PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB,
			"hook_prefs_tab", "prefFeeds");
		print ""; #container
	}
	private function feedlist_init_cat($cat_id) {
		$obj = array();
		$cat_id = (int) $cat_id;
		if ($cat_id > 0) {
			$cat_unread = CCache::find($cat_id, $_SESSION["uid"], true);
		} else if ($cat_id == 0 || $cat_id == -2) {
			$cat_unread = Feeds::getCategoryUnread($cat_id);
		}
		$obj['id'] = 'CAT:' . $cat_id;
		$obj['items'] = array();
		$obj['name'] = Feeds::getCategoryTitle($cat_id);
		$obj['type'] = 'category';
		$obj['unread'] = (int) $cat_unread;
		$obj['bare_id'] = $cat_id;
		return $obj;
	}
	private function feedlist_init_feed($feed_id, $title = false, $unread = false, $error = '', $updated = '') {
		$obj = array();
		$feed_id = (int) $feed_id;
		if (!$title)
			$title = Feeds::getFeedTitle($feed_id, false);
		if ($unread === false)
			$unread = getFeedUnread($feed_id, false);
		$obj['id'] = 'FEED:' . $feed_id;
		$obj['name'] = $title;
		$obj['unread'] = (int) $unread;
		$obj['type'] = 'feed';
		$obj['error'] = $error;
		$obj['updated'] = $updated;
		$obj['icon'] = Feeds::getFeedIcon($feed_id);
		$obj['bare_id'] = $feed_id;
		$obj['auxcounter'] = 0;
		return $obj;
	}
	function inactiveFeeds() {
		if (DB_TYPE == "pgsql") {
			$interval_qpart = "NOW() - INTERVAL '3 months'";
		} else {
			$interval_qpart = "DATE_SUB(NOW(), INTERVAL 3 MONTH)";
		}
		$sth = $this->pdo->prepare("SELECT ttrss_feeds.title, ttrss_feeds.site_url,
		  		ttrss_feeds.feed_url, ttrss_feeds.id, MAX(updated) AS last_article
			FROM ttrss_feeds, ttrss_entries, ttrss_user_entries WHERE
				(SELECT MAX(updated) FROM ttrss_entries, ttrss_user_entries WHERE
					ttrss_entries.id = ref_id AND
						ttrss_user_entries.feed_id = ttrss_feeds.id) < $interval_qpart
			AND ttrss_feeds.owner_uid = ? AND
				ttrss_user_entries.feed_id = ttrss_feeds.id AND
				ttrss_entries.id = ref_id
			GROUP BY ttrss_feeds.title, ttrss_feeds.id, ttrss_feeds.site_url, ttrss_feeds.feed_url
			ORDER BY last_article");
		$sth->execute([$_SESSION['uid']]);
		print "";
		print "
".
				"
" . __('Select')." ";
		print "
";
		print "
".__('All')."
";
		print "
".__('None')."
";
		print "
";
		print "
			"
			.__('Unsubscribe from selected feeds')." 
			"
			.__('Close this window')." 
			 ";
	}
	function feedsWithErrors() {
		$sth = $this->pdo->prepare("SELECT id,title,feed_url,last_error,site_url
			FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?");
		$sth->execute([$_SESSION['uid']]);
		print "";
		print "
".
				"
" . __('Select')." ";
		print "
";
		print "
".__('All')."
";
		print "
".__('None')."
";
		print "
";
		print "
";
		print ""
			.__('Unsubscribe from selected feeds')."  ";
		print "".
			__('Close this window')." ";
		print " ";
	}
	private function remove_feed_category($id, $owner_uid) {
		$sth = $this->pdo->prepare("DELETE FROM ttrss_feed_categories
			WHERE id = ? AND owner_uid = ?");
		$sth->execute([$id, $owner_uid]);
		CCache::remove($id, $owner_uid, true);
	}
	static function remove_feed($id, $owner_uid) {
		foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_UNSUBSCRIBE_FEED) as $p) {
			if (! $p->hook_unsubscribe_feed($id, $owner_uid)) {
                user_error("Feed $id (owner: $owner_uid) not removed due to plugin error (HOOK_UNSUBSCRIBE_FEED).", E_USER_WARNING);
                return;
			}
		}
		$pdo = Db::pdo();
		if ($id > 0) {
			$pdo->beginTransaction();
			/* save starred articles in Archived feed */
			/* prepare feed if necessary */
			$sth = $pdo->prepare("SELECT feed_url FROM ttrss_feeds WHERE id = ?
				AND owner_uid = ?");
			$sth->execute([$id, $owner_uid]);
			if ($row = $sth->fetch()) {
				$feed_url = $row["feed_url"];
				$sth = $pdo->prepare("SELECT id FROM ttrss_archived_feeds
					WHERE feed_url = ? AND owner_uid = ?");
				$sth->execute([$feed_url, $owner_uid]);
				if ($row = $sth->fetch()) {
					$archive_id = $row["id"];
				} else {
					$res = $pdo->query("SELECT MAX(id) AS id FROM ttrss_archived_feeds");
					$row = $res->fetch();
					$new_feed_id = (int)$row['id'] + 1;
					$sth = $pdo->prepare("INSERT INTO ttrss_archived_feeds
						(id, owner_uid, title, feed_url, site_url, created)
							SELECT ?, owner_uid, title, feed_url, site_url, NOW() from ttrss_feeds
							WHERE id = ?");
					$sth->execute([$new_feed_id, $id]);
					$archive_id = $new_feed_id;
				}
				$sth = $pdo->prepare("UPDATE ttrss_user_entries SET feed_id = NULL,
					orig_feed_id = ? WHERE feed_id = ? AND
						marked = true AND owner_uid = ?");
				$sth->execute([$archive_id, $id, $owner_uid]);
				/* Remove access key for the feed */
				$sth = $pdo->prepare("DELETE FROM ttrss_access_keys WHERE
					feed_id = ? AND owner_uid = ?");
				$sth->execute([$id, $owner_uid]);
				/* remove the feed */
				$sth = $pdo->prepare("DELETE FROM ttrss_feeds
					WHERE id = ? AND owner_uid = ?");
				$sth->execute([$id, $owner_uid]);
			}
			$pdo->commit();
			if (file_exists(ICONS_DIR . "/$id.ico")) {
				unlink(ICONS_DIR . "/$id.ico");
			}
			CCache::remove($id, $owner_uid);
		} else {
			Labels::remove(Labels::feed_to_label_id($id), $owner_uid);
			//CCache::remove($id, $owner_uid); don't think labels are cached
		}
	}
	function batchSubscribe() {
		print_hidden("op", "pref-feeds");
		print_hidden("method", "batchaddfeeds");
		print "".__("One valid feed per line (no detection is done)")." ";
		print "";
		print "";
		if (get_pref('ENABLE_FEED_CATS')) {
			print "";
			print "" . __('Place in category:') . "  ";
			print_feed_cat_select("cat", false, 'dojoType="dijit.form.Select"');
			print " ";
		}
		print " ";
		print "";
		print "" . __("Authentication") . " ";
		print "
";
		print "";
		print "";
		print "
			 ";
		print "
			".__('Subscribe')." 
			".__('Cancel')." 
			 ";
	}
	function batchAddFeeds() {
		$cat_id = clean($_REQUEST['cat']);
		$feeds = explode("\n", clean($_REQUEST['feeds']));
		$login = clean($_REQUEST['login']);
		$pass = trim(clean($_REQUEST['pass']));
		$csth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
						WHERE feed_url = ? AND owner_uid = ?");
		$isth = $this->pdo->prepare("INSERT INTO ttrss_feeds
							(owner_uid,feed_url,title,cat_id,auth_login,auth_pass,update_method,auth_pass_encrypted)
						VALUES (?, ?, '[Unknown]', ?, ?, ?, 0, false)");
		foreach ($feeds as $feed) {
			$feed = trim($feed);
			if (validate_feed_url($feed)) {
				$this->pdo->beginTransaction();
				$csth->execute([$feed, $_SESSION['uid']]);
				if (!$csth->fetch()) {
					$isth->execute([$_SESSION['uid'], $feed, $cat_id ? $cat_id : null, $login, $pass]);
				}
				$this->pdo->commit();
			}
		}
	}
	function regenOPMLKey() {
		$this->update_feed_access_key('OPML:Publish',
		false, $_SESSION["uid"]);
		$new_link = Opml::opml_publish_url();
		print json_encode(array("link" => $new_link));
	}
	function regenFeedKey() {
		$feed_id = clean($_REQUEST['id']);
		$is_cat = clean($_REQUEST['is_cat']);
		$new_key = $this->update_feed_access_key($feed_id, $is_cat);
		print json_encode(["link" => $new_key]);
	}
	private function update_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
		if (!$owner_uid) $owner_uid = $_SESSION["uid"];
		// clear old value and generate new one
		$sth = $this->pdo->prepare("DELETE FROM ttrss_access_keys
			WHERE feed_id = ? AND is_cat = ? AND owner_uid = ?");
		$sth->execute([$feed_id, bool_to_sql_bool($is_cat), $owner_uid]);
		return get_feed_access_key($feed_id, $is_cat, $owner_uid);
	}
	// Silent
	function clearKeys() {
		$sth = $this->pdo->prepare("DELETE FROM ttrss_access_keys WHERE
			owner_uid = ?");
		$sth->execute([$_SESSION['uid']]);
	}
	private function calculate_children_count($cat) {
		$c = 0;
		foreach ($cat['items'] as $child) {
			if ($child['type'] == 'category') {
				$c += $this->calculate_children_count($child);
			} else {
				$c += 1;
			}
		}
		return $c;
	}
	function getinactivefeeds() {
		if (DB_TYPE == "pgsql") {
			$interval_qpart = "NOW() - INTERVAL '3 months'";
		} else {
			$interval_qpart = "DATE_SUB(NOW(), INTERVAL 3 MONTH)";
		}
		$sth = $this->pdo->prepare("SELECT COUNT(id) AS num_inactive FROM ttrss_feeds WHERE
				(SELECT MAX(updated) FROM ttrss_entries, ttrss_user_entries WHERE
					ttrss_entries.id = ref_id AND
						ttrss_user_entries.feed_id = ttrss_feeds.id) < $interval_qpart AND
			  ttrss_feeds.owner_uid = ?");
		$sth->execute([$_SESSION['uid']]);
		if ($row = $sth->fetch()) {
			print (int)$row["num_inactive"];
		}
	}
	static function subscribe_to_feed_url() {
		$url_path = get_self_url_prefix() .
			"/public.php?op=subscribe&feed_url=%s";
		return $url_path;
	}
}