]>
git.wh0rd.org - tt-rss.git/blob - include/functions2.php
b8900e78be0c9697380d9daec812afcf6ebb4ca1
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]|//audio/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'));
912 $cached_filename = CACHE_DIR
. '/images/' . sha1($src);
914 if (file_exists($cached_filename)) {
916 // this is strictly cosmetic
917 if ($entry->tagName
== 'img') {
919 } else if ($entry->parentNode
&& $entry->parentNode
->tagName
== "video") {
921 } else if ($entry->parentNode
&& $entry->parentNode
->tagName
== "audio") {
927 $src = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($src) . $suffix;
929 if ($entry->hasAttribute('srcset')) {
930 $entry->removeAttribute('srcset');
933 if ($entry->hasAttribute('sizes')) {
934 $entry->removeAttribute('sizes');
938 $entry->setAttribute('src', $src);
941 if ($entry->nodeName
== 'img') {
943 if ($entry->hasAttribute('src')) {
944 $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME
) === 'https';
946 if ($ttrss_uses_https && !$is_https_url) {
948 if ($entry->hasAttribute('srcset')) {
949 $entry->removeAttribute('srcset');
952 if ($entry->hasAttribute('sizes')) {
953 $entry->removeAttribute('sizes');
958 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
959 $force_remove_images ||
$_SESSION["bw_limit"]) {
961 $p = $doc->createElement('p');
963 $a = $doc->createElement('a');
964 $a->setAttribute('href', $entry->getAttribute('src'));
966 $a->appendChild(new DOMText($entry->getAttribute('src')));
967 $a->setAttribute('target', '_blank');
968 $a->setAttribute('rel', 'noopener noreferrer');
972 $entry->parentNode
->replaceChild($p, $entry);
976 if (strtolower($entry->nodeName
) == "a") {
977 $entry->setAttribute("target", "_blank");
978 $entry->setAttribute("rel", "noopener noreferrer");
982 $entries = $xpath->query('//iframe');
983 foreach ($entries as $entry) {
984 if (!iframe_whitelisted($entry)) {
985 $entry->setAttribute('sandbox', 'allow-scripts');
987 if ($_SERVER['HTTPS'] == "on") {
988 $entry->setAttribute("src",
989 str_replace("http://", "https://",
990 $entry->getAttribute("src")));
995 $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside',
996 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
997 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
998 'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font',
999 'dt', 'em', 'footer', 'figure', 'figcaption',
1000 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
1001 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
1002 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
1003 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
1004 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
1005 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
1007 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
1009 $disallowed_attributes = array('id', 'style', 'class');
1011 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SANITIZE
) as $plugin) {
1012 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
1013 if (is_array($retval)) {
1015 $allowed_elements = $retval[1];
1016 $disallowed_attributes = $retval[2];
1022 $doc->removeChild($doc->firstChild
); //remove doctype
1023 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
1025 if ($highlight_words) {
1026 foreach ($highlight_words as $word) {
1028 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
1030 $elements = $xpath->query("//*/text()");
1032 foreach ($elements as $child) {
1034 $fragment = $doc->createDocumentFragment();
1035 $text = $child->textContent
;
1037 while (($pos = mb_stripos($text, $word)) !== false) {
1038 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1039 $word = mb_substr($text, $pos, mb_strlen($word));
1040 $highlight = $doc->createElement('span');
1041 $highlight->appendChild(new DomText($word));
1042 $highlight->setAttribute('class', 'highlight');
1043 $fragment->appendChild($highlight);
1044 $text = mb_substr($text, $pos +
mb_strlen($word));
1047 if (!empty($text)) $fragment->appendChild(new DomText($text));
1049 $child->parentNode
->replaceChild($fragment, $child);
1054 $res = $doc->saveHTML();
1056 /* strip everything outside of <body>...</body> */
1058 $res_frag = array();
1059 if (preg_match('/<body>(.*)<\/body>/is', $res, $res_frag)) {
1060 return $res_frag[1];
1066 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1067 $xpath = new DOMXPath($doc);
1068 $entries = $xpath->query('//*');
1070 foreach ($entries as $entry) {
1071 if (!in_array($entry->nodeName
, $allowed_elements)) {
1072 $entry->parentNode
->removeChild($entry);
1075 if ($entry->hasAttributes()) {
1076 $attrs_to_remove = array();
1078 foreach ($entry->attributes
as $attr) {
1080 if (strpos($attr->nodeName
, 'on') === 0) {
1081 array_push($attrs_to_remove, $attr);
1084 if ($attr->nodeName
== 'href' && stripos($attr->value
, 'javascript:') === 0) {
1085 array_push($attrs_to_remove, $attr);
1088 if (in_array($attr->nodeName
, $disallowed_attributes)) {
1089 array_push($attrs_to_remove, $attr);
1093 foreach ($attrs_to_remove as $attr) {
1094 $entry->removeAttributeNode($attr);
1102 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
1104 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1105 if (count($ids) == 0) return;
1109 foreach ($ids as $id) {
1110 array_push($tmp_ids, "ref_id = '$id'");
1113 $ids_qpart = join(" OR ", $tmp_ids);
1116 db_query("UPDATE ttrss_user_entries SET
1117 unread = false,last_read = NOW()
1118 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1119 } else if ($cmode == 1) {
1120 db_query("UPDATE ttrss_user_entries SET
1122 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1124 db_query("UPDATE ttrss_user_entries SET
1125 unread = NOT unread,last_read = NOW()
1126 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1131 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
1132 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1134 while ($line = db_fetch_assoc($result)) {
1135 ccache_update($line["feed_id"], $owner_uid);
1139 function trim_array($array) {
1141 array_walk($tmp, 'trim');
1145 function tag_is_valid($tag) {
1146 if ($tag == '') return false;
1147 if (is_numeric($tag)) return false;
1148 if (mb_strlen($tag) > 250) return false;
1150 if (!$tag) return false;
1155 function render_login_form() {
1156 header('Cache-Control: public');
1158 require_once "login_form.php";
1162 function T_sprintf() {
1163 $args = func_get_args();
1164 return vsprintf(__(array_shift($args)), $args);
1167 function print_checkpoint($n, $s) {
1168 $ts = microtime(true);
1169 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1173 function sanitize_tag($tag) {
1176 $tag = mb_strtolower($tag, 'utf-8');
1178 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1180 if (DB_TYPE
== "mysql") {
1181 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1187 function get_self_url_prefix() {
1188 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
1189 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
1191 return SELF_URL_PATH
;
1196 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1198 * @return string The Mozilla Firefox feed adding URL.
1200 function add_feed_url() {
1201 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1203 $url_path = get_self_url_prefix() .
1204 "/public.php?op=subscribe&feed_url=%s";
1206 } // function add_feed_url
1208 function encrypt_password($pass, $salt = '', $mode2 = false) {
1209 if ($salt && $mode2) {
1210 return "MODE2:" . hash('sha256', $salt . $pass);
1212 return "SHA1X:" . sha1("$salt:$pass");
1214 return "SHA1:" . sha1($pass);
1216 } // function encrypt_password
1218 function load_filters($feed_id, $owner_uid) {
1221 $cat_id = (int)getFeedCategory($feed_id);
1224 $null_cat_qpart = "cat_id IS NULL OR";
1226 $null_cat_qpart = "";
1228 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1229 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1231 $check_cats = join(",", array_merge(
1232 getParentCategories($cat_id, $owner_uid),
1235 while ($line = db_fetch_assoc($result)) {
1236 $filter_id = $line["id"];
1238 $result2 = db_query("SELECT
1239 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1240 FROM ttrss_filters2_rules AS r,
1241 ttrss_filter_types AS t
1243 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1244 (feed_id IS NULL OR feed_id = '$feed_id') AND
1245 filter_type = t.id AND filter_id = '$filter_id'");
1250 while ($rule_line = db_fetch_assoc($result2)) {
1251 # print_r($rule_line);
1254 $rule["reg_exp"] = $rule_line["reg_exp"];
1255 $rule["type"] = $rule_line["type_name"];
1256 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1258 array_push($rules, $rule);
1261 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1262 FROM ttrss_filters2_actions AS a,
1263 ttrss_filter_actions AS t
1265 action_id = t.id AND filter_id = '$filter_id'");
1267 while ($action_line = db_fetch_assoc($result2)) {
1268 # print_r($action_line);
1271 $action["type"] = $action_line["type_name"];
1272 $action["param"] = $action_line["action_param"];
1274 array_push($actions, $action);
1279 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1280 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1281 $filter["rules"] = $rules;
1282 $filter["actions"] = $actions;
1284 if (count($rules) > 0 && count($actions) > 0) {
1285 array_push($filters, $filter);
1292 function get_score_pic($score) {
1294 return "score_high.png";
1295 } else if ($score > 0) {
1296 return "score_half_high.png";
1297 } else if ($score < -100) {
1298 return "score_low.png";
1299 } else if ($score < 0) {
1300 return "score_half_low.png";
1302 return "score_neutral.png";
1306 function feed_has_icon($id) {
1307 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
1310 function init_plugins() {
1311 PluginHost
::getInstance()->load(PLUGINS
, PluginHost
::KIND_ALL
);
1316 function add_feed_category($feed_cat, $parent_cat_id = false) {
1318 if (!$feed_cat) return false;
1322 if ($parent_cat_id) {
1323 $parent_qpart = "parent_cat = '$parent_cat_id'";
1324 $parent_insert = "'$parent_cat_id'";
1326 $parent_qpart = "parent_cat IS NULL";
1327 $parent_insert = "NULL";
1330 $feed_cat = mb_substr($feed_cat, 0, 250);
1333 "SELECT id FROM ttrss_feed_categories
1334 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1336 if (db_num_rows($result) == 0) {
1339 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1340 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1351 * Fixes incomplete URLs by prepending "http://".
1352 * Also replaces feed:// with http://, and
1353 * prepends a trailing slash if the url is a domain name only.
1355 * @param string $url Possibly incomplete URL
1357 * @return string Fixed URL.
1359 function fix_url($url) {
1361 // support schema-less urls
1362 if (strpos($url, '//') === 0) {
1363 $url = 'https:' . $url;
1366 if (strpos($url, '://') === false) {
1367 $url = 'http://' . $url;
1368 } else if (substr($url, 0, 5) == 'feed:') {
1369 $url = 'http:' . substr($url, 5);
1372 //prepend slash if the URL has no slash in it
1373 // "http://www.example" -> "http://www.example/"
1374 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
1378 //convert IDNA hostname to punycode if possible
1379 if (function_exists("idn_to_ascii")) {
1380 $parts = parse_url($url);
1381 if (mb_detect_encoding($parts['host']) != 'ASCII')
1383 $parts['host'] = idn_to_ascii($parts['host']);
1384 $url = build_url($parts);
1388 if ($url != "http:///")
1394 function validate_feed_url($url) {
1395 $parts = parse_url($url);
1397 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
1401 /* function save_email_address($email) {
1402 // FIXME: implement persistent storage of emails
1404 if (!$_SESSION['stored_emails'])
1405 $_SESSION['stored_emails'] = array();
1407 if (!in_array($email, $_SESSION['stored_emails']))
1408 array_push($_SESSION['stored_emails'], $email);
1412 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1414 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1416 $sql_is_cat = bool_to_sql_bool($is_cat);
1418 $result = db_query("SELECT access_key FROM ttrss_access_keys
1419 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1420 AND owner_uid = " . $owner_uid);
1422 if (db_num_rows($result) == 1) {
1423 return db_fetch_result($result, 0, "access_key");
1425 $key = db_escape_string(uniqid_short());
1427 $result = db_query("INSERT INTO ttrss_access_keys
1428 (access_key, feed_id, is_cat, owner_uid)
1429 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1436 function get_feeds_from_html($url, $content)
1438 $url = fix_url($url);
1439 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
1441 libxml_use_internal_errors(true);
1443 $doc = new DOMDocument();
1444 $doc->loadHTML($content);
1445 $xpath = new DOMXPath($doc);
1446 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
1447 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
1448 $feedUrls = array();
1449 foreach ($entries as $entry) {
1450 if ($entry->hasAttribute('href')) {
1451 $title = $entry->getAttribute('title');
1453 $title = $entry->getAttribute('type');
1455 $feedUrl = rewrite_relative_url(
1456 $baseUrl, $entry->getAttribute('href')
1458 $feedUrls[$feedUrl] = $title;
1464 function is_html($content) {
1465 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
1468 function url_is_html($url, $login = false, $pass = false) {
1469 return is_html(fetch_file_contents($url, false, $login, $pass));
1472 function getLastArticleId() {
1473 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
1474 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
1476 if (db_num_rows($result) == 1) {
1477 return db_fetch_result($result, 0, "id");
1483 function build_url($parts) {
1484 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
1487 function cleanup_url_path($path) {
1488 $path = str_replace("/./", "/", $path);
1489 $path = str_replace("//", "/", $path);
1495 * Converts a (possibly) relative URL to a absolute one.
1497 * @param string $url Base URL (i.e. from where the document is)
1498 * @param string $rel_url Possibly relative URL in the document
1500 * @return string Absolute URL
1502 function rewrite_relative_url($url, $rel_url) {
1503 if (strpos($rel_url, "://") !== false) {
1505 } else if (strpos($rel_url, "//") === 0) {
1506 # protocol-relative URL (rare but they exist)
1508 } else if (preg_match("/^[a-z]+:/i", $rel_url)) {
1509 # magnet:, feed:, etc
1511 } else if (strpos($rel_url, "/") === 0) {
1512 $parts = parse_url($url);
1513 $parts['path'] = $rel_url;
1514 $parts['path'] = cleanup_url_path($parts['path']);
1516 return build_url($parts);
1519 $parts = parse_url($url);
1520 if (!isset($parts['path'])) {
1521 $parts['path'] = '/';
1523 $dir = $parts['path'];
1524 if (substr($dir, -1) !== '/') {
1525 $dir = dirname($parts['path']);
1526 $dir !== '/' && $dir .= '/';
1528 $parts['path'] = $dir . $rel_url;
1529 $parts['path'] = cleanup_url_path($parts['path']);
1531 return build_url($parts);
1535 function cleanup_tags($days = 14, $limit = 1000) {
1537 if (DB_TYPE
== "pgsql") {
1538 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
1539 } else if (DB_TYPE
== "mysql") {
1540 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
1545 while ($limit > 0) {
1548 $query = "SELECT ttrss_tags.id AS id
1549 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
1550 WHERE post_int_id = int_id AND $interval_query AND
1551 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
1553 $result = db_query($query);
1557 while ($line = db_fetch_assoc($result)) {
1558 array_push($ids, $line['id']);
1561 if (count($ids) > 0) {
1562 $ids = join(",", $ids);
1564 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
1565 $tags_deleted +
= db_affected_rows($tmp_result);
1570 $limit -= $limit_part;
1573 return $tags_deleted;
1576 function print_user_stylesheet() {
1577 $value = get_pref('USER_STYLESHEET');
1580 print "<style type=\"text/css\">";
1581 print str_replace("<br/>", "\n", $value);
1587 function filter_to_sql($filter, $owner_uid) {
1590 if (DB_TYPE
== "pgsql")
1593 $reg_qpart = "REGEXP";
1595 foreach ($filter["rules"] AS $rule) {
1596 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
1597 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
1598 $rule['reg_exp']) !== FALSE;
1600 if ($regexp_valid) {
1602 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
1604 switch ($rule["type"]) {
1606 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
1607 $rule['reg_exp'] . "')";
1610 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
1611 $rule['reg_exp'] . "')";
1614 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
1615 $rule['reg_exp'] . "') OR LOWER(" .
1616 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
1619 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
1620 $rule['reg_exp'] . "')";
1623 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
1624 $rule['reg_exp'] . "')";
1627 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
1628 $rule['reg_exp'] . "')";
1632 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
1634 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
1635 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
1638 if (isset($rule["cat_id"])) {
1640 if ($rule["cat_id"] > 0) {
1641 $children = getChildCategories($rule["cat_id"], $owner_uid);
1642 array_push($children, $rule["cat_id"]);
1644 $children = join(",", $children);
1646 $cat_qpart = "cat_id IN ($children)";
1648 $cat_qpart = "cat_id IS NULL";
1651 $qpart .= " AND $cat_qpart";
1654 $qpart .= " AND feed_id IS NOT NULL";
1656 array_push($query, "($qpart)");
1661 if (count($query) > 0) {
1662 $fullquery = "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
1664 $fullquery = "(false)";
1667 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
1672 if (!function_exists('gzdecode')) {
1673 function gzdecode($string) { // no support for 2nd argument
1674 return file_get_contents('compress.zlib://data:who/cares;base64,'.
1675 base64_encode($string));
1679 function get_random_bytes($length) {
1680 if (function_exists('openssl_random_pseudo_bytes')) {
1681 return openssl_random_pseudo_bytes($length);
1685 for ($i = 0; $i < $length; $i++
)
1686 $output .= chr(mt_rand(0, 255));
1692 function read_stdin() {
1693 $fp = fopen("php://stdin", "r");
1696 $line = trim(fgets($fp));
1704 function getFeedCategory($feed) {
1705 $result = db_query("SELECT cat_id FROM ttrss_feeds
1706 WHERE id = '$feed'");
1708 if (db_num_rows($result) > 0) {
1709 return db_fetch_result($result, 0, "cat_id");
1716 function implements_interface($class, $interface) {
1717 return in_array($interface, class_implements($class));
1720 function get_minified_js($files) {
1721 require_once 'lib/jshrink/Minifier.php';
1725 foreach ($files as $js) {
1726 if (!isset($_GET['debug'])) {
1727 $cached_file = CACHE_DIR
. "/js/".basename($js).".js";
1729 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
1731 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
1733 if ($header && $contents) {
1734 list($htag, $hversion) = explode(":", $header);
1736 if ($htag == "tt-rss" && $hversion == VERSION
) {
1743 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
1744 file_put_contents($cached_file, "tt-rss:" . VERSION
. "\n" . $minified);
1748 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
1755 function calculate_dep_timestamp() {
1756 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
1760 foreach ($files as $file) {
1761 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
1767 function T_js_decl($s1, $s2) {
1769 $s1 = preg_replace("/\n/", "", $s1);
1770 $s2 = preg_replace("/\n/", "", $s2);
1772 $s1 = preg_replace("/\"/", "\\\"", $s1);
1773 $s2 = preg_replace("/\"/", "\\\"", $s2);
1775 return "T_messages[\"$s1\"] = \"$s2\";\n";
1779 function init_js_translations() {
1781 print 'var T_messages = new Object();
1784 if (T_messages[msg]) {
1785 return T_messages[msg];
1791 function ngettext(msg1, msg2, n) {
1792 return __((parseInt(n) > 1) ? msg2 : msg1);
1795 $l10n = _get_reader();
1797 for ($i = 0; $i < $l10n->total
; $i++
) {
1798 $orig = $l10n->get_original_string($i);
1799 if(strpos($orig, "\000") !== FALSE) { // Plural forms
1800 $key = explode(chr(0), $orig);
1801 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
1802 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
1804 $translation = __($orig);
1805 print T_js_decl($orig, $translation);
1810 function label_to_feed_id($label) {
1811 return LABEL_BASE_INDEX
- 1 - abs($label);
1814 function feed_to_label_id($feed) {
1815 return LABEL_BASE_INDEX
- 1 +
abs($feed);
1818 function get_theme_path($theme) {
1819 $check = "themes/$theme";
1820 if (file_exists($check)) return $check;
1822 $check = "themes.local/$theme";
1823 if (file_exists($check)) return $check;
1826 function theme_valid($theme) {
1827 $bundled_themes = [ "default.php", "night.css", "compact.css" ];
1829 if (in_array($theme, $bundled_themes)) return true;
1831 $file = "themes/" . basename($theme);
1833 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
1835 if (file_exists($file) && is_readable($file)) {
1836 $fh = fopen($file, "r");
1839 $header = fgets($fh);
1842 return strpos($header, "supports-version:" . VERSION_STATIC
) !== FALSE;
1850 * @SuppressWarnings(unused)
1852 function error_json($code) {
1853 require_once "errors.php";
1855 @$message = $ERRORS[$code];
1857 return json_encode(array("error" =>
1858 array("code" => $code, "message" => $message)));
1862 function abs_to_rel_path($dir) {
1863 $tmp = str_replace(dirname(__DIR__
), "", $dir);
1865 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
1870 function get_upload_error_message($code) {
1873 0 => __('There is no error, the file uploaded with success'),
1874 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
1875 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
1876 3 => __('The uploaded file was only partially uploaded'),
1877 4 => __('No file was uploaded'),
1878 6 => __('Missing a temporary folder'),
1879 7 => __('Failed to write file to disk.'),
1880 8 => __('A PHP extension stopped the file upload.'),
1883 return $errors[$code];
1886 function base64_img($filename) {
1887 if (file_exists($filename)) {
1888 $ext = pathinfo($filename, PATHINFO_EXTENSION
);
1890 return "data:image/$ext;base64," . base64_encode(file_get_contents($filename));