]>
git.wh0rd.org - tt-rss.git/blob - include/functions2.php
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
;
47 $params["icon_alert"] = base64_img("images/alert.png");
48 $params["icon_information"] = base64_img("images/information.png");
49 $params["icon_cross"] = base64_img("images/cross.png");
50 $params["icon_indicator_white"] = base64_img("images/indicator_white.gif");
55 function get_hotkeys_info() {
57 __("Navigation") => array(
58 "next_feed" => __("Open next feed"),
59 "prev_feed" => __("Open previous feed"),
60 "next_article" => __("Open next article"),
61 "prev_article" => __("Open previous article"),
62 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
63 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
64 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
65 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
66 "search_dialog" => __("Show search dialog")),
67 __("Article") => array(
68 "toggle_mark" => __("Toggle starred"),
69 "toggle_publ" => __("Toggle published"),
70 "toggle_unread" => __("Toggle unread"),
71 "edit_tags" => __("Edit tags"),
72 "open_in_new_window" => __("Open in new window"),
73 "catchup_below" => __("Mark below as read"),
74 "catchup_above" => __("Mark above as read"),
75 "article_scroll_down" => __("Scroll down"),
76 "article_scroll_up" => __("Scroll up"),
77 "select_article_cursor" => __("Select article under cursor"),
78 "email_article" => __("Email article"),
79 "close_article" => __("Close/collapse article"),
80 "toggle_expand" => __("Toggle article expansion (combined mode)"),
81 "toggle_widescreen" => __("Toggle widescreen mode"),
82 "toggle_embed_original" => __("Toggle embed original")),
83 __("Article selection") => array(
84 "select_all" => __("Select all articles"),
85 "select_unread" => __("Select unread"),
86 "select_marked" => __("Select starred"),
87 "select_published" => __("Select published"),
88 "select_invert" => __("Invert selection"),
89 "select_none" => __("Deselect everything")),
91 "feed_refresh" => __("Refresh current feed"),
92 "feed_unhide_read" => __("Un/hide read feeds"),
93 "feed_subscribe" => __("Subscribe to feed"),
94 "feed_edit" => __("Edit feed"),
95 "feed_catchup" => __("Mark as read"),
96 "feed_reverse" => __("Reverse headlines"),
97 "feed_toggle_vgroup" => __("Toggle headline grouping"),
98 "feed_debug_update" => __("Debug feed update"),
99 "feed_debug_viewfeed" => __("Debug viewfeed()"),
100 "catchup_all" => __("Mark all feeds as read"),
101 "cat_toggle_collapse" => __("Un/collapse current category"),
102 "toggle_combined_mode" => __("Toggle combined mode"),
103 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
104 __("Go to") => array(
105 "goto_all" => __("All articles"),
106 "goto_fresh" => __("Fresh"),
107 "goto_marked" => __("Starred"),
108 "goto_published" => __("Published"),
109 "goto_tagcloud" => __("Tag cloud"),
110 "goto_prefs" => __("Preferences")),
111 __("Other") => array(
112 "create_label" => __("Create label"),
113 "create_filter" => __("Create filter"),
114 "collapse_sidebar" => __("Un/collapse sidebar"),
115 "help_dialog" => __("Show help dialog"))
118 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_INFO
) as $plugin) {
119 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
125 function get_hotkeys_map() {
127 // "navigation" => array(
130 "n" => "next_article",
131 "p" => "prev_article",
132 "(38)|up" => "prev_article",
133 "(40)|down" => "next_article",
134 // "^(38)|Ctrl-up" => "prev_article_noscroll",
135 // "^(40)|Ctrl-down" => "next_article_noscroll",
136 "(191)|/" => "search_dialog",
137 // "article" => array(
138 "s" => "toggle_mark",
139 "*s" => "toggle_publ",
140 "u" => "toggle_unread",
142 "o" => "open_in_new_window",
143 "c p" => "catchup_below",
144 "c n" => "catchup_above",
145 "*n" => "article_scroll_down",
146 "*p" => "article_scroll_up",
147 "*(38)|Shift+up" => "article_scroll_up",
148 "*(40)|Shift+down" => "article_scroll_down",
149 "a *w" => "toggle_widescreen",
150 "a e" => "toggle_embed_original",
151 "e" => "email_article",
152 "a q" => "close_article",
153 // "article_selection" => array(
154 "a a" => "select_all",
155 "a u" => "select_unread",
156 "a *u" => "select_marked",
157 "a p" => "select_published",
158 "a i" => "select_invert",
159 "a n" => "select_none",
161 "f r" => "feed_refresh",
162 "f a" => "feed_unhide_read",
163 "f s" => "feed_subscribe",
164 "f e" => "feed_edit",
165 "f q" => "feed_catchup",
166 "f x" => "feed_reverse",
167 "f g" => "feed_toggle_vgroup",
168 "f *d" => "feed_debug_update",
169 "f *g" => "feed_debug_viewfeed",
170 "f *c" => "toggle_combined_mode",
171 "f c" => "toggle_cdm_expanded",
172 "*q" => "catchup_all",
173 "x" => "cat_toggle_collapse",
176 "g f" => "goto_fresh",
177 "g s" => "goto_marked",
178 "g p" => "goto_published",
179 "g t" => "goto_tagcloud",
180 "g *p" => "goto_prefs",
182 "(9)|Tab" => "select_article_cursor", // tab
183 "c l" => "create_label",
184 "c f" => "create_filter",
185 "c s" => "collapse_sidebar",
186 "^(191)|Ctrl+/" => "help_dialog",
189 if (get_pref('COMBINED_DISPLAY_MODE')) {
190 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
191 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
194 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_MAP
) as $plugin) {
195 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
200 foreach (array_keys($hotkeys) as $hotkey) {
201 $pair = explode(" ", $hotkey, 2);
203 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
204 array_push($prefixes, $pair[0]);
208 return array($prefixes, $hotkeys);
211 function check_for_update() {
212 if (defined("GIT_VERSION_TIMESTAMP")) {
213 $content = @fetch_file_contents
(array("url" => "http://tt-rss.org/version.json", "timeout" => 5));
216 $content = json_decode($content, true);
218 if ($content && isset($content["changeset"])) {
219 if ((int)GIT_VERSION_TIMESTAMP
< (int)$content["changeset"]["timestamp"] &&
220 GIT_VERSION_HEAD
!= $content["changeset"]["id"]) {
222 return $content["changeset"]["id"];
231 function make_runtime_info($disable_update_check = false) {
234 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
235 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
237 $max_feed_id = db_fetch_result($result, 0, "mid");
238 $num_feeds = db_fetch_result($result, 0, "nf");
240 $data["max_feed_id"] = (int) $max_feed_id;
241 $data["num_feeds"] = (int) $num_feeds;
243 $data['last_article_id'] = getLastArticleId();
244 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
246 $data['dep_ts'] = calculate_dep_timestamp();
247 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
250 if (CHECK_FOR_UPDATES
&& !$disable_update_check && $_SESSION["last_version_check"] +
86400 +
rand(-1000, 1000) < time()) {
251 $update_result = @check_for_update
();
253 $data["update_result"] = $update_result;
255 $_SESSION["last_version_check"] = time();
258 if (file_exists(LOCK_DIRECTORY
. "/update_daemon.lock")) {
260 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
262 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
264 $stamp = (int) @file_get_contents
(LOCK_DIRECTORY
. "/update_daemon.stamp");
267 $stamp_delta = time() - $stamp;
269 if ($stamp_delta > 1800) {
273 $_SESSION["daemon_stamp_check"] = time();
276 $data['daemon_stamp_ok'] = $stamp_check;
278 $stamp_fmt = date("Y.m.d, G:i", $stamp);
280 $data['daemon_stamp'] = $stamp_fmt;
288 function search_to_sql($search, $search_language) {
290 $keywords = str_getcsv(trim($search), " ");
291 $query_keywords = array();
292 $search_words = array();
293 $search_query_leftover = array();
295 if ($search_language)
296 $search_language = db_escape_string(mb_strtolower($search_language));
298 $search_language = "english";
300 foreach ($keywords as $k) {
301 if (strpos($k, "-") === 0) {
308 $commandpair = explode(":", mb_strtolower($k), 2);
310 switch ($commandpair[0]) {
312 if ($commandpair[1]) {
313 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
314 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
316 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
317 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
318 array_push($search_words, $k);
322 if ($commandpair[1]) {
323 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
324 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
326 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
327 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
328 array_push($search_words, $k);
332 if ($commandpair[1]) {
333 if ($commandpair[1] == "true")
334 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
335 else if ($commandpair[1] == "false")
336 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
338 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
339 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
341 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
342 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
343 if (!$not) array_push($search_words, $k);
348 if ($commandpair[1]) {
349 if ($commandpair[1] == "true")
350 array_push($query_keywords, "($not (marked = true))");
352 array_push($query_keywords, "($not (marked = false))");
354 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
355 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
356 if (!$not) array_push($search_words, $k);
360 if ($commandpair[1]) {
361 if ($commandpair[1] == "true")
362 array_push($query_keywords, "($not (published = true))");
364 array_push($query_keywords, "($not (published = false))");
367 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
368 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
369 if (!$not) array_push($search_words, $k);
373 if ($commandpair[1]) {
374 if ($commandpair[1] == "true")
375 array_push($query_keywords, "($not (unread = true))");
377 array_push($query_keywords, "($not (unread = false))");
380 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
381 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
382 if (!$not) array_push($search_words, $k);
386 if (strpos($k, "@") === 0) {
388 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
389 $orig_ts = strtotime(substr($k, 1));
390 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
392 //$k = date("Y-m-d", strtotime(substr($k, 1)));
394 array_push($query_keywords, "(".SUBSTRING_FOR_DATE
."(updated,1,LENGTH('$k')) $not = '$k')");
397 if (DB_TYPE
== "pgsql") {
398 $k = mb_strtolower($k);
399 array_push($search_query_leftover, $not ?
"!$k" : $k);
401 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
402 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
405 if (!$not) array_push($search_words, $k);
410 if (count($search_query_leftover) > 0) {
411 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
413 if (DB_TYPE
== "pgsql") {
414 array_push($query_keywords,
415 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
420 $search_query_part = implode("AND", $query_keywords);
422 return array($search_query_part, $search_words);
425 function getParentCategories($cat, $owner_uid) {
428 $result = db_query("SELECT parent_cat FROM ttrss_feed_categories
429 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
431 while ($line = db_fetch_assoc($result)) {
432 array_push($rv, $line["parent_cat"]);
433 $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid));
439 function getChildCategories($cat, $owner_uid) {
442 $result = db_query("SELECT id FROM ttrss_feed_categories
443 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
445 while ($line = db_fetch_assoc($result)) {
446 array_push($rv, $line["id"]);
447 $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid));
453 function queryFeedHeadlines($params) {
455 $feed = $params["feed"];
456 $limit = isset($params["limit"]) ?
$params["limit"] : 30;
457 $view_mode = $params["view_mode"];
458 $cat_view = isset($params["cat_view"]) ?
$params["cat_view"] : false;
459 $search = isset($params["search"]) ?
$params["search"] : false;
460 $search_language = isset($params["search_language"]) ?
$params["search_language"] : "";
461 $override_order = isset($params["override_order"]) ?
$params["override_order"] : false;
462 $offset = isset($params["offset"]) ?
$params["offset"] : 0;
463 $owner_uid = isset($params["owner_uid"]) ?
$params["owner_uid"] : $_SESSION["uid"];
464 $since_id = isset($params["since_id"]) ?
$params["since_id"] : 0;
465 $include_children = isset($params["include_children"]) ?
$params["include_children"] : false;
466 $ignore_vfeed_group = isset($params["ignore_vfeed_group"]) ?
$params["ignore_vfeed_group"] : false;
467 $override_strategy = isset($params["override_strategy"]) ?
$params["override_strategy"] : false;
468 $override_vfeed = isset($params["override_vfeed"]) ?
$params["override_vfeed"] : false;
469 $start_ts = isset($params["start_ts"]) ?
$params["start_ts"] : false;
470 $check_first_id = isset($params["check_first_id"]) ?
$params["check_first_id"] : false;
471 $skip_first_id_check = isset($params["skip_first_id_check"]) ?
$params["skip_first_id_check"] : false;
473 $ext_tables_part = "";
474 $query_strategy_part = "";
476 $search_words = array();
479 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SEARCH
) as $plugin) {
480 list($search_query_part, $search_words) = $plugin->hook_search($search);
484 // fall back in case of no plugins
485 if (!$search_query_part) {
486 list($search_query_part, $search_words) = search_to_sql($search, $search_language);
488 $search_query_part .= " AND ";
490 $search_query_part = "";
494 $since_id_part = "ttrss_entries.id > $since_id AND ";
499 $view_query_part = "";
501 if ($view_mode == "adaptive") {
503 $view_query_part = " ";
504 } else if ($feed != -1) {
506 $unread = getFeedUnread($feed, $cat_view);
508 if ($cat_view && $feed > 0 && $include_children)
509 $unread +
= getCategoryChildrenUnread($feed);
512 $view_query_part = " unread = true AND ";
517 if ($view_mode == "marked") {
518 $view_query_part = " marked = true AND ";
521 if ($view_mode == "has_note") {
522 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
525 if ($view_mode == "published") {
526 $view_query_part = " published = true AND ";
529 if ($view_mode == "unread" && $feed != -6) {
530 $view_query_part = " unread = true AND ";
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 last_read > NOW() - INTERVAL '1 DAY' ";
613 $query_strategy_part .= " AND last_read > 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 && !$skip_first_id_check) {
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 $query = "SELECT DISTINCT
781 ttrss_entries.id,ttrss_entries.title,
785 always_display_enclosures,
794 unread,feed_id,marked,published,link,last_read,orig_feed_id,
795 last_marked, last_published,
803 ttrss_user_entries.owner_uid = '$owner_uid' AND
808 $query_strategy_part ORDER BY $order_by
809 $limit_query_part $offset_query_part";
811 if ($_REQUEST["debug"]) print $query;
813 $result = db_query($query);
818 $query = "SELECT DISTINCT
822 ttrss_entries.id as id,
838 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images,
839 last_marked, last_published,
844 FROM ttrss_entries, ttrss_user_entries, ttrss_tags
846 ref_id = ttrss_entries.id AND
847 ttrss_user_entries.owner_uid = $owner_uid AND
848 post_int_id = int_id AND
849 tag_name = '$feed' AND
852 $query_strategy_part ORDER BY $order_by
853 $limit_query_part $offset_query_part";
855 if ($_REQUEST["debug"]) print $query;
857 $result = db_query($query);
860 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
864 function iframe_whitelisted($entry) {
865 $whitelist = array("youtube.com", "youtu.be", "vimeo.com", "player.vimeo.com");
867 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST
);
870 foreach ($whitelist as $w) {
871 if ($src == $w ||
$src == "www.$w")
879 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
880 if (!$owner) $owner = $_SESSION["uid"];
882 $res = trim($str); if (!$res) return '';
884 $charset_hack = '<head>
885 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
888 $res = trim($res); if (!$res) return '';
890 libxml_use_internal_errors(true);
892 $doc = new DOMDocument();
893 $doc->loadHTML($charset_hack . $res);
894 $xpath = new DOMXPath($doc);
896 $ttrss_uses_https = parse_url(get_self_url_prefix(), PHP_URL_SCHEME
) === 'https';
897 $rewrite_base_url = $site_url ?
$site_url : SELF_URL_PATH
;
899 $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src])');
901 foreach ($entries as $entry) {
903 if ($entry->hasAttribute('href')) {
904 $entry->setAttribute('href',
905 rewrite_relative_url($rewrite_base_url, $entry->getAttribute('href')));
907 $entry->setAttribute('rel', 'noopener noreferrer');
910 if ($entry->hasAttribute('src')) {
911 $src = rewrite_relative_url($rewrite_base_url, $entry->getAttribute('src'));
913 $extension = $entry->tagName
== 'source' ?
'.mp4' : '.png';
914 $cached_filename = CACHE_DIR
. '/images/' . sha1($src) . $extension;
916 if (file_exists($cached_filename)) {
917 $src = get_self_url_prefix() . '/public.php?op=cached_image&hash=' . sha1($src) . $extension;
919 if ($entry->hasAttribute('srcset')) {
920 $entry->removeAttribute('srcset');
923 if ($entry->hasAttribute('sizes')) {
924 $entry->removeAttribute('sizes');
928 $entry->setAttribute('src', $src);
931 if ($entry->nodeName
== 'img') {
933 if ($entry->hasAttribute('src')) {
934 $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME
) === 'https';
936 if ($ttrss_uses_https && !$is_https_url) {
938 if ($entry->hasAttribute('srcset')) {
939 $entry->removeAttribute('srcset');
942 if ($entry->hasAttribute('sizes')) {
943 $entry->removeAttribute('sizes');
948 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
949 $force_remove_images ||
$_SESSION["bw_limit"]) {
951 $p = $doc->createElement('p');
953 $a = $doc->createElement('a');
954 $a->setAttribute('href', $entry->getAttribute('src'));
956 $a->appendChild(new DOMText($entry->getAttribute('src')));
957 $a->setAttribute('target', '_blank');
958 $a->setAttribute('rel', 'noopener noreferrer');
962 $entry->parentNode
->replaceChild($p, $entry);
966 if (strtolower($entry->nodeName
) == "a") {
967 $entry->setAttribute("target", "_blank");
968 $entry->setAttribute("rel", "noopener noreferrer");
972 $entries = $xpath->query('//iframe');
973 foreach ($entries as $entry) {
974 if (!iframe_whitelisted($entry)) {
975 $entry->setAttribute('sandbox', 'allow-scripts');
977 if ($_SERVER['HTTPS'] == "on") {
978 $entry->setAttribute("src",
979 str_replace("http://", "https://",
980 $entry->getAttribute("src")));
985 $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside',
986 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
987 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
988 'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font',
989 'dt', 'em', 'footer', 'figure', 'figcaption',
990 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
991 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
992 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
993 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
994 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
995 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
997 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
999 $disallowed_attributes = array('id', 'style', 'class');
1001 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SANITIZE
) as $plugin) {
1002 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
1003 if (is_array($retval)) {
1005 $allowed_elements = $retval[1];
1006 $disallowed_attributes = $retval[2];
1012 $doc->removeChild($doc->firstChild
); //remove doctype
1013 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
1015 if ($highlight_words) {
1016 foreach ($highlight_words as $word) {
1018 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
1020 $elements = $xpath->query("//*/text()");
1022 foreach ($elements as $child) {
1024 $fragment = $doc->createDocumentFragment();
1025 $text = $child->textContent
;
1027 while (($pos = mb_stripos($text, $word)) !== false) {
1028 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1029 $word = mb_substr($text, $pos, mb_strlen($word));
1030 $highlight = $doc->createElement('span');
1031 $highlight->appendChild(new DomText($word));
1032 $highlight->setAttribute('class', 'highlight');
1033 $fragment->appendChild($highlight);
1034 $text = mb_substr($text, $pos +
mb_strlen($word));
1037 if (!empty($text)) $fragment->appendChild(new DomText($text));
1039 $child->parentNode
->replaceChild($fragment, $child);
1044 $res = $doc->saveHTML();
1046 /* strip everything outside of <body>...</body> */
1048 $res_frag = array();
1049 if (preg_match('/<body>(.*)<\/body>/is', $res, $res_frag)) {
1050 return $res_frag[1];
1056 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1057 $xpath = new DOMXPath($doc);
1058 $entries = $xpath->query('//*');
1060 foreach ($entries as $entry) {
1061 if (!in_array($entry->nodeName
, $allowed_elements)) {
1062 $entry->parentNode
->removeChild($entry);
1065 if ($entry->hasAttributes()) {
1066 $attrs_to_remove = array();
1068 foreach ($entry->attributes
as $attr) {
1070 if (strpos($attr->nodeName
, 'on') === 0) {
1071 array_push($attrs_to_remove, $attr);
1074 if ($attr->nodeName
== 'href' && stripos($attr->value
, 'javascript:') === 0) {
1075 array_push($attrs_to_remove, $attr);
1078 if (in_array($attr->nodeName
, $disallowed_attributes)) {
1079 array_push($attrs_to_remove, $attr);
1083 foreach ($attrs_to_remove as $attr) {
1084 $entry->removeAttributeNode($attr);
1092 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
1094 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1095 if (count($ids) == 0) return;
1099 foreach ($ids as $id) {
1100 array_push($tmp_ids, "ref_id = '$id'");
1103 $ids_qpart = join(" OR ", $tmp_ids);
1106 db_query("UPDATE ttrss_user_entries SET
1107 unread = false,last_read = NOW()
1108 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1109 } else if ($cmode == 1) {
1110 db_query("UPDATE ttrss_user_entries SET
1112 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1114 db_query("UPDATE ttrss_user_entries SET
1115 unread = NOT unread,last_read = NOW()
1116 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1121 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
1122 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1124 while ($line = db_fetch_assoc($result)) {
1125 ccache_update($line["feed_id"], $owner_uid);
1129 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
1131 $a_id = db_escape_string($id);
1133 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1135 $query = "SELECT DISTINCT tag_name,
1136 owner_uid as owner FROM
1137 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
1138 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
1142 /* check cache first */
1144 if ($tag_cache === false) {
1145 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
1146 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1148 if (db_num_rows($result) != 0)
1149 $tag_cache = db_fetch_result($result, 0, "tag_cache");
1153 $tags = explode(",", $tag_cache);
1156 /* do it the hard way */
1158 $tmp_result = db_query($query);
1160 while ($tmp_line = db_fetch_assoc($tmp_result)) {
1161 array_push($tags, $tmp_line["tag_name"]);
1164 /* update the cache */
1166 $tags_str = db_escape_string(join(",", $tags));
1168 db_query("UPDATE ttrss_user_entries
1169 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
1170 AND owner_uid = $owner_uid");
1176 function trim_array($array) {
1178 array_walk($tmp, 'trim');
1182 function tag_is_valid($tag) {
1183 if ($tag == '') return false;
1184 if (is_numeric($tag)) return false;
1185 if (mb_strlen($tag) > 250) return false;
1187 if (!$tag) return false;
1192 function render_login_form() {
1193 header('Cache-Control: public');
1195 require_once "login_form.php";
1199 function format_warning($msg, $id = "") {
1200 return "<div class=\"alert\" id=\"$id\">$msg</div>";
1203 function format_notice($msg, $id = "") {
1204 return "<div class=\"alert alert-info\" id=\"$id\">$msg</div>";
1207 function format_error($msg, $id = "") {
1208 return "<div class=\"alert alert-danger\" id=\"$id\">$msg</div>";
1211 function print_notice($msg) {
1212 return print format_notice($msg);
1215 function print_warning($msg) {
1216 return print format_warning($msg);
1219 function print_error($msg) {
1220 return print format_error($msg);
1224 function T_sprintf() {
1225 $args = func_get_args();
1226 return vsprintf(__(array_shift($args)), $args);
1229 function format_inline_player($url, $ctype) {
1233 $url = htmlspecialchars($url);
1235 if (strpos($ctype, "audio/") === 0) {
1237 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1238 $_SESSION["hasMp3"])) {
1240 $entry .= "<audio preload=\"none\" controls>
1241 <source type=\"$ctype\" src=\"$url\"/>
1246 $entry .= "<object type=\"application/x-shockwave-flash\"
1247 data=\"lib/button/musicplayer.swf?song_url=$url\"
1248 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1249 <param name=\"movie\"
1250 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1254 if ($entry) $entry .= " <a target=\"_blank\" rel=\"noopener noreferrer\"
1255 href=\"$url\">" . basename($url) . "</a>";
1263 /* $filename = substr($url, strrpos($url, "/")+1);
1265 $entry .= " <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"" . htmlspecialchars($url) . "\">" .
1266 $filename . " (" . $ctype . ")" . "</a>"; */
1270 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1271 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1277 /* we can figure out feed_id from article id anyway, why do we
1278 * pass feed_id here? let's ignore the argument :(*/
1280 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1281 WHERE ref_id = '$id'");
1283 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1285 $rv['feed_id'] = $feed_id;
1287 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1289 if ($mark_as_read) {
1290 $result = db_query("UPDATE ttrss_user_entries
1291 SET unread = false,last_read = NOW()
1292 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1294 ccache_update($feed_id, $owner_uid);
1297 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1298 ".SUBSTRING_FOR_DATE
."(updated,1,16) as updated,
1299 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1300 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1301 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1302 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1309 FROM ttrss_entries,ttrss_user_entries
1310 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1314 $line = db_fetch_assoc($result);
1316 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1317 unset($line["tag_cache"]);
1319 $line["content"] = sanitize($line["content"],
1320 sql_bool_to_bool($line['hide_images']),
1321 $owner_uid, $line["site_url"], false, $line["id"]);
1323 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_RENDER_ARTICLE
) as $p) {
1324 $line = $p->hook_render_article($line);
1327 $num_comments = (int) $line["num_comments"];
1328 $entry_comments = "";
1330 if ($num_comments > 0) {
1331 if ($line["comments"]) {
1332 $comments_url = htmlspecialchars($line["comments"]);
1334 $comments_url = htmlspecialchars($line["link"]);
1336 $entry_comments = "<a class=\"postComments\"
1337 target='_blank' rel=\"noopener noreferrer\" href=\"$comments_url\">$num_comments ".
1338 _ngettext("comment", "comments", $num_comments)."</a>";
1341 if ($line["comments"] && $line["link"] != $line["comments"]) {
1342 $entry_comments = "<a class=\"postComments\" target='_blank' rel=\"noopener noreferrer\" href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
1347 header("Content-Type: text/html");
1348 $rv['content'] .= "<html><head>
1349 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1350 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1351 stylesheet_tag("css/tt-rss.css").
1352 stylesheet_tag("css/zoom.css").
1353 stylesheet_tag("css/dijit.css")."
1355 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1356 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1358 </head><body id=\"ttrssZoom\">";
1361 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1363 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1365 $entry_author = $line["author"];
1367 if ($entry_author) {
1368 $entry_author = __(" - ") . $entry_author;
1371 $parsed_updated = make_local_datetime($line["updated"], true,
1375 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1377 if ($line["link"]) {
1378 $rv['content'] .= "<div class='postTitle'><a target='_blank' rel='noopener noreferrer'
1379 title=\"".htmlspecialchars($line['title'])."\"
1381 htmlspecialchars($line["link"]) . "\">" .
1382 $line["title"] . "</a>" .
1383 "<span class='author'>$entry_author</span></div>";
1385 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1389 $feed_title = htmlspecialchars($line["feed_title"]);
1391 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1393 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1396 $tags_str = format_tags_string($line["tags"], $id);
1397 $tags_str_full = join(", ", $line["tags"]);
1399 if (!$tags_str_full) $tags_str_full = __("no tags");
1401 if (!$entry_comments) $entry_comments = " "; # placeholder
1403 $rv['content'] .= "<div class='postTags' style='float : right'>
1404 <img src='images/tag.png'
1405 class='tagsPic' alt='Tags' title='Tags'> ";
1408 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1409 <a title=\"".__('Edit tags for this article')."\"
1410 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1412 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1413 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1414 position=\"below\">$tags_str_full</div>";
1416 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_BUTTON
) as $p) {
1417 $rv['content'] .= $p->hook_article_button($line);
1421 $tags_str = strip_tags($tags_str);
1422 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1424 $rv['content'] .= "</div>";
1425 $rv['content'] .= "<div clear='both'>";
1427 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_LEFT_BUTTON
) as $p) {
1428 $rv['content'] .= $p->hook_article_left_button($line);
1431 $rv['content'] .= "$entry_comments</div>";
1433 if ($line["orig_feed_id"]) {
1435 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
1436 WHERE id = ".$line["orig_feed_id"] . " AND owner_uid = " . $_SESSION["uid"]);
1438 if (db_num_rows($tmp_result) != 0) {
1440 $rv['content'] .= "<div clear='both'>";
1441 $rv['content'] .= __("Originally from:");
1443 $rv['content'] .= " ";
1445 $tmp_line = db_fetch_assoc($tmp_result);
1447 $rv['content'] .= "<a target='_blank' rel='noopener noreferrer'
1448 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1449 $tmp_line['title'] . "</a>";
1451 $rv['content'] .= " ";
1453 $rv['content'] .= "<a target='_blank' rel='noopener noreferrer' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
1454 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1456 $rv['content'] .= "</div>";
1460 $rv['content'] .= "</div>";
1462 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1463 if ($line['note']) {
1464 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1466 $rv['content'] .= "</div>";
1468 if (!$line['lang']) $line['lang'] = 'en';
1470 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1472 $rv['content'] .= $line["content"];
1475 $rv['content'] .= format_article_enclosures($id,
1476 sql_bool_to_bool($line["always_display_enclosures"]),
1478 sql_bool_to_bool($line["hide_images"]));
1481 $rv['content'] .= "</div>";
1483 $rv['content'] .= "</div>";
1489 <div class='footer'>
1490 <button onclick=\"return window.close()\">".
1491 __("Close this window")."</button></div>";
1492 $rv['content'] .= "</body></html>";
1499 function print_checkpoint($n, $s) {
1500 $ts = microtime(true);
1501 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1505 function sanitize_tag($tag) {
1508 $tag = mb_strtolower($tag, 'utf-8');
1510 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1512 if (DB_TYPE
== "mysql") {
1513 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1519 function get_self_url_prefix() {
1520 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
1521 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
1523 return SELF_URL_PATH
;
1528 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1530 * @return string The Mozilla Firefox feed adding URL.
1532 function add_feed_url() {
1533 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1535 $url_path = get_self_url_prefix() .
1536 "/public.php?op=subscribe&feed_url=%s";
1538 } // function add_feed_url
1540 function encrypt_password($pass, $salt = '', $mode2 = false) {
1541 if ($salt && $mode2) {
1542 return "MODE2:" . hash('sha256', $salt . $pass);
1544 return "SHA1X:" . sha1("$salt:$pass");
1546 return "SHA1:" . sha1($pass);
1548 } // function encrypt_password
1550 function load_filters($feed_id, $owner_uid, $action_id = false) {
1553 $cat_id = (int)getFeedCategory($feed_id);
1556 $null_cat_qpart = "cat_id IS NULL OR";
1558 $null_cat_qpart = "";
1560 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1561 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1563 $check_cats = join(",", array_merge(
1564 getParentCategories($cat_id, $owner_uid),
1567 while ($line = db_fetch_assoc($result)) {
1568 $filter_id = $line["id"];
1570 $result2 = db_query("SELECT
1571 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1572 FROM ttrss_filters2_rules AS r,
1573 ttrss_filter_types AS t
1575 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1576 (feed_id IS NULL OR feed_id = '$feed_id') AND
1577 filter_type = t.id AND filter_id = '$filter_id'");
1582 while ($rule_line = db_fetch_assoc($result2)) {
1583 # print_r($rule_line);
1586 $rule["reg_exp"] = $rule_line["reg_exp"];
1587 $rule["type"] = $rule_line["type_name"];
1588 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1590 array_push($rules, $rule);
1593 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1594 FROM ttrss_filters2_actions AS a,
1595 ttrss_filter_actions AS t
1597 action_id = t.id AND filter_id = '$filter_id'");
1599 while ($action_line = db_fetch_assoc($result2)) {
1600 # print_r($action_line);
1603 $action["type"] = $action_line["type_name"];
1604 $action["param"] = $action_line["action_param"];
1606 array_push($actions, $action);
1611 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1612 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1613 $filter["rules"] = $rules;
1614 $filter["actions"] = $actions;
1616 if (count($rules) > 0 && count($actions) > 0) {
1617 array_push($filters, $filter);
1624 function get_score_pic($score) {
1626 return "score_high.png";
1627 } else if ($score > 0) {
1628 return "score_half_high.png";
1629 } else if ($score < -100) {
1630 return "score_low.png";
1631 } else if ($score < 0) {
1632 return "score_half_low.png";
1634 return "score_neutral.png";
1638 function feed_has_icon($id) {
1639 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
1642 function init_plugins() {
1643 PluginHost
::getInstance()->load(PLUGINS
, PluginHost
::KIND_ALL
);
1648 function format_tags_string($tags, $id) {
1649 if (!is_array($tags) ||
count($tags) == 0) {
1650 return __("no tags");
1652 $maxtags = min(5, count($tags));
1655 for ($i = 0; $i < $maxtags; $i++
) {
1656 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
1659 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1661 if (count($tags) > $maxtags)
1662 $tags_str .= ", …";
1668 function format_article_labels($labels, $id) {
1670 if (!is_array($labels)) return '';
1674 foreach ($labels as $l) {
1675 $labels_str .= sprintf("<span class='hlLabelRef'
1676 style='color : %s; background-color : %s'>%s</span>",
1677 $l[2], $l[3], $l[1]);
1684 function format_article_note($id, $note, $allow_edit = true) {
1686 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1687 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1688 ($allow_edit ?
__('(edit note)') : "")."</div>$note</div>";
1694 function get_feed_category($feed_cat, $parent_cat_id = false) {
1695 if ($parent_cat_id) {
1696 $parent_qpart = "parent_cat = '$parent_cat_id'";
1697 $parent_insert = "'$parent_cat_id'";
1699 $parent_qpart = "parent_cat IS NULL";
1700 $parent_insert = "NULL";
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 return db_fetch_result($result, 0, "id");
1714 function add_feed_category($feed_cat, $parent_cat_id = false) {
1716 if (!$feed_cat) return false;
1720 if ($parent_cat_id) {
1721 $parent_qpart = "parent_cat = '$parent_cat_id'";
1722 $parent_insert = "'$parent_cat_id'";
1724 $parent_qpart = "parent_cat IS NULL";
1725 $parent_insert = "NULL";
1728 $feed_cat = mb_substr($feed_cat, 0, 250);
1731 "SELECT id FROM ttrss_feed_categories
1732 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1734 if (db_num_rows($result) == 0) {
1737 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1738 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1748 function getArticleFeed($id) {
1749 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1750 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1752 if (db_num_rows($result) != 0) {
1753 return db_fetch_result($result, 0, "feed_id");
1760 * Fixes incomplete URLs by prepending "http://".
1761 * Also replaces feed:// with http://, and
1762 * prepends a trailing slash if the url is a domain name only.
1764 * @param string $url Possibly incomplete URL
1766 * @return string Fixed URL.
1768 function fix_url($url) {
1770 // support schema-less urls
1771 if (strpos($url, '//') === 0) {
1772 $url = 'https:' . $url;
1775 if (strpos($url, '://') === false) {
1776 $url = 'http://' . $url;
1777 } else if (substr($url, 0, 5) == 'feed:') {
1778 $url = 'http:' . substr($url, 5);
1781 //prepend slash if the URL has no slash in it
1782 // "http://www.example" -> "http://www.example/"
1783 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
1787 //convert IDNA hostname to punycode if possible
1788 if (function_exists("idn_to_ascii")) {
1789 $parts = parse_url($url);
1790 if (mb_detect_encoding($parts['host']) != 'ASCII')
1792 $parts['host'] = idn_to_ascii($parts['host']);
1793 $url = build_url($parts);
1797 if ($url != "http:///")
1803 function validate_feed_url($url) {
1804 $parts = parse_url($url);
1806 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
1810 function get_article_enclosures($id) {
1812 $query = "SELECT * FROM ttrss_enclosures
1813 WHERE post_id = '$id' AND content_url != ''";
1817 $result = db_query($query);
1819 if (db_num_rows($result) > 0) {
1820 while ($line = db_fetch_assoc($result)) {
1821 array_push($rv, $line);
1828 /* function save_email_address($email) {
1829 // FIXME: implement persistent storage of emails
1831 if (!$_SESSION['stored_emails'])
1832 $_SESSION['stored_emails'] = array();
1834 if (!in_array($email, $_SESSION['stored_emails']))
1835 array_push($_SESSION['stored_emails'], $email);
1839 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1841 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1843 $sql_is_cat = bool_to_sql_bool($is_cat);
1845 $result = db_query("SELECT access_key FROM ttrss_access_keys
1846 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1847 AND owner_uid = " . $owner_uid);
1849 if (db_num_rows($result) == 1) {
1850 return db_fetch_result($result, 0, "access_key");
1852 $key = db_escape_string(uniqid_short());
1854 $result = db_query("INSERT INTO ttrss_access_keys
1855 (access_key, feed_id, is_cat, owner_uid)
1856 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1863 function get_feeds_from_html($url, $content)
1865 $url = fix_url($url);
1866 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
1868 libxml_use_internal_errors(true);
1870 $doc = new DOMDocument();
1871 $doc->loadHTML($content);
1872 $xpath = new DOMXPath($doc);
1873 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
1874 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
1875 $feedUrls = array();
1876 foreach ($entries as $entry) {
1877 if ($entry->hasAttribute('href')) {
1878 $title = $entry->getAttribute('title');
1880 $title = $entry->getAttribute('type');
1882 $feedUrl = rewrite_relative_url(
1883 $baseUrl, $entry->getAttribute('href')
1885 $feedUrls[$feedUrl] = $title;
1891 function is_html($content) {
1892 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
1895 function url_is_html($url, $login = false, $pass = false) {
1896 return is_html(fetch_file_contents($url, false, $login, $pass));
1899 function print_label_select($name, $value, $attributes = "") {
1901 $result = db_query("SELECT caption FROM ttrss_labels2
1902 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1904 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1905 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
1907 while ($line = db_fetch_assoc($result)) {
1909 $issel = ($line["caption"] == $value) ?
"selected=\"1\"" : "";
1911 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1912 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1916 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1923 function format_article_enclosures($id, $always_display_enclosures,
1924 $article_content, $hide_images = false) {
1926 $result = get_article_enclosures($id);
1929 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_FORMAT_ENCLOSURES
) as $plugin) {
1930 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
1931 if (is_array($retval)) {
1933 $result = $retval[1];
1938 unset($retval); // Unset to prevent breaking render if there are no HOOK_RENDER_ENCLOSURE hooks below.
1940 if ($rv === '' && !empty($result)) {
1941 $entries_html = array();
1943 $entries_inline = array();
1945 foreach ($result as $line) {
1947 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ENCLOSURE_ENTRY
) as $plugin) {
1948 $line = $plugin->hook_enclosure_entry($line);
1951 $url = $line["content_url"];
1952 $ctype = $line["content_type"];
1953 $title = $line["title"];
1954 $width = $line["width"];
1955 $height = $line["height"];
1957 if (!$ctype) $ctype = __("unknown type");
1959 //$filename = substr($url, strrpos($url, "/")+1);
1960 $filename = basename($url);
1962 $player = format_inline_player($url, $ctype);
1964 if ($player) array_push($entries_inline, $player);
1966 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\" rel=\"noopener noreferrer\">" .
1967 # $filename . " (" . $ctype . ")" . "</a>";
1969 $entry = "<div onclick=\"openUrlPopup('".htmlspecialchars($url)."')\"
1970 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1972 array_push($entries_html, $entry);
1976 $entry["type"] = $ctype;
1977 $entry["filename"] = $filename;
1978 $entry["url"] = $url;
1979 $entry["title"] = $title;
1980 $entry["width"] = $width;
1981 $entry["height"] = $height;
1983 array_push($entries, $entry);
1986 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
1987 if ($always_display_enclosures ||
1988 !preg_match("/<img/i", $article_content)) {
1990 foreach ($entries as $entry) {
1992 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_RENDER_ENCLOSURE
) as $plugin)
1993 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
2000 if (preg_match("/image/", $entry["type"]) ||
2001 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
2003 if (!$hide_images) {
2005 if ($entry['height'] > 0)
2006 $encsize .= ' height="' . intval($entry['height']) . '"';
2007 if ($entry['width'] > 0)
2008 $encsize .= ' width="' . intval($entry['width']) . '"';
2010 alt=\"".htmlspecialchars($entry["filename"])."\"
2011 src=\"" .htmlspecialchars($entry["url"]) . "\"
2012 " . $encsize . " /></p>";
2014 $rv .= "<p><a target=\"_blank\" rel=\"noopener noreferrer\"
2015 href=\"".htmlspecialchars($entry["url"])."\"
2016 >" .htmlspecialchars($entry["url"]) . "</a></p>";
2019 if ($entry['title']) {
2020 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
2028 if (count($entries_inline) > 0) {
2029 $rv .= "<hr clear='both'/>";
2030 foreach ($entries_inline as $entry) { $rv .= $entry; };
2031 $rv .= "<hr clear='both'/>";
2034 $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
2035 "<span>" . __('Attachments')."</span>";
2037 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
2039 foreach ($entries as $entry) {
2040 if ($entry["title"])
2041 $title = " — " . truncate_string($entry["title"], 30);
2045 if ($entry["filename"])
2046 $filename = truncate_middle(htmlspecialchars($entry["filename"]), 60);
2050 $rv .= "<div onclick='openUrlPopup(\"".htmlspecialchars($entry["url"])."\")'
2051 dojoType=\"dijit.MenuItem\">".$filename . $title."</div>";
2062 function getLastArticleId() {
2063 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
2064 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
2066 if (db_num_rows($result) == 1) {
2067 return db_fetch_result($result, 0, "id");
2073 function build_url($parts) {
2074 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2078 * Converts a (possibly) relative URL to a absolute one.
2080 * @param string $url Base URL (i.e. from where the document is)
2081 * @param string $rel_url Possibly relative URL in the document
2083 * @return string Absolute URL
2085 function rewrite_relative_url($url, $rel_url) {
2086 if (strpos($rel_url, "://") !== false) {
2088 } else if (strpos($rel_url, "//") === 0) {
2089 # protocol-relative URL (rare but they exist)
2091 } else if (preg_match("/^[a-z]+:/i", $rel_url)) {
2092 # magnet:, feed:, etc
2094 } else if (strpos($rel_url, "/") === 0) {
2095 $parts = parse_url($url);
2096 $parts['path'] = $rel_url;
2098 return build_url($parts);
2101 $parts = parse_url($url);
2102 if (!isset($parts['path'])) {
2103 $parts['path'] = '/';
2105 $dir = $parts['path'];
2106 if (substr($dir, -1) !== '/') {
2107 $dir = dirname($parts['path']);
2108 $dir !== '/' && $dir .= '/';
2110 $parts['path'] = $dir . $rel_url;
2112 return build_url($parts);
2116 function cleanup_tags($days = 14, $limit = 1000) {
2118 if (DB_TYPE
== "pgsql") {
2119 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2120 } else if (DB_TYPE
== "mysql") {
2121 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2126 while ($limit > 0) {
2129 $query = "SELECT ttrss_tags.id AS id
2130 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2131 WHERE post_int_id = int_id AND $interval_query AND
2132 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2134 $result = db_query($query);
2138 while ($line = db_fetch_assoc($result)) {
2139 array_push($ids, $line['id']);
2142 if (count($ids) > 0) {
2143 $ids = join(",", $ids);
2145 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2146 $tags_deleted +
= db_affected_rows($tmp_result);
2151 $limit -= $limit_part;
2154 return $tags_deleted;
2157 function print_user_stylesheet() {
2158 $value = get_pref('USER_STYLESHEET');
2161 print "<style type=\"text/css\">";
2162 print str_replace("<br/>", "\n", $value);
2168 function filter_to_sql($filter, $owner_uid) {
2171 if (DB_TYPE
== "pgsql")
2174 $reg_qpart = "REGEXP";
2176 foreach ($filter["rules"] AS $rule) {
2177 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2178 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2179 $rule['reg_exp']) !== FALSE;
2181 if ($regexp_valid) {
2183 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2185 switch ($rule["type"]) {
2187 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2188 $rule['reg_exp'] . "')";
2191 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2192 $rule['reg_exp'] . "')";
2195 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2196 $rule['reg_exp'] . "') OR LOWER(" .
2197 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2200 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2201 $rule['reg_exp'] . "')";
2204 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2205 $rule['reg_exp'] . "')";
2208 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2209 $rule['reg_exp'] . "')";
2213 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2215 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2216 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2219 if (isset($rule["cat_id"])) {
2221 if ($rule["cat_id"] > 0) {
2222 $children = getChildCategories($rule["cat_id"], $owner_uid);
2223 array_push($children, $rule["cat_id"]);
2225 $children = join(",", $children);
2227 $cat_qpart = "cat_id IN ($children)";
2229 $cat_qpart = "cat_id IS NULL";
2232 $qpart .= " AND $cat_qpart";
2235 $qpart .= " AND feed_id IS NOT NULL";
2237 array_push($query, "($qpart)");
2242 if (count($query) > 0) {
2243 $fullquery = "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
2245 $fullquery = "(false)";
2248 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2253 if (!function_exists('gzdecode')) {
2254 function gzdecode($string) { // no support for 2nd argument
2255 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2256 base64_encode($string));
2260 function get_random_bytes($length) {
2261 if (function_exists('openssl_random_pseudo_bytes')) {
2262 return openssl_random_pseudo_bytes($length);
2266 for ($i = 0; $i < $length; $i++
)
2267 $output .= chr(mt_rand(0, 255));
2273 function read_stdin() {
2274 $fp = fopen("php://stdin", "r");
2277 $line = trim(fgets($fp));
2285 function tmpdirname($path, $prefix) {
2286 // Use PHP's tmpfile function to create a temporary
2287 // directory name. Delete the file and keep the name.
2288 $tempname = tempnam($path,$prefix);
2292 if (!unlink($tempname))
2298 function getFeedCategory($feed) {
2299 $result = db_query("SELECT cat_id FROM ttrss_feeds
2300 WHERE id = '$feed'");
2302 if (db_num_rows($result) > 0) {
2303 return db_fetch_result($result, 0, "cat_id");
2310 function implements_interface($class, $interface) {
2311 return in_array($interface, class_implements($class));
2314 function get_minified_js($files) {
2315 require_once 'lib/jshrink/Minifier.php';
2319 foreach ($files as $js) {
2320 if (!isset($_GET['debug'])) {
2321 $cached_file = CACHE_DIR
. "/js/".basename($js).".js";
2323 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2325 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2327 if ($header && $contents) {
2328 list($htag, $hversion) = explode(":", $header);
2330 if ($htag == "tt-rss" && $hversion == VERSION
) {
2337 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
2338 file_put_contents($cached_file, "tt-rss:" . VERSION
. "\n" . $minified);
2342 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2349 function stylesheet_tag($filename) {
2350 $timestamp = filemtime($filename);
2352 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2355 function javascript_tag($filename) {
2358 if (!(strpos($filename, "?") === FALSE)) {
2359 $query = substr($filename, strpos($filename, "?")+
1);
2360 $filename = substr($filename, 0, strpos($filename, "?"));
2363 $timestamp = filemtime($filename);
2365 if ($query) $timestamp .= "&$query";
2367 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2370 function calculate_dep_timestamp() {
2371 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2375 foreach ($files as $file) {
2376 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2382 function T_js_decl($s1, $s2) {
2384 $s1 = preg_replace("/\n/", "", $s1);
2385 $s2 = preg_replace("/\n/", "", $s2);
2387 $s1 = preg_replace("/\"/", "\\\"", $s1);
2388 $s2 = preg_replace("/\"/", "\\\"", $s2);
2390 return "T_messages[\"$s1\"] = \"$s2\";\n";
2394 function init_js_translations() {
2396 print 'var T_messages = new Object();
2399 if (T_messages[msg]) {
2400 return T_messages[msg];
2406 function ngettext(msg1, msg2, n) {
2407 return __((parseInt(n) > 1) ? msg2 : msg1);
2410 $l10n = _get_reader();
2412 for ($i = 0; $i < $l10n->total
; $i++
) {
2413 $orig = $l10n->get_original_string($i);
2414 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2415 $key = explode(chr(0), $orig);
2416 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2417 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2419 $translation = __($orig);
2420 print T_js_decl($orig, $translation);
2425 function label_to_feed_id($label) {
2426 return LABEL_BASE_INDEX
- 1 - abs($label);
2429 function feed_to_label_id($feed) {
2430 return LABEL_BASE_INDEX
- 1 +
abs($feed);
2433 function get_theme_path($theme) {
2434 $check = "themes/$theme";
2435 if (file_exists($check)) return $check;
2437 $check = "themes.local/$theme";
2438 if (file_exists($check)) return $check;
2441 function theme_valid($theme) {
2442 $bundled_themes = [ "default.php", "night.css", "compact.css" ];
2444 if (in_array($theme, $bundled_themes)) return true;
2446 $file = "themes/" . basename($theme);
2448 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2450 if (file_exists($file) && is_readable($file)) {
2451 $fh = fopen($file, "r");
2454 $header = fgets($fh);
2457 return strpos($header, "supports-version:" . VERSION_STATIC
) !== FALSE;
2464 function error_json($code) {
2465 require_once "errors.php";
2467 @$message = $ERRORS[$code];
2469 return json_encode(array("error" =>
2470 array("code" => $code, "message" => $message)));
2474 function abs_to_rel_path($dir) {
2475 $tmp = str_replace(dirname(__DIR__
), "", $dir);
2477 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
2482 function get_upload_error_message($code) {
2485 0 => __('There is no error, the file uploaded with success'),
2486 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
2487 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
2488 3 => __('The uploaded file was only partially uploaded'),
2489 4 => __('No file was uploaded'),
2490 6 => __('Missing a temporary folder'),
2491 7 => __('Failed to write file to disk.'),
2492 8 => __('A PHP extension stopped the file upload.'),
2495 return $errors[$code];
2498 function base64_img($filename) {
2499 if (file_exists($filename)) {
2500 $ext = pathinfo($filename, PATHINFO_EXTENSION
);
2502 return "data:image/$ext;base64," . base64_encode(file_get_contents($filename));