summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md27
-rwxr-xr-xclasses/article.php2
-rw-r--r--classes/diskcache.php2
-rwxr-xr-xclasses/feeds.php78
-rwxr-xr-xclasses/handler/public.php19
-rwxr-xr-xclasses/pluginhost.php12
-rwxr-xr-xclasses/pref/feeds.php53
-rw-r--r--classes/pref/prefs.php2
-rwxr-xr-xclasses/rpc.php175
-rwxr-xr-xclasses/rssutils.php15
-rw-r--r--classes/userhelper.php4
-rw-r--r--js/Article.js7
-rw-r--r--js/CommonDialogs.js125
-rwxr-xr-xjs/Headlines.js61
-rw-r--r--plugins/af_psql_trgm/init.php78
-rwxr-xr-xplugins/af_redditimgur/init.php573
-rw-r--r--plugins/scored_oldest_first/init.php35
-rw-r--r--themes/compact.css6
-rw-r--r--themes/compact_night.css32
-rw-r--r--themes/light.css6
-rw-r--r--themes/light/tt-rss.less1
-rw-r--r--themes/light/utility.less4
-rw-r--r--themes/light/zoom.less1
-rw-r--r--themes/night.css32
-rw-r--r--themes/night_base.less23
-rw-r--r--themes/night_blue.css32
26 files changed, 608 insertions, 797 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2e97cffbb..091e735a0 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,33 +1,20 @@
## Contributing code the right way
-*(or: how I learned to post merge requests without crying myself to sleep)*
+TLDR: it works like Github.
-New user accounts on Gogs are not allowed to fork repositories because people
-use development accounts to spam. To get initial fork access, do the following:
+1. Register on the [development website](https://git.tt-rss.org);
+2. Fork the repository you're interested in;
+3. Do the needful;
+4. File a PR against master branch;
-1. Register on the forums and on [Gogs](https://git.tt-rss.org);
-2. Create a thread describing your proposed changes in [Development subforum](https://community.tt-rss.org/c/tiny-tiny-rss/development)
-while including your Gogs username;
-3. You'll be given proper access and will be able to fork repositories and file PRs, etc;
-
-If you already have a fully functional Gogs account it works pretty much like Github:
-
-1. Fork the repository you're interested in;
-2. Do the needful;
-3. File a pull request with your changes against master branch;
-
-That's it. If you have any other questions, see this [forum thread](https://discourse.tt-rss.org/t/how-to-contribute-code-via-pull-requests-on-git-tt-rss-org/1850).
-
-If you don't want to deal with the above, you can also clone one of the repositories
-locally, do the needful, and post resulting patches on the [forums](https://community.tt-rss.org/c/tiny-tiny-rss/development).
+If you have any other questions, see this [forum thread](https://discourse.tt-rss.org/t/how-to-contribute-code-via-pull-requests-on-git-tt-rss-org/1850).
Please don't inline patches in forum posts, attach files instead (``.patch`` or ``.diff`` file
extensions should work).
## Contributing translations
-Believe it or not, people also spam using Weblate. Therefore, there's some minor
-jumping through hoops involved here:
+Believe it or not, people also spam using Weblate. Therefore, some minor jumping through hoops is involved here:
1. Register on [Weblate](https://weblate.tt-rss.org/) / forums;
2. Post in the [Weblate discussion thread](https://community.tt-rss.org/t/easier-translations-with-weblate/1680) on the forum, ask to be added to a project
diff --git a/classes/article.php b/classes/article.php
index 5527b7253..3a58f4576 100755
--- a/classes/article.php
+++ b/classes/article.php
@@ -29,7 +29,7 @@ class Article extends Handler_Protected {
if (!$content) {
$pluginhost = new PluginHost();
$pluginhost->load_all(PluginHost::KIND_ALL, $owner_uid);
- $pluginhost->load_data();
+ //$pluginhost->load_data();
foreach ($pluginhost->get_hooks(PluginHost::HOOK_GET_FULL_TEXT) as $p) {
$extracted_content = $p->hook_get_full_text($url);
diff --git a/classes/diskcache.php b/classes/diskcache.php
index c56dc6f14..daa171bf6 100644
--- a/classes/diskcache.php
+++ b/classes/diskcache.php
@@ -395,7 +395,7 @@ class DiskCache {
$tmppluginhost = new PluginHost();
$tmppluginhost->load(PLUGINS, PluginHost::KIND_SYSTEM);
- $tmppluginhost->load_data();
+ //$tmppluginhost->load_data();
foreach ($tmppluginhost->get_hooks(PluginHost::HOOK_SEND_LOCAL_FILE) as $plugin) {
if ($plugin->hook_send_local_file($filename)) return true;
diff --git a/classes/feeds.php b/classes/feeds.php
index 744c463af..2015f2435 100755
--- a/classes/feeds.php
+++ b/classes/feeds.php
@@ -61,54 +61,42 @@ class Feeds extends Handler_Protected {
$reply .= "<span class=\"right\">";
$reply .= "<span id='selected_prompt'></span>";
$reply .= "&nbsp;";
- $reply .= "<select dojoType=\"fox.form.Select\"
- onchange=\"Headlines.onActionChanged(this)\">";
- $reply .= "<option value=\"0\" disabled='1'>".__('Select...')."</option>";
-
- $reply .= "<option value=\"Headlines.select('all')\">".__('All')."</option>";
- $reply .= "<option value=\"Headlines.select('unread')\">".__('Unread')."</option>";
- $reply .= "<option value=\"Headlines.select('invert')\">".__('Invert')."</option>";
- $reply .= "<option value=\"Headlines.select('none')\">".__('None')."</option>";
-
- $reply .= "<option value=\"0\" disabled=\"1\">".__('Selection toggle:')."</option>";
-
- $reply .= "<option value=\"Headlines.selectionToggleUnread()\">".__('Unread')."</option>
- <option value=\"Headlines.selectionToggleMarked()\">".__('Starred')."</option>
- <option value=\"Headlines.selectionTogglePublished()\">".__('Published')."</option>";
-
- $reply .= "<option value=\"0\" disabled=\"1\">".__('Selection:')."</option>";
-
- $reply .= "<option value=\"Headlines.catchupSelection()\">".__('Mark as read')."</option>";
- $reply .= "<option value=\"Article.selectionSetScore()\">".__('Set score')."</option>";
-
- if ($feed_id == 0 && !$is_cat) {
- $reply .= "<option value=\"Headlines.archiveSelection()\">".__('Move back')."</option>";
- $reply .= "<option value=\"Headlines.deleteSelection()\">".__('Delete')."</option>";
- } else {
- $reply .= "<option value=\"Headlines.archiveSelection()\">".__('Archive')."</option>";
- }
+ $reply .= "<div dojoType='fox.form.DropDownButton' title='".__('Select articles')."'>
+ <span>".__("Select...")."</span>
+ <div dojoType='dijit.Menu' style='display: none;'>
+ <div dojoType='dijit.MenuItem' onclick='Headlines.select(\"all\")'>".__('All')."</div>
+ <div dojoType='dijit.MenuItem' onclick='Headlines.select(\"unread\")'>".__('Unread')."</div>
+ <div dojoType='dijit.MenuItem' onclick='Headlines.select(\"invert\")'>".__('Invert')."</div>
+ <div dojoType='dijit.MenuItem' onclick='Headlines.select(\"none\")'>".__('None')."</div>
+ <div dojoType='dijit.MenuSeparator'></div>
+ <div dojoType='dijit.MenuItem' onclick='Headlines.selectionToggleUnread()'>".__('Toggle unread')."</div>
+ <div dojoType='dijit.MenuItem' onclick='Headlines.selectionToggleMarked()'>".__('Toggle starred')."</div>
+ <div dojoType='dijit.MenuItem' onclick='Headlines.selectionTogglePublished()'>".__('Toggle published')."</div>
+ <div dojoType='dijit.MenuSeparator'></div>
+ <div dojoType='dijit.MenuItem' onclick='Headlines.catchupSelection()'>".__('Mark as read')."</div>
+ <div dojoType='dijit.MenuItem' onclick='Article.selectionSetScore()'>".__('Set score')."</div>";
if (PluginHost::getInstance()->get_plugin("mail")) {
- $reply .= "<option value=\"Plugins.Mail.send()\">".__('Forward by email').
- "</option>";
+ $reply .= "<div dojoType='dijit.MenuItem' value='Plugins.Mail.send()'>".__('Forward by email')."</div>";
}
if (PluginHost::getInstance()->get_plugin("mailto")) {
- $reply .= "<option value=\"Plugins.Mailto.send()\">".__('Forward by email').
- "</option>";
+ $reply .= "<div dojoType='dijit.MenuItem' value='Plugins.Mailto.send()'>".__('Forward by email')."</div>";
}
- $reply .= "<option value=\"0\" disabled=\"1\">".__('Feed:')."</option>";
+ foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HEADLINE_TOOLBAR_SELECT_MENU_ITEM) as $p) {
+ $reply .= $p->hook_headline_toolbar_select_menu_item($feed_id, $is_cat);
+ }
- //$reply .= "<option value=\"catchupPage()\">".__('Mark as read')."</option>";
-
- $reply .= "<option value=\"App.displayDlg('".__("Show as feed")."','generatedFeed', '$feed_id:$is_cat:$rss_link')\">".
- __('Show as feed')."</option>";
+ if ($feed_id == 0 && !$is_cat) {
+ $reply .= "<div dojoType='dijit.MenuSeparator'></div>
+ <div dojoType='dijit.MenuItem' class='text-error' onclick='Headlines.deleteSelection()'>".__('Delete permanently')."</div>";
+ }
- $reply .= "</select>";
+ $reply .= "</div>"; /* menu */
- //$reply .= "</h2";
+ $reply .= "</div>"; /* dropdown */
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HEADLINE_TOOLBAR_BUTTON) as $p) {
$reply .= $p->hook_headline_toolbar_button($feed_id, $is_cat);
@@ -362,19 +350,6 @@ class Feeds extends Handler_Protected {
$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);
@@ -1852,7 +1827,7 @@ class Feeds extends Handler_Protected {
uuid,
lang,
hide_images,
- unread,feed_id,marked,published,link,last_read,orig_feed_id,
+ unread,feed_id,marked,published,link,last_read,
last_marked, last_published,
$vfeed_query_part
$content_query_part
@@ -1896,7 +1871,6 @@ class Feeds extends Handler_Protected {
updated,
unread,
feed_id,
- orig_feed_id,
marked,
published,
num_comments,
diff --git a/classes/handler/public.php b/classes/handler/public.php
index 86a82cc61..fdf55b1d2 100755
--- a/classes/handler/public.php
+++ b/classes/handler/public.php
@@ -45,7 +45,7 @@ class Handler_Public extends Handler {
$tmppluginhost = new PluginHost();
$tmppluginhost->load(PLUGINS, PluginHost::KIND_ALL);
$tmppluginhost->load($user_plugins, PluginHost::KIND_USER, $owner_uid);
- $tmppluginhost->load_data();
+ //$tmppluginhost->load_data();
$handler = $tmppluginhost->get_feed_handler(
PluginHost::feed_to_pfeed_id($feed));
@@ -82,13 +82,14 @@ class Handler_Public extends Handler {
while ($line = $result->fetch()) {
$line["content_preview"] = Sanitizer::sanitize(truncate_string(strip_tags($line["content"]), 100, '...'));
+ $line["tags"] = Article::get_article_tags($line["id"], $owner_uid);
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
$line = $p->hook_query_headlines($line);
}
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_EXPORT_FEED) as $p) {
- $line = $p->hook_article_export_feed($line, $feed, $is_cat);
+ $line = $p->hook_article_export_feed($line, $feed, $is_cat, $owner_uid);
}
$tpl->setVariable('ARTICLE_ID',
@@ -121,9 +122,7 @@ class Handler_Public extends Handler {
$tpl->setVariable('ARTICLE_SOURCE_LINK', htmlspecialchars($line['site_url'] ? $line["site_url"] : get_self_url_prefix()), true);
$tpl->setVariable('ARTICLE_SOURCE_TITLE', htmlspecialchars($line['feed_title'] ? $line['feed_title'] : $feed_title), true);
- $tags = Article::get_article_tags($line["id"], $owner_uid);
-
- foreach ($tags as $tag) {
+ foreach ($line["tags"] as $tag) {
$tpl->setVariable('ARTICLE_CATEGORY', htmlspecialchars($tag), true);
$tpl->addBlock('category');
}
@@ -181,6 +180,7 @@ class Handler_Public extends Handler {
while ($line = $result->fetch()) {
$line["content_preview"] = Sanitizer::sanitize(truncate_string(strip_tags($line["content_preview"]), 100, '...'));
+ $line["tags"] = Article::get_article_tags($line["id"], $owner_uid);
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
$line = $p->hook_query_headlines($line, 100);
@@ -202,12 +202,10 @@ class Handler_Public extends Handler {
if ($line['note']) $article['note'] = $line['note'];
if ($article['author']) $article['author'] = $line['author'];
- $tags = Article::get_article_tags($line["id"], $owner_uid);
-
- if (count($tags) > 0) {
+ if (count($line["tags"]) > 0) {
$article['tags'] = array();
- foreach ($tags as $tag) {
+ foreach ($line["tags"] as $tag) {
array_push($article['tags'], $tag);
}
}
@@ -330,7 +328,6 @@ class Handler_Public extends Handler {
tag_cache,
author,
guid,
- orig_feed_id,
note
FROM ttrss_entries,ttrss_user_entries
WHERE id = ? AND ref_id = id AND owner_uid = ?");
@@ -1248,7 +1245,7 @@ class Handler_Public extends Handler {
$method = clean($_REQUEST["pmethod"]);
$host->load($plugin_name, PluginHost::KIND_USER, 0);
- $host->load_data();
+ //$host->load_data();
$plugin = $host->get_plugin($plugin_name);
diff --git a/classes/pluginhost.php b/classes/pluginhost.php
index 3ff658918..08871af51 100755
--- a/classes/pluginhost.php
+++ b/classes/pluginhost.php
@@ -14,6 +14,7 @@ class PluginHost {
private $plugin_actions = array();
private $owner_uid;
private $last_registered;
+ private $data_loaded;
private static $instance;
const API_VERSION = 2;
@@ -68,6 +69,7 @@ class PluginHost {
const HOOK_ENCLOSURE_IMPORTED = 45;
const HOOK_HEADLINES_CUSTOM_SORT_MAP = 46;
const HOOK_HEADLINES_CUSTOM_SORT_OVERRIDE = 47;
+ const HOOK_HEADLINE_TOOLBAR_SELECT_MENU_ITEM = 48;
const KIND_ALL = 1;
const KIND_SYSTEM = 2;
@@ -268,6 +270,8 @@ class PluginHost {
}
}
}
+
+ $this->load_data();
}
function is_system($plugin) {
@@ -352,8 +356,8 @@ class PluginHost {
}
}
- function load_data() {
- if ($this->owner_uid) {
+ private function load_data() {
+ if ($this->owner_uid && !$this->data_loaded && get_schema_version() > 100) {
$sth = $this->pdo->prepare("SELECT name, content FROM ttrss_plugin_storage
WHERE owner_uid = ?");
$sth->execute([$this->owner_uid]);
@@ -361,6 +365,8 @@ class PluginHost {
while ($line = $sth->fetch()) {
$this->storage[$line["name"]] = unserialize($line["content"]);
}
+
+ $this->data_loaded = true;
}
}
@@ -411,6 +417,8 @@ class PluginHost {
function get($sender, $name, $default_value = false) {
$idx = get_class($sender);
+ $this->load_data();
+
if (isset($this->storage[$idx][$name])) {
return $this->storage[$idx][$name];
} else {
diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php
index e1e88ddc0..88c5b7f0e 100755
--- a/classes/pref/feeds.php
+++ b/classes/pref/feeds.php
@@ -1607,54 +1607,23 @@ class Pref_Feeds extends Handler_Protected {
/* save starred articles in Archived feed */
- /* prepare feed if necessary */
+ $sth = $pdo->prepare("UPDATE ttrss_user_entries SET
+ feed_id = NULL, orig_feed_id = NULL
+ WHERE feed_id = ? AND marked = true AND owner_uid = ?");
- $sth = $pdo->prepare("SELECT feed_url FROM ttrss_feeds WHERE id = ?
- AND owner_uid = ?");
$sth->execute([$id, $owner_uid]);
- if ($row = $sth->fetch()) {
- $feed_url = $row["feed_url"];
+ /* Remove access key for the feed */
- $sth = $pdo->prepare("SELECT id FROM ttrss_archived_feeds
- WHERE feed_url = ? AND owner_uid = ?");
- $sth->execute([$feed_url, $owner_uid]);
-
- if ($row = $sth->fetch()) {
- $archive_id = $row["id"];
- } else {
- $res = $pdo->query("SELECT MAX(id) AS id FROM ttrss_archived_feeds");
- $row = $res->fetch();
-
- $new_feed_id = (int)$row['id'] + 1;
-
- $sth = $pdo->prepare("INSERT INTO ttrss_archived_feeds
- (id, owner_uid, title, feed_url, site_url, created)
- SELECT ?, owner_uid, title, feed_url, site_url, NOW() from ttrss_feeds
- WHERE id = ?");
- $sth->execute([$new_feed_id, $id]);
-
- $archive_id = $new_feed_id;
- }
-
- $sth = $pdo->prepare("UPDATE ttrss_user_entries SET feed_id = NULL,
- orig_feed_id = ? WHERE feed_id = ? AND
- marked = true AND owner_uid = ?");
-
- $sth->execute([$archive_id, $id, $owner_uid]);
-
- /* Remove access key for the feed */
-
- $sth = $pdo->prepare("DELETE FROM ttrss_access_keys WHERE
- feed_id = ? AND owner_uid = ?");
- $sth->execute([$id, $owner_uid]);
+ $sth = $pdo->prepare("DELETE FROM ttrss_access_keys WHERE
+ feed_id = ? AND owner_uid = ?");
+ $sth->execute([$id, $owner_uid]);
- /* remove the feed */
+ /* remove the feed */
- $sth = $pdo->prepare("DELETE FROM ttrss_feeds
- WHERE id = ? AND owner_uid = ?");
- $sth->execute([$id, $owner_uid]);
- }
+ $sth = $pdo->prepare("DELETE FROM ttrss_feeds
+ WHERE id = ? AND owner_uid = ?");
+ $sth->execute([$id, $owner_uid]);
$pdo->commit();
diff --git a/classes/pref/prefs.php b/classes/pref/prefs.php
index bce3c171b..55a15efb8 100644
--- a/classes/pref/prefs.php
+++ b/classes/pref/prefs.php
@@ -911,7 +911,7 @@ class Pref_Prefs extends Handler_Protected {
$tmppluginhost = new PluginHost();
$tmppluginhost->load_all($tmppluginhost::KIND_ALL, $_SESSION["uid"], true);
- $tmppluginhost->load_data(true);
+ //$tmppluginhost->load_data(true);
foreach ($tmppluginhost->get_plugins() as $name => $plugin) {
$about = $plugin->about();
diff --git a/classes/rpc.php b/classes/rpc.php
index 6b41a51b8..0e881b3ce 100755
--- a/classes/rpc.php
+++ b/classes/rpc.php
@@ -80,20 +80,6 @@ class RPC extends Handler_Protected {
}
}
- // Silent
- function remarchive() {
- $ids = explode(",", clean($_REQUEST["ids"]));
-
- $sth = $this->pdo->prepare("DELETE FROM ttrss_archived_feeds WHERE
- (SELECT COUNT(*) FROM ttrss_user_entries
- WHERE orig_feed_id = :id) = 0 AND
- id = :id AND owner_uid = :uid");
-
- foreach ($ids as $id) {
- $sth->execute([":id" => $id, ":uid" => $_SESSION['uid']]);
- }
- }
-
function addfeed() {
$feed = clean($_REQUEST['feed']);
$cat = clean($_REQUEST['cat']);
@@ -150,113 +136,6 @@ class RPC extends Handler_Protected {
print json_encode(array("message" => "UPDATE_COUNTERS"));
}
- function unarchive() {
- $ids = explode(",", clean($_REQUEST["ids"]));
-
- foreach ($ids as $id) {
- $this->pdo->beginTransaction();
-
- $sth = $this->pdo->prepare("SELECT feed_url,site_url,title FROM ttrss_archived_feeds
- WHERE id = (SELECT orig_feed_id FROM ttrss_user_entries WHERE ref_id = :id
- AND owner_uid = :uid) AND owner_uid = :uid");
- $sth->execute([":uid" => $_SESSION['uid'], ":id" => $id]);
-
- if ($row = $sth->fetch()) {
- $feed_url = $row['feed_url'];
- $site_url = $row['site_url'];
- $title = $row['title'];
-
- $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE feed_url = ?
- AND owner_uid = ?");
- $sth->execute([$feed_url, $_SESSION['uid']]);
-
- if ($row = $sth->fetch()) {
- $feed_id = $row["id"];
- } else {
- if (!$title) $title = '[Unknown]';
-
- $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds
- (owner_uid,feed_url,site_url,title,cat_id,auth_login,auth_pass,update_method)
- VALUES (?, ?, ?, ?, NULL, '', '', 0)");
- $sth->execute([$_SESSION['uid'], $feed_url, $site_url, $title]);
-
- $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE feed_url = ?
- AND owner_uid = ?");
- $sth->execute([$feed_url, $_SESSION['uid']]);
-
- if ($row = $sth->fetch()) {
- $feed_id = $row['id'];
- }
- }
-
- if ($feed_id) {
- $sth = $this->pdo->prepare("UPDATE ttrss_user_entries
- SET feed_id = ?, orig_feed_id = NULL
- WHERE ref_id = ? AND owner_uid = ?");
- $sth->execute([$feed_id, $id, $_SESSION['uid']]);
- }
- }
-
- $this->pdo->commit();
- }
-
- print json_encode(array("message" => "UPDATE_COUNTERS"));
- }
-
- function archive() {
- $ids = explode(",", clean($_REQUEST["ids"]));
-
- foreach ($ids as $id) {
- $this->archive_article($id, $_SESSION["uid"]);
- }
-
- print json_encode(array("message" => "UPDATE_COUNTERS"));
- }
-
- private function archive_article($id, $owner_uid) {
- $this->pdo->beginTransaction();
-
- if (!$owner_uid) $owner_uid = $_SESSION['uid'];
-
- $sth = $this->pdo->prepare("SELECT feed_id FROM ttrss_user_entries
- WHERE ref_id = ? AND owner_uid = ?");
- $sth->execute([$id, $owner_uid]);
-
- if ($row = $sth->fetch()) {
-
- /* prepare the archived table */
-
- $feed_id = (int) $row['feed_id'];
-
- if ($feed_id) {
- $sth = $this->pdo->prepare("SELECT id FROM ttrss_archived_feeds
- WHERE id = ? AND owner_uid = ?");
- $sth->execute([$feed_id, $owner_uid]);
-
- if ($row = $sth->fetch()) {
- $new_feed_id = $row['id'];
- } else {
- $row = $this->pdo->query("SELECT MAX(id) AS id FROM ttrss_archived_feeds")->fetch();
- $new_feed_id = (int)$row['id'] + 1;
-
- $sth = $this->pdo->prepare("INSERT INTO ttrss_archived_feeds
- (id, owner_uid, title, feed_url, site_url, created)
- SELECT ?, owner_uid, title, feed_url, site_url, NOW() from ttrss_feeds
- WHERE id = ?");
-
- $sth->execute([$new_feed_id, $feed_id]);
- }
-
- $sth = $this->pdo->prepare("UPDATE ttrss_user_entries
- SET orig_feed_id = ?, feed_id = NULL
- WHERE ref_id = ? AND owner_uid = ?");
- $sth->execute([$new_feed_id, $id, $owner_uid]);
- }
- }
-
- $this->pdo->commit();
- }
-
function publ() {
$pub = clean($_REQUEST["pub"]);
$id = clean($_REQUEST["id"]);
@@ -347,60 +226,6 @@ class RPC extends Handler_Protected {
print "</ul>";
}
- // Silent
- function massSubscribe() {
-
- $payload = json_decode(clean($_REQUEST["payload"]), false);
- $mode = clean($_REQUEST["mode"]);
-
- if (!$payload || !is_array($payload)) return;
-
- if ($mode == 1) {
- foreach ($payload as $feed) {
-
- $title = $feed[0];
- $feed_url = $feed[1];
-
- $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE
- feed_url = ? AND owner_uid = ?");
- $sth->execute([$feed_url, $_SESSION['uid']]);
-
- if (!$sth->fetch()) {
- $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds
- (owner_uid,feed_url,title,cat_id,site_url)
- VALUES (?, ?, ?, NULL, '')");
-
- $sth->execute([$_SESSION['uid'], $feed_url, $title]);
- }
- }
- } else if ($mode == 2) {
- // feed archive
- foreach ($payload as $id) {
- $sth = $this->pdo->prepare("SELECT * FROM ttrss_archived_feeds
- WHERE id = ? AND owner_uid = ?");
- $sth->execute([$id, $_SESSION['uid']]);
-
- if ($row = $sth->fetch()) {
- $site_url = $row['site_url'];
- $feed_url = $row['feed_url'];
- $title = $row['title'];
-
- $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE
- feed_url = ? AND owner_uid = ?");
- $sth->execute([$feed_url, $_SESSION['uid']]);
-
- if (!$sth->fetch()) {
- $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds
- (owner_uid,feed_url,title,cat_id,site_url)
- VALUES (?, ?, ?, NULL, ?)");
-
- $sth->execute([$_SESSION['uid'], $feed_url, $title, $site_url]);
- }
- }
- }
- }
- }
-
function catchupFeed() {
$feed_id = clean($_REQUEST['feed_id']);
$is_cat = clean($_REQUEST['is_cat']) == "true";
diff --git a/classes/rssutils.php b/classes/rssutils.php
index 857bc2948..96f7b7c36 100755
--- a/classes/rssutils.php
+++ b/classes/rssutils.php
@@ -276,7 +276,7 @@ class RSSUtils {
$pluginhost->load(PLUGINS, PluginHost::KIND_ALL);
$pluginhost->load($user_plugins, PluginHost::KIND_USER, $owner_uid);
- $pluginhost->load_data();
+ //$pluginhost->load_data();
$basic_info = array();
foreach ($pluginhost->get_hooks(PluginHost::HOOK_FEED_BASIC_INFO) as $plugin) {
@@ -403,7 +403,7 @@ class RSSUtils {
$pluginhost->load(PLUGINS, PluginHost::KIND_ALL);
$pluginhost->load($user_plugins, PluginHost::KIND_USER, $owner_uid);
- $pluginhost->load_data();
+ //$pluginhost->load_data();
$rss_hash = false;
@@ -1379,18 +1379,11 @@ class RSSUtils {
}
}
+ // deprecated; table not used
static function expire_feed_archive() {
- Debug::log("Removing old archived feeds...");
-
$pdo = Db::pdo();
- if (DB_TYPE == "pgsql") {
- $pdo->query("DELETE FROM ttrss_archived_feeds
- WHERE created < NOW() - INTERVAL '1 month'");
- } else {
- $pdo->query("DELETE FROM ttrss_archived_feeds
- WHERE created < DATE_SUB(NOW(), INTERVAL 1 MONTH)");
- }
+ $pdo->query("DELETE FROM ttrss_archived_feeds");
}
static function expire_lock_files() {
diff --git a/classes/userhelper.php b/classes/userhelper.php
index 6a80aed2b..76bb338d4 100644
--- a/classes/userhelper.php
+++ b/classes/userhelper.php
@@ -80,9 +80,9 @@ class UserHelper {
$pluginhost->load($plugins, PluginHost::KIND_USER, $owner_uid);
- if (get_schema_version() > 100) {
+ /*if (get_schema_version() > 100) {
$pluginhost->load_data();
- }
+ }*/
}
}
diff --git a/js/Article.js b/js/Article.js
index 587243a01..a075e321f 100644
--- a/js/Article.js
+++ b/js/Article.js
@@ -175,11 +175,6 @@ const Article = {
return comments;
},
- formatOriginallyFrom: function(hl) {
- return hl.orig_feed ? `<span>
- ${__('Originally from:')} <a target="_blank" rel="noopener noreferrer" href="${App.escapeHtml(hl.orig_feed[1])}">${hl.orig_feed[0]}</a>
- </span>` : "";
- },
unpack: function(row) {
if (row.hasAttribute("data-content")) {
console.log("unpacking: " + row.id);
@@ -220,7 +215,6 @@ const Article = {
if (hl) {
const comments = this.formatComments(hl);
- const originally_from = this.formatOriginallyFrom(hl);
const article = `<div class="post post-${hl.id}" data-article-id="${hl.id}">
<div class="header">
@@ -243,7 +237,6 @@ const Article = {
</div>
<div id="POSTNOTE-${hl.id}">${hl.note}</div>
<div class="content" lang="${hl.lang ? hl.lang : 'en'}">
- ${originally_from}
${hl.content}
${hl.enclosures}
</div>
diff --git a/js/CommonDialogs.js b/js/CommonDialogs.js
index c59ef9b25..b344967a7 100644
--- a/js/CommonDialogs.js
+++ b/js/CommonDialogs.js
@@ -241,131 +241,6 @@ const CommonDialogs = {
dialog.show();
},
- feedBrowser: function() {
- const query = {op: "feeds", method: "feedBrowser"};
-
- if (dijit.byId("feedAddDlg"))
- dijit.byId("feedAddDlg").hide();
-
- if (dijit.byId("feedBrowserDlg"))
- dijit.byId("feedBrowserDlg").destroyRecursive();
-
- // noinspection JSUnusedGlobalSymbols
- const dialog = new dijit.Dialog({
- id: "feedBrowserDlg",
- title: __("More Feeds"),
- style: "width: 600px",
- getSelectedFeedIds: function () {
- const list = $$("#browseFeedList li[id*=FBROW]");
- const selected = [];
-
- list.each(function (child) {
- const id = child.id.replace("FBROW-", "");
-
- if (child.hasClassName('Selected')) {
- selected.push(id);
- }
- });
-
- return selected;
- },
- getSelectedFeeds: function () {
- const list = $$("#browseFeedList li.Selected");
- const selected = [];
-
- list.each(function (child) {
- const title = child.getElementsBySelector("span.fb_feedTitle")[0].innerHTML;
- const url = child.getElementsBySelector("a.fb_feedUrl")[0].href;
-
- selected.push([title, url]);
-
- });
-
- return selected;
- },
-
- subscribe: function () {
- const mode = this.attr('value').mode;
- let selected = [];
-
- if (mode == "1")
- selected = this.getSelectedFeeds();
- else
- selected = this.getSelectedFeedIds();
-
- if (selected.length > 0) {
- dijit.byId("feedBrowserDlg").hide();
-
- Notify.progress("Loading, please wait...", true);
-
- const query = {
- op: "rpc", method: "massSubscribe",
- payload: JSON.stringify(selected), mode: mode
- };
-
- xhrPost("backend.php", query, () => {
- Notify.close();
-
- if (App.isPrefs())
- dijit.byId("feedTree").reload();
- else
- Feeds.reload();
- });
-
- } else {
- alert(__("No feeds selected."));
- }
-
- },
- update: function () {
- Element.show('feed_browser_spinner');
-
- xhrPost("backend.php", dialog.attr("value"), (transport) => {
- Notify.close();
-
- Element.hide('feed_browser_spinner');
-
- const reply = JSON.parse(transport.responseText);
- const mode = reply['mode'];
-
- if ($("browseFeedList") && reply['content']) {
- $("browseFeedList").innerHTML = reply['content'];
- }
-
- dojo.parser.parse("browseFeedList");
-
- if (mode == 2) {
- Element.show(dijit.byId('feed_archive_remove').domNode);
- } else {
- Element.hide(dijit.byId('feed_archive_remove').domNode);
- }
- });
- },
- removeFromArchive: function () {
- const selected = this.getSelectedFeedIds();
-
- if (selected.length > 0) {
- if (confirm(__("Remove selected feeds from the archive? Feeds with stored articles will not be removed."))) {
- Element.show('feed_browser_spinner');
-
- const query = {op: "rpc", method: "remarchive", ids: selected.toString()};
-
- xhrPost("backend.php", query, () => {
- dialog.update();
- });
- }
- }
- },
- execute: function () {
- if (this.validate()) {
- this.subscribe();
- }
- },
- href: "backend.php?" + dojo.objectToQuery(query)
- });
-
- dialog.show();
- },
addLabel: function(select, callback) {
const caption = prompt(__("Please enter label caption:"), "");
diff --git a/js/Headlines.js b/js/Headlines.js
index b98098c33..3b4498430 100755
--- a/js/Headlines.js
+++ b/js/Headlines.js
@@ -300,6 +300,16 @@ const Headlines = {
}
}
},
+ unpackVisible: function(container) {
+ const rows = $$("#headlines-frame > div[id*=RROW][data-content].cdm");
+
+ for (let i = 0; i < rows.length; i++) {
+ if (App.Scrollable.isChildVisible(rows[i], container)) {
+ console.log('force unpacking:', rows[i].getAttribute('id'));
+ Article.unpack(rows[i]);
+ }
+ }
+ },
scrollHandler: function (/*event*/) {
try {
if (!Feeds.infscroll_disabled && !Feeds.infscroll_in_progress) {
@@ -320,6 +330,14 @@ const Headlines = {
}
}
+ if (App.isCombinedMode() && App.getInitParam("cdm_expanded")) {
+ const container = $("headlines-frame")
+
+ /* don't do anything until there was some scrolling */
+ if (container.scrollTop > 0)
+ Headlines.unpackVisible(container);
+ }
+
if (App.getInitParam("cdm_auto_catchup")) {
const rows = $$("#headlines-frame > div[id*=RROW][class*=Unread]");
@@ -420,7 +438,6 @@ const Headlines = {
row_class += App.getInitParam("cdm_expanded") ? " expanded" : " expandable";
const comments = Article.formatComments(hl);
- const originally_from = Article.formatOriginallyFrom(hl);
row = `<div class="cdm ${row_class} ${Article.getScoreClass(hl.score)}"
id="RROW-${hl.id}"
@@ -483,7 +500,6 @@ const Headlines = {
</div>
<div class="right">
- ${originally_from}
${hl.buttons}
</div>
</div>
@@ -1085,42 +1101,6 @@ const Headlines = {
}
}
},
- archiveSelection: function () {
- const rows = Headlines.getSelected();
-
- if (rows.length == 0) {
- alert(__("No articles selected."));
- return;
- }
-
- const fn = Feeds.getName(Feeds.getActive(), Feeds.activeIsCat());
- let str;
- let op;
-
- if (Feeds.getActive() != 0) {
- str = ngettext("Archive %d selected article in %s?", "Archive %d selected articles in %s?", rows.length);
- op = "archive";
- } else {
- str = ngettext("Move %d archived article back?", "Move %d archived articles back?", rows.length);
- str += " " + __("Please note that unstarred articles might get purged on next feed update.");
-
- op = "unarchive";
- }
-
- str = str.replace("%d", rows.length);
- str = str.replace("%s", fn);
-
- if (App.getInitParam("confirm_feed_catchup") && !confirm(str)) {
- return;
- }
-
- const query = {op: "rpc", method: op, ids: rows.toString()};
-
- xhrPost("backend.php", query, (transport) => {
- App.handleRpcJson(transport);
- Feeds.reloadCurrent();
- });
- },
catchupSelection: function () {
const rows = Headlines.getSelected();
@@ -1206,11 +1186,6 @@ const Headlines = {
});
}
},
- onActionChanged: function (elem) {
- // eslint-disable-next-line no-eval
- eval(elem.value);
- elem.attr('value', 'false');
- },
scrollToArticleId: function (id) {
const container = $("headlines-frame");
const row = $("RROW-" + id);
diff --git a/plugins/af_psql_trgm/init.php b/plugins/af_psql_trgm/init.php
index 20e3981ce..a38386d5b 100644
--- a/plugins/af_psql_trgm/init.php
+++ b/plugins/af_psql_trgm/init.php
@@ -3,6 +3,8 @@ class Af_Psql_Trgm extends Plugin {
/* @var PluginHost $host */
private $host;
+ private $default_similarity = 0.75;
+ private $default_min_length = 32;
function about() {
return array(1.0,
@@ -37,7 +39,6 @@ class Af_Psql_Trgm extends Plugin {
$host->add_hook($host::HOOK_PREFS_EDIT_FEED, $this);
$host->add_hook($host::HOOK_PREFS_SAVE_FEED, $this);
$host->add_hook($host::HOOK_ARTICLE_BUTTON, $this);
-
}
function get_js() {
@@ -124,7 +125,7 @@ class Af_Psql_Trgm extends Plugin {
if ($args != "prefFeeds") return;
print "<div dojoType=\"dijit.layout.AccordionPane\"
- title=\"<i class='material-icons'>extension</i> ".__('Mark similar articles as read')."\">";
+ title=\"<i class='material-icons'>extension</i> ".__('Mark similar articles as read (af_psql_trgm)')."\">";
if (DB_TYPE != "pgsql") {
print_error("Database type not supported.");
@@ -132,17 +133,14 @@ class Af_Psql_Trgm extends Plugin {
$res = $this->pdo->query("select 'similarity'::regproc");
- if (!$res->fetch()) {
+ if (!$res || !$res->fetch()) {
print_error("pg_trgm extension not found.");
}
- $similarity = $this->host->get($this, "similarity");
- $min_title_length = $this->host->get($this, "min_title_length");
+ $similarity = $this->host->get($this, "similarity", $this->default_similarity);
+ $min_title_length = $this->host->get($this, "min_title_length", $this->default_min_length);
$enable_globally = $this->host->get($this, "enable_globally");
- if (!$similarity) $similarity = '0.75';
- if (!$min_title_length) $min_title_length = '32';
-
print "<form dojoType=\"dijit.form.Form\">";
print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\">
@@ -197,10 +195,10 @@ class Af_Psql_Trgm extends Plugin {
print_button("submit", __("Save"), "class='alt-primary'");
print "</form>";
- $enabled_feeds = $this->host->get($this, "enabled_feeds");
- if (!array($enabled_feeds)) $enabled_feeds = array();
+ /* cleanup */
+ $enabled_feeds = $this->filter_unknown_feeds(
+ $this->get_stored_array("enabled_feeds"));
- $enabled_feeds = $this->filter_unknown_feeds($enabled_feeds);
$this->host->set($this, "enabled_feeds", $enabled_feeds);
if (count($enabled_feeds) > 0) {
@@ -221,14 +219,11 @@ class Af_Psql_Trgm extends Plugin {
}
function hook_prefs_edit_feed($feed_id) {
- print "<header>".__("Similarity (pg_trgm)")."</header>";
+ print "<header>".__("Similarity (af_psql_trgm)")."</header>";
print "<section>";
- $enabled_feeds = $this->host->get($this, "enabled_feeds");
- if (!array($enabled_feeds)) $enabled_feeds = array();
-
- $key = array_search($feed_id, $enabled_feeds);
- $checked = $key !== false ? "checked" : "";
+ $enabled_feeds = $this->get_stored_array("enabled_feeds");
+ $checked = in_array($feed_id, $enabled_feeds) ? "checked" : "";
print "<fieldset>";
@@ -241,8 +236,7 @@ class Af_Psql_Trgm extends Plugin {
}
function hook_prefs_save_feed($feed_id) {
- $enabled_feeds = $this->host->get($this, "enabled_feeds");
- if (!is_array($enabled_feeds)) $enabled_feeds = array();
+ $enabled_feeds = $this->get_stored_array("enabled_feeds");
$enable = checkbox_to_sql_bool($_POST["trgm_similarity_enabled"]);
$key = array_search($feed_id, $enabled_feeds);
@@ -265,29 +259,39 @@ class Af_Psql_Trgm extends Plugin {
if (DB_TYPE != "pgsql") return $article;
$res = $this->pdo->query("select 'similarity'::regproc");
- if (!$res->fetch()) return $article;
+ if (!$res || !$res->fetch()) return $article;
$enable_globally = $this->host->get($this, "enable_globally");
- if (!$enable_globally) {
- $enabled_feeds = $this->host->get($this, "enabled_feeds");
- $key = array_search($article["feed"]["id"], $enabled_feeds);
- if ($key === false) return $article;
+ if (!$enable_globally &&
+ !in_array($article["feed"]["id"],
+ $this->get_stored_array("enabled_feeds"))) {
+
+ return $article;
}
- $similarity = (float) $this->host->get($this, "similarity");
- if ($similarity < 0.01) return $article;
+ $similarity = (float) $this->host->get($this, "similarity", $this->default_similarity);
- $min_title_length = (int) $this->host->get($this, "min_title_length");
- if (mb_strlen($article["title"]) < $min_title_length) return $article;
+ if ($similarity < 0.01) {
+ Debug::log("af_psql_trgm: similarity is set too low ($similarity)", Debug::$LOG_EXTENDED);
+ return $article;
+ }
+
+ $min_title_length = (int) $this->host->get($this, "min_title_length", $this->default_min_length);
+
+ if (mb_strlen($article["title"]) < $min_title_length) {
+ Debug::log("af_psql_trgm: article title is too short (min: $min_title_length)", Debug::$LOG_EXTENDED);
+ return $article;
+ }
$owner_uid = $article["owner_uid"];
$entry_guid = $article["guid_hashed"];
$title_escaped = $article["title"];
// trgm does not return similarity=1 for completely equal strings
+ // this seems to be no longer the case (fixed in upstream?)
- $sth = $this->pdo->prepare("SELECT COUNT(id) AS nequal
+ /* $sth = $this->pdo->prepare("SELECT COUNT(id) AS nequal
FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id AND
date_entered >= NOW() - interval '3 days' AND
title = ? AND
@@ -303,7 +307,7 @@ class Af_Psql_Trgm extends Plugin {
if ($nequal != 0) {
$article["force_catchup"] = true;
return $article;
- }
+ } */
$sth = $this->pdo->prepare("SELECT MAX(SIMILARITY(title, ?)) AS ms
FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id AND
@@ -315,14 +319,15 @@ class Af_Psql_Trgm extends Plugin {
$row = $sth->fetch();
$similarity_result = $row['ms'];
- Debug::log("af_psql_trgm: similarity result: $similarity_result", Debug::$LOG_EXTENDED);
+ Debug::log("af_psql_trgm: similarity result for $title_escaped: $similarity_result", Debug::$LOG_EXTENDED);
if ($similarity_result >= $similarity) {
+ Debug::log("af_psql_trgm: marking article as read ($similarity_result >= $similarity)", Debug::$LOG_EXTENDED);
+
$article["force_catchup"] = true;
}
return $article;
-
}
function api_version() {
@@ -345,4 +350,13 @@ class Af_Psql_Trgm extends Plugin {
return $tmp;
}
+ private function get_stored_array($name) {
+ $tmp = $this->host->get($this, $name);
+
+ if (!is_array($tmp)) $tmp = [];
+
+ return $tmp;
+ }
+
+
}
diff --git a/plugins/af_redditimgur/init.php b/plugins/af_redditimgur/init.php
index 8d34bb556..6eb530e27 100755
--- a/plugins/af_redditimgur/init.php
+++ b/plugins/af_redditimgur/init.php
@@ -4,6 +4,8 @@ class Af_RedditImgur extends Plugin {
/* @var PluginHost $host */
private $host;
private $domain_blacklist = [ "github.com" ];
+ private $dump_json_data = false;
+ private $fallback_preview_urls = [];
function about() {
return array(1.0,
@@ -96,315 +98,414 @@ class Af_RedditImgur extends Plugin {
echo __("Configuration saved");
}
- /**
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
- */
- private function inline_stuff($article, &$doc, $xpath) {
+ private function process_post_media($data, $doc, $xpath, $anchor) {
+ $found = 0;
- $entries = $xpath->query('(//a[@href]|//img[@src])');
- $img_entries = $xpath->query("(//img[@src])");
+ if (is_array($data["media_metadata"])) {
+ foreach ($data["media_metadata"] as $media) {
+ $media_url = htmlspecialchars_decode($media["s"]["u"]);
- $found = false;
- //$debug = 1;
+ Debug::log("found media_metadata (gallery): $media_url", Debug::$LOG_VERBOSE);
- foreach ($entries as $entry) {
- if ($entry->hasAttribute("href") && strpos($entry->getAttribute("href"), "reddit.com") === false) {
+ if ($media_url) {
+ $this->handle_as_image($doc, $anchor, $media_url);
+ $found = 1;
+ }
+ }
+ }
- if ($this->is_blacklisted($entry->getAttribute("href")))
- continue;
+ // v.redd.it - see below
+ /* if (is_array($data["media"])) {
+ foreach ($data["media"] as $media) {
+ if (isset($media["fallback_url"])) {
+ $stream_url = $media["fallback_url"];
- Debug::log("processing href: " . $entry->getAttribute("href"), Debug::$LOG_VERBOSE);
+ if (isset($data["preview"]["images"][0]["source"]))
+ $poster_url = $data["preview"]["images"][0]["source"]["url"];
+ else
+ $poster_url = "";
- $matches = array();
+ Debug::log("found stream fallback_url: $stream_url / poster $poster_url", Debug::$LOG_VERBOSE);
- if (!$found && preg_match("/^https?:\/\/twitter.com\/(.*?)\/status\/(.*)/", $entry->getAttribute("href"), $matches)) {
- Debug::log("handling as twitter: " . $matches[1] . " " . $matches[2], Debug::$LOG_VERBOSE);
+ $this->handle_as_video($doc, $anchor, $stream_url, $poster_url);
+ }
- $oembed_result = UrlHelper::fetch("https://publish.twitter.com/oembed?url=" . urlencode($entry->getAttribute("href")));
+ $found = 1;
+ }
+ } */
- if ($oembed_result) {
- $oembed_result = json_decode($oembed_result, true);
+ if (!$found && $data["post_hint"] == "hosted:video") {
+ $media_url = $data["url"];
- if ($oembed_result && isset($oembed_result["html"])) {
+ if (isset($data["preview"]["images"][0]["source"]))
+ $poster_url = htmlspecialchars_decode($data["preview"]["images"][0]["source"]["url"]);
+ else
+ $poster_url = "";
- $tmp = new DOMDocument();
- if (@$tmp->loadHTML('<?xml encoding="utf-8" ?>' . $oembed_result["html"])) {
- $p = $doc->createElement("p");
+ Debug::log("found hosted video url: $media_url / poster $poster_url, looking up fallback url...", Debug::$LOG_VERBOSE);
- $p->appendChild($doc->importNode(
- $tmp->getElementsByTagName("blockquote")->item(0), TRUE));
+ $fallback_url = $data["media"]["reddit_video"]["fallback_url"];
- $br = $doc->createElement('br');
- $entry->parentNode->insertBefore($p, $entry);
- $entry->parentNode->insertBefore($br, $entry);
+ if ($fallback_url) {
+ Debug::log("found video fallback_url: $fallback_url", Debug::$LOG_VERBOSE);
+ $this->handle_as_video($doc, $anchor, $fallback_url, $poster_url);
- $found = 1;
- }
+ $found = 1;
+ }
+ }
+
+ if (!$found && $data["post_hint"] == "video") {
+ $media_url = $data["url"];
+
+ if (isset($data["preview"]["images"][0]["source"]))
+ $poster_url = htmlspecialchars_decode($data["preview"]["images"][0]["source"]["url"]);
+ else
+ $poster_url = "";
+
+ Debug::log("found video url: $media_url / poster $poster_url", Debug::$LOG_VERBOSE);
+ $this->handle_as_video($doc, $anchor, $media_url, $poster_url);
+
+ $found = 1;
+ }
+
+ if (!$found && $data["post_hint"] == "image") {
+ $media_url = $data["url"];
+
+ Debug::log("found image url: $media_url", Debug::$LOG_VERBOSE);
+ $this->handle_as_image($doc, $anchor, $media_url);
+
+ $found = 1;
+ }
+
+ if (!$found && is_array($data["preview"]["images"])) {
+ foreach ($data["preview"]["images"] as $img) {
+ if (isset($img["source"]["url"])) {
+ $media_url = htmlspecialchars_decode($img["source"]["url"]);
+ $target_url = $data["url"];
+
+ if ($media_url) {
+ if ($data["post_hint"] == "self") {
+ Debug::log("found preview image url: $media_url (link: $target_url)", Debug::$LOG_VERBOSE);
+ $this->handle_as_image($doc, $anchor, $media_url, $target_url);
+
+ $found = 1;
+ } else { // gonna use this later if nothing is found using generic link processing
+ Debug::log("found fallback preview image url: $media_url (link: $target_url);", Debug::$LOG_VERBOSE);
+ array_push($this->fallback_preview_urls, $media_url);
}
}
}
+ }
+ }
- if (!$found && preg_match("/\.gfycat.com\/([a-z]+)?(\.[a-z]+)$/i", $entry->getAttribute("href"), $matches)) {
- $entry->setAttribute("href", "http://www.gfycat.com/".$matches[1]);
- }
+ return $found;
+ }
- if (!$found && preg_match("/https?:\/\/(www\.)?gfycat.com\/([a-z]+)$/i", $entry->getAttribute("href"), $matches)) {
+ private function inline_stuff($article, &$doc, $xpath) {
- Debug::log("Handling as Gfycat", Debug::$LOG_VERBOSE);
+ $found = false;
- $source_stream = 'https://giant.gfycat.com/' . $matches[2] . '.mp4';
- $poster_url = 'https://thumbs.gfycat.com/' . $matches[2] . '-mobile.jpg';
+ // embed before reddit <table> post layout
+ $anchor = $xpath->query('//body/*')->item(0);
- $content_type = $this->get_content_type($source_stream);
+ // deal with json-provided media content first
+ if ($article["link"] && $anchor) {
+ Debug::log("JSON: requesting from URL: " . $article["link"] . "/.json", Debug::$LOG_VERBOSE);
- if (strpos($content_type, "video/") !== false) {
- $this->handle_as_video($doc, $entry, $source_stream, $poster_url);
- $found = 1;
- }
- }
+ $tmp = UrlHelper::fetch($article["link"] . "/.json");
- if (!$found && preg_match("/https?:\/\/v\.redd\.it\/(.*)$/i", $entry->getAttribute("href"), $matches)) {
+ $this->fallback_preview_urls = [];
- Debug::log("Handling as reddit inline video", Debug::$LOG_VERBOSE);
+ if ($tmp && $anchor) {
+ $json = json_decode($tmp, true);
- $img = $img_entries->item(0);
+ if ($json) {
+ Debug::log("JSON: processing media elements...", Debug::$LOG_EXTENDED);
- if ($img) {
- $poster_url = $img->getAttribute("src");
- } else {
- $poster_url = false;
- }
+ if ($this->dump_json_data) print_r($json);
- // Get original article URL from v.redd.it redirects
- $source_article_url = $this->get_location($matches[0]);
- Debug::log("Resolved ".$matches[0]." to ".$source_article_url, Debug::$LOG_VERBOSE);
+ foreach ($json as $listing) {
+ foreach ($listing["data"]["children"] as $child) {
- $source_stream = false;
+ $data = $child["data"];
- if ($source_article_url) {
- $j = json_decode(UrlHelper::fetch($source_article_url.".json"), true);
+ if (is_array($data["crosspost_parent_list"])) {
+ Debug::log("JSON: processing child crosspost_parent_list", Debug::$LOG_EXTENDED);
+
+ foreach ($data["crosspost_parent_list"] as $parent) {
+ if ($this->process_post_media($parent, $doc, $xpath, $anchor)) {
+ $found = 1;
- if ($j) {
- foreach ($j as $listing) {
- foreach ($listing["data"]["children"] as $child) {
- if ($child["data"]["url"] == $matches[0]) {
- try {
- $source_stream = $child["data"]["media"]["reddit_video"]["fallback_url"];
- }
- catch (Exception $e) {
- }
break 2;
}
}
}
+
+ Debug::log("JSON: processing child data element...", Debug::$LOG_EXTENDED);
+
+ if (!$found && $this->process_post_media($data, $doc, $xpath, $anchor)) {
+ $found = 1;
+
+ break 2;
+ }
}
}
+ } else {
+ Debug::log("JSON: failed to parse received data.", Debug::$LOG_EXTENDED);
+ }
+ } else {
+ if (!$tmp) {
+ global $fetch_last_error;
+ Debug::log("JSON: failed to fetch post:" . $fetch_last_error, Debug::$LOG_EXTENDED);
+ }
+ }
+ } else if (!$anchor) {
+ Debug::log("JSON: anchor element not found, unable to embed", Debug::$LOG_EXTENDED);
+ }
- if (!$source_stream) {
- $source_stream = "https://v.redd.it/" . $matches[1] . "/DASH_600_K";
- }
+ if ($found) {
+ Debug::log("JSON: found media data, skipping further processing of content", Debug::$LOG_VERBOSE);
+ $this->remove_post_thumbnail($doc, $xpath);
+ return true;
+ }
- $this->handle_as_video($doc, $entry, $source_stream, $poster_url);
- $found = 1;
- }
+ $entries = $xpath->query('//a[@href]');
- if (!$found && preg_match("/https?:\/\/(www\.)?streamable.com\//i", $entry->getAttribute("href"))) {
+ foreach ($entries as $entry) {
+ $entry_href = $entry->getAttribute("href");
- Debug::log("Handling as Streamable", Debug::$LOG_VERBOSE);
+ $matches = [];
- $tmp = UrlHelper::fetch($entry->getAttribute("href"));
+ /* skip links going back to reddit (and any other blacklisted stuff) */
+ if (!$found && $this->is_blacklisted($entry_href, ["reddit.com"])) {
+ Debug::log("BODY: domain of $entry_href is blacklisted, skipping", Debug::$LOG_EXTENDED);
+ continue;
+ }
- if ($tmp) {
- $tmpdoc = new DOMDocument();
+ Debug::log("BODY: processing URL: " . $entry_href, Debug::$LOG_VERBOSE);
- if (@$tmpdoc->loadHTML($tmp)) {
- $tmpxpath = new DOMXPath($tmpdoc);
+ if (!$found && preg_match("/^https?:\/\/twitter.com\/(.*?)\/status\/(.*)/", $entry_href, $matches)) {
+ Debug::log("handling as twitter: " . $matches[1] . " " . $matches[2], Debug::$LOG_VERBOSE);
- $source_node = $tmpxpath->query("//video[contains(@class,'video-player-tag')]//source[contains(@src, '.mp4')]")->item(0);
- $poster_node = $tmpxpath->query("//video[contains(@class,'video-player-tag') and @poster]")->item(0);
+ $oembed_result = UrlHelper::fetch("https://publish.twitter.com/oembed?url=" . urlencode($entry_href));
- if ($source_node && $poster_node) {
- $source_stream = $source_node->getAttribute("src");
- $poster_url = $poster_node->getAttribute("poster");
+ if ($oembed_result) {
+ $oembed_result = json_decode($oembed_result, true);
- $this->handle_as_video($doc, $entry, $source_stream, $poster_url);
- $found = 1;
- }
+ if ($oembed_result && isset($oembed_result["html"])) {
+
+ $tmp = new DOMDocument();
+ if (@$tmp->loadHTML('<?xml encoding="utf-8" ?>' . $oembed_result["html"])) {
+ $p = $doc->createElement("p");
+
+ $p->appendChild($doc->importNode(
+ $tmp->getElementsByTagName("blockquote")->item(0), TRUE));
+
+ $br = $doc->createElement('br');
+ $entry->parentNode->insertBefore($p, $entry);
+ $entry->parentNode->insertBefore($br, $entry);
+
+ $found = 1;
}
}
}
+ }
- // imgur .gif -> .gifv
- if (!$found && preg_match("/i\.imgur\.com\/(.*?)\.gif$/i", $entry->getAttribute("href"))) {
- Debug::log("Handling as imgur gif (->gifv)", Debug::$LOG_VERBOSE);
+ if (!$found && preg_match("/\.gfycat.com\/([a-z]+)?(\.[a-z]+)$/i", $entry_href, $matches)) {
+ $entry->setAttribute("href", "http://www.gfycat.com/".$matches[1]);
+ }
- $entry->setAttribute("href",
- str_replace(".gif", ".gifv", $entry->getAttribute("href")));
- }
+ if (!$found && preg_match("/https?:\/\/(www\.)?gfycat.com\/([a-z]+)$/i", $entry_href, $matches)) {
- if (!$found && preg_match("/\.(gifv|mp4)$/i", $entry->getAttribute("href"))) {
- Debug::log("Handling as imgur gifv", Debug::$LOG_VERBOSE);
+ Debug::log("Handling as Gfycat", Debug::$LOG_VERBOSE);
- $source_stream = str_replace(".gifv", ".mp4", $entry->getAttribute("href"));
+ $source_stream = 'https://giant.gfycat.com/' . $matches[2] . '.mp4';
+ $poster_url = 'https://thumbs.gfycat.com/' . $matches[2] . '-mobile.jpg';
- if (strpos($source_stream, "imgur.com") !== false)
- $poster_url = str_replace(".mp4", "h.jpg", $source_stream);
+ $content_type = $this->get_content_type($source_stream);
+ if (strpos($content_type, "video/") !== false) {
$this->handle_as_video($doc, $entry, $source_stream, $poster_url);
-
- $found = true;
+ $found = 1;
}
+ }
- $matches = array();
- if (!$found && (preg_match("/youtube\.com\/v\/([\w-]+)/", $entry->getAttribute("href"), $matches) ||
- preg_match("/youtube\.com\/.*?[\&\?]v=([\w-]+)/", $entry->getAttribute("href"), $matches) ||
- preg_match("/youtube\.com\/watch\?v=([\w-]+)/", $entry->getAttribute("href"), $matches) ||
- preg_match("/\/\/youtu.be\/([\w-]+)/", $entry->getAttribute("href"), $matches))) {
+ // imgur .gif -> .gifv
+ if (!$found && preg_match("/i\.imgur\.com\/(.*?)\.gif$/i", $entry_href)) {
+ Debug::log("Handling as imgur gif (->gifv)", Debug::$LOG_VERBOSE);
- $vid_id = $matches[1];
+ $entry->setAttribute("href",
+ str_replace(".gif", ".gifv", $entry_href));
+ }
- Debug::log("Handling as youtube: $vid_id", Debug::$LOG_VERBOSE);
+ if (!$found && preg_match("/\.(gifv|mp4)$/i", $entry_href)) {
+ Debug::log("Handling as imgur gifv", Debug::$LOG_VERBOSE);
- $iframe = $doc->createElement("iframe");
- $iframe->setAttribute("class", "youtube-player");
- $iframe->setAttribute("type", "text/html");
- $iframe->setAttribute("width", "640");
- $iframe->setAttribute("height", "385");
- $iframe->setAttribute("src", "https://www.youtube.com/embed/$vid_id");
- $iframe->setAttribute("allowfullscreen", "1");
- $iframe->setAttribute("frameborder", "0");
+ $source_stream = str_replace(".gifv", ".mp4", $entry_href);
- $br = $doc->createElement('br');
- $entry->parentNode->insertBefore($iframe, $entry);
- $entry->parentNode->insertBefore($br, $entry);
+ if (strpos($source_stream, "imgur.com") !== false)
+ $poster_url = str_replace(".mp4", "h.jpg", $source_stream);
- $found = true;
- }
+ $this->handle_as_video($doc, $entry, $source_stream, $poster_url);
- if (!$found && (preg_match("/\.(jpg|jpeg|gif|png)(\?[0-9][0-9]*)?[$\?]/i", $entry->getAttribute("href")) ||
- mb_strpos($entry->getAttribute("href"), "i.reddituploads.com") !== false ||
- mb_strpos($this->get_content_type($entry->getAttribute("href")), "image/") !== false)) {
+ $found = true;
+ }
- Debug::log("Handling as a picture", Debug::$LOG_VERBOSE);
+ $matches = array();
+ if (!$found && (preg_match("/youtube\.com\/v\/([\w-]+)/", $entry_href, $matches) ||
+ preg_match("/youtube\.com\/.*?[\&\?]v=([\w-]+)/", $entry_href, $matches) ||
+ preg_match("/youtube\.com\/embed\/([\w-]+)/", $entry_href, $matches) ||
+ preg_match("/youtube\.com\/watch\?v=([\w-]+)/", $entry_href, $matches) ||
+ preg_match("/\/\/youtu.be\/([\w-]+)/", $entry_href, $matches))) {
- $img = $doc->createElement('img');
- $img->setAttribute("src", $entry->getAttribute("href"));
+ $vid_id = $matches[1];
- $br = $doc->createElement('br');
- $entry->parentNode->insertBefore($img, $entry);
- $entry->parentNode->insertBefore($br, $entry);
+ Debug::log("Handling as youtube: $vid_id", Debug::$LOG_VERBOSE);
- $found = true;
- }
+ $iframe = $doc->createElement("iframe");
+ $iframe->setAttribute("class", "youtube-player");
+ $iframe->setAttribute("type", "text/html");
+ $iframe->setAttribute("width", "640");
+ $iframe->setAttribute("height", "385");
+ $iframe->setAttribute("src", "https://www.youtube.com/embed/$vid_id");
+ $iframe->setAttribute("allowfullscreen", "1");
+ $iframe->setAttribute("frameborder", "0");
- // imgur via link rel="image_src" href="..."
- if (!$found && preg_match("/imgur/", $entry->getAttribute("href"))) {
+ $br = $doc->createElement('br');
+ $entry->parentNode->insertBefore($iframe, $entry);
+ $entry->parentNode->insertBefore($br, $entry);
- Debug::log("handling as imgur page/whatever", Debug::$LOG_VERBOSE);
+ $found = true;
+ }
- $content = UrlHelper::fetch(["url" => $entry->getAttribute("href"),
- "http_accept" => "text/*"]);
+ if (!$found && (preg_match("/\.(jpg|jpeg|gif|png)(\?[0-9][0-9]*)?[$\?]/i", $entry_href) ||
+ /* mb_strpos($entry_href, "i.reddituploads.com") !== false || */
+ mb_strpos($this->get_content_type($entry_href), "image/") !== false)) {
- if ($content) {
- $cdoc = new DOMDocument();
+ Debug::log("Handling as a picture", Debug::$LOG_VERBOSE);
- if (@$cdoc->loadHTML($content)) {
- $cxpath = new DOMXPath($cdoc);
+ $img = $doc->createElement('img');
+ $img->setAttribute("src", $entry_href);
- $rel_image = $cxpath->query("//link[@rel='image_src']")->item(0);
+ $br = $doc->createElement('br');
+ $entry->parentNode->insertBefore($img, $entry);
+ $entry->parentNode->insertBefore($br, $entry);
- if ($rel_image) {
+ $found = true;
+ }
- $img = $doc->createElement('img');
- $img->setAttribute("src", $rel_image->getAttribute("href"));
+ // imgur via link rel="image_src" href="..."
+ if (!$found && preg_match("/imgur/", $entry_href)) {
- $br = $doc->createElement('br');
- $entry->parentNode->insertBefore($img, $entry);
- $entry->parentNode->insertBefore($br, $entry);
+ Debug::log("handling as imgur page/whatever", Debug::$LOG_VERBOSE);
- $found = true;
- }
+ $content = UrlHelper::fetch(["url" => $entry_href,
+ "http_accept" => "text/*"]);
+
+ if ($content) {
+ $cdoc = new DOMDocument();
+
+ if (@$cdoc->loadHTML($content)) {
+ $cxpath = new DOMXPath($cdoc);
+
+ $rel_image = $cxpath->query("//link[@rel='image_src']")->item(0);
+
+ if ($rel_image) {
+
+ $img = $doc->createElement('img');
+ $img->setAttribute("src", $rel_image->getAttribute("href"));
+
+ $br = $doc->createElement('br');
+ $entry->parentNode->insertBefore($img, $entry);
+ $entry->parentNode->insertBefore($br, $entry);
+
+ $found = true;
}
}
}
+ }
- // wtf is this even
- if (!$found && preg_match("/^https?:\/\/gyazo\.com\/([^\.\/]+$)/", $entry->getAttribute("href"), $matches)) {
- $img_id = $matches[1];
+ // wtf is this even
+ if (!$found && preg_match("/^https?:\/\/gyazo\.com\/([^\.\/]+$)/", $entry_href, $matches)) {
+ $img_id = $matches[1];
- Debug::log("handling as gyazo: $img_id", Debug::$LOG_VERBOSE);
+ Debug::log("handling as gyazo: $img_id", Debug::$LOG_VERBOSE);
- $img = $doc->createElement('img');
- $img->setAttribute("src", "https://i.gyazo.com/$img_id.jpg");
+ $img = $doc->createElement('img');
+ $img->setAttribute("src", "https://i.gyazo.com/$img_id.jpg");
- $br = $doc->createElement('br');
- $entry->parentNode->insertBefore($img, $entry);
- $entry->parentNode->insertBefore($br, $entry);
+ $br = $doc->createElement('br');
+ $entry->parentNode->insertBefore($img, $entry);
+ $entry->parentNode->insertBefore($br, $entry);
- $found = true;
- }
-
- // let's try meta properties
- if (!$found) {
- Debug::log("looking for meta og:image", Debug::$LOG_VERBOSE);
+ $found = true;
+ }
- $content = UrlHelper::fetch(["url" => $entry->getAttribute("href"),
- "http_accept" => "text/*"]);
+ // let's try meta properties
+ if (!$found) {
+ Debug::log("looking for meta og:image", Debug::$LOG_VERBOSE);
- if ($content) {
- $cdoc = new DOMDocument();
+ $content = UrlHelper::fetch(["url" => $entry_href,
+ "http_accept" => "text/*"]);
- if (@$cdoc->loadHTML($content)) {
- $cxpath = new DOMXPath($cdoc);
+ if ($content) {
+ $cdoc = new DOMDocument();
- $og_image = $cxpath->query("//meta[@property='og:image']")->item(0);
- $og_video = $cxpath->query("//meta[@property='og:video']")->item(0);
+ if (@$cdoc->loadHTML($content)) {
+ $cxpath = new DOMXPath($cdoc);
- if ($og_video) {
+ $og_image = $cxpath->query("//meta[@property='og:image']")->item(0);
+ $og_video = $cxpath->query("//meta[@property='og:video']")->item(0);
- $source_stream = $og_video->getAttribute("content");
+ if ($og_video) {
- if ($source_stream) {
+ $source_stream = $og_video->getAttribute("content");
- if ($og_image) {
- $poster_url = $og_image->getAttribute("content");
- } else {
- $poster_url = false;
- }
+ if ($source_stream) {
- $this->handle_as_video($doc, $entry, $source_stream, $poster_url);
- $found = true;
+ if ($og_image) {
+ $poster_url = $og_image->getAttribute("content");
+ } else {
+ $poster_url = false;
}
- } else if ($og_image) {
+ $this->handle_as_video($doc, $entry, $source_stream, $poster_url);
+ $found = true;
+ }
- $og_src = $og_image->getAttribute("content");
+ } else if ($og_image) {
- if ($og_src) {
- $img = $doc->createElement('img');
- $img->setAttribute("src", $og_src);
+ $og_src = $og_image->getAttribute("content");
- $br = $doc->createElement('br');
- $entry->parentNode->insertBefore($img, $entry);
- $entry->parentNode->insertBefore($br, $entry);
+ if ($og_src) {
+ $img = $doc->createElement('img');
+ $img->setAttribute("src", $og_src);
- $found = true;
- }
+ $br = $doc->createElement('br');
+ $entry->parentNode->insertBefore($img, $entry);
+ $entry->parentNode->insertBefore($br, $entry);
+
+ $found = true;
}
}
}
}
-
}
+ }
- // remove tiny thumbnails
- if ($entry->hasAttribute("src")) {
- if ($entry->parentNode && $entry->parentNode->parentNode) {
- $entry->parentNode->parentNode->removeChild($entry->parentNode);
- }
+ if (!$found && $anchor && count($this->fallback_preview_urls) > 0) {
+ Debug::log("JSON: processing fallback preview urls...", Debug::$LOG_VERBOSE);
+
+ foreach ($this->fallback_preview_urls as $media_url) {
+ $this->handle_as_image($doc, $anchor, $media_url);
+
+ $found = 1;
}
}
+ if ($found)
+ $this->remove_post_thumbnail($doc, $xpath);
+
return $found;
}
@@ -470,6 +571,32 @@ class Af_RedditImgur extends Plugin {
return 2;
}
+ private function remove_post_thumbnail($doc, $xpath) {
+ $thumb = $xpath->query("//td/a/img[@src]")->item(0);
+
+ if ($thumb)
+ $thumb->parentNode->parentNode->removeChild($thumb->parentNode);
+ }
+
+ private function handle_as_image($doc, $entry, $image_url, $link_url = false) {
+ $img = $doc->createElement("img");
+ $img->setAttribute("src", $image_url);
+
+ $p = $doc->createElement("p");
+
+ if ($link_url) {
+ $a = $doc->createElement("a");
+ $a->setAttribute("href", $link_url);
+
+ $a->appendChild($img);
+ $p->appendChild($a);
+ } else {
+ $p->appendChild($img);
+ }
+
+ $entry->parentNode->insertBefore($p, $entry);
+ }
+
private function handle_as_video($doc, $entry, $source_stream, $poster_url = false) {
Debug::log("handle_as_video: $source_stream", Debug::$LOG_VERBOSE);
@@ -499,31 +626,63 @@ class Af_RedditImgur extends Plugin {
}
function testurl() {
- $url = htmlspecialchars($_REQUEST["url"]);
+
+ $url = clean($_POST["url"]);
+ $article_url = clean($_POST["article_url"]);
+
+ $this->dump_json_data = true;
+
+ if (!$url && !$article_url) {
+ header("Content-type: text/html");
+ ?>
+ <style type="text/css">
+ fieldset { border : 0; }
+ label { display : inline-block; min-width : 120px; }
+ </style>
+ <form action="backend.php?op=pluginhandler&method=testurl&plugin=af_redditimgur" method="post">
+ <fieldset>
+ <label>URL:</label>
+ <input name="url" size="100" value="<?php echo htmlspecialchars($url) ?>"></input>
+ </fieldset>
+ <fieldset>
+ <label>Article URL:</label>
+ <input name="article_url" size="100" value="<?php echo htmlspecialchars($article_url) ?>"></input>
+ </fieldset>
+ <fieldset>
+ <button type="submit">Test</button>
+ </fieldset>
+ </form>
+ <?php
+ return;
+ }
header("Content-type: text/plain");
- print "URL: $url\n";
+ Debug::set_enabled(true);
+ Debug::set_loglevel(Debug::$LOG_EXTENDED);
+
+ Debug::log("URL: $url", Debug::$LOG_VERBOSE);
$doc = new DOMDocument();
- @$doc->loadHTML("<html><body><a href=\"$url\">[link]</a></body>");
+ @$doc->loadHTML("<html><body><table><tr><td><a href=\"$url\">[link]</a></td></tr></table></body>");
$xpath = new DOMXPath($doc);
- $found = $this->inline_stuff([], $doc, $xpath);
+ $found = $this->inline_stuff(["link" => $article_url], $doc, $xpath);
- print "Inline result: $found\n";
+ Debug::log("Inline result: $found", Debug::$LOG_VERBOSE);
if (!$found) {
- print "\nReadability result:\n";
+ Debug::log("Readability result:", Debug::$LOG_VERBOSE);
$article = $this->readability([], $url, $doc, $xpath);
print_r($article);
} else {
- print "\nResulting HTML:\n";
+ Debug::log("Resulting HTML:", Debug::$LOG_VERBOSE);
print $doc->saveHTML();
}
+
}
private function get_header($url, $header, $useragent = SELF_USER_AGENT) {
@@ -588,10 +747,10 @@ class Af_RedditImgur extends Plugin {
return $article;
}
- private function is_blacklisted($src) {
+ private function is_blacklisted($src, $also_blacklist = []) {
$src_domain = parse_url($src, PHP_URL_HOST);
- foreach ($this->domain_blacklist as $domain) {
+ foreach (array_merge($this->domain_blacklist, $also_blacklist) as $domain) {
if (strstr($src_domain, $domain) !== false) {
return true;
}
diff --git a/plugins/scored_oldest_first/init.php b/plugins/scored_oldest_first/init.php
new file mode 100644
index 000000000..fc3b1f8db
--- /dev/null
+++ b/plugins/scored_oldest_first/init.php
@@ -0,0 +1,35 @@
+<?php
+class Scored_Oldest_First extends Plugin {
+
+ function init($host) {
+ $host->add_hook($host::HOOK_HEADLINES_CUSTOM_SORT_MAP, $this);
+ $host->add_hook($host::HOOK_HEADLINES_CUSTOM_SORT_OVERRIDE, $this);
+ }
+
+ function hook_headlines_custom_sort_map() {
+ return [
+ "dates_reverse_scored" => "Oldest first (with score)"
+ ];
+ }
+
+ function hook_headlines_custom_sort_override($order) {
+ if ($order == "dates_reverse_scored") {
+ return [ "score DESC, updated", true ];
+ } else {
+ return [ "", false ];
+ }
+ }
+
+ function about() {
+ return array(1.0,
+ "Consider article score while sorting by oldest first",
+ "fox",
+ false,
+ "");
+ }
+
+ function api_version() {
+ return 2;
+ }
+
+}
diff --git a/themes/compact.css b/themes/compact.css
index c2828c587..9e936befa 100644
--- a/themes/compact.css
+++ b/themes/compact.css
@@ -343,6 +343,7 @@ body.ttrss_main .dijitContentPane pre {
display: block;
max-width: 98%;
overflow: auto;
+ white-space: pre-wrap;
}
body.ttrss_main div.prefHelp {
color: #555;
@@ -1646,6 +1647,10 @@ body.ttrss_utility.installer,
body.ttrss_utility.feed_debugger {
margin: 2em;
}
+body.ttrss_utility.installer pre,
+body.ttrss_utility.feed_debugger pre {
+ white-space: pre-wrap;
+}
body.ttrss_utility.share_popup {
margin: 0;
padding: 0;
@@ -1880,6 +1885,7 @@ body.ttrss_zoom div.post div.content pre {
display: block;
max-width: 98%;
overflow: auto;
+ white-space: pre-wrap;
}
/* rules specific to compact.css */
body.ttrss_main.ttrss_index.flat #feedTree.dijitTree .dijitTreeLabel {
diff --git a/themes/compact_night.css b/themes/compact_night.css
index 6666787b0..0e56dc5cc 100644
--- a/themes/compact_night.css
+++ b/themes/compact_night.css
@@ -343,6 +343,7 @@ body.ttrss_main .dijitContentPane pre {
display: block;
max-width: 98%;
overflow: auto;
+ white-space: pre-wrap;
}
body.ttrss_main div.prefHelp {
color: #ccc;
@@ -1800,6 +1801,10 @@ body.ttrss_utility.installer,
body.ttrss_utility.feed_debugger {
margin: 2em;
}
+body.ttrss_utility.installer pre,
+body.ttrss_utility.feed_debugger pre {
+ white-space: pre-wrap;
+}
body.ttrss_utility.share_popup {
margin: 0;
padding: 0;
@@ -1871,6 +1876,7 @@ body.ttrss_zoom div.post div.content pre {
display: block;
max-width: 98%;
overflow: auto;
+ white-space: pre-wrap;
}
body.flat.ttrss_main.ttrss_prefs #main,
body.flat.ttrss_main.ttrss_prefs #footer {
@@ -1886,23 +1892,6 @@ body.flat.ttrss_main.ttrss_prefs div.prefHelp {
body.flat.ttrss_main.ttrss_prefs hr {
border-color: #666;
}
-body.flat.ttrss_main {
- /*
- .post .content img,
- .cdm .content-inner img,
- .post .content video,
- .cdm .content-inner video {
- transition : opacity 0.2s linear, filter 0.2s linear;
- }
-
- .post .content img:not(:hover),
- .cdm .content-inner img:not(:hover),
- .post .content video:not(:hover),
- .cdm .content-inner video:not(:hover) {
- opacity : 0.5;
- filter: grayscale(80%);
- } */
-}
body.flat.ttrss_main img[src*='indicator_white.gif'] {
filter: invert(1);
}
@@ -2027,6 +2016,9 @@ body.flat.ttrss_main .dijitToolbar .dijitSelect:not(.dijitHover) .dijitButtonCon
body.flat.ttrss_main .dijitToolbar .dijitSelect:not(.dijitHover) .dijitButtonNode {
background-color: #222;
}
+body.flat.ttrss_main .dijitSplitterHover {
+ background-color: rgba(255, 255, 255, 0.25);
+}
body.flat.ttrss_main .dijitCheckBox:not(.dijitChecked)::before {
color: #999999;
background: #222;
@@ -2080,6 +2072,12 @@ body.flat.ttrss_main ul#filterDlg_Actions {
background: #222;
border-color: #666;
}
+body.flat.ttrss_main .post .content img,
+body.flat.ttrss_main .cdm .content-inner img,
+body.flat.ttrss_main .post .content video,
+body.flat.ttrss_main .cdm .content-inner video {
+ filter: saturate(0.9) brightness(0.8);
+}
body.flat.ttrss_main .article-note {
background: #b87d2c;
border-color: #b87d2c;
diff --git a/themes/light.css b/themes/light.css
index d2f5aa4ee..8467c346f 100644
--- a/themes/light.css
+++ b/themes/light.css
@@ -343,6 +343,7 @@ body.ttrss_main .dijitContentPane pre {
display: block;
max-width: 98%;
overflow: auto;
+ white-space: pre-wrap;
}
body.ttrss_main div.prefHelp {
color: #555;
@@ -1646,6 +1647,10 @@ body.ttrss_utility.installer,
body.ttrss_utility.feed_debugger {
margin: 2em;
}
+body.ttrss_utility.installer pre,
+body.ttrss_utility.feed_debugger pre {
+ white-space: pre-wrap;
+}
body.ttrss_utility.share_popup {
margin: 0;
padding: 0;
@@ -1880,4 +1885,5 @@ body.ttrss_zoom div.post div.content pre {
display: block;
max-width: 98%;
overflow: auto;
+ white-space: pre-wrap;
}
diff --git a/themes/light/tt-rss.less b/themes/light/tt-rss.less
index 5a0f8542d..835585fe6 100644
--- a/themes/light/tt-rss.less
+++ b/themes/light/tt-rss.less
@@ -394,6 +394,7 @@ body.ttrss_main {
display: block;
max-width: 98%;
overflow: auto;
+ white-space: pre-wrap;
}
div.prefHelp {
diff --git a/themes/light/utility.less b/themes/light/utility.less
index 087c4ced3..1c30c77ab 100644
--- a/themes/light/utility.less
+++ b/themes/light/utility.less
@@ -101,6 +101,10 @@ body.ttrss_utility.ttrss_login {
body.ttrss_utility.installer,
body.ttrss_utility.feed_debugger {
margin : 2em;
+
+ pre {
+ white-space : pre-wrap;
+ }
}
body.ttrss_utility.share_popup {
diff --git a/themes/light/zoom.less b/themes/light/zoom.less
index e06939ac2..e599bc447 100644
--- a/themes/light/zoom.less
+++ b/themes/light/zoom.less
@@ -64,6 +64,7 @@ body.ttrss_zoom {
display : block;
max-width : 98%;
overflow : auto;
+ white-space: pre-wrap;
}
}
}
diff --git a/themes/night.css b/themes/night.css
index face01932..86e8f0a0f 100644
--- a/themes/night.css
+++ b/themes/night.css
@@ -344,6 +344,7 @@ body.ttrss_main .dijitContentPane pre {
display: block;
max-width: 98%;
overflow: auto;
+ white-space: pre-wrap;
}
body.ttrss_main div.prefHelp {
color: #ccc;
@@ -1801,6 +1802,10 @@ body.ttrss_utility.installer,
body.ttrss_utility.feed_debugger {
margin: 2em;
}
+body.ttrss_utility.installer pre,
+body.ttrss_utility.feed_debugger pre {
+ white-space: pre-wrap;
+}
body.ttrss_utility.share_popup {
margin: 0;
padding: 0;
@@ -1872,6 +1877,7 @@ body.ttrss_zoom div.post div.content pre {
display: block;
max-width: 98%;
overflow: auto;
+ white-space: pre-wrap;
}
body.flat.ttrss_main.ttrss_prefs #main,
body.flat.ttrss_main.ttrss_prefs #footer {
@@ -1887,23 +1893,6 @@ body.flat.ttrss_main.ttrss_prefs div.prefHelp {
body.flat.ttrss_main.ttrss_prefs hr {
border-color: #666;
}
-body.flat.ttrss_main {
- /*
- .post .content img,
- .cdm .content-inner img,
- .post .content video,
- .cdm .content-inner video {
- transition : opacity 0.2s linear, filter 0.2s linear;
- }
-
- .post .content img:not(:hover),
- .cdm .content-inner img:not(:hover),
- .post .content video:not(:hover),
- .cdm .content-inner video:not(:hover) {
- opacity : 0.5;
- filter: grayscale(80%);
- } */
-}
body.flat.ttrss_main img[src*='indicator_white.gif'] {
filter: invert(1);
}
@@ -2028,6 +2017,9 @@ body.flat.ttrss_main .dijitToolbar .dijitSelect:not(.dijitHover) .dijitButtonCon
body.flat.ttrss_main .dijitToolbar .dijitSelect:not(.dijitHover) .dijitButtonNode {
background-color: #222;
}
+body.flat.ttrss_main .dijitSplitterHover {
+ background-color: rgba(255, 255, 255, 0.25);
+}
body.flat.ttrss_main .dijitCheckBox:not(.dijitChecked)::before {
color: #999999;
background: #222;
@@ -2081,6 +2073,12 @@ body.flat.ttrss_main ul#filterDlg_Actions {
background: #222;
border-color: #666;
}
+body.flat.ttrss_main .post .content img,
+body.flat.ttrss_main .cdm .content-inner img,
+body.flat.ttrss_main .post .content video,
+body.flat.ttrss_main .cdm .content-inner video {
+ filter: saturate(0.9) brightness(0.8);
+}
body.flat.ttrss_main .article-note {
background: #b87d2c;
border-color: #b87d2c;
diff --git a/themes/night_base.less b/themes/night_base.less
index 0dff7d191..76ea04a7f 100644
--- a/themes/night_base.less
+++ b/themes/night_base.less
@@ -200,6 +200,10 @@ body.flat.ttrss_main {
}
}
+ .dijitSplitterHover {
+ background-color : rgba(255, 255, 255, 0.25);
+ }
+
.dijitCheckBox:not(.dijitChecked)::before {
color : @fg-text-muted;
background: @color-panel-bg;
@@ -267,22 +271,13 @@ body.flat.ttrss_main {
border-color : @border-light;
}
- /*
- .post .content img,
- .cdm .content-inner img,
- .post .content video,
- .cdm .content-inner video {
- transition : opacity 0.2s linear, filter 0.2s linear;
+ .post .content,
+ .cdm .content-inner {
+ img, video {
+ filter: saturate(0.9) brightness(0.8);
+ }
}
- .post .content img:not(:hover),
- .cdm .content-inner img:not(:hover),
- .post .content video:not(:hover),
- .cdm .content-inner video:not(:hover) {
- opacity : 0.5;
- filter: grayscale(80%);
- } */
-
.article-note {
background : @color-accent;
border-color : @color-accent;
diff --git a/themes/night_blue.css b/themes/night_blue.css
index d5cda927f..51c12baa3 100644
--- a/themes/night_blue.css
+++ b/themes/night_blue.css
@@ -344,6 +344,7 @@ body.ttrss_main .dijitContentPane pre {
display: block;
max-width: 98%;
overflow: auto;
+ white-space: pre-wrap;
}
body.ttrss_main div.prefHelp {
color: #ccc;
@@ -1801,6 +1802,10 @@ body.ttrss_utility.installer,
body.ttrss_utility.feed_debugger {
margin: 2em;
}
+body.ttrss_utility.installer pre,
+body.ttrss_utility.feed_debugger pre {
+ white-space: pre-wrap;
+}
body.ttrss_utility.share_popup {
margin: 0;
padding: 0;
@@ -1872,6 +1877,7 @@ body.ttrss_zoom div.post div.content pre {
display: block;
max-width: 98%;
overflow: auto;
+ white-space: pre-wrap;
}
body.flat.ttrss_main.ttrss_prefs #main,
body.flat.ttrss_main.ttrss_prefs #footer {
@@ -1887,23 +1893,6 @@ body.flat.ttrss_main.ttrss_prefs div.prefHelp {
body.flat.ttrss_main.ttrss_prefs hr {
border-color: #666;
}
-body.flat.ttrss_main {
- /*
- .post .content img,
- .cdm .content-inner img,
- .post .content video,
- .cdm .content-inner video {
- transition : opacity 0.2s linear, filter 0.2s linear;
- }
-
- .post .content img:not(:hover),
- .cdm .content-inner img:not(:hover),
- .post .content video:not(:hover),
- .cdm .content-inner video:not(:hover) {
- opacity : 0.5;
- filter: grayscale(80%);
- } */
-}
body.flat.ttrss_main img[src*='indicator_white.gif'] {
filter: invert(1);
}
@@ -2028,6 +2017,9 @@ body.flat.ttrss_main .dijitToolbar .dijitSelect:not(.dijitHover) .dijitButtonCon
body.flat.ttrss_main .dijitToolbar .dijitSelect:not(.dijitHover) .dijitButtonNode {
background-color: #222;
}
+body.flat.ttrss_main .dijitSplitterHover {
+ background-color: rgba(255, 255, 255, 0.25);
+}
body.flat.ttrss_main .dijitCheckBox:not(.dijitChecked)::before {
color: #999999;
background: #222;
@@ -2081,6 +2073,12 @@ body.flat.ttrss_main ul#filterDlg_Actions {
background: #222;
border-color: #666;
}
+body.flat.ttrss_main .post .content img,
+body.flat.ttrss_main .cdm .content-inner img,
+body.flat.ttrss_main .post .content video,
+body.flat.ttrss_main .cdm .content-inner video {
+ filter: saturate(0.9) brightness(0.8);
+}
body.flat.ttrss_main .article-note {
background: #257aa7;
border-color: #257aa7;