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 <script type=\"text/javascript\">
1318 function openSelectedAttachment(elem) {
1320 var url = elem[elem.selectedIndex].value;
1324 elem.selectedIndex = 0;
1328 exception_error(\"openSelectedAttachment\", e);
1332 </head><body id=\"ttrssZoom\">";
1335 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1337 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1339 $entry_author = $line["author"];
1341 if ($entry_author) {
1342 $entry_author = __(" - ") . $entry_author;
1345 $parsed_updated = make_local_datetime($line["updated"], true,
1349 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1351 if ($line["link"]) {
1352 $rv['content'] .= "<div class='postTitle'><a target='_blank'
1353 title=\"".htmlspecialchars($line['title'])."\"
1355 htmlspecialchars($line["link"]) . "\">" .
1356 $line["title"] . "</a>" .
1357 "<span class='author'>$entry_author</span></div>";
1359 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1363 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
1364 "\" target=\"_blank\">".
1365 htmlspecialchars($line["feed_title"])."</a>";
1367 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1369 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1372 $tags_str = format_tags_string($line["tags"], $id);
1373 $tags_str_full = join(", ", $line["tags"]);
1375 if (!$tags_str_full) $tags_str_full = __("no tags");
1377 if (!$entry_comments) $entry_comments = " "; # placeholder
1379 $rv['content'] .= "<div class='postTags' style='float : right'>
1380 <img src='images/tag.png'
1381 class='tagsPic' alt='Tags' title='Tags'> ";
1384 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1385 <a title=\"".__('Edit tags for this article')."\"
1386 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1388 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1389 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1390 position=\"below\">$tags_str_full</div>";
1392 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
1393 $rv['content'] .= $p->hook_article_button($line);
1397 $tags_str = strip_tags($tags_str);
1398 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1400 $rv['content'] .= "</div>";
1401 $rv['content'] .= "<div clear='both'>";
1403 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
1404 $rv['content'] .= $p->hook_article_left_button($line);
1407 $rv['content'] .= "$entry_comments</div>";
1409 if ($line["orig_feed_id"]) {
1411 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
1412 WHERE id = ".$line["orig_feed_id"]);
1414 if (db_num_rows($tmp_result) != 0) {
1416 $rv['content'] .= "<div clear='both'>";
1417 $rv['content'] .= __("Originally from:");
1419 $rv['content'] .= " ";
1421 $tmp_line = db_fetch_assoc($tmp_result);
1423 $rv['content'] .= "<a target='_blank'
1424 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1425 $tmp_line['title'] . "</a>";
1427 $rv['content'] .= " ";
1429 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
1430 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1432 $rv['content'] .= "</div>";
1436 $rv['content'] .= "</div>";
1438 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1439 if ($line['note']) {
1440 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1442 $rv['content'] .= "</div>";
1444 if (!$line['lang']) $line['lang'] = 'en';
1446 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1448 $rv['content'] .= $line["content"];
1449 $rv['content'] .= format_article_enclosures($id,
1450 sql_bool_to_bool($line["always_display_enclosures"]),
1452 sql_bool_to_bool($line["hide_images"]));
1454 $rv['content'] .= "</div>";
1456 $rv['content'] .= "</div>";
1462 <div class='footer'>
1463 <button onclick=\"return window.close()\">".
1464 __("Close this window")."</button></div>";
1465 $rv['content'] .= "</body></html>";
1472 function print_checkpoint($n, $s) {
1473 $ts = microtime(true);
1474 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1478 function sanitize_tag($tag) {
1481 $tag = mb_strtolower($tag, 'utf-8');
1483 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1485 if (DB_TYPE == "mysql") {
1486 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1492 function get_self_url_prefix() {
1493 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
1494 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
1496 return SELF_URL_PATH;
1501 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1503 * @return string The Mozilla Firefox feed adding URL.
1505 function add_feed_url() {
1506 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1508 $url_path = get_self_url_prefix() .
1509 "/public.php?op=subscribe&feed_url=%s";
1511 } // function add_feed_url
1513 function encrypt_password($pass, $salt = '', $mode2 = false) {
1514 if ($salt && $mode2) {
1515 return "MODE2:" . hash('sha256', $salt . $pass);
1517 return "SHA1X:" . sha1("$salt:$pass");
1519 return "SHA1:" . sha1($pass);
1521 } // function encrypt_password
1523 function load_filters($feed_id, $owner_uid, $action_id = false) {
1526 $cat_id = (int)getFeedCategory($feed_id);
1529 $null_cat_qpart = "cat_id IS NULL OR";
1531 $null_cat_qpart = "";
1533 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1534 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1536 $check_cats = join(",", array_merge(
1537 getParentCategories($cat_id, $owner_uid),
1540 while ($line = db_fetch_assoc($result)) {
1541 $filter_id = $line["id"];
1543 $result2 = db_query("SELECT
1544 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1545 FROM ttrss_filters2_rules AS r,
1546 ttrss_filter_types AS t
1548 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1549 (feed_id IS NULL OR feed_id = '$feed_id') AND
1550 filter_type = t.id AND filter_id = '$filter_id'");
1555 while ($rule_line = db_fetch_assoc($result2)) {
1556 # print_r($rule_line);
1559 $rule["reg_exp"] = $rule_line["reg_exp"];
1560 $rule["type"] = $rule_line["type_name"];
1561 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1563 array_push($rules, $rule);
1566 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1567 FROM ttrss_filters2_actions AS a,
1568 ttrss_filter_actions AS t
1570 action_id = t.id AND filter_id = '$filter_id'");
1572 while ($action_line = db_fetch_assoc($result2)) {
1573 # print_r($action_line);
1576 $action["type"] = $action_line["type_name"];
1577 $action["param"] = $action_line["action_param"];
1579 array_push($actions, $action);
1584 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1585 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1586 $filter["rules"] = $rules;
1587 $filter["actions"] = $actions;
1589 if (count($rules) > 0 && count($actions) > 0) {
1590 array_push($filters, $filter);
1597 function get_score_pic($score) {
1599 return "score_high.png";
1600 } else if ($score > 0) {
1601 return "score_half_high.png";
1602 } else if ($score < -100) {
1603 return "score_low.png";
1604 } else if ($score < 0) {
1605 return "score_half_low.png";
1607 return "score_neutral.png";
1611 function feed_has_icon($id) {
1612 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
1615 function init_plugins() {
1616 PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL);
1621 function format_tags_string($tags, $id) {
1622 if (!is_array($tags) || count($tags) == 0) {
1623 return __("no tags");
1625 $maxtags = min(5, count($tags));
1628 for ($i = 0; $i < $maxtags; $i++) {
1629 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed('".$tags[$i]."')\">" . $tags[$i] . "</a>, ";
1632 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1634 if (count($tags) > $maxtags)
1635 $tags_str .= ", …";
1641 function format_article_labels($labels, $id) {
1643 if (!is_array($labels)) return '';
1647 foreach ($labels as $l) {
1648 $labels_str .= sprintf("<span class='hlLabelRef'
1649 style='color : %s; background-color : %s'>%s</span>",
1650 $l[2], $l[3], $l[1]);
1657 function format_article_note($id, $note, $allow_edit = true) {
1659 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1660 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1661 ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
1667 function get_feed_category($feed_cat, $parent_cat_id = false) {
1668 if ($parent_cat_id) {
1669 $parent_qpart = "parent_cat = '$parent_cat_id'";
1670 $parent_insert = "'$parent_cat_id'";
1672 $parent_qpart = "parent_cat IS NULL";
1673 $parent_insert = "NULL";
1677 "SELECT id FROM ttrss_feed_categories
1678 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1680 if (db_num_rows($result) == 0) {
1683 return db_fetch_result($result, 0, "id");
1687 function add_feed_category($feed_cat, $parent_cat_id = false) {
1689 if (!$feed_cat) return false;
1693 if ($parent_cat_id) {
1694 $parent_qpart = "parent_cat = '$parent_cat_id'";
1695 $parent_insert = "'$parent_cat_id'";
1697 $parent_qpart = "parent_cat IS NULL";
1698 $parent_insert = "NULL";
1701 $feed_cat = mb_substr($feed_cat, 0, 250);
1704 "SELECT id FROM ttrss_feed_categories
1705 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1707 if (db_num_rows($result) == 0) {
1710 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1711 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1721 function getArticleFeed($id) {
1722 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1723 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1725 if (db_num_rows($result) != 0) {
1726 return db_fetch_result($result, 0, "feed_id");
1733 * Fixes incomplete URLs by prepending "http://".
1734 * Also replaces feed:// with http://, and
1735 * prepends a trailing slash if the url is a domain name only.
1737 * @param string $url Possibly incomplete URL
1739 * @return string Fixed URL.
1741 function fix_url($url) {
1743 // support schema-less urls
1744 if (strpos($url, '//') === 0) {
1745 $url = 'https:' . $url;
1748 if (strpos($url, '://') === false) {
1749 $url = 'http://' . $url;
1750 } else if (substr($url, 0, 5) == 'feed:') {
1751 $url = 'http:' . substr($url, 5);
1754 //prepend slash if the URL has no slash in it
1755 // "http://www.example" -> "http://www.example/"
1756 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
1760 if ($url != "http:///")
1766 function validate_feed_url($url) {
1767 $parts = parse_url($url);
1769 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
1773 function get_article_enclosures($id) {
1775 $query = "SELECT * FROM ttrss_enclosures
1776 WHERE post_id = '$id' AND content_url != ''";
1780 $result = db_query($query);
1782 if (db_num_rows($result) > 0) {
1783 while ($line = db_fetch_assoc($result)) {
1784 array_push($rv, $line);
1791 /* function save_email_address($email) {
1792 // FIXME: implement persistent storage of emails
1794 if (!$_SESSION['stored_emails'])
1795 $_SESSION['stored_emails'] = array();
1797 if (!in_array($email, $_SESSION['stored_emails']))
1798 array_push($_SESSION['stored_emails'], $email);
1802 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1804 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1806 $sql_is_cat = bool_to_sql_bool($is_cat);
1808 $result = db_query("SELECT access_key FROM ttrss_access_keys
1809 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1810 AND owner_uid = " . $owner_uid);
1812 if (db_num_rows($result) == 1) {
1813 return db_fetch_result($result, 0, "access_key");
1815 $key = db_escape_string(uniqid_short());
1817 $result = db_query("INSERT INTO ttrss_access_keys
1818 (access_key, feed_id, is_cat, owner_uid)
1819 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1826 function get_feeds_from_html($url, $content)
1828 $url = fix_url($url);
1829 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
1831 libxml_use_internal_errors(true);
1833 $doc = new DOMDocument();
1834 $doc->loadHTML($content);
1835 $xpath = new DOMXPath($doc);
1836 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
1837 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
1838 $feedUrls = array();
1839 foreach ($entries as $entry) {
1840 if ($entry->hasAttribute('href')) {
1841 $title = $entry->getAttribute('title');
1843 $title = $entry->getAttribute('type');
1845 $feedUrl = rewrite_relative_url(
1846 $baseUrl, $entry->getAttribute('href')
1848 $feedUrls[$feedUrl] = $title;
1854 function is_html($content) {
1855 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
1858 function url_is_html($url, $login = false, $pass = false) {
1859 return is_html(fetch_file_contents($url, false, $login, $pass));
1862 function print_label_select($name, $value, $attributes = "") {
1864 $result = db_query("SELECT caption FROM ttrss_labels2
1865 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1867 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1868 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
1870 while ($line = db_fetch_assoc($result)) {
1872 $issel = ($line["caption"] == $value) ? "selected=\"1\"" : "";
1874 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1875 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1879 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1886 function format_article_enclosures($id, $always_display_enclosures,
1887 $article_content, $hide_images = false) {
1889 $result = get_article_enclosures($id);
1892 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ENCLOSURES) as $plugin) {
1893 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
1894 if (is_array($retval)) {
1896 $result = $retval[1];
1902 if ($rv === '' && !empty($result)) {
1903 $entries_html = array();
1905 $entries_inline = array();
1907 foreach ($result as $line) {
1909 $url = $line["content_url"];
1910 $ctype = $line["content_type"];
1911 $title = $line["title"];
1912 $width = $line["width"];
1913 $height = $line["height"];
1915 if (!$ctype) $ctype = __("unknown type");
1917 $filename = substr($url, strrpos($url, "/")+1);
1919 $player = format_inline_player($url, $ctype);
1921 if ($player) array_push($entries_inline, $player);
1923 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1924 # $filename . " (" . $ctype . ")" . "</a>";
1926 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
1927 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1929 array_push($entries_html, $entry);
1933 $entry["type"] = $ctype;
1934 $entry["filename"] = $filename;
1935 $entry["url"] = $url;
1936 $entry["title"] = $title;
1937 $entry["width"] = $width;
1938 $entry["height"] = $height;
1940 array_push($entries, $entry);
1943 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
1944 if ($always_display_enclosures ||
1945 !preg_match("/<img/i", $article_content)) {
1947 foreach ($entries as $entry) {
1949 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ENCLOSURE) as $plugin)
1950 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
1957 if (preg_match("/image/", $entry["type"]) ||
1958 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
1960 if (!$hide_images) {
1962 if ($entry['height'] > 0)
1963 $encsize .= ' height="' . intval($entry['width']) . '"';
1964 if ($entry['width'] > 0)
1965 $encsize .= ' width="' . intval($entry['height']) . '"';
1967 alt=\"".htmlspecialchars($entry["filename"])."\"
1968 src=\"" .htmlspecialchars($entry["url"]) . "\"
1969 " . $encsize . " /></p>";
1971 $rv .= "<p><a target=\"_blank\"
1972 href=\"".htmlspecialchars($entry["url"])."\"
1973 >" .htmlspecialchars($entry["url"]) . "</a></p>";
1976 if ($entry['title']) {
1977 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
1985 if (count($entries_inline) > 0) {
1986 $rv .= "<hr clear='both'/>";
1987 foreach ($entries_inline as $entry) { $rv .= $entry; };
1988 $rv .= "<hr clear='both'/>";
1991 $rv .= "<select class=\"attachments\" onchange=\"openSelectedAttachment(this)\">".
1992 "<option value=''>" . __('Attachments')."</option>";
1994 foreach ($entries as $entry) {
1995 if ($entry["title"])
1996 $title = "— " . truncate_string($entry["title"], 30);
2000 $rv .= "<option value=\"".htmlspecialchars($entry["url"])."\">" . htmlspecialchars($entry["filename"]) . "$title</option>";
2010 function getLastArticleId() {
2011 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
2012 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
2014 if (db_num_rows($result) == 1) {
2015 return db_fetch_result($result, 0, "id");
2021 function build_url($parts) {
2022 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2026 * Converts a (possibly) relative URL to a absolute one.
2028 * @param string $url Base URL (i.e. from where the document is)
2029 * @param string $rel_url Possibly relative URL in the document
2031 * @return string Absolute URL
2033 function rewrite_relative_url($url, $rel_url) {
2034 if (strpos($rel_url, ":") !== false) {
2036 } else if (strpos($rel_url, "://") !== false) {
2038 } else if (strpos($rel_url, "//") === 0) {
2039 # protocol-relative URL (rare but they exist)
2041 } else if (strpos($rel_url, "/") === 0)
2043 $parts = parse_url($url);
2044 $parts['path'] = $rel_url;
2046 return build_url($parts);
2049 $parts = parse_url($url);
2050 if (!isset($parts['path'])) {
2051 $parts['path'] = '/';
2053 $dir = $parts['path'];
2054 if (substr($dir, -1) !== '/') {
2055 $dir = dirname($parts['path']);
2056 $dir !== '/' && $dir .= '/';
2058 $parts['path'] = $dir . $rel_url;
2060 return build_url($parts);
2064 function cleanup_tags($days = 14, $limit = 1000) {
2066 if (DB_TYPE == "pgsql") {
2067 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2068 } else if (DB_TYPE == "mysql") {
2069 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2074 while ($limit > 0) {
2077 $query = "SELECT ttrss_tags.id AS id
2078 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2079 WHERE post_int_id = int_id AND $interval_query AND
2080 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2082 $result = db_query($query);
2086 while ($line = db_fetch_assoc($result)) {
2087 array_push($ids, $line['id']);
2090 if (count($ids) > 0) {
2091 $ids = join(",", $ids);
2093 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2094 $tags_deleted += db_affected_rows($tmp_result);
2099 $limit -= $limit_part;
2102 return $tags_deleted;
2105 function print_user_stylesheet() {
2106 $value = get_pref('USER_STYLESHEET');
2109 print "<style type=\"text/css\">";
2110 print str_replace("<br/>", "\n", $value);
2116 function filter_to_sql($filter, $owner_uid) {
2119 if (DB_TYPE == "pgsql")
2122 $reg_qpart = "REGEXP";
2124 foreach ($filter["rules"] AS $rule) {
2125 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2126 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2127 $rule['reg_exp']) !== FALSE;
2129 if ($regexp_valid) {
2131 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2133 switch ($rule["type"]) {
2135 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2136 $rule['reg_exp'] . "')";
2139 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2140 $rule['reg_exp'] . "')";
2143 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2144 $rule['reg_exp'] . "') OR LOWER(" .
2145 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2148 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2149 $rule['reg_exp'] . "')";
2152 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2153 $rule['reg_exp'] . "')";
2156 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2157 $rule['reg_exp'] . "')";
2161 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2163 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2164 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2167 if (isset($rule["cat_id"])) {
2169 if ($rule["cat_id"] > 0) {
2170 $children = getChildCategories($rule["cat_id"], $owner_uid);
2171 array_push($children, $rule["cat_id"]);
2173 $children = join(",", $children);
2175 $cat_qpart = "cat_id IN ($children)";
2177 $cat_qpart = "cat_id IS NULL";
2180 $qpart .= " AND $cat_qpart";
2183 $qpart .= " AND feed_id IS NOT NULL";
2185 array_push($query, "($qpart)");
2190 if (count($query) > 0) {
2191 $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
2193 $fullquery = "(false)";
2196 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2201 if (!function_exists('gzdecode')) {
2202 function gzdecode($string) { // no support for 2nd argument
2203 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2204 base64_encode($string));
2208 function get_random_bytes($length) {
2209 if (function_exists('openssl_random_pseudo_bytes')) {
2210 return openssl_random_pseudo_bytes($length);
2214 for ($i = 0; $i < $length; $i++)
2215 $output .= chr(mt_rand(0, 255));
2221 function read_stdin() {
2222 $fp = fopen("php://stdin", "r");
2225 $line = trim(fgets($fp));
2233 function tmpdirname($path, $prefix) {
2234 // Use PHP's tmpfile function to create a temporary
2235 // directory name. Delete the file and keep the name.
2236 $tempname = tempnam($path,$prefix);
2240 if (!unlink($tempname))
2246 function getFeedCategory($feed) {
2247 $result = db_query("SELECT cat_id FROM ttrss_feeds
2248 WHERE id = '$feed'");
2250 if (db_num_rows($result) > 0) {
2251 return db_fetch_result($result, 0, "cat_id");
2258 function implements_interface($class, $interface) {
2259 return in_array($interface, class_implements($class));
2262 function geturl($url, $depth = 0, $nobody = true){
2264 if ($depth == 20) return $url;
2266 if (!function_exists('curl_init'))
2267 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);
2269 $curl = curl_init();
2270 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
2271 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
2272 $header[] = "Cache-Control: max-age=0";
2273 $header[] = "Connection: keep-alive";
2274 $header[] = "Keep-Alive: 300";
2275 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
2276 $header[] = "Accept-Language: en-us,en;q=0.5";
2277 $header[] = "Pragma: ";
2279 curl_setopt($curl, CURLOPT_URL, $url);
2280 curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
2281 curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
2282 curl_setopt($curl, CURLOPT_HEADER, true);
2283 curl_setopt($curl, CURLOPT_NOBODY, $nobody);
2284 curl_setopt($curl, CURLOPT_REFERER, $url);
2285 curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
2286 curl_setopt($curl, CURLOPT_AUTOREFERER, true);
2287 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
2288 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
2289 curl_setopt($curl, CURLOPT_TIMEOUT, 60);
2290 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
2292 if (defined('_CURL_HTTP_PROXY')) {
2293 curl_setopt($curl, CURLOPT_PROXY, _CURL_HTTP_PROXY);
2296 $html = curl_exec($curl);
2298 $status = curl_getinfo($curl);
2300 if($status['http_code']!=200){
2302 // idiot site not allowing http head
2303 if($status['http_code'] == 405) {
2305 return geturl($url, $depth +1, false);
2308 if($status['http_code'] == 301 || $status['http_code'] == 302) {
2310 list($header) = explode("\r\n\r\n", $html, 2);
2312 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
2313 $url = trim(str_replace($matches[1],"",$matches[0]));
2314 $url_parsed = parse_url($url);
2315 return (isset($url_parsed))? geturl($url, $depth + 1):'';
2318 global $fetch_last_error;
2320 $fetch_last_error = curl_errno($curl) . " " . curl_error($curl);
2324 # foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
2325 # $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
2326 # $handle = @fopen('./curl.error.log', 'a');
2327 # fwrite($handle, $line);
2334 function get_minified_js($files) {
2335 require_once 'lib/jshrink/Minifier.php';
2339 foreach ($files as $js) {
2340 if (!isset($_GET['debug'])) {
2341 $cached_file = CACHE_DIR . "/js/".basename($js).".js";
2343 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2345 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2347 if ($header && $contents) {
2348 list($htag, $hversion) = explode(":", $header);
2350 if ($htag == "tt-rss" && $hversion == VERSION) {
2357 $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
2358 file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified);
2362 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2369 function stylesheet_tag($filename) {
2370 $timestamp = filemtime($filename);
2372 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2375 function javascript_tag($filename) {
2378 if (!(strpos($filename, "?") === FALSE)) {
2379 $query = substr($filename, strpos($filename, "?")+1);
2380 $filename = substr($filename, 0, strpos($filename, "?"));
2383 $timestamp = filemtime($filename);
2385 if ($query) $timestamp .= "&$query";
2387 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2390 function calculate_dep_timestamp() {
2391 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2395 foreach ($files as $file) {
2396 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2402 function T_js_decl($s1, $s2) {
2404 $s1 = preg_replace("/\n/", "", $s1);
2405 $s2 = preg_replace("/\n/", "", $s2);
2407 $s1 = preg_replace("/\"/", "\\\"", $s1);
2408 $s2 = preg_replace("/\"/", "\\\"", $s2);
2410 return "T_messages[\"$s1\"] = \"$s2\";\n";
2414 function init_js_translations() {
2416 print 'var T_messages = new Object();
2419 if (T_messages[msg]) {
2420 return T_messages[msg];
2426 function ngettext(msg1, msg2, n) {
2427 return __((parseInt(n) > 1) ? msg2 : msg1);
2430 $l10n = _get_reader();
2432 for ($i = 0; $i < $l10n->total; $i++) {
2433 $orig = $l10n->get_original_string($i);
2434 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2435 $key = explode(chr(0), $orig);
2436 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2437 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2439 $translation = __($orig);
2440 print T_js_decl($orig, $translation);
2445 function label_to_feed_id($label) {
2446 return LABEL_BASE_INDEX - 1 - abs($label);
2449 function feed_to_label_id($feed) {
2450 return LABEL_BASE_INDEX - 1 + abs($feed);
2453 function get_theme_path($theme) {
2454 $check = "themes/$theme";
2455 if (file_exists($check)) return $check;
2457 $check = "themes.local/$theme";
2458 if (file_exists($check)) return $check;
2461 function theme_valid($theme) {
2462 if ($theme == "default.css" || $theme == "night.css") return true; // needed for array_filter
2463 $file = "themes/" . basename($theme);
2465 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2467 if (file_exists($file) && is_readable($file)) {
2468 $fh = fopen($file, "r");
2471 $header = fgets($fh);
2474 return strpos($header, "supports-version:" . VERSION_STATIC) !== FALSE;
2481 function error_json($code) {
2482 require_once "errors.php";
2484 @$message = $ERRORS[$code];
2486 return json_encode(array("error" =>
2487 array("code" => $code, "message" => $message)));
2491 function abs_to_rel_path($dir) {
2492 $tmp = str_replace(dirname(__DIR__), "", $dir);
2494 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);