diff options
Diffstat (limited to 'include')
-rw-r--r-- | include/ccache.php | 12 | ||||
-rw-r--r-- | include/colors.php | 23 | ||||
-rw-r--r-- | include/digest.php | 2 | ||||
-rw-r--r-- | include/feedbrowser.php | 7 | ||||
-rw-r--r-- | include/functions.php | 2459 | ||||
-rw-r--r-- | include/functions2.php | 2403 | ||||
-rw-r--r-- | include/labels.php | 5 | ||||
-rw-r--r-- | include/login_form.php | 24 | ||||
-rw-r--r-- | include/rssfuncs.php | 377 | ||||
-rw-r--r-- | include/sanity_check.php | 6 | ||||
-rw-r--r-- | include/sanity_config.php | 4 | ||||
-rw-r--r-- | include/sessions.php | 18 | ||||
-rw-r--r-- | include/version.php | 6 |
13 files changed, 2776 insertions, 2570 deletions
diff --git a/include/ccache.php b/include/ccache.php index d89b9efe0..406dec81d 100644 --- a/include/ccache.php +++ b/include/ccache.php @@ -92,7 +92,7 @@ } function ccache_update($feed_id, $owner_uid, $is_cat = false, - $update_pcat = true) { + $update_pcat = true, $pcat_fast = false) { if (!is_numeric($feed_id)) return; @@ -127,11 +127,13 @@ /* Recalculate counters for child feeds */ - $result = db_query("SELECT id FROM ttrss_feeds + if (!$pcat_fast) { + $result = db_query("SELECT id FROM ttrss_feeds WHERE owner_uid = '$owner_uid' AND $cat_qpart"); - while ($line = db_fetch_assoc($result)) { - ccache_update($line["id"], $owner_uid, false, false); + while ($line = db_fetch_assoc($result)) { + ccache_update($line["id"], $owner_uid, false, false); + } } $result = db_query("SELECT SUM(value) AS sv @@ -177,7 +179,7 @@ $cat_id = (int) db_fetch_result($result, 0, "cat_id"); - ccache_update($cat_id, $owner_uid, true); + ccache_update($cat_id, $owner_uid, true, true, true); } } diff --git a/include/colors.php b/include/colors.php index 91eaa2dc2..491d38c15 100644 --- a/include/colors.php +++ b/include/colors.php @@ -288,17 +288,24 @@ function hsl2rgb($arr) { $size = @getimagesize($imageFile); - if (strtolower($size['mime']) == 'image/vnd.microsoft.icon' && class_exists("floIcon")) { + // to enable .ico support place floIcon.php into lib/ + if (strtolower($size['mime']) == 'image/vnd.microsoft.icon') { - $ico = new floIcon(); - @$ico->readICO($imageFile); + if (class_exists("floIcon")) { - if(count($ico->images)==0) - return null; - else - $img = @$ico->images[count($ico->images)-1]->getImageResource(); + $ico = new floIcon(); + @$ico->readICO($imageFile); - } else { + if(count($ico->images)==0) + return false; + else + $img = @$ico->images[count($ico->images)-1]->getImageResource(); + + } else { + return false; + } + + } else if ($size[0] > 0 && $size[1] > 0) { $img = @imagecreatefromstring(file_get_contents($imageFile)); } diff --git a/include/digest.php b/include/digest.php index 4427936e5..71ee6d203 100644 --- a/include/digest.php +++ b/include/digest.php @@ -127,8 +127,6 @@ ORDER BY ttrss_feed_categories.title, ttrss_feeds.title, score DESC, date_updated DESC LIMIT $limit"); - $cur_feed_title = ""; - $headlines_count = db_num_rows($result); $headlines = array(); diff --git a/include/feedbrowser.php b/include/feedbrowser.php index 6c245121f..8907aa9e4 100644 --- a/include/feedbrowser.php +++ b/include/feedbrowser.php @@ -63,7 +63,7 @@ htmlspecialchars($line["title"])."</span></a>"; $feed_url = "<a target=\"_blank\" class=\"fb_feedUrl\" - href=\"$feed_url\"><img src='images/pub_set.svg' + href=\"$feed_url\"><img src='images/pub_set.png' style='vertical-align : middle'></a>"; $rv .= "<li>$check_box $feed_url $site_url". @@ -72,7 +72,6 @@ } else if ($mode == 2) { $feed_url = htmlspecialchars($line["feed_url"]); $site_url = htmlspecialchars($line["site_url"]); - $title = htmlspecialchars($line["title"]); $check_box = "<input onclick='toggleSelectListRow2(this)' dojoType=\"dijit.form.CheckBox\" type=\"checkbox\">"; @@ -80,7 +79,7 @@ $class = ($feedctr % 2) ? "even" : "odd"; if ($line['articles_archived'] > 0) { - $archived = sprintf(ngettext("%d archived article", "%d archived articles", $line['articles_archived']), $line['articles_archived']); + $archived = sprintf(_ngettext("%d archived article", "%d archived articles", $line['articles_archived']), $line['articles_archived']); $archived = " <span class='subscribers'>($archived)</span>"; } else { $archived = ''; @@ -92,7 +91,7 @@ htmlspecialchars($line["title"])."</span></a>"; $feed_url = "<a target=\"_blank\" class=\"fb_feedUrl\" - href=\"$feed_url\"><img src='images/pub_set.svg' + href=\"$feed_url\"><img src='images/pub_set.png' style='vertical-align : middle'></a>"; diff --git a/include/functions.php b/include/functions.php index 067f8d457..1dbf004da 100644 --- a/include/functions.php +++ b/include/functions.php @@ -1,6 +1,6 @@ <?php define('EXPECTED_CONFIG_VERSION', 26); - define('SCHEMA_VERSION', 121); + define('SCHEMA_VERSION', 126); define('LABEL_BASE_INDEX', -1024); define('PLUGIN_FEED_BASE_INDEX', -128); @@ -10,7 +10,9 @@ $fetch_last_error = false; $fetch_last_error_code = false; $fetch_last_content_type = false; + $fetch_last_error_content = false; // curl only for the time being $fetch_curl_used = false; + $suppress_debugging = false; mb_internal_encoding("UTF-8"); date_default_timezone_set('UTC'); @@ -62,25 +64,31 @@ function get_translations() { $tr = array( "auto" => "Detect automatically", + "ar_SA" => "العربيّة (Arabic)", + "da_DA" => "Dansk", "ca_CA" => "Català", "cs_CZ" => "Česky", "en_US" => "English", - "es_ES" => "Español", + "el_GR" => "Ελληνικά", + "es_ES" => "Español (España)", + "es_LA" => "Español", "de_DE" => "Deutsch", "fr_FR" => "Français", "hu_HU" => "Magyar (Hungarian)", "it_IT" => "Italiano", "ja_JP" => "日本語 (Japanese)", - "ko_KR" => "한국어 (Korean)", "lv_LV" => "Latviešu", "nb_NO" => "Norwegian bokmål", "nl_NL" => "Dutch", "pl_PL" => "Polski", "ru_RU" => "Русский", "pt_BR" => "Portuguese/Brazil", + "pt_PT" => "Portuguese/Portugal", "zh_CN" => "Simplified Chinese", + "zh_TW" => "Traditional Chinese", "sv_SE" => "Svenska", - "fi_FI" => "Suomi"); + "fi_FI" => "Suomi", + "tr_TR" => "Türkçe"); return $tr; } @@ -88,6 +96,7 @@ require_once "lib/accept-to-gettext.php"; require_once "lib/gettext/gettext.inc"; + require_once "lib/languagedetect/LanguageDetect.php"; function startup_gettext() { @@ -132,6 +141,12 @@ $schema_version = false; + function _debug_suppress($suppress) { + global $suppress_debugging; + + $suppress_debugging = $suppress; + } + /** * Print a timestamped debug message. * @@ -139,6 +154,11 @@ * @return void */ function _debug($msg, $show = true) { + global $suppress_debugging; + + //echo "[$suppress_debugging] $msg $show\n"; + + if ($suppress_debugging) return false; $ts = strftime("%H:%M:%S", time()); if (function_exists('posix_getpid')) { @@ -153,7 +173,29 @@ $fp = fopen(LOGFILE, 'a+'); if ($fp) { + $locked = false; + + if (function_exists("flock")) { + $tries = 0; + + // try to lock logfile for writing + while ($tries < 5 && !$locked = flock($fp, LOCK_EX | LOCK_NB)) { + sleep(1); + ++$tries; + } + + if (!$locked) { + fclose($fp); + return; + } + } + fputs($fp, "[$ts] $msg\n"); + + if (function_exists("flock")) { + flock($fp, LOCK_UN); + } + fclose($fp); } } @@ -304,20 +346,22 @@ } } - function fetch_file_contents($url, $type = false, $login = false, $pass = false, $post_query = false, $timeout = false, $timestamp = 0) { + function fetch_file_contents($url, $type = false, $login = false, $pass = false, $post_query = false, $timeout = false, $timestamp = 0, $useragent = false) { global $fetch_last_error; global $fetch_last_error_code; + global $fetch_last_error_content; global $fetch_last_content_type; global $fetch_curl_used; + $url = ltrim($url, ' '); $url = str_replace(' ', '%20', $url); if (!defined('NO_CURL') && function_exists('curl_init')) { $fetch_curl_used = true; - if (ini_get("safe_mode") || ini_get("open_basedir")) { + if (ini_get("safe_mode") || ini_get("open_basedir") || defined("FORCE_GETURL")) { $new_url = geturl($url); if (!$new_url) { // geturl has already populated $fetch_last_error @@ -341,9 +385,18 @@ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY); - curl_setopt($ch, CURLOPT_USERAGENT, SELF_USER_AGENT); + curl_setopt($ch, CURLOPT_USERAGENT, $useragent ? $useragent : + SELF_USER_AGENT); curl_setopt($ch, CURLOPT_ENCODING, ""); - curl_setopt($ch, CURLOPT_REFERER, $url); + //curl_setopt($ch, CURLOPT_REFERER, $url); + + if (!ini_get("safe_mode") && !ini_get("open_basedir")) { + curl_setopt($ch, CURLOPT_COOKIEJAR, "/dev/null"); + } + + if (defined('_CURL_HTTP_PROXY')) { + curl_setopt($ch, CURLOPT_PROXY, _CURL_HTTP_PROXY); + } if ($post_query) { curl_setopt($ch, CURLOPT_POST, true); @@ -377,6 +430,7 @@ } else { $fetch_last_error = "HTTP Code: $http_code"; } + $fetch_last_error_content = $contents; curl_close($ch); return false; } @@ -645,6 +699,12 @@ $_SERVER["REDIRECT_SSL_CLIENT_V_END"] . $_SERVER["REDIRECT_SSL_CLIENT_S_DN"]); } + if ($_SERVER["SSL_CLIENT_M_SERIAL"]) { + return sha1($_SERVER["SSL_CLIENT_M_SERIAL"] . + $_SERVER["SSL_CLIENT_V_START"] . + $_SERVER["SSL_CLIENT_V_END"] . + $_SERVER["SSL_CLIENT_S_DN"]); + } return ""; } @@ -674,7 +734,7 @@ $_SESSION["name"] = db_fetch_result($result, 0, "login"); $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level"); - $_SESSION["csrf_token"] = sha1(uniqid(rand(), true)); + $_SESSION["csrf_token"] = uniqid(rand(), true); db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " . $_SESSION["uid"]); @@ -704,7 +764,7 @@ $_SESSION["auth_module"] = false; if (!$_SESSION["csrf_token"]) { - $_SESSION["csrf_token"] = sha1(uniqid(rand(), true)); + $_SESSION["csrf_token"] = uniqid(rand(), true); } $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"]; @@ -761,7 +821,7 @@ } function load_user_plugins($owner_uid) { - if ($owner_uid) { + if ($owner_uid && SCHEMA_VERSION >= 100) { $plugins = get_pref("_ENABLED_PLUGINS", $owner_uid); PluginHost::getInstance()->load($plugins, PluginHost::KIND_USER, $owner_uid); @@ -776,6 +836,7 @@ if (SINGLE_USER_MODE) { @session_start(); authenticate_user("admin", null); + startup_gettext(); load_user_plugins($_SESSION["uid"]); } else { if (!validate_session()) $_SESSION["uid"] = false; @@ -825,7 +886,7 @@ } function truncate_string($str, $max_len, $suffix = '…') { - if (mb_strlen($str, "utf-8") > $max_len - 3) { + if (mb_strlen($str, "utf-8") > $max_len) { return mb_substr($str, 0, $max_len, "utf-8") . $suffix; } else { return $str; @@ -944,6 +1005,7 @@ function sanity_check() { require_once 'errors.php'; + global $ERRORS; $error_code = 0; $schema_version = get_schema_version(true); @@ -1059,7 +1121,7 @@ $date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 1 WEEK) "; } break; - case "2weeks": + case "2week": if (DB_TYPE == "pgsql") { $date_qpart = "date_entered < NOW() - INTERVAL '2 week' "; } else { @@ -1097,8 +1159,8 @@ db_query("UPDATE ttrss_user_entries SET unread = false,last_read = NOW() WHERE (SELECT COUNT(*) - FROM ttrss_user_labels2 WHERE article_id = ref_id) > 0 - AND unread = true AND $date_qpart AND owner_uid = $owner_uid"); + FROM ttrss_user_labels2, ttrss_entries WHERE article_id = ref_id AND id = ref_id AND $date_qpart) > 0 + AND unread = true AND owner_uid = $owner_uid"); } } else if ($feed > 0) { @@ -1142,7 +1204,7 @@ SET unread = false, last_read = NOW() WHERE ref_id IN (SELECT id FROM (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id - AND owner_uid = $owner_uid AND unread = true AND $date_qpart AND $match_part) as tmp)"); + AND owner_uid = $owner_uid AND score >= 0 AND unread = true AND $date_qpart AND $match_part) as tmp)"); } if ($feed == -4) { @@ -1379,9 +1441,9 @@ $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid); if (DB_TYPE == "pgsql") { - $match_part .= " AND updated > NOW() - INTERVAL '$intl hour' "; + $match_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' "; } else { - $match_part .= " AND updated > DATE_SUB(NOW(), INTERVAL $intl HOUR) "; + $match_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) "; } $need_entries = true; @@ -1411,6 +1473,7 @@ $from_where = "ttrss_entries.id = ttrss_user_entries.ref_id AND"; } else { $from_qpart = "ttrss_user_entries"; + $from_where = ""; } $query = "SELECT count(int_id) AS unread @@ -1502,7 +1565,11 @@ foreach ($feeds as $feed) { $cv = array("id" => PluginHost::pfeed_to_feed_id($feed['id']), "counter" => $feed['sender']->get_unread($feed['id'])); - array_push($ret_arr, $cv); + + if (method_exists($feed['sender'], 'get_total')) + $cv["auxcounter"] = $feed['sender']->get_total($feed['id']); + + array_push($ret_arr, $cv); } } @@ -1515,13 +1582,10 @@ $owner_uid = $_SESSION["uid"]; - $result = db_query("SELECT id,caption,COUNT(u1.unread) AS unread,COUNT(u2.unread) AS total + $result = db_query("SELECT id,caption,SUM(CASE WHEN u1.unread = true THEN 1 ELSE 0 END) AS unread, COUNT(u1.unread) AS total FROM ttrss_labels2 LEFT JOIN ttrss_user_labels2 ON (ttrss_labels2.id = label_id) - LEFT JOIN ttrss_user_entries AS u1 ON (u1.ref_id = article_id AND u1.unread = true - AND u1.owner_uid = $owner_uid) - LEFT JOIN ttrss_user_entries AS u2 ON (u2.ref_id = article_id AND u2.unread = false - AND u2.owner_uid = $owner_uid) + LEFT JOIN ttrss_user_entries AS u1 ON u1.ref_id = article_id WHERE ttrss_labels2.owner_uid = $owner_uid GROUP BY ttrss_labels2.id, ttrss_labels2.caption"); @@ -1556,7 +1620,6 @@ AND ttrss_counters_cache.feed_id = id"; $result = db_query($query); - $fctrs_modified = false; while ($line = db_fetch_assoc($result)) { @@ -1628,6 +1691,10 @@ return array("code" => 5, "message" => $fetch_last_error); } + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SUBSCRIBE_FEED) as $plugin) { + $contents = $plugin->hook_subscribe_feed($contents, $url, $auth_login, $auth_pass); + } + if (is_html($contents)) { $feedUrls = get_feeds_from_html($url, $contents); @@ -1640,18 +1707,6 @@ $url = key($feedUrls); } - /* libxml_use_internal_errors(true); - $doc = new DOMDocument(); - $doc->loadXML($contents); - $error = libxml_get_last_error(); - libxml_clear_errors(); - - if ($error) { - $error_message = format_libxml_error($error); - - return array("code" => 6, "message" => $error_message); - } */ - if ($cat_id == "0" || !$cat_id) { $cat_qpart = "NULL"; } else { @@ -1876,19 +1931,19 @@ return "images/archive.png"; break; case -1: - return "images/mark_set.svg"; + return "images/star.png"; break; case -2: - return "images/pub_set.svg"; + return "images/feed.png"; break; case -3: return "images/fresh.png"; break; case -4: - return "images/tag.png"; + return "images/folder.png"; break; case -6: - return "images/recently_read.png"; + return "images/time.png"; break; default: if ($id < LABEL_BASE_INDEX) { @@ -1939,2325 +1994,7 @@ } } - function make_init_params() { - $params = array(); - - foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS", - "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP", - "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE", - "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) { - - $params[strtolower($param)] = (int) get_pref($param); - } - - $params["icons_url"] = ICONS_URL; - $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME; - $params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE"); - $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT"); - $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY"); - $params["bw_limit"] = (int) $_SESSION["bw_limit"]; - $params["label_base_index"] = (int) LABEL_BASE_INDEX; - - $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM - ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]); - - $max_feed_id = db_fetch_result($result, 0, "mid"); - $num_feeds = db_fetch_result($result, 0, "nf"); - - $params["max_feed_id"] = (int) $max_feed_id; - $params["num_feeds"] = (int) $num_feeds; - - $params["hotkeys"] = get_hotkeys_map(); - - $params["csrf_token"] = $_SESSION["csrf_token"]; - $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"]; - - $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE; - - return $params; - } - - function get_hotkeys_info() { - $hotkeys = array( - __("Navigation") => array( - "next_feed" => __("Open next feed"), - "prev_feed" => __("Open previous feed"), - "next_article" => __("Open next article"), - "prev_article" => __("Open previous article"), - "next_article_noscroll" => __("Open next article (don't scroll long articles)"), - "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"), - "next_article_noexpand" => __("Move to next article (don't expand or mark read)"), - "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"), - "search_dialog" => __("Show search dialog")), - __("Article") => array( - "toggle_mark" => __("Toggle starred"), - "toggle_publ" => __("Toggle published"), - "toggle_unread" => __("Toggle unread"), - "edit_tags" => __("Edit tags"), - "dismiss_selected" => __("Dismiss selected"), - "dismiss_read" => __("Dismiss read"), - "open_in_new_window" => __("Open in new window"), - "catchup_below" => __("Mark below as read"), - "catchup_above" => __("Mark above as read"), - "article_scroll_down" => __("Scroll down"), - "article_scroll_up" => __("Scroll up"), - "select_article_cursor" => __("Select article under cursor"), - "email_article" => __("Email article"), - "close_article" => __("Close/collapse article"), - "toggle_expand" => __("Toggle article expansion (combined mode)"), - "toggle_widescreen" => __("Toggle widescreen mode"), - "toggle_embed_original" => __("Toggle embed original")), - __("Article selection") => array( - "select_all" => __("Select all articles"), - "select_unread" => __("Select unread"), - "select_marked" => __("Select starred"), - "select_published" => __("Select published"), - "select_invert" => __("Invert selection"), - "select_none" => __("Deselect everything")), - __("Feed") => array( - "feed_refresh" => __("Refresh current feed"), - "feed_unhide_read" => __("Un/hide read feeds"), - "feed_subscribe" => __("Subscribe to feed"), - "feed_edit" => __("Edit feed"), - "feed_catchup" => __("Mark as read"), - "feed_reverse" => __("Reverse headlines"), - "feed_debug_update" => __("Debug feed update"), - "catchup_all" => __("Mark all feeds as read"), - "cat_toggle_collapse" => __("Un/collapse current category"), - "toggle_combined_mode" => __("Toggle combined mode"), - "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")), - __("Go to") => array( - "goto_all" => __("All articles"), - "goto_fresh" => __("Fresh"), - "goto_marked" => __("Starred"), - "goto_published" => __("Published"), - "goto_tagcloud" => __("Tag cloud"), - "goto_prefs" => __("Preferences")), - __("Other") => array( - "create_label" => __("Create label"), - "create_filter" => __("Create filter"), - "collapse_sidebar" => __("Un/collapse sidebar"), - "help_dialog" => __("Show help dialog")) - ); - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_INFO) as $plugin) { - $hotkeys = $plugin->hook_hotkey_info($hotkeys); - } - - return $hotkeys; - } - - function get_hotkeys_map() { - $hotkeys = array( -// "navigation" => array( - "k" => "next_feed", - "j" => "prev_feed", - "n" => "next_article", - "p" => "prev_article", - "(38)|up" => "prev_article", - "(40)|down" => "next_article", -// "^(38)|Ctrl-up" => "prev_article_noscroll", -// "^(40)|Ctrl-down" => "next_article_noscroll", - "(191)|/" => "search_dialog", -// "article" => array( - "s" => "toggle_mark", - "*s" => "toggle_publ", - "u" => "toggle_unread", - "*t" => "edit_tags", - "*d" => "dismiss_selected", - "*x" => "dismiss_read", - "o" => "open_in_new_window", - "c p" => "catchup_below", - "c n" => "catchup_above", - "*n" => "article_scroll_down", - "*p" => "article_scroll_up", - "*(38)|Shift+up" => "article_scroll_up", - "*(40)|Shift+down" => "article_scroll_down", - "a *w" => "toggle_widescreen", - "a e" => "toggle_embed_original", - "e" => "email_article", - "a q" => "close_article", -// "article_selection" => array( - "a a" => "select_all", - "a u" => "select_unread", - "a *u" => "select_marked", - "a p" => "select_published", - "a i" => "select_invert", - "a n" => "select_none", -// "feed" => array( - "f r" => "feed_refresh", - "f a" => "feed_unhide_read", - "f s" => "feed_subscribe", - "f e" => "feed_edit", - "f q" => "feed_catchup", - "f x" => "feed_reverse", - "f *d" => "feed_debug_update", - "f *c" => "toggle_combined_mode", - "f c" => "toggle_cdm_expanded", - "*q" => "catchup_all", - "x" => "cat_toggle_collapse", -// "goto" => array( - "g a" => "goto_all", - "g f" => "goto_fresh", - "g s" => "goto_marked", - "g p" => "goto_published", - "g t" => "goto_tagcloud", - "g *p" => "goto_prefs", -// "other" => array( - "(9)|Tab" => "select_article_cursor", // tab - "c l" => "create_label", - "c f" => "create_filter", - "c s" => "collapse_sidebar", - "^(191)|Ctrl+/" => "help_dialog", - ); - - if (get_pref('COMBINED_DISPLAY_MODE')) { - $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll"; - $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll"; - } - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) { - $hotkeys = $plugin->hook_hotkey_map($hotkeys); - } - - $prefixes = array(); - - foreach (array_keys($hotkeys) as $hotkey) { - $pair = explode(" ", $hotkey, 2); - - if (count($pair) > 1 && !in_array($pair[0], $prefixes)) { - array_push($prefixes, $pair[0]); - } - } - - return array($prefixes, $hotkeys); - } - - function make_runtime_info() { - $data = array(); - - $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM - ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]); - - $max_feed_id = db_fetch_result($result, 0, "mid"); - $num_feeds = db_fetch_result($result, 0, "nf"); - - $data["max_feed_id"] = (int) $max_feed_id; - $data["num_feeds"] = (int) $num_feeds; - - $data['last_article_id'] = getLastArticleId(); - $data['cdm_expanded'] = get_pref('CDM_EXPANDED'); - - $data['dep_ts'] = calculate_dep_timestamp(); - $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE'); - - if (file_exists(LOCK_DIRECTORY . "/update_daemon.lock")) { - - $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock"); - - if (time() - $_SESSION["daemon_stamp_check"] > 30) { - - $stamp = (int) @file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp"); - - if ($stamp) { - $stamp_delta = time() - $stamp; - - if ($stamp_delta > 1800) { - $stamp_check = 0; - } else { - $stamp_check = 1; - $_SESSION["daemon_stamp_check"] = time(); - } - - $data['daemon_stamp_ok'] = $stamp_check; - - $stamp_fmt = date("Y.m.d, G:i", $stamp); - - $data['daemon_stamp'] = $stamp_fmt; - } - } - } - - if ($_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) { - $new_version_details = @check_for_update(); - - $data['new_version_available'] = (int) ($new_version_details != false); - - $_SESSION["last_version_check"] = time(); - $_SESSION["version_data"] = $new_version_details; - } - - return $data; - } - - function search_to_sql($search) { - - $search_query_part = ""; - - $keywords = explode(" ", $search); - $query_keywords = array(); - - foreach ($keywords as $k) { - if (strpos($k, "-") === 0) { - $k = substr($k, 1); - $not = "NOT"; - } else { - $not = ""; - } - - $commandpair = explode(":", mb_strtolower($k), 2); - - switch ($commandpair[0]) { - case "title": - if ($commandpair[1]) { - array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%". - db_escape_string(mb_strtolower($commandpair[1]))."%'))"); - } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); - } - break; - case "author": - if ($commandpair[1]) { - array_push($query_keywords, "($not (LOWER(author) LIKE '%". - db_escape_string(mb_strtolower($commandpair[1]))."%'))"); - } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); - } - break; - case "note": - if ($commandpair[1]) { - if ($commandpair[1] == "true") - array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))"); - else if ($commandpair[1] == "false") - array_push($query_keywords, "($not (note IS NULL OR note = ''))"); - else - array_push($query_keywords, "($not (LOWER(note) LIKE '%". - db_escape_string(mb_strtolower($commandpair[1]))."%'))"); - } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); - } - break; - case "star": - - if ($commandpair[1]) { - if ($commandpair[1] == "true") - array_push($query_keywords, "($not (marked = true))"); - else - array_push($query_keywords, "($not (marked = false))"); - } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); - } - break; - case "pub": - if ($commandpair[1]) { - if ($commandpair[1] == "true") - array_push($query_keywords, "($not (published = true))"); - else - array_push($query_keywords, "($not (published = false))"); - - } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); - } - break; - default: - if (strpos($k, "@") === 0) { - - $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']); - $orig_ts = strtotime(substr($k, 1)); - $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC')); - - //$k = date("Y-m-d", strtotime(substr($k, 1))); - - array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')"); - } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); - } - } - } - - $search_query_part = implode("AND", $query_keywords); - - return $search_query_part; - } - - function getParentCategories($cat, $owner_uid) { - $rv = array(); - - $result = db_query("SELECT parent_cat FROM ttrss_feed_categories - WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid"); - - while ($line = db_fetch_assoc($result)) { - array_push($rv, $line["parent_cat"]); - $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid)); - } - - return $rv; - } - - function getChildCategories($cat, $owner_uid) { - $rv = array(); - - $result = db_query("SELECT id FROM ttrss_feed_categories - WHERE parent_cat = '$cat' AND owner_uid = $owner_uid"); - - while ($line = db_fetch_assoc($result)) { - array_push($rv, $line["id"]); - $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid)); - } - - return $rv; - } - - 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) { - - if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - - $ext_tables_part = ""; - - if ($search) { - - if (SPHINX_ENABLED) { - $ids = join(",", @sphinx_search($search, 0, 500)); - - if ($ids) - $search_query_part = "ref_id IN ($ids) AND "; - else - $search_query_part = "ref_id = -1 AND "; - - } else { - $search_query_part = search_to_sql($search); - $search_query_part .= " AND "; - } - - } else { - $search_query_part = ""; - } - - if ($filter) { - - if (DB_TYPE == "pgsql") { - $query_strategy_part .= " AND updated > NOW() - INTERVAL '14 days' "; - } else { - $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL 14 DAY) "; - } - - $override_order = "updated DESC"; - - $filter_query_part = filter_to_sql($filter, $owner_uid); - - // Try to check if SQL regexp implementation chokes on a valid regexp - - - $result = db_query("SELECT true AS true_val FROM ttrss_entries, - ttrss_user_entries, ttrss_feeds - WHERE $filter_query_part LIMIT 1", false); - - if ($result) { - $test = db_fetch_result($result, 0, "true_val"); - - if (!$test) { - $filter_query_part = "false AND"; - } else { - $filter_query_part .= " AND"; - } - } else { - $filter_query_part = "false AND"; - } - - } else { - $filter_query_part = ""; - } - - if ($since_id) { - $since_id_part = "ttrss_entries.id > $since_id AND "; - } else { - $since_id_part = ""; - } - - $view_query_part = ""; - - if ($view_mode == "adaptive") { - if ($search) { - $view_query_part = " "; - } else if ($feed != -1) { - - $unread = getFeedUnread($feed, $cat_view); - - if ($cat_view && $feed > 0 && $include_children) - $unread += getCategoryChildrenUnread($feed); - - if ($unread > 0) - $view_query_part = " unread = true AND "; - - } - } - - if ($view_mode == "marked") { - $view_query_part = " marked = true AND "; - } - - if ($view_mode == "has_note") { - $view_query_part = " (note IS NOT NULL AND note != '') AND "; - } - - if ($view_mode == "published") { - $view_query_part = " published = true AND "; - } - - if ($view_mode == "unread" && $feed != -6) { - $view_query_part = " unread = true AND "; - } - - if ($limit > 0) { - $limit_query_part = "LIMIT " . $limit; - } - - $allow_archived = false; - - $vfeed_query_part = ""; - - // override query strategy and enable feed display when searching globally - if ($search && $search_mode == "all_feeds") { - $query_strategy_part = "true"; - $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; - /* tags */ - } else if (!is_numeric($feed)) { - $query_strategy_part = "true"; - $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE - id = feed_id) as feed_title,"; - } else if ($search && $search_mode == "this_cat") { - $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; - - if ($feed > 0) { - if ($include_children) { - $subcats = getChildCategories($feed, $owner_uid); - array_push($subcats, $feed); - $cats_qpart = join(",", $subcats); - } else { - $cats_qpart = $feed; - } - - $query_strategy_part = "ttrss_feeds.cat_id IN ($cats_qpart)"; - - } else { - $query_strategy_part = "ttrss_feeds.cat_id IS NULL"; - } - - } else if ($feed > 0) { - - if ($cat_view) { - - if ($feed > 0) { - if ($include_children) { - # sub-cats - $subcats = getChildCategories($feed, $owner_uid); - - array_push($subcats, $feed); - $query_strategy_part = "cat_id IN (". - implode(",", $subcats).")"; - - } else { - $query_strategy_part = "cat_id = '$feed'"; - } - - } else { - $query_strategy_part = "cat_id IS NULL"; - } - - $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; - - } else { - $query_strategy_part = "feed_id = '$feed'"; - } - } else if ($feed == 0 && !$cat_view) { // archive virtual feed - $query_strategy_part = "feed_id IS NULL"; - $allow_archived = true; - } else if ($feed == 0 && $cat_view) { // uncategorized - $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL"; - $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; - } else if ($feed == -1) { // starred virtual feed - $query_strategy_part = "marked = true"; - $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; - $allow_archived = true; - - if (!$override_order) { - $override_order = "last_marked DESC, date_entered DESC, updated DESC"; - } - - } else if ($feed == -2) { // published virtual feed OR labels category - - if (!$cat_view) { - $query_strategy_part = "published = true"; - $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; - $allow_archived = true; - - if (!$override_order) { - $override_order = "last_published DESC, date_entered DESC, updated DESC"; - } - - } else { - $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; - - $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2"; - - $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND - ttrss_user_labels2.article_id = ref_id"; - - } - } else if ($feed == -6) { // recently read - $query_strategy_part = "unread = false AND last_read IS NOT NULL"; - $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; - $allow_archived = true; - - if (!$override_order) $override_order = "last_read DESC"; - } else if ($feed == -3) { // fresh virtual feed - $query_strategy_part = "unread = true AND score >= 0"; - - $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid); - - if (DB_TYPE == "pgsql") { - $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' "; - } else { - $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) "; - } - - $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; - } else if ($feed == -4) { // all articles virtual feed - $allow_archived = true; - $query_strategy_part = "true"; - $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; - } else if ($feed <= LABEL_BASE_INDEX) { // labels - $label_id = feed_to_label_id($feed); - - $query_strategy_part = "label_id = '$label_id' AND - ttrss_labels2.id = ttrss_user_labels2.label_id AND - ttrss_user_labels2.article_id = ref_id"; - - $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; - $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2"; - $allow_archived = true; - - } else { - $query_strategy_part = "true"; - } - - $order_by = "score DESC, date_entered DESC, updated DESC"; - - if ($view_mode == "unread_first") { - $order_by = "unread DESC, $order_by"; - } - - if ($override_order) { - $order_by = $override_order; - } - - $feed_title = ""; - - if ($search) { - $feed_title = T_sprintf("Search results: %s", $search); - } else { - if ($cat_view) { - $feed_title = getCategoryTitle($feed); - } else { - if (is_numeric($feed) && $feed > 0) { - $result = db_query("SELECT title,site_url,last_error,last_updated - FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid"); - - $feed_title = db_fetch_result($result, 0, "title"); - $feed_site_url = db_fetch_result($result, 0, "site_url"); - $last_error = db_fetch_result($result, 0, "last_error"); - $last_updated = db_fetch_result($result, 0, "last_updated"); - } else { - $feed_title = getFeedTitle($feed); - } - } - } - - $content_query_part = "content as content_preview, cached_content, "; - - if (is_numeric($feed)) { - - if ($feed >= 0) { - $feed_kind = "Feeds"; - } else { - $feed_kind = "Labels"; - } - - if ($limit_query_part) { - $offset_query_part = "OFFSET $offset"; - } - - // proper override_order applied above - if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) { - if (!$override_order) { - $order_by = "ttrss_feeds.title, $order_by"; - } else { - $order_by = "ttrss_feeds.title, $override_order"; - } - } - - if (!$allow_archived) { - $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part"; - $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND"; - - } else { - $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries - LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)"; - } - - if ($vfeed_query_part) - $vfeed_query_part .= "favicon_avg_color,"; - - $query = "SELECT DISTINCT - date_entered, - guid, - ttrss_entries.id,ttrss_entries.title, - updated, - label_cache, - tag_cache, - always_display_enclosures, - site_url, - note, - num_comments, - comments, - int_id, - hide_images, - unread,feed_id,marked,published,link,last_read,orig_feed_id, - last_marked, last_published, - $vfeed_query_part - $content_query_part - author,score - FROM - $from_qpart - WHERE - $feed_check_qpart - ttrss_user_entries.ref_id = ttrss_entries.id AND - ttrss_user_entries.owner_uid = '$owner_uid' AND - $search_query_part - $filter_query_part - $view_query_part - $since_id_part - $query_strategy_part ORDER BY $order_by - $limit_query_part $offset_query_part"; - - if ($_REQUEST["debug"]) print $query; - - $result = db_query($query); - - } else { - // browsing by tag - - $select_qpart = "SELECT DISTINCT " . - "date_entered," . - "guid," . - "note," . - "ttrss_entries.id as id," . - "title," . - "updated," . - "unread," . - "feed_id," . - "orig_feed_id," . - "marked," . - "num_comments, " . - "comments, " . - "tag_cache," . - "label_cache," . - "link," . - "last_read," . - "(SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images," . - "last_marked, last_published, " . - $since_id_part . - $vfeed_query_part . - $content_query_part . - "score "; - - $feed_kind = "Tags"; - $all_tags = explode(",", $feed); - if ($search_mode == 'any') { - $tag_sql = "tag_name in (" . implode(", ", array_map("db_quote", $all_tags)) . ")"; - $from_qpart = " FROM ttrss_entries,ttrss_user_entries,ttrss_tags "; - $where_qpart = " WHERE " . - "ref_id = ttrss_entries.id AND " . - "ttrss_user_entries.owner_uid = $owner_uid AND " . - "post_int_id = int_id AND $tag_sql AND " . - $view_query_part . - $search_query_part . - $query_strategy_part . " ORDER BY $order_by " . - $limit_query_part; - - } else { - $i = 1; - $sub_selects = array(); - $sub_ands = array(); - foreach ($all_tags as $term) { - array_push($sub_selects, "(SELECT post_int_id from ttrss_tags WHERE tag_name = " . db_quote($term) . " AND owner_uid = $owner_uid) as A$i"); - $i++; - } - if ($i > 2) { - $x = 1; - $y = 2; - do { - array_push($sub_ands, "A$x.post_int_id = A$y.post_int_id"); - $x++; - $y++; - } while ($y < $i); - } - array_push($sub_ands, "A1.post_int_id = ttrss_user_entries.int_id and ttrss_user_entries.owner_uid = $owner_uid"); - array_push($sub_ands, "ttrss_user_entries.ref_id = ttrss_entries.id"); - $from_qpart = " FROM " . implode(", ", $sub_selects) . ", ttrss_user_entries, ttrss_entries"; - $where_qpart = " WHERE " . implode(" AND ", $sub_ands); - } - // error_log("TAG SQL: " . $tag_sql); - // $tag_sql = "tag_name = '$feed'"; DEFAULT way - - // error_log("[". $select_qpart . "][" . $from_qpart . "][" .$where_qpart . "]"); - $result = db_query($select_qpart . $from_qpart . $where_qpart); - } - - return array($result, $feed_title, $feed_site_url, $last_error, $last_updated); - - } - - function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false) { - if (!$owner) $owner = $_SESSION["uid"]; - - $res = trim($str); if (!$res) return ''; - - if (strpos($res, "href=") === false) - $res = rewrite_urls($res); - - $charset_hack = '<head> - <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> - </head>'; - - $res = trim($res); if (!$res) return ''; - - libxml_use_internal_errors(true); - - $doc = new DOMDocument(); - $doc->loadHTML($charset_hack . $res); - $xpath = new DOMXPath($doc); - - $entries = $xpath->query('(//a[@href]|//img[@src])'); - - foreach ($entries as $entry) { - - if ($site_url) { - - if ($entry->hasAttribute('href')) - $entry->setAttribute('href', - rewrite_relative_url($site_url, $entry->getAttribute('href'))); - - if ($entry->hasAttribute('src')) { - $src = rewrite_relative_url($site_url, $entry->getAttribute('src')); - - $cached_filename = CACHE_DIR . '/images/' . sha1($src) . '.png'; - - if (file_exists($cached_filename)) { - $src = SELF_URL_PATH . '/image.php?hash=' . sha1($src); - } - - $entry->setAttribute('src', $src); - } - - if ($entry->nodeName == 'img') { - if (($owner && get_pref("STRIP_IMAGES", $owner)) || - $force_remove_images || $_SESSION["bw_limit"]) { - - $p = $doc->createElement('p'); - - $a = $doc->createElement('a'); - $a->setAttribute('href', $entry->getAttribute('src')); - - $a->appendChild(new DOMText($entry->getAttribute('src'))); - $a->setAttribute('target', '_blank'); - - $p->appendChild($a); - - $entry->parentNode->replaceChild($p, $entry); - } - } - } - - if (strtolower($entry->nodeName) == "a") { - $entry->setAttribute("target", "_blank"); - } - } - - $entries = $xpath->query('//iframe'); - foreach ($entries as $entry) { - $entry->setAttribute('sandbox', 'allow-scripts'); - - } - - $allowed_elements = array('a', 'address', 'audio', 'article', 'aside', - 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', - 'caption', 'cite', 'center', 'code', 'col', 'colgroup', - 'data', 'dd', 'del', 'details', 'div', 'dl', 'font', - 'dt', 'em', 'footer', 'figure', 'figcaption', - 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i', - 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript', - 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section', - 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary', - 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time', - 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' ); - - if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe'; - - $disallowed_attributes = array('id', 'style', 'class'); - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) { - $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes); - if (is_array($retval)) { - $doc = $retval[0]; - $allowed_elements = $retval[1]; - $disallowed_attributes = $retval[2]; - } else { - $doc = $retval; - } - } - - $doc->removeChild($doc->firstChild); //remove doctype - $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes); - $res = $doc->saveHTML(); - return $res; - } - - function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) { - $xpath = new DOMXPath($doc); - $entries = $xpath->query('//*'); - - foreach ($entries as $entry) { - if (!in_array($entry->nodeName, $allowed_elements)) { - $entry->parentNode->removeChild($entry); - } - - if ($entry->hasAttributes()) { - $attrs_to_remove = array(); - - foreach ($entry->attributes as $attr) { - - if (strpos($attr->nodeName, 'on') === 0) { - array_push($attrs_to_remove, $attr); - } - - if (in_array($attr->nodeName, $disallowed_attributes)) { - array_push($attrs_to_remove, $attr); - } - } - - foreach ($attrs_to_remove as $attr) { - $entry->removeAttributeNode($attr); - } - } - } - - return $doc; - } - - function check_for_update() { - if (CHECK_FOR_NEW_VERSION && $_SESSION['access_level'] >= 10) { - $version_url = "http://tt-rss.org/version.php?ver=" . VERSION . - "&iid=" . sha1(SELF_URL_PATH); - - $version_data = @fetch_file_contents($version_url); - - if ($version_data) { - $version_data = json_decode($version_data, true); - if ($version_data && $version_data['version']) { - if (version_compare(VERSION_STATIC, $version_data['version']) == -1) { - return $version_data; - } - } - } - } - return false; - } - - function catchupArticlesById($ids, $cmode, $owner_uid = false) { - - if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - if (count($ids) == 0) return; - - $tmp_ids = array(); - - foreach ($ids as $id) { - array_push($tmp_ids, "ref_id = '$id'"); - } - - $ids_qpart = join(" OR ", $tmp_ids); - - if ($cmode == 0) { - db_query("UPDATE ttrss_user_entries SET - unread = false,last_read = NOW() - WHERE ($ids_qpart) AND owner_uid = $owner_uid"); - } else if ($cmode == 1) { - db_query("UPDATE ttrss_user_entries SET - unread = true - WHERE ($ids_qpart) AND owner_uid = $owner_uid"); - } else { - db_query("UPDATE ttrss_user_entries SET - unread = NOT unread,last_read = NOW() - WHERE ($ids_qpart) AND owner_uid = $owner_uid"); - } - - /* update ccache */ - - $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries - WHERE ($ids_qpart) AND owner_uid = $owner_uid"); - - while ($line = db_fetch_assoc($result)) { - ccache_update($line["feed_id"], $owner_uid); - } - } - - function get_article_tags($id, $owner_uid = 0, $tag_cache = false) { - - $a_id = db_escape_string($id); - - if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - - $query = "SELECT DISTINCT tag_name, - owner_uid as owner FROM - ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE - ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name"; - - $tags = array(); - - /* check cache first */ - - if ($tag_cache === false) { - $result = db_query("SELECT tag_cache FROM ttrss_user_entries - WHERE ref_id = '$id' AND owner_uid = $owner_uid"); - - $tag_cache = db_fetch_result($result, 0, "tag_cache"); - } - - if ($tag_cache) { - $tags = explode(",", $tag_cache); - } else { - - /* do it the hard way */ - - $tmp_result = db_query($query); - - while ($tmp_line = db_fetch_assoc($tmp_result)) { - array_push($tags, $tmp_line["tag_name"]); - } - - /* update the cache */ - - $tags_str = db_escape_string(join(",", $tags)); - - db_query("UPDATE ttrss_user_entries - SET tag_cache = '$tags_str' WHERE ref_id = '$id' - AND owner_uid = $owner_uid"); - } - - return $tags; - } - - function trim_array($array) { - $tmp = $array; - array_walk($tmp, 'trim'); - return $tmp; - } - - function tag_is_valid($tag) { - if ($tag == '') return false; - if (preg_match("/^[0-9]*$/", $tag)) return false; - if (mb_strlen($tag) > 250) return false; - - if (function_exists('iconv')) { - $tag = iconv("utf-8", "utf-8", $tag); - } - - if (!$tag) return false; - - return true; - } - - function render_login_form() { - header('Cache-Control: public'); - - require_once "login_form.php"; - exit; - } - - function format_warning($msg, $id = "") { - global $link; - return "<div class=\"warning\" id=\"$id\"> - <span><img src=\"images/sign_excl.svg\"></span><span>$msg</span></div>"; - } - - function format_notice($msg, $id = "") { - global $link; - return "<div class=\"notice\" id=\"$id\"> - <span><img src=\"images/sign_info.svg\"></span><span>$msg</span></div>"; - } - - function format_error($msg, $id = "") { - global $link; - return "<div class=\"error\" id=\"$id\"> - <span><img src=\"images/sign_excl.svg\"></span><span>$msg</span></div>"; - } - - function print_notice($msg) { - return print format_notice($msg); - } - - function print_warning($msg) { - return print format_warning($msg); - } - - function print_error($msg) { - return print format_error($msg); - } - - - function T_sprintf() { - $args = func_get_args(); - return vsprintf(__(array_shift($args)), $args); - } - - function format_inline_player($url, $ctype) { - - $entry = ""; - - $url = htmlspecialchars($url); - - if (strpos($ctype, "audio/") === 0) { - - if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false || - $_SESSION["hasMp3"])) { - - $entry .= "<audio controls> - <source type=\"$ctype\" src=\"$url\"></source> - </audio>"; - - } else { - - $entry .= "<object type=\"application/x-shockwave-flash\" - data=\"lib/button/musicplayer.swf?song_url=$url\" - width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'> - <param name=\"movie\" - value=\"lib/button/musicplayer.swf?song_url=$url\" /> - </object>"; - } - - if ($entry) $entry .= " <a target=\"_blank\" - href=\"$url\">" . basename($url) . "</a>"; - - return $entry; - - } - - return ""; - -/* $filename = substr($url, strrpos($url, "/")+1); - - $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" . - $filename . " (" . $ctype . ")" . "</a>"; */ - - } - - function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) { - if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - - $rv = array(); - - $rv['id'] = $id; - - /* we can figure out feed_id from article id anyway, why do we - * pass feed_id here? let's ignore the argument :(*/ - - $result = db_query("SELECT feed_id FROM ttrss_user_entries - WHERE ref_id = '$id'"); - - $feed_id = (int) db_fetch_result($result, 0, "feed_id"); - - $rv['feed_id'] = $feed_id; - - //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; }; - - if ($mark_as_read) { - $result = db_query("UPDATE ttrss_user_entries - SET unread = false,last_read = NOW() - WHERE ref_id = '$id' AND owner_uid = $owner_uid"); - - ccache_update($feed_id, $owner_uid); - } - - $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id, - ".SUBSTRING_FOR_DATE."(updated,1,16) as updated, - (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url, - (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images, - (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures, - num_comments, - tag_cache, - author, - orig_feed_id, - note, - cached_content - FROM ttrss_entries,ttrss_user_entries - WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid"); - - if ($result) { - - $line = db_fetch_assoc($result); - - $tag_cache = $line["tag_cache"]; - - $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]); - unset($line["tag_cache"]); - - $line["content"] = sanitize($line["content"], - sql_bool_to_bool($line['hide_images']), - $owner_uid, $line["site_url"]); - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) { - $line = $p->hook_render_article($line); - } - - $num_comments = $line["num_comments"]; - $entry_comments = ""; - - if ($num_comments > 0) { - if ($line["comments"]) { - $comments_url = htmlspecialchars($line["comments"]); - } else { - $comments_url = htmlspecialchars($line["link"]); - } - $entry_comments = "<a target='_blank' href=\"$comments_url\">$num_comments comments</a>"; - } else { - if ($line["comments"] && $line["link"] != $line["comments"]) { - $entry_comments = "<a target='_blank' href=\"".htmlspecialchars($line["comments"])."\">comments</a>"; - } - } - - if ($zoom_mode) { - header("Content-Type: text/html"); - $rv['content'] .= "<html><head> - <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/> - <title>Tiny Tiny RSS - ".$line["title"]."</title> - <link rel=\"stylesheet\" type=\"text/css\" href=\"css/tt-rss.css\"> - </head><body id=\"ttrssZoom\">"; - } - - $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">"; - - $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">"; - - $entry_author = $line["author"]; - - if ($entry_author) { - $entry_author = __(" - ") . $entry_author; - } - - $parsed_updated = make_local_datetime($line["updated"], true, - $owner_uid, true); - - $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>"; - - if ($line["link"]) { - $rv['content'] .= "<div class='postTitle'><a target='_blank' - title=\"".htmlspecialchars($line['title'])."\" - href=\"" . - htmlspecialchars($line["link"]) . "\">" . - $line["title"] . "</a>" . - "<span class='author'>$entry_author</span></div>"; - } else { - $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>"; - } - - $tags_str = format_tags_string($line["tags"], $id); - $tags_str_full = join(", ", $line["tags"]); - - if (!$tags_str_full) $tags_str_full = __("no tags"); - - if (!$entry_comments) $entry_comments = " "; # placeholder - - $rv['content'] .= "<div class='postTags' style='float : right'> - <img src='images/tag.png' - class='tagsPic' alt='Tags' title='Tags'> "; - - if (!$zoom_mode) { - $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span> - <a title=\"".__('Edit tags for this article')."\" - href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>"; - - $rv['content'] .= "<div dojoType=\"dijit.Tooltip\" - id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\" - position=\"below\">$tags_str_full</div>"; - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) { - $rv['content'] .= $p->hook_article_button($line); - } - - } else { - $tags_str = strip_tags($tags_str); - $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>"; - } - $rv['content'] .= "</div>"; - $rv['content'] .= "<div clear='both'>"; - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) { - $rv['content'] .= $p->hook_article_left_button($line); - } - - $rv['content'] .= "$entry_comments</div>"; - - if ($line["orig_feed_id"]) { - - $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds - WHERE id = ".$line["orig_feed_id"]); - - if (db_num_rows($tmp_result) != 0) { - - $rv['content'] .= "<div clear='both'>"; - $rv['content'] .= __("Originally from:"); - - $rv['content'] .= " "; - - $tmp_line = db_fetch_assoc($tmp_result); - - $rv['content'] .= "<a target='_blank' - href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" . - $tmp_line['title'] . "</a>"; - - $rv['content'] .= " "; - - $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>"; - $rv['content'] .= "<img title='".__('Feed URL')."'class='tinyFeedIcon' src='images/pub_set.svg'></a>"; - - $rv['content'] .= "</div>"; - } - } - - $rv['content'] .= "</div>"; - - $rv['content'] .= "<div id=\"POSTNOTE-$id\">"; - if ($line['note']) { - $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode); - } - $rv['content'] .= "</div>"; - - $rv['content'] .= "<div class=\"postContent\">"; - - $rv['content'] .= $line["content"]; - $rv['content'] .= format_article_enclosures($id, - sql_bool_to_bool($line["always_display_enclosures"]), - $line["content"], - sql_bool_to_bool($line["hide_images"])); - - $rv['content'] .= "</div>"; - - $rv['content'] .= "</div>"; - - } - - if ($zoom_mode) { - $rv['content'] .= " - <div class='footer'> - <button onclick=\"return window.close()\">". - __("Close this window")."</button></div>"; - $rv['content'] .= "</body></html>"; - } - - return $rv; - - } - - function print_checkpoint($n, $s) { - $ts = microtime(true); - echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s); - return $ts; - } - - function sanitize_tag($tag) { - $tag = trim($tag); - - $tag = mb_strtolower($tag, 'utf-8'); - - $tag = preg_replace('/[\'\"\+\>\<]/', "", $tag); - -// $tag = str_replace('"', "", $tag); -// $tag = str_replace("+", " ", $tag); - $tag = str_replace("technorati tag: ", "", $tag); - - return $tag; - } - - function get_self_url_prefix() { - if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) { - return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1); - } else { - return SELF_URL_PATH; - } - } - - /** - * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI. - * - * @return string The Mozilla Firefox feed adding URL. - */ - function add_feed_url() { - //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH); - - $url_path = get_self_url_prefix() . - "/public.php?op=subscribe&feed_url=%s"; - return $url_path; - } // function add_feed_url - - function encrypt_password($pass, $salt = '', $mode2 = false) { - if ($salt && $mode2) { - return "MODE2:" . hash('sha256', $salt . $pass); - } else if ($salt) { - return "SHA1X:" . sha1("$salt:$pass"); - } else { - return "SHA1:" . sha1($pass); - } - } // function encrypt_password - - function load_filters($feed_id, $owner_uid, $action_id = false) { - $filters = array(); - - $cat_id = (int)getFeedCategory($feed_id); - - $result = db_query("SELECT * FROM ttrss_filters2 WHERE - owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title"); - - $check_cats = join(",", array_merge( - getParentCategories($cat_id, $owner_uid), - array($cat_id))); - - while ($line = db_fetch_assoc($result)) { - $filter_id = $line["id"]; - - $result2 = db_query("SELECT - r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name - FROM ttrss_filters2_rules AS r, - ttrss_filter_types AS t - WHERE - (cat_id IS NULL OR cat_id IN ($check_cats)) AND - (feed_id IS NULL OR feed_id = '$feed_id') AND - filter_type = t.id AND filter_id = '$filter_id'"); - - $rules = array(); - $actions = array(); - - while ($rule_line = db_fetch_assoc($result2)) { -# print_r($rule_line); - - $rule = array(); - $rule["reg_exp"] = $rule_line["reg_exp"]; - $rule["type"] = $rule_line["type_name"]; - $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]); - - array_push($rules, $rule); - } - - $result2 = db_query("SELECT a.action_param,t.name AS type_name - FROM ttrss_filters2_actions AS a, - ttrss_filter_actions AS t - WHERE - action_id = t.id AND filter_id = '$filter_id'"); - - while ($action_line = db_fetch_assoc($result2)) { -# print_r($action_line); - - $action = array(); - $action["type"] = $action_line["type_name"]; - $action["param"] = $action_line["action_param"]; - - array_push($actions, $action); - } - - - $filter = array(); - $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]); - $filter["inverse"] = sql_bool_to_bool($line["inverse"]); - $filter["rules"] = $rules; - $filter["actions"] = $actions; - - if (count($rules) > 0 && count($actions) > 0) { - array_push($filters, $filter); - } - } - - return $filters; - } - - function get_score_pic($score) { - if ($score > 100) { - return "score_high.png"; - } else if ($score > 0) { - return "score_half_high.png"; - } else if ($score < -100) { - return "score_low.png"; - } else if ($score < 0) { - return "score_half_low.png"; - } else { - return "score_neutral.png"; - } - } - - function feed_has_icon($id) { - return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0; - } - - function init_plugins() { - PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL); - - return true; - } - - function format_tags_string($tags, $id) { - if (!is_array($tags) || count($tags) == 0) { - return __("no tags"); - } else { - $maxtags = min(5, count($tags)); - - for ($i = 0; $i < $maxtags; $i++) { - $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed('".$tags[$i]."')\">" . $tags[$i] . "</a>, "; - } - - $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2); - - if (count($tags) > $maxtags) - $tags_str .= ", …"; - - return $tags_str; - } - } - - function format_article_labels($labels, $id) { - - if (!is_array($labels)) return ''; - - $labels_str = ""; - - foreach ($labels as $l) { - $labels_str .= sprintf("<span class='hlLabelRef' - style='color : %s; background-color : %s'>%s</span>", - $l[2], $l[3], $l[1]); - } - - return $labels_str; - - } - - function format_article_note($id, $note, $allow_edit = true) { - - $str = "<div class='articleNote' onclick=\"editArticleNote($id)\"> - <div class='noteEdit' onclick=\"editArticleNote($id)\">". - ($allow_edit ? __('(edit note)') : "")."</div>$note</div>"; - - return $str; - } - - - function get_feed_category($feed_cat, $parent_cat_id = false) { - if ($parent_cat_id) { - $parent_qpart = "parent_cat = '$parent_cat_id'"; - $parent_insert = "'$parent_cat_id'"; - } else { - $parent_qpart = "parent_cat IS NULL"; - $parent_insert = "NULL"; - } - - $result = db_query( - "SELECT id FROM ttrss_feed_categories - WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]); - - if (db_num_rows($result) == 0) { - return false; - } else { - return db_fetch_result($result, 0, "id"); - } - } - - function add_feed_category($feed_cat, $parent_cat_id = false) { - - if (!$feed_cat) return false; - - db_query("BEGIN"); - - if ($parent_cat_id) { - $parent_qpart = "parent_cat = '$parent_cat_id'"; - $parent_insert = "'$parent_cat_id'"; - } else { - $parent_qpart = "parent_cat IS NULL"; - $parent_insert = "NULL"; - } - - $feed_cat = mb_substr($feed_cat, 0, 250); - - $result = db_query( - "SELECT id FROM ttrss_feed_categories - WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]); - - if (db_num_rows($result) == 0) { - - $result = db_query( - "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat) - VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)"); - - db_query("COMMIT"); - - return true; - } - - return false; - } - - function getArticleFeed($id) { - $result = db_query("SELECT feed_id FROM ttrss_user_entries - WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]); - - if (db_num_rows($result) != 0) { - return db_fetch_result($result, 0, "feed_id"); - } else { - return 0; - } - } - - /** - * Fixes incomplete URLs by prepending "http://". - * Also replaces feed:// with http://, and - * prepends a trailing slash if the url is a domain name only. - * - * @param string $url Possibly incomplete URL - * - * @return string Fixed URL. - */ - function fix_url($url) { - if (strpos($url, '://') === false) { - $url = 'http://' . $url; - } else if (substr($url, 0, 5) == 'feed:') { - $url = 'http:' . substr($url, 5); - } - - //prepend slash if the URL has no slash in it - // "http://www.example" -> "http://www.example/" - if (strpos($url, '/', strpos($url, ':') + 3) === false) { - $url .= '/'; - } - - if ($url != "http:///") - return $url; - else - return ''; - } - - function validate_feed_url($url) { - $parts = parse_url($url); - - return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https'); - - } - - function get_article_enclosures($id) { - - $query = "SELECT * FROM ttrss_enclosures - WHERE post_id = '$id' AND content_url != ''"; - - $rv = array(); - - $result = db_query($query); - - if (db_num_rows($result) > 0) { - while ($line = db_fetch_assoc($result)) { - array_push($rv, $line); - } - } - - return $rv; - } - - function save_email_address($email) { - // FIXME: implement persistent storage of emails - - if (!$_SESSION['stored_emails']) - $_SESSION['stored_emails'] = array(); - - if (!in_array($email, $_SESSION['stored_emails'])) - array_push($_SESSION['stored_emails'], $email); - } - - - function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) { - - if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - - $sql_is_cat = bool_to_sql_bool($is_cat); - - $result = db_query("SELECT access_key FROM ttrss_access_keys - WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat - AND owner_uid = " . $owner_uid); - - if (db_num_rows($result) == 1) { - return db_fetch_result($result, 0, "access_key"); - } else { - $key = db_escape_string(sha1(uniqid(rand(), true))); - - $result = db_query("INSERT INTO ttrss_access_keys - (access_key, feed_id, is_cat, owner_uid) - VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')"); - - return $key; - } - return false; - } - - function get_feeds_from_html($url, $content) - { - $url = fix_url($url); - $baseUrl = substr($url, 0, strrpos($url, '/') + 1); - - libxml_use_internal_errors(true); - - $doc = new DOMDocument(); - $doc->loadHTML($content); - $xpath = new DOMXPath($doc); - $entries = $xpath->query('/html/head/link[@rel="alternate"]'); - $feedUrls = array(); - foreach ($entries as $entry) { - if ($entry->hasAttribute('href')) { - $title = $entry->getAttribute('title'); - if ($title == '') { - $title = $entry->getAttribute('type'); - } - $feedUrl = rewrite_relative_url( - $baseUrl, $entry->getAttribute('href') - ); - $feedUrls[$feedUrl] = $title; - } - } - return $feedUrls; - } - - function is_html($content) { - return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 20)) !== 0; - } - - function url_is_html($url, $login = false, $pass = false) { - return is_html(fetch_file_contents($url, false, $login, $pass)); - } - - function print_label_select($name, $value, $attributes = "") { - - $result = db_query("SELECT caption FROM ttrss_labels2 - WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption"); - - print "<select default=\"$value\" name=\"" . htmlspecialchars($name) . - "\" $attributes onchange=\"labelSelectOnChange(this)\" >"; - - while ($line = db_fetch_assoc($result)) { - - $issel = ($line["caption"] == $value) ? "selected=\"1\"" : ""; - - print "<option value=\"".htmlspecialchars($line["caption"])."\" - $issel>" . htmlspecialchars($line["caption"]) . "</option>"; - - } - -# print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>"; - - print "</select>"; - - - } - - function format_article_enclosures($id, $always_display_enclosures, - $article_content, $hide_images = false) { - - $result = get_article_enclosures($id); - $rv = ''; - - if (count($result) > 0) { - - $entries_html = array(); - $entries = array(); - $entries_inline = array(); - - foreach ($result as $line) { - - $url = $line["content_url"]; - $ctype = $line["content_type"]; - - if (!$ctype) $ctype = __("unknown type"); - - $filename = substr($url, strrpos($url, "/")+1); - - $player = format_inline_player($url, $ctype); - - if ($player) array_push($entries_inline, $player); - -# $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" . -# $filename . " (" . $ctype . ")" . "</a>"; - - $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\" - dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>"; - - array_push($entries_html, $entry); - - $entry = array(); - - $entry["type"] = $ctype; - $entry["filename"] = $filename; - $entry["url"] = $url; - - array_push($entries, $entry); - } - - if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) { - if ($always_display_enclosures || - !preg_match("/<img/i", $article_content)) { - - foreach ($entries as $entry) { - - if (preg_match("/image/", $entry["type"]) || - preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) { - - if (!$hide_images) { - $rv .= "<p><img - alt=\"".htmlspecialchars($entry["filename"])."\" - src=\"" .htmlspecialchars($entry["url"]) . "\"/></p>"; - } else { - $rv .= "<p><a target=\"_blank\" - href=\"".htmlspecialchars($entry["url"])."\" - >" .htmlspecialchars($entry["url"]) . "</a></p>"; - - } - } - } - } - } - - if (count($entries_inline) > 0) { - $rv .= "<hr clear='both'/>"; - foreach ($entries_inline as $entry) { $rv .= $entry; }; - $rv .= "<hr clear='both'/>"; - } - - $rv .= "<select class=\"attachments\" onchange=\"openSelectedAttachment(this)\">". - "<option value=''>" . __('Attachments')."</option>"; - - foreach ($entries as $entry) { - $rv .= "<option value=\"".htmlspecialchars($entry["url"])."\">" . htmlspecialchars($entry["filename"]) . "</option>"; - - }; - - $rv .= "</select>"; - } - - return $rv; - } - - function getLastArticleId() { - $result = db_query("SELECT MAX(ref_id) AS id FROM ttrss_user_entries - WHERE owner_uid = " . $_SESSION["uid"]); - - if (db_num_rows($result) == 1) { - return db_fetch_result($result, 0, "id"); - } else { - return -1; - } - } - - function build_url($parts) { - return $parts['scheme'] . "://" . $parts['host'] . $parts['path']; - } - - /** - * Converts a (possibly) relative URL to a absolute one. - * - * @param string $url Base URL (i.e. from where the document is) - * @param string $rel_url Possibly relative URL in the document - * - * @return string Absolute URL - */ - function rewrite_relative_url($url, $rel_url) { - if (strpos($rel_url, "magnet:") === 0) { - return $rel_url; - } else if (strpos($rel_url, "://") !== false) { - return $rel_url; - } else if (strpos($rel_url, "//") === 0) { - # protocol-relative URL (rare but they exist) - return $rel_url; - } else if (strpos($rel_url, "/") === 0) - { - $parts = parse_url($url); - $parts['path'] = $rel_url; - - return build_url($parts); - - } else { - $parts = parse_url($url); - if (!isset($parts['path'])) { - $parts['path'] = '/'; - } - $dir = $parts['path']; - if (substr($dir, -1) !== '/') { - $dir = dirname($parts['path']); - $dir !== '/' && $dir .= '/'; - } - $parts['path'] = $dir . $rel_url; - - return build_url($parts); - } - } - - function sphinx_search($query, $offset = 0, $limit = 30) { - require_once 'lib/sphinxapi.php'; - - $sphinxClient = new SphinxClient(); - - $sphinxpair = explode(":", SPHINX_SERVER, 2); - - $sphinxClient->SetServer($sphinxpair[0], (int)$sphinxpair[1]); - $sphinxClient->SetConnectTimeout(1); - - $sphinxClient->SetFieldWeights(array('title' => 70, 'content' => 30, - 'feed_title' => 20)); - - $sphinxClient->SetMatchMode(SPH_MATCH_EXTENDED2); - $sphinxClient->SetRankingMode(SPH_RANK_PROXIMITY_BM25); - $sphinxClient->SetLimits($offset, $limit, 1000); - $sphinxClient->SetArrayResult(false); - $sphinxClient->SetFilter('owner_uid', array($_SESSION['uid'])); - - $result = $sphinxClient->Query($query, SPHINX_INDEX); - - $ids = array(); - - if (is_array($result['matches'])) { - foreach (array_keys($result['matches']) as $int_id) { - $ref_id = $result['matches'][$int_id]['attrs']['ref_id']; - array_push($ids, $ref_id); - } - } - - return $ids; - } - - function cleanup_tags($days = 14, $limit = 1000) { - - if (DB_TYPE == "pgsql") { - $interval_query = "date_updated < NOW() - INTERVAL '$days days'"; - } else if (DB_TYPE == "mysql") { - $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)"; - } - - $tags_deleted = 0; - - while ($limit > 0) { - $limit_part = 500; - - $query = "SELECT ttrss_tags.id AS id - FROM ttrss_tags, ttrss_user_entries, ttrss_entries - WHERE post_int_id = int_id AND $interval_query AND - ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part"; - - $result = db_query($query); - - $ids = array(); - - while ($line = db_fetch_assoc($result)) { - array_push($ids, $line['id']); - } - - if (count($ids) > 0) { - $ids = join(",", $ids); - - $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)"); - $tags_deleted += db_affected_rows($tmp_result); - } else { - break; - } - - $limit -= $limit_part; - } - - return $tags_deleted; - } - - function print_user_stylesheet() { - $value = get_pref('USER_STYLESHEET'); - - if ($value) { - print "<style type=\"text/css\">"; - print str_replace("<br/>", "\n", $value); - print "</style>"; - } - - } - - function rewrite_urls($html) { - libxml_use_internal_errors(true); - - $charset_hack = '<head> - <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> - </head>'; - - $doc = new DOMDocument(); - $doc->loadHTML($charset_hack . $html); - $xpath = new DOMXPath($doc); - - $entries = $xpath->query('//*/text()'); - - foreach ($entries as $entry) { - if (strstr($entry->wholeText, "://") !== false) { - $text = preg_replace("/((?<!=.)((http|https|ftp)+):\/\/[^ ,!]+)/i", - "<a target=\"_blank\" href=\"\\1\">\\1</a>", $entry->wholeText); - - if ($text != $entry->wholeText) { - $cdoc = new DOMDocument(); - $cdoc->loadHTML($charset_hack . $text); - - - foreach ($cdoc->childNodes as $cnode) { - $cnode = $doc->importNode($cnode, true); - - if ($cnode) { - $entry->parentNode->insertBefore($cnode); - } - } - - $entry->parentNode->removeChild($entry); - - } - } - } - - $node = $doc->getElementsByTagName('body')->item(0); - - // http://tt-rss.org/forum/viewtopic.php?f=1&t=970 - if ($node) - return $doc->saveXML($node); - else - return $html; - } - - function filter_to_sql($filter, $owner_uid) { - $query = array(); - - if (DB_TYPE == "pgsql") - $reg_qpart = "~"; - else - $reg_qpart = "REGEXP"; - - foreach ($filter["rules"] AS $rule) { - $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/', - $rule['reg_exp']) !== FALSE; - - if ($regexp_valid) { - - $rule['reg_exp'] = db_escape_string($rule['reg_exp']); - - switch ($rule["type"]) { - case "title": - $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('". - $rule['reg_exp'] . "')"; - break; - case "content": - $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('". - $rule['reg_exp'] . "')"; - break; - case "both": - $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('". - $rule['reg_exp'] . "') OR LOWER(" . - "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')"; - break; - case "tag": - $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('". - $rule['reg_exp'] . "')"; - break; - case "link": - $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('". - $rule['reg_exp'] . "')"; - break; - case "author": - $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('". - $rule['reg_exp'] . "')"; - break; - } - - if (isset($rule['inverse'])) $qpart = "NOT ($qpart)"; - - if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) { - $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]); - } - - if (isset($rule["cat_id"])) { - - if ($rule["cat_id"] > 0) { - $children = getChildCategories($rule["cat_id"], $owner_uid); - array_push($children, $rule["cat_id"]); - - $children = join(",", $children); - - $cat_qpart = "cat_id IN ($children)"; - } else { - $cat_qpart = "cat_id IS NULL"; - } - - $qpart .= " AND $cat_qpart"; - } - - array_push($query, "($qpart)"); - - } - } - - if (count($query) > 0) { - $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")"; - } else { - $fullquery = "(false)"; - } - - if ($filter['inverse']) $fullquery = "(NOT $fullquery)"; - - return $fullquery; - } - - if (!function_exists('gzdecode')) { - function gzdecode($string) { // no support for 2nd argument - return file_get_contents('compress.zlib://data:who/cares;base64,'. - base64_encode($string)); - } - } - - function get_random_bytes($length) { - if (function_exists('openssl_random_pseudo_bytes')) { - return openssl_random_pseudo_bytes($length); - } else { - $output = ""; - - for ($i = 0; $i < $length; $i++) - $output .= chr(mt_rand(0, 255)); - - return $output; - } - } - - function read_stdin() { - $fp = fopen("php://stdin", "r"); - - if ($fp) { - $line = trim(fgets($fp)); - fclose($fp); - return $line; - } - - return null; - } - - function tmpdirname($path, $prefix) { - // Use PHP's tmpfile function to create a temporary - // directory name. Delete the file and keep the name. - $tempname = tempnam($path,$prefix); - if (!$tempname) - return false; - - if (!unlink($tempname)) - return false; - - return $tempname; - } - - function getFeedCategory($feed) { - $result = db_query("SELECT cat_id FROM ttrss_feeds - WHERE id = '$feed'"); - - if (db_num_rows($result) > 0) { - return db_fetch_result($result, 0, "cat_id"); - } else { - return false; - } - - } - - function implements_interface($class, $interface) { - return in_array($interface, class_implements($class)); - } - - function geturl($url){ - - if (!function_exists('curl_init')) - 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); - - $curl = curl_init(); - $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,"; - $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"; - $header[] = "Cache-Control: max-age=0"; - $header[] = "Connection: keep-alive"; - $header[] = "Keep-Alive: 300"; - $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7"; - $header[] = "Accept-Language: en-us,en;q=0.5"; - $header[] = "Pragma: "; - - curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0'); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header); - curl_setopt($curl, CURLOPT_HEADER, true); - curl_setopt($curl, CURLOPT_REFERER, $url); - curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate'); - curl_setopt($curl, CURLOPT_AUTOREFERER, true); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); - //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled... - curl_setopt($curl, CURLOPT_TIMEOUT, 60); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); - - $html = curl_exec($curl); - - $status = curl_getinfo($curl); - - if($status['http_code']!=200){ - if($status['http_code'] == 301 || $status['http_code'] == 302) { - curl_close($curl); - list($header) = explode("\r\n\r\n", $html, 2); - $matches = array(); - preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches); - $url = trim(str_replace($matches[1],"",$matches[0])); - $url_parsed = parse_url($url); - return (isset($url_parsed))? geturl($url):''; - } - - global $fetch_last_error; - - $fetch_last_error = curl_errno($curl) . " " . curl_error($curl); - curl_close($curl); - - $oline=''; - foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';} - $line =$oline." \r\n ".$url."\r\n-----------------\r\n"; -# $handle = @fopen('./curl.error.log', 'a'); -# fwrite($handle, $line); - return FALSE; - } - curl_close($curl); - return $url; - } - - function get_minified_js($files) { - require_once 'lib/jshrink/Minifier.php'; - - $rv = ''; - - foreach ($files as $js) { - if (!isset($_GET['debug'])) { - $cached_file = CACHE_DIR . "/js/".basename($js).".js"; - - if (file_exists($cached_file) && - is_readable($cached_file) && - filemtime($cached_file) >= filemtime("js/$js.js")) { - - $rv .= file_get_contents($cached_file); - - } else { - $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js")); - file_put_contents($cached_file, $minified); - $rv .= $minified; - } - } else { - $rv .= file_get_contents("js/$js.js"); - } - } - - return $rv; - } - - function stylesheet_tag($filename) { - $timestamp = filemtime($filename); - - echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n"; - } - - function javascript_tag($filename) { - $query = ""; - - if (!(strpos($filename, "?") === FALSE)) { - $query = substr($filename, strpos($filename, "?")+1); - $filename = substr($filename, 0, strpos($filename, "?")); - } - - $timestamp = filemtime($filename); - - if ($query) $timestamp .= "&$query"; - - echo "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n"; - } - - function calculate_dep_timestamp() { - $files = array_merge(glob("js/*.js"), glob("css/*.css")); - - $max_ts = -1; - - foreach ($files as $file) { - if (filemtime($file) > $max_ts) $max_ts = filemtime($file); - } - - return $max_ts; - } - - function T_js_decl($s1, $s2) { - if ($s1 && $s2) { - $s1 = preg_replace("/\n/", "", $s1); - $s2 = preg_replace("/\n/", "", $s2); - - $s1 = preg_replace("/\"/", "\\\"", $s1); - $s2 = preg_replace("/\"/", "\\\"", $s2); - - return "T_messages[\"$s1\"] = \"$s2\";\n"; - } - } - - function init_js_translations() { - - print 'var T_messages = new Object(); - - function __(msg) { - if (T_messages[msg]) { - return T_messages[msg]; - } else { - return msg; - } - } - - function ngettext(msg1, msg2, n) { - return (parseInt(n) > 1) ? msg2 : msg1; - }'; - - $l10n = _get_reader(); - - for ($i = 0; $i < $l10n->total; $i++) { - $orig = $l10n->get_original_string($i); - $translation = __($orig); - - print T_js_decl($orig, $translation); - } - } - - function label_to_feed_id($label) { - return LABEL_BASE_INDEX - 1 - abs($label); - } - - function feed_to_label_id($feed) { - return LABEL_BASE_INDEX - 1 + abs($feed); - } - - function format_libxml_error($error) { - return T_sprintf("LibXML error %s at line %d (column %d): %s", - $error->code, $error->line, $error->column, - $error->message); - } + // TODO: less dumb splitting + require_once "functions2.php"; ?> diff --git a/include/functions2.php b/include/functions2.php new file mode 100644 index 000000000..672373e6d --- /dev/null +++ b/include/functions2.php @@ -0,0 +1,2403 @@ +<?php + function make_init_params() { + $params = array(); + + foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS", + "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP", + "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE", + "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) { + + $params[strtolower($param)] = (int) get_pref($param); + } + + $params["icons_url"] = ICONS_URL; + $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME; + $params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE"); + $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT"); + $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY"); + $params["bw_limit"] = (int) $_SESSION["bw_limit"]; + $params["label_base_index"] = (int) LABEL_BASE_INDEX; + $params["theme"] = get_pref("USER_CSS_THEME", false, false); + $params["plugins"] = implode(", ", PluginHost::getInstance()->get_plugin_names()); + + $params["php_platform"] = PHP_OS; + $params["php_version"] = PHP_VERSION; + + $params["sanity_checksum"] = sha1(file_get_contents("include/sanity_check.php")); + + $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM + ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]); + + $max_feed_id = db_fetch_result($result, 0, "mid"); + $num_feeds = db_fetch_result($result, 0, "nf"); + + $params["max_feed_id"] = (int) $max_feed_id; + $params["num_feeds"] = (int) $num_feeds; + + $params["hotkeys"] = get_hotkeys_map(); + + $params["csrf_token"] = $_SESSION["csrf_token"]; + $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"]; + + $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE; + + return $params; + } + + function get_hotkeys_info() { + $hotkeys = array( + __("Navigation") => array( + "next_feed" => __("Open next feed"), + "prev_feed" => __("Open previous feed"), + "next_article" => __("Open next article"), + "prev_article" => __("Open previous article"), + "next_article_noscroll" => __("Open next article (don't scroll long articles)"), + "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"), + "next_article_noexpand" => __("Move to next article (don't expand or mark read)"), + "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"), + "search_dialog" => __("Show search dialog")), + __("Article") => array( + "toggle_mark" => __("Toggle starred"), + "toggle_publ" => __("Toggle published"), + "toggle_unread" => __("Toggle unread"), + "edit_tags" => __("Edit tags"), + "dismiss_selected" => __("Dismiss selected"), + "dismiss_read" => __("Dismiss read"), + "open_in_new_window" => __("Open in new window"), + "catchup_below" => __("Mark below as read"), + "catchup_above" => __("Mark above as read"), + "article_scroll_down" => __("Scroll down"), + "article_scroll_up" => __("Scroll up"), + "select_article_cursor" => __("Select article under cursor"), + "email_article" => __("Email article"), + "close_article" => __("Close/collapse article"), + "toggle_expand" => __("Toggle article expansion (combined mode)"), + "toggle_widescreen" => __("Toggle widescreen mode"), + "toggle_embed_original" => __("Toggle embed original")), + __("Article selection") => array( + "select_all" => __("Select all articles"), + "select_unread" => __("Select unread"), + "select_marked" => __("Select starred"), + "select_published" => __("Select published"), + "select_invert" => __("Invert selection"), + "select_none" => __("Deselect everything")), + __("Feed") => array( + "feed_refresh" => __("Refresh current feed"), + "feed_unhide_read" => __("Un/hide read feeds"), + "feed_subscribe" => __("Subscribe to feed"), + "feed_edit" => __("Edit feed"), + "feed_catchup" => __("Mark as read"), + "feed_reverse" => __("Reverse headlines"), + "feed_debug_update" => __("Debug feed update"), + "catchup_all" => __("Mark all feeds as read"), + "cat_toggle_collapse" => __("Un/collapse current category"), + "toggle_combined_mode" => __("Toggle combined mode"), + "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")), + __("Go to") => array( + "goto_all" => __("All articles"), + "goto_fresh" => __("Fresh"), + "goto_marked" => __("Starred"), + "goto_published" => __("Published"), + "goto_tagcloud" => __("Tag cloud"), + "goto_prefs" => __("Preferences")), + __("Other") => array( + "create_label" => __("Create label"), + "create_filter" => __("Create filter"), + "collapse_sidebar" => __("Un/collapse sidebar"), + "help_dialog" => __("Show help dialog")) + ); + + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_INFO) as $plugin) { + $hotkeys = $plugin->hook_hotkey_info($hotkeys); + } + + return $hotkeys; + } + + function get_hotkeys_map() { + $hotkeys = array( +// "navigation" => array( + "k" => "next_feed", + "j" => "prev_feed", + "n" => "next_article", + "p" => "prev_article", + "(38)|up" => "prev_article", + "(40)|down" => "next_article", +// "^(38)|Ctrl-up" => "prev_article_noscroll", +// "^(40)|Ctrl-down" => "next_article_noscroll", + "(191)|/" => "search_dialog", +// "article" => array( + "s" => "toggle_mark", + "*s" => "toggle_publ", + "u" => "toggle_unread", + "*t" => "edit_tags", + "*d" => "dismiss_selected", + "*x" => "dismiss_read", + "o" => "open_in_new_window", + "c p" => "catchup_below", + "c n" => "catchup_above", + "*n" => "article_scroll_down", + "*p" => "article_scroll_up", + "*(38)|Shift+up" => "article_scroll_up", + "*(40)|Shift+down" => "article_scroll_down", + "a *w" => "toggle_widescreen", + "a e" => "toggle_embed_original", + "e" => "email_article", + "a q" => "close_article", +// "article_selection" => array( + "a a" => "select_all", + "a u" => "select_unread", + "a *u" => "select_marked", + "a p" => "select_published", + "a i" => "select_invert", + "a n" => "select_none", +// "feed" => array( + "f r" => "feed_refresh", + "f a" => "feed_unhide_read", + "f s" => "feed_subscribe", + "f e" => "feed_edit", + "f q" => "feed_catchup", + "f x" => "feed_reverse", + "f *d" => "feed_debug_update", + "f *c" => "toggle_combined_mode", + "f c" => "toggle_cdm_expanded", + "*q" => "catchup_all", + "x" => "cat_toggle_collapse", +// "goto" => array( + "g a" => "goto_all", + "g f" => "goto_fresh", + "g s" => "goto_marked", + "g p" => "goto_published", + "g t" => "goto_tagcloud", + "g *p" => "goto_prefs", +// "other" => array( + "(9)|Tab" => "select_article_cursor", // tab + "c l" => "create_label", + "c f" => "create_filter", + "c s" => "collapse_sidebar", + "^(191)|Ctrl+/" => "help_dialog", + ); + + if (get_pref('COMBINED_DISPLAY_MODE')) { + $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll"; + $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll"; + } + + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) { + $hotkeys = $plugin->hook_hotkey_map($hotkeys); + } + + $prefixes = array(); + + foreach (array_keys($hotkeys) as $hotkey) { + $pair = explode(" ", $hotkey, 2); + + if (count($pair) > 1 && !in_array($pair[0], $prefixes)) { + array_push($prefixes, $pair[0]); + } + } + + return array($prefixes, $hotkeys); + } + + function make_runtime_info() { + $data = array(); + + $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM + ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]); + + $max_feed_id = db_fetch_result($result, 0, "mid"); + $num_feeds = db_fetch_result($result, 0, "nf"); + + $data["max_feed_id"] = (int) $max_feed_id; + $data["num_feeds"] = (int) $num_feeds; + + $data['last_article_id'] = getLastArticleId(); + $data['cdm_expanded'] = get_pref('CDM_EXPANDED'); + + $data['dep_ts'] = calculate_dep_timestamp(); + $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE'); + + if (file_exists(LOCK_DIRECTORY . "/update_daemon.lock")) { + + $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock"); + + if (time() - $_SESSION["daemon_stamp_check"] > 30) { + + $stamp = (int) @file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp"); + + if ($stamp) { + $stamp_delta = time() - $stamp; + + if ($stamp_delta > 1800) { + $stamp_check = 0; + } else { + $stamp_check = 1; + $_SESSION["daemon_stamp_check"] = time(); + } + + $data['daemon_stamp_ok'] = $stamp_check; + + $stamp_fmt = date("Y.m.d, G:i", $stamp); + + $data['daemon_stamp'] = $stamp_fmt; + } + } + } + + if ($_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) { + $new_version_details = @check_for_update(); + + $data['new_version_available'] = (int) ($new_version_details != false); + + $_SESSION["last_version_check"] = time(); + $_SESSION["version_data"] = $new_version_details; + } + + return $data; + } + + function search_to_sql($search) { + + $search_query_part = ""; + + $keywords = str_getcsv($search, " "); + $query_keywords = array(); + $search_words = array(); + + foreach ($keywords as $k) { + if (strpos($k, "-") === 0) { + $k = substr($k, 1); + $not = "NOT"; + } else { + $not = ""; + } + + $commandpair = explode(":", mb_strtolower($k), 2); + + switch ($commandpair[0]) { + case "title": + if ($commandpair[1]) { + array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%". + db_escape_string(mb_strtolower($commandpair[1]))."%'))"); + } else { + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') + OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + array_push($search_words, $k); + } + break; + case "author": + if ($commandpair[1]) { + array_push($query_keywords, "($not (LOWER(author) LIKE '%". + db_escape_string(mb_strtolower($commandpair[1]))."%'))"); + } else { + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') + OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + array_push($search_words, $k); + } + break; + case "note": + if ($commandpair[1]) { + if ($commandpair[1] == "true") + array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))"); + else if ($commandpair[1] == "false") + array_push($query_keywords, "($not (note IS NULL OR note = ''))"); + else + array_push($query_keywords, "($not (LOWER(note) LIKE '%". + db_escape_string(mb_strtolower($commandpair[1]))."%'))"); + } else { + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') + OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + if (!$not) array_push($search_words, $k); + } + break; + case "star": + + if ($commandpair[1]) { + if ($commandpair[1] == "true") + array_push($query_keywords, "($not (marked = true))"); + else + array_push($query_keywords, "($not (marked = false))"); + } else { + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') + OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + if (!$not) array_push($search_words, $k); + } + break; + case "pub": + if ($commandpair[1]) { + if ($commandpair[1] == "true") + array_push($query_keywords, "($not (published = true))"); + else + array_push($query_keywords, "($not (published = false))"); + + } else { + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') + OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + if (!$not) array_push($search_words, $k); + } + break; + default: + if (strpos($k, "@") === 0) { + + $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']); + $orig_ts = strtotime(substr($k, 1)); + $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC')); + + //$k = date("Y-m-d", strtotime(substr($k, 1))); + + array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')"); + } else { + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') + OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + + if (!$not) array_push($search_words, $k); + } + } + } + + $search_query_part = implode("AND", $query_keywords); + + return array($search_query_part, $search_words); + } + + function getParentCategories($cat, $owner_uid) { + $rv = array(); + + $result = db_query("SELECT parent_cat FROM ttrss_feed_categories + WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid"); + + while ($line = db_fetch_assoc($result)) { + array_push($rv, $line["parent_cat"]); + $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid)); + } + + return $rv; + } + + function getChildCategories($cat, $owner_uid) { + $rv = array(); + + $result = db_query("SELECT id FROM ttrss_feed_categories + WHERE parent_cat = '$cat' AND owner_uid = $owner_uid"); + + while ($line = db_fetch_assoc($result)) { + array_push($rv, $line["id"]); + $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid)); + } + + return $rv; + } + + 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) { + + if (!$owner_uid) $owner_uid = $_SESSION["uid"]; + + $ext_tables_part = ""; + $search_words = array(); + + if ($search) { + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH) as $plugin) { + list($search_query_part, $search_words) = $plugin->hook_search($search); + break; + } + + // fall back in case of no plugins + if (!$search_query_part) { + list($search_query_part, $search_words) = search_to_sql($search); + } + $search_query_part .= " AND "; + } else { + $search_query_part = ""; + } + + if ($filter) { + + if (DB_TYPE == "pgsql") { + $query_strategy_part .= " AND updated > NOW() - INTERVAL '14 days' "; + } else { + $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL 14 DAY) "; + } + + $override_order = "updated DESC"; + + $filter_query_part = filter_to_sql($filter, $owner_uid); + + // Try to check if SQL regexp implementation chokes on a valid regexp + + + $result = db_query("SELECT true AS true_val + FROM ttrss_entries + JOIN ttrss_user_entries ON ttrss_entries.id = ttrss_user_entries.ref_id + JOIN ttrss_feeds ON ttrss_feeds.id = ttrss_user_entries.feed_id + WHERE $filter_query_part LIMIT 1", false); + + if ($result) { + $test = db_fetch_result($result, 0, "true_val"); + + if (!$test) { + $filter_query_part = "false AND"; + } else { + $filter_query_part .= " AND"; + } + } else { + $filter_query_part = "false AND"; + } + + } else { + $filter_query_part = ""; + } + + if ($since_id) { + $since_id_part = "ttrss_entries.id > $since_id AND "; + } else { + $since_id_part = ""; + } + + $view_query_part = ""; + + if ($view_mode == "adaptive") { + if ($search) { + $view_query_part = " "; + } else if ($feed != -1) { + + $unread = getFeedUnread($feed, $cat_view); + + if ($cat_view && $feed > 0 && $include_children) + $unread += getCategoryChildrenUnread($feed); + + if ($unread > 0) + $view_query_part = " unread = true AND "; + + } + } + + if ($view_mode == "marked") { + $view_query_part = " marked = true AND "; + } + + if ($view_mode == "has_note") { + $view_query_part = " (note IS NOT NULL AND note != '') AND "; + } + + if ($view_mode == "published") { + $view_query_part = " published = true AND "; + } + + if ($view_mode == "unread" && $feed != -6) { + $view_query_part = " unread = true AND "; + } + + if ($limit > 0) { + $limit_query_part = "LIMIT " . $limit; + } + + $allow_archived = false; + + $vfeed_query_part = ""; + + // override query strategy and enable feed display when searching globally + if ($search && $search_mode == "all_feeds") { + $query_strategy_part = "true"; + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + /* tags */ + } else if (!is_numeric($feed)) { + $query_strategy_part = "true"; + $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE + id = feed_id) as feed_title,"; + } else if ($search && $search_mode == "this_cat") { + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + + if ($feed > 0) { + if ($include_children) { + $subcats = getChildCategories($feed, $owner_uid); + array_push($subcats, $feed); + $cats_qpart = join(",", $subcats); + } else { + $cats_qpart = $feed; + } + + $query_strategy_part = "ttrss_feeds.cat_id IN ($cats_qpart)"; + + } else { + $query_strategy_part = "ttrss_feeds.cat_id IS NULL"; + } + + } else if ($feed > 0) { + + if ($cat_view) { + + if ($feed > 0) { + if ($include_children) { + # sub-cats + $subcats = getChildCategories($feed, $owner_uid); + + array_push($subcats, $feed); + $query_strategy_part = "cat_id IN (". + implode(",", $subcats).")"; + + } else { + $query_strategy_part = "cat_id = '$feed'"; + } + + } else { + $query_strategy_part = "cat_id IS NULL"; + } + + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + + } else { + $query_strategy_part = "feed_id = '$feed'"; + } + } else if ($feed == 0 && !$cat_view) { // archive virtual feed + $query_strategy_part = "feed_id IS NULL"; + $allow_archived = true; + } else if ($feed == 0 && $cat_view) { // uncategorized + $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL"; + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + } else if ($feed == -1) { // starred virtual feed + $query_strategy_part = "marked = true"; + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + $allow_archived = true; + + if (!$override_order) { + $override_order = "last_marked DESC, date_entered DESC, updated DESC"; + } + + } else if ($feed == -2) { // published virtual feed OR labels category + + if (!$cat_view) { + $query_strategy_part = "published = true"; + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + $allow_archived = true; + + if (!$override_order) { + $override_order = "last_published DESC, date_entered DESC, updated DESC"; + } + + } else { + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + + $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2"; + + $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND + ttrss_user_labels2.article_id = ref_id"; + + } + } else if ($feed == -6) { // recently read + $query_strategy_part = "unread = false AND last_read IS NOT NULL"; + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + $allow_archived = true; + $ignore_vfeed_group = true; + + if (!$override_order) $override_order = "last_read DESC"; + +/* } else if ($feed == -7) { // shared + $query_strategy_part = "uuid != ''"; + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + $allow_archived = true; */ + } else if ($feed == -3) { // fresh virtual feed + $query_strategy_part = "unread = true AND score >= 0"; + + $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid); + + if (DB_TYPE == "pgsql") { + $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' "; + } else { + $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) "; + } + + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + } else if ($feed == -4) { // all articles virtual feed + $allow_archived = true; + $query_strategy_part = "true"; + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + } else if ($feed <= LABEL_BASE_INDEX) { // labels + $label_id = feed_to_label_id($feed); + + $query_strategy_part = "label_id = '$label_id' AND + ttrss_labels2.id = ttrss_user_labels2.label_id AND + ttrss_user_labels2.article_id = ref_id"; + + $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; + $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2"; + $allow_archived = true; + + } else { + $query_strategy_part = "true"; + } + + $order_by = "score DESC, date_entered DESC, updated DESC"; + + if ($view_mode == "unread_first") { + $order_by = "unread DESC, $order_by"; + } + + if ($override_order) { + $order_by = $override_order; + } + + if ($override_strategy) { + $query_strategy_part = $override_strategy; + } + + if ($override_vfeed) { + $vfeed_query_part = $override_vfeed; + } + + $feed_title = ""; + + if ($search) { + $feed_title = T_sprintf("Search results: %s", $search); + } else { + if ($cat_view) { + $feed_title = getCategoryTitle($feed); + } else { + if (is_numeric($feed) && $feed > 0) { + $result = db_query("SELECT title,site_url,last_error,last_updated + FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid"); + + $feed_title = db_fetch_result($result, 0, "title"); + $feed_site_url = db_fetch_result($result, 0, "site_url"); + $last_error = db_fetch_result($result, 0, "last_error"); + $last_updated = db_fetch_result($result, 0, "last_updated"); + } else { + $feed_title = getFeedTitle($feed); + } + } + } + + + $content_query_part = "content, "; + + + if (is_numeric($feed)) { + + if ($feed >= 0) { + $feed_kind = "Feeds"; + } else { + $feed_kind = "Labels"; + } + + if ($limit_query_part) { + $offset_query_part = "OFFSET $offset"; + } + + // proper override_order applied above + if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) { + if (!$override_order) { + $order_by = "ttrss_feeds.title, $order_by"; + } else { + $order_by = "ttrss_feeds.title, $override_order"; + } + } + + if (!$allow_archived) { + $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part"; + $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND"; + + } else { + $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries + LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)"; + } + + if ($vfeed_query_part) + $vfeed_query_part .= "favicon_avg_color,"; + + if ($start_ts) { + $start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts)); + $start_ts_query_part = "date_entered >= '$start_ts_formatted' AND"; + } else { + $start_ts_query_part = ""; + } + + $query = "SELECT DISTINCT + date_entered, + guid, + ttrss_entries.id,ttrss_entries.title, + updated, + label_cache, + tag_cache, + always_display_enclosures, + site_url, + note, + num_comments, + comments, + int_id, + uuid, + lang, + hide_images, + unread,feed_id,marked,published,link,last_read,orig_feed_id, + last_marked, last_published, + $vfeed_query_part + $content_query_part + author,score + FROM + $from_qpart + WHERE + $feed_check_qpart + ttrss_user_entries.ref_id = ttrss_entries.id AND + ttrss_user_entries.owner_uid = '$owner_uid' AND + $search_query_part + $start_ts_query_part + $filter_query_part + $view_query_part + $since_id_part + $query_strategy_part ORDER BY $order_by + $limit_query_part $offset_query_part"; + + if ($_REQUEST["debug"]) print $query; + + $result = db_query($query); + + } else { + // browsing by tag + + $select_qpart = "SELECT DISTINCT " . + "date_entered," . + "guid," . + "note," . + "ttrss_entries.id as id," . + "title," . + "updated," . + "unread," . + "feed_id," . + "orig_feed_id," . + "marked," . + "num_comments, " . + "comments, " . + "tag_cache," . + "label_cache," . + "link," . + "lang," . + "uuid," . + "last_read," . + "(SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images," . + "last_marked, last_published, " . + $since_id_part . + $vfeed_query_part . + $content_query_part . + "score "; + + $feed_kind = "Tags"; + $all_tags = explode(",", $feed); + if ($search_mode == 'any') { + $tag_sql = "tag_name in (" . implode(", ", array_map("db_quote", $all_tags)) . ")"; + $from_qpart = " FROM ttrss_entries,ttrss_user_entries,ttrss_tags "; + $where_qpart = " WHERE " . + "ref_id = ttrss_entries.id AND " . + "ttrss_user_entries.owner_uid = $owner_uid AND " . + "post_int_id = int_id AND $tag_sql AND " . + $view_query_part . + $search_query_part . + $query_strategy_part . " ORDER BY $order_by " . + $limit_query_part; + + } else { + $i = 1; + $sub_selects = array(); + $sub_ands = array(); + foreach ($all_tags as $term) { + array_push($sub_selects, "(SELECT post_int_id from ttrss_tags WHERE tag_name = " . db_quote($term) . " AND owner_uid = $owner_uid) as A$i"); + $i++; + } + if ($i > 2) { + $x = 1; + $y = 2; + do { + array_push($sub_ands, "A$x.post_int_id = A$y.post_int_id"); + $x++; + $y++; + } while ($y < $i); + } + array_push($sub_ands, "A1.post_int_id = ttrss_user_entries.int_id and ttrss_user_entries.owner_uid = $owner_uid"); + array_push($sub_ands, "ttrss_user_entries.ref_id = ttrss_entries.id"); + $from_qpart = " FROM " . implode(", ", $sub_selects) . ", ttrss_user_entries, ttrss_entries"; + $where_qpart = " WHERE " . implode(" AND ", $sub_ands); + } + // error_log("TAG SQL: " . $tag_sql); + // $tag_sql = "tag_name = '$feed'"; DEFAULT way + + // error_log("[". $select_qpart . "][" . $from_qpart . "][" .$where_qpart . "]"); + $result = db_query($select_qpart . $from_qpart . $where_qpart); + } + + return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words); + + } + + function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) { + if (!$owner) $owner = $_SESSION["uid"]; + + $res = trim($str); if (!$res) return ''; + + $charset_hack = '<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> + </head>'; + + $res = trim($res); if (!$res) return ''; + + libxml_use_internal_errors(true); + + $doc = new DOMDocument(); + $doc->loadHTML($charset_hack . $res); + $xpath = new DOMXPath($doc); + + $entries = $xpath->query('(//a[@href]|//img[@src])'); + + foreach ($entries as $entry) { + + if ($site_url) { + + if ($entry->hasAttribute('href')) { + $entry->setAttribute('href', + rewrite_relative_url($site_url, $entry->getAttribute('href'))); + + $entry->setAttribute('rel', 'noreferrer'); + } + + if ($entry->hasAttribute('src')) { + $src = rewrite_relative_url($site_url, $entry->getAttribute('src')); + + $cached_filename = CACHE_DIR . '/images/' . sha1($src) . '.png'; + + if (file_exists($cached_filename)) { + $src = SELF_URL_PATH . '/image.php?hash=' . sha1($src); + } + + $entry->setAttribute('src', $src); + } + + if ($entry->nodeName == 'img') { + if (($owner && get_pref("STRIP_IMAGES", $owner)) || + $force_remove_images || $_SESSION["bw_limit"]) { + + $p = $doc->createElement('p'); + + $a = $doc->createElement('a'); + $a->setAttribute('href', $entry->getAttribute('src')); + + $a->appendChild(new DOMText($entry->getAttribute('src'))); + $a->setAttribute('target', '_blank'); + + $p->appendChild($a); + + $entry->parentNode->replaceChild($p, $entry); + } + } + } + + if (strtolower($entry->nodeName) == "a") { + $entry->setAttribute("target", "_blank"); + } + } + + $entries = $xpath->query('//iframe'); + foreach ($entries as $entry) { + $entry->setAttribute('sandbox', 'allow-scripts'); + + } + + $allowed_elements = array('a', 'address', 'audio', 'article', 'aside', + 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', + 'caption', 'cite', 'center', 'code', 'col', 'colgroup', + 'data', 'dd', 'del', 'details', 'div', 'dl', 'font', + 'dt', 'em', 'footer', 'figure', 'figcaption', + 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i', + 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript', + 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section', + 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary', + 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time', + 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' ); + + if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe'; + + $disallowed_attributes = array('id', 'style', 'class'); + + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) { + $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id); + if (is_array($retval)) { + $doc = $retval[0]; + $allowed_elements = $retval[1]; + $disallowed_attributes = $retval[2]; + } else { + $doc = $retval; + } + } + + $doc->removeChild($doc->firstChild); //remove doctype + $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes); + + if ($highlight_words) { + foreach ($highlight_words as $word) { + + // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph + + $elements = $xpath->query("//*/text()"); + + foreach ($elements as $child) { + + $fragment = $doc->createDocumentFragment(); + $text = $child->textContent; + + while (($pos = mb_stripos($text, $word)) !== false) { + $fragment->appendChild(new DomText(mb_substr($text, 0, $pos))); + $word = mb_substr($text, $pos, mb_strlen($word)); + $highlight = $doc->createElement('span'); + $highlight->appendChild(new DomText($word)); + $highlight->setAttribute('class', 'highlight'); + $fragment->appendChild($highlight); + $text = mb_substr($text, $pos + mb_strlen($word)); + } + + if (!empty($text)) $fragment->appendChild(new DomText($text)); + + $child->parentNode->replaceChild($fragment, $child); + } + } + } + + $res = $doc->saveHTML(); + + return $res; + } + + function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) { + $xpath = new DOMXPath($doc); + $entries = $xpath->query('//*'); + + foreach ($entries as $entry) { + if (!in_array($entry->nodeName, $allowed_elements)) { + $entry->parentNode->removeChild($entry); + } + + if ($entry->hasAttributes()) { + $attrs_to_remove = array(); + + foreach ($entry->attributes as $attr) { + + if (strpos($attr->nodeName, 'on') === 0) { + array_push($attrs_to_remove, $attr); + } + + if (in_array($attr->nodeName, $disallowed_attributes)) { + array_push($attrs_to_remove, $attr); + } + } + + foreach ($attrs_to_remove as $attr) { + $entry->removeAttributeNode($attr); + } + } + } + + return $doc; + } + + function check_for_update() { + if (CHECK_FOR_NEW_VERSION && $_SESSION['access_level'] >= 10) { + $version_url = "http://tt-rss.org/version.php?ver=" . VERSION . + "&iid=" . sha1(SELF_URL_PATH); + + $version_data = @fetch_file_contents($version_url); + + if ($version_data) { + $version_data = json_decode($version_data, true); + if ($version_data && $version_data['version']) { + if (version_compare(VERSION_STATIC, $version_data['version']) == -1) { + return $version_data; + } + } + } + } + return false; + } + + function catchupArticlesById($ids, $cmode, $owner_uid = false) { + + if (!$owner_uid) $owner_uid = $_SESSION["uid"]; + if (count($ids) == 0) return; + + $tmp_ids = array(); + + foreach ($ids as $id) { + array_push($tmp_ids, "ref_id = '$id'"); + } + + $ids_qpart = join(" OR ", $tmp_ids); + + if ($cmode == 0) { + db_query("UPDATE ttrss_user_entries SET + unread = false,last_read = NOW() + WHERE ($ids_qpart) AND owner_uid = $owner_uid"); + } else if ($cmode == 1) { + db_query("UPDATE ttrss_user_entries SET + unread = true + WHERE ($ids_qpart) AND owner_uid = $owner_uid"); + } else { + db_query("UPDATE ttrss_user_entries SET + unread = NOT unread,last_read = NOW() + WHERE ($ids_qpart) AND owner_uid = $owner_uid"); + } + + /* update ccache */ + + $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries + WHERE ($ids_qpart) AND owner_uid = $owner_uid"); + + while ($line = db_fetch_assoc($result)) { + ccache_update($line["feed_id"], $owner_uid); + } + } + + function get_article_tags($id, $owner_uid = 0, $tag_cache = false) { + + $a_id = db_escape_string($id); + + if (!$owner_uid) $owner_uid = $_SESSION["uid"]; + + $query = "SELECT DISTINCT tag_name, + owner_uid as owner FROM + ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE + ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name"; + + $tags = array(); + + /* check cache first */ + + if ($tag_cache === false) { + $result = db_query("SELECT tag_cache FROM ttrss_user_entries + WHERE ref_id = '$id' AND owner_uid = $owner_uid"); + + $tag_cache = db_fetch_result($result, 0, "tag_cache"); + } + + if ($tag_cache) { + $tags = explode(",", $tag_cache); + } else { + + /* do it the hard way */ + + $tmp_result = db_query($query); + + while ($tmp_line = db_fetch_assoc($tmp_result)) { + array_push($tags, $tmp_line["tag_name"]); + } + + /* update the cache */ + + $tags_str = db_escape_string(join(",", $tags)); + + db_query("UPDATE ttrss_user_entries + SET tag_cache = '$tags_str' WHERE ref_id = '$id' + AND owner_uid = $owner_uid"); + } + + return $tags; + } + + function trim_array($array) { + $tmp = $array; + array_walk($tmp, 'trim'); + return $tmp; + } + + function tag_is_valid($tag) { + if ($tag == '') return false; + if (preg_match("/^[0-9]*$/", $tag)) return false; + if (mb_strlen($tag) > 250) return false; + + if (!$tag) return false; + + return true; + } + + function render_login_form() { + header('Cache-Control: public'); + + require_once "login_form.php"; + exit; + } + + function format_warning($msg, $id = "") { + return "<div class=\"warning\" id=\"$id\"> + <span><img src=\"images/alert.png\"></span><span>$msg</span></div>"; + } + + function format_notice($msg, $id = "") { + return "<div class=\"notice\" id=\"$id\"> + <span><img src=\"images/information.png\"></span><span>$msg</span></div>"; + } + + function format_error($msg, $id = "") { + return "<div class=\"error\" id=\"$id\"> + <span><img src=\"images/alert.png\"></span><span>$msg</span></div>"; + } + + function print_notice($msg) { + return print format_notice($msg); + } + + function print_warning($msg) { + return print format_warning($msg); + } + + function print_error($msg) { + return print format_error($msg); + } + + + function T_sprintf() { + $args = func_get_args(); + return vsprintf(__(array_shift($args)), $args); + } + + function format_inline_player($url, $ctype) { + + $entry = ""; + + $url = htmlspecialchars($url); + + if (strpos($ctype, "audio/") === 0) { + + if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false || + $_SESSION["hasMp3"])) { + + $entry .= "<audio preload=\"none\" controls> + <source type=\"$ctype\" src=\"$url\"></source> + </audio>"; + + } else { + + $entry .= "<object type=\"application/x-shockwave-flash\" + data=\"lib/button/musicplayer.swf?song_url=$url\" + width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'> + <param name=\"movie\" + value=\"lib/button/musicplayer.swf?song_url=$url\" /> + </object>"; + } + + if ($entry) $entry .= " <a target=\"_blank\" + href=\"$url\">" . basename($url) . "</a>"; + + return $entry; + + } + + return ""; + +/* $filename = substr($url, strrpos($url, "/")+1); + + $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" . + $filename . " (" . $ctype . ")" . "</a>"; */ + + } + + function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) { + if (!$owner_uid) $owner_uid = $_SESSION["uid"]; + + $rv = array(); + + $rv['id'] = $id; + + /* we can figure out feed_id from article id anyway, why do we + * pass feed_id here? let's ignore the argument :(*/ + + $result = db_query("SELECT feed_id FROM ttrss_user_entries + WHERE ref_id = '$id'"); + + $feed_id = (int) db_fetch_result($result, 0, "feed_id"); + + $rv['feed_id'] = $feed_id; + + //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; }; + + if ($mark_as_read) { + $result = db_query("UPDATE ttrss_user_entries + SET unread = false,last_read = NOW() + WHERE ref_id = '$id' AND owner_uid = $owner_uid"); + + ccache_update($feed_id, $owner_uid); + } + + $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang, + ".SUBSTRING_FOR_DATE."(updated,1,16) as updated, + (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url, + (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title, + (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images, + (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures, + num_comments, + tag_cache, + author, + orig_feed_id, + note + FROM ttrss_entries,ttrss_user_entries + WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid"); + + if ($result) { + + $line = db_fetch_assoc($result); + + $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]); + unset($line["tag_cache"]); + + $line["content"] = sanitize($line["content"], + sql_bool_to_bool($line['hide_images']), + $owner_uid, $line["site_url"], false, $line["id"]); + + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) { + $line = $p->hook_render_article($line); + } + + $num_comments = $line["num_comments"]; + $entry_comments = ""; + + if ($num_comments > 0) { + if ($line["comments"]) { + $comments_url = htmlspecialchars($line["comments"]); + } else { + $comments_url = htmlspecialchars($line["link"]); + } + $entry_comments = "<a class=\"postComments\" + target='_blank' href=\"$comments_url\">$num_comments ". + _ngettext("comment", "comments", $num_comments)."</a>"; + + } else { + if ($line["comments"] && $line["link"] != $line["comments"]) { + $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>"; + } + } + + if ($zoom_mode) { + header("Content-Type: text/html"); + $rv['content'] .= "<html><head> + <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/> + <title>Tiny Tiny RSS - ".$line["title"]."</title>". + stylesheet_tag("css/tt-rss.css"). + stylesheet_tag("css/zoom.css"). + stylesheet_tag("css/dijit.css")." + + <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\"> + <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\"> + + <script type=\"text/javascript\"> + function openSelectedAttachment(elem) { + try { + var url = elem[elem.selectedIndex].value; + + if (url) { + window.open(url); + elem.selectedIndex = 0; + } + + } catch (e) { + exception_error(\"openSelectedAttachment\", e); + } + } + </script> + </head><body id=\"ttrssZoom\">"; + } + + $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">"; + + $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">"; + + $entry_author = $line["author"]; + + if ($entry_author) { + $entry_author = __(" - ") . $entry_author; + } + + $parsed_updated = make_local_datetime($line["updated"], true, + $owner_uid, true); + + if (!$zoom_mode) + $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>"; + + if ($line["link"]) { + $rv['content'] .= "<div class='postTitle'><a target='_blank' + title=\"".htmlspecialchars($line['title'])."\" + href=\"" . + htmlspecialchars($line["link"]) . "\">" . + $line["title"] . "</a>" . + "<span class='author'>$entry_author</span></div>"; + } else { + $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>"; + } + + if ($zoom_mode) { + $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]). + "\" target=\"_blank\">". + htmlspecialchars($line["feed_title"])."</a>"; + + $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>"; + + $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>"; + } + + $tags_str = format_tags_string($line["tags"], $id); + $tags_str_full = join(", ", $line["tags"]); + + if (!$tags_str_full) $tags_str_full = __("no tags"); + + if (!$entry_comments) $entry_comments = " "; # placeholder + + $rv['content'] .= "<div class='postTags' style='float : right'> + <img src='images/tag.png' + class='tagsPic' alt='Tags' title='Tags'> "; + + if (!$zoom_mode) { + $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span> + <a title=\"".__('Edit tags for this article')."\" + href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>"; + + $rv['content'] .= "<div dojoType=\"dijit.Tooltip\" + id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\" + position=\"below\">$tags_str_full</div>"; + + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) { + $rv['content'] .= $p->hook_article_button($line); + } + + } else { + $tags_str = strip_tags($tags_str); + $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>"; + } + $rv['content'] .= "</div>"; + $rv['content'] .= "<div clear='both'>"; + + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) { + $rv['content'] .= $p->hook_article_left_button($line); + } + + $rv['content'] .= "$entry_comments</div>"; + + if ($line["orig_feed_id"]) { + + $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds + WHERE id = ".$line["orig_feed_id"]); + + if (db_num_rows($tmp_result) != 0) { + + $rv['content'] .= "<div clear='both'>"; + $rv['content'] .= __("Originally from:"); + + $rv['content'] .= " "; + + $tmp_line = db_fetch_assoc($tmp_result); + + $rv['content'] .= "<a target='_blank' + href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" . + $tmp_line['title'] . "</a>"; + + $rv['content'] .= " "; + + $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>"; + $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>"; + + $rv['content'] .= "</div>"; + } + } + + $rv['content'] .= "</div>"; + + $rv['content'] .= "<div id=\"POSTNOTE-$id\">"; + if ($line['note']) { + $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode); + } + $rv['content'] .= "</div>"; + + if (!$line['lang']) $line['lang'] = 'en'; + + $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">"; + + $rv['content'] .= $line["content"]; + $rv['content'] .= format_article_enclosures($id, + sql_bool_to_bool($line["always_display_enclosures"]), + $line["content"], + sql_bool_to_bool($line["hide_images"])); + + $rv['content'] .= "</div>"; + + $rv['content'] .= "</div>"; + + } + + if ($zoom_mode) { + $rv['content'] .= " + <div class='footer'> + <button onclick=\"return window.close()\">". + __("Close this window")."</button></div>"; + $rv['content'] .= "</body></html>"; + } + + return $rv; + + } + + function print_checkpoint($n, $s) { + $ts = microtime(true); + echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s); + return $ts; + } + + function sanitize_tag($tag) { + $tag = trim($tag); + + $tag = mb_strtolower($tag, 'utf-8'); + + $tag = preg_replace('/[\'\"\+\>\<]/', "", $tag); + +// $tag = str_replace('"', "", $tag); +// $tag = str_replace("+", " ", $tag); + $tag = str_replace("technorati tag: ", "", $tag); + + return $tag; + } + + function get_self_url_prefix() { + if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) { + return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1); + } else { + return SELF_URL_PATH; + } + } + + /** + * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI. + * + * @return string The Mozilla Firefox feed adding URL. + */ + function add_feed_url() { + //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH); + + $url_path = get_self_url_prefix() . + "/public.php?op=subscribe&feed_url=%s"; + return $url_path; + } // function add_feed_url + + function encrypt_password($pass, $salt = '', $mode2 = false) { + if ($salt && $mode2) { + return "MODE2:" . hash('sha256', $salt . $pass); + } else if ($salt) { + return "SHA1X:" . sha1("$salt:$pass"); + } else { + return "SHA1:" . sha1($pass); + } + } // function encrypt_password + + function load_filters($feed_id, $owner_uid, $action_id = false) { + $filters = array(); + + $cat_id = (int)getFeedCategory($feed_id); + + if ($cat_id == 0) + $null_cat_qpart = "cat_id IS NULL OR"; + else + $null_cat_qpart = ""; + + $result = db_query("SELECT * FROM ttrss_filters2 WHERE + owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title"); + + $check_cats = join(",", array_merge( + getParentCategories($cat_id, $owner_uid), + array($cat_id))); + + while ($line = db_fetch_assoc($result)) { + $filter_id = $line["id"]; + + $result2 = db_query("SELECT + r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name + FROM ttrss_filters2_rules AS r, + ttrss_filter_types AS t + WHERE + ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND + (feed_id IS NULL OR feed_id = '$feed_id') AND + filter_type = t.id AND filter_id = '$filter_id'"); + + $rules = array(); + $actions = array(); + + while ($rule_line = db_fetch_assoc($result2)) { +# print_r($rule_line); + + $rule = array(); + $rule["reg_exp"] = $rule_line["reg_exp"]; + $rule["type"] = $rule_line["type_name"]; + $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]); + + array_push($rules, $rule); + } + + $result2 = db_query("SELECT a.action_param,t.name AS type_name + FROM ttrss_filters2_actions AS a, + ttrss_filter_actions AS t + WHERE + action_id = t.id AND filter_id = '$filter_id'"); + + while ($action_line = db_fetch_assoc($result2)) { +# print_r($action_line); + + $action = array(); + $action["type"] = $action_line["type_name"]; + $action["param"] = $action_line["action_param"]; + + array_push($actions, $action); + } + + + $filter = array(); + $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]); + $filter["inverse"] = sql_bool_to_bool($line["inverse"]); + $filter["rules"] = $rules; + $filter["actions"] = $actions; + + if (count($rules) > 0 && count($actions) > 0) { + array_push($filters, $filter); + } + } + + return $filters; + } + + function get_score_pic($score) { + if ($score > 100) { + return "score_high.png"; + } else if ($score > 0) { + return "score_half_high.png"; + } else if ($score < -100) { + return "score_low.png"; + } else if ($score < 0) { + return "score_half_low.png"; + } else { + return "score_neutral.png"; + } + } + + function feed_has_icon($id) { + return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0; + } + + function init_plugins() { + PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL); + + return true; + } + + function format_tags_string($tags, $id) { + if (!is_array($tags) || count($tags) == 0) { + return __("no tags"); + } else { + $maxtags = min(5, count($tags)); + + for ($i = 0; $i < $maxtags; $i++) { + $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed('".$tags[$i]."')\">" . $tags[$i] . "</a>, "; + } + + $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2); + + if (count($tags) > $maxtags) + $tags_str .= ", …"; + + return $tags_str; + } + } + + function format_article_labels($labels, $id) { + + if (!is_array($labels)) return ''; + + $labels_str = ""; + + foreach ($labels as $l) { + $labels_str .= sprintf("<span class='hlLabelRef' + style='color : %s; background-color : %s'>%s</span>", + $l[2], $l[3], $l[1]); + } + + return $labels_str; + + } + + function format_article_note($id, $note, $allow_edit = true) { + + $str = "<div class='articleNote' onclick=\"editArticleNote($id)\"> + <div class='noteEdit' onclick=\"editArticleNote($id)\">". + ($allow_edit ? __('(edit note)') : "")."</div>$note</div>"; + + return $str; + } + + + function get_feed_category($feed_cat, $parent_cat_id = false) { + if ($parent_cat_id) { + $parent_qpart = "parent_cat = '$parent_cat_id'"; + $parent_insert = "'$parent_cat_id'"; + } else { + $parent_qpart = "parent_cat IS NULL"; + $parent_insert = "NULL"; + } + + $result = db_query( + "SELECT id FROM ttrss_feed_categories + WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]); + + if (db_num_rows($result) == 0) { + return false; + } else { + return db_fetch_result($result, 0, "id"); + } + } + + function add_feed_category($feed_cat, $parent_cat_id = false) { + + if (!$feed_cat) return false; + + db_query("BEGIN"); + + if ($parent_cat_id) { + $parent_qpart = "parent_cat = '$parent_cat_id'"; + $parent_insert = "'$parent_cat_id'"; + } else { + $parent_qpart = "parent_cat IS NULL"; + $parent_insert = "NULL"; + } + + $feed_cat = mb_substr($feed_cat, 0, 250); + + $result = db_query( + "SELECT id FROM ttrss_feed_categories + WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]); + + if (db_num_rows($result) == 0) { + + $result = db_query( + "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat) + VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)"); + + db_query("COMMIT"); + + return true; + } + + return false; + } + + function getArticleFeed($id) { + $result = db_query("SELECT feed_id FROM ttrss_user_entries + WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]); + + if (db_num_rows($result) != 0) { + return db_fetch_result($result, 0, "feed_id"); + } else { + return 0; + } + } + + /** + * Fixes incomplete URLs by prepending "http://". + * Also replaces feed:// with http://, and + * prepends a trailing slash if the url is a domain name only. + * + * @param string $url Possibly incomplete URL + * + * @return string Fixed URL. + */ + function fix_url($url) { + if (strpos($url, '://') === false) { + $url = 'http://' . $url; + } else if (substr($url, 0, 5) == 'feed:') { + $url = 'http:' . substr($url, 5); + } + + //prepend slash if the URL has no slash in it + // "http://www.example" -> "http://www.example/" + if (strpos($url, '/', strpos($url, ':') + 3) === false) { + $url .= '/'; + } + + if ($url != "http:///") + return $url; + else + return ''; + } + + function validate_feed_url($url) { + $parts = parse_url($url); + + return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https'); + + } + + function get_article_enclosures($id) { + + $query = "SELECT * FROM ttrss_enclosures + WHERE post_id = '$id' AND content_url != ''"; + + $rv = array(); + + $result = db_query($query); + + if (db_num_rows($result) > 0) { + while ($line = db_fetch_assoc($result)) { + array_push($rv, $line); + } + } + + return $rv; + } + + /* function save_email_address($email) { + // FIXME: implement persistent storage of emails + + if (!$_SESSION['stored_emails']) + $_SESSION['stored_emails'] = array(); + + if (!in_array($email, $_SESSION['stored_emails'])) + array_push($_SESSION['stored_emails'], $email); + } */ + + + function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) { + + if (!$owner_uid) $owner_uid = $_SESSION["uid"]; + + $sql_is_cat = bool_to_sql_bool($is_cat); + + $result = db_query("SELECT access_key FROM ttrss_access_keys + WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat + AND owner_uid = " . $owner_uid); + + if (db_num_rows($result) == 1) { + return db_fetch_result($result, 0, "access_key"); + } else { + $key = db_escape_string(uniqid(base_convert(rand(), 10, 36))); + + $result = db_query("INSERT INTO ttrss_access_keys + (access_key, feed_id, is_cat, owner_uid) + VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')"); + + return $key; + } + return false; + } + + function get_feeds_from_html($url, $content) + { + $url = fix_url($url); + $baseUrl = substr($url, 0, strrpos($url, '/') + 1); + + libxml_use_internal_errors(true); + + $doc = new DOMDocument(); + $doc->loadHTML($content); + $xpath = new DOMXPath($doc); + $entries = $xpath->query('/html/head/link[@rel="alternate" and '. + '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]'); + $feedUrls = array(); + foreach ($entries as $entry) { + if ($entry->hasAttribute('href')) { + $title = $entry->getAttribute('title'); + if ($title == '') { + $title = $entry->getAttribute('type'); + } + $feedUrl = rewrite_relative_url( + $baseUrl, $entry->getAttribute('href') + ); + $feedUrls[$feedUrl] = $title; + } + } + return $feedUrls; + } + + function is_html($content) { + return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 20)) !== 0; + } + + function url_is_html($url, $login = false, $pass = false) { + return is_html(fetch_file_contents($url, false, $login, $pass)); + } + + function print_label_select($name, $value, $attributes = "") { + + $result = db_query("SELECT caption FROM ttrss_labels2 + WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption"); + + print "<select default=\"$value\" name=\"" . htmlspecialchars($name) . + "\" $attributes onchange=\"labelSelectOnChange(this)\" >"; + + while ($line = db_fetch_assoc($result)) { + + $issel = ($line["caption"] == $value) ? "selected=\"1\"" : ""; + + print "<option value=\"".htmlspecialchars($line["caption"])."\" + $issel>" . htmlspecialchars($line["caption"]) . "</option>"; + + } + +# print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>"; + + print "</select>"; + + + } + + function format_article_enclosures($id, $always_display_enclosures, + $article_content, $hide_images = false) { + + $result = get_article_enclosures($id); + $rv = ''; + + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ENCLOSURES) as $plugin) { + $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images); + if (is_array($retval)) { + $rv = $retval[0]; + $result = $retval[1]; + } else { + $rv = $retval; + } + } + + if ($rv === '' && !empty($result)) { + $entries_html = array(); + $entries = array(); + $entries_inline = array(); + + foreach ($result as $line) { + + $url = $line["content_url"]; + $ctype = $line["content_type"]; + $title = $line["title"]; + $width = $line["width"]; + $height = $line["height"]; + + if (!$ctype) $ctype = __("unknown type"); + + $filename = substr($url, strrpos($url, "/")+1); + + $player = format_inline_player($url, $ctype); + + if ($player) array_push($entries_inline, $player); + +# $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" . +# $filename . " (" . $ctype . ")" . "</a>"; + + $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\" + dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>"; + + array_push($entries_html, $entry); + + $entry = array(); + + $entry["type"] = $ctype; + $entry["filename"] = $filename; + $entry["url"] = $url; + $entry["title"] = $title; + $entry["width"] = $width; + $entry["height"] = $height; + + array_push($entries, $entry); + } + + if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) { + if ($always_display_enclosures || + !preg_match("/<img/i", $article_content)) { + + foreach ($entries as $entry) { + + if (preg_match("/image/", $entry["type"]) || + preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) { + + if (!$hide_images) { + $encsize = ''; + if ($entry['height'] > 0) + $encsize .= ' height="' . intval($entry['width']) . '"'; + if ($entry['width'] > 0) + $encsize .= ' width="' . intval($entry['height']) . '"'; + $rv .= "<p><img + alt=\"".htmlspecialchars($entry["filename"])."\" + src=\"" .htmlspecialchars($entry["url"]) . "\" + " . $encsize . " /></p>"; + } else { + $rv .= "<p><a target=\"_blank\" + href=\"".htmlspecialchars($entry["url"])."\" + >" .htmlspecialchars($entry["url"]) . "</a></p>"; + } + + if ($entry['title']) { + $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>"; + } + } + } + } + } + + if (count($entries_inline) > 0) { + $rv .= "<hr clear='both'/>"; + foreach ($entries_inline as $entry) { $rv .= $entry; }; + $rv .= "<hr clear='both'/>"; + } + + $rv .= "<select class=\"attachments\" onchange=\"openSelectedAttachment(this)\">". + "<option value=''>" . __('Attachments')."</option>"; + + foreach ($entries as $entry) { + if ($entry["title"]) + $title = "— " . truncate_string($entry["title"], 30); + else + $title = ""; + + $rv .= "<option value=\"".htmlspecialchars($entry["url"])."\">" . htmlspecialchars($entry["filename"]) . "$title</option>"; + + }; + + $rv .= "</select>"; + } + + return $rv; + } + + function getLastArticleId() { + $result = db_query("SELECT MAX(ref_id) AS id FROM ttrss_user_entries + WHERE owner_uid = " . $_SESSION["uid"]); + + if (db_num_rows($result) == 1) { + return db_fetch_result($result, 0, "id"); + } else { + return -1; + } + } + + function build_url($parts) { + return $parts['scheme'] . "://" . $parts['host'] . $parts['path']; + } + + /** + * Converts a (possibly) relative URL to a absolute one. + * + * @param string $url Base URL (i.e. from where the document is) + * @param string $rel_url Possibly relative URL in the document + * + * @return string Absolute URL + */ + function rewrite_relative_url($url, $rel_url) { + if (strpos($rel_url, ":") !== false) { + return $rel_url; + } else if (strpos($rel_url, "://") !== false) { + return $rel_url; + } else if (strpos($rel_url, "//") === 0) { + # protocol-relative URL (rare but they exist) + return $rel_url; + } else if (strpos($rel_url, "/") === 0) + { + $parts = parse_url($url); + $parts['path'] = $rel_url; + + return build_url($parts); + + } else { + $parts = parse_url($url); + if (!isset($parts['path'])) { + $parts['path'] = '/'; + } + $dir = $parts['path']; + if (substr($dir, -1) !== '/') { + $dir = dirname($parts['path']); + $dir !== '/' && $dir .= '/'; + } + $parts['path'] = $dir . $rel_url; + + return build_url($parts); + } + } + + function cleanup_tags($days = 14, $limit = 1000) { + + if (DB_TYPE == "pgsql") { + $interval_query = "date_updated < NOW() - INTERVAL '$days days'"; + } else if (DB_TYPE == "mysql") { + $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)"; + } + + $tags_deleted = 0; + + while ($limit > 0) { + $limit_part = 500; + + $query = "SELECT ttrss_tags.id AS id + FROM ttrss_tags, ttrss_user_entries, ttrss_entries + WHERE post_int_id = int_id AND $interval_query AND + ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part"; + + $result = db_query($query); + + $ids = array(); + + while ($line = db_fetch_assoc($result)) { + array_push($ids, $line['id']); + } + + if (count($ids) > 0) { + $ids = join(",", $ids); + + $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)"); + $tags_deleted += db_affected_rows($tmp_result); + } else { + break; + } + + $limit -= $limit_part; + } + + return $tags_deleted; + } + + function print_user_stylesheet() { + $value = get_pref('USER_STYLESHEET'); + + if ($value) { + print "<style type=\"text/css\">"; + print str_replace("<br/>", "\n", $value); + print "</style>"; + } + + } + + function filter_to_sql($filter, $owner_uid) { + $query = array(); + + if (DB_TYPE == "pgsql") + $reg_qpart = "~"; + else + $reg_qpart = "REGEXP"; + + foreach ($filter["rules"] AS $rule) { + $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]); + $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/', + $rule['reg_exp']) !== FALSE; + + if ($regexp_valid) { + + $rule['reg_exp'] = db_escape_string($rule['reg_exp']); + + switch ($rule["type"]) { + case "title": + $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('". + $rule['reg_exp'] . "')"; + break; + case "content": + $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('". + $rule['reg_exp'] . "')"; + break; + case "both": + $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('". + $rule['reg_exp'] . "') OR LOWER(" . + "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')"; + break; + case "tag": + $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('". + $rule['reg_exp'] . "')"; + break; + case "link": + $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('". + $rule['reg_exp'] . "')"; + break; + case "author": + $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('". + $rule['reg_exp'] . "')"; + break; + } + + if (isset($rule['inverse'])) $qpart = "NOT ($qpart)"; + + if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) { + $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]); + } + + if (isset($rule["cat_id"])) { + + if ($rule["cat_id"] > 0) { + $children = getChildCategories($rule["cat_id"], $owner_uid); + array_push($children, $rule["cat_id"]); + + $children = join(",", $children); + + $cat_qpart = "cat_id IN ($children)"; + } else { + $cat_qpart = "cat_id IS NULL"; + } + + $qpart .= " AND $cat_qpart"; + } + + $qpart .= " AND feed_id IS NOT NULL"; + + array_push($query, "($qpart)"); + + } + } + + if (count($query) > 0) { + $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")"; + } else { + $fullquery = "(false)"; + } + + if ($filter['inverse']) $fullquery = "(NOT $fullquery)"; + + return $fullquery; + } + + if (!function_exists('gzdecode')) { + function gzdecode($string) { // no support for 2nd argument + return file_get_contents('compress.zlib://data:who/cares;base64,'. + base64_encode($string)); + } + } + + function get_random_bytes($length) { + if (function_exists('openssl_random_pseudo_bytes')) { + return openssl_random_pseudo_bytes($length); + } else { + $output = ""; + + for ($i = 0; $i < $length; $i++) + $output .= chr(mt_rand(0, 255)); + + return $output; + } + } + + function read_stdin() { + $fp = fopen("php://stdin", "r"); + + if ($fp) { + $line = trim(fgets($fp)); + fclose($fp); + return $line; + } + + return null; + } + + function tmpdirname($path, $prefix) { + // Use PHP's tmpfile function to create a temporary + // directory name. Delete the file and keep the name. + $tempname = tempnam($path,$prefix); + if (!$tempname) + return false; + + if (!unlink($tempname)) + return false; + + return $tempname; + } + + function getFeedCategory($feed) { + $result = db_query("SELECT cat_id FROM ttrss_feeds + WHERE id = '$feed'"); + + if (db_num_rows($result) > 0) { + return db_fetch_result($result, 0, "cat_id"); + } else { + return false; + } + + } + + function implements_interface($class, $interface) { + return in_array($interface, class_implements($class)); + } + + function geturl($url, $depth = 0, $nobody = true){ + + if ($depth == 20) return $url; + + if (!function_exists('curl_init')) + 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); + + $curl = curl_init(); + $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,"; + $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"; + $header[] = "Cache-Control: max-age=0"; + $header[] = "Connection: keep-alive"; + $header[] = "Keep-Alive: 300"; + $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7"; + $header[] = "Accept-Language: en-us,en;q=0.5"; + $header[] = "Pragma: "; + + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0'); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header); + curl_setopt($curl, CURLOPT_HEADER, true); + curl_setopt($curl, CURLOPT_NOBODY, $nobody); + curl_setopt($curl, CURLOPT_REFERER, $url); + curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate'); + curl_setopt($curl, CURLOPT_AUTOREFERER, true); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled... + curl_setopt($curl, CURLOPT_TIMEOUT, 60); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + + if (defined('_CURL_HTTP_PROXY')) { + curl_setopt($curl, CURLOPT_PROXY, _CURL_HTTP_PROXY); + } + + $html = curl_exec($curl); + + $status = curl_getinfo($curl); + + if($status['http_code']!=200){ + + // idiot site not allowing http head + if($status['http_code'] == 405) { + curl_close($curl); + return geturl($url, $depth +1, false); + } + + if($status['http_code'] == 301 || $status['http_code'] == 302) { + curl_close($curl); + list($header) = explode("\r\n\r\n", $html, 2); + $matches = array(); + preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches); + $url = trim(str_replace($matches[1],"",$matches[0])); + $url_parsed = parse_url($url); + return (isset($url_parsed))? geturl($url, $depth + 1):''; + } + + global $fetch_last_error; + + $fetch_last_error = curl_errno($curl) . " " . curl_error($curl); + curl_close($curl); + +# $oline=''; +# foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';} +# $line =$oline." \r\n ".$url."\r\n-----------------\r\n"; +# $handle = @fopen('./curl.error.log', 'a'); +# fwrite($handle, $line); + return FALSE; + } + curl_close($curl); + return $url; + } + + function get_minified_js($files) { + require_once 'lib/jshrink/Minifier.php'; + + $rv = ''; + + foreach ($files as $js) { + if (!isset($_GET['debug'])) { + $cached_file = CACHE_DIR . "/js/".basename($js).".js"; + + if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) { + + list($header, $contents) = explode("\n", file_get_contents($cached_file), 2); + + if ($header && $contents) { + list($htag, $hversion) = explode(":", $header); + + if ($htag == "tt-rss" && $hversion == VERSION) { + $rv .= $contents; + continue; + } + } + } + + $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js")); + file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified); + $rv .= $minified; + + } else { + $rv .= file_get_contents("js/$js.js"); // no cache in debug mode + } + } + + return $rv; + } + + function stylesheet_tag($filename) { + $timestamp = filemtime($filename); + + return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n"; + } + + function javascript_tag($filename) { + $query = ""; + + if (!(strpos($filename, "?") === FALSE)) { + $query = substr($filename, strpos($filename, "?")+1); + $filename = substr($filename, 0, strpos($filename, "?")); + } + + $timestamp = filemtime($filename); + + if ($query) $timestamp .= "&$query"; + + return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n"; + } + + function calculate_dep_timestamp() { + $files = array_merge(glob("js/*.js"), glob("css/*.css")); + + $max_ts = -1; + + foreach ($files as $file) { + if (filemtime($file) > $max_ts) $max_ts = filemtime($file); + } + + return $max_ts; + } + + function T_js_decl($s1, $s2) { + if ($s1 && $s2) { + $s1 = preg_replace("/\n/", "", $s1); + $s2 = preg_replace("/\n/", "", $s2); + + $s1 = preg_replace("/\"/", "\\\"", $s1); + $s2 = preg_replace("/\"/", "\\\"", $s2); + + return "T_messages[\"$s1\"] = \"$s2\";\n"; + } + } + + function init_js_translations() { + + print 'var T_messages = new Object(); + + function __(msg) { + if (T_messages[msg]) { + return T_messages[msg]; + } else { + return msg; + } + } + + function ngettext(msg1, msg2, n) { + return __((parseInt(n) > 1) ? msg2 : msg1); + }'; + + $l10n = _get_reader(); + + for ($i = 0; $i < $l10n->total; $i++) { + $orig = $l10n->get_original_string($i); + if(strpos($orig, "\000") !== FALSE) { // Plural forms + $key = explode(chr(0), $orig); + print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular + print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural + } else { + $translation = __($orig); + print T_js_decl($orig, $translation); + } + } + } + + function label_to_feed_id($label) { + return LABEL_BASE_INDEX - 1 - abs($label); + } + + function feed_to_label_id($feed) { + return LABEL_BASE_INDEX - 1 + abs($feed); + } + +?> diff --git a/include/labels.php b/include/labels.php index c2a48a338..4149864b3 100644 --- a/include/labels.php +++ b/include/labels.php @@ -42,7 +42,8 @@ ORDER BY caption"); while ($line = db_fetch_assoc($result)) { - $rk = array($line["label_id"], $line["caption"], $line["fg_color"], + $rk = array(label_to_feed_id($line["label_id"]), + $line["caption"], $line["fg_color"], $line["bg_color"]); array_push($rv, $rk); } @@ -108,7 +109,7 @@ if (!$label_id) return; - $result = db_query( + db_query( "DELETE FROM ttrss_user_labels2 WHERE label_id = '$label_id' AND diff --git a/include/login_form.php b/include/login_form.php index 891e25e13..0000bc2f6 100644 --- a/include/login_form.php +++ b/include/login_form.php @@ -2,15 +2,22 @@ <html> <head> <title>Tiny Tiny RSS : Login</title> - <link rel="stylesheet" type="text/css" href="lib/dijit/themes/claro/claro.css"/> - <link rel="stylesheet" type="text/css" href="css/tt-rss.css"> + <?php echo stylesheet_tag("lib/dijit/themes/claro/claro.css") ?> + <?php echo stylesheet_tag("css/tt-rss.css") ?> + <?php echo stylesheet_tag("css/dijit.css") ?> <link rel="shortcut icon" type="image/png" href="images/favicon.png"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> - <script type="text/javascript" src="lib/dojo/dojo.js"></script> - <script type="text/javascript" src="lib/dojo/tt-rss-layer.js"></script> - <script type="text/javascript" src="lib/prototype.js"></script> - <script type="text/javascript" src="js/functions.js"></script> - <script type="text/javascript" charset="utf-8" src="errors.php?mode=js"></script> + <?php + foreach (array("lib/prototype.js", + "lib/dojo/dojo.js", + "lib/dojo/tt-rss-layer.js", + "js/functions.js", + "errors.php?mode=js") as $jsfile) { + + echo javascript_tag($jsfile); + + } ?> + <script type="text/javascript"> require({cache:{}}); Event.observe(window, 'load', function() { @@ -246,9 +253,6 @@ function bwLimitChange(elem) { <div class='footer'> <a href="http://tt-rss.org/">Tiny Tiny RSS</a> - <?php if (!defined('HIDE_VERSION')) { ?> - v<?php echo VERSION ?> - <?php } ?> © 2005–<?php echo date('Y') ?> <a href="http://fakecake.org/">Andrew Dolgov</a> </div> diff --git a/include/rssfuncs.php b/include/rssfuncs.php index 4c3e86123..4cd0f6888 100644 --- a/include/rssfuncs.php +++ b/include/rssfuncs.php @@ -2,6 +2,19 @@ define_default('DAEMON_UPDATE_LOGIN_LIMIT', 30); define_default('DAEMON_FEED_LIMIT', 500); define_default('DAEMON_SLEEP_INTERVAL', 120); + define_default('_MIN_CACHE_IMAGE_SIZE', 1024); + + function calculate_article_hash($article, $pluginhost) { + $tmp = ""; + + foreach ($article as $k => $v) { + if ($k != "feed" && isset($v)) { + $tmp .= sha1("$k:" . (is_array($v) ? implode(",", $v) : $v)); + } + } + + return sha1(implode(",", $pluginhost->get_plugin_names()) . $tmp); + } function update_feedbrowser_cache() { @@ -79,7 +92,7 @@ $login_thresh_qpart = ""; } - // Test if the feed need a update (update interval exceded). + // Test if the feed need a update (update interval exceeded). if (DB_TYPE == "pgsql") { $update_limit_qpart = "AND (( ttrss_feeds.update_interval = 0 @@ -88,8 +101,10 @@ ) OR ( ttrss_feeds.update_interval > 0 AND ttrss_feeds.last_updated < NOW() - CAST((ttrss_feeds.update_interval || ' minutes') AS INTERVAL) - ) OR ttrss_feeds.last_updated IS NULL - OR last_updated = '1970-01-01 00:00:00')"; + ) OR (ttrss_feeds.last_updated IS NULL + AND ttrss_user_prefs.value != '-1') + OR (last_updated = '1970-01-01 00:00:00' + AND ttrss_user_prefs.value != '-1'))"; } else { $update_limit_qpart = "AND (( ttrss_feeds.update_interval = 0 @@ -98,8 +113,10 @@ ) OR ( ttrss_feeds.update_interval > 0 AND ttrss_feeds.last_updated < DATE_SUB(NOW(), INTERVAL ttrss_feeds.update_interval MINUTE) - ) OR ttrss_feeds.last_updated IS NULL - OR last_updated = '1970-01-01 00:00:00')"; + ) OR (ttrss_feeds.last_updated IS NULL + AND ttrss_user_prefs.value != '-1') + OR (last_updated = '1970-01-01 00:00:00' + AND ttrss_user_prefs.value != '-1'))"; } // Test if feed is currently being updated by another process. @@ -118,6 +135,7 @@ ttrss_feeds, ttrss_users, ttrss_user_prefs WHERE ttrss_feeds.owner_uid = ttrss_users.id + AND ttrss_user_prefs.profile IS NULL AND ttrss_users.id = ttrss_user_prefs.owner_uid AND ttrss_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL' $login_thresh_qpart $update_limit_qpart @@ -151,6 +169,7 @@ } $nf = 0; + $bstarted = microtime(true); // For each feed, we call the feed update function. foreach ($feeds_to_update as $feed) { @@ -165,6 +184,7 @@ ttrss_user_prefs.owner_uid = ttrss_feeds.owner_uid AND ttrss_users.id = ttrss_user_prefs.owner_uid AND ttrss_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL' AND + ttrss_user_prefs.profile IS NULL AND feed_url = '".db_escape_string($feed)."' AND (ttrss_feeds.update_interval > 0 OR ttrss_user_prefs.value != '-1') @@ -172,14 +192,27 @@ ORDER BY ttrss_feeds.id $query_limit"); if (db_num_rows($tmp_result) > 0) { + $rss = false; + while ($tline = db_fetch_assoc($tmp_result)) { if($debug) _debug(" => " . $tline["last_updated"] . ", " . $tline["id"] . " " . $tline["owner_uid"]); - update_rss_feed($tline["id"], true); + + $fstarted = microtime(true); + $rss = update_rss_feed($tline["id"], true, false); + _debug_suppress(false); + + _debug(sprintf(" %.4f (sec)", microtime(true) - $fstarted)); + ++$nf; } } } + if ($nf > 0) { + _debug(sprintf("Processed %d feeds in %.4f (sec), %.4f (sec/feed avg)", $nf, + microtime(true) - $bstarted, (microtime(true) - $bstarted) / $nf)); + } + require_once "digest.php"; // Send feed digests by email if needed. @@ -190,14 +223,15 @@ } // function update_daemon_common // ignore_daemon is not used - function update_rss_feed($feed, $ignore_daemon = false, $no_cache = false) { + function update_rss_feed($feed, $ignore_daemon = false, $no_cache = false, $rss = false) { $debug_enabled = defined('DAEMON_EXTENDED_DEBUG') || $_REQUEST['xdebug']; + _debug_suppress(!$debug_enabled); _debug("start", $debug_enabled); $result = db_query("SELECT id,update_interval,auth_login, - feed_url,auth_pass,cache_images,last_updated, + feed_url,auth_pass,cache_images, mark_unread_on_update, owner_uid, pubsub_state, auth_pass_encrypted, (SELECT max(date_entered) FROM @@ -209,7 +243,6 @@ return false; } - $last_updated = db_fetch_result($result, 0, "last_updated"); $last_article_timestamp = @strtotime(db_fetch_result($result, 0, "last_article_timestamp")); if (defined('_DISABLE_HTTP_304')) @@ -250,34 +283,37 @@ $pluginhost->load($user_plugins, PluginHost::KIND_USER, $owner_uid); $pluginhost->load_data(); - $rss = false; - $rss_hash = false; + if ($rss && is_object($rss) && get_class($rss) == "FeedParser") { + _debug("using previously initialized parser object"); + } else { + $rss_hash = false; - $force_refetch = isset($_REQUEST["force_refetch"]); + $force_refetch = isset($_REQUEST["force_refetch"]); - if (file_exists($cache_filename) && - is_readable($cache_filename) && - !$auth_login && !$auth_pass && - filemtime($cache_filename) > time() - 30) { + foreach ($pluginhost->get_hooks(PluginHost::HOOK_FETCH_FEED) as $plugin) { + $feed_data = $plugin->hook_fetch_feed($feed_data, $fetch_url, $owner_uid, $feed, $last_article_timestamp, $auth_login, $auth_pass); + } - _debug("using local cache.", $debug_enabled); + // try cache + if (!$feed_data && + file_exists($cache_filename) && + is_readable($cache_filename) && + !$auth_login && !$auth_pass && + filemtime($cache_filename) > time() - 30) { - @$feed_data = file_get_contents($cache_filename); + _debug("using local cache [$cache_filename].", $debug_enabled); - if ($feed_data) { - $rss_hash = sha1($feed_data); - } + @$feed_data = file_get_contents($cache_filename); - } else { - _debug("local cache will not be used for this feed", $debug_enabled); - } - - if (!$rss) { + if ($feed_data) { + $rss_hash = sha1($feed_data); + } - foreach ($pluginhost->get_hooks(PluginHost::HOOK_FETCH_FEED) as $plugin) { - $feed_data = $plugin->hook_fetch_feed($feed_data, $fetch_url, $owner_uid, $feed); + } else { + _debug("local cache will not be used for this feed", $debug_enabled); } + // fetch feed from source if (!$feed_data) { _debug("fetching [$fetch_url]...", $debug_enabled); _debug("If-Modified-Since: ".gmdate('D, d M Y H:i:s \G\M\T', $last_article_timestamp), $debug_enabled); @@ -316,6 +352,16 @@ } } } */ + + // cache vanilla feed data for re-use + if ($feed_data && !$auth_pass && !$auth_login && is_writable(CACHE_DIR . "/simplepie")) { + $new_rss_hash = sha1($feed_data); + + if ($new_rss_hash != $rss_hash) { + _debug("saving $cache_filename", $debug_enabled); + @file_put_contents($cache_filename, $feed_data); + } + } } if (!$feed_data) { @@ -354,22 +400,19 @@ $rss->init(); } + if (DETECT_ARTICLE_LANGUAGE) { + require_once "lib/languagedetect/LanguageDetect.php"; + + $lang = new Text_LanguageDetect(); + $lang->setNameMode(2); + } + // print_r($rss); $feed = db_escape_string($feed); if (!$rss->error()) { - // cache data for later - if (!$auth_pass && !$auth_login && is_writable(CACHE_DIR . "/simplepie")) { - $new_rss_hash = sha1($rss_data); - - if ($new_rss_hash != $rss_hash && count($rss->get_items()) > 0 ) { - _debug("saving $cache_filename", $debug_enabled); - @file_put_contents($cache_filename, $feed_data); - } - } - // We use local pluginhost here because we need to load different per-user feed plugins $pluginhost->run_hooks(PluginHost::HOOK_FEED_PARSED, "hook_feed_parsed", $rss); @@ -438,7 +481,7 @@ if (!$registered_title || $registered_title == "[Unknown]") { - $feed_title = db_escape_string($rss->get_title()); + $feed_title = db_escape_string(mb_substr($rss->get_title(), 0, 199)); if ($feed_title) { _debug("registering title: $feed_title", $debug_enabled); @@ -488,7 +531,20 @@ _debug("feed hub url: $feed_hub_url", $debug_enabled); - if ($feed_hub_url && function_exists('curl_init') && + $feed_self_url = $fetch_url; + + $links = $rss->get_links('self'); + + if ($links && is_array($links)) { + foreach ($links as $l) { + $feed_self_url = $l; + break; + } + } + + _debug("feed self url = $feed_self_url"); + + if ($feed_hub_url && $feed_self_url && function_exists('curl_init') && !ini_get("open_basedir")) { require_once 'lib/pubsubhubbub/subscriber.php'; @@ -498,9 +554,9 @@ $s = new Subscriber($feed_hub_url, $callback_url); - $rc = $s->subscribe($fetch_url); + $rc = $s->subscribe($feed_self_url); - _debug("feed hub url found, subscribe request sent.", $debug_enabled); + _debug("feed hub url found, subscribe request sent. [rc=$rc]", $debug_enabled); db_query("UPDATE ttrss_feeds SET pubsub_state = 1 WHERE id = '$feed'"); @@ -517,9 +573,6 @@ $entry_guid = $item->get_id(); if (!$entry_guid) $entry_guid = $item->get_link(); if (!$entry_guid) $entry_guid = make_guid_from_title($item->get_title()); - - _debug("f_guid $entry_guid", $debug_enabled); - if (!$entry_guid) continue; $entry_guid = "$owner_uid,$entry_guid"; @@ -536,9 +589,6 @@ if ($entry_timestamp == -1 || !$entry_timestamp || $entry_timestamp > time()) { $entry_timestamp = time(); - $no_orig_date = 'true'; - } else { - $no_orig_date = 'false'; } $entry_timestamp_fmt = strftime("%Y/%m/%d %H:%M:%S", $entry_timestamp); @@ -565,6 +615,21 @@ print "\n"; } + $entry_language = ""; + + if (DETECT_ARTICLE_LANGUAGE) { + $entry_language = $lang->detect($entry_title . " " . $entry_content, 1); + + if (count($entry_language) > 0) { + $possible = array_keys($entry_language); + $entry_language = $possible[0]; + + _debug("detected language: $entry_language", $debug_enabled); + } else { + $entry_language = ""; + } + } + $entry_comments = $item->get_comments_url(); $entry_author = $item->get_author(); @@ -600,24 +665,15 @@ _debug("done collecting data.", $debug_enabled); - // TODO: less memory-hungry implementation - - _debug("applying plugin filters..", $debug_enabled); - - // FIXME not sure if owner_uid is a good idea here, we may have a base entry without user entry (?) - $result = db_query("SELECT plugin_data,title,content,link,tag_cache,author FROM ttrss_entries, ttrss_user_entries - WHERE ref_id = id AND (guid = '".db_escape_string($entry_guid)."' OR guid = '$entry_guid_hashed') AND owner_uid = $owner_uid"); + $result = db_query("SELECT id, content_hash FROM ttrss_entries + WHERE guid = '".db_escape_string($entry_guid)."' OR guid = '$entry_guid_hashed'"); if (db_num_rows($result) != 0) { - $entry_plugin_data = db_fetch_result($result, 0, "plugin_data"); - $stored_article = array("title" => db_fetch_result($result, 0, "title"), - "content" => db_fetch_result($result, 0, "content"), - "link" => db_fetch_result($result, 0, "link"), - "tags" => explode(",", db_fetch_result($result, 0, "tag_cache")), - "author" => db_fetch_result($result, 0, "author")); + $base_entry_id = db_fetch_result($result, 0, "id"); + $entry_stored_hash = db_fetch_result($result, 0, "content_hash"); } else { - $entry_plugin_data = ""; - $stored_article = array(); + $base_entry_id = false; + $entry_stored_hash = ""; } $article = array("owner_uid" => $owner_uid, // read only @@ -626,32 +682,67 @@ "content" => $entry_content, "link" => $entry_link, "tags" => $entry_tags, - "plugin_data" => $entry_plugin_data, "author" => $entry_author, - "stored" => $stored_article); + "language" => $entry_language, // read only + "feed" => array("id" => $feed, + "fetch_url" => $fetch_url, + "site_url" => $site_url) + ); + + $entry_plugin_data = ""; + $entry_current_hash = calculate_article_hash($article, $pluginhost); + + _debug("article hash: $entry_current_hash [stored=$entry_stored_hash]", $debug_enabled); + + if ($entry_current_hash == $entry_stored_hash && !isset($_REQUEST["force_rehash"])) { + _debug("stored article seems up to date [IID: $base_entry_id], updating timestamp only", $debug_enabled); + + // we keep encountering the entry in feeds, so we need to + // update date_updated column so that we don't get horrible + // dupes when the entry gets purged and reinserted again e.g. + // in the case of SLOW SLOW OMG SLOW updating feeds + + $base_entry_id = db_fetch_result($result, 0, "id"); + + db_query("UPDATE ttrss_entries SET date_updated = NOW() + WHERE id = '$base_entry_id'"); + + // if we allow duplicate posts, we have to continue to + // create the user entries for this feed + if (!get_pref("ALLOW_DUPLICATE_POSTS", $owner_uid, false)) { + continue; + } + } + + _debug("hash differs, applying plugin filters:", $debug_enabled); foreach ($pluginhost->get_hooks(PluginHost::HOOK_ARTICLE_FILTER) as $plugin) { + _debug("... " . get_class($plugin), $debug_enabled); + + $start = microtime(true); $article = $plugin->hook_article_filter($article); + + _debug("=== " . sprintf("%.4f (sec)", microtime(true) - $start), $debug_enabled); + + $entry_plugin_data .= mb_strtolower(get_class($plugin)) . ","; } + $entry_plugin_data = db_escape_string($entry_plugin_data); + + _debug("plugin data: $entry_plugin_data", $debug_enabled); + $entry_tags = $article["tags"]; $entry_guid = db_escape_string($entry_guid); $entry_title = db_escape_string($article["title"]); $entry_author = db_escape_string($article["author"]); $entry_link = db_escape_string($article["link"]); - $entry_plugin_data = db_escape_string($article["plugin_data"]); $entry_content = $article["content"]; // escaped below - - _debug("plugin data: $entry_plugin_data", $debug_enabled); - if ($cache_images && is_writable(CACHE_DIR . '/images')) cache_images($entry_content, $site_url, $debug_enabled); $entry_content = db_escape_string($entry_content, false); - $content_hash = "SHA1:" . sha1($entry_content); - db_query("BEGIN"); $result = db_query("SELECT id FROM ttrss_entries @@ -671,13 +762,13 @@ updated, content, content_hash, - cached_content, no_orig_date, date_updated, date_entered, comments, num_comments, plugin_data, + lang, author) VALUES ('$entry_title', @@ -685,41 +776,27 @@ '$entry_link', '$entry_timestamp_fmt', '$entry_content', - '$content_hash', - '', - $no_orig_date, + '$entry_current_hash', + false, NOW(), '$date_feed_processed', '$entry_comments', '$num_comments', '$entry_plugin_data', + '$entry_language', '$entry_author')"); $article_labels = array(); } else { - // we keep encountering the entry in feeds, so we need to - // update date_updated column so that we don't get horrible - // dupes when the entry gets purged and reinserted again e.g. - // in the case of SLOW SLOW OMG SLOW updating feeds - $base_entry_id = db_fetch_result($result, 0, "id"); - db_query("UPDATE ttrss_entries SET date_updated = NOW() - WHERE id = '$base_entry_id'"); - $article_labels = get_article_labels($base_entry_id, $owner_uid); } // now it should exist, if not - bad luck then - $result = db_query("SELECT - id,content_hash,no_orig_date,title,plugin_data,guid, - ".SUBSTRING_FOR_DATE."(date_updated,1,19) as date_updated, - ".SUBSTRING_FOR_DATE."(updated,1,19) as updated, - num_comments - FROM - ttrss_entries + $result = db_query("SELECT id FROM ttrss_entries WHERE guid = '$entry_guid' OR guid = '$entry_guid_hashed'"); $entry_ref_id = 0; @@ -729,14 +806,6 @@ _debug("base guid found, checking for user record", $debug_enabled); - // this will be used below in update handler - $orig_content_hash = db_fetch_result($result, 0, "content_hash"); - $orig_title = db_fetch_result($result, 0, "title"); - $orig_num_comments = db_fetch_result($result, 0, "num_comments"); - $orig_date_updated = strtotime(db_fetch_result($result, - 0, "date_updated")); - $orig_plugin_data = db_fetch_result($result, 0, "plugin_data"); - $ref_id = db_fetch_result($result, 0, "id"); $entry_ref_id = $ref_id; @@ -850,7 +919,7 @@ $p = new Publisher(PUBSUBHUBBUB_HUB); - $pubsub_result = $p->publish_update($rss_link); + /* $pubsub_result = */ $p->publish_update($rss_link); } $result = db_query( @@ -870,53 +939,20 @@ _debug("RID: $entry_ref_id, IID: $entry_int_id", $debug_enabled); - $post_needs_update = false; - $update_insignificant = false; - - if ($orig_num_comments != $num_comments) { - $post_needs_update = true; - $update_insignificant = true; - } - - if ($entry_plugin_data != $orig_plugin_data) { - $post_needs_update = true; - $update_insignificant = true; - } - - if ($content_hash != $orig_content_hash) { - $post_needs_update = true; - $update_insignificant = false; - } - - if (db_escape_string($orig_title) != $entry_title) { - $post_needs_update = true; - $update_insignificant = false; - } - - // if post needs update, update it and mark all user entries - // linking to this post as updated - if ($post_needs_update) { - - if (defined('DAEMON_EXTENDED_DEBUG')) { - _debug("post $entry_guid_hashed needs update...", $debug_enabled); - } - -// print "<!-- post $orig_title needs update : $post_needs_update -->"; - - db_query("UPDATE ttrss_entries - SET title = '$entry_title', content = '$entry_content', - content_hash = '$content_hash', - updated = '$entry_timestamp_fmt', - num_comments = '$num_comments', - plugin_data = '$entry_plugin_data' - WHERE id = '$ref_id'"); - - if (!$update_insignificant) { - if ($mark_unread_on_update) { - db_query("UPDATE ttrss_user_entries - SET last_read = null, unread = true WHERE ref_id = '$ref_id'"); - } - } + db_query("UPDATE ttrss_entries + SET title = '$entry_title', + content = '$entry_content', + content_hash = '$entry_current_hash', + updated = '$entry_timestamp_fmt', + num_comments = '$num_comments', + plugin_data = '$entry_plugin_data', + author = '$entry_author', + lang = '$entry_language' + WHERE id = '$ref_id'"); + + if ($mark_unread_on_update) { + db_query("UPDATE ttrss_user_entries + SET last_read = null, unread = true WHERE ref_id = '$ref_id'"); } } @@ -938,7 +974,7 @@ if (is_array($encs)) { foreach ($encs as $e) { $e_item = array( - $e->link, $e->type, $e->length); + $e->link, $e->type, $e->length, $e->title, $e->width, $e->height); array_push($enclosures, $e_item); } } @@ -950,18 +986,24 @@ db_query("BEGIN"); +// debugging +// db_query("DELETE FROM ttrss_enclosures WHERE post_id = '$entry_ref_id'"); + foreach ($enclosures as $enc) { $enc_url = db_escape_string($enc[0]); $enc_type = db_escape_string($enc[1]); $enc_dur = db_escape_string($enc[2]); + $enc_title = db_escape_string($enc[3]); + $enc_width = intval($enc[4]); + $enc_height = intval($enc[5]); $result = db_query("SELECT id FROM ttrss_enclosures WHERE content_url = '$enc_url' AND post_id = '$entry_ref_id'"); if (db_num_rows($result) == 0) { db_query("INSERT INTO ttrss_enclosures - (content_url, content_type, title, duration, post_id) VALUES - ('$enc_url', '$enc_type', '', '$enc_dur', '$entry_ref_id')"); + (content_url, content_type, title, duration, post_id, width, height) VALUES + ('$enc_url', '$enc_type', '$enc_title', '$enc_dur', '$entry_ref_id', $enc_width, $enc_height)"); } } @@ -1062,11 +1104,6 @@ _debug("article processed", $debug_enabled); } - if (!$last_updated) { - _debug("new feed, catching it up...", $debug_enabled); - catchup_feed($feed, false, $owner_uid); - } - _debug("purging feed...", $debug_enabled); purge_feed($feed, 0, $debug_enabled); @@ -1080,21 +1117,27 @@ $error_msg = db_escape_string(mb_substr($rss->error(), 0, 245)); - _debug("error fetching feed: $error_msg", $debug_enabled); + _debug("fetch error: $error_msg", $debug_enabled); + + if (count($rss->errors()) > 1) { + foreach ($rss->errors() as $error) { + _debug("+ $error"); + } + } db_query( "UPDATE ttrss_feeds SET last_error = '$error_msg', - last_updated = NOW() WHERE id = '$feed'"); - } + last_updated = NOW() WHERE id = '$feed'"); - unset($rss); + unset($rss); + } _debug("done", $debug_enabled); + + return $rss; } function cache_images($html, $site_url, $debug) { - $cache_dir = CACHE_DIR . "/images"; - libxml_use_internal_errors(true); $charset_hack = '<head> @@ -1118,21 +1161,20 @@ if (!file_exists($local_filename)) { $file_content = fetch_file_contents($src); - if ($file_content && strlen($file_content) > 1024) { + if ($file_content && strlen($file_content) > _MIN_CACHE_IMAGE_SIZE) { file_put_contents($local_filename, $file_content); } } - if (file_exists($local_filename)) { + /* if (file_exists($local_filename)) { $entry->setAttribute('src', SELF_URL_PATH . '/image.php?url=' . base64_encode($src)); - } + } */ } } - $node = $doc->getElementsByTagName('body')->item(0); - - return $doc->saveXML($node); + //$node = $doc->getElementsByTagName('body')->item(0); + //return $doc->saveXML($node); } function expire_error_log($debug) { @@ -1370,5 +1412,8 @@ $rc = cleanup_tags( 14, 50000); _debug("Cleaned $rc cached tags."); + + PluginHost::getInstance()->run_hooks(PluginHost::HOOK_HOUSE_KEEPING, "hook_house_keeping", ""); + } ?> diff --git a/include/sanity_check.php b/include/sanity_check.php index 29e53fa33..6bec43051 100644 --- a/include/sanity_check.php +++ b/include/sanity_check.php @@ -95,7 +95,7 @@ } } - if (SELF_URL_PATH == "http://yourserver/tt-rss/") { + if (SELF_URL_PATH == "http://example.org/tt-rss/") { $urlpath = preg_replace("/\w+\.php$/", "", make_self_url_path()); array_push($errors, @@ -138,10 +138,6 @@ array_push($errors, "PHP support for ctype functions are required by HTMLPurifier."); } - if (!function_exists("iconv")) { - array_push($errors, "PHP support for iconv is required to handle multiple charsets."); - } - /* if (ini_get("safe_mode")) { array_push($errors, "PHP safe mode setting is not supported."); } */ diff --git a/include/sanity_config.php b/include/sanity_config.php index 527fa6dc4..76fba4b81 100644 --- a/include/sanity_config.php +++ b/include/sanity_config.php @@ -1,3 +1,3 @@ -<?php # This file has been generated at: Thu May 30 08:39:20 MSK 2013 +<?php # This file has been generated at: Fri Sep 27 13:42:37 MSK 2013 define('GENERATED_CONFIG_CHECK', 26); -$requred_defines = array( 'DB_TYPE', 'DB_HOST', 'DB_USER', 'DB_NAME', 'DB_PASS', 'MYSQL_CHARSET', 'SELF_URL_PATH', 'FEED_CRYPT_KEY', 'SINGLE_USER_MODE', 'SIMPLE_UPDATE_MODE', 'PHP_EXECUTABLE', 'LOCK_DIRECTORY', 'CACHE_DIR', 'ICONS_DIR', 'ICONS_URL', 'AUTH_AUTO_CREATE', 'AUTH_AUTO_LOGIN', 'FORCE_ARTICLE_PURGE', 'PUBSUBHUBBUB_HUB', 'PUBSUBHUBBUB_ENABLED', 'SPHINX_ENABLED', 'SPHINX_SERVER', 'SPHINX_INDEX', 'ENABLE_REGISTRATION', 'REG_NOTIFY_ADDRESS', 'REG_MAX_USERS', 'SESSION_COOKIE_LIFETIME', 'SESSION_CHECK_ADDRESS', 'SMTP_FROM_NAME', 'SMTP_FROM_ADDRESS', 'DIGEST_SUBJECT', 'SMTP_SERVER', 'SMTP_LOGIN', 'SMTP_PASSWORD', 'SMTP_SECURE', 'CHECK_FOR_NEW_VERSION', 'ENABLE_GZIP_OUTPUT', 'PLUGINS', 'LOG_DESTINATION', 'CONFIG_VERSION'); ?> +$requred_defines = array( 'DB_TYPE', 'DB_HOST', 'DB_USER', 'DB_NAME', 'DB_PASS', 'MYSQL_CHARSET', 'SELF_URL_PATH', 'FEED_CRYPT_KEY', 'SINGLE_USER_MODE', 'SIMPLE_UPDATE_MODE', 'PHP_EXECUTABLE', 'LOCK_DIRECTORY', 'CACHE_DIR', 'ICONS_DIR', 'ICONS_URL', 'AUTH_AUTO_CREATE', 'AUTH_AUTO_LOGIN', 'FORCE_ARTICLE_PURGE', 'PUBSUBHUBBUB_HUB', 'PUBSUBHUBBUB_ENABLED', 'ENABLE_REGISTRATION', 'REG_NOTIFY_ADDRESS', 'REG_MAX_USERS', 'SESSION_COOKIE_LIFETIME', 'SESSION_CHECK_ADDRESS', 'SMTP_FROM_NAME', 'SMTP_FROM_ADDRESS', 'DIGEST_SUBJECT', 'SMTP_SERVER', 'SMTP_LOGIN', 'SMTP_PASSWORD', 'SMTP_SECURE', 'CHECK_FOR_NEW_VERSION', 'DETECT_ARTICLE_LANGUAGE', 'ENABLE_GZIP_OUTPUT', 'PLUGINS', 'LOG_DESTINATION', 'CONFIG_VERSION'); ?> diff --git a/include/sessions.php b/include/sessions.php index 423ef0eda..66214afe7 100644 --- a/include/sessions.php +++ b/include/sessions.php @@ -62,11 +62,17 @@ return false; } - if ($_SESSION["ref_schema_version"] != session_get_schema_version(true)) + if ($_SESSION["ref_schema_version"] != session_get_schema_version(true)) { + $_SESSION["login_error_msg"] = + __("Session failed to validate (schema version changed)"); return false; + } - if (sha1($_SERVER['HTTP_USER_AGENT']) != $_SESSION["user_agent"]) + if (sha1($_SERVER['HTTP_USER_AGENT']) != $_SESSION["user_agent"]) { + $_SESSION["login_error_msg"] = + __("Session failed to validate (user agent changed)"); return false; + } if ($_SESSION["uid"]) { $result = Db::get()->query( @@ -74,11 +80,19 @@ // user not found if (Db::get()->num_rows($result) == 0) { + + $_SESSION["login_error_msg"] = + __("Session failed to validate (user not found)"); + return false; } else { $pwd_hash = Db::get()->fetch_result($result, 0, "pwd_hash"); if ($pwd_hash != $_SESSION["pwd_hash"]) { + + $_SESSION["login_error_msg"] = + __("Session failed to validate (password changed)"); + return false; } } diff --git a/include/version.php b/include/version.php index c58dc3a19..93115140e 100644 --- a/include/version.php +++ b/include/version.php @@ -1,13 +1,13 @@ <?php - define('VERSION_STATIC', '1.8'); + define('VERSION_STATIC', '1.14'); function get_version() { date_default_timezone_set('UTC'); $root_dir = dirname(dirname(__FILE__)); - if (is_dir("$root_dir/.git") && file_exists("$root_dir/.git/ORIG_HEAD")) { + if (is_dir("$root_dir/.git") && file_exists("$root_dir/.git/refs/heads/master")) { - $suffix = substr(trim(file_get_contents("$root_dir/.git/ORIG_HEAD")), 0, 7); + $suffix = substr(trim(file_get_contents("$root_dir/.git/refs/heads/master")), 0, 7); return VERSION_STATIC . ".$suffix"; } else { |