diff options
Diffstat (limited to 'plugins')
37 files changed, 1162 insertions, 801 deletions
diff --git a/plugins/af_comics/filters/af_comics_gocomics.php b/plugins/af_comics/filters/af_comics_gocomics.php index 949b7a235..6c5c7c0d3 100644 --- a/plugins/af_comics/filters/af_comics_gocomics.php +++ b/plugins/af_comics/filters/af_comics_gocomics.php @@ -11,7 +11,7 @@ class Af_Comics_Gocomics extends Af_ComicFilter { public function on_subscribe($url) { if (preg_match('#^https?://www\.gocomics\.com/([-a-z0-9]+)$#i', $url)) - return '<?xml version="1.0" encoding="utf-8"?>'; // Get is_html() to return false. + return '<?xml version="1.0" encoding="utf-8"?>'; // Get _is_html() to return false. else return false; } diff --git a/plugins/af_comics/filters/af_comics_gocomics_farside.php b/plugins/af_comics/filters/af_comics_gocomics_farside.php index 9663da8f9..89322209d 100644 --- a/plugins/af_comics/filters/af_comics_gocomics_farside.php +++ b/plugins/af_comics/filters/af_comics_gocomics_farside.php @@ -11,7 +11,7 @@ class Af_Comics_Gocomics_FarSide extends Af_ComicFilter { public function on_subscribe($url) { if (preg_match("#^https?://www\.thefarside\.com#", $url)) - return '<?xml version="1.0" encoding="utf-8"?>'; // Get is_html() to return false. + return '<?xml version="1.0" encoding="utf-8"?>'; // Get _is_html() to return false. else return false; } diff --git a/plugins/af_comics/init.php b/plugins/af_comics/init.php index c99d4b1d8..c8a8f8637 100755 --- a/plugins/af_comics/init.php +++ b/plugins/af_comics/init.php @@ -47,11 +47,6 @@ class Af_Comics extends Plugin { function hook_prefs_tab($args) { if ($args != "prefFeeds") return; - print "<div dojoType=\"dijit.layout.AccordionPane\" - title=\"<i class='material-icons'>photo</i> ".__('Feeds supported by af_comics')."\">"; - - print "<p>" . __("The following comics are currently supported:") . "</p>"; - $comics = []; foreach ($this->filters as $f) { @@ -62,17 +57,22 @@ class Af_Comics extends Plugin { asort($comics); - print "<ul class='panel panel-scrollable list list-unstyled'>"; - foreach ($comics as $comic) { - print "<li>$comic</li>"; - } - print "</ul>"; + ?> + <div dojoType="dijit.layout.AccordionPane" + title="<i class='material-icons'>photo</i> <?= __('Feeds supported by af_comics') ?>"> - print_notice("To subscribe to GoComics use the comic's regular web page as the feed URL (e.g. for the <em>Garfield</em> comic use <code>http://www.gocomics.com/garfield</code>)."); + <h3><?= __("The following comics are currently supported:") ?></h3> - print_notice('Drop any updated filters into <code>filters.local</code> in plugin directory.'); + <ul class='panel panel-scrollable list list-unstyled'> + <?php foreach ($comics as $comic) { ?> + <li><?= htmlspecialchars($comic) ?></li> + <?php } ?> + </ul> - print "</div>"; + <?= format_notice("To subscribe to GoComics use the comic's regular web page as the feed URL (e.g. for the <em>Garfield</em> comic use <code>http://www.gocomics.com/garfield</code>).") ?> + <?= format_notice('Drop any updated filters into <code>filters.local</code> in plugin directory.') ?> + </div> + <?php } function hook_article_filter($article) { diff --git a/plugins/af_fsckportal/init.php b/plugins/af_fsckportal/init.php index 04b77a15a..8caa617c6 100644 --- a/plugins/af_fsckportal/init.php +++ b/plugins/af_fsckportal/init.php @@ -19,9 +19,7 @@ class Af_Fsckportal extends Plugin { $doc = new DOMDocument(); - @$doc->loadHTML('<?xml encoding="UTF-8">' . $article["content"]); - - if ($doc) { + if (@$doc->loadHTML('<?xml encoding="UTF-8">' . $article["content"])) { $xpath = new DOMXPath($doc); $entries = $xpath->query('(//img[@src]|//a[@href])'); @@ -34,7 +32,6 @@ class Af_Fsckportal extends Plugin { } $article["content"] = $doc->saveHTML(); - } return $article; diff --git a/plugins/af_proxy_http/init.php b/plugins/af_proxy_http/init.php index 3bde08fdb..b03cacfe4 100644 --- a/plugins/af_proxy_http/init.php +++ b/plugins/af_proxy_http/init.php @@ -50,8 +50,14 @@ class Af_Proxy_Http extends Plugin { public function imgproxy() { $url = UrlHelper::validate(clean($_REQUEST["url"])); - // called without user context, let's just redirect to original URL - if (!$_SESSION["uid"] || $_REQUEST['af_proxy_http_token'] != $_SESSION['af_proxy_http_token']) { + // immediately redirect to original URL if: + // - url points back to ourselves + // - called without user context + // - session-spefific token is invalid + if ( + strpos($url, get_self_url_prefix()) === 0 || + empty($_SESSION["uid"]) || + $_REQUEST['af_proxy_http_token'] != $_SESSION['af_proxy_http_token']) { header("Location: $url"); return; } @@ -59,14 +65,14 @@ class Af_Proxy_Http extends Plugin { $local_filename = sha1($url); if ($this->cache->exists($local_filename)) { - header("Location: " . $this->cache->getUrl($local_filename)); + header("Location: " . $this->cache->get_url($local_filename)); return; } else { - $data = UrlHelper::fetch(["url" => $url, "max_size" => MAX_CACHE_FILE_SIZE]); + $data = UrlHelper::fetch(["url" => $url, "max_size" => Config::get(Config::MAX_CACHE_FILE_SIZE)]); if ($data) { if ($this->cache->put($local_filename, $data)) { - header("Location: " . $this->cache->getUrl($local_filename)); + header("Location: " . $this->cache->get_url($local_filename)); return; } } else { @@ -104,6 +110,11 @@ class Af_Proxy_Http extends Plugin { } private function rewrite_url_if_needed($url, $all_remote = false) { + /* don't rewrite urls pointing to ourselves */ + + if (strpos($url, get_self_url_prefix()) === 0) + return $url; + /* we don't need to handle URLs where local cache already exists, tt-rss rewrites those automatically */ if (!$this->cache->exists(sha1($url))) { @@ -198,43 +209,41 @@ class Af_Proxy_Http extends Plugin { function hook_prefs_tab($args) { if ($args != "prefFeeds") return; + ?> - print "<div dojoType=\"dijit.layout.AccordionPane\" - title=\"<i class='material-icons'>extension</i> ".__('Image proxy settings (af_proxy_http)')."\">"; + <div dojoType="dijit.layout.AccordionPane" + title="<i class='material-icons'>extension</i> <?= __('Image proxy settings (af_proxy_http)') ?>"> - print "<form dojoType=\"dijit.form.Form\">"; + <form dojoType="dijit.form.Form"> - print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\"> - evt.preventDefault(); - if (this.validate()) { - console.log(dojo.objectToQuery(this.getValues())); - new Ajax.Request('backend.php', { - parameters: dojo.objectToQuery(this.getValues()), - onComplete: function(transport) { - Notify.info(transport.responseText); - } - }); - //this.reset(); - } - </script>"; + <?= \Controls\pluginhandler_tags($this, "save") ?> - print_hidden("op", "pluginhandler"); - print_hidden("method", "save"); - print_hidden("plugin", "af_proxy_http"); - - $proxy_all = $this->host->get($this, "proxy_all"); - print_checkbox("proxy_all", $proxy_all); - print " <label for=\"proxy_all\">" . __("Enable proxy for all remote images.") . "</label><br/>"; + <script type="dojo/method" event="onSubmit" args="evt"> + evt.preventDefault(); + if (this.validate()) { + xhr.post("backend.php", this.getValues(), (reply) => { + Notify.info(reply); + }) + } + </script> - print "<p>"; print_button("submit", __("Save")); + <fieldset> + <label class="checkbox"> + <?= \Controls\checkbox_tag("proxy_all", $this->host->get($this, "proxy_all")) ?> + <?= __("Enable proxy for all remote images.") ?> + </label> + </fieldset> - print "</form>"; + <hr/> - print "</div>"; + <?= \Controls\submit_tag(__("Save")) ?> + </form> + </div> + <?php } function save() { - $proxy_all = checkbox_to_sql_bool($_POST["proxy_all"]); + $proxy_all = checkbox_to_sql_bool($_POST["proxy_all"] ?? ""); $this->host->set($this, "proxy_all", $proxy_all); diff --git a/plugins/af_psql_trgm/init.js b/plugins/af_psql_trgm/init.js index a22e673f6..921272c4b 100644 --- a/plugins/af_psql_trgm/init.js +++ b/plugins/af_psql_trgm/init.js @@ -1,15 +1,18 @@ -/* global dijit, Plugins, __ */ +/* global dijit, dojo, Plugins, xhr, __ */ Plugins.Psql_Trgm = { showRelated: function (id) { - const query = "backend.php?op=pluginhandler&plugin=af_psql_trgm&method=showrelated¶m=" + encodeURIComponent(id); - const dialog = new dijit.Dialog({ title: __("Related articles"), - execute: function () { - // - }, - href: query, + content: __("Loading, please wait...") + }); + + const tmph = dojo.connect(dialog, "onShow", null, function (/* e */) { + dojo.disconnect(tmph); + + xhr.post("backend.php", {op: 'pluginhandler', plugin: 'af_psql_trgm', method: 'showrelated', id: id}, (reply) => { + dialog.attr('content', reply); + }); }); dialog.show(); diff --git a/plugins/af_psql_trgm/init.php b/plugins/af_psql_trgm/init.php index 163b0ec38..5611d8998 100644 --- a/plugins/af_psql_trgm/init.php +++ b/plugins/af_psql_trgm/init.php @@ -15,7 +15,7 @@ class Af_Psql_Trgm extends Plugin { function save() { $similarity = (float) $_POST["similarity"]; $min_title_length = (int) $_POST["min_title_length"]; - $enable_globally = checkbox_to_sql_bool($_POST["enable_globally"]); + $enable_globally = checkbox_to_sql_bool($_POST["enable_globally"] ?? ""); if ($similarity < 0) $similarity = 0; if ($similarity > 1) $similarity = 1; @@ -46,7 +46,7 @@ class Af_Psql_Trgm extends Plugin { } function showrelated() { - $id = (int) $_REQUEST['param']; + $id = (int) $_REQUEST['id']; $owner_uid = $_SESSION["uid"]; $sth = $this->pdo->prepare("SELECT title FROM ttrss_entries, ttrss_user_entries @@ -124,115 +124,117 @@ class Af_Psql_Trgm extends Plugin { function hook_prefs_tab($args) { if ($args != "prefFeeds") return; - print "<div dojoType=\"dijit.layout.AccordionPane\" - title=\"<i class='material-icons'>extension</i> ".__('Mark similar articles as read (af_psql_trgm)')."\">"; + $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 = sql_bool_to_bool($this->host->get($this, "enable_globally")); - if (DB_TYPE != "pgsql") { - print_error("Database type not supported."); - } else { - - $res = $this->pdo->query("select 'similarity'::regproc"); - - if (!$res || !$res->fetch()) { - print_error("pg_trgm extension not found."); - } - - $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"); - - print "<form dojoType=\"dijit.form.Form\">"; - - print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\"> - evt.preventDefault(); - if (this.validate()) { - console.log(dojo.objectToQuery(this.getValues())); - new Ajax.Request('backend.php', { - parameters: dojo.objectToQuery(this.getValues()), - onComplete: function(transport) { - Notify.info(transport.responseText); - } - }); - //this.reset(); - } - </script>"; - - print_hidden("op", "pluginhandler"); - print_hidden("method", "save"); - print_hidden("plugin", "af_psql_trgm"); - - print "<h2>" . __("Global settings") . "</h2>"; - - print_notice("Enable for specific feeds in the feed editor."); - - print "<fieldset>"; - - print "<label>" . __("Minimum similarity:") . "</label> "; - print "<input dojoType=\"dijit.form.NumberSpinner\" - placeholder=\"0.75\" id='psql_trgm_similarity' - required=\"1\" name=\"similarity\" value=\"$similarity\">"; - - print "<div dojoType='dijit.Tooltip' connectId='psql_trgm_similarity' position='below'>" . - __("PostgreSQL trigram extension returns string similarity as a floating point number (0-1). Setting it too low might produce false positives, zero disables checking.") . - "</div>"; - - print "</fieldset><fieldset>"; - - print "<label>" . __("Minimum title length:") . "</label> "; - print "<input dojoType=\"dijit.form.NumberSpinner\" - placeholder=\"32\" - required=\"1\" name=\"min_title_length\" value=\"$min_title_length\">"; + ?> - print "</fieldset><fieldset>"; + <div dojoType="dijit.layout.AccordionPane" + title="<i class='material-icons'>extension</i> <?= __('Mark similar articles as read (af_psql_trgm)') ?>"> - print "<label class='checkbox'>"; - print_checkbox("enable_globally", $enable_globally); - print " " . __("Enable for all feeds:"); - print "</label>"; + <?php + if (Config::get(Config::DB_TYPE) != "pgsql") { + print_error("Database type not supported."); + } else { + $res = $this->pdo->query("select 'similarity'::regproc"); - print "</fieldset>"; - - print_button("submit", __("Save"), "class='alt-primary'"); - print "</form>"; - - /* cleanup */ - $enabled_feeds = $this->filter_unknown_feeds( - $this->get_stored_array("enabled_feeds")); - - $this->host->set($this, "enabled_feeds", $enabled_feeds); - - if (count($enabled_feeds) > 0) { - print "<h3>" . __("Currently enabled for (click to edit):") . "</h3>"; - - print "<ul class=\"panel panel-scrollable list list-unstyled\">"; - foreach ($enabled_feeds as $f) { - print "<li>" . - "<i class='material-icons'>rss_feed</i> <a href='#' - onclick='CommonDialogs.editFeed($f)'>" . - Feeds::getFeedTitle($f) . "</a></li>"; + if (!$res || !$res->fetch()) { + print_error("pg_trgm extension not found."); } - print "</ul>"; - } - } - - print "</div>"; + } ?> + + <form dojoType="dijit.form.Form"> + + <?= \Controls\pluginhandler_tags($this, "save") ?> + + <script type="dojo/method" event="onSubmit" args="evt"> + evt.preventDefault(); + if (this.validate()) { + Notify.progress('Saving data...', true); + xhr.post("backend.php", this.getValues(), (reply) => { + Notify.info(reply); + }) + } + </script> + + <?= format_notice("Enable for specific feeds in the feed editor.") ?> + + <fieldset> + <label><?= __("Minimum similarity:") ?></label> + <input dojoType="dijit.form.NumberSpinner" + placeholder="<?= $this->default_similarity ?>" + id='psql_trgm_similarity' + required="1" + name="similarity" value="<?= htmlspecialchars($similarity) ?>"> + + <div dojoType='dijit.Tooltip' connectId='psql_trgm_similarity' position='below'> + <?= __("PostgreSQL trigram extension returns string similarity as a floating point number (0-1). Setting it too low might produce false positives, zero disables checking.") ?> + </div> + </fieldset> + + <fieldset> + <label><?= __("Minimum title length:") ?></label> + <input dojoType="dijit.form.NumberSpinner" + placeholder="<?= $this->default_min_length ?>" + required="1" + name="min_title_length" value="<?= htmlspecialchars($min_title_length) ?>"> + </fieldset> + + <fieldset> + <label class='checkbox'> + <?= \Controls\checkbox_tag("enable_globally", $enable_globally) ?> + <?= __("Enable for all feeds.") ?> + </label> + </fieldset> + + <hr/> + + <?= \Controls\submit_tag(__("Save")) ?> + </form> + + <?php + /* cleanup */ + $enabled_feeds = $this->filter_unknown_feeds( + $this->get_stored_array("enabled_feeds")); + + $this->host->set($this, "enabled_feeds", $enabled_feeds); + ?> + + <?php if (count($enabled_feeds) > 0) { ?> + <hr/> + <h3><?= __("Currently enabled for (click to edit):") ?></h3> + + <ul class="panel panel-scrollable list list-unstyled"> + <?php foreach ($enabled_feeds as $f) { ?> + <li> + <i class='material-icons'>rss_feed</i> + <a href='#' onclick="CommonDialogs.editFeed(<?= $f ?>)"> + <?= Feeds::_get_title($f) ?> + </a> + </li> + <?php } ?> + </ul> + <?php } ?> + </div> + <?php } function hook_prefs_edit_feed($feed_id) { - print "<header>".__("Similarity (af_psql_trgm)")."</header>"; - print "<section>"; - - $enabled_feeds = $this->get_stored_array("enabled_feeds"); - $checked = in_array($feed_id, $enabled_feeds) ? "checked" : ""; - - print "<fieldset>"; - - print "<label class='checkbox'><input dojoType='dijit.form.CheckBox' type='checkbox' id='trgm_similarity_enabled' - name='trgm_similarity_enabled' $checked> ".__('Mark similar articles as read')."</label>"; - - print "</fieldset>"; - - print "</section>"; + $enabled_feeds = $this->get_stored_array("enabled_feeds"); + ?> + <header><?= __("Similarity (af_psql_trgm)") ?></header> + + <section> + <fieldset> + <label class="checkbox"> + <?= \Controls\checkbox_tag("trgm_similarity_enabled", in_array($feed_id, $enabled_feeds)) ?> + <?= __('Mark similar articles as read') ?> + </label> + </fieldset> + </section> + </section> + <?php } function hook_prefs_save_feed($feed_id) { @@ -256,7 +258,7 @@ class Af_Psql_Trgm extends Plugin { function hook_article_filter($article) { - if (DB_TYPE != "pgsql") return $article; + if (Config::get(Config::DB_TYPE) != "pgsql") return $article; $res = $this->pdo->query("select 'similarity'::regproc"); if (!$res || !$res->fetch()) return $article; diff --git a/plugins/af_readability/init.js b/plugins/af_readability/init.js index 3155475cc..0232ed32d 100644 --- a/plugins/af_readability/init.js +++ b/plugins/af_readability/init.js @@ -1,9 +1,11 @@ +/* global xhr, App, Plugins, Article, Notify */ + Plugins.Af_Readability = { orig_attr_name: 'data-readability-orig-content', self: this, embed: function(id) { - const content = $$(App.isCombinedMode() ? ".cdm[data-article-id=" + id + "] .content-inner" : - ".post[data-article-id=" + id + "] .content")[0]; + const content = App.find(App.isCombinedMode() ? `.cdm[data-article-id="${id}"] .content-inner` : + `.post[data-article-id="${id}"] .content`); if (content.hasAttribute(self.orig_attr_name)) { content.innerHTML = content.getAttribute(self.orig_attr_name); @@ -16,7 +18,7 @@ Plugins.Af_Readability = { Notify.progress("Loading, please wait..."); - xhrJson("backend.php",{ op: "pluginhandler", plugin: "af_readability", method: "embed", param: id }, (reply) => { + xhr.json("backend.php", App.getPhArgs("af_readability", "embed", {id: id}), (reply) => { if (content && reply.content) { content.setAttribute(self.orig_attr_name, content.innerHTML); diff --git a/plugins/af_readability/init.php b/plugins/af_readability/init.php index a76c98380..be9220cda 100755 --- a/plugins/af_readability/init.php +++ b/plugins/af_readability/init.php @@ -18,7 +18,7 @@ class Af_Readability extends Plugin { } function save() { - $enable_share_anything = checkbox_to_sql_bool($_POST["enable_share_anything"]); + $enable_share_anything = checkbox_to_sql_bool($_POST["enable_share_anything"] ?? ""); $this->host->set($this, "enable_share_anything", $enable_share_anything); @@ -29,11 +29,6 @@ class Af_Readability extends Plugin { { $this->host = $host; - if (version_compare(PHP_VERSION, '7.0.0', '<')) { - user_error("af_readability requires PHP 7.0", E_USER_WARNING); - return; - } - $host->add_hook($host::HOOK_ARTICLE_FILTER, $this); $host->add_hook($host::HOOK_PREFS_TAB, $this); $host->add_hook($host::HOOK_PREFS_EDIT_FEED, $this); @@ -60,99 +55,92 @@ class Af_Readability extends Plugin { function hook_prefs_tab($args) { if ($args != "prefFeeds") return; - print "<div dojoType='dijit.layout.AccordionPane' - title=\"<i class='material-icons'>extension</i> ".__('Readability settings (af_readability)')."\">"; + $enable_share_anything = sql_bool_to_bool($this->host->get($this, "enable_share_anything")); - if (version_compare(PHP_VERSION, '7.0.0', '<')) { - print_error("This plugin requires PHP 7.0."); - } else { + ?> + <div dojoType='dijit.layout.AccordionPane' + title="<i class='material-icons'>extension</i> <?= __('Readability settings (af_readability)') ?>"> - print "<h2>" . __("Global settings") . "</h2>"; + <?= format_notice("Enable for specific feeds in the feed editor.") ?> - print_notice("Enable for specific feeds in the feed editor."); + <form dojoType='dijit.form.Form'> - print "<form dojoType='dijit.form.Form'>"; + <?= \Controls\pluginhandler_tags($this, "save") ?> - print "<script type='dojo/method' event='onSubmit' args='evt'> - evt.preventDefault(); - if (this.validate()) { - console.log(dojo.objectToQuery(this.getValues())); - new Ajax.Request('backend.php', { - parameters: dojo.objectToQuery(this.getValues()), - onComplete: function(transport) { - Notify.info(transport.responseText); + <script type="dojo/method" event="onSubmit" args="evt"> + evt.preventDefault(); + if (this.validate()) { + Notify.progress('Saving data...', true); + xhr.post("backend.php", this.getValues(), (reply) => { + Notify.info(reply); + }) } - }); - //this.reset(); - } - </script>"; - - print_hidden("op", "pluginhandler"); - print_hidden("method", "save"); - print_hidden("plugin", "af_readability"); - - $enable_share_anything = $this->host->get($this, "enable_share_anything"); - - print "<fieldset>"; - print "<label class='checkbox'> "; - print_checkbox("enable_share_anything", $enable_share_anything); - print " " . __("Provide full-text services to core code (bookmarklets) and other plugins"); - print "</label>"; - print "</fieldset>"; - - print_button("submit", __("Save"), "class='alt-primary'"); - print "</form>"; - - /* cleanup */ - $enabled_feeds = $this->filter_unknown_feeds( - $this->get_stored_array("enabled_feeds")); - - $append_feeds = $this->filter_unknown_feeds( - $this->get_stored_array("append_feeds")); - - $this->host->set($this, "enabled_feeds", $enabled_feeds); - $this->host->set($this, "append_feeds", $append_feeds); - - if (count($enabled_feeds) > 0) { - print "<h3>" . __("Currently enabled for (click to edit):") . "</h3>"; - - print "<ul class='panel panel-scrollable list list-unstyled'>"; - foreach ($enabled_feeds as $f) { - $is_append = in_array($f, $append_feeds); - - print "<li><i class='material-icons'>rss_feed</i> <a href='#' - onclick='CommonDialogs.editFeed($f)'>". - Feeds::getFeedTitle($f) . " " . ($is_append ? __("(append)") : "") . "</a></li>"; - } - print "</ul>"; - } - - } - - print "</div>"; + </script> + + <fieldset> + <label class='checkbox'> + <?= \Controls\checkbox_tag("enable_share_anything", $enable_share_anything) ?> + <?= __("Provide full-text services to core code (bookmarklets) and other plugins") ?> + </label> + </fieldset> + + <hr/> + + <?= \Controls\submit_tag(__("Save")) ?> + </form> + + <?php + /* cleanup */ + $enabled_feeds = $this->filter_unknown_feeds( + $this->get_stored_array("enabled_feeds")); + + $append_feeds = $this->filter_unknown_feeds( + $this->get_stored_array("append_feeds")); + + $this->host->set($this, "enabled_feeds", $enabled_feeds); + $this->host->set($this, "append_feeds", $append_feeds); + ?> + + <?php if (count($enabled_feeds) > 0) { ?> + <hr/> + <h3><?= __("Currently enabled for (click to edit):") ?></h3> + + <ul class='panel panel-scrollable list list-unstyled'> + <?php foreach ($enabled_feeds as $f) { ?> + <li> + <i class='material-icons'>rss_feed</i> + <a href='#' onclick="CommonDialogs.editFeed(<?= $f ?>)"> + <?= Feeds::_get_title($f) . " " . (in_array($f, $append_feeds) ? __("(append)") : "") ?> + </a> + </li> + <?php } ?> + </ul> + <?php } ?> + </div> + <?php } function hook_prefs_edit_feed($feed_id) { - print "<header>".__("Readability")."</header>"; - print "<section>"; - $enabled_feeds = $this->get_stored_array("enabled_feeds"); $append_feeds = $this->get_stored_array("append_feeds"); - - $enable_checked = in_array($feed_id, $enabled_feeds) ? "checked" : ""; - $append_checked = in_array($feed_id, $append_feeds) ? "checked" : ""; - - print "<fieldset>"; - - print "<label class='checkbox'><input dojoType='dijit.form.CheckBox' type='checkbox' id='af_readability_enabled' - name='af_readability_enabled' $enable_checked> ".__('Inline article content')."</label>"; - - print "</fieldset><fieldset>"; - - print "<label class='checkbox'><input dojoType='dijit.form.CheckBox' type='checkbox' id='af_readability_append' - name='af_readability_append' $append_checked> ".__('Append to summary, instead of replacing it')."</label>"; - - print "</section>"; + ?> + + <header><?= __("Readability") ?></header> + <section> + <fieldset> + <label class='checkbox'> + <?= \Controls\checkbox_tag("af_readability_enabled", in_array($feed_id, $enabled_feeds)) ?> + <?= __('Inline article content') ?> + </label> + </fieldset> + <fieldset> + <label class='checkbox'> + <?= \Controls\checkbox_tag("af_readability_append", in_array($feed_id, $append_feeds)) ?> + <?= __('Append to summary, instead of replacing it') ?> + </label> + </fieldset> + </section> + <?php } function hook_prefs_save_feed($feed_id) { @@ -333,7 +321,7 @@ class Af_Readability extends Plugin { } function embed() { - $article_id = (int) $_REQUEST["param"]; + $article_id = (int) $_REQUEST["id"]; $sth = $this->pdo->prepare("SELECT link FROM ttrss_entries WHERE id = ?"); $sth->execute([$article_id]); diff --git a/plugins/af_redditimgur/init.php b/plugins/af_redditimgur/init.php index 2e89fcdff..1aa4793ea 100755 --- a/plugins/af_redditimgur/init.php +++ b/plugins/af_redditimgur/init.php @@ -31,65 +31,62 @@ class Af_RedditImgur extends Plugin { function hook_prefs_tab($args) { if ($args != "prefFeeds") return; - print "<div dojoType=\"dijit.layout.AccordionPane\" - title=\"<i class='material-icons'>extension</i> ".__('Reddit content settings (af_redditimgur)')."\">"; + $enable_readability = $this->host->get($this, "enable_readability"); + $enable_content_dupcheck = $this->host->get($this, "enable_content_dupcheck"); + $reddit_to_teddit = $this->host->get($this, "reddit_to_teddit"); + ?> - $enable_readability = $this->host->get($this, "enable_readability"); - $enable_content_dupcheck = $this->host->get($this, "enable_content_dupcheck"); - $reddit_to_teddit = $this->host->get($this, "reddit_to_teddit"); + <div dojoType="dijit.layout.AccordionPane" + title="<i class='material-icons'>extension</i> <?= __('Reddit content settings (af_redditimgur)') ?>"> - if (version_compare(PHP_VERSION, '5.6.0', '<')) { - print_error("Readability requires PHP version 5.6."); - } + <form dojoType='dijit.form.Form'> - print "<form dojoType='dijit.form.Form'>"; + <?= \Controls\pluginhandler_tags($this, "save") ?> - print "<script type='dojo/method' event='onSubmit' args='evt'> - evt.preventDefault(); - if (this.validate()) { - console.log(dojo.objectToQuery(this.getValues())); - new Ajax.Request('backend.php', { - parameters: dojo.objectToQuery(this.getValues()), - onComplete: function(transport) { - Notify.info(transport.responseText); + <script type="dojo/method" event="onSubmit" args="evt"> + evt.preventDefault(); + if (this.validate()) { + Notify.progress('Saving data...', true); + xhr.post("backend.php", this.getValues(), (reply) => { + Notify.info(reply); + }) } - }); - //this.reset(); - } - </script>"; - - print_hidden("op", "pluginhandler"); - print_hidden("method", "save"); - print_hidden("plugin", "af_redditimgur"); - - print "<fieldset class='narrow'>"; - print "<label class='checkbox'>"; - print_checkbox("enable_readability", $enable_readability); - print " " . __("Extract missing content using Readability (requires af_readability)") . "</label>"; - print "</fieldset>"; - - print "<fieldset class='narrow'>"; - print "<label class='checkbox'>"; - print_checkbox("enable_content_dupcheck", $enable_content_dupcheck); - print " " . __("Enable additional duplicate checking") . "</label>"; - print "</fieldset>"; - - print "<fieldset class='narrow'>"; - print "<label class='checkbox'>"; - print_checkbox("reddit_to_teddit", $reddit_to_teddit); - print " " . T_sprintf("Rewrite Reddit URLs to %s", - "<a target=\"_blank\" href=\"https://teddit.net/about\">Teddit</a>") . "</label>"; - - print_button("submit", __("Save"), 'class="alt-primary"'); - print "</form>"; - - print "</div>"; + </script> + + <fieldset class='narrow'> + <label class='checkbox'> + <?= \Controls\checkbox_tag("enable_readability", $enable_readability) ?> + <?= __("Extract missing content using Readability (requires af_readability)") ?> + </label> + </fieldset> + + <fieldset class='narrow'> + <label class='checkbox'> + <?= \Controls\checkbox_tag("enable_content_dupcheck", $enable_content_dupcheck) ?> + <?= __("Enable additional duplicate checking") ?> + </label> + </fieldset> + + <fieldset class='narrow'> + <label class='checkbox'> + <?= \Controls\checkbox_tag("reddit_to_teddit", $reddit_to_teddit) ?> + <?= T_sprintf("Rewrite Reddit URLs to %s", + "<a target=\"_blank\" href=\"https://teddit.net/about\">Teddit</a>") ?> + </label> + </fieldset> + + <hr/> + <?= \Controls\submit_tag(__("Save")) ?> + </form> + </div> + + <?php } function save() { - $enable_readability = checkbox_to_sql_bool($_POST["enable_readability"]); - $enable_content_dupcheck = checkbox_to_sql_bool($_POST["enable_content_dupcheck"]); - $reddit_to_teddit = checkbox_to_sql_bool($_POST["reddit_to_teddit"]); + $enable_readability = checkbox_to_sql_bool($_POST["enable_readability"] ?? ""); + $enable_content_dupcheck = checkbox_to_sql_bool($_POST["enable_content_dupcheck"] ?? ""); + $reddit_to_teddit = checkbox_to_sql_bool($_POST["reddit_to_teddit"] ?? ""); $this->host->set($this, "enable_readability", $enable_readability, false); $this->host->set($this, "reddit_to_teddit", $reddit_to_teddit, false); @@ -220,6 +217,7 @@ class Af_RedditImgur extends Plugin { $this->fallback_preview_urls = []; + // @phpstan-ignore-next-line if ($tmp && $anchor) { $json = json_decode($tmp, true); @@ -349,6 +347,8 @@ class Af_RedditImgur extends Plugin { if (strpos($source_stream, "imgur.com") !== false) $poster_url = str_replace(".mp4", "h.jpg", $source_stream); + else + $poster_url = false; $this->handle_as_video($doc, $entry, $source_stream, $poster_url); @@ -530,7 +530,7 @@ class Af_RedditImgur extends Plugin { $entry_guid = $article["guid_hashed"]; $owner_uid = $article["owner_uid"]; - if (DB_TYPE == "pgsql") { + if (Config::get(Config::DB_TYPE) == "pgsql") { $interval_qpart = "date_entered < NOW() - INTERVAL '1 day'"; } else { $interval_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 1 DAY)"; @@ -631,6 +631,10 @@ class Af_RedditImgur extends Plugin { $entry->parentNode->insertBefore($img, $entry);*/ } + function csrf_ignore($method) { + return $method === "testurl"; + } + function testurl() { $url = clean($_POST["url"]); @@ -645,14 +649,17 @@ class Af_RedditImgur extends Plugin { fieldset { border : 0; } label { display : inline-block; min-width : 120px; } </style> - <form action="backend.php?op=pluginhandler&method=testurl&plugin=af_redditimgur" method="post"> + <form action="backend.php" method="post"> + <input type="hidden" name="op" value="pluginhandler"> + <input type="hidden" name="method" value="testurl"> + <input type="hidden" name="plugin" value="af_redditimgur"> <fieldset> <label>URL:</label> - <input name="url" size="100" value="<?php echo htmlspecialchars($url) ?>"></input> + <input name="url" size="100" value="<?= htmlspecialchars($url) ?>"></input> </fieldset> <fieldset> <label>Article URL:</label> - <input name="article_url" size="100" value="<?php echo htmlspecialchars($article_url) ?>"></input> + <input name="article_url" size="100" value="<?= htmlspecialchars($article_url) ?>"></input> </fieldset> <fieldset> <button type="submit">Test</button> @@ -694,7 +701,7 @@ class Af_RedditImgur extends Plugin { private function get_header($url, $header, $useragent = SELF_USER_AGENT) { $ret = false; - if (function_exists("curl_init") && !defined("NO_CURL")) { + if (function_exists("curl_init")) { $ch = curl_init($url); curl_setopt($ch, CURLOPT_TIMEOUT, 5); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); @@ -720,7 +727,7 @@ class Af_RedditImgur extends Plugin { private function readability($article, $url, $doc, $xpath, $debug = false) { - if (!defined('NO_CURL') && function_exists("curl_init") && $this->host->get($this, "enable_readability") && + if (function_exists("curl_init") && $this->host->get($this, "enable_readability") && mb_strlen(strip_tags($article["content"])) <= 150) { // do not try to embed posts linking back to other reddit posts diff --git a/plugins/af_tumblr_1280/init.php b/plugins/af_tumblr_1280/init.php deleted file mode 100755 index 5d7f366a4..000000000 --- a/plugins/af_tumblr_1280/init.php +++ /dev/null @@ -1,91 +0,0 @@ -<?php -class Af_Tumblr_1280 extends Plugin { - private $host; - - function about() { - return array(1.0, - "Replace Tumblr pictures and videos with largest size if available (requires CURL)", - "fox"); - } - - function flags() { - return array("needs_curl" => true); - } - - function init($host) { - $this->host = $host; - - if (function_exists("curl_init")) { - $host->add_hook($host::HOOK_ARTICLE_FILTER, $this); - } - } - - function hook_article_filter($article) { - - if (!function_exists("curl_init") || ini_get("open_basedir")) - return $article; - - $doc = new DOMDocument(); - $doc->loadHTML('<?xml encoding="UTF-8">' . $article["content"]); - - $found = false; - - if ($doc) { - $xpath = new DOMXpath($doc); - - $images = $xpath->query('(//img[contains(@src, \'media.tumblr.com\')])'); - - foreach ($images as $img) { - $src = $img->getAttribute("src"); - - $test_src = preg_replace("/_\d{3}.(jpg|gif|png)/", "_1280.$1", $src); - - if ($src != $test_src) { - - $ch = curl_init($test_src); - curl_setopt($ch, CURLOPT_TIMEOUT, 5); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_HEADER, true); - curl_setopt($ch, CURLOPT_NOBODY, true); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_USERAGENT, SELF_USER_AGENT); - - @$result = curl_exec($ch); - $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - - if ($result && $http_code == 200) { - $img->setAttribute("src", $test_src); - $found = true; - } - } - } - - $video_sources = $xpath->query('//video/source[contains(@src, \'.tumblr.com/video_file\')]'); - - foreach ($video_sources as $source) { - $src = $source->getAttribute("src"); - - $new_src = preg_replace("/\/\d{3}$/", "", $src); - - if ($src != $new_src) { - $source->setAttribute("src", $new_src); - $found = true; - } - } - - if ($found) { - $doc->removeChild($doc->firstChild); //remove doctype - $article["content"] = $doc->saveHTML(); - } - } - - return $article; - - } - - - function api_version() { - return 2; - } - -} diff --git a/plugins/af_unburn/init.php b/plugins/af_unburn/init.php index 4d0c56740..386b6387f 100755 --- a/plugins/af_unburn/init.php +++ b/plugins/af_unburn/init.php @@ -21,7 +21,7 @@ class Af_Unburn extends Plugin { function hook_article_filter($article) { $owner_uid = $article["owner_uid"]; - if (defined('NO_CURL') || !function_exists("curl_init") || ini_get("open_basedir")) + if (!function_exists("curl_init") || ini_get("open_basedir")) return $article; if ((strpos($article["link"], "feedproxy.google.com") !== false || @@ -37,8 +37,8 @@ class Af_Unburn extends Plugin { curl_setopt($ch, CURLOPT_NOBODY, true); curl_setopt($ch, CURLOPT_USERAGENT, SELF_USER_AGENT); - if (defined('_CURL_HTTP_PROXY')) { - curl_setopt($ch, CURLOPT_PROXY, _CURL_HTTP_PROXY); + if (Config::get(Config::HTTP_PROXY)) { + curl_setopt($ch, CURLOPT_PROXY, Config::get(Config::HTTP_PROXY)); } @curl_exec($ch); @@ -80,4 +80,4 @@ class Af_Unburn extends Plugin { return 2; } -}
\ No newline at end of file +} diff --git a/plugins/af_youtube_embed/init.php b/plugins/af_youtube_embed/init.php index db82dc9f5..6309aac02 100644 --- a/plugins/af_youtube_embed/init.php +++ b/plugins/af_youtube_embed/init.php @@ -23,9 +23,9 @@ class Af_Youtube_Embed extends Plugin { $matches = array(); - if (preg_match("/\/\/www\.youtube\.com\/v\/([\w-]+)/", $entry["url"], $matches) || - preg_match("/\/\/www\.youtube\.com\/watch?v=([\w-]+)/", $entry["url"], $matches) || - preg_match("/\/\/youtu.be\/([\w-]+)/", $entry["url"], $matches)) { + if (preg_match("/\/\/www\.youtube\.com\/v\/([\w-]+)/", $entry["content_url"], $matches) || + preg_match("/\/\/www\.youtube\.com\/watch?v=([\w-]+)/", $entry["content_url"], $matches) || + preg_match("/\/\/youtu.be\/([\w-]+)/", $entry["content_url"], $matches)) { $vid_id = $matches[1]; diff --git a/plugins/af_zz_vidmute/init.js b/plugins/af_zz_vidmute/init.js index fab9b99e6..b8be8cecd 100644 --- a/plugins/af_zz_vidmute/init.js +++ b/plugins/af_zz_vidmute/init.js @@ -3,7 +3,7 @@ require(['dojo/_base/kernel', 'dojo/ready'], function (dojo, ready) { PluginHost.register(PluginHost.HOOK_ARTICLE_RENDERED_CDM, function (row) { if (row) { - row.select("video").each(function (v) { + row.querySelectorAll("video").forEach(function (v) { v.muted = true; }); } @@ -14,7 +14,7 @@ require(['dojo/_base/kernel', 'dojo/ready'], function (dojo, ready) { PluginHost.register(PluginHost.HOOK_ARTICLE_RENDERED, function (row) { if (row) { - row.select("video").each(function (v) { + row.querySelectorAll("video").forEach(function (v) { v.muted = true; }); } diff --git a/plugins/auth_internal/init.php b/plugins/auth_internal/init.php index a69ea444c..13a7bc969 100644 --- a/plugins/auth_internal/init.php +++ b/plugins/auth_internal/init.php @@ -63,21 +63,21 @@ class Auth_Internal extends Auth_Base { <title>Tiny Tiny RSS</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> - <?php echo stylesheet_tag("themes/light.css") ?> + <?= stylesheet_tag("themes/light.css") ?> <body class="ttrss_utility otp"> - <h1><?php echo __("Authentication") ?></h1> + <h1><?= __("Authentication") ?></h1> <div class="content"> - <form action="public.php?return=<?php echo $return ?>" + <form action="public.php?return=<?= $return ?>" method="POST" class="otpform"> <input type="hidden" name="op" value="login"> - <input type="hidden" name="login" value="<?php echo htmlspecialchars($login) ?>"> - <input type="hidden" name="password" value="<?php echo htmlspecialchars($password) ?>"> - <input type="hidden" name="bw_limit" value="<?php echo htmlspecialchars($_POST["bw_limit"]) ?>"> - <input type="hidden" name="remember_me" value="<?php echo htmlspecialchars($_POST["remember_me"]) ?>"> - <input type="hidden" name="profile" value="<?php echo htmlspecialchars($_POST["profile"]) ?>"> + <input type="hidden" name="login" value="<?= htmlspecialchars($login) ?>"> + <input type="hidden" name="password" value="<?= htmlspecialchars($password) ?>"> + <input type="hidden" name="bw_limit" value="<?= htmlspecialchars($_POST["bw_limit"]) ?>"> + <input type="hidden" name="remember_me" value="<?= htmlspecialchars($_POST["remember_me"]) ?>"> + <input type="hidden" name="profile" value="<?= htmlspecialchars($_POST["profile"]) ?>"> <fieldset> - <label><?php echo __("Please enter your one time password:") ?></label> + <label><?= __("Please enter your one time password:") ?></label> <input autocomplete="off" size="6" name="otp" value=""/> <input type="submit" value="Continue"/> </fieldset> @@ -244,7 +244,7 @@ class Auth_Internal extends Auth_Base { $tpl->readTemplateFromFile("password_change_template.txt"); $tpl->setVariable('LOGIN', $row["login"]); - $tpl->setVariable('TTRSS_HOST', SELF_URL_PATH); + $tpl->setVariable('TTRSS_HOST', Config::get(Config::SELF_URL_PATH)); $tpl->addBlock('message'); diff --git a/plugins/auth_remote/init.php b/plugins/auth_remote/init.php index 85be67d05..f2dcfb318 100644 --- a/plugins/auth_remote/init.php +++ b/plugins/auth_remote/init.php @@ -56,7 +56,7 @@ class Auth_Remote extends Auth_Base { $_SESSION["hide_logout"] = true; // LemonLDAP can send user informations via HTTP HEADER - if (defined('AUTH_AUTO_CREATE') && AUTH_AUTO_CREATE){ + if (Config::get(Config::AUTH_AUTO_CREATE)) { // update user name $fullname = isset($_SERVER['HTTP_USER_NAME']) ? $_SERVER['HTTP_USER_NAME'] : ($_SERVER['AUTHENTICATE_CN'] ?? ""); if ($fullname){ diff --git a/plugins/auto_assign_labels/init.php b/plugins/auto_assign_labels/init.php index 3fa4ad8c0..341895cef 100755 --- a/plugins/auto_assign_labels/init.php +++ b/plugins/auto_assign_labels/init.php @@ -19,6 +19,7 @@ class Auto_Assign_Labels extends Plugin { function get_all_labels_filter_format($owner_uid) { $rv = array(); + // TODO: use Labels::get_all() $sth = $this->pdo->prepare("SELECT id, fg_color, bg_color, caption FROM ttrss_labels2 WHERE owner_uid = ?"); $sth->execute([$owner_uid]); diff --git a/plugins/bookmarklets/init.php b/plugins/bookmarklets/init.php index fa1bb8cf6..967918823 100644 --- a/plugins/bookmarklets/init.php +++ b/plugins/bookmarklets/init.php @@ -1,53 +1,371 @@ <?php class Bookmarklets extends Plugin { - private $host; - - function about() { - return array(1.0, - "Easy feed subscription and web page sharing using bookmarklets", - "fox", - false, - "https://git.tt-rss.org/fox/tt-rss/wiki/ShareAnything"); + private $host; + + function about() { + return array(1.0, + "Easy feed subscription and web page sharing using bookmarklets", + "fox", + false, + "https://git.tt-rss.org/fox/tt-rss/wiki/ShareAnything"); } - function init($host) { - $this->host = $host; + function init($host) { + $this->host = $host; - $host->add_hook($host::HOOK_PREFS_TAB, $this); - } + $host->add_hook($host::HOOK_PREFS_TAB, $this); + } + + function is_public_method($method) { + return in_array($method, ["subscribe", "sharepopup"]); + } - function hook_prefs_tab($args) { - if ($args == "prefFeeds") { + function subscribe() { + if (Config::get(Config::SINGLE_USER_MODE)) { + UserHelper::login_sequence(); + } - print "<div dojoType=\"dijit.layout.AccordionPane\" - title=\"<i class='material-icons'>bookmark</i> ".__('Bookmarklets')."\">"; + if (!empty($_SESSION["uid"])) { - print "<h3>" . __("Drag the link below to your browser toolbar, open the feed you're interested in in your browser and click on the link to subscribe to it.") . "</h3>"; + $feed_url = clean($_REQUEST["feed_url"] ?? ""); + $csrf_token = clean($_POST["csrf_token"] ?? ""); - $bm_subscribe_url = str_replace('%s', '', Pref_Feeds::subscribe_to_feed_url()); + header('Content-Type: text/html; charset=utf-8'); + ?> + <!DOCTYPE html> + <html> + <head> + <title><?= __("Subscribe to feed...") ?></title> + <?= javascript_tag("lib/dojo/dojo.js") ?> + <?= javascript_tag("js/utility.js") ?> + <?= javascript_tag("js/common.js") ?> + <?= javascript_tag("lib/dojo/tt-rss-layer.js") ?> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> + <link rel="shortcut icon" type="image/png" href="images/favicon.png"> + <link rel="icon" type="image/png" sizes="72x72" href="images/favicon-72px.png"> + <style type="text/css"> + @media (prefers-color-scheme: dark) { + body { + background : #303030; + } + } - $confirm_str = str_replace("'", "\'", __('Subscribe to %s in Tiny Tiny RSS?')); + body.css_loading * { + display : none; + } + </style> + </head> + <body class='flat ttrss_utility css_loading'> + <script type="text/javascript"> + const UtilityApp = { + init: function() { + require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox', 'dijit/form/Form', + 'dijit/form/Select','dijit/form/TextBox','dijit/form/ValidationTextBox'], function(parser, ready){ + ready(function() { + parser.parse(); + }); + }); + } + }; + </script> + <div class="container"> + <h1><?= __("Subscribe to feed...") ?></h1> + <div class='content'> + <?php - $bm_url = htmlspecialchars("javascript:{if(confirm('$confirm_str'.replace('%s',window.location.href)))window.location.href='$bm_subscribe_url'+encodeURIComponent(window.location.href)}"); + if (!$feed_url || !validate_csrf($csrf_token)) { + ?> + <form method="post" action='public.php'> + <?= \Controls\public_method_tags($this, "subscribe") ?> + <?= \Controls\hidden_tag("csrf_token", $_SESSION["csrf_token"]) ?> - print "<p><label class='dijitButton'>"; - print "<a href=\"$bm_url\">" . __('Subscribe in Tiny Tiny RSS'). "</a>"; - print "</label></p>"; + <fieldset> + <label>Feed or site URL:</label> + <input style="width: 300px" dojoType="dijit.form.ValidationTextBox" required="1" name="feed_url" value="<?= htmlspecialchars($feed_url) ?>"> + </fieldset> - print "<h3>" . __("Use this bookmarklet to publish arbitrary pages using Tiny Tiny RSS") . "</h3>"; + <button class="alt-primary" dojoType="dijit.form.Button" type="submit"> + <?= __("Subscribe") ?> + </button> - print "<label class='dijitButton'>"; - $bm_url = htmlspecialchars("javascript:(function(){var d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),f='".get_self_url_prefix()."/public.php?op=sharepopup',l=d.location,e=encodeURIComponent,g=f+'&title='+((e(s))?e(s):e(document.title))+'&url='+e(l.href);function a(){if(!w.open(g,'t','toolbar=0,resizable=0,scrollbars=1,status=1,width=500,height=250')){l.href=g;}}a();})()"); - print "<a href=\"$bm_url\">" . __('Share with Tiny Tiny RSS'). "</a>"; - print "</label>"; + <a href="index.php"><?= __("Return to Tiny Tiny RSS") ?></a> + </form> + <?php + } else { - print "<button dojoType='dijit.form.Button' class='alt-info' onclick='window.open(\"https://tt-rss.org/wiki/ShareAnything\")'> - <i class='material-icons'>help</i> ".__("More info...")."</button>"; + $rc = Feeds::_subscribe($feed_url); + $feed_urls = false; - print "</div>"; #pane + switch ($rc['code']) { + case 0: + print_warning(T_sprintf("Already subscribed to <b>%s</b>.", $feed_url)); + break; + case 1: + print_notice(T_sprintf("Subscribed to <b>%s</b>.", $feed_url)); + break; + case 2: + print_error(T_sprintf("Could not subscribe to <b>%s</b>.", $feed_url)); + break; + case 3: + print_error(T_sprintf("No feeds found in <b>%s</b>.", $feed_url)); + break; + case 4: + $feed_urls = $rc["feeds"]; + break; + case 5: + print_error(T_sprintf("Could not subscribe to <b>%s</b>.<br>Can't download the Feed URL.", $feed_url)); + break; + } - } - } + if ($feed_urls) { + ?> + <form method='post' action='public.php'> + <?= \Controls\public_method_tags($this, "subscribe") ?> + <?= \Controls\hidden_tag("csrf_token", $_SESSION["csrf_token"]) ?> + + <fieldset> + <label style='display : inline'><?= __("Multiple feed URLs found:") ?></label> + <select name='feed_url' dojoType='dijit.form.Select'> + <?php foreach ($feed_urls as $url => $name) { ?> + <option value="<?= htmlspecialchars($url) ?>"><?= htmlspecialchars($name) ?></option> + <?php } ?> + </select> + </fieldset> + + <button class='alt-primary' dojoType='dijit.form.Button' type='submit'><?= __("Subscribe to selected feed") ?></button> + <a href='index.php'><?= __("Return to Tiny Tiny RSS") ?></a> + </form> + <?php + } + + if ($rc['code'] <= 2) { + $feed_id = Feeds::_find_by_url($feed_url, $_SESSION["uid"]); + } else { + $feed_id = 0; + } + + if ($feed_id) { + ?> + <form method='GET' action="<?= htmlspecialchars(get_self_url_prefix() . "/prefs.php") ?>"> + <input type='hidden' name='tab' value='feeds'> + <input type='hidden' name='method' value='editfeed'> + <input type='hidden' name='methodparam' value="<?= $feed_id ?>"> + <button dojoType='dijit.form.Button' class='alt-info' type='submit'><?= __("Edit subscription options") ?></button> + <a href='index.php'><?= __("Return to Tiny Tiny RSS") ?></a> + </form> + <?php + } else { + ?> + <a href='index.php'><?= __("Return to Tiny Tiny RSS") ?></a> + <?php + } + } + ?> + </div> + </div> + </body> + </html> + <?php + } else { + Handler_Public::_render_login_form(); + } + } + + function sharepopup() { + if (Config::get(Config::SINGLE_USER_MODE)) { + UserHelper::login_sequence(); + } + + header('Content-Type: text/html; charset=utf-8'); + ?> + <!DOCTYPE html> + <html> + <head> + <title><?= __("Share with Tiny Tiny RSS") ?></title> + <?= javascript_tag("lib/dojo/dojo.js") ?> + <?= javascript_tag("js/utility.js") ?> + <?= javascript_tag("js/common.js") ?> + <?= javascript_tag("lib/dojo/tt-rss-layer.js") ?> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> + <link rel="shortcut icon" type="image/png" href="images/favicon.png"> + <link rel="icon" type="image/png" sizes="72x72" href="images/favicon-72px.png"> + <style type="text/css"> + @media (prefers-color-scheme: dark) { + body { + background : #303030; + } + } + + body.css_loading * { + display : none; + } + </style> + </head> + <body class='flat ttrss_utility share_popup css_loading'> + <script type="text/javascript"> + const UtilityApp = { + init: function() { + require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox', 'dijit/form/Form', + 'dijit/form/Select','dijit/form/TextBox','dijit/form/ValidationTextBox'], function(parser, ready){ + ready(function() { + parser.parse(); + + /* new Ajax.Autocompleter('labels_value', 'labels_choices', + "backend.php?op=rpc&method=completeLabels", + { tokens: ',', paramName: "search" }); */ + }); + }); + } + }; + </script> + <div class="content"> + + <?php + if ($_SESSION["uid"]) { + + $action = clean($_REQUEST["action"] ?? ""); + + if ($action == 'share') { + + $title = strip_tags(clean($_REQUEST["title"])); + $url = strip_tags(clean($_REQUEST["url"])); + $content = strip_tags(clean($_REQUEST["content"])); + $labels = strip_tags(clean($_REQUEST["labels"])); + + Article::_create_published_article($title, $url, $content, $labels, + $_SESSION["uid"]); + + ?> + <script type="text/javascript"> + window.close(); + </script> + <?php + + } else { + $title = htmlspecialchars(clean($_REQUEST["title"])); + $url = htmlspecialchars(clean($_REQUEST["url"])); + + ?> + <form method='post' action='public.php'> + + <?= \Controls\public_method_tags($this, "sharepopup") ?> + <?= \Controls\hidden_tag("csrf_token", $_SESSION["csrf_token"]) ?> + <?= \Controls\hidden_tag("action", "share") ?> + + <fieldset> + <label><?= __("Title:") ?></label> + <input style='width : 270px' dojoType='dijit.form.TextBox' name='title' value="<?= $title ?>"> + </fieldset> + + <fieldset> + <label><?= __("URL:") ?></label> + <input style='width : 270px' name='url' dojoType='dijit.form.TextBox' value="<?= $url ?>"> + </fieldset> + + <fieldset> + <label><?= __("Content:") ?></label> + <input style='width : 270px' name='content' dojoType='dijit.form.TextBox' value=""> + </fieldset> + + <fieldset> + <label><?= __("Labels:") ?></label> + <input style='width : 270px' name='labels' dojoType='dijit.form.TextBox' id="labels_value" + placeholder='Alpha, Beta, Gamma' value=""> + <!-- <div class="autocomplete" id="labels_choices" + style="display : block"></div> --> + </fieldset> + + <hr/> + + <fieldset> + <?= \Controls\submit_tag(__("Share")) ?> + <?= \Controls\button_tag(__("Cancel"), "", ["onclick" => "window.close()"]) ?> + <span class="text-muted small"><?= __("Shared article will appear in the Published feed.") ?></span> + </fieldset> + + </form> + <?php + + } + + } else { + print_error("Not logged in"); + ?> + + <form action="public.php?return=<?= urlencode(make_self_url()) ?>" method="post"> + + <input type="hidden" name="op" value="login"> + + <fieldset> + <label><?= __("Login:") ?></label> + <input name="login" id="login" dojoType="dijit.form.TextBox" type="text" + onchange="fetchProfiles()" onfocus="fetchProfiles()" onblur="fetchProfiles()" + required="1" value="<?= $_SESSION["fake_login"] ?>" /> + </fieldset> + + <fieldset> + <label><?= __("Password:") ?></label> + + <input type="password" name="password" required="1" + dojoType="dijit.form.TextBox" + class="input input-text" + value="<?= $_SESSION["fake_password"] ?>"/> + </fieldset> + + <hr/> + + <fieldset> + <label> </label> + + <button dojoType="dijit.form.Button" type="submit" class="alt-primary"><?= __('Log in') ?></button> + </fieldset> + + </form> + <?php + } + ?> + </div> + </body> + </html> + <?php + } + + + function hook_prefs_tab($args) { + if ($args != "prefFeeds") + return; + + $bm_subscribe_url = $this->host->get_public_method_url($this, "subscribe"); + $bm_share_url = $this->host->get_public_method_url($this, "sharepopup"); + + $confirm_str = str_replace("'", "\'", __('Subscribe to %s in Tiny Tiny RSS?')); + + $bm_subscribe_url = htmlspecialchars("javascript:{if(confirm('$confirm_str'.replace('%s',window.location.href)))window.location.href='$bm_subscribe_url&feed_url='+encodeURIComponent(window.location.href)}"); + $bm_share_url = htmlspecialchars("javascript:(function(){var d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),f='$bm_share_url',l=d.location,e=encodeURIComponent,g=f+'&title='+((e(s))?e(s):e(document.title))+'&url='+e(l.href);function a(){if(!w.open(g,'t','toolbar=0,resizable=0,scrollbars=1,status=1,width=500,height=250')){l.href=g;}}a();})()"); + ?> + + <div dojoType="dijit.layout.AccordionPane" + title="<i class='material-icons'>bookmark</i> <?= __('Bookmarklets') ?>"> + + <h3><?= __("Drag the link below to your browser toolbar, open the feed you're interested in in your browser and click on the link to subscribe to it.") ?></h3> + + <label class='dijitButton'> + <a href="<?= $bm_subscribe_url ?>"><?= __('Subscribe in Tiny Tiny RSS') ?></a> + </label> + + <h3><?= __("Use this bookmarklet to publish arbitrary pages using Tiny Tiny RSS") ?></h3> + + <label class='dijitButton'> + <a href="<?= $bm_share_url ?>"><?= __('Share with Tiny Tiny RSS') ?></a> + </label> + + <?= \Controls\button_tag(\Controls\icon("help") . " " . __("More info..."), "", + ["class" => 'alt-info', "onclick" => "window.open('https://tt-rss.org/wiki/ShareAnything')"]) ?> + + </div> + + <?php + } function api_version() { return 2; diff --git a/plugins/cache_starred_images/init.php b/plugins/cache_starred_images/init.php index 9dd4cd49d..2dbdb99cc 100755 --- a/plugins/cache_starred_images/init.php +++ b/plugins/cache_starred_images/init.php @@ -5,7 +5,7 @@ class Cache_Starred_Images extends Plugin { private $host; /* @var DiskCache $cache */ private $cache; - private $max_cache_attempts = 5; // per-article + private $max_cache_attempts = 5; // per-article function about() { return array(1.0, @@ -17,18 +17,18 @@ class Cache_Starred_Images extends Plugin { $this->host = $host; $this->cache = new DiskCache("starred-images"); - if ($this->cache->makeDir()) - chmod($this->cache->getDir(), 0777); + if ($this->cache->make_dir()) + chmod($this->cache->get_dir(), 0777); if (!$this->cache->exists(".no-auto-expiry")) $this->cache->touch(".no-auto-expiry"); - if ($this->cache->isWritable()) { + if ($this->cache->is_writable()) { $host->add_hook($host::HOOK_HOUSE_KEEPING, $this); $host->add_hook($host::HOOK_ENCLOSURE_ENTRY, $this); $host->add_hook($host::HOOK_SANITIZE, $this); } else { - user_error("Starred cache directory ".$this->cache->getDir()." is not writable.", E_USER_WARNING); + user_error("Starred cache directory ".$this->cache->get_dir()." is not writable.", E_USER_WARNING); } } @@ -38,13 +38,13 @@ class Cache_Starred_Images extends Plugin { Debug::log("caching media of starred articles for user " . $this->host->get_owner_uid() . "..."); $sth = $this->pdo->prepare("SELECT content, ttrss_entries.title, - ttrss_user_entries.owner_uid, link, site_url, ttrss_entries.id, plugin_data + ttrss_user_entries.owner_uid, link, site_url, ttrss_entries.id, plugin_data FROM ttrss_entries, ttrss_user_entries LEFT JOIN ttrss_feeds ON (ttrss_user_entries.feed_id = ttrss_feeds.id) WHERE ref_id = ttrss_entries.id AND marked = true AND site_url != '' AND - ttrss_user_entries.owner_uid = ? AND + ttrss_user_entries.owner_uid = ? AND plugin_data NOT LIKE '%starred_cache_images%' ORDER BY ".Db::sql_random_function()." LIMIT 100"); @@ -59,7 +59,7 @@ class Cache_Starred_Images extends Plugin { $success = $this->cache_article_images($line["content"], $line["site_url"], $line["owner_uid"], $line["id"]); if ($success) { - $plugin_data = "starred_cache_images,${line['owner_uid']}:" . $line["plugin_data"]; + $plugin_data = "starred_cache_images," . $line["owner_uid"] . ":" . $line["plugin_data"]; $usth->execute([$plugin_data, $line['id']]); } @@ -69,9 +69,12 @@ class Cache_Starred_Images extends Plugin { /* actual housekeeping */ - Debug::log("expiring " . $this->cache->getDir() . "..."); + Debug::log("expiring " . $this->cache->get_dir() . "..."); - $files = glob($this->cache->getDir() . "/*.{png,mp4,status}", GLOB_BRACE); + $files = array_merge( + glob($this->cache->get_dir() . "/*.png"), + glob($this->cache->get_dir() . "/*.mp4"), + glob($this->cache->get_dir() . "/*.status")); $last_article_id = 0; $article_exists = 1; @@ -98,14 +101,14 @@ class Cache_Starred_Images extends Plugin { $local_filename = $article_id . "-" . sha1($enc["content_url"]); if ($this->cache->exists($local_filename)) { - $enc["content_url"] = $this->cache->getUrl($local_filename); + $enc["content_url"] = $this->cache->get_url($local_filename); } return $enc; } function hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id) { - $xpath = new DOMXpath($doc); + $xpath = new DOMXPath($doc); if ($article_id) { $entries = $xpath->query('(//img[@src])|(//video/source[@src])'); @@ -117,7 +120,7 @@ class Cache_Starred_Images extends Plugin { $local_filename = $article_id . "-" . sha1($src); if ($this->cache->exists($local_filename)) { - $entry->setAttribute("src", $this->cache->getUrl($local_filename)); + $entry->setAttribute("src", $this->cache->get_url($local_filename)); $entry->removeAttribute("srcset"); } } @@ -133,7 +136,7 @@ class Cache_Starred_Images extends Plugin { if (!$this->cache->exists($local_filename)) { Debug::log("cache_images: downloading: $url to $local_filename", Debug::$LOG_VERBOSE); - $data = UrlHelper::fetch(["url" => $url, "max_size" => MAX_CACHE_FILE_SIZE]); + $data = UrlHelper::fetch(["url" => $url, "max_size" => Config::get(Config::MAX_CACHE_FILE_SIZE)]); if ($data) return $this->cache->put($local_filename, $data);; @@ -151,37 +154,37 @@ class Cache_Starred_Images extends Plugin { $status_filename = $article_id . "-" . sha1($site_url) . ".status"; /* housekeeping might run as a separate user, in this case status/media might not be writable */ - if (!$this->cache->isWritable($status_filename)) { + if (!$this->cache->is_writable($status_filename)) { Debug::log("status not writable: $status_filename", Debug::$LOG_VERBOSE); return false; } Debug::log("status: $status_filename", Debug::$LOG_VERBOSE); - if ($this->cache->exists($status_filename)) - $status = json_decode($this->cache->get($status_filename), true); - else - $status = []; + if ($this->cache->exists($status_filename)) + $status = json_decode($this->cache->get($status_filename), true); + else + $status = ["attempt" => 0]; - $status["attempt"] += 1; + $status["attempt"] += 1; - // only allow several download attempts for article - if ($status["attempt"] > $this->max_cache_attempts) { - Debug::log("too many attempts for $site_url", Debug::$LOG_VERBOSE); - return false; - } + // only allow several download attempts for article + if ($status["attempt"] > $this->max_cache_attempts) { + Debug::log("too many attempts for $site_url", Debug::$LOG_VERBOSE); + return false; + } - if (!$this->cache->put($status_filename, json_encode($status))) { - user_error("unable to write status file: $status_filename", E_USER_WARNING); - return false; - } + if (!$this->cache->put($status_filename, json_encode($status))) { + user_error("unable to write status file: $status_filename", E_USER_WARNING); + return false; + } $doc = new DOMDocument(); $has_images = false; $success = false; - if (@$doc->loadHTML('<?xml encoding="UTF-8">' . $content)) { + if (@$doc->loadHTML('<?xml encoding="UTF-8">' . $content)) { $xpath = new DOMXPath($doc); $entries = $xpath->query('(//img[@src])|(//video/source[@src])'); @@ -203,11 +206,11 @@ class Cache_Starred_Images extends Plugin { $esth = $this->pdo->prepare("SELECT content_url FROM ttrss_enclosures WHERE post_id = ? AND (content_type LIKE '%image%' OR content_type LIKE '%video%')"); - if ($esth->execute([$article_id])) { - while ($enc = $esth->fetch()) { + if ($esth->execute([$article_id])) { + while ($enc = $esth->fetch()) { - $has_images = true; - $url = rewrite_relative_url($site_url, $enc["content_url"]); + $has_images = true; + $url = rewrite_relative_url($site_url, $enc["content_url"]); if ($this->cache_url($article_id, $url)) { $success = true; diff --git a/plugins/close_button/init.php b/plugins/close_button/init.php index a2ba89478..4f33d1af0 100644 --- a/plugins/close_button/init.php +++ b/plugins/close_button/init.php @@ -15,7 +15,7 @@ class Close_Button extends Plugin { } function get_css() { - return "i.icon-close-article { color : red; }"; + return ".post .header .buttons i.material-icons.icon-close-article { color : red; }"; } function hook_article_button($line) { diff --git a/plugins/mail/init.php b/plugins/mail/init.php index 40d147fc9..467f8294a 100644 --- a/plugins/mail/init.php +++ b/plugins/mail/init.php @@ -15,10 +15,15 @@ class Mail extends Plugin { $host->add_hook($host::HOOK_ARTICLE_BUTTON, $this); $host->add_hook($host::HOOK_PREFS_TAB, $this); + $host->add_hook($host::HOOK_HEADLINE_TOOLBAR_SELECT_MENU_ITEM, $this); } function get_js() { - return file_get_contents(dirname(__FILE__) . "/mail.js"); + return file_get_contents(__DIR__ . "/mail.js"); + } + + function hook_headline_toolbar_select_menu_item($feed_id, $is_cat) { + return "<div dojoType='dijit.MenuItem' onclick='Plugins.Mail.send()'>".__('Forward by email')."</div>"; } function save() { @@ -32,42 +37,38 @@ class Mail extends Plugin { function hook_prefs_tab($args) { if ($args != "prefPrefs") return; - print "<div dojoType=\"dijit.layout.AccordionPane\" - title=\"<i class='material-icons'>mail</i> ".__('Mail plugin')."\">"; + $addresslist = $this->host->get($this, "addresslist"); - print "<p>" . __("You can set predefined email addressed here (comma-separated list):") . "</p>"; + ?> - print "<form dojoType=\"dijit.form.Form\">"; + <div dojoType="dijit.layout.AccordionPane" + title="<i class='material-icons'>mail</i> <?= __('Mail plugin') ?>"> - print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\"> - evt.preventDefault(); - if (this.validate()) { - console.log(dojo.objectToQuery(this.getValues())); - new Ajax.Request('backend.php', { - parameters: dojo.objectToQuery(this.getValues()), - onComplete: function(transport) { - Notify.info(transport.responseText); - } - }); - //this.reset(); - } - </script>"; + <form dojoType="dijit.form.Form"> + <?= \Controls\pluginhandler_tags($this, "save") ?> - print_hidden("op", "pluginhandler"); - print_hidden("method", "save"); - print_hidden("plugin", "mail"); + <script type="dojo/method" event="onSubmit" args="evt"> + evt.preventDefault(); + if (this.validate()) { + Notify.progress('Saving data...', true); + xhr.post("backend.php", this.getValues(), (reply) => { + Notify.info(reply); + }) + } + </script> - $addresslist = $this->host->get($this, "addresslist"); + <header><?= __("You can set predefined email addressed here (comma-separated list):") ?></header> - print "<textarea dojoType=\"dijit.form.SimpleTextarea\" style='font-size : 12px; width : 50%' rows=\"3\" - name='addresslist'>$addresslist</textarea>"; + <textarea dojoType="dijit.form.SimpleTextarea" style='font-size : 12px; width : 50%' rows="3" + name='addresslist'><?= $addresslist ?></textarea> - print "<p><button dojoType=\"dijit.form.Button\" type=\"submit\">". - __("Save")."</button>"; + <hr/> - print "</form>"; + <?= \Controls\submit_tag(__("Save")) ?> - print "</div>"; + </form> + </div> + <?php } function hook_article_button($line) { @@ -78,12 +79,9 @@ class Mail extends Plugin { function emailArticle() { - $ids = explode(",", $_REQUEST['param']); + $ids = explode(",", clean($_REQUEST['ids'])); $ids_qmarks = arr_qmarks($ids); - print_hidden("op", "pluginhandler"); - print_hidden("plugin", "mail"); - print_hidden("method", "sendEmail"); $sth = $this->pdo->prepare("SELECT email, full_name FROM ttrss_users WHERE id = ?"); @@ -100,9 +98,6 @@ class Mail extends Plugin { if (!$user_name) $user_name = $_SESSION['name']; - print_hidden("from_email", "$user_email"); - print_hidden("from_name", "$user_name"); - $tpl = new Templator(); $tpl->readTemplateFromFile("email_article_template.txt"); @@ -143,46 +138,56 @@ class Mail extends Plugin { $content = ""; $tpl->generateOutputToString($content); - print "<table width='100%'><tr><td>"; - $addresslist = explode(",", $this->host->get($this, "addresslist")); - print __('To:'); - - print "</td><td>"; - -/* print "<input dojoType=\"dijit.form.ValidationTextBox\" required=\"true\" - style=\"width : 30em;\" - name=\"destination\" id=\"emailArticleDlg_destination\">"; */ - - print_select("destination", "", $addresslist, 'style="width: 30em" dojoType="dijit.form.ComboBox"'); - -/* print "<div class=\"autocomplete\" id=\"emailArticleDlg_dst_choices\" - style=\"z-index: 30; display : none\"></div>"; */ - - print "</td></tr><tr><td>"; - - print __('Subject:'); - - print "</td><td>"; - - print "<input dojoType='dijit.form.ValidationTextBox' required='true' - style='width : 30em;' name='subject' value=\"$subject\" id='subject'>"; - - print "</td></tr>"; - - print "<tr><td colspan='2'><textarea dojoType='dijit.form.SimpleTextarea' - style='height : 200px; font-size : 12px; width : 98%' rows=\"20\" - name='content'>$content</textarea>"; - - print "</td></tr></table>"; - - print "<footer>"; - print "<button dojoType='dijit.form.Button' onclick=\"dijit.byId('emailArticleDlg').execute()\">".__('Send e-mail')."</button> "; - print "<button dojoType='dijit.form.Button' onclick=\"dijit.byId('emailArticleDlg').hide()\">".__('Cancel')."</button>"; - print "</footer>"; - - //return; + ?> + + <form dojoType='dijit.form.Form'> + + <?= \Controls\pluginhandler_tags($this, "sendemail") ?> + + <?= \Controls\hidden_tag("from_email", $user_email) ?> + <?= \Controls\hidden_tag("from_name", $user_name) ?> + + <script type='dojo/method' event='onSubmit' args='evt'> + evt.preventDefault(); + if (this.validate()) { + xhr.json("backend.php", this.getValues(), (reply) => { + if (reply && reply.error) + Notify.error(reply.error); + else + this.hide(); + }); + } + </script> + + <section> + <fieldset class='narrow'> + <label><?= __('To:') ?></label> + <?= \Controls\select_tag("destination", "", $addresslist, + ["style" => "width: 380px", "required" => 1, "dojoType" => "dijit.form.ComboBox"]) ?> + </fieldset> + </section> + + <section> + <fieldset class='narrow'> + <label><?= __('Subject:') ?></label> + <input dojoType='dijit.form.ValidationTextBox' required='true' + style='width : 380px' name='subject' value="<?= htmlspecialchars($subject) ?>" id='subject'> + </fieldset> + </section> + + <textarea dojoType='dijit.form.SimpleTextarea' + style='height : 200px; font-size : 12px; width : 98%' rows="20" + name='content'><?= $content ?></textarea> + + <footer> + <?= \Controls\submit_tag(__('Send email')) ?> + <?= \Controls\cancel_dialog_tag(__('Cancel')) ?> + </footer> + + </form> + <?php } function sendEmail() { @@ -223,20 +228,6 @@ class Mail extends Plugin { print json_encode($reply); } - /* function completeEmails() { - $search = $_REQUEST["search"]; - - print "<ul>"; - - foreach ($_SESSION['stored_emails'] as $email) { - if (strpos($email, $search) !== false) { - print "<li>$email</li>"; - } - } - - print "</ul>"; - } */ - function api_version() { return 2; } diff --git a/plugins/mail/mail.js b/plugins/mail/mail.js index 5ddc0dc41..d2bafe0e9 100644 --- a/plugins/mail/mail.js +++ b/plugins/mail/mail.js @@ -1,4 +1,4 @@ -/* global Plugins, Headlines, xhrJson, Notify, fox, __ */ +/* global Plugins, Headlines, dojo, App, xhr, Notify, fox, __ */ Plugins.Mail = { send: function(id) { @@ -13,14 +13,11 @@ Plugins.Mail = { id = ids.toString(); } - const query = "backend.php?op=pluginhandler&plugin=mail&method=emailArticle¶m=" + encodeURIComponent(id); - const dialog = new fox.SingleUseDialog({ - id: "emailArticleDlg", title: __("Forward article by email"), execute: function () { if (this.validate()) { - xhrJson("backend.php", this.attr('value'), (reply) => { + xhr.json("backend.php", this.attr('value'), (reply) => { if (reply) { const error = reply['error']; @@ -35,16 +32,16 @@ Plugins.Mail = { }); } }, - href: query + content: __("Loading, please wait...") }); - /* var tmph = dojo.connect(dialog, 'onLoad', function() { - dojo.disconnect(tmph); + const tmph = dojo.connect(dialog, 'onShow', function () { + dojo.disconnect(tmph); - new Ajax.Autocompleter('emailArticleDlg_destination', 'emailArticleDlg_dst_choices', - "backend.php?op=pluginhandler&plugin=mail&method=completeEmails", - { tokens: '', paramName: "search" }); - }); */ + xhr.post("backend.php", App.getPhArgs("mail", "emailArticle", {ids: id}), (reply) => { + dialog.attr('content', reply); + }); + }); dialog.show(); }, diff --git a/plugins/mail/mail.png b/plugins/mail/mail.png Binary files differdeleted file mode 100644 index 7348aed77..000000000 --- a/plugins/mail/mail.png +++ /dev/null diff --git a/plugins/mailto/init.js b/plugins/mailto/init.js index ae68bf49b..4a9557249 100644 --- a/plugins/mailto/init.js +++ b/plugins/mailto/init.js @@ -1,4 +1,4 @@ -/* global Plugins, Headlines, fox, __ */ +/* global Plugins, Headlines, xhr, dojo, fox, __ */ Plugins.Mailto = { send: function (id) { @@ -13,12 +13,19 @@ Plugins.Mailto = { id = ids.toString(); } - const query = "backend.php?op=pluginhandler&plugin=mailto&method=emailArticle¶m=" + encodeURIComponent(id); - const dialog = new fox.SingleUseDialog({ - id: "emailArticleDlg", - title: __("Forward article by email"), - href: query}); + title: __("Forward article by email (mailto:)"), + content: __("Loading, please wait...") + }); + + const tmph = dojo.connect(dialog, 'onShow', function () { + dojo.disconnect(tmph); + + xhr.post("backend.php", App.getPhArgs("mailto", "emailArticle", {ids: id}), (reply) => { + dialog.attr('content', reply); + }); + }); + dialog.show(); } diff --git a/plugins/mailto/init.php b/plugins/mailto/init.php index 390984b71..c34b400ce 100644 --- a/plugins/mailto/init.php +++ b/plugins/mailto/init.php @@ -12,21 +12,26 @@ class MailTo extends Plugin { $this->host = $host; $host->add_hook($host::HOOK_ARTICLE_BUTTON, $this); + $host->add_hook($host::HOOK_HEADLINE_TOOLBAR_SELECT_MENU_ITEM, $this); + } + + function hook_headline_toolbar_select_menu_item($feed_id, $is_cat) { + return "<div dojoType='dijit.MenuItem' onclick='Plugins.Mailto.send()'>".__('Forward by email (mailto:)')."</div>"; } function get_js() { - return file_get_contents(dirname(__FILE__) . "/init.js"); + return file_get_contents(__DIR__ . "/init.js"); } function hook_article_button($line) { return "<i class='material-icons' style=\"cursor : pointer\" onclick=\"Plugins.Mailto.send(".$line["id"].")\" - title='".__('Forward by email')."'>mail_outline</i>"; + title='".__('Forward by email (mailto:)')."'>mail_outline</i>"; } function emailArticle() { - $ids = explode(",", $_REQUEST['param']); + $ids = explode(",", clean($_REQUEST['ids'])); $ids_qmarks = arr_qmarks($ids); $tpl = new Templator(); @@ -37,7 +42,6 @@ class MailTo extends Plugin { //$tpl->setVariable('USER_EMAIL', $user_email, true); $tpl->setVariable('TTRSS_HOST', $_SERVER["HTTP_HOST"], true); - $sth = $this->pdo->prepare("SELECT DISTINCT link, content, title FROM ttrss_user_entries, ttrss_entries WHERE id = ref_id AND id IN ($ids_qmarks) AND owner_uid = ?"); @@ -65,25 +69,23 @@ class MailTo extends Plugin { $content = ""; $tpl->generateOutputToString($content); - $mailto_link = htmlspecialchars("mailto:?subject=".rawurlencode($subject). - "&body=".rawurlencode($content)); - - print __("Clicking the following link to invoke your mail client:"); - - print "<div class='panel text-center'>"; - print "<a target=\"_blank\" href=\"$mailto_link\">". - __("Forward selected article(s) by email.")."</a>"; - print "</div>"; + $mailto_link = "mailto:?subject=".rawurlencode($subject)."&body=".rawurlencode($content); - print __("You should be able to edit the message before sending in your mail client."); + ?> - print "<p>"; + <section> + <div class='panel text-center'> + <a target="_blank" href="<?= htmlspecialchars($mailto_link) ?>"> + <?= __("Click to open your mail client") ?> + </a> + </div> + </section> - print "<footer class='text-center'>"; - print "<button dojoType='dijit.form.Button' onclick=\"dijit.byId('emailArticleDlg').hide()\">".__('Close this dialog')."</button>"; - print "</footer>"; + <footer class='text-center'> + <?= \Controls\submit_tag(__('Close this dialog')) ?> + </footer> - //return; + <?php } function api_version() { diff --git a/plugins/mailto/mail.png b/plugins/mailto/mail.png Binary files differdeleted file mode 100644 index 2c49f78a6..000000000 --- a/plugins/mailto/mail.png +++ /dev/null diff --git a/plugins/note/init.php b/plugins/note/init.php index 3c2ca0075..52f7be3eb 100644 --- a/plugins/note/init.php +++ b/plugins/note/init.php @@ -17,7 +17,7 @@ class Note extends Plugin { } function get_js() { - return file_get_contents(dirname(__FILE__) . "/note.js"); + return file_get_contents(__DIR__ . "/note.js"); } @@ -27,48 +27,42 @@ class Note extends Plugin { } function edit() { - $param = $_REQUEST['param']; + $id = clean($_REQUEST['id']); $sth = $this->pdo->prepare("SELECT note FROM ttrss_user_entries WHERE ref_id = ? AND owner_uid = ?"); - $sth->execute([$param, $_SESSION['uid']]); + $sth->execute([$id, $_SESSION['uid']]); if ($row = $sth->fetch()) { $note = $row['note']; - print_hidden("id", "$param"); - print_hidden("op", "pluginhandler"); - print_hidden("method", "setNote"); - print_hidden("plugin", "note"); + print \Controls\hidden_tag("id", $id); + print \Controls\pluginhandler_tags($this, "setnote"); - print "<textarea dojoType='dijit.form.SimpleTextarea' + ?> + <textarea dojoType='dijit.form.SimpleTextarea' style='font-size : 12px; width : 98%; height: 100px;' - name='note'>$note</textarea>"; - + name='note'><?= $note ?></textarea> + <?php } - - print "<footer class='text-center'>"; - print "<button dojoType=\"dijit.form.Button\" - onclick=\"dijit.byId('editNoteDlg').execute()\">".__('Save')."</button> "; - print "<button dojoType=\"dijit.form.Button\" - onclick=\"dijit.byId('editNoteDlg').hide()\">".__('Cancel')."</button>"; - print "</footer>"; - + ?> + <footer class='text-center'> + <?= \Controls\submit_tag(__('Save')) ?> + <?= \Controls\cancel_dialog_tag(__('Cancel')) ?> + </footer> + <?php } function setNote() { - $id = $_REQUEST["id"]; - $note = trim(strip_tags($_REQUEST["note"])); + $id = (int)clean($_REQUEST["id"]); + $note = clean($_REQUEST["note"]); $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET note = ? WHERE ref_id = ? AND owner_uid = ?"); $sth->execute([$note, $id, $_SESSION['uid']]); - $formatted_note = Article::format_article_note($id, $note); - - print json_encode(array("note" => $formatted_note, - "raw_length" => mb_strlen($note))); + print json_encode(["id" => $id, "note" => $note]); } function api_version() { diff --git a/plugins/note/note.js b/plugins/note/note.js index ab2ed9208..a46acb355 100644 --- a/plugins/note/note.js +++ b/plugins/note/note.js @@ -1,36 +1,39 @@ -/* global Plugins, xhrJson, Notify, fox, __ */ +/* global dojo, Plugins, xhr, App, Notify, fox, __ */ Plugins.Note = { edit: function(id) { - const query = "backend.php?op=pluginhandler&plugin=note&method=edit¶m=" + encodeURIComponent(id); - const dialog = new fox.SingleUseDialog({ - id: "editNoteDlg", title: __("Edit article note"), execute: function () { if (this.validate()) { Notify.progress("Saving article note...", true); - xhrJson("backend.php", this.attr('value'), (reply) => { + xhr.json("backend.php", this.attr('value'), (reply) => { Notify.close(); dialog.hide(); if (reply) { - const elem = $("POSTNOTE-" + id); - - if (elem) { - elem.innerHTML = reply.note; + App.findAll(`div[data-note-for="${reply.id}"]`).forEach((elem) => { + elem.querySelector(".body").innerHTML = reply.note; - if (reply.raw_length != 0) - Element.show(elem); + if (reply.note) + elem.show(); else - Element.hide(elem); - } + elem.hide(); + }); } }); } }, - href: query, + content: __("Loading, please wait...") + }); + + const tmph = dojo.connect(dialog, 'onShow', function () { + dojo.disconnect(tmph); + + xhr.post("backend.php", App.getPhArgs("note", "edit", {id: id}), (reply) => { + dialog.attr('content', reply); + }); }); dialog.show(); diff --git a/plugins/nsfw/init.js b/plugins/nsfw/init.js index adb6d43c0..4bc2443e8 100644 --- a/plugins/nsfw/init.js +++ b/plugins/nsfw/init.js @@ -1,7 +1,12 @@ -function nsfwShow(elem) { - let content = elem.parentNode.getElementsBySelector("div.nswf.content")[0]; +/* global Plugins */ - if (content) { - Element.toggle(content); +Plugins.NSFW = { + toggle: function(elem) { + const content = elem.domNode.parentNode.querySelector(".nswf.content"); + + if (content) { + Element.toggle(content); + } } } + diff --git a/plugins/nsfw/init.php b/plugins/nsfw/init.php index 02344eb14..7c5b8d00f 100644 --- a/plugins/nsfw/init.php +++ b/plugins/nsfw/init.php @@ -19,7 +19,7 @@ class NSFW extends Plugin { } function get_js() { - return file_get_contents(dirname(__FILE__) . "/init.js"); + return file_get_contents(__DIR__ . "/init.js"); } function hook_render_article($article) { @@ -27,74 +27,60 @@ class NSFW extends Plugin { $a_tags = array_map("trim", explode(",", $article["tag_cache"])); if (count(array_intersect($tags, $a_tags)) > 0) { - $article["content"] = "<div class='nswf wrapper'><button onclick=\"nsfwShow(this)\">".__("Not work safe (click to toggle)")."</button> - <div class='nswf content' style='display : none'>".$article["content"]."</div></div>"; + $article["content"] = "<div class='nswf wrapper'>". + \Controls\button_tag(__("Not work safe (click to toggle)"), '', ['onclick' => 'Plugins.NSFW.toggle(this)']). + "<div class='nswf content' style='display : none'>".$article["content"]."</div> + </div>"; } return $article; } function hook_render_article_cdm($article) { - $tags = array_map("trim", explode(",", $this->host->get($this, "tags"))); - $a_tags = array_map("trim", explode(",", $article["tag_cache"])); - - if (count(array_intersect($tags, $a_tags)) > 0) { - $article["content"] = "<div class='nswf wrapper'><button onclick=\"nsfwShow(this)\">".__("Not work safe (click to toggle)")."</button> - <div class='nswf content' style='display : none'>".$article["content"]."</div></div>"; - } - - return $article; + return $this->hook_render_article($article); } function hook_prefs_tab($args) { if ($args != "prefPrefs") return; - print "<div dojoType=\"dijit.layout.AccordionPane\" - title=\"<i class='material-icons'>extension</i> ".__("NSFW Plugin")."\">"; - - print "<br/>"; - $tags = $this->host->get($this, "tags"); - print "<form dojoType=\"dijit.form.Form\">"; - - print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\"> - evt.preventDefault(); - if (this.validate()) { - new Ajax.Request('backend.php', { - parameters: dojo.objectToQuery(this.getValues()), - onComplete: function(transport) { - Notify.info(transport.responseText); - } - }); - //this.reset(); - } - </script>"; - - print_hidden("op", "pluginhandler"); - print_hidden("method", "save"); - print_hidden("plugin", "nsfw"); + ?> + <div dojoType="dijit.layout.AccordionPane" + title="<i class='material-icons'>extension</i> <?= __("NSFW Plugin") ?>"> + <form dojoType="dijit.form.Form"> - print "<table width=\"100%\" class=\"prefPrefsList\">"; + <?= \Controls\pluginhandler_tags($this, "save") ?> - print "<tr><td width=\"40%\">".__("Tags to consider NSFW (comma-separated)")."</td>"; - print "<td class=\"prefValue\"><input dojoType=\"dijit.form.ValidationTextBox\" required=\"1\" name=\"tags\" value=\"$tags\"></td></tr>"; + <script type="dojo/method" event="onSubmit" args="evt"> + evt.preventDefault(); + if (this.validate()) { + Notify.progress('Saving data...', true); + xhr.post("backend.php", this.getValues(), (reply) => { + Notify.info(reply); + }) + } + </script> - print "</table>"; + <header><?= __("Tags to consider NSFW (comma-separated):") ?></header> - print "<p><button dojoType=\"dijit.form.Button\" type=\"submit\">". - __("Save")."</button>"; + <fieldset> + <textarea dojoType='dijit.form.SimpleTextarea' rows='4' + style='width: 500px; font-size : 12px;' + name='tags'><?= $tags ?></textarea> + </fieldset> - print "</form>"; + <hr/> - print "</div>"; #pane + <?= \Controls\submit_tag(__("Save")) ?> + </form> + </div> + <?php } function save() { - $tags = explode(",", $_POST["tags"]); - $tags = array_map("trim", $tags); - $tags = array_map("mb_strtolower", $tags); - $tags = join(", ", $tags); + $tags = implode(", ", + FeedItem_Common::normalize_categories(explode(",", $_POST["tags"] ?? ""))); $this->host->set($this, "tags", $tags); diff --git a/plugins/share/init.php b/plugins/share/init.php index 0794f5125..37799fba6 100644 --- a/plugins/share/init.php +++ b/plugins/share/init.php @@ -16,19 +16,22 @@ class Share extends Plugin { $host->add_hook($host::HOOK_PREFS_TAB_SECTION, $this); } + function is_public_method($method) { + return $method == "get"; + } + function get_js() { - return file_get_contents(dirname(__FILE__) . "/share.js"); + return file_get_contents(__DIR__ . "/share.js"); } function get_css() { - return file_get_contents(dirname(__FILE__) . "/share.css"); + return file_get_contents(__DIR__ . "/share.css"); } function get_prefs_js() { - return file_get_contents(dirname(__FILE__) . "/share_prefs.js"); + return file_get_contents(__DIR__ . "/share_prefs.js"); } - function unshare() { $id = $_REQUEST['id']; @@ -36,32 +39,30 @@ class Share extends Plugin { AND owner_uid = ?"); $sth->execute([$id, $_SESSION['uid']]); - print "OK"; + print __("Article unshared"); } function hook_prefs_tab_section($id) { if ($id == "prefFeedsPublishedGenerated") { + ?> + <hr/> - print "<h3>" . __("You can disable all articles shared by unique URLs here.") . "</h3>"; - - print "<button class='alt-danger' dojoType='dijit.form.Button' onclick=\"return Plugins.Share.clearKeys()\">". - __('Unshare all articles')."</button> "; - - print "</p>"; + <h2><?= __("You can disable all articles shared by unique URLs here.") ?></h2> + <button class='alt-danger' dojoType='dijit.form.Button' onclick="return Plugins.Share.clearKeys()"> + <?= __('Unshare all articles') ?></button> + <?php } } - // Silent function clearArticleKeys() { $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET uuid = '' WHERE owner_uid = ?"); $sth->execute([$_SESSION['uid']]); - return; + print __("Shared URLs cleared."); } - function newkey() { $id = $_REQUEST['id']; $uuid = uniqid_short(); @@ -70,26 +71,169 @@ class Share extends Plugin { AND owner_uid = ?"); $sth->execute([$uuid, $id, $_SESSION['uid']]); - print json_encode(array("link" => $uuid)); + print json_encode(["link" => $uuid]); } function hook_article_button($line) { - $img_class = $line['uuid'] ? "shared" : ""; + $icon_class = !empty($line['uuid']) ? "is-shared" : ""; - return "<i id='SHARE-IMG-".$line['int_id']."' class='material-icons icon-share $img_class' + return "<i class='material-icons icon-share share-icon-".$line['int_id']." $icon_class' style='cursor : pointer' onclick=\"Plugins.Share.shareArticle(".$line['int_id'].")\" title='".__('Share by URL')."'>link</i>"; } - function shareArticle() { - $param = $_REQUEST['param']; + function get() { + $uuid = clean($_REQUEST["key"] ?? ""); + + if ($uuid) { + $sth = $this->pdo->prepare("SELECT ref_id, owner_uid + FROM ttrss_user_entries WHERE uuid = ?"); + $sth->execute([$uuid]); + + if ($row = $sth->fetch()) { + header("Content-Type: text/html"); + + $id = $row["ref_id"]; + $owner_uid = $row["owner_uid"]; + + $this->format_article($id, $owner_uid); + + return; + } + } + + header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found"); + print "Article not found."; + } + + private function format_article($id, $owner_uid) { + + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT id,title,link,content,feed_id,comments,int_id,lang, + ".SUBSTRING_FOR_DATE."(updated,1,16) as updated, + (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url, + (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title, + (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images, + (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures, + num_comments, + tag_cache, + author, + guid, + note + FROM ttrss_entries,ttrss_user_entries + WHERE id = ? AND ref_id = id AND owner_uid = ?"); + $sth->execute([$id, $owner_uid]); + + if ($line = $sth->fetch()) { + + $line["tags"] = Article::_get_tags($id, $owner_uid, $line["tag_cache"]); + unset($line["tag_cache"]); + + $line["content"] = Sanitizer::sanitize($line["content"], + $line['hide_images'], + $owner_uid, $line["site_url"], false, $line["id"]); + + PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_RENDER_ARTICLE, + function ($result) use (&$line) { + $line = $result; + }, + $line); + + $enclosures = Article::_get_enclosures($line["id"]); + list ($og_image, $og_stream) = Article::_get_image($enclosures, $line['content'], $line["site_url"]); + + $content_decoded = html_entity_decode($line["title"], ENT_NOQUOTES | ENT_HTML401); + $parsed_updated = TimeHelper::make_local_datetime($line["updated"], true, $owner_uid, true); + + $line['content'] = DiskCache::rewrite_urls($line['content']); + + ob_start(); + + ?> + <!DOCTYPE html> + <html> + <head> + <meta http-equiv='Content-Type' content='text/html; charset=utf-8'/> + <title><?= $line["title"] ?></title> + <?= javascript_tag("js/common.js") ?> + <?= javascript_tag("js/utility.js") ?> + <style type='text/css'> + @media (prefers-color-scheme: dark) { + body { + background : #222; + } + } + body.css_loading * { + display : none; + } + </style> + <link rel='shortcut icon' type='image/png' href='images/favicon.png'> + <link rel='icon' type='image/png' sizes='72x72' href='images/favicon-72px.png'> + + <meta property='og:title' content="<?= htmlspecialchars($content_decoded) ?>"> + <meta property='og:description' content="<?= htmlspecialchars( + truncate_string( + preg_replace("/[\r\n\t]/", "", + preg_replace("/ {1,}/", " ", + strip_tags($content_decoded) + ) + ), 500, "...")) ?>"> + </head> + + <?php if ($og_image) { ?> + <meta property='og:image' content="<?= htmlspecialchars($og_image) ?>"> + <?php } ?> + + <body class='flat ttrss_utility ttrss_zoom css_loading'> + <div class='container'> + + <?php if (!empty($line["link"])) { ?> + <h1> + <a target='_blank' rel='noopener noreferrer' + href="<?= htmlspecialchars($line["link"]) ?>"><?= htmlspecialchars($line["title"]) ?></a> + </h1> + <?php } else { ?> + <h1><?= $line["title"] ?></h1> + <?php } ?> + + <div class='content post'> + <div class='header'> + <div class='row'> + <div><?= $line['author'] ?></div> + <div><?= $parsed_updated ?></div> + </div> + </div> + + <div class='content' lang="<?= $line['lang'] ? $line['lang'] : "en" ?>"> + <?= $line["content"] ?> + </div> + </div> + </body> + </html> + <?php + + $rv = ob_get_contents(); + ob_end_clean(); + + PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_FORMAT_ARTICLE, + function ($result) use (&$rv) { + $rv = $result; + }, + $rv, $line); + + print $rv; + } + } + + function shareDialog() { + $id = (int)clean($_REQUEST['id'] ?? 0); $sth = $this->pdo->prepare("SELECT uuid FROM ttrss_user_entries WHERE int_id = ? AND owner_uid = ?"); - $sth->execute([$param, $_SESSION['uid']]); + $sth->execute([$id, $_SESSION['uid']]); if ($row = $sth->fetch()) { - $uuid = $row['uuid']; if (!$uuid) { @@ -97,42 +241,34 @@ class Share extends Plugin { $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET uuid = ? WHERE int_id = ? AND owner_uid = ?"); - $sth->execute([$uuid, $param, $_SESSION['uid']]); + $sth->execute([$uuid, $id, $_SESSION['uid']]); } - print "<header>" . __("You can share this article by the following unique URL:") . "</header>"; + $url_path = $this->host->get_public_method_url($this, "get", ["key" => $uuid]); + ?> - $url_path = get_self_url_prefix(); - $url_path .= "/public.php?op=share&key=$uuid"; + <header><?= __("You can share this article by the following unique URL:") ?></header> - print "<section> + <section> <div class='panel text-center'> - <a id='gen_article_url' href='$url_path' target='_blank' rel='noopener noreferrer'>$url_path</a> + <a class='target-url' href="<?= htmlspecialchars($url_path) ?>" + target='_blank' rel='noopener noreferrer'><?= htmlspecialchars($url_path) ?></a> </div> - </section>"; - - /* if (!label_find_id(__('Shared'), $_SESSION["uid"])) - label_create(__('Shared'), $_SESSION["uid"]); - - label_add_article($ref_id, __('Shared'), $_SESSION['uid']); */ + </section> + <?php } else { - print "Article not found."; + print format_error(__("Article not found.")); } - print "<footer class='text-center'>"; - - print "<button dojoType='dijit.form.Button' onclick=\"return App.dialogOf(this).unshare()\">". - __('Unshare article')."</button>"; - - print "<button dojoType='dijit.form.Button' onclick=\"return App.dialogOf(this).newurl()\">". - __('Generate new URL')."</button>"; - - print "<button dojoType='dijit.form.Button' type='submit' class='alt-primary'>". - __('Close this window')."</button>"; - - print "</footer>"; + ?> + <footer class='text-center'> + <?= \Controls\button_tag(__('Unshare article'), '', ['class' => 'alt-danger', 'onclick' => "App.dialogOf(this).unshare()"]) ?> + <?= \Controls\button_tag(__('Generate new URL'), '', ['onclick' => "App.dialogOf(this).newurl()"]) ?> + <?= \Controls\submit_tag(__("Close this window")) ?> + </footer> + <?php } function api_version() { diff --git a/plugins/share/share.css b/plugins/share/share.css index 00bad68dd..ac9247a54 100644 --- a/plugins/share/share.css +++ b/plugins/share/share.css @@ -1,3 +1,3 @@ -i.icon-share.shared { +i.material-icons.icon-share.is-shared { color : #0a0; }
\ No newline at end of file diff --git a/plugins/share/share.js b/plugins/share/share.js index 3fc42d654..1be9db682 100644 --- a/plugins/share/share.js +++ b/plugins/share/share.js @@ -1,9 +1,7 @@ -/* global Plugins, xhrJson, Notify, fox, xhrPost, __ */ +/* global dojo, Plugins, App, Notify, fox, xhr, __ */ Plugins.Share = { shareArticle: function(id) { - const query = "backend.php?op=pluginhandler&plugin=share&method=shareArticle¶m=" + encodeURIComponent(id); - const dialog = new fox.SingleUseDialog({ id: "shareArticleDlg", title: __("Share article by URL"), @@ -12,25 +10,23 @@ Plugins.Share = { Notify.progress("Trying to change URL...", true); - const query = {op: "pluginhandler", plugin: "share", method: "newkey", id: id}; - - xhrJson("backend.php", query, (reply) => { + xhr.json("backend.php", App.getPhArgs("share", "newkey", {id: id}), (reply) => { if (reply) { const new_link = reply.link; - const e = $('gen_article_url'); + const target = dialog.domNode.querySelector(".target-url"); - if (new_link) { + if (new_link && target) { - e.innerHTML = e.innerHTML.replace(/\&key=.*$/, + target.innerHTML = target.innerHTML.replace(/&key=.*$/, "&key=" + new_link); - e.href = e.href.replace(/\&key=.*$/, + target.href = target.href.replace(/&key=.*$/, "&key=" + new_link); - new Effect.Highlight(e); + const icon = document.querySelector(".share-icon-" + id); - const img = $("SHARE-IMG-" + id); - img.addClassName("shared"); + if (icon) + icon.addClassName("is-shared"); Notify.close(); @@ -44,32 +40,35 @@ Plugins.Share = { }, unshare: function () { if (confirm(__("Remove sharing for this article?"))) { + xhr.post("backend.php", App.getPhArgs("share", "unshare", {id: id}), (reply) => { + Notify.info(reply); - const query = {op: "pluginhandler", plugin: "share", method: "unshare", id: id}; - - xhrPost("backend.php", query, () => { - try { - const img = $("SHARE-IMG-" + id); + const icon = document.querySelector(".share-icon-" + id); - if (img) { - img.removeClassName("shared"); - img.up("div[id*=RROW]").removeClassName("shared"); - } + if (icon) + icon.removeClassName("is-shared"); - dialog.hide(); - } catch (e) { - console.error(e); - } + dialog.hide(); }); } }, - href: query + content: __("Loading, please wait...") }); - dialog.show(); + const tmph = dojo.connect(dialog, 'onShow', function () { + dojo.disconnect(tmph); - const img = $("SHARE-IMG-" + id); - img.addClassName("shared"); + xhr.post("backend.php", App.getPhArgs("share", "shareDialog", {id: id}), (reply) => { + dialog.attr('content', reply) + + const icon = document.querySelector(".share-icon-" + id); + + if (icon) + icon.addClassName("is-shared"); + }); + }); + + dialog.show(); } } diff --git a/plugins/share/share_prefs.js b/plugins/share/share_prefs.js index 071a6667c..d974af618 100644 --- a/plugins/share/share_prefs.js +++ b/plugins/share/share_prefs.js @@ -1,12 +1,12 @@ +/* global Plugins, Notify, xhr, App */ + Plugins.Share = { clearKeys: function() { if (confirm(__("This will invalidate all previously shared article URLs. Continue?"))) { Notify.progress("Clearing URLs..."); - const query = {op: "pluginhandler", plugin: "share", method: "clearArticleKeys"}; - - xhrPost("backend.php", query, () => { - Notify.info("Shared URLs cleared."); + xhr.post("backend.php", App.getPhArgs("share", "clearArticleKeys"), (reply) => { + Notify.info(reply); }); } diff --git a/plugins/shorten_expanded/init.js b/plugins/shorten_expanded/init.js index 30bfac6ba..0abc8c129 100644 --- a/plugins/shorten_expanded/init.js +++ b/plugins/shorten_expanded/init.js @@ -1,3 +1,5 @@ +/* global Plugins, __, require, PluginHost */ + const _shorten_expanded_threshold = 1.5; //window heights Plugins.Shorten_Expanded = { @@ -5,8 +7,8 @@ Plugins.Shorten_Expanded = { const row = $(id); if (row) { - const content = row.select(".content-shrink-wrap")[0]; - const link = row.select(".expand-prompt")[0]; + const content = row.querySelector(".content-shrink-wrap"); + const link = row.querySelector(".expand-prompt"); if (content) content.removeClassName("content-shrink-wrap"); if (link) Element.hide(link); @@ -22,26 +24,26 @@ require(['dojo/_base/kernel', 'dojo/ready'], function (dojo, ready) { window.setTimeout(function() { if (row) { - const c_inner = row.select(".content-inner")[0]; - const c_inter = row.select(".intermediate")[0]; + const content = row.querySelector(".content-inner"); - if (c_inner && c_inter && - row.offsetHeight >= _shorten_expanded_threshold * window.innerHeight) { + //console.log('shorten', row.offsetHeight, 'vs', _shorten_expanded_threshold * window.innerHeight); - let tmp = document.createElement("div"); + if (content && row.offsetHeight >= _shorten_expanded_threshold * window.innerHeight) { - c_inter.select("> *:not([class*='attachments'])").each(function(p) { - p.parentNode.removeChild(p); - tmp.appendChild(p); - }); + const attachments = row.querySelector(".attachments-inline"); // optional - c_inner.innerHTML = `<div class="content-shrink-wrap"> - ${c_inner.innerHTML} - ${tmp.innerHTML}</div> + content.innerHTML = ` + <div class="content-shrink-wrap"> + ${content.innerHTML} + ${attachments ? attachments.innerHTML : ''} + </div> <button dojoType="dijit.form.Button" class="alt-info expand-prompt" onclick="return Plugins.Shorten_Expanded.expand('${row.id}')" href="#"> ${__("Click to expand article")}</button>`; - dojo.parser.parse(c_inner); + if (attachments) + attachments.innerHTML = ""; + + dojo.parser.parse(content); } } }, 150); diff --git a/plugins/toggle_sidebar/init.php b/plugins/toggle_sidebar/init.php index f8ec35a91..19ca960e2 100644 --- a/plugins/toggle_sidebar/init.php +++ b/plugins/toggle_sidebar/init.php @@ -24,7 +24,7 @@ class Toggle_Sidebar extends Plugin { <button dojoType="dijit.form.Button" onclick="Plugins.Toggle_Sidebar.toggle(this)"> <i class="material-icons toggle-sidebar-label" - title="<?php echo __('Toggle sidebar') ?>">chevron_left</i> + title="<?= __('Toggle sidebar') ?>">chevron_left</i> </button> <?php diff --git a/plugins/vf_shared/init.php b/plugins/vf_shared/init.php index 8c38cbf32..1112f6f2f 100644 --- a/plugins/vf_shared/init.php +++ b/plugins/vf_shared/init.php @@ -60,7 +60,7 @@ class VF_Shared extends Plugin { "override_vfeed" => "ttrss_feeds.title AS feed_title," ); - $qfh_ret = Feeds::queryFeedHeadlines($params); + $qfh_ret = Feeds::_get_headlines($params); $qfh_ret[1] = __("Shared articles"); return $qfh_ret; |