]> git.wh0rd.org - tt-rss.git/blame - classes/article.php
filters: remove sql_bool_to_bool()
[tt-rss.git] / classes / article.php
CommitLineData
6afcbcd1
AD
1<?php
2class Article extends Handler_Protected {
3
4 function csrf_ignore($method) {
1c9bda91 5 $csrf_ignored = array("redirect", "editarticletags");
6afcbcd1
AD
6
7 return array_search($method, $csrf_ignored) !== false;
8 }
9
10 function redirect() {
c39ee272
AD
11 $sth = $this->pdo->prepare("SELECT link FROM ttrss_entries, ttrss_user_entries
12 WHERE id = ? AND id = ref_id AND owner_uid = ?
6afcbcd1 13 LIMIT 1");
c39ee272 14 $sth->execute([$id, $_SESSION['uid']]);
6afcbcd1 15
c39ee272
AD
16 if ($row = $sth->fetch()) {
17 $article_url = $row['link'];
6afcbcd1
AD
18 $article_url = str_replace("\n", "", $article_url);
19
20 header("Location: $article_url");
21 return;
22
23 } else {
24 print_error(__("Article not found."));
25 }
26 }
27
28 function view() {
2c57df75
AD
29 $id = $_REQUEST["id"];
30 $cids = explode(",", $_REQUEST["cids"]);
31 $mode = $_REQUEST["mode"];
6afcbcd1
AD
32
33 // in prefetch mode we only output requested cids, main article
34 // just gets marked as read (it already exists in client cache)
35
36 $articles = array();
37
38 if ($mode == "") {
7e5f8d9f 39 array_push($articles, $this->format_article($id, false));
6afcbcd1 40 } else if ($mode == "zoom") {
7e5f8d9f 41 array_push($articles, $this->format_article($id, true, true));
6afcbcd1 42 } else if ($mode == "raw") {
f48f292d 43 if (isset($_REQUEST['html'])) {
6afcbcd1 44 header("Content-Type: text/html");
9dd336a2 45 print '<link rel="stylesheet" type="text/css" href="css/default.css"/>';
6afcbcd1
AD
46 }
47
7e5f8d9f 48 $article = $this->format_article($id, false, isset($_REQUEST["zoom"]));
6afcbcd1
AD
49 print $article['content'];
50 return;
51 }
52
a42c55f0 53 $this->catchupArticleById($id, 0);
6afcbcd1
AD
54
55 if (!$_SESSION["bw_limit"]) {
56 foreach ($cids as $cid) {
57 if ($cid) {
7e5f8d9f 58 array_push($articles, $this->format_article($cid, false, false));
6afcbcd1
AD
59 }
60 }
61 }
62
63 print json_encode($articles);
64 }
65
a42c55f0 66 private function catchupArticleById($id, $cmode) {
6afcbcd1
AD
67
68 if ($cmode == 0) {
c39ee272 69 $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
6afcbcd1 70 unread = false,last_read = NOW()
c39ee272 71 WHERE ref_id = ? AND owner_uid = ?");
6afcbcd1 72 } else if ($cmode == 1) {
c39ee272 73 $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
6afcbcd1 74 unread = true
c39ee272 75 WHERE ref_id = ? AND owner_uid = ?");
6afcbcd1 76 } else {
c39ee272 77 $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
6afcbcd1 78 unread = NOT unread,last_read = NOW()
c39ee272 79 WHERE ref_id = ? AND owner_uid = ?");
6afcbcd1
AD
80 }
81
c39ee272
AD
82 $sth->execute([$id, $_SESSION['uid']]);
83
4122da02 84 $feed_id = $this->getArticleFeed($id);
2ed0d6c4 85 CCache::update($feed_id, $_SESSION["uid"]);
6afcbcd1
AD
86 }
87
a42c55f0 88 static function create_published_article($title, $url, $content, $labels_str,
6afcbcd1
AD
89 $owner_uid) {
90
5e3d5480 91 $guid = 'SHA1:' . sha1("ttshared:" . $url . $owner_uid); // include owner_uid to prevent global GUID clash
666cd333
AD
92
93 if (!$content) {
94 $pluginhost = new PluginHost();
95 $pluginhost->load_all(PluginHost::KIND_ALL, $owner_uid);
7af2e795 96 $pluginhost->load_data();
666cd333
AD
97
98 $af_readability = $pluginhost->get_plugin("Af_Readability");
99
100 if ($af_readability) {
7af2e795 101 $enable_share_anything = $pluginhost->get($af_readability, "enable_share_anything");
666cd333 102
7af2e795
AD
103 if ($enable_share_anything) {
104 $extracted_content = $af_readability->extract_content($url);
105
2c57df75 106 if ($extracted_content) $content = $extracted_content;
7af2e795 107 }
666cd333
AD
108 }
109 }
110
6afcbcd1
AD
111 $content_hash = sha1($content);
112
113 if ($labels_str != "") {
114 $labels = explode(",", $labels_str);
115 } else {
116 $labels = array();
117 }
118
119 $rc = false;
120
121 if (!$title) $title = $url;
122 if (!$title && !$url) return false;
123
124 if (filter_var($url, FILTER_VALIDATE_URL) === FALSE) return false;
125
d0e73ed8
AD
126 $pdo = Db::pdo();
127
128 $pdo->beginTransaction();
6afcbcd1
AD
129
130 // only check for our user data here, others might have shared this with different content etc
d0e73ed8 131 $sth = $pdo->prepare("SELECT id FROM ttrss_entries, ttrss_user_entries WHERE
c39ee272
AD
132 guid = ? AND ref_id = id AND owner_uid = ? LIMIT 1");
133 $sth->execute([$guid, $owner_uid]);
6afcbcd1 134
c39ee272
AD
135 if ($row = $sth->fetch()) {
136 $ref_id = $row['id'];
6afcbcd1 137
d0e73ed8 138 $sth = $pdo->prepare("SELECT int_id FROM ttrss_user_entries WHERE
c39ee272
AD
139 ref_id = ? AND owner_uid = ? LIMIT 1");
140 $sth->execute([$ref_id, $owner_uid]);
6afcbcd1 141
c39ee272
AD
142 if ($row = $sth->fetch()) {
143 $int_id = $row['int_id'];
6afcbcd1 144
d0e73ed8 145 $sth = $pdo->prepare("UPDATE ttrss_entries SET
c39ee272
AD
146 content = ?, content_hash = ? WHERE id = ?");
147 $sth->execute([$content, $content_hash, $ref_id]);
6afcbcd1 148
d0e73ed8 149 $sth = $pdo->prepare("UPDATE ttrss_user_entries SET published = true,
d2888e88 150 last_published = NOW() WHERE
c39ee272
AD
151 int_id = ? AND owner_uid = ?");
152 $sth->execute([$int_id, $owner_uid]);
153
6afcbcd1
AD
154 } else {
155
d0e73ed8 156 $sth = $pdo->prepare("INSERT INTO ttrss_user_entries
d2888e88
AD
157 (ref_id, uuid, feed_id, orig_feed_id, owner_uid, published, tag_cache, label_cache,
158 last_read, note, unread, last_published)
6afcbcd1 159 VALUES
c39ee272
AD
160 (?, '', NULL, NULL, ?, true, '', '', NOW(), '', false, NOW())");
161 $sth->execute([$ref_id, $owner_uid]);
6afcbcd1
AD
162 }
163
164 if (count($labels) != 0) {
165 foreach ($labels as $label) {
7c9b5a3f 166 Labels::add_article($ref_id, trim($label), $owner_uid);
6afcbcd1
AD
167 }
168 }
169
170 $rc = true;
171
172 } else {
d0e73ed8 173 $sth = $pdo->prepare("INSERT INTO ttrss_entries
6afcbcd1
AD
174 (title, guid, link, updated, content, content_hash, date_entered, date_updated)
175 VALUES
c39ee272
AD
176 (?, ?, ?, NOW(), ?, ?, NOW(), NOW())");
177 $sth->execute([$title, $guid, $url, $content, $content_hash]);
6afcbcd1 178
d0e73ed8 179 $sth = $pdo->prepare("SELECT id FROM ttrss_entries WHERE guid = ?");
c39ee272 180 $sth->execute([$guid]);
6afcbcd1 181
c39ee272
AD
182 if ($row = $sth->fetch()) {
183 $ref_id = $row["id"];
6afcbcd1 184
d0e73ed8 185 $sth = $pdo->prepare("INSERT INTO ttrss_user_entries
d2888e88
AD
186 (ref_id, uuid, feed_id, orig_feed_id, owner_uid, published, tag_cache, label_cache,
187 last_read, note, unread, last_published)
6afcbcd1 188 VALUES
c39ee272
AD
189 (?, '', NULL, NULL, ?, true, '', '', NOW(), '', false, NOW())");
190 $sth->execute([$ref_id, $owner_uid]);
6afcbcd1
AD
191
192 if (count($labels) != 0) {
193 foreach ($labels as $label) {
7c9b5a3f 194 Labels::add_article($ref_id, trim($label), $owner_uid);
6afcbcd1
AD
195 }
196 }
197
198 $rc = true;
199 }
200 }
201
d0e73ed8 202 $pdo->commit();
6afcbcd1
AD
203
204 return $rc;
205 }
206
1c9bda91
AD
207 function editArticleTags() {
208
209 print __("Tags for this article (separated by commas):")."<br>";
210
2c57df75 211 $param = $_REQUEST['param'];
1c9bda91 212
2c57df75 213 $tags = Article::get_article_tags($param);
1c9bda91
AD
214
215 $tags_str = join(", ", $tags);
216
328118d1
AD
217 print_hidden("id", "$param");
218 print_hidden("op", "article");
219 print_hidden("method", "setArticleTags");
1c9bda91
AD
220
221 print "<table width='100%'><tr><td>";
222
223 print "<textarea dojoType=\"dijit.form.SimpleTextarea\" rows='4'
8b8568e9 224 style='height : 100px; font-size : 12px; width : 98%' id=\"tags_str\"
1c9bda91
AD
225 name='tags_str'>$tags_str</textarea>
226 <div class=\"autocomplete\" id=\"tags_choices\"
227 style=\"display:none\"></div>";
228
229 print "</td></tr></table>";
230
231 print "<div class='dlgButtons'>";
232
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>";
237 print "</div>";
238
239 }
6afcbcd1 240
d719b062 241 function setScore() {
c39ee272
AD
242 $ids = explode(",", $_REQUEST['id']);
243 $score = (int)$_REQUEST['score'];
d719b062 244
c39ee272
AD
245 $ids_qmarks = arr_qmarks($ids);
246
247 $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
248 score = ? WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
249
250 $sth->execute(array_merge([$score], $ids, [$_SESSION['uid']]));
d719b062 251
6f7798b6 252 print json_encode(array("id" => $ids,
a72cd54c
AD
253 "score" => (int)$score,
254 "score_pic" => get_score_pic($score)));
255 }
256
257 function getScore() {
c39ee272
AD
258 $id = $_REQUEST['id'];
259
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();
a72cd54c 263
c39ee272 264 $score = $row['score'];
a72cd54c
AD
265
266 print json_encode(array("id" => $id,
267 "score" => (int)$score,
d719b062
AD
268 "score_pic" => get_score_pic($score)));
269 }
270
6afcbcd1 271
5df8be5c
AD
272 function setArticleTags() {
273
2c57df75 274 $id = $_REQUEST["id"];
5df8be5c 275
2c57df75 276 $tags_str = $_REQUEST["tags_str"];
5df8be5c
AD
277 $tags = array_unique(trim_array(explode(",", $tags_str)));
278
2e46b434 279 $this->pdo->beginTransaction();
5df8be5c 280
d0e73ed8
AD
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']]);
5df8be5c 284
d0e73ed8 285 if ($row = $sth->fetch()) {
5df8be5c
AD
286
287 $tags_to_cache = array();
288
d0e73ed8 289 $int_id = $row['int_id'];
5df8be5c 290
d0e73ed8
AD
291 $sth = $this->pdo->prepare("DELETE FROM ttrss_tags WHERE
292 post_int_id = ? AND owner_uid = ?");
293 $sth->execute([$int_id, $_SESSION['uid']]);
5df8be5c
AD
294
295 foreach ($tags as $tag) {
296 $tag = sanitize_tag($tag);
297
298 if (!tag_is_valid($tag)) {
299 continue;
300 }
301
302 if (preg_match("/^[0-9]*$/", $tag)) {
303 continue;
304 }
305
306 // print "<!-- $id : $int_id : $tag -->";
307
308 if ($tag != '') {
d0e73ed8
AD
309 $sth = $this->pdo->prepare("INSERT INTO ttrss_tags
310 (post_int_id, owner_uid, tag_name)
311 VALUES (?, ?, ?)");
312
313 $sth->execute([$int_id, $_SESSION['uid'], $tag]);
5df8be5c
AD
314 }
315
316 array_push($tags_to_cache, $tag);
317 }
318
319 /* update tag cache */
320
321 sort($tags_to_cache);
322 $tags_str = join(",", $tags_to_cache);
323
d0e73ed8
AD
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']]);
5df8be5c
AD
327 }
328
2e46b434 329 $this->pdo->commit();
5df8be5c 330
7e5f8d9f
AD
331 $tags = Article::get_article_tags($id);
332 $tags_str = $this->format_tags_string($tags, $id);
5df8be5c
AD
333 $tags_str_full = join(", ", $tags);
334
335 if (!$tags_str_full) $tags_str_full = __("no tags");
336
337 print json_encode(array("id" => (int)$id,
338 "content" => $tags_str, "content_full" => $tags_str_full));
339 }
340
c83554bd
AD
341
342 function completeTags() {
d0e73ed8 343 $search = $_REQUEST["search"];
c83554bd 344
d0e73ed8
AD
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
c83554bd
AD
348 LIMIT 10");
349
d0e73ed8
AD
350 $sth->execute([$_SESSION['uid'], "$search%"]);
351
c83554bd 352 print "<ul>";
d0e73ed8 353 while ($line = $sth->fetch()) {
c83554bd
AD
354 print "<li>" . $line["tag_name"] . "</li>";
355 }
356 print "</ul>";
357 }
358
4b7726f0
AD
359 function assigntolabel() {
360 return $this->labelops(true);
361 }
362
363 function removefromlabel() {
364 return $this->labelops(false);
365 }
366
367 private function labelops($assign) {
368 $reply = array();
369
2c57df75
AD
370 $ids = explode(",", $_REQUEST["ids"]);
371 $label_id = $_REQUEST["lid"];
4b7726f0 372
c39ee272 373 $label = db_escape_string(Labels::find_caption($label_id,
4b7726f0
AD
374 $_SESSION["uid"]));
375
376 $reply["info-for-headlines"] = array();
377
378 if ($label) {
379
380 foreach ($ids as $id) {
381
382 if ($assign)
7c9b5a3f 383 Labels::add_article($id, $label, $_SESSION["uid"]);
4b7726f0 384 else
7c9b5a3f 385 Labels::remove_article($id, $label, $_SESSION["uid"]);
4b7726f0 386
4a0da0e5 387 $labels = $this->get_article_labels($id, $_SESSION["uid"]);
4b7726f0
AD
388
389 array_push($reply["info-for-headlines"],
7e5f8d9f 390 array("id" => $id, "labels" => $this->format_article_labels($labels)));
4b7726f0
AD
391
392 }
393 }
394
395 $reply["message"] = "UPDATE_COUNTERS";
396
397 print json_encode($reply);
398 }
399
4122da02 400 function getArticleFeed($id) {
d0e73ed8
AD
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']]);
4122da02 404
d0e73ed8
AD
405 if ($row = $sth->fetch()) {
406 return $row["feed_id"];
4122da02
AD
407 } else {
408 return 0;
409 }
410 }
4b7726f0 411
7e5f8d9f
AD
412 static function format_article_enclosures($id, $always_display_enclosures,
413 $article_content, $hide_images = false) {
414
415 $result = Article::get_article_enclosures($id);
416 $rv = '';
417
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)) {
421 $rv = $retval[0];
422 $result = $retval[1];
423 } else {
424 $rv = $retval;
425 }
426 }
427 unset($retval); // Unset to prevent breaking render if there are no HOOK_RENDER_ENCLOSURE hooks below.
428
429 if ($rv === '' && !empty($result)) {
430 $entries_html = array();
431 $entries = array();
432 $entries_inline = array();
433
434 foreach ($result as $line) {
435
436 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ENCLOSURE_ENTRY) as $plugin) {
437 $line = $plugin->hook_enclosure_entry($line);
438 }
439
440 $url = $line["content_url"];
441 $ctype = $line["content_type"];
442 $title = $line["title"];
443 $width = $line["width"];
444 $height = $line["height"];
445
446 if (!$ctype) $ctype = __("unknown type");
447
448 //$filename = substr($url, strrpos($url, "/")+1);
449 $filename = basename($url);
450
451 $player = format_inline_player($url, $ctype);
452
453 if ($player) array_push($entries_inline, $player);
454
455# $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\" rel=\"noopener noreferrer\">" .
456# $filename . " (" . $ctype . ")" . "</a>";
457
458 $entry = "<div onclick=\"openUrlPopup('".htmlspecialchars($url)."')\"
459 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
460
461 array_push($entries_html, $entry);
462
463 $entry = array();
464
465 $entry["type"] = $ctype;
466 $entry["filename"] = $filename;
467 $entry["url"] = $url;
468 $entry["title"] = $title;
469 $entry["width"] = $width;
470 $entry["height"] = $height;
471
472 array_push($entries, $entry);
473 }
474
475 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
476 if ($always_display_enclosures ||
477 !preg_match("/<img/i", $article_content)) {
478
479 foreach ($entries as $entry) {
480
481 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ENCLOSURE) as $plugin)
482 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
483
484
485 if ($retval) {
486 $rv .= $retval;
487 } else {
488
489 if (preg_match("/image/", $entry["type"])) {
490
491 if (!$hide_images) {
492 $encsize = '';
493 if ($entry['height'] > 0)
494 $encsize .= ' height="' . intval($entry['height']) . '"';
495 if ($entry['width'] > 0)
496 $encsize .= ' width="' . intval($entry['width']) . '"';
497 $rv .= "<p><img
498 alt=\"".htmlspecialchars($entry["filename"])."\"
499 src=\"" .htmlspecialchars($entry["url"]) . "\"
500 " . $encsize . " /></p>";
501 } else {
502 $rv .= "<p><a target=\"_blank\" rel=\"noopener noreferrer\"
503 href=\"".htmlspecialchars($entry["url"])."\"
504 >" .htmlspecialchars($entry["url"]) . "</a></p>";
505 }
506
507 if ($entry['title']) {
508 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
509 }
510 }
511 }
512 }
513 }
514 }
515
516 if (count($entries_inline) > 0) {
517 $rv .= "<hr clear='both'/>";
518 foreach ($entries_inline as $entry) { $rv .= $entry; };
519 $rv .= "<hr clear='both'/>";
520 }
521
522 $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
523 "<span>" . __('Attachments')."</span>";
524
525 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
526
527 foreach ($entries as $entry) {
528 if ($entry["title"])
529 $title = " &mdash; " . truncate_string($entry["title"], 30);
530 else
531 $title = "";
532
533 if ($entry["filename"])
534 $filename = truncate_middle(htmlspecialchars($entry["filename"]), 60);
535 else
536 $filename = "";
537
538 $rv .= "<div onclick='openUrlPopup(\"".htmlspecialchars($entry["url"])."\")'
539 dojoType=\"dijit.MenuItem\">".$filename . $title."</div>";
540
541 };
542
543 $rv .= "</div>";
544 $rv .= "</div>";
545 }
546
547 return $rv;
548 }
549
550 static function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
551 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
552
553 $rv = array();
554
555 $rv['id'] = $id;
556
557 /* we can figure out feed_id from article id anyway, why do we
558 * pass feed_id here? let's ignore the argument :(*/
559
d0e73ed8
AD
560 $pdo = Db::pdo();
561
562 $sth = $pdo->prepare("SELECT feed_id FROM ttrss_user_entries
563 WHERE ref_id = ?");
564 $sth->execute([$id]);
565 $row = $sth->fetch();
7e5f8d9f 566
d0e73ed8 567 $feed_id = (int) $row["feed_id"];
7e5f8d9f
AD
568
569 $rv['feed_id'] = $feed_id;
570
571 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
572
573 if ($mark_as_read) {
d0e73ed8 574 $sth = $pdo->prepare("UPDATE ttrss_user_entries
7e5f8d9f 575 SET unread = false,last_read = NOW()
d0e73ed8
AD
576 WHERE ref_id = ? AND owner_uid = ?");
577 $sth->execute([$id, $owner_uid]);
7e5f8d9f 578
2ed0d6c4 579 CCache::update($feed_id, $owner_uid);
7e5f8d9f
AD
580 }
581
d0e73ed8 582 $sth = $pdo->prepare("SELECT id,title,link,content,feed_id,comments,int_id,lang,
7e5f8d9f
AD
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,
588 num_comments,
589 tag_cache,
590 author,
591 guid,
592 orig_feed_id,
593 note
594 FROM ttrss_entries,ttrss_user_entries
d0e73ed8
AD
595 WHERE id = ? AND ref_id = id AND owner_uid = ?");
596 $sth->execute([$id, $owner_uid]);
7e5f8d9f 597
d0e73ed8 598 if ($line = $sth->fetch()) {
7e5f8d9f
AD
599
600 $line["tags"] = Article::get_article_tags($id, $owner_uid, $line["tag_cache"]);
601 unset($line["tag_cache"]);
602
603 $line["content"] = sanitize($line["content"],
604 sql_bool_to_bool($line['hide_images']),
605 $owner_uid, $line["site_url"], false, $line["id"]);
606
607 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) {
608 $line = $p->hook_render_article($line);
609 }
610
611 $num_comments = (int) $line["num_comments"];
612 $entry_comments = "";
613
614 if ($num_comments > 0) {
615 if ($line["comments"]) {
616 $comments_url = htmlspecialchars($line["comments"]);
617 } else {
618 $comments_url = htmlspecialchars($line["link"]);
619 }
620 $entry_comments = "<a class=\"postComments\"
621 target='_blank' rel=\"noopener noreferrer\" href=\"$comments_url\">$num_comments ".
622 _ngettext("comment", "comments", $num_comments)."</a>";
623
624 } else {
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>";
627 }
628 }
629
630 if ($zoom_mode) {
631 header("Content-Type: text/html");
632 $rv['content'] .= "<html><head>
633 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
8f0a59f3 634 <title>".$line["title"]."</title>".
9dd336a2 635 stylesheet_tag("css/default.css")."
7e5f8d9f
AD
636
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\">
639
640 </head><body id=\"ttrssZoom\">";
641 }
642
643 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
644
645 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
646
647 $entry_author = $line["author"];
648
649 if ($entry_author) {
650 $entry_author = __(" - ") . $entry_author;
651 }
652
653 $parsed_updated = make_local_datetime($line["updated"], true,
654 $owner_uid, true);
655
656 if (!$zoom_mode)
657 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
658
659 if ($line["link"]) {
660 $rv['content'] .= "<div class='postTitle'><a target='_blank' rel='noopener noreferrer'
661 title=\"".htmlspecialchars($line['title'])."\"
662 href=\"" .
663 htmlspecialchars($line["link"]) . "\">" .
664 $line["title"] . "</a>" .
665 "<span class='author'>$entry_author</span></div>";
666 } else {
667 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
668 }
669
670 if ($zoom_mode) {
671 $feed_title = htmlspecialchars($line["feed_title"]);
672
673 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
674
675 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
676 }
677
678 $tags_str = Article::format_tags_string($line["tags"], $id);
679 $tags_str_full = join(", ", $line["tags"]);
680
681 if (!$tags_str_full) $tags_str_full = __("no tags");
682
683 if (!$entry_comments) $entry_comments = "&nbsp;"; # placeholder
684
685 $rv['content'] .= "<div class='postTags' style='float : right'>
686 <img src='images/tag.png'
687 class='tagsPic' alt='Tags' title='Tags'>&nbsp;";
688
689 if (!$zoom_mode) {
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>";
693
694 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
695 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
696 position=\"below\">$tags_str_full</div>";
697
698 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
699 $rv['content'] .= $p->hook_article_button($line);
700 }
701
702 } else {
703 $tags_str = strip_tags($tags_str);
704 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
705 }
706 $rv['content'] .= "</div>";
707 $rv['content'] .= "<div clear='both'>";
708
709 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
710 $rv['content'] .= $p->hook_article_left_button($line);
711 }
712
713 $rv['content'] .= "$entry_comments</div>";
714
715 if ($line["orig_feed_id"]) {
716
d0e73ed8
AD
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]);
7e5f8d9f 720
d0e73ed8 721 if ($tmp_line = $of_sth->fetch()) {
7e5f8d9f
AD
722
723 $rv['content'] .= "<div clear='both'>";
724 $rv['content'] .= __("Originally from:");
725
726 $rv['content'] .= "&nbsp;";
727
7e5f8d9f
AD
728 $rv['content'] .= "<a target='_blank' rel='noopener noreferrer'
729 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
730 $tmp_line['title'] . "</a>";
731
732 $rv['content'] .= "&nbsp;";
733
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>";
736
737 $rv['content'] .= "</div>";
738 }
739 }
740
741 $rv['content'] .= "</div>";
742
743 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
744 if ($line['note']) {
745 $rv['content'] .= Article::format_article_note($id, $line['note'], !$zoom_mode);
746 }
747 $rv['content'] .= "</div>";
748
749 if (!$line['lang']) $line['lang'] = 'en';
750
751 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
752
753 $rv['content'] .= $line["content"];
754
755 if (!$zoom_mode) {
756 $rv['content'] .= Article::format_article_enclosures($id,
757 sql_bool_to_bool($line["always_display_enclosures"]),
758 $line["content"],
759 sql_bool_to_bool($line["hide_images"]));
760 }
761
762 $rv['content'] .= "</div>";
763
764 $rv['content'] .= "</div>";
765
766 }
767
768 if ($zoom_mode) {
769 $rv['content'] .= "
770 <div class='footer'>
771 <button onclick=\"return window.close()\">".
772 __("Close this window")."</button></div>";
773 $rv['content'] .= "</body></html>";
774 }
775
e50a6479
AD
776 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ARTICLE) as $p) {
777 $rv['content'] = $p->hook_format_article($rv['content'], $line, $zoom_mode);
778 }
779
7e5f8d9f
AD
780 return $rv;
781
782 }
783
784 static function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
785
2c57df75 786 $a_id = $id;
7e5f8d9f
AD
787
788 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
789
d0e73ed8
AD
790 $pdo = Db::pdo();
791
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");
7e5f8d9f
AD
796
797 $tags = array();
798
799 /* check cache first */
800
801 if ($tag_cache === false) {
d0e73ed8
AD
802 $csth = $pdo->prepare("SELECT tag_cache FROM ttrss_user_entries
803 WHERE ref_id = ? AND owner_uid = ?");
804 $csth->execute([$id, $owner_uid]);
7e5f8d9f 805
d0e73ed8 806 if ($row = $csth->fetch()) $tag_cache = $row["tag_cache"];
7e5f8d9f
AD
807 }
808
809 if ($tag_cache) {
810 $tags = explode(",", $tag_cache);
811 } else {
812
813 /* do it the hard way */
814
d0e73ed8 815 $sth->execute([$a_id, $owner_uid]);
7e5f8d9f 816
d0e73ed8 817 while ($tmp_line = $sth->fetch()) {
7e5f8d9f
AD
818 array_push($tags, $tmp_line["tag_name"]);
819 }
820
821 /* update the cache */
822
2c57df75 823 $tags_str = join(",", $tags);
7e5f8d9f 824
d0e73ed8
AD
825 $sth = $pdo->prepare("UPDATE ttrss_user_entries
826 SET tag_cache = ? WHERE ref_id = ?
827 AND owner_uid = ?");
828 $sth->execute([$tags_str, $id, $owner_uid]);
7e5f8d9f
AD
829 }
830
831 return $tags;
832 }
833
834 static function format_tags_string($tags) {
835 if (!is_array($tags) || count($tags) == 0) {
836 return __("no tags");
837 } else {
838 $maxtags = min(5, count($tags));
839 $tags_str = "";
840
841 for ($i = 0; $i < $maxtags; $i++) {
842 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
843 }
844
845 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
846
847 if (count($tags) > $maxtags)
848 $tags_str .= ", &hellip;";
849
850 return $tags_str;
851 }
852 }
853
854 static function format_article_labels($labels) {
855
856 if (!is_array($labels)) return '';
857
858 $labels_str = "";
859
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]);
864 }
865
866 return $labels_str;
867
868 }
869
870 static function format_article_note($id, $note, $allow_edit = true) {
871
872 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
873 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
874 ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
875
876 return $str;
877 }
878
879 static function get_article_enclosures($id) {
880
d0e73ed8 881 $pdo = Db::pdo();
7e5f8d9f 882
d0e73ed8
AD
883 $sth = $pdo->prepare("SELECT * FROM ttrss_enclosures
884 WHERE post_id = ? AND content_url != ''");
885 $sth->execute([$id]);
7e5f8d9f 886
d0e73ed8 887 $rv = array();
7e5f8d9f 888
d0e73ed8 889 while ($line = $sth->fetch()) {
7e5f8d9f 890
d0e73ed8
AD
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"]);
7e5f8d9f 893 }
d0e73ed8
AD
894
895 array_push($rv, $line);
7e5f8d9f
AD
896 }
897
898 return $rv;
899 }
c83554bd 900
a230bf88
AD
901 static function purge_orphans($do_output = false) {
902
903 // purge orphaned posts in main content table
d0e73ed8
AD
904
905 $pdo = Db::pdo();
906 $res = $pdo->query("DELETE FROM ttrss_entries WHERE
a230bf88
AD
907 NOT EXISTS (SELECT ref_id FROM ttrss_user_entries WHERE ref_id = id)");
908
909 if ($do_output) {
d0e73ed8 910 $rows = $res->rowCount();
a230bf88
AD
911 _debug("Purged $rows orphaned posts.");
912 }
913 }
914
aeb1abed
AD
915 static function catchupArticlesById($ids, $cmode, $owner_uid = false) {
916
917 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
aeb1abed 918
d0e73ed8 919 $pdo = Db::pdo();
aeb1abed 920
d0e73ed8 921 $ids_qmarks = arr_qmarks($ids);
aeb1abed
AD
922
923 if ($cmode == 0) {
d0e73ed8 924 $sth = $pdo->prepare("UPDATE ttrss_user_entries SET
aeb1abed 925 unread = false,last_read = NOW()
d0e73ed8 926 WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
aeb1abed 927 } else if ($cmode == 1) {
d0e73ed8 928 $sth = $pdo->prepare("UPDATE ttrss_user_entries SET
aeb1abed 929 unread = true
d0e73ed8 930 WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
aeb1abed 931 } else {
d0e73ed8
AD
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 = ?");
aeb1abed
AD
935 }
936
d0e73ed8
AD
937 $sth->execute(array_merge($ids, [$owner_uid]));
938
aeb1abed
AD
939 /* update ccache */
940
d0e73ed8
AD
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]));
aeb1abed 944
d0e73ed8 945 while ($line = $sth->fetch()) {
2ed0d6c4 946 CCache::update($line["feed_id"], $owner_uid);
aeb1abed
AD
947 }
948 }
949
950 static function getLastArticleId() {
d0e73ed8
AD
951 $pdo = DB::pdo();
952
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']]);
aeb1abed 956
d0e73ed8
AD
957 if ($row = $sth->fetch()) {
958 return $row['id'];
aeb1abed
AD
959 } else {
960 return -1;
961 }
962 }
a230bf88 963
4a0da0e5
AD
964 static function get_article_labels($id, $owner_uid = false) {
965 $rv = array();
966
967 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
968
d0e73ed8
AD
969 $pdo = Db::pdo();
970
971 $sth = $pdo->prepare("SELECT label_cache FROM
972 ttrss_user_entries WHERE ref_id = ? AND owner_uid = ?");
973 $sth->execute([$id, $owner_uid]);
4a0da0e5 974
d0e73ed8
AD
975 if ($row = $sth->fetch()) {
976 $label_cache = $row["label_cache"];
4a0da0e5
AD
977
978 if ($label_cache) {
e4befe6b 979 $tmp = json_decode($label_cache, true);
4a0da0e5 980
e4befe6b 981 if (!$tmp || $tmp["no-labels"] == 1)
4a0da0e5
AD
982 return $rv;
983 else
e4befe6b 984 return $tmp;
4a0da0e5
AD
985 }
986 }
987
d0e73ed8 988 $sth = $pdo->prepare("SELECT DISTINCT label_id,caption,fg_color,bg_color
4a0da0e5
AD
989 FROM ttrss_labels2, ttrss_user_labels2
990 WHERE id = label_id
d0e73ed8
AD
991 AND article_id = ?
992 AND owner_uid = ?
4a0da0e5 993 ORDER BY caption");
d0e73ed8 994 $sth->execute([$id, $owner_uid]);
4a0da0e5 995
d0e73ed8 996 while ($line = $sth->fetch()) {
7c9b5a3f 997 $rk = array(Labels::label_to_feed_id($line["label_id"]),
4a0da0e5
AD
998 $line["caption"], $line["fg_color"],
999 $line["bg_color"]);
1000 array_push($rv, $rk);
1001 }
1002
1003 if (count($rv) > 0)
7c9b5a3f 1004 Labels::update_cache($owner_uid, $id, $rv);
4a0da0e5 1005 else
7c9b5a3f 1006 Labels::update_cache($owner_uid, $id, array("no-labels" => 1));
4a0da0e5
AD
1007
1008 return $rv;
1009 }
1010
6afcbcd1 1011}