diff options
Diffstat (limited to 'classes')
| -rwxr-xr-x | classes/api.php | 8 | ||||
| -rwxr-xr-x | classes/article.php | 38 | ||||
| -rw-r--r-- | classes/diskcache.php | 2 | ||||
| -rwxr-xr-x | classes/feeditem/common.php | 3 | ||||
| -rwxr-xr-x | classes/feeds.php | 323 | ||||
| -rwxr-xr-x | classes/handler/public.php | 2 | ||||
| -rwxr-xr-x | classes/logger/sql.php | 5 | ||||
| -rwxr-xr-x | classes/pref/feeds.php | 51 | ||||
| -rwxr-xr-x | classes/pref/filters.php | 12 | ||||
| -rw-r--r-- | classes/pref/prefs.php | 33 | ||||
| -rw-r--r-- | classes/pref/system.php | 88 | ||||
| -rwxr-xr-x | classes/rssutils.php | 58 | ||||
| -rw-r--r-- | classes/urlhelper.php | 19 | ||||
| -rw-r--r-- | classes/userhelper.php | 11 |
14 files changed, 442 insertions, 211 deletions
diff --git a/classes/api.php b/classes/api.php index 3bba4fa8d..aa39171bf 100755 --- a/classes/api.php +++ b/classes/api.php @@ -81,7 +81,7 @@ class API extends Handler { $this->wrap(self::STATUS_OK, array("session_id" => session_id(), "api_level" => self::API_LEVEL)); } else { // else we are not logged in - user_error("Failed login attempt for $login from {$_SERVER['REMOTE_ADDR']}", E_USER_WARNING); + user_error("Failed login attempt for $login from " . UserHelper::get_user_ip(), E_USER_WARNING); $this->wrap(self::STATUS_ERR, array("error" => "LOGIN_ERROR")); } } else { @@ -786,11 +786,15 @@ class API extends Handler { $headline_row["content"] = DiskCache::rewriteUrls($headline_row['content']); - list ($flavor_image, $flavor_stream) = Article::get_article_image($enclosures, $line["content"], $line["site_url"]); + list ($flavor_image, $flavor_stream, $flavor_kind) = Article::get_article_image($enclosures, $line["content"], $line["site_url"]); $headline_row["flavor_image"] = $flavor_image; $headline_row["flavor_stream"] = $flavor_stream; + /* optional */ + if ($flavor_kind) + $headline_row["flavor_kind"] = $flavor_kind; + array_push($headlines, $headline_row); } } else if (is_numeric($result) && $result == -1) { diff --git a/classes/article.php b/classes/article.php index 430109283..5527b7253 100755 --- a/classes/article.php +++ b/classes/article.php @@ -179,7 +179,7 @@ class Article extends Handler_Protected { print "<footer>"; print "<button dojoType='dijit.form.Button' - type='submit' class='alt-primary' onclick=\"dijit.byId('editTagsDlg').execute()\">".__('Save')."</button> "; + type='submit' class='alt-primary'>".__('Save')."</button> "; print "<button dojoType='dijit.form.Button' onclick=\"dijit.byId('editTagsDlg').hide()\">".__('Cancel')."</button>"; print "</footer>"; @@ -218,7 +218,7 @@ class Article extends Handler_Protected { $id = clean($_REQUEST["id"]); $tags_str = clean($_REQUEST["tags_str"]); - $tags = array_unique(trim_array(explode(",", $tags_str))); + $tags = array_unique(array_map('trim', explode(",", $tags_str))); $this->pdo->beginTransaction(); @@ -232,19 +232,24 @@ class Article extends Handler_Protected { $int_id = $row['int_id']; - $sth = $this->pdo->prepare("DELETE FROM ttrss_tags WHERE + $dsth = $this->pdo->prepare("DELETE FROM ttrss_tags WHERE post_int_id = ? AND owner_uid = ?"); - $sth->execute([$int_id, $_SESSION['uid']]); + $dsth->execute([$int_id, $_SESSION['uid']]); + + $csth = $this->pdo->prepare("SELECT post_int_id FROM ttrss_tags + WHERE post_int_id = ? AND owner_uid = ? AND tag_name = ?"); + + $usth = $this->pdo->prepare("INSERT INTO ttrss_tags + (post_int_id, owner_uid, tag_name) + VALUES (?, ?, ?)"); $tags = FeedItem_Common::normalize_categories($tags); foreach ($tags as $tag) { - if ($tag != '') { - $sth = $this->pdo->prepare("INSERT INTO ttrss_tags - (post_int_id, owner_uid, tag_name) - VALUES (?, ?, ?)"); + $csth->execute([$int_id, $_SESSION['uid'], $tag]); - $sth->execute([$int_id, $_SESSION['uid'], $tag]); + if (!$csth->fetch()) { + $usth->execute([$int_id, $_SESSION['uid'], $tag]); } array_push($tags_to_cache, $tag); @@ -716,6 +721,11 @@ class Article extends Handler_Protected { $article_image = ""; $article_stream = ""; + $article_kind = 0; + + define('ARTICLE_KIND_ALBUM', 1); /* TODO */ + define('ARTICLE_KIND_VIDEO', 2); + define('ARTICLE_KIND_YOUTUBE', 3); foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_IMAGE) as $p) { list ($article_image, $article_stream, $content) = $p->hook_article_image($enclosures, $content, $site_url); @@ -734,6 +744,7 @@ class Article extends Handler_Protected { if ($rrr = preg_match("/\/embed\/([\w-]+)/", $e->getAttribute("src"), $matches)) { $article_image = "https://img.youtube.com/vi/" . $matches[1] . "/hqdefault.jpg"; $article_stream = "https://youtu.be/" . $matches[1]; + $article_kind = ARTICLE_KIND_YOUTUBE; break; } } else if ($e->nodeName == "video") { @@ -743,6 +754,7 @@ class Article extends Handler_Protected { if ($src) { $article_stream = $src->getAttribute("src"); + $article_kind = ARTICLE_KIND_VIDEO; } break; @@ -763,9 +775,13 @@ class Article extends Handler_Protected { } } - if ($article_image) + if ($article_image) { $article_image = rewrite_relative_url($site_url, $article_image); + if (!$article_kind && (count($enclosures) > 1 || $elems->length > 1)) + $article_kind = ARTICLE_KIND_ALBUM; + } + if ($article_stream) $article_stream = rewrite_relative_url($site_url, $article_stream); } @@ -778,7 +794,7 @@ class Article extends Handler_Protected { if ($article_stream && $cache->exists(sha1($article_stream))) $article_stream = $cache->getUrl(sha1($article_stream)); - return [$article_image, $article_stream]; + return [$article_image, $article_stream, $article_kind]; } } diff --git a/classes/diskcache.php b/classes/diskcache.php index 6a8aa89c4..c56dc6f14 100644 --- a/classes/diskcache.php +++ b/classes/diskcache.php @@ -384,7 +384,7 @@ class DiskCache { $mimetype_blacklist = [ "image/svg+xml" ]; /* only serve video and images */ - if (!preg_match("/(image|video)\//", $mimetype) || in_array($mimetype, $mimetype_blacklist)) { + if (!preg_match("/(image|audio|video)\//", $mimetype) || in_array($mimetype, $mimetype_blacklist)) { http_response_code(400); header("Content-type: text/plain"); diff --git a/classes/feeditem/common.php b/classes/feeditem/common.php index f208f4a48..1e9d62228 100755 --- a/classes/feeditem/common.php +++ b/classes/feeditem/common.php @@ -189,6 +189,9 @@ abstract class FeedItem_Common extends FeedItem { return $cat; }, $tmp); + // remove empty values + $tmp = array_filter($tmp, 'strlen'); + asort($tmp); return array_unique($tmp); diff --git a/classes/feeds.php b/classes/feeds.php index c1b7e8022..744c463af 100755 --- a/classes/feeds.php +++ b/classes/feeds.php @@ -5,7 +5,10 @@ class Feeds extends Handler_Protected { const NEVER_GROUP_FEEDS = [ -6, 0 ]; const NEVER_GROUP_BY_DATE = [ -2, -1, -3 ]; - private $params; + private $params; + + private $viewfeed_timestamp; + private $viewfeed_timestamp_last; function csrf_ignore($method) { $csrf_ignored = array("index"); @@ -122,6 +125,8 @@ class Feeds extends Handler_Protected { $disable_cache = false; + $this->mark_timestamp("init"); + $reply = array(); $rgba_cache = array(); @@ -203,6 +208,8 @@ class Feeds extends Handler_Protected { $qfh_ret = $this->queryFeedHeadlines($params); } + $this->mark_timestamp("db query"); + $vfeed_group_enabled = get_pref("VFEED_GROUP_BY_FEED") && !(in_array($feed, self::NEVER_GROUP_FEEDS) && !$cat_view); @@ -233,10 +240,13 @@ class Feeds extends Handler_Protected { $reply['content'] = []; + $this->mark_timestamp("object header"); + $headlines_count = 0; if (is_object($result)) { while ($line = $result->fetch(PDO::FETCH_ASSOC)) { + $this->mark_timestamp("article start: " . $line["id"] . " " . $line["title"]); ++$headlines_count; @@ -248,7 +258,9 @@ class Feeds extends Handler_Protected { foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) { $line = $p->hook_query_headlines($line, 250, false); } - } + } + + $this->mark_timestamp(" hook_query_headlines"); $id = $line["id"]; @@ -293,62 +305,83 @@ class Feeds extends Handler_Protected { array_push($topmost_article_ids, $id); } + $this->mark_timestamp(" labels"); + if (!$line["feed_title"]) $line["feed_title"] = ""; - $line["buttons_left"] = ""; - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) { - $line["buttons_left"] .= $p->hook_article_left_button($line); - } + $line["buttons_left"] = ""; + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) { + $line["buttons_left"] .= $p->hook_article_left_button($line); + } + + $line["buttons"] = ""; + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) { + $line["buttons"] .= $p->hook_article_button($line); + } - $line["buttons"] = ""; - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) { - $line["buttons"] .= $p->hook_article_button($line); - } + $this->mark_timestamp(" pre-sanitize"); - $line["content"] = Sanitizer::sanitize($line["content"], - $line['hide_images'], false, $line["site_url"], $highlight_words, $line["id"]); + $line["content"] = Sanitizer::sanitize($line["content"], + $line['hide_images'], false, $line["site_url"], $highlight_words, $line["id"]); - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_CDM) as $p) { - $line = $p->hook_render_article_cdm($line); - } + $this->mark_timestamp(" sanitize"); - $line['content'] = DiskCache::rewriteUrls($line['content']); + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_CDM) as $p) { + $line = $p->hook_render_article_cdm($line); - if ($line['note']) - $line['note'] = Article::format_article_note($id, $line['note']); - else - $line['note'] = ""; + $this->mark_timestamp(" hook_render_cdm: " . get_class($p)); + } - if (!get_pref("CDM_EXPANDED")) { - $line["cdm_excerpt"] = "<span class='collapse'> - <i class='material-icons' onclick='return Article.cdmUnsetActive(event)' - title=\"" . __("Collapse article") . "\">remove_circle</i></span>"; + $this->mark_timestamp(" hook_render_cdm"); - if (get_pref('SHOW_CONTENT_PREVIEW')) { - $line["cdm_excerpt"] .= "<span class='excerpt'>" . $line["content_preview"] . "</span>"; - } - } + $line['content'] = DiskCache::rewriteUrls($line['content']); - $line["enclosures"] = Article::format_article_enclosures($id, $line["always_display_enclosures"], - $line["content"], $line["hide_images"]); + $this->mark_timestamp(" disk_cache_rewrite"); - if ($line["orig_feed_id"]) { + if ($line['note']) + $line['note'] = Article::format_article_note($id, $line['note']); + else + $line['note'] = ""; - $ofgh = $this->pdo->prepare("SELECT * FROM ttrss_archived_feeds - WHERE id = ? AND owner_uid = ?"); - $ofgh->execute([$line["orig_feed_id"], $_SESSION['uid']]); + $this->mark_timestamp(" note"); - if ($tmp_line = $ofgh->fetch()) { - $line["orig_feed"] = [ $tmp_line["title"], $tmp_line["site_url"], $tmp_line["feed_url"] ]; - } - } + if (!get_pref("CDM_EXPANDED")) { + $line["cdm_excerpt"] = "<span class='collapse'> + <i class='material-icons' onclick='return Article.cdmUnsetActive(event)' + title=\"" . __("Collapse article") . "\">remove_circle</i></span>"; + + if (get_pref('SHOW_CONTENT_PREVIEW')) { + $line["cdm_excerpt"] .= "<span class='excerpt'>" . $line["content_preview"] . "</span>"; + } + } + + $this->mark_timestamp(" pre-enclosures"); + + $line["enclosures"] = Article::format_article_enclosures($id, $line["always_display_enclosures"], + $line["content"], $line["hide_images"]); + + $this->mark_timestamp(" enclosures"); + + if ($line["orig_feed_id"]) { + + $ofgh = $this->pdo->prepare("SELECT * FROM ttrss_archived_feeds + WHERE id = ? AND owner_uid = ?"); + $ofgh->execute([$line["orig_feed_id"], $_SESSION['uid']]); + + if ($tmp_line = $ofgh->fetch()) { + $line["orig_feed"] = [ $tmp_line["title"], $tmp_line["site_url"], $tmp_line["feed_url"] ]; + } + } + + $this->mark_timestamp(" orig-feed-id"); $line["updated_long"] = TimeHelper::make_local_datetime($line["updated"],true); $line["updated"] = TimeHelper::make_local_datetime($line["updated"], false, false, false, true); - $line['imported'] = T_sprintf("Imported at %s", - TimeHelper::make_local_datetime($line["date_entered"], false)); + TimeHelper::make_local_datetime($line["date_entered"], false)); + + $this->mark_timestamp(" local-datetime"); if ($line["tag_cache"]) $tags = explode(",", $line["tag_cache"]); @@ -357,6 +390,8 @@ class Feeds extends Handler_Protected { $line["tags_str"] = Article::format_tags_string($tags, $id); + $this->mark_timestamp(" tags"); + if (self::feedHasIcon($feed_id)) { $line['feed_icon'] = "<img class=\"icon\" src=\"".ICONS_URL."/$feed_id.ico\" alt=\"\">"; } else { @@ -366,6 +401,8 @@ class Feeds extends Handler_Protected { //setting feed headline background color, needs to change text color based on dark/light $fav_color = $line['favicon_avg_color']; + $this->mark_timestamp(" pre-color"); + require_once "colors.php"; if (!isset($rgba_cache[$feed_id])) { @@ -378,17 +415,23 @@ class Feeds extends Handler_Protected { if (isset($rgba_cache[$feed_id])) { $line['feed_bg_color'] = 'rgba(' . implode(",", $rgba_cache[$feed_id]) . ',0.3)'; - } + } + + $this->mark_timestamp(" color"); /* we don't need those */ - foreach (["date_entered", "guid", "last_published", "last_marked", "tag_cache", "favicon_avg_color", - "uuid", "label_cache", "yyiw"] as $k) - unset($line[$k]); + foreach (["date_entered", "guid", "last_published", "last_marked", "tag_cache", "favicon_avg_color", + "uuid", "label_cache", "yyiw"] as $k) + unset($line[$k]); array_push($reply['content'], $line); + + $this->mark_timestamp("article end"); } - } + } + + $this->mark_timestamp("end of articles"); if (!$headlines_count) { @@ -450,6 +493,8 @@ class Feeds extends Handler_Protected { } } + $this->mark_timestamp("end"); + return array($topmost_article_ids, $headlines_count, $feed, $disable_cache, $reply); } @@ -746,8 +791,10 @@ class Feeds extends Handler_Protected { function update_debugger() { header("Content-type: text/html"); + $xdebug = isset($_REQUEST["xdebug"]) ? (int)$_REQUEST["xdebug"] : 1; + Debug::set_enabled(true); - Debug::set_loglevel($_REQUEST["xdebug"]); + Debug::set_loglevel($xdebug); $feed_id = (int)$_REQUEST["feed_id"]; @$do_update = $_REQUEST["action"] == "do_update"; @@ -788,7 +835,7 @@ class Feeds extends Handler_Protected { </head> <body class="flat ttrss_utility feed_debugger css_loading"> <script type="text/javascript"> - require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox', 'dijit/form/Form', + require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox', 'dijit/form/Select', 'dijit/form/Form', 'dijit/form/Select','dijit/form/TextBox','dijit/form/ValidationTextBox'],function(parser, ready){ ready(function() { parser.parse(); @@ -802,12 +849,19 @@ class Feeds extends Handler_Protected { <form method="post" action=""> <input type="hidden" name="op" value="feeds"> <input type="hidden" name="method" value="update_debugger"> - <input type="hidden" name="xdebug" value="1"> <input type="hidden" name="csrf_token" value="<?php echo $csrf_token ?>"> <input type="hidden" name="action" value="do_update"> <input type="hidden" name="feed_id" value="<?php echo $feed_id ?>"> - <fieldset class="narrow"> + <fieldset> + <label> + <?php print_select_hash("xdebug", $xdebug, + [Debug::$LOG_VERBOSE => "LOG_VERBOSE", Debug::$LOG_EXTENDED => "LOG_EXTENDED"], + 'dojoType="dijit.form.Select"'); + ?></label> + </fieldset> + + <fieldset> <label class="checkbox"><input dojoType="dijit.form.CheckBox" type="checkbox" name="force_refetch" value="1" <?php echo $refetch_checked ?>> Force refetch</label> </fieldset> @@ -1413,7 +1467,7 @@ class Feeds extends Handler_Protected { $pdo = Db::pdo(); // WARNING: due to highly dynamic nature of this query its going to quote parameters - // right before adding them to SQL part + // right before adding them to SQL part $feed = $params["feed"]; $limit = isset($params["limit"]) ? $params["limit"] : 30; @@ -1647,6 +1701,11 @@ class Feeds extends Handler_Protected { $vfeed_query_part = $override_vfeed; } + $feed_title = ""; + $feed_site_url = ""; + $last_error = ""; + $last_updated = ""; + if ($search) { $feed_title = T_sprintf("Search results: %s", $search); } else { @@ -1684,6 +1743,8 @@ class Feeds extends Handler_Protected { $start_ts_query_part = ""; } + $first_id = 0; + if (is_numeric($feed)) { // proper override_order applied above if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) { @@ -1709,11 +1770,11 @@ class Feeds extends Handler_Protected { } else { $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id) LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)"; + $feed_check_qpart = ""; } if ($vfeed_query_part) $vfeed_query_part .= "favicon_avg_color,"; - $first_id = 0; $first_id_query_strategy_part = $query_strategy_part; if ($feed == -3) @@ -1722,20 +1783,24 @@ class Feeds extends Handler_Protected { if (DB_TYPE == "pgsql") { $sanity_interval_qpart = "date_entered >= NOW() - INTERVAL '1 hour' AND"; $yyiw_qpart = "to_char(date_entered, 'IYYY-IW') AS yyiw"; + + $distinct_columns = str_replace("desc", "", strtolower($order_by)); + $distinct_qpart = "DISTINCT ON (id, $distinct_columns)"; } else { $sanity_interval_qpart = "date_entered >= DATE_SUB(NOW(), INTERVAL 1 hour) AND"; $yyiw_qpart = "date_format(date_entered, '%Y-%u') AS yyiw"; + $distinct_qpart = "DISTINCT"; //fallback } if (!$search && !$skip_first_id_check) { // if previous topmost article id changed that means our current pagination is no longer valid - $query = "SELECT DISTINCT - ttrss_feeds.title, + $query = "SELECT + ttrss_entries.id, date_entered, - $yyiw_qpart, + $yyiw_qpart, guid, - ttrss_entries.id, ttrss_entries.title, + ttrss_feeds.title, updated, score, marked, @@ -1754,9 +1819,9 @@ class Feeds extends Handler_Protected { $sanity_interval_qpart $first_id_query_strategy_part ORDER BY $order_by LIMIT 1"; - /*if ($_REQUEST["debug"]) { - print $query; - }*/ + if ($_REQUEST["debug"]) { + print "\n*** FIRST ID QUERY ***\n$query\n"; + } $res = $pdo->query($query); @@ -1769,11 +1834,12 @@ class Feeds extends Handler_Protected { } } - $query = "SELECT DISTINCT + $query = "SELECT $distinct_qpart + ttrss_entries.id AS id, date_entered, - $yyiw_qpart, + $yyiw_qpart, guid, - ttrss_entries.id,ttrss_entries.title, + ttrss_entries.title, updated, label_cache, tag_cache, @@ -1805,12 +1871,23 @@ class Feeds extends Handler_Protected { //if ($_REQUEST["debug"]) print $query; + if ($_REQUEST["debug"]) { + print "\n*** HEADLINES QUERY ***\n$query\n"; + } + $res = $pdo->query($query); } else { // browsing by tag - $query = "SELECT DISTINCT + if (DB_TYPE == "pgsql") { + $distinct_columns = str_replace("desc", "", strtolower($order_by)); + $distinct_qpart = "DISTINCT ON (id, $distinct_columns)"; + } else { + $distinct_qpart = "DISTINCT"; //fallback + } + + $query = "SELECT $distinct_qpart date_entered, guid, note, @@ -1849,7 +1926,11 @@ class Feeds extends Handler_Protected { $query_strategy_part ORDER BY $order_by $limit_query_part $offset_query_part"; - if ($_REQUEST["debug"]) print $query; + //if ($_REQUEST["debug"]) print $query; + + if ($_REQUEST["debug"]) { + print "\n*** TAGS QUERY ***\n$query\n"; + } $res = $pdo->query($query); } @@ -2023,12 +2104,10 @@ class Feeds extends Handler_Protected { /** * Purge a feed old posts. * - * @param mixed $link A database connection. * @param mixed $feed_id The id of the purged feed. * @param mixed $purge_interval Olderness of purged posts. - * @param boolean $debug Set to True to enable the debug. False by default. * @access public - * @return void + * @return mixed */ static function purge_feed($feed_id, $purge_interval) { @@ -2036,63 +2115,68 @@ class Feeds extends Handler_Protected { $pdo = Db::pdo(); + $owner_uid = false; + $rows_deleted = 0; + $sth = $pdo->prepare("SELECT owner_uid FROM ttrss_feeds WHERE id = ?"); $sth->execute([$feed_id]); - $owner_uid = false; - if ($row = $sth->fetch()) { $owner_uid = $row["owner_uid"]; - } - if ($purge_interval == -1 || !$purge_interval) { - return; - } + if (FORCE_ARTICLE_PURGE != 0) { + Debug::log("purge_feed: FORCE_ARTICLE_PURGE is set, overriding interval to " . FORCE_ARTICLE_PURGE, Debug::$LOG_VERBOSE); + $purge_unread = true; + $purge_interval = FORCE_ARTICLE_PURGE; + } else { + $purge_unread = get_pref("PURGE_UNREAD_ARTICLES", $owner_uid, false); + } - if (!$owner_uid) return; + $purge_interval = (int) $purge_interval; - if (FORCE_ARTICLE_PURGE == 0) { - $purge_unread = get_pref("PURGE_UNREAD_ARTICLES", - $owner_uid, false); - } else { - $purge_unread = true; - $purge_interval = FORCE_ARTICLE_PURGE; - } + Debug::log("purge_feed: interval $purge_interval days for feed $feed_id, owner: $owner_uid, purge unread: $purge_unread", Debug::$LOG_VERBOSE); - if (!$purge_unread) - $query_limit = " unread = false AND "; - else - $query_limit = ""; + if ($purge_interval <= 0) { + Debug::log("purge_feed: purging disabled for this feed, nothing to do.", Debug::$LOG_VERBOSE); + return; + } - $purge_interval = (int) $purge_interval; + if (!$purge_unread) + $query_limit = " unread = false AND "; + else + $query_limit = ""; - if (DB_TYPE == "pgsql") { - $sth = $pdo->prepare("DELETE FROM ttrss_user_entries - USING ttrss_entries - WHERE ttrss_entries.id = ref_id AND - marked = false AND - feed_id = ? AND - $query_limit - ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'"); - $sth->execute([$feed_id]); + if (DB_TYPE == "pgsql") { + $sth = $pdo->prepare("DELETE FROM ttrss_user_entries + USING ttrss_entries + WHERE ttrss_entries.id = ref_id AND + marked = false AND + feed_id = ? AND + $query_limit + ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'"); + $sth->execute([$feed_id]); - } else { - $sth = $pdo->prepare("DELETE FROM ttrss_user_entries - USING ttrss_user_entries, ttrss_entries - WHERE ttrss_entries.id = ref_id AND - marked = false AND - feed_id = ? AND - $query_limit - ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); - $sth->execute([$feed_id]); + } else { + $sth = $pdo->prepare("DELETE FROM ttrss_user_entries + USING ttrss_user_entries, ttrss_entries + WHERE ttrss_entries.id = ref_id AND + marked = false AND + feed_id = ? AND + $query_limit + ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); + $sth->execute([$feed_id]); - } + } + + $rows_deleted = $sth->rowCount(); - $rows = $sth->rowCount(); + Debug::log("purge_feed: deleted $rows_deleted articles.", Debug::$LOG_VERBOSE); - Debug::log("Purged feed $feed_id ($purge_interval): deleted $rows articles"); + } else { + Debug::log("purge_feed: owner of $feed_id not found", Debug::$LOG_VERBOSE); + } - return $rows; + return $rows_deleted; } static function feed_purge_interval($feed_id) { @@ -2107,11 +2191,10 @@ class Feeds extends Handler_Protected { $purge_interval = $row["purge_interval"]; $owner_uid = $row["owner_uid"]; - if ($purge_interval == 0) $purge_interval = get_pref( - 'PURGE_OLD_DAYS', $owner_uid); + if ($purge_interval == 0) + $purge_interval = get_pref('PURGE_OLD_DAYS', $owner_uid, false); return $purge_interval; - } else { return -1; } @@ -2310,5 +2393,25 @@ class Feeds extends Handler_Protected { return [$query, $skip_first_id]; } + + function mark_timestamp($label) { + + if (!$_REQUEST['timestamps']) + return; + + + if (!$this->viewfeed_timestamp) $this->viewfeed_timestamp = hrtime(true); + if (!$this->viewfeed_timestamp_last) $this->viewfeed_timestamp_last = hrtime(true); + + $timestamp = hrtime(true); + + printf("[%4d ms, %4d abs] %s\n", + ($timestamp - $this->viewfeed_timestamp_last) / 1e6, + ($timestamp - $this->viewfeed_timestamp) / 1e6, + $label); + + $this->viewfeed_timestamp_last = $timestamp; + } + } diff --git a/classes/handler/public.php b/classes/handler/public.php index 4bd9c06f9..86a82cc61 100755 --- a/classes/handler/public.php +++ b/classes/handler/public.php @@ -714,7 +714,7 @@ class Handler_Public extends Handler { if (!isset($_SESSION["login_error_msg"])) $_SESSION["login_error_msg"] = __("Incorrect username or password"); - user_error("Failed login attempt for $login from {$_SERVER['REMOTE_ADDR']}", E_USER_WARNING); + user_error("Failed login attempt for $login from " . UserHelper::get_user_ip(), E_USER_WARNING); } $return = clean($_REQUEST['return']); diff --git a/classes/logger/sql.php b/classes/logger/sql.php index 1b44b1e5f..c1ea16ef9 100755 --- a/classes/logger/sql.php +++ b/classes/logger/sql.php @@ -16,7 +16,10 @@ class Logger_SQL { $context = mb_substr($context, 0, 8192); $server_params = [ - "IP" => "REMOTE_ADDR", + "Real IP" => "HTTP_X_REAL_IP", + "Forwarded For" => "HTTP_X_FORWARDED_FOR", + "Forwarded Protocol" => "HTTP_X_FORWARDED_PROTO", + "Remote IP" => "REMOTE_ADDR", "Request URI" => "REQUEST_URI", "User agent" => "HTTP_USER_AGENT", ]; diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php index 8b9099007..e1e88ddc0 100755 --- a/classes/pref/feeds.php +++ b/classes/pref/feeds.php @@ -74,7 +74,7 @@ class Pref_Feeds extends Handler_Protected { $cat['items'] = $this->get_category_items($line['id']); $num_children = $this->calculate_children_count($cat); - $cat['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', (int) $num_children), $num_children); + $cat['param'] = sprintf(_ngettext('(%d feed)', '(%d feeds)', (int) $num_children), $num_children); if ($num_children > 0 || $show_empty_cats) array_push($items, $cat); @@ -126,6 +126,7 @@ class Pref_Feeds extends Handler_Protected { $root['id'] = 'root'; $root['name'] = __('Feeds'); $root['items'] = array(); + $root['param'] = 0; $root['type'] = 'category'; $enable_cats = get_pref('ENABLE_FEED_CATS'); @@ -229,7 +230,7 @@ class Pref_Feeds extends Handler_Protected { $cat['items'] = $this->get_category_items($line['id']); $num_children = $this->calculate_children_count($cat); - $cat['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', (int) $num_children), $num_children); + $cat['param'] = sprintf(_ngettext('(%d feed)', '(%d feeds)', (int) $num_children), $num_children); if ($num_children > 0 || $show_empty_cats) array_push($root['items'], $cat); @@ -277,13 +278,13 @@ class Pref_Feeds extends Handler_Protected { array_push($cat['items'], $feed); } - $cat['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', count($cat['items'])), count($cat['items'])); + $cat['param'] = sprintf(_ngettext('(%d feed)', '(%d feeds)', count($cat['items'])), count($cat['items'])); if (count($cat['items']) > 0 || $show_empty_cats) array_push($root['items'], $cat); $num_children = $this->calculate_children_count($root); - $root['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', (int) $num_children), $num_children); + $root['param'] = sprintf(_ngettext('(%d feed)', '(%d feeds)', (int) $num_children), $num_children); } else { $fsth = $this->pdo->prepare("SELECT id, title, last_error, @@ -312,7 +313,7 @@ class Pref_Feeds extends Handler_Protected { array_push($root['items'], $feed); } - $root['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', count($root['items'])), count($root['items'])); + $root['param'] = sprintf(_ngettext('(%d feed)', '(%d feeds)', count($root['items'])), count($root['items'])); } $fl = array(); @@ -509,7 +510,6 @@ class Pref_Feeds extends Handler_Protected { global $purge_intervals; global $update_intervals; - $feed_id = clean($_REQUEST["id"]); $sth = $this->pdo->prepare("SELECT * FROM ttrss_feeds WHERE id = ? AND @@ -620,7 +620,10 @@ class Pref_Feeds extends Handler_Protected { print "<label>".__("Interval:")."</label> "; - print_select_hash("update_interval", $update_interval, $update_intervals, + $local_update_intervals = $update_intervals; + $local_update_intervals[0] .= sprintf(" (%s)", $update_intervals[get_pref("DEFAULT_UPDATE_INTERVAL")]); + + print_select_hash("update_interval", $update_interval, $local_update_intervals, 'dojoType="fox.form.Select"'); print "</fieldset>"; @@ -633,7 +636,21 @@ class Pref_Feeds extends Handler_Protected { print "<label>" . __('Article purging:') . "</label> "; - print_select_hash("purge_interval", $purge_interval, $purge_intervals, + if (FORCE_ARTICLE_PURGE == 0) { + $local_purge_intervals = $purge_intervals; + $default_purge_interval = get_pref("PURGE_OLD_DAYS"); + + if ($default_purge_interval > 0) + $local_purge_intervals[0] .= " " . T_nsprintf('(%d day)', '(%d days)', $default_purge_interval, $default_purge_interval); + else + $local_purge_intervals[0] .= " " . sprintf("(%s)", __("Disabled")); + + } else { + $purge_interval = FORCE_ARTICLE_PURGE; + $local_purge_intervals = [ T_nsprintf('%d day', '%d days', $purge_interval, $purge_interval) ]; + } + + print_select_hash("purge_interval", $purge_interval, $local_purge_intervals, 'dojoType="fox.form.Select" ' . ((FORCE_ARTICLE_PURGE == 0) ? "" : 'disabled="1"')); @@ -859,7 +876,10 @@ class Pref_Feeds extends Handler_Protected { print "<label>".__("Interval:")."</label> "; - print_select_hash("update_interval", "", $update_intervals, + $local_update_intervals = $update_intervals; + $local_update_intervals[0] .= sprintf(" (%s)", $update_intervals[get_pref("DEFAULT_UPDATE_INTERVAL")]); + + print_select_hash("update_interval", "", $local_update_intervals, 'disabled="1" dojoType="fox.form.Select"'); $this->batch_edit_cbox("update_interval"); @@ -874,7 +894,15 @@ class Pref_Feeds extends Handler_Protected { print "<label>" . __('Article purging:') . "</label> "; - print_select_hash("purge_interval", "", $purge_intervals, + $local_purge_intervals = $purge_intervals; + $default_purge_interval = get_pref("PURGE_OLD_DAYS"); + + if ($default_purge_interval > 0) + $local_purge_intervals[0] .= " " . T_sprintf("(%d days)", $default_purge_interval); + else + $local_purge_intervals[0] .= " " . sprintf("(%s)", __("Disabled")); + + print_select_hash("purge_interval", "", $local_purge_intervals, 'disabled="1" dojoType="fox.form.Select"'); $this->batch_edit_cbox("purge_interval"); @@ -1187,10 +1215,11 @@ class Pref_Feeds extends Handler_Protected { } if ($num_errors > 0) { - $error_button = "<button dojoType=\"dijit.form.Button\" onclick=\"CommonDialogs.showFeedsWithErrors()\" id=\"errorButton\">" . __("Feeds with errors") . "</button>"; + } else { + $error_button = ""; } $inactive_button = "<button dojoType=\"dijit.form.Button\" diff --git a/classes/pref/filters.php b/classes/pref/filters.php index 1113f251e..70b7d0326 100755 --- a/classes/pref/filters.php +++ b/classes/pref/filters.php @@ -552,7 +552,7 @@ class Pref_Filters extends Handler_Protected { return "<span class='filterRule $inverse'>" . T_sprintf("%s on %s in %s %s", htmlspecialchars($rule["reg_exp"]), - $filter_type, $feed, isset($rule["inverse"]) ? __("(inverse)") : "") . "</span>"; + "<span class='field'>$filter_type</span>", "<span class='feed'>$feed</span>", isset($rule["inverse"]) ? __("(inverse)") : "") . "</span>"; } function printRuleName() { @@ -736,10 +736,8 @@ class Pref_Filters extends Handler_Protected { } function index() { - - $filter_search = clean($_REQUEST["search"]); - if (array_key_exists("search", $_REQUEST)) { + $filter_search = clean($_REQUEST["search"]); $_SESSION["prefs_filter_search"] = $filter_search; } else { $filter_search = $_SESSION["prefs_filter_search"]; @@ -749,12 +747,6 @@ class Pref_Filters extends Handler_Protected { print "<div style='padding : 0px' dojoType='dijit.layout.ContentPane' region='top'>"; print "<div dojoType='fox.Toolbar'>"; - if (array_key_exists("search", $_REQUEST)) { - $_SESSION["prefs_filter_search"] = $filter_search; - } else { - $filter_search = $_SESSION["prefs_filter_search"]; - } - print "<div style='float : right; padding-right : 4px;'> <input dojoType=\"dijit.form.TextBox\" id=\"filter_search\" size=\"20\" type=\"search\" value=\"$filter_search\"> diff --git a/classes/pref/prefs.php b/classes/pref/prefs.php index d7b486cbb..bce3c171b 100644 --- a/classes/pref/prefs.php +++ b/classes/pref/prefs.php @@ -66,9 +66,13 @@ class Pref_Prefs extends Handler_Protected { ] ]; + $this->pref_help_bottom = [ + "BLACKLISTED_TAGS" => __("Never apply these tags automatically (comma-separated list)."), + ]; + $this->pref_help = [ "ALLOW_DUPLICATE_POSTS" => array(__("Allow duplicate articles"), ""), - "BLACKLISTED_TAGS" => array(__("Blacklisted tags"), __("Never apply these tags automatically (comma-separated list).")), + "BLACKLISTED_TAGS" => array(__("Blacklisted tags"), ""), "DEFAULT_SEARCH_LANGUAGE" => array(__("Default language"), __("Used for full-text search")), "CDM_AUTO_CATCHUP" => array(__("Mark read on scroll"), __("Mark articles as read as you scroll past them")), "CDM_EXPANDED" => array(__("Always expand articles")), @@ -192,6 +196,12 @@ class Pref_Prefs extends Handler_Protected { case 'USER_CSS_THEME': if (!$need_reload) $need_reload = get_pref($pref_name) != $value; break; + + case 'BLACKLISTED_TAGS': + $cats = FeedItem_Common::normalize_categories(explode(",", $value)); + asort($cats); + $value = implode(", ", $cats); + break; } set_pref($pref_name, $value); @@ -671,6 +681,19 @@ class Pref_Prefs extends Handler_Protected { $timezones = explode("\n", file_get_contents("lib/timezones.txt")); print_select($pref_name, $value, $timezones, 'dojoType="dijit.form.FilteringSelect"'); + + } else if ($pref_name == "BLACKLISTED_TAGS") { # TODO: other possible <textarea> prefs go here + + print "<div>"; + + print "<textarea dojoType='dijit.form.SimpleTextarea' rows='4' + style='width: 500px; font-size : 12px;' + name='$pref_name'>$value</textarea><br/>"; + + print "<div class='help-text-bottom text-muted'>" . $this->pref_help_bottom[$pref_name] . "</div>"; + + print "</div>"; + } else if ($pref_name == "USER_CSS_THEME") { $themes = array_merge(glob("themes/*.php"), glob("themes/*.css"), glob("themes.local/*.css")); @@ -725,8 +748,8 @@ class Pref_Prefs extends Handler_Protected { print "<input type='checkbox' name='$pref_name' $checked $disabled dojoType='dijit.form.CheckBox' id='CB_$pref_name' value='1'>"; - } else if (array_search($pref_name, array('FRESH_ARTICLE_MAX_AGE', - 'PURGE_OLD_DAYS', 'LONG_DATE_FORMAT', 'SHORT_DATE_FORMAT')) !== false) { + } else if (in_array($pref_name, ['FRESH_ARTICLE_MAX_AGE', + 'PURGE_OLD_DAYS', 'LONG_DATE_FORMAT', 'SHORT_DATE_FORMAT'])) { $regexp = ($type_name == 'integer') ? 'regexp="^\d*$"' : ''; @@ -1271,14 +1294,14 @@ class Pref_Prefs extends Handler_Protected { } private function getShortDesc($pref_name) { - if (isset($this->pref_help[$pref_name])) { + if (isset($this->pref_help[$pref_name][0])) { return $this->pref_help[$pref_name][0]; } return ""; } private function getHelpText($pref_name) { - if (isset($this->pref_help[$pref_name])) { + if (isset($this->pref_help[$pref_name][1])) { return $this->pref_help[$pref_name][1]; } return ""; diff --git a/classes/pref/system.php b/classes/pref/system.php index 7e9aa44a1..89052c6e3 100644 --- a/classes/pref/system.php +++ b/classes/pref/system.php @@ -25,27 +25,43 @@ class Pref_System extends Handler_Protected { function index() { - print "<div dojoType=\"dijit.layout.AccordionContainer\" region=\"center\">"; - print "<div dojoType=\"dijit.layout.AccordionPane\" - title=\"<i class='material-icons'>report</i> ".__('Event Log')."\">"; + $severity = isset($_REQUEST["severity"]) ? (int) clean($_REQUEST["severity"]) : E_USER_WARNING; + + print "<div dojoType='dijit.layout.AccordionContainer' region='center'>"; + print "<div dojoType='dijit.layout.AccordionPane' style='padding : 0' + title='<i class=\"material-icons\">report</i> ".__('Event Log')."'>"; if (LOG_DESTINATION == "sql") { - $res = $this->pdo->query("SELECT errno, errstr, filename, lineno, - created_at, login, context FROM ttrss_error_log - LEFT JOIN ttrss_users ON (owner_uid = ttrss_users.id) - ORDER BY ttrss_error_log.id DESC - LIMIT 100"); + print "<div dojoType='dijit.layout.BorderContainer' gutters='false'>"; + + print "<div region='top' dojoType='fox.Toolbar'>"; + + print "<button dojoType='dijit.form.Button' + onclick='Helpers.updateEventLog()'>".__('Refresh')."</button>"; + + print "<button dojoType='dijit.form.Button' + onclick='Helpers.clearEventLog()'>".__('Clear')."</button>"; - print "<button dojoType=\"dijit.form.Button\" - onclick=\"Helpers.updateEventLog()\">".__('Refresh')."</button> "; + print "<div class='pull-right'>"; - print " <button dojoType=\"dijit.form.Button\" - class=\"alt-danger\" onclick=\"Helpers.clearEventLog()\">".__('Clear')."</button> "; + print __("Severity:") . " "; + print_select_hash("severity", $severity, + [ + E_USER_ERROR => __("Errors"), + E_USER_WARNING => __("Warnings"), + E_USER_NOTICE => __("Everything") + ], 'dojoType="fox.form.Select" onchange="Helpers.updateEventLog()"'); - print "<p><table width=\"100%\" cellspacing=\"10\" class=\"prefErrorLog\">"; + print "</div>"; # pull-right - print "<tr class=\"title\"> + print "</div>"; # toolbar + + print '<div style="padding : 0px" dojoType="dijit.layout.ContentPane" region="center">'; + + print "<table width='100%' cellspacing='10' class='prefErrorLog'>"; + + print "<tr class='title'> <td width='5%'>".__("Error")."</td> <td>".__("Filename")."</td> <td>".__("Message")."</td> @@ -53,7 +69,37 @@ class Pref_System extends Handler_Protected { <td width='5%'>".__("Date")."</td> </tr>"; - while ($line = $res->fetch()) { + $errno_values = []; + + switch ($severity) { + case E_USER_ERROR: + $errno_values = [ E_ERROR, E_USER_ERROR, E_PARSE ]; + break; + case E_USER_WARNING: + $errno_values = [ E_ERROR, E_USER_ERROR, E_PARSE, E_WARNING, E_USER_WARNING, E_DEPRECATED, E_USER_DEPRECATED ]; + break; + } + + if (count($errno_values) > 0) { + $errno_qmarks = arr_qmarks($errno_values); + $errno_filter_qpart = "errno IN ($errno_qmarks)"; + } else { + $errno_filter_qpart = "true"; + } + + $sth = $this->pdo->prepare("SELECT + errno, errstr, filename, lineno, created_at, login, context + FROM + ttrss_error_log LEFT JOIN ttrss_users ON (owner_uid = ttrss_users.id) + WHERE + $errno_filter_qpart + ORDER BY + ttrss_error_log.id DESC + LIMIT 100"); + + $sth->execute($errno_values); + + while ($line = $sth->fetch()) { print "<tr>"; foreach ($line as $k => $v) { @@ -73,15 +119,15 @@ class Pref_System extends Handler_Protected { print "</table>"; } else { - print_notice("Please set LOG_DESTINATION to 'sql' in config.php to enable database logging."); - } - print "</div>"; + print "</div>"; # content pane + print "</div>"; # container + print "</div>"; # accordion pane - print "<div dojoType=\"dijit.layout.AccordionPane\" - title=\"<i class='material-icons'>info</i> ".__('PHP Information')."\">"; + print "<div dojoType='dijit.layout.AccordionPane' + title='<i class=\"material-icons\">info</i> ".__('PHP Information')."'>"; ob_start(); phpinfo(); @@ -92,7 +138,7 @@ class Pref_System extends Handler_Protected { print preg_replace( '%^.*<body>(.*)</body>.*$%ms','$1', $info); print "</div>"; - print "</div>"; + print "</div>"; # accordion pane PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB, "hook_prefs_tab", "prefSystem"); diff --git a/classes/rssutils.php b/classes/rssutils.php index 3954f76dc..857bc2948 100755 --- a/classes/rssutils.php +++ b/classes/rssutils.php @@ -187,11 +187,11 @@ class RSSUtils { // -1 can be caused by a SIGCHLD handler which daemon master process installs (not every setup, apparently) if ($exit_code != 0 && $exit_code != -1) { - $esth = $pdo->prepare("SELECT last_error FROM ttrss_feeds WHERE id = ?"); - $esth->execute([$tline["id"]]); + $festh = $pdo->prepare("SELECT last_error FROM ttrss_feeds WHERE id = ?"); + $festh->execute([$tline["id"]]); - if ($erow = $esth->fetch()) { - $error_message = $erow["last_error"]; + if ($ferow = $festh->fetch()) { + $error_message = $ferow["last_error"]; } else { $error_message = "N/A"; } @@ -201,6 +201,13 @@ class RSSUtils { Logger::get()->log(E_USER_NOTICE, sprintf("Update process for feed %d (%s, owner UID: %d) failed with exit code: %d (%s).", $tline["id"], clean($tline["title"]), $tline["owner_uid"], $exit_code, clean($error_message))); + + $combined_error_message = sprintf("Update process failed with exit code: %d (%s)", + $exit_code, clean($error_message)); + + # mark failed feed as having an update error (unless it is already marked) + $fusth = $pdo->prepare("UPDATE ttrss_feeds SET last_error = ? WHERE id = ? AND last_error = ''"); + $fusth->execute([$combined_error_message, $tline["id"]]); } } else { @@ -857,7 +864,7 @@ class RSSUtils { if (Debug::get_loglevel() >= Debug::$LOG_EXTENDED) { Debug::log("matched filters: ", Debug::$LOG_VERBOSE); - if (count($matched_filters != 0)) { + if (count($matched_filters) != 0) { print_r($matched_filters); } @@ -1167,7 +1174,7 @@ class RSSUtils { foreach ($article_filters as $f) { if ($f["type"] == "tag") { - $manual_tags = trim_array(explode(",", $f["param"])); + $manual_tags = array_map('trim', explode(",", mb_strtolower($f["param"]))); foreach ($manual_tags as $tag) { array_push($entry_tags, $tag); @@ -1177,28 +1184,19 @@ class RSSUtils { // Skip boring tags - $boring_tags = trim_array(explode(",", mb_strtolower(get_pref( - 'BLACKLISTED_TAGS', $owner_uid, ''), 'utf-8'))); - - $filtered_tags = array(); - $tags_to_cache = array(); - - foreach ($entry_tags as $tag) { - if (array_search($tag, $boring_tags) === false) { - array_push($filtered_tags, $tag); - } - } + $boring_tags = array_map('trim', + explode(",", mb_strtolower( + get_pref('BLACKLISTED_TAGS', $owner_uid)))); - $filtered_tags = array_unique($filtered_tags); + $entry_tags = FeedItem_Common::normalize_categories( + array_unique( + array_diff($entry_tags, $boring_tags))); - if (Debug::get_loglevel() >= Debug::$LOG_VERBOSE) { - Debug::log("filtered tags: " . implode(", ", $filtered_tags), Debug::$LOG_VERBOSE); - - } + Debug::log("filtered tags: " . implode(", ", $entry_tags), Debug::$LOG_VERBOSE); // Save article tags in the database - if (count($filtered_tags) > 0) { + if (count($entry_tags) > 0) { $tsth = $pdo->prepare("SELECT id FROM ttrss_tags WHERE tag_name = ? AND post_int_id = ? AND @@ -1208,25 +1206,25 @@ class RSSUtils { (owner_uid,tag_name,post_int_id) VALUES (?, ?, ?)"); - $filtered_tags = FeedItem_Common::normalize_categories($filtered_tags); - - foreach ($filtered_tags as $tag) { + foreach ($entry_tags as $tag) { $tsth->execute([$tag, $entry_int_id, $owner_uid]); if (!$tsth->fetch()) { $usth->execute([$owner_uid, $tag, $entry_int_id]); } - - array_push($tags_to_cache, $tag); } /* update the cache */ - $tags_str = join(",", $tags_to_cache); $tsth = $pdo->prepare("UPDATE ttrss_user_entries SET tag_cache = ? WHERE ref_id = ? AND owner_uid = ?"); - $tsth->execute([$tags_str, $entry_ref_id, $owner_uid]); + + $tsth->execute([ + join(",", $entry_tags), + $entry_ref_id, + $owner_uid + ]); } Debug::log("article processed", Debug::$LOG_VERBOSE); diff --git a/classes/urlhelper.php b/classes/urlhelper.php index d7b7d004a..fec36de51 100644 --- a/classes/urlhelper.php +++ b/classes/urlhelper.php @@ -1,8 +1,9 @@ <?php class UrlHelper { static function build_url($parts) { - $tmp = $parts['scheme'] . "://" . $parts['host'] . $parts['path']; + $tmp = $parts['scheme'] . "://" . $parts['host']; + if (isset($parts['path'])) $tmp .= $parts['path']; if (isset($parts['query'])) $tmp .= '?' . $parts['query']; if (isset($parts['fragment'])) $tmp .= '#' . $parts['fragment']; @@ -35,11 +36,13 @@ class UrlHelper { $rel_parts['host'] = $parts['host']; $rel_parts['scheme'] = $parts['scheme']; - if (strpos($rel_parts['path'], '/') !== 0) - $rel_parts['path'] = '/' . $rel_parts['path']; + if (isset($rel_parts['path'])) { + if (strpos($rel_parts['path'], '/') !== 0) + $rel_parts['path'] = '/' . $rel_parts['path']; - $rel_parts['path'] = str_replace("/./", "/", $rel_parts['path']); - $rel_parts['path'] = str_replace("//", "/", $rel_parts['path']); + $rel_parts['path'] = str_replace("/./", "/", $rel_parts['path']); + $rel_parts['path'] = str_replace("//", "/", $rel_parts['path']); + } return self::validate(self::build_url($rel_parts)); } @@ -67,7 +70,11 @@ class UrlHelper { //convert IDNA hostname to punycode if possible if (function_exists("idn_to_ascii")) { if (mb_detect_encoding($tokens['host']) != 'ASCII') { - $tokens['host'] = idn_to_ascii($tokens['host']); + if (defined('IDNA_NONTRANSITIONAL_TO_ASCII') && defined('INTL_IDNA_VARIANT_UTS46')) { + $tokens['host'] = idn_to_ascii($tokens['host'], IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + } else { + $tokens['host'] = idn_to_ascii($tokens['host']); + } } } diff --git a/classes/userhelper.php b/classes/userhelper.php index fd0b0ac57..6a80aed2b 100644 --- a/classes/userhelper.php +++ b/classes/userhelper.php @@ -38,7 +38,7 @@ class UserHelper { $usth = $pdo->prepare("UPDATE ttrss_users SET last_login = NOW() WHERE id = ?"); $usth->execute([$user_id]); - $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"]; + $_SESSION["ip_address"] = UserHelper::get_user_ip(); $_SESSION["user_agent"] = sha1($_SERVER['HTTP_USER_AGENT']); $_SESSION["pwd_hash"] = $row["pwd_hash"]; @@ -63,7 +63,7 @@ class UserHelper { if (!$_SESSION["csrf_token"]) $_SESSION["csrf_token"] = bin2hex(get_random_bytes(16)); - $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"]; + $_SESSION["ip_address"] = UserHelper::get_user_ip(); Pref_Prefs::initialize_user_prefs($_SESSION["uid"]); @@ -138,4 +138,11 @@ class UserHelper { } + static function get_user_ip() { + foreach (["HTTP_X_REAL_IP", "REMOTE_ADDR"] as $hdr) { + if (isset($_SERVER[$hdr])) + return $_SERVER[$hdr]; + } + } + } |