]>
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
;
50 function get_hotkeys_info() {
52 __("Navigation") => array(
53 "next_feed" => __("Open next feed"),
54 "prev_feed" => __("Open previous feed"),
55 "next_article" => __("Open next article"),
56 "prev_article" => __("Open previous article"),
57 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
58 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
59 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
60 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
61 "search_dialog" => __("Show search dialog")),
62 __("Article") => array(
63 "toggle_mark" => __("Toggle starred"),
64 "toggle_publ" => __("Toggle published"),
65 "toggle_unread" => __("Toggle unread"),
66 "edit_tags" => __("Edit tags"),
67 "dismiss_selected" => __("Dismiss selected"),
68 "dismiss_read" => __("Dismiss read"),
69 "open_in_new_window" => __("Open in new window"),
70 "catchup_below" => __("Mark below as read"),
71 "catchup_above" => __("Mark above as read"),
72 "article_scroll_down" => __("Scroll down"),
73 "article_scroll_up" => __("Scroll up"),
74 "select_article_cursor" => __("Select article under cursor"),
75 "email_article" => __("Email article"),
76 "close_article" => __("Close/collapse article"),
77 "toggle_expand" => __("Toggle article expansion (combined mode)"),
78 "toggle_widescreen" => __("Toggle widescreen mode"),
79 "toggle_embed_original" => __("Toggle embed original")),
80 __("Article selection") => array(
81 "select_all" => __("Select all articles"),
82 "select_unread" => __("Select unread"),
83 "select_marked" => __("Select starred"),
84 "select_published" => __("Select published"),
85 "select_invert" => __("Invert selection"),
86 "select_none" => __("Deselect everything")),
88 "feed_refresh" => __("Refresh current feed"),
89 "feed_unhide_read" => __("Un/hide read feeds"),
90 "feed_subscribe" => __("Subscribe to feed"),
91 "feed_edit" => __("Edit feed"),
92 "feed_catchup" => __("Mark as read"),
93 "feed_reverse" => __("Reverse headlines"),
94 "feed_debug_update" => __("Debug feed update"),
95 "feed_debug_viewfeed" => __("Debug viewfeed()"),
96 "catchup_all" => __("Mark all feeds as read"),
97 "cat_toggle_collapse" => __("Un/collapse current category"),
98 "toggle_combined_mode" => __("Toggle combined mode"),
99 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
100 __("Go to") => array(
101 "goto_all" => __("All articles"),
102 "goto_fresh" => __("Fresh"),
103 "goto_marked" => __("Starred"),
104 "goto_published" => __("Published"),
105 "goto_tagcloud" => __("Tag cloud"),
106 "goto_prefs" => __("Preferences")),
107 __("Other") => array(
108 "create_label" => __("Create label"),
109 "create_filter" => __("Create filter"),
110 "collapse_sidebar" => __("Un/collapse sidebar"),
111 "help_dialog" => __("Show help dialog"))
114 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_INFO
) as $plugin) {
115 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
121 function get_hotkeys_map() {
123 // "navigation" => array(
126 "n" => "next_article",
127 "p" => "prev_article",
128 "(38)|up" => "prev_article",
129 "(40)|down" => "next_article",
130 // "^(38)|Ctrl-up" => "prev_article_noscroll",
131 // "^(40)|Ctrl-down" => "next_article_noscroll",
132 "(191)|/" => "search_dialog",
133 // "article" => array(
134 "s" => "toggle_mark",
135 "*s" => "toggle_publ",
136 "u" => "toggle_unread",
138 "*d" => "dismiss_selected",
139 "*x" => "dismiss_read",
140 "o" => "open_in_new_window",
141 "c p" => "catchup_below",
142 "c n" => "catchup_above",
143 "*n" => "article_scroll_down",
144 "*p" => "article_scroll_up",
145 "*(38)|Shift+up" => "article_scroll_up",
146 "*(40)|Shift+down" => "article_scroll_down",
147 "a *w" => "toggle_widescreen",
148 "a e" => "toggle_embed_original",
149 "e" => "email_article",
150 "a q" => "close_article",
151 // "article_selection" => array(
152 "a a" => "select_all",
153 "a u" => "select_unread",
154 "a *u" => "select_marked",
155 "a p" => "select_published",
156 "a i" => "select_invert",
157 "a n" => "select_none",
159 "f r" => "feed_refresh",
160 "f a" => "feed_unhide_read",
161 "f s" => "feed_subscribe",
162 "f e" => "feed_edit",
163 "f q" => "feed_catchup",
164 "f x" => "feed_reverse",
165 "f *d" => "feed_debug_update",
166 "f *g" => "feed_debug_viewfeed",
167 "f *c" => "toggle_combined_mode",
168 "f c" => "toggle_cdm_expanded",
169 "*q" => "catchup_all",
170 "x" => "cat_toggle_collapse",
173 "g f" => "goto_fresh",
174 "g s" => "goto_marked",
175 "g p" => "goto_published",
176 "g t" => "goto_tagcloud",
177 "g *p" => "goto_prefs",
179 "(9)|Tab" => "select_article_cursor", // tab
180 "c l" => "create_label",
181 "c f" => "create_filter",
182 "c s" => "collapse_sidebar",
183 "^(191)|Ctrl+/" => "help_dialog",
186 if (get_pref('COMBINED_DISPLAY_MODE')) {
187 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
188 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
191 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_MAP
) as $plugin) {
192 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
197 foreach (array_keys($hotkeys) as $hotkey) {
198 $pair = explode(" ", $hotkey, 2);
200 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
201 array_push($prefixes, $pair[0]);
205 return array($prefixes, $hotkeys);
208 function check_for_update() {
209 if (defined("GIT_VERSION_TIMESTAMP")) {
210 $content = @fetch_file_contents
("http://tt-rss.org/version.json");
213 $content = json_decode($content, true);
215 if ($content && isset($content["changeset"])) {
216 if ((int)GIT_VERSION_TIMESTAMP
< (int)$content["changeset"]["timestamp"] &&
217 GIT_VERSION_HEAD
!= $content["changeset"]["id"]) {
219 return $content["changeset"]["id"];
228 function make_runtime_info() {
231 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
232 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
234 $max_feed_id = db_fetch_result($result, 0, "mid");
235 $num_feeds = db_fetch_result($result, 0, "nf");
237 $data["max_feed_id"] = (int) $max_feed_id;
238 $data["num_feeds"] = (int) $num_feeds;
240 $data['last_article_id'] = getLastArticleId();
241 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
243 $data['dep_ts'] = calculate_dep_timestamp();
244 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
247 if (CHECK_FOR_UPDATES
&& $_SESSION["last_version_check"] +
86400 +
rand(-1000, 1000) < time()) {
248 $update_result = @check_for_update
();
250 $data["update_result"] = $update_result;
252 $_SESSION["last_version_check"] = time();
255 if (file_exists(LOCK_DIRECTORY
. "/update_daemon.lock")) {
257 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
259 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
261 $stamp = (int) @file_get_contents
(LOCK_DIRECTORY
. "/update_daemon.stamp");
264 $stamp_delta = time() - $stamp;
266 if ($stamp_delta > 1800) {
270 $_SESSION["daemon_stamp_check"] = time();
273 $data['daemon_stamp_ok'] = $stamp_check;
275 $stamp_fmt = date("Y.m.d, G:i", $stamp);
277 $data['daemon_stamp'] = $stamp_fmt;
285 function search_to_sql($search, $search_language) {
287 $keywords = str_getcsv(trim($search), " ");
288 $query_keywords = array();
289 $search_words = array();
290 $search_query_leftover = array();
292 if ($search_language)
293 $search_language = db_escape_string(mb_strtolower($search_language));
295 $search_language = "english";
297 foreach ($keywords as $k) {
298 if (strpos($k, "-") === 0) {
305 $commandpair = explode(":", mb_strtolower($k), 2);
307 switch ($commandpair[0]) {
309 if ($commandpair[1]) {
310 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
311 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
313 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
314 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
315 array_push($search_words, $k);
319 if ($commandpair[1]) {
320 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
321 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
323 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
324 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
325 array_push($search_words, $k);
329 if ($commandpair[1]) {
330 if ($commandpair[1] == "true")
331 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
332 else if ($commandpair[1] == "false")
333 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
335 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
336 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
338 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
339 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
340 if (!$not) array_push($search_words, $k);
345 if ($commandpair[1]) {
346 if ($commandpair[1] == "true")
347 array_push($query_keywords, "($not (marked = true))");
349 array_push($query_keywords, "($not (marked = false))");
351 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
352 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
353 if (!$not) array_push($search_words, $k);
357 if ($commandpair[1]) {
358 if ($commandpair[1] == "true")
359 array_push($query_keywords, "($not (published = true))");
361 array_push($query_keywords, "($not (published = false))");
364 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
365 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
366 if (!$not) array_push($search_words, $k);
370 if ($commandpair[1]) {
371 if ($commandpair[1] == "true")
372 array_push($query_keywords, "($not (unread = true))");
374 array_push($query_keywords, "($not (unread = false))");
377 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
378 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
379 if (!$not) array_push($search_words, $k);
383 if (strpos($k, "@") === 0) {
385 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
386 $orig_ts = strtotime(substr($k, 1));
387 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
389 //$k = date("Y-m-d", strtotime(substr($k, 1)));
391 array_push($query_keywords, "(".SUBSTRING_FOR_DATE
."(updated,1,LENGTH('$k')) $not = '$k')");
394 if (DB_TYPE
== "pgsql") {
395 $k = mb_strtolower($k);
396 array_push($search_query_leftover, $not ?
"!$k" : $k);
398 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
399 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
402 if (!$not) array_push($search_words, $k);
407 if (count($search_query_leftover) > 0) {
408 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
410 if (DB_TYPE
== "pgsql") {
411 array_push($query_keywords,
412 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
417 $search_query_part = implode("AND", $query_keywords);
419 return array($search_query_part, $search_words);
422 function getParentCategories($cat, $owner_uid) {
425 $result = db_query("SELECT parent_cat FROM ttrss_feed_categories
426 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
428 while ($line = db_fetch_assoc($result)) {
429 array_push($rv, $line["parent_cat"]);
430 $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid));
436 function getChildCategories($cat, $owner_uid) {
439 $result = db_query("SELECT id FROM ttrss_feed_categories
440 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
442 while ($line = db_fetch_assoc($result)) {
443 array_push($rv, $line["id"]);
444 $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid));
450 function queryFeedHeadlines($params) {
452 $feed = $params["feed"];
453 $limit = isset($params["limit"]) ?
$params["limit"] : 30;
454 $view_mode = $params["view_mode"];
455 $cat_view = isset($params["cat_view"]) ?
$params["cat_view"] : false;
456 $search = isset($params["search"]) ?
$params["search"] : false;
457 $search_language = isset($params["search_language"]) ?
$params["search_language"] : "";
458 $override_order = isset($params["override_order"]) ?
$params["override_order"] : false;
459 $offset = isset($params["offset"]) ?
$params["offset"] : 0;
460 $owner_uid = isset($params["owner_uid"]) ?
$params["owner_uid"] : $_SESSION["uid"];
461 $since_id = isset($params["since_id"]) ?
$params["since_id"] : 0;
462 $include_children = isset($params["include_children"]) ?
$params["include_children"] : false;
463 $ignore_vfeed_group = isset($params["ignore_vfeed_group"]) ?
$params["ignore_vfeed_group"] : false;
464 $override_strategy = isset($params["override_strategy"]) ?
$params["override_strategy"] : false;
465 $override_vfeed = isset($params["override_vfeed"]) ?
$params["override_vfeed"] : false;
466 $start_ts = isset($params["start_ts"]) ?
$params["start_ts"] : false;
467 $check_first_id = isset($params["check_first_id"]) ?
$params["check_first_id"] : false;
468 $skip_first_id_check = isset($params["skip_first_id_check"]) ?
$params["skip_first_id_check"] : false;
470 $ext_tables_part = "";
471 $query_strategy_part = "";
473 $search_words = array();
476 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SEARCH
) as $plugin) {
477 list($search_query_part, $search_words) = $plugin->hook_search($search);
481 // fall back in case of no plugins
482 if (!$search_query_part) {
483 list($search_query_part, $search_words) = search_to_sql($search, $search_language);
485 $search_query_part .= " AND ";
487 $search_query_part = "";
491 $since_id_part = "ttrss_entries.id > $since_id AND ";
496 $view_query_part = "";
498 if ($view_mode == "adaptive") {
500 $view_query_part = " ";
501 } else if ($feed != -1) {
503 $unread = getFeedUnread($feed, $cat_view);
505 if ($cat_view && $feed > 0 && $include_children)
506 $unread +
= getCategoryChildrenUnread($feed);
509 $view_query_part = " unread = true AND ";
514 if ($view_mode == "marked") {
515 $view_query_part = " marked = true AND ";
518 if ($view_mode == "has_note") {
519 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
522 if ($view_mode == "published") {
523 $view_query_part = " published = true AND ";
526 if ($view_mode == "unread" && $feed != -6) {
527 $view_query_part = " unread = true AND ";
531 $limit_query_part = "LIMIT " . $limit;
534 $allow_archived = false;
536 $vfeed_query_part = "";
539 if (!is_numeric($feed)) {
540 $query_strategy_part = "true";
541 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
542 id = feed_id) as feed_title,";
543 } else if ($feed > 0) {
548 if ($include_children) {
550 $subcats = getChildCategories($feed, $owner_uid);
552 array_push($subcats, $feed);
553 $query_strategy_part = "cat_id IN (".
554 implode(",", $subcats).")";
557 $query_strategy_part = "cat_id = '$feed'";
561 $query_strategy_part = "cat_id IS NULL";
564 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
567 $query_strategy_part = "feed_id = '$feed'";
569 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
570 $query_strategy_part = "feed_id IS NULL";
571 $allow_archived = true;
572 } else if ($feed == 0 && $cat_view) { // uncategorized
573 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
574 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
575 } else if ($feed == -1) { // starred virtual feed
576 $query_strategy_part = "marked = true";
577 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
578 $allow_archived = true;
580 if (!$override_order) {
581 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
584 } else if ($feed == -2) { // published virtual feed OR labels category
587 $query_strategy_part = "published = true";
588 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
589 $allow_archived = true;
591 if (!$override_order) {
592 $override_order = "last_published DESC, date_entered DESC, updated DESC";
596 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
598 $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,";
600 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
601 ttrss_user_labels2.article_id = ref_id";
604 } else if ($feed == -6) { // recently read
605 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
607 if (DB_TYPE
== "pgsql") {
608 $query_strategy_part .= " AND last_read > NOW() - INTERVAL '1 DAY' ";
610 $query_strategy_part .= " AND last_read > DATE_SUB(NOW(), INTERVAL 1 DAY) ";
613 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
614 $allow_archived = true;
615 $ignore_vfeed_group = true;
617 if (!$override_order) $override_order = "last_read DESC";
619 } else if ($feed == -3) { // fresh virtual feed
620 $query_strategy_part = "unread = true AND score >= 0";
622 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
624 if (DB_TYPE
== "pgsql") {
625 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
627 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
630 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
631 } else if ($feed == -4) { // all articles virtual feed
632 $allow_archived = true;
633 $query_strategy_part = "true";
634 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
635 } else if ($feed <= LABEL_BASE_INDEX
) { // labels
636 $label_id = feed_to_label_id($feed);
638 $query_strategy_part = "label_id = '$label_id' AND
639 ttrss_labels2.id = ttrss_user_labels2.label_id AND
640 ttrss_user_labels2.article_id = ref_id";
642 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
643 $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,";
644 $allow_archived = true;
647 $query_strategy_part = "true";
650 $order_by = "score DESC, date_entered DESC, updated DESC";
652 if ($override_order) {
653 $order_by = $override_order;
656 if ($override_strategy) {
657 $query_strategy_part = $override_strategy;
660 if ($override_vfeed) {
661 $vfeed_query_part = $override_vfeed;
667 $feed_title = T_sprintf("Search results: %s", $search);
670 $feed_title = getCategoryTitle($feed);
672 if (is_numeric($feed) && $feed > 0) {
673 $result = db_query("SELECT title,site_url,last_error,last_updated
674 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
676 $feed_title = db_fetch_result($result, 0, "title");
677 $feed_site_url = db_fetch_result($result, 0, "site_url");
678 $last_error = db_fetch_result($result, 0, "last_error");
679 $last_updated = db_fetch_result($result, 0, "last_updated");
681 $feed_title = getFeedTitle($feed);
687 $content_query_part = "content, ";
689 if ($limit_query_part) {
690 $offset_query_part = "OFFSET $offset";
692 $offset_query_part = "";
695 if (is_numeric($feed)) {
696 // proper override_order applied above
697 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
698 if (!$override_order) {
699 $order_by = "ttrss_feeds.title, $order_by";
701 $order_by = "ttrss_feeds.title, $override_order";
705 if (!$allow_archived) {
706 $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id),ttrss_feeds";
707 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
710 $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id)
711 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
714 if ($vfeed_query_part) $vfeed_query_part .= "favicon_avg_color,";
717 $start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts));
718 $start_ts_query_part = "date_entered >= '$start_ts_formatted' AND";
720 $start_ts_query_part = "";
724 $first_id_query_strategy_part = $query_strategy_part;
727 $first_id_query_strategy_part = "true";
729 if (DB_TYPE
== "pgsql") {
730 $sanity_interval_qpart = "date_entered >= NOW() - INTERVAL '1 hour' AND";
732 $sanity_interval_qpart = "date_entered >= DATE_SUB(NOW(), INTERVAL 1 hour) AND";
735 if (!$search && !$skip_first_id_check) {
736 // if previous topmost article id changed that means our current pagination is no longer valid
737 $query = "SELECT DISTINCT
754 ttrss_user_entries.owner_uid = '$owner_uid' AND
758 $sanity_interval_qpart
759 $first_id_query_strategy_part ORDER BY $order_by LIMIT 1";
761 if ($_REQUEST["debug"]) {
765 $result = db_query($query);
766 if ($result && db_num_rows($result) > 0) {
767 $first_id = (int)db_fetch_result($result, 0, "id");
769 if ($offset > 0 && $first_id && $check_first_id && $first_id != $check_first_id) {
770 return array(-1, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
775 $query = "SELECT DISTINCT
778 ttrss_entries.id,ttrss_entries.title,
782 always_display_enclosures,
791 unread,feed_id,marked,published,link,last_read,orig_feed_id,
792 last_marked, last_published,
800 ttrss_user_entries.owner_uid = '$owner_uid' AND
805 $query_strategy_part ORDER BY $order_by
806 $limit_query_part $offset_query_part";
808 if ($_REQUEST["debug"]) print $query;
810 $result = db_query($query);
815 $query = "SELECT DISTINCT
819 ttrss_entries.id as id,
835 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images,
836 last_marked, last_published,
841 FROM ttrss_entries, ttrss_user_entries, ttrss_tags
843 ref_id = ttrss_entries.id AND
844 ttrss_user_entries.owner_uid = $owner_uid AND
845 post_int_id = int_id AND
846 tag_name = '$feed' AND
849 $query_strategy_part ORDER BY $order_by
850 $limit_query_part $offset_query_part";
852 if ($_REQUEST["debug"]) print $query;
854 $result = db_query($query);
857 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
861 function iframe_whitelisted($entry) {
862 $whitelist = array("youtube.com", "youtu.be", "vimeo.com", "player.vimeo.com");
864 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST
);
867 foreach ($whitelist as $w) {
868 if ($src == $w ||
$src == "www.$w")
876 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
877 if (!$owner) $owner = $_SESSION["uid"];
879 $res = trim($str); if (!$res) return '';
881 $charset_hack = '<head>
882 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
885 $res = trim($res); if (!$res) return '';
887 libxml_use_internal_errors(true);
889 $doc = new DOMDocument();
890 $doc->loadHTML($charset_hack . $res);
891 $xpath = new DOMXPath($doc);
893 $entries = $xpath->query('(//a[@href]|//img[@src])');
895 foreach ($entries as $entry) {
899 if ($entry->hasAttribute('href')) {
900 $entry->setAttribute('href',
901 rewrite_relative_url($site_url, $entry->getAttribute('href')));
903 $entry->setAttribute('rel', 'noreferrer');
906 if ($entry->hasAttribute('src')) {
907 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
909 $cached_filename = CACHE_DIR
. '/images/' . sha1($src) . '.png';
911 if (file_exists($cached_filename)) {
912 $src = SELF_URL_PATH
. '/public.php?op=cached_image&hash=' . sha1($src);
915 $entry->setAttribute('src', $src);
918 if ($entry->nodeName
== 'img') {
919 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
920 $force_remove_images ||
$_SESSION["bw_limit"]) {
922 $p = $doc->createElement('p');
924 $a = $doc->createElement('a');
925 $a->setAttribute('href', $entry->getAttribute('src'));
927 $a->appendChild(new DOMText($entry->getAttribute('src')));
928 $a->setAttribute('target', '_blank');
932 $entry->parentNode
->replaceChild($p, $entry);
937 if (strtolower($entry->nodeName
) == "a") {
938 $entry->setAttribute("target", "_blank");
942 $entries = $xpath->query('//iframe');
943 foreach ($entries as $entry) {
944 if (!iframe_whitelisted($entry)) {
945 $entry->setAttribute('sandbox', 'allow-scripts');
947 if ($_SERVER['HTTPS'] == "on") {
948 $entry->setAttribute("src",
949 str_replace("http://", "https://",
950 $entry->getAttribute("src")));
955 $allowed_elements = array('a', 'address', 'audio', 'article', 'aside',
956 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
957 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
958 'data', 'dd', 'del', 'details', 'div', 'dl', 'font',
959 'dt', 'em', 'footer', 'figure', 'figcaption',
960 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
961 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
962 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
963 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
964 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
965 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
967 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
969 $disallowed_attributes = array('id', 'style', 'class');
971 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SANITIZE
) as $plugin) {
972 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
973 if (is_array($retval)) {
975 $allowed_elements = $retval[1];
976 $disallowed_attributes = $retval[2];
982 $doc->removeChild($doc->firstChild
); //remove doctype
983 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
985 if ($highlight_words) {
986 foreach ($highlight_words as $word) {
988 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
990 $elements = $xpath->query("//*/text()");
992 foreach ($elements as $child) {
994 $fragment = $doc->createDocumentFragment();
995 $text = $child->textContent
;
997 while (($pos = mb_stripos($text, $word)) !== false) {
998 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
999 $word = mb_substr($text, $pos, mb_strlen($word));
1000 $highlight = $doc->createElement('span');
1001 $highlight->appendChild(new DomText($word));
1002 $highlight->setAttribute('class', 'highlight');
1003 $fragment->appendChild($highlight);
1004 $text = mb_substr($text, $pos +
mb_strlen($word));
1007 if (!empty($text)) $fragment->appendChild(new DomText($text));
1009 $child->parentNode
->replaceChild($fragment, $child);
1014 $res = $doc->saveHTML();
1019 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1020 $xpath = new DOMXPath($doc);
1021 $entries = $xpath->query('//*');
1023 foreach ($entries as $entry) {
1024 if (!in_array($entry->nodeName
, $allowed_elements)) {
1025 $entry->parentNode
->removeChild($entry);
1028 if ($entry->hasAttributes()) {
1029 $attrs_to_remove = array();
1031 foreach ($entry->attributes
as $attr) {
1033 if (strpos($attr->nodeName
, 'on') === 0) {
1034 array_push($attrs_to_remove, $attr);
1037 if (in_array($attr->nodeName
, $disallowed_attributes)) {
1038 array_push($attrs_to_remove, $attr);
1042 foreach ($attrs_to_remove as $attr) {
1043 $entry->removeAttributeNode($attr);
1051 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
1053 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1054 if (count($ids) == 0) return;
1058 foreach ($ids as $id) {
1059 array_push($tmp_ids, "ref_id = '$id'");
1062 $ids_qpart = join(" OR ", $tmp_ids);
1065 db_query("UPDATE ttrss_user_entries SET
1066 unread = false,last_read = NOW()
1067 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1068 } else if ($cmode == 1) {
1069 db_query("UPDATE ttrss_user_entries SET
1071 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1073 db_query("UPDATE ttrss_user_entries SET
1074 unread = NOT unread,last_read = NOW()
1075 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1080 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
1081 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1083 while ($line = db_fetch_assoc($result)) {
1084 ccache_update($line["feed_id"], $owner_uid);
1088 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
1090 $a_id = db_escape_string($id);
1092 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1094 $query = "SELECT DISTINCT tag_name,
1095 owner_uid as owner FROM
1096 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
1097 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
1101 /* check cache first */
1103 if ($tag_cache === false) {
1104 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
1105 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1107 $tag_cache = db_fetch_result($result, 0, "tag_cache");
1111 $tags = explode(",", $tag_cache);
1114 /* do it the hard way */
1116 $tmp_result = db_query($query);
1118 while ($tmp_line = db_fetch_assoc($tmp_result)) {
1119 array_push($tags, $tmp_line["tag_name"]);
1122 /* update the cache */
1124 $tags_str = db_escape_string(join(",", $tags));
1126 db_query("UPDATE ttrss_user_entries
1127 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
1128 AND owner_uid = $owner_uid");
1134 function trim_array($array) {
1136 array_walk($tmp, 'trim');
1140 function tag_is_valid($tag) {
1141 if ($tag == '') return false;
1142 if (preg_match("/^[0-9]*$/", $tag)) return false;
1143 if (mb_strlen($tag) > 250) return false;
1145 if (!$tag) return false;
1150 function render_login_form() {
1151 header('Cache-Control: public');
1153 require_once "login_form.php";
1157 function format_warning($msg, $id = "") {
1158 return "<div class=\"alert\" id=\"$id\">$msg</div>";
1161 function format_notice($msg, $id = "") {
1162 return "<div class=\"alert alert-info\" id=\"$id\">$msg</div>";
1165 function format_error($msg, $id = "") {
1166 return "<div class=\"alert alert-danger\" id=\"$id\">$msg</div>";
1169 function print_notice($msg) {
1170 return print format_notice($msg);
1173 function print_warning($msg) {
1174 return print format_warning($msg);
1177 function print_error($msg) {
1178 return print format_error($msg);
1182 function T_sprintf() {
1183 $args = func_get_args();
1184 return vsprintf(__(array_shift($args)), $args);
1187 function format_inline_player($url, $ctype) {
1191 $url = htmlspecialchars($url);
1193 if (strpos($ctype, "audio/") === 0) {
1195 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1196 $_SESSION["hasMp3"])) {
1198 $entry .= "<audio preload=\"none\" controls>
1199 <source type=\"$ctype\" src=\"$url\"/>
1204 $entry .= "<object type=\"application/x-shockwave-flash\"
1205 data=\"lib/button/musicplayer.swf?song_url=$url\"
1206 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1207 <param name=\"movie\"
1208 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1212 if ($entry) $entry .= " <a target=\"_blank\"
1213 href=\"$url\">" . basename($url) . "</a>";
1221 /* $filename = substr($url, strrpos($url, "/")+1);
1223 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1224 $filename . " (" . $ctype . ")" . "</a>"; */
1228 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1229 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1235 /* we can figure out feed_id from article id anyway, why do we
1236 * pass feed_id here? let's ignore the argument :(*/
1238 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1239 WHERE ref_id = '$id'");
1241 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1243 $rv['feed_id'] = $feed_id;
1245 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1247 if ($mark_as_read) {
1248 $result = db_query("UPDATE ttrss_user_entries
1249 SET unread = false,last_read = NOW()
1250 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1252 ccache_update($feed_id, $owner_uid);
1255 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1256 ".SUBSTRING_FOR_DATE
."(updated,1,16) as updated,
1257 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1258 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1259 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1260 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1266 FROM ttrss_entries,ttrss_user_entries
1267 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1271 $line = db_fetch_assoc($result);
1273 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1274 unset($line["tag_cache"]);
1276 $line["content"] = sanitize($line["content"],
1277 sql_bool_to_bool($line['hide_images']),
1278 $owner_uid, $line["site_url"], false, $line["id"]);
1280 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_RENDER_ARTICLE
) as $p) {
1281 $line = $p->hook_render_article($line);
1284 $num_comments = $line["num_comments"];
1285 $entry_comments = "";
1287 if ($num_comments > 0) {
1288 if ($line["comments"]) {
1289 $comments_url = htmlspecialchars($line["comments"]);
1291 $comments_url = htmlspecialchars($line["link"]);
1293 $entry_comments = "<a class=\"postComments\"
1294 target='_blank' href=\"$comments_url\">$num_comments ".
1295 _ngettext("comment", "comments", $num_comments)."</a>";
1298 if ($line["comments"] && $line["link"] != $line["comments"]) {
1299 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
1304 header("Content-Type: text/html");
1305 $rv['content'] .= "<html><head>
1306 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1307 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1308 stylesheet_tag("css/tt-rss.css").
1309 stylesheet_tag("css/zoom.css").
1310 stylesheet_tag("css/dijit.css")."
1312 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1313 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1315 </head><body id=\"ttrssZoom\">";
1318 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1320 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1322 $entry_author = $line["author"];
1324 if ($entry_author) {
1325 $entry_author = __(" - ") . $entry_author;
1328 $parsed_updated = make_local_datetime($line["updated"], true,
1332 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1334 if ($line["link"]) {
1335 $rv['content'] .= "<div class='postTitle'><a target='_blank'
1336 title=\"".htmlspecialchars($line['title'])."\"
1338 htmlspecialchars($line["link"]) . "\">" .
1339 $line["title"] . "</a>" .
1340 "<span class='author'>$entry_author</span></div>";
1342 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1346 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
1347 "\" target=\"_blank\">".
1348 htmlspecialchars($line["feed_title"])."</a>";
1350 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1352 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1355 $tags_str = format_tags_string($line["tags"], $id);
1356 $tags_str_full = join(", ", $line["tags"]);
1358 if (!$tags_str_full) $tags_str_full = __("no tags");
1360 if (!$entry_comments) $entry_comments = " "; # placeholder
1362 $rv['content'] .= "<div class='postTags' style='float : right'>
1363 <img src='images/tag.png'
1364 class='tagsPic' alt='Tags' title='Tags'> ";
1367 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1368 <a title=\"".__('Edit tags for this article')."\"
1369 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1371 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1372 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1373 position=\"below\">$tags_str_full</div>";
1375 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_BUTTON
) as $p) {
1376 $rv['content'] .= $p->hook_article_button($line);
1380 $tags_str = strip_tags($tags_str);
1381 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1383 $rv['content'] .= "</div>";
1384 $rv['content'] .= "<div clear='both'>";
1386 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_LEFT_BUTTON
) as $p) {
1387 $rv['content'] .= $p->hook_article_left_button($line);
1390 $rv['content'] .= "$entry_comments</div>";
1392 if ($line["orig_feed_id"]) {
1394 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
1395 WHERE id = ".$line["orig_feed_id"]);
1397 if (db_num_rows($tmp_result) != 0) {
1399 $rv['content'] .= "<div clear='both'>";
1400 $rv['content'] .= __("Originally from:");
1402 $rv['content'] .= " ";
1404 $tmp_line = db_fetch_assoc($tmp_result);
1406 $rv['content'] .= "<a target='_blank'
1407 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1408 $tmp_line['title'] . "</a>";
1410 $rv['content'] .= " ";
1412 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
1413 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1415 $rv['content'] .= "</div>";
1419 $rv['content'] .= "</div>";
1421 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1422 if ($line['note']) {
1423 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1425 $rv['content'] .= "</div>";
1427 if (!$line['lang']) $line['lang'] = 'en';
1429 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1431 $rv['content'] .= $line["content"];
1434 $rv['content'] .= format_article_enclosures($id,
1435 sql_bool_to_bool($line["always_display_enclosures"]),
1437 sql_bool_to_bool($line["hide_images"]));
1440 $rv['content'] .= "</div>";
1442 $rv['content'] .= "</div>";
1448 <div class='footer'>
1449 <button onclick=\"return window.close()\">".
1450 __("Close this window")."</button></div>";
1451 $rv['content'] .= "</body></html>";
1458 function print_checkpoint($n, $s) {
1459 $ts = microtime(true);
1460 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1464 function sanitize_tag($tag) {
1467 $tag = mb_strtolower($tag, 'utf-8');
1469 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1471 if (DB_TYPE
== "mysql") {
1472 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1478 function get_self_url_prefix() {
1479 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
1480 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
1482 return SELF_URL_PATH
;
1487 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1489 * @return string The Mozilla Firefox feed adding URL.
1491 function add_feed_url() {
1492 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1494 $url_path = get_self_url_prefix() .
1495 "/public.php?op=subscribe&feed_url=%s";
1497 } // function add_feed_url
1499 function encrypt_password($pass, $salt = '', $mode2 = false) {
1500 if ($salt && $mode2) {
1501 return "MODE2:" . hash('sha256', $salt . $pass);
1503 return "SHA1X:" . sha1("$salt:$pass");
1505 return "SHA1:" . sha1($pass);
1507 } // function encrypt_password
1509 function load_filters($feed_id, $owner_uid, $action_id = false) {
1512 $cat_id = (int)getFeedCategory($feed_id);
1515 $null_cat_qpart = "cat_id IS NULL OR";
1517 $null_cat_qpart = "";
1519 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1520 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1522 $check_cats = join(",", array_merge(
1523 getParentCategories($cat_id, $owner_uid),
1526 while ($line = db_fetch_assoc($result)) {
1527 $filter_id = $line["id"];
1529 $result2 = db_query("SELECT
1530 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1531 FROM ttrss_filters2_rules AS r,
1532 ttrss_filter_types AS t
1534 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1535 (feed_id IS NULL OR feed_id = '$feed_id') AND
1536 filter_type = t.id AND filter_id = '$filter_id'");
1541 while ($rule_line = db_fetch_assoc($result2)) {
1542 # print_r($rule_line);
1545 $rule["reg_exp"] = $rule_line["reg_exp"];
1546 $rule["type"] = $rule_line["type_name"];
1547 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1549 array_push($rules, $rule);
1552 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1553 FROM ttrss_filters2_actions AS a,
1554 ttrss_filter_actions AS t
1556 action_id = t.id AND filter_id = '$filter_id'");
1558 while ($action_line = db_fetch_assoc($result2)) {
1559 # print_r($action_line);
1562 $action["type"] = $action_line["type_name"];
1563 $action["param"] = $action_line["action_param"];
1565 array_push($actions, $action);
1570 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1571 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1572 $filter["rules"] = $rules;
1573 $filter["actions"] = $actions;
1575 if (count($rules) > 0 && count($actions) > 0) {
1576 array_push($filters, $filter);
1583 function get_score_pic($score) {
1585 return "score_high.png";
1586 } else if ($score > 0) {
1587 return "score_half_high.png";
1588 } else if ($score < -100) {
1589 return "score_low.png";
1590 } else if ($score < 0) {
1591 return "score_half_low.png";
1593 return "score_neutral.png";
1597 function feed_has_icon($id) {
1598 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
1601 function init_plugins() {
1602 PluginHost
::getInstance()->load(PLUGINS
, PluginHost
::KIND_ALL
);
1607 function format_tags_string($tags, $id) {
1608 if (!is_array($tags) ||
count($tags) == 0) {
1609 return __("no tags");
1611 $maxtags = min(5, count($tags));
1614 for ($i = 0; $i < $maxtags; $i++
) {
1615 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
1618 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1620 if (count($tags) > $maxtags)
1621 $tags_str .= ", …";
1627 function format_article_labels($labels, $id) {
1629 if (!is_array($labels)) return '';
1633 foreach ($labels as $l) {
1634 $labels_str .= sprintf("<span class='hlLabelRef'
1635 style='color : %s; background-color : %s'>%s</span>",
1636 $l[2], $l[3], $l[1]);
1643 function format_article_note($id, $note, $allow_edit = true) {
1645 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1646 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1647 ($allow_edit ?
__('(edit note)') : "")."</div>$note</div>";
1653 function get_feed_category($feed_cat, $parent_cat_id = false) {
1654 if ($parent_cat_id) {
1655 $parent_qpart = "parent_cat = '$parent_cat_id'";
1656 $parent_insert = "'$parent_cat_id'";
1658 $parent_qpart = "parent_cat IS NULL";
1659 $parent_insert = "NULL";
1663 "SELECT id FROM ttrss_feed_categories
1664 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1666 if (db_num_rows($result) == 0) {
1669 return db_fetch_result($result, 0, "id");
1673 function add_feed_category($feed_cat, $parent_cat_id = false) {
1675 if (!$feed_cat) return false;
1679 if ($parent_cat_id) {
1680 $parent_qpart = "parent_cat = '$parent_cat_id'";
1681 $parent_insert = "'$parent_cat_id'";
1683 $parent_qpart = "parent_cat IS NULL";
1684 $parent_insert = "NULL";
1687 $feed_cat = mb_substr($feed_cat, 0, 250);
1690 "SELECT id FROM ttrss_feed_categories
1691 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1693 if (db_num_rows($result) == 0) {
1696 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1697 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1707 function getArticleFeed($id) {
1708 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1709 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1711 if (db_num_rows($result) != 0) {
1712 return db_fetch_result($result, 0, "feed_id");
1719 * Fixes incomplete URLs by prepending "http://".
1720 * Also replaces feed:// with http://, and
1721 * prepends a trailing slash if the url is a domain name only.
1723 * @param string $url Possibly incomplete URL
1725 * @return string Fixed URL.
1727 function fix_url($url) {
1729 // support schema-less urls
1730 if (strpos($url, '//') === 0) {
1731 $url = 'https:' . $url;
1734 if (strpos($url, '://') === false) {
1735 $url = 'http://' . $url;
1736 } else if (substr($url, 0, 5) == 'feed:') {
1737 $url = 'http:' . substr($url, 5);
1740 //prepend slash if the URL has no slash in it
1741 // "http://www.example" -> "http://www.example/"
1742 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
1746 if ($url != "http:///")
1752 function validate_feed_url($url) {
1753 $parts = parse_url($url);
1755 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
1759 function get_article_enclosures($id) {
1761 $query = "SELECT * FROM ttrss_enclosures
1762 WHERE post_id = '$id' AND content_url != ''";
1766 $result = db_query($query);
1768 if (db_num_rows($result) > 0) {
1769 while ($line = db_fetch_assoc($result)) {
1770 array_push($rv, $line);
1777 /* function save_email_address($email) {
1778 // FIXME: implement persistent storage of emails
1780 if (!$_SESSION['stored_emails'])
1781 $_SESSION['stored_emails'] = array();
1783 if (!in_array($email, $_SESSION['stored_emails']))
1784 array_push($_SESSION['stored_emails'], $email);
1788 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1790 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1792 $sql_is_cat = bool_to_sql_bool($is_cat);
1794 $result = db_query("SELECT access_key FROM ttrss_access_keys
1795 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1796 AND owner_uid = " . $owner_uid);
1798 if (db_num_rows($result) == 1) {
1799 return db_fetch_result($result, 0, "access_key");
1801 $key = db_escape_string(uniqid_short());
1803 $result = db_query("INSERT INTO ttrss_access_keys
1804 (access_key, feed_id, is_cat, owner_uid)
1805 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1812 function get_feeds_from_html($url, $content)
1814 $url = fix_url($url);
1815 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
1817 libxml_use_internal_errors(true);
1819 $doc = new DOMDocument();
1820 $doc->loadHTML($content);
1821 $xpath = new DOMXPath($doc);
1822 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
1823 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
1824 $feedUrls = array();
1825 foreach ($entries as $entry) {
1826 if ($entry->hasAttribute('href')) {
1827 $title = $entry->getAttribute('title');
1829 $title = $entry->getAttribute('type');
1831 $feedUrl = rewrite_relative_url(
1832 $baseUrl, $entry->getAttribute('href')
1834 $feedUrls[$feedUrl] = $title;
1840 function is_html($content) {
1841 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
1844 function url_is_html($url, $login = false, $pass = false) {
1845 return is_html(fetch_file_contents($url, false, $login, $pass));
1848 function print_label_select($name, $value, $attributes = "") {
1850 $result = db_query("SELECT caption FROM ttrss_labels2
1851 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1853 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1854 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
1856 while ($line = db_fetch_assoc($result)) {
1858 $issel = ($line["caption"] == $value) ?
"selected=\"1\"" : "";
1860 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1861 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1865 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1872 function format_article_enclosures($id, $always_display_enclosures,
1873 $article_content, $hide_images = false) {
1875 $result = get_article_enclosures($id);
1878 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_FORMAT_ENCLOSURES
) as $plugin) {
1879 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
1880 if (is_array($retval)) {
1882 $result = $retval[1];
1887 unset($retval); // Unset to prevent breaking render if there are no HOOK_RENDER_ENCLOSURE hooks below.
1889 if ($rv === '' && !empty($result)) {
1890 $entries_html = array();
1892 $entries_inline = array();
1894 foreach ($result as $line) {
1896 $url = $line["content_url"];
1897 $ctype = $line["content_type"];
1898 $title = $line["title"];
1899 $width = $line["width"];
1900 $height = $line["height"];
1902 if (!$ctype) $ctype = __("unknown type");
1904 $filename = substr($url, strrpos($url, "/")+
1);
1906 $player = format_inline_player($url, $ctype);
1908 if ($player) array_push($entries_inline, $player);
1910 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1911 # $filename . " (" . $ctype . ")" . "</a>";
1913 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
1914 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1916 array_push($entries_html, $entry);
1920 $entry["type"] = $ctype;
1921 $entry["filename"] = $filename;
1922 $entry["url"] = $url;
1923 $entry["title"] = $title;
1924 $entry["width"] = $width;
1925 $entry["height"] = $height;
1927 array_push($entries, $entry);
1930 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
1931 if ($always_display_enclosures ||
1932 !preg_match("/<img/i", $article_content)) {
1934 foreach ($entries as $entry) {
1936 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_RENDER_ENCLOSURE
) as $plugin)
1937 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
1944 if (preg_match("/image/", $entry["type"]) ||
1945 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
1947 if (!$hide_images) {
1949 if ($entry['height'] > 0)
1950 $encsize .= ' height="' . intval($entry['height']) . '"';
1951 if ($entry['width'] > 0)
1952 $encsize .= ' width="' . intval($entry['width']) . '"';
1954 alt=\"".htmlspecialchars($entry["filename"])."\"
1955 src=\"" .htmlspecialchars($entry["url"]) . "\"
1956 " . $encsize . " /></p>";
1958 $rv .= "<p><a target=\"_blank\"
1959 href=\"".htmlspecialchars($entry["url"])."\"
1960 >" .htmlspecialchars($entry["url"]) . "</a></p>";
1963 if ($entry['title']) {
1964 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
1972 if (count($entries_inline) > 0) {
1973 $rv .= "<hr clear='both'/>";
1974 foreach ($entries_inline as $entry) { $rv .= $entry; };
1975 $rv .= "<hr clear='both'/>";
1978 $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
1979 "<span>" . __('Attachments')."</span>";
1981 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
1983 foreach ($entries as $entry) {
1984 if ($entry["title"])
1985 $title = "— " . truncate_string($entry["title"], 30);
1989 $rv .= "<div onclick='window.open(\"".htmlspecialchars($entry["url"])."\")'
1990 dojoType=\"dijit.MenuItem\">".htmlspecialchars($entry["filename"])."$title</div>";
2001 function getLastArticleId() {
2002 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
2003 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
2005 if (db_num_rows($result) == 1) {
2006 return db_fetch_result($result, 0, "id");
2012 function build_url($parts) {
2013 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2017 * Converts a (possibly) relative URL to a absolute one.
2019 * @param string $url Base URL (i.e. from where the document is)
2020 * @param string $rel_url Possibly relative URL in the document
2022 * @return string Absolute URL
2024 function rewrite_relative_url($url, $rel_url) {
2025 if (strpos($rel_url, ":") !== false) {
2027 } else if (strpos($rel_url, "://") !== false) {
2029 } else if (strpos($rel_url, "//") === 0) {
2030 # protocol-relative URL (rare but they exist)
2032 } else if (strpos($rel_url, "/") === 0)
2034 $parts = parse_url($url);
2035 $parts['path'] = $rel_url;
2037 return build_url($parts);
2040 $parts = parse_url($url);
2041 if (!isset($parts['path'])) {
2042 $parts['path'] = '/';
2044 $dir = $parts['path'];
2045 if (substr($dir, -1) !== '/') {
2046 $dir = dirname($parts['path']);
2047 $dir !== '/' && $dir .= '/';
2049 $parts['path'] = $dir . $rel_url;
2051 return build_url($parts);
2055 function cleanup_tags($days = 14, $limit = 1000) {
2057 if (DB_TYPE
== "pgsql") {
2058 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2059 } else if (DB_TYPE
== "mysql") {
2060 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2065 while ($limit > 0) {
2068 $query = "SELECT ttrss_tags.id AS id
2069 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2070 WHERE post_int_id = int_id AND $interval_query AND
2071 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2073 $result = db_query($query);
2077 while ($line = db_fetch_assoc($result)) {
2078 array_push($ids, $line['id']);
2081 if (count($ids) > 0) {
2082 $ids = join(",", $ids);
2084 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2085 $tags_deleted +
= db_affected_rows($tmp_result);
2090 $limit -= $limit_part;
2093 return $tags_deleted;
2096 function print_user_stylesheet() {
2097 $value = get_pref('USER_STYLESHEET');
2100 print "<style type=\"text/css\">";
2101 print str_replace("<br/>", "\n", $value);
2107 function filter_to_sql($filter, $owner_uid) {
2110 if (DB_TYPE
== "pgsql")
2113 $reg_qpart = "REGEXP";
2115 foreach ($filter["rules"] AS $rule) {
2116 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2117 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2118 $rule['reg_exp']) !== FALSE;
2120 if ($regexp_valid) {
2122 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2124 switch ($rule["type"]) {
2126 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2127 $rule['reg_exp'] . "')";
2130 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2131 $rule['reg_exp'] . "')";
2134 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2135 $rule['reg_exp'] . "') OR LOWER(" .
2136 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2139 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2140 $rule['reg_exp'] . "')";
2143 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2144 $rule['reg_exp'] . "')";
2147 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2148 $rule['reg_exp'] . "')";
2152 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2154 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2155 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2158 if (isset($rule["cat_id"])) {
2160 if ($rule["cat_id"] > 0) {
2161 $children = getChildCategories($rule["cat_id"], $owner_uid);
2162 array_push($children, $rule["cat_id"]);
2164 $children = join(",", $children);
2166 $cat_qpart = "cat_id IN ($children)";
2168 $cat_qpart = "cat_id IS NULL";
2171 $qpart .= " AND $cat_qpart";
2174 $qpart .= " AND feed_id IS NOT NULL";
2176 array_push($query, "($qpart)");
2181 if (count($query) > 0) {
2182 $fullquery = "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
2184 $fullquery = "(false)";
2187 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2192 if (!function_exists('gzdecode')) {
2193 function gzdecode($string) { // no support for 2nd argument
2194 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2195 base64_encode($string));
2199 function get_random_bytes($length) {
2200 if (function_exists('openssl_random_pseudo_bytes')) {
2201 return openssl_random_pseudo_bytes($length);
2205 for ($i = 0; $i < $length; $i++
)
2206 $output .= chr(mt_rand(0, 255));
2212 function read_stdin() {
2213 $fp = fopen("php://stdin", "r");
2216 $line = trim(fgets($fp));
2224 function tmpdirname($path, $prefix) {
2225 // Use PHP's tmpfile function to create a temporary
2226 // directory name. Delete the file and keep the name.
2227 $tempname = tempnam($path,$prefix);
2231 if (!unlink($tempname))
2237 function getFeedCategory($feed) {
2238 $result = db_query("SELECT cat_id FROM ttrss_feeds
2239 WHERE id = '$feed'");
2241 if (db_num_rows($result) > 0) {
2242 return db_fetch_result($result, 0, "cat_id");
2249 function implements_interface($class, $interface) {
2250 return in_array($interface, class_implements($class));
2253 function get_minified_js($files) {
2254 require_once 'lib/jshrink/Minifier.php';
2258 foreach ($files as $js) {
2259 if (!isset($_GET['debug'])) {
2260 $cached_file = CACHE_DIR
. "/js/".basename($js).".js";
2262 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2264 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2266 if ($header && $contents) {
2267 list($htag, $hversion) = explode(":", $header);
2269 if ($htag == "tt-rss" && $hversion == VERSION
) {
2276 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
2277 file_put_contents($cached_file, "tt-rss:" . VERSION
. "\n" . $minified);
2281 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2288 function stylesheet_tag($filename) {
2289 $timestamp = filemtime($filename);
2291 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2294 function javascript_tag($filename) {
2297 if (!(strpos($filename, "?") === FALSE)) {
2298 $query = substr($filename, strpos($filename, "?")+
1);
2299 $filename = substr($filename, 0, strpos($filename, "?"));
2302 $timestamp = filemtime($filename);
2304 if ($query) $timestamp .= "&$query";
2306 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2309 function calculate_dep_timestamp() {
2310 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2314 foreach ($files as $file) {
2315 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2321 function T_js_decl($s1, $s2) {
2323 $s1 = preg_replace("/\n/", "", $s1);
2324 $s2 = preg_replace("/\n/", "", $s2);
2326 $s1 = preg_replace("/\"/", "\\\"", $s1);
2327 $s2 = preg_replace("/\"/", "\\\"", $s2);
2329 return "T_messages[\"$s1\"] = \"$s2\";\n";
2333 function init_js_translations() {
2335 print 'var T_messages = new Object();
2338 if (T_messages[msg]) {
2339 return T_messages[msg];
2345 function ngettext(msg1, msg2, n) {
2346 return __((parseInt(n) > 1) ? msg2 : msg1);
2349 $l10n = _get_reader();
2351 for ($i = 0; $i < $l10n->total
; $i++
) {
2352 $orig = $l10n->get_original_string($i);
2353 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2354 $key = explode(chr(0), $orig);
2355 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2356 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2358 $translation = __($orig);
2359 print T_js_decl($orig, $translation);
2364 function label_to_feed_id($label) {
2365 return LABEL_BASE_INDEX
- 1 - abs($label);
2368 function feed_to_label_id($feed) {
2369 return LABEL_BASE_INDEX
- 1 +
abs($feed);
2372 function get_theme_path($theme) {
2373 $check = "themes/$theme";
2374 if (file_exists($check)) return $check;
2376 $check = "themes.local/$theme";
2377 if (file_exists($check)) return $check;
2380 function theme_valid($theme) {
2381 if ($theme == "default.css" ||
$theme == "night.css") return true; // needed for array_filter
2382 $file = "themes/" . basename($theme);
2384 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2386 if (file_exists($file) && is_readable($file)) {
2387 $fh = fopen($file, "r");
2390 $header = fgets($fh);
2393 return strpos($header, "supports-version:" . VERSION_STATIC
) !== FALSE;
2400 function error_json($code) {
2401 require_once "errors.php";
2403 @$message = $ERRORS[$code];
2405 return json_encode(array("error" =>
2406 array("code" => $code, "message" => $message)));
2410 function abs_to_rel_path($dir) {
2411 $tmp = str_replace(dirname(__DIR__
), "", $dir);
2413 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);