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;
20 $params["theme"] = get_pref("USER_CSS_THEME", false, false);
21 $params["plugins"] = implode(", ", PluginHost::getInstance()->get_plugin_names());
23 $params["php_platform"] = PHP_OS;
24 $params["php_version"] = PHP_VERSION;
26 $params["sanity_checksum"] = sha1(file_get_contents("include/sanity_check.php"));
28 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
29 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
31 $max_feed_id = db_fetch_result($result, 0, "mid");
32 $num_feeds = db_fetch_result($result, 0, "nf");
34 $params["max_feed_id"] = (int) $max_feed_id;
35 $params["num_feeds"] = (int) $num_feeds;
37 $params["hotkeys"] = get_hotkeys_map();
39 $params["csrf_token"] = $_SESSION["csrf_token"];
40 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
42 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE;
47 function get_hotkeys_info() {
49 __("Navigation") => array(
50 "next_feed" => __("Open next feed"),
51 "prev_feed" => __("Open previous feed"),
52 "next_article" => __("Open next article"),
53 "prev_article" => __("Open previous article"),
54 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
55 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
56 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
57 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
58 "search_dialog" => __("Show search dialog")),
59 __("Article") => array(
60 "toggle_mark" => __("Toggle starred"),
61 "toggle_publ" => __("Toggle published"),
62 "toggle_unread" => __("Toggle unread"),
63 "edit_tags" => __("Edit tags"),
64 "dismiss_selected" => __("Dismiss selected"),
65 "dismiss_read" => __("Dismiss read"),
66 "open_in_new_window" => __("Open in new window"),
67 "catchup_below" => __("Mark below as read"),
68 "catchup_above" => __("Mark above as read"),
69 "article_scroll_down" => __("Scroll down"),
70 "article_scroll_up" => __("Scroll up"),
71 "select_article_cursor" => __("Select article under cursor"),
72 "email_article" => __("Email article"),
73 "close_article" => __("Close/collapse article"),
74 "toggle_expand" => __("Toggle article expansion (combined mode)"),
75 "toggle_widescreen" => __("Toggle widescreen mode"),
76 "toggle_embed_original" => __("Toggle embed original")),
77 __("Article selection") => array(
78 "select_all" => __("Select all articles"),
79 "select_unread" => __("Select unread"),
80 "select_marked" => __("Select starred"),
81 "select_published" => __("Select published"),
82 "select_invert" => __("Invert selection"),
83 "select_none" => __("Deselect everything")),
85 "feed_refresh" => __("Refresh current feed"),
86 "feed_unhide_read" => __("Un/hide read feeds"),
87 "feed_subscribe" => __("Subscribe to feed"),
88 "feed_edit" => __("Edit feed"),
89 "feed_catchup" => __("Mark as read"),
90 "feed_reverse" => __("Reverse headlines"),
91 "feed_debug_update" => __("Debug feed update"),
92 "catchup_all" => __("Mark all feeds as read"),
93 "cat_toggle_collapse" => __("Un/collapse current category"),
94 "toggle_combined_mode" => __("Toggle combined mode"),
95 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
97 "goto_all" => __("All articles"),
98 "goto_fresh" => __("Fresh"),
99 "goto_marked" => __("Starred"),
100 "goto_published" => __("Published"),
101 "goto_tagcloud" => __("Tag cloud"),
102 "goto_prefs" => __("Preferences")),
103 __("Other") => array(
104 "create_label" => __("Create label"),
105 "create_filter" => __("Create filter"),
106 "collapse_sidebar" => __("Un/collapse sidebar"),
107 "help_dialog" => __("Show help dialog"))
110 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_INFO) as $plugin) {
111 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
117 function get_hotkeys_map() {
119 // "navigation" => array(
122 "n" => "next_article",
123 "p" => "prev_article",
124 "(38)|up" => "prev_article",
125 "(40)|down" => "next_article",
126 // "^(38)|Ctrl-up" => "prev_article_noscroll",
127 // "^(40)|Ctrl-down" => "next_article_noscroll",
128 "(191)|/" => "search_dialog",
129 // "article" => array(
130 "s" => "toggle_mark",
131 "*s" => "toggle_publ",
132 "u" => "toggle_unread",
134 "*d" => "dismiss_selected",
135 "*x" => "dismiss_read",
136 "o" => "open_in_new_window",
137 "c p" => "catchup_below",
138 "c n" => "catchup_above",
139 "*n" => "article_scroll_down",
140 "*p" => "article_scroll_up",
141 "*(38)|Shift+up" => "article_scroll_up",
142 "*(40)|Shift+down" => "article_scroll_down",
143 "a *w" => "toggle_widescreen",
144 "a e" => "toggle_embed_original",
145 "e" => "email_article",
146 "a q" => "close_article",
147 // "article_selection" => array(
148 "a a" => "select_all",
149 "a u" => "select_unread",
150 "a *u" => "select_marked",
151 "a p" => "select_published",
152 "a i" => "select_invert",
153 "a n" => "select_none",
155 "f r" => "feed_refresh",
156 "f a" => "feed_unhide_read",
157 "f s" => "feed_subscribe",
158 "f e" => "feed_edit",
159 "f q" => "feed_catchup",
160 "f x" => "feed_reverse",
161 "f *d" => "feed_debug_update",
162 "f *c" => "toggle_combined_mode",
163 "f c" => "toggle_cdm_expanded",
164 "*q" => "catchup_all",
165 "x" => "cat_toggle_collapse",
168 "g f" => "goto_fresh",
169 "g s" => "goto_marked",
170 "g p" => "goto_published",
171 "g t" => "goto_tagcloud",
172 "g *p" => "goto_prefs",
174 "(9)|Tab" => "select_article_cursor", // tab
175 "c l" => "create_label",
176 "c f" => "create_filter",
177 "c s" => "collapse_sidebar",
178 "^(191)|Ctrl+/" => "help_dialog",
181 if (get_pref('COMBINED_DISPLAY_MODE')) {
182 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
183 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
186 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) {
187 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
192 foreach (array_keys($hotkeys) as $hotkey) {
193 $pair = explode(" ", $hotkey, 2);
195 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
196 array_push($prefixes, $pair[0]);
200 return array($prefixes, $hotkeys);
203 function make_runtime_info() {
206 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
207 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
209 $max_feed_id = db_fetch_result($result, 0, "mid");
210 $num_feeds = db_fetch_result($result, 0, "nf");
212 $data["max_feed_id"] = (int) $max_feed_id;
213 $data["num_feeds"] = (int) $num_feeds;
215 $data['last_article_id'] = getLastArticleId();
216 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
218 $data['dep_ts'] = calculate_dep_timestamp();
219 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
221 if (file_exists(LOCK_DIRECTORY . "/update_daemon.lock")) {
223 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
225 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
227 $stamp = (int) @file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
230 $stamp_delta = time() - $stamp;
232 if ($stamp_delta > 1800) {
236 $_SESSION["daemon_stamp_check"] = time();
239 $data['daemon_stamp_ok'] = $stamp_check;
241 $stamp_fmt = date("Y.m.d, G:i", $stamp);
243 $data['daemon_stamp'] = $stamp_fmt;
248 if ($_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) {
249 $new_version_details = @check_for_update();
251 $data['new_version_available'] = (int) ($new_version_details != false);
253 $_SESSION["last_version_check"] = time();
254 $_SESSION["version_data"] = $new_version_details;
260 function search_to_sql($search) {
262 $search_query_part = "";
264 $keywords = str_getcsv($search, " ");
265 $query_keywords = array();
266 $search_words = array();
268 foreach ($keywords as $k) {
269 if (strpos($k, "-") === 0) {
276 $commandpair = explode(":", mb_strtolower($k), 2);
278 switch ($commandpair[0]) {
280 if ($commandpair[1]) {
281 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
282 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
284 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
285 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
286 array_push($search_words, $k);
290 if ($commandpair[1]) {
291 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
292 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
294 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
295 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
296 array_push($search_words, $k);
300 if ($commandpair[1]) {
301 if ($commandpair[1] == "true")
302 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
303 else if ($commandpair[1] == "false")
304 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
306 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
307 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
309 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
310 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
311 if (!$not) array_push($search_words, $k);
316 if ($commandpair[1]) {
317 if ($commandpair[1] == "true")
318 array_push($query_keywords, "($not (marked = true))");
320 array_push($query_keywords, "($not (marked = false))");
322 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
323 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
324 if (!$not) array_push($search_words, $k);
328 if ($commandpair[1]) {
329 if ($commandpair[1] == "true")
330 array_push($query_keywords, "($not (published = true))");
332 array_push($query_keywords, "($not (published = false))");
335 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
336 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
337 if (!$not) array_push($search_words, $k);
341 if (strpos($k, "@") === 0) {
343 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
344 $orig_ts = strtotime(substr($k, 1));
345 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
347 //$k = date("Y-m-d", strtotime(substr($k, 1)));
349 array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')");
351 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
352 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
354 if (!$not) array_push($search_words, $k);
359 $search_query_part = implode("AND", $query_keywords);
361 return array($search_query_part, $search_words);
364 function getParentCategories($cat, $owner_uid) {
367 $result = db_query("SELECT parent_cat FROM ttrss_feed_categories
368 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
370 while ($line = db_fetch_assoc($result)) {
371 array_push($rv, $line["parent_cat"]);
372 $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid));
378 function getChildCategories($cat, $owner_uid) {
381 $result = db_query("SELECT id FROM ttrss_feed_categories
382 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
384 while ($line = db_fetch_assoc($result)) {
385 array_push($rv, $line["id"]);
386 $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid));
392 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) {
394 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
396 $ext_tables_part = "";
397 $search_words = array();
401 if (SPHINX_ENABLED) {
402 $ids = join(",", @sphinx_search($search, 0, 500));
405 $search_query_part = "ref_id IN ($ids) AND ";
407 $search_query_part = "ref_id = -1 AND ";
410 list($search_query_part, $search_words) = search_to_sql($search);
411 $search_query_part .= " AND ";
415 $search_query_part = "";
420 if (DB_TYPE == "pgsql") {
421 $query_strategy_part .= " AND updated > NOW() - INTERVAL '14 days' ";
423 $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL 14 DAY) ";
426 $override_order = "updated DESC";
428 $filter_query_part = filter_to_sql($filter, $owner_uid);
430 // Try to check if SQL regexp implementation chokes on a valid regexp
433 $result = db_query("SELECT true AS true_val FROM ttrss_entries,
434 ttrss_user_entries, ttrss_feeds
435 WHERE $filter_query_part LIMIT 1", false);
438 $test = db_fetch_result($result, 0, "true_val");
441 $filter_query_part = "false AND";
443 $filter_query_part .= " AND";
446 $filter_query_part = "false AND";
450 $filter_query_part = "";
454 $since_id_part = "ttrss_entries.id > $since_id AND ";
459 $view_query_part = "";
461 if ($view_mode == "adaptive") {
463 $view_query_part = " ";
464 } else if ($feed != -1) {
466 $unread = getFeedUnread($feed, $cat_view);
468 if ($cat_view && $feed > 0 && $include_children)
469 $unread += getCategoryChildrenUnread($feed);
472 $view_query_part = " unread = true AND ";
477 if ($view_mode == "marked") {
478 $view_query_part = " marked = true AND ";
481 if ($view_mode == "has_note") {
482 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
485 if ($view_mode == "published") {
486 $view_query_part = " published = true AND ";
489 if ($view_mode == "unread" && $feed != -6) {
490 $view_query_part = " unread = true AND ";
494 $limit_query_part = "LIMIT " . $limit;
497 $allow_archived = false;
499 $vfeed_query_part = "";
501 // override query strategy and enable feed display when searching globally
502 if ($search && $search_mode == "all_feeds") {
503 $query_strategy_part = "true";
504 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
506 } else if (!is_numeric($feed)) {
507 $query_strategy_part = "true";
508 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
509 id = feed_id) as feed_title,";
510 } else if ($search && $search_mode == "this_cat") {
511 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
514 if ($include_children) {
515 $subcats = getChildCategories($feed, $owner_uid);
516 array_push($subcats, $feed);
517 $cats_qpart = join(",", $subcats);
522 $query_strategy_part = "ttrss_feeds.cat_id IN ($cats_qpart)";
525 $query_strategy_part = "ttrss_feeds.cat_id IS NULL";
528 } else if ($feed > 0) {
533 if ($include_children) {
535 $subcats = getChildCategories($feed, $owner_uid);
537 array_push($subcats, $feed);
538 $query_strategy_part = "cat_id IN (".
539 implode(",", $subcats).")";
542 $query_strategy_part = "cat_id = '$feed'";
546 $query_strategy_part = "cat_id IS NULL";
549 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
552 $query_strategy_part = "feed_id = '$feed'";
554 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
555 $query_strategy_part = "feed_id IS NULL";
556 $allow_archived = true;
557 } else if ($feed == 0 && $cat_view) { // uncategorized
558 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
559 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
560 } else if ($feed == -1) { // starred virtual feed
561 $query_strategy_part = "marked = true";
562 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
563 $allow_archived = true;
565 if (!$override_order) {
566 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
569 } else if ($feed == -2) { // published virtual feed OR labels category
572 $query_strategy_part = "published = true";
573 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
574 $allow_archived = true;
576 if (!$override_order) {
577 $override_order = "last_published DESC, date_entered DESC, updated DESC";
581 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
583 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
585 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
586 ttrss_user_labels2.article_id = ref_id";
589 } else if ($feed == -6) { // recently read
590 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
591 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
592 $allow_archived = true;
593 $ignore_vfeed_group = true;
595 if (!$override_order) $override_order = "last_read DESC";
597 /* } else if ($feed == -7) { // shared
598 $query_strategy_part = "uuid != ''";
599 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
600 $allow_archived = true; */
601 } else if ($feed == -3) { // fresh virtual feed
602 $query_strategy_part = "unread = true AND score >= 0";
604 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
606 if (DB_TYPE == "pgsql") {
607 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
609 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
612 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
613 } else if ($feed == -4) { // all articles virtual feed
614 $allow_archived = true;
615 $query_strategy_part = "true";
616 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
617 } else if ($feed <= LABEL_BASE_INDEX) { // labels
618 $label_id = feed_to_label_id($feed);
620 $query_strategy_part = "label_id = '$label_id' AND
621 ttrss_labels2.id = ttrss_user_labels2.label_id AND
622 ttrss_user_labels2.article_id = ref_id";
624 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
625 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
626 $allow_archived = true;
629 $query_strategy_part = "true";
632 $order_by = "score DESC, date_entered DESC, updated DESC";
634 if ($view_mode == "unread_first") {
635 $order_by = "unread DESC, $order_by";
638 if ($override_order) {
639 $order_by = $override_order;
642 if ($override_strategy) {
643 $query_strategy_part = $override_strategy;
646 if ($override_vfeed) {
647 $vfeed_query_part = $override_vfeed;
653 $feed_title = T_sprintf("Search results: %s", $search);
656 $feed_title = getCategoryTitle($feed);
658 if (is_numeric($feed) && $feed > 0) {
659 $result = db_query("SELECT title,site_url,last_error,last_updated
660 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
662 $feed_title = db_fetch_result($result, 0, "title");
663 $feed_site_url = db_fetch_result($result, 0, "site_url");
664 $last_error = db_fetch_result($result, 0, "last_error");
665 $last_updated = db_fetch_result($result, 0, "last_updated");
667 $feed_title = getFeedTitle($feed);
673 $content_query_part = "content, ";
676 if (is_numeric($feed)) {
679 $feed_kind = "Feeds";
681 $feed_kind = "Labels";
684 if ($limit_query_part) {
685 $offset_query_part = "OFFSET $offset";
688 // proper override_order applied above
689 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
690 if (!$override_order) {
691 $order_by = "ttrss_feeds.title, $order_by";
693 $order_by = "ttrss_feeds.title, $override_order";
697 if (!$allow_archived) {
698 $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part";
699 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
702 $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries
703 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
706 if ($vfeed_query_part)
707 $vfeed_query_part .= "favicon_avg_color,";
710 $start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts));
711 $start_ts_query_part = "date_entered >= '$start_ts_formatted' AND";
713 $start_ts_query_part = "";
716 $query = "SELECT DISTINCT
719 ttrss_entries.id,ttrss_entries.title,
723 always_display_enclosures,
732 unread,feed_id,marked,published,link,last_read,orig_feed_id,
733 last_marked, last_published,
741 ttrss_user_entries.ref_id = ttrss_entries.id AND
742 ttrss_user_entries.owner_uid = '$owner_uid' AND
748 $query_strategy_part ORDER BY $order_by
749 $limit_query_part $offset_query_part";
751 if ($_REQUEST["debug"]) print $query;
753 $result = db_query($query);
758 $select_qpart = "SELECT DISTINCT " .
762 "ttrss_entries.id as id," .
777 "(SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images," .
778 "last_marked, last_published, " .
781 $content_query_part .
785 $all_tags = explode(",", $feed);
786 if ($search_mode == 'any') {
787 $tag_sql = "tag_name in (" . implode(", ", array_map("db_quote", $all_tags)) . ")";
788 $from_qpart = " FROM ttrss_entries,ttrss_user_entries,ttrss_tags ";
789 $where_qpart = " WHERE " .
790 "ref_id = ttrss_entries.id AND " .
791 "ttrss_user_entries.owner_uid = $owner_uid AND " .
792 "post_int_id = int_id AND $tag_sql AND " .
795 $query_strategy_part . " ORDER BY $order_by " .
800 $sub_selects = array();
802 foreach ($all_tags as $term) {
803 array_push($sub_selects, "(SELECT post_int_id from ttrss_tags WHERE tag_name = " . db_quote($term) . " AND owner_uid = $owner_uid) as A$i");
810 array_push($sub_ands, "A$x.post_int_id = A$y.post_int_id");
815 array_push($sub_ands, "A1.post_int_id = ttrss_user_entries.int_id and ttrss_user_entries.owner_uid = $owner_uid");
816 array_push($sub_ands, "ttrss_user_entries.ref_id = ttrss_entries.id");
817 $from_qpart = " FROM " . implode(", ", $sub_selects) . ", ttrss_user_entries, ttrss_entries";
818 $where_qpart = " WHERE " . implode(" AND ", $sub_ands);
820 // error_log("TAG SQL: " . $tag_sql);
821 // $tag_sql = "tag_name = '$feed'"; DEFAULT way
823 // error_log("[". $select_qpart . "][" . $from_qpart . "][" .$where_qpart . "]");
824 $result = db_query($select_qpart . $from_qpart . $where_qpart);
827 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words);
831 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
832 if (!$owner) $owner = $_SESSION["uid"];
834 $res = trim($str); if (!$res) return '';
836 $charset_hack = '<head>
837 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
840 $res = trim($res); if (!$res) return '';
842 libxml_use_internal_errors(true);
844 $doc = new DOMDocument();
845 $doc->loadHTML($charset_hack . $res);
846 $xpath = new DOMXPath($doc);
848 $entries = $xpath->query('(//a[@href]|//img[@src])');
850 foreach ($entries as $entry) {
854 if ($entry->hasAttribute('href')) {
855 $entry->setAttribute('href',
856 rewrite_relative_url($site_url, $entry->getAttribute('href')));
858 $entry->setAttribute('rel', 'noreferrer');
861 if ($entry->hasAttribute('src')) {
862 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
864 $cached_filename = CACHE_DIR . '/images/' . sha1($src) . '.png';
866 if (file_exists($cached_filename)) {
867 $src = SELF_URL_PATH . '/image.php?hash=' . sha1($src);
870 $entry->setAttribute('src', $src);
873 if ($entry->nodeName == 'img') {
874 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
875 $force_remove_images || $_SESSION["bw_limit"]) {
877 $p = $doc->createElement('p');
879 $a = $doc->createElement('a');
880 $a->setAttribute('href', $entry->getAttribute('src'));
882 $a->appendChild(new DOMText($entry->getAttribute('src')));
883 $a->setAttribute('target', '_blank');
887 $entry->parentNode->replaceChild($p, $entry);
892 if (strtolower($entry->nodeName) == "a") {
893 $entry->setAttribute("target", "_blank");
897 $entries = $xpath->query('//iframe');
898 foreach ($entries as $entry) {
899 $entry->setAttribute('sandbox', 'allow-scripts');
903 $allowed_elements = array('a', 'address', 'audio', 'article', 'aside',
904 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
905 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
906 'data', 'dd', 'del', 'details', 'div', 'dl', 'font',
907 'dt', 'em', 'footer', 'figure', 'figcaption',
908 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
909 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
910 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
911 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
912 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
913 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
915 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
917 $disallowed_attributes = array('id', 'style', 'class');
919 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) {
920 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
921 if (is_array($retval)) {
923 $allowed_elements = $retval[1];
924 $disallowed_attributes = $retval[2];
930 $doc->removeChild($doc->firstChild); //remove doctype
931 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
933 if ($highlight_words) {
934 foreach ($highlight_words as $word) {
936 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
938 $elements = $xpath->query("//*/text()");
940 foreach ($elements as $child) {
942 $fragment = $doc->createDocumentFragment();
943 $text = $child->textContent;
945 while (($pos = mb_stripos($text, $word)) !== false) {
946 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
947 $word = mb_substr($text, $pos, mb_strlen($word));
948 $highlight = $doc->createElement('span');
949 $highlight->appendChild(new DomText($word));
950 $highlight->setAttribute('class', 'highlight');
951 $fragment->appendChild($highlight);
952 $text = mb_substr($text, $pos + mb_strlen($word));
955 if (!empty($text)) $fragment->appendChild(new DomText($text));
957 $child->parentNode->replaceChild($fragment, $child);
962 $res = $doc->saveHTML();
967 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
968 $xpath = new DOMXPath($doc);
969 $entries = $xpath->query('//*');
971 foreach ($entries as $entry) {
972 if (!in_array($entry->nodeName, $allowed_elements)) {
973 $entry->parentNode->removeChild($entry);
976 if ($entry->hasAttributes()) {
977 $attrs_to_remove = array();
979 foreach ($entry->attributes as $attr) {
981 if (strpos($attr->nodeName, 'on') === 0) {
982 array_push($attrs_to_remove, $attr);
985 if (in_array($attr->nodeName, $disallowed_attributes)) {
986 array_push($attrs_to_remove, $attr);
990 foreach ($attrs_to_remove as $attr) {
991 $entry->removeAttributeNode($attr);
999 function check_for_update() {
1000 if (CHECK_FOR_NEW_VERSION && $_SESSION['access_level'] >= 10) {
1001 $version_url = "http://tt-rss.org/version.php?ver=" . VERSION .
1002 "&iid=" . sha1(SELF_URL_PATH);
1004 $version_data = @fetch_file_contents($version_url);
1006 if ($version_data) {
1007 $version_data = json_decode($version_data, true);
1008 if ($version_data && $version_data['version']) {
1009 if (version_compare(VERSION_STATIC, $version_data['version']) == -1) {
1010 return $version_data;
1018 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
1020 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1021 if (count($ids) == 0) return;
1025 foreach ($ids as $id) {
1026 array_push($tmp_ids, "ref_id = '$id'");
1029 $ids_qpart = join(" OR ", $tmp_ids);
1032 db_query("UPDATE ttrss_user_entries SET
1033 unread = false,last_read = NOW()
1034 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1035 } else if ($cmode == 1) {
1036 db_query("UPDATE ttrss_user_entries SET
1038 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1040 db_query("UPDATE ttrss_user_entries SET
1041 unread = NOT unread,last_read = NOW()
1042 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1047 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
1048 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1050 while ($line = db_fetch_assoc($result)) {
1051 ccache_update($line["feed_id"], $owner_uid);
1055 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
1057 $a_id = db_escape_string($id);
1059 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1061 $query = "SELECT DISTINCT tag_name,
1062 owner_uid as owner FROM
1063 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
1064 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
1068 /* check cache first */
1070 if ($tag_cache === false) {
1071 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
1072 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1074 $tag_cache = db_fetch_result($result, 0, "tag_cache");
1078 $tags = explode(",", $tag_cache);
1081 /* do it the hard way */
1083 $tmp_result = db_query($query);
1085 while ($tmp_line = db_fetch_assoc($tmp_result)) {
1086 array_push($tags, $tmp_line["tag_name"]);
1089 /* update the cache */
1091 $tags_str = db_escape_string(join(",", $tags));
1093 db_query("UPDATE ttrss_user_entries
1094 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
1095 AND owner_uid = $owner_uid");
1101 function trim_array($array) {
1103 array_walk($tmp, 'trim');
1107 function tag_is_valid($tag) {
1108 if ($tag == '') return false;
1109 if (preg_match("/^[0-9]*$/", $tag)) return false;
1110 if (mb_strlen($tag) > 250) return false;
1112 if (!$tag) return false;
1117 function render_login_form() {
1118 header('Cache-Control: public');
1120 require_once "login_form.php";
1124 function format_warning($msg, $id = "") {
1125 return "<div class=\"warning\" id=\"$id\">
1126 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
1129 function format_notice($msg, $id = "") {
1130 return "<div class=\"notice\" id=\"$id\">
1131 <span><img src=\"images/information.png\"></span><span>$msg</span></div>";
1134 function format_error($msg, $id = "") {
1135 return "<div class=\"error\" id=\"$id\">
1136 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
1139 function print_notice($msg) {
1140 return print format_notice($msg);
1143 function print_warning($msg) {
1144 return print format_warning($msg);
1147 function print_error($msg) {
1148 return print format_error($msg);
1152 function T_sprintf() {
1153 $args = func_get_args();
1154 return vsprintf(__(array_shift($args)), $args);
1157 function format_inline_player($url, $ctype) {
1161 $url = htmlspecialchars($url);
1163 if (strpos($ctype, "audio/") === 0) {
1165 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1166 $_SESSION["hasMp3"])) {
1168 $entry .= "<audio preload=\"none\" controls>
1169 <source type=\"$ctype\" src=\"$url\"></source>
1174 $entry .= "<object type=\"application/x-shockwave-flash\"
1175 data=\"lib/button/musicplayer.swf?song_url=$url\"
1176 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1177 <param name=\"movie\"
1178 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1182 if ($entry) $entry .= " <a target=\"_blank\"
1183 href=\"$url\">" . basename($url) . "</a>";
1191 /* $filename = substr($url, strrpos($url, "/")+1);
1193 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1194 $filename . " (" . $ctype . ")" . "</a>"; */
1198 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1199 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1205 /* we can figure out feed_id from article id anyway, why do we
1206 * pass feed_id here? let's ignore the argument :(*/
1208 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1209 WHERE ref_id = '$id'");
1211 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1213 $rv['feed_id'] = $feed_id;
1215 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1217 if ($mark_as_read) {
1218 $result = db_query("UPDATE ttrss_user_entries
1219 SET unread = false,last_read = NOW()
1220 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1222 ccache_update($feed_id, $owner_uid);
1225 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1226 ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
1227 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1228 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1229 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1230 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1236 FROM ttrss_entries,ttrss_user_entries
1237 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1241 $line = db_fetch_assoc($result);
1243 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1244 unset($line["tag_cache"]);
1246 $line["content"] = sanitize($line["content"],
1247 sql_bool_to_bool($line['hide_images']),
1248 $owner_uid, $line["site_url"], false, $line["id"]);
1250 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) {
1251 $line = $p->hook_render_article($line);
1254 $num_comments = $line["num_comments"];
1255 $entry_comments = "";
1257 if ($num_comments > 0) {
1258 if ($line["comments"]) {
1259 $comments_url = htmlspecialchars($line["comments"]);
1261 $comments_url = htmlspecialchars($line["link"]);
1263 $entry_comments = "<a class=\"postComments\"
1264 target='_blank' href=\"$comments_url\">$num_comments ".
1265 _ngettext("comment", "comments", $num_comments)."</a>";
1268 if ($line["comments"] && $line["link"] != $line["comments"]) {
1269 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
1274 header("Content-Type: text/html");
1275 $rv['content'] .= "<html><head>
1276 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1277 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1278 stylesheet_tag("css/tt-rss.css").
1279 stylesheet_tag("css/zoom.css").
1280 stylesheet_tag("css/dijit.css")."
1282 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1283 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1285 <script type=\"text/javascript\">
1286 function openSelectedAttachment(elem) {
1288 var url = elem[elem.selectedIndex].value;
1292 elem.selectedIndex = 0;
1296 exception_error(\"openSelectedAttachment\", e);
1300 </head><body id=\"ttrssZoom\">";
1303 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1305 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1307 $entry_author = $line["author"];
1309 if ($entry_author) {
1310 $entry_author = __(" - ") . $entry_author;
1313 $parsed_updated = make_local_datetime($line["updated"], true,
1317 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1319 if ($line["link"]) {
1320 $rv['content'] .= "<div class='postTitle'><a target='_blank'
1321 title=\"".htmlspecialchars($line['title'])."\"
1323 htmlspecialchars($line["link"]) . "\">" .
1324 $line["title"] . "</a>" .
1325 "<span class='author'>$entry_author</span></div>";
1327 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1331 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
1332 "\" target=\"_blank\">".
1333 htmlspecialchars($line["feed_title"])."</a>";
1335 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1337 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1340 $tags_str = format_tags_string($line["tags"], $id);
1341 $tags_str_full = join(", ", $line["tags"]);
1343 if (!$tags_str_full) $tags_str_full = __("no tags");
1345 if (!$entry_comments) $entry_comments = " "; # placeholder
1347 $rv['content'] .= "<div class='postTags' style='float : right'>
1348 <img src='images/tag.png'
1349 class='tagsPic' alt='Tags' title='Tags'> ";
1352 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1353 <a title=\"".__('Edit tags for this article')."\"
1354 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1356 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1357 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1358 position=\"below\">$tags_str_full</div>";
1360 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
1361 $rv['content'] .= $p->hook_article_button($line);
1365 $tags_str = strip_tags($tags_str);
1366 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1368 $rv['content'] .= "</div>";
1369 $rv['content'] .= "<div clear='both'>";
1371 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
1372 $rv['content'] .= $p->hook_article_left_button($line);
1375 $rv['content'] .= "$entry_comments</div>";
1377 if ($line["orig_feed_id"]) {
1379 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
1380 WHERE id = ".$line["orig_feed_id"]);
1382 if (db_num_rows($tmp_result) != 0) {
1384 $rv['content'] .= "<div clear='both'>";
1385 $rv['content'] .= __("Originally from:");
1387 $rv['content'] .= " ";
1389 $tmp_line = db_fetch_assoc($tmp_result);
1391 $rv['content'] .= "<a target='_blank'
1392 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1393 $tmp_line['title'] . "</a>";
1395 $rv['content'] .= " ";
1397 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
1398 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1400 $rv['content'] .= "</div>";
1404 $rv['content'] .= "</div>";
1406 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1407 if ($line['note']) {
1408 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1410 $rv['content'] .= "</div>";
1412 if (!$line['lang']) $line['lang'] = 'en';
1414 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1416 $rv['content'] .= $line["content"];
1417 $rv['content'] .= format_article_enclosures($id,
1418 sql_bool_to_bool($line["always_display_enclosures"]),
1420 sql_bool_to_bool($line["hide_images"]));
1422 $rv['content'] .= "</div>";
1424 $rv['content'] .= "</div>";
1430 <div class='footer'>
1431 <button onclick=\"return window.close()\">".
1432 __("Close this window")."</button></div>";
1433 $rv['content'] .= "</body></html>";
1440 function print_checkpoint($n, $s) {
1441 $ts = microtime(true);
1442 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1446 function sanitize_tag($tag) {
1449 $tag = mb_strtolower($tag, 'utf-8');
1451 $tag = preg_replace('/[\'\"\+\>\<]/', "", $tag);
1453 // $tag = str_replace('"', "", $tag);
1454 // $tag = str_replace("+", " ", $tag);
1455 $tag = str_replace("technorati tag: ", "", $tag);
1460 function get_self_url_prefix() {
1461 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
1462 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
1464 return SELF_URL_PATH;
1469 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1471 * @return string The Mozilla Firefox feed adding URL.
1473 function add_feed_url() {
1474 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1476 $url_path = get_self_url_prefix() .
1477 "/public.php?op=subscribe&feed_url=%s";
1479 } // function add_feed_url
1481 function encrypt_password($pass, $salt = '', $mode2 = false) {
1482 if ($salt && $mode2) {
1483 return "MODE2:" . hash('sha256', $salt . $pass);
1485 return "SHA1X:" . sha1("$salt:$pass");
1487 return "SHA1:" . sha1($pass);
1489 } // function encrypt_password
1491 function load_filters($feed_id, $owner_uid, $action_id = false) {
1494 $cat_id = (int)getFeedCategory($feed_id);
1497 $null_cat_qpart = "cat_id IS NULL OR";
1499 $null_cat_qpart = "";
1501 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1502 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1504 $check_cats = join(",", array_merge(
1505 getParentCategories($cat_id, $owner_uid),
1508 while ($line = db_fetch_assoc($result)) {
1509 $filter_id = $line["id"];
1511 $result2 = db_query("SELECT
1512 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1513 FROM ttrss_filters2_rules AS r,
1514 ttrss_filter_types AS t
1516 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1517 (feed_id IS NULL OR feed_id = '$feed_id') AND
1518 filter_type = t.id AND filter_id = '$filter_id'");
1523 while ($rule_line = db_fetch_assoc($result2)) {
1524 # print_r($rule_line);
1527 $rule["reg_exp"] = $rule_line["reg_exp"];
1528 $rule["type"] = $rule_line["type_name"];
1529 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1531 array_push($rules, $rule);
1534 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1535 FROM ttrss_filters2_actions AS a,
1536 ttrss_filter_actions AS t
1538 action_id = t.id AND filter_id = '$filter_id'");
1540 while ($action_line = db_fetch_assoc($result2)) {
1541 # print_r($action_line);
1544 $action["type"] = $action_line["type_name"];
1545 $action["param"] = $action_line["action_param"];
1547 array_push($actions, $action);
1552 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1553 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1554 $filter["rules"] = $rules;
1555 $filter["actions"] = $actions;
1557 if (count($rules) > 0 && count($actions) > 0) {
1558 array_push($filters, $filter);
1565 function get_score_pic($score) {
1567 return "score_high.png";
1568 } else if ($score > 0) {
1569 return "score_half_high.png";
1570 } else if ($score < -100) {
1571 return "score_low.png";
1572 } else if ($score < 0) {
1573 return "score_half_low.png";
1575 return "score_neutral.png";
1579 function feed_has_icon($id) {
1580 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
1583 function init_plugins() {
1584 PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL);
1589 function format_tags_string($tags, $id) {
1590 if (!is_array($tags) || count($tags) == 0) {
1591 return __("no tags");
1593 $maxtags = min(5, count($tags));
1595 for ($i = 0; $i < $maxtags; $i++) {
1596 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed('".$tags[$i]."')\">" . $tags[$i] . "</a>, ";
1599 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1601 if (count($tags) > $maxtags)
1602 $tags_str .= ", …";
1608 function format_article_labels($labels, $id) {
1610 if (!is_array($labels)) return '';
1614 foreach ($labels as $l) {
1615 $labels_str .= sprintf("<span class='hlLabelRef'
1616 style='color : %s; background-color : %s'>%s</span>",
1617 $l[2], $l[3], $l[1]);
1624 function format_article_note($id, $note, $allow_edit = true) {
1626 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1627 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1628 ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
1634 function get_feed_category($feed_cat, $parent_cat_id = false) {
1635 if ($parent_cat_id) {
1636 $parent_qpart = "parent_cat = '$parent_cat_id'";
1637 $parent_insert = "'$parent_cat_id'";
1639 $parent_qpart = "parent_cat IS NULL";
1640 $parent_insert = "NULL";
1644 "SELECT id FROM ttrss_feed_categories
1645 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1647 if (db_num_rows($result) == 0) {
1650 return db_fetch_result($result, 0, "id");
1654 function add_feed_category($feed_cat, $parent_cat_id = false) {
1656 if (!$feed_cat) return false;
1660 if ($parent_cat_id) {
1661 $parent_qpart = "parent_cat = '$parent_cat_id'";
1662 $parent_insert = "'$parent_cat_id'";
1664 $parent_qpart = "parent_cat IS NULL";
1665 $parent_insert = "NULL";
1668 $feed_cat = mb_substr($feed_cat, 0, 250);
1671 "SELECT id FROM ttrss_feed_categories
1672 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1674 if (db_num_rows($result) == 0) {
1677 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1678 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1688 function getArticleFeed($id) {
1689 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1690 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1692 if (db_num_rows($result) != 0) {
1693 return db_fetch_result($result, 0, "feed_id");
1700 * Fixes incomplete URLs by prepending "http://".
1701 * Also replaces feed:// with http://, and
1702 * prepends a trailing slash if the url is a domain name only.
1704 * @param string $url Possibly incomplete URL
1706 * @return string Fixed URL.
1708 function fix_url($url) {
1709 if (strpos($url, '://') === false) {
1710 $url = 'http://' . $url;
1711 } else if (substr($url, 0, 5) == 'feed:') {
1712 $url = 'http:' . substr($url, 5);
1715 //prepend slash if the URL has no slash in it
1716 // "http://www.example" -> "http://www.example/"
1717 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
1721 if ($url != "http:///")
1727 function validate_feed_url($url) {
1728 $parts = parse_url($url);
1730 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
1734 function get_article_enclosures($id) {
1736 $query = "SELECT * FROM ttrss_enclosures
1737 WHERE post_id = '$id' AND content_url != ''";
1741 $result = db_query($query);
1743 if (db_num_rows($result) > 0) {
1744 while ($line = db_fetch_assoc($result)) {
1745 array_push($rv, $line);
1752 function save_email_address($email) {
1753 // FIXME: implement persistent storage of emails
1755 if (!$_SESSION['stored_emails'])
1756 $_SESSION['stored_emails'] = array();
1758 if (!in_array($email, $_SESSION['stored_emails']))
1759 array_push($_SESSION['stored_emails'], $email);
1763 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1765 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1767 $sql_is_cat = bool_to_sql_bool($is_cat);
1769 $result = db_query("SELECT access_key FROM ttrss_access_keys
1770 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1771 AND owner_uid = " . $owner_uid);
1773 if (db_num_rows($result) == 1) {
1774 return db_fetch_result($result, 0, "access_key");
1776 $key = db_escape_string(uniqid(base_convert(rand(), 10, 36)));
1778 $result = db_query("INSERT INTO ttrss_access_keys
1779 (access_key, feed_id, is_cat, owner_uid)
1780 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1787 function get_feeds_from_html($url, $content)
1789 $url = fix_url($url);
1790 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
1792 libxml_use_internal_errors(true);
1794 $doc = new DOMDocument();
1795 $doc->loadHTML($content);
1796 $xpath = new DOMXPath($doc);
1797 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
1798 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
1799 $feedUrls = array();
1800 foreach ($entries as $entry) {
1801 if ($entry->hasAttribute('href')) {
1802 $title = $entry->getAttribute('title');
1804 $title = $entry->getAttribute('type');
1806 $feedUrl = rewrite_relative_url(
1807 $baseUrl, $entry->getAttribute('href')
1809 $feedUrls[$feedUrl] = $title;
1815 function is_html($content) {
1816 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 20)) !== 0;
1819 function url_is_html($url, $login = false, $pass = false) {
1820 return is_html(fetch_file_contents($url, false, $login, $pass));
1823 function print_label_select($name, $value, $attributes = "") {
1825 $result = db_query("SELECT caption FROM ttrss_labels2
1826 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1828 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1829 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
1831 while ($line = db_fetch_assoc($result)) {
1833 $issel = ($line["caption"] == $value) ? "selected=\"1\"" : "";
1835 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1836 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1840 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1847 function format_article_enclosures($id, $always_display_enclosures,
1848 $article_content, $hide_images = false) {
1850 $result = get_article_enclosures($id);
1853 if (count($result) > 0) {
1855 $entries_html = array();
1857 $entries_inline = array();
1859 foreach ($result as $line) {
1861 $url = $line["content_url"];
1862 $ctype = $line["content_type"];
1863 $title = $line["title"];
1865 if (!$ctype) $ctype = __("unknown type");
1867 $filename = substr($url, strrpos($url, "/")+1);
1869 $player = format_inline_player($url, $ctype);
1871 if ($player) array_push($entries_inline, $player);
1873 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1874 # $filename . " (" . $ctype . ")" . "</a>";
1876 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
1877 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1879 array_push($entries_html, $entry);
1883 $entry["type"] = $ctype;
1884 $entry["filename"] = $filename;
1885 $entry["url"] = $url;
1886 $entry["title"] = $title;
1888 array_push($entries, $entry);
1891 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
1892 if ($always_display_enclosures ||
1893 !preg_match("/<img/i", $article_content)) {
1895 foreach ($entries as $entry) {
1897 if (preg_match("/image/", $entry["type"]) ||
1898 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
1900 if (!$hide_images) {
1902 alt=\"".htmlspecialchars($entry["filename"])."\"
1903 src=\"" .htmlspecialchars($entry["url"]) . "\"/></p>";
1905 $rv .= "<p><a target=\"_blank\"
1906 href=\"".htmlspecialchars($entry["url"])."\"
1907 >" .htmlspecialchars($entry["url"]) . "</a></p>";
1910 if ($entry['title']) {
1911 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
1918 if (count($entries_inline) > 0) {
1919 $rv .= "<hr clear='both'/>";
1920 foreach ($entries_inline as $entry) { $rv .= $entry; };
1921 $rv .= "<hr clear='both'/>";
1924 $rv .= "<select class=\"attachments\" onchange=\"openSelectedAttachment(this)\">".
1925 "<option value=''>" . __('Attachments')."</option>";
1927 foreach ($entries as $entry) {
1928 if ($entry["title"])
1929 $title = "— " . truncate_string($entry["title"], 30);
1933 $rv .= "<option value=\"".htmlspecialchars($entry["url"])."\">" . htmlspecialchars($entry["filename"]) . "$title</option>";
1943 function getLastArticleId() {
1944 $result = db_query("SELECT MAX(ref_id) AS id FROM ttrss_user_entries
1945 WHERE owner_uid = " . $_SESSION["uid"]);
1947 if (db_num_rows($result) == 1) {
1948 return db_fetch_result($result, 0, "id");
1954 function build_url($parts) {
1955 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
1959 * Converts a (possibly) relative URL to a absolute one.
1961 * @param string $url Base URL (i.e. from where the document is)
1962 * @param string $rel_url Possibly relative URL in the document
1964 * @return string Absolute URL
1966 function rewrite_relative_url($url, $rel_url) {
1967 if (strpos($rel_url, ":") !== false) {
1969 } else if (strpos($rel_url, "://") !== false) {
1971 } else if (strpos($rel_url, "//") === 0) {
1972 # protocol-relative URL (rare but they exist)
1974 } else if (strpos($rel_url, "/") === 0)
1976 $parts = parse_url($url);
1977 $parts['path'] = $rel_url;
1979 return build_url($parts);
1982 $parts = parse_url($url);
1983 if (!isset($parts['path'])) {
1984 $parts['path'] = '/';
1986 $dir = $parts['path'];
1987 if (substr($dir, -1) !== '/') {
1988 $dir = dirname($parts['path']);
1989 $dir !== '/' && $dir .= '/';
1991 $parts['path'] = $dir . $rel_url;
1993 return build_url($parts);
1997 function sphinx_search($query, $offset = 0, $limit = 30) {
1998 require_once 'lib/sphinxapi.php';
2000 $sphinxClient = new SphinxClient();
2002 $sphinxpair = explode(":", SPHINX_SERVER, 2);
2004 $sphinxClient->SetServer($sphinxpair[0], (int)$sphinxpair[1]);
2005 $sphinxClient->SetConnectTimeout(1);
2007 $sphinxClient->SetFieldWeights(array('title' => 70, 'content' => 30,
2008 'feed_title' => 20));
2010 $sphinxClient->SetMatchMode(SPH_MATCH_EXTENDED2);
2011 $sphinxClient->SetRankingMode(SPH_RANK_PROXIMITY_BM25);
2012 $sphinxClient->SetLimits($offset, $limit, 1000);
2013 $sphinxClient->SetArrayResult(false);
2014 $sphinxClient->SetFilter('owner_uid', array($_SESSION['uid']));
2016 $result = $sphinxClient->Query($query, SPHINX_INDEX);
2020 if (is_array($result['matches'])) {
2021 foreach (array_keys($result['matches']) as $int_id) {
2022 $ref_id = $result['matches'][$int_id]['attrs']['ref_id'];
2023 array_push($ids, $ref_id);
2030 function cleanup_tags($days = 14, $limit = 1000) {
2032 if (DB_TYPE == "pgsql") {
2033 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2034 } else if (DB_TYPE == "mysql") {
2035 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2040 while ($limit > 0) {
2043 $query = "SELECT ttrss_tags.id AS id
2044 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2045 WHERE post_int_id = int_id AND $interval_query AND
2046 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2048 $result = db_query($query);
2052 while ($line = db_fetch_assoc($result)) {
2053 array_push($ids, $line['id']);
2056 if (count($ids) > 0) {
2057 $ids = join(",", $ids);
2059 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2060 $tags_deleted += db_affected_rows($tmp_result);
2065 $limit -= $limit_part;
2068 return $tags_deleted;
2071 function print_user_stylesheet() {
2072 $value = get_pref('USER_STYLESHEET');
2075 print "<style type=\"text/css\">";
2076 print str_replace("<br/>", "\n", $value);
2082 function filter_to_sql($filter, $owner_uid) {
2085 if (DB_TYPE == "pgsql")
2088 $reg_qpart = "REGEXP";
2090 foreach ($filter["rules"] AS $rule) {
2091 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2092 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2093 $rule['reg_exp']) !== FALSE;
2095 if ($regexp_valid) {
2097 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2099 switch ($rule["type"]) {
2101 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2102 $rule['reg_exp'] . "')";
2105 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2106 $rule['reg_exp'] . "')";
2109 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2110 $rule['reg_exp'] . "') OR LOWER(" .
2111 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2114 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2115 $rule['reg_exp'] . "')";
2118 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2119 $rule['reg_exp'] . "')";
2122 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2123 $rule['reg_exp'] . "')";
2127 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2129 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2130 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2133 if (isset($rule["cat_id"])) {
2135 if ($rule["cat_id"] > 0) {
2136 $children = getChildCategories($rule["cat_id"], $owner_uid);
2137 array_push($children, $rule["cat_id"]);
2139 $children = join(",", $children);
2141 $cat_qpart = "cat_id IN ($children)";
2143 $cat_qpart = "cat_id IS NULL";
2146 $qpart .= " AND $cat_qpart";
2149 $qpart .= " AND feed_id IS NOT NULL";
2151 array_push($query, "($qpart)");
2156 if (count($query) > 0) {
2157 $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
2159 $fullquery = "(false)";
2162 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2167 if (!function_exists('gzdecode')) {
2168 function gzdecode($string) { // no support for 2nd argument
2169 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2170 base64_encode($string));
2174 function get_random_bytes($length) {
2175 if (function_exists('openssl_random_pseudo_bytes')) {
2176 return openssl_random_pseudo_bytes($length);
2180 for ($i = 0; $i < $length; $i++)
2181 $output .= chr(mt_rand(0, 255));
2187 function read_stdin() {
2188 $fp = fopen("php://stdin", "r");
2191 $line = trim(fgets($fp));
2199 function tmpdirname($path, $prefix) {
2200 // Use PHP's tmpfile function to create a temporary
2201 // directory name. Delete the file and keep the name.
2202 $tempname = tempnam($path,$prefix);
2206 if (!unlink($tempname))
2212 function getFeedCategory($feed) {
2213 $result = db_query("SELECT cat_id FROM ttrss_feeds
2214 WHERE id = '$feed'");
2216 if (db_num_rows($result) > 0) {
2217 return db_fetch_result($result, 0, "cat_id");
2224 function implements_interface($class, $interface) {
2225 return in_array($interface, class_implements($class));
2228 function geturl($url, $depth = 0){
2230 if ($depth == 20) return $url;
2232 if (!function_exists('curl_init'))
2233 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);
2235 $curl = curl_init();
2236 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
2237 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
2238 $header[] = "Cache-Control: max-age=0";
2239 $header[] = "Connection: keep-alive";
2240 $header[] = "Keep-Alive: 300";
2241 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
2242 $header[] = "Accept-Language: en-us,en;q=0.5";
2243 $header[] = "Pragma: ";
2245 curl_setopt($curl, CURLOPT_URL, $url);
2246 curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
2247 curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
2248 curl_setopt($curl, CURLOPT_HEADER, true);
2249 curl_setopt($curl, CURLOPT_REFERER, $url);
2250 curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
2251 curl_setopt($curl, CURLOPT_AUTOREFERER, true);
2252 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
2253 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
2254 curl_setopt($curl, CURLOPT_TIMEOUT, 60);
2255 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
2257 if (defined('_CURL_HTTP_PROXY')) {
2258 curl_setopt($curl, CURLOPT_PROXY, _CURL_HTTP_PROXY);
2261 if ((OPENSSL_VERSION_NUMBER >= 0x0090808f) && (OPENSSL_VERSION_NUMBER < 0x10000000)) {
2262 curl_setopt($curl, CURLOPT_SSLVERSION, 3);
2265 $html = curl_exec($curl);
2267 $status = curl_getinfo($curl);
2269 if($status['http_code']!=200){
2270 if($status['http_code'] == 301 || $status['http_code'] == 302) {
2272 list($header) = explode("\r\n\r\n", $html, 2);
2274 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
2275 $url = trim(str_replace($matches[1],"",$matches[0]));
2276 $url_parsed = parse_url($url);
2277 return (isset($url_parsed))? geturl($url, $depth + 1):'';
2280 global $fetch_last_error;
2282 $fetch_last_error = curl_errno($curl) . " " . curl_error($curl);
2286 # foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
2287 # $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
2288 # $handle = @fopen('./curl.error.log', 'a');
2289 # fwrite($handle, $line);
2296 function get_minified_js($files) {
2297 require_once 'lib/jshrink/Minifier.php';
2301 foreach ($files as $js) {
2302 if (!isset($_GET['debug'])) {
2303 $cached_file = CACHE_DIR . "/js/".basename($js).".js";
2305 if (file_exists($cached_file) &&
2306 is_readable($cached_file) &&
2307 filemtime($cached_file) >= filemtime("js/$js.js")) {
2309 $rv .= file_get_contents($cached_file);
2312 $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
2313 file_put_contents($cached_file, $minified);
2317 $rv .= file_get_contents("js/$js.js");
2324 function stylesheet_tag($filename) {
2325 $timestamp = filemtime($filename);
2327 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2330 function javascript_tag($filename) {
2333 if (!(strpos($filename, "?") === FALSE)) {
2334 $query = substr($filename, strpos($filename, "?")+1);
2335 $filename = substr($filename, 0, strpos($filename, "?"));
2338 $timestamp = filemtime($filename);
2340 if ($query) $timestamp .= "&$query";
2342 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2345 function calculate_dep_timestamp() {
2346 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2350 foreach ($files as $file) {
2351 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2357 function T_js_decl($s1, $s2) {
2359 $s1 = preg_replace("/\n/", "", $s1);
2360 $s2 = preg_replace("/\n/", "", $s2);
2362 $s1 = preg_replace("/\"/", "\\\"", $s1);
2363 $s2 = preg_replace("/\"/", "\\\"", $s2);
2365 return "T_messages[\"$s1\"] = \"$s2\";\n";
2369 function init_js_translations() {
2371 print 'var T_messages = new Object();
2374 if (T_messages[msg]) {
2375 return T_messages[msg];
2381 function ngettext(msg1, msg2, n) {
2382 return __((parseInt(n) > 1) ? msg2 : msg1);
2385 $l10n = _get_reader();
2387 for ($i = 0; $i < $l10n->total; $i++) {
2388 $orig = $l10n->get_original_string($i);
2389 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2390 $key = explode(chr(0), $orig);
2391 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2392 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2394 $translation = __($orig);
2395 print T_js_decl($orig, $translation);
2400 function label_to_feed_id($label) {
2401 return LABEL_BASE_INDEX - 1 - abs($label);
2404 function feed_to_label_id($feed) {
2405 return LABEL_BASE_INDEX - 1 + abs($feed);
2408 function format_libxml_error($error) {
2409 return T_sprintf("LibXML error %s at line %d (column %d): %s",
2410 $error->code, $error->line, $error->column,