]>
git.wh0rd.org - tt-rss.git/blob - include/functions2.php
f31a619420fc3f6f636caf1c8116d039b1759a89
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
("http://tt-rss.org/version.json");
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() {
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
&& $_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();
1040 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1041 $xpath = new DOMXPath($doc);
1042 $entries = $xpath->query('//*');
1044 foreach ($entries as $entry) {
1045 if (!in_array($entry->nodeName
, $allowed_elements)) {
1046 $entry->parentNode
->removeChild($entry);
1049 if ($entry->hasAttributes()) {
1050 $attrs_to_remove = array();
1052 foreach ($entry->attributes
as $attr) {
1054 if (strpos($attr->nodeName
, 'on') === 0) {
1055 array_push($attrs_to_remove, $attr);
1058 if (in_array($attr->nodeName
, $disallowed_attributes)) {
1059 array_push($attrs_to_remove, $attr);
1063 foreach ($attrs_to_remove as $attr) {
1064 $entry->removeAttributeNode($attr);
1072 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
1074 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1075 if (count($ids) == 0) return;
1079 foreach ($ids as $id) {
1080 array_push($tmp_ids, "ref_id = '$id'");
1083 $ids_qpart = join(" OR ", $tmp_ids);
1086 db_query("UPDATE ttrss_user_entries SET
1087 unread = false,last_read = NOW()
1088 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1089 } else if ($cmode == 1) {
1090 db_query("UPDATE ttrss_user_entries SET
1092 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1094 db_query("UPDATE ttrss_user_entries SET
1095 unread = NOT unread,last_read = NOW()
1096 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1101 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
1102 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1104 while ($line = db_fetch_assoc($result)) {
1105 ccache_update($line["feed_id"], $owner_uid);
1109 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
1111 $a_id = db_escape_string($id);
1113 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1115 $query = "SELECT DISTINCT tag_name,
1116 owner_uid as owner FROM
1117 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
1118 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
1122 /* check cache first */
1124 if ($tag_cache === false) {
1125 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
1126 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1128 if (db_num_rows($result) != 0)
1129 $tag_cache = db_fetch_result($result, 0, "tag_cache");
1133 $tags = explode(",", $tag_cache);
1136 /* do it the hard way */
1138 $tmp_result = db_query($query);
1140 while ($tmp_line = db_fetch_assoc($tmp_result)) {
1141 array_push($tags, $tmp_line["tag_name"]);
1144 /* update the cache */
1146 $tags_str = db_escape_string(join(",", $tags));
1148 db_query("UPDATE ttrss_user_entries
1149 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
1150 AND owner_uid = $owner_uid");
1156 function trim_array($array) {
1158 array_walk($tmp, 'trim');
1162 function tag_is_valid($tag) {
1163 if ($tag == '') return false;
1164 if (is_numeric($tag)) return false;
1165 if (mb_strlen($tag) > 250) return false;
1167 if (!$tag) return false;
1172 function render_login_form() {
1173 header('Cache-Control: public');
1175 require_once "login_form.php";
1179 function format_warning($msg, $id = "") {
1180 return "<div class=\"alert\" id=\"$id\">$msg</div>";
1183 function format_notice($msg, $id = "") {
1184 return "<div class=\"alert alert-info\" id=\"$id\">$msg</div>";
1187 function format_error($msg, $id = "") {
1188 return "<div class=\"alert alert-danger\" id=\"$id\">$msg</div>";
1191 function print_notice($msg) {
1192 return print format_notice($msg);
1195 function print_warning($msg) {
1196 return print format_warning($msg);
1199 function print_error($msg) {
1200 return print format_error($msg);
1204 function T_sprintf() {
1205 $args = func_get_args();
1206 return vsprintf(__(array_shift($args)), $args);
1209 function format_inline_player($url, $ctype) {
1213 $url = htmlspecialchars($url);
1215 if (strpos($ctype, "audio/") === 0) {
1217 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1218 $_SESSION["hasMp3"])) {
1220 $entry .= "<audio preload=\"none\" controls>
1221 <source type=\"$ctype\" src=\"$url\"/>
1226 $entry .= "<object type=\"application/x-shockwave-flash\"
1227 data=\"lib/button/musicplayer.swf?song_url=$url\"
1228 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1229 <param name=\"movie\"
1230 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1234 if ($entry) $entry .= " <a target=\"_blank\"
1235 href=\"$url\">" . basename($url) . "</a>";
1243 /* $filename = substr($url, strrpos($url, "/")+1);
1245 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1246 $filename . " (" . $ctype . ")" . "</a>"; */
1250 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1251 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1257 /* we can figure out feed_id from article id anyway, why do we
1258 * pass feed_id here? let's ignore the argument :(*/
1260 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1261 WHERE ref_id = '$id'");
1263 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1265 $rv['feed_id'] = $feed_id;
1267 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1269 if ($mark_as_read) {
1270 $result = db_query("UPDATE ttrss_user_entries
1271 SET unread = false,last_read = NOW()
1272 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1274 ccache_update($feed_id, $owner_uid);
1277 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1278 ".SUBSTRING_FOR_DATE
."(updated,1,16) as updated,
1279 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1280 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1281 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1282 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1288 FROM ttrss_entries,ttrss_user_entries
1289 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1293 $line = db_fetch_assoc($result);
1295 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1296 unset($line["tag_cache"]);
1298 $line["content"] = sanitize($line["content"],
1299 sql_bool_to_bool($line['hide_images']),
1300 $owner_uid, $line["site_url"], false, $line["id"]);
1302 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_RENDER_ARTICLE
) as $p) {
1303 $line = $p->hook_render_article($line);
1306 $num_comments = $line["num_comments"];
1307 $entry_comments = "";
1309 if ($num_comments > 0) {
1310 if ($line["comments"]) {
1311 $comments_url = htmlspecialchars($line["comments"]);
1313 $comments_url = htmlspecialchars($line["link"]);
1315 $entry_comments = "<a class=\"postComments\"
1316 target='_blank' href=\"$comments_url\">$num_comments ".
1317 _ngettext("comment", "comments", $num_comments)."</a>";
1320 if ($line["comments"] && $line["link"] != $line["comments"]) {
1321 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
1326 header("Content-Type: text/html");
1327 $rv['content'] .= "<html><head>
1328 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1329 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1330 stylesheet_tag("css/tt-rss.css").
1331 stylesheet_tag("css/zoom.css").
1332 stylesheet_tag("css/dijit.css")."
1334 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1335 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1337 </head><body id=\"ttrssZoom\">";
1340 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1342 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1344 $entry_author = $line["author"];
1346 if ($entry_author) {
1347 $entry_author = __(" - ") . $entry_author;
1350 $parsed_updated = make_local_datetime($line["updated"], true,
1354 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1356 if ($line["link"]) {
1357 $rv['content'] .= "<div class='postTitle'><a target='_blank'
1358 title=\"".htmlspecialchars($line['title'])."\"
1360 htmlspecialchars($line["link"]) . "\">" .
1361 $line["title"] . "</a>" .
1362 "<span class='author'>$entry_author</span></div>";
1364 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1368 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
1369 "\" target=\"_blank\">".
1370 htmlspecialchars($line["feed_title"])."</a>";
1372 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1374 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1377 $tags_str = format_tags_string($line["tags"], $id);
1378 $tags_str_full = join(", ", $line["tags"]);
1380 if (!$tags_str_full) $tags_str_full = __("no tags");
1382 if (!$entry_comments) $entry_comments = " "; # placeholder
1384 $rv['content'] .= "<div class='postTags' style='float : right'>
1385 <img src='images/tag.png'
1386 class='tagsPic' alt='Tags' title='Tags'> ";
1389 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1390 <a title=\"".__('Edit tags for this article')."\"
1391 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1393 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1394 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1395 position=\"below\">$tags_str_full</div>";
1397 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_BUTTON
) as $p) {
1398 $rv['content'] .= $p->hook_article_button($line);
1402 $tags_str = strip_tags($tags_str);
1403 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1405 $rv['content'] .= "</div>";
1406 $rv['content'] .= "<div clear='both'>";
1408 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_LEFT_BUTTON
) as $p) {
1409 $rv['content'] .= $p->hook_article_left_button($line);
1412 $rv['content'] .= "$entry_comments</div>";
1414 if ($line["orig_feed_id"]) {
1416 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
1417 WHERE id = ".$line["orig_feed_id"] . " AND owner_uid = " . $_SESSION["uid"]);
1419 if (db_num_rows($tmp_result) != 0) {
1421 $rv['content'] .= "<div clear='both'>";
1422 $rv['content'] .= __("Originally from:");
1424 $rv['content'] .= " ";
1426 $tmp_line = db_fetch_assoc($tmp_result);
1428 $rv['content'] .= "<a target='_blank'
1429 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1430 $tmp_line['title'] . "</a>";
1432 $rv['content'] .= " ";
1434 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
1435 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1437 $rv['content'] .= "</div>";
1441 $rv['content'] .= "</div>";
1443 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1444 if ($line['note']) {
1445 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1447 $rv['content'] .= "</div>";
1449 if (!$line['lang']) $line['lang'] = 'en';
1451 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1453 $rv['content'] .= $line["content"];
1456 $rv['content'] .= format_article_enclosures($id,
1457 sql_bool_to_bool($line["always_display_enclosures"]),
1459 sql_bool_to_bool($line["hide_images"]));
1462 $rv['content'] .= "</div>";
1464 $rv['content'] .= "</div>";
1470 <div class='footer'>
1471 <button onclick=\"return window.close()\">".
1472 __("Close this window")."</button></div>";
1473 $rv['content'] .= "</body></html>";
1480 function print_checkpoint($n, $s) {
1481 $ts = microtime(true);
1482 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1486 function sanitize_tag($tag) {
1489 $tag = mb_strtolower($tag, 'utf-8');
1491 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1493 if (DB_TYPE
== "mysql") {
1494 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1500 function get_self_url_prefix() {
1501 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
1502 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
1504 return SELF_URL_PATH
;
1509 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1511 * @return string The Mozilla Firefox feed adding URL.
1513 function add_feed_url() {
1514 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1516 $url_path = get_self_url_prefix() .
1517 "/public.php?op=subscribe&feed_url=%s";
1519 } // function add_feed_url
1521 function encrypt_password($pass, $salt = '', $mode2 = false) {
1522 if ($salt && $mode2) {
1523 return "MODE2:" . hash('sha256', $salt . $pass);
1525 return "SHA1X:" . sha1("$salt:$pass");
1527 return "SHA1:" . sha1($pass);
1529 } // function encrypt_password
1531 function load_filters($feed_id, $owner_uid, $action_id = false) {
1534 $cat_id = (int)getFeedCategory($feed_id);
1537 $null_cat_qpart = "cat_id IS NULL OR";
1539 $null_cat_qpart = "";
1541 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1542 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1544 $check_cats = join(",", array_merge(
1545 getParentCategories($cat_id, $owner_uid),
1548 while ($line = db_fetch_assoc($result)) {
1549 $filter_id = $line["id"];
1551 $result2 = db_query("SELECT
1552 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1553 FROM ttrss_filters2_rules AS r,
1554 ttrss_filter_types AS t
1556 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1557 (feed_id IS NULL OR feed_id = '$feed_id') AND
1558 filter_type = t.id AND filter_id = '$filter_id'");
1563 while ($rule_line = db_fetch_assoc($result2)) {
1564 # print_r($rule_line);
1567 $rule["reg_exp"] = $rule_line["reg_exp"];
1568 $rule["type"] = $rule_line["type_name"];
1569 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1571 array_push($rules, $rule);
1574 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1575 FROM ttrss_filters2_actions AS a,
1576 ttrss_filter_actions AS t
1578 action_id = t.id AND filter_id = '$filter_id'");
1580 while ($action_line = db_fetch_assoc($result2)) {
1581 # print_r($action_line);
1584 $action["type"] = $action_line["type_name"];
1585 $action["param"] = $action_line["action_param"];
1587 array_push($actions, $action);
1592 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1593 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1594 $filter["rules"] = $rules;
1595 $filter["actions"] = $actions;
1597 if (count($rules) > 0 && count($actions) > 0) {
1598 array_push($filters, $filter);
1605 function get_score_pic($score) {
1607 return "score_high.png";
1608 } else if ($score > 0) {
1609 return "score_half_high.png";
1610 } else if ($score < -100) {
1611 return "score_low.png";
1612 } else if ($score < 0) {
1613 return "score_half_low.png";
1615 return "score_neutral.png";
1619 function feed_has_icon($id) {
1620 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
1623 function init_plugins() {
1624 PluginHost
::getInstance()->load(PLUGINS
, PluginHost
::KIND_ALL
);
1629 function format_tags_string($tags, $id) {
1630 if (!is_array($tags) ||
count($tags) == 0) {
1631 return __("no tags");
1633 $maxtags = min(5, count($tags));
1636 for ($i = 0; $i < $maxtags; $i++
) {
1637 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
1640 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1642 if (count($tags) > $maxtags)
1643 $tags_str .= ", …";
1649 function format_article_labels($labels, $id) {
1651 if (!is_array($labels)) return '';
1655 foreach ($labels as $l) {
1656 $labels_str .= sprintf("<span class='hlLabelRef'
1657 style='color : %s; background-color : %s'>%s</span>",
1658 $l[2], $l[3], $l[1]);
1665 function format_article_note($id, $note, $allow_edit = true) {
1667 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1668 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1669 ($allow_edit ?
__('(edit note)') : "")."</div>$note</div>";
1675 function get_feed_category($feed_cat, $parent_cat_id = false) {
1676 if ($parent_cat_id) {
1677 $parent_qpart = "parent_cat = '$parent_cat_id'";
1678 $parent_insert = "'$parent_cat_id'";
1680 $parent_qpart = "parent_cat IS NULL";
1681 $parent_insert = "NULL";
1685 "SELECT id FROM ttrss_feed_categories
1686 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1688 if (db_num_rows($result) == 0) {
1691 return db_fetch_result($result, 0, "id");
1695 function add_feed_category($feed_cat, $parent_cat_id = false) {
1697 if (!$feed_cat) return false;
1701 if ($parent_cat_id) {
1702 $parent_qpart = "parent_cat = '$parent_cat_id'";
1703 $parent_insert = "'$parent_cat_id'";
1705 $parent_qpart = "parent_cat IS NULL";
1706 $parent_insert = "NULL";
1709 $feed_cat = mb_substr($feed_cat, 0, 250);
1712 "SELECT id FROM ttrss_feed_categories
1713 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1715 if (db_num_rows($result) == 0) {
1718 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1719 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1729 function getArticleFeed($id) {
1730 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1731 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1733 if (db_num_rows($result) != 0) {
1734 return db_fetch_result($result, 0, "feed_id");
1741 * Fixes incomplete URLs by prepending "http://".
1742 * Also replaces feed:// with http://, and
1743 * prepends a trailing slash if the url is a domain name only.
1745 * @param string $url Possibly incomplete URL
1747 * @return string Fixed URL.
1749 function fix_url($url) {
1751 // support schema-less urls
1752 if (strpos($url, '//') === 0) {
1753 $url = 'https:' . $url;
1756 if (strpos($url, '://') === false) {
1757 $url = 'http://' . $url;
1758 } else if (substr($url, 0, 5) == 'feed:') {
1759 $url = 'http:' . substr($url, 5);
1762 //prepend slash if the URL has no slash in it
1763 // "http://www.example" -> "http://www.example/"
1764 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
1768 if ($url != "http:///")
1774 function validate_feed_url($url) {
1775 $parts = parse_url($url);
1777 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
1781 function get_article_enclosures($id) {
1783 $query = "SELECT * FROM ttrss_enclosures
1784 WHERE post_id = '$id' AND content_url != ''";
1788 $result = db_query($query);
1790 if (db_num_rows($result) > 0) {
1791 while ($line = db_fetch_assoc($result)) {
1792 array_push($rv, $line);
1799 /* function save_email_address($email) {
1800 // FIXME: implement persistent storage of emails
1802 if (!$_SESSION['stored_emails'])
1803 $_SESSION['stored_emails'] = array();
1805 if (!in_array($email, $_SESSION['stored_emails']))
1806 array_push($_SESSION['stored_emails'], $email);
1810 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1812 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1814 $sql_is_cat = bool_to_sql_bool($is_cat);
1816 $result = db_query("SELECT access_key FROM ttrss_access_keys
1817 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1818 AND owner_uid = " . $owner_uid);
1820 if (db_num_rows($result) == 1) {
1821 return db_fetch_result($result, 0, "access_key");
1823 $key = db_escape_string(uniqid_short());
1825 $result = db_query("INSERT INTO ttrss_access_keys
1826 (access_key, feed_id, is_cat, owner_uid)
1827 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1834 function get_feeds_from_html($url, $content)
1836 $url = fix_url($url);
1837 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
1839 libxml_use_internal_errors(true);
1841 $doc = new DOMDocument();
1842 $doc->loadHTML($content);
1843 $xpath = new DOMXPath($doc);
1844 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
1845 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
1846 $feedUrls = array();
1847 foreach ($entries as $entry) {
1848 if ($entry->hasAttribute('href')) {
1849 $title = $entry->getAttribute('title');
1851 $title = $entry->getAttribute('type');
1853 $feedUrl = rewrite_relative_url(
1854 $baseUrl, $entry->getAttribute('href')
1856 $feedUrls[$feedUrl] = $title;
1862 function is_html($content) {
1863 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
1866 function url_is_html($url, $login = false, $pass = false) {
1867 return is_html(fetch_file_contents($url, false, $login, $pass));
1870 function print_label_select($name, $value, $attributes = "") {
1872 $result = db_query("SELECT caption FROM ttrss_labels2
1873 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1875 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1876 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
1878 while ($line = db_fetch_assoc($result)) {
1880 $issel = ($line["caption"] == $value) ?
"selected=\"1\"" : "";
1882 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1883 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1887 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1894 function format_article_enclosures($id, $always_display_enclosures,
1895 $article_content, $hide_images = false) {
1897 $result = get_article_enclosures($id);
1900 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_FORMAT_ENCLOSURES
) as $plugin) {
1901 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
1902 if (is_array($retval)) {
1904 $result = $retval[1];
1909 unset($retval); // Unset to prevent breaking render if there are no HOOK_RENDER_ENCLOSURE hooks below.
1911 if ($rv === '' && !empty($result)) {
1912 $entries_html = array();
1914 $entries_inline = array();
1916 foreach ($result as $line) {
1918 $url = $line["content_url"];
1919 $ctype = $line["content_type"];
1920 $title = $line["title"];
1921 $width = $line["width"];
1922 $height = $line["height"];
1924 if (!$ctype) $ctype = __("unknown type");
1926 $filename = substr($url, strrpos($url, "/")+
1);
1928 $player = format_inline_player($url, $ctype);
1930 if ($player) array_push($entries_inline, $player);
1932 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1933 # $filename . " (" . $ctype . ")" . "</a>";
1935 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
1936 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1938 array_push($entries_html, $entry);
1942 $entry["type"] = $ctype;
1943 $entry["filename"] = $filename;
1944 $entry["url"] = $url;
1945 $entry["title"] = $title;
1946 $entry["width"] = $width;
1947 $entry["height"] = $height;
1949 array_push($entries, $entry);
1952 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
1953 if ($always_display_enclosures ||
1954 !preg_match("/<img/i", $article_content)) {
1956 foreach ($entries as $entry) {
1958 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_RENDER_ENCLOSURE
) as $plugin)
1959 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
1966 if (preg_match("/image/", $entry["type"]) ||
1967 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
1969 if (!$hide_images) {
1971 if ($entry['height'] > 0)
1972 $encsize .= ' height="' . intval($entry['height']) . '"';
1973 if ($entry['width'] > 0)
1974 $encsize .= ' width="' . intval($entry['width']) . '"';
1976 alt=\"".htmlspecialchars($entry["filename"])."\"
1977 src=\"" .htmlspecialchars($entry["url"]) . "\"
1978 " . $encsize . " /></p>";
1980 $rv .= "<p><a target=\"_blank\"
1981 href=\"".htmlspecialchars($entry["url"])."\"
1982 >" .htmlspecialchars($entry["url"]) . "</a></p>";
1985 if ($entry['title']) {
1986 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
1994 if (count($entries_inline) > 0) {
1995 $rv .= "<hr clear='both'/>";
1996 foreach ($entries_inline as $entry) { $rv .= $entry; };
1997 $rv .= "<hr clear='both'/>";
2000 $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
2001 "<span>" . __('Attachments')."</span>";
2003 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
2005 foreach ($entries as $entry) {
2006 if ($entry["title"])
2007 $title = "— " . truncate_string($entry["title"], 30);
2011 $rv .= "<div onclick='window.open(\"".htmlspecialchars($entry["url"])."\")'
2012 dojoType=\"dijit.MenuItem\">".htmlspecialchars($entry["filename"])."$title</div>";
2023 function getLastArticleId() {
2024 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
2025 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
2027 if (db_num_rows($result) == 1) {
2028 return db_fetch_result($result, 0, "id");
2034 function build_url($parts) {
2035 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2039 * Converts a (possibly) relative URL to a absolute one.
2041 * @param string $url Base URL (i.e. from where the document is)
2042 * @param string $rel_url Possibly relative URL in the document
2044 * @return string Absolute URL
2046 function rewrite_relative_url($url, $rel_url) {
2047 if (strpos($rel_url, "://") !== false) {
2049 } else if (strpos($rel_url, "//") === 0) {
2050 # protocol-relative URL (rare but they exist)
2052 } else if (preg_match("/^[a-z]+:/i", $rel_url)) {
2053 # magnet:, feed:, etc
2055 } else if (strpos($rel_url, "/") === 0) {
2056 $parts = parse_url($url);
2057 $parts['path'] = $rel_url;
2059 return build_url($parts);
2062 $parts = parse_url($url);
2063 if (!isset($parts['path'])) {
2064 $parts['path'] = '/';
2066 $dir = $parts['path'];
2067 if (substr($dir, -1) !== '/') {
2068 $dir = dirname($parts['path']);
2069 $dir !== '/' && $dir .= '/';
2071 $parts['path'] = $dir . $rel_url;
2073 return build_url($parts);
2077 function cleanup_tags($days = 14, $limit = 1000) {
2079 if (DB_TYPE
== "pgsql") {
2080 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2081 } else if (DB_TYPE
== "mysql") {
2082 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2087 while ($limit > 0) {
2090 $query = "SELECT ttrss_tags.id AS id
2091 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2092 WHERE post_int_id = int_id AND $interval_query AND
2093 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2095 $result = db_query($query);
2099 while ($line = db_fetch_assoc($result)) {
2100 array_push($ids, $line['id']);
2103 if (count($ids) > 0) {
2104 $ids = join(",", $ids);
2106 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2107 $tags_deleted +
= db_affected_rows($tmp_result);
2112 $limit -= $limit_part;
2115 return $tags_deleted;
2118 function print_user_stylesheet() {
2119 $value = get_pref('USER_STYLESHEET');
2122 print "<style type=\"text/css\">";
2123 print str_replace("<br/>", "\n", $value);
2129 function filter_to_sql($filter, $owner_uid) {
2132 if (DB_TYPE
== "pgsql")
2135 $reg_qpart = "REGEXP";
2137 foreach ($filter["rules"] AS $rule) {
2138 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2139 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2140 $rule['reg_exp']) !== FALSE;
2142 if ($regexp_valid) {
2144 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2146 switch ($rule["type"]) {
2148 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2149 $rule['reg_exp'] . "')";
2152 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2153 $rule['reg_exp'] . "')";
2156 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2157 $rule['reg_exp'] . "') OR LOWER(" .
2158 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2161 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2162 $rule['reg_exp'] . "')";
2165 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2166 $rule['reg_exp'] . "')";
2169 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2170 $rule['reg_exp'] . "')";
2174 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2176 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2177 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2180 if (isset($rule["cat_id"])) {
2182 if ($rule["cat_id"] > 0) {
2183 $children = getChildCategories($rule["cat_id"], $owner_uid);
2184 array_push($children, $rule["cat_id"]);
2186 $children = join(",", $children);
2188 $cat_qpart = "cat_id IN ($children)";
2190 $cat_qpart = "cat_id IS NULL";
2193 $qpart .= " AND $cat_qpart";
2196 $qpart .= " AND feed_id IS NOT NULL";
2198 array_push($query, "($qpart)");
2203 if (count($query) > 0) {
2204 $fullquery = "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
2206 $fullquery = "(false)";
2209 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2214 if (!function_exists('gzdecode')) {
2215 function gzdecode($string) { // no support for 2nd argument
2216 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2217 base64_encode($string));
2221 function get_random_bytes($length) {
2222 if (function_exists('openssl_random_pseudo_bytes')) {
2223 return openssl_random_pseudo_bytes($length);
2227 for ($i = 0; $i < $length; $i++
)
2228 $output .= chr(mt_rand(0, 255));
2234 function read_stdin() {
2235 $fp = fopen("php://stdin", "r");
2238 $line = trim(fgets($fp));
2246 function tmpdirname($path, $prefix) {
2247 // Use PHP's tmpfile function to create a temporary
2248 // directory name. Delete the file and keep the name.
2249 $tempname = tempnam($path,$prefix);
2253 if (!unlink($tempname))
2259 function getFeedCategory($feed) {
2260 $result = db_query("SELECT cat_id FROM ttrss_feeds
2261 WHERE id = '$feed'");
2263 if (db_num_rows($result) > 0) {
2264 return db_fetch_result($result, 0, "cat_id");
2271 function implements_interface($class, $interface) {
2272 return in_array($interface, class_implements($class));
2275 function get_minified_js($files) {
2276 require_once 'lib/jshrink/Minifier.php';
2280 foreach ($files as $js) {
2281 if (!isset($_GET['debug'])) {
2282 $cached_file = CACHE_DIR
. "/js/".basename($js).".js";
2284 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2286 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2288 if ($header && $contents) {
2289 list($htag, $hversion) = explode(":", $header);
2291 if ($htag == "tt-rss" && $hversion == VERSION
) {
2298 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
2299 file_put_contents($cached_file, "tt-rss:" . VERSION
. "\n" . $minified);
2303 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2310 function stylesheet_tag($filename) {
2311 $timestamp = filemtime($filename);
2313 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2316 function javascript_tag($filename) {
2319 if (!(strpos($filename, "?") === FALSE)) {
2320 $query = substr($filename, strpos($filename, "?")+
1);
2321 $filename = substr($filename, 0, strpos($filename, "?"));
2324 $timestamp = filemtime($filename);
2326 if ($query) $timestamp .= "&$query";
2328 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2331 function calculate_dep_timestamp() {
2332 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2336 foreach ($files as $file) {
2337 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2343 function T_js_decl($s1, $s2) {
2345 $s1 = preg_replace("/\n/", "", $s1);
2346 $s2 = preg_replace("/\n/", "", $s2);
2348 $s1 = preg_replace("/\"/", "\\\"", $s1);
2349 $s2 = preg_replace("/\"/", "\\\"", $s2);
2351 return "T_messages[\"$s1\"] = \"$s2\";\n";
2355 function init_js_translations() {
2357 print 'var T_messages = new Object();
2360 if (T_messages[msg]) {
2361 return T_messages[msg];
2367 function ngettext(msg1, msg2, n) {
2368 return __((parseInt(n) > 1) ? msg2 : msg1);
2371 $l10n = _get_reader();
2373 for ($i = 0; $i < $l10n->total
; $i++
) {
2374 $orig = $l10n->get_original_string($i);
2375 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2376 $key = explode(chr(0), $orig);
2377 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2378 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2380 $translation = __($orig);
2381 print T_js_decl($orig, $translation);
2386 function label_to_feed_id($label) {
2387 return LABEL_BASE_INDEX
- 1 - abs($label);
2390 function feed_to_label_id($feed) {
2391 return LABEL_BASE_INDEX
- 1 +
abs($feed);
2394 function get_theme_path($theme) {
2395 $check = "themes/$theme";
2396 if (file_exists($check)) return $check;
2398 $check = "themes.local/$theme";
2399 if (file_exists($check)) return $check;
2402 function theme_valid($theme) {
2403 if ($theme == "default.css" ||
$theme == "night.css") return true; // needed for array_filter
2404 $file = "themes/" . basename($theme);
2406 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2408 if (file_exists($file) && is_readable($file)) {
2409 $fh = fopen($file, "r");
2412 $header = fgets($fh);
2415 return strpos($header, "supports-version:" . VERSION_STATIC
) !== FALSE;
2422 function error_json($code) {
2423 require_once "errors.php";
2425 @$message = $ERRORS[$code];
2427 return json_encode(array("error" =>
2428 array("code" => $code, "message" => $message)));
2432 function abs_to_rel_path($dir) {
2433 $tmp = str_replace(dirname(__DIR__
), "", $dir);
2435 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);