article.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  1. <?php
  2. class Article extends Handler_Protected {
  3. function csrf_ignore($method) {
  4. $csrf_ignored = array("redirect", "editarticletags");
  5. return array_search($method, $csrf_ignored) !== false;
  6. }
  7. function redirect() {
  8. $id = clean($_REQUEST['id']);
  9. $sth = $this->pdo->prepare("SELECT link FROM ttrss_entries, ttrss_user_entries
  10. WHERE id = ? AND id = ref_id AND owner_uid = ?
  11. LIMIT 1");
  12. $sth->execute([$id, $_SESSION['uid']]);
  13. if ($row = $sth->fetch()) {
  14. $article_url = $row['link'];
  15. $article_url = str_replace("\n", "", $article_url);
  16. header("Location: $article_url");
  17. return;
  18. } else {
  19. print_error(__("Article not found."));
  20. }
  21. }
  22. /*
  23. function view() {
  24. $id = clean($_REQUEST["id"]);
  25. $cids = explode(",", clean($_REQUEST["cids"]));
  26. $mode = clean($_REQUEST["mode"]);
  27. // in prefetch mode we only output requested cids, main article
  28. // just gets marked as read (it already exists in client cache)
  29. $articles = array();
  30. if ($mode == "") {
  31. array_push($articles, $this->format_article($id, false));
  32. } else if ($mode == "zoom") {
  33. array_push($articles, $this->format_article($id, true, true));
  34. } else if ($mode == "raw") {
  35. if (isset($_REQUEST['html'])) {
  36. header("Content-Type: text/html");
  37. print '<link rel="stylesheet" type="text/css" href="css/default.css"/>';
  38. }
  39. $article = $this->format_article($id, false, isset($_REQUEST["zoom"]));
  40. print $article['content'];
  41. return;
  42. }
  43. $this->catchupArticleById($id, 0);
  44. if (!$_SESSION["bw_limit"]) {
  45. foreach ($cids as $cid) {
  46. if ($cid) {
  47. array_push($articles, $this->format_article($cid, false, false));
  48. }
  49. }
  50. }
  51. print json_encode($articles);
  52. } */
  53. /*
  54. private function catchupArticleById($id, $cmode) {
  55. if ($cmode == 0) {
  56. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  57. unread = false,last_read = NOW()
  58. WHERE ref_id = ? AND owner_uid = ?");
  59. } else if ($cmode == 1) {
  60. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  61. unread = true
  62. WHERE ref_id = ? AND owner_uid = ?");
  63. } else {
  64. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  65. unread = NOT unread,last_read = NOW()
  66. WHERE ref_id = ? AND owner_uid = ?");
  67. }
  68. $sth->execute([$id, $_SESSION['uid']]);
  69. $feed_id = $this->getArticleFeed($id);
  70. CCache::update($feed_id, $_SESSION["uid"]);
  71. }
  72. */
  73. static function create_published_article($title, $url, $content, $labels_str,
  74. $owner_uid) {
  75. $guid = 'SHA1:' . sha1("ttshared:" . $url . $owner_uid); // include owner_uid to prevent global GUID clash
  76. if (!$content) {
  77. $pluginhost = new PluginHost();
  78. $pluginhost->load_all(PluginHost::KIND_ALL, $owner_uid);
  79. $pluginhost->load_data();
  80. $af_readability = $pluginhost->get_plugin("Af_Readability");
  81. if ($af_readability) {
  82. $enable_share_anything = $pluginhost->get($af_readability, "enable_share_anything");
  83. if ($enable_share_anything) {
  84. $extracted_content = $af_readability->extract_content($url);
  85. if ($extracted_content) $content = $extracted_content;
  86. }
  87. }
  88. }
  89. $content_hash = sha1($content);
  90. if ($labels_str != "") {
  91. $labels = explode(",", $labels_str);
  92. } else {
  93. $labels = array();
  94. }
  95. $rc = false;
  96. if (!$title) $title = $url;
  97. if (!$title && !$url) return false;
  98. if (filter_var($url, FILTER_VALIDATE_URL) === FALSE) return false;
  99. $pdo = Db::pdo();
  100. $pdo->beginTransaction();
  101. // only check for our user data here, others might have shared this with different content etc
  102. $sth = $pdo->prepare("SELECT id FROM ttrss_entries, ttrss_user_entries WHERE
  103. guid = ? AND ref_id = id AND owner_uid = ? LIMIT 1");
  104. $sth->execute([$guid, $owner_uid]);
  105. if ($row = $sth->fetch()) {
  106. $ref_id = $row['id'];
  107. $sth = $pdo->prepare("SELECT int_id FROM ttrss_user_entries WHERE
  108. ref_id = ? AND owner_uid = ? LIMIT 1");
  109. $sth->execute([$ref_id, $owner_uid]);
  110. if ($row = $sth->fetch()) {
  111. $int_id = $row['int_id'];
  112. $sth = $pdo->prepare("UPDATE ttrss_entries SET
  113. content = ?, content_hash = ? WHERE id = ?");
  114. $sth->execute([$content, $content_hash, $ref_id]);
  115. $sth = $pdo->prepare("UPDATE ttrss_user_entries SET published = true,
  116. last_published = NOW() WHERE
  117. int_id = ? AND owner_uid = ?");
  118. $sth->execute([$int_id, $owner_uid]);
  119. } else {
  120. $sth = $pdo->prepare("INSERT INTO ttrss_user_entries
  121. (ref_id, uuid, feed_id, orig_feed_id, owner_uid, published, tag_cache, label_cache,
  122. last_read, note, unread, last_published)
  123. VALUES
  124. (?, '', NULL, NULL, ?, true, '', '', NOW(), '', false, NOW())");
  125. $sth->execute([$ref_id, $owner_uid]);
  126. }
  127. if (count($labels) != 0) {
  128. foreach ($labels as $label) {
  129. Labels::add_article($ref_id, trim($label), $owner_uid);
  130. }
  131. }
  132. $rc = true;
  133. } else {
  134. $sth = $pdo->prepare("INSERT INTO ttrss_entries
  135. (title, guid, link, updated, content, content_hash, date_entered, date_updated)
  136. VALUES
  137. (?, ?, ?, NOW(), ?, ?, NOW(), NOW())");
  138. $sth->execute([$title, $guid, $url, $content, $content_hash]);
  139. $sth = $pdo->prepare("SELECT id FROM ttrss_entries WHERE guid = ?");
  140. $sth->execute([$guid]);
  141. if ($row = $sth->fetch()) {
  142. $ref_id = $row["id"];
  143. $sth = $pdo->prepare("INSERT INTO ttrss_user_entries
  144. (ref_id, uuid, feed_id, orig_feed_id, owner_uid, published, tag_cache, label_cache,
  145. last_read, note, unread, last_published)
  146. VALUES
  147. (?, '', NULL, NULL, ?, true, '', '', NOW(), '', false, NOW())");
  148. $sth->execute([$ref_id, $owner_uid]);
  149. if (count($labels) != 0) {
  150. foreach ($labels as $label) {
  151. Labels::add_article($ref_id, trim($label), $owner_uid);
  152. }
  153. }
  154. $rc = true;
  155. }
  156. }
  157. $pdo->commit();
  158. return $rc;
  159. }
  160. function editArticleTags() {
  161. $param = clean($_REQUEST['param']);
  162. $tags = Article::get_article_tags($param);
  163. $tags_str = join(", ", $tags);
  164. print_hidden("id", "$param");
  165. print_hidden("op", "article");
  166. print_hidden("method", "setArticleTags");
  167. print "<header class='horizontal'>" . __("Tags for this article (separated by commas):")."</header>";
  168. print "<section>";
  169. print "<textarea dojoType='dijit.form.SimpleTextarea' rows='4'
  170. style='height : 100px; font-size : 12px; width : 98%' id='tags_str'
  171. name='tags_str'>$tags_str</textarea>
  172. <div class='autocomplete' id='tags_choices'
  173. style='display:none'></div>";
  174. print "</section>";
  175. print "<footer>";
  176. print "<button dojoType='dijit.form.Button'
  177. type='submit' class='alt-primary' onclick=\"dijit.byId('editTagsDlg').execute()\">".__('Save')."</button> ";
  178. print "<button dojoType='dijit.form.Button'
  179. onclick=\"dijit.byId('editTagsDlg').hide()\">".__('Cancel')."</button>";
  180. print "</footer>";
  181. }
  182. function setScore() {
  183. $ids = explode(",", clean($_REQUEST['id']));
  184. $score = (int)clean($_REQUEST['score']);
  185. $ids_qmarks = arr_qmarks($ids);
  186. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  187. score = ? WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  188. $sth->execute(array_merge([$score], $ids, [$_SESSION['uid']]));
  189. print json_encode(["id" => $ids, "score" => (int)$score]);
  190. }
  191. function getScore() {
  192. $id = clean($_REQUEST['id']);
  193. $sth = $this->pdo->prepare("SELECT score FROM ttrss_user_entries WHERE ref_id = ? AND owner_uid = ?");
  194. $sth->execute([$id, $_SESSION['uid']]);
  195. $row = $sth->fetch();
  196. $score = $row['score'];
  197. print json_encode(["id" => $id, "score" => (int)$score]);
  198. }
  199. function setArticleTags() {
  200. $id = clean($_REQUEST["id"]);
  201. $tags_str = clean($_REQUEST["tags_str"]);
  202. $tags = array_unique(trim_array(explode(",", $tags_str)));
  203. $this->pdo->beginTransaction();
  204. $sth = $this->pdo->prepare("SELECT int_id FROM ttrss_user_entries WHERE
  205. ref_id = ? AND owner_uid = ? LIMIT 1");
  206. $sth->execute([$id, $_SESSION['uid']]);
  207. if ($row = $sth->fetch()) {
  208. $tags_to_cache = array();
  209. $int_id = $row['int_id'];
  210. $sth = $this->pdo->prepare("DELETE FROM ttrss_tags WHERE
  211. post_int_id = ? AND owner_uid = ?");
  212. $sth->execute([$int_id, $_SESSION['uid']]);
  213. foreach ($tags as $tag) {
  214. $tag = sanitize_tag($tag);
  215. if (!tag_is_valid($tag)) {
  216. continue;
  217. }
  218. if (preg_match("/^[0-9]*$/", $tag)) {
  219. continue;
  220. }
  221. // print "<!-- $id : $int_id : $tag -->";
  222. if ($tag != '') {
  223. $sth = $this->pdo->prepare("INSERT INTO ttrss_tags
  224. (post_int_id, owner_uid, tag_name)
  225. VALUES (?, ?, ?)");
  226. $sth->execute([$int_id, $_SESSION['uid'], $tag]);
  227. }
  228. array_push($tags_to_cache, $tag);
  229. }
  230. /* update tag cache */
  231. sort($tags_to_cache);
  232. $tags_str = join(",", $tags_to_cache);
  233. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries
  234. SET tag_cache = ? WHERE ref_id = ? AND owner_uid = ?");
  235. $sth->execute([$tags_str, $id, $_SESSION['uid']]);
  236. }
  237. $this->pdo->commit();
  238. $tags = Article::get_article_tags($id);
  239. $tags_str = $this->format_tags_string($tags, $id);
  240. $tags_str_full = join(", ", $tags);
  241. if (!$tags_str_full) $tags_str_full = __("no tags");
  242. print json_encode(array("id" => (int)$id,
  243. "content" => $tags_str, "content_full" => $tags_str_full));
  244. }
  245. function completeTags() {
  246. $search = clean($_REQUEST["search"]);
  247. $sth = $this->pdo->prepare("SELECT DISTINCT tag_name FROM ttrss_tags
  248. WHERE owner_uid = ? AND
  249. tag_name LIKE ? ORDER BY tag_name
  250. LIMIT 10");
  251. $sth->execute([$_SESSION['uid'], "$search%"]);
  252. print "<ul>";
  253. while ($line = $sth->fetch()) {
  254. print "<li>" . $line["tag_name"] . "</li>";
  255. }
  256. print "</ul>";
  257. }
  258. function assigntolabel() {
  259. return $this->labelops(true);
  260. }
  261. function removefromlabel() {
  262. return $this->labelops(false);
  263. }
  264. private function labelops($assign) {
  265. $reply = array();
  266. $ids = explode(",", clean($_REQUEST["ids"]));
  267. $label_id = clean($_REQUEST["lid"]);
  268. $label = Labels::find_caption($label_id, $_SESSION["uid"]);
  269. $reply["info-for-headlines"] = array();
  270. if ($label) {
  271. foreach ($ids as $id) {
  272. if ($assign)
  273. Labels::add_article($id, $label, $_SESSION["uid"]);
  274. else
  275. Labels::remove_article($id, $label, $_SESSION["uid"]);
  276. $labels = $this->get_article_labels($id, $_SESSION["uid"]);
  277. array_push($reply["info-for-headlines"],
  278. array("id" => $id, "labels" => $this->format_article_labels($labels)));
  279. }
  280. }
  281. $reply["message"] = "UPDATE_COUNTERS";
  282. print json_encode($reply);
  283. }
  284. function getArticleFeed($id) {
  285. $sth = $this->pdo->prepare("SELECT feed_id FROM ttrss_user_entries
  286. WHERE ref_id = ? AND owner_uid = ?");
  287. $sth->execute([$id, $_SESSION['uid']]);
  288. if ($row = $sth->fetch()) {
  289. return $row["feed_id"];
  290. } else {
  291. return 0;
  292. }
  293. }
  294. static function format_article_enclosures($id, $always_display_enclosures,
  295. $article_content, $hide_images = false) {
  296. $result = Article::get_article_enclosures($id);
  297. $rv = '';
  298. foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ENCLOSURES) as $plugin) {
  299. $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
  300. if (is_array($retval)) {
  301. $rv = $retval[0];
  302. $result = $retval[1];
  303. } else {
  304. $rv = $retval;
  305. }
  306. }
  307. unset($retval); // Unset to prevent breaking render if there are no HOOK_RENDER_ENCLOSURE hooks below.
  308. if ($rv === '' && !empty($result)) {
  309. $entries_html = array();
  310. $entries = array();
  311. $entries_inline = array();
  312. foreach ($result as $line) {
  313. foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ENCLOSURE_ENTRY) as $plugin) {
  314. $line = $plugin->hook_enclosure_entry($line);
  315. }
  316. $url = $line["content_url"];
  317. $ctype = $line["content_type"];
  318. $title = $line["title"];
  319. $width = $line["width"];
  320. $height = $line["height"];
  321. if (!$ctype) $ctype = __("unknown type");
  322. //$filename = substr($url, strrpos($url, "/")+1);
  323. $filename = basename($url);
  324. $player = format_inline_player($url, $ctype);
  325. if ($player) array_push($entries_inline, $player);
  326. # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\" rel=\"noopener noreferrer\">" .
  327. # $filename . " (" . $ctype . ")" . "</a>";
  328. $entry = "<div onclick=\"popupOpenUrl('".htmlspecialchars($url)."')\"
  329. dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
  330. array_push($entries_html, $entry);
  331. $entry = array();
  332. $entry["type"] = $ctype;
  333. $entry["filename"] = $filename;
  334. $entry["url"] = $url;
  335. $entry["title"] = $title;
  336. $entry["width"] = $width;
  337. $entry["height"] = $height;
  338. array_push($entries, $entry);
  339. }
  340. if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
  341. if ($always_display_enclosures ||
  342. !preg_match("/<img/i", $article_content)) {
  343. foreach ($entries as $entry) {
  344. foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ENCLOSURE) as $plugin)
  345. $retval = $plugin->hook_render_enclosure($entry, $hide_images);
  346. if ($retval) {
  347. $rv .= $retval;
  348. } else {
  349. if (preg_match("/image/", $entry["type"])) {
  350. if (!$hide_images) {
  351. $encsize = '';
  352. if ($entry['height'] > 0)
  353. $encsize .= ' height="' . intval($entry['height']) . '"';
  354. if ($entry['width'] > 0)
  355. $encsize .= ' width="' . intval($entry['width']) . '"';
  356. $rv .= "<p><img
  357. alt=\"".htmlspecialchars($entry["filename"])."\"
  358. src=\"" .htmlspecialchars($entry["url"]) . "\"
  359. " . $encsize . " /></p>";
  360. } else {
  361. $rv .= "<p><a target=\"_blank\" rel=\"noopener noreferrer\"
  362. href=\"".htmlspecialchars($entry["url"])."\"
  363. >" .htmlspecialchars($entry["url"]) . "</a></p>";
  364. }
  365. if ($entry['title']) {
  366. $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
  367. }
  368. }
  369. }
  370. }
  371. }
  372. }
  373. if (count($entries_inline) > 0) {
  374. //$rv .= "<hr clear='both'/>";
  375. foreach ($entries_inline as $entry) { $rv .= $entry; };
  376. $rv .= "<br clear='both'/>";
  377. }
  378. $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
  379. "<span>" . __('Attachments')."</span>";
  380. $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
  381. foreach ($entries as $entry) {
  382. if ($entry["title"])
  383. $title = " &mdash; " . truncate_string($entry["title"], 30);
  384. else
  385. $title = "";
  386. if ($entry["filename"])
  387. $filename = truncate_middle(htmlspecialchars($entry["filename"]), 60);
  388. else
  389. $filename = "";
  390. $rv .= "<div onclick='popupOpenUrl(\"".htmlspecialchars($entry["url"])."\")'
  391. dojoType=\"dijit.MenuItem\">".$filename . $title."</div>";
  392. };
  393. $rv .= "</div>";
  394. $rv .= "</div>";
  395. }
  396. return $rv;
  397. }
  398. static function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
  399. $a_id = $id;
  400. if (!$owner_uid) $owner_uid = $_SESSION["uid"];
  401. $pdo = Db::pdo();
  402. $sth = $pdo->prepare("SELECT DISTINCT tag_name,
  403. owner_uid as owner FROM ttrss_tags
  404. WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
  405. ref_id = ? AND owner_uid = ? LIMIT 1) ORDER BY tag_name");
  406. $tags = array();
  407. /* check cache first */
  408. if ($tag_cache === false) {
  409. $csth = $pdo->prepare("SELECT tag_cache FROM ttrss_user_entries
  410. WHERE ref_id = ? AND owner_uid = ?");
  411. $csth->execute([$id, $owner_uid]);
  412. if ($row = $csth->fetch()) $tag_cache = $row["tag_cache"];
  413. }
  414. if ($tag_cache) {
  415. $tags = explode(",", $tag_cache);
  416. } else {
  417. /* do it the hard way */
  418. $sth->execute([$a_id, $owner_uid]);
  419. while ($tmp_line = $sth->fetch()) {
  420. array_push($tags, $tmp_line["tag_name"]);
  421. }
  422. /* update the cache */
  423. $tags_str = join(",", $tags);
  424. $sth = $pdo->prepare("UPDATE ttrss_user_entries
  425. SET tag_cache = ? WHERE ref_id = ?
  426. AND owner_uid = ?");
  427. $sth->execute([$tags_str, $id, $owner_uid]);
  428. }
  429. return $tags;
  430. }
  431. static function format_tags_string($tags) {
  432. if (!is_array($tags) || count($tags) == 0) {
  433. return __("no tags");
  434. } else {
  435. $maxtags = min(5, count($tags));
  436. $tags_str = "";
  437. for ($i = 0; $i < $maxtags; $i++) {
  438. $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"Feeds.open({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
  439. }
  440. $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
  441. if (count($tags) > $maxtags)
  442. $tags_str .= ", &hellip;";
  443. return $tags_str;
  444. }
  445. }
  446. static function format_article_labels($labels) {
  447. if (!is_array($labels)) return '';
  448. $labels_str = "";
  449. foreach ($labels as $l) {
  450. $labels_str .= sprintf("<div class='label'
  451. style='color : %s; background-color : %s'>%s</div>",
  452. $l[2], $l[3], $l[1]);
  453. }
  454. return $labels_str;
  455. }
  456. static function format_article_note($id, $note, $allow_edit = true) {
  457. if ($allow_edit) {
  458. $onclick = "onclick='Plugins.Note.edit($id)'";
  459. $note_class = 'editable';
  460. } else {
  461. $onclick = '';
  462. $note_class = '';
  463. }
  464. return "<div class='article-note $note_class'>
  465. <i class='material-icons'>note</i>
  466. <div $onclick class='body'>$note</div>
  467. </div>";
  468. return $str;
  469. }
  470. static function get_article_enclosures($id) {
  471. $pdo = Db::pdo();
  472. $sth = $pdo->prepare("SELECT * FROM ttrss_enclosures
  473. WHERE post_id = ? AND content_url != ''");
  474. $sth->execute([$id]);
  475. $rv = array();
  476. while ($line = $sth->fetch()) {
  477. if (file_exists(CACHE_DIR . '/images/' . sha1($line["content_url"]))) {
  478. $line["content_url"] = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($line["content_url"]);
  479. }
  480. array_push($rv, $line);
  481. }
  482. return $rv;
  483. }
  484. static function purge_orphans() {
  485. // purge orphaned posts in main content table
  486. if (DB_TYPE == "mysql")
  487. $limit_qpart = "LIMIT 5000";
  488. else
  489. $limit_qpart = "";
  490. $pdo = Db::pdo();
  491. $res = $pdo->query("DELETE FROM ttrss_entries WHERE
  492. NOT EXISTS (SELECT ref_id FROM ttrss_user_entries WHERE ref_id = id) $limit_qpart");
  493. if (Debug::enabled()) {
  494. $rows = $res->rowCount();
  495. Debug::log("Purged $rows orphaned posts.");
  496. }
  497. }
  498. static function catchupArticlesById($ids, $cmode, $owner_uid = false) {
  499. if (!$owner_uid) $owner_uid = $_SESSION["uid"];
  500. $pdo = Db::pdo();
  501. $ids_qmarks = arr_qmarks($ids);
  502. if ($cmode == 1) {
  503. $sth = $pdo->prepare("UPDATE ttrss_user_entries SET
  504. unread = true
  505. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  506. } else if ($cmode == 2) {
  507. $sth = $pdo->prepare("UPDATE ttrss_user_entries SET
  508. unread = NOT unread,last_read = NOW()
  509. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  510. } else {
  511. $sth = $pdo->prepare("UPDATE ttrss_user_entries SET
  512. unread = false,last_read = NOW()
  513. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  514. }
  515. $sth->execute(array_merge($ids, [$owner_uid]));
  516. /* update ccache */
  517. $sth = $pdo->prepare("SELECT DISTINCT feed_id FROM ttrss_user_entries
  518. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  519. $sth->execute(array_merge($ids, [$owner_uid]));
  520. while ($line = $sth->fetch()) {
  521. CCache::update($line["feed_id"], $owner_uid);
  522. }
  523. }
  524. static function getLastArticleId() {
  525. $pdo = DB::pdo();
  526. $sth = $pdo->prepare("SELECT ref_id AS id FROM ttrss_user_entries
  527. WHERE owner_uid = ? ORDER BY ref_id DESC LIMIT 1");
  528. $sth->execute([$_SESSION['uid']]);
  529. if ($row = $sth->fetch()) {
  530. return $row['id'];
  531. } else {
  532. return -1;
  533. }
  534. }
  535. static function get_article_labels($id, $owner_uid = false) {
  536. $rv = array();
  537. if (!$owner_uid) $owner_uid = $_SESSION["uid"];
  538. $pdo = Db::pdo();
  539. $sth = $pdo->prepare("SELECT label_cache FROM
  540. ttrss_user_entries WHERE ref_id = ? AND owner_uid = ?");
  541. $sth->execute([$id, $owner_uid]);
  542. if ($row = $sth->fetch()) {
  543. $label_cache = $row["label_cache"];
  544. if ($label_cache) {
  545. $tmp = json_decode($label_cache, true);
  546. if (!$tmp || $tmp["no-labels"] == 1)
  547. return $rv;
  548. else
  549. return $tmp;
  550. }
  551. }
  552. $sth = $pdo->prepare("SELECT DISTINCT label_id,caption,fg_color,bg_color
  553. FROM ttrss_labels2, ttrss_user_labels2
  554. WHERE id = label_id
  555. AND article_id = ?
  556. AND owner_uid = ?
  557. ORDER BY caption");
  558. $sth->execute([$id, $owner_uid]);
  559. while ($line = $sth->fetch()) {
  560. $rk = array(Labels::label_to_feed_id($line["label_id"]),
  561. $line["caption"], $line["fg_color"],
  562. $line["bg_color"]);
  563. array_push($rv, $rk);
  564. }
  565. if (count($rv) > 0)
  566. Labels::update_cache($owner_uid, $id, $rv);
  567. else
  568. Labels::update_cache($owner_uid, $id, array("no-labels" => 1));
  569. return $rv;
  570. }
  571. }