]>
git.wh0rd.org - tt-rss.git/blob - include/functions2.php
2 function make_init_params() {
5 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
6 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
7 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE",
8 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
10 $params[strtolower($param)] = (int) get_pref($param);
13 $params["icons_url"] = ICONS_URL
;
14 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME
;
15 $params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE");
16 $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT");
17 $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY");
18 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
19 $params["label_base_index"] = (int) LABEL_BASE_INDEX
;
21 $theme = get_pref( "USER_CSS_THEME", false, false);
22 $params["theme"] = theme_valid("$theme") ?
$theme : "";
24 $params["plugins"] = implode(", ", PluginHost
::getInstance()->get_plugin_names());
26 $params["php_platform"] = PHP_OS
;
27 $params["php_version"] = PHP_VERSION
;
29 $params["sanity_checksum"] = sha1(file_get_contents("include/sanity_check.php"));
31 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
32 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
34 $max_feed_id = db_fetch_result($result, 0, "mid");
35 $num_feeds = db_fetch_result($result, 0, "nf");
37 $params["max_feed_id"] = (int) $max_feed_id;
38 $params["num_feeds"] = (int) $num_feeds;
40 $params["hotkeys"] = get_hotkeys_map();
42 $params["csrf_token"] = $_SESSION["csrf_token"];
43 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
45 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE
;
50 function get_hotkeys_info() {
52 __("Navigation") => array(
53 "next_feed" => __("Open next feed"),
54 "prev_feed" => __("Open previous feed"),
55 "next_article" => __("Open next article"),
56 "prev_article" => __("Open previous article"),
57 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
58 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
59 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
60 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
61 "search_dialog" => __("Show search dialog")),
62 __("Article") => array(
63 "toggle_mark" => __("Toggle starred"),
64 "toggle_publ" => __("Toggle published"),
65 "toggle_unread" => __("Toggle unread"),
66 "edit_tags" => __("Edit tags"),
67 "dismiss_selected" => __("Dismiss selected"),
68 "dismiss_read" => __("Dismiss read"),
69 "open_in_new_window" => __("Open in new window"),
70 "catchup_below" => __("Mark below as read"),
71 "catchup_above" => __("Mark above as read"),
72 "article_scroll_down" => __("Scroll down"),
73 "article_scroll_up" => __("Scroll up"),
74 "select_article_cursor" => __("Select article under cursor"),
75 "email_article" => __("Email article"),
76 "close_article" => __("Close/collapse article"),
77 "toggle_expand" => __("Toggle article expansion (combined mode)"),
78 "toggle_widescreen" => __("Toggle widescreen mode"),
79 "toggle_embed_original" => __("Toggle embed original")),
80 __("Article selection") => array(
81 "select_all" => __("Select all articles"),
82 "select_unread" => __("Select unread"),
83 "select_marked" => __("Select starred"),
84 "select_published" => __("Select published"),
85 "select_invert" => __("Invert selection"),
86 "select_none" => __("Deselect everything")),
88 "feed_refresh" => __("Refresh current feed"),
89 "feed_unhide_read" => __("Un/hide read feeds"),
90 "feed_subscribe" => __("Subscribe to feed"),
91 "feed_edit" => __("Edit feed"),
92 "feed_catchup" => __("Mark as read"),
93 "feed_reverse" => __("Reverse headlines"),
94 "feed_debug_update" => __("Debug feed update"),
95 "feed_debug_viewfeed" => __("Debug viewfeed()"),
96 "catchup_all" => __("Mark all feeds as read"),
97 "cat_toggle_collapse" => __("Un/collapse current category"),
98 "toggle_combined_mode" => __("Toggle combined mode"),
99 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
100 __("Go to") => array(
101 "goto_all" => __("All articles"),
102 "goto_fresh" => __("Fresh"),
103 "goto_marked" => __("Starred"),
104 "goto_published" => __("Published"),
105 "goto_tagcloud" => __("Tag cloud"),
106 "goto_prefs" => __("Preferences")),
107 __("Other") => array(
108 "create_label" => __("Create label"),
109 "create_filter" => __("Create filter"),
110 "collapse_sidebar" => __("Un/collapse sidebar"),
111 "help_dialog" => __("Show help dialog"))
114 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_INFO
) as $plugin) {
115 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
121 function get_hotkeys_map() {
123 // "navigation" => array(
126 "n" => "next_article",
127 "p" => "prev_article",
128 "(38)|up" => "prev_article",
129 "(40)|down" => "next_article",
130 // "^(38)|Ctrl-up" => "prev_article_noscroll",
131 // "^(40)|Ctrl-down" => "next_article_noscroll",
132 "(191)|/" => "search_dialog",
133 // "article" => array(
134 "s" => "toggle_mark",
135 "*s" => "toggle_publ",
136 "u" => "toggle_unread",
138 "*d" => "dismiss_selected",
139 "*x" => "dismiss_read",
140 "o" => "open_in_new_window",
141 "c p" => "catchup_below",
142 "c n" => "catchup_above",
143 "*n" => "article_scroll_down",
144 "*p" => "article_scroll_up",
145 "*(38)|Shift+up" => "article_scroll_up",
146 "*(40)|Shift+down" => "article_scroll_down",
147 "a *w" => "toggle_widescreen",
148 "a e" => "toggle_embed_original",
149 "e" => "email_article",
150 "a q" => "close_article",
151 // "article_selection" => array(
152 "a a" => "select_all",
153 "a u" => "select_unread",
154 "a *u" => "select_marked",
155 "a p" => "select_published",
156 "a i" => "select_invert",
157 "a n" => "select_none",
159 "f r" => "feed_refresh",
160 "f a" => "feed_unhide_read",
161 "f s" => "feed_subscribe",
162 "f e" => "feed_edit",
163 "f q" => "feed_catchup",
164 "f x" => "feed_reverse",
165 "f *d" => "feed_debug_update",
166 "f *g" => "feed_debug_viewfeed",
167 "f *c" => "toggle_combined_mode",
168 "f c" => "toggle_cdm_expanded",
169 "*q" => "catchup_all",
170 "x" => "cat_toggle_collapse",
173 "g f" => "goto_fresh",
174 "g s" => "goto_marked",
175 "g p" => "goto_published",
176 "g t" => "goto_tagcloud",
177 "g *p" => "goto_prefs",
179 "(9)|Tab" => "select_article_cursor", // tab
180 "c l" => "create_label",
181 "c f" => "create_filter",
182 "c s" => "collapse_sidebar",
183 "^(191)|Ctrl+/" => "help_dialog",
186 if (get_pref('COMBINED_DISPLAY_MODE')) {
187 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
188 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
191 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_MAP
) as $plugin) {
192 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
197 foreach (array_keys($hotkeys) as $hotkey) {
198 $pair = explode(" ", $hotkey, 2);
200 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
201 array_push($prefixes, $pair[0]);
205 return array($prefixes, $hotkeys);
208 function check_for_update() {
209 if (defined("GIT_VERSION_TIMESTAMP")) {
210 $content = @fetch_file_contents
("http://tt-rss.org/version.json");
213 $content = json_decode($content, true);
215 if ($content && isset($content["changeset"])) {
216 if ((int)GIT_VERSION_TIMESTAMP
< (int)$content["changeset"]["timestamp"] &&
217 GIT_VERSION_HEAD
!= $content["changeset"]["id"]) {
219 return $content["changeset"]["id"];
228 function make_runtime_info() {
231 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
232 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
234 $max_feed_id = db_fetch_result($result, 0, "mid");
235 $num_feeds = db_fetch_result($result, 0, "nf");
237 $data["max_feed_id"] = (int) $max_feed_id;
238 $data["num_feeds"] = (int) $num_feeds;
240 $data['last_article_id'] = getLastArticleId();
241 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
243 $data['dep_ts'] = calculate_dep_timestamp();
244 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
247 if (CHECK_FOR_UPDATES
&& $_SESSION["last_version_check"] +
86400 +
rand(-1000, 1000) < time()) {
248 $update_result = @check_for_update
();
250 $data["update_result"] = $update_result;
252 $_SESSION["last_version_check"] = time();
255 if (file_exists(LOCK_DIRECTORY
. "/update_daemon.lock")) {
257 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
259 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
261 $stamp = (int) @file_get_contents
(LOCK_DIRECTORY
. "/update_daemon.stamp");
264 $stamp_delta = time() - $stamp;
266 if ($stamp_delta > 1800) {
270 $_SESSION["daemon_stamp_check"] = time();
273 $data['daemon_stamp_ok'] = $stamp_check;
275 $stamp_fmt = date("Y.m.d, G:i", $stamp);
277 $data['daemon_stamp'] = $stamp_fmt;
285 function search_to_sql($search, $search_language) {
287 $keywords = str_getcsv(trim($search), " ");
288 $query_keywords = array();
289 $search_words = array();
290 $search_query_leftover = array();
292 if ($search_language)
293 $search_language = db_escape_string(mb_strtolower($search_language));
295 $search_language = "english";
297 foreach ($keywords as $k) {
298 if (strpos($k, "-") === 0) {
305 $commandpair = explode(":", mb_strtolower($k), 2);
307 switch ($commandpair[0]) {
309 if ($commandpair[1]) {
310 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
311 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
313 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
314 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
315 array_push($search_words, $k);
319 if ($commandpair[1]) {
320 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
321 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
323 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
324 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
325 array_push($search_words, $k);
329 if ($commandpair[1]) {
330 if ($commandpair[1] == "true")
331 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
332 else if ($commandpair[1] == "false")
333 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
335 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
336 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
338 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
339 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
340 if (!$not) array_push($search_words, $k);
345 if ($commandpair[1]) {
346 if ($commandpair[1] == "true")
347 array_push($query_keywords, "($not (marked = true))");
349 array_push($query_keywords, "($not (marked = false))");
351 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
352 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
353 if (!$not) array_push($search_words, $k);
357 if ($commandpair[1]) {
358 if ($commandpair[1] == "true")
359 array_push($query_keywords, "($not (published = true))");
361 array_push($query_keywords, "($not (published = false))");
364 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
365 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
366 if (!$not) array_push($search_words, $k);
370 if ($commandpair[1]) {
371 if ($commandpair[1] == "true")
372 array_push($query_keywords, "($not (unread = true))");
374 array_push($query_keywords, "($not (unread = false))");
377 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
378 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
379 if (!$not) array_push($search_words, $k);
383 if (strpos($k, "@") === 0) {
385 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
386 $orig_ts = strtotime(substr($k, 1));
387 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
389 //$k = date("Y-m-d", strtotime(substr($k, 1)));
391 array_push($query_keywords, "(".SUBSTRING_FOR_DATE
."(updated,1,LENGTH('$k')) $not = '$k')");
394 if (DB_TYPE
== "pgsql") {
395 $k = mb_strtolower($k);
396 array_push($search_query_leftover, $not ?
"!$k" : $k);
398 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
399 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
402 if (!$not) array_push($search_words, $k);
407 if (count($search_query_leftover) > 0) {
408 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
410 if (DB_TYPE
== "pgsql") {
411 array_push($query_keywords,
412 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
417 $search_query_part = implode("AND", $query_keywords);
419 return array($search_query_part, $search_words);
422 function getParentCategories($cat, $owner_uid) {
425 $result = db_query("SELECT parent_cat FROM ttrss_feed_categories
426 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
428 while ($line = db_fetch_assoc($result)) {
429 array_push($rv, $line["parent_cat"]);
430 $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid));
436 function getChildCategories($cat, $owner_uid) {
439 $result = db_query("SELECT id FROM ttrss_feed_categories
440 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
442 while ($line = db_fetch_assoc($result)) {
443 array_push($rv, $line["id"]);
444 $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid));
450 function queryFeedHeadlines($params) {
452 $feed = $params["feed"];
453 $limit = isset($params["limit"]) ?
$params["limit"] : 30;
454 $view_mode = $params["view_mode"];
455 $cat_view = isset($params["cat_view"]) ?
$params["cat_view"] : false;
456 $search = isset($params["search"]) ?
$params["search"] : false;
457 $search_language = isset($params["search_language"]) ?
$params["search_language"] : "";
458 $override_order = isset($params["override_order"]) ?
$params["override_order"] : false;
459 $offset = isset($params["offset"]) ?
$params["offset"] : 0;
460 $owner_uid = isset($params["owner_uid"]) ?
$params["owner_uid"] : $_SESSION["uid"];
461 $since_id = isset($params["since_id"]) ?
$params["since_id"] : 0;
462 $include_children = isset($params["include_children"]) ?
$params["include_children"] : false;
463 $ignore_vfeed_group = isset($params["ignore_vfeed_group"]) ?
$params["ignore_vfeed_group"] : false;
464 $override_strategy = isset($params["override_strategy"]) ?
$params["override_strategy"] : false;
465 $override_vfeed = isset($params["override_vfeed"]) ?
$params["override_vfeed"] : false;
466 $start_ts = isset($params["start_ts"]) ?
$params["start_ts"] : false;
467 $check_first_id = isset($params["check_first_id"]) ?
$params["check_first_id"] : false;
468 $skip_first_id_check = isset($params["skip_first_id_check"]) ?
$params["skip_first_id_check"] : false;
470 $ext_tables_part = "";
471 $query_strategy_part = "";
473 $search_words = array();
476 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SEARCH
) as $plugin) {
477 list($search_query_part, $search_words) = $plugin->hook_search($search);
481 // fall back in case of no plugins
482 if (!$search_query_part) {
483 list($search_query_part, $search_words) = search_to_sql($search, $search_language);
485 $search_query_part .= " AND ";
487 $search_query_part = "";
491 $since_id_part = "ttrss_entries.id > $since_id AND ";
496 $view_query_part = "";
498 if ($view_mode == "adaptive") {
500 $view_query_part = " ";
501 } else if ($feed != -1) {
503 $unread = getFeedUnread($feed, $cat_view);
505 if ($cat_view && $feed > 0 && $include_children)
506 $unread +
= getCategoryChildrenUnread($feed);
509 $view_query_part = " unread = true AND ";
514 if ($view_mode == "marked") {
515 $view_query_part = " marked = true AND ";
518 if ($view_mode == "has_note") {
519 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
522 if ($view_mode == "published") {
523 $view_query_part = " published = true AND ";
526 if ($view_mode == "unread" && $feed != -6) {
527 $view_query_part = " unread = true AND ";
531 $limit_query_part = "LIMIT " . $limit;
534 $allow_archived = false;
536 $vfeed_query_part = "";
539 if (!is_numeric($feed)) {
540 $query_strategy_part = "true";
541 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
542 id = feed_id) as feed_title,";
543 } else if ($feed > 0) {
548 if ($include_children) {
550 $subcats = getChildCategories($feed, $owner_uid);
552 array_push($subcats, $feed);
553 $query_strategy_part = "cat_id IN (".
554 implode(",", $subcats).")";
557 $query_strategy_part = "cat_id = '$feed'";
561 $query_strategy_part = "cat_id IS NULL";
564 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
567 $query_strategy_part = "feed_id = '$feed'";
569 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
570 $query_strategy_part = "feed_id IS NULL";
571 $allow_archived = true;
572 } else if ($feed == 0 && $cat_view) { // uncategorized
573 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
574 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
575 } else if ($feed == -1) { // starred virtual feed
576 $query_strategy_part = "marked = true";
577 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
578 $allow_archived = true;
580 if (!$override_order) {
581 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
584 } else if ($feed == -2) { // published virtual feed OR labels category
587 $query_strategy_part = "published = true";
588 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
589 $allow_archived = true;
591 if (!$override_order) {
592 $override_order = "last_published DESC, date_entered DESC, updated DESC";
596 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
598 $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,";
600 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
601 ttrss_user_labels2.article_id = ref_id";
604 } else if ($feed == -6) { // recently read
605 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
607 if (DB_TYPE
== "pgsql") {
608 $query_strategy_part .= " AND last_read > NOW() - INTERVAL '1 DAY' ";
610 $query_strategy_part .= " AND last_read > DATE_SUB(NOW(), INTERVAL 1 DAY) ";
613 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
614 $allow_archived = true;
615 $ignore_vfeed_group = true;
617 if (!$override_order) $override_order = "last_read DESC";
619 } else if ($feed == -3) { // fresh virtual feed
620 $query_strategy_part = "unread = true AND score >= 0";
622 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
624 if (DB_TYPE
== "pgsql") {
625 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
627 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
630 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
631 } else if ($feed == -4) { // all articles virtual feed
632 $allow_archived = true;
633 $query_strategy_part = "true";
634 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
635 } else if ($feed <= LABEL_BASE_INDEX
) { // labels
636 $label_id = feed_to_label_id($feed);
638 $query_strategy_part = "label_id = '$label_id' AND
639 ttrss_labels2.id = ttrss_user_labels2.label_id AND
640 ttrss_user_labels2.article_id = ref_id";
642 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
643 $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,";
644 $allow_archived = true;
647 $query_strategy_part = "true";
650 $order_by = "score DESC, date_entered DESC, updated DESC";
652 if ($override_order) {
653 $order_by = $override_order;
656 if ($override_strategy) {
657 $query_strategy_part = $override_strategy;
660 if ($override_vfeed) {
661 $vfeed_query_part = $override_vfeed;
667 $feed_title = T_sprintf("Search results: %s", $search);
670 $feed_title = getCategoryTitle($feed);
672 if (is_numeric($feed) && $feed > 0) {
673 $result = db_query("SELECT title,site_url,last_error,last_updated
674 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
676 $feed_title = db_fetch_result($result, 0, "title");
677 $feed_site_url = db_fetch_result($result, 0, "site_url");
678 $last_error = db_fetch_result($result, 0, "last_error");
679 $last_updated = db_fetch_result($result, 0, "last_updated");
681 $feed_title = getFeedTitle($feed);
687 $content_query_part = "content, ";
689 if ($limit_query_part) {
690 $offset_query_part = "OFFSET $offset";
692 $offset_query_part = "";
695 if (is_numeric($feed)) {
696 // proper override_order applied above
697 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
698 if (!$override_order) {
699 $order_by = "ttrss_feeds.title, $order_by";
701 $order_by = "ttrss_feeds.title, $override_order";
705 if (!$allow_archived) {
706 $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id),ttrss_feeds";
707 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
710 $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id)
711 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
714 if ($vfeed_query_part) $vfeed_query_part .= "favicon_avg_color,";
717 $start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts));
718 $start_ts_query_part = "date_entered >= '$start_ts_formatted' AND";
720 $start_ts_query_part = "";
724 $first_id_query_strategy_part = $query_strategy_part;
727 $first_id_query_strategy_part = "true";
729 if (DB_TYPE
== "pgsql") {
730 $sanity_interval_qpart = "date_entered >= NOW() - INTERVAL '1 hour' AND";
732 $sanity_interval_qpart = "date_entered >= DATE_SUB(NOW(), INTERVAL 1 hour) AND";
735 if (!$search && !$skip_first_id_check) {
736 // if previous topmost article id changed that means our current pagination is no longer valid
737 $query = "SELECT DISTINCT
754 ttrss_user_entries.owner_uid = '$owner_uid' AND
758 $sanity_interval_qpart
759 $first_id_query_strategy_part ORDER BY $order_by LIMIT 1";
761 if ($_REQUEST["debug"]) {
765 $result = db_query($query);
766 if ($result && db_num_rows($result) > 0) {
767 $first_id = (int)db_fetch_result($result, 0, "id");
769 if ($offset > 0 && $first_id && $check_first_id && $first_id != $check_first_id) {
770 return array(-1, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
775 $query = "SELECT DISTINCT
778 ttrss_entries.id,ttrss_entries.title,
782 always_display_enclosures,
791 unread,feed_id,marked,published,link,last_read,orig_feed_id,
792 last_marked, last_published,
800 ttrss_user_entries.owner_uid = '$owner_uid' AND
805 $query_strategy_part ORDER BY $order_by
806 $limit_query_part $offset_query_part";
808 if ($_REQUEST["debug"]) print $query;
810 $result = db_query($query);
815 $query = "SELECT DISTINCT
819 ttrss_entries.id as id,
835 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images,
836 last_marked, last_published,
841 FROM ttrss_entries, ttrss_user_entries, ttrss_tags
843 ref_id = ttrss_entries.id AND
844 ttrss_user_entries.owner_uid = $owner_uid AND
845 post_int_id = int_id AND
846 tag_name = '$feed' AND
849 $query_strategy_part ORDER BY $order_by
850 $limit_query_part $offset_query_part";
852 if ($_REQUEST["debug"]) print $query;
854 $result = db_query($query);
857 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
861 function iframe_whitelisted($entry) {
862 $whitelist = array("youtube.com", "youtu.be", "vimeo.com", "player.vimeo.com");
864 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST
);
867 foreach ($whitelist as $w) {
868 if ($src == $w ||
$src == "www.$w")
876 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
877 if (!$owner) $owner = $_SESSION["uid"];
879 $res = trim($str); if (!$res) return '';
881 $charset_hack = '<head>
882 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
885 $res = trim($res); if (!$res) return '';
887 libxml_use_internal_errors(true);
889 $doc = new DOMDocument();
890 $doc->loadHTML($charset_hack . $res);
891 $xpath = new DOMXPath($doc);
893 $entries = $xpath->query('(//a[@href]|//img[@src])');
895 $ttrss_uses_https = parse_url(get_self_url_prefix(), PHP_URL_SCHEME
) === 'https';
897 foreach ($entries as $entry) {
901 if ($entry->hasAttribute('href')) {
902 $entry->setAttribute('href',
903 rewrite_relative_url($site_url, $entry->getAttribute('href')));
905 $entry->setAttribute('rel', 'noreferrer');
908 if ($entry->hasAttribute('src')) {
909 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
911 $cached_filename = CACHE_DIR
. '/images/' . sha1($src) . '.png';
913 if (file_exists($cached_filename)) {
914 $src = SELF_URL_PATH
. '/public.php?op=cached_image&hash=' . sha1($src);
917 $entry->setAttribute('src', $src);
920 if ($entry->nodeName
== 'img') {
921 if ($entry->hasAttribute('src')) {
922 $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME
) === 'https';
924 if ($ttrss_uses_https && !$is_https_url) {
926 if ($entry->hasAttribute('srcset')) {
927 $entry->removeAttribute('srcset');
930 if ($entry->hasAttribute('sizes')) {
931 $entry->removeAttribute('sizes');
936 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
937 $force_remove_images ||
$_SESSION["bw_limit"]) {
939 $p = $doc->createElement('p');
941 $a = $doc->createElement('a');
942 $a->setAttribute('href', $entry->getAttribute('src'));
944 $a->appendChild(new DOMText($entry->getAttribute('src')));
945 $a->setAttribute('target', '_blank');
949 $entry->parentNode
->replaceChild($p, $entry);
954 if (strtolower($entry->nodeName
) == "a") {
955 $entry->setAttribute("target", "_blank");
959 $entries = $xpath->query('//iframe');
960 foreach ($entries as $entry) {
961 if (!iframe_whitelisted($entry)) {
962 $entry->setAttribute('sandbox', 'allow-scripts');
964 if ($_SERVER['HTTPS'] == "on") {
965 $entry->setAttribute("src",
966 str_replace("http://", "https://",
967 $entry->getAttribute("src")));
972 $allowed_elements = array('a', 'address', 'audio', 'article', 'aside',
973 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
974 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
975 'data', 'dd', 'del', 'details', 'description', 'div', 'dl', 'font',
976 'dt', 'em', 'footer', 'figure', 'figcaption',
977 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
978 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
979 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
980 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
981 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
982 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
984 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
986 $disallowed_attributes = array('id', 'style', 'class');
988 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SANITIZE
) as $plugin) {
989 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
990 if (is_array($retval)) {
992 $allowed_elements = $retval[1];
993 $disallowed_attributes = $retval[2];
999 $doc->removeChild($doc->firstChild
); //remove doctype
1000 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
1002 if ($highlight_words) {
1003 foreach ($highlight_words as $word) {
1005 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
1007 $elements = $xpath->query("//*/text()");
1009 foreach ($elements as $child) {
1011 $fragment = $doc->createDocumentFragment();
1012 $text = $child->textContent
;
1014 while (($pos = mb_stripos($text, $word)) !== false) {
1015 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1016 $word = mb_substr($text, $pos, mb_strlen($word));
1017 $highlight = $doc->createElement('span');
1018 $highlight->appendChild(new DomText($word));
1019 $highlight->setAttribute('class', 'highlight');
1020 $fragment->appendChild($highlight);
1021 $text = mb_substr($text, $pos +
mb_strlen($word));
1024 if (!empty($text)) $fragment->appendChild(new DomText($text));
1026 $child->parentNode
->replaceChild($fragment, $child);
1031 $res = $doc->saveHTML();
1036 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1037 $xpath = new DOMXPath($doc);
1038 $entries = $xpath->query('//*');
1040 foreach ($entries as $entry) {
1041 if (!in_array($entry->nodeName
, $allowed_elements)) {
1042 $entry->parentNode
->removeChild($entry);
1045 if ($entry->hasAttributes()) {
1046 $attrs_to_remove = array();
1048 foreach ($entry->attributes
as $attr) {
1050 if (strpos($attr->nodeName
, 'on') === 0) {
1051 array_push($attrs_to_remove, $attr);
1054 if (in_array($attr->nodeName
, $disallowed_attributes)) {
1055 array_push($attrs_to_remove, $attr);
1059 foreach ($attrs_to_remove as $attr) {
1060 $entry->removeAttributeNode($attr);
1068 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
1070 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1071 if (count($ids) == 0) return;
1075 foreach ($ids as $id) {
1076 array_push($tmp_ids, "ref_id = '$id'");
1079 $ids_qpart = join(" OR ", $tmp_ids);
1082 db_query("UPDATE ttrss_user_entries SET
1083 unread = false,last_read = NOW()
1084 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1085 } else if ($cmode == 1) {
1086 db_query("UPDATE ttrss_user_entries SET
1088 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1090 db_query("UPDATE ttrss_user_entries SET
1091 unread = NOT unread,last_read = NOW()
1092 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1097 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
1098 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1100 while ($line = db_fetch_assoc($result)) {
1101 ccache_update($line["feed_id"], $owner_uid);
1105 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
1107 $a_id = db_escape_string($id);
1109 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1111 $query = "SELECT DISTINCT tag_name,
1112 owner_uid as owner FROM
1113 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
1114 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
1118 /* check cache first */
1120 if ($tag_cache === false) {
1121 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
1122 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1124 if (db_num_rows($result) != 0)
1125 $tag_cache = db_fetch_result($result, 0, "tag_cache");
1129 $tags = explode(",", $tag_cache);
1132 /* do it the hard way */
1134 $tmp_result = db_query($query);
1136 while ($tmp_line = db_fetch_assoc($tmp_result)) {
1137 array_push($tags, $tmp_line["tag_name"]);
1140 /* update the cache */
1142 $tags_str = db_escape_string(join(",", $tags));
1144 db_query("UPDATE ttrss_user_entries
1145 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
1146 AND owner_uid = $owner_uid");
1152 function trim_array($array) {
1154 array_walk($tmp, 'trim');
1158 function tag_is_valid($tag) {
1159 if ($tag == '') return false;
1160 if (is_numeric($tag)) return false;
1161 if (mb_strlen($tag) > 250) return false;
1163 if (!$tag) return false;
1168 function render_login_form() {
1169 header('Cache-Control: public');
1171 require_once "login_form.php";
1175 function format_warning($msg, $id = "") {
1176 return "<div class=\"alert\" id=\"$id\">$msg</div>";
1179 function format_notice($msg, $id = "") {
1180 return "<div class=\"alert alert-info\" id=\"$id\">$msg</div>";
1183 function format_error($msg, $id = "") {
1184 return "<div class=\"alert alert-danger\" id=\"$id\">$msg</div>";
1187 function print_notice($msg) {
1188 return print format_notice($msg);
1191 function print_warning($msg) {
1192 return print format_warning($msg);
1195 function print_error($msg) {
1196 return print format_error($msg);
1200 function T_sprintf() {
1201 $args = func_get_args();
1202 return vsprintf(__(array_shift($args)), $args);
1205 function format_inline_player($url, $ctype) {
1209 $url = htmlspecialchars($url);
1211 if (strpos($ctype, "audio/") === 0) {
1213 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1214 $_SESSION["hasMp3"])) {
1216 $entry .= "<audio preload=\"none\" controls>
1217 <source type=\"$ctype\" src=\"$url\"/>
1222 $entry .= "<object type=\"application/x-shockwave-flash\"
1223 data=\"lib/button/musicplayer.swf?song_url=$url\"
1224 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1225 <param name=\"movie\"
1226 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1230 if ($entry) $entry .= " <a target=\"_blank\"
1231 href=\"$url\">" . basename($url) . "</a>";
1239 /* $filename = substr($url, strrpos($url, "/")+1);
1241 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1242 $filename . " (" . $ctype . ")" . "</a>"; */
1246 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1247 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1253 /* we can figure out feed_id from article id anyway, why do we
1254 * pass feed_id here? let's ignore the argument :(*/
1256 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1257 WHERE ref_id = '$id'");
1259 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1261 $rv['feed_id'] = $feed_id;
1263 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1265 if ($mark_as_read) {
1266 $result = db_query("UPDATE ttrss_user_entries
1267 SET unread = false,last_read = NOW()
1268 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1270 ccache_update($feed_id, $owner_uid);
1273 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1274 ".SUBSTRING_FOR_DATE
."(updated,1,16) as updated,
1275 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1276 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1277 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1278 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1284 FROM ttrss_entries,ttrss_user_entries
1285 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1289 $line = db_fetch_assoc($result);
1291 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1292 unset($line["tag_cache"]);
1294 $line["content"] = sanitize($line["content"],
1295 sql_bool_to_bool($line['hide_images']),
1296 $owner_uid, $line["site_url"], false, $line["id"]);
1298 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_RENDER_ARTICLE
) as $p) {
1299 $line = $p->hook_render_article($line);
1302 $num_comments = $line["num_comments"];
1303 $entry_comments = "";
1305 if ($num_comments > 0) {
1306 if ($line["comments"]) {
1307 $comments_url = htmlspecialchars($line["comments"]);
1309 $comments_url = htmlspecialchars($line["link"]);
1311 $entry_comments = "<a class=\"postComments\"
1312 target='_blank' href=\"$comments_url\">$num_comments ".
1313 _ngettext("comment", "comments", $num_comments)."</a>";
1316 if ($line["comments"] && $line["link"] != $line["comments"]) {
1317 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
1322 header("Content-Type: text/html");
1323 $rv['content'] .= "<html><head>
1324 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1325 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1326 stylesheet_tag("css/tt-rss.css").
1327 stylesheet_tag("css/zoom.css").
1328 stylesheet_tag("css/dijit.css")."
1330 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1331 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1333 </head><body id=\"ttrssZoom\">";
1336 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1338 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1340 $entry_author = $line["author"];
1342 if ($entry_author) {
1343 $entry_author = __(" - ") . $entry_author;
1346 $parsed_updated = make_local_datetime($line["updated"], true,
1350 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1352 if ($line["link"]) {
1353 $rv['content'] .= "<div class='postTitle'><a target='_blank'
1354 title=\"".htmlspecialchars($line['title'])."\"
1356 htmlspecialchars($line["link"]) . "\">" .
1357 $line["title"] . "</a>" .
1358 "<span class='author'>$entry_author</span></div>";
1360 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1364 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
1365 "\" target=\"_blank\">".
1366 htmlspecialchars($line["feed_title"])."</a>";
1368 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1370 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1373 $tags_str = format_tags_string($line["tags"], $id);
1374 $tags_str_full = join(", ", $line["tags"]);
1376 if (!$tags_str_full) $tags_str_full = __("no tags");
1378 if (!$entry_comments) $entry_comments = " "; # placeholder
1380 $rv['content'] .= "<div class='postTags' style='float : right'>
1381 <img src='images/tag.png'
1382 class='tagsPic' alt='Tags' title='Tags'> ";
1385 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1386 <a title=\"".__('Edit tags for this article')."\"
1387 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1389 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1390 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1391 position=\"below\">$tags_str_full</div>";
1393 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_BUTTON
) as $p) {
1394 $rv['content'] .= $p->hook_article_button($line);
1398 $tags_str = strip_tags($tags_str);
1399 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1401 $rv['content'] .= "</div>";
1402 $rv['content'] .= "<div clear='both'>";
1404 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_LEFT_BUTTON
) as $p) {
1405 $rv['content'] .= $p->hook_article_left_button($line);
1408 $rv['content'] .= "$entry_comments</div>";
1410 if ($line["orig_feed_id"]) {
1412 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
1413 WHERE id = ".$line["orig_feed_id"] . " AND owner_uid = " . $_SESSION["uid"]);
1415 if (db_num_rows($tmp_result) != 0) {
1417 $rv['content'] .= "<div clear='both'>";
1418 $rv['content'] .= __("Originally from:");
1420 $rv['content'] .= " ";
1422 $tmp_line = db_fetch_assoc($tmp_result);
1424 $rv['content'] .= "<a target='_blank'
1425 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1426 $tmp_line['title'] . "</a>";
1428 $rv['content'] .= " ";
1430 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
1431 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1433 $rv['content'] .= "</div>";
1437 $rv['content'] .= "</div>";
1439 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1440 if ($line['note']) {
1441 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1443 $rv['content'] .= "</div>";
1445 if (!$line['lang']) $line['lang'] = 'en';
1447 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1449 $rv['content'] .= $line["content"];
1452 $rv['content'] .= format_article_enclosures($id,
1453 sql_bool_to_bool($line["always_display_enclosures"]),
1455 sql_bool_to_bool($line["hide_images"]));
1458 $rv['content'] .= "</div>";
1460 $rv['content'] .= "</div>";
1466 <div class='footer'>
1467 <button onclick=\"return window.close()\">".
1468 __("Close this window")."</button></div>";
1469 $rv['content'] .= "</body></html>";
1476 function print_checkpoint($n, $s) {
1477 $ts = microtime(true);
1478 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1482 function sanitize_tag($tag) {
1485 $tag = mb_strtolower($tag, 'utf-8');
1487 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1489 if (DB_TYPE
== "mysql") {
1490 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1496 function get_self_url_prefix() {
1497 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
1498 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
1500 return SELF_URL_PATH
;
1505 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1507 * @return string The Mozilla Firefox feed adding URL.
1509 function add_feed_url() {
1510 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1512 $url_path = get_self_url_prefix() .
1513 "/public.php?op=subscribe&feed_url=%s";
1515 } // function add_feed_url
1517 function encrypt_password($pass, $salt = '', $mode2 = false) {
1518 if ($salt && $mode2) {
1519 return "MODE2:" . hash('sha256', $salt . $pass);
1521 return "SHA1X:" . sha1("$salt:$pass");
1523 return "SHA1:" . sha1($pass);
1525 } // function encrypt_password
1527 function load_filters($feed_id, $owner_uid, $action_id = false) {
1530 $cat_id = (int)getFeedCategory($feed_id);
1533 $null_cat_qpart = "cat_id IS NULL OR";
1535 $null_cat_qpart = "";
1537 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1538 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1540 $check_cats = join(",", array_merge(
1541 getParentCategories($cat_id, $owner_uid),
1544 while ($line = db_fetch_assoc($result)) {
1545 $filter_id = $line["id"];
1547 $result2 = db_query("SELECT
1548 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1549 FROM ttrss_filters2_rules AS r,
1550 ttrss_filter_types AS t
1552 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1553 (feed_id IS NULL OR feed_id = '$feed_id') AND
1554 filter_type = t.id AND filter_id = '$filter_id'");
1559 while ($rule_line = db_fetch_assoc($result2)) {
1560 # print_r($rule_line);
1563 $rule["reg_exp"] = $rule_line["reg_exp"];
1564 $rule["type"] = $rule_line["type_name"];
1565 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1567 array_push($rules, $rule);
1570 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1571 FROM ttrss_filters2_actions AS a,
1572 ttrss_filter_actions AS t
1574 action_id = t.id AND filter_id = '$filter_id'");
1576 while ($action_line = db_fetch_assoc($result2)) {
1577 # print_r($action_line);
1580 $action["type"] = $action_line["type_name"];
1581 $action["param"] = $action_line["action_param"];
1583 array_push($actions, $action);
1588 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1589 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1590 $filter["rules"] = $rules;
1591 $filter["actions"] = $actions;
1593 if (count($rules) > 0 && count($actions) > 0) {
1594 array_push($filters, $filter);
1601 function get_score_pic($score) {
1603 return "score_high.png";
1604 } else if ($score > 0) {
1605 return "score_half_high.png";
1606 } else if ($score < -100) {
1607 return "score_low.png";
1608 } else if ($score < 0) {
1609 return "score_half_low.png";
1611 return "score_neutral.png";
1615 function feed_has_icon($id) {
1616 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
1619 function init_plugins() {
1620 PluginHost
::getInstance()->load(PLUGINS
, PluginHost
::KIND_ALL
);
1625 function format_tags_string($tags, $id) {
1626 if (!is_array($tags) ||
count($tags) == 0) {
1627 return __("no tags");
1629 $maxtags = min(5, count($tags));
1632 for ($i = 0; $i < $maxtags; $i++
) {
1633 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
1636 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1638 if (count($tags) > $maxtags)
1639 $tags_str .= ", …";
1645 function format_article_labels($labels, $id) {
1647 if (!is_array($labels)) return '';
1651 foreach ($labels as $l) {
1652 $labels_str .= sprintf("<span class='hlLabelRef'
1653 style='color : %s; background-color : %s'>%s</span>",
1654 $l[2], $l[3], $l[1]);
1661 function format_article_note($id, $note, $allow_edit = true) {
1663 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1664 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1665 ($allow_edit ?
__('(edit note)') : "")."</div>$note</div>";
1671 function get_feed_category($feed_cat, $parent_cat_id = false) {
1672 if ($parent_cat_id) {
1673 $parent_qpart = "parent_cat = '$parent_cat_id'";
1674 $parent_insert = "'$parent_cat_id'";
1676 $parent_qpart = "parent_cat IS NULL";
1677 $parent_insert = "NULL";
1681 "SELECT id FROM ttrss_feed_categories
1682 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1684 if (db_num_rows($result) == 0) {
1687 return db_fetch_result($result, 0, "id");
1691 function add_feed_category($feed_cat, $parent_cat_id = false) {
1693 if (!$feed_cat) return false;
1697 if ($parent_cat_id) {
1698 $parent_qpart = "parent_cat = '$parent_cat_id'";
1699 $parent_insert = "'$parent_cat_id'";
1701 $parent_qpart = "parent_cat IS NULL";
1702 $parent_insert = "NULL";
1705 $feed_cat = mb_substr($feed_cat, 0, 250);
1708 "SELECT id FROM ttrss_feed_categories
1709 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1711 if (db_num_rows($result) == 0) {
1714 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1715 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1725 function getArticleFeed($id) {
1726 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1727 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1729 if (db_num_rows($result) != 0) {
1730 return db_fetch_result($result, 0, "feed_id");
1737 * Fixes incomplete URLs by prepending "http://".
1738 * Also replaces feed:// with http://, and
1739 * prepends a trailing slash if the url is a domain name only.
1741 * @param string $url Possibly incomplete URL
1743 * @return string Fixed URL.
1745 function fix_url($url) {
1747 // support schema-less urls
1748 if (strpos($url, '//') === 0) {
1749 $url = 'https:' . $url;
1752 if (strpos($url, '://') === false) {
1753 $url = 'http://' . $url;
1754 } else if (substr($url, 0, 5) == 'feed:') {
1755 $url = 'http:' . substr($url, 5);
1758 //prepend slash if the URL has no slash in it
1759 // "http://www.example" -> "http://www.example/"
1760 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
1764 if ($url != "http:///")
1770 function validate_feed_url($url) {
1771 $parts = parse_url($url);
1773 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
1777 function get_article_enclosures($id) {
1779 $query = "SELECT * FROM ttrss_enclosures
1780 WHERE post_id = '$id' AND content_url != ''";
1784 $result = db_query($query);
1786 if (db_num_rows($result) > 0) {
1787 while ($line = db_fetch_assoc($result)) {
1788 array_push($rv, $line);
1795 /* function save_email_address($email) {
1796 // FIXME: implement persistent storage of emails
1798 if (!$_SESSION['stored_emails'])
1799 $_SESSION['stored_emails'] = array();
1801 if (!in_array($email, $_SESSION['stored_emails']))
1802 array_push($_SESSION['stored_emails'], $email);
1806 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1808 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1810 $sql_is_cat = bool_to_sql_bool($is_cat);
1812 $result = db_query("SELECT access_key FROM ttrss_access_keys
1813 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1814 AND owner_uid = " . $owner_uid);
1816 if (db_num_rows($result) == 1) {
1817 return db_fetch_result($result, 0, "access_key");
1819 $key = db_escape_string(uniqid_short());
1821 $result = db_query("INSERT INTO ttrss_access_keys
1822 (access_key, feed_id, is_cat, owner_uid)
1823 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1830 function get_feeds_from_html($url, $content)
1832 $url = fix_url($url);
1833 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
1835 libxml_use_internal_errors(true);
1837 $doc = new DOMDocument();
1838 $doc->loadHTML($content);
1839 $xpath = new DOMXPath($doc);
1840 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
1841 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
1842 $feedUrls = array();
1843 foreach ($entries as $entry) {
1844 if ($entry->hasAttribute('href')) {
1845 $title = $entry->getAttribute('title');
1847 $title = $entry->getAttribute('type');
1849 $feedUrl = rewrite_relative_url(
1850 $baseUrl, $entry->getAttribute('href')
1852 $feedUrls[$feedUrl] = $title;
1858 function is_html($content) {
1859 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
1862 function url_is_html($url, $login = false, $pass = false) {
1863 return is_html(fetch_file_contents($url, false, $login, $pass));
1866 function print_label_select($name, $value, $attributes = "") {
1868 $result = db_query("SELECT caption FROM ttrss_labels2
1869 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1871 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1872 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
1874 while ($line = db_fetch_assoc($result)) {
1876 $issel = ($line["caption"] == $value) ?
"selected=\"1\"" : "";
1878 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1879 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1883 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1890 function format_article_enclosures($id, $always_display_enclosures,
1891 $article_content, $hide_images = false) {
1893 $result = get_article_enclosures($id);
1896 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_FORMAT_ENCLOSURES
) as $plugin) {
1897 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
1898 if (is_array($retval)) {
1900 $result = $retval[1];
1905 unset($retval); // Unset to prevent breaking render if there are no HOOK_RENDER_ENCLOSURE hooks below.
1907 if ($rv === '' && !empty($result)) {
1908 $entries_html = array();
1910 $entries_inline = array();
1912 foreach ($result as $line) {
1914 $url = $line["content_url"];
1915 $ctype = $line["content_type"];
1916 $title = $line["title"];
1917 $width = $line["width"];
1918 $height = $line["height"];
1920 if (!$ctype) $ctype = __("unknown type");
1922 $filename = substr($url, strrpos($url, "/")+
1);
1924 $player = format_inline_player($url, $ctype);
1926 if ($player) array_push($entries_inline, $player);
1928 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1929 # $filename . " (" . $ctype . ")" . "</a>";
1931 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
1932 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1934 array_push($entries_html, $entry);
1938 $entry["type"] = $ctype;
1939 $entry["filename"] = $filename;
1940 $entry["url"] = $url;
1941 $entry["title"] = $title;
1942 $entry["width"] = $width;
1943 $entry["height"] = $height;
1945 array_push($entries, $entry);
1948 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
1949 if ($always_display_enclosures ||
1950 !preg_match("/<img/i", $article_content)) {
1952 foreach ($entries as $entry) {
1954 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_RENDER_ENCLOSURE
) as $plugin)
1955 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
1962 if (preg_match("/image/", $entry["type"]) ||
1963 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
1965 if (!$hide_images) {
1967 if ($entry['height'] > 0)
1968 $encsize .= ' height="' . intval($entry['height']) . '"';
1969 if ($entry['width'] > 0)
1970 $encsize .= ' width="' . intval($entry['width']) . '"';
1972 alt=\"".htmlspecialchars($entry["filename"])."\"
1973 src=\"" .htmlspecialchars($entry["url"]) . "\"
1974 " . $encsize . " /></p>";
1976 $rv .= "<p><a target=\"_blank\"
1977 href=\"".htmlspecialchars($entry["url"])."\"
1978 >" .htmlspecialchars($entry["url"]) . "</a></p>";
1981 if ($entry['title']) {
1982 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
1990 if (count($entries_inline) > 0) {
1991 $rv .= "<hr clear='both'/>";
1992 foreach ($entries_inline as $entry) { $rv .= $entry; };
1993 $rv .= "<hr clear='both'/>";
1996 $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
1997 "<span>" . __('Attachments')."</span>";
1999 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
2001 foreach ($entries as $entry) {
2002 if ($entry["title"])
2003 $title = "— " . truncate_string($entry["title"], 30);
2007 $rv .= "<div onclick='window.open(\"".htmlspecialchars($entry["url"])."\")'
2008 dojoType=\"dijit.MenuItem\">".htmlspecialchars($entry["filename"])."$title</div>";
2019 function getLastArticleId() {
2020 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
2021 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
2023 if (db_num_rows($result) == 1) {
2024 return db_fetch_result($result, 0, "id");
2030 function build_url($parts) {
2031 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2035 * Converts a (possibly) relative URL to a absolute one.
2037 * @param string $url Base URL (i.e. from where the document is)
2038 * @param string $rel_url Possibly relative URL in the document
2040 * @return string Absolute URL
2042 function rewrite_relative_url($url, $rel_url) {
2043 if (strpos($rel_url, ":") !== false) {
2045 } else if (strpos($rel_url, "://") !== false) {
2047 } else if (strpos($rel_url, "//") === 0) {
2048 # protocol-relative URL (rare but they exist)
2050 } else if (strpos($rel_url, "/") === 0)
2052 $parts = parse_url($url);
2053 $parts['path'] = $rel_url;
2055 return build_url($parts);
2058 $parts = parse_url($url);
2059 if (!isset($parts['path'])) {
2060 $parts['path'] = '/';
2062 $dir = $parts['path'];
2063 if (substr($dir, -1) !== '/') {
2064 $dir = dirname($parts['path']);
2065 $dir !== '/' && $dir .= '/';
2067 $parts['path'] = $dir . $rel_url;
2069 return build_url($parts);
2073 function cleanup_tags($days = 14, $limit = 1000) {
2075 if (DB_TYPE
== "pgsql") {
2076 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2077 } else if (DB_TYPE
== "mysql") {
2078 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2083 while ($limit > 0) {
2086 $query = "SELECT ttrss_tags.id AS id
2087 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2088 WHERE post_int_id = int_id AND $interval_query AND
2089 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2091 $result = db_query($query);
2095 while ($line = db_fetch_assoc($result)) {
2096 array_push($ids, $line['id']);
2099 if (count($ids) > 0) {
2100 $ids = join(",", $ids);
2102 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2103 $tags_deleted +
= db_affected_rows($tmp_result);
2108 $limit -= $limit_part;
2111 return $tags_deleted;
2114 function print_user_stylesheet() {
2115 $value = get_pref('USER_STYLESHEET');
2118 print "<style type=\"text/css\">";
2119 print str_replace("<br/>", "\n", $value);
2125 function filter_to_sql($filter, $owner_uid) {
2128 if (DB_TYPE
== "pgsql")
2131 $reg_qpart = "REGEXP";
2133 foreach ($filter["rules"] AS $rule) {
2134 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2135 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2136 $rule['reg_exp']) !== FALSE;
2138 if ($regexp_valid) {
2140 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2142 switch ($rule["type"]) {
2144 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2145 $rule['reg_exp'] . "')";
2148 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2149 $rule['reg_exp'] . "')";
2152 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2153 $rule['reg_exp'] . "') OR LOWER(" .
2154 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2157 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2158 $rule['reg_exp'] . "')";
2161 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2162 $rule['reg_exp'] . "')";
2165 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2166 $rule['reg_exp'] . "')";
2170 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2172 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2173 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2176 if (isset($rule["cat_id"])) {
2178 if ($rule["cat_id"] > 0) {
2179 $children = getChildCategories($rule["cat_id"], $owner_uid);
2180 array_push($children, $rule["cat_id"]);
2182 $children = join(",", $children);
2184 $cat_qpart = "cat_id IN ($children)";
2186 $cat_qpart = "cat_id IS NULL";
2189 $qpart .= " AND $cat_qpart";
2192 $qpart .= " AND feed_id IS NOT NULL";
2194 array_push($query, "($qpart)");
2199 if (count($query) > 0) {
2200 $fullquery = "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
2202 $fullquery = "(false)";
2205 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2210 if (!function_exists('gzdecode')) {
2211 function gzdecode($string) { // no support for 2nd argument
2212 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2213 base64_encode($string));
2217 function get_random_bytes($length) {
2218 if (function_exists('openssl_random_pseudo_bytes')) {
2219 return openssl_random_pseudo_bytes($length);
2223 for ($i = 0; $i < $length; $i++
)
2224 $output .= chr(mt_rand(0, 255));
2230 function read_stdin() {
2231 $fp = fopen("php://stdin", "r");
2234 $line = trim(fgets($fp));
2242 function tmpdirname($path, $prefix) {
2243 // Use PHP's tmpfile function to create a temporary
2244 // directory name. Delete the file and keep the name.
2245 $tempname = tempnam($path,$prefix);
2249 if (!unlink($tempname))
2255 function getFeedCategory($feed) {
2256 $result = db_query("SELECT cat_id FROM ttrss_feeds
2257 WHERE id = '$feed'");
2259 if (db_num_rows($result) > 0) {
2260 return db_fetch_result($result, 0, "cat_id");
2267 function implements_interface($class, $interface) {
2268 return in_array($interface, class_implements($class));
2271 function get_minified_js($files) {
2272 require_once 'lib/jshrink/Minifier.php';
2276 foreach ($files as $js) {
2277 if (!isset($_GET['debug'])) {
2278 $cached_file = CACHE_DIR
. "/js/".basename($js).".js";
2280 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2282 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2284 if ($header && $contents) {
2285 list($htag, $hversion) = explode(":", $header);
2287 if ($htag == "tt-rss" && $hversion == VERSION
) {
2294 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
2295 file_put_contents($cached_file, "tt-rss:" . VERSION
. "\n" . $minified);
2299 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2306 function stylesheet_tag($filename) {
2307 $timestamp = filemtime($filename);
2309 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2312 function javascript_tag($filename) {
2315 if (!(strpos($filename, "?") === FALSE)) {
2316 $query = substr($filename, strpos($filename, "?")+
1);
2317 $filename = substr($filename, 0, strpos($filename, "?"));
2320 $timestamp = filemtime($filename);
2322 if ($query) $timestamp .= "&$query";
2324 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2327 function calculate_dep_timestamp() {
2328 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2332 foreach ($files as $file) {
2333 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2339 function T_js_decl($s1, $s2) {
2341 $s1 = preg_replace("/\n/", "", $s1);
2342 $s2 = preg_replace("/\n/", "", $s2);
2344 $s1 = preg_replace("/\"/", "\\\"", $s1);
2345 $s2 = preg_replace("/\"/", "\\\"", $s2);
2347 return "T_messages[\"$s1\"] = \"$s2\";\n";
2351 function init_js_translations() {
2353 print 'var T_messages = new Object();
2356 if (T_messages[msg]) {
2357 return T_messages[msg];
2363 function ngettext(msg1, msg2, n) {
2364 return __((parseInt(n) > 1) ? msg2 : msg1);
2367 $l10n = _get_reader();
2369 for ($i = 0; $i < $l10n->total
; $i++
) {
2370 $orig = $l10n->get_original_string($i);
2371 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2372 $key = explode(chr(0), $orig);
2373 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2374 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2376 $translation = __($orig);
2377 print T_js_decl($orig, $translation);
2382 function label_to_feed_id($label) {
2383 return LABEL_BASE_INDEX
- 1 - abs($label);
2386 function feed_to_label_id($feed) {
2387 return LABEL_BASE_INDEX
- 1 +
abs($feed);
2390 function get_theme_path($theme) {
2391 $check = "themes/$theme";
2392 if (file_exists($check)) return $check;
2394 $check = "themes.local/$theme";
2395 if (file_exists($check)) return $check;
2398 function theme_valid($theme) {
2399 if ($theme == "default.css" ||
$theme == "night.css") return true; // needed for array_filter
2400 $file = "themes/" . basename($theme);
2402 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2404 if (file_exists($file) && is_readable($file)) {
2405 $fh = fopen($file, "r");
2408 $header = fgets($fh);
2411 return strpos($header, "supports-version:" . VERSION_STATIC
) !== FALSE;
2418 function error_json($code) {
2419 require_once "errors.php";
2421 @$message = $ERRORS[$code];
2423 return json_encode(array("error" =>
2424 array("code" => $code, "message" => $message)));
2428 function abs_to_rel_path($dir) {
2429 $tmp = str_replace(dirname(__DIR__
), "", $dir);
2431 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);