diff options
Diffstat (limited to 'classes')
-rwxr-xr-x | classes/article.php | 295 | ||||
-rw-r--r-- | classes/ccache.php | 2 | ||||
-rw-r--r-- | classes/counters.php | 6 | ||||
-rw-r--r-- | classes/dlg.php | 11 | ||||
-rwxr-xr-x | classes/feeds.php | 528 | ||||
-rwxr-xr-x | classes/handler/public.php | 236 | ||||
-rw-r--r-- | classes/labels.php | 4 | ||||
-rwxr-xr-x | classes/pluginhost.php | 3 | ||||
-rwxr-xr-x | classes/pref/feeds.php | 40 | ||||
-rwxr-xr-x | classes/pref/filters.php | 27 | ||||
-rw-r--r-- | classes/pref/labels.php | 39 | ||||
-rw-r--r-- | classes/pref/prefs.php | 89 | ||||
-rw-r--r-- | classes/pref/system.php | 3 | ||||
-rw-r--r-- | classes/pref/users.php | 11 | ||||
-rwxr-xr-x | classes/rpc.php | 2 | ||||
-rwxr-xr-x | classes/rssutils.php | 31 |
16 files changed, 509 insertions, 818 deletions
diff --git a/classes/article.php b/classes/article.php index e5c98b2c5..7253f2ead 100755 --- a/classes/article.php +++ b/classes/article.php @@ -27,6 +27,7 @@ class Article extends Handler_Protected { } } + /* function view() { $id = clean($_REQUEST["id"]); $cids = explode(",", clean($_REQUEST["cids"])); @@ -63,8 +64,9 @@ class Article extends Handler_Protected { } print json_encode($articles); - } + } */ + /* private function catchupArticleById($id, $cmode) { if ($cmode == 0) { @@ -86,6 +88,7 @@ class Article extends Handler_Protected { $feed_id = $this->getArticleFeed($id); CCache::update($feed_id, $_SESSION["uid"]); } + */ static function create_published_article($title, $url, $content, $labels_str, $owner_uid) { @@ -253,6 +256,7 @@ class Article extends Handler_Protected { print json_encode(array("id" => $ids, "score" => (int)$score, + "score_class" => get_score_class($score), "score_pic" => get_score_pic($score))); } @@ -515,9 +519,9 @@ class Article extends Handler_Protected { } if (count($entries_inline) > 0) { - $rv .= "<hr clear='both'/>"; + //$rv .= "<hr clear='both'/>"; foreach ($entries_inline as $entry) { $rv .= $entry; }; - $rv .= "<hr clear='both'/>"; + $rv .= "<br clear='both'/>"; } $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">". @@ -548,276 +552,6 @@ class Article extends Handler_Protected { return $rv; } - static function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) { - if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - - $rv = array(); - - $rv['id'] = $id; - - /* we can figure out feed_id from article id anyway, why do we - * pass feed_id here? let's ignore the argument :(*/ - - $pdo = Db::pdo(); - - $sth = $pdo->prepare("SELECT feed_id FROM ttrss_user_entries - WHERE ref_id = ?"); - $sth->execute([$id]); - $row = $sth->fetch(); - - $feed_id = (int) $row["feed_id"]; - - $rv['feed_id'] = $feed_id; - - //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; }; - - if ($mark_as_read) { - $sth = $pdo->prepare("UPDATE ttrss_user_entries - SET unread = false,last_read = NOW() - WHERE ref_id = ? AND owner_uid = ?"); - $sth->execute([$id, $owner_uid]); - - CCache::update($feed_id, $owner_uid); - } - - $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, - orig_feed_id, - 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_article_tags($id, $owner_uid, $line["tag_cache"]); - unset($line["tag_cache"]); - - $line["content"] = sanitize($line["content"], - $line['hide_images'], - $owner_uid, $line["site_url"], false, $line["id"]); - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) { - $line = $p->hook_render_article($line); - } - - $line['content'] = rewrite_cached_urls($line['content']); - - $num_comments = (int) $line["num_comments"]; - $entry_comments = ""; - - if ($num_comments > 0) { - if ($line["comments"]) { - $comments_url = htmlspecialchars($line["comments"]); - } else { - $comments_url = htmlspecialchars($line["link"]); - } - $entry_comments = "<a class=\"comments\" - target='_blank' rel=\"noopener noreferrer\" href=\"$comments_url\">$num_comments ". - _ngettext("comment", "comments", $num_comments)."</a>"; - - } else { - if ($line["comments"] && $line["link"] != $line["comments"]) { - $entry_comments = "<a class=\"comments\" target='_blank' rel=\"noopener noreferrer\" href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>"; - } - } - - $enclosures = self::get_article_enclosures($line["id"]); - - if ($zoom_mode) { - header("Content-Type: text/html"); - $rv['content'] .= "<!DOCTYPE html> - <html><head> - <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/> - <title>".$line["title"]."</title>". - stylesheet_tag("css/default.css")." - <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\"> - <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">"; - - $rv['content'] .= "<meta property=\"og:title\" content=\"".htmlspecialchars($line["title"])."\"/>\n"; - $rv['content'] .= "<meta property=\"og:site_name\" content=\"".htmlspecialchars($line["feed_title"])."\"/>\n"; - $rv['content'] .= "<meta property=\"og:description\" content=\"". - htmlspecialchars(truncate_string(strip_tags($line["content"]), 500, "..."))."\"/>\n"; - - $rv['content'] .= "</head>"; - - $og_image = false; - - foreach ($enclosures as $enc) { - if (strpos($enc["content_type"], "image/") !== FALSE) { - $og_image = $enc["content_url"]; - break; - } - } - - if (!$og_image) { - $tmpdoc = new DOMDocument(); - - if (@$tmpdoc->loadHTML(mb_substr($line["content"], 0, 131070))) { - $tmpxpath = new DOMXPath($tmpdoc); - $first_img = $tmpxpath->query("//img")->item(0); - - if ($first_img) { - $og_image = $first_img->getAttribute("src"); - } - } - } - - if ($og_image) { - $rv['content'] .= "<meta property=\"og:image\" content=\"" . htmlspecialchars($og_image) . "\"/>"; - } - - $rv['content'] .= "<body class=\"claro ttrss_utility ttrss_zoom\">"; - } - - $rv['content'] .= "<div class=\"post\" id=\"POST-$id\">"; - - $rv['content'] .= "<div class=\"header\">"; - - $entry_author = $line["author"]; - - if ($entry_author) { - $entry_author = __(" - ") . $entry_author; - } - - $parsed_updated = make_local_datetime($line["updated"], true, - $owner_uid, true); - - if (!$zoom_mode) - $rv['content'] .= "<div class=\"date\">$parsed_updated</div>"; - - if ($line["link"]) { - $rv['content'] .= "<div class='title'><a target='_blank' rel='noopener noreferrer' - title=\"".htmlspecialchars($line['title'])."\" - href=\"" . - htmlspecialchars($line["link"]) . "\">" . - $line["title"] . "</a>" . - "<span class='author'>$entry_author</span></div>"; - } else { - $rv['content'] .= "<div class='title'>" . $line["title"] . "$entry_author</div>"; - } - - if ($zoom_mode) { - $feed_title = htmlspecialchars($line["feed_title"]); - - $rv['content'] .= "<div class=\"feed-title\">$feed_title</div>"; - - $rv['content'] .= "<div class=\"date\">$parsed_updated</div>"; - } - - $tags_str = Article::format_tags_string($line["tags"], $id); - $tags_str_full = join(", ", $line["tags"]); - - if (!$tags_str_full) $tags_str_full = __("no tags"); - - if (!$entry_comments) $entry_comments = " "; # placeholder - - $rv['content'] .= "<div class='tags' style='float : right'> - <img src='images/tag.png' - class='tagsPic' alt='Tags' title='Tags'> "; - - if (!$zoom_mode) { - $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span> - <a title=\"".__('Edit tags for this article')."\" - href=\"#\" onclick=\"Article.editTags($id)\">(+)</a>"; - - $rv['content'] .= "<div dojoType=\"dijit.Tooltip\" - id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\" - position=\"below\">$tags_str_full</div>"; - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) { - $rv['content'] .= $p->hook_article_button($line); - } - - } else { - $tags_str = strip_tags($tags_str); - $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>"; - } - $rv['content'] .= "</div>"; - $rv['content'] .= "<div clear='both'>"; - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) { - $rv['content'] .= $p->hook_article_left_button($line); - } - - $rv['content'] .= "$entry_comments</div>"; - - if ($line["orig_feed_id"]) { - - $of_sth = $pdo->prepare("SELECT * FROM ttrss_archived_feeds - WHERE id = ? AND owner_uid = ?"); - $of_sth->execute([$line["orig_feed_id"], $owner_uid]); - - if ($tmp_line = $of_sth->fetch()) { - - $rv['content'] .= "<div clear='both'>"; - $rv['content'] .= __("Originally from:"); - - $rv['content'] .= " "; - - $rv['content'] .= "<a target='_blank' rel='noopener noreferrer' - href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" . - $tmp_line['title'] . "</a>"; - - $rv['content'] .= " "; - - $rv['content'] .= "<a target='_blank' rel='noopener noreferrer' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>"; - - $rv['content'] .= "</div>"; - } - } - - $rv['content'] .= "</div>"; - - $rv['content'] .= "<div id=\"POSTNOTE-$id\">"; - if ($line['note']) { - $rv['content'] .= Article::format_article_note($id, $line['note'], !$zoom_mode); - } - $rv['content'] .= "</div>"; - - if (!$line['lang']) $line['lang'] = 'en'; - - $rv['content'] .= "<div class=\"content\" lang=\"".$line['lang']."\">"; - - $rv['content'] .= $line["content"]; - - if (!$zoom_mode) { - $rv['content'] .= Article::format_article_enclosures($id, - $line["always_display_enclosures"], - $line["content"], - $line["hide_images"]); - } - - $rv['content'] .= "</div>"; - - $rv['content'] .= "</div>"; - - } - - if ($zoom_mode) { - $rv['content'] .= " - <div class='footer'> - <button onclick=\"return window.close()\">". - __("Close this window")."</button></div>"; - $rv['content'] .= "</body></html>"; - } - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ARTICLE) as $p) { - $rv['content'] = $p->hook_format_article($rv['content'], $line, $zoom_mode); - } - - return $rv; - - } - static function get_article_tags($id, $owner_uid = 0, $tag_cache = false) { $a_id = $id; @@ -906,9 +640,18 @@ class Article extends Handler_Protected { static function format_article_note($id, $note, $allow_edit = true) { - $str = "<div class='articleNote' onclick=\"Plugins.Note.edit($id)\"> - <div class='noteEdit' onclick=\"Plugins.Note.edit($id)\">". - ($allow_edit ? __('(edit note)') : "")."</div>$note</div>"; + if ($allow_edit) { + $onclick = "onclick='Plugins.Note.edit($id)'"; + $note_class = 'editable'; + } else { + $onclick = ''; + $note_class = ''; + } + + return "<div class='article-note $note_class'> + <i class='material-icons'>note</i> + <div $onclick class='body'>$note</div> + </div>"; return $str; } diff --git a/classes/ccache.php b/classes/ccache.php index 9c5547e7d..2f19140e0 100644 --- a/classes/ccache.php +++ b/classes/ccache.php @@ -141,7 +141,7 @@ class CCache { $sth = $pdo->prepare("SELECT SUM(value) AS sv FROM ttrss_counters_cache, ttrss_feeds - WHERE id = feed_id AND + WHERE ttrss_feeds.id = feed_id AND (cat_id = :cat OR (:cat = 0 AND cat_id IS NULL)) AND ttrss_counters_cache.owner_uid = :uid AND ttrss_feeds.owner_uid = :uid"); diff --git a/classes/counters.php b/classes/counters.php index 2de7d5a3a..cee901304 100644 --- a/classes/counters.php +++ b/classes/counters.php @@ -24,11 +24,11 @@ class Counters { $pdo = DB::pdo(); - $sth = $pdo->prepare("SELECT id AS cat_id, value AS unread, + $sth = $pdo->prepare("SELECT ttrss_feed_categories.id AS cat_id, value AS unread, (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE c2.parent_cat = ttrss_feed_categories.id) AS num_children FROM ttrss_feed_categories, ttrss_cat_counters_cache - WHERE ttrss_cat_counters_cache.feed_id = id AND + WHERE ttrss_cat_counters_cache.feed_id = ttrss_feed_categories.id AND ttrss_cat_counters_cache.owner_uid = ttrss_feed_categories.owner_uid AND ttrss_feed_categories.owner_uid = ?"); $sth->execute([$_SESSION['uid']]); @@ -172,7 +172,7 @@ class Counters { FROM ttrss_feeds, ttrss_counters_cache WHERE ttrss_feeds.owner_uid = ? AND ttrss_counters_cache.owner_uid = ttrss_feeds.owner_uid - AND ttrss_counters_cache.feed_id = id"); + AND ttrss_counters_cache.feed_id = ttrss_feeds.id"); $sth->execute([$_SESSION['uid']]); while ($line = $sth->fetch()) { diff --git a/classes/dlg.php b/classes/dlg.php index 7ac18bb90..fa2b01156 100644 --- a/classes/dlg.php +++ b/classes/dlg.php @@ -16,15 +16,12 @@ class Dlg extends Handler_Protected { function importOpml() { print __("If you have imported labels and/or filters, you might need to reload preferences to see your new data.") . "</p>"; - print "<div class=\"prefFeedOPMLHolder\">"; - - print "<ul class='nomarks'>"; + print "<div class='panel panel-scrollable'>"; $opml = new Opml($_REQUEST); $opml->opml_import($_SESSION["uid"]); - print "</ul>"; print "</div>"; print "<div align='center'>"; @@ -43,7 +40,7 @@ class Dlg extends Handler_Protected { print __("Your Public OPML URL is:"); - print "<div class=\"tagCloudContainer\">"; + print "<div class='panel text-center'>"; print "<a id='pub_opml_url' href='$url_path' target='_blank'>$url_path</a>"; print "</div>"; @@ -94,7 +91,7 @@ class Dlg extends Handler_Protected { } function printTagCloud() { - print "<div class=\"tagCloudContainer\">"; + print "<div class='panel text-center'>"; // from here: http://www.roscripts.com/Create_tag_cloud-71.html @@ -170,7 +167,7 @@ class Dlg extends Handler_Protected { print "<div>".T_sprintf("%s can be accessed via the following secret URL:", $feed_title)."</div>"; - print "<div class=\"tagCloudContainer\">"; + print "<div class='panel text-center'>"; print "<a id='gen_feed_url' href='$url_path' target='_blank'>$url_path</a>"; print "</div>"; diff --git a/classes/feeds.php b/classes/feeds.php index d9c772c9c..a79a1ebd2 100755 --- a/classes/feeds.php +++ b/classes/feeds.php @@ -28,8 +28,6 @@ class Feeds extends Handler_Protected { $rss_link = htmlspecialchars(get_self_url_prefix() . "/public.php?op=rss&id=$feed_id$cat_q$search_q"); - $error_class = $error ? "error" : ""; - $reply .= "<span class='left'>"; $reply .= "<a href=\"#\" @@ -37,25 +35,20 @@ class Feeds extends Handler_Protected { onclick=\"App.displayDlg('".__("Show as feed")."','generatedFeed', '$feed_id:$is_cat:$rss_link')\"> <i class='icon-syndicate material-icons'>rss_feed</i></a>"; - $reply .= "<span id='feed_title' class='$error_class'>"; + $reply .= "<span id='feed_title'>"; if ($feed_site_url) { - $last_updated = T_sprintf("Last updated: %s", - $feed_last_updated); + $last_updated = T_sprintf("Last updated: %s", $feed_last_updated); - $target = "target=\"_blank\""; - $reply .= "<a title=\"$last_updated\" $target href=\"$feed_site_url\">". + $reply .= "<a title=\"$last_updated\" target='_blank' href=\"$feed_site_url\">". truncate_string(strip_tags($feed_title), 30)."</a>"; - - if ($error) { - $error = htmlspecialchars($error); - $reply .= " <img title=\"$error\" src='images/error.png' alt='error' class=\"noborder\">"; - } - } else { $reply .= strip_tags($feed_title); } + if ($error) + $reply .= " <i title=\"" . htmlspecialchars($error) . "\" class='material-icons icon-error'>error</i>"; + $reply .= "</span></span>"; $reply .= "<span class=\"right\">"; @@ -120,18 +113,14 @@ class Feeds extends Handler_Protected { } private function format_headlines_list($feed, $method, $view_mode, $limit, $cat_view, - $offset, $vgr_last_feed = false, - $override_order = false, $include_children = false, $check_first_id = false, - $skip_first_id_check = false) { + $offset, $override_order = false, $include_children = false, $check_first_id = false, + $skip_first_id_check = false, $order_by = false) { $disable_cache = false; $reply = array(); $rgba_cache = array(); - - $timing_info = microtime(true); - $topmost_article_ids = array(); if (!$offset) $offset = 0; @@ -168,8 +157,6 @@ class Feeds extends Handler_Protected { $disable_cache = true; } - if ($_REQUEST["debug"]) $timing_info = print_checkpoint("H0", $timing_info); - if (!$cat_view && is_numeric($feed) && $feed < PLUGIN_FEED_BASE_INDEX && $feed > LABEL_BASE_INDEX) { $handler = PluginHost::getInstance()->get_feed_handler( PluginHost::feed_to_pfeed_id($feed)); @@ -185,7 +172,8 @@ class Feeds extends Handler_Protected { "owner_uid" => $_SESSION["uid"], "filter" => false, "since_id" => 0, - "include_children" => $include_children); + "include_children" => $include_children, + "order_by" => $order_by); $qfh_ret = $handler->get_headlines(PluginHost::feed_to_pfeed_id($feed), $options); @@ -204,7 +192,8 @@ class Feeds extends Handler_Protected { "offset" => $offset, "include_children" => $include_children, "check_first_id" => $check_first_id, - "skip_first_id_check" => $skip_first_id_check + "skip_first_id_check" => $skip_first_id_check, + "order_by" => $order_by ); $qfh_ret = $this->queryFeedHeadlines($params); @@ -212,8 +201,6 @@ class Feeds extends Handler_Protected { $vfeed_group_enabled = get_pref("VFEED_GROUP_BY_FEED") && $feed != -6; - if ($_REQUEST["debug"]) $timing_info = print_checkpoint("H1", $timing_info); - $result = $qfh_ret[0]; // this could be either a PDO query result or a -1 if first id changed $feed_title = $qfh_ret[1]; $feed_site_url = $qfh_ret[2]; @@ -223,8 +210,7 @@ class Feeds extends Handler_Protected { $highlight_words = $qfh_ret[5]; $reply['first_id'] = $qfh_ret[6]; $reply['search_query'] = [$search, $search_language]; - - $vgroup_last_feed = $vgr_last_feed; + $reply['vfeed_group_enabled'] = $vfeed_group_enabled; $reply['toolbar'] = $this->format_headline_subtoolbar($feed_site_url, $feed_title, @@ -237,37 +223,45 @@ class Feeds extends Handler_Protected { } } - $reply['content'] = ''; + $reply['content'] = []; $headlines_count = 0; - $lnum = $offset; - $num_unread = 0; - if ($_REQUEST["debug"]) $timing_info = print_checkpoint("PS", $timing_info); - if (is_object($result)) { - - while ($line = $result->fetch()) { + while ($line = $result->fetch(PDO::FETCH_ASSOC)) { ++$headlines_count; - $line["content_preview"] = "— " . truncate_string(strip_tags($line["content"]), 250); + if (!get_pref('SHOW_CONTENT_PREVIEW')) { + $line["content_preview"] = ""; + } else { + $line["content_preview"] = "— " . truncate_string(strip_tags($line["content"]), 250); + + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) { + $line = $p->hook_query_headlines($line, 250, false); + } + } + + $id = $line["id"]; - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) { - $line = $p->hook_query_headlines($line, 250, false); + // frontend doesn't expect pdo returning booleans as strings on mysql + if (DB_TYPE == "mysql") { + foreach (["unread", "marked", "published"] as $k) { + $line[$k] = $line[$k] === "1"; + } } - if (get_pref('SHOW_CONTENT_PREVIEW')) { - $content_preview = $line["content_preview"]; + // normalize archived feed + if ($line['feed_id'] === null) { + $line['feed_id'] = 0; + $line["feed_title"] = __("Archived articles"); } - $id = $line["id"]; $feed_id = $line["feed_id"]; + $label_cache = $line["label_cache"]; $labels = false; - $mouseover_attrs = "onmouseover='Article.mouseIn($id)' onmouseout='Article.mouseOut($id)'"; - if ($label_cache) { $label_cache = json_decode($label_cache, true); @@ -285,372 +279,112 @@ class Feeds extends Handler_Protected { $labels_str .= Article::format_article_labels($labels); $labels_str .= "</span>"; + $line["labels"] = $labels_str; + if (count($topmost_article_ids) < 3) { array_push($topmost_article_ids, $id); } - $class = ""; - - if ($line["unread"]) { - $class .= " Unread"; - ++$num_unread; - } - - $class .= $line["marked"] ? " marked" : ""; - $marked_pic = "<i class=\"marked-pic marked-$id material-icons\" onclick='Headlines.toggleMark($id)'>star</i>"; + if (!$line["feed_title"]) $line["feed_title"] = ""; - $class .= $line["published"] ? " published" : ""; - $published_pic = "<i class=\"pub-pic pub-$id material-icons\" onclick='Headlines.togglePub($id)'>rss_feed</i>"; + $line["buttons_left"] = ""; + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) { + $line["buttons_left"] .= $p->hook_article_left_button($line); + } - $updated_fmt = make_local_datetime($line["updated"], false, false, false, true); - $date_entered_fmt = T_sprintf("Imported at %s", - make_local_datetime($line["date_entered"], false)); - - $score = $line["score"]; + $line["buttons"] = ""; + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) { + $line["buttons"] .= $p->hook_article_button($line); + } - $score_pic = "images/" . get_score_pic($score); + $line["content"] = sanitize($line["content"], + $line['hide_images'], false, $line["site_url"], $highlight_words, $line["id"]); - $score_pic = "<img class='score-pic' score='$score' onclick='Article.setScore($id, this)' src=\"$score_pic\" - title=\"$score\">"; + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_CDM) as $p) { + $line = $p->hook_render_article_cdm($line); + } - if ($score > 500) { - $hlc_suffix = "high"; - } else if ($score < -100) { - $hlc_suffix = "low"; - } else { - $hlc_suffix = ""; - } + $line['content'] = rewrite_cached_urls($line['content']); - $entry_author = $line["author"]; + if ($line['note']) + $line['note'] = Article::format_article_note($id, $line['note']); + else + $line['note'] = ""; - if ($entry_author) { - $entry_author = " — $entry_author"; - } + if (!get_pref("CDM_EXPANDED")) { + $line["cdm_excerpt"] = "<span class='collapse'> + <i class='material-icons' onclick='return Article.cdmUnsetActive(event)' + title=\"" . __("Collapse article") . "\">remove_circle</i></span>"; - if (feeds::feedHasIcon($feed_id)) { - $feed_icon_img = "<img class=\"icon\" src=\"".ICONS_URL."/$feed_id.ico\" alt=\"\">"; - } else { - $feed_icon_img = "<i class='icon-syndicate material-icons'>rss_feed</i>"; - } + if (get_pref('SHOW_CONTENT_PREVIEW')) { + $line["cdm_excerpt"] .= "<span class='excerpt'>" . $line["content_preview"] . "</span>"; + } + } - $entry_site_url = $line["site_url"]; + $line["enclosures"] = Article::format_article_enclosures($id, $line["always_display_enclosures"], + $line["content"], $line["hide_images"]); - //setting feed headline background color, needs to change text color based on dark/light - $fav_color = $line['favicon_avg_color']; + if ($line["orig_feed_id"]) { - require_once "colors.php"; + $ofgh = $this->pdo->prepare("SELECT * FROM ttrss_archived_feeds + WHERE id = ? AND owner_uid = ?"); + $ofgh->execute([$line["orig_feed_id"], $_SESSION['uid']]); - if ($fav_color && $fav_color != 'fail') { - if (!isset($rgba_cache[$feed_id])) { - $rgba_cache[$feed_id] = join(",", _color_unpack($fav_color)); - } - } + if ($tmp_line = $ofgh->fetch()) { + $line["orig_feed"] = [ $tmp_line["title"], $tmp_line["site_url"], $tmp_line["feed_url"] ]; + } + } - if (!get_pref('COMBINED_DISPLAY_MODE')) { + $line["updated_long"] = make_local_datetime($line["updated"],true); + $line["updated"] = make_local_datetime($line["updated"], false, false, false, true); - if ($vfeed_group_enabled) { - if ($feed_id != $vgroup_last_feed && $line["feed_title"]) { - $vgroup_last_feed = $feed_id; - - $vf_catchup_link = "<a class='catchup' onclick='Feeds.catchupFeedInGroup($feed_id);' href='#'>".__('mark feed as read')."</a>"; - - $reply['content'] .= "<div data-feed-id='$feed_id' class='feed-title'>". - "<div style='float : right'>$feed_icon_img</div>". - "<a class='title' href=\"#\" onclick=\"Feeds.open({feed:$feed_id})\">". - $line["feed_title"]."</a> - $vf_catchup_link</div>"; - - - } - } - - $reply['content'] .= "<div class='hl $class' data-orig-feed-id='$feed_id' data-article-id='$id' id='RROW-$id' $mouseover_attrs>"; - - $reply['content'] .= "<div class='left'>"; - - $reply['content'] .= "<input dojoType=\"dijit.form.CheckBox\" - type=\"checkbox\" onclick=\"Headlines.onRowChecked(this)\" - class='rchk'>"; - - $reply['content'] .= "$marked_pic"; - $reply['content'] .= "$published_pic"; - - $reply['content'] .= "</div>"; - - $reply['content'] .= "<div onclick='return Headlines.click(event, $id)' - class=\"title\"><span data-article-id=\"$id\" class='hl-content hlMenuAttach $hlc_suffix'>"; - $reply['content'] .= "<a class=\"title $hlc_suffix\" - href=\"" . htmlspecialchars($line["link"]) . "\" - onclick=\"\">" . - truncate_string($line["title"], 200); - - if (get_pref('SHOW_CONTENT_PREVIEW')) { - $reply['content'] .= "<span class=\"preview\">" . $line["content_preview"] . "</span>"; - } - - $reply['content'] .= "</a></span>"; - - $reply['content'] .= $labels_str; - - $reply['content'] .= "</div>"; - - if (!$vfeed_group_enabled) { - if (@$line["feed_title"]) { - $rgba = @$rgba_cache[$feed_id]; - - $reply['content'] .= "<span class=\"feed\"><a style=\"background : rgba($rgba, 0.3)\" href=\"#\" onclick=\"Feeds.open({feed:$feed_id})\">". - truncate_string($line["feed_title"],30)."</a></span>"; - } - } - - - $reply['content'] .= "<span class=\"updated\">"; - - $reply['content'] .= "<div title='$date_entered_fmt'>$updated_fmt</div> - </span>"; - - $reply['content'] .= "<div class=\"right\">"; + $line['imported'] = T_sprintf("Imported at %s", + make_local_datetime($line["date_entered"], false)); - $reply['content'] .= $score_pic; + $score = $line["score"]; - if ($line["feed_title"] && !$vfeed_group_enabled) { + $line["score_pic"] = get_score_pic($score); + $line["score_class"] = get_score_class($score); - $reply['content'] .= "<span onclick=\"Feeds.open({feed:$feed_id})\" - style=\"cursor : pointer\" - title=\"".htmlspecialchars($line['feed_title'])."\"> - $feed_icon_img</span>"; - } + if ($line["tag_cache"]) + $tags = explode(",", $line["tag_cache"]); + else + $tags = false; - $reply['content'] .= "</div>"; - $reply['content'] .= "</div>"; + $line["tags_str"] = Article::format_tags_string($tags, $id); + if (feeds::feedHasIcon($feed_id)) { + $line['feed_icon'] = "<img class=\"icon\" src=\"".ICONS_URL."/$feed_id.ico\" alt=\"\">"; } else { + $line['feed_icon'] = "<i class='icon-no-feed material-icons'>rss_feed</i>"; + } - if ($line["tag_cache"]) - $tags = explode(",", $line["tag_cache"]); - else - $tags = false; - - $line["content"] = sanitize($line["content"], - $line['hide_images'], false, $entry_site_url, $highlight_words, $line["id"]); - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_CDM) as $p) { - $line = $p->hook_render_article_cdm($line); - } - - $line['content'] = rewrite_cached_urls($line['content']); - - if ($vfeed_group_enabled && $line["feed_title"]) { - if ($feed_id != $vgroup_last_feed) { - - $vgroup_last_feed = $feed_id; - - $vf_catchup_link = "<a class='catchup' onclick='Feeds.catchupFeedInGroup($feed_id);' href='#'>".__('mark feed as read')."</a>"; - - $feed_icon_src = Feeds::getFeedIcon($feed_id); - $feed_icon_img = "<img class=\"icon\" src=\"$feed_icon_src\">"; - - $reply['content'] .= "<div data-feed-id='$feed_id' class='feed-title'>". - "<div style=\"float : right\">$feed_icon_img</div>". - "<a href=\"#\" class='title' onclick=\"Feeds.open({feed:$feed_id})\">". - $line["feed_title"]."</a> $vf_catchup_link</div>"; - - } - } - - $content_encoded = htmlspecialchars($line["content"]); - - $expanded_class = get_pref("CDM_EXPANDED") ? "expanded" : "expandable"; - $tmp_content = "<div class=\"cdm $expanded_class $hlc_suffix $class\" - id=\"RROW-$id\" data-content=\"$content_encoded\" data-article-id='$id' data-orig-feed-id='$feed_id' $mouseover_attrs>"; - - $tmp_content .= "<div class=\"header\">"; - $tmp_content .= "<div class=\"left\">"; - - $tmp_content .= "<input dojoType=\"dijit.form.CheckBox\" - type=\"checkbox\" onclick=\"Headlines.onRowChecked(this)\" - class='rchk'>"; - - $tmp_content .= "$marked_pic"; - $tmp_content .= "$published_pic"; - - $tmp_content .= "</div>"; - - if ($highlight_words && count($highlight_words) > 0) { - foreach ($highlight_words as $word) { - $word = preg_quote($word, "/"); - - $line["title"] = preg_replace("/($word)/i", - "<span class=\"highlight\">$1</span>", $line["title"]); - } - } - - // data-article-id included for context menu - $tmp_content .= "<span - onclick=\"return Headlines.click(event, $id);\" - data-article-id=\"$id\" - class=\"titleWrap hlMenuAttach $hlc_suffix\"> - <a class=\"title $hlc_suffix\" - title=\"".htmlspecialchars($line["title"])."\" - target=\"_blank\" rel=\"noopener noreferrer\" href=\"". - htmlspecialchars($line["link"])."\">". - $line["title"] . - "</a> <span class=\"author\">$entry_author</span>"; - - $tmp_content .= $labels_str; - - if (!get_pref("CDM_EXPANDED")) { - $tmp_content .= "<span class='collapse'> - <img src=\"images/collapse.png\" onclick=\"return Article.cdmUnsetActive(event)\" - title=\"" . __("Collapse article") . "\"/></span>"; - - if (get_pref('SHOW_CONTENT_PREVIEW')) { - $tmp_content .= "<span class='excerpt'>" . $line["content_preview"] . "</span>"; - } - } - - $tmp_content .= "</span>"; - - if (!$vfeed_group_enabled) { - if (@$line["feed_title"]) { - $rgba = @$rgba_cache[$feed_id]; - - $tmp_content .= "<div class=\"feed\"> - <a href=\"#\" style=\"background-color: rgba($rgba,0.3)\" - onclick=\"Feeds.open({feed:$feed_id})\">". - truncate_string($line["feed_title"],30)."</a> - </div>"; - } - } - - $tmp_content .= "<span class='updated' title='$date_entered_fmt'>$updated_fmt</span>"; - - $tmp_content .= "<div class='right'>"; - $tmp_content .= "$score_pic"; - - if (!get_pref("VFEED_GROUP_BY_FEED") && $line["feed_title"]) { - $tmp_content .= "<span style=\"cursor : pointer\" - title=\"".htmlspecialchars($line["feed_title"])."\" - onclick=\"Feeds.open({feed:$feed_id})\">$feed_icon_img</span>"; - } - $tmp_content .= "</div>"; //score wrapper2 - - $tmp_content .= "</div>"; //header - - $tmp_content .= "<div class=\"content\" onclick=\"return Headlines.click(event, $id, true);\">"; - - $tmp_content .= "<div id=\"POSTNOTE-$id\">"; - if ($line['note']) { - $tmp_content .= Article::format_article_note($id, $line['note']); - } - $tmp_content .= "</div>"; //POSTNOTE - - if (!$line['lang']) $line['lang'] = 'en'; - - // this is filled from RROW data-content - $tmp_content .= "<div class=\"content-inner\" lang=\"".$line['lang']."\"> - <img src='images/indicator_white.gif'> - </div>"; - $tmp_content .= "<div class=\"intermediate\">"; - - if ($line["orig_feed_id"]) { - - $ofgh = $this->pdo->prepare("SELECT * FROM ttrss_archived_feeds - WHERE id = ? AND owner_uid = ?"); - $ofgh->execute([$line["orig_feed_id"], $_SESSION['uid']]); - - if ($tmp_line = $ofgh->fetch()) { - - $tmp_content .= "<div clear='both'>"; - $tmp_content .= __("Originally from:"); - - $tmp_content .= " "; - - $tmp_content .= "<a target='_blank' rel='noopener noreferrer' - href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" . - $tmp_line['title'] . "</a>"; - - $tmp_content .= " "; - - $tmp_content .= "<a target='_blank' rel='noopener noreferrer' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>"; - - $tmp_content .= "</div>"; - } - } - - - $always_display_enclosures = $line["always_display_enclosures"]; - $tmp_content .= Article::format_article_enclosures($id, $always_display_enclosures, - $line["content"], $line["hide_images"]); - - $tmp_content .= "</div>"; // cdmIntermediate - - $tmp_content .= "<div class=\"footer\" onclick=\"event.stopPropagation()\">"; - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) { - $tmp_content .= $p->hook_article_left_button($line); - } - - $tags_str = Article::format_tags_string($tags, $id); - - $tmp_content .= "<div class='left'>"; - - $tmp_content .= "<img src='images/tag.png' alt='Tags' title='Tags'> - <span id=\"ATSTR-$id\">$tags_str</span> - <a title=\"".__('Edit tags for this article')."\" - href=\"#\" onclick=\"Article.editTags($id)\">(+)</a>"; - - $num_comments = (int) $line["num_comments"]; - $entry_comments = ""; - - if ($num_comments > 0) { - if ($line["comments"]) { - $comments_url = htmlspecialchars($line["comments"]); - } else { - $comments_url = htmlspecialchars($line["link"]); - } - $entry_comments = "<a class=\"comments\" - target='_blank' rel='noopener noreferrer' href=\"$comments_url\">$num_comments ". - _ngettext("comment", "comments", $num_comments)."</a>"; - - } else { - if ($line["comments"] && $line["link"] != $line["comments"]) { - $entry_comments = "<a class=\"comments\" target='_blank' rel='noopener noreferrer' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>"; - } - } - - if ($entry_comments) $tmp_content .= " ($entry_comments)"; + //setting feed headline background color, needs to change text color based on dark/light + $fav_color = $line['favicon_avg_color']; - $tmp_content .= "</div>"; - $tmp_content .= "<div class='right'>"; + require_once "colors.php"; - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) { - $tmp_content .= $p->hook_article_button($line); + if ($fav_color && $fav_color != 'fail') { + if (!isset($rgba_cache[$feed_id])) { + $rgba_cache[$feed_id] = join(",", _color_unpack($fav_color)) . ",0.3"; } - $tmp_content .= "</div>"; // buttons + $line['favicon_avg_color_rgba'] = $rgba_cache[$feed_id]; + } - $tmp_content .= "</div>"; // cdm footer - $tmp_content .= "</div>"; // cdmContent - $tmp_content .= "</div>"; // RROW.cdm + /* we don't need those */ - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ARTICLE_CDM) as $p) { - $tmp_content = $p->hook_format_article_cdm($tmp_content, $line); - } + foreach (["date_entered", "guid", "last_published", "last_marked", "tag_cache", "favicon_avg_color", "uuid", "label_cache"] as $k) + unset($line[$k]); - $reply['content'] .= $tmp_content; - } - - ++$lnum; + array_push($reply['content'], $line); } } - if ($_REQUEST["debug"]) $timing_info = print_checkpoint("PE", $timing_info); - if (!$headlines_count) { - if (!is_numeric($result)) { + if (is_object($result)) { switch ($view_mode) { case "unread": @@ -704,10 +438,7 @@ class Feeds extends Handler_Protected { } } - if ($_REQUEST["debug"]) $timing_info = print_checkpoint("H2", $timing_info); - - return array($topmost_article_ids, $headlines_count, $feed, $disable_cache, - $vgroup_last_feed, $reply); + return array($topmost_article_ids, $headlines_count, $feed, $disable_cache, $reply); } function catchupAll() { @@ -719,12 +450,8 @@ class Feeds extends Handler_Protected { } function view() { - $timing_info = microtime(true); - $reply = array(); - if ($_REQUEST["debug"]) $timing_info = print_checkpoint("0", $timing_info); - $feed = $_REQUEST["feed"]; $method = $_REQUEST["m"]; $view_mode = $_REQUEST["view_mode"]; @@ -732,7 +459,6 @@ class Feeds extends Handler_Protected { @$cat_view = $_REQUEST["cat"] == "true"; @$next_unread_feed = $_REQUEST["nuf"]; @$offset = $_REQUEST["skip"]; - @$vgroup_last_feed = $_REQUEST["vgrlf"]; $order_by = $_REQUEST["order_by"]; $check_first_id = $_REQUEST["fid"]; @@ -798,7 +524,7 @@ class Feeds extends Handler_Protected { $sth->execute([$feed, $_SESSION['uid']]); } - $reply['headlines'] = array(); + $reply['headlines'] = []; $override_order = false; $skip_first_id_check = false; @@ -816,22 +542,13 @@ class Feeds extends Handler_Protected { break; } - if ($_REQUEST["debug"]) $timing_info = print_checkpoint("04", $timing_info); - $ret = $this->format_headlines_list($feed, $method, $view_mode, $limit, $cat_view, $offset, - $vgroup_last_feed, $override_order, true, $check_first_id, $skip_first_id_check); + $override_order, true, $check_first_id, $skip_first_id_check, $order_by); - //$topmost_article_ids = $ret[0]; $headlines_count = $ret[1]; - /* $returned_feed = $ret[2]; */ $disable_cache = $ret[3]; - $vgroup_last_feed = $ret[4]; - - //$reply['headlines']['content'] =& $ret[5]['content']; - //$reply['headlines']['toolbar'] =& $ret[5]['toolbar']; - - $reply['headlines'] = $ret[5]; + $reply['headlines'] = $ret[4]; if (!$next_unread_feed) $reply['headlines']['id'] = $feed; @@ -840,14 +557,10 @@ class Feeds extends Handler_Protected { $reply['headlines']['is_cat'] = (bool) $cat_view; - if ($_REQUEST["debug"]) $timing_info = print_checkpoint("05", $timing_info); - - $reply['headlines-info'] = array("count" => (int) $headlines_count, - "vgroup_last_feed" => $vgroup_last_feed, - "disable_cache" => (bool) $disable_cache); - - if ($_REQUEST["debug"]) $timing_info = print_checkpoint("30", $timing_info); + $reply['headlines-info'] = ["count" => (int) $headlines_count, + "disable_cache" => (bool) $disable_cache]; + // this is parsed by handleRpcJson() on first viewfeed() to set cdm expanded, etc $reply['runtime-info'] = make_runtime_info(); print json_encode($reply); @@ -890,7 +603,6 @@ class Feeds extends Handler_Protected { $reply['headlines']['content'] .= "</span></p>"; $reply['headlines-info'] = array("count" => 0, - "vgroup_last_feed" => '', "unread" => 0, "disable_cache" => true); @@ -907,7 +619,6 @@ class Feeds extends Handler_Protected { $reply['headlines']['content'] = "<div class='whiteBox'>". $error . "</div>"; $reply['headlines-info'] = array("count" => 0, - "vgroup_last_feed" => '', "unread" => 0, "disable_cache" => true); @@ -1501,17 +1212,17 @@ class Feeds extends Handler_Protected { return "rss_feed"; break; case -3: - return "new_releases"; + return "whatshot"; break; case -4: return "inbox"; break; case -6: - return "cached"; + return "restore"; break; default: if ($id < LABEL_BASE_INDEX) { - return "images/label.png"; + return "label"; } else { $icon = self::getIconFile($id); @@ -1722,6 +1433,7 @@ class Feeds extends Handler_Protected { $start_ts = isset($params["start_ts"]) ? $params["start_ts"] : false; $check_first_id = isset($params["check_first_id"]) ? $params["check_first_id"] : false; $skip_first_id_check = isset($params["skip_first_id_check"]) ? $params["skip_first_id_check"] : false; + $order_by = isset($params["order_by"]) ? $params["order_by"] : false; $ext_tables_part = ""; $limit_query_part = ""; @@ -1951,10 +1663,12 @@ class Feeds extends Handler_Protected { if (is_numeric($feed)) { // proper override_order applied above if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) { + $yyiw_desc = $order_by == "date_reverse" ? "" : "desc"; + if (!$override_order) { - $order_by = "ttrss_feeds.title, ".$order_by; + $order_by = "yyiw $yyiw_desc, ttrss_feeds.title, ".$order_by; } else { - $order_by = "ttrss_feeds.title, ".$override_order; + $order_by = "yyiw $yyiw_desc, ttrss_feeds.title, ".$override_order; } } @@ -1984,8 +1698,10 @@ class Feeds extends Handler_Protected { if (DB_TYPE == "pgsql") { $sanity_interval_qpart = "date_entered >= NOW() - INTERVAL '1 hour' AND"; + $yyiw_qpart = "to_char(date_entered, 'IYYY-IW') AS yyiw"; } else { $sanity_interval_qpart = "date_entered >= DATE_SUB(NOW(), INTERVAL 1 hour) AND"; + $yyiw_qpart = "date_format(date_entered, '%Y-%u') AS yyiw"; } if (!$search && !$skip_first_id_check) { @@ -1993,6 +1709,7 @@ class Feeds extends Handler_Protected { $query = "SELECT DISTINCT ttrss_feeds.title, date_entered, + $yyiw_qpart, guid, ttrss_entries.id, ttrss_entries.title, @@ -2031,6 +1748,7 @@ class Feeds extends Handler_Protected { $query = "SELECT DISTINCT date_entered, + $yyiw_qpart, guid, ttrss_entries.id,ttrss_entries.title, updated, diff --git a/classes/handler/public.php b/classes/handler/public.php index e216d7a36..3a0b328ff 100755 --- a/classes/handler/public.php +++ b/classes/handler/public.php @@ -139,18 +139,27 @@ class Handler_Public extends Handler { $enclosures = Article::get_article_enclosures($line["id"]); - foreach ($enclosures as $e) { - $type = htmlspecialchars($e['content_type']); - $url = htmlspecialchars($e['content_url']); - $length = $e['duration'] ? $e['duration'] : 1; + if (count($enclosures) > 0) { + foreach ($enclosures as $e) { + $type = htmlspecialchars($e['content_type']); + $url = htmlspecialchars($e['content_url']); + $length = $e['duration'] ? $e['duration'] : 1; - $tpl->setVariable('ARTICLE_ENCLOSURE_URL', $url, true); - $tpl->setVariable('ARTICLE_ENCLOSURE_TYPE', $type, true); - $tpl->setVariable('ARTICLE_ENCLOSURE_LENGTH', $length, true); + $tpl->setVariable('ARTICLE_ENCLOSURE_URL', $url, true); + $tpl->setVariable('ARTICLE_ENCLOSURE_TYPE', $type, true); + $tpl->setVariable('ARTICLE_ENCLOSURE_LENGTH', $length, true); - $tpl->addBlock('enclosure'); + $tpl->addBlock('enclosure'); + } + } else { + $tpl->setVariable('ARTICLE_ENCLOSURE_URL', null, true); + $tpl->setVariable('ARTICLE_ENCLOSURE_TYPE', null, true); + $tpl->setVariable('ARTICLE_ENCLOSURE_LENGTH', null, true); } + $tpl->setVariable('ARTICLE_OG_IMAGE', + $this->get_article_image($enclosures, $line['content'], $feed_site_url), true); + $tpl->addBlock('entry'); } @@ -300,16 +309,213 @@ class Handler_Public extends Handler { $id = $row["ref_id"]; $owner_uid = $row["owner_uid"]; - $article = Article::format_article($id, false, true, $owner_uid); - - print_r($article['content']); + print $this->format_article($id, $owner_uid); } else { + header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found"); print "Article not found."; } } + private function get_article_image($enclosures, $content, $site_url) { + $og_image = false; + + foreach ($enclosures as $enc) { + if (strpos($enc["content_type"], "image/") !== FALSE) { + $og_image = $enc["content_url"]; + break; + } + } + + if (!$og_image) { + $tmpdoc = new DOMDocument(); + + if (@$tmpdoc->loadHTML(mb_substr($content, 0, 131070))) { + $tmpxpath = new DOMXPath($tmpdoc); + $first_img = $tmpxpath->query("//img")->item(0); + + if ($first_img) { + $og_image = $first_img->getAttribute("src"); + } + } + } + + return rewrite_relative_url($site_url, $og_image); + } + + 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, + orig_feed_id, + note + FROM ttrss_entries,ttrss_user_entries + WHERE id = ? AND ref_id = id AND owner_uid = ?"); + $sth->execute([$id, $owner_uid]); + + $rv = ''; + + if ($line = $sth->fetch()) { + + $line["tags"] = Article::get_article_tags($id, $owner_uid, $line["tag_cache"]); + unset($line["tag_cache"]); + + $line["content"] = sanitize($line["content"], + $line['hide_images'], + $owner_uid, $line["site_url"], false, $line["id"]); + + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) { + $line = $p->hook_render_article($line); + } + + $line['content'] = rewrite_cached_urls($line['content']); + + $num_comments = (int) $line["num_comments"]; + $entry_comments = ""; + + if ($num_comments > 0) { + if ($line["comments"]) { + $comments_url = htmlspecialchars($line["comments"]); + } else { + $comments_url = htmlspecialchars($line["link"]); + } + $entry_comments = "<a class=\"comments\" + target='_blank' rel=\"noopener noreferrer\" href=\"$comments_url\">$num_comments ". + _ngettext("comment", "comments", $num_comments)."</a>"; + + } else { + if ($line["comments"] && $line["link"] != $line["comments"]) { + $entry_comments = "<a class=\"comments\" target='_blank' rel=\"noopener noreferrer\" href=\"". + htmlspecialchars($line["comments"])."\">".__("comments")."</a>"; + } + } + + $enclosures = Article::get_article_enclosures($line["id"]); + + header("Content-Type: text/html"); + + $rv .= "<!DOCTYPE html> + <html><head> + <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/> + <title>".$line["title"]."</title>". + stylesheet_tag("css/default.css")." + <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\"> + <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">"; + + $rv .= "<meta property=\"og:title\" content=\"".htmlspecialchars($line["title"])."\"/>\n"; + $rv .= "<meta property=\"og:site_name\" content=\"".htmlspecialchars($line["feed_title"])."\"/>\n"; + $rv .= "<meta property=\"og:description\" content=\"". + htmlspecialchars(truncate_string(strip_tags($line["content"]), 500, "..."))."\"/>\n"; + + $rv .= "</head>"; + + $og_image = $this->get_article_image($enclosures, $line['content'], $line["site_url"]); + + if ($og_image) { + $rv .= "<meta property=\"og:image\" content=\"" . htmlspecialchars($og_image) . "\"/>"; + } + + $rv .= "<body class='flat ttrss_utility ttrss_zoom'>"; + $rv .= "<div class='post post-$id'>"; + + /* header */ + + $rv .= "<div class='header'>"; + $rv .= "<div class='row'>"; # row + + //$entry_author = $line["author"] ? " - " . $line["author"] : ""; + $parsed_updated = make_local_datetime($line["updated"], true, + $owner_uid, true); + + if ($line["link"]) { + $rv .= "<div class='title'><a target='_blank' rel='noopener noreferrer' + title=\"".htmlspecialchars($line['title'])."\" + href=\"" .htmlspecialchars($line["link"]) . "\">" . $line["title"] . "</a></div>"; + } else { + $rv .= "<div class='title'>" . $line["title"] . "</div>"; + } + + $rv .= "<div class='date'>$parsed_updated<br/></div>"; + + $rv .= "</div>"; # row + + $rv .= "<div class='row'>"; # row + + /* left buttons */ + + $rv .= "<div class='buttons left'>"; + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) { + $rv .= $p->hook_article_left_button($line); + } + $rv .= "</div>"; + + /* comments */ + + $rv .= "<div class='comments'>$entry_comments</div>"; + $rv .= "<div class='author'>".$line['author']."</div>"; + + /* tags */ + + $tags_str = Article::format_tags_string($line["tags"], $id); + + $rv .= "<i class='material-icons'>label_outline</i><div>"; + + $tags_str = strip_tags($tags_str); + $rv .= "<span id=\"ATSTR-$id\">$tags_str</span>"; + + $rv .= "</div>"; + + /* buttons */ + + $rv .= "<div class='buttons right'>"; + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) { + $rv .= $p->hook_article_button($line); + } + $rv .= "</div>"; + + $rv .= "</div>"; # row + + $rv .= "</div>"; # header + + /* content */ + + $lang = $line['lang'] ? $line['lang'] : "en"; + $rv .= "<div class=\"content\" lang=\"$lang\">"; + + /* content body */ + + $rv .= $line["content"]; + + $rv .= Article::format_article_enclosures($id, + $line["always_display_enclosures"], + $line["content"], + $line["hide_images"]); + + $rv .= "</div>"; # content + + $rv .= "</div>"; # post + + } + + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ARTICLE) as $p) { + $rv = $p->hook_format_article($rv, $line, true); + } + + return $rv; + + } + function rss() { $feed = clean($_REQUEST["id"]); $key = clean($_REQUEST["key"]); @@ -404,7 +610,7 @@ class Handler_Public extends Handler { ?> - <table height='100%' width='100%'><tr><td colspan='2'> + <table height='100%' width='100%' class="panel"><tr><td colspan='2'> <h1><?php echo __("Share with Tiny Tiny RSS") ?></h1> </td></tr> @@ -509,7 +715,7 @@ class Handler_Public extends Handler { if (clean($_POST["profile"])) { - $profile = clean($_POST["profile"]); + $profile = (int) clean($_POST["profile"]); $sth = $this->pdo->prepare("SELECT id FROM ttrss_settings_profiles WHERE id = ? AND owner_uid = ?"); @@ -517,7 +723,9 @@ class Handler_Public extends Handler { if ($sth->fetch()) { $_SESSION["profile"] = $profile; - } + } else { + $_SESSION["profile"] = null; + } } } else { diff --git a/classes/labels.php b/classes/labels.php index fd9e454bb..19d060617 100644 --- a/classes/labels.php +++ b/classes/labels.php @@ -163,8 +163,8 @@ class Labels /* Remove cached data */ $sth = $pdo->prepare("UPDATE ttrss_user_entries SET label_cache = '' - WHERE label_cache LIKE ? AND owner_uid = ?"); - $sth->execute(["%$caption%", $owner_uid]); + WHERE owner_uid = ?"); + $sth->execute([$owner_uid]); } diff --git a/classes/pluginhost.php b/classes/pluginhost.php index 7e3fb08ab..96b1ce499 100755 --- a/classes/pluginhost.php +++ b/classes/pluginhost.php @@ -52,11 +52,12 @@ class PluginHost { const HOOK_MAIN_TOOLBAR_BUTTON = 32; const HOOK_ENCLOSURE_ENTRY = 33; const HOOK_FORMAT_ARTICLE = 34; - const HOOK_FORMAT_ARTICLE_CDM = 35; + const HOOK_FORMAT_ARTICLE_CDM = 35; /* RIP */ const HOOK_FEED_BASIC_INFO = 36; const HOOK_SEND_LOCAL_FILE = 37; const HOOK_UNSUBSCRIBE_FEED = 38; const HOOK_SEND_MAIL = 39; + const HOOK_FILTER_TRIGGERED = 40; const KIND_ALL = 1; const KIND_SYSTEM = 2; diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php index d29cf70f5..71379a12b 100755 --- a/classes/pref/feeds.php +++ b/classes/pref/feeds.php @@ -542,9 +542,8 @@ class Pref_Feeds extends Handler_Protected { $last_error = $row["last_error"]; if ($last_error) { - print " <img src=\"images/error.png\" alt=\"(error)\" - style=\"vertical-align : middle\" - title=\"".htmlspecialchars($last_error)."\">"; + print " <i class=\"material-icons\" + title=\"".htmlspecialchars($last_error)."\">error</i>"; } @@ -1133,8 +1132,9 @@ class Pref_Feeds extends Handler_Protected { function index() { - print "<div dojoType=\"dijit.layout.AccordionContainer\" region=\"center\">"; - print "<div id=\"pref-feeds-feeds\" dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Feeds')."\">"; + print "<div dojoType='dijit.layout.AccordionContainer' region='center'>"; + print "<div style='padding : 0px' dojoType='dijit.layout.AccordionPane' + title=\"<i class='material-icons'>rss_feed</i> ".__('Feeds')."\">"; $sth = $this->pdo->prepare("SELECT COUNT(id) AS num_errors FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?"); @@ -1222,7 +1222,7 @@ class Pref_Feeds extends Handler_Protected { print "</div>"; # toolbar //print '</div>'; - print '<div dojoType="dijit.layout.ContentPane" region="center">'; + print '<div style="padding : 0px" dojoType="dijit.layout.ContentPane" region="center">'; print "<div id=\"feedlistLoading\"> <img src='images/indicator_tiny.gif'>". @@ -1268,7 +1268,8 @@ class Pref_Feeds extends Handler_Protected { print "</div>"; # feeds pane - print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('OPML')."\">"; + print "<div dojoType=\"dijit.layout.AccordionPane\" + title=\"<i class='material-icons'>import_export</i> ".__('OPML')."\">"; print __("Using OPML you can export and import your feeds, filters, labels and Tiny Tiny RSS settings.") . __("Only main settings profile can be migrated using OPML."); @@ -1323,7 +1324,8 @@ class Pref_Feeds extends Handler_Protected { print "</div>"; # pane - print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Published & shared articles / Generated feeds')."\">"; + print "<div dojoType=\"dijit.layout.AccordionPane\" + title=\"<i class='material-icons'>share</i> ".__('Published & shared articles / Generated feeds')."\">"; print __('Published articles can be subscribed by anyone who knows the following URL:'); @@ -1421,16 +1423,15 @@ class Pref_Feeds extends Handler_Protected { print "<div dojoType=\"dijit.form.DropDownButton\">". "<span>" . __('Select')."</span>"; print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">"; - print "<div onclick=\"Tables.select('prefInactiveFeedList', true)\" + print "<div onclick=\"Tables.select('inactive-feeds-list', true)\" dojoType=\"dijit.MenuItem\">".__('All')."</div>"; - print "<div onclick=\"Tables.select('prefInactiveFeedList', false)\" + print "<div onclick=\"Tables.select('inactive-feeds-list', false)\" dojoType=\"dijit.MenuItem\">".__('None')."</div>"; print "</div></div>"; print "</div>"; #toolbar - print "<div class=\"inactiveFeedHolder\">"; - - print "<table width=\"100%\" cellspacing=\"0\" id=\"prefInactiveFeedList\">"; + print "<div class='panel panel-scrollable'>"; + print "<table width='100%' id='inactive-feeds-list'>"; $lnum = 1; @@ -1438,7 +1439,7 @@ class Pref_Feeds extends Handler_Protected { $feed_id = $line["id"]; - print "<tr class=\"placeholder\" data-row-id='$feed_id'>"; + print "<tr data-row-id='$feed_id'>"; print "<td width='5%' align='center'><input onclick='Tables.onRowChecked(this);' dojoType=\"dijit.form.CheckBox\" @@ -1483,16 +1484,15 @@ class Pref_Feeds extends Handler_Protected { print "<div dojoType=\"dijit.form.DropDownButton\">". "<span>" . __('Select')."</span>"; print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">"; - print "<div onclick=\"Tables.select('prefErrorFeedList', true)\" + print "<div onclick=\"Tables.select('error-feeds-list', true)\" dojoType=\"dijit.MenuItem\">".__('All')."</div>"; - print "<div onclick=\"Tables.select('prefErrorFeedList', false)\" + print "<div onclick=\"Tables.select('error-feeds-list', false)\" dojoType=\"dijit.MenuItem\">".__('None')."</div>"; print "</div></div>"; print "</div>"; #toolbar - print "<div class=\"inactiveFeedHolder\">"; - - print "<table width=\"100%\" cellspacing=\"0\" id=\"prefErrorFeedList\">"; + print "<div class='panel panel-scrollable'>"; + print "<table width='100%' id='error-feeds-list'>"; $lnum = 1; @@ -1500,7 +1500,7 @@ class Pref_Feeds extends Handler_Protected { $feed_id = $line["id"]; - print "<tr class=\"placeholder\" data-row-id='$feed_id'>"; + print "<tr data-row-id='$feed_id'>"; print "<td width='5%' align='center'><input onclick='Tables.onRowChecked(this);' dojoType=\"dijit.form.CheckBox\" diff --git a/classes/pref/filters.php b/classes/pref/filters.php index 0bb1493e5..ec980d5f9 100755 --- a/classes/pref/filters.php +++ b/classes/pref/filters.php @@ -199,8 +199,8 @@ class Pref_Filters extends Handler_Protected { print "<div><img id='prefFilterLoadingIndicator' src='images/indicator_tiny.gif'> <span id='prefFilterProgressMsg'>Looking for articles...</span></div>"; - print "<br/><div class=\"filterTestHolder\">"; - print "<table width=\"100%\" cellspacing=\"0\" id=\"prefFilterTestResultList\">"; + print "<br/><div class='panel panel-scrollable'>"; + print "<table width='100%' id='prefFilterTestResultList'>"; print "</table></div>"; print "<div style='text-align : center'>"; @@ -320,10 +320,10 @@ class Pref_Filters extends Handler_Protected { $label_sth->execute([$line['action_param'], $_SESSION['uid']]); if ($label_row = $label_sth->fetch()) { - $fg_color = $label_row["fg_color"]; + //$fg_color = $label_row["fg_color"]; $bg_color = $label_row["bg_color"]; - $name[1] = "<span class=\"labelColorIndicator\" id=\"label-editor-indicator\" style='color : $fg_color; background-color : $bg_color; margin-right : 4px'>α</span>" . $name[1]; + $name[1] = "<i class=\"material-icons\" style='color : $bg_color; margin-right : 4px'>label</i>" . $name[1]; } } @@ -333,6 +333,7 @@ class Pref_Filters extends Handler_Protected { $filter['name'] = $name[0]; $filter['param'] = $name[1]; $filter['checkbox'] = false; + $filter['last_triggered'] = $line["last_triggered"] ? make_local_datetime($line["last_triggered"], false) : null; $filter['enabled'] = $line["enabled"]; $filter['rules'] = $this->getfilterrules_concise($line['id']); @@ -518,7 +519,7 @@ class Pref_Filters extends Handler_Protected { __('Remove')."</button>"; print "</div>"; - print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').test()\">". + print "<button dojoType=\"dijit.form.Button\" class=\"alt-info\" onclick=\"return dijit.byId('filterEditDlg').test()\">". __('Test')."</button> "; print "<button dojoType=\"dijit.form.Button\" type=\"submit\" class=\"alt-primary\" onclick=\"return dijit.byId('filterEditDlg').execute()\">". @@ -771,9 +772,9 @@ class Pref_Filters extends Handler_Protected { $filter_search = $_SESSION["prefs_filter_search"]; } - print "<div id=\"pref-filter-wrap\" dojoType=\"dijit.layout.BorderContainer\" gutters=\"false\">"; - print "<div id=\"pref-filter-header\" dojoType=\"dijit.layout.ContentPane\" region=\"top\">"; - print "<div id=\"pref-filter-toolbar\" dojoType=\"dijit.Toolbar\">"; + print "<div dojoType='dijit.layout.BorderContainer' gutters='false'>"; + print "<div style='padding : 0px' dojoType='dijit.layout.ContentPane' region='top'>"; + print "<div dojoType='dijit.Toolbar'>"; if (array_key_exists("search", $_REQUEST)) { $_SESSION["prefs_filter_search"] = $filter_search; @@ -815,9 +816,9 @@ class Pref_Filters extends Handler_Protected { print "</div>"; # toolbar print "</div>"; # toolbar-frame - print "<div id=\"pref-filter-content\" dojoType=\"dijit.layout.ContentPane\" region=\"center\">"; + print "<div style='padding : 0px' dojoType='dijit.layout.ContentPane' region='center'>"; - print "<div id=\"filterlistLoading\"> + print "<div id='filterlistLoading'> <img src='images/indicator_tiny.gif'>". __("Loading, please wait...")."</div>"; @@ -857,7 +858,7 @@ class Pref_Filters extends Handler_Protected { function newfilter() { - print "<form name='filter_new_form' id='filter_new_form'>"; + print "<form name='filter_new_form' id='filter_new_form' onsubmit='return false'>"; print_hidden("op", "pref-filters"); print_hidden("method", "add"); @@ -935,10 +936,10 @@ class Pref_Filters extends Handler_Protected { print "<div class=\"dlgButtons\">"; - print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').test()\">". + print "<button dojoType=\"dijit.form.Button\" class=\"alt-info\" onclick=\"return dijit.byId('filterEditDlg').test()\">". __('Test')."</button> "; - print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').execute()\">". + print "<button dojoType=\"dijit.form.Button\" type=\"submit\" class=\"alt-primary\" onclick=\"return dijit.byId('filterEditDlg').execute()\">". __('Create')."</button> "; print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').hide()\">". diff --git a/classes/pref/labels.php b/classes/pref/labels.php index 1e297818c..a58e33ac9 100644 --- a/classes/pref/labels.php +++ b/classes/pref/labels.php @@ -29,12 +29,9 @@ class Pref_Labels extends Handler_Protected { $fg_color = $line['fg_color']; $bg_color = $line['bg_color']; - print "<span class=\"labelColorIndicator\" id=\"label-editor-indicator\" style='color : $fg_color; background-color : $bg_color; margin-bottom : 4px; margin-right : 4px'>α</span>"; - - print "<input style=\"font-size : 16px\" name=\"caption\" - dojoType=\"dijit.form.ValidationTextBox\" - required=\"true\" - value=\"".htmlspecialchars($line['caption'])."\">"; + print "<input style='font-size : 16px; color : $fg_color; background : $bg_color; transition : background 0.1s linear' + id='labelEdit_caption' name='caption' dojoType='dijit.form.ValidationTextBox' + required='true' value=\"".htmlspecialchars($line['caption'])."\">"; print "</div>"; print "<div class=\"dlgSec\">" . __("Colors") . "</div>"; @@ -56,8 +53,8 @@ class Pref_Labels extends Handler_Protected { print "<div dojoType=\"dijit.ColorPalette\"> <script type=\"dojo/method\" event=\"onChange\" args=\"fg_color\"> - dijit.byId(\"labelEdit_fgColor\").attr('value', fg_color); - $('label-editor-indicator').setStyle({color: fg_color}); + dijit.byId('labelEdit_fgColor').attr('value', fg_color); + dijit.byId('labelEdit_caption').domNode.setStyle({color: fg_color}); </script> </div>"; print "</div>"; @@ -66,8 +63,8 @@ class Pref_Labels extends Handler_Protected { print "<div dojoType=\"dijit.ColorPalette\"> <script type=\"dojo/method\" event=\"onChange\" args=\"bg_color\"> - dijit.byId(\"labelEdit_bgColor\").attr('value', bg_color); - $('label-editor-indicator').setStyle({backgroundColor: bg_color}); + dijit.byId('labelEdit_bgColor').attr('value', bg_color); + dijit.byId('labelEdit_caption').domNode.setStyle({backgroundColor: bg_color}); </script> </div>"; print "</div>"; @@ -147,13 +144,11 @@ class Pref_Labels extends Handler_Protected { $sth->execute([$fg, $bg, $id, $_SESSION['uid']]); } - $caption = Labels::find_caption($id, $_SESSION["uid"]); - /* Remove cached data */ $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET label_cache = '' - WHERE label_cache LIKE ? AND owner_uid = ?"); - $sth->execute(["%$caption%", $_SESSION['uid']]); + WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); } } @@ -166,13 +161,11 @@ class Pref_Labels extends Handler_Protected { AND owner_uid = ?"); $sth->execute([$id, $_SESSION['uid']]); - $caption = Labels::find_caption($id, $_SESSION["uid"]); - /* Remove cached data */ $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET label_cache = '' - WHERE label_cache LIKE ? AND owner_uid = ?"); - $sth->execute(["%$caption%", $_SESSION['uid']]); + WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); } } @@ -262,11 +255,11 @@ class Pref_Labels extends Handler_Protected { function index() { - print "<div id=\"pref-label-wrap\" dojoType=\"dijit.layout.BorderContainer\" gutters=\"false\">"; - print "<div id=\"pref-label-header\" dojoType=\"dijit.layout.ContentPane\" region=\"top\">"; - print "<div id=\"pref-label-toolbar\" dojoType=\"dijit.Toolbar\">"; + print "<div dojoType='dijit.layout.BorderContainer' gutters='false'>"; + print "<div style='padding : 0px' dojoType='dijit.layout.ContentPane' region='top'>"; + print "<div dojoType='dijit.Toolbar'>"; - print "<div dojoType=\"dijit.form.DropDownButton\">". + print "<div dojoType='dijit.form.DropDownButton'>". "<span>" . __('Select')."</span>"; print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">"; print "<div onclick=\"dijit.byId('labelTree').model.setAllChecked(true)\" @@ -287,7 +280,7 @@ class Pref_Labels extends Handler_Protected { print "</div>"; #toolbar print "</div>"; #pane - print "<div id=\"pref-label-content\" dojoType=\"dijit.layout.ContentPane\" region=\"center\">"; + print "<div style='padding : 0px' dojoType=\"dijit.layout.ContentPane\" region=\"center\">"; print "<div id=\"labellistLoading\"> <img src='images/indicator_tiny.gif'>". diff --git a/classes/pref/prefs.php b/classes/pref/prefs.php index af827af58..4af0bef33 100644 --- a/classes/pref/prefs.php +++ b/classes/pref/prefs.php @@ -101,20 +101,23 @@ class Pref_Prefs extends Handler_Protected { $value = $_POST[$pref_name]; - if ($pref_name == 'DIGEST_PREFERRED_TIME') { - if (get_pref('DIGEST_PREFERRED_TIME') != $value) { + switch ($pref_name) { + case 'DIGEST_PREFERRED_TIME': + if (get_pref('DIGEST_PREFERRED_TIME') != $value) { - $sth = $this->pdo->prepare("UPDATE ttrss_users SET + $sth = $this->pdo->prepare("UPDATE ttrss_users SET last_digest_sent = NULL WHERE id = ?"); - $sth->execute([$_SESSION['uid']]); - - } - } + $sth->execute([$_SESSION['uid']]); - if ($pref_name == "USER_LANGUAGE") { - if ($_SESSION["language"] != $value) { - $need_reload = true; - } + } + break; + case 'USER_LANGUAGE': + if (!$need_reload) $need_reload = $_SESSION["language"] != $value; + break; + + case 'USER_CSS_THEME': + if (!$need_reload) $need_reload = get_pref($pref_name) != $value; + break; } set_pref($pref_name, $value); @@ -176,7 +179,8 @@ class Pref_Prefs extends Handler_Protected { $_SESSION["prefs_op_result"] = ""; print "<div dojoType=\"dijit.layout.AccordionContainer\" region=\"center\">"; - print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Personal data / Authentication')."\">"; + print "<div dojoType=\"dijit.layout.AccordionPane\" + title=\"<i class='material-icons'>person</i> ".__('Personal data / Authentication')."\">"; print "<form dojoType=\"dijit.form.Form\" id=\"changeUserdataForm\">"; @@ -422,7 +426,8 @@ class Pref_Prefs extends Handler_Protected { print "</div>"; #pane - print "<div dojoType=\"dijit.layout.AccordionPane\" selected=\"true\" title=\"".__('Preferences')."\">"; + print "<div dojoType=\"dijit.layout.AccordionPane\" selected=\"true\" + title=\"<i class='material-icons'>settings</i> ".__('Preferences')."\">"; print "<form dojoType=\"dijit.form.Form\" id=\"changeSettingsForm\">"; @@ -454,13 +459,9 @@ class Pref_Prefs extends Handler_Protected { $profile = $_SESSION["profile"]; - if (!is_numeric($profile) || !$profile || get_schema_version() < 63) $profile = null; - if ($profile) { print_notice(__("Some preferences are only available in default profile.")); - } - if ($_SESSION["profile"]) { initialize_user_prefs($_SESSION["uid"], $profile); } else { initialize_user_prefs($_SESSION["uid"]); @@ -544,20 +545,29 @@ class Pref_Prefs extends Handler_Protected { print_select($pref_name, $value, $timezones, 'dojoType="dijit.form.FilteringSelect"'); } else if ($pref_name == "USER_STYLESHEET") { - print "<button dojoType=\"dijit.form.Button\" + print "<button dojoType=\"dijit.form.Button\" class='alt-info' onclick=\"Helpers.customizeCSS()\">" . __('Customize') . "</button>"; } else if ($pref_name == "USER_CSS_THEME") { $themes = array_merge(glob("themes/*.php"), glob("themes/*.css"), glob("themes.local/*.css")); $themes = array_map("basename", $themes); - $themes = array_filter($themes, "theme_valid"); + $themes = array_filter($themes, "theme_exists"); asort($themes); - if (!theme_valid($value)) $value = "default.php"; + if (!theme_exists($value)) $value = "default.php"; - print_select($pref_name, $value, $themes, - 'dojoType="dijit.form.Select"'); + print "<select name='$pref_name' id='$pref_name' dojoType='dijit.form.Select'>"; + + $issel = $value == "default.php" ? "selected='selected'" : ""; + print "<option $issel value='default.php'>".__("default")."</option>"; + + foreach ($themes as $theme) { + $issel = $value == $theme ? "selected='selected'" : ""; + print "<option $issel value='$theme'>$theme</option>"; + } + + print "</select>"; } else if ($pref_name == "DEFAULT_UPDATE_INTERVAL") { @@ -679,7 +689,8 @@ class Pref_Prefs extends Handler_Protected { print "</div>"; #pane - print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Plugins')."\">"; + print "<div dojoType=\"dijit.layout.AccordionPane\" + title=\"<i class='material-icons'>extension</i> ".__('Plugins')."\">"; print "<form dojoType=\"dijit.form.Form\" id=\"changePluginsForm\">"; @@ -843,14 +854,16 @@ class Pref_Prefs extends Handler_Protected { print "</div>"; #pane print "</div>"; #pane - print "</doiv>"; #border-container + print "</div>"; #border-container print "</form>"; + PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB, "hook_prefs_tab", "prefPrefs"); print "</div>"; #container + } function toggleAdvanced() { @@ -1002,9 +1015,9 @@ class Pref_Prefs extends Handler_Protected { print "<div dojoType=\"dijit.form.DropDownButton\">". "<span>" . __('Select')."</span>"; print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">"; - print "<div onclick=\"Tables.select('prefFeedProfileList', true)\" + print "<div onclick=\"Tables.select('pref-profiles-list', true)\" dojoType=\"dijit.MenuItem\">".__('All')."</div>"; - print "<div onclick=\"Tables.select('prefFeedProfileList', false)\" + print "<div onclick=\"Tables.select('pref-profiles-list', false)\" dojoType=\"dijit.MenuItem\">".__('None')."</div>"; print "</div></div>"; @@ -1022,19 +1035,15 @@ class Pref_Prefs extends Handler_Protected { WHERE owner_uid = ? ORDER BY title"); $sth->execute([$_SESSION['uid']]); - print "<div class=\"prefProfileHolder\">"; + print "<div class='panel panel-scrollable'>"; - print "<form id=\"profile_edit_form\" onsubmit=\"return false\">"; + print "<form id='profile_edit_form' onsubmit='return false'>"; - print "<table width=\"100%\" class=\"prefFeedProfileList\" - cellspacing=\"0\" id=\"prefFeedProfileList\">"; + print "<table width='100%' id='pref-profiles-list'>"; - print "<tr class=\"placeholder\">"; # data-row-id='0' <-- no point, shouldn't be removed + print "<tr>"; # data-row-id='0' <-- no point, shouldn't be removed - print "<td width='5%' align='center'><input - onclick='Tables.onRowChecked(this);' - dojoType=\"dijit.form.CheckBox\" - type=\"checkbox\"></td>"; + print "<td><input onclick='Tables.onRowChecked(this);' dojoType='dijit.form.CheckBox' type='checkbox'></td>"; if (!$_SESSION["profile"]) { $is_active = __("(active)"); @@ -1042,8 +1051,7 @@ class Pref_Prefs extends Handler_Protected { $is_active = ""; } - print "<td><span>" . - __("Default profile") . " $is_active</span></td>"; + print "<td width='100%'><span>" . __("Default profile") . " $is_active</span></td>"; print "</tr>"; @@ -1053,14 +1061,11 @@ class Pref_Prefs extends Handler_Protected { $profile_id = $line["id"]; - print "<tr class=\"placeholder\" data-row-id='$profile_id'>"; + print "<tr data-row-id='$profile_id'>"; $edit_title = htmlspecialchars($line["title"]); - print "<td width='5%' align='center'><input - onclick='Tables.onRowChecked(this);' - dojoType=\"dijit.form.CheckBox\" - type=\"checkbox\"></td>"; + print "<td><input onclick='Tables.onRowChecked(this);' dojoType='dijit.form.CheckBox' type='checkbox'></td>"; if ($_SESSION["profile"] == $line["id"]) { $is_active = __("(active)"); diff --git a/classes/pref/system.php b/classes/pref/system.php index ef2ca98b0..f44b499c8 100644 --- a/classes/pref/system.php +++ b/classes/pref/system.php @@ -26,7 +26,8 @@ class Pref_System extends Handler_Protected { function index() { print "<div dojoType=\"dijit.layout.AccordionContainer\" region=\"center\">"; - print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Event Log')."\">"; + print "<div dojoType=\"dijit.layout.AccordionPane\" + title=\"<i class='material-icons'>report</i> ".__('Event Log')."\">"; if (LOG_DESTINATION == "sql") { diff --git a/classes/pref/users.php b/classes/pref/users.php index 6fedddf1d..206dc34d5 100644 --- a/classes/pref/users.php +++ b/classes/pref/users.php @@ -153,7 +153,7 @@ class Pref_Users extends Handler_Protected { WHERE owner_uid = ? ORDER BY title"); $sth->execute([$id]); - print "<ul class=\"userFeedList\">"; + print "<ul class=\"panel panel-scrollable list list-unstyled\">"; while ($line = $sth->fetch()) { @@ -326,10 +326,9 @@ class Pref_Users extends Handler_Protected { global $access_level_names; - print "<div id=\"pref-user-wrap\" dojoType=\"dijit.layout.BorderContainer\" gutters=\"false\">"; - print "<div id=\"pref-user-header\" dojoType=\"dijit.layout.ContentPane\" region=\"top\">"; - - print "<div id=\"pref-user-toolbar\" dojoType=\"dijit.Toolbar\">"; + print "<div dojoType='dijit.layout.BorderContainer' gutters='false'>"; + print "<div style='padding : 0px' dojoType='dijit.layout.ContentPane' region='top'>"; + print "<div dojoType='dijit.Toolbar'>"; $user_search = trim(clean($_REQUEST["search"])); @@ -376,7 +375,7 @@ class Pref_Users extends Handler_Protected { print "</div>"; #toolbar print "</div>"; #pane - print "<div id=\"pref-user-content\" dojoType=\"dijit.layout.ContentPane\" region=\"center\">"; + print "<div style='padding : 0px' dojoType='dijit.layout.ContentPane' region='center'>"; $sort = validate_field($sort, ["login", "access_level", "created", "num_feeds", "created", "last_login"], "login"); diff --git a/classes/rpc.php b/classes/rpc.php index bd4337fbe..41325d62a 100755 --- a/classes/rpc.php +++ b/classes/rpc.php @@ -8,7 +8,7 @@ class RPC extends Handler_Protected { } function setprofile() { - $_SESSION["profile"] = clean($_REQUEST["id"]); + $_SESSION["profile"] = (int) clean($_REQUEST["id"]); // default value if (!$_SESSION["profile"]) $_SESSION["profile"] = null; diff --git a/classes/rssutils.php b/classes/rssutils.php index 56108bd1e..8b3c7c0d0 100755 --- a/classes/rssutils.php +++ b/classes/rssutils.php @@ -769,13 +769,36 @@ class RSSUtils { /* Collect article tags here so we could filter by them: */ - $matched_rules = array(); + $matched_rules = []; + $matched_filters = []; $article_filters = RSSUtils::get_article_filters($filters, $article["title"], $article["content"], $article["link"], $article["author"], - $article["tags"], $matched_rules); + $article["tags"], $matched_rules, $matched_filters); + + // $article_filters should be renamed to something like $filter_actions; actual filter objects are in $matched_filters + foreach ($pluginhost->get_hooks(PluginHost::HOOK_FILTER_TRIGGERED) as $plugin) { + $plugin->hook_filter_triggered($feed, $owner_uid, $article, $matched_filters, $matched_rules, $article_filters); + } + + $matched_filter_ids = array_map(function($f) { return $f['id']; }, $matched_filters); + + if (count($matched_filter_ids) > 0) { + $filter_ids_qmarks = arr_qmarks($matched_filter_ids); + + $fsth = $pdo->prepare("UPDATE ttrss_filters2 SET last_triggered = NOW() WHERE + id IN ($filter_ids_qmarks) AND owner_uid = ?"); + + $fsth->execute(array_merge($matched_filter_ids, [$owner_uid])); + } if (Debug::get_loglevel() >= Debug::$LOG_EXTENDED) { + Debug::log("matched filters: ", Debug::$LOG_VERBOSE); + + if (count($matched_filters != 0)) { + print_r($matched_filters); + } + Debug::log("matched filter rules: ", Debug::$LOG_VERBOSE); if (count($matched_rules) != 0) { @@ -900,6 +923,7 @@ class RSSUtils { $entry_ref_id = $ref_id; if (RSSUtils::find_article_filter($article_filters, "filter")) { + Debug::log("article is filtered out, nothing to do."); $pdo->commit(); continue; } @@ -1342,7 +1366,7 @@ class RSSUtils { return $params; } - static function get_article_filters($filters, $title, $content, $link, $author, $tags, &$matched_rules = false) { + static function get_article_filters($filters, $title, $content, $link, $author, $tags, &$matched_rules = false, &$matched_filters = false) { $matches = array(); foreach ($filters as $filter) { @@ -1409,6 +1433,7 @@ class RSSUtils { if ($filter_match) { if (is_array($matched_rules)) array_push($matched_rules, $rule); + if (is_array($matched_filters)) array_push($matched_filters, $filter); foreach ($filter["actions"] AS $action) { array_push($matches, $action); |