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, $search_language) {
285 $keywords = str_getcsv($search, " ");
286 $query_keywords = array();
287 $search_words = array();
288 $search_query_leftover = array();
290 if ($search_language)
291 $search_language = db_escape_string(mb_strtolower($search_language));
293 $search_language = "english";
295 foreach ($keywords as $k) {
296 if (strpos($k, "-") === 0) {
303 $commandpair = explode(":", mb_strtolower($k), 2);
305 switch ($commandpair[0]) {
307 if ($commandpair[1]) {
308 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
309 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
311 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
312 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
313 array_push($search_words, $k);
317 if ($commandpair[1]) {
318 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
319 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
321 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
322 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
323 array_push($search_words, $k);
327 if ($commandpair[1]) {
328 if ($commandpair[1] == "true")
329 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
330 else if ($commandpair[1] == "false")
331 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
333 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
334 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
336 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
337 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
338 if (!$not) array_push($search_words, $k);
343 if ($commandpair[1]) {
344 if ($commandpair[1] == "true")
345 array_push($query_keywords, "($not (marked = true))");
347 array_push($query_keywords, "($not (marked = false))");
349 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
350 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
351 if (!$not) array_push($search_words, $k);
355 if ($commandpair[1]) {
356 if ($commandpair[1] == "true")
357 array_push($query_keywords, "($not (published = true))");
359 array_push($query_keywords, "($not (published = false))");
362 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
363 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
364 if (!$not) array_push($search_words, $k);
368 if ($commandpair[1]) {
369 if ($commandpair[1] == "true")
370 array_push($query_keywords, "($not (unread = true))");
372 array_push($query_keywords, "($not (unread = false))");
375 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
376 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
377 if (!$not) array_push($search_words, $k);
381 if (strpos($k, "@") === 0) {
383 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
384 $orig_ts = strtotime(substr($k, 1));
385 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
387 //$k = date("Y-m-d", strtotime(substr($k, 1)));
389 array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')");
392 if (DB_TYPE == "pgsql") {
393 $k = mb_strtolower($k);
394 array_push($search_query_leftover, $not ? "!$k" : $k);
396 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
397 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
400 if (!$not) array_push($search_words, $k);
405 if (count($search_query_leftover) > 0) {
406 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
408 if (DB_TYPE == "pgsql") {
409 array_push($query_keywords,
410 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
415 $search_query_part = implode("AND", $query_keywords);
417 return array($search_query_part, $search_words);
420 function getParentCategories($cat, $owner_uid) {
423 $result = db_query("SELECT parent_cat FROM ttrss_feed_categories
424 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
426 while ($line = db_fetch_assoc($result)) {
427 array_push($rv, $line["parent_cat"]);
428 $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid));
434 function getChildCategories($cat, $owner_uid) {
437 $result = db_query("SELECT id FROM ttrss_feed_categories
438 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
440 while ($line = db_fetch_assoc($result)) {
441 array_push($rv, $line["id"]);
442 $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid));
448 function queryFeedHeadlines($params) {
450 $feed = $params["feed"];
451 $limit = isset($params["limit"]) ? $params["limit"] : 30;
452 $view_mode = $params["view_mode"];
453 $cat_view = isset($params["cat_view"]) ? $params["cat_view"] : false;
454 $search = isset($params["search"]) ? $params["search"] : false;
455 $search_language = isset($params["search_language"]) ? $params["search_language"] : "";
456 $override_order = isset($params["override_order"]) ? $params["override_order"] : false;
457 $offset = isset($params["offset"]) ? $params["offset"] : 0;
458 $owner_uid = isset($params["owner_uid"]) ? $params["owner_uid"] : $_SESSION["uid"];
459 $since_id = isset($params["since_id"]) ? $params["since_id"] : 0;
460 $include_children = isset($params["include_children"]) ? $params["include_children"] : false;
461 $ignore_vfeed_group = isset($params["ignore_vfeed_group"]) ? $params["ignore_vfeed_group"] : false;
462 $override_strategy = isset($params["override_strategy"]) ? $params["override_strategy"] : false;
463 $override_vfeed = isset($params["override_vfeed"]) ? $params["override_vfeed"] : false;
464 $start_ts = isset($params["start_ts"]) ? $params["start_ts"] : false;
465 $check_first_id = isset($params["check_first_id"]) ? $params["check_first_id"] : false;
466 $api_request = isset($params["api_request"]) ? $params["api_request"] : false;
468 $ext_tables_part = "";
469 $query_strategy_part = "";
471 $search_words = array();
474 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH) as $plugin) {
475 list($search_query_part, $search_words) = $plugin->hook_search($search);
479 // fall back in case of no plugins
480 if (!$search_query_part) {
481 list($search_query_part, $search_words) = search_to_sql($search, $search_language);
483 $search_query_part .= " AND ";
485 $search_query_part = "";
489 $since_id_part = "ttrss_entries.id > $since_id AND ";
494 $view_query_part = "";
495 $disable_offsets = false;
497 if ($view_mode == "adaptive") {
499 $view_query_part = " ";
500 } else if ($feed != -1) {
502 $unread = getFeedUnread($feed, $cat_view);
504 if ($cat_view && $feed > 0 && $include_children)
505 $unread += getCategoryChildrenUnread($feed);
508 $view_query_part = " unread = true AND ";
509 $disable_offsets = !$api_request && get_pref("CDM_AUTO_CATCHUP") && get_pref("CDM_EXPANDED");
514 if ($view_mode == "marked") {
515 $view_query_part = " marked = true AND ";
518 if ($view_mode == "has_note") {
519 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
522 if ($view_mode == "published") {
523 $view_query_part = " published = true AND ";
526 if ($view_mode == "unread" && $feed != -6) {
527 $view_query_part = " unread = true AND ";
528 $disable_offsets = !$api_request && get_pref("CDM_AUTO_CATCHUP") && get_pref("CDM_EXPANDED");
532 $limit_query_part = "LIMIT " . $limit;
535 $allow_archived = false;
537 $vfeed_query_part = "";
540 if (!is_numeric($feed)) {
541 $query_strategy_part = "true";
542 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
543 id = feed_id) as feed_title,";
544 } else if ($feed > 0) {
549 if ($include_children) {
551 $subcats = getChildCategories($feed, $owner_uid);
553 array_push($subcats, $feed);
554 $query_strategy_part = "cat_id IN (".
555 implode(",", $subcats).")";
558 $query_strategy_part = "cat_id = '$feed'";
562 $query_strategy_part = "cat_id IS NULL";
565 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
568 $query_strategy_part = "feed_id = '$feed'";
570 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
571 $query_strategy_part = "feed_id IS NULL";
572 $allow_archived = true;
573 } else if ($feed == 0 && $cat_view) { // uncategorized
574 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
575 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
576 } else if ($feed == -1) { // starred virtual feed
577 $query_strategy_part = "marked = true";
578 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
579 $allow_archived = true;
581 if (!$override_order) {
582 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
585 } else if ($feed == -2) { // published virtual feed OR labels category
588 $query_strategy_part = "published = true";
589 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
590 $allow_archived = true;
592 if (!$override_order) {
593 $override_order = "last_published DESC, date_entered DESC, updated DESC";
597 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
599 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
601 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
602 ttrss_user_labels2.article_id = ref_id";
605 } else if ($feed == -6) { // recently read
606 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
607 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
608 $allow_archived = true;
609 $ignore_vfeed_group = true;
611 if (!$override_order) $override_order = "last_read DESC";
613 } else if ($feed == -3) { // fresh virtual feed
614 $query_strategy_part = "unread = true AND score >= 0";
616 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
618 if (DB_TYPE == "pgsql") {
619 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
621 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
624 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
625 } else if ($feed == -4) { // all articles virtual feed
626 $allow_archived = true;
627 $query_strategy_part = "true";
628 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
629 } else if ($feed <= LABEL_BASE_INDEX) { // labels
630 $label_id = feed_to_label_id($feed);
632 $query_strategy_part = "label_id = '$label_id' AND
633 ttrss_labels2.id = ttrss_user_labels2.label_id AND
634 ttrss_user_labels2.article_id = ref_id";
636 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
637 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
638 $allow_archived = true;
641 $query_strategy_part = "true";
644 $order_by = "score DESC, date_entered DESC, updated DESC";
646 if ($override_order) {
647 $order_by = $override_order;
650 if ($override_strategy) {
651 $query_strategy_part = $override_strategy;
654 if ($override_vfeed) {
655 $vfeed_query_part = $override_vfeed;
661 $feed_title = T_sprintf("Search results: %s", $search);
664 $feed_title = getCategoryTitle($feed);
666 if (is_numeric($feed) && $feed > 0) {
667 $result = db_query("SELECT title,site_url,last_error,last_updated
668 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
670 $feed_title = db_fetch_result($result, 0, "title");
671 $feed_site_url = db_fetch_result($result, 0, "site_url");
672 $last_error = db_fetch_result($result, 0, "last_error");
673 $last_updated = db_fetch_result($result, 0, "last_updated");
675 $feed_title = getFeedTitle($feed);
681 $content_query_part = "content, ";
683 if ($limit_query_part) {
684 $offset_query_part = "OFFSET $offset";
686 $offset_query_part = "";
689 if (is_numeric($feed)) {
690 // proper override_order applied above
691 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
692 if (!$override_order) {
693 $order_by = "ttrss_feeds.title, $order_by";
695 $order_by = "ttrss_feeds.title, $override_order";
699 if (!$allow_archived) {
700 $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part";
701 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
704 $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries
705 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
708 if ($vfeed_query_part) $vfeed_query_part .= "favicon_avg_color,";
711 $start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts));
712 $start_ts_query_part = "date_entered >= '$start_ts_formatted' AND";
714 $start_ts_query_part = "";
718 $first_id_query_strategy_part = $query_strategy_part;
721 $first_id_query_strategy_part = "true";
723 if (DB_TYPE == "pgsql") {
724 $sanity_interval_qpart = "date_entered >= NOW() - INTERVAL '1 hour' AND";
726 $sanity_interval_qpart = "date_entered >= DATE_SUB(NOW(), INTERVAL 1 hour) AND";
729 if (!$search && !$disable_offsets) {
730 // if previous topmost article id changed that means our current pagination is no longer valid
731 $query = "SELECT DISTINCT
748 ttrss_user_entries.ref_id = ttrss_entries.id AND
749 ttrss_user_entries.owner_uid = '$owner_uid' AND
753 $sanity_interval_qpart
754 $first_id_query_strategy_part ORDER BY $order_by LIMIT 1";
756 if ($_REQUEST["debug"]) {
760 $result = db_query($query);
761 if ($result && db_num_rows($result) > 0) {
762 $first_id = (int)db_fetch_result($result, 0, "id");
764 if ($offset > 0 && $first_id && $check_first_id && $first_id != $check_first_id) {
765 return array(-1, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
770 if ($disable_offsets) {
771 $offset_query_part = "";
774 $query = "SELECT DISTINCT
777 ttrss_entries.id,ttrss_entries.title,
781 always_display_enclosures,
790 unread,feed_id,marked,published,link,last_read,orig_feed_id,
791 last_marked, last_published,
799 ttrss_user_entries.ref_id = ttrss_entries.id AND
800 ttrss_user_entries.owner_uid = '$owner_uid' AND
805 $query_strategy_part ORDER BY $order_by
806 $limit_query_part $offset_query_part";
808 if ($_REQUEST["debug"]) print $query;
810 $result = db_query($query);
815 $query = "SELECT DISTINCT
819 ttrss_entries.id as id,
834 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images,
835 last_marked, last_published,
840 FROM ttrss_entries, ttrss_user_entries, ttrss_tags
842 ref_id = ttrss_entries.id AND
843 ttrss_user_entries.owner_uid = $owner_uid AND
844 post_int_id = int_id AND
845 tag_name = '$feed' AND
848 $query_strategy_part ORDER BY $order_by
849 $limit_query_part $offset_query_part";
851 if ($_REQUEST["debug"]) print $query;
853 $result = db_query($query);
856 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
860 function iframe_whitelisted($entry) {
861 $whitelist = array("youtube.com", "youtu.be", "vimeo.com");
863 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST);
866 foreach ($whitelist as $w) {
867 if ($src == $w || $src == "www.$w")
875 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
876 if (!$owner) $owner = $_SESSION["uid"];
878 $res = trim($str); if (!$res) return '';
880 $charset_hack = '<head>
881 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
884 $res = trim($res); if (!$res) return '';
886 libxml_use_internal_errors(true);
888 $doc = new DOMDocument();
889 $doc->loadHTML($charset_hack . $res);
890 $xpath = new DOMXPath($doc);
892 $entries = $xpath->query('(//a[@href]|//img[@src])');
894 foreach ($entries as $entry) {
898 if ($entry->hasAttribute('href')) {
899 $entry->setAttribute('href',
900 rewrite_relative_url($site_url, $entry->getAttribute('href')));
902 $entry->setAttribute('rel', 'noreferrer');
905 if ($entry->hasAttribute('src')) {
906 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
908 $cached_filename = CACHE_DIR . '/images/' . sha1($src) . '.png';
910 if (file_exists($cached_filename)) {
911 $src = SELF_URL_PATH . '/public.php?op=cached_image&hash=' . sha1($src);
914 $entry->setAttribute('src', $src);
917 if ($entry->nodeName == 'img') {
918 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
919 $force_remove_images || $_SESSION["bw_limit"]) {
921 $p = $doc->createElement('p');
923 $a = $doc->createElement('a');
924 $a->setAttribute('href', $entry->getAttribute('src'));
926 $a->appendChild(new DOMText($entry->getAttribute('src')));
927 $a->setAttribute('target', '_blank');
931 $entry->parentNode->replaceChild($p, $entry);
936 if (strtolower($entry->nodeName) == "a") {
937 $entry->setAttribute("target", "_blank");
941 $entries = $xpath->query('//iframe');
942 foreach ($entries as $entry) {
943 if (!iframe_whitelisted($entry)) {
944 $entry->setAttribute('sandbox', 'allow-scripts');
946 if ($_SERVER['HTTPS'] == "on") {
947 $entry->setAttribute("src",
948 str_replace("http://", "https://",
949 $entry->getAttribute("src")));
954 $allowed_elements = array('a', 'address', 'audio', 'article', 'aside',
955 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
956 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
957 'data', 'dd', 'del', 'details', 'div', 'dl', 'font',
958 'dt', 'em', 'footer', 'figure', 'figcaption',
959 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
960 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
961 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
962 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
963 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
964 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
966 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
968 $disallowed_attributes = array('id', 'style', 'class');
970 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) {
971 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
972 if (is_array($retval)) {
974 $allowed_elements = $retval[1];
975 $disallowed_attributes = $retval[2];
981 $doc->removeChild($doc->firstChild); //remove doctype
982 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
984 if ($highlight_words) {
985 foreach ($highlight_words as $word) {
987 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
989 $elements = $xpath->query("//*/text()");
991 foreach ($elements as $child) {
993 $fragment = $doc->createDocumentFragment();
994 $text = $child->textContent;
996 while (($pos = mb_stripos($text, $word)) !== false) {
997 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
998 $word = mb_substr($text, $pos, mb_strlen($word));
999 $highlight = $doc->createElement('span');
1000 $highlight->appendChild(new DomText($word));
1001 $highlight->setAttribute('class', 'highlight');
1002 $fragment->appendChild($highlight);
1003 $text = mb_substr($text, $pos + mb_strlen($word));
1006 if (!empty($text)) $fragment->appendChild(new DomText($text));
1008 $child->parentNode->replaceChild($fragment, $child);
1013 $res = $doc->saveHTML();
1018 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1019 $xpath = new DOMXPath($doc);
1020 $entries = $xpath->query('//*');
1022 foreach ($entries as $entry) {
1023 if (!in_array($entry->nodeName, $allowed_elements)) {
1024 $entry->parentNode->removeChild($entry);
1027 if ($entry->hasAttributes()) {
1028 $attrs_to_remove = array();
1030 foreach ($entry->attributes as $attr) {
1032 if (strpos($attr->nodeName, 'on') === 0) {
1033 array_push($attrs_to_remove, $attr);
1036 if (in_array($attr->nodeName, $disallowed_attributes)) {
1037 array_push($attrs_to_remove, $attr);
1041 foreach ($attrs_to_remove as $attr) {
1042 $entry->removeAttributeNode($attr);
1050 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
1052 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1053 if (count($ids) == 0) return;
1057 foreach ($ids as $id) {
1058 array_push($tmp_ids, "ref_id = '$id'");
1061 $ids_qpart = join(" OR ", $tmp_ids);
1064 db_query("UPDATE ttrss_user_entries SET
1065 unread = false,last_read = NOW()
1066 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1067 } else if ($cmode == 1) {
1068 db_query("UPDATE ttrss_user_entries SET
1070 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1072 db_query("UPDATE ttrss_user_entries SET
1073 unread = NOT unread,last_read = NOW()
1074 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1079 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
1080 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1082 while ($line = db_fetch_assoc($result)) {
1083 ccache_update($line["feed_id"], $owner_uid);
1087 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
1089 $a_id = db_escape_string($id);
1091 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1093 $query = "SELECT DISTINCT tag_name,
1094 owner_uid as owner FROM
1095 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
1096 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
1100 /* check cache first */
1102 if ($tag_cache === false) {
1103 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
1104 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1106 $tag_cache = db_fetch_result($result, 0, "tag_cache");
1110 $tags = explode(",", $tag_cache);
1113 /* do it the hard way */
1115 $tmp_result = db_query($query);
1117 while ($tmp_line = db_fetch_assoc($tmp_result)) {
1118 array_push($tags, $tmp_line["tag_name"]);
1121 /* update the cache */
1123 $tags_str = db_escape_string(join(",", $tags));
1125 db_query("UPDATE ttrss_user_entries
1126 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
1127 AND owner_uid = $owner_uid");
1133 function trim_array($array) {
1135 array_walk($tmp, 'trim');
1139 function tag_is_valid($tag) {
1140 if ($tag == '') return false;
1141 if (preg_match("/^[0-9]*$/", $tag)) return false;
1142 if (mb_strlen($tag) > 250) return false;
1144 if (!$tag) return false;
1149 function render_login_form() {
1150 header('Cache-Control: public');
1152 require_once "login_form.php";
1156 function format_warning($msg, $id = "") {
1157 return "<div class=\"warning\" id=\"$id\">
1158 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
1161 function format_notice($msg, $id = "") {
1162 return "<div class=\"notice\" id=\"$id\">
1163 <span><img src=\"images/information.png\"></span><span>$msg</span></div>";
1166 function format_error($msg, $id = "") {
1167 return "<div class=\"error\" id=\"$id\">
1168 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
1171 function print_notice($msg) {
1172 return print format_notice($msg);
1175 function print_warning($msg) {
1176 return print format_warning($msg);
1179 function print_error($msg) {
1180 return print format_error($msg);
1184 function T_sprintf() {
1185 $args = func_get_args();
1186 return vsprintf(__(array_shift($args)), $args);
1189 function format_inline_player($url, $ctype) {
1193 $url = htmlspecialchars($url);
1195 if (strpos($ctype, "audio/") === 0) {
1197 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1198 $_SESSION["hasMp3"])) {
1200 $entry .= "<audio preload=\"none\" controls>
1201 <source type=\"$ctype\" src=\"$url\"/>
1206 $entry .= "<object type=\"application/x-shockwave-flash\"
1207 data=\"lib/button/musicplayer.swf?song_url=$url\"
1208 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1209 <param name=\"movie\"
1210 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1214 if ($entry) $entry .= " <a target=\"_blank\"
1215 href=\"$url\">" . basename($url) . "</a>";
1223 /* $filename = substr($url, strrpos($url, "/")+1);
1225 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1226 $filename . " (" . $ctype . ")" . "</a>"; */
1230 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1231 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1237 /* we can figure out feed_id from article id anyway, why do we
1238 * pass feed_id here? let's ignore the argument :(*/
1240 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1241 WHERE ref_id = '$id'");
1243 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1245 $rv['feed_id'] = $feed_id;
1247 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1249 if ($mark_as_read) {
1250 $result = db_query("UPDATE ttrss_user_entries
1251 SET unread = false,last_read = NOW()
1252 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1254 ccache_update($feed_id, $owner_uid);
1257 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1258 ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
1259 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1260 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1261 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1262 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1268 FROM ttrss_entries,ttrss_user_entries
1269 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1273 $line = db_fetch_assoc($result);
1275 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1276 unset($line["tag_cache"]);
1278 $line["content"] = sanitize($line["content"],
1279 sql_bool_to_bool($line['hide_images']),
1280 $owner_uid, $line["site_url"], false, $line["id"]);
1282 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) {
1283 $line = $p->hook_render_article($line);
1286 $num_comments = $line["num_comments"];
1287 $entry_comments = "";
1289 if ($num_comments > 0) {
1290 if ($line["comments"]) {
1291 $comments_url = htmlspecialchars($line["comments"]);
1293 $comments_url = htmlspecialchars($line["link"]);
1295 $entry_comments = "<a class=\"postComments\"
1296 target='_blank' href=\"$comments_url\">$num_comments ".
1297 _ngettext("comment", "comments", $num_comments)."</a>";
1300 if ($line["comments"] && $line["link"] != $line["comments"]) {
1301 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
1306 header("Content-Type: text/html");
1307 $rv['content'] .= "<html><head>
1308 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1309 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1310 stylesheet_tag("css/tt-rss.css").
1311 stylesheet_tag("css/zoom.css").
1312 stylesheet_tag("css/dijit.css")."
1314 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1315 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1317 </head><body id=\"ttrssZoom\">";
1320 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1322 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1324 $entry_author = $line["author"];
1326 if ($entry_author) {
1327 $entry_author = __(" - ") . $entry_author;
1330 $parsed_updated = make_local_datetime($line["updated"], true,
1334 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1336 if ($line["link"]) {
1337 $rv['content'] .= "<div class='postTitle'><a target='_blank'
1338 title=\"".htmlspecialchars($line['title'])."\"
1340 htmlspecialchars($line["link"]) . "\">" .
1341 $line["title"] . "</a>" .
1342 "<span class='author'>$entry_author</span></div>";
1344 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1348 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
1349 "\" target=\"_blank\">".
1350 htmlspecialchars($line["feed_title"])."</a>";
1352 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1354 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1357 $tags_str = format_tags_string($line["tags"], $id);
1358 $tags_str_full = join(", ", $line["tags"]);
1360 if (!$tags_str_full) $tags_str_full = __("no tags");
1362 if (!$entry_comments) $entry_comments = " "; # placeholder
1364 $rv['content'] .= "<div class='postTags' style='float : right'>
1365 <img src='images/tag.png'
1366 class='tagsPic' alt='Tags' title='Tags'> ";
1369 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1370 <a title=\"".__('Edit tags for this article')."\"
1371 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1373 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1374 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1375 position=\"below\">$tags_str_full</div>";
1377 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
1378 $rv['content'] .= $p->hook_article_button($line);
1382 $tags_str = strip_tags($tags_str);
1383 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1385 $rv['content'] .= "</div>";
1386 $rv['content'] .= "<div clear='both'>";
1388 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
1389 $rv['content'] .= $p->hook_article_left_button($line);
1392 $rv['content'] .= "$entry_comments</div>";
1394 if ($line["orig_feed_id"]) {
1396 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
1397 WHERE id = ".$line["orig_feed_id"]);
1399 if (db_num_rows($tmp_result) != 0) {
1401 $rv['content'] .= "<div clear='both'>";
1402 $rv['content'] .= __("Originally from:");
1404 $rv['content'] .= " ";
1406 $tmp_line = db_fetch_assoc($tmp_result);
1408 $rv['content'] .= "<a target='_blank'
1409 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1410 $tmp_line['title'] . "</a>";
1412 $rv['content'] .= " ";
1414 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
1415 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1417 $rv['content'] .= "</div>";
1421 $rv['content'] .= "</div>";
1423 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1424 if ($line['note']) {
1425 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1427 $rv['content'] .= "</div>";
1429 if (!$line['lang']) $line['lang'] = 'en';
1431 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1433 $rv['content'] .= $line["content"];
1436 $rv['content'] .= format_article_enclosures($id,
1437 sql_bool_to_bool($line["always_display_enclosures"]),
1439 sql_bool_to_bool($line["hide_images"]));
1442 $rv['content'] .= "</div>";
1444 $rv['content'] .= "</div>";
1450 <div class='footer'>
1451 <button onclick=\"return window.close()\">".
1452 __("Close this window")."</button></div>";
1453 $rv['content'] .= "</body></html>";
1460 function print_checkpoint($n, $s) {
1461 $ts = microtime(true);
1462 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1466 function sanitize_tag($tag) {
1469 $tag = mb_strtolower($tag, 'utf-8');
1471 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1473 if (DB_TYPE == "mysql") {
1474 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1480 function get_self_url_prefix() {
1481 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
1482 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
1484 return SELF_URL_PATH;
1489 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1491 * @return string The Mozilla Firefox feed adding URL.
1493 function add_feed_url() {
1494 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1496 $url_path = get_self_url_prefix() .
1497 "/public.php?op=subscribe&feed_url=%s";
1499 } // function add_feed_url
1501 function encrypt_password($pass, $salt = '', $mode2 = false) {
1502 if ($salt && $mode2) {
1503 return "MODE2:" . hash('sha256', $salt . $pass);
1505 return "SHA1X:" . sha1("$salt:$pass");
1507 return "SHA1:" . sha1($pass);
1509 } // function encrypt_password
1511 function load_filters($feed_id, $owner_uid, $action_id = false) {
1514 $cat_id = (int)getFeedCategory($feed_id);
1517 $null_cat_qpart = "cat_id IS NULL OR";
1519 $null_cat_qpart = "";
1521 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1522 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1524 $check_cats = join(",", array_merge(
1525 getParentCategories($cat_id, $owner_uid),
1528 while ($line = db_fetch_assoc($result)) {
1529 $filter_id = $line["id"];
1531 $result2 = db_query("SELECT
1532 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1533 FROM ttrss_filters2_rules AS r,
1534 ttrss_filter_types AS t
1536 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1537 (feed_id IS NULL OR feed_id = '$feed_id') AND
1538 filter_type = t.id AND filter_id = '$filter_id'");
1543 while ($rule_line = db_fetch_assoc($result2)) {
1544 # print_r($rule_line);
1547 $rule["reg_exp"] = $rule_line["reg_exp"];
1548 $rule["type"] = $rule_line["type_name"];
1549 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1551 array_push($rules, $rule);
1554 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1555 FROM ttrss_filters2_actions AS a,
1556 ttrss_filter_actions AS t
1558 action_id = t.id AND filter_id = '$filter_id'");
1560 while ($action_line = db_fetch_assoc($result2)) {
1561 # print_r($action_line);
1564 $action["type"] = $action_line["type_name"];
1565 $action["param"] = $action_line["action_param"];
1567 array_push($actions, $action);
1572 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1573 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1574 $filter["rules"] = $rules;
1575 $filter["actions"] = $actions;
1577 if (count($rules) > 0 && count($actions) > 0) {
1578 array_push($filters, $filter);
1585 function get_score_pic($score) {
1587 return "score_high.png";
1588 } else if ($score > 0) {
1589 return "score_half_high.png";
1590 } else if ($score < -100) {
1591 return "score_low.png";
1592 } else if ($score < 0) {
1593 return "score_half_low.png";
1595 return "score_neutral.png";
1599 function feed_has_icon($id) {
1600 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
1603 function init_plugins() {
1604 PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL);
1609 function format_tags_string($tags, $id) {
1610 if (!is_array($tags) || count($tags) == 0) {
1611 return __("no tags");
1613 $maxtags = min(5, count($tags));
1616 for ($i = 0; $i < $maxtags; $i++) {
1617 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed('".$tags[$i]."')\">" . $tags[$i] . "</a>, ";
1620 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1622 if (count($tags) > $maxtags)
1623 $tags_str .= ", …";
1629 function format_article_labels($labels, $id) {
1631 if (!is_array($labels)) return '';
1635 foreach ($labels as $l) {
1636 $labels_str .= sprintf("<span class='hlLabelRef'
1637 style='color : %s; background-color : %s'>%s</span>",
1638 $l[2], $l[3], $l[1]);
1645 function format_article_note($id, $note, $allow_edit = true) {
1647 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1648 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1649 ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
1655 function get_feed_category($feed_cat, $parent_cat_id = false) {
1656 if ($parent_cat_id) {
1657 $parent_qpart = "parent_cat = '$parent_cat_id'";
1658 $parent_insert = "'$parent_cat_id'";
1660 $parent_qpart = "parent_cat IS NULL";
1661 $parent_insert = "NULL";
1665 "SELECT id FROM ttrss_feed_categories
1666 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1668 if (db_num_rows($result) == 0) {
1671 return db_fetch_result($result, 0, "id");
1675 function add_feed_category($feed_cat, $parent_cat_id = false) {
1677 if (!$feed_cat) return false;
1681 if ($parent_cat_id) {
1682 $parent_qpart = "parent_cat = '$parent_cat_id'";
1683 $parent_insert = "'$parent_cat_id'";
1685 $parent_qpart = "parent_cat IS NULL";
1686 $parent_insert = "NULL";
1689 $feed_cat = mb_substr($feed_cat, 0, 250);
1692 "SELECT id FROM ttrss_feed_categories
1693 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1695 if (db_num_rows($result) == 0) {
1698 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1699 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1709 function getArticleFeed($id) {
1710 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1711 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1713 if (db_num_rows($result) != 0) {
1714 return db_fetch_result($result, 0, "feed_id");
1721 * Fixes incomplete URLs by prepending "http://".
1722 * Also replaces feed:// with http://, and
1723 * prepends a trailing slash if the url is a domain name only.
1725 * @param string $url Possibly incomplete URL
1727 * @return string Fixed URL.
1729 function fix_url($url) {
1731 // support schema-less urls
1732 if (strpos($url, '//') === 0) {
1733 $url = 'https:' . $url;
1736 if (strpos($url, '://') === false) {
1737 $url = 'http://' . $url;
1738 } else if (substr($url, 0, 5) == 'feed:') {
1739 $url = 'http:' . substr($url, 5);
1742 //prepend slash if the URL has no slash in it
1743 // "http://www.example" -> "http://www.example/"
1744 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
1748 if ($url != "http:///")
1754 function validate_feed_url($url) {
1755 $parts = parse_url($url);
1757 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
1761 function get_article_enclosures($id) {
1763 $query = "SELECT * FROM ttrss_enclosures
1764 WHERE post_id = '$id' AND content_url != ''";
1768 $result = db_query($query);
1770 if (db_num_rows($result) > 0) {
1771 while ($line = db_fetch_assoc($result)) {
1772 array_push($rv, $line);
1779 /* function save_email_address($email) {
1780 // FIXME: implement persistent storage of emails
1782 if (!$_SESSION['stored_emails'])
1783 $_SESSION['stored_emails'] = array();
1785 if (!in_array($email, $_SESSION['stored_emails']))
1786 array_push($_SESSION['stored_emails'], $email);
1790 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1792 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1794 $sql_is_cat = bool_to_sql_bool($is_cat);
1796 $result = db_query("SELECT access_key FROM ttrss_access_keys
1797 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1798 AND owner_uid = " . $owner_uid);
1800 if (db_num_rows($result) == 1) {
1801 return db_fetch_result($result, 0, "access_key");
1803 $key = db_escape_string(uniqid_short());
1805 $result = db_query("INSERT INTO ttrss_access_keys
1806 (access_key, feed_id, is_cat, owner_uid)
1807 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1814 function get_feeds_from_html($url, $content)
1816 $url = fix_url($url);
1817 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
1819 libxml_use_internal_errors(true);
1821 $doc = new DOMDocument();
1822 $doc->loadHTML($content);
1823 $xpath = new DOMXPath($doc);
1824 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
1825 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
1826 $feedUrls = array();
1827 foreach ($entries as $entry) {
1828 if ($entry->hasAttribute('href')) {
1829 $title = $entry->getAttribute('title');
1831 $title = $entry->getAttribute('type');
1833 $feedUrl = rewrite_relative_url(
1834 $baseUrl, $entry->getAttribute('href')
1836 $feedUrls[$feedUrl] = $title;
1842 function is_html($content) {
1843 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
1846 function url_is_html($url, $login = false, $pass = false) {
1847 return is_html(fetch_file_contents($url, false, $login, $pass));
1850 function print_label_select($name, $value, $attributes = "") {
1852 $result = db_query("SELECT caption FROM ttrss_labels2
1853 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1855 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1856 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
1858 while ($line = db_fetch_assoc($result)) {
1860 $issel = ($line["caption"] == $value) ? "selected=\"1\"" : "";
1862 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1863 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1867 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1874 function format_article_enclosures($id, $always_display_enclosures,
1875 $article_content, $hide_images = false) {
1877 $result = get_article_enclosures($id);
1880 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ENCLOSURES) as $plugin) {
1881 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
1882 if (is_array($retval)) {
1884 $result = $retval[1];
1890 if ($rv === '' && !empty($result)) {
1891 $entries_html = array();
1893 $entries_inline = array();
1895 foreach ($result as $line) {
1897 $url = $line["content_url"];
1898 $ctype = $line["content_type"];
1899 $title = $line["title"];
1900 $width = $line["width"];
1901 $height = $line["height"];
1903 if (!$ctype) $ctype = __("unknown type");
1905 $filename = substr($url, strrpos($url, "/")+1);
1907 $player = format_inline_player($url, $ctype);
1909 if ($player) array_push($entries_inline, $player);
1911 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1912 # $filename . " (" . $ctype . ")" . "</a>";
1914 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
1915 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1917 array_push($entries_html, $entry);
1921 $entry["type"] = $ctype;
1922 $entry["filename"] = $filename;
1923 $entry["url"] = $url;
1924 $entry["title"] = $title;
1925 $entry["width"] = $width;
1926 $entry["height"] = $height;
1928 array_push($entries, $entry);
1931 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
1932 if ($always_display_enclosures ||
1933 !preg_match("/<img/i", $article_content)) {
1935 foreach ($entries as $entry) {
1937 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ENCLOSURE) as $plugin)
1938 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
1945 if (preg_match("/image/", $entry["type"]) ||
1946 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
1948 if (!$hide_images) {
1950 if ($entry['height'] > 0)
1951 $encsize .= ' height="' . intval($entry['width']) . '"';
1952 if ($entry['width'] > 0)
1953 $encsize .= ' width="' . intval($entry['height']) . '"';
1955 alt=\"".htmlspecialchars($entry["filename"])."\"
1956 src=\"" .htmlspecialchars($entry["url"]) . "\"
1957 " . $encsize . " /></p>";
1959 $rv .= "<p><a target=\"_blank\"
1960 href=\"".htmlspecialchars($entry["url"])."\"
1961 >" .htmlspecialchars($entry["url"]) . "</a></p>";
1964 if ($entry['title']) {
1965 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
1973 if (count($entries_inline) > 0) {
1974 $rv .= "<hr clear='both'/>";
1975 foreach ($entries_inline as $entry) { $rv .= $entry; };
1976 $rv .= "<hr clear='both'/>";
1979 $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
1980 "<span>" . __('Attachments')."</span>";
1982 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
1984 foreach ($entries as $entry) {
1985 if ($entry["title"])
1986 $title = "— " . truncate_string($entry["title"], 30);
1990 $rv .= "<div onclick='window.open(\"".htmlspecialchars($entry["url"])."\")'
1991 dojoType=\"dijit.MenuItem\">".htmlspecialchars($entry["filename"])."$title</div>";
2002 function getLastArticleId() {
2003 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
2004 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
2006 if (db_num_rows($result) == 1) {
2007 return db_fetch_result($result, 0, "id");
2013 function build_url($parts) {
2014 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2018 * Converts a (possibly) relative URL to a absolute one.
2020 * @param string $url Base URL (i.e. from where the document is)
2021 * @param string $rel_url Possibly relative URL in the document
2023 * @return string Absolute URL
2025 function rewrite_relative_url($url, $rel_url) {
2026 if (strpos($rel_url, ":") !== false) {
2028 } else if (strpos($rel_url, "://") !== false) {
2030 } else if (strpos($rel_url, "//") === 0) {
2031 # protocol-relative URL (rare but they exist)
2033 } else if (strpos($rel_url, "/") === 0)
2035 $parts = parse_url($url);
2036 $parts['path'] = $rel_url;
2038 return build_url($parts);
2041 $parts = parse_url($url);
2042 if (!isset($parts['path'])) {
2043 $parts['path'] = '/';
2045 $dir = $parts['path'];
2046 if (substr($dir, -1) !== '/') {
2047 $dir = dirname($parts['path']);
2048 $dir !== '/' && $dir .= '/';
2050 $parts['path'] = $dir . $rel_url;
2052 return build_url($parts);
2056 function cleanup_tags($days = 14, $limit = 1000) {
2058 if (DB_TYPE == "pgsql") {
2059 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2060 } else if (DB_TYPE == "mysql") {
2061 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2066 while ($limit > 0) {
2069 $query = "SELECT ttrss_tags.id AS id
2070 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2071 WHERE post_int_id = int_id AND $interval_query AND
2072 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2074 $result = db_query($query);
2078 while ($line = db_fetch_assoc($result)) {
2079 array_push($ids, $line['id']);
2082 if (count($ids) > 0) {
2083 $ids = join(",", $ids);
2085 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2086 $tags_deleted += db_affected_rows($tmp_result);
2091 $limit -= $limit_part;
2094 return $tags_deleted;
2097 function print_user_stylesheet() {
2098 $value = get_pref('USER_STYLESHEET');
2101 print "<style type=\"text/css\">";
2102 print str_replace("<br/>", "\n", $value);
2108 function filter_to_sql($filter, $owner_uid) {
2111 if (DB_TYPE == "pgsql")
2114 $reg_qpart = "REGEXP";
2116 foreach ($filter["rules"] AS $rule) {
2117 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2118 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2119 $rule['reg_exp']) !== FALSE;
2121 if ($regexp_valid) {
2123 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2125 switch ($rule["type"]) {
2127 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2128 $rule['reg_exp'] . "')";
2131 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2132 $rule['reg_exp'] . "')";
2135 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2136 $rule['reg_exp'] . "') OR LOWER(" .
2137 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2140 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2141 $rule['reg_exp'] . "')";
2144 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2145 $rule['reg_exp'] . "')";
2148 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2149 $rule['reg_exp'] . "')";
2153 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2155 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2156 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2159 if (isset($rule["cat_id"])) {
2161 if ($rule["cat_id"] > 0) {
2162 $children = getChildCategories($rule["cat_id"], $owner_uid);
2163 array_push($children, $rule["cat_id"]);
2165 $children = join(",", $children);
2167 $cat_qpart = "cat_id IN ($children)";
2169 $cat_qpart = "cat_id IS NULL";
2172 $qpart .= " AND $cat_qpart";
2175 $qpart .= " AND feed_id IS NOT NULL";
2177 array_push($query, "($qpart)");
2182 if (count($query) > 0) {
2183 $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
2185 $fullquery = "(false)";
2188 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2193 if (!function_exists('gzdecode')) {
2194 function gzdecode($string) { // no support for 2nd argument
2195 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2196 base64_encode($string));
2200 function get_random_bytes($length) {
2201 if (function_exists('openssl_random_pseudo_bytes')) {
2202 return openssl_random_pseudo_bytes($length);
2206 for ($i = 0; $i < $length; $i++)
2207 $output .= chr(mt_rand(0, 255));
2213 function read_stdin() {
2214 $fp = fopen("php://stdin", "r");
2217 $line = trim(fgets($fp));
2225 function tmpdirname($path, $prefix) {
2226 // Use PHP's tmpfile function to create a temporary
2227 // directory name. Delete the file and keep the name.
2228 $tempname = tempnam($path,$prefix);
2232 if (!unlink($tempname))
2238 function getFeedCategory($feed) {
2239 $result = db_query("SELECT cat_id FROM ttrss_feeds
2240 WHERE id = '$feed'");
2242 if (db_num_rows($result) > 0) {
2243 return db_fetch_result($result, 0, "cat_id");
2250 function implements_interface($class, $interface) {
2251 return in_array($interface, class_implements($class));
2254 function geturl($url, $depth = 0, $nobody = true){
2256 if ($depth == 20) return $url;
2258 if (!function_exists('curl_init'))
2259 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);
2261 $curl = curl_init();
2262 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
2263 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
2264 $header[] = "Cache-Control: max-age=0";
2265 $header[] = "Connection: keep-alive";
2266 $header[] = "Keep-Alive: 300";
2267 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
2268 $header[] = "Accept-Language: en-us,en;q=0.5";
2269 $header[] = "Pragma: ";
2271 curl_setopt($curl, CURLOPT_URL, $url);
2272 curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
2273 curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
2274 curl_setopt($curl, CURLOPT_HEADER, true);
2275 curl_setopt($curl, CURLOPT_NOBODY, $nobody);
2276 curl_setopt($curl, CURLOPT_REFERER, $url);
2277 curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
2278 curl_setopt($curl, CURLOPT_AUTOREFERER, true);
2279 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
2280 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
2281 curl_setopt($curl, CURLOPT_TIMEOUT, 60);
2282 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
2284 if (defined('_CURL_HTTP_PROXY')) {
2285 curl_setopt($curl, CURLOPT_PROXY, _CURL_HTTP_PROXY);
2288 $html = curl_exec($curl);
2290 $status = curl_getinfo($curl);
2292 if($status['http_code']!=200){
2294 // idiot site not allowing http head
2295 if($status['http_code'] == 405) {
2297 return geturl($url, $depth +1, false);
2300 if($status['http_code'] == 301 || $status['http_code'] == 302) {
2302 list($header) = explode("\r\n\r\n", $html, 2);
2304 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
2305 $url = trim(str_replace($matches[1],"",$matches[0]));
2306 $url_parsed = parse_url($url);
2307 return (isset($url_parsed))? geturl($url, $depth + 1):'';
2310 global $fetch_last_error;
2312 $fetch_last_error = curl_errno($curl) . " " . curl_error($curl);
2316 # foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
2317 # $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
2318 # $handle = @fopen('./curl.error.log', 'a');
2319 # fwrite($handle, $line);
2326 function get_minified_js($files) {
2327 require_once 'lib/jshrink/Minifier.php';
2331 foreach ($files as $js) {
2332 if (!isset($_GET['debug'])) {
2333 $cached_file = CACHE_DIR . "/js/".basename($js).".js";
2335 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2337 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2339 if ($header && $contents) {
2340 list($htag, $hversion) = explode(":", $header);
2342 if ($htag == "tt-rss" && $hversion == VERSION) {
2349 $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
2350 file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified);
2354 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2361 function stylesheet_tag($filename) {
2362 $timestamp = filemtime($filename);
2364 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2367 function javascript_tag($filename) {
2370 if (!(strpos($filename, "?") === FALSE)) {
2371 $query = substr($filename, strpos($filename, "?")+1);
2372 $filename = substr($filename, 0, strpos($filename, "?"));
2375 $timestamp = filemtime($filename);
2377 if ($query) $timestamp .= "&$query";
2379 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2382 function calculate_dep_timestamp() {
2383 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2387 foreach ($files as $file) {
2388 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2394 function T_js_decl($s1, $s2) {
2396 $s1 = preg_replace("/\n/", "", $s1);
2397 $s2 = preg_replace("/\n/", "", $s2);
2399 $s1 = preg_replace("/\"/", "\\\"", $s1);
2400 $s2 = preg_replace("/\"/", "\\\"", $s2);
2402 return "T_messages[\"$s1\"] = \"$s2\";\n";
2406 function init_js_translations() {
2408 print 'var T_messages = new Object();
2411 if (T_messages[msg]) {
2412 return T_messages[msg];
2418 function ngettext(msg1, msg2, n) {
2419 return __((parseInt(n) > 1) ? msg2 : msg1);
2422 $l10n = _get_reader();
2424 for ($i = 0; $i < $l10n->total; $i++) {
2425 $orig = $l10n->get_original_string($i);
2426 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2427 $key = explode(chr(0), $orig);
2428 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2429 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2431 $translation = __($orig);
2432 print T_js_decl($orig, $translation);
2437 function label_to_feed_id($label) {
2438 return LABEL_BASE_INDEX - 1 - abs($label);
2441 function feed_to_label_id($feed) {
2442 return LABEL_BASE_INDEX - 1 + abs($feed);
2445 function get_theme_path($theme) {
2446 $check = "themes/$theme";
2447 if (file_exists($check)) return $check;
2449 $check = "themes.local/$theme";
2450 if (file_exists($check)) return $check;
2453 function theme_valid($theme) {
2454 if ($theme == "default.css" || $theme == "night.css") return true; // needed for array_filter
2455 $file = "themes/" . basename($theme);
2457 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2459 if (file_exists($file) && is_readable($file)) {
2460 $fh = fopen($file, "r");
2463 $header = fgets($fh);
2466 return strpos($header, "supports-version:" . VERSION_STATIC) !== FALSE;
2473 function error_json($code) {
2474 require_once "errors.php";
2476 @$message = $ERRORS[$code];
2478 return json_encode(array("error" =>
2479 array("code" => $code, "message" => $message)));
2483 function abs_to_rel_path($dir) {
2484 $tmp = str_replace(dirname(__DIR__), "", $dir);
2486 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);