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 "catchup_all" => __("Mark all feeds as read"),
96 "cat_toggle_collapse" => __("Un/collapse current category"),
97 "toggle_combined_mode" => __("Toggle combined mode"),
98 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
100 "goto_all" => __("All articles"),
101 "goto_fresh" => __("Fresh"),
102 "goto_marked" => __("Starred"),
103 "goto_published" => __("Published"),
104 "goto_tagcloud" => __("Tag cloud"),
105 "goto_prefs" => __("Preferences")),
106 __("Other") => array(
107 "create_label" => __("Create label"),
108 "create_filter" => __("Create filter"),
109 "collapse_sidebar" => __("Un/collapse sidebar"),
110 "help_dialog" => __("Show help dialog"))
113 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_INFO) as $plugin) {
114 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
120 function get_hotkeys_map() {
122 // "navigation" => array(
125 "n" => "next_article",
126 "p" => "prev_article",
127 "(38)|up" => "prev_article",
128 "(40)|down" => "next_article",
129 // "^(38)|Ctrl-up" => "prev_article_noscroll",
130 // "^(40)|Ctrl-down" => "next_article_noscroll",
131 "(191)|/" => "search_dialog",
132 // "article" => array(
133 "s" => "toggle_mark",
134 "*s" => "toggle_publ",
135 "u" => "toggle_unread",
137 "*d" => "dismiss_selected",
138 "*x" => "dismiss_read",
139 "o" => "open_in_new_window",
140 "c p" => "catchup_below",
141 "c n" => "catchup_above",
142 "*n" => "article_scroll_down",
143 "*p" => "article_scroll_up",
144 "*(38)|Shift+up" => "article_scroll_up",
145 "*(40)|Shift+down" => "article_scroll_down",
146 "a *w" => "toggle_widescreen",
147 "a e" => "toggle_embed_original",
148 "e" => "email_article",
149 "a q" => "close_article",
150 // "article_selection" => array(
151 "a a" => "select_all",
152 "a u" => "select_unread",
153 "a *u" => "select_marked",
154 "a p" => "select_published",
155 "a i" => "select_invert",
156 "a n" => "select_none",
158 "f r" => "feed_refresh",
159 "f a" => "feed_unhide_read",
160 "f s" => "feed_subscribe",
161 "f e" => "feed_edit",
162 "f q" => "feed_catchup",
163 "f x" => "feed_reverse",
164 "f *d" => "feed_debug_update",
165 "f *c" => "toggle_combined_mode",
166 "f c" => "toggle_cdm_expanded",
167 "*q" => "catchup_all",
168 "x" => "cat_toggle_collapse",
171 "g f" => "goto_fresh",
172 "g s" => "goto_marked",
173 "g p" => "goto_published",
174 "g t" => "goto_tagcloud",
175 "g *p" => "goto_prefs",
177 "(9)|Tab" => "select_article_cursor", // tab
178 "c l" => "create_label",
179 "c f" => "create_filter",
180 "c s" => "collapse_sidebar",
181 "^(191)|Ctrl+/" => "help_dialog",
184 if (get_pref('COMBINED_DISPLAY_MODE')) {
185 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
186 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
189 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) {
190 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
195 foreach (array_keys($hotkeys) as $hotkey) {
196 $pair = explode(" ", $hotkey, 2);
198 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
199 array_push($prefixes, $pair[0]);
203 return array($prefixes, $hotkeys);
206 function check_for_update() {
207 if (defined("GIT_VERSION_TIMESTAMP")) {
208 $content = @fetch_file_contents("http://tt-rss.org/version.json");
211 $content = json_decode($content, true);
213 if ($content && isset($content["changeset"])) {
214 if ((int)GIT_VERSION_TIMESTAMP < (int)$content["changeset"]["timestamp"] &&
215 GIT_VERSION_HEAD != $content["changeset"]["id"]) {
217 return $content["changeset"]["id"];
226 function make_runtime_info() {
229 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
230 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
232 $max_feed_id = db_fetch_result($result, 0, "mid");
233 $num_feeds = db_fetch_result($result, 0, "nf");
235 $data["max_feed_id"] = (int) $max_feed_id;
236 $data["num_feeds"] = (int) $num_feeds;
238 $data['last_article_id'] = getLastArticleId();
239 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
241 $data['dep_ts'] = calculate_dep_timestamp();
242 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
245 if (CHECK_FOR_UPDATES && $_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) {
246 $update_result = @check_for_update();
248 $data["update_result"] = $update_result;
250 $_SESSION["last_version_check"] = time();
253 if (file_exists(LOCK_DIRECTORY . "/update_daemon.lock")) {
255 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
257 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
259 $stamp = (int) @file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
262 $stamp_delta = time() - $stamp;
264 if ($stamp_delta > 1800) {
268 $_SESSION["daemon_stamp_check"] = time();
271 $data['daemon_stamp_ok'] = $stamp_check;
273 $stamp_fmt = date("Y.m.d, G:i", $stamp);
275 $data['daemon_stamp'] = $stamp_fmt;
283 function search_to_sql($search) {
285 $search_query_part = "";
287 $keywords = str_getcsv($search, " ");
288 $query_keywords = array();
289 $search_words = array();
291 foreach ($keywords as $k) {
292 if (strpos($k, "-") === 0) {
299 $commandpair = explode(":", mb_strtolower($k), 2);
301 switch ($commandpair[0]) {
303 if ($commandpair[1]) {
304 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
305 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
307 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
308 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
309 array_push($search_words, $k);
313 if ($commandpair[1]) {
314 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
315 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
317 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
318 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
319 array_push($search_words, $k);
323 if ($commandpair[1]) {
324 if ($commandpair[1] == "true")
325 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
326 else if ($commandpair[1] == "false")
327 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
329 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
330 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
332 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
333 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
334 if (!$not) array_push($search_words, $k);
339 if ($commandpair[1]) {
340 if ($commandpair[1] == "true")
341 array_push($query_keywords, "($not (marked = true))");
343 array_push($query_keywords, "($not (marked = false))");
345 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
346 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
347 if (!$not) array_push($search_words, $k);
351 if ($commandpair[1]) {
352 if ($commandpair[1] == "true")
353 array_push($query_keywords, "($not (published = true))");
355 array_push($query_keywords, "($not (published = false))");
358 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
359 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
360 if (!$not) array_push($search_words, $k);
364 if ($commandpair[1]) {
365 if ($commandpair[1] == "true")
366 array_push($query_keywords, "($not (unread = true))");
368 array_push($query_keywords, "($not (unread = false))");
371 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
372 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
373 if (!$not) array_push($search_words, $k);
377 if (strpos($k, "@") === 0) {
379 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
380 $orig_ts = strtotime(substr($k, 1));
381 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
383 //$k = date("Y-m-d", strtotime(substr($k, 1)));
385 array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')");
387 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
388 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
390 if (!$not) array_push($search_words, $k);
395 $search_query_part = implode("AND", $query_keywords);
397 return array($search_query_part, $search_words);
400 function getParentCategories($cat, $owner_uid) {
403 $result = db_query("SELECT parent_cat FROM ttrss_feed_categories
404 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
406 while ($line = db_fetch_assoc($result)) {
407 array_push($rv, $line["parent_cat"]);
408 $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid));
414 function getChildCategories($cat, $owner_uid) {
417 $result = db_query("SELECT id FROM ttrss_feed_categories
418 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
420 while ($line = db_fetch_assoc($result)) {
421 array_push($rv, $line["id"]);
422 $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid));
428 // $search_mode is obsolete/unused
429 function queryFeedHeadlines($feed, $limit, $view_mode, $cat_view, $search, $search_mode, $override_order = false, $offset = 0, $owner_uid = 0, $filter = false, $since_id = 0, $include_children = false, $ignore_vfeed_group = false, $override_strategy = false, $override_vfeed = false, $start_ts = false) {
431 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
433 $ext_tables_part = "";
434 $search_words = array();
437 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH) as $plugin) {
438 list($search_query_part, $search_words) = $plugin->hook_search($search);
442 // fall back in case of no plugins
443 if (!$search_query_part) {
444 list($search_query_part, $search_words) = search_to_sql($search);
446 $search_query_part .= " AND ";
448 $search_query_part = "";
453 if (DB_TYPE == "pgsql") {
454 $query_strategy_part .= " AND updated > NOW() - INTERVAL '14 days' ";
456 $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL 14 DAY) ";
459 $override_order = "updated DESC";
461 $filter_query_part = filter_to_sql($filter, $owner_uid);
463 // Try to check if SQL regexp implementation chokes on a valid regexp
466 $result = db_query("SELECT true AS true_val
468 JOIN ttrss_user_entries ON ttrss_entries.id = ttrss_user_entries.ref_id
469 JOIN ttrss_feeds ON ttrss_feeds.id = ttrss_user_entries.feed_id
470 WHERE $filter_query_part LIMIT 1", false);
473 $test = db_fetch_result($result, 0, "true_val");
476 $filter_query_part = "false AND";
478 $filter_query_part .= " AND";
481 $filter_query_part = "false AND";
485 $filter_query_part = "";
489 $since_id_part = "ttrss_entries.id > $since_id AND ";
494 $view_query_part = "";
496 if ($view_mode == "adaptive") {
498 $view_query_part = " ";
499 } else if ($feed != -1) {
501 $unread = getFeedUnread($feed, $cat_view);
503 if ($cat_view && $feed > 0 && $include_children)
504 $unread += getCategoryChildrenUnread($feed);
507 $view_query_part = " unread = true AND ";
512 if ($view_mode == "marked") {
513 $view_query_part = " marked = true AND ";
516 if ($view_mode == "has_note") {
517 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
520 if ($view_mode == "published") {
521 $view_query_part = " published = true AND ";
524 if ($view_mode == "unread" && $feed != -6) {
525 $view_query_part = " unread = true AND ";
529 $limit_query_part = "LIMIT " . $limit;
532 $allow_archived = false;
534 $vfeed_query_part = "";
537 if (!is_numeric($feed)) {
538 $query_strategy_part = "true";
539 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
540 id = feed_id) as feed_title,";
541 } else if ($feed > 0) {
546 if ($include_children) {
548 $subcats = getChildCategories($feed, $owner_uid);
550 array_push($subcats, $feed);
551 $query_strategy_part = "cat_id IN (".
552 implode(",", $subcats).")";
555 $query_strategy_part = "cat_id = '$feed'";
559 $query_strategy_part = "cat_id IS NULL";
562 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
565 $query_strategy_part = "feed_id = '$feed'";
567 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
568 $query_strategy_part = "feed_id IS NULL";
569 $allow_archived = true;
570 } else if ($feed == 0 && $cat_view) { // uncategorized
571 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
572 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
573 } else if ($feed == -1) { // starred virtual feed
574 $query_strategy_part = "marked = true";
575 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
576 $allow_archived = true;
578 if (!$override_order) {
579 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
582 } else if ($feed == -2) { // published virtual feed OR labels category
585 $query_strategy_part = "published = true";
586 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
587 $allow_archived = true;
589 if (!$override_order) {
590 $override_order = "last_published DESC, date_entered DESC, updated DESC";
594 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
596 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
598 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
599 ttrss_user_labels2.article_id = ref_id";
602 } else if ($feed == -6) { // recently read
603 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
604 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
605 $allow_archived = true;
606 $ignore_vfeed_group = true;
608 if (!$override_order) $override_order = "last_read DESC";
610 } else if ($feed == -3) { // fresh virtual feed
611 $query_strategy_part = "unread = true AND score >= 0";
613 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
615 if (DB_TYPE == "pgsql") {
616 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
618 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
621 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
622 } else if ($feed == -4) { // all articles virtual feed
623 $allow_archived = true;
624 $query_strategy_part = "true";
625 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
626 } else if ($feed <= LABEL_BASE_INDEX) { // labels
627 $label_id = feed_to_label_id($feed);
629 $query_strategy_part = "label_id = '$label_id' AND
630 ttrss_labels2.id = ttrss_user_labels2.label_id AND
631 ttrss_user_labels2.article_id = ref_id";
633 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
634 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
635 $allow_archived = true;
638 $query_strategy_part = "true";
641 $order_by = "score DESC, date_entered DESC, updated DESC";
643 if ($override_order) {
644 $order_by = $override_order;
647 if ($override_strategy) {
648 $query_strategy_part = $override_strategy;
651 if ($override_vfeed) {
652 $vfeed_query_part = $override_vfeed;
658 $feed_title = T_sprintf("Search results: %s", $search);
661 $feed_title = getCategoryTitle($feed);
663 if (is_numeric($feed) && $feed > 0) {
664 $result = db_query("SELECT title,site_url,last_error,last_updated
665 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
667 $feed_title = db_fetch_result($result, 0, "title");
668 $feed_site_url = db_fetch_result($result, 0, "site_url");
669 $last_error = db_fetch_result($result, 0, "last_error");
670 $last_updated = db_fetch_result($result, 0, "last_updated");
672 $feed_title = getFeedTitle($feed);
678 $content_query_part = "content, ";
680 if ($limit_query_part) {
681 $offset_query_part = "OFFSET $offset";
683 $offset_query_part = "";
686 if (is_numeric($feed)) {
687 // proper override_order applied above
688 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
689 if (!$override_order) {
690 $order_by = "ttrss_feeds.title, $order_by";
692 $order_by = "ttrss_feeds.title, $override_order";
696 if (!$allow_archived) {
697 $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part";
698 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
701 $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries
702 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
705 if ($vfeed_query_part) $vfeed_query_part .= "favicon_avg_color,";
708 $start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts));
709 $start_ts_query_part = "date_entered >= '$start_ts_formatted' AND";
711 $start_ts_query_part = "";
714 $query = "SELECT DISTINCT
717 ttrss_entries.id,ttrss_entries.title,
721 always_display_enclosures,
730 unread,feed_id,marked,published,link,last_read,orig_feed_id,
731 last_marked, last_published,
739 ttrss_user_entries.ref_id = ttrss_entries.id AND
740 ttrss_user_entries.owner_uid = '$owner_uid' AND
746 $query_strategy_part ORDER BY $order_by
747 $limit_query_part $offset_query_part";
749 if ($_REQUEST["debug"]) print $query;
751 $result = db_query($query);
756 $query = "SELECT DISTINCT
760 ttrss_entries.id as id,
775 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images,
776 last_marked, last_published,
781 FROM ttrss_entries, ttrss_user_entries, ttrss_tags
783 ref_id = ttrss_entries.id AND
784 ttrss_user_entries.owner_uid = $owner_uid AND
785 post_int_id = int_id AND
786 tag_name = '$feed' AND
789 $query_strategy_part ORDER BY $order_by
790 $limit_query_part $offset_query_part";
792 if ($_REQUEST["debug"]) print $query;
794 $result = db_query($query);
797 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words);
801 function iframe_whitelisted($entry) {
802 $whitelist = array("youtube.com", "youtu.be", "vimeo.com");
804 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST);
807 foreach ($whitelist as $w) {
808 if ($src == $w || $src == "www.$w")
816 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
817 if (!$owner) $owner = $_SESSION["uid"];
819 $res = trim($str); if (!$res) return '';
821 $charset_hack = '<head>
822 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
825 $res = trim($res); if (!$res) return '';
827 libxml_use_internal_errors(true);
829 $doc = new DOMDocument();
830 $doc->loadHTML($charset_hack . $res);
831 $xpath = new DOMXPath($doc);
833 $entries = $xpath->query('(//a[@href]|//img[@src])');
835 foreach ($entries as $entry) {
839 if ($entry->hasAttribute('href')) {
840 $entry->setAttribute('href',
841 rewrite_relative_url($site_url, $entry->getAttribute('href')));
843 $entry->setAttribute('rel', 'noreferrer');
846 if ($entry->hasAttribute('src')) {
847 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
849 $cached_filename = CACHE_DIR . '/images/' . sha1($src) . '.png';
851 if (file_exists($cached_filename)) {
852 $src = SELF_URL_PATH . '/public.php?op=cached_image&hash=' . sha1($src);
855 $entry->setAttribute('src', $src);
858 if ($entry->nodeName == 'img') {
859 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
860 $force_remove_images || $_SESSION["bw_limit"]) {
862 $p = $doc->createElement('p');
864 $a = $doc->createElement('a');
865 $a->setAttribute('href', $entry->getAttribute('src'));
867 $a->appendChild(new DOMText($entry->getAttribute('src')));
868 $a->setAttribute('target', '_blank');
872 $entry->parentNode->replaceChild($p, $entry);
877 if (strtolower($entry->nodeName) == "a") {
878 $entry->setAttribute("target", "_blank");
882 $entries = $xpath->query('//iframe');
883 foreach ($entries as $entry) {
884 if (!iframe_whitelisted($entry)) {
885 $entry->setAttribute('sandbox', 'allow-scripts');
887 if ($_SERVER['HTTPS'] == "on") {
888 $entry->setAttribute("src",
889 str_replace("http://", "https://",
890 $entry->getAttribute("src")));
895 $allowed_elements = array('a', 'address', 'audio', 'article', 'aside',
896 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
897 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
898 'data', 'dd', 'del', 'details', 'div', 'dl', 'font',
899 'dt', 'em', 'footer', 'figure', 'figcaption',
900 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
901 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
902 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
903 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
904 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
905 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
907 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
909 $disallowed_attributes = array('id', 'style', 'class');
911 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) {
912 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
913 if (is_array($retval)) {
915 $allowed_elements = $retval[1];
916 $disallowed_attributes = $retval[2];
922 $doc->removeChild($doc->firstChild); //remove doctype
923 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
925 if ($highlight_words) {
926 foreach ($highlight_words as $word) {
928 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
930 $elements = $xpath->query("//*/text()");
932 foreach ($elements as $child) {
934 $fragment = $doc->createDocumentFragment();
935 $text = $child->textContent;
937 while (($pos = mb_stripos($text, $word)) !== false) {
938 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
939 $word = mb_substr($text, $pos, mb_strlen($word));
940 $highlight = $doc->createElement('span');
941 $highlight->appendChild(new DomText($word));
942 $highlight->setAttribute('class', 'highlight');
943 $fragment->appendChild($highlight);
944 $text = mb_substr($text, $pos + mb_strlen($word));
947 if (!empty($text)) $fragment->appendChild(new DomText($text));
949 $child->parentNode->replaceChild($fragment, $child);
954 $res = $doc->saveHTML();
959 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
960 $xpath = new DOMXPath($doc);
961 $entries = $xpath->query('//*');
963 foreach ($entries as $entry) {
964 if (!in_array($entry->nodeName, $allowed_elements)) {
965 $entry->parentNode->removeChild($entry);
968 if ($entry->hasAttributes()) {
969 $attrs_to_remove = array();
971 foreach ($entry->attributes as $attr) {
973 if (strpos($attr->nodeName, 'on') === 0) {
974 array_push($attrs_to_remove, $attr);
977 if (in_array($attr->nodeName, $disallowed_attributes)) {
978 array_push($attrs_to_remove, $attr);
982 foreach ($attrs_to_remove as $attr) {
983 $entry->removeAttributeNode($attr);
991 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
993 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
994 if (count($ids) == 0) return;
998 foreach ($ids as $id) {
999 array_push($tmp_ids, "ref_id = '$id'");
1002 $ids_qpart = join(" OR ", $tmp_ids);
1005 db_query("UPDATE ttrss_user_entries SET
1006 unread = false,last_read = NOW()
1007 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1008 } else if ($cmode == 1) {
1009 db_query("UPDATE ttrss_user_entries SET
1011 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1013 db_query("UPDATE ttrss_user_entries SET
1014 unread = NOT unread,last_read = NOW()
1015 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1020 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
1021 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1023 while ($line = db_fetch_assoc($result)) {
1024 ccache_update($line["feed_id"], $owner_uid);
1028 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
1030 $a_id = db_escape_string($id);
1032 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1034 $query = "SELECT DISTINCT tag_name,
1035 owner_uid as owner FROM
1036 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
1037 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
1041 /* check cache first */
1043 if ($tag_cache === false) {
1044 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
1045 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1047 $tag_cache = db_fetch_result($result, 0, "tag_cache");
1051 $tags = explode(",", $tag_cache);
1054 /* do it the hard way */
1056 $tmp_result = db_query($query);
1058 while ($tmp_line = db_fetch_assoc($tmp_result)) {
1059 array_push($tags, $tmp_line["tag_name"]);
1062 /* update the cache */
1064 $tags_str = db_escape_string(join(",", $tags));
1066 db_query("UPDATE ttrss_user_entries
1067 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
1068 AND owner_uid = $owner_uid");
1074 function trim_array($array) {
1076 array_walk($tmp, 'trim');
1080 function tag_is_valid($tag) {
1081 if ($tag == '') return false;
1082 if (preg_match("/^[0-9]*$/", $tag)) return false;
1083 if (mb_strlen($tag) > 250) return false;
1085 if (!$tag) return false;
1090 function render_login_form() {
1091 header('Cache-Control: public');
1093 require_once "login_form.php";
1097 function format_warning($msg, $id = "") {
1098 return "<div class=\"warning\" id=\"$id\">
1099 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
1102 function format_notice($msg, $id = "") {
1103 return "<div class=\"notice\" id=\"$id\">
1104 <span><img src=\"images/information.png\"></span><span>$msg</span></div>";
1107 function format_error($msg, $id = "") {
1108 return "<div class=\"error\" id=\"$id\">
1109 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
1112 function print_notice($msg) {
1113 return print format_notice($msg);
1116 function print_warning($msg) {
1117 return print format_warning($msg);
1120 function print_error($msg) {
1121 return print format_error($msg);
1125 function T_sprintf() {
1126 $args = func_get_args();
1127 return vsprintf(__(array_shift($args)), $args);
1130 function format_inline_player($url, $ctype) {
1134 $url = htmlspecialchars($url);
1136 if (strpos($ctype, "audio/") === 0) {
1138 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1139 $_SESSION["hasMp3"])) {
1141 $entry .= "<audio preload=\"none\" controls>
1142 <source type=\"$ctype\" src=\"$url\"></source>
1147 $entry .= "<object type=\"application/x-shockwave-flash\"
1148 data=\"lib/button/musicplayer.swf?song_url=$url\"
1149 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1150 <param name=\"movie\"
1151 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1155 if ($entry) $entry .= " <a target=\"_blank\"
1156 href=\"$url\">" . basename($url) . "</a>";
1164 /* $filename = substr($url, strrpos($url, "/")+1);
1166 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1167 $filename . " (" . $ctype . ")" . "</a>"; */
1171 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1172 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1178 /* we can figure out feed_id from article id anyway, why do we
1179 * pass feed_id here? let's ignore the argument :(*/
1181 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1182 WHERE ref_id = '$id'");
1184 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1186 $rv['feed_id'] = $feed_id;
1188 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1190 if ($mark_as_read) {
1191 $result = db_query("UPDATE ttrss_user_entries
1192 SET unread = false,last_read = NOW()
1193 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1195 ccache_update($feed_id, $owner_uid);
1198 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1199 ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
1200 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1201 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1202 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1203 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1209 FROM ttrss_entries,ttrss_user_entries
1210 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1214 $line = db_fetch_assoc($result);
1216 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1217 unset($line["tag_cache"]);
1219 $line["content"] = sanitize($line["content"],
1220 sql_bool_to_bool($line['hide_images']),
1221 $owner_uid, $line["site_url"], false, $line["id"]);
1223 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) {
1224 $line = $p->hook_render_article($line);
1227 $num_comments = $line["num_comments"];
1228 $entry_comments = "";
1230 if ($num_comments > 0) {
1231 if ($line["comments"]) {
1232 $comments_url = htmlspecialchars($line["comments"]);
1234 $comments_url = htmlspecialchars($line["link"]);
1236 $entry_comments = "<a class=\"postComments\"
1237 target='_blank' href=\"$comments_url\">$num_comments ".
1238 _ngettext("comment", "comments", $num_comments)."</a>";
1241 if ($line["comments"] && $line["link"] != $line["comments"]) {
1242 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
1247 header("Content-Type: text/html");
1248 $rv['content'] .= "<html><head>
1249 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1250 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1251 stylesheet_tag("css/tt-rss.css").
1252 stylesheet_tag("css/zoom.css").
1253 stylesheet_tag("css/dijit.css")."
1255 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1256 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1258 <script type=\"text/javascript\">
1259 function openSelectedAttachment(elem) {
1261 var url = elem[elem.selectedIndex].value;
1265 elem.selectedIndex = 0;
1269 exception_error(\"openSelectedAttachment\", e);
1273 </head><body id=\"ttrssZoom\">";
1276 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1278 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1280 $entry_author = $line["author"];
1282 if ($entry_author) {
1283 $entry_author = __(" - ") . $entry_author;
1286 $parsed_updated = make_local_datetime($line["updated"], true,
1290 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1292 if ($line["link"]) {
1293 $rv['content'] .= "<div class='postTitle'><a target='_blank'
1294 title=\"".htmlspecialchars($line['title'])."\"
1296 htmlspecialchars($line["link"]) . "\">" .
1297 $line["title"] . "</a>" .
1298 "<span class='author'>$entry_author</span></div>";
1300 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1304 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
1305 "\" target=\"_blank\">".
1306 htmlspecialchars($line["feed_title"])."</a>";
1308 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1310 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1313 $tags_str = format_tags_string($line["tags"], $id);
1314 $tags_str_full = join(", ", $line["tags"]);
1316 if (!$tags_str_full) $tags_str_full = __("no tags");
1318 if (!$entry_comments) $entry_comments = " "; # placeholder
1320 $rv['content'] .= "<div class='postTags' style='float : right'>
1321 <img src='images/tag.png'
1322 class='tagsPic' alt='Tags' title='Tags'> ";
1325 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1326 <a title=\"".__('Edit tags for this article')."\"
1327 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1329 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1330 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1331 position=\"below\">$tags_str_full</div>";
1333 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
1334 $rv['content'] .= $p->hook_article_button($line);
1338 $tags_str = strip_tags($tags_str);
1339 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1341 $rv['content'] .= "</div>";
1342 $rv['content'] .= "<div clear='both'>";
1344 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
1345 $rv['content'] .= $p->hook_article_left_button($line);
1348 $rv['content'] .= "$entry_comments</div>";
1350 if ($line["orig_feed_id"]) {
1352 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
1353 WHERE id = ".$line["orig_feed_id"]);
1355 if (db_num_rows($tmp_result) != 0) {
1357 $rv['content'] .= "<div clear='both'>";
1358 $rv['content'] .= __("Originally from:");
1360 $rv['content'] .= " ";
1362 $tmp_line = db_fetch_assoc($tmp_result);
1364 $rv['content'] .= "<a target='_blank'
1365 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1366 $tmp_line['title'] . "</a>";
1368 $rv['content'] .= " ";
1370 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
1371 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1373 $rv['content'] .= "</div>";
1377 $rv['content'] .= "</div>";
1379 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1380 if ($line['note']) {
1381 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1383 $rv['content'] .= "</div>";
1385 if (!$line['lang']) $line['lang'] = 'en';
1387 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1389 $rv['content'] .= $line["content"];
1390 $rv['content'] .= format_article_enclosures($id,
1391 sql_bool_to_bool($line["always_display_enclosures"]),
1393 sql_bool_to_bool($line["hide_images"]));
1395 $rv['content'] .= "</div>";
1397 $rv['content'] .= "</div>";
1403 <div class='footer'>
1404 <button onclick=\"return window.close()\">".
1405 __("Close this window")."</button></div>";
1406 $rv['content'] .= "</body></html>";
1413 function print_checkpoint($n, $s) {
1414 $ts = microtime(true);
1415 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1419 function sanitize_tag($tag) {
1422 $tag = mb_strtolower($tag, 'utf-8');
1424 $tag = preg_replace('/[\'\"\+\>\<]/', "", $tag);
1426 if (DB_TYPE == "mysql") {
1427 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1433 function get_self_url_prefix() {
1434 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
1435 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
1437 return SELF_URL_PATH;
1442 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1444 * @return string The Mozilla Firefox feed adding URL.
1446 function add_feed_url() {
1447 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1449 $url_path = get_self_url_prefix() .
1450 "/public.php?op=subscribe&feed_url=%s";
1452 } // function add_feed_url
1454 function encrypt_password($pass, $salt = '', $mode2 = false) {
1455 if ($salt && $mode2) {
1456 return "MODE2:" . hash('sha256', $salt . $pass);
1458 return "SHA1X:" . sha1("$salt:$pass");
1460 return "SHA1:" . sha1($pass);
1462 } // function encrypt_password
1464 function load_filters($feed_id, $owner_uid, $action_id = false) {
1467 $cat_id = (int)getFeedCategory($feed_id);
1470 $null_cat_qpart = "cat_id IS NULL OR";
1472 $null_cat_qpart = "";
1474 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1475 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1477 $check_cats = join(",", array_merge(
1478 getParentCategories($cat_id, $owner_uid),
1481 while ($line = db_fetch_assoc($result)) {
1482 $filter_id = $line["id"];
1484 $result2 = db_query("SELECT
1485 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1486 FROM ttrss_filters2_rules AS r,
1487 ttrss_filter_types AS t
1489 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1490 (feed_id IS NULL OR feed_id = '$feed_id') AND
1491 filter_type = t.id AND filter_id = '$filter_id'");
1496 while ($rule_line = db_fetch_assoc($result2)) {
1497 # print_r($rule_line);
1500 $rule["reg_exp"] = $rule_line["reg_exp"];
1501 $rule["type"] = $rule_line["type_name"];
1502 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1504 array_push($rules, $rule);
1507 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1508 FROM ttrss_filters2_actions AS a,
1509 ttrss_filter_actions AS t
1511 action_id = t.id AND filter_id = '$filter_id'");
1513 while ($action_line = db_fetch_assoc($result2)) {
1514 # print_r($action_line);
1517 $action["type"] = $action_line["type_name"];
1518 $action["param"] = $action_line["action_param"];
1520 array_push($actions, $action);
1525 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1526 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1527 $filter["rules"] = $rules;
1528 $filter["actions"] = $actions;
1530 if (count($rules) > 0 && count($actions) > 0) {
1531 array_push($filters, $filter);
1538 function get_score_pic($score) {
1540 return "score_high.png";
1541 } else if ($score > 0) {
1542 return "score_half_high.png";
1543 } else if ($score < -100) {
1544 return "score_low.png";
1545 } else if ($score < 0) {
1546 return "score_half_low.png";
1548 return "score_neutral.png";
1552 function feed_has_icon($id) {
1553 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
1556 function init_plugins() {
1557 PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL);
1562 function format_tags_string($tags, $id) {
1563 if (!is_array($tags) || count($tags) == 0) {
1564 return __("no tags");
1566 $maxtags = min(5, count($tags));
1568 for ($i = 0; $i < $maxtags; $i++) {
1569 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed('".$tags[$i]."')\">" . $tags[$i] . "</a>, ";
1572 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1574 if (count($tags) > $maxtags)
1575 $tags_str .= ", …";
1581 function format_article_labels($labels, $id) {
1583 if (!is_array($labels)) return '';
1587 foreach ($labels as $l) {
1588 $labels_str .= sprintf("<span class='hlLabelRef'
1589 style='color : %s; background-color : %s'>%s</span>",
1590 $l[2], $l[3], $l[1]);
1597 function format_article_note($id, $note, $allow_edit = true) {
1599 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1600 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1601 ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
1607 function get_feed_category($feed_cat, $parent_cat_id = false) {
1608 if ($parent_cat_id) {
1609 $parent_qpart = "parent_cat = '$parent_cat_id'";
1610 $parent_insert = "'$parent_cat_id'";
1612 $parent_qpart = "parent_cat IS NULL";
1613 $parent_insert = "NULL";
1617 "SELECT id FROM ttrss_feed_categories
1618 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1620 if (db_num_rows($result) == 0) {
1623 return db_fetch_result($result, 0, "id");
1627 function add_feed_category($feed_cat, $parent_cat_id = false) {
1629 if (!$feed_cat) return false;
1633 if ($parent_cat_id) {
1634 $parent_qpart = "parent_cat = '$parent_cat_id'";
1635 $parent_insert = "'$parent_cat_id'";
1637 $parent_qpart = "parent_cat IS NULL";
1638 $parent_insert = "NULL";
1641 $feed_cat = mb_substr($feed_cat, 0, 250);
1644 "SELECT id FROM ttrss_feed_categories
1645 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1647 if (db_num_rows($result) == 0) {
1650 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1651 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1661 function getArticleFeed($id) {
1662 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1663 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1665 if (db_num_rows($result) != 0) {
1666 return db_fetch_result($result, 0, "feed_id");
1673 * Fixes incomplete URLs by prepending "http://".
1674 * Also replaces feed:// with http://, and
1675 * prepends a trailing slash if the url is a domain name only.
1677 * @param string $url Possibly incomplete URL
1679 * @return string Fixed URL.
1681 function fix_url($url) {
1683 // support schema-less urls
1684 if (strpos($url, '//') === 0) {
1685 $url = 'https:' . $url;
1688 if (strpos($url, '://') === false) {
1689 $url = 'http://' . $url;
1690 } else if (substr($url, 0, 5) == 'feed:') {
1691 $url = 'http:' . substr($url, 5);
1694 //prepend slash if the URL has no slash in it
1695 // "http://www.example" -> "http://www.example/"
1696 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
1700 if ($url != "http:///")
1706 function validate_feed_url($url) {
1707 $parts = parse_url($url);
1709 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
1713 function get_article_enclosures($id) {
1715 $query = "SELECT * FROM ttrss_enclosures
1716 WHERE post_id = '$id' AND content_url != ''";
1720 $result = db_query($query);
1722 if (db_num_rows($result) > 0) {
1723 while ($line = db_fetch_assoc($result)) {
1724 array_push($rv, $line);
1731 /* function save_email_address($email) {
1732 // FIXME: implement persistent storage of emails
1734 if (!$_SESSION['stored_emails'])
1735 $_SESSION['stored_emails'] = array();
1737 if (!in_array($email, $_SESSION['stored_emails']))
1738 array_push($_SESSION['stored_emails'], $email);
1742 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1744 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1746 $sql_is_cat = bool_to_sql_bool($is_cat);
1748 $result = db_query("SELECT access_key FROM ttrss_access_keys
1749 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1750 AND owner_uid = " . $owner_uid);
1752 if (db_num_rows($result) == 1) {
1753 return db_fetch_result($result, 0, "access_key");
1755 $key = db_escape_string(uniqid(base_convert(rand(), 10, 36)));
1757 $result = db_query("INSERT INTO ttrss_access_keys
1758 (access_key, feed_id, is_cat, owner_uid)
1759 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1766 function get_feeds_from_html($url, $content)
1768 $url = fix_url($url);
1769 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
1771 libxml_use_internal_errors(true);
1773 $doc = new DOMDocument();
1774 $doc->loadHTML($content);
1775 $xpath = new DOMXPath($doc);
1776 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
1777 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
1778 $feedUrls = array();
1779 foreach ($entries as $entry) {
1780 if ($entry->hasAttribute('href')) {
1781 $title = $entry->getAttribute('title');
1783 $title = $entry->getAttribute('type');
1785 $feedUrl = rewrite_relative_url(
1786 $baseUrl, $entry->getAttribute('href')
1788 $feedUrls[$feedUrl] = $title;
1794 function is_html($content) {
1795 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 20)) !== 0;
1798 function url_is_html($url, $login = false, $pass = false) {
1799 return is_html(fetch_file_contents($url, false, $login, $pass));
1802 function print_label_select($name, $value, $attributes = "") {
1804 $result = db_query("SELECT caption FROM ttrss_labels2
1805 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1807 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1808 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
1810 while ($line = db_fetch_assoc($result)) {
1812 $issel = ($line["caption"] == $value) ? "selected=\"1\"" : "";
1814 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1815 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1819 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1826 function format_article_enclosures($id, $always_display_enclosures,
1827 $article_content, $hide_images = false) {
1829 $result = get_article_enclosures($id);
1832 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ENCLOSURES) as $plugin) {
1833 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
1834 if (is_array($retval)) {
1836 $result = $retval[1];
1842 if ($rv === '' && !empty($result)) {
1843 $entries_html = array();
1845 $entries_inline = array();
1847 foreach ($result as $line) {
1849 $url = $line["content_url"];
1850 $ctype = $line["content_type"];
1851 $title = $line["title"];
1852 $width = $line["width"];
1853 $height = $line["height"];
1855 if (!$ctype) $ctype = __("unknown type");
1857 $filename = substr($url, strrpos($url, "/")+1);
1859 $player = format_inline_player($url, $ctype);
1861 if ($player) array_push($entries_inline, $player);
1863 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1864 # $filename . " (" . $ctype . ")" . "</a>";
1866 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
1867 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1869 array_push($entries_html, $entry);
1873 $entry["type"] = $ctype;
1874 $entry["filename"] = $filename;
1875 $entry["url"] = $url;
1876 $entry["title"] = $title;
1877 $entry["width"] = $width;
1878 $entry["height"] = $height;
1880 array_push($entries, $entry);
1883 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
1884 if ($always_display_enclosures ||
1885 !preg_match("/<img/i", $article_content)) {
1887 foreach ($entries as $entry) {
1889 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ENCLOSURE) as $plugin)
1890 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
1897 if (preg_match("/image/", $entry["type"]) ||
1898 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
1900 if (!$hide_images) {
1902 if ($entry['height'] > 0)
1903 $encsize .= ' height="' . intval($entry['width']) . '"';
1904 if ($entry['width'] > 0)
1905 $encsize .= ' width="' . intval($entry['height']) . '"';
1907 alt=\"".htmlspecialchars($entry["filename"])."\"
1908 src=\"" .htmlspecialchars($entry["url"]) . "\"
1909 " . $encsize . " /></p>";
1911 $rv .= "<p><a target=\"_blank\"
1912 href=\"".htmlspecialchars($entry["url"])."\"
1913 >" .htmlspecialchars($entry["url"]) . "</a></p>";
1916 if ($entry['title']) {
1917 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
1925 if (count($entries_inline) > 0) {
1926 $rv .= "<hr clear='both'/>";
1927 foreach ($entries_inline as $entry) { $rv .= $entry; };
1928 $rv .= "<hr clear='both'/>";
1931 $rv .= "<select class=\"attachments\" onchange=\"openSelectedAttachment(this)\">".
1932 "<option value=''>" . __('Attachments')."</option>";
1934 foreach ($entries as $entry) {
1935 if ($entry["title"])
1936 $title = "— " . truncate_string($entry["title"], 30);
1940 $rv .= "<option value=\"".htmlspecialchars($entry["url"])."\">" . htmlspecialchars($entry["filename"]) . "$title</option>";
1950 function getLastArticleId() {
1951 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
1952 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
1954 if (db_num_rows($result) == 1) {
1955 return db_fetch_result($result, 0, "id");
1961 function build_url($parts) {
1962 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
1966 * Converts a (possibly) relative URL to a absolute one.
1968 * @param string $url Base URL (i.e. from where the document is)
1969 * @param string $rel_url Possibly relative URL in the document
1971 * @return string Absolute URL
1973 function rewrite_relative_url($url, $rel_url) {
1974 if (strpos($rel_url, ":") !== false) {
1976 } else if (strpos($rel_url, "://") !== false) {
1978 } else if (strpos($rel_url, "//") === 0) {
1979 # protocol-relative URL (rare but they exist)
1981 } else if (strpos($rel_url, "/") === 0)
1983 $parts = parse_url($url);
1984 $parts['path'] = $rel_url;
1986 return build_url($parts);
1989 $parts = parse_url($url);
1990 if (!isset($parts['path'])) {
1991 $parts['path'] = '/';
1993 $dir = $parts['path'];
1994 if (substr($dir, -1) !== '/') {
1995 $dir = dirname($parts['path']);
1996 $dir !== '/' && $dir .= '/';
1998 $parts['path'] = $dir . $rel_url;
2000 return build_url($parts);
2004 function cleanup_tags($days = 14, $limit = 1000) {
2006 if (DB_TYPE == "pgsql") {
2007 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2008 } else if (DB_TYPE == "mysql") {
2009 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2014 while ($limit > 0) {
2017 $query = "SELECT ttrss_tags.id AS id
2018 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2019 WHERE post_int_id = int_id AND $interval_query AND
2020 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2022 $result = db_query($query);
2026 while ($line = db_fetch_assoc($result)) {
2027 array_push($ids, $line['id']);
2030 if (count($ids) > 0) {
2031 $ids = join(",", $ids);
2033 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2034 $tags_deleted += db_affected_rows($tmp_result);
2039 $limit -= $limit_part;
2042 return $tags_deleted;
2045 function print_user_stylesheet() {
2046 $value = get_pref('USER_STYLESHEET');
2049 print "<style type=\"text/css\">";
2050 print str_replace("<br/>", "\n", $value);
2056 function filter_to_sql($filter, $owner_uid) {
2059 if (DB_TYPE == "pgsql")
2062 $reg_qpart = "REGEXP";
2064 foreach ($filter["rules"] AS $rule) {
2065 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2066 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2067 $rule['reg_exp']) !== FALSE;
2069 if ($regexp_valid) {
2071 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2073 switch ($rule["type"]) {
2075 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2076 $rule['reg_exp'] . "')";
2079 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2080 $rule['reg_exp'] . "')";
2083 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2084 $rule['reg_exp'] . "') OR LOWER(" .
2085 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2088 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2089 $rule['reg_exp'] . "')";
2092 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2093 $rule['reg_exp'] . "')";
2096 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2097 $rule['reg_exp'] . "')";
2101 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2103 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2104 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2107 if (isset($rule["cat_id"])) {
2109 if ($rule["cat_id"] > 0) {
2110 $children = getChildCategories($rule["cat_id"], $owner_uid);
2111 array_push($children, $rule["cat_id"]);
2113 $children = join(",", $children);
2115 $cat_qpart = "cat_id IN ($children)";
2117 $cat_qpart = "cat_id IS NULL";
2120 $qpart .= " AND $cat_qpart";
2123 $qpart .= " AND feed_id IS NOT NULL";
2125 array_push($query, "($qpart)");
2130 if (count($query) > 0) {
2131 $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
2133 $fullquery = "(false)";
2136 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2141 if (!function_exists('gzdecode')) {
2142 function gzdecode($string) { // no support for 2nd argument
2143 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2144 base64_encode($string));
2148 function get_random_bytes($length) {
2149 if (function_exists('openssl_random_pseudo_bytes')) {
2150 return openssl_random_pseudo_bytes($length);
2154 for ($i = 0; $i < $length; $i++)
2155 $output .= chr(mt_rand(0, 255));
2161 function read_stdin() {
2162 $fp = fopen("php://stdin", "r");
2165 $line = trim(fgets($fp));
2173 function tmpdirname($path, $prefix) {
2174 // Use PHP's tmpfile function to create a temporary
2175 // directory name. Delete the file and keep the name.
2176 $tempname = tempnam($path,$prefix);
2180 if (!unlink($tempname))
2186 function getFeedCategory($feed) {
2187 $result = db_query("SELECT cat_id FROM ttrss_feeds
2188 WHERE id = '$feed'");
2190 if (db_num_rows($result) > 0) {
2191 return db_fetch_result($result, 0, "cat_id");
2198 function implements_interface($class, $interface) {
2199 return in_array($interface, class_implements($class));
2202 function geturl($url, $depth = 0, $nobody = true){
2204 if ($depth == 20) return $url;
2206 if (!function_exists('curl_init'))
2207 return user_error('CURL Must be installed for geturl function to work. Ask your host to enable it or uncomment extension=php_curl.dll in php.ini', E_USER_ERROR);
2209 $curl = curl_init();
2210 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
2211 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
2212 $header[] = "Cache-Control: max-age=0";
2213 $header[] = "Connection: keep-alive";
2214 $header[] = "Keep-Alive: 300";
2215 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
2216 $header[] = "Accept-Language: en-us,en;q=0.5";
2217 $header[] = "Pragma: ";
2219 curl_setopt($curl, CURLOPT_URL, $url);
2220 curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
2221 curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
2222 curl_setopt($curl, CURLOPT_HEADER, true);
2223 curl_setopt($curl, CURLOPT_NOBODY, $nobody);
2224 curl_setopt($curl, CURLOPT_REFERER, $url);
2225 curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
2226 curl_setopt($curl, CURLOPT_AUTOREFERER, true);
2227 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
2228 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
2229 curl_setopt($curl, CURLOPT_TIMEOUT, 60);
2230 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
2232 if (defined('_CURL_HTTP_PROXY')) {
2233 curl_setopt($curl, CURLOPT_PROXY, _CURL_HTTP_PROXY);
2236 $html = curl_exec($curl);
2238 $status = curl_getinfo($curl);
2240 if($status['http_code']!=200){
2242 // idiot site not allowing http head
2243 if($status['http_code'] == 405) {
2245 return geturl($url, $depth +1, false);
2248 if($status['http_code'] == 301 || $status['http_code'] == 302) {
2250 list($header) = explode("\r\n\r\n", $html, 2);
2252 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
2253 $url = trim(str_replace($matches[1],"",$matches[0]));
2254 $url_parsed = parse_url($url);
2255 return (isset($url_parsed))? geturl($url, $depth + 1):'';
2258 global $fetch_last_error;
2260 $fetch_last_error = curl_errno($curl) . " " . curl_error($curl);
2264 # foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
2265 # $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
2266 # $handle = @fopen('./curl.error.log', 'a');
2267 # fwrite($handle, $line);
2274 function get_minified_js($files) {
2275 require_once 'lib/jshrink/Minifier.php';
2279 foreach ($files as $js) {
2280 if (!isset($_GET['debug'])) {
2281 $cached_file = CACHE_DIR . "/js/".basename($js).".js";
2283 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2285 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2287 if ($header && $contents) {
2288 list($htag, $hversion) = explode(":", $header);
2290 if ($htag == "tt-rss" && $hversion == VERSION) {
2297 $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
2298 file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified);
2302 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2309 function stylesheet_tag($filename) {
2310 $timestamp = filemtime($filename);
2312 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2315 function javascript_tag($filename) {
2318 if (!(strpos($filename, "?") === FALSE)) {
2319 $query = substr($filename, strpos($filename, "?")+1);
2320 $filename = substr($filename, 0, strpos($filename, "?"));
2323 $timestamp = filemtime($filename);
2325 if ($query) $timestamp .= "&$query";
2327 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2330 function calculate_dep_timestamp() {
2331 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2335 foreach ($files as $file) {
2336 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2342 function T_js_decl($s1, $s2) {
2344 $s1 = preg_replace("/\n/", "", $s1);
2345 $s2 = preg_replace("/\n/", "", $s2);
2347 $s1 = preg_replace("/\"/", "\\\"", $s1);
2348 $s2 = preg_replace("/\"/", "\\\"", $s2);
2350 return "T_messages[\"$s1\"] = \"$s2\";\n";
2354 function init_js_translations() {
2356 print 'var T_messages = new Object();
2359 if (T_messages[msg]) {
2360 return T_messages[msg];
2366 function ngettext(msg1, msg2, n) {
2367 return __((parseInt(n) > 1) ? msg2 : msg1);
2370 $l10n = _get_reader();
2372 for ($i = 0; $i < $l10n->total; $i++) {
2373 $orig = $l10n->get_original_string($i);
2374 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2375 $key = explode(chr(0), $orig);
2376 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2377 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2379 $translation = __($orig);
2380 print T_js_decl($orig, $translation);
2385 function label_to_feed_id($label) {
2386 return LABEL_BASE_INDEX - 1 - abs($label);
2389 function feed_to_label_id($feed) {
2390 return LABEL_BASE_INDEX - 1 + abs($feed);
2393 function get_theme_path($theme) {
2394 $check = "themes/$theme";
2395 if (file_exists($check)) return $check;
2397 $check = "themes.local/$theme";
2398 if (file_exists($check)) return $check;
2401 function theme_valid($theme) {
2402 if ($theme == "default.css" || $theme == "night.css") return true; // needed for array_filter
2403 $file = "themes/" . basename($theme);
2405 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2407 if (file_exists($file) && is_readable($file)) {
2408 $fh = fopen($file, "r");
2411 $header = fgets($fh);
2414 return strpos($header, "supports-version:" . VERSION_STATIC) !== FALSE;
2421 function error_json($code) {
2422 require_once "errors.php";
2424 @$message = $ERRORS[$code];
2426 return json_encode(array("error" =>
2427 array("code" => $code, "message" => $message)));
2431 function abs_to_rel_path($dir) {
2432 $tmp = str_replace(dirname(__DIR__), "", $dir);
2434 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);