]>
git.wh0rd.org - tt-rss.git/blob - classes/article.php
2 class Article
extends Handler_Protected
{
4 function csrf_ignore($method) {
5 $csrf_ignored = array("redirect", "editarticletags");
7 return array_search($method, $csrf_ignored) !== false;
11 $sth = $this->pdo
->prepare("SELECT link FROM ttrss_entries, ttrss_user_entries
12 WHERE id = ? AND id = ref_id AND owner_uid = ?
14 $sth->execute([$id, $_SESSION['uid']]);
16 if ($row = $sth->fetch()) {
17 $article_url = $row['link'];
18 $article_url = str_replace("\n", "", $article_url);
20 header("Location: $article_url");
24 print_error(__("Article not found."));
29 $id = $_REQUEST["id"];
30 $cids = explode(",", $_REQUEST["cids"]);
31 $mode = $_REQUEST["mode"];
33 // in prefetch mode we only output requested cids, main article
34 // just gets marked as read (it already exists in client cache)
39 array_push($articles, $this->format_article($id, false));
40 } else if ($mode == "zoom") {
41 array_push($articles, $this->format_article($id, true, true));
42 } else if ($mode == "raw") {
43 if (isset($_REQUEST['html'])) {
44 header("Content-Type: text/html");
45 print '<link rel="stylesheet" type="text/css" href="css/default.css"/>';
48 $article = $this->format_article($id, false, isset($_REQUEST["zoom"]));
49 print $article['content'];
53 $this->catchupArticleById($id, 0);
55 if (!$_SESSION["bw_limit"]) {
56 foreach ($cids as $cid) {
58 array_push($articles, $this->format_article($cid, false, false));
63 print json_encode($articles);
66 private function catchupArticleById($id, $cmode) {
69 $sth = $this->pdo
->prepare("UPDATE ttrss_user_entries SET
70 unread = false,last_read = NOW()
71 WHERE ref_id = ? AND owner_uid = ?");
72 } else if ($cmode == 1) {
73 $sth = $this->pdo
->prepare("UPDATE ttrss_user_entries SET
75 WHERE ref_id = ? AND owner_uid = ?");
77 $sth = $this->pdo
->prepare("UPDATE ttrss_user_entries SET
78 unread = NOT unread,last_read = NOW()
79 WHERE ref_id = ? AND owner_uid = ?");
82 $sth->execute([$id, $_SESSION['uid']]);
84 $feed_id = $this->getArticleFeed($id);
85 CCache
::update($feed_id, $_SESSION["uid"]);
88 static function create_published_article($title, $url, $content, $labels_str,
91 $guid = 'SHA1:' . sha1("ttshared:" . $url . $owner_uid); // include owner_uid to prevent global GUID clash
94 $pluginhost = new PluginHost();
95 $pluginhost->load_all(PluginHost
::KIND_ALL
, $owner_uid);
96 $pluginhost->load_data();
98 $af_readability = $pluginhost->get_plugin("Af_Readability");
100 if ($af_readability) {
101 $enable_share_anything = $pluginhost->get($af_readability, "enable_share_anything");
103 if ($enable_share_anything) {
104 $extracted_content = $af_readability->extract_content($url);
106 if ($extracted_content) $content = $extracted_content;
111 $content_hash = sha1($content);
113 if ($labels_str != "") {
114 $labels = explode(",", $labels_str);
121 if (!$title) $title = $url;
122 if (!$title && !$url) return false;
124 if (filter_var($url, FILTER_VALIDATE_URL
) === FALSE) return false;
128 $pdo->beginTransaction();
130 // only check for our user data here, others might have shared this with different content etc
131 $sth = $pdo->prepare("SELECT id FROM ttrss_entries, ttrss_user_entries WHERE
132 guid = ? AND ref_id = id AND owner_uid = ? LIMIT 1");
133 $sth->execute([$guid, $owner_uid]);
135 if ($row = $sth->fetch()) {
136 $ref_id = $row['id'];
138 $sth = $pdo->prepare("SELECT int_id FROM ttrss_user_entries WHERE
139 ref_id = ? AND owner_uid = ? LIMIT 1");
140 $sth->execute([$ref_id, $owner_uid]);
142 if ($row = $sth->fetch()) {
143 $int_id = $row['int_id'];
145 $sth = $pdo->prepare("UPDATE ttrss_entries SET
146 content = ?, content_hash = ? WHERE id = ?");
147 $sth->execute([$content, $content_hash, $ref_id]);
149 $sth = $pdo->prepare("UPDATE ttrss_user_entries SET published = true,
150 last_published = NOW() WHERE
151 int_id = ? AND owner_uid = ?");
152 $sth->execute([$int_id, $owner_uid]);
156 $sth = $pdo->prepare("INSERT INTO ttrss_user_entries
157 (ref_id, uuid, feed_id, orig_feed_id, owner_uid, published, tag_cache, label_cache,
158 last_read, note, unread, last_published)
160 (?, '', NULL, NULL, ?, true, '', '', NOW(), '', false, NOW())");
161 $sth->execute([$ref_id, $owner_uid]);
164 if (count($labels) != 0) {
165 foreach ($labels as $label) {
166 Labels
::add_article($ref_id, trim($label), $owner_uid);
173 $sth = $pdo->prepare("INSERT INTO ttrss_entries
174 (title, guid, link, updated, content, content_hash, date_entered, date_updated)
176 (?, ?, ?, NOW(), ?, ?, NOW(), NOW())");
177 $sth->execute([$title, $guid, $url, $content, $content_hash]);
179 $sth = $pdo->prepare("SELECT id FROM ttrss_entries WHERE guid = ?");
180 $sth->execute([$guid]);
182 if ($row = $sth->fetch()) {
183 $ref_id = $row["id"];
185 $sth = $pdo->prepare("INSERT INTO ttrss_user_entries
186 (ref_id, uuid, feed_id, orig_feed_id, owner_uid, published, tag_cache, label_cache,
187 last_read, note, unread, last_published)
189 (?, '', NULL, NULL, ?, true, '', '', NOW(), '', false, NOW())");
190 $sth->execute([$ref_id, $owner_uid]);
192 if (count($labels) != 0) {
193 foreach ($labels as $label) {
194 Labels
::add_article($ref_id, trim($label), $owner_uid);
207 function editArticleTags() {
209 print __("Tags for this article (separated by commas):")."<br>";
211 $param = $_REQUEST['param'];
213 $tags = Article
::get_article_tags($param);
215 $tags_str = join(", ", $tags);
217 print_hidden("id", "$param");
218 print_hidden("op", "article");
219 print_hidden("method", "setArticleTags");
221 print "<table width='100%'><tr><td>";
223 print "<textarea dojoType=\"dijit.form.SimpleTextarea\" rows='4'
224 style='height : 100px; font-size : 12px; width : 98%' id=\"tags_str\"
225 name='tags_str'>$tags_str</textarea>
226 <div class=\"autocomplete\" id=\"tags_choices\"
227 style=\"display:none\"></div>";
229 print "</td></tr></table>";
231 print "<div class='dlgButtons'>";
233 print "<button dojoType=\"dijit.form.Button\"
234 onclick=\"dijit.byId('editTagsDlg').execute()\">".__('Save')."</button> ";
235 print "<button dojoType=\"dijit.form.Button\"
236 onclick=\"dijit.byId('editTagsDlg').hide()\">".__('Cancel')."</button>";
241 function setScore() {
242 $ids = explode(",", $_REQUEST['id']);
243 $score = (int)$_REQUEST['score'];
245 $ids_qmarks = arr_qmarks($ids);
247 $sth = $this->pdo
->prepare("UPDATE ttrss_user_entries SET
248 score = ? WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
250 $sth->execute(array_merge([$score], $ids, [$_SESSION['uid']]));
252 print json_encode(array("id" => $ids,
253 "score" => (int)$score,
254 "score_pic" => get_score_pic($score)));
257 function getScore() {
258 $id = $_REQUEST['id'];
260 $sth = $this->pdo
->prepare("SELECT score FROM ttrss_user_entries WHERE ref_id = ? AND owner_uid = ?");
261 $sth->execute([$id, $_SESSION['uid']]);
262 $row = $sth->fetch();
264 $score = $row['score'];
266 print json_encode(array("id" => $id,
267 "score" => (int)$score,
268 "score_pic" => get_score_pic($score)));
272 function setArticleTags() {
274 $id = $_REQUEST["id"];
276 $tags_str = $_REQUEST["tags_str"];
277 $tags = array_unique(trim_array(explode(",", $tags_str)));
279 $this->pdo
->beginTransaction();
281 $sth = $this->pdo
->prepare("SELECT int_id FROM ttrss_user_entries WHERE
282 ref_id = ? AND owner_uid = ? LIMIT 1");
283 $sth->execute([$id, $_SESSION['uid']]);
285 if ($row = $sth->fetch()) {
287 $tags_to_cache = array();
289 $int_id = $row['int_id'];
291 $sth = $this->pdo
->prepare("DELETE FROM ttrss_tags WHERE
292 post_int_id = ? AND owner_uid = ?");
293 $sth->execute([$int_id, $_SESSION['uid']]);
295 foreach ($tags as $tag) {
296 $tag = sanitize_tag($tag);
298 if (!tag_is_valid($tag)) {
302 if (preg_match("/^[0-9]*$/", $tag)) {
306 // print "<!-- $id : $int_id : $tag -->";
309 $sth = $this->pdo
->prepare("INSERT INTO ttrss_tags
310 (post_int_id, owner_uid, tag_name)
313 $sth->execute([$int_id, $_SESSION['uid'], $tag]);
316 array_push($tags_to_cache, $tag);
319 /* update tag cache */
321 sort($tags_to_cache);
322 $tags_str = join(",", $tags_to_cache);
324 $sth = $this->pdo
->prepare("UPDATE ttrss_user_entries
325 SET tag_cache = ? WHERE ref_id = ? AND owner_uid = ?");
326 $sth->execute([$tags_str, $id, $_SESSION['uid']]);
329 $this->pdo
->commit();
331 $tags = Article
::get_article_tags($id);
332 $tags_str = $this->format_tags_string($tags, $id);
333 $tags_str_full = join(", ", $tags);
335 if (!$tags_str_full) $tags_str_full = __("no tags");
337 print json_encode(array("id" => (int)$id,
338 "content" => $tags_str, "content_full" => $tags_str_full));
342 function completeTags() {
343 $search = $_REQUEST["search"];
345 $sth = $this->pdo
->prepare("SELECT DISTINCT tag_name FROM ttrss_tags
346 WHERE owner_uid = ? AND
347 tag_name LIKE ? ORDER BY tag_name
350 $sth->execute([$_SESSION['uid'], "$search%"]);
353 while ($line = $sth->fetch()) {
354 print "<li>" . $line["tag_name"] . "</li>";
359 function assigntolabel() {
360 return $this->labelops(true);
363 function removefromlabel() {
364 return $this->labelops(false);
367 private function labelops($assign) {
370 $ids = explode(",", $_REQUEST["ids"]);
371 $label_id = $_REQUEST["lid"];
373 $label = db_escape_string(Labels
::find_caption($label_id,
376 $reply["info-for-headlines"] = array();
380 foreach ($ids as $id) {
383 Labels
::add_article($id, $label, $_SESSION["uid"]);
385 Labels
::remove_article($id, $label, $_SESSION["uid"]);
387 $labels = $this->get_article_labels($id, $_SESSION["uid"]);
389 array_push($reply["info-for-headlines"],
390 array("id" => $id, "labels" => $this->format_article_labels($labels)));
395 $reply["message"] = "UPDATE_COUNTERS";
397 print json_encode($reply);
400 function getArticleFeed($id) {
401 $sth = $this->pdo
->prepare("SELECT feed_id FROM ttrss_user_entries
402 WHERE ref_id = ? AND owner_uid = ?");
403 $sth->execute([$id, $_SESSION['uid']]);
405 if ($row = $sth->fetch()) {
406 return $row["feed_id"];
412 static function format_article_enclosures($id, $always_display_enclosures,
413 $article_content, $hide_images = false) {
415 $result = Article
::get_article_enclosures($id);
418 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_FORMAT_ENCLOSURES
) as $plugin) {
419 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
420 if (is_array($retval)) {
422 $result = $retval[1];
427 unset($retval); // Unset to prevent breaking render if there are no HOOK_RENDER_ENCLOSURE hooks below.
429 if ($rv === '' && !empty($result)) {
430 $entries_html = array();
432 $entries_inline = array();
434 foreach ($result as $line) {
436 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ENCLOSURE_ENTRY
) as $plugin) {
437 $line = $plugin->hook_enclosure_entry($line);
440 $url = $line["content_url"];
441 $ctype = $line["content_type"];
442 $title = $line["title"];
443 $width = $line["width"];
444 $height = $line["height"];
446 if (!$ctype) $ctype = __("unknown type");
448 //$filename = substr($url, strrpos($url, "/")+1);
449 $filename = basename($url);
451 $player = format_inline_player($url, $ctype);
453 if ($player) array_push($entries_inline, $player);
455 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\" rel=\"noopener noreferrer\">" .
456 # $filename . " (" . $ctype . ")" . "</a>";
458 $entry = "<div onclick=\"openUrlPopup('".htmlspecialchars($url)."')\"
459 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
461 array_push($entries_html, $entry);
465 $entry["type"] = $ctype;
466 $entry["filename"] = $filename;
467 $entry["url"] = $url;
468 $entry["title"] = $title;
469 $entry["width"] = $width;
470 $entry["height"] = $height;
472 array_push($entries, $entry);
475 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
476 if ($always_display_enclosures ||
477 !preg_match("/<img/i", $article_content)) {
479 foreach ($entries as $entry) {
481 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_RENDER_ENCLOSURE
) as $plugin)
482 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
489 if (preg_match("/image/", $entry["type"])) {
493 if ($entry['height'] > 0)
494 $encsize .= ' height="' . intval($entry['height']) . '"';
495 if ($entry['width'] > 0)
496 $encsize .= ' width="' . intval($entry['width']) . '"';
498 alt=\"".htmlspecialchars($entry["filename"])."\"
499 src=\"" .htmlspecialchars($entry["url"]) . "\"
500 " . $encsize . " /></p>";
502 $rv .= "<p><a target=\"_blank\" rel=\"noopener noreferrer\"
503 href=\"".htmlspecialchars($entry["url"])."\"
504 >" .htmlspecialchars($entry["url"]) . "</a></p>";
507 if ($entry['title']) {
508 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
516 if (count($entries_inline) > 0) {
517 $rv .= "<hr clear='both'/>";
518 foreach ($entries_inline as $entry) { $rv .= $entry; };
519 $rv .= "<hr clear='both'/>";
522 $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
523 "<span>" . __('Attachments')."</span>";
525 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
527 foreach ($entries as $entry) {
529 $title = " — " . truncate_string($entry["title"], 30);
533 if ($entry["filename"])
534 $filename = truncate_middle(htmlspecialchars($entry["filename"]), 60);
538 $rv .= "<div onclick='openUrlPopup(\"".htmlspecialchars($entry["url"])."\")'
539 dojoType=\"dijit.MenuItem\">".$filename . $title."</div>";
550 static function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
551 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
557 /* we can figure out feed_id from article id anyway, why do we
558 * pass feed_id here? let's ignore the argument :(*/
562 $sth = $pdo->prepare("SELECT feed_id FROM ttrss_user_entries
564 $sth->execute([$id]);
565 $row = $sth->fetch();
567 $feed_id = (int) $row["feed_id"];
569 $rv['feed_id'] = $feed_id;
571 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
574 $sth = $pdo->prepare("UPDATE ttrss_user_entries
575 SET unread = false,last_read = NOW()
576 WHERE ref_id = ? AND owner_uid = ?");
577 $sth->execute([$id, $owner_uid]);
579 CCache
::update($feed_id, $owner_uid);
582 $sth = $pdo->prepare("SELECT id,title,link,content,feed_id,comments,int_id,lang,
583 ".SUBSTRING_FOR_DATE
."(updated,1,16) as updated,
584 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
585 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
586 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
587 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
594 FROM ttrss_entries,ttrss_user_entries
595 WHERE id = ? AND ref_id = id AND owner_uid = ?");
596 $sth->execute([$id, $owner_uid]);
598 if ($line = $sth->fetch()) {
600 $line["tags"] = Article
::get_article_tags($id, $owner_uid, $line["tag_cache"]);
601 unset($line["tag_cache"]);
603 $line["content"] = sanitize($line["content"],
604 sql_bool_to_bool($line['hide_images']),
605 $owner_uid, $line["site_url"], false, $line["id"]);
607 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_RENDER_ARTICLE
) as $p) {
608 $line = $p->hook_render_article($line);
611 $num_comments = (int) $line["num_comments"];
612 $entry_comments = "";
614 if ($num_comments > 0) {
615 if ($line["comments"]) {
616 $comments_url = htmlspecialchars($line["comments"]);
618 $comments_url = htmlspecialchars($line["link"]);
620 $entry_comments = "<a class=\"postComments\"
621 target='_blank' rel=\"noopener noreferrer\" href=\"$comments_url\">$num_comments ".
622 _ngettext("comment", "comments", $num_comments)."</a>";
625 if ($line["comments"] && $line["link"] != $line["comments"]) {
626 $entry_comments = "<a class=\"postComments\" target='_blank' rel=\"noopener noreferrer\" href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
631 header("Content-Type: text/html");
632 $rv['content'] .= "<html><head>
633 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
634 <title>".$line["title"]."</title>".
635 stylesheet_tag("css/default.css")."
637 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
638 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
640 </head><body id=\"ttrssZoom\">";
643 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
645 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
647 $entry_author = $line["author"];
650 $entry_author = __(" - ") . $entry_author;
653 $parsed_updated = make_local_datetime($line["updated"], true,
657 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
660 $rv['content'] .= "<div class='postTitle'><a target='_blank' rel='noopener noreferrer'
661 title=\"".htmlspecialchars($line['title'])."\"
663 htmlspecialchars($line["link"]) . "\">" .
664 $line["title"] . "</a>" .
665 "<span class='author'>$entry_author</span></div>";
667 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
671 $feed_title = htmlspecialchars($line["feed_title"]);
673 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
675 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
678 $tags_str = Article
::format_tags_string($line["tags"], $id);
679 $tags_str_full = join(", ", $line["tags"]);
681 if (!$tags_str_full) $tags_str_full = __("no tags");
683 if (!$entry_comments) $entry_comments = " "; # placeholder
685 $rv['content'] .= "<div class='postTags' style='float : right'>
686 <img src='images/tag.png'
687 class='tagsPic' alt='Tags' title='Tags'> ";
690 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
691 <a title=\"".__('Edit tags for this article')."\"
692 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
694 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
695 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
696 position=\"below\">$tags_str_full</div>";
698 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_BUTTON
) as $p) {
699 $rv['content'] .= $p->hook_article_button($line);
703 $tags_str = strip_tags($tags_str);
704 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
706 $rv['content'] .= "</div>";
707 $rv['content'] .= "<div clear='both'>";
709 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_LEFT_BUTTON
) as $p) {
710 $rv['content'] .= $p->hook_article_left_button($line);
713 $rv['content'] .= "$entry_comments</div>";
715 if ($line["orig_feed_id"]) {
717 $of_sth = $pdo->prepare("SELECT * FROM ttrss_archived_feeds
718 WHERE id = ? AND owner_uid = ?");
719 $of_sth->execute([$line["orig_feed_id"], $owner_uid]);
721 if ($tmp_line = $of_sth->fetch()) {
723 $rv['content'] .= "<div clear='both'>";
724 $rv['content'] .= __("Originally from:");
726 $rv['content'] .= " ";
728 $rv['content'] .= "<a target='_blank' rel='noopener noreferrer'
729 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
730 $tmp_line['title'] . "</a>";
732 $rv['content'] .= " ";
734 $rv['content'] .= "<a target='_blank' rel='noopener noreferrer' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
735 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
737 $rv['content'] .= "</div>";
741 $rv['content'] .= "</div>";
743 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
745 $rv['content'] .= Article
::format_article_note($id, $line['note'], !$zoom_mode);
747 $rv['content'] .= "</div>";
749 if (!$line['lang']) $line['lang'] = 'en';
751 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
753 $rv['content'] .= $line["content"];
756 $rv['content'] .= Article
::format_article_enclosures($id,
757 sql_bool_to_bool($line["always_display_enclosures"]),
759 sql_bool_to_bool($line["hide_images"]));
762 $rv['content'] .= "</div>";
764 $rv['content'] .= "</div>";
771 <button onclick=\"return window.close()\">".
772 __("Close this window")."</button></div>";
773 $rv['content'] .= "</body></html>";
776 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_FORMAT_ARTICLE
) as $p) {
777 $rv['content'] = $p->hook_format_article($rv['content'], $line, $zoom_mode);
784 static function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
788 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
792 $sth = $pdo->prepare("SELECT DISTINCT tag_name,
793 owner_uid as owner FROM ttrss_tags
794 WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
795 ref_id = ? AND owner_uid = ? LIMIT 1) ORDER BY tag_name");
799 /* check cache first */
801 if ($tag_cache === false) {
802 $csth = $pdo->prepare("SELECT tag_cache FROM ttrss_user_entries
803 WHERE ref_id = ? AND owner_uid = ?");
804 $csth->execute([$id, $owner_uid]);
806 if ($row = $csth->fetch()) $tag_cache = $row["tag_cache"];
810 $tags = explode(",", $tag_cache);
813 /* do it the hard way */
815 $sth->execute([$a_id, $owner_uid]);
817 while ($tmp_line = $sth->fetch()) {
818 array_push($tags, $tmp_line["tag_name"]);
821 /* update the cache */
823 $tags_str = join(",", $tags);
825 $sth = $pdo->prepare("UPDATE ttrss_user_entries
826 SET tag_cache = ? WHERE ref_id = ?
828 $sth->execute([$tags_str, $id, $owner_uid]);
834 static function format_tags_string($tags) {
835 if (!is_array($tags) ||
count($tags) == 0) {
836 return __("no tags");
838 $maxtags = min(5, count($tags));
841 for ($i = 0; $i < $maxtags; $i++
) {
842 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
845 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
847 if (count($tags) > $maxtags)
848 $tags_str .= ", …";
854 static function format_article_labels($labels) {
856 if (!is_array($labels)) return '';
860 foreach ($labels as $l) {
861 $labels_str .= sprintf("<span class='hlLabelRef'
862 style='color : %s; background-color : %s'>%s</span>",
863 $l[2], $l[3], $l[1]);
870 static function format_article_note($id, $note, $allow_edit = true) {
872 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
873 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
874 ($allow_edit ?
__('(edit note)') : "")."</div>$note</div>";
879 static function get_article_enclosures($id) {
883 $sth = $pdo->prepare("SELECT * FROM ttrss_enclosures
884 WHERE post_id = ? AND content_url != ''");
885 $sth->execute([$id]);
889 while ($line = $sth->fetch()) {
891 if (file_exists(CACHE_DIR
. '/images/' . sha1($line["content_url"]))) {
892 $line["content_url"] = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($line["content_url"]);
895 array_push($rv, $line);
901 static function purge_orphans($do_output = false) {
903 // purge orphaned posts in main content table
906 $res = $pdo->query("DELETE FROM ttrss_entries WHERE
907 NOT EXISTS (SELECT ref_id FROM ttrss_user_entries WHERE ref_id = id)");
910 $rows = $res->rowCount();
911 _debug("Purged $rows orphaned posts.");
915 static function catchupArticlesById($ids, $cmode, $owner_uid = false) {
917 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
921 $ids_qmarks = arr_qmarks($ids);
924 $sth = $pdo->prepare("UPDATE ttrss_user_entries SET
925 unread = false,last_read = NOW()
926 WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
927 } else if ($cmode == 1) {
928 $sth = $pdo->prepare("UPDATE ttrss_user_entries SET
930 WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
932 $sth = $pdo->prepare("UPDATE ttrss_user_entries SET
933 unread = NOT unread,last_read = NOW()
934 WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
937 $sth->execute(array_merge($ids, [$owner_uid]));
941 $sth = $pdo->prepare("SELECT DISTINCT feed_id FROM ttrss_user_entries
942 WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
943 $sth->execute(array_merge($ids, [$owner_uid]));
945 while ($line = $sth->fetch()) {
946 CCache
::update($line["feed_id"], $owner_uid);
950 static function getLastArticleId() {
953 $sth = $pdo->prepare("SELECT ref_id AS id FROM ttrss_user_entries
954 WHERE owner_uid = ? ORDER BY ref_id DESC LIMIT 1");
955 $sth->execute([$_SESSION['uid']]);
957 if ($row = $sth->fetch()) {
964 static function get_article_labels($id, $owner_uid = false) {
967 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
971 $sth = $pdo->prepare("SELECT label_cache FROM
972 ttrss_user_entries WHERE ref_id = ? AND owner_uid = ?");
973 $sth->execute([$id, $owner_uid]);
975 if ($row = $sth->fetch()) {
976 $label_cache = $row["label_cache"];
979 $label_cache = json_decode($label_cache, true);
981 if ($label_cache["no-labels"] == 1)
988 $sth = $pdo->prepare("SELECT DISTINCT label_id,caption,fg_color,bg_color
989 FROM ttrss_labels2, ttrss_user_labels2
994 $sth->execute([$id, $owner_uid]);
996 while ($line = $sth->fetch()) {
997 $rk = array(Labels
::label_to_feed_id($line["label_id"]),
998 $line["caption"], $line["fg_color"],
1000 array_push($rv, $rk);
1004 Labels
::update_cache($owner_uid, $id, $rv);
1006 Labels
::update_cache($owner_uid, $id, array("no-labels" => 1));