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 "feed_debug_viewfeed" => __("Debug viewfeed()"),
96 "catchup_all" => __("Mark all feeds as read"),
97 "cat_toggle_collapse" => __("Un/collapse current category"),
98 "toggle_combined_mode" => __("Toggle combined mode"),
99 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
100 __("Go to") => array(
101 "goto_all" => __("All articles"),
102 "goto_fresh" => __("Fresh"),
103 "goto_marked" => __("Starred"),
104 "goto_published" => __("Published"),
105 "goto_tagcloud" => __("Tag cloud"),
106 "goto_prefs" => __("Preferences")),
107 __("Other") => array(
108 "create_label" => __("Create label"),
109 "create_filter" => __("Create filter"),
110 "collapse_sidebar" => __("Un/collapse sidebar"),
111 "help_dialog" => __("Show help dialog"))
114 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_INFO) as $plugin) {
115 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
121 function get_hotkeys_map() {
123 // "navigation" => array(
126 "n" => "next_article",
127 "p" => "prev_article",
128 "(38)|up" => "prev_article",
129 "(40)|down" => "next_article",
130 // "^(38)|Ctrl-up" => "prev_article_noscroll",
131 // "^(40)|Ctrl-down" => "next_article_noscroll",
132 "(191)|/" => "search_dialog",
133 // "article" => array(
134 "s" => "toggle_mark",
135 "*s" => "toggle_publ",
136 "u" => "toggle_unread",
138 "*d" => "dismiss_selected",
139 "*x" => "dismiss_read",
140 "o" => "open_in_new_window",
141 "c p" => "catchup_below",
142 "c n" => "catchup_above",
143 "*n" => "article_scroll_down",
144 "*p" => "article_scroll_up",
145 "*(38)|Shift+up" => "article_scroll_up",
146 "*(40)|Shift+down" => "article_scroll_down",
147 "a *w" => "toggle_widescreen",
148 "a e" => "toggle_embed_original",
149 "e" => "email_article",
150 "a q" => "close_article",
151 // "article_selection" => array(
152 "a a" => "select_all",
153 "a u" => "select_unread",
154 "a *u" => "select_marked",
155 "a p" => "select_published",
156 "a i" => "select_invert",
157 "a n" => "select_none",
159 "f r" => "feed_refresh",
160 "f a" => "feed_unhide_read",
161 "f s" => "feed_subscribe",
162 "f e" => "feed_edit",
163 "f q" => "feed_catchup",
164 "f x" => "feed_reverse",
165 "f *d" => "feed_debug_update",
166 "f *g" => "feed_debug_viewfeed",
167 "f *c" => "toggle_combined_mode",
168 "f c" => "toggle_cdm_expanded",
169 "*q" => "catchup_all",
170 "x" => "cat_toggle_collapse",
173 "g f" => "goto_fresh",
174 "g s" => "goto_marked",
175 "g p" => "goto_published",
176 "g t" => "goto_tagcloud",
177 "g *p" => "goto_prefs",
179 "(9)|Tab" => "select_article_cursor", // tab
180 "c l" => "create_label",
181 "c f" => "create_filter",
182 "c s" => "collapse_sidebar",
183 "^(191)|Ctrl+/" => "help_dialog",
186 if (get_pref('COMBINED_DISPLAY_MODE')) {
187 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
188 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
191 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) {
192 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
197 foreach (array_keys($hotkeys) as $hotkey) {
198 $pair = explode(" ", $hotkey, 2);
200 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
201 array_push($prefixes, $pair[0]);
205 return array($prefixes, $hotkeys);
208 function check_for_update() {
209 if (defined("GIT_VERSION_TIMESTAMP")) {
210 $content = @fetch_file_contents("http://tt-rss.org/version.json");
213 $content = json_decode($content, true);
215 if ($content && isset($content["changeset"])) {
216 if ((int)GIT_VERSION_TIMESTAMP < (int)$content["changeset"]["timestamp"] &&
217 GIT_VERSION_HEAD != $content["changeset"]["id"]) {
219 return $content["changeset"]["id"];
228 function make_runtime_info() {
231 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
232 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
234 $max_feed_id = db_fetch_result($result, 0, "mid");
235 $num_feeds = db_fetch_result($result, 0, "nf");
237 $data["max_feed_id"] = (int) $max_feed_id;
238 $data["num_feeds"] = (int) $num_feeds;
240 $data['last_article_id'] = getLastArticleId();
241 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
243 $data['dep_ts'] = calculate_dep_timestamp();
244 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
247 if (CHECK_FOR_UPDATES && $_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) {
248 $update_result = @check_for_update();
250 $data["update_result"] = $update_result;
252 $_SESSION["last_version_check"] = time();
255 if (file_exists(LOCK_DIRECTORY . "/update_daemon.lock")) {
257 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
259 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
261 $stamp = (int) @file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
264 $stamp_delta = time() - $stamp;
266 if ($stamp_delta > 1800) {
270 $_SESSION["daemon_stamp_check"] = time();
273 $data['daemon_stamp_ok'] = $stamp_check;
275 $stamp_fmt = date("Y.m.d, G:i", $stamp);
277 $data['daemon_stamp'] = $stamp_fmt;
285 function search_to_sql($search, $search_language) {
287 $keywords = str_getcsv($search, " ");
288 $query_keywords = array();
289 $search_words = array();
290 $search_query_leftover = array();
292 if ($search_language)
293 $search_language = db_escape_string(mb_strtolower($search_language));
295 $search_language = "english";
297 foreach ($keywords as $k) {
298 if (strpos($k, "-") === 0) {
305 $commandpair = explode(":", mb_strtolower($k), 2);
307 switch ($commandpair[0]) {
309 if ($commandpair[1]) {
310 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
311 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
313 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
314 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
315 array_push($search_words, $k);
319 if ($commandpair[1]) {
320 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
321 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
323 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
324 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
325 array_push($search_words, $k);
329 if ($commandpair[1]) {
330 if ($commandpair[1] == "true")
331 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
332 else if ($commandpair[1] == "false")
333 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
335 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
336 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
338 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
339 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
340 if (!$not) array_push($search_words, $k);
345 if ($commandpair[1]) {
346 if ($commandpair[1] == "true")
347 array_push($query_keywords, "($not (marked = true))");
349 array_push($query_keywords, "($not (marked = false))");
351 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
352 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
353 if (!$not) array_push($search_words, $k);
357 if ($commandpair[1]) {
358 if ($commandpair[1] == "true")
359 array_push($query_keywords, "($not (published = true))");
361 array_push($query_keywords, "($not (published = false))");
364 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
365 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
366 if (!$not) array_push($search_words, $k);
370 if ($commandpair[1]) {
371 if ($commandpair[1] == "true")
372 array_push($query_keywords, "($not (unread = true))");
374 array_push($query_keywords, "($not (unread = false))");
377 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
378 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
379 if (!$not) array_push($search_words, $k);
383 if (strpos($k, "@") === 0) {
385 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
386 $orig_ts = strtotime(substr($k, 1));
387 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
389 //$k = date("Y-m-d", strtotime(substr($k, 1)));
391 array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')");
394 if (DB_TYPE == "pgsql") {
395 $k = mb_strtolower($k);
396 array_push($search_query_leftover, $not ? "!$k" : $k);
398 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
399 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
402 if (!$not) array_push($search_words, $k);
407 if (count($search_query_leftover) > 0) {
408 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
410 if (DB_TYPE == "pgsql") {
411 array_push($query_keywords,
412 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
417 $search_query_part = implode("AND", $query_keywords);
419 return array($search_query_part, $search_words);
422 function getParentCategories($cat, $owner_uid) {
425 $result = db_query("SELECT parent_cat FROM ttrss_feed_categories
426 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
428 while ($line = db_fetch_assoc($result)) {
429 array_push($rv, $line["parent_cat"]);
430 $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid));
436 function getChildCategories($cat, $owner_uid) {
439 $result = db_query("SELECT id FROM ttrss_feed_categories
440 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
442 while ($line = db_fetch_assoc($result)) {
443 array_push($rv, $line["id"]);
444 $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid));
450 function queryFeedHeadlines($params) {
452 $feed = $params["feed"];
453 $limit = isset($params["limit"]) ? $params["limit"] : 30;
454 $view_mode = $params["view_mode"];
455 $cat_view = isset($params["cat_view"]) ? $params["cat_view"] : false;
456 $search = isset($params["search"]) ? $params["search"] : false;
457 $search_language = isset($params["search_language"]) ? $params["search_language"] : "";
458 $override_order = isset($params["override_order"]) ? $params["override_order"] : false;
459 $offset = isset($params["offset"]) ? $params["offset"] : 0;
460 $owner_uid = isset($params["owner_uid"]) ? $params["owner_uid"] : $_SESSION["uid"];
461 $since_id = isset($params["since_id"]) ? $params["since_id"] : 0;
462 $include_children = isset($params["include_children"]) ? $params["include_children"] : false;
463 $ignore_vfeed_group = isset($params["ignore_vfeed_group"]) ? $params["ignore_vfeed_group"] : false;
464 $override_strategy = isset($params["override_strategy"]) ? $params["override_strategy"] : false;
465 $override_vfeed = isset($params["override_vfeed"]) ? $params["override_vfeed"] : false;
466 $start_ts = isset($params["start_ts"]) ? $params["start_ts"] : false;
467 $check_first_id = isset($params["check_first_id"]) ? $params["check_first_id"] : false;
468 $api_request = isset($params["api_request"]) ? $params["api_request"] : false;
470 $ext_tables_part = "";
471 $query_strategy_part = "";
473 $search_words = array();
476 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH) as $plugin) {
477 list($search_query_part, $search_words) = $plugin->hook_search($search);
481 // fall back in case of no plugins
482 if (!$search_query_part) {
483 list($search_query_part, $search_words) = search_to_sql($search, $search_language);
485 $search_query_part .= " AND ";
487 $search_query_part = "";
491 $since_id_part = "ttrss_entries.id > $since_id AND ";
496 $view_query_part = "";
497 $disable_offsets = false;
499 if ($view_mode == "adaptive") {
501 $view_query_part = " ";
502 } else if ($feed != -1) {
504 $unread = getFeedUnread($feed, $cat_view);
506 if ($cat_view && $feed > 0 && $include_children)
507 $unread += getCategoryChildrenUnread($feed);
510 $view_query_part = " unread = true AND ";
511 $disable_offsets = !$api_request && get_pref("CDM_AUTO_CATCHUP") && get_pref("CDM_EXPANDED");
516 if ($view_mode == "marked") {
517 $view_query_part = " marked = true AND ";
520 if ($view_mode == "has_note") {
521 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
524 if ($view_mode == "published") {
525 $view_query_part = " published = true AND ";
528 if ($view_mode == "unread" && $feed != -6) {
529 $view_query_part = " unread = true AND ";
530 $disable_offsets = !$api_request && get_pref("CDM_AUTO_CATCHUP") && get_pref("CDM_EXPANDED");
534 $limit_query_part = "LIMIT " . $limit;
537 $allow_archived = false;
539 $vfeed_query_part = "";
542 if (!is_numeric($feed)) {
543 $query_strategy_part = "true";
544 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
545 id = feed_id) as feed_title,";
546 } else if ($feed > 0) {
551 if ($include_children) {
553 $subcats = getChildCategories($feed, $owner_uid);
555 array_push($subcats, $feed);
556 $query_strategy_part = "cat_id IN (".
557 implode(",", $subcats).")";
560 $query_strategy_part = "cat_id = '$feed'";
564 $query_strategy_part = "cat_id IS NULL";
567 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
570 $query_strategy_part = "feed_id = '$feed'";
572 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
573 $query_strategy_part = "feed_id IS NULL";
574 $allow_archived = true;
575 } else if ($feed == 0 && $cat_view) { // uncategorized
576 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
577 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
578 } else if ($feed == -1) { // starred virtual feed
579 $query_strategy_part = "marked = true";
580 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
581 $allow_archived = true;
583 if (!$override_order) {
584 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
587 } else if ($feed == -2) { // published virtual feed OR labels category
590 $query_strategy_part = "published = true";
591 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
592 $allow_archived = true;
594 if (!$override_order) {
595 $override_order = "last_published DESC, date_entered DESC, updated DESC";
599 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
601 $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,";
603 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
604 ttrss_user_labels2.article_id = ref_id";
607 } else if ($feed == -6) { // recently read
608 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
610 if (DB_TYPE == "pgsql") {
611 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '1 DAY' ";
613 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL 1 DAY) ";
616 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
617 $allow_archived = true;
618 $ignore_vfeed_group = true;
620 if (!$override_order) $override_order = "last_read DESC";
622 } else if ($feed == -3) { // fresh virtual feed
623 $query_strategy_part = "unread = true AND score >= 0";
625 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
627 if (DB_TYPE == "pgsql") {
628 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
630 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
633 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
634 } else if ($feed == -4) { // all articles virtual feed
635 $allow_archived = true;
636 $query_strategy_part = "true";
637 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
638 } else if ($feed <= LABEL_BASE_INDEX) { // labels
639 $label_id = feed_to_label_id($feed);
641 $query_strategy_part = "label_id = '$label_id' AND
642 ttrss_labels2.id = ttrss_user_labels2.label_id AND
643 ttrss_user_labels2.article_id = ref_id";
645 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
646 $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,";
647 $allow_archived = true;
650 $query_strategy_part = "true";
653 $order_by = "score DESC, date_entered DESC, updated DESC";
655 if ($override_order) {
656 $order_by = $override_order;
659 if ($override_strategy) {
660 $query_strategy_part = $override_strategy;
663 if ($override_vfeed) {
664 $vfeed_query_part = $override_vfeed;
670 $feed_title = T_sprintf("Search results: %s", $search);
673 $feed_title = getCategoryTitle($feed);
675 if (is_numeric($feed) && $feed > 0) {
676 $result = db_query("SELECT title,site_url,last_error,last_updated
677 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
679 $feed_title = db_fetch_result($result, 0, "title");
680 $feed_site_url = db_fetch_result($result, 0, "site_url");
681 $last_error = db_fetch_result($result, 0, "last_error");
682 $last_updated = db_fetch_result($result, 0, "last_updated");
684 $feed_title = getFeedTitle($feed);
690 $content_query_part = "content, ";
692 if ($limit_query_part) {
693 $offset_query_part = "OFFSET $offset";
695 $offset_query_part = "";
698 if (is_numeric($feed)) {
699 // proper override_order applied above
700 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
701 if (!$override_order) {
702 $order_by = "ttrss_feeds.title, $order_by";
704 $order_by = "ttrss_feeds.title, $override_order";
708 if (!$allow_archived) {
709 $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id),ttrss_feeds";
710 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
713 $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id)
714 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
717 if ($vfeed_query_part) $vfeed_query_part .= "favicon_avg_color,";
720 $start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts));
721 $start_ts_query_part = "date_entered >= '$start_ts_formatted' AND";
723 $start_ts_query_part = "";
727 $first_id_query_strategy_part = $query_strategy_part;
730 $first_id_query_strategy_part = "true";
732 if (DB_TYPE == "pgsql") {
733 $sanity_interval_qpart = "date_entered >= NOW() - INTERVAL '1 hour' AND";
735 $sanity_interval_qpart = "date_entered >= DATE_SUB(NOW(), INTERVAL 1 hour) AND";
738 if (!$search && !$disable_offsets) {
739 // if previous topmost article id changed that means our current pagination is no longer valid
740 $query = "SELECT DISTINCT
757 ttrss_user_entries.owner_uid = '$owner_uid' AND
761 $sanity_interval_qpart
762 $first_id_query_strategy_part ORDER BY $order_by LIMIT 1";
764 if ($_REQUEST["debug"]) {
768 $result = db_query($query);
769 if ($result && db_num_rows($result) > 0) {
770 $first_id = (int)db_fetch_result($result, 0, "id");
772 if ($offset > 0 && $first_id && $check_first_id && $first_id != $check_first_id) {
773 return array(-1, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
778 if ($disable_offsets) {
779 $offset_query_part = "";
782 $query = "SELECT DISTINCT
785 ttrss_entries.id,ttrss_entries.title,
789 always_display_enclosures,
798 unread,feed_id,marked,published,link,last_read,orig_feed_id,
799 last_marked, last_published,
807 ttrss_user_entries.owner_uid = '$owner_uid' AND
812 $query_strategy_part ORDER BY $order_by
813 $limit_query_part $offset_query_part";
815 if ($_REQUEST["debug"]) print $query;
817 $result = db_query($query);
822 $query = "SELECT DISTINCT
826 ttrss_entries.id as id,
841 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images,
842 last_marked, last_published,
847 FROM ttrss_entries, ttrss_user_entries, ttrss_tags
849 ref_id = ttrss_entries.id AND
850 ttrss_user_entries.owner_uid = $owner_uid AND
851 post_int_id = int_id AND
852 tag_name = '$feed' AND
855 $query_strategy_part ORDER BY $order_by
856 $limit_query_part $offset_query_part";
858 if ($_REQUEST["debug"]) print $query;
860 $result = db_query($query);
863 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
867 function iframe_whitelisted($entry) {
868 $whitelist = array("youtube.com", "youtu.be", "vimeo.com");
870 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST);
873 foreach ($whitelist as $w) {
874 if ($src == $w || $src == "www.$w")
882 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
883 if (!$owner) $owner = $_SESSION["uid"];
885 $res = trim($str); if (!$res) return '';
887 $charset_hack = '<head>
888 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
891 $res = trim($res); if (!$res) return '';
893 libxml_use_internal_errors(true);
895 $doc = new DOMDocument();
896 $doc->loadHTML($charset_hack . $res);
897 $xpath = new DOMXPath($doc);
899 $entries = $xpath->query('(//a[@href]|//img[@src])');
901 foreach ($entries as $entry) {
905 if ($entry->hasAttribute('href')) {
906 $entry->setAttribute('href',
907 rewrite_relative_url($site_url, $entry->getAttribute('href')));
909 $entry->setAttribute('rel', 'noreferrer');
912 if ($entry->hasAttribute('src')) {
913 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
915 $cached_filename = CACHE_DIR . '/images/' . sha1($src) . '.png';
917 if (file_exists($cached_filename)) {
918 $src = SELF_URL_PATH . '/public.php?op=cached_image&hash=' . sha1($src);
921 $entry->setAttribute('src', $src);
924 if ($entry->nodeName == 'img') {
925 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
926 $force_remove_images || $_SESSION["bw_limit"]) {
928 $p = $doc->createElement('p');
930 $a = $doc->createElement('a');
931 $a->setAttribute('href', $entry->getAttribute('src'));
933 $a->appendChild(new DOMText($entry->getAttribute('src')));
934 $a->setAttribute('target', '_blank');
938 $entry->parentNode->replaceChild($p, $entry);
943 if (strtolower($entry->nodeName) == "a") {
944 $entry->setAttribute("target", "_blank");
948 $entries = $xpath->query('//iframe');
949 foreach ($entries as $entry) {
950 if (!iframe_whitelisted($entry)) {
951 $entry->setAttribute('sandbox', 'allow-scripts');
953 if ($_SERVER['HTTPS'] == "on") {
954 $entry->setAttribute("src",
955 str_replace("http://", "https://",
956 $entry->getAttribute("src")));
961 $allowed_elements = array('a', 'address', 'audio', 'article', 'aside',
962 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
963 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
964 'data', 'dd', 'del', 'details', 'div', 'dl', 'font',
965 'dt', 'em', 'footer', 'figure', 'figcaption',
966 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
967 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
968 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
969 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
970 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
971 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
973 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
975 $disallowed_attributes = array('id', 'style', 'class');
977 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) {
978 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
979 if (is_array($retval)) {
981 $allowed_elements = $retval[1];
982 $disallowed_attributes = $retval[2];
988 $doc->removeChild($doc->firstChild); //remove doctype
989 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
991 if ($highlight_words) {
992 foreach ($highlight_words as $word) {
994 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
996 $elements = $xpath->query("//*/text()");
998 foreach ($elements as $child) {
1000 $fragment = $doc->createDocumentFragment();
1001 $text = $child->textContent;
1003 while (($pos = mb_stripos($text, $word)) !== false) {
1004 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1005 $word = mb_substr($text, $pos, mb_strlen($word));
1006 $highlight = $doc->createElement('span');
1007 $highlight->appendChild(new DomText($word));
1008 $highlight->setAttribute('class', 'highlight');
1009 $fragment->appendChild($highlight);
1010 $text = mb_substr($text, $pos + mb_strlen($word));
1013 if (!empty($text)) $fragment->appendChild(new DomText($text));
1015 $child->parentNode->replaceChild($fragment, $child);
1020 $res = $doc->saveHTML();
1025 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1026 $xpath = new DOMXPath($doc);
1027 $entries = $xpath->query('//*');
1029 foreach ($entries as $entry) {
1030 if (!in_array($entry->nodeName, $allowed_elements)) {
1031 $entry->parentNode->removeChild($entry);
1034 if ($entry->hasAttributes()) {
1035 $attrs_to_remove = array();
1037 foreach ($entry->attributes as $attr) {
1039 if (strpos($attr->nodeName, 'on') === 0) {
1040 array_push($attrs_to_remove, $attr);
1043 if (in_array($attr->nodeName, $disallowed_attributes)) {
1044 array_push($attrs_to_remove, $attr);
1048 foreach ($attrs_to_remove as $attr) {
1049 $entry->removeAttributeNode($attr);
1057 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
1059 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1060 if (count($ids) == 0) return;
1064 foreach ($ids as $id) {
1065 array_push($tmp_ids, "ref_id = '$id'");
1068 $ids_qpart = join(" OR ", $tmp_ids);
1071 db_query("UPDATE ttrss_user_entries SET
1072 unread = false,last_read = NOW()
1073 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1074 } else if ($cmode == 1) {
1075 db_query("UPDATE ttrss_user_entries SET
1077 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1079 db_query("UPDATE ttrss_user_entries SET
1080 unread = NOT unread,last_read = NOW()
1081 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1086 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
1087 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1089 while ($line = db_fetch_assoc($result)) {
1090 ccache_update($line["feed_id"], $owner_uid);
1094 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
1096 $a_id = db_escape_string($id);
1098 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1100 $query = "SELECT DISTINCT tag_name,
1101 owner_uid as owner FROM
1102 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
1103 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
1107 /* check cache first */
1109 if ($tag_cache === false) {
1110 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
1111 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1113 $tag_cache = db_fetch_result($result, 0, "tag_cache");
1117 $tags = explode(",", $tag_cache);
1120 /* do it the hard way */
1122 $tmp_result = db_query($query);
1124 while ($tmp_line = db_fetch_assoc($tmp_result)) {
1125 array_push($tags, $tmp_line["tag_name"]);
1128 /* update the cache */
1130 $tags_str = db_escape_string(join(",", $tags));
1132 db_query("UPDATE ttrss_user_entries
1133 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
1134 AND owner_uid = $owner_uid");
1140 function trim_array($array) {
1142 array_walk($tmp, 'trim');
1146 function tag_is_valid($tag) {
1147 if ($tag == '') return false;
1148 if (preg_match("/^[0-9]*$/", $tag)) return false;
1149 if (mb_strlen($tag) > 250) return false;
1151 if (!$tag) return false;
1156 function render_login_form() {
1157 header('Cache-Control: public');
1159 require_once "login_form.php";
1163 function format_warning($msg, $id = "") {
1164 return "<div class=\"alert\" id=\"$id\">$msg</div>";
1167 function format_notice($msg, $id = "") {
1168 return "<div class=\"alert alert-info\" id=\"$id\">$msg</div>";
1171 function format_error($msg, $id = "") {
1172 return "<div class=\"alert alert-danger\" id=\"$id\">$msg</div>";
1175 function print_notice($msg) {
1176 return print format_notice($msg);
1179 function print_warning($msg) {
1180 return print format_warning($msg);
1183 function print_error($msg) {
1184 return print format_error($msg);
1188 function T_sprintf() {
1189 $args = func_get_args();
1190 return vsprintf(__(array_shift($args)), $args);
1193 function format_inline_player($url, $ctype) {
1197 $url = htmlspecialchars($url);
1199 if (strpos($ctype, "audio/") === 0) {
1201 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1202 $_SESSION["hasMp3"])) {
1204 $entry .= "<audio preload=\"none\" controls>
1205 <source type=\"$ctype\" src=\"$url\"/>
1210 $entry .= "<object type=\"application/x-shockwave-flash\"
1211 data=\"lib/button/musicplayer.swf?song_url=$url\"
1212 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1213 <param name=\"movie\"
1214 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1218 if ($entry) $entry .= " <a target=\"_blank\"
1219 href=\"$url\">" . basename($url) . "</a>";
1227 /* $filename = substr($url, strrpos($url, "/")+1);
1229 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1230 $filename . " (" . $ctype . ")" . "</a>"; */
1234 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1235 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1241 /* we can figure out feed_id from article id anyway, why do we
1242 * pass feed_id here? let's ignore the argument :(*/
1244 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1245 WHERE ref_id = '$id'");
1247 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1249 $rv['feed_id'] = $feed_id;
1251 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1253 if ($mark_as_read) {
1254 $result = db_query("UPDATE ttrss_user_entries
1255 SET unread = false,last_read = NOW()
1256 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1258 ccache_update($feed_id, $owner_uid);
1261 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1262 ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
1263 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1264 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1265 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1266 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1272 FROM ttrss_entries,ttrss_user_entries
1273 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1277 $line = db_fetch_assoc($result);
1279 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1280 unset($line["tag_cache"]);
1282 $line["content"] = sanitize($line["content"],
1283 sql_bool_to_bool($line['hide_images']),
1284 $owner_uid, $line["site_url"], false, $line["id"]);
1286 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) {
1287 $line = $p->hook_render_article($line);
1290 $num_comments = $line["num_comments"];
1291 $entry_comments = "";
1293 if ($num_comments > 0) {
1294 if ($line["comments"]) {
1295 $comments_url = htmlspecialchars($line["comments"]);
1297 $comments_url = htmlspecialchars($line["link"]);
1299 $entry_comments = "<a class=\"postComments\"
1300 target='_blank' href=\"$comments_url\">$num_comments ".
1301 _ngettext("comment", "comments", $num_comments)."</a>";
1304 if ($line["comments"] && $line["link"] != $line["comments"]) {
1305 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
1310 header("Content-Type: text/html");
1311 $rv['content'] .= "<html><head>
1312 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1313 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1314 stylesheet_tag("css/tt-rss.css").
1315 stylesheet_tag("css/zoom.css").
1316 stylesheet_tag("css/dijit.css")."
1318 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1319 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1321 </head><body id=\"ttrssZoom\">";
1324 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1326 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1328 $entry_author = $line["author"];
1330 if ($entry_author) {
1331 $entry_author = __(" - ") . $entry_author;
1334 $parsed_updated = make_local_datetime($line["updated"], true,
1338 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1340 if ($line["link"]) {
1341 $rv['content'] .= "<div class='postTitle'><a target='_blank'
1342 title=\"".htmlspecialchars($line['title'])."\"
1344 htmlspecialchars($line["link"]) . "\">" .
1345 $line["title"] . "</a>" .
1346 "<span class='author'>$entry_author</span></div>";
1348 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1352 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
1353 "\" target=\"_blank\">".
1354 htmlspecialchars($line["feed_title"])."</a>";
1356 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1358 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1361 $tags_str = format_tags_string($line["tags"], $id);
1362 $tags_str_full = join(", ", $line["tags"]);
1364 if (!$tags_str_full) $tags_str_full = __("no tags");
1366 if (!$entry_comments) $entry_comments = " "; # placeholder
1368 $rv['content'] .= "<div class='postTags' style='float : right'>
1369 <img src='images/tag.png'
1370 class='tagsPic' alt='Tags' title='Tags'> ";
1373 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1374 <a title=\"".__('Edit tags for this article')."\"
1375 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1377 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1378 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1379 position=\"below\">$tags_str_full</div>";
1381 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
1382 $rv['content'] .= $p->hook_article_button($line);
1386 $tags_str = strip_tags($tags_str);
1387 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1389 $rv['content'] .= "</div>";
1390 $rv['content'] .= "<div clear='both'>";
1392 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
1393 $rv['content'] .= $p->hook_article_left_button($line);
1396 $rv['content'] .= "$entry_comments</div>";
1398 if ($line["orig_feed_id"]) {
1400 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
1401 WHERE id = ".$line["orig_feed_id"]);
1403 if (db_num_rows($tmp_result) != 0) {
1405 $rv['content'] .= "<div clear='both'>";
1406 $rv['content'] .= __("Originally from:");
1408 $rv['content'] .= " ";
1410 $tmp_line = db_fetch_assoc($tmp_result);
1412 $rv['content'] .= "<a target='_blank'
1413 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1414 $tmp_line['title'] . "</a>";
1416 $rv['content'] .= " ";
1418 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
1419 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1421 $rv['content'] .= "</div>";
1425 $rv['content'] .= "</div>";
1427 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1428 if ($line['note']) {
1429 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1431 $rv['content'] .= "</div>";
1433 if (!$line['lang']) $line['lang'] = 'en';
1435 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1437 $rv['content'] .= $line["content"];
1440 $rv['content'] .= format_article_enclosures($id,
1441 sql_bool_to_bool($line["always_display_enclosures"]),
1443 sql_bool_to_bool($line["hide_images"]));
1446 $rv['content'] .= "</div>";
1448 $rv['content'] .= "</div>";
1454 <div class='footer'>
1455 <button onclick=\"return window.close()\">".
1456 __("Close this window")."</button></div>";
1457 $rv['content'] .= "</body></html>";
1464 function print_checkpoint($n, $s) {
1465 $ts = microtime(true);
1466 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1470 function sanitize_tag($tag) {
1473 $tag = mb_strtolower($tag, 'utf-8');
1475 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1477 if (DB_TYPE == "mysql") {
1478 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1484 function get_self_url_prefix() {
1485 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
1486 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
1488 return SELF_URL_PATH;
1493 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1495 * @return string The Mozilla Firefox feed adding URL.
1497 function add_feed_url() {
1498 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1500 $url_path = get_self_url_prefix() .
1501 "/public.php?op=subscribe&feed_url=%s";
1503 } // function add_feed_url
1505 function encrypt_password($pass, $salt = '', $mode2 = false) {
1506 if ($salt && $mode2) {
1507 return "MODE2:" . hash('sha256', $salt . $pass);
1509 return "SHA1X:" . sha1("$salt:$pass");
1511 return "SHA1:" . sha1($pass);
1513 } // function encrypt_password
1515 function load_filters($feed_id, $owner_uid, $action_id = false) {
1518 $cat_id = (int)getFeedCategory($feed_id);
1521 $null_cat_qpart = "cat_id IS NULL OR";
1523 $null_cat_qpart = "";
1525 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1526 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1528 $check_cats = join(",", array_merge(
1529 getParentCategories($cat_id, $owner_uid),
1532 while ($line = db_fetch_assoc($result)) {
1533 $filter_id = $line["id"];
1535 $result2 = db_query("SELECT
1536 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1537 FROM ttrss_filters2_rules AS r,
1538 ttrss_filter_types AS t
1540 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1541 (feed_id IS NULL OR feed_id = '$feed_id') AND
1542 filter_type = t.id AND filter_id = '$filter_id'");
1547 while ($rule_line = db_fetch_assoc($result2)) {
1548 # print_r($rule_line);
1551 $rule["reg_exp"] = $rule_line["reg_exp"];
1552 $rule["type"] = $rule_line["type_name"];
1553 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1555 array_push($rules, $rule);
1558 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1559 FROM ttrss_filters2_actions AS a,
1560 ttrss_filter_actions AS t
1562 action_id = t.id AND filter_id = '$filter_id'");
1564 while ($action_line = db_fetch_assoc($result2)) {
1565 # print_r($action_line);
1568 $action["type"] = $action_line["type_name"];
1569 $action["param"] = $action_line["action_param"];
1571 array_push($actions, $action);
1576 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1577 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1578 $filter["rules"] = $rules;
1579 $filter["actions"] = $actions;
1581 if (count($rules) > 0 && count($actions) > 0) {
1582 array_push($filters, $filter);
1589 function get_score_pic($score) {
1591 return "score_high.png";
1592 } else if ($score > 0) {
1593 return "score_half_high.png";
1594 } else if ($score < -100) {
1595 return "score_low.png";
1596 } else if ($score < 0) {
1597 return "score_half_low.png";
1599 return "score_neutral.png";
1603 function feed_has_icon($id) {
1604 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
1607 function init_plugins() {
1608 PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL);
1613 function format_tags_string($tags, $id) {
1614 if (!is_array($tags) || count($tags) == 0) {
1615 return __("no tags");
1617 $maxtags = min(5, count($tags));
1620 for ($i = 0; $i < $maxtags; $i++) {
1621 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
1624 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1626 if (count($tags) > $maxtags)
1627 $tags_str .= ", …";
1633 function format_article_labels($labels, $id) {
1635 if (!is_array($labels)) return '';
1639 foreach ($labels as $l) {
1640 $labels_str .= sprintf("<span class='hlLabelRef'
1641 style='color : %s; background-color : %s'>%s</span>",
1642 $l[2], $l[3], $l[1]);
1649 function format_article_note($id, $note, $allow_edit = true) {
1651 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1652 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1653 ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
1659 function get_feed_category($feed_cat, $parent_cat_id = false) {
1660 if ($parent_cat_id) {
1661 $parent_qpart = "parent_cat = '$parent_cat_id'";
1662 $parent_insert = "'$parent_cat_id'";
1664 $parent_qpart = "parent_cat IS NULL";
1665 $parent_insert = "NULL";
1669 "SELECT id FROM ttrss_feed_categories
1670 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1672 if (db_num_rows($result) == 0) {
1675 return db_fetch_result($result, 0, "id");
1679 function add_feed_category($feed_cat, $parent_cat_id = false) {
1681 if (!$feed_cat) return false;
1685 if ($parent_cat_id) {
1686 $parent_qpart = "parent_cat = '$parent_cat_id'";
1687 $parent_insert = "'$parent_cat_id'";
1689 $parent_qpart = "parent_cat IS NULL";
1690 $parent_insert = "NULL";
1693 $feed_cat = mb_substr($feed_cat, 0, 250);
1696 "SELECT id FROM ttrss_feed_categories
1697 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1699 if (db_num_rows($result) == 0) {
1702 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1703 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1713 function getArticleFeed($id) {
1714 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1715 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1717 if (db_num_rows($result) != 0) {
1718 return db_fetch_result($result, 0, "feed_id");
1725 * Fixes incomplete URLs by prepending "http://".
1726 * Also replaces feed:// with http://, and
1727 * prepends a trailing slash if the url is a domain name only.
1729 * @param string $url Possibly incomplete URL
1731 * @return string Fixed URL.
1733 function fix_url($url) {
1735 // support schema-less urls
1736 if (strpos($url, '//') === 0) {
1737 $url = 'https:' . $url;
1740 if (strpos($url, '://') === false) {
1741 $url = 'http://' . $url;
1742 } else if (substr($url, 0, 5) == 'feed:') {
1743 $url = 'http:' . substr($url, 5);
1746 //prepend slash if the URL has no slash in it
1747 // "http://www.example" -> "http://www.example/"
1748 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
1752 if ($url != "http:///")
1758 function validate_feed_url($url) {
1759 $parts = parse_url($url);
1761 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
1765 function get_article_enclosures($id) {
1767 $query = "SELECT * FROM ttrss_enclosures
1768 WHERE post_id = '$id' AND content_url != ''";
1772 $result = db_query($query);
1774 if (db_num_rows($result) > 0) {
1775 while ($line = db_fetch_assoc($result)) {
1776 array_push($rv, $line);
1783 /* function save_email_address($email) {
1784 // FIXME: implement persistent storage of emails
1786 if (!$_SESSION['stored_emails'])
1787 $_SESSION['stored_emails'] = array();
1789 if (!in_array($email, $_SESSION['stored_emails']))
1790 array_push($_SESSION['stored_emails'], $email);
1794 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1796 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1798 $sql_is_cat = bool_to_sql_bool($is_cat);
1800 $result = db_query("SELECT access_key FROM ttrss_access_keys
1801 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1802 AND owner_uid = " . $owner_uid);
1804 if (db_num_rows($result) == 1) {
1805 return db_fetch_result($result, 0, "access_key");
1807 $key = db_escape_string(uniqid_short());
1809 $result = db_query("INSERT INTO ttrss_access_keys
1810 (access_key, feed_id, is_cat, owner_uid)
1811 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1818 function get_feeds_from_html($url, $content)
1820 $url = fix_url($url);
1821 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
1823 libxml_use_internal_errors(true);
1825 $doc = new DOMDocument();
1826 $doc->loadHTML($content);
1827 $xpath = new DOMXPath($doc);
1828 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
1829 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
1830 $feedUrls = array();
1831 foreach ($entries as $entry) {
1832 if ($entry->hasAttribute('href')) {
1833 $title = $entry->getAttribute('title');
1835 $title = $entry->getAttribute('type');
1837 $feedUrl = rewrite_relative_url(
1838 $baseUrl, $entry->getAttribute('href')
1840 $feedUrls[$feedUrl] = $title;
1846 function is_html($content) {
1847 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
1850 function url_is_html($url, $login = false, $pass = false) {
1851 return is_html(fetch_file_contents($url, false, $login, $pass));
1854 function print_label_select($name, $value, $attributes = "") {
1856 $result = db_query("SELECT caption FROM ttrss_labels2
1857 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1859 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1860 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
1862 while ($line = db_fetch_assoc($result)) {
1864 $issel = ($line["caption"] == $value) ? "selected=\"1\"" : "";
1866 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1867 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1871 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1878 function format_article_enclosures($id, $always_display_enclosures,
1879 $article_content, $hide_images = false) {
1881 $result = get_article_enclosures($id);
1884 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ENCLOSURES) as $plugin) {
1885 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
1886 if (is_array($retval)) {
1888 $result = $retval[1];
1894 if ($rv === '' && !empty($result)) {
1895 $entries_html = array();
1897 $entries_inline = array();
1899 foreach ($result as $line) {
1901 $url = $line["content_url"];
1902 $ctype = $line["content_type"];
1903 $title = $line["title"];
1904 $width = $line["width"];
1905 $height = $line["height"];
1907 if (!$ctype) $ctype = __("unknown type");
1909 $filename = substr($url, strrpos($url, "/")+1);
1911 $player = format_inline_player($url, $ctype);
1913 if ($player) array_push($entries_inline, $player);
1915 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1916 # $filename . " (" . $ctype . ")" . "</a>";
1918 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
1919 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1921 array_push($entries_html, $entry);
1925 $entry["type"] = $ctype;
1926 $entry["filename"] = $filename;
1927 $entry["url"] = $url;
1928 $entry["title"] = $title;
1929 $entry["width"] = $width;
1930 $entry["height"] = $height;
1932 array_push($entries, $entry);
1935 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
1936 if ($always_display_enclosures ||
1937 !preg_match("/<img/i", $article_content)) {
1939 foreach ($entries as $entry) {
1941 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ENCLOSURE) as $plugin)
1942 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
1949 if (preg_match("/image/", $entry["type"]) ||
1950 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
1952 if (!$hide_images) {
1954 if ($entry['height'] > 0)
1955 $encsize .= ' height="' . intval($entry['width']) . '"';
1956 if ($entry['width'] > 0)
1957 $encsize .= ' width="' . intval($entry['height']) . '"';
1959 alt=\"".htmlspecialchars($entry["filename"])."\"
1960 src=\"" .htmlspecialchars($entry["url"]) . "\"
1961 " . $encsize . " /></p>";
1963 $rv .= "<p><a target=\"_blank\"
1964 href=\"".htmlspecialchars($entry["url"])."\"
1965 >" .htmlspecialchars($entry["url"]) . "</a></p>";
1968 if ($entry['title']) {
1969 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
1977 if (count($entries_inline) > 0) {
1978 $rv .= "<hr clear='both'/>";
1979 foreach ($entries_inline as $entry) { $rv .= $entry; };
1980 $rv .= "<hr clear='both'/>";
1983 $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
1984 "<span>" . __('Attachments')."</span>";
1986 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
1988 foreach ($entries as $entry) {
1989 if ($entry["title"])
1990 $title = "— " . truncate_string($entry["title"], 30);
1994 $rv .= "<div onclick='window.open(\"".htmlspecialchars($entry["url"])."\")'
1995 dojoType=\"dijit.MenuItem\">".htmlspecialchars($entry["filename"])."$title</div>";
2006 function getLastArticleId() {
2007 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
2008 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
2010 if (db_num_rows($result) == 1) {
2011 return db_fetch_result($result, 0, "id");
2017 function build_url($parts) {
2018 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2022 * Converts a (possibly) relative URL to a absolute one.
2024 * @param string $url Base URL (i.e. from where the document is)
2025 * @param string $rel_url Possibly relative URL in the document
2027 * @return string Absolute URL
2029 function rewrite_relative_url($url, $rel_url) {
2030 if (strpos($rel_url, ":") !== false) {
2032 } else if (strpos($rel_url, "://") !== false) {
2034 } else if (strpos($rel_url, "//") === 0) {
2035 # protocol-relative URL (rare but they exist)
2037 } else if (strpos($rel_url, "/") === 0)
2039 $parts = parse_url($url);
2040 $parts['path'] = $rel_url;
2042 return build_url($parts);
2045 $parts = parse_url($url);
2046 if (!isset($parts['path'])) {
2047 $parts['path'] = '/';
2049 $dir = $parts['path'];
2050 if (substr($dir, -1) !== '/') {
2051 $dir = dirname($parts['path']);
2052 $dir !== '/' && $dir .= '/';
2054 $parts['path'] = $dir . $rel_url;
2056 return build_url($parts);
2060 function cleanup_tags($days = 14, $limit = 1000) {
2062 if (DB_TYPE == "pgsql") {
2063 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2064 } else if (DB_TYPE == "mysql") {
2065 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2070 while ($limit > 0) {
2073 $query = "SELECT ttrss_tags.id AS id
2074 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2075 WHERE post_int_id = int_id AND $interval_query AND
2076 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2078 $result = db_query($query);
2082 while ($line = db_fetch_assoc($result)) {
2083 array_push($ids, $line['id']);
2086 if (count($ids) > 0) {
2087 $ids = join(",", $ids);
2089 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2090 $tags_deleted += db_affected_rows($tmp_result);
2095 $limit -= $limit_part;
2098 return $tags_deleted;
2101 function print_user_stylesheet() {
2102 $value = get_pref('USER_STYLESHEET');
2105 print "<style type=\"text/css\">";
2106 print str_replace("<br/>", "\n", $value);
2112 function filter_to_sql($filter, $owner_uid) {
2115 if (DB_TYPE == "pgsql")
2118 $reg_qpart = "REGEXP";
2120 foreach ($filter["rules"] AS $rule) {
2121 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2122 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2123 $rule['reg_exp']) !== FALSE;
2125 if ($regexp_valid) {
2127 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2129 switch ($rule["type"]) {
2131 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2132 $rule['reg_exp'] . "')";
2135 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2136 $rule['reg_exp'] . "')";
2139 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2140 $rule['reg_exp'] . "') OR LOWER(" .
2141 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2144 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2145 $rule['reg_exp'] . "')";
2148 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2149 $rule['reg_exp'] . "')";
2152 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2153 $rule['reg_exp'] . "')";
2157 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2159 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2160 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2163 if (isset($rule["cat_id"])) {
2165 if ($rule["cat_id"] > 0) {
2166 $children = getChildCategories($rule["cat_id"], $owner_uid);
2167 array_push($children, $rule["cat_id"]);
2169 $children = join(",", $children);
2171 $cat_qpart = "cat_id IN ($children)";
2173 $cat_qpart = "cat_id IS NULL";
2176 $qpart .= " AND $cat_qpart";
2179 $qpart .= " AND feed_id IS NOT NULL";
2181 array_push($query, "($qpart)");
2186 if (count($query) > 0) {
2187 $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
2189 $fullquery = "(false)";
2192 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2197 if (!function_exists('gzdecode')) {
2198 function gzdecode($string) { // no support for 2nd argument
2199 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2200 base64_encode($string));
2204 function get_random_bytes($length) {
2205 if (function_exists('openssl_random_pseudo_bytes')) {
2206 return openssl_random_pseudo_bytes($length);
2210 for ($i = 0; $i < $length; $i++)
2211 $output .= chr(mt_rand(0, 255));
2217 function read_stdin() {
2218 $fp = fopen("php://stdin", "r");
2221 $line = trim(fgets($fp));
2229 function tmpdirname($path, $prefix) {
2230 // Use PHP's tmpfile function to create a temporary
2231 // directory name. Delete the file and keep the name.
2232 $tempname = tempnam($path,$prefix);
2236 if (!unlink($tempname))
2242 function getFeedCategory($feed) {
2243 $result = db_query("SELECT cat_id FROM ttrss_feeds
2244 WHERE id = '$feed'");
2246 if (db_num_rows($result) > 0) {
2247 return db_fetch_result($result, 0, "cat_id");
2254 function implements_interface($class, $interface) {
2255 return in_array($interface, class_implements($class));
2258 function geturl($url, $depth = 0, $nobody = true){
2260 if ($depth == 20) return $url;
2262 if (!function_exists('curl_init'))
2263 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);
2265 $curl = curl_init();
2266 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
2267 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
2268 $header[] = "Cache-Control: max-age=0";
2269 $header[] = "Connection: keep-alive";
2270 $header[] = "Keep-Alive: 300";
2271 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
2272 $header[] = "Accept-Language: en-us,en;q=0.5";
2273 $header[] = "Pragma: ";
2275 curl_setopt($curl, CURLOPT_URL, $url);
2276 curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
2277 curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
2278 curl_setopt($curl, CURLOPT_HEADER, true);
2279 curl_setopt($curl, CURLOPT_NOBODY, $nobody);
2280 curl_setopt($curl, CURLOPT_REFERER, $url);
2281 curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
2282 curl_setopt($curl, CURLOPT_AUTOREFERER, true);
2283 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
2284 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
2285 curl_setopt($curl, CURLOPT_TIMEOUT, 60);
2286 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
2288 if (defined('_CURL_HTTP_PROXY')) {
2289 curl_setopt($curl, CURLOPT_PROXY, _CURL_HTTP_PROXY);
2292 $html = curl_exec($curl);
2294 $status = curl_getinfo($curl);
2296 if($status['http_code']!=200){
2298 // idiot site not allowing http head
2299 if($status['http_code'] == 405) {
2301 return geturl($url, $depth +1, false);
2304 if($status['http_code'] == 301 || $status['http_code'] == 302) {
2306 list($header) = explode("\r\n\r\n", $html, 2);
2308 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
2309 $url = trim(str_replace($matches[1],"",$matches[0]));
2310 $url_parsed = parse_url($url);
2311 return (isset($url_parsed))? geturl($url, $depth + 1):'';
2314 global $fetch_last_error;
2316 $fetch_last_error = curl_errno($curl) . " " . curl_error($curl);
2320 # foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
2321 # $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
2322 # $handle = @fopen('./curl.error.log', 'a');
2323 # fwrite($handle, $line);
2330 function get_minified_js($files) {
2331 require_once 'lib/jshrink/Minifier.php';
2335 foreach ($files as $js) {
2336 if (!isset($_GET['debug'])) {
2337 $cached_file = CACHE_DIR . "/js/".basename($js).".js";
2339 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2341 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2343 if ($header && $contents) {
2344 list($htag, $hversion) = explode(":", $header);
2346 if ($htag == "tt-rss" && $hversion == VERSION) {
2353 $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
2354 file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified);
2358 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2365 function stylesheet_tag($filename) {
2366 $timestamp = filemtime($filename);
2368 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2371 function javascript_tag($filename) {
2374 if (!(strpos($filename, "?") === FALSE)) {
2375 $query = substr($filename, strpos($filename, "?")+1);
2376 $filename = substr($filename, 0, strpos($filename, "?"));
2379 $timestamp = filemtime($filename);
2381 if ($query) $timestamp .= "&$query";
2383 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2386 function calculate_dep_timestamp() {
2387 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2391 foreach ($files as $file) {
2392 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2398 function T_js_decl($s1, $s2) {
2400 $s1 = preg_replace("/\n/", "", $s1);
2401 $s2 = preg_replace("/\n/", "", $s2);
2403 $s1 = preg_replace("/\"/", "\\\"", $s1);
2404 $s2 = preg_replace("/\"/", "\\\"", $s2);
2406 return "T_messages[\"$s1\"] = \"$s2\";\n";
2410 function init_js_translations() {
2412 print 'var T_messages = new Object();
2415 if (T_messages[msg]) {
2416 return T_messages[msg];
2422 function ngettext(msg1, msg2, n) {
2423 return __((parseInt(n) > 1) ? msg2 : msg1);
2426 $l10n = _get_reader();
2428 for ($i = 0; $i < $l10n->total; $i++) {
2429 $orig = $l10n->get_original_string($i);
2430 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2431 $key = explode(chr(0), $orig);
2432 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2433 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2435 $translation = __($orig);
2436 print T_js_decl($orig, $translation);
2441 function label_to_feed_id($label) {
2442 return LABEL_BASE_INDEX - 1 - abs($label);
2445 function feed_to_label_id($feed) {
2446 return LABEL_BASE_INDEX - 1 + abs($feed);
2449 function get_theme_path($theme) {
2450 $check = "themes/$theme";
2451 if (file_exists($check)) return $check;
2453 $check = "themes.local/$theme";
2454 if (file_exists($check)) return $check;
2457 function theme_valid($theme) {
2458 if ($theme == "default.css" || $theme == "night.css") return true; // needed for array_filter
2459 $file = "themes/" . basename($theme);
2461 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2463 if (file_exists($file) && is_readable($file)) {
2464 $fh = fopen($file, "r");
2467 $header = fgets($fh);
2470 return strpos($header, "supports-version:" . VERSION_STATIC) !== FALSE;
2477 function error_json($code) {
2478 require_once "errors.php";
2480 @$message = $ERRORS[$code];
2482 return json_encode(array("error" =>
2483 array("code" => $code, "message" => $message)));
2487 function abs_to_rel_path($dir) {
2488 $tmp = str_replace(dirname(__DIR__), "", $dir);
2490 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);