]>
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 "open_in_new_window" => __("Open in new window"),
68 "catchup_below" => __("Mark below as read"),
69 "catchup_above" => __("Mark above as read"),
70 "article_scroll_down" => __("Scroll down"),
71 "article_scroll_up" => __("Scroll up"),
72 "select_article_cursor" => __("Select article under cursor"),
73 "email_article" => __("Email article"),
74 "close_article" => __("Close/collapse article"),
75 "toggle_expand" => __("Toggle article expansion (combined mode)"),
76 "toggle_widescreen" => __("Toggle widescreen mode"),
77 "toggle_embed_original" => __("Toggle embed original")),
78 __("Article selection") => array(
79 "select_all" => __("Select all articles"),
80 "select_unread" => __("Select unread"),
81 "select_marked" => __("Select starred"),
82 "select_published" => __("Select published"),
83 "select_invert" => __("Invert selection"),
84 "select_none" => __("Deselect everything")),
86 "feed_refresh" => __("Refresh current feed"),
87 "feed_unhide_read" => __("Un/hide read feeds"),
88 "feed_subscribe" => __("Subscribe to feed"),
89 "feed_edit" => __("Edit feed"),
90 "feed_catchup" => __("Mark as read"),
91 "feed_reverse" => __("Reverse headlines"),
92 "feed_debug_update" => __("Debug feed update"),
93 "feed_debug_viewfeed" => __("Debug viewfeed()"),
94 "catchup_all" => __("Mark all feeds as read"),
95 "cat_toggle_collapse" => __("Un/collapse current category"),
96 "toggle_combined_mode" => __("Toggle combined mode"),
97 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
99 "goto_all" => __("All articles"),
100 "goto_fresh" => __("Fresh"),
101 "goto_marked" => __("Starred"),
102 "goto_published" => __("Published"),
103 "goto_tagcloud" => __("Tag cloud"),
104 "goto_prefs" => __("Preferences")),
105 __("Other") => array(
106 "create_label" => __("Create label"),
107 "create_filter" => __("Create filter"),
108 "collapse_sidebar" => __("Un/collapse sidebar"),
109 "help_dialog" => __("Show help dialog"))
112 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_INFO
) as $plugin) {
113 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
119 function get_hotkeys_map() {
121 // "navigation" => array(
124 "n" => "next_article",
125 "p" => "prev_article",
126 "(38)|up" => "prev_article",
127 "(40)|down" => "next_article",
128 // "^(38)|Ctrl-up" => "prev_article_noscroll",
129 // "^(40)|Ctrl-down" => "next_article_noscroll",
130 "(191)|/" => "search_dialog",
131 // "article" => array(
132 "s" => "toggle_mark",
133 "*s" => "toggle_publ",
134 "u" => "toggle_unread",
136 "o" => "open_in_new_window",
137 "c p" => "catchup_below",
138 "c n" => "catchup_above",
139 "*n" => "article_scroll_down",
140 "*p" => "article_scroll_up",
141 "*(38)|Shift+up" => "article_scroll_up",
142 "*(40)|Shift+down" => "article_scroll_down",
143 "a *w" => "toggle_widescreen",
144 "a e" => "toggle_embed_original",
145 "e" => "email_article",
146 "a q" => "close_article",
147 // "article_selection" => array(
148 "a a" => "select_all",
149 "a u" => "select_unread",
150 "a *u" => "select_marked",
151 "a p" => "select_published",
152 "a i" => "select_invert",
153 "a n" => "select_none",
155 "f r" => "feed_refresh",
156 "f a" => "feed_unhide_read",
157 "f s" => "feed_subscribe",
158 "f e" => "feed_edit",
159 "f q" => "feed_catchup",
160 "f x" => "feed_reverse",
161 "f *d" => "feed_debug_update",
162 "f *g" => "feed_debug_viewfeed",
163 "f *c" => "toggle_combined_mode",
164 "f c" => "toggle_cdm_expanded",
165 "*q" => "catchup_all",
166 "x" => "cat_toggle_collapse",
169 "g f" => "goto_fresh",
170 "g s" => "goto_marked",
171 "g p" => "goto_published",
172 "g t" => "goto_tagcloud",
173 "g *p" => "goto_prefs",
175 "(9)|Tab" => "select_article_cursor", // tab
176 "c l" => "create_label",
177 "c f" => "create_filter",
178 "c s" => "collapse_sidebar",
179 "^(191)|Ctrl+/" => "help_dialog",
182 if (get_pref('COMBINED_DISPLAY_MODE')) {
183 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
184 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
187 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_MAP
) as $plugin) {
188 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
193 foreach (array_keys($hotkeys) as $hotkey) {
194 $pair = explode(" ", $hotkey, 2);
196 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
197 array_push($prefixes, $pair[0]);
201 return array($prefixes, $hotkeys);
204 function check_for_update() {
205 if (defined("GIT_VERSION_TIMESTAMP")) {
206 $content = @fetch_file_contents
(array("url" => "http://tt-rss.org/version.json", "timeout" => 5));
209 $content = json_decode($content, true);
211 if ($content && isset($content["changeset"])) {
212 if ((int)GIT_VERSION_TIMESTAMP
< (int)$content["changeset"]["timestamp"] &&
213 GIT_VERSION_HEAD
!= $content["changeset"]["id"]) {
215 return $content["changeset"]["id"];
224 function make_runtime_info($disable_update_check = false) {
227 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
228 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
230 $max_feed_id = db_fetch_result($result, 0, "mid");
231 $num_feeds = db_fetch_result($result, 0, "nf");
233 $data["max_feed_id"] = (int) $max_feed_id;
234 $data["num_feeds"] = (int) $num_feeds;
236 $data['last_article_id'] = getLastArticleId();
237 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
239 $data['dep_ts'] = calculate_dep_timestamp();
240 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
243 if (CHECK_FOR_UPDATES
&& !$disable_update_check && $_SESSION["last_version_check"] +
86400 +
rand(-1000, 1000) < time()) {
244 $update_result = @check_for_update
();
246 $data["update_result"] = $update_result;
248 $_SESSION["last_version_check"] = time();
251 if (file_exists(LOCK_DIRECTORY
. "/update_daemon.lock")) {
253 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
255 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
257 $stamp = (int) @file_get_contents
(LOCK_DIRECTORY
. "/update_daemon.stamp");
260 $stamp_delta = time() - $stamp;
262 if ($stamp_delta > 1800) {
266 $_SESSION["daemon_stamp_check"] = time();
269 $data['daemon_stamp_ok'] = $stamp_check;
271 $stamp_fmt = date("Y.m.d, G:i", $stamp);
273 $data['daemon_stamp'] = $stamp_fmt;
281 function search_to_sql($search, $search_language) {
283 $keywords = str_getcsv(trim($search), " ");
284 $query_keywords = array();
285 $search_words = array();
286 $search_query_leftover = array();
288 if ($search_language)
289 $search_language = db_escape_string(mb_strtolower($search_language));
291 $search_language = "english";
293 foreach ($keywords as $k) {
294 if (strpos($k, "-") === 0) {
301 $commandpair = explode(":", mb_strtolower($k), 2);
303 switch ($commandpair[0]) {
305 if ($commandpair[1]) {
306 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
307 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
309 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
310 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
311 array_push($search_words, $k);
315 if ($commandpair[1]) {
316 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
317 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
319 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
320 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
321 array_push($search_words, $k);
325 if ($commandpair[1]) {
326 if ($commandpair[1] == "true")
327 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
328 else if ($commandpair[1] == "false")
329 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
331 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
332 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
334 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
335 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
336 if (!$not) array_push($search_words, $k);
341 if ($commandpair[1]) {
342 if ($commandpair[1] == "true")
343 array_push($query_keywords, "($not (marked = true))");
345 array_push($query_keywords, "($not (marked = false))");
347 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
348 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
349 if (!$not) array_push($search_words, $k);
353 if ($commandpair[1]) {
354 if ($commandpair[1] == "true")
355 array_push($query_keywords, "($not (published = true))");
357 array_push($query_keywords, "($not (published = false))");
360 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
361 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
362 if (!$not) array_push($search_words, $k);
366 if ($commandpair[1]) {
367 if ($commandpair[1] == "true")
368 array_push($query_keywords, "($not (unread = true))");
370 array_push($query_keywords, "($not (unread = false))");
373 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
374 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
375 if (!$not) array_push($search_words, $k);
379 if (strpos($k, "@") === 0) {
381 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
382 $orig_ts = strtotime(substr($k, 1));
383 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
385 //$k = date("Y-m-d", strtotime(substr($k, 1)));
387 array_push($query_keywords, "(".SUBSTRING_FOR_DATE
."(updated,1,LENGTH('$k')) $not = '$k')");
390 if (DB_TYPE
== "pgsql") {
391 $k = mb_strtolower($k);
392 array_push($search_query_leftover, $not ?
"!$k" : $k);
394 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
395 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
398 if (!$not) array_push($search_words, $k);
403 if (count($search_query_leftover) > 0) {
404 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
406 if (DB_TYPE
== "pgsql") {
407 array_push($query_keywords,
408 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
413 $search_query_part = implode("AND", $query_keywords);
415 return array($search_query_part, $search_words);
418 function getParentCategories($cat, $owner_uid) {
421 $result = db_query("SELECT parent_cat FROM ttrss_feed_categories
422 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
424 while ($line = db_fetch_assoc($result)) {
425 array_push($rv, $line["parent_cat"]);
426 $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid));
432 function getChildCategories($cat, $owner_uid) {
435 $result = db_query("SELECT id FROM ttrss_feed_categories
436 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
438 while ($line = db_fetch_assoc($result)) {
439 array_push($rv, $line["id"]);
440 $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid));
446 function queryFeedHeadlines($params) {
448 $feed = $params["feed"];
449 $limit = isset($params["limit"]) ?
$params["limit"] : 30;
450 $view_mode = $params["view_mode"];
451 $cat_view = isset($params["cat_view"]) ?
$params["cat_view"] : false;
452 $search = isset($params["search"]) ?
$params["search"] : false;
453 $search_language = isset($params["search_language"]) ?
$params["search_language"] : "";
454 $override_order = isset($params["override_order"]) ?
$params["override_order"] : false;
455 $offset = isset($params["offset"]) ?
$params["offset"] : 0;
456 $owner_uid = isset($params["owner_uid"]) ?
$params["owner_uid"] : $_SESSION["uid"];
457 $since_id = isset($params["since_id"]) ?
$params["since_id"] : 0;
458 $include_children = isset($params["include_children"]) ?
$params["include_children"] : false;
459 $ignore_vfeed_group = isset($params["ignore_vfeed_group"]) ?
$params["ignore_vfeed_group"] : false;
460 $override_strategy = isset($params["override_strategy"]) ?
$params["override_strategy"] : false;
461 $override_vfeed = isset($params["override_vfeed"]) ?
$params["override_vfeed"] : false;
462 $start_ts = isset($params["start_ts"]) ?
$params["start_ts"] : false;
463 $check_first_id = isset($params["check_first_id"]) ?
$params["check_first_id"] : false;
464 $skip_first_id_check = isset($params["skip_first_id_check"]) ?
$params["skip_first_id_check"] : false;
466 $ext_tables_part = "";
467 $query_strategy_part = "";
469 $search_words = array();
472 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SEARCH
) as $plugin) {
473 list($search_query_part, $search_words) = $plugin->hook_search($search);
477 // fall back in case of no plugins
478 if (!$search_query_part) {
479 list($search_query_part, $search_words) = search_to_sql($search, $search_language);
481 $search_query_part .= " AND ";
483 $search_query_part = "";
487 $since_id_part = "ttrss_entries.id > $since_id AND ";
492 $view_query_part = "";
494 if ($view_mode == "adaptive") {
496 $view_query_part = " ";
497 } else if ($feed != -1) {
499 $unread = getFeedUnread($feed, $cat_view);
501 if ($cat_view && $feed > 0 && $include_children)
502 $unread +
= getCategoryChildrenUnread($feed);
505 $view_query_part = " unread = true AND ";
510 if ($view_mode == "marked") {
511 $view_query_part = " marked = true AND ";
514 if ($view_mode == "has_note") {
515 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
518 if ($view_mode == "published") {
519 $view_query_part = " published = true AND ";
522 if ($view_mode == "unread" && $feed != -6) {
523 $view_query_part = " unread = true AND ";
527 $limit_query_part = "LIMIT " . $limit;
530 $allow_archived = false;
532 $vfeed_query_part = "";
535 if (!is_numeric($feed)) {
536 $query_strategy_part = "true";
537 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
538 id = feed_id) as feed_title,";
539 } else if ($feed > 0) {
544 if ($include_children) {
546 $subcats = getChildCategories($feed, $owner_uid);
548 array_push($subcats, $feed);
549 $query_strategy_part = "cat_id IN (".
550 implode(",", $subcats).")";
553 $query_strategy_part = "cat_id = '$feed'";
557 $query_strategy_part = "cat_id IS NULL";
560 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
563 $query_strategy_part = "feed_id = '$feed'";
565 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
566 $query_strategy_part = "feed_id IS NULL";
567 $allow_archived = true;
568 } else if ($feed == 0 && $cat_view) { // uncategorized
569 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
570 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
571 } else if ($feed == -1) { // starred virtual feed
572 $query_strategy_part = "marked = true";
573 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
574 $allow_archived = true;
576 if (!$override_order) {
577 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
580 } else if ($feed == -2) { // published virtual feed OR labels category
583 $query_strategy_part = "published = true";
584 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
585 $allow_archived = true;
587 if (!$override_order) {
588 $override_order = "last_published DESC, date_entered DESC, updated DESC";
592 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
594 $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,";
596 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
597 ttrss_user_labels2.article_id = ref_id";
600 } else if ($feed == -6) { // recently read
601 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
603 if (DB_TYPE
== "pgsql") {
604 $query_strategy_part .= " AND last_read > NOW() - INTERVAL '1 DAY' ";
606 $query_strategy_part .= " AND last_read > DATE_SUB(NOW(), INTERVAL 1 DAY) ";
609 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
610 $allow_archived = true;
611 $ignore_vfeed_group = true;
613 if (!$override_order) $override_order = "last_read DESC";
615 } else if ($feed == -3) { // fresh virtual feed
616 $query_strategy_part = "unread = true AND score >= 0";
618 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
620 if (DB_TYPE
== "pgsql") {
621 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
623 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
626 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
627 } else if ($feed == -4) { // all articles virtual feed
628 $allow_archived = true;
629 $query_strategy_part = "true";
630 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
631 } else if ($feed <= LABEL_BASE_INDEX
) { // labels
632 $label_id = feed_to_label_id($feed);
634 $query_strategy_part = "label_id = '$label_id' AND
635 ttrss_labels2.id = ttrss_user_labels2.label_id AND
636 ttrss_user_labels2.article_id = ref_id";
638 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
639 $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,";
640 $allow_archived = true;
643 $query_strategy_part = "true";
646 $order_by = "score DESC, date_entered DESC, updated DESC";
648 if ($override_order) {
649 $order_by = $override_order;
652 if ($override_strategy) {
653 $query_strategy_part = $override_strategy;
656 if ($override_vfeed) {
657 $vfeed_query_part = $override_vfeed;
663 $feed_title = T_sprintf("Search results: %s", $search);
666 $feed_title = getCategoryTitle($feed);
668 if (is_numeric($feed) && $feed > 0) {
669 $result = db_query("SELECT title,site_url,last_error,last_updated
670 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
672 $feed_title = db_fetch_result($result, 0, "title");
673 $feed_site_url = db_fetch_result($result, 0, "site_url");
674 $last_error = db_fetch_result($result, 0, "last_error");
675 $last_updated = db_fetch_result($result, 0, "last_updated");
677 $feed_title = getFeedTitle($feed);
683 $content_query_part = "content, ";
685 if ($limit_query_part) {
686 $offset_query_part = "OFFSET $offset";
688 $offset_query_part = "";
691 if (is_numeric($feed)) {
692 // proper override_order applied above
693 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
694 if (!$override_order) {
695 $order_by = "ttrss_feeds.title, $order_by";
697 $order_by = "ttrss_feeds.title, $override_order";
701 if (!$allow_archived) {
702 $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id),ttrss_feeds";
703 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
706 $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id)
707 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
710 if ($vfeed_query_part) $vfeed_query_part .= "favicon_avg_color,";
713 $start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts));
714 $start_ts_query_part = "date_entered >= '$start_ts_formatted' AND";
716 $start_ts_query_part = "";
720 $first_id_query_strategy_part = $query_strategy_part;
723 $first_id_query_strategy_part = "true";
725 if (DB_TYPE
== "pgsql") {
726 $sanity_interval_qpart = "date_entered >= NOW() - INTERVAL '1 hour' AND";
728 $sanity_interval_qpart = "date_entered >= DATE_SUB(NOW(), INTERVAL 1 hour) AND";
731 if (!$search && !$skip_first_id_check) {
732 // if previous topmost article id changed that means our current pagination is no longer valid
733 $query = "SELECT DISTINCT
750 ttrss_user_entries.owner_uid = '$owner_uid' AND
754 $sanity_interval_qpart
755 $first_id_query_strategy_part ORDER BY $order_by LIMIT 1";
757 if ($_REQUEST["debug"]) {
761 $result = db_query($query);
762 if ($result && db_num_rows($result) > 0) {
763 $first_id = (int)db_fetch_result($result, 0, "id");
765 if ($offset > 0 && $first_id && $check_first_id && $first_id != $check_first_id) {
766 return array(-1, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
771 $query = "SELECT DISTINCT
774 ttrss_entries.id,ttrss_entries.title,
778 always_display_enclosures,
787 unread,feed_id,marked,published,link,last_read,orig_feed_id,
788 last_marked, last_published,
796 ttrss_user_entries.owner_uid = '$owner_uid' AND
801 $query_strategy_part ORDER BY $order_by
802 $limit_query_part $offset_query_part";
804 if ($_REQUEST["debug"]) print $query;
806 $result = db_query($query);
811 $query = "SELECT DISTINCT
815 ttrss_entries.id as id,
831 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images,
832 last_marked, last_published,
837 FROM ttrss_entries, ttrss_user_entries, ttrss_tags
839 ref_id = ttrss_entries.id AND
840 ttrss_user_entries.owner_uid = $owner_uid AND
841 post_int_id = int_id AND
842 tag_name = '$feed' AND
845 $query_strategy_part ORDER BY $order_by
846 $limit_query_part $offset_query_part";
848 if ($_REQUEST["debug"]) print $query;
850 $result = db_query($query);
853 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
857 function iframe_whitelisted($entry) {
858 $whitelist = array("youtube.com", "youtu.be", "vimeo.com", "player.vimeo.com");
860 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST
);
863 foreach ($whitelist as $w) {
864 if ($src == $w ||
$src == "www.$w")
872 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
873 if (!$owner) $owner = $_SESSION["uid"];
875 $res = trim($str); if (!$res) return '';
877 $charset_hack = '<head>
878 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
881 $res = trim($res); if (!$res) return '';
883 libxml_use_internal_errors(true);
885 $doc = new DOMDocument();
886 $doc->loadHTML($charset_hack . $res);
887 $xpath = new DOMXPath($doc);
889 $entries = $xpath->query('(//a[@href]|//img[@src])');
891 $ttrss_uses_https = parse_url(get_self_url_prefix(), PHP_URL_SCHEME
) === 'https';
893 foreach ($entries as $entry) {
897 if ($entry->hasAttribute('href')) {
898 $entry->setAttribute('href',
899 rewrite_relative_url($site_url, $entry->getAttribute('href')));
901 $entry->setAttribute('rel', 'noreferrer');
904 if ($entry->hasAttribute('src')) {
905 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
907 $cached_filename = CACHE_DIR
. '/images/' . sha1($src) . '.png';
909 if (file_exists($cached_filename)) {
910 $src = SELF_URL_PATH
. '/public.php?op=cached_image&hash=' . sha1($src);
912 if ($entry->hasAttribute('srcset')) {
913 $entry->removeAttribute('srcset');
916 if ($entry->hasAttribute('sizes')) {
917 $entry->removeAttribute('sizes');
921 $entry->setAttribute('src', $src);
924 if ($entry->nodeName
== 'img') {
925 if ($entry->hasAttribute('src')) {
926 $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME
) === 'https';
928 if ($ttrss_uses_https && !$is_https_url) {
930 if ($entry->hasAttribute('srcset')) {
931 $entry->removeAttribute('srcset');
934 if ($entry->hasAttribute('sizes')) {
935 $entry->removeAttribute('sizes');
940 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
941 $force_remove_images ||
$_SESSION["bw_limit"]) {
943 $p = $doc->createElement('p');
945 $a = $doc->createElement('a');
946 $a->setAttribute('href', $entry->getAttribute('src'));
948 $a->appendChild(new DOMText($entry->getAttribute('src')));
949 $a->setAttribute('target', '_blank');
953 $entry->parentNode
->replaceChild($p, $entry);
958 if (strtolower($entry->nodeName
) == "a") {
959 $entry->setAttribute("target", "_blank");
963 $entries = $xpath->query('//iframe');
964 foreach ($entries as $entry) {
965 if (!iframe_whitelisted($entry)) {
966 $entry->setAttribute('sandbox', 'allow-scripts');
968 if ($_SERVER['HTTPS'] == "on") {
969 $entry->setAttribute("src",
970 str_replace("http://", "https://",
971 $entry->getAttribute("src")));
976 $allowed_elements = array('a', 'address', 'audio', 'article', 'aside',
977 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
978 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
979 'data', 'dd', 'del', 'details', 'description', 'div', 'dl', 'font',
980 'dt', 'em', 'footer', 'figure', 'figcaption',
981 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
982 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
983 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
984 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
985 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
986 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
988 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
990 $disallowed_attributes = array('id', 'style', 'class');
992 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SANITIZE
) as $plugin) {
993 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
994 if (is_array($retval)) {
996 $allowed_elements = $retval[1];
997 $disallowed_attributes = $retval[2];
1003 $doc->removeChild($doc->firstChild
); //remove doctype
1004 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
1006 if ($highlight_words) {
1007 foreach ($highlight_words as $word) {
1009 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
1011 $elements = $xpath->query("//*/text()");
1013 foreach ($elements as $child) {
1015 $fragment = $doc->createDocumentFragment();
1016 $text = $child->textContent
;
1018 while (($pos = mb_stripos($text, $word)) !== false) {
1019 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1020 $word = mb_substr($text, $pos, mb_strlen($word));
1021 $highlight = $doc->createElement('span');
1022 $highlight->appendChild(new DomText($word));
1023 $highlight->setAttribute('class', 'highlight');
1024 $fragment->appendChild($highlight);
1025 $text = mb_substr($text, $pos +
mb_strlen($word));
1028 if (!empty($text)) $fragment->appendChild(new DomText($text));
1030 $child->parentNode
->replaceChild($fragment, $child);
1035 $res = $doc->saveHTML();
1037 /* strip everything outside of <body>...</body> */
1039 $res_frag = array();
1040 if (preg_match('/<body>(.*)<\/body>/is', $res, $res_frag)) {
1041 return $res_frag[1];
1047 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1048 $xpath = new DOMXPath($doc);
1049 $entries = $xpath->query('//*');
1051 foreach ($entries as $entry) {
1052 if (!in_array($entry->nodeName
, $allowed_elements)) {
1053 $entry->parentNode
->removeChild($entry);
1056 if ($entry->hasAttributes()) {
1057 $attrs_to_remove = array();
1059 foreach ($entry->attributes
as $attr) {
1061 if (strpos($attr->nodeName
, 'on') === 0) {
1062 array_push($attrs_to_remove, $attr);
1065 if (in_array($attr->nodeName
, $disallowed_attributes)) {
1066 array_push($attrs_to_remove, $attr);
1070 foreach ($attrs_to_remove as $attr) {
1071 $entry->removeAttributeNode($attr);
1079 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
1081 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1082 if (count($ids) == 0) return;
1086 foreach ($ids as $id) {
1087 array_push($tmp_ids, "ref_id = '$id'");
1090 $ids_qpart = join(" OR ", $tmp_ids);
1093 db_query("UPDATE ttrss_user_entries SET
1094 unread = false,last_read = NOW()
1095 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1096 } else if ($cmode == 1) {
1097 db_query("UPDATE ttrss_user_entries SET
1099 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1101 db_query("UPDATE ttrss_user_entries SET
1102 unread = NOT unread,last_read = NOW()
1103 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1108 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
1109 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1111 while ($line = db_fetch_assoc($result)) {
1112 ccache_update($line["feed_id"], $owner_uid);
1116 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
1118 $a_id = db_escape_string($id);
1120 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1122 $query = "SELECT DISTINCT tag_name,
1123 owner_uid as owner FROM
1124 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
1125 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
1129 /* check cache first */
1131 if ($tag_cache === false) {
1132 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
1133 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1135 if (db_num_rows($result) != 0)
1136 $tag_cache = db_fetch_result($result, 0, "tag_cache");
1140 $tags = explode(",", $tag_cache);
1143 /* do it the hard way */
1145 $tmp_result = db_query($query);
1147 while ($tmp_line = db_fetch_assoc($tmp_result)) {
1148 array_push($tags, $tmp_line["tag_name"]);
1151 /* update the cache */
1153 $tags_str = db_escape_string(join(",", $tags));
1155 db_query("UPDATE ttrss_user_entries
1156 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
1157 AND owner_uid = $owner_uid");
1163 function trim_array($array) {
1165 array_walk($tmp, 'trim');
1169 function tag_is_valid($tag) {
1170 if ($tag == '') return false;
1171 if (is_numeric($tag)) return false;
1172 if (mb_strlen($tag) > 250) return false;
1174 if (!$tag) return false;
1179 function render_login_form() {
1180 header('Cache-Control: public');
1182 require_once "login_form.php";
1186 function format_warning($msg, $id = "") {
1187 return "<div class=\"alert\" id=\"$id\">$msg</div>";
1190 function format_notice($msg, $id = "") {
1191 return "<div class=\"alert alert-info\" id=\"$id\">$msg</div>";
1194 function format_error($msg, $id = "") {
1195 return "<div class=\"alert alert-danger\" id=\"$id\">$msg</div>";
1198 function print_notice($msg) {
1199 return print format_notice($msg);
1202 function print_warning($msg) {
1203 return print format_warning($msg);
1206 function print_error($msg) {
1207 return print format_error($msg);
1211 function T_sprintf() {
1212 $args = func_get_args();
1213 return vsprintf(__(array_shift($args)), $args);
1216 function format_inline_player($url, $ctype) {
1220 $url = htmlspecialchars($url);
1222 if (strpos($ctype, "audio/") === 0) {
1224 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1225 $_SESSION["hasMp3"])) {
1227 $entry .= "<audio preload=\"none\" controls>
1228 <source type=\"$ctype\" src=\"$url\"/>
1233 $entry .= "<object type=\"application/x-shockwave-flash\"
1234 data=\"lib/button/musicplayer.swf?song_url=$url\"
1235 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1236 <param name=\"movie\"
1237 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1241 if ($entry) $entry .= " <a target=\"_blank\"
1242 href=\"$url\">" . basename($url) . "</a>";
1250 /* $filename = substr($url, strrpos($url, "/")+1);
1252 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1253 $filename . " (" . $ctype . ")" . "</a>"; */
1257 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1258 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1264 /* we can figure out feed_id from article id anyway, why do we
1265 * pass feed_id here? let's ignore the argument :(*/
1267 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1268 WHERE ref_id = '$id'");
1270 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1272 $rv['feed_id'] = $feed_id;
1274 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1276 if ($mark_as_read) {
1277 $result = db_query("UPDATE ttrss_user_entries
1278 SET unread = false,last_read = NOW()
1279 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1281 ccache_update($feed_id, $owner_uid);
1284 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1285 ".SUBSTRING_FOR_DATE
."(updated,1,16) as updated,
1286 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1287 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1288 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1289 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1295 FROM ttrss_entries,ttrss_user_entries
1296 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1300 $line = db_fetch_assoc($result);
1302 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1303 unset($line["tag_cache"]);
1305 $line["content"] = sanitize($line["content"],
1306 sql_bool_to_bool($line['hide_images']),
1307 $owner_uid, $line["site_url"], false, $line["id"]);
1309 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_RENDER_ARTICLE
) as $p) {
1310 $line = $p->hook_render_article($line);
1313 $num_comments = $line["num_comments"];
1314 $entry_comments = "";
1316 if ($num_comments > 0) {
1317 if ($line["comments"]) {
1318 $comments_url = htmlspecialchars($line["comments"]);
1320 $comments_url = htmlspecialchars($line["link"]);
1322 $entry_comments = "<a class=\"postComments\"
1323 target='_blank' href=\"$comments_url\">$num_comments ".
1324 _ngettext("comment", "comments", $num_comments)."</a>";
1327 if ($line["comments"] && $line["link"] != $line["comments"]) {
1328 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
1333 header("Content-Type: text/html");
1334 $rv['content'] .= "<html><head>
1335 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1336 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1337 stylesheet_tag("css/tt-rss.css").
1338 stylesheet_tag("css/zoom.css").
1339 stylesheet_tag("css/dijit.css")."
1341 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1342 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1344 </head><body id=\"ttrssZoom\">";
1347 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1349 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1351 $entry_author = $line["author"];
1353 if ($entry_author) {
1354 $entry_author = __(" - ") . $entry_author;
1357 $parsed_updated = make_local_datetime($line["updated"], true,
1361 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1363 if ($line["link"]) {
1364 $rv['content'] .= "<div class='postTitle'><a target='_blank'
1365 title=\"".htmlspecialchars($line['title'])."\"
1367 htmlspecialchars($line["link"]) . "\">" .
1368 $line["title"] . "</a>" .
1369 "<span class='author'>$entry_author</span></div>";
1371 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1375 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
1376 "\" target=\"_blank\">".
1377 htmlspecialchars($line["feed_title"])."</a>";
1379 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1381 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1384 $tags_str = format_tags_string($line["tags"], $id);
1385 $tags_str_full = join(", ", $line["tags"]);
1387 if (!$tags_str_full) $tags_str_full = __("no tags");
1389 if (!$entry_comments) $entry_comments = " "; # placeholder
1391 $rv['content'] .= "<div class='postTags' style='float : right'>
1392 <img src='images/tag.png'
1393 class='tagsPic' alt='Tags' title='Tags'> ";
1396 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1397 <a title=\"".__('Edit tags for this article')."\"
1398 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1400 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1401 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1402 position=\"below\">$tags_str_full</div>";
1404 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_BUTTON
) as $p) {
1405 $rv['content'] .= $p->hook_article_button($line);
1409 $tags_str = strip_tags($tags_str);
1410 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1412 $rv['content'] .= "</div>";
1413 $rv['content'] .= "<div clear='both'>";
1415 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_LEFT_BUTTON
) as $p) {
1416 $rv['content'] .= $p->hook_article_left_button($line);
1419 $rv['content'] .= "$entry_comments</div>";
1421 if ($line["orig_feed_id"]) {
1423 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
1424 WHERE id = ".$line["orig_feed_id"] . " AND owner_uid = " . $_SESSION["uid"]);
1426 if (db_num_rows($tmp_result) != 0) {
1428 $rv['content'] .= "<div clear='both'>";
1429 $rv['content'] .= __("Originally from:");
1431 $rv['content'] .= " ";
1433 $tmp_line = db_fetch_assoc($tmp_result);
1435 $rv['content'] .= "<a target='_blank'
1436 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1437 $tmp_line['title'] . "</a>";
1439 $rv['content'] .= " ";
1441 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
1442 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1444 $rv['content'] .= "</div>";
1448 $rv['content'] .= "</div>";
1450 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1451 if ($line['note']) {
1452 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1454 $rv['content'] .= "</div>";
1456 if (!$line['lang']) $line['lang'] = 'en';
1458 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1460 $rv['content'] .= $line["content"];
1463 $rv['content'] .= format_article_enclosures($id,
1464 sql_bool_to_bool($line["always_display_enclosures"]),
1466 sql_bool_to_bool($line["hide_images"]));
1469 $rv['content'] .= "</div>";
1471 $rv['content'] .= "</div>";
1477 <div class='footer'>
1478 <button onclick=\"return window.close()\">".
1479 __("Close this window")."</button></div>";
1480 $rv['content'] .= "</body></html>";
1487 function print_checkpoint($n, $s) {
1488 $ts = microtime(true);
1489 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1493 function sanitize_tag($tag) {
1496 $tag = mb_strtolower($tag, 'utf-8');
1498 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1500 if (DB_TYPE
== "mysql") {
1501 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1507 function get_self_url_prefix() {
1508 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
1509 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
1511 return SELF_URL_PATH
;
1516 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1518 * @return string The Mozilla Firefox feed adding URL.
1520 function add_feed_url() {
1521 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1523 $url_path = get_self_url_prefix() .
1524 "/public.php?op=subscribe&feed_url=%s";
1526 } // function add_feed_url
1528 function encrypt_password($pass, $salt = '', $mode2 = false) {
1529 if ($salt && $mode2) {
1530 return "MODE2:" . hash('sha256', $salt . $pass);
1532 return "SHA1X:" . sha1("$salt:$pass");
1534 return "SHA1:" . sha1($pass);
1536 } // function encrypt_password
1538 function load_filters($feed_id, $owner_uid, $action_id = false) {
1541 $cat_id = (int)getFeedCategory($feed_id);
1544 $null_cat_qpart = "cat_id IS NULL OR";
1546 $null_cat_qpart = "";
1548 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1549 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1551 $check_cats = join(",", array_merge(
1552 getParentCategories($cat_id, $owner_uid),
1555 while ($line = db_fetch_assoc($result)) {
1556 $filter_id = $line["id"];
1558 $result2 = db_query("SELECT
1559 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1560 FROM ttrss_filters2_rules AS r,
1561 ttrss_filter_types AS t
1563 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1564 (feed_id IS NULL OR feed_id = '$feed_id') AND
1565 filter_type = t.id AND filter_id = '$filter_id'");
1570 while ($rule_line = db_fetch_assoc($result2)) {
1571 # print_r($rule_line);
1574 $rule["reg_exp"] = $rule_line["reg_exp"];
1575 $rule["type"] = $rule_line["type_name"];
1576 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1578 array_push($rules, $rule);
1581 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1582 FROM ttrss_filters2_actions AS a,
1583 ttrss_filter_actions AS t
1585 action_id = t.id AND filter_id = '$filter_id'");
1587 while ($action_line = db_fetch_assoc($result2)) {
1588 # print_r($action_line);
1591 $action["type"] = $action_line["type_name"];
1592 $action["param"] = $action_line["action_param"];
1594 array_push($actions, $action);
1599 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1600 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1601 $filter["rules"] = $rules;
1602 $filter["actions"] = $actions;
1604 if (count($rules) > 0 && count($actions) > 0) {
1605 array_push($filters, $filter);
1612 function get_score_pic($score) {
1614 return "score_high.png";
1615 } else if ($score > 0) {
1616 return "score_half_high.png";
1617 } else if ($score < -100) {
1618 return "score_low.png";
1619 } else if ($score < 0) {
1620 return "score_half_low.png";
1622 return "score_neutral.png";
1626 function feed_has_icon($id) {
1627 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
1630 function init_plugins() {
1631 PluginHost
::getInstance()->load(PLUGINS
, PluginHost
::KIND_ALL
);
1636 function format_tags_string($tags, $id) {
1637 if (!is_array($tags) ||
count($tags) == 0) {
1638 return __("no tags");
1640 $maxtags = min(5, count($tags));
1643 for ($i = 0; $i < $maxtags; $i++
) {
1644 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
1647 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1649 if (count($tags) > $maxtags)
1650 $tags_str .= ", …";
1656 function format_article_labels($labels, $id) {
1658 if (!is_array($labels)) return '';
1662 foreach ($labels as $l) {
1663 $labels_str .= sprintf("<span class='hlLabelRef'
1664 style='color : %s; background-color : %s'>%s</span>",
1665 $l[2], $l[3], $l[1]);
1672 function format_article_note($id, $note, $allow_edit = true) {
1674 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1675 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1676 ($allow_edit ?
__('(edit note)') : "")."</div>$note</div>";
1682 function get_feed_category($feed_cat, $parent_cat_id = false) {
1683 if ($parent_cat_id) {
1684 $parent_qpart = "parent_cat = '$parent_cat_id'";
1685 $parent_insert = "'$parent_cat_id'";
1687 $parent_qpart = "parent_cat IS NULL";
1688 $parent_insert = "NULL";
1692 "SELECT id FROM ttrss_feed_categories
1693 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1695 if (db_num_rows($result) == 0) {
1698 return db_fetch_result($result, 0, "id");
1702 function add_feed_category($feed_cat, $parent_cat_id = false) {
1704 if (!$feed_cat) return false;
1708 if ($parent_cat_id) {
1709 $parent_qpart = "parent_cat = '$parent_cat_id'";
1710 $parent_insert = "'$parent_cat_id'";
1712 $parent_qpart = "parent_cat IS NULL";
1713 $parent_insert = "NULL";
1716 $feed_cat = mb_substr($feed_cat, 0, 250);
1719 "SELECT id FROM ttrss_feed_categories
1720 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1722 if (db_num_rows($result) == 0) {
1725 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1726 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1736 function getArticleFeed($id) {
1737 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1738 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1740 if (db_num_rows($result) != 0) {
1741 return db_fetch_result($result, 0, "feed_id");
1748 * Fixes incomplete URLs by prepending "http://".
1749 * Also replaces feed:// with http://, and
1750 * prepends a trailing slash if the url is a domain name only.
1752 * @param string $url Possibly incomplete URL
1754 * @return string Fixed URL.
1756 function fix_url($url) {
1758 // support schema-less urls
1759 if (strpos($url, '//') === 0) {
1760 $url = 'https:' . $url;
1763 if (strpos($url, '://') === false) {
1764 $url = 'http://' . $url;
1765 } else if (substr($url, 0, 5) == 'feed:') {
1766 $url = 'http:' . substr($url, 5);
1769 //prepend slash if the URL has no slash in it
1770 // "http://www.example" -> "http://www.example/"
1771 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
1775 if ($url != "http:///")
1781 function validate_feed_url($url) {
1782 $parts = parse_url($url);
1784 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
1788 function get_article_enclosures($id) {
1790 $query = "SELECT * FROM ttrss_enclosures
1791 WHERE post_id = '$id' AND content_url != ''";
1795 $result = db_query($query);
1797 if (db_num_rows($result) > 0) {
1798 while ($line = db_fetch_assoc($result)) {
1799 array_push($rv, $line);
1806 /* function save_email_address($email) {
1807 // FIXME: implement persistent storage of emails
1809 if (!$_SESSION['stored_emails'])
1810 $_SESSION['stored_emails'] = array();
1812 if (!in_array($email, $_SESSION['stored_emails']))
1813 array_push($_SESSION['stored_emails'], $email);
1817 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1819 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1821 $sql_is_cat = bool_to_sql_bool($is_cat);
1823 $result = db_query("SELECT access_key FROM ttrss_access_keys
1824 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1825 AND owner_uid = " . $owner_uid);
1827 if (db_num_rows($result) == 1) {
1828 return db_fetch_result($result, 0, "access_key");
1830 $key = db_escape_string(uniqid_short());
1832 $result = db_query("INSERT INTO ttrss_access_keys
1833 (access_key, feed_id, is_cat, owner_uid)
1834 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1841 function get_feeds_from_html($url, $content)
1843 $url = fix_url($url);
1844 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
1846 libxml_use_internal_errors(true);
1848 $doc = new DOMDocument();
1849 $doc->loadHTML($content);
1850 $xpath = new DOMXPath($doc);
1851 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
1852 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
1853 $feedUrls = array();
1854 foreach ($entries as $entry) {
1855 if ($entry->hasAttribute('href')) {
1856 $title = $entry->getAttribute('title');
1858 $title = $entry->getAttribute('type');
1860 $feedUrl = rewrite_relative_url(
1861 $baseUrl, $entry->getAttribute('href')
1863 $feedUrls[$feedUrl] = $title;
1869 function is_html($content) {
1870 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
1873 function url_is_html($url, $login = false, $pass = false) {
1874 return is_html(fetch_file_contents($url, false, $login, $pass));
1877 function print_label_select($name, $value, $attributes = "") {
1879 $result = db_query("SELECT caption FROM ttrss_labels2
1880 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1882 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1883 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
1885 while ($line = db_fetch_assoc($result)) {
1887 $issel = ($line["caption"] == $value) ?
"selected=\"1\"" : "";
1889 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1890 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1894 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1901 function format_article_enclosures($id, $always_display_enclosures,
1902 $article_content, $hide_images = false) {
1904 $result = get_article_enclosures($id);
1907 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_FORMAT_ENCLOSURES
) as $plugin) {
1908 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
1909 if (is_array($retval)) {
1911 $result = $retval[1];
1916 unset($retval); // Unset to prevent breaking render if there are no HOOK_RENDER_ENCLOSURE hooks below.
1918 if ($rv === '' && !empty($result)) {
1919 $entries_html = array();
1921 $entries_inline = array();
1923 foreach ($result as $line) {
1925 $url = $line["content_url"];
1926 $ctype = $line["content_type"];
1927 $title = $line["title"];
1928 $width = $line["width"];
1929 $height = $line["height"];
1931 if (!$ctype) $ctype = __("unknown type");
1933 $filename = substr($url, strrpos($url, "/")+
1);
1935 $player = format_inline_player($url, $ctype);
1937 if ($player) array_push($entries_inline, $player);
1939 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1940 # $filename . " (" . $ctype . ")" . "</a>";
1942 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
1943 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1945 array_push($entries_html, $entry);
1949 $entry["type"] = $ctype;
1950 $entry["filename"] = $filename;
1951 $entry["url"] = $url;
1952 $entry["title"] = $title;
1953 $entry["width"] = $width;
1954 $entry["height"] = $height;
1956 array_push($entries, $entry);
1959 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
1960 if ($always_display_enclosures ||
1961 !preg_match("/<img/i", $article_content)) {
1963 foreach ($entries as $entry) {
1965 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_RENDER_ENCLOSURE
) as $plugin)
1966 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
1973 if (preg_match("/image/", $entry["type"]) ||
1974 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
1976 if (!$hide_images) {
1978 if ($entry['height'] > 0)
1979 $encsize .= ' height="' . intval($entry['height']) . '"';
1980 if ($entry['width'] > 0)
1981 $encsize .= ' width="' . intval($entry['width']) . '"';
1983 alt=\"".htmlspecialchars($entry["filename"])."\"
1984 src=\"" .htmlspecialchars($entry["url"]) . "\"
1985 " . $encsize . " /></p>";
1987 $rv .= "<p><a target=\"_blank\"
1988 href=\"".htmlspecialchars($entry["url"])."\"
1989 >" .htmlspecialchars($entry["url"]) . "</a></p>";
1992 if ($entry['title']) {
1993 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
2001 if (count($entries_inline) > 0) {
2002 $rv .= "<hr clear='both'/>";
2003 foreach ($entries_inline as $entry) { $rv .= $entry; };
2004 $rv .= "<hr clear='both'/>";
2007 $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
2008 "<span>" . __('Attachments')."</span>";
2010 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
2012 foreach ($entries as $entry) {
2013 if ($entry["title"])
2014 $title = "— " . truncate_string($entry["title"], 30);
2018 $rv .= "<div onclick='window.open(\"".htmlspecialchars($entry["url"])."\")'
2019 dojoType=\"dijit.MenuItem\">".htmlspecialchars($entry["filename"])."$title</div>";
2030 function getLastArticleId() {
2031 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
2032 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
2034 if (db_num_rows($result) == 1) {
2035 return db_fetch_result($result, 0, "id");
2041 function build_url($parts) {
2042 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2046 * Converts a (possibly) relative URL to a absolute one.
2048 * @param string $url Base URL (i.e. from where the document is)
2049 * @param string $rel_url Possibly relative URL in the document
2051 * @return string Absolute URL
2053 function rewrite_relative_url($url, $rel_url) {
2054 if (strpos($rel_url, "://") !== false) {
2056 } else if (strpos($rel_url, "//") === 0) {
2057 # protocol-relative URL (rare but they exist)
2059 } else if (preg_match("/^[a-z]+:/i", $rel_url)) {
2060 # magnet:, feed:, etc
2062 } else if (strpos($rel_url, "/") === 0) {
2063 $parts = parse_url($url);
2064 $parts['path'] = $rel_url;
2066 return build_url($parts);
2069 $parts = parse_url($url);
2070 if (!isset($parts['path'])) {
2071 $parts['path'] = '/';
2073 $dir = $parts['path'];
2074 if (substr($dir, -1) !== '/') {
2075 $dir = dirname($parts['path']);
2076 $dir !== '/' && $dir .= '/';
2078 $parts['path'] = $dir . $rel_url;
2080 return build_url($parts);
2084 function cleanup_tags($days = 14, $limit = 1000) {
2086 if (DB_TYPE
== "pgsql") {
2087 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2088 } else if (DB_TYPE
== "mysql") {
2089 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2094 while ($limit > 0) {
2097 $query = "SELECT ttrss_tags.id AS id
2098 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2099 WHERE post_int_id = int_id AND $interval_query AND
2100 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2102 $result = db_query($query);
2106 while ($line = db_fetch_assoc($result)) {
2107 array_push($ids, $line['id']);
2110 if (count($ids) > 0) {
2111 $ids = join(",", $ids);
2113 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2114 $tags_deleted +
= db_affected_rows($tmp_result);
2119 $limit -= $limit_part;
2122 return $tags_deleted;
2125 function print_user_stylesheet() {
2126 $value = get_pref('USER_STYLESHEET');
2129 print "<style type=\"text/css\">";
2130 print str_replace("<br/>", "\n", $value);
2136 function filter_to_sql($filter, $owner_uid) {
2139 if (DB_TYPE
== "pgsql")
2142 $reg_qpart = "REGEXP";
2144 foreach ($filter["rules"] AS $rule) {
2145 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2146 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2147 $rule['reg_exp']) !== FALSE;
2149 if ($regexp_valid) {
2151 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2153 switch ($rule["type"]) {
2155 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2156 $rule['reg_exp'] . "')";
2159 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2160 $rule['reg_exp'] . "')";
2163 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2164 $rule['reg_exp'] . "') OR LOWER(" .
2165 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2168 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2169 $rule['reg_exp'] . "')";
2172 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2173 $rule['reg_exp'] . "')";
2176 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2177 $rule['reg_exp'] . "')";
2181 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2183 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2184 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2187 if (isset($rule["cat_id"])) {
2189 if ($rule["cat_id"] > 0) {
2190 $children = getChildCategories($rule["cat_id"], $owner_uid);
2191 array_push($children, $rule["cat_id"]);
2193 $children = join(",", $children);
2195 $cat_qpart = "cat_id IN ($children)";
2197 $cat_qpart = "cat_id IS NULL";
2200 $qpart .= " AND $cat_qpart";
2203 $qpart .= " AND feed_id IS NOT NULL";
2205 array_push($query, "($qpart)");
2210 if (count($query) > 0) {
2211 $fullquery = "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
2213 $fullquery = "(false)";
2216 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2221 if (!function_exists('gzdecode')) {
2222 function gzdecode($string) { // no support for 2nd argument
2223 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2224 base64_encode($string));
2228 function get_random_bytes($length) {
2229 if (function_exists('openssl_random_pseudo_bytes')) {
2230 return openssl_random_pseudo_bytes($length);
2234 for ($i = 0; $i < $length; $i++
)
2235 $output .= chr(mt_rand(0, 255));
2241 function read_stdin() {
2242 $fp = fopen("php://stdin", "r");
2245 $line = trim(fgets($fp));
2253 function tmpdirname($path, $prefix) {
2254 // Use PHP's tmpfile function to create a temporary
2255 // directory name. Delete the file and keep the name.
2256 $tempname = tempnam($path,$prefix);
2260 if (!unlink($tempname))
2266 function getFeedCategory($feed) {
2267 $result = db_query("SELECT cat_id FROM ttrss_feeds
2268 WHERE id = '$feed'");
2270 if (db_num_rows($result) > 0) {
2271 return db_fetch_result($result, 0, "cat_id");
2278 function implements_interface($class, $interface) {
2279 return in_array($interface, class_implements($class));
2282 function get_minified_js($files) {
2283 require_once 'lib/jshrink/Minifier.php';
2287 foreach ($files as $js) {
2288 if (!isset($_GET['debug'])) {
2289 $cached_file = CACHE_DIR
. "/js/".basename($js).".js";
2291 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2293 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2295 if ($header && $contents) {
2296 list($htag, $hversion) = explode(":", $header);
2298 if ($htag == "tt-rss" && $hversion == VERSION
) {
2305 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
2306 file_put_contents($cached_file, "tt-rss:" . VERSION
. "\n" . $minified);
2310 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2317 function stylesheet_tag($filename) {
2318 $timestamp = filemtime($filename);
2320 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2323 function javascript_tag($filename) {
2326 if (!(strpos($filename, "?") === FALSE)) {
2327 $query = substr($filename, strpos($filename, "?")+
1);
2328 $filename = substr($filename, 0, strpos($filename, "?"));
2331 $timestamp = filemtime($filename);
2333 if ($query) $timestamp .= "&$query";
2335 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2338 function calculate_dep_timestamp() {
2339 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2343 foreach ($files as $file) {
2344 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2350 function T_js_decl($s1, $s2) {
2352 $s1 = preg_replace("/\n/", "", $s1);
2353 $s2 = preg_replace("/\n/", "", $s2);
2355 $s1 = preg_replace("/\"/", "\\\"", $s1);
2356 $s2 = preg_replace("/\"/", "\\\"", $s2);
2358 return "T_messages[\"$s1\"] = \"$s2\";\n";
2362 function init_js_translations() {
2364 print 'var T_messages = new Object();
2367 if (T_messages[msg]) {
2368 return T_messages[msg];
2374 function ngettext(msg1, msg2, n) {
2375 return __((parseInt(n) > 1) ? msg2 : msg1);
2378 $l10n = _get_reader();
2380 for ($i = 0; $i < $l10n->total
; $i++
) {
2381 $orig = $l10n->get_original_string($i);
2382 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2383 $key = explode(chr(0), $orig);
2384 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2385 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2387 $translation = __($orig);
2388 print T_js_decl($orig, $translation);
2393 function label_to_feed_id($label) {
2394 return LABEL_BASE_INDEX
- 1 - abs($label);
2397 function feed_to_label_id($feed) {
2398 return LABEL_BASE_INDEX
- 1 +
abs($feed);
2401 function get_theme_path($theme) {
2402 $check = "themes/$theme";
2403 if (file_exists($check)) return $check;
2405 $check = "themes.local/$theme";
2406 if (file_exists($check)) return $check;
2409 function theme_valid($theme) {
2410 if ($theme == "default.css" ||
$theme == "night.css") return true; // needed for array_filter
2411 $file = "themes/" . basename($theme);
2413 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2415 if (file_exists($file) && is_readable($file)) {
2416 $fh = fopen($file, "r");
2419 $header = fgets($fh);
2422 return strpos($header, "supports-version:" . VERSION_STATIC
) !== FALSE;
2429 function error_json($code) {
2430 require_once "errors.php";
2432 @$message = $ERRORS[$code];
2434 return json_encode(array("error" =>
2435 array("code" => $code, "message" => $message)));
2439 function abs_to_rel_path($dir) {
2440 $tmp = str_replace(dirname(__DIR__
), "", $dir);
2442 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);