2 function make_init_params() {
5 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
6 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
7 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE",
8 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
10 $params[strtolower($param)] = (int) get_pref($param);
13 $params["icons_url"] = ICONS_URL;
14 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME;
15 $params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE");
16 $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT");
17 $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY");
18 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
19 $params["label_base_index"] = (int) LABEL_BASE_INDEX;
21 $theme = get_pref( "USER_CSS_THEME", false, false);
22 $params["theme"] = theme_valid("$theme") ? $theme : "";
24 $params["plugins"] = implode(", ", PluginHost::getInstance()->get_plugin_names());
26 $params["php_platform"] = PHP_OS;
27 $params["php_version"] = PHP_VERSION;
29 $params["sanity_checksum"] = sha1(file_get_contents("include/sanity_check.php"));
31 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
32 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
34 $max_feed_id = db_fetch_result($result, 0, "mid");
35 $num_feeds = db_fetch_result($result, 0, "nf");
37 $params["max_feed_id"] = (int) $max_feed_id;
38 $params["num_feeds"] = (int) $num_feeds;
40 $params["hotkeys"] = get_hotkeys_map();
42 $params["csrf_token"] = $_SESSION["csrf_token"];
43 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
45 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE;
50 function get_hotkeys_info() {
52 __("Navigation") => array(
53 "next_feed" => __("Open next feed"),
54 "prev_feed" => __("Open previous feed"),
55 "next_article" => __("Open next article"),
56 "prev_article" => __("Open previous article"),
57 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
58 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
59 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
60 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
61 "search_dialog" => __("Show search dialog")),
62 __("Article") => array(
63 "toggle_mark" => __("Toggle starred"),
64 "toggle_publ" => __("Toggle published"),
65 "toggle_unread" => __("Toggle unread"),
66 "edit_tags" => __("Edit tags"),
67 "dismiss_selected" => __("Dismiss selected"),
68 "dismiss_read" => __("Dismiss read"),
69 "open_in_new_window" => __("Open in new window"),
70 "catchup_below" => __("Mark below as read"),
71 "catchup_above" => __("Mark above as read"),
72 "article_scroll_down" => __("Scroll down"),
73 "article_scroll_up" => __("Scroll up"),
74 "select_article_cursor" => __("Select article under cursor"),
75 "email_article" => __("Email article"),
76 "close_article" => __("Close/collapse article"),
77 "toggle_expand" => __("Toggle article expansion (combined mode)"),
78 "toggle_widescreen" => __("Toggle widescreen mode"),
79 "toggle_embed_original" => __("Toggle embed original")),
80 __("Article selection") => array(
81 "select_all" => __("Select all articles"),
82 "select_unread" => __("Select unread"),
83 "select_marked" => __("Select starred"),
84 "select_published" => __("Select published"),
85 "select_invert" => __("Invert selection"),
86 "select_none" => __("Deselect everything")),
88 "feed_refresh" => __("Refresh current feed"),
89 "feed_unhide_read" => __("Un/hide read feeds"),
90 "feed_subscribe" => __("Subscribe to feed"),
91 "feed_edit" => __("Edit feed"),
92 "feed_catchup" => __("Mark as read"),
93 "feed_reverse" => __("Reverse headlines"),
94 "feed_debug_update" => __("Debug feed update"),
95 "catchup_all" => __("Mark all feeds as read"),
96 "cat_toggle_collapse" => __("Un/collapse current category"),
97 "toggle_combined_mode" => __("Toggle combined mode"),
98 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
100 "goto_all" => __("All articles"),
101 "goto_fresh" => __("Fresh"),
102 "goto_marked" => __("Starred"),
103 "goto_published" => __("Published"),
104 "goto_tagcloud" => __("Tag cloud"),
105 "goto_prefs" => __("Preferences")),
106 __("Other") => array(
107 "create_label" => __("Create label"),
108 "create_filter" => __("Create filter"),
109 "collapse_sidebar" => __("Un/collapse sidebar"),
110 "help_dialog" => __("Show help dialog"))
113 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_INFO) as $plugin) {
114 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
120 function get_hotkeys_map() {
122 // "navigation" => array(
125 "n" => "next_article",
126 "p" => "prev_article",
127 "(38)|up" => "prev_article",
128 "(40)|down" => "next_article",
129 // "^(38)|Ctrl-up" => "prev_article_noscroll",
130 // "^(40)|Ctrl-down" => "next_article_noscroll",
131 "(191)|/" => "search_dialog",
132 // "article" => array(
133 "s" => "toggle_mark",
134 "*s" => "toggle_publ",
135 "u" => "toggle_unread",
137 "*d" => "dismiss_selected",
138 "*x" => "dismiss_read",
139 "o" => "open_in_new_window",
140 "c p" => "catchup_below",
141 "c n" => "catchup_above",
142 "*n" => "article_scroll_down",
143 "*p" => "article_scroll_up",
144 "*(38)|Shift+up" => "article_scroll_up",
145 "*(40)|Shift+down" => "article_scroll_down",
146 "a *w" => "toggle_widescreen",
147 "a e" => "toggle_embed_original",
148 "e" => "email_article",
149 "a q" => "close_article",
150 // "article_selection" => array(
151 "a a" => "select_all",
152 "a u" => "select_unread",
153 "a *u" => "select_marked",
154 "a p" => "select_published",
155 "a i" => "select_invert",
156 "a n" => "select_none",
158 "f r" => "feed_refresh",
159 "f a" => "feed_unhide_read",
160 "f s" => "feed_subscribe",
161 "f e" => "feed_edit",
162 "f q" => "feed_catchup",
163 "f x" => "feed_reverse",
164 "f *d" => "feed_debug_update",
165 "f *c" => "toggle_combined_mode",
166 "f c" => "toggle_cdm_expanded",
167 "*q" => "catchup_all",
168 "x" => "cat_toggle_collapse",
171 "g f" => "goto_fresh",
172 "g s" => "goto_marked",
173 "g p" => "goto_published",
174 "g t" => "goto_tagcloud",
175 "g *p" => "goto_prefs",
177 "(9)|Tab" => "select_article_cursor", // tab
178 "c l" => "create_label",
179 "c f" => "create_filter",
180 "c s" => "collapse_sidebar",
181 "^(191)|Ctrl+/" => "help_dialog",
184 if (get_pref('COMBINED_DISPLAY_MODE')) {
185 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
186 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
189 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) {
190 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
195 foreach (array_keys($hotkeys) as $hotkey) {
196 $pair = explode(" ", $hotkey, 2);
198 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
199 array_push($prefixes, $pair[0]);
203 return array($prefixes, $hotkeys);
206 function check_for_update() {
207 if (defined("GIT_VERSION_TIMESTAMP")) {
208 $content = @fetch_file_contents("http://tt-rss.org/version.json");
211 $content = json_decode($content, true);
213 if ($content && isset($content["changeset"])) {
214 if ((int)GIT_VERSION_TIMESTAMP < (int)$content["changeset"]["timestamp"] &&
215 GIT_VERSION_HEAD != $content["changeset"]["id"]) {
217 return $content["changeset"]["id"];
226 function make_runtime_info() {
229 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
230 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
232 $max_feed_id = db_fetch_result($result, 0, "mid");
233 $num_feeds = db_fetch_result($result, 0, "nf");
235 $data["max_feed_id"] = (int) $max_feed_id;
236 $data["num_feeds"] = (int) $num_feeds;
238 $data['last_article_id'] = getLastArticleId();
239 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
241 $data['dep_ts'] = calculate_dep_timestamp();
242 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
245 if (CHECK_FOR_UPDATES && $_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) {
246 $update_result = @check_for_update();
248 $data["update_result"] = $update_result;
250 $_SESSION["last_version_check"] = time();
253 if (file_exists(LOCK_DIRECTORY . "/update_daemon.lock")) {
255 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
257 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
259 $stamp = (int) @file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
262 $stamp_delta = time() - $stamp;
264 if ($stamp_delta > 1800) {
268 $_SESSION["daemon_stamp_check"] = time();
271 $data['daemon_stamp_ok'] = $stamp_check;
273 $stamp_fmt = date("Y.m.d, G:i", $stamp);
275 $data['daemon_stamp'] = $stamp_fmt;
283 function search_to_sql($search) {
285 $keywords = str_getcsv($search, " ");
286 $query_keywords = array();
287 $search_words = array();
288 $search_query_leftover = array();
290 foreach ($keywords as $k) {
291 if (strpos($k, "-") === 0) {
298 $commandpair = explode(":", mb_strtolower($k), 2);
300 switch ($commandpair[0]) {
302 if ($commandpair[1]) {
303 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
304 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
306 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
307 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
308 array_push($search_words, $k);
312 if ($commandpair[1]) {
313 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
314 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
316 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
317 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
318 array_push($search_words, $k);
322 if ($commandpair[1]) {
323 if ($commandpair[1] == "true")
324 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
325 else if ($commandpair[1] == "false")
326 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
328 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
329 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
331 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
332 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
333 if (!$not) array_push($search_words, $k);
338 if ($commandpair[1]) {
339 if ($commandpair[1] == "true")
340 array_push($query_keywords, "($not (marked = true))");
342 array_push($query_keywords, "($not (marked = false))");
344 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
345 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
346 if (!$not) array_push($search_words, $k);
350 if ($commandpair[1]) {
351 if ($commandpair[1] == "true")
352 array_push($query_keywords, "($not (published = true))");
354 array_push($query_keywords, "($not (published = false))");
357 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
358 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
359 if (!$not) array_push($search_words, $k);
363 if ($commandpair[1]) {
364 if ($commandpair[1] == "true")
365 array_push($query_keywords, "($not (unread = true))");
367 array_push($query_keywords, "($not (unread = false))");
370 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
371 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
372 if (!$not) array_push($search_words, $k);
376 if (strpos($k, "@") === 0) {
378 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
379 $orig_ts = strtotime(substr($k, 1));
380 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
382 //$k = date("Y-m-d", strtotime(substr($k, 1)));
384 array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')");
387 if (DB_TYPE == "pgsql") {
388 $k = mb_strtolower($k);
389 array_push($search_query_leftover, $not ? "!$k" : $k);
391 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
392 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
395 if (!$not) array_push($search_words, $k);
400 if (count($search_query_leftover) > 0) {
401 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
403 if (DB_TYPE == "pgsql") {
404 array_push($query_keywords,
405 "(tsvector_combined @@ '$search_query_leftover'::tsquery)");
410 $search_query_part = implode("AND", $query_keywords);
412 return array($search_query_part, $search_words);
415 function getParentCategories($cat, $owner_uid) {
418 $result = db_query("SELECT parent_cat FROM ttrss_feed_categories
419 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
421 while ($line = db_fetch_assoc($result)) {
422 array_push($rv, $line["parent_cat"]);
423 $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid));
429 function getChildCategories($cat, $owner_uid) {
432 $result = db_query("SELECT id FROM ttrss_feed_categories
433 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
435 while ($line = db_fetch_assoc($result)) {
436 array_push($rv, $line["id"]);
437 $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid));
443 function queryFeedHeadlines($params) {
445 $feed = $params["feed"];
446 $limit = isset($params["limit"]) ? $params["limit"] : 30;
447 $view_mode = $params["view_mode"];
448 $cat_view = isset($params["cat_view"]) ? $params["cat_view"] : false;
449 $search = isset($params["search"]) ? $params["search"] : false;
450 $override_order = isset($params["override_order"]) ? $params["override_order"] : false;
451 $offset = isset($params["offset"]) ? $params["offset"] : 0;
452 $owner_uid = isset($params["owner_uid"]) ? $params["owner_uid"] : $_SESSION["uid"];
453 $since_id = isset($params["since_id"]) ? $params["since_id"] : 0;
454 $include_children = isset($params["include_children"]) ? $params["include_children"] : false;
455 $ignore_vfeed_group = isset($params["ignore_vfeed_group"]) ? $params["ignore_vfeed_group"] : false;
456 $override_strategy = isset($params["override_strategy"]) ? $params["override_strategy"] : false;
457 $override_vfeed = isset($params["override_vfeed"]) ? $params["override_vfeed"] : false;
458 $start_ts = isset($params["start_ts"]) ? $params["start_ts"] : false;
459 $check_first_id = isset($params["check_first_id"]) ? $params["check_first_id"] : false;
461 $ext_tables_part = "";
462 $query_strategy_part = "";
464 $search_words = array();
467 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH) as $plugin) {
468 list($search_query_part, $search_words) = $plugin->hook_search($search);
472 // fall back in case of no plugins
473 if (!$search_query_part) {
474 list($search_query_part, $search_words) = search_to_sql($search);
476 $search_query_part .= " AND ";
478 $search_query_part = "";
482 $since_id_part = "ttrss_entries.id > $since_id AND ";
487 $view_query_part = "";
489 if ($view_mode == "adaptive") {
491 $view_query_part = " ";
492 } else if ($feed != -1) {
494 $unread = getFeedUnread($feed, $cat_view);
496 if ($cat_view && $feed > 0 && $include_children)
497 $unread += getCategoryChildrenUnread($feed);
500 $view_query_part = " unread = true AND ";
505 if ($view_mode == "marked") {
506 $view_query_part = " marked = true AND ";
509 if ($view_mode == "has_note") {
510 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
513 if ($view_mode == "published") {
514 $view_query_part = " published = true AND ";
517 if ($view_mode == "unread" && $feed != -6) {
518 $view_query_part = " unread = true AND ";
522 $limit_query_part = "LIMIT " . $limit;
525 $allow_archived = false;
527 $vfeed_query_part = "";
530 if (!is_numeric($feed)) {
531 $query_strategy_part = "true";
532 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
533 id = feed_id) as feed_title,";
534 } else if ($feed > 0) {
539 if ($include_children) {
541 $subcats = getChildCategories($feed, $owner_uid);
543 array_push($subcats, $feed);
544 $query_strategy_part = "cat_id IN (".
545 implode(",", $subcats).")";
548 $query_strategy_part = "cat_id = '$feed'";
552 $query_strategy_part = "cat_id IS NULL";
555 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
558 $query_strategy_part = "feed_id = '$feed'";
560 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
561 $query_strategy_part = "feed_id IS NULL";
562 $allow_archived = true;
563 } else if ($feed == 0 && $cat_view) { // uncategorized
564 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
565 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
566 } else if ($feed == -1) { // starred virtual feed
567 $query_strategy_part = "marked = true";
568 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
569 $allow_archived = true;
571 if (!$override_order) {
572 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
575 } else if ($feed == -2) { // published virtual feed OR labels category
578 $query_strategy_part = "published = true";
579 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
580 $allow_archived = true;
582 if (!$override_order) {
583 $override_order = "last_published DESC, date_entered DESC, updated DESC";
587 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
589 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
591 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
592 ttrss_user_labels2.article_id = ref_id";
595 } else if ($feed == -6) { // recently read
596 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
597 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
598 $allow_archived = true;
599 $ignore_vfeed_group = true;
601 if (!$override_order) $override_order = "last_read DESC";
603 } else if ($feed == -3) { // fresh virtual feed
604 $query_strategy_part = "unread = true AND score >= 0";
606 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
608 if (DB_TYPE == "pgsql") {
609 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
611 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
614 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
615 } else if ($feed == -4) { // all articles virtual feed
616 $allow_archived = true;
617 $query_strategy_part = "true";
618 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
619 } else if ($feed <= LABEL_BASE_INDEX) { // labels
620 $label_id = feed_to_label_id($feed);
622 $query_strategy_part = "label_id = '$label_id' AND
623 ttrss_labels2.id = ttrss_user_labels2.label_id AND
624 ttrss_user_labels2.article_id = ref_id";
626 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
627 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
628 $allow_archived = true;
631 $query_strategy_part = "true";
634 $order_by = "score DESC, date_entered DESC, updated DESC";
636 if ($override_order) {
637 $order_by = $override_order;
640 if ($override_strategy) {
641 $query_strategy_part = $override_strategy;
644 if ($override_vfeed) {
645 $vfeed_query_part = $override_vfeed;
651 $feed_title = T_sprintf("Search results: %s", $search);
654 $feed_title = getCategoryTitle($feed);
656 if (is_numeric($feed) && $feed > 0) {
657 $result = db_query("SELECT title,site_url,last_error,last_updated
658 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
660 $feed_title = db_fetch_result($result, 0, "title");
661 $feed_site_url = db_fetch_result($result, 0, "site_url");
662 $last_error = db_fetch_result($result, 0, "last_error");
663 $last_updated = db_fetch_result($result, 0, "last_updated");
665 $feed_title = getFeedTitle($feed);
671 $content_query_part = "content, ";
673 if ($limit_query_part) {
674 $offset_query_part = "OFFSET $offset";
676 $offset_query_part = "";
679 if (is_numeric($feed)) {
680 // proper override_order applied above
681 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
682 if (!$override_order) {
683 $order_by = "ttrss_feeds.title, $order_by";
685 $order_by = "ttrss_feeds.title, $override_order";
689 if (!$allow_archived) {
690 $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part";
691 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
694 $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries
695 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
698 if ($vfeed_query_part) $vfeed_query_part .= "favicon_avg_color,";
701 $start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts));
702 $start_ts_query_part = "date_entered >= '$start_ts_formatted' AND";
704 $start_ts_query_part = "";
708 $first_id_query_strategy_part = $query_strategy_part;
711 $first_id_query_strategy_part = "true";
714 // if previous topmost article id changed that means our current pagination is no longer valid
715 $query = "SELECT DISTINCT
731 ttrss_user_entries.ref_id = ttrss_entries.id AND
732 ttrss_user_entries.owner_uid = '$owner_uid' AND
736 $first_id_query_strategy_part ORDER BY $order_by LIMIT 1";
738 if ($_REQUEST["debug"]) {
742 $result = db_query($query);
743 if ($result && db_num_rows($result) > 0) {
744 $first_id = (int)db_fetch_result($result, 0, "id");
746 if ($offset > 0 && $first_id && $check_first_id && $first_id != $check_first_id) {
747 return array(-1, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
752 $query = "SELECT DISTINCT
755 ttrss_entries.id,ttrss_entries.title,
759 always_display_enclosures,
768 unread,feed_id,marked,published,link,last_read,orig_feed_id,
769 last_marked, last_published,
777 ttrss_user_entries.ref_id = ttrss_entries.id AND
778 ttrss_user_entries.owner_uid = '$owner_uid' AND
783 $query_strategy_part ORDER BY $order_by
784 $limit_query_part $offset_query_part";
786 if ($_REQUEST["debug"]) print $query;
788 $result = db_query($query);
793 $query = "SELECT DISTINCT
797 ttrss_entries.id as id,
812 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images,
813 last_marked, last_published,
818 FROM ttrss_entries, ttrss_user_entries, ttrss_tags
820 ref_id = ttrss_entries.id AND
821 ttrss_user_entries.owner_uid = $owner_uid AND
822 post_int_id = int_id AND
823 tag_name = '$feed' AND
826 $query_strategy_part ORDER BY $order_by
827 $limit_query_part $offset_query_part";
829 if ($_REQUEST["debug"]) print $query;
831 $result = db_query($query);
834 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
838 function iframe_whitelisted($entry) {
839 $whitelist = array("youtube.com", "youtu.be", "vimeo.com");
841 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST);
844 foreach ($whitelist as $w) {
845 if ($src == $w || $src == "www.$w")
853 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
854 if (!$owner) $owner = $_SESSION["uid"];
856 $res = trim($str); if (!$res) return '';
858 $charset_hack = '<head>
859 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
862 $res = trim($res); if (!$res) return '';
864 libxml_use_internal_errors(true);
866 $doc = new DOMDocument();
867 $doc->loadHTML($charset_hack . $res);
868 $xpath = new DOMXPath($doc);
870 $entries = $xpath->query('(//a[@href]|//img[@src])');
872 foreach ($entries as $entry) {
876 if ($entry->hasAttribute('href')) {
877 $entry->setAttribute('href',
878 rewrite_relative_url($site_url, $entry->getAttribute('href')));
880 $entry->setAttribute('rel', 'noreferrer');
883 if ($entry->hasAttribute('src')) {
884 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
886 $cached_filename = CACHE_DIR . '/images/' . sha1($src) . '.png';
888 if (file_exists($cached_filename)) {
889 $src = SELF_URL_PATH . '/public.php?op=cached_image&hash=' . sha1($src);
892 $entry->setAttribute('src', $src);
895 if ($entry->nodeName == 'img') {
896 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
897 $force_remove_images || $_SESSION["bw_limit"]) {
899 $p = $doc->createElement('p');
901 $a = $doc->createElement('a');
902 $a->setAttribute('href', $entry->getAttribute('src'));
904 $a->appendChild(new DOMText($entry->getAttribute('src')));
905 $a->setAttribute('target', '_blank');
909 $entry->parentNode->replaceChild($p, $entry);
914 if (strtolower($entry->nodeName) == "a") {
915 $entry->setAttribute("target", "_blank");
919 $entries = $xpath->query('//iframe');
920 foreach ($entries as $entry) {
921 if (!iframe_whitelisted($entry)) {
922 $entry->setAttribute('sandbox', 'allow-scripts');
924 if ($_SERVER['HTTPS'] == "on") {
925 $entry->setAttribute("src",
926 str_replace("http://", "https://",
927 $entry->getAttribute("src")));
932 $allowed_elements = array('a', 'address', 'audio', 'article', 'aside',
933 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
934 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
935 'data', 'dd', 'del', 'details', 'div', 'dl', 'font',
936 'dt', 'em', 'footer', 'figure', 'figcaption',
937 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
938 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
939 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
940 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
941 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
942 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
944 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
946 $disallowed_attributes = array('id', 'style', 'class');
948 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) {
949 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
950 if (is_array($retval)) {
952 $allowed_elements = $retval[1];
953 $disallowed_attributes = $retval[2];
959 $doc->removeChild($doc->firstChild); //remove doctype
960 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
962 if ($highlight_words) {
963 foreach ($highlight_words as $word) {
965 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
967 $elements = $xpath->query("//*/text()");
969 foreach ($elements as $child) {
971 $fragment = $doc->createDocumentFragment();
972 $text = $child->textContent;
974 while (($pos = mb_stripos($text, $word)) !== false) {
975 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
976 $word = mb_substr($text, $pos, mb_strlen($word));
977 $highlight = $doc->createElement('span');
978 $highlight->appendChild(new DomText($word));
979 $highlight->setAttribute('class', 'highlight');
980 $fragment->appendChild($highlight);
981 $text = mb_substr($text, $pos + mb_strlen($word));
984 if (!empty($text)) $fragment->appendChild(new DomText($text));
986 $child->parentNode->replaceChild($fragment, $child);
991 $res = $doc->saveHTML();
996 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
997 $xpath = new DOMXPath($doc);
998 $entries = $xpath->query('//*');
1000 foreach ($entries as $entry) {
1001 if (!in_array($entry->nodeName, $allowed_elements)) {
1002 $entry->parentNode->removeChild($entry);
1005 if ($entry->hasAttributes()) {
1006 $attrs_to_remove = array();
1008 foreach ($entry->attributes as $attr) {
1010 if (strpos($attr->nodeName, 'on') === 0) {
1011 array_push($attrs_to_remove, $attr);
1014 if (in_array($attr->nodeName, $disallowed_attributes)) {
1015 array_push($attrs_to_remove, $attr);
1019 foreach ($attrs_to_remove as $attr) {
1020 $entry->removeAttributeNode($attr);
1028 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
1030 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1031 if (count($ids) == 0) return;
1035 foreach ($ids as $id) {
1036 array_push($tmp_ids, "ref_id = '$id'");
1039 $ids_qpart = join(" OR ", $tmp_ids);
1042 db_query("UPDATE ttrss_user_entries SET
1043 unread = false,last_read = NOW()
1044 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1045 } else if ($cmode == 1) {
1046 db_query("UPDATE ttrss_user_entries SET
1048 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1050 db_query("UPDATE ttrss_user_entries SET
1051 unread = NOT unread,last_read = NOW()
1052 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1057 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
1058 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1060 while ($line = db_fetch_assoc($result)) {
1061 ccache_update($line["feed_id"], $owner_uid);
1065 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
1067 $a_id = db_escape_string($id);
1069 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1071 $query = "SELECT DISTINCT tag_name,
1072 owner_uid as owner FROM
1073 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
1074 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
1078 /* check cache first */
1080 if ($tag_cache === false) {
1081 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
1082 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1084 $tag_cache = db_fetch_result($result, 0, "tag_cache");
1088 $tags = explode(",", $tag_cache);
1091 /* do it the hard way */
1093 $tmp_result = db_query($query);
1095 while ($tmp_line = db_fetch_assoc($tmp_result)) {
1096 array_push($tags, $tmp_line["tag_name"]);
1099 /* update the cache */
1101 $tags_str = db_escape_string(join(",", $tags));
1103 db_query("UPDATE ttrss_user_entries
1104 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
1105 AND owner_uid = $owner_uid");
1111 function trim_array($array) {
1113 array_walk($tmp, 'trim');
1117 function tag_is_valid($tag) {
1118 if ($tag == '') return false;
1119 if (preg_match("/^[0-9]*$/", $tag)) return false;
1120 if (mb_strlen($tag) > 250) return false;
1122 if (!$tag) return false;
1127 function render_login_form() {
1128 header('Cache-Control: public');
1130 require_once "login_form.php";
1134 function format_warning($msg, $id = "") {
1135 return "<div class=\"warning\" id=\"$id\">
1136 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
1139 function format_notice($msg, $id = "") {
1140 return "<div class=\"notice\" id=\"$id\">
1141 <span><img src=\"images/information.png\"></span><span>$msg</span></div>";
1144 function format_error($msg, $id = "") {
1145 return "<div class=\"error\" id=\"$id\">
1146 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
1149 function print_notice($msg) {
1150 return print format_notice($msg);
1153 function print_warning($msg) {
1154 return print format_warning($msg);
1157 function print_error($msg) {
1158 return print format_error($msg);
1162 function T_sprintf() {
1163 $args = func_get_args();
1164 return vsprintf(__(array_shift($args)), $args);
1167 function format_inline_player($url, $ctype) {
1171 $url = htmlspecialchars($url);
1173 if (strpos($ctype, "audio/") === 0) {
1175 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1176 $_SESSION["hasMp3"])) {
1178 $entry .= "<audio preload=\"none\" controls>
1179 <source type=\"$ctype\" src=\"$url\"/>
1184 $entry .= "<object type=\"application/x-shockwave-flash\"
1185 data=\"lib/button/musicplayer.swf?song_url=$url\"
1186 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1187 <param name=\"movie\"
1188 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1192 if ($entry) $entry .= " <a target=\"_blank\"
1193 href=\"$url\">" . basename($url) . "</a>";
1201 /* $filename = substr($url, strrpos($url, "/")+1);
1203 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1204 $filename . " (" . $ctype . ")" . "</a>"; */
1208 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1209 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1215 /* we can figure out feed_id from article id anyway, why do we
1216 * pass feed_id here? let's ignore the argument :(*/
1218 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1219 WHERE ref_id = '$id'");
1221 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1223 $rv['feed_id'] = $feed_id;
1225 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1227 if ($mark_as_read) {
1228 $result = db_query("UPDATE ttrss_user_entries
1229 SET unread = false,last_read = NOW()
1230 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1232 ccache_update($feed_id, $owner_uid);
1235 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1236 ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
1237 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1238 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1239 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1240 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1246 FROM ttrss_entries,ttrss_user_entries
1247 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1251 $line = db_fetch_assoc($result);
1253 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1254 unset($line["tag_cache"]);
1256 $line["content"] = sanitize($line["content"],
1257 sql_bool_to_bool($line['hide_images']),
1258 $owner_uid, $line["site_url"], false, $line["id"]);
1260 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) {
1261 $line = $p->hook_render_article($line);
1264 $num_comments = $line["num_comments"];
1265 $entry_comments = "";
1267 if ($num_comments > 0) {
1268 if ($line["comments"]) {
1269 $comments_url = htmlspecialchars($line["comments"]);
1271 $comments_url = htmlspecialchars($line["link"]);
1273 $entry_comments = "<a class=\"postComments\"
1274 target='_blank' href=\"$comments_url\">$num_comments ".
1275 _ngettext("comment", "comments", $num_comments)."</a>";
1278 if ($line["comments"] && $line["link"] != $line["comments"]) {
1279 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
1284 header("Content-Type: text/html");
1285 $rv['content'] .= "<html><head>
1286 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1287 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1288 stylesheet_tag("css/tt-rss.css").
1289 stylesheet_tag("css/zoom.css").
1290 stylesheet_tag("css/dijit.css")."
1292 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1293 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1295 <script type=\"text/javascript\">
1296 function openSelectedAttachment(elem) {
1298 var url = elem[elem.selectedIndex].value;
1302 elem.selectedIndex = 0;
1306 exception_error(\"openSelectedAttachment\", e);
1310 </head><body id=\"ttrssZoom\">";
1313 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1315 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1317 $entry_author = $line["author"];
1319 if ($entry_author) {
1320 $entry_author = __(" - ") . $entry_author;
1323 $parsed_updated = make_local_datetime($line["updated"], true,
1327 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1329 if ($line["link"]) {
1330 $rv['content'] .= "<div class='postTitle'><a target='_blank'
1331 title=\"".htmlspecialchars($line['title'])."\"
1333 htmlspecialchars($line["link"]) . "\">" .
1334 $line["title"] . "</a>" .
1335 "<span class='author'>$entry_author</span></div>";
1337 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1341 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
1342 "\" target=\"_blank\">".
1343 htmlspecialchars($line["feed_title"])."</a>";
1345 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1347 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1350 $tags_str = format_tags_string($line["tags"], $id);
1351 $tags_str_full = join(", ", $line["tags"]);
1353 if (!$tags_str_full) $tags_str_full = __("no tags");
1355 if (!$entry_comments) $entry_comments = " "; # placeholder
1357 $rv['content'] .= "<div class='postTags' style='float : right'>
1358 <img src='images/tag.png'
1359 class='tagsPic' alt='Tags' title='Tags'> ";
1362 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1363 <a title=\"".__('Edit tags for this article')."\"
1364 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1366 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1367 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1368 position=\"below\">$tags_str_full</div>";
1370 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
1371 $rv['content'] .= $p->hook_article_button($line);
1375 $tags_str = strip_tags($tags_str);
1376 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1378 $rv['content'] .= "</div>";
1379 $rv['content'] .= "<div clear='both'>";
1381 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
1382 $rv['content'] .= $p->hook_article_left_button($line);
1385 $rv['content'] .= "$entry_comments</div>";
1387 if ($line["orig_feed_id"]) {
1389 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
1390 WHERE id = ".$line["orig_feed_id"]);
1392 if (db_num_rows($tmp_result) != 0) {
1394 $rv['content'] .= "<div clear='both'>";
1395 $rv['content'] .= __("Originally from:");
1397 $rv['content'] .= " ";
1399 $tmp_line = db_fetch_assoc($tmp_result);
1401 $rv['content'] .= "<a target='_blank'
1402 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1403 $tmp_line['title'] . "</a>";
1405 $rv['content'] .= " ";
1407 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
1408 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1410 $rv['content'] .= "</div>";
1414 $rv['content'] .= "</div>";
1416 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1417 if ($line['note']) {
1418 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1420 $rv['content'] .= "</div>";
1422 if (!$line['lang']) $line['lang'] = 'en';
1424 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1426 $rv['content'] .= $line["content"];
1427 $rv['content'] .= format_article_enclosures($id,
1428 sql_bool_to_bool($line["always_display_enclosures"]),
1430 sql_bool_to_bool($line["hide_images"]));
1432 $rv['content'] .= "</div>";
1434 $rv['content'] .= "</div>";
1440 <div class='footer'>
1441 <button onclick=\"return window.close()\">".
1442 __("Close this window")."</button></div>";
1443 $rv['content'] .= "</body></html>";
1450 function print_checkpoint($n, $s) {
1451 $ts = microtime(true);
1452 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1456 function sanitize_tag($tag) {
1459 $tag = mb_strtolower($tag, 'utf-8');
1461 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1463 if (DB_TYPE == "mysql") {
1464 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1470 function get_self_url_prefix() {
1471 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
1472 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
1474 return SELF_URL_PATH;
1479 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1481 * @return string The Mozilla Firefox feed adding URL.
1483 function add_feed_url() {
1484 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1486 $url_path = get_self_url_prefix() .
1487 "/public.php?op=subscribe&feed_url=%s";
1489 } // function add_feed_url
1491 function encrypt_password($pass, $salt = '', $mode2 = false) {
1492 if ($salt && $mode2) {
1493 return "MODE2:" . hash('sha256', $salt . $pass);
1495 return "SHA1X:" . sha1("$salt:$pass");
1497 return "SHA1:" . sha1($pass);
1499 } // function encrypt_password
1501 function load_filters($feed_id, $owner_uid, $action_id = false) {
1504 $cat_id = (int)getFeedCategory($feed_id);
1507 $null_cat_qpart = "cat_id IS NULL OR";
1509 $null_cat_qpart = "";
1511 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1512 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1514 $check_cats = join(",", array_merge(
1515 getParentCategories($cat_id, $owner_uid),
1518 while ($line = db_fetch_assoc($result)) {
1519 $filter_id = $line["id"];
1521 $result2 = db_query("SELECT
1522 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1523 FROM ttrss_filters2_rules AS r,
1524 ttrss_filter_types AS t
1526 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1527 (feed_id IS NULL OR feed_id = '$feed_id') AND
1528 filter_type = t.id AND filter_id = '$filter_id'");
1533 while ($rule_line = db_fetch_assoc($result2)) {
1534 # print_r($rule_line);
1537 $rule["reg_exp"] = $rule_line["reg_exp"];
1538 $rule["type"] = $rule_line["type_name"];
1539 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1541 array_push($rules, $rule);
1544 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1545 FROM ttrss_filters2_actions AS a,
1546 ttrss_filter_actions AS t
1548 action_id = t.id AND filter_id = '$filter_id'");
1550 while ($action_line = db_fetch_assoc($result2)) {
1551 # print_r($action_line);
1554 $action["type"] = $action_line["type_name"];
1555 $action["param"] = $action_line["action_param"];
1557 array_push($actions, $action);
1562 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1563 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1564 $filter["rules"] = $rules;
1565 $filter["actions"] = $actions;
1567 if (count($rules) > 0 && count($actions) > 0) {
1568 array_push($filters, $filter);
1575 function get_score_pic($score) {
1577 return "score_high.png";
1578 } else if ($score > 0) {
1579 return "score_half_high.png";
1580 } else if ($score < -100) {
1581 return "score_low.png";
1582 } else if ($score < 0) {
1583 return "score_half_low.png";
1585 return "score_neutral.png";
1589 function feed_has_icon($id) {
1590 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
1593 function init_plugins() {
1594 PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL);
1599 function format_tags_string($tags, $id) {
1600 if (!is_array($tags) || count($tags) == 0) {
1601 return __("no tags");
1603 $maxtags = min(5, count($tags));
1606 for ($i = 0; $i < $maxtags; $i++) {
1607 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed('".$tags[$i]."')\">" . $tags[$i] . "</a>, ";
1610 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1612 if (count($tags) > $maxtags)
1613 $tags_str .= ", …";
1619 function format_article_labels($labels, $id) {
1621 if (!is_array($labels)) return '';
1625 foreach ($labels as $l) {
1626 $labels_str .= sprintf("<span class='hlLabelRef'
1627 style='color : %s; background-color : %s'>%s</span>",
1628 $l[2], $l[3], $l[1]);
1635 function format_article_note($id, $note, $allow_edit = true) {
1637 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1638 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1639 ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
1645 function get_feed_category($feed_cat, $parent_cat_id = false) {
1646 if ($parent_cat_id) {
1647 $parent_qpart = "parent_cat = '$parent_cat_id'";
1648 $parent_insert = "'$parent_cat_id'";
1650 $parent_qpart = "parent_cat IS NULL";
1651 $parent_insert = "NULL";
1655 "SELECT id FROM ttrss_feed_categories
1656 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1658 if (db_num_rows($result) == 0) {
1661 return db_fetch_result($result, 0, "id");
1665 function add_feed_category($feed_cat, $parent_cat_id = false) {
1667 if (!$feed_cat) return false;
1671 if ($parent_cat_id) {
1672 $parent_qpart = "parent_cat = '$parent_cat_id'";
1673 $parent_insert = "'$parent_cat_id'";
1675 $parent_qpart = "parent_cat IS NULL";
1676 $parent_insert = "NULL";
1679 $feed_cat = mb_substr($feed_cat, 0, 250);
1682 "SELECT id FROM ttrss_feed_categories
1683 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1685 if (db_num_rows($result) == 0) {
1688 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1689 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1699 function getArticleFeed($id) {
1700 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1701 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1703 if (db_num_rows($result) != 0) {
1704 return db_fetch_result($result, 0, "feed_id");
1711 * Fixes incomplete URLs by prepending "http://".
1712 * Also replaces feed:// with http://, and
1713 * prepends a trailing slash if the url is a domain name only.
1715 * @param string $url Possibly incomplete URL
1717 * @return string Fixed URL.
1719 function fix_url($url) {
1721 // support schema-less urls
1722 if (strpos($url, '//') === 0) {
1723 $url = 'https:' . $url;
1726 if (strpos($url, '://') === false) {
1727 $url = 'http://' . $url;
1728 } else if (substr($url, 0, 5) == 'feed:') {
1729 $url = 'http:' . substr($url, 5);
1732 //prepend slash if the URL has no slash in it
1733 // "http://www.example" -> "http://www.example/"
1734 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
1738 if ($url != "http:///")
1744 function validate_feed_url($url) {
1745 $parts = parse_url($url);
1747 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
1751 function get_article_enclosures($id) {
1753 $query = "SELECT * FROM ttrss_enclosures
1754 WHERE post_id = '$id' AND content_url != ''";
1758 $result = db_query($query);
1760 if (db_num_rows($result) > 0) {
1761 while ($line = db_fetch_assoc($result)) {
1762 array_push($rv, $line);
1769 /* function save_email_address($email) {
1770 // FIXME: implement persistent storage of emails
1772 if (!$_SESSION['stored_emails'])
1773 $_SESSION['stored_emails'] = array();
1775 if (!in_array($email, $_SESSION['stored_emails']))
1776 array_push($_SESSION['stored_emails'], $email);
1780 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1782 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1784 $sql_is_cat = bool_to_sql_bool($is_cat);
1786 $result = db_query("SELECT access_key FROM ttrss_access_keys
1787 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1788 AND owner_uid = " . $owner_uid);
1790 if (db_num_rows($result) == 1) {
1791 return db_fetch_result($result, 0, "access_key");
1793 $key = db_escape_string(uniqid_short());
1795 $result = db_query("INSERT INTO ttrss_access_keys
1796 (access_key, feed_id, is_cat, owner_uid)
1797 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1804 function get_feeds_from_html($url, $content)
1806 $url = fix_url($url);
1807 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
1809 libxml_use_internal_errors(true);
1811 $doc = new DOMDocument();
1812 $doc->loadHTML($content);
1813 $xpath = new DOMXPath($doc);
1814 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
1815 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
1816 $feedUrls = array();
1817 foreach ($entries as $entry) {
1818 if ($entry->hasAttribute('href')) {
1819 $title = $entry->getAttribute('title');
1821 $title = $entry->getAttribute('type');
1823 $feedUrl = rewrite_relative_url(
1824 $baseUrl, $entry->getAttribute('href')
1826 $feedUrls[$feedUrl] = $title;
1832 function is_html($content) {
1833 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
1836 function url_is_html($url, $login = false, $pass = false) {
1837 return is_html(fetch_file_contents($url, false, $login, $pass));
1840 function print_label_select($name, $value, $attributes = "") {
1842 $result = db_query("SELECT caption FROM ttrss_labels2
1843 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1845 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1846 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
1848 while ($line = db_fetch_assoc($result)) {
1850 $issel = ($line["caption"] == $value) ? "selected=\"1\"" : "";
1852 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1853 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1857 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1864 function format_article_enclosures($id, $always_display_enclosures,
1865 $article_content, $hide_images = false) {
1867 $result = get_article_enclosures($id);
1870 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ENCLOSURES) as $plugin) {
1871 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
1872 if (is_array($retval)) {
1874 $result = $retval[1];
1880 if ($rv === '' && !empty($result)) {
1881 $entries_html = array();
1883 $entries_inline = array();
1885 foreach ($result as $line) {
1887 $url = $line["content_url"];
1888 $ctype = $line["content_type"];
1889 $title = $line["title"];
1890 $width = $line["width"];
1891 $height = $line["height"];
1893 if (!$ctype) $ctype = __("unknown type");
1895 $filename = substr($url, strrpos($url, "/")+1);
1897 $player = format_inline_player($url, $ctype);
1899 if ($player) array_push($entries_inline, $player);
1901 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1902 # $filename . " (" . $ctype . ")" . "</a>";
1904 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
1905 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1907 array_push($entries_html, $entry);
1911 $entry["type"] = $ctype;
1912 $entry["filename"] = $filename;
1913 $entry["url"] = $url;
1914 $entry["title"] = $title;
1915 $entry["width"] = $width;
1916 $entry["height"] = $height;
1918 array_push($entries, $entry);
1921 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
1922 if ($always_display_enclosures ||
1923 !preg_match("/<img/i", $article_content)) {
1925 foreach ($entries as $entry) {
1927 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ENCLOSURE) as $plugin)
1928 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
1935 if (preg_match("/image/", $entry["type"]) ||
1936 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
1938 if (!$hide_images) {
1940 if ($entry['height'] > 0)
1941 $encsize .= ' height="' . intval($entry['width']) . '"';
1942 if ($entry['width'] > 0)
1943 $encsize .= ' width="' . intval($entry['height']) . '"';
1945 alt=\"".htmlspecialchars($entry["filename"])."\"
1946 src=\"" .htmlspecialchars($entry["url"]) . "\"
1947 " . $encsize . " /></p>";
1949 $rv .= "<p><a target=\"_blank\"
1950 href=\"".htmlspecialchars($entry["url"])."\"
1951 >" .htmlspecialchars($entry["url"]) . "</a></p>";
1954 if ($entry['title']) {
1955 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
1963 if (count($entries_inline) > 0) {
1964 $rv .= "<hr clear='both'/>";
1965 foreach ($entries_inline as $entry) { $rv .= $entry; };
1966 $rv .= "<hr clear='both'/>";
1969 $rv .= "<select class=\"attachments\" onchange=\"openSelectedAttachment(this)\">".
1970 "<option value=''>" . __('Attachments')."</option>";
1972 foreach ($entries as $entry) {
1973 if ($entry["title"])
1974 $title = "— " . truncate_string($entry["title"], 30);
1978 $rv .= "<option value=\"".htmlspecialchars($entry["url"])."\">" . htmlspecialchars($entry["filename"]) . "$title</option>";
1988 function getLastArticleId() {
1989 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
1990 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
1992 if (db_num_rows($result) == 1) {
1993 return db_fetch_result($result, 0, "id");
1999 function build_url($parts) {
2000 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2004 * Converts a (possibly) relative URL to a absolute one.
2006 * @param string $url Base URL (i.e. from where the document is)
2007 * @param string $rel_url Possibly relative URL in the document
2009 * @return string Absolute URL
2011 function rewrite_relative_url($url, $rel_url) {
2012 if (strpos($rel_url, ":") !== false) {
2014 } else if (strpos($rel_url, "://") !== false) {
2016 } else if (strpos($rel_url, "//") === 0) {
2017 # protocol-relative URL (rare but they exist)
2019 } else if (strpos($rel_url, "/") === 0)
2021 $parts = parse_url($url);
2022 $parts['path'] = $rel_url;
2024 return build_url($parts);
2027 $parts = parse_url($url);
2028 if (!isset($parts['path'])) {
2029 $parts['path'] = '/';
2031 $dir = $parts['path'];
2032 if (substr($dir, -1) !== '/') {
2033 $dir = dirname($parts['path']);
2034 $dir !== '/' && $dir .= '/';
2036 $parts['path'] = $dir . $rel_url;
2038 return build_url($parts);
2042 function cleanup_tags($days = 14, $limit = 1000) {
2044 if (DB_TYPE == "pgsql") {
2045 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2046 } else if (DB_TYPE == "mysql") {
2047 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2052 while ($limit > 0) {
2055 $query = "SELECT ttrss_tags.id AS id
2056 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2057 WHERE post_int_id = int_id AND $interval_query AND
2058 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2060 $result = db_query($query);
2064 while ($line = db_fetch_assoc($result)) {
2065 array_push($ids, $line['id']);
2068 if (count($ids) > 0) {
2069 $ids = join(",", $ids);
2071 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2072 $tags_deleted += db_affected_rows($tmp_result);
2077 $limit -= $limit_part;
2080 return $tags_deleted;
2083 function print_user_stylesheet() {
2084 $value = get_pref('USER_STYLESHEET');
2087 print "<style type=\"text/css\">";
2088 print str_replace("<br/>", "\n", $value);
2094 function filter_to_sql($filter, $owner_uid) {
2097 if (DB_TYPE == "pgsql")
2100 $reg_qpart = "REGEXP";
2102 foreach ($filter["rules"] AS $rule) {
2103 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2104 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2105 $rule['reg_exp']) !== FALSE;
2107 if ($regexp_valid) {
2109 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2111 switch ($rule["type"]) {
2113 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2114 $rule['reg_exp'] . "')";
2117 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2118 $rule['reg_exp'] . "')";
2121 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2122 $rule['reg_exp'] . "') OR LOWER(" .
2123 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2126 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2127 $rule['reg_exp'] . "')";
2130 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2131 $rule['reg_exp'] . "')";
2134 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2135 $rule['reg_exp'] . "')";
2139 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2141 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2142 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2145 if (isset($rule["cat_id"])) {
2147 if ($rule["cat_id"] > 0) {
2148 $children = getChildCategories($rule["cat_id"], $owner_uid);
2149 array_push($children, $rule["cat_id"]);
2151 $children = join(",", $children);
2153 $cat_qpart = "cat_id IN ($children)";
2155 $cat_qpart = "cat_id IS NULL";
2158 $qpart .= " AND $cat_qpart";
2161 $qpart .= " AND feed_id IS NOT NULL";
2163 array_push($query, "($qpart)");
2168 if (count($query) > 0) {
2169 $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
2171 $fullquery = "(false)";
2174 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2179 if (!function_exists('gzdecode')) {
2180 function gzdecode($string) { // no support for 2nd argument
2181 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2182 base64_encode($string));
2186 function get_random_bytes($length) {
2187 if (function_exists('openssl_random_pseudo_bytes')) {
2188 return openssl_random_pseudo_bytes($length);
2192 for ($i = 0; $i < $length; $i++)
2193 $output .= chr(mt_rand(0, 255));
2199 function read_stdin() {
2200 $fp = fopen("php://stdin", "r");
2203 $line = trim(fgets($fp));
2211 function tmpdirname($path, $prefix) {
2212 // Use PHP's tmpfile function to create a temporary
2213 // directory name. Delete the file and keep the name.
2214 $tempname = tempnam($path,$prefix);
2218 if (!unlink($tempname))
2224 function getFeedCategory($feed) {
2225 $result = db_query("SELECT cat_id FROM ttrss_feeds
2226 WHERE id = '$feed'");
2228 if (db_num_rows($result) > 0) {
2229 return db_fetch_result($result, 0, "cat_id");
2236 function implements_interface($class, $interface) {
2237 return in_array($interface, class_implements($class));
2240 function geturl($url, $depth = 0, $nobody = true){
2242 if ($depth == 20) return $url;
2244 if (!function_exists('curl_init'))
2245 return user_error('CURL Must be installed for geturl function to work. Ask your host to enable it or uncomment extension=php_curl.dll in php.ini', E_USER_ERROR);
2247 $curl = curl_init();
2248 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
2249 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
2250 $header[] = "Cache-Control: max-age=0";
2251 $header[] = "Connection: keep-alive";
2252 $header[] = "Keep-Alive: 300";
2253 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
2254 $header[] = "Accept-Language: en-us,en;q=0.5";
2255 $header[] = "Pragma: ";
2257 curl_setopt($curl, CURLOPT_URL, $url);
2258 curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
2259 curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
2260 curl_setopt($curl, CURLOPT_HEADER, true);
2261 curl_setopt($curl, CURLOPT_NOBODY, $nobody);
2262 curl_setopt($curl, CURLOPT_REFERER, $url);
2263 curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
2264 curl_setopt($curl, CURLOPT_AUTOREFERER, true);
2265 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
2266 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
2267 curl_setopt($curl, CURLOPT_TIMEOUT, 60);
2268 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
2270 if (defined('_CURL_HTTP_PROXY')) {
2271 curl_setopt($curl, CURLOPT_PROXY, _CURL_HTTP_PROXY);
2274 $html = curl_exec($curl);
2276 $status = curl_getinfo($curl);
2278 if($status['http_code']!=200){
2280 // idiot site not allowing http head
2281 if($status['http_code'] == 405) {
2283 return geturl($url, $depth +1, false);
2286 if($status['http_code'] == 301 || $status['http_code'] == 302) {
2288 list($header) = explode("\r\n\r\n", $html, 2);
2290 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
2291 $url = trim(str_replace($matches[1],"",$matches[0]));
2292 $url_parsed = parse_url($url);
2293 return (isset($url_parsed))? geturl($url, $depth + 1):'';
2296 global $fetch_last_error;
2298 $fetch_last_error = curl_errno($curl) . " " . curl_error($curl);
2302 # foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
2303 # $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
2304 # $handle = @fopen('./curl.error.log', 'a');
2305 # fwrite($handle, $line);
2312 function get_minified_js($files) {
2313 require_once 'lib/jshrink/Minifier.php';
2317 foreach ($files as $js) {
2318 if (!isset($_GET['debug'])) {
2319 $cached_file = CACHE_DIR . "/js/".basename($js).".js";
2321 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2323 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2325 if ($header && $contents) {
2326 list($htag, $hversion) = explode(":", $header);
2328 if ($htag == "tt-rss" && $hversion == VERSION) {
2335 $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
2336 file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified);
2340 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2347 function stylesheet_tag($filename) {
2348 $timestamp = filemtime($filename);
2350 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2353 function javascript_tag($filename) {
2356 if (!(strpos($filename, "?") === FALSE)) {
2357 $query = substr($filename, strpos($filename, "?")+1);
2358 $filename = substr($filename, 0, strpos($filename, "?"));
2361 $timestamp = filemtime($filename);
2363 if ($query) $timestamp .= "&$query";
2365 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2368 function calculate_dep_timestamp() {
2369 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2373 foreach ($files as $file) {
2374 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2380 function T_js_decl($s1, $s2) {
2382 $s1 = preg_replace("/\n/", "", $s1);
2383 $s2 = preg_replace("/\n/", "", $s2);
2385 $s1 = preg_replace("/\"/", "\\\"", $s1);
2386 $s2 = preg_replace("/\"/", "\\\"", $s2);
2388 return "T_messages[\"$s1\"] = \"$s2\";\n";
2392 function init_js_translations() {
2394 print 'var T_messages = new Object();
2397 if (T_messages[msg]) {
2398 return T_messages[msg];
2404 function ngettext(msg1, msg2, n) {
2405 return __((parseInt(n) > 1) ? msg2 : msg1);
2408 $l10n = _get_reader();
2410 for ($i = 0; $i < $l10n->total; $i++) {
2411 $orig = $l10n->get_original_string($i);
2412 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2413 $key = explode(chr(0), $orig);
2414 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2415 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2417 $translation = __($orig);
2418 print T_js_decl($orig, $translation);
2423 function label_to_feed_id($label) {
2424 return LABEL_BASE_INDEX - 1 - abs($label);
2427 function feed_to_label_id($feed) {
2428 return LABEL_BASE_INDEX - 1 + abs($feed);
2431 function get_theme_path($theme) {
2432 $check = "themes/$theme";
2433 if (file_exists($check)) return $check;
2435 $check = "themes.local/$theme";
2436 if (file_exists($check)) return $check;
2439 function theme_valid($theme) {
2440 if ($theme == "default.css" || $theme == "night.css") return true; // needed for array_filter
2441 $file = "themes/" . basename($theme);
2443 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2445 if (file_exists($file) && is_readable($file)) {
2446 $fh = fopen($file, "r");
2449 $header = fgets($fh);
2452 return strpos($header, "supports-version:" . VERSION_STATIC) !== FALSE;
2459 function error_json($code) {
2460 require_once "errors.php";
2462 @$message = $ERRORS[$code];
2464 return json_encode(array("error" =>
2465 array("code" => $code, "message" => $message)));
2469 function abs_to_rel_path($dir) {
2470 $tmp = str_replace(dirname(__DIR__), "", $dir);
2472 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);