diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php index 97c529d07..2c275349b 100755 --- a/classes/pref/feeds.php +++ b/classes/pref/feeds.php @@ -464,8 +464,10 @@ class Pref_Feeds extends Handler_Protected { if (is_uploaded_file($_FILES['icon_file']['tmp_name'])) { $tmp_file = tempnam(CACHE_DIR . '/upload', 'icon'); - $result = move_uploaded_file($_FILES['icon_file']['tmp_name'], - $tmp_file); + if (!$tmp_file) + return; + + $result = move_uploaded_file($_FILES['icon_file']['tmp_name'], $tmp_file); if (!$result) { return; @@ -478,7 +480,7 @@ class Pref_Feeds extends Handler_Protected { $feed_id = clean($_REQUEST["feed_id"]); $rc = 2; // failed - if (is_file($icon_file) && $feed_id) { + if ($icon_file && is_file($icon_file) && $feed_id) { if (filesize($icon_file) < 65535) { $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds @@ -486,8 +488,12 @@ class Pref_Feeds extends Handler_Protected { $sth->execute([$feed_id, $_SESSION['uid']]); if ($row = $sth->fetch()) { - @unlink(ICONS_DIR . "/$feed_id.ico"); - if (rename($icon_file, ICONS_DIR . "/$feed_id.ico")) { + $new_filename = ICONS_DIR . "/$feed_id.ico"; + + if (file_exists($new_filename)) unlink($new_filename); + + if (rename($icon_file, $new_filename)) { + chmod($new_filename, 644); $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET favicon_avg_color = '' @@ -502,7 +508,9 @@ class Pref_Feeds extends Handler_Protected { } } - if (is_file($icon_file)) @unlink($icon_file); + if ($icon_file && is_file($icon_file)) { + unlink($icon_file); + } print $rc; return; @@ -512,12 +520,62 @@ class Pref_Feeds extends Handler_Protected { global $purge_intervals; global $update_intervals; - $feed_id = clean($_REQUEST["id"]); + $feed_id = (int)clean($_REQUEST["id"]); $sth = $this->pdo->prepare("SELECT * FROM ttrss_feeds WHERE id = ? AND owner_uid = ?"); $sth->execute([$feed_id, $_SESSION['uid']]); + if ($row = $sth->fetch(PDO::FETCH_ASSOC)) { + + ob_start(); + PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_EDIT_FEED, $feed_id); + $plugin_data = trim((string)ob_get_contents()); + ob_end_clean(); + + $row["icon"] = Feeds::_get_icon($feed_id); + + $local_update_intervals = $update_intervals; + $local_update_intervals[0] .= sprintf(" (%s)", $update_intervals[get_pref("DEFAULT_UPDATE_INTERVAL")]); + + 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 json_encode([ + "feed" => $row, + "cats" => [ + "enabled" => get_pref('ENABLE_FEED_CATS'), + "select" => \Controls\select_feeds_cats("cat_id", $row["cat_id"]), + ], + "plugin_data" => $plugin_data, + "force_purge" => (int)FORCE_ARTICLE_PURGE, + "intervals" => [ + "update" => $local_update_intervals, + "purge" => $local_purge_intervals, + ], + "lang" => [ + "enabled" => DB_TYPE == "pgsql", + "default" => get_pref('DEFAULT_SEARCH_LANGUAGE'), + "all" => $this::get_ts_languages(), + ] + ]); + } else { + print json_encode(["error" => "FEED_NOT_FOUND"]); + } + + return; + if ($row = $sth->fetch()) { print '
'; diff --git a/js/App.js b/js/App.js index 764003ca9..a3618409a 100644 --- a/js/App.js +++ b/js/App.js @@ -343,16 +343,20 @@ const App = { }); }, // htmlspecialchars()-alike for headlines data-content attribute - escapeHtml: function(text) { - const map = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; + escapeHtml: function(p) { + if (typeof p == "string") { + const map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; - return text.replace(/[&<>"']/g, function(m) { return map[m]; }); + return p.replace(/[&<>"']/g, function(m) { return map[m]; }); + } else { + return p; + } }, displayIfChecked: function(checkbox, elemId) { if (checkbox.checked) { diff --git a/js/CommonDialogs.js b/js/CommonDialogs.js index a75b36ed8..5477e9ecd 100644 --- a/js/CommonDialogs.js +++ b/js/CommonDialogs.js @@ -389,19 +389,20 @@ const CommonDialogs = { return false; }, - editFeed: function (feed) { - if (feed <= 0) + editFeed: function (feed_id) { + if (feed_id <= 0) return alert(__("You can't edit this kind of feed.")); - const query = {op: "pref-feeds", method: "editfeed", id: feed}; + const query = {op: "pref-feeds", method: "editfeed", id: feed_id}; console.log("editFeed", query); const dialog = new fox.SingleUseDialog({ id: "feedEditDlg", title: __("Edit Feed"), - unsubscribeFeed: function(feed_id, title) { - if (confirm(__("Unsubscribe from %s?").replace("%s", title))) { + feed_title: "", + unsubscribe: function() { + if (confirm(__("Unsubscribe from %s?").replace("%s", this.feed_title))) { dialog.hide(); CommonDialogs.unsubscribeFeed(feed_id); } @@ -430,8 +431,212 @@ const CommonDialogs = { const tmph = dojo.connect(dialog, 'onShow', function () { dojo.disconnect(tmph); - xhr.post("backend.php", {op: "pref-feeds", method: "editfeed", id: feed}, (reply) => { - dialog.attr('content', reply); + xhr.json("backend.php", {op: "pref-feeds", method: "editfeed", id: feed_id}, (reply) => { + const feed = reply.feed; + + // for unsub prompt + dialog.feed_title = feed.title; + + dialog.attr('content', + ` +
+
+ + ${App.FormFields.hidden_tag("id", feed_id)} + ${App.FormFields.hidden_tag("op", "pref-feeds")} + ${App.FormFields.hidden_tag("method", "editSave")} + +
+
+ +
+ +
+ + + + ${feed.last_error ? + `error + ` : ""} +
+ + ${reply.cats.enabled ? + ` +
+ + ${reply.cats.select} +
+ ` : ""} + +
+ + +
+ + ${reply.lang.enabled ? + ` +
+ + ${App.FormFields.select_tag("feed_language", feed.feed_language, reply.lang.all)} +
+ ` : ""} + +
+ +
+ + ${App.FormFields.select_hash("update_interval", feed.update_interval, reply.intervals.update)} +
+
+ + + ${App.FormFields.select_hash("purge_interval", + feed.purge_interval, + reply.intervals.purge, + reply.force_purge ? {disabled: 1} : {})} + +
+
+
+
+
+
+ + +
+
+ + +
+
+
+
+ +
+ + $include_in_digest = $row["include_in_digest"]; + + if ($include_in_digest) { + $checked = "checked="1" + } else { + $checked = " + } + +
+ + + +
+ + $always_display_enclosures = $row["always_display_enclosures"]; + + if ($always_display_enclosures) { + $checked = "checked + } else { + $checked = " + } + +
+ + + +
+ + $hide_images = $row["hide_images"]; + + if ($hide_images) { + $checked = "checked="1" + } else { + $checked = " + } + +
+ + + +
+ + $cache_images = $row["cache_images"]; + + if ($cache_images) { + $checked = "checked="1" + } else { + $checked = " + } + +
+ + + +
+ + $mark_unread_on_update = $row["mark_unread_on_update"]; + + if ($mark_unread_on_update) { + $checked = "checked + } else { + $checked = " + } + +
+ + + +
+ +
+ +
+ + + +
+ + + ${App.FormFields.hidden_tag("op", "pref-feeds")} + ${App.FormFields.hidden_tag("feed_id", feed_id)} + ${App.FormFields.hidden_tag("method", "uploadIcon")} + ${App.FormFields.hidden_tag("csrf_token", App.getInitParam("csrf_token"))} + + ${App.FormFields.submit_tag(__("Replace"), {onclick: "return CommonDialogs.uploadFeedIcon()"})} + ${App.FormFields.submit_tag(__("Remove"), {class: "alt-danger", onclick: "return CommonDialogs.removeFeedIcon("+feed_id+")"})} +
+
+ +
+ ${reply.plugin_data} +
+ +
+ + + `); }) });