1
|
|
|
<?php |
2
|
|
|
class Article extends Handler_Protected { |
3
|
|
|
|
4
|
|
|
public function csrf_ignore($method) { |
5
|
|
|
$csrf_ignored = array("redirect", "editarticletags"); |
6
|
|
|
|
7
|
|
|
return array_search($method, $csrf_ignored) !== false; |
8
|
|
|
} |
9
|
|
|
|
10
|
|
|
public function redirect() { |
11
|
|
|
$id = clean($_REQUEST['id']); |
12
|
|
|
|
13
|
|
|
$sth = $this->pdo->prepare("SELECT link FROM ttrss_entries, ttrss_user_entries |
14
|
|
|
WHERE id = ? AND id = ref_id AND owner_uid = ? |
15
|
|
|
LIMIT 1"); |
16
|
|
|
$sth->execute([$id, $_SESSION['uid']]); |
17
|
|
|
|
18
|
|
|
if ($row = $sth->fetch()) { |
19
|
|
|
$article_url = $row['link']; |
20
|
|
|
$article_url = str_replace("\n", "", $article_url); |
21
|
|
|
|
22
|
|
|
header("Location: $article_url"); |
23
|
|
|
return; |
24
|
|
|
|
25
|
|
|
} else { |
26
|
|
|
print_error(__("Article not found.")); |
|
|
|
|
27
|
|
|
} |
28
|
|
|
} |
29
|
|
|
|
30
|
|
|
public static function create_published_article($title, $url, $content, $labels_str, |
31
|
|
|
$owner_uid) { |
32
|
|
|
|
33
|
|
|
$guid = 'SHA1:'.sha1("ttshared:".$url.$owner_uid); // include owner_uid to prevent global GUID clash |
34
|
|
|
|
35
|
|
|
if (!$content) { |
36
|
|
|
$pluginhost = new PluginHost(); |
37
|
|
|
$pluginhost->load_all(PluginHost::KIND_ALL, $owner_uid); |
38
|
|
|
$pluginhost->load_data(); |
39
|
|
|
|
40
|
|
|
foreach ($pluginhost->get_hooks(PluginHost::HOOK_GET_FULL_TEXT) as $p) { |
41
|
|
|
$extracted_content = $p->hook_get_full_text($url); |
42
|
|
|
|
43
|
|
|
if ($extracted_content) { |
44
|
|
|
$content = $extracted_content; |
45
|
|
|
break; |
46
|
|
|
} |
47
|
|
|
} |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
$content_hash = sha1($content); |
51
|
|
|
|
52
|
|
|
if ($labels_str != "") { |
53
|
|
|
$labels = explode(",", $labels_str); |
54
|
|
|
} else { |
55
|
|
|
$labels = array(); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
$rc = false; |
59
|
|
|
|
60
|
|
|
if (!$title) { |
61
|
|
|
$title = $url; |
62
|
|
|
} |
63
|
|
|
if (!$title && !$url) { |
64
|
|
|
return false; |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
if (filter_var($url, FILTER_VALIDATE_URL) === false) { |
68
|
|
|
return false; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
$pdo = Db::pdo(); |
72
|
|
|
|
73
|
|
|
$pdo->beginTransaction(); |
74
|
|
|
|
75
|
|
|
// only check for our user data here, others might have shared this with different content etc |
76
|
|
|
$sth = $pdo->prepare("SELECT id FROM ttrss_entries, ttrss_user_entries WHERE |
77
|
|
|
guid = ? AND ref_id = id AND owner_uid = ? LIMIT 1"); |
78
|
|
|
$sth->execute([$guid, $owner_uid]); |
79
|
|
|
|
80
|
|
|
if ($row = $sth->fetch()) { |
81
|
|
|
$ref_id = $row['id']; |
82
|
|
|
|
83
|
|
|
$sth = $pdo->prepare("SELECT int_id FROM ttrss_user_entries WHERE |
84
|
|
|
ref_id = ? AND owner_uid = ? LIMIT 1"); |
85
|
|
|
$sth->execute([$ref_id, $owner_uid]); |
86
|
|
|
|
87
|
|
|
if ($row = $sth->fetch()) { |
88
|
|
|
$int_id = $row['int_id']; |
89
|
|
|
|
90
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_entries SET |
91
|
|
|
content = ?, content_hash = ? WHERE id = ?"); |
92
|
|
|
$sth->execute([$content, $content_hash, $ref_id]); |
93
|
|
|
|
94
|
|
|
if (DB_TYPE == "pgsql") { |
95
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_entries |
96
|
|
|
SET tsvector_combined = to_tsvector( :ts_content) |
97
|
|
|
WHERE id = :id"); |
98
|
|
|
$params = [ |
99
|
|
|
":ts_content" => mb_substr(strip_tags($content), 0, 900000), |
100
|
|
|
":id" => $ref_id]; |
101
|
|
|
$sth->execute($params); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_user_entries SET published = true, |
105
|
|
|
last_published = NOW() WHERE |
106
|
|
|
int_id = ? AND owner_uid = ?"); |
107
|
|
|
$sth->execute([$int_id, $owner_uid]); |
108
|
|
|
|
109
|
|
|
} else { |
110
|
|
|
|
111
|
|
|
$sth = $pdo->prepare("INSERT INTO ttrss_user_entries |
112
|
|
|
(ref_id, uuid, feed_id, orig_feed_id, owner_uid, published, tag_cache, label_cache, |
113
|
|
|
last_read, note, unread, last_published) |
114
|
|
|
VALUES |
115
|
|
|
(?, '', NULL, NULL, ?, true, '', '', NOW(), '', false, NOW())"); |
116
|
|
|
$sth->execute([$ref_id, $owner_uid]); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
if (count($labels) != 0) { |
120
|
|
|
foreach ($labels as $label) { |
121
|
|
|
Labels::add_article($ref_id, trim($label), $owner_uid); |
122
|
|
|
} |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
$rc = true; |
126
|
|
|
|
127
|
|
|
} else { |
128
|
|
|
$sth = $pdo->prepare("INSERT INTO ttrss_entries |
129
|
|
|
(title, guid, link, updated, content, content_hash, date_entered, date_updated) |
130
|
|
|
VALUES |
131
|
|
|
(?, ?, ?, NOW(), ?, ?, NOW(), NOW())"); |
132
|
|
|
$sth->execute([$title, $guid, $url, $content, $content_hash]); |
133
|
|
|
|
134
|
|
|
$sth = $pdo->prepare("SELECT id FROM ttrss_entries WHERE guid = ?"); |
135
|
|
|
$sth->execute([$guid]); |
136
|
|
|
|
137
|
|
|
if ($row = $sth->fetch()) { |
138
|
|
|
$ref_id = $row["id"]; |
139
|
|
|
if (DB_TYPE == "pgsql") { |
140
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_entries |
141
|
|
|
SET tsvector_combined = to_tsvector( :ts_content) |
142
|
|
|
WHERE id = :id"); |
143
|
|
|
$params = [ |
144
|
|
|
":ts_content" => mb_substr(strip_tags($content), 0, 900000), |
145
|
|
|
":id" => $ref_id]; |
146
|
|
|
$sth->execute($params); |
147
|
|
|
} |
148
|
|
|
$sth = $pdo->prepare("INSERT INTO ttrss_user_entries |
149
|
|
|
(ref_id, uuid, feed_id, orig_feed_id, owner_uid, published, tag_cache, label_cache, |
150
|
|
|
last_read, note, unread, last_published) |
151
|
|
|
VALUES |
152
|
|
|
(?, '', NULL, NULL, ?, true, '', '', NOW(), '', false, NOW())"); |
153
|
|
|
$sth->execute([$ref_id, $owner_uid]); |
154
|
|
|
|
155
|
|
|
if (count($labels) != 0) { |
156
|
|
|
foreach ($labels as $label) { |
157
|
|
|
Labels::add_article($ref_id, trim($label), $owner_uid); |
158
|
|
|
} |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
$rc = true; |
162
|
|
|
} |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
$pdo->commit(); |
166
|
|
|
|
167
|
|
|
return $rc; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
public function editArticleTags() { |
171
|
|
|
|
172
|
|
|
$param = clean($_REQUEST['param']); |
173
|
|
|
|
174
|
|
|
$tags = Article::get_article_tags($param); |
175
|
|
|
|
176
|
|
|
$tags_str = join(", ", $tags); |
177
|
|
|
|
178
|
|
|
print_hidden("id", "$param"); |
179
|
|
|
print_hidden("op", "article"); |
180
|
|
|
print_hidden("method", "setArticleTags"); |
181
|
|
|
|
182
|
|
|
print "<header class='horizontal'>".__("Tags for this article (separated by commas):")."</header>"; |
183
|
|
|
|
184
|
|
|
print "<section>"; |
185
|
|
|
print "<textarea dojoType='dijit.form.SimpleTextarea' rows='4' |
186
|
|
|
style='height : 100px; font-size : 12px; width : 98%' id='tags_str' |
187
|
|
|
name='tags_str'>$tags_str</textarea> |
188
|
|
|
<div class='autocomplete' id='tags_choices' |
189
|
|
|
style='display:none'></div>"; |
190
|
|
|
print "</section>"; |
191
|
|
|
|
192
|
|
|
print "<footer>"; |
193
|
|
|
print "<button dojoType='dijit.form.Button' |
194
|
|
|
type='submit' class='alt-primary' onclick=\"dijit.byId('editTagsDlg').execute()\">".__('Save')."</button> "; |
195
|
|
|
print "<button dojoType='dijit.form.Button' |
196
|
|
|
onclick=\"dijit.byId('editTagsDlg').hide()\">".__('Cancel')."</button>"; |
197
|
|
|
print "</footer>"; |
198
|
|
|
|
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
public function setScore() { |
202
|
|
|
$ids = explode(",", clean($_REQUEST['id'])); |
203
|
|
|
$score = (int) clean($_REQUEST['score']); |
204
|
|
|
|
205
|
|
|
$ids_qmarks = arr_qmarks($ids); |
206
|
|
|
|
207
|
|
|
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET |
208
|
|
|
score = ? WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); |
209
|
|
|
|
210
|
|
|
$sth->execute(array_merge([$score], $ids, [$_SESSION['uid']])); |
211
|
|
|
|
212
|
|
|
print json_encode(["id" => $ids, "score" => (int) $score]); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
public function getScore() { |
216
|
|
|
$id = clean($_REQUEST['id']); |
217
|
|
|
|
218
|
|
|
$sth = $this->pdo->prepare("SELECT score FROM ttrss_user_entries WHERE ref_id = ? AND owner_uid = ?"); |
219
|
|
|
$sth->execute([$id, $_SESSION['uid']]); |
220
|
|
|
$row = $sth->fetch(); |
221
|
|
|
|
222
|
|
|
$score = $row['score']; |
223
|
|
|
|
224
|
|
|
print json_encode(["id" => $id, "score" => (int) $score]); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
|
228
|
|
|
public function setArticleTags() { |
229
|
|
|
|
230
|
|
|
$id = clean($_REQUEST["id"]); |
231
|
|
|
|
232
|
|
|
$tags_str = clean($_REQUEST["tags_str"]); |
233
|
|
|
$tags = array_unique(trim_array(explode(",", $tags_str))); |
234
|
|
|
|
235
|
|
|
$this->pdo->beginTransaction(); |
236
|
|
|
|
237
|
|
|
$sth = $this->pdo->prepare("SELECT int_id FROM ttrss_user_entries WHERE |
238
|
|
|
ref_id = ? AND owner_uid = ? LIMIT 1"); |
239
|
|
|
$sth->execute([$id, $_SESSION['uid']]); |
240
|
|
|
|
241
|
|
|
if ($row = $sth->fetch()) { |
242
|
|
|
|
243
|
|
|
$tags_to_cache = array(); |
244
|
|
|
|
245
|
|
|
$int_id = $row['int_id']; |
246
|
|
|
|
247
|
|
|
$sth = $this->pdo->prepare("DELETE FROM ttrss_tags WHERE |
248
|
|
|
post_int_id = ? AND owner_uid = ?"); |
249
|
|
|
$sth->execute([$int_id, $_SESSION['uid']]); |
250
|
|
|
|
251
|
|
|
$tags = FeedItem_Common::normalize_categories($tags); |
252
|
|
|
|
253
|
|
|
foreach ($tags as $tag) { |
254
|
|
|
if ($tag != '') { |
255
|
|
|
$sth = $this->pdo->prepare("INSERT INTO ttrss_tags |
256
|
|
|
(post_int_id, owner_uid, tag_name) |
257
|
|
|
VALUES (?, ?, ?)"); |
258
|
|
|
|
259
|
|
|
$sth->execute([$int_id, $_SESSION['uid'], $tag]); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
array_push($tags_to_cache, $tag); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/* update tag cache */ |
266
|
|
|
|
267
|
|
|
$tags_str = join(",", $tags_to_cache); |
268
|
|
|
|
269
|
|
|
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries |
270
|
|
|
SET tag_cache = ? WHERE ref_id = ? AND owner_uid = ?"); |
271
|
|
|
$sth->execute([$tags_str, $id, $_SESSION['uid']]); |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
$this->pdo->commit(); |
275
|
|
|
|
276
|
|
|
$tags = Article::get_article_tags($id); |
277
|
|
|
$tags_str = $this->format_tags_string($tags); |
278
|
|
|
$tags_str_full = join(", ", $tags); |
279
|
|
|
|
280
|
|
|
if (!$tags_str_full) { |
281
|
|
|
$tags_str_full = __("no tags"); |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
print json_encode([ |
285
|
|
|
"id" => (int) $id, |
286
|
|
|
"content" => $tags_str, |
287
|
|
|
"content_full" => $tags_str_full |
288
|
|
|
]); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
|
292
|
|
|
public function completeTags() { |
293
|
|
|
$search = clean($_REQUEST["search"]); |
294
|
|
|
|
295
|
|
|
$sth = $this->pdo->prepare("SELECT DISTINCT tag_name FROM ttrss_tags |
296
|
|
|
WHERE owner_uid = ? AND |
297
|
|
|
tag_name LIKE ? ORDER BY tag_name |
298
|
|
|
LIMIT 10"); |
299
|
|
|
|
300
|
|
|
$sth->execute([$_SESSION['uid'], "$search%"]); |
301
|
|
|
|
302
|
|
|
print "<ul>"; |
303
|
|
|
while ($line = $sth->fetch()) { |
304
|
|
|
print "<li>".$line["tag_name"]."</li>"; |
305
|
|
|
} |
306
|
|
|
print "</ul>"; |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
public function assigntolabel() { |
310
|
|
|
return $this->labelops(true); |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
public function removefromlabel() { |
314
|
|
|
return $this->labelops(false); |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
private function labelops($assign) { |
318
|
|
|
$reply = array(); |
319
|
|
|
|
320
|
|
|
$ids = explode(",", clean($_REQUEST["ids"])); |
321
|
|
|
$label_id = clean($_REQUEST["lid"]); |
322
|
|
|
|
323
|
|
|
$label = Labels::find_caption($label_id, $_SESSION["uid"]); |
324
|
|
|
|
325
|
|
|
$reply["info-for-headlines"] = array(); |
326
|
|
|
|
327
|
|
|
if ($label) { |
328
|
|
|
|
329
|
|
|
foreach ($ids as $id) { |
330
|
|
|
|
331
|
|
|
if ($assign) { |
332
|
|
|
Labels::add_article($id, $label, $_SESSION["uid"]); |
333
|
|
|
} else { |
334
|
|
|
Labels::remove_article($id, $label, $_SESSION["uid"]); |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
$labels = $this->get_article_labels($id, $_SESSION["uid"]); |
338
|
|
|
|
339
|
|
|
array_push($reply["info-for-headlines"], |
340
|
|
|
array("id" => $id, "labels" => $this->format_article_labels($labels))); |
341
|
|
|
|
342
|
|
|
} |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
$reply["message"] = "UPDATE_COUNTERS"; |
346
|
|
|
|
347
|
|
|
print json_encode($reply); |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
public function getArticleFeed($id) { |
351
|
|
|
$sth = $this->pdo->prepare("SELECT feed_id FROM ttrss_user_entries |
352
|
|
|
WHERE ref_id = ? AND owner_uid = ?"); |
353
|
|
|
$sth->execute([$id, $_SESSION['uid']]); |
354
|
|
|
|
355
|
|
|
if ($row = $sth->fetch()) { |
356
|
|
|
return $row["feed_id"]; |
357
|
|
|
} else { |
358
|
|
|
return 0; |
359
|
|
|
} |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
public static function format_article_enclosures($id, $always_display_enclosures, |
363
|
|
|
$article_content, $hide_images = false) { |
364
|
|
|
|
365
|
|
|
$result = Article::get_article_enclosures($id); |
366
|
|
|
$rv = ''; |
367
|
|
|
|
368
|
|
|
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ENCLOSURES) as $plugin) { |
369
|
|
|
$retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images); |
370
|
|
|
if (is_array($retval)) { |
371
|
|
|
$rv = $retval[0]; |
372
|
|
|
$result = $retval[1]; |
373
|
|
|
} else { |
374
|
|
|
$rv = $retval; |
375
|
|
|
} |
376
|
|
|
} |
377
|
|
|
unset($retval); // Unset to prevent breaking render if there are no HOOK_RENDER_ENCLOSURE hooks below. |
378
|
|
|
|
379
|
|
|
if ($rv === '' && !empty($result)) { |
380
|
|
|
$entries_html = array(); |
381
|
|
|
$entries = array(); |
382
|
|
|
$entries_inline = array(); |
383
|
|
|
|
384
|
|
|
foreach ($result as $line) { |
385
|
|
|
|
386
|
|
|
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ENCLOSURE_ENTRY) as $plugin) { |
387
|
|
|
$line = $plugin->hook_enclosure_entry($line, $id); |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
$url = $line["content_url"]; |
391
|
|
|
$ctype = $line["content_type"]; |
392
|
|
|
$title = $line["title"]; |
393
|
|
|
$width = $line["width"]; |
394
|
|
|
$height = $line["height"]; |
395
|
|
|
|
396
|
|
|
if (!$ctype) { |
397
|
|
|
$ctype = __("unknown type"); |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
//$filename = substr($url, strrpos($url, "/")+1); |
401
|
|
|
$filename = basename($url); |
402
|
|
|
|
403
|
|
|
$player = format_inline_player($url, $ctype); |
404
|
|
|
|
405
|
|
|
if ($player) { |
406
|
|
|
array_push($entries_inline, $player); |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
# $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\" rel=\"noopener noreferrer\">" . |
410
|
|
|
# $filename . " (" . $ctype . ")" . "</a>"; |
411
|
|
|
|
412
|
|
|
$entry = "<div onclick=\"popupOpenUrl('".htmlspecialchars($url)."')\" |
413
|
|
|
dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>"; |
414
|
|
|
|
415
|
|
|
array_push($entries_html, $entry); |
416
|
|
|
|
417
|
|
|
$entry = array(); |
418
|
|
|
|
419
|
|
|
$entry["type"] = $ctype; |
420
|
|
|
$entry["filename"] = $filename; |
421
|
|
|
$entry["url"] = $url; |
422
|
|
|
$entry["title"] = $title; |
423
|
|
|
$entry["width"] = $width; |
424
|
|
|
$entry["height"] = $height; |
425
|
|
|
|
426
|
|
|
array_push($entries, $entry); |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) { |
430
|
|
|
if ($always_display_enclosures || |
431
|
|
|
!preg_match("/<img/i", $article_content)) { |
432
|
|
|
|
433
|
|
|
foreach ($entries as $entry) { |
434
|
|
|
|
435
|
|
|
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ENCLOSURE) as $plugin) { |
436
|
|
|
$retval = $plugin->hook_render_enclosure($entry, $hide_images); |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
|
440
|
|
|
if ($retval) { |
441
|
|
|
$rv .= $retval; |
442
|
|
|
} else { |
443
|
|
|
|
444
|
|
|
if (preg_match("/image/", $entry["type"])) { |
445
|
|
|
|
446
|
|
|
if (!$hide_images) { |
447
|
|
|
$encsize = ''; |
448
|
|
|
if ($entry['height'] > 0) { |
449
|
|
|
$encsize .= ' height="'.intval($entry['height']).'"'; |
450
|
|
|
} |
451
|
|
|
if ($entry['width'] > 0) { |
452
|
|
|
$encsize .= ' width="'.intval($entry['width']).'"'; |
453
|
|
|
} |
454
|
|
|
$rv .= "<p><img |
455
|
|
|
alt=\"".htmlspecialchars($entry["filename"])."\" |
456
|
|
|
src=\"" .htmlspecialchars($entry["url"])."\" |
457
|
|
|
" . $encsize." /></p>"; |
458
|
|
|
} else { |
459
|
|
|
$rv .= "<p><a target=\"_blank\" rel=\"noopener noreferrer\" |
460
|
|
|
href=\"".htmlspecialchars($entry["url"])."\" |
461
|
|
|
>" .htmlspecialchars($entry["url"])."</a></p>"; |
462
|
|
|
} |
463
|
|
|
|
464
|
|
|
if ($entry['title']) { |
465
|
|
|
$rv .= "<div class=\"enclosure_title\">${entry['title']}</div>"; |
466
|
|
|
} |
467
|
|
|
} |
468
|
|
|
} |
469
|
|
|
} |
470
|
|
|
} |
471
|
|
|
} |
472
|
|
|
|
473
|
|
|
if (count($entries_inline) > 0) { |
474
|
|
|
//$rv .= "<hr clear='both'/>"; |
475
|
|
|
foreach ($entries_inline as $entry) { $rv .= $entry; }; |
476
|
|
|
$rv .= "<br clear='both'/>"; |
477
|
|
|
} |
478
|
|
|
|
479
|
|
|
$rv .= "<div class=\"attachments\" dojoType=\"fox.form.DropDownButton\">". |
480
|
|
|
"<span>".__('Attachments')."</span>"; |
481
|
|
|
|
482
|
|
|
$rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">"; |
483
|
|
|
|
484
|
|
|
foreach ($entries as $entry) { |
485
|
|
|
if ($entry["title"]) { |
486
|
|
|
$title = " — ".truncate_string($entry["title"], 30); |
487
|
|
|
} else { |
488
|
|
|
$title = ""; |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
if ($entry["filename"]) { |
492
|
|
|
$filename = truncate_middle(htmlspecialchars($entry["filename"]), 60); |
493
|
|
|
} else { |
494
|
|
|
$filename = ""; |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
$rv .= "<div onclick='popupOpenUrl(\"".htmlspecialchars($entry["url"])."\")' |
498
|
|
|
dojoType=\"dijit.MenuItem\">".$filename.$title."</div>"; |
499
|
|
|
|
500
|
|
|
}; |
501
|
|
|
|
502
|
|
|
$rv .= "</div>"; |
503
|
|
|
$rv .= "</div>"; |
504
|
|
|
} |
505
|
|
|
|
506
|
|
|
return $rv; |
507
|
|
|
} |
508
|
|
|
|
509
|
|
|
public static function get_article_tags($id, $owner_uid = 0, $tag_cache = false) { |
510
|
|
|
|
511
|
|
|
$a_id = $id; |
512
|
|
|
|
513
|
|
|
if (!$owner_uid) { |
514
|
|
|
$owner_uid = $_SESSION["uid"]; |
515
|
|
|
} |
516
|
|
|
|
517
|
|
|
$pdo = Db::pdo(); |
518
|
|
|
|
519
|
|
|
$sth = $pdo->prepare("SELECT DISTINCT tag_name, |
520
|
|
|
owner_uid as owner FROM ttrss_tags |
521
|
|
|
WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE |
522
|
|
|
ref_id = ? AND owner_uid = ? LIMIT 1) ORDER BY tag_name"); |
523
|
|
|
|
524
|
|
|
$tags = array(); |
525
|
|
|
|
526
|
|
|
/* check cache first */ |
527
|
|
|
|
528
|
|
|
if ($tag_cache === false) { |
529
|
|
|
$csth = $pdo->prepare("SELECT tag_cache FROM ttrss_user_entries |
530
|
|
|
WHERE ref_id = ? AND owner_uid = ?"); |
531
|
|
|
$csth->execute([$id, $owner_uid]); |
532
|
|
|
|
533
|
|
|
if ($row = $csth->fetch()) { |
534
|
|
|
$tag_cache = $row["tag_cache"]; |
535
|
|
|
} |
536
|
|
|
} |
537
|
|
|
|
538
|
|
|
if ($tag_cache) { |
539
|
|
|
$tags = explode(",", $tag_cache); |
540
|
|
|
} else { |
541
|
|
|
|
542
|
|
|
/* do it the hard way */ |
543
|
|
|
|
544
|
|
|
$sth->execute([$a_id, $owner_uid]); |
545
|
|
|
|
546
|
|
|
while ($tmp_line = $sth->fetch()) { |
547
|
|
|
array_push($tags, $tmp_line["tag_name"]); |
548
|
|
|
} |
549
|
|
|
|
550
|
|
|
/* update the cache */ |
551
|
|
|
|
552
|
|
|
$tags_str = join(",", $tags); |
553
|
|
|
|
554
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_user_entries |
555
|
|
|
SET tag_cache = ? WHERE ref_id = ? |
556
|
|
|
AND owner_uid = ?"); |
557
|
|
|
$sth->execute([$tags_str, $id, $owner_uid]); |
558
|
|
|
} |
559
|
|
|
|
560
|
|
|
return $tags; |
561
|
|
|
} |
562
|
|
|
|
563
|
|
|
public static function format_tags_string($tags) { |
564
|
|
|
if (!is_array($tags) || count($tags) == 0) { |
565
|
|
|
return __("no tags"); |
566
|
|
|
} else { |
567
|
|
|
$maxtags = min(5, count($tags)); |
568
|
|
|
$tags_str = ""; |
569
|
|
|
|
570
|
|
|
for ($i = 0; $i < $maxtags; $i++) { |
571
|
|
|
$tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"Feeds.open({feed:'".$tags[$i]."'})\">".$tags[$i]."</a>, "; |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
$tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str) - 2); |
575
|
|
|
|
576
|
|
|
if (count($tags) > $maxtags) { |
577
|
|
|
$tags_str .= ", …"; |
578
|
|
|
} |
579
|
|
|
|
580
|
|
|
return $tags_str; |
581
|
|
|
} |
582
|
|
|
} |
583
|
|
|
|
584
|
|
|
public static function format_article_labels($labels) { |
585
|
|
|
|
586
|
|
|
if (!is_array($labels)) { |
587
|
|
|
return ''; |
588
|
|
|
} |
589
|
|
|
|
590
|
|
|
$labels_str = ""; |
591
|
|
|
|
592
|
|
|
foreach ($labels as $l) { |
593
|
|
|
$labels_str .= sprintf("<div class='label' |
594
|
|
|
style='color : %s; background-color : %s'>%s</div>", |
595
|
|
|
$l[2], $l[3], $l[1]); |
596
|
|
|
} |
597
|
|
|
|
598
|
|
|
return $labels_str; |
599
|
|
|
|
600
|
|
|
} |
601
|
|
|
|
602
|
|
|
public static function format_article_note($id, $note, $allow_edit = true) { |
603
|
|
|
|
604
|
|
|
if ($allow_edit) { |
605
|
|
|
$onclick = "onclick='Plugins.Note.edit($id)'"; |
606
|
|
|
$note_class = 'editable'; |
607
|
|
|
} else { |
608
|
|
|
$onclick = ''; |
609
|
|
|
$note_class = ''; |
610
|
|
|
} |
611
|
|
|
|
612
|
|
|
return "<div class='article-note $note_class'> |
613
|
|
|
<i class='material-icons'>note</i> |
614
|
|
|
<div $onclick class='body'>$note</div> |
615
|
|
|
</div>"; |
616
|
|
|
} |
617
|
|
|
|
618
|
|
|
public static function get_article_enclosures($id) { |
619
|
|
|
|
620
|
|
|
$pdo = Db::pdo(); |
621
|
|
|
|
622
|
|
|
$sth = $pdo->prepare("SELECT * FROM ttrss_enclosures |
623
|
|
|
WHERE post_id = ? AND content_url != ''"); |
624
|
|
|
$sth->execute([$id]); |
625
|
|
|
|
626
|
|
|
$rv = array(); |
627
|
|
|
|
628
|
|
|
$cache = new DiskCache("images"); |
629
|
|
|
|
630
|
|
|
while ($line = $sth->fetch()) { |
631
|
|
|
|
632
|
|
|
if ($cache->exists(sha1($line["content_url"]))) { |
633
|
|
|
$line["content_url"] = $cache->getUrl(sha1($line["content_url"])); |
634
|
|
|
} |
635
|
|
|
|
636
|
|
|
array_push($rv, $line); |
637
|
|
|
} |
638
|
|
|
|
639
|
|
|
return $rv; |
640
|
|
|
} |
641
|
|
|
|
642
|
|
|
public static function purge_orphans() { |
643
|
|
|
|
644
|
|
|
// purge orphaned posts in main content table |
645
|
|
|
|
646
|
|
|
if (DB_TYPE == "mysql") { |
647
|
|
|
$limit_qpart = "LIMIT 5000"; |
648
|
|
|
} else { |
649
|
|
|
$limit_qpart = ""; |
650
|
|
|
} |
651
|
|
|
|
652
|
|
|
$pdo = Db::pdo(); |
653
|
|
|
$res = $pdo->query("DELETE FROM ttrss_entries WHERE |
654
|
|
|
NOT EXISTS (SELECT ref_id FROM ttrss_user_entries WHERE ref_id = id) $limit_qpart"); |
655
|
|
|
|
656
|
|
|
if (Debug::enabled()) { |
657
|
|
|
$rows = $res->rowCount(); |
658
|
|
|
Debug::log("purged $rows orphaned posts."); |
659
|
|
|
} |
660
|
|
|
} |
661
|
|
|
|
662
|
|
|
public static function catchupArticlesById($ids, $cmode, $owner_uid = false) { |
663
|
|
|
|
664
|
|
|
if (!$owner_uid) { |
665
|
|
|
$owner_uid = $_SESSION["uid"]; |
666
|
|
|
} |
667
|
|
|
|
668
|
|
|
$pdo = Db::pdo(); |
669
|
|
|
|
670
|
|
|
$ids_qmarks = arr_qmarks($ids); |
671
|
|
|
|
672
|
|
|
if ($cmode == 1) { |
673
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_user_entries SET |
674
|
|
|
unread = true |
675
|
|
|
WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); |
676
|
|
|
} else if ($cmode == 2) { |
677
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_user_entries SET |
678
|
|
|
unread = NOT unread,last_read = NOW() |
679
|
|
|
WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); |
680
|
|
|
} else { |
681
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_user_entries SET |
682
|
|
|
unread = false,last_read = NOW() |
683
|
|
|
WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); |
684
|
|
|
} |
685
|
|
|
|
686
|
|
|
$sth->execute(array_merge($ids, [$owner_uid])); |
687
|
|
|
|
688
|
|
|
/* update ccache */ |
689
|
|
|
|
690
|
|
|
$sth = $pdo->prepare("SELECT DISTINCT feed_id FROM ttrss_user_entries |
691
|
|
|
WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); |
692
|
|
|
$sth->execute(array_merge($ids, [$owner_uid])); |
693
|
|
|
|
694
|
|
|
while ($line = $sth->fetch()) { |
695
|
|
|
CCache::update($line["feed_id"], $owner_uid); |
696
|
|
|
} |
697
|
|
|
} |
698
|
|
|
|
699
|
|
|
public static function getLastArticleId() { |
700
|
|
|
$pdo = DB::pdo(); |
701
|
|
|
|
702
|
|
|
$sth = $pdo->prepare("SELECT ref_id AS id FROM ttrss_user_entries |
703
|
|
|
WHERE owner_uid = ? ORDER BY ref_id DESC LIMIT 1"); |
704
|
|
|
$sth->execute([$_SESSION['uid']]); |
705
|
|
|
|
706
|
|
|
if ($row = $sth->fetch()) { |
707
|
|
|
return $row['id']; |
708
|
|
|
} else { |
709
|
|
|
return -1; |
710
|
|
|
} |
711
|
|
|
} |
712
|
|
|
|
713
|
|
|
public static function get_article_labels($id, $owner_uid = false) { |
714
|
|
|
$rv = array(); |
715
|
|
|
|
716
|
|
|
if (!$owner_uid) { |
717
|
|
|
$owner_uid = $_SESSION["uid"]; |
718
|
|
|
} |
719
|
|
|
|
720
|
|
|
$pdo = Db::pdo(); |
721
|
|
|
|
722
|
|
|
$sth = $pdo->prepare("SELECT label_cache FROM |
723
|
|
|
ttrss_user_entries WHERE ref_id = ? AND owner_uid = ?"); |
724
|
|
|
$sth->execute([$id, $owner_uid]); |
725
|
|
|
|
726
|
|
|
if ($row = $sth->fetch()) { |
727
|
|
|
$label_cache = $row["label_cache"]; |
728
|
|
|
|
729
|
|
|
if ($label_cache) { |
730
|
|
|
$tmp = json_decode($label_cache, true); |
731
|
|
|
|
732
|
|
|
if (!$tmp || $tmp["no-labels"] == 1) { |
733
|
|
|
return $rv; |
734
|
|
|
} else { |
735
|
|
|
return $tmp; |
736
|
|
|
} |
737
|
|
|
} |
738
|
|
|
} |
739
|
|
|
|
740
|
|
|
$sth = $pdo->prepare("SELECT DISTINCT label_id,caption,fg_color,bg_color |
741
|
|
|
FROM ttrss_labels2, ttrss_user_labels2 |
742
|
|
|
WHERE id = label_id |
743
|
|
|
AND article_id = ? |
744
|
|
|
AND owner_uid = ? |
745
|
|
|
ORDER BY caption"); |
746
|
|
|
$sth->execute([$id, $owner_uid]); |
747
|
|
|
|
748
|
|
|
while ($line = $sth->fetch()) { |
749
|
|
|
$rk = array(Labels::label_to_feed_id($line["label_id"]), |
750
|
|
|
$line["caption"], $line["fg_color"], |
751
|
|
|
$line["bg_color"]); |
752
|
|
|
array_push($rv, $rk); |
753
|
|
|
} |
754
|
|
|
|
755
|
|
|
if (count($rv) > 0) { |
756
|
|
|
Labels::update_cache($owner_uid, $id, $rv); |
757
|
|
|
} else { |
758
|
|
|
Labels::update_cache($owner_uid, $id, array("no-labels" => 1)); |
759
|
|
|
} |
760
|
|
|
|
761
|
|
|
return $rv; |
762
|
|
|
} |
763
|
|
|
|
764
|
|
|
public static function get_article_image($enclosures, $content, $site_url) { |
765
|
|
|
|
766
|
|
|
$article_image = ""; |
767
|
|
|
$article_stream = ""; |
768
|
|
|
|
769
|
|
|
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_IMAGE) as $p) { |
770
|
|
|
list ($article_image, $article_stream, $content) = $p->hook_article_image($enclosures, $content, $site_url); |
771
|
|
|
} |
772
|
|
|
|
773
|
|
|
if (!$article_image && !$article_stream) { |
774
|
|
|
$tmpdoc = new DOMDocument(); |
775
|
|
|
|
776
|
|
|
if (@$tmpdoc->loadHTML('<?xml encoding="UTF-8">'.mb_substr($content, 0, 131070))) { |
777
|
|
|
$tmpxpath = new DOMXPath($tmpdoc); |
778
|
|
|
$elems = $tmpxpath->query('(//img[@src]|//video[@poster]|//iframe[contains(@src , "youtube.com/embed/")])'); |
779
|
|
|
|
780
|
|
|
foreach ($elems as $e) { |
781
|
|
|
if ($e->nodeName == "iframe") { |
782
|
|
|
$matches = []; |
783
|
|
|
if (preg_match("/\/embed\/([\w-]+)/", $e->getAttribute("src"), $matches)) { |
784
|
|
|
$article_image = "https://img.youtube.com/vi/".$matches[1]."/hqdefault.jpg"; |
785
|
|
|
$article_stream = "https://youtu.be/".$matches[1]; |
786
|
|
|
break; |
787
|
|
|
} |
788
|
|
|
} else if ($e->nodeName == "video") { |
789
|
|
|
$article_image = $e->getAttribute("poster"); |
790
|
|
|
|
791
|
|
|
$src = $tmpxpath->query("//source[@src]", $e)->item(0); |
792
|
|
|
|
793
|
|
|
if ($src) { |
794
|
|
|
$article_stream = $src->getAttribute("src"); |
795
|
|
|
} |
796
|
|
|
|
797
|
|
|
break; |
798
|
|
|
} else if ($e->nodeName == 'img') { |
799
|
|
|
if (mb_strpos($e->getAttribute("src"), "data:") !== 0) { |
800
|
|
|
$article_image = $e->getAttribute("src"); |
801
|
|
|
} |
802
|
|
|
break; |
803
|
|
|
} |
804
|
|
|
} |
805
|
|
|
} |
806
|
|
|
|
807
|
|
|
if (!$article_image) { |
808
|
|
|
foreach ($enclosures as $enc) { |
809
|
|
|
if (strpos($enc["content_type"], "image/") !== false) { |
810
|
|
|
$article_image = $enc["content_url"]; |
811
|
|
|
} |
812
|
|
|
break; |
813
|
|
|
} |
814
|
|
|
} |
815
|
|
|
|
816
|
|
|
if ($article_image) { |
817
|
|
|
$article_image = rewrite_relative_url($site_url, $article_image); |
818
|
|
|
} |
819
|
|
|
|
820
|
|
|
if ($article_stream) { |
821
|
|
|
$article_stream = rewrite_relative_url($site_url, $article_stream); |
822
|
|
|
} |
823
|
|
|
} |
824
|
|
|
|
825
|
|
|
$cache = new DiskCache("images"); |
826
|
|
|
|
827
|
|
|
if ($article_image && $cache->exists(sha1($article_image))) { |
828
|
|
|
$article_image = $cache->getUrl(sha1($article_image)); |
829
|
|
|
} |
830
|
|
|
|
831
|
|
|
if ($article_stream && $cache->exists(sha1($article_stream))) { |
832
|
|
|
$article_stream = $cache->getUrl(sha1($article_stream)); |
833
|
|
|
} |
834
|
|
|
|
835
|
|
|
return [$article_image, $article_stream]; |
836
|
|
|
} |
837
|
|
|
|
838
|
|
|
} |
839
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.