summaryrefslogtreecommitdiff
path: root/classes
diff options
context:
space:
mode:
Diffstat (limited to 'classes')
-rwxr-xr-xclasses/api.php8
-rwxr-xr-xclasses/article.php38
-rw-r--r--classes/diskcache.php2
-rwxr-xr-xclasses/feeditem/common.php3
-rwxr-xr-xclasses/feeds.php323
-rwxr-xr-xclasses/handler/public.php2
-rwxr-xr-xclasses/logger/sql.php5
-rwxr-xr-xclasses/pref/feeds.php51
-rwxr-xr-xclasses/pref/filters.php12
-rw-r--r--classes/pref/prefs.php33
-rw-r--r--classes/pref/system.php88
-rwxr-xr-xclasses/rssutils.php58
-rw-r--r--classes/urlhelper.php19
-rw-r--r--classes/userhelper.php11
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 "&nbsp;<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];
+ }
+ }
+
}