1
|
|
|
<?php |
2
|
|
|
require_once "colors.php"; |
3
|
|
|
|
4
|
|
|
class Feeds extends Handler_Protected { |
5
|
|
|
const NEVER_GROUP_FEEDS = [ -6, 0]; |
6
|
|
|
const NEVER_GROUP_BY_DATE = [ -2, -1, -3]; |
7
|
|
|
|
8
|
|
|
private $params; |
9
|
|
|
|
10
|
|
|
function csrf_ignore($method) { |
11
|
|
|
$csrf_ignored = array("index", "quickaddfeed", "search"); |
12
|
|
|
|
13
|
|
|
return array_search($method, $csrf_ignored) !== false; |
14
|
|
|
} |
15
|
|
|
|
16
|
|
|
private function format_headline_subtoolbar($feed_site_url, $feed_title, |
17
|
|
|
$feed_id, $is_cat, $search, |
18
|
|
|
$error, $feed_last_updated) { |
19
|
|
|
|
20
|
|
|
if ($is_cat) { |
21
|
|
|
$cat_q = "&is_cat=$is_cat"; |
22
|
|
|
} |
23
|
|
|
|
24
|
|
|
if ($search) { |
25
|
|
|
$search_q = "&q=$search"; |
26
|
|
|
} else { |
27
|
|
|
$search_q = ""; |
28
|
|
|
} |
29
|
|
|
|
30
|
|
|
$reply = ""; |
31
|
|
|
|
32
|
|
|
$rss_link = htmlspecialchars(get_self_url_prefix(). |
33
|
|
|
"/public.php?op=rss&id=$feed_id$cat_q$search_q"); |
|
|
|
|
34
|
|
|
|
35
|
|
|
$reply .= "<span class='left'>"; |
36
|
|
|
|
37
|
|
|
$reply .= "<a href=\"#\" |
38
|
|
|
title=\"".__("Show as feed")."\" |
39
|
|
|
onclick=\"App.displayDlg('".__("Show as feed")."','generatedFeed', '$feed_id:$is_cat:$rss_link')\"> |
40
|
|
|
<i class='icon-syndicate material-icons'>rss_feed</i></a>"; |
41
|
|
|
|
42
|
|
|
$reply .= "<span id='feed_title'>"; |
43
|
|
|
|
44
|
|
|
if ($feed_site_url) { |
45
|
|
|
$last_updated = T_sprintf("Last updated: %s", $feed_last_updated); |
46
|
|
|
|
47
|
|
|
$reply .= "<a title=\"$last_updated\" target='_blank' href=\"$feed_site_url\">". |
48
|
|
|
truncate_string(strip_tags($feed_title), 30)."</a>"; |
49
|
|
|
} else { |
50
|
|
|
$reply .= strip_tags($feed_title); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
if ($error) { |
54
|
|
|
$reply .= " <i title=\"".htmlspecialchars($error)."\" class='material-icons icon-error'>error</i>"; |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
$reply .= "</span>"; |
58
|
|
|
$reply .= "<span id='feed_current_unread' style='display: none'></span>"; |
59
|
|
|
$reply .= "</span>"; |
60
|
|
|
|
61
|
|
|
$reply .= "<span class=\"right\">"; |
62
|
|
|
$reply .= "<span id='selected_prompt'></span>"; |
63
|
|
|
$reply .= " "; |
64
|
|
|
$reply .= "<select dojoType=\"fox.form.Select\" |
65
|
|
|
onchange=\"Headlines.onActionChanged(this)\">"; |
66
|
|
|
|
67
|
|
|
$reply .= "<option value=\"0\" disabled='1'>".__('Select...')."</option>"; |
68
|
|
|
|
69
|
|
|
$reply .= "<option value=\"Headlines.select('all')\">".__('All')."</option>"; |
70
|
|
|
$reply .= "<option value=\"Headlines.select('unread')\">".__('Unread')."</option>"; |
71
|
|
|
$reply .= "<option value=\"Headlines.select('invert')\">".__('Invert')."</option>"; |
72
|
|
|
$reply .= "<option value=\"Headlines.select('none')\">".__('None')."</option>"; |
73
|
|
|
|
74
|
|
|
$reply .= "<option value=\"0\" disabled=\"1\">".__('Selection toggle:')."</option>"; |
75
|
|
|
|
76
|
|
|
$reply .= "<option value=\"Headlines.selectionToggleUnread()\">".__('Unread')."</option> |
77
|
|
|
<option value=\"Headlines.selectionToggleMarked()\">".__('Starred')."</option> |
78
|
|
|
<option value=\"Headlines.selectionTogglePublished()\">".__('Published')."</option>"; |
79
|
|
|
|
80
|
|
|
$reply .= "<option value=\"0\" disabled=\"1\">".__('Selection:')."</option>"; |
81
|
|
|
|
82
|
|
|
$reply .= "<option value=\"Headlines.catchupSelection()\">".__('Mark as read')."</option>"; |
83
|
|
|
$reply .= "<option value=\"Article.selectionSetScore()\">".__('Set score')."</option>"; |
84
|
|
|
|
85
|
|
|
if ($feed_id == 0 && !$is_cat) { |
86
|
|
|
$reply .= "<option value=\"Headlines.archiveSelection()\">".__('Move back')."</option>"; |
87
|
|
|
$reply .= "<option value=\"Headlines.deleteSelection()\">".__('Delete')."</option>"; |
88
|
|
|
} else { |
89
|
|
|
$reply .= "<option value=\"Headlines.archiveSelection()\">".__('Archive')."</option>"; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
if (PluginHost::getInstance()->get_plugin("mail")) { |
93
|
|
|
$reply .= "<option value=\"Plugins.Mail.send()\">".__('Forward by email'). |
94
|
|
|
"</option>"; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
if (PluginHost::getInstance()->get_plugin("mailto")) { |
98
|
|
|
$reply .= "<option value=\"Plugins.Mailto.send()\">".__('Forward by email'). |
99
|
|
|
"</option>"; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
$reply .= "<option value=\"0\" disabled=\"1\">".__('Feed:')."</option>"; |
103
|
|
|
|
104
|
|
|
//$reply .= "<option value=\"catchupPage()\">".__('Mark as read')."</option>"; |
105
|
|
|
|
106
|
|
|
$reply .= "<option value=\"App.displayDlg('".__("Show as feed")."','generatedFeed', '$feed_id:$is_cat:$rss_link')\">". |
107
|
|
|
__('Show as feed')."</option>"; |
108
|
|
|
|
109
|
|
|
$reply .= "</select>"; |
110
|
|
|
|
111
|
|
|
//$reply .= "</h2"; |
112
|
|
|
|
113
|
|
|
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HEADLINE_TOOLBAR_BUTTON) as $p) { |
114
|
|
|
$reply .= $p->hook_headline_toolbar_button($feed_id, $is_cat); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
$reply .= "</span>"; |
118
|
|
|
|
119
|
|
|
return $reply; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
private function format_headlines_list($feed, $method, $view_mode, $limit, $cat_view, |
123
|
|
|
$offset, $override_order = false, $include_children = false, $check_first_id = false, |
124
|
|
|
$skip_first_id_check = false, $order_by = false) { |
125
|
|
|
|
126
|
|
|
$disable_cache = false; |
127
|
|
|
|
128
|
|
|
$reply = array(); |
129
|
|
|
|
130
|
|
|
$rgba_cache = array(); |
131
|
|
|
$topmost_article_ids = array(); |
132
|
|
|
|
133
|
|
|
if (!$offset) { |
134
|
|
|
$offset = 0; |
135
|
|
|
} |
136
|
|
|
if ($method == "undefined") { |
137
|
|
|
$method = ""; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
$method_split = explode(":", $method); |
141
|
|
|
|
142
|
|
|
if ($method == "ForceUpdate" && $feed > 0 && is_numeric($feed)) { |
143
|
|
|
$sth = $this->pdo->prepare("UPDATE ttrss_feeds |
144
|
|
|
SET last_updated = '1970-01-01', last_update_started = '1970-01-01' |
145
|
|
|
WHERE id = ?"); |
146
|
|
|
$sth->execute([$feed]); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
if ($method_split[0] == "MarkAllReadGR") { |
150
|
|
|
$this->catchup_feed($method_split[1], false); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
// FIXME: might break tag display? |
154
|
|
|
|
155
|
|
|
if (is_numeric($feed) && $feed > 0 && !$cat_view) { |
156
|
|
|
$sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE id = ? LIMIT 1"); |
157
|
|
|
$sth->execute([$feed]); |
158
|
|
|
|
159
|
|
|
if (!$sth->fetch()) { |
160
|
|
|
$reply['content'] = "<div align='center'>".__('Feed not found.')."</div>"; |
161
|
|
|
} |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
@$search = $_REQUEST["query"]; |
165
|
|
|
@$search_language = $_REQUEST["search_language"]; // PGSQL only |
166
|
|
|
|
167
|
|
|
if ($search) { |
168
|
|
|
$disable_cache = true; |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
if (!$cat_view && is_numeric($feed) && $feed < PLUGIN_FEED_BASE_INDEX && $feed > LABEL_BASE_INDEX) { |
172
|
|
|
$handler = PluginHost::getInstance()->get_feed_handler( |
173
|
|
|
PluginHost::feed_to_pfeed_id($feed)); |
174
|
|
|
|
175
|
|
|
if ($handler) { |
176
|
|
|
$options = array( |
177
|
|
|
"limit" => $limit, |
178
|
|
|
"view_mode" => $view_mode, |
179
|
|
|
"cat_view" => $cat_view, |
180
|
|
|
"search" => $search, |
181
|
|
|
"override_order" => $override_order, |
182
|
|
|
"offset" => $offset, |
183
|
|
|
"owner_uid" => $_SESSION["uid"], |
184
|
|
|
"filter" => false, |
185
|
|
|
"since_id" => 0, |
186
|
|
|
"include_children" => $include_children, |
187
|
|
|
"order_by" => $order_by); |
188
|
|
|
|
189
|
|
|
$qfh_ret = $handler->get_headlines(PluginHost::feed_to_pfeed_id($feed), |
190
|
|
|
$options); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
} else { |
194
|
|
|
|
195
|
|
|
$params = array( |
196
|
|
|
"feed" => $feed, |
197
|
|
|
"limit" => $limit, |
198
|
|
|
"view_mode" => $view_mode, |
199
|
|
|
"cat_view" => $cat_view, |
200
|
|
|
"search" => $search, |
201
|
|
|
"search_language" => $search_language, |
202
|
|
|
"override_order" => $override_order, |
203
|
|
|
"offset" => $offset, |
204
|
|
|
"include_children" => $include_children, |
205
|
|
|
"check_first_id" => $check_first_id, |
206
|
|
|
"skip_first_id_check" => $skip_first_id_check, |
207
|
|
|
"order_by" => $order_by |
208
|
|
|
); |
209
|
|
|
|
210
|
|
|
$qfh_ret = $this->queryFeedHeadlines($params); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
$vfeed_group_enabled = get_pref("VFEED_GROUP_BY_FEED") && |
214
|
|
|
!(in_array($feed, Feeds::NEVER_GROUP_FEEDS) && !$cat_view); |
215
|
|
|
|
216
|
|
|
$result = $qfh_ret[0]; // this could be either a PDO query result or a -1 if first id changed |
|
|
|
|
217
|
|
|
$feed_title = $qfh_ret[1]; |
218
|
|
|
$feed_site_url = $qfh_ret[2]; |
219
|
|
|
$last_error = $qfh_ret[3]; |
220
|
|
|
$last_updated = strpos($qfh_ret[4], '1970-') === false ? |
221
|
|
|
make_local_datetime($qfh_ret[4], false) : __("Never"); |
222
|
|
|
$highlight_words = $qfh_ret[5]; |
223
|
|
|
$reply['first_id'] = $qfh_ret[6]; |
224
|
|
|
$reply['is_vfeed'] = $qfh_ret[7]; |
225
|
|
|
$query_error_override = $qfh_ret[8]; |
226
|
|
|
|
227
|
|
|
$reply['search_query'] = [$search, $search_language]; |
228
|
|
|
$reply['vfeed_group_enabled'] = $vfeed_group_enabled; |
229
|
|
|
|
230
|
|
|
$reply['toolbar'] = $this->format_headline_subtoolbar($feed_site_url, |
231
|
|
|
$feed_title, |
232
|
|
|
$feed, $cat_view, $search, |
233
|
|
|
$last_error, $last_updated); |
234
|
|
|
|
235
|
|
|
if ($offset == 0) { |
236
|
|
|
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HEADLINES_BEFORE) as $p) { |
237
|
|
|
$reply['content'] .= $p->hook_headlines_before($feed, $cat_view, $qfh_ret); |
238
|
|
|
} |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
$reply['content'] = []; |
242
|
|
|
|
243
|
|
|
$headlines_count = 0; |
244
|
|
|
|
245
|
|
|
if (is_object($result)) { |
246
|
|
|
while ($line = $result->fetch(PDO::FETCH_ASSOC)) { |
247
|
|
|
|
248
|
|
|
++$headlines_count; |
249
|
|
|
|
250
|
|
|
if (!get_pref('SHOW_CONTENT_PREVIEW')) { |
251
|
|
|
$line["content_preview"] = ""; |
252
|
|
|
} else { |
253
|
|
|
$line["content_preview"] = "— ".truncate_string(strip_tags($line["content"]), 250); |
254
|
|
|
|
255
|
|
|
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) { |
256
|
|
|
$line = $p->hook_query_headlines($line, 250, false); |
257
|
|
|
} |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
$id = $line["id"]; |
261
|
|
|
|
262
|
|
|
// frontend doesn't expect pdo returning booleans as strings on mysql |
263
|
|
|
if (DB_TYPE == "mysql") { |
|
|
|
|
264
|
|
|
foreach (["unread", "marked", "published"] as $k) { |
265
|
|
|
$line[$k] = $line[$k] === "1"; |
266
|
|
|
} |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
// normalize archived feed |
270
|
|
|
if ($line['feed_id'] === null) { |
271
|
|
|
$line['feed_id'] = 0; |
272
|
|
|
$line["feed_title"] = __("Archived articles"); |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
$feed_id = $line["feed_id"]; |
276
|
|
|
|
277
|
|
|
$label_cache = $line["label_cache"]; |
278
|
|
|
$labels = false; |
279
|
|
|
|
280
|
|
|
if ($label_cache) { |
281
|
|
|
$label_cache = json_decode($label_cache, true); |
282
|
|
|
|
283
|
|
|
if ($label_cache) { |
284
|
|
|
if ($label_cache["no-labels"] == 1) { |
285
|
|
|
$labels = array(); |
286
|
|
|
} else { |
287
|
|
|
$labels = $label_cache; |
288
|
|
|
} |
289
|
|
|
} |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
if (!is_array($labels)) { |
293
|
|
|
$labels = Article::get_article_labels($id); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
$labels_str = "<span class=\"HLLCTR-$id\">"; |
297
|
|
|
$labels_str .= Article::format_article_labels($labels); |
298
|
|
|
$labels_str .= "</span>"; |
299
|
|
|
|
300
|
|
|
$line["labels"] = $labels_str; |
301
|
|
|
|
302
|
|
|
if (count($topmost_article_ids) < 3) { |
303
|
|
|
array_push($topmost_article_ids, $id); |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
if (!$line["feed_title"]) { |
307
|
|
|
$line["feed_title"] = ""; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
$line["buttons_left"] = ""; |
311
|
|
|
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) { |
312
|
|
|
$line["buttons_left"] .= $p->hook_article_left_button($line); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
$line["buttons"] = ""; |
316
|
|
|
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) { |
317
|
|
|
$line["buttons"] .= $p->hook_article_button($line); |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
$line["content"] = sanitize($line["content"], |
321
|
|
|
$line['hide_images'], false, $line["site_url"], $highlight_words, $line["id"]); |
322
|
|
|
|
323
|
|
|
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_CDM) as $p) { |
324
|
|
|
$line = $p->hook_render_article_cdm($line); |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
$line['content'] = DiskCache::rewriteUrls($line['content']); |
328
|
|
|
|
329
|
|
|
if ($line['note']) { |
330
|
|
|
$line['note'] = Article::format_article_note($id, $line['note']); |
331
|
|
|
} else { |
332
|
|
|
$line['note'] = ""; |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
if (!get_pref("CDM_EXPANDED")) { |
336
|
|
|
$line["cdm_excerpt"] = "<span class='collapse'> |
337
|
|
|
<i class='material-icons' onclick='return Article.cdmUnsetActive(event)' |
338
|
|
|
title=\"" . __("Collapse article")."\">remove_circle</i></span>"; |
339
|
|
|
|
340
|
|
|
if (get_pref('SHOW_CONTENT_PREVIEW')) { |
341
|
|
|
$line["cdm_excerpt"] .= "<span class='excerpt'>".$line["content_preview"]."</span>"; |
342
|
|
|
} |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
$line["enclosures"] = Article::format_article_enclosures($id, $line["always_display_enclosures"], |
346
|
|
|
$line["content"], $line["hide_images"]); |
347
|
|
|
|
348
|
|
|
if ($line["orig_feed_id"]) { |
349
|
|
|
|
350
|
|
|
$ofgh = $this->pdo->prepare("SELECT * FROM ttrss_archived_feeds |
351
|
|
|
WHERE id = ? AND owner_uid = ?"); |
352
|
|
|
$ofgh->execute([$line["orig_feed_id"], $_SESSION['uid']]); |
353
|
|
|
|
354
|
|
|
if ($tmp_line = $ofgh->fetch()) { |
355
|
|
|
$line["orig_feed"] = [$tmp_line["title"], $tmp_line["site_url"], $tmp_line["feed_url"]]; |
356
|
|
|
} |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
$line["updated_long"] = make_local_datetime($line["updated"], true); |
360
|
|
|
$line["updated"] = make_local_datetime($line["updated"], false, false, false, true); |
361
|
|
|
|
362
|
|
|
|
363
|
|
|
$line['imported'] = T_sprintf("Imported at %s", |
364
|
|
|
make_local_datetime($line["date_entered"], false)); |
365
|
|
|
|
366
|
|
|
if ($line["tag_cache"]) { |
367
|
|
|
$tags = explode(",", $line["tag_cache"]); |
368
|
|
|
} else { |
369
|
|
|
$tags = false; |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
$line["tags_str"] = Article::format_tags_string($tags); |
373
|
|
|
|
374
|
|
|
if (feeds::feedHasIcon($feed_id)) { |
375
|
|
|
$line['feed_icon'] = "<img class=\"icon\" src=\"".ICONS_URL."/$feed_id.ico\" alt=\"\">"; |
|
|
|
|
376
|
|
|
} else { |
377
|
|
|
$line['feed_icon'] = "<i class='icon-no-feed material-icons'>rss_feed</i>"; |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
//setting feed headline background color, needs to change text color based on dark/light |
381
|
|
|
$fav_color = $line['favicon_avg_color']; |
382
|
|
|
|
383
|
|
|
require_once "colors.php"; |
384
|
|
|
|
385
|
|
|
if (!isset($rgba_cache[$feed_id])) { |
386
|
|
|
if ($fav_color && $fav_color != 'fail') { |
387
|
|
|
$rgba_cache[$feed_id] = _color_unpack($fav_color); |
388
|
|
|
} else { |
389
|
|
|
$rgba_cache[$feed_id] = _color_unpack($this->color_of($line['feed_title'])); |
390
|
|
|
} |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
if (isset($rgba_cache[$feed_id])) { |
394
|
|
|
$line['feed_bg_color'] = 'rgba('.implode(",", $rgba_cache[$feed_id]).',0.3)'; |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
/* we don't need those */ |
398
|
|
|
|
399
|
|
|
foreach (["date_entered", "guid", "last_published", "last_marked", "tag_cache", "favicon_avg_color", |
400
|
|
|
"uuid", "label_cache", "yyiw"] as $k) { |
401
|
|
|
unset($line[$k]); |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
array_push($reply['content'], $line); |
405
|
|
|
} |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
if (!$headlines_count) { |
409
|
|
|
|
410
|
|
|
if (is_object($result)) { |
411
|
|
|
|
412
|
|
|
if ($query_error_override) { |
413
|
|
|
$message = $query_error_override; |
414
|
|
|
} else { |
415
|
|
|
switch ($view_mode) { |
416
|
|
|
case "unread": |
417
|
|
|
$message = __("No unread articles found to display."); |
418
|
|
|
break; |
419
|
|
|
case "updated": |
420
|
|
|
$message = __("No updated articles found to display."); |
421
|
|
|
break; |
422
|
|
|
case "marked": |
423
|
|
|
$message = __("No starred articles found to display."); |
424
|
|
|
break; |
425
|
|
|
default: |
426
|
|
|
if ($feed < LABEL_BASE_INDEX) { |
427
|
|
|
$message = __("No articles found to display. You can assign articles to labels manually from article header context menu (applies to all selected articles) or use a filter."); |
428
|
|
|
} else { |
429
|
|
|
$message = __("No articles found to display."); |
430
|
|
|
} |
431
|
|
|
} |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
if (!$offset && $message) { |
435
|
|
|
$reply['content'] = "<div class='whiteBox'>$message"; |
436
|
|
|
|
437
|
|
|
$reply['content'] .= "<p><span class=\"text-muted\">"; |
438
|
|
|
|
439
|
|
|
$sth = $this->pdo->prepare("SELECT ".SUBSTRING_FOR_DATE."(MAX(last_updated), 1, 19) AS last_updated FROM ttrss_feeds |
440
|
|
|
WHERE owner_uid = ?"); |
441
|
|
|
$sth->execute([$_SESSION['uid']]); |
442
|
|
|
$row = $sth->fetch(); |
443
|
|
|
|
444
|
|
|
$last_updated = make_local_datetime($row["last_updated"], false); |
445
|
|
|
|
446
|
|
|
$reply['content'] .= sprintf(__("Feeds last updated at %s"), $last_updated); |
447
|
|
|
|
448
|
|
|
$sth = $this->pdo->prepare("SELECT COUNT(id) AS num_errors |
449
|
|
|
FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?"); |
450
|
|
|
$sth->execute([$_SESSION['uid']]); |
451
|
|
|
$row = $sth->fetch(); |
452
|
|
|
|
453
|
|
|
$num_errors = $row["num_errors"]; |
454
|
|
|
|
455
|
|
|
if ($num_errors > 0) { |
456
|
|
|
$reply['content'] .= "<br/>"; |
457
|
|
|
$reply['content'] .= "<a class=\"text-muted\" href=\"#\" onclick=\"CommonDialogs.showFeedsWithErrors()\">". |
458
|
|
|
__('Some feeds have update errors (click for details)')."</a>"; |
459
|
|
|
} |
460
|
|
|
$reply['content'] .= "</span></p></div>"; |
461
|
|
|
|
462
|
|
|
} |
463
|
|
|
} else if (is_numeric($result) && $result == -1) { |
464
|
|
|
$reply['first_id_changed'] = true; |
465
|
|
|
} |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
return array($topmost_article_ids, $headlines_count, $feed, $disable_cache, $reply); |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
public function catchupAll() { |
472
|
|
|
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET |
473
|
|
|
last_read = NOW(), unread = false WHERE unread = true AND owner_uid = ?"); |
474
|
|
|
$sth->execute([$_SESSION['uid']]); |
475
|
|
|
|
476
|
|
|
CCache::zero_all($_SESSION["uid"]); |
477
|
|
|
} |
478
|
|
|
|
479
|
|
|
public function view() { |
480
|
|
|
$reply = array(); |
481
|
|
|
|
482
|
|
|
$feed = $_REQUEST["feed"]; |
483
|
|
|
$method = $_REQUEST["m"]; |
484
|
|
|
$view_mode = $_REQUEST["view_mode"]; |
485
|
|
|
$limit = 30; |
486
|
|
|
@$cat_view = $_REQUEST["cat"] == "true"; |
487
|
|
|
@$next_unread_feed = $_REQUEST["nuf"]; |
488
|
|
|
@$offset = $_REQUEST["skip"]; |
489
|
|
|
$order_by = $_REQUEST["order_by"]; |
490
|
|
|
$check_first_id = $_REQUEST["fid"]; |
491
|
|
|
|
492
|
|
|
if (is_numeric($feed)) { |
493
|
|
|
$feed = (int) $feed; |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
/* Feed -5 is a special case: it is used to display auxiliary information |
497
|
|
|
* when there's nothing to load - e.g. no stuff in fresh feed */ |
498
|
|
|
|
499
|
|
|
if ($feed == -5) { |
500
|
|
|
print json_encode($this->generate_dashboard_feed()); |
501
|
|
|
return; |
502
|
|
|
} |
503
|
|
|
|
504
|
|
|
$sth = false; |
505
|
|
|
if ($feed < LABEL_BASE_INDEX) { |
506
|
|
|
|
507
|
|
|
$label_feed = Labels::feed_to_label_id($feed); |
508
|
|
|
|
509
|
|
|
$sth = $this->pdo->prepare("SELECT id FROM ttrss_labels2 WHERE |
510
|
|
|
id = ? AND owner_uid = ?"); |
511
|
|
|
$sth->execute([$label_feed, $_SESSION['uid']]); |
512
|
|
|
|
513
|
|
|
} else if (!$cat_view && is_numeric($feed) && $feed > 0) { |
514
|
|
|
|
515
|
|
|
$sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE |
516
|
|
|
id = ? AND owner_uid = ?"); |
517
|
|
|
$sth->execute([$feed, $_SESSION['uid']]); |
518
|
|
|
|
519
|
|
|
} else if ($cat_view && is_numeric($feed) && $feed > 0) { |
520
|
|
|
|
521
|
|
|
$sth = $this->pdo->prepare("SELECT id FROM ttrss_feed_categories WHERE |
522
|
|
|
id = ? AND owner_uid = ?"); |
523
|
|
|
|
524
|
|
|
$sth->execute([$feed, $_SESSION['uid']]); |
525
|
|
|
} |
526
|
|
|
|
527
|
|
|
if ($sth && !$sth->fetch()) { |
528
|
|
|
print json_encode($this->generate_error_feed(__("Feed not found."))); |
529
|
|
|
return; |
530
|
|
|
} |
531
|
|
|
|
532
|
|
|
/* Updating a label ccache means recalculating all of the caches |
533
|
|
|
* so for performance reasons we don't do that here */ |
534
|
|
|
|
535
|
|
|
if ($feed >= 0) { |
536
|
|
|
CCache::update($feed, $_SESSION["uid"], $cat_view); |
537
|
|
|
} |
538
|
|
|
|
539
|
|
|
set_pref("_DEFAULT_VIEW_MODE", $view_mode); |
540
|
|
|
set_pref("_DEFAULT_VIEW_ORDER_BY", $order_by); |
541
|
|
|
|
542
|
|
|
/* bump login timestamp if needed */ |
543
|
|
|
if (time() - $_SESSION["last_login_update"] > 3600) { |
544
|
|
|
$sth = $this->pdo->prepare("UPDATE ttrss_users SET last_login = NOW() WHERE id = ?"); |
545
|
|
|
$sth->execute([$_SESSION['uid']]); |
546
|
|
|
|
547
|
|
|
$_SESSION["last_login_update"] = time(); |
548
|
|
|
} |
549
|
|
|
|
550
|
|
|
if (!$cat_view && is_numeric($feed) && $feed > 0) { |
551
|
|
|
$sth = $this->pdo->prepare("UPDATE ttrss_feeds SET last_viewed = NOW() |
552
|
|
|
WHERE id = ? AND owner_uid = ?"); |
553
|
|
|
$sth->execute([$feed, $_SESSION['uid']]); |
554
|
|
|
} |
555
|
|
|
|
556
|
|
|
$reply['headlines'] = []; |
557
|
|
|
|
558
|
|
|
$override_order = false; |
559
|
|
|
$skip_first_id_check = false; |
560
|
|
|
|
561
|
|
|
switch ($order_by) { |
562
|
|
|
case "title": |
563
|
|
|
$override_order = "ttrss_entries.title, date_entered, updated"; |
564
|
|
|
break; |
565
|
|
|
case "date_reverse": |
566
|
|
|
$override_order = "score DESC, date_entered, updated"; |
567
|
|
|
$skip_first_id_check = true; |
568
|
|
|
break; |
569
|
|
|
case "feed_dates": |
570
|
|
|
$override_order = "updated DESC"; |
571
|
|
|
break; |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
$ret = $this->format_headlines_list($feed, $method, |
575
|
|
|
$view_mode, $limit, $cat_view, $offset, |
576
|
|
|
$override_order, true, $check_first_id, $skip_first_id_check, $order_by); |
577
|
|
|
|
578
|
|
|
$headlines_count = $ret[1]; |
579
|
|
|
$disable_cache = $ret[3]; |
580
|
|
|
$reply['headlines'] = $ret[4]; |
581
|
|
|
|
582
|
|
|
if (!$next_unread_feed) { |
583
|
|
|
$reply['headlines']['id'] = $feed; |
584
|
|
|
} else { |
585
|
|
|
$reply['headlines']['id'] = $next_unread_feed; |
586
|
|
|
} |
587
|
|
|
|
588
|
|
|
$reply['headlines']['is_cat'] = (bool) $cat_view; |
589
|
|
|
|
590
|
|
|
$reply['headlines-info'] = ["count" => (int) $headlines_count, |
591
|
|
|
"disable_cache" => (bool) $disable_cache]; |
592
|
|
|
|
593
|
|
|
// this is parsed by handleRpcJson() on first viewfeed() to set cdm expanded, etc |
594
|
|
|
$reply['runtime-info'] = make_runtime_info(); |
595
|
|
|
|
596
|
|
|
$reply_json = json_encode($reply); |
597
|
|
|
|
598
|
|
|
if (!$reply_json) { |
599
|
|
|
$reply_json = json_encode(["error" => ["code" => 15, |
600
|
|
|
"message" => json_last_error_msg()]]); |
601
|
|
|
} |
602
|
|
|
|
603
|
|
|
print $reply_json; |
604
|
|
|
|
605
|
|
|
} |
606
|
|
|
|
607
|
|
|
private function generate_dashboard_feed() { |
608
|
|
|
$reply = array(); |
609
|
|
|
|
610
|
|
|
$reply['headlines']['id'] = -5; |
611
|
|
|
$reply['headlines']['is_cat'] = false; |
612
|
|
|
|
613
|
|
|
$reply['headlines']['toolbar'] = ''; |
614
|
|
|
|
615
|
|
|
$reply['headlines']['content'] = "<div class='whiteBox'>".__('No feed selected.'); |
616
|
|
|
|
617
|
|
|
$reply['headlines']['content'] .= "<p><span class=\"text-muted\">"; |
618
|
|
|
|
619
|
|
|
$sth = $this->pdo->prepare("SELECT ".SUBSTRING_FOR_DATE."(MAX(last_updated), 1, 19) AS last_updated FROM ttrss_feeds |
620
|
|
|
WHERE owner_uid = ?"); |
621
|
|
|
$sth->execute([$_SESSION['uid']]); |
622
|
|
|
$row = $sth->fetch(); |
623
|
|
|
|
624
|
|
|
$last_updated = make_local_datetime($row["last_updated"], false); |
625
|
|
|
|
626
|
|
|
$reply['headlines']['content'] .= sprintf(__("Feeds last updated at %s"), $last_updated); |
627
|
|
|
|
628
|
|
|
$sth = $this->pdo->prepare("SELECT COUNT(id) AS num_errors |
629
|
|
|
FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?"); |
630
|
|
|
$sth->execute([$_SESSION['uid']]); |
631
|
|
|
$row = $sth->fetch(); |
632
|
|
|
|
633
|
|
|
$num_errors = $row["num_errors"]; |
634
|
|
|
|
635
|
|
|
if ($num_errors > 0) { |
636
|
|
|
$reply['headlines']['content'] .= "<br/>"; |
637
|
|
|
$reply['headlines']['content'] .= "<a class=\"text-muted\" href=\"#\" onclick=\"CommonDialogs.showFeedsWithErrors()\">". |
638
|
|
|
__('Some feeds have update errors (click for details)')."</a>"; |
639
|
|
|
} |
640
|
|
|
$reply['headlines']['content'] .= "</span></p>"; |
641
|
|
|
|
642
|
|
|
$reply['headlines-info'] = array("count" => 0, |
643
|
|
|
"unread" => 0, |
644
|
|
|
"disable_cache" => true); |
645
|
|
|
|
646
|
|
|
return $reply; |
647
|
|
|
} |
648
|
|
|
|
649
|
|
|
private function generate_error_feed($error) { |
650
|
|
|
$reply = array(); |
651
|
|
|
|
652
|
|
|
$reply['headlines']['id'] = -7; |
653
|
|
|
$reply['headlines']['is_cat'] = false; |
654
|
|
|
|
655
|
|
|
$reply['headlines']['toolbar'] = ''; |
656
|
|
|
$reply['headlines']['content'] = "<div class='whiteBox'>".$error."</div>"; |
657
|
|
|
|
658
|
|
|
$reply['headlines-info'] = array("count" => 0, |
659
|
|
|
"unread" => 0, |
660
|
|
|
"disable_cache" => true); |
661
|
|
|
|
662
|
|
|
return $reply; |
663
|
|
|
} |
664
|
|
|
|
665
|
|
|
public function quickAddFeed() { |
666
|
|
|
print "<form onsubmit='return false'>"; |
667
|
|
|
|
668
|
|
|
print_hidden("op", "rpc"); |
669
|
|
|
print_hidden("method", "addfeed"); |
670
|
|
|
|
671
|
|
|
print "<div id='fadd_error_message' style='display : none' class='alert alert-danger'></div>"; |
672
|
|
|
|
673
|
|
|
print "<div id='fadd_multiple_notify' style='display : none'>"; |
674
|
|
|
print_notice("Provided URL is a HTML page referencing multiple feeds, please select required feed from the dropdown menu below."); |
|
|
|
|
675
|
|
|
print "<p></div>"; |
676
|
|
|
|
677
|
|
|
print "<section>"; |
678
|
|
|
|
679
|
|
|
print "<fieldset>"; |
680
|
|
|
print "<div style='float : right'><img style='display : none' id='feed_add_spinner' src='images/indicator_white.gif'></div>"; |
681
|
|
|
print "<input style='font-size : 16px; width : 500px;' |
682
|
|
|
placeHolder=\"".__("Feed or site URL")."\" |
683
|
|
|
dojoType='dijit.form.ValidationTextBox' required='1' name='feed' id='feedDlg_feedUrl'>"; |
684
|
|
|
|
685
|
|
|
print "</fieldset>"; |
686
|
|
|
|
687
|
|
|
print "<fieldset>"; |
688
|
|
|
|
689
|
|
|
if (get_pref('ENABLE_FEED_CATS')) { |
690
|
|
|
print "<label class='inline'>".__('Place in category:')."</label> "; |
691
|
|
|
print_feed_cat_select("cat", false, 'dojoType="fox.form.Select"'); |
692
|
|
|
} |
693
|
|
|
|
694
|
|
|
print "</fieldset>"; |
695
|
|
|
|
696
|
|
|
print "</section>"; |
697
|
|
|
|
698
|
|
|
print '<div id="feedDlg_feedsContainer" style="display : none"> |
699
|
|
|
<header>' . __('Available feeds').'</header> |
700
|
|
|
<section> |
701
|
|
|
<fieldset> |
702
|
|
|
<select id="feedDlg_feedContainerSelect" |
703
|
|
|
dojoType="fox.form.Select" size="3"> |
704
|
|
|
<script type="dojo/method" event="onChange" args="value"> |
705
|
|
|
dijit.byId("feedDlg_feedUrl").attr("value", value); |
706
|
|
|
</script> |
707
|
|
|
</select> |
708
|
|
|
</fieldset> |
709
|
|
|
</section> |
710
|
|
|
</div>'; |
711
|
|
|
|
712
|
|
|
print "<div id='feedDlg_loginContainer' style='display : none'> |
713
|
|
|
<section> |
714
|
|
|
<fieldset> |
715
|
|
|
<input dojoType=\"dijit.form.TextBox\" name='login'\" |
716
|
|
|
placeHolder=\"".__("Login")."\" |
717
|
|
|
autocomplete=\"new-password\" |
718
|
|
|
style=\"width : 10em;\"> |
719
|
|
|
<input |
720
|
|
|
placeHolder=\"".__("Password")."\" |
721
|
|
|
dojoType=\"dijit.form.TextBox\" type='password' |
722
|
|
|
autocomplete=\"new-password\" |
723
|
|
|
style=\"width : 10em;\" name='pass'\"> |
724
|
|
|
</fieldset> |
725
|
|
|
</section> |
726
|
|
|
</div>"; |
727
|
|
|
|
728
|
|
|
print "<section>"; |
729
|
|
|
print "<label> |
730
|
|
|
<label class='checkbox'><input type='checkbox' name='need_auth' dojoType='dijit.form.CheckBox' id='feedDlg_loginCheck' |
731
|
|
|
onclick='displayIfChecked(this, \"feedDlg_loginContainer\")'> |
732
|
|
|
".__('This feed requires authentication.')."</label>"; |
733
|
|
|
print "</section>"; |
734
|
|
|
|
735
|
|
|
print "<footer>"; |
736
|
|
|
print "<button dojoType='dijit.form.Button' class='alt-primary' type='submit' |
737
|
|
|
onclick=\"return dijit.byId('feedAddDlg').execute()\">".__('Subscribe')."</button>"; |
738
|
|
|
|
739
|
|
|
print "<button dojoType='dijit.form.Button' onclick=\"return dijit.byId('feedAddDlg').hide()\">".__('Cancel')."</button>"; |
740
|
|
|
print "</footer>"; |
741
|
|
|
|
742
|
|
|
print "</form>"; |
743
|
|
|
} |
744
|
|
|
|
745
|
|
|
public function search() { |
746
|
|
|
$this->params = explode(":", $_REQUEST["param"], 2); |
747
|
|
|
|
748
|
|
|
$active_feed_id = sprintf("%d", $this->params[0]); |
749
|
|
|
$is_cat = $this->params[1] != "false"; |
750
|
|
|
|
751
|
|
|
print "<form onsubmit='return false;'>"; |
752
|
|
|
|
753
|
|
|
print "<section>"; |
754
|
|
|
|
755
|
|
|
print "<fieldset>"; |
756
|
|
|
print "<input dojoType='dijit.form.ValidationTextBox' id='search_query' |
757
|
|
|
style='font-size : 16px; width : 540px;' |
758
|
|
|
placeHolder=\"".T_sprintf("Search %s...", $this->getFeedTitle($active_feed_id, $is_cat))."\" |
759
|
|
|
name='query' type='search' value=''>"; |
760
|
|
|
print "</fieldset>"; |
761
|
|
|
|
762
|
|
|
if (DB_TYPE == "pgsql") { |
|
|
|
|
763
|
|
|
print "<fieldset>"; |
764
|
|
|
print "<label class='inline'>".__("Language:")."</label>"; |
765
|
|
|
print_select("search_language", get_pref('DEFAULT_SEARCH_LANGUAGE'), Pref_Feeds::get_ts_languages(), |
766
|
|
|
"dojoType='fox.form.Select' title=\"".__('Used for word stemming')."\""); |
767
|
|
|
print "</fieldset>"; |
768
|
|
|
} |
769
|
|
|
|
770
|
|
|
print "</section>"; |
771
|
|
|
|
772
|
|
|
print "<footer>"; |
773
|
|
|
|
774
|
|
|
if (count(PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH)) == 0) { |
775
|
|
|
print "<button dojoType='dijit.form.Button' style='float : left' class='alt-info' onclick='window.open(\"https://tt-rss.org/wiki/SearchSyntax\")'> |
776
|
|
|
<i class='material-icons'>help</i> ".__("Search syntax")."</button>"; |
777
|
|
|
} |
778
|
|
|
|
779
|
|
|
print "<button dojoType='dijit.form.Button' type='submit' class='alt-primary' onclick=\"dijit.byId('searchDlg').execute()\">".__('Search')."</button> |
780
|
|
|
<button dojoType='dijit.form.Button' onclick=\"dijit.byId('searchDlg').hide()\">".__('Cancel')."</button>"; |
781
|
|
|
|
782
|
|
|
print "</footer>"; |
783
|
|
|
|
784
|
|
|
print "</form>"; |
785
|
|
|
} |
786
|
|
|
|
787
|
|
|
public function update_debugger() { |
788
|
|
|
header("Content-type: text/html"); |
789
|
|
|
|
790
|
|
|
Debug::set_enabled(true); |
791
|
|
|
Debug::set_loglevel($_REQUEST["xdebug"]); |
792
|
|
|
|
793
|
|
|
$feed_id = (int) $_REQUEST["feed_id"]; |
794
|
|
|
@$do_update = $_REQUEST["action"] == "do_update"; |
795
|
|
|
$csrf_token = $_REQUEST["csrf_token"]; |
796
|
|
|
|
797
|
|
|
$sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE id = ? AND owner_uid = ?"); |
798
|
|
|
$sth->execute([$feed_id, $_SESSION['uid']]); |
799
|
|
|
|
800
|
|
|
if (!$sth->fetch()) { |
801
|
|
|
print "Access denied."; |
802
|
|
|
return; |
803
|
|
|
} |
804
|
|
|
|
805
|
|
|
$refetch_checked = isset($_REQUEST["force_refetch"]) ? "checked" : ""; |
806
|
|
|
$rehash_checked = isset($_REQUEST["force_rehash"]) ? "checked" : ""; |
807
|
|
|
|
808
|
|
|
?> |
809
|
|
|
<!DOCTYPE html> |
810
|
|
|
<html> |
811
|
|
|
<head> |
812
|
|
|
<?php echo stylesheet_tag("css/default.css") ?> |
|
|
|
|
813
|
|
|
<title>Feed Debugger</title> |
814
|
|
|
<?php |
815
|
|
|
echo stylesheet_tag("css/default.css"); |
|
|
|
|
816
|
|
|
echo javascript_tag("lib/prototype.js"); |
|
|
|
|
817
|
|
|
echo javascript_tag("lib/dojo/dojo.js"); |
|
|
|
|
818
|
|
|
echo javascript_tag("lib/dojo/tt-rss-layer.js"); |
|
|
|
|
819
|
|
|
?> |
820
|
|
|
</head> |
821
|
|
|
<body class="flat ttrss_utility feed_debugger"> |
822
|
|
|
<script type="text/javascript"> |
823
|
|
|
require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox', 'dijit/form/Form', |
824
|
|
|
'dijit/form/Select','dijit/form/TextBox','dijit/form/ValidationTextBox'],function(parser, ready){ |
825
|
|
|
ready(function() { |
826
|
|
|
parser.parse(); |
827
|
|
|
}); |
828
|
|
|
}); |
829
|
|
|
</script> |
830
|
|
|
|
831
|
|
|
<div class="container"> |
832
|
|
|
<h1>Feed Debugger: <?php echo "$feed_id: ".$this->getFeedTitle($feed_id) ?></h1> |
833
|
|
|
<div class="content"> |
834
|
|
|
<form method="GET" action=""> |
835
|
|
|
<input type="hidden" name="op" value="feeds"> |
836
|
|
|
<input type="hidden" name="method" value="update_debugger"> |
837
|
|
|
<input type="hidden" name="xdebug" value="1"> |
838
|
|
|
<input type="hidden" name="csrf_token" value="<?php echo $csrf_token ?>"> |
839
|
|
|
<input type="hidden" name="action" value="do_update"> |
840
|
|
|
<input type="hidden" name="feed_id" value="<?php echo $feed_id ?>"> |
841
|
|
|
|
842
|
|
|
<fieldset class="narrow"> |
843
|
|
|
<label class="checkbox"><input dojoType="dijit.form.CheckBox" type="checkbox" name="force_refetch" value="1" <?php echo $refetch_checked ?>> Force refetch</label> |
844
|
|
|
</fieldset> |
845
|
|
|
|
846
|
|
|
<fieldset class="narrow"> |
847
|
|
|
<label class="checkbox"><input dojoType="dijit.form.CheckBox" type="checkbox" name="force_rehash" value="1" <?php echo $rehash_checked ?>> Force rehash</label> |
848
|
|
|
</fieldset> |
849
|
|
|
|
850
|
|
|
<button type="submit" dojoType="dijit.form.Button" class="alt-primary">Continue</button> |
851
|
|
|
</form> |
852
|
|
|
|
853
|
|
|
<hr> |
854
|
|
|
|
855
|
|
|
<pre><?php |
856
|
|
|
|
857
|
|
|
if ($do_update) { |
858
|
|
|
RSSUtils::update_rss_feed($feed_id, true); |
859
|
|
|
} |
860
|
|
|
|
861
|
|
|
?></pre> |
862
|
|
|
</div> |
863
|
|
|
</div> |
864
|
|
|
</body> |
865
|
|
|
</html> |
866
|
|
|
<?php |
867
|
|
|
|
868
|
|
|
} |
869
|
|
|
|
870
|
|
|
public static function catchup_feed($feed, $cat_view, $owner_uid = false, $mode = 'all', $search = false) { |
871
|
|
|
|
872
|
|
|
if (!$owner_uid) { |
873
|
|
|
$owner_uid = $_SESSION['uid']; |
874
|
|
|
} |
875
|
|
|
|
876
|
|
|
$pdo = Db::pdo(); |
877
|
|
|
|
878
|
|
|
if (is_array($search) && $search[0]) { |
879
|
|
|
$search_qpart = ""; |
880
|
|
|
|
881
|
|
|
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH) as $plugin) { |
882
|
|
|
list($search_qpart, $search_words) = $plugin->hook_search($search[0]); |
883
|
|
|
break; |
884
|
|
|
} |
885
|
|
|
|
886
|
|
|
// fall back in case of no plugins |
887
|
|
|
if (!$search_qpart) { |
888
|
|
|
list($search_qpart, $search_words) = Feeds::search_to_sql($search[0], $search[1]); |
889
|
|
|
} |
890
|
|
|
} else { |
891
|
|
|
$search_qpart = "true"; |
892
|
|
|
} |
893
|
|
|
|
894
|
|
|
// TODO: all this interval stuff needs some generic generator function |
895
|
|
|
|
896
|
|
|
switch ($mode) { |
897
|
|
|
case "1day": |
898
|
|
|
if (DB_TYPE == "pgsql") { |
|
|
|
|
899
|
|
|
$date_qpart = "date_entered < NOW() - INTERVAL '1 day' "; |
900
|
|
|
} else { |
901
|
|
|
$date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 1 DAY) "; |
902
|
|
|
} |
903
|
|
|
break; |
904
|
|
|
case "1week": |
905
|
|
|
if (DB_TYPE == "pgsql") { |
906
|
|
|
$date_qpart = "date_entered < NOW() - INTERVAL '1 week' "; |
907
|
|
|
} else { |
908
|
|
|
$date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 1 WEEK) "; |
909
|
|
|
} |
910
|
|
|
break; |
911
|
|
|
case "2week": |
912
|
|
|
if (DB_TYPE == "pgsql") { |
913
|
|
|
$date_qpart = "date_entered < NOW() - INTERVAL '2 week' "; |
914
|
|
|
} else { |
915
|
|
|
$date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 2 WEEK) "; |
916
|
|
|
} |
917
|
|
|
break; |
918
|
|
|
default: |
919
|
|
|
$date_qpart = "true"; |
920
|
|
|
} |
921
|
|
|
|
922
|
|
|
if (is_numeric($feed)) { |
923
|
|
|
if ($cat_view) { |
924
|
|
|
|
925
|
|
|
if ($feed >= 0) { |
926
|
|
|
|
927
|
|
|
if ($feed > 0) { |
928
|
|
|
$children = Feeds::getChildCategories($feed, $owner_uid); |
929
|
|
|
array_push($children, $feed); |
930
|
|
|
$children = array_map("intval", $children); |
931
|
|
|
|
932
|
|
|
$children = join(",", $children); |
933
|
|
|
|
934
|
|
|
$cat_qpart = "cat_id IN ($children)"; |
935
|
|
|
} else { |
936
|
|
|
$cat_qpart = "cat_id IS NULL"; |
937
|
|
|
} |
938
|
|
|
|
939
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_user_entries |
940
|
|
|
SET unread = false, last_read = NOW() WHERE ref_id IN |
941
|
|
|
(SELECT id FROM |
942
|
|
|
(SELECT DISTINCT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id |
943
|
|
|
AND owner_uid = ? AND unread = true AND feed_id IN |
944
|
|
|
(SELECT id FROM ttrss_feeds WHERE $cat_qpart) AND $date_qpart AND $search_qpart) as tmp)"); |
945
|
|
|
$sth->execute([$owner_uid]); |
946
|
|
|
|
947
|
|
|
} else if ($feed == -2) { |
948
|
|
|
|
949
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_user_entries |
950
|
|
|
SET unread = false,last_read = NOW() WHERE (SELECT COUNT(*) |
951
|
|
|
FROM ttrss_user_labels2, ttrss_entries WHERE article_id = ref_id AND id = ref_id AND $date_qpart AND $search_qpart) > 0 |
952
|
|
|
AND unread = true AND owner_uid = ?"); |
953
|
|
|
$sth->execute([$owner_uid]); |
954
|
|
|
} |
955
|
|
|
|
956
|
|
|
} else if ($feed > 0) { |
957
|
|
|
|
958
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_user_entries |
959
|
|
|
SET unread = false, last_read = NOW() WHERE ref_id IN |
960
|
|
|
(SELECT id FROM |
961
|
|
|
(SELECT DISTINCT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id |
962
|
|
|
AND owner_uid = ? AND unread = true AND feed_id = ? AND $date_qpart AND $search_qpart) as tmp)"); |
963
|
|
|
$sth->execute([$owner_uid, $feed]); |
964
|
|
|
|
965
|
|
|
} else if ($feed < 0 && $feed > LABEL_BASE_INDEX) { // special, like starred |
966
|
|
|
|
967
|
|
|
if ($feed == -1) { |
968
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_user_entries |
969
|
|
|
SET unread = false, last_read = NOW() WHERE ref_id IN |
970
|
|
|
(SELECT id FROM |
971
|
|
|
(SELECT DISTINCT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id |
972
|
|
|
AND owner_uid = ? AND unread = true AND marked = true AND $date_qpart AND $search_qpart) as tmp)"); |
973
|
|
|
$sth->execute([$owner_uid]); |
974
|
|
|
} |
975
|
|
|
|
976
|
|
|
if ($feed == -2) { |
977
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_user_entries |
978
|
|
|
SET unread = false, last_read = NOW() WHERE ref_id IN |
979
|
|
|
(SELECT id FROM |
980
|
|
|
(SELECT DISTINCT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id |
981
|
|
|
AND owner_uid = ? AND unread = true AND published = true AND $date_qpart AND $search_qpart) as tmp)"); |
982
|
|
|
$sth->execute([$owner_uid]); |
983
|
|
|
} |
984
|
|
|
|
985
|
|
|
if ($feed == -3) { |
986
|
|
|
|
987
|
|
|
$intl = (int) get_pref("FRESH_ARTICLE_MAX_AGE"); |
988
|
|
|
|
989
|
|
|
if (DB_TYPE == "pgsql") { |
990
|
|
|
$match_part = "date_entered > NOW() - INTERVAL '$intl hour' "; |
991
|
|
|
} else { |
992
|
|
|
$match_part = "date_entered > DATE_SUB(NOW(), |
993
|
|
|
INTERVAL $intl HOUR) "; |
994
|
|
|
} |
995
|
|
|
|
996
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_user_entries |
997
|
|
|
SET unread = false, last_read = NOW() WHERE ref_id IN |
998
|
|
|
(SELECT id FROM |
999
|
|
|
(SELECT DISTINCT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id |
1000
|
|
|
AND owner_uid = ? AND score >= 0 AND unread = true AND $date_qpart AND $match_part AND $search_qpart) as tmp)"); |
1001
|
|
|
$sth->execute([$owner_uid]); |
1002
|
|
|
} |
1003
|
|
|
|
1004
|
|
|
if ($feed == -4) { |
1005
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_user_entries |
1006
|
|
|
SET unread = false, last_read = NOW() WHERE ref_id IN |
1007
|
|
|
(SELECT id FROM |
1008
|
|
|
(SELECT DISTINCT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id |
1009
|
|
|
AND owner_uid = ? AND unread = true AND $date_qpart AND $search_qpart) as tmp)"); |
1010
|
|
|
$sth->execute([$owner_uid]); |
1011
|
|
|
} |
1012
|
|
|
|
1013
|
|
|
} else if ($feed < LABEL_BASE_INDEX) { // label |
1014
|
|
|
|
1015
|
|
|
$label_id = Labels::feed_to_label_id($feed); |
1016
|
|
|
|
1017
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_user_entries |
1018
|
|
|
SET unread = false, last_read = NOW() WHERE ref_id IN |
1019
|
|
|
(SELECT id FROM |
1020
|
|
|
(SELECT DISTINCT ttrss_entries.id FROM ttrss_entries, ttrss_user_entries, ttrss_user_labels2 WHERE ref_id = id |
1021
|
|
|
AND label_id = ? AND ref_id = article_id |
1022
|
|
|
AND owner_uid = ? AND unread = true AND $date_qpart AND $search_qpart) as tmp)"); |
1023
|
|
|
$sth->execute([$label_id, $owner_uid]); |
1024
|
|
|
|
1025
|
|
|
} |
1026
|
|
|
|
1027
|
|
|
CCache::update($feed, $owner_uid, $cat_view); |
1028
|
|
|
|
1029
|
|
|
} else { // tag |
1030
|
|
|
$sth = $pdo->prepare("UPDATE ttrss_user_entries |
1031
|
|
|
SET unread = false, last_read = NOW() WHERE ref_id IN |
1032
|
|
|
(SELECT id FROM |
1033
|
|
|
(SELECT DISTINCT ttrss_entries.id FROM ttrss_entries, ttrss_user_entries, ttrss_tags WHERE ref_id = ttrss_entries.id |
1034
|
|
|
AND post_int_id = int_id AND tag_name = ? |
1035
|
|
|
AND ttrss_user_entries.owner_uid = ? AND unread = true AND $date_qpart AND $search_qpart) as tmp)"); |
1036
|
|
|
$sth->execute([$feed, $owner_uid]); |
1037
|
|
|
|
1038
|
|
|
} |
1039
|
|
|
} |
1040
|
|
|
|
1041
|
|
|
public static function getFeedArticles($feed, $is_cat = false, $unread_only = false, |
1042
|
|
|
$owner_uid = false) { |
1043
|
|
|
|
1044
|
|
|
$n_feed = (int) $feed; |
1045
|
|
|
$need_entries = false; |
1046
|
|
|
|
1047
|
|
|
$pdo = Db::pdo(); |
1048
|
|
|
|
1049
|
|
|
if (!$owner_uid) { |
1050
|
|
|
$owner_uid = $_SESSION["uid"]; |
1051
|
|
|
} |
1052
|
|
|
|
1053
|
|
|
if ($unread_only) { |
1054
|
|
|
$unread_qpart = "unread = true"; |
1055
|
|
|
} else { |
1056
|
|
|
$unread_qpart = "true"; |
1057
|
|
|
} |
1058
|
|
|
|
1059
|
|
|
$match_part = ""; |
1060
|
|
|
|
1061
|
|
|
if ($is_cat) { |
1062
|
|
|
return Feeds::getCategoryUnread($n_feed, $owner_uid); |
1063
|
|
|
} else if ($n_feed == -6) { |
1064
|
|
|
return 0; |
1065
|
|
|
} else if ($feed != "0" && $n_feed == 0) { |
1066
|
|
|
|
1067
|
|
|
$sth = $pdo->prepare("SELECT SUM((SELECT COUNT(int_id) |
1068
|
|
|
FROM ttrss_user_entries,ttrss_entries WHERE int_id = post_int_id |
1069
|
|
|
AND ref_id = id AND $unread_qpart)) AS count FROM ttrss_tags |
1070
|
|
|
WHERE owner_uid = ? AND tag_name = ?"); |
1071
|
|
|
|
1072
|
|
|
$sth->execute([$owner_uid, $feed]); |
1073
|
|
|
$row = $sth->fetch(); |
1074
|
|
|
|
1075
|
|
|
return $row["count"]; |
1076
|
|
|
|
1077
|
|
|
} else if ($n_feed == -1) { |
1078
|
|
|
$match_part = "marked = true"; |
1079
|
|
|
} else if ($n_feed == -2) { |
1080
|
|
|
$match_part = "published = true"; |
1081
|
|
|
} else if ($n_feed == -3) { |
1082
|
|
|
$match_part = "unread = true AND score >= 0"; |
1083
|
|
|
|
1084
|
|
|
$intl = (int) get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid); |
1085
|
|
|
|
1086
|
|
|
if (DB_TYPE == "pgsql") { |
|
|
|
|
1087
|
|
|
$match_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' "; |
1088
|
|
|
} else { |
1089
|
|
|
$match_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) "; |
1090
|
|
|
} |
1091
|
|
|
|
1092
|
|
|
$need_entries = true; |
1093
|
|
|
|
1094
|
|
|
} else if ($n_feed == -4) { |
1095
|
|
|
$match_part = "true"; |
1096
|
|
|
} else if ($n_feed >= 0) { |
1097
|
|
|
|
1098
|
|
|
if ($n_feed != 0) { |
1099
|
|
|
$match_part = "feed_id = ".(int) $n_feed; |
1100
|
|
|
} else { |
1101
|
|
|
$match_part = "feed_id IS NULL"; |
1102
|
|
|
} |
1103
|
|
|
|
1104
|
|
|
} else if ($feed < LABEL_BASE_INDEX) { |
1105
|
|
|
|
1106
|
|
|
$label_id = Labels::feed_to_label_id($feed); |
1107
|
|
|
|
1108
|
|
|
return Feeds::getLabelUnread($label_id, $owner_uid); |
1109
|
|
|
} |
1110
|
|
|
|
1111
|
|
|
if ($match_part) { |
1112
|
|
|
|
1113
|
|
|
if ($need_entries) { |
1114
|
|
|
$from_qpart = "ttrss_user_entries,ttrss_entries"; |
1115
|
|
|
$from_where = "ttrss_entries.id = ttrss_user_entries.ref_id AND"; |
1116
|
|
|
} else { |
1117
|
|
|
$from_qpart = "ttrss_user_entries"; |
1118
|
|
|
$from_where = ""; |
1119
|
|
|
} |
1120
|
|
|
|
1121
|
|
|
$sth = $pdo->prepare("SELECT count(int_id) AS unread |
1122
|
|
|
FROM $from_qpart WHERE |
1123
|
|
|
$unread_qpart AND $from_where ($match_part) AND ttrss_user_entries.owner_uid = ?"); |
1124
|
|
|
$sth->execute([$owner_uid]); |
1125
|
|
|
$row = $sth->fetch(); |
1126
|
|
|
|
1127
|
|
|
return $row["unread"]; |
1128
|
|
|
|
1129
|
|
|
} else { |
1130
|
|
|
|
1131
|
|
|
$sth = $pdo->prepare("SELECT COUNT(post_int_id) AS unread |
1132
|
|
|
FROM ttrss_tags,ttrss_user_entries,ttrss_entries |
1133
|
|
|
WHERE tag_name = ? AND post_int_id = int_id AND ref_id = ttrss_entries.id |
1134
|
|
|
AND $unread_qpart AND ttrss_tags.owner_uid = ,"); |
1135
|
|
|
|
1136
|
|
|
$sth->execute([$feed, $owner_uid]); |
1137
|
|
|
$row = $sth->fetch(); |
1138
|
|
|
|
1139
|
|
|
return $row["unread"]; |
1140
|
|
|
} |
1141
|
|
|
} |
1142
|
|
|
|
1143
|
|
|
/** |
1144
|
|
|
* @return array (code => Status code, message => error message if available) |
1145
|
|
|
* |
1146
|
|
|
* 0 - OK, Feed already exists |
1147
|
|
|
* 1 - OK, Feed added |
1148
|
|
|
* 2 - Invalid URL |
1149
|
|
|
* 3 - URL content is HTML, no feeds available |
1150
|
|
|
* 4 - URL content is HTML which contains multiple feeds. |
1151
|
|
|
* Here you should call extractfeedurls in rpc-backend |
1152
|
|
|
* to get all possible feeds. |
1153
|
|
|
* 5 - Couldn't download the URL content. |
1154
|
|
|
* 6 - Content is an invalid XML. |
1155
|
|
|
*/ |
1156
|
|
|
public static function subscribe_to_feed($url, $cat_id = 0, |
1157
|
|
|
$auth_login = '', $auth_pass = '') { |
1158
|
|
|
|
1159
|
|
|
global $fetch_last_error; |
1160
|
|
|
global $fetch_last_error_content; |
1161
|
|
|
global $fetch_last_content_type; |
1162
|
|
|
|
1163
|
|
|
$pdo = Db::pdo(); |
1164
|
|
|
|
1165
|
|
|
$url = Feeds::fix_url($url); |
1166
|
|
|
|
1167
|
|
|
if (!$url || !Feeds::validate_feed_url($url)) { |
1168
|
|
|
return array("code" => 2); |
1169
|
|
|
} |
1170
|
|
|
|
1171
|
|
|
$contents = @fetch_file_contents($url, false, $auth_login, $auth_pass); |
1172
|
|
|
|
1173
|
|
|
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SUBSCRIBE_FEED) as $plugin) { |
1174
|
|
|
$contents = $plugin->hook_subscribe_feed($contents, $url, $auth_login, $auth_pass); |
1175
|
|
|
} |
1176
|
|
|
|
1177
|
|
|
if (!$contents) { |
1178
|
|
|
if (preg_match("/cloudflare\.com/", $fetch_last_error_content)) { |
1179
|
|
|
$fetch_last_error .= " (feed behind Cloudflare)"; |
1180
|
|
|
} |
1181
|
|
|
|
1182
|
|
|
return array("code" => 5, "message" => $fetch_last_error); |
1183
|
|
|
} |
1184
|
|
|
|
1185
|
|
|
if (mb_strpos($fetch_last_content_type, "html") !== false && Feeds::is_html($contents)) { |
1186
|
|
|
$feedUrls = Feeds::get_feeds_from_html($url, $contents); |
1187
|
|
|
|
1188
|
|
|
if (count($feedUrls) == 0) { |
1189
|
|
|
return array("code" => 3); |
1190
|
|
|
} else if (count($feedUrls) > 1) { |
1191
|
|
|
return array("code" => 4, "feeds" => $feedUrls); |
1192
|
|
|
} |
1193
|
|
|
//use feed url as new URL |
1194
|
|
|
$url = key($feedUrls); |
1195
|
|
|
} |
1196
|
|
|
|
1197
|
|
|
if (!$cat_id) { |
1198
|
|
|
$cat_id = null; |
1199
|
|
|
} |
1200
|
|
|
|
1201
|
|
|
$sth = $pdo->prepare("SELECT id FROM ttrss_feeds |
1202
|
|
|
WHERE feed_url = ? AND owner_uid = ?"); |
1203
|
|
|
$sth->execute([$url, $_SESSION['uid']]); |
1204
|
|
|
|
1205
|
|
|
if ($row = $sth->fetch()) { |
1206
|
|
|
return array("code" => 0, "feed_id" => (int) $row["id"]); |
1207
|
|
|
} else { |
1208
|
|
|
$sth = $pdo->prepare( |
1209
|
|
|
"INSERT INTO ttrss_feeds |
1210
|
|
|
(owner_uid,feed_url,title,cat_id, auth_login,auth_pass,update_method,auth_pass_encrypted) |
1211
|
|
|
VALUES (?, ?, ?, ?, ?, ?, 0, false)"); |
1212
|
|
|
|
1213
|
|
|
$sth->execute([$_SESSION['uid'], $url, "[Unknown]", $cat_id, (string) $auth_login, (string) $auth_pass]); |
1214
|
|
|
|
1215
|
|
|
$sth = $pdo->prepare("SELECT id FROM ttrss_feeds WHERE feed_url = ? |
1216
|
|
|
AND owner_uid = ?"); |
1217
|
|
|
$sth->execute([$url, $_SESSION['uid']]); |
1218
|
|
|
$row = $sth->fetch(); |
1219
|
|
|
|
1220
|
|
|
$feed_id = $row["id"]; |
1221
|
|
|
|
1222
|
|
|
if ($feed_id) { |
1223
|
|
|
RSSUtils::set_basic_feed_info($feed_id); |
1224
|
|
|
} |
1225
|
|
|
|
1226
|
|
|
return array("code" => 1, "feed_id" => (int) $feed_id); |
1227
|
|
|
|
1228
|
|
|
} |
1229
|
|
|
} |
1230
|
|
|
|
1231
|
|
|
public static function getIconFile($feed_id) { |
1232
|
|
|
return ICONS_DIR."/$feed_id.ico"; |
|
|
|
|
1233
|
|
|
} |
1234
|
|
|
|
1235
|
|
|
public static function feedHasIcon($id) { |
1236
|
|
|
return is_file(ICONS_DIR."/$id.ico") && filesize(ICONS_DIR."/$id.ico") > 0; |
|
|
|
|
1237
|
|
|
} |
1238
|
|
|
|
1239
|
|
|
public static function getFeedIcon($id) { |
1240
|
|
|
switch ($id) { |
1241
|
|
|
case 0: |
1242
|
|
|
return "archive"; |
1243
|
|
|
case -1: |
1244
|
|
|
return "star"; |
1245
|
|
|
case -2: |
1246
|
|
|
return "rss_feed"; |
1247
|
|
|
case -3: |
1248
|
|
|
return "whatshot"; |
1249
|
|
|
case -4: |
1250
|
|
|
return "inbox"; |
1251
|
|
|
case -6: |
1252
|
|
|
return "restore"; |
1253
|
|
|
default: |
1254
|
|
|
if ($id < LABEL_BASE_INDEX) { |
1255
|
|
|
return "label"; |
1256
|
|
|
} else { |
1257
|
|
|
$icon = self::getIconFile($id); |
1258
|
|
|
|
1259
|
|
|
if ($icon && file_exists($icon)) { |
1260
|
|
|
return ICONS_URL."/".basename($icon)."?".filemtime($icon); |
|
|
|
|
1261
|
|
|
} |
1262
|
|
|
} |
1263
|
|
|
} |
1264
|
|
|
|
1265
|
|
|
return false; |
1266
|
|
|
} |
1267
|
|
|
|
1268
|
|
|
public static function getFeedTitle($id, $cat = false) { |
1269
|
|
|
$pdo = Db::pdo(); |
1270
|
|
|
|
1271
|
|
|
if ($cat) { |
1272
|
|
|
return Feeds::getCategoryTitle($id); |
1273
|
|
|
} else if ($id == -1) { |
1274
|
|
|
return __("Starred articles"); |
1275
|
|
|
} else if ($id == -2) { |
1276
|
|
|
return __("Published articles"); |
1277
|
|
|
} else if ($id == -3) { |
1278
|
|
|
return __("Fresh articles"); |
1279
|
|
|
} else if ($id == -4) { |
1280
|
|
|
return __("All articles"); |
1281
|
|
|
} else if ($id === 0 || $id === "0") { |
1282
|
|
|
return __("Archived articles"); |
1283
|
|
|
} else if ($id == -6) { |
1284
|
|
|
return __("Recently read"); |
1285
|
|
|
} else if ($id < LABEL_BASE_INDEX) { |
1286
|
|
|
|
1287
|
|
|
$label_id = Labels::feed_to_label_id($id); |
1288
|
|
|
|
1289
|
|
|
$sth = $pdo->prepare("SELECT caption FROM ttrss_labels2 WHERE id = ?"); |
1290
|
|
|
$sth->execute([$label_id]); |
1291
|
|
|
|
1292
|
|
|
if ($row = $sth->fetch()) { |
1293
|
|
|
return $row["caption"]; |
1294
|
|
|
} else { |
1295
|
|
|
return "Unknown label ($label_id)"; |
1296
|
|
|
} |
1297
|
|
|
|
1298
|
|
|
} else if (is_numeric($id) && $id > 0) { |
1299
|
|
|
|
1300
|
|
|
$sth = $pdo->prepare("SELECT title FROM ttrss_feeds WHERE id = ?"); |
1301
|
|
|
$sth->execute([$id]); |
1302
|
|
|
|
1303
|
|
|
if ($row = $sth->fetch()) { |
1304
|
|
|
return $row["title"]; |
1305
|
|
|
} else { |
1306
|
|
|
return "Unknown feed ($id)"; |
1307
|
|
|
} |
1308
|
|
|
|
1309
|
|
|
} else { |
1310
|
|
|
return $id; |
1311
|
|
|
} |
1312
|
|
|
} |
1313
|
|
|
|
1314
|
|
|
public static function getCategoryUnread($cat, $owner_uid = false) { |
1315
|
|
|
|
1316
|
|
|
if (!$owner_uid) { |
1317
|
|
|
$owner_uid = $_SESSION["uid"]; |
1318
|
|
|
} |
1319
|
|
|
|
1320
|
|
|
$pdo = Db::pdo(); |
1321
|
|
|
|
1322
|
|
|
if ($cat >= 0) { |
1323
|
|
|
|
1324
|
|
|
if (!$cat) { |
1325
|
|
|
$cat = null; |
1326
|
|
|
} |
1327
|
|
|
|
1328
|
|
|
$sth = $pdo->prepare("SELECT id FROM ttrss_feeds |
1329
|
|
|
WHERE (cat_id = :cat OR (:cat IS NULL AND cat_id IS NULL)) |
1330
|
|
|
AND owner_uid = :uid"); |
1331
|
|
|
|
1332
|
|
|
$sth->execute([":cat" => $cat, ":uid" => $owner_uid]); |
1333
|
|
|
|
1334
|
|
|
$cat_feeds = array(); |
1335
|
|
|
while ($line = $sth->fetch()) { |
1336
|
|
|
array_push($cat_feeds, "feed_id = ".(int) $line["id"]); |
1337
|
|
|
} |
1338
|
|
|
|
1339
|
|
|
if (count($cat_feeds) == 0) { |
1340
|
|
|
return 0; |
1341
|
|
|
} |
1342
|
|
|
|
1343
|
|
|
$match_part = implode(" OR ", $cat_feeds); |
1344
|
|
|
|
1345
|
|
|
$sth = $pdo->prepare("SELECT COUNT(int_id) AS unread |
1346
|
|
|
FROM ttrss_user_entries |
1347
|
|
|
WHERE unread = true AND ($match_part) |
1348
|
|
|
AND owner_uid = ?"); |
1349
|
|
|
$sth->execute([$owner_uid]); |
1350
|
|
|
|
1351
|
|
|
$unread = 0; |
1352
|
|
|
|
1353
|
|
|
# this needs to be rewritten |
1354
|
|
|
while ($line = $sth->fetch()) { |
1355
|
|
|
$unread += $line["unread"]; |
1356
|
|
|
} |
1357
|
|
|
|
1358
|
|
|
return $unread; |
1359
|
|
|
} else if ($cat == -1) { |
1360
|
|
|
return getFeedUnread(-1) + getFeedUnread(-2) + getFeedUnread(-3) + getFeedUnread(0); |
1361
|
|
|
} else if ($cat == -2) { |
1362
|
|
|
|
1363
|
|
|
$sth = $pdo->prepare("SELECT COUNT(unread) AS unread FROM |
1364
|
|
|
ttrss_user_entries, ttrss_user_labels2 |
1365
|
|
|
WHERE article_id = ref_id AND unread = true |
1366
|
|
|
AND ttrss_user_entries.owner_uid = ?"); |
1367
|
|
|
$sth->execute([$owner_uid]); |
1368
|
|
|
$row = $sth->fetch(); |
1369
|
|
|
|
1370
|
|
|
return $row["unread"]; |
1371
|
|
|
} |
1372
|
|
|
} |
1373
|
|
|
|
1374
|
|
|
// only accepts real cats (>= 0) |
1375
|
|
|
public static function getCategoryChildrenUnread($cat, $owner_uid = false) { |
1376
|
|
|
if (!$owner_uid) { |
1377
|
|
|
$owner_uid = $_SESSION["uid"]; |
1378
|
|
|
} |
1379
|
|
|
|
1380
|
|
|
$pdo = Db::pdo(); |
1381
|
|
|
|
1382
|
|
|
$sth = $pdo->prepare("SELECT id FROM ttrss_feed_categories WHERE parent_cat = ? |
1383
|
|
|
AND owner_uid = ?"); |
1384
|
|
|
$sth->execute([$cat, $owner_uid]); |
1385
|
|
|
|
1386
|
|
|
$unread = 0; |
1387
|
|
|
|
1388
|
|
|
while ($line = $sth->fetch()) { |
1389
|
|
|
$unread += Feeds::getCategoryUnread($line["id"], $owner_uid); |
1390
|
|
|
$unread += Feeds::getCategoryChildrenUnread($line["id"], $owner_uid); |
1391
|
|
|
} |
1392
|
|
|
|
1393
|
|
|
return $unread; |
1394
|
|
|
} |
1395
|
|
|
|
1396
|
|
|
public static function getGlobalUnread($user_id = false) { |
1397
|
|
|
|
1398
|
|
|
if (!$user_id) { |
1399
|
|
|
$user_id = $_SESSION["uid"]; |
1400
|
|
|
} |
1401
|
|
|
|
1402
|
|
|
$pdo = Db::pdo(); |
1403
|
|
|
|
1404
|
|
|
$sth = $pdo->prepare("SELECT SUM(value) AS c_id FROM ttrss_counters_cache |
1405
|
|
|
WHERE owner_uid = ? AND feed_id > 0"); |
1406
|
|
|
$sth->execute([$user_id]); |
1407
|
|
|
$row = $sth->fetch(); |
1408
|
|
|
|
1409
|
|
|
return $row["c_id"]; |
1410
|
|
|
} |
1411
|
|
|
|
1412
|
|
|
public static function getCategoryTitle($cat_id) { |
1413
|
|
|
|
1414
|
|
|
if ($cat_id == -1) { |
1415
|
|
|
return __("Special"); |
1416
|
|
|
} else if ($cat_id == -2) { |
1417
|
|
|
return __("Labels"); |
1418
|
|
|
} else { |
1419
|
|
|
|
1420
|
|
|
$pdo = Db::pdo(); |
1421
|
|
|
|
1422
|
|
|
$sth = $pdo->prepare("SELECT title FROM ttrss_feed_categories WHERE |
1423
|
|
|
id = ?"); |
1424
|
|
|
$sth->execute([$cat_id]); |
1425
|
|
|
|
1426
|
|
|
if ($row = $sth->fetch()) { |
1427
|
|
|
return $row["title"]; |
1428
|
|
|
} else { |
1429
|
|
|
return __("Uncategorized"); |
1430
|
|
|
} |
1431
|
|
|
} |
1432
|
|
|
} |
1433
|
|
|
|
1434
|
|
|
public static function getLabelUnread($label_id, $owner_uid = false) { |
1435
|
|
|
if (!$owner_uid) { |
1436
|
|
|
$owner_uid = $_SESSION["uid"]; |
1437
|
|
|
} |
1438
|
|
|
|
1439
|
|
|
$pdo = Db::pdo(); |
1440
|
|
|
|
1441
|
|
|
$sth = $pdo->prepare("SELECT COUNT(ref_id) AS unread FROM ttrss_user_entries, ttrss_user_labels2 |
1442
|
|
|
WHERE owner_uid = ? AND unread = true AND label_id = ? AND article_id = ref_id"); |
1443
|
|
|
|
1444
|
|
|
$sth->execute([$owner_uid, $label_id]); |
1445
|
|
|
|
1446
|
|
|
if ($row = $sth->fetch()) { |
1447
|
|
|
return $row["unread"]; |
1448
|
|
|
} else { |
1449
|
|
|
return 0; |
1450
|
|
|
} |
1451
|
|
|
} |
1452
|
|
|
|
1453
|
|
|
public static function queryFeedHeadlines($params) { |
1454
|
|
|
|
1455
|
|
|
$pdo = Db::pdo(); |
1456
|
|
|
|
1457
|
|
|
// WARNING: due to highly dynamic nature of this query its going to quote parameters |
1458
|
|
|
// right before adding them to SQL part |
1459
|
|
|
|
1460
|
|
|
$feed = $params["feed"]; |
1461
|
|
|
$limit = isset($params["limit"]) ? $params["limit"] : 30; |
1462
|
|
|
$view_mode = $params["view_mode"]; |
1463
|
|
|
$cat_view = isset($params["cat_view"]) ? $params["cat_view"] : false; |
1464
|
|
|
$search = isset($params["search"]) ? $params["search"] : false; |
1465
|
|
|
$search_language = isset($params["search_language"]) ? $params["search_language"] : ""; |
1466
|
|
|
$override_order = isset($params["override_order"]) ? $params["override_order"] : false; |
1467
|
|
|
$offset = isset($params["offset"]) ? $params["offset"] : 0; |
1468
|
|
|
$owner_uid = isset($params["owner_uid"]) ? $params["owner_uid"] : $_SESSION["uid"]; |
1469
|
|
|
$since_id = isset($params["since_id"]) ? $params["since_id"] : 0; |
1470
|
|
|
$include_children = isset($params["include_children"]) ? $params["include_children"] : false; |
1471
|
|
|
$ignore_vfeed_group = isset($params["ignore_vfeed_group"]) ? $params["ignore_vfeed_group"] : false; |
1472
|
|
|
$override_strategy = isset($params["override_strategy"]) ? $params["override_strategy"] : false; |
1473
|
|
|
$override_vfeed = isset($params["override_vfeed"]) ? $params["override_vfeed"] : false; |
1474
|
|
|
$start_ts = isset($params["start_ts"]) ? $params["start_ts"] : false; |
1475
|
|
|
$check_first_id = isset($params["check_first_id"]) ? $params["check_first_id"] : false; |
1476
|
|
|
$skip_first_id_check = isset($params["skip_first_id_check"]) ? $params["skip_first_id_check"] : false; |
1477
|
|
|
//$order_by = isset($params["order_by"]) ? $params["order_by"] : false; |
1478
|
|
|
|
1479
|
|
|
$ext_tables_part = ""; |
1480
|
|
|
$limit_query_part = ""; |
1481
|
|
|
$query_error_override = ""; |
1482
|
|
|
|
1483
|
|
|
$search_words = []; |
1484
|
|
|
|
1485
|
|
|
if ($search) { |
1486
|
|
|
$search_query_part = ""; |
1487
|
|
|
|
1488
|
|
|
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH) as $plugin) { |
1489
|
|
|
list($search_query_part, $search_words) = $plugin->hook_search($search); |
1490
|
|
|
break; |
1491
|
|
|
} |
1492
|
|
|
|
1493
|
|
|
// fall back in case of no plugins |
1494
|
|
|
if (!$search_query_part) { |
1495
|
|
|
list($search_query_part, $search_words) = Feeds::search_to_sql($search, $search_language); |
1496
|
|
|
} |
1497
|
|
|
|
1498
|
|
|
if (DB_TYPE == "pgsql") { |
|
|
|
|
1499
|
|
|
$test_sth = $pdo->prepare("select $search_query_part |
1500
|
|
|
FROM ttrss_entries, ttrss_user_entries WHERE id = ref_id limit 1"); |
1501
|
|
|
|
1502
|
|
|
try { |
1503
|
|
|
$test_sth->execute(); |
1504
|
|
|
} catch (PDOException $e) { |
1505
|
|
|
// looks like tsquery syntax is invalid |
1506
|
|
|
$search_query_part = "false"; |
1507
|
|
|
|
1508
|
|
|
$query_error_override = T_sprintf("Incorrect search syntax: %s.", implode(" ", $search_words)); |
1509
|
|
|
} |
1510
|
|
|
} |
1511
|
|
|
|
1512
|
|
|
$search_query_part .= " AND "; |
1513
|
|
|
} else { |
1514
|
|
|
$search_query_part = ""; |
1515
|
|
|
} |
1516
|
|
|
|
1517
|
|
|
if ($since_id) { |
1518
|
|
|
$since_id_part = "ttrss_entries.id > ".$pdo->quote($since_id)." AND "; |
1519
|
|
|
} else { |
1520
|
|
|
$since_id_part = ""; |
1521
|
|
|
} |
1522
|
|
|
|
1523
|
|
|
$view_query_part = ""; |
1524
|
|
|
|
1525
|
|
|
if ($view_mode == "adaptive") { |
1526
|
|
|
if ($search) { |
1527
|
|
|
$view_query_part = " "; |
1528
|
|
|
} else if ($feed != -1) { |
1529
|
|
|
|
1530
|
|
|
$unread = getFeedUnread($feed, $cat_view); |
1531
|
|
|
|
1532
|
|
|
if ($cat_view && $feed > 0 && $include_children) { |
1533
|
|
|
$unread += Feeds::getCategoryChildrenUnread($feed); |
1534
|
|
|
} |
1535
|
|
|
|
1536
|
|
|
if ($unread > 0) { |
1537
|
|
|
$view_query_part = " unread = true AND "; |
1538
|
|
|
} |
1539
|
|
|
} |
1540
|
|
|
} |
1541
|
|
|
|
1542
|
|
|
if ($view_mode == "marked") { |
1543
|
|
|
$view_query_part = " marked = true AND "; |
1544
|
|
|
} |
1545
|
|
|
|
1546
|
|
|
if ($view_mode == "has_note") { |
1547
|
|
|
$view_query_part = " (note IS NOT NULL AND note != '') AND "; |
1548
|
|
|
} |
1549
|
|
|
|
1550
|
|
|
if ($view_mode == "published") { |
1551
|
|
|
$view_query_part = " published = true AND "; |
1552
|
|
|
} |
1553
|
|
|
|
1554
|
|
|
if ($view_mode == "unread" && $feed != -6) { |
1555
|
|
|
$view_query_part = " unread = true AND "; |
1556
|
|
|
} |
1557
|
|
|
|
1558
|
|
|
if ($limit > 0) { |
1559
|
|
|
$limit_query_part = "LIMIT ".(int) $limit; |
1560
|
|
|
} |
1561
|
|
|
|
1562
|
|
|
$allow_archived = false; |
1563
|
|
|
|
1564
|
|
|
$vfeed_query_part = ""; |
1565
|
|
|
|
1566
|
|
|
/* tags */ |
1567
|
|
|
if (!is_numeric($feed)) { |
1568
|
|
|
$query_strategy_part = "true"; |
1569
|
|
|
$vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE |
1570
|
|
|
id = feed_id) as feed_title,"; |
1571
|
|
|
} else if ($feed > 0) { |
1572
|
|
|
|
1573
|
|
|
if ($cat_view) { |
1574
|
|
|
|
1575
|
|
|
if ($feed > 0) { |
1576
|
|
|
if ($include_children) { |
1577
|
|
|
# sub-cats |
1578
|
|
|
$subcats = Feeds::getChildCategories($feed, $owner_uid); |
1579
|
|
|
array_push($subcats, $feed); |
1580
|
|
|
$subcats = array_map("intval", $subcats); |
1581
|
|
|
|
1582
|
|
|
$query_strategy_part = "cat_id IN (". |
1583
|
|
|
implode(",", $subcats).")"; |
1584
|
|
|
|
1585
|
|
|
} else { |
1586
|
|
|
$query_strategy_part = "cat_id = ".$pdo->quote($feed); |
1587
|
|
|
} |
1588
|
|
|
|
1589
|
|
|
} else { |
1590
|
|
|
$query_strategy_part = "cat_id IS NULL"; |
1591
|
|
|
} |
1592
|
|
|
|
1593
|
|
|
$vfeed_query_part = "ttrss_feeds.title AS feed_title,"; |
1594
|
|
|
|
1595
|
|
|
} else { |
1596
|
|
|
$query_strategy_part = "feed_id = ".$pdo->quote($feed); |
1597
|
|
|
} |
1598
|
|
|
} else if ($feed == 0 && !$cat_view) { // archive virtual feed |
1599
|
|
|
$query_strategy_part = "feed_id IS NULL"; |
1600
|
|
|
$allow_archived = true; |
1601
|
|
|
} else if ($feed == 0 && $cat_view) { // uncategorized |
1602
|
|
|
$query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL"; |
1603
|
|
|
$vfeed_query_part = "ttrss_feeds.title AS feed_title,"; |
1604
|
|
|
} else if ($feed == -1) { // starred virtual feed |
1605
|
|
|
$query_strategy_part = "marked = true"; |
1606
|
|
|
$vfeed_query_part = "ttrss_feeds.title AS feed_title,"; |
1607
|
|
|
$allow_archived = true; |
1608
|
|
|
|
1609
|
|
|
if (!$override_order) { |
1610
|
|
|
$override_order = "last_marked DESC, date_entered DESC, updated DESC"; |
1611
|
|
|
} |
1612
|
|
|
|
1613
|
|
|
} else if ($feed == -2) { // published virtual feed OR labels category |
1614
|
|
|
|
1615
|
|
|
if (!$cat_view) { |
1616
|
|
|
$query_strategy_part = "published = true"; |
1617
|
|
|
$vfeed_query_part = "ttrss_feeds.title AS feed_title,"; |
1618
|
|
|
$allow_archived = true; |
1619
|
|
|
|
1620
|
|
|
if (!$override_order) { |
1621
|
|
|
$override_order = "last_published DESC, date_entered DESC, updated DESC"; |
1622
|
|
|
} |
1623
|
|
|
|
1624
|
|
|
} else { |
1625
|
|
|
$vfeed_query_part = "ttrss_feeds.title AS feed_title,"; |
1626
|
|
|
|
1627
|
|
|
$ext_tables_part = "ttrss_labels2,ttrss_user_labels2,"; |
1628
|
|
|
|
1629
|
|
|
$query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND |
1630
|
|
|
ttrss_user_labels2.article_id = ref_id"; |
1631
|
|
|
|
1632
|
|
|
} |
1633
|
|
|
} else if ($feed == -6) { // recently read |
1634
|
|
|
$query_strategy_part = "unread = false AND last_read IS NOT NULL"; |
1635
|
|
|
|
1636
|
|
|
if (DB_TYPE == "pgsql") { |
1637
|
|
|
$query_strategy_part .= " AND last_read > NOW() - INTERVAL '1 DAY' "; |
1638
|
|
|
} else { |
1639
|
|
|
$query_strategy_part .= " AND last_read > DATE_SUB(NOW(), INTERVAL 1 DAY) "; |
1640
|
|
|
} |
1641
|
|
|
|
1642
|
|
|
$vfeed_query_part = "ttrss_feeds.title AS feed_title,"; |
1643
|
|
|
$allow_archived = true; |
1644
|
|
|
$ignore_vfeed_group = true; |
1645
|
|
|
|
1646
|
|
|
if (!$override_order) { |
1647
|
|
|
$override_order = "last_read DESC"; |
1648
|
|
|
} |
1649
|
|
|
|
1650
|
|
|
} else if ($feed == -3) { // fresh virtual feed |
1651
|
|
|
$query_strategy_part = "unread = true AND score >= 0"; |
1652
|
|
|
|
1653
|
|
|
$intl = (int) get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid); |
1654
|
|
|
|
1655
|
|
|
if (DB_TYPE == "pgsql") { |
1656
|
|
|
$query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' "; |
1657
|
|
|
} else { |
1658
|
|
|
$query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) "; |
1659
|
|
|
} |
1660
|
|
|
|
1661
|
|
|
$vfeed_query_part = "ttrss_feeds.title AS feed_title,"; |
1662
|
|
|
} else if ($feed == -4) { // all articles virtual feed |
1663
|
|
|
$allow_archived = true; |
1664
|
|
|
$query_strategy_part = "true"; |
1665
|
|
|
$vfeed_query_part = "ttrss_feeds.title AS feed_title,"; |
1666
|
|
|
} else if ($feed <= LABEL_BASE_INDEX) { // labels |
1667
|
|
|
$label_id = Labels::feed_to_label_id($feed); |
1668
|
|
|
|
1669
|
|
|
$query_strategy_part = "label_id = ".$pdo->quote($label_id)." AND |
1670
|
|
|
ttrss_labels2.id = ttrss_user_labels2.label_id AND |
1671
|
|
|
ttrss_user_labels2.article_id = ref_id"; |
1672
|
|
|
|
1673
|
|
|
$vfeed_query_part = "ttrss_feeds.title AS feed_title,"; |
1674
|
|
|
$ext_tables_part = "ttrss_labels2,ttrss_user_labels2,"; |
1675
|
|
|
$allow_archived = true; |
1676
|
|
|
|
1677
|
|
|
} else { |
1678
|
|
|
$query_strategy_part = "true"; |
1679
|
|
|
} |
1680
|
|
|
|
1681
|
|
|
$order_by = "score DESC, date_entered DESC, updated DESC"; |
1682
|
|
|
|
1683
|
|
|
if ($override_order) { |
1684
|
|
|
$order_by = $override_order; |
1685
|
|
|
} |
1686
|
|
|
|
1687
|
|
|
if ($override_strategy) { |
1688
|
|
|
$query_strategy_part = $override_strategy; |
1689
|
|
|
} |
1690
|
|
|
|
1691
|
|
|
if ($override_vfeed) { |
1692
|
|
|
$vfeed_query_part = $override_vfeed; |
1693
|
|
|
} |
1694
|
|
|
|
1695
|
|
|
if ($search) { |
1696
|
|
|
$feed_title = T_sprintf("Search results: %s", $search); |
1697
|
|
|
} else { |
1698
|
|
|
if ($cat_view) { |
1699
|
|
|
$feed_title = Feeds::getCategoryTitle($feed); |
1700
|
|
|
} else { |
1701
|
|
|
if (is_numeric($feed) && $feed > 0) { |
1702
|
|
|
$ssth = $pdo->prepare("SELECT title,site_url,last_error,last_updated |
1703
|
|
|
FROM ttrss_feeds WHERE id = ? AND owner_uid = ?"); |
1704
|
|
|
$ssth->execute([$feed, $owner_uid]); |
1705
|
|
|
$row = $ssth->fetch(); |
1706
|
|
|
|
1707
|
|
|
$feed_title = $row["title"]; |
1708
|
|
|
$feed_site_url = $row["site_url"]; |
1709
|
|
|
$last_error = $row["last_error"]; |
1710
|
|
|
$last_updated = $row["last_updated"]; |
1711
|
|
|
} else { |
1712
|
|
|
$feed_title = Feeds::getFeedTitle($feed); |
1713
|
|
|
} |
1714
|
|
|
} |
1715
|
|
|
} |
1716
|
|
|
|
1717
|
|
|
$content_query_part = "content, "; |
1718
|
|
|
|
1719
|
|
|
if ($limit_query_part) { |
1720
|
|
|
$offset_query_part = "OFFSET ".(int) $offset; |
1721
|
|
|
} else { |
1722
|
|
|
$offset_query_part = ""; |
1723
|
|
|
} |
1724
|
|
|
|
1725
|
|
|
if ($start_ts) { |
1726
|
|
|
$start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts)); |
1727
|
|
|
$start_ts_query_part = "date_entered >= '$start_ts_formatted' AND"; |
1728
|
|
|
} else { |
1729
|
|
|
$start_ts_query_part = ""; |
1730
|
|
|
} |
1731
|
|
|
|
1732
|
|
|
if (is_numeric($feed)) { |
1733
|
|
|
// proper override_order applied above |
1734
|
|
|
if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) { |
1735
|
|
|
|
1736
|
|
|
if (!(in_array($feed, Feeds::NEVER_GROUP_BY_DATE) && !$cat_view)) { |
1737
|
|
|
$yyiw_desc = $order_by == "date_reverse" ? "" : "desc"; |
1738
|
|
|
$yyiw_order_qpart = "yyiw $yyiw_desc, "; |
1739
|
|
|
} else { |
1740
|
|
|
$yyiw_order_qpart = ""; |
1741
|
|
|
} |
1742
|
|
|
|
1743
|
|
|
if (!$override_order) { |
1744
|
|
|
$order_by = "$yyiw_order_qpart ttrss_feeds.title, $order_by"; |
1745
|
|
|
} else { |
1746
|
|
|
$order_by = "$yyiw_order_qpart ttrss_feeds.title, $override_order"; |
1747
|
|
|
} |
1748
|
|
|
} |
1749
|
|
|
|
1750
|
|
|
if (!$allow_archived) { |
1751
|
|
|
$from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id),ttrss_feeds"; |
1752
|
|
|
$feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND"; |
1753
|
|
|
|
1754
|
|
|
} else { |
1755
|
|
|
$from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id) |
1756
|
|
|
LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)"; |
1757
|
|
|
} |
1758
|
|
|
|
1759
|
|
|
if ($vfeed_query_part) { |
1760
|
|
|
$vfeed_query_part .= "favicon_avg_color,"; |
1761
|
|
|
} |
1762
|
|
|
|
1763
|
|
|
$first_id = 0; |
1764
|
|
|
$first_id_query_strategy_part = $query_strategy_part; |
1765
|
|
|
|
1766
|
|
|
if ($feed == -3) { |
1767
|
|
|
$first_id_query_strategy_part = "true"; |
1768
|
|
|
} |
1769
|
|
|
|
1770
|
|
|
if (DB_TYPE == "pgsql") { |
1771
|
|
|
$sanity_interval_qpart = "date_entered >= NOW() - INTERVAL '1 hour' AND"; |
1772
|
|
|
$yyiw_qpart = "to_char(date_entered, 'IYYY-IW') AS yyiw"; |
1773
|
|
|
} else { |
1774
|
|
|
$sanity_interval_qpart = "date_entered >= DATE_SUB(NOW(), INTERVAL 1 hour) AND"; |
1775
|
|
|
$yyiw_qpart = "date_format(date_entered, '%Y-%u') AS yyiw"; |
1776
|
|
|
} |
1777
|
|
|
|
1778
|
|
|
if (!$search && !$skip_first_id_check) { |
1779
|
|
|
// if previous topmost article id changed that means our current pagination is no longer valid |
1780
|
|
|
$query = "SELECT DISTINCT |
1781
|
|
|
ttrss_feeds.title, |
1782
|
|
|
date_entered, |
1783
|
|
|
$yyiw_qpart, |
1784
|
|
|
guid, |
1785
|
|
|
ttrss_entries.id, |
1786
|
|
|
ttrss_entries.title, |
1787
|
|
|
updated, |
1788
|
|
|
score, |
1789
|
|
|
marked, |
1790
|
|
|
published, |
1791
|
|
|
last_marked, |
1792
|
|
|
last_published, |
1793
|
|
|
last_read |
1794
|
|
|
FROM |
1795
|
|
|
$from_qpart |
1796
|
|
|
WHERE |
1797
|
|
|
$feed_check_qpart |
|
|
|
|
1798
|
|
|
ttrss_user_entries.owner_uid = ".$pdo->quote($owner_uid)." AND |
1799
|
|
|
$search_query_part |
1800
|
|
|
$start_ts_query_part |
1801
|
|
|
$since_id_part |
1802
|
|
|
$sanity_interval_qpart |
1803
|
|
|
$first_id_query_strategy_part ORDER BY $order_by LIMIT 1"; |
1804
|
|
|
|
1805
|
|
|
/*if ($_REQUEST["debug"]) { |
1806
|
|
|
print $query; |
1807
|
|
|
}*/ |
1808
|
|
|
|
1809
|
|
|
$res = $pdo->query($query); |
1810
|
|
|
|
1811
|
|
|
if ($row = $res->fetch()) { |
1812
|
|
|
$first_id = (int) $row["id"]; |
1813
|
|
|
|
1814
|
|
|
if ($offset > 0 && $first_id && $check_first_id && $first_id != $check_first_id) { |
1815
|
|
|
return array(-1, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id, $vfeed_query_part != "", $query_error_override); |
|
|
|
|
1816
|
|
|
} |
1817
|
|
|
} |
1818
|
|
|
} |
1819
|
|
|
|
1820
|
|
|
$query = "SELECT DISTINCT |
1821
|
|
|
date_entered, |
1822
|
|
|
$yyiw_qpart, |
1823
|
|
|
guid, |
1824
|
|
|
ttrss_entries.id,ttrss_entries.title, |
1825
|
|
|
updated, |
1826
|
|
|
label_cache, |
1827
|
|
|
tag_cache, |
1828
|
|
|
always_display_enclosures, |
1829
|
|
|
site_url, |
1830
|
|
|
note, |
1831
|
|
|
num_comments, |
1832
|
|
|
comments, |
1833
|
|
|
int_id, |
1834
|
|
|
uuid, |
1835
|
|
|
lang, |
1836
|
|
|
hide_images, |
1837
|
|
|
unread,feed_id,marked,published,link,last_read,orig_feed_id, |
1838
|
|
|
last_marked, last_published, |
1839
|
|
|
$vfeed_query_part |
1840
|
|
|
$content_query_part |
1841
|
|
|
author,score |
1842
|
|
|
FROM |
1843
|
|
|
$from_qpart |
1844
|
|
|
WHERE |
1845
|
|
|
$feed_check_qpart |
1846
|
|
|
ttrss_user_entries.owner_uid = ".$pdo->quote($owner_uid)." AND |
1847
|
|
|
$search_query_part |
1848
|
|
|
$start_ts_query_part |
1849
|
|
|
$view_query_part |
1850
|
|
|
$since_id_part |
1851
|
|
|
$query_strategy_part ORDER BY $order_by |
1852
|
|
|
$limit_query_part $offset_query_part"; |
1853
|
|
|
|
1854
|
|
|
//if ($_REQUEST["debug"]) print $query; |
1855
|
|
|
|
1856
|
|
|
$res = $pdo->query($query); |
1857
|
|
|
|
1858
|
|
|
} else { |
1859
|
|
|
// browsing by tag |
1860
|
|
|
|
1861
|
|
|
$query = "SELECT DISTINCT |
1862
|
|
|
date_entered, |
1863
|
|
|
guid, |
1864
|
|
|
note, |
1865
|
|
|
ttrss_entries.id as id, |
1866
|
|
|
title, |
1867
|
|
|
updated, |
1868
|
|
|
unread, |
1869
|
|
|
feed_id, |
1870
|
|
|
orig_feed_id, |
1871
|
|
|
marked, |
1872
|
|
|
published, |
1873
|
|
|
num_comments, |
1874
|
|
|
comments, |
1875
|
|
|
int_id, |
1876
|
|
|
tag_cache, |
1877
|
|
|
label_cache, |
1878
|
|
|
link, |
1879
|
|
|
lang, |
1880
|
|
|
uuid, |
1881
|
|
|
last_read, |
1882
|
|
|
(SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images, |
1883
|
|
|
last_marked, last_published, |
1884
|
|
|
$since_id_part |
1885
|
|
|
$vfeed_query_part |
1886
|
|
|
$content_query_part |
1887
|
|
|
author, score |
1888
|
|
|
FROM ttrss_entries, ttrss_user_entries, ttrss_tags |
1889
|
|
|
WHERE |
1890
|
|
|
ref_id = ttrss_entries.id AND |
1891
|
|
|
ttrss_user_entries.owner_uid = ".$pdo->quote($owner_uid)." AND |
1892
|
|
|
post_int_id = int_id AND |
1893
|
|
|
tag_name = ".$pdo->quote($feed)." AND |
1894
|
|
|
$view_query_part |
1895
|
|
|
$search_query_part |
1896
|
|
|
$start_ts_query_part |
1897
|
|
|
$query_strategy_part ORDER BY $order_by |
1898
|
|
|
$limit_query_part $offset_query_part"; |
1899
|
|
|
|
1900
|
|
|
if ($_REQUEST["debug"]) { |
1901
|
|
|
print $query; |
1902
|
|
|
} |
1903
|
|
|
|
1904
|
|
|
$res = $pdo->query($query); |
1905
|
|
|
} |
1906
|
|
|
|
1907
|
|
|
return array($res, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id, $vfeed_query_part != "", $query_error_override); |
|
|
|
|
1908
|
|
|
|
1909
|
|
|
} |
1910
|
|
|
|
1911
|
|
|
public static function getParentCategories($cat, $owner_uid) { |
1912
|
|
|
$rv = array(); |
1913
|
|
|
|
1914
|
|
|
$pdo = Db::pdo(); |
1915
|
|
|
|
1916
|
|
|
$sth = $pdo->prepare("SELECT parent_cat FROM ttrss_feed_categories |
1917
|
|
|
WHERE id = ? AND parent_cat IS NOT NULL AND owner_uid = ?"); |
1918
|
|
|
$sth->execute([$cat, $owner_uid]); |
1919
|
|
|
|
1920
|
|
|
while ($line = $sth->fetch()) { |
1921
|
|
|
array_push($rv, $line["parent_cat"]); |
1922
|
|
|
$rv = array_merge($rv, Feeds::getParentCategories($line["parent_cat"], $owner_uid)); |
1923
|
|
|
} |
1924
|
|
|
|
1925
|
|
|
return $rv; |
1926
|
|
|
} |
1927
|
|
|
|
1928
|
|
|
public static function getChildCategories($cat, $owner_uid) { |
1929
|
|
|
$rv = array(); |
1930
|
|
|
|
1931
|
|
|
$pdo = Db::pdo(); |
1932
|
|
|
|
1933
|
|
|
$sth = $pdo->prepare("SELECT id FROM ttrss_feed_categories |
1934
|
|
|
WHERE parent_cat = ? AND owner_uid = ?"); |
1935
|
|
|
$sth->execute([$cat, $owner_uid]); |
1936
|
|
|
|
1937
|
|
|
while ($line = $sth->fetch()) { |
1938
|
|
|
array_push($rv, $line["id"]); |
1939
|
|
|
$rv = array_merge($rv, Feeds::getChildCategories($line["id"], $owner_uid)); |
1940
|
|
|
} |
1941
|
|
|
|
1942
|
|
|
return $rv; |
1943
|
|
|
} |
1944
|
|
|
|
1945
|
|
|
public static function getFeedCategory($feed) { |
1946
|
|
|
$pdo = Db::pdo(); |
1947
|
|
|
|
1948
|
|
|
$sth = $pdo->prepare("SELECT cat_id FROM ttrss_feeds |
1949
|
|
|
WHERE id = ?"); |
1950
|
|
|
$sth->execute([$feed]); |
1951
|
|
|
|
1952
|
|
|
if ($row = $sth->fetch()) { |
1953
|
|
|
return $row["cat_id"]; |
1954
|
|
|
} else { |
1955
|
|
|
return false; |
1956
|
|
|
} |
1957
|
|
|
|
1958
|
|
|
} |
1959
|
|
|
|
1960
|
|
|
function color_of($name) { |
1961
|
|
|
$colormap = ["#1cd7d7", "#d91111", "#1212d7", "#8e16e5", "#7b7b7b", |
1962
|
|
|
"#39f110", "#0bbea6", "#ec0e0e", "#1534f2", "#b9e416", |
1963
|
|
|
"#479af2", "#f36b14", "#10c7e9", "#1e8fe7", "#e22727"]; |
1964
|
|
|
|
1965
|
|
|
$sum = 0; |
1966
|
|
|
|
1967
|
|
|
for ($i = 0; $i < strlen($name); $i++) { |
1968
|
|
|
$sum += ord($name[$i]); |
1969
|
|
|
} |
1970
|
|
|
|
1971
|
|
|
$sum %= count($colormap); |
1972
|
|
|
|
1973
|
|
|
return $colormap[$sum]; |
1974
|
|
|
} |
1975
|
|
|
|
1976
|
|
|
public static function get_feeds_from_html($url, $content) { |
1977
|
|
|
$url = Feeds::fix_url($url); |
1978
|
|
|
$baseUrl = substr($url, 0, strrpos($url, '/') + 1); |
1979
|
|
|
|
1980
|
|
|
$feedUrls = []; |
1981
|
|
|
|
1982
|
|
|
$doc = new DOMDocument(); |
1983
|
|
|
if ($doc->loadHTML($content)) { |
1984
|
|
|
$xpath = new DOMXPath($doc); |
1985
|
|
|
$entries = $xpath->query('/html/head/link[@rel="alternate" and '. |
1986
|
|
|
'(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]'); |
1987
|
|
|
|
1988
|
|
|
foreach ($entries as $entry) { |
1989
|
|
|
if ($entry->hasAttribute('href')) { |
1990
|
|
|
$title = $entry->getAttribute('title'); |
1991
|
|
|
if ($title == '') { |
1992
|
|
|
$title = $entry->getAttribute('type'); |
1993
|
|
|
} |
1994
|
|
|
$feedUrl = rewrite_relative_url( |
1995
|
|
|
$baseUrl, $entry->getAttribute('href') |
1996
|
|
|
); |
1997
|
|
|
$feedUrls[$feedUrl] = $title; |
1998
|
|
|
} |
1999
|
|
|
} |
2000
|
|
|
} |
2001
|
|
|
return $feedUrls; |
2002
|
|
|
} |
2003
|
|
|
|
2004
|
|
|
public static function is_html($content) { |
2005
|
|
|
return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 8192)) !== 0; |
2006
|
|
|
} |
2007
|
|
|
|
2008
|
|
|
public static function validate_feed_url($url) { |
2009
|
|
|
$parts = parse_url($url); |
2010
|
|
|
|
2011
|
|
|
return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https'); |
2012
|
|
|
} |
2013
|
|
|
|
2014
|
|
|
/** |
2015
|
|
|
* Fixes incomplete URLs by prepending "http://". |
2016
|
|
|
* Also replaces feed:// with http://, and |
2017
|
|
|
* prepends a trailing slash if the url is a domain name only. |
2018
|
|
|
* |
2019
|
|
|
* @param string $url Possibly incomplete URL |
2020
|
|
|
* |
2021
|
|
|
* @return string Fixed URL. |
2022
|
|
|
*/ |
2023
|
|
|
public static function fix_url($url) { |
2024
|
|
|
|
2025
|
|
|
// support schema-less urls |
2026
|
|
|
if (strpos($url, '//') === 0) { |
2027
|
|
|
$url = 'https:'.$url; |
2028
|
|
|
} |
2029
|
|
|
|
2030
|
|
|
if (strpos($url, '://') === false) { |
2031
|
|
|
$url = 'http://'.$url; |
2032
|
|
|
} else if (substr($url, 0, 5) == 'feed:') { |
2033
|
|
|
$url = 'http:'.substr($url, 5); |
2034
|
|
|
} |
2035
|
|
|
|
2036
|
|
|
//prepend slash if the URL has no slash in it |
2037
|
|
|
// "http://www.example" -> "http://www.example/" |
2038
|
|
|
if (strpos($url, '/', strpos($url, ':') + 3) === false) { |
2039
|
|
|
$url .= '/'; |
2040
|
|
|
} |
2041
|
|
|
|
2042
|
|
|
//convert IDNA hostname to punycode if possible |
2043
|
|
|
if (function_exists("idn_to_ascii")) { |
2044
|
|
|
$parts = parse_url($url); |
2045
|
|
|
if (mb_detect_encoding($parts['host']) != 'ASCII') |
2046
|
|
|
{ |
2047
|
|
|
$parts['host'] = idn_to_ascii($parts['host']); |
2048
|
|
|
$url = build_url($parts); |
2049
|
|
|
} |
2050
|
|
|
} |
2051
|
|
|
|
2052
|
|
|
if ($url != "http:///") { |
2053
|
|
|
return $url; |
2054
|
|
|
} else { |
2055
|
|
|
return ''; |
2056
|
|
|
} |
2057
|
|
|
} |
2058
|
|
|
|
2059
|
|
|
public static function add_feed_category($feed_cat, $parent_cat_id = false, $order_id = 0) { |
2060
|
|
|
|
2061
|
|
|
if (!$feed_cat) { |
2062
|
|
|
return false; |
2063
|
|
|
} |
2064
|
|
|
|
2065
|
|
|
$feed_cat = mb_substr($feed_cat, 0, 250); |
2066
|
|
|
if (!$parent_cat_id) { |
2067
|
|
|
$parent_cat_id = null; |
2068
|
|
|
} |
2069
|
|
|
|
2070
|
|
|
$pdo = Db::pdo(); |
2071
|
|
|
$tr_in_progress = false; |
2072
|
|
|
|
2073
|
|
|
try { |
2074
|
|
|
$pdo->beginTransaction(); |
2075
|
|
|
} catch (Exception $e) { |
2076
|
|
|
$tr_in_progress = true; |
2077
|
|
|
} |
2078
|
|
|
|
2079
|
|
|
$sth = $pdo->prepare("SELECT id FROM ttrss_feed_categories |
2080
|
|
|
WHERE (parent_cat = :parent OR (:parent IS NULL AND parent_cat IS NULL)) |
2081
|
|
|
AND title = :title AND owner_uid = :uid"); |
2082
|
|
|
$sth->execute([':parent' => $parent_cat_id, ':title' => $feed_cat, ':uid' => $_SESSION['uid']]); |
2083
|
|
|
|
2084
|
|
|
if (!$sth->fetch()) { |
2085
|
|
|
|
2086
|
|
|
$sth = $pdo->prepare("INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat,order_id) |
2087
|
|
|
VALUES (?, ?, ?, ?)"); |
2088
|
|
|
$sth->execute([$_SESSION['uid'], $feed_cat, $parent_cat_id, (int) $order_id]); |
2089
|
|
|
|
2090
|
|
|
if (!$tr_in_progress) { |
2091
|
|
|
$pdo->commit(); |
2092
|
|
|
} |
2093
|
|
|
|
2094
|
|
|
return true; |
2095
|
|
|
} |
2096
|
|
|
|
2097
|
|
|
$pdo->commit(); |
2098
|
|
|
|
2099
|
|
|
return false; |
2100
|
|
|
} |
2101
|
|
|
|
2102
|
|
|
public static function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) { |
2103
|
|
|
|
2104
|
|
|
if (!$owner_uid) { |
2105
|
|
|
$owner_uid = $_SESSION["uid"]; |
2106
|
|
|
} |
2107
|
|
|
|
2108
|
|
|
$is_cat = bool_to_sql_bool($is_cat); |
2109
|
|
|
|
2110
|
|
|
$pdo = Db::pdo(); |
2111
|
|
|
|
2112
|
|
|
$sth = $pdo->prepare("SELECT access_key FROM ttrss_access_keys |
2113
|
|
|
WHERE feed_id = ? AND is_cat = ? |
2114
|
|
|
AND owner_uid = ?"); |
2115
|
|
|
$sth->execute([$feed_id, $is_cat, $owner_uid]); |
2116
|
|
|
|
2117
|
|
|
if ($row = $sth->fetch()) { |
2118
|
|
|
return $row["access_key"]; |
2119
|
|
|
} else { |
2120
|
|
|
$key = uniqid_short(); |
2121
|
|
|
|
2122
|
|
|
$sth = $pdo->prepare("INSERT INTO ttrss_access_keys |
2123
|
|
|
(access_key, feed_id, is_cat, owner_uid) |
2124
|
|
|
VALUES (?, ?, ?, ?)"); |
2125
|
|
|
|
2126
|
|
|
$sth->execute([$key, $feed_id, $is_cat, $owner_uid]); |
2127
|
|
|
|
2128
|
|
|
return $key; |
2129
|
|
|
} |
2130
|
|
|
} |
2131
|
|
|
|
2132
|
|
|
/** |
2133
|
|
|
* Purge a feed old posts. |
2134
|
|
|
* |
2135
|
|
|
* @param mixed $link A database connection. |
2136
|
|
|
* @param mixed $feed_id The id of the purged feed. |
2137
|
|
|
* @param mixed $purge_interval Olderness of purged posts. |
2138
|
|
|
* @param boolean $debug Set to True to enable the debug. False by default. |
2139
|
|
|
* @access public |
2140
|
|
|
* @return void |
2141
|
|
|
*/ |
2142
|
|
|
public static function purge_feed($feed_id, $purge_interval) { |
2143
|
|
|
|
2144
|
|
|
if (!$purge_interval) { |
2145
|
|
|
$purge_interval = Feeds::feed_purge_interval($feed_id); |
2146
|
|
|
} |
2147
|
|
|
|
2148
|
|
|
$pdo = Db::pdo(); |
2149
|
|
|
|
2150
|
|
|
$sth = $pdo->prepare("SELECT owner_uid FROM ttrss_feeds WHERE id = ?"); |
2151
|
|
|
$sth->execute([$feed_id]); |
2152
|
|
|
|
2153
|
|
|
$owner_uid = false; |
2154
|
|
|
|
2155
|
|
|
if ($row = $sth->fetch()) { |
2156
|
|
|
$owner_uid = $row["owner_uid"]; |
2157
|
|
|
} |
2158
|
|
|
|
2159
|
|
|
if ($purge_interval == -1 || !$purge_interval) { |
2160
|
|
|
if ($owner_uid) { |
2161
|
|
|
CCache::update($feed_id, $owner_uid); |
2162
|
|
|
} |
2163
|
|
|
return; |
2164
|
|
|
} |
2165
|
|
|
|
2166
|
|
|
if (!$owner_uid) { |
2167
|
|
|
return; |
2168
|
|
|
} |
2169
|
|
|
|
2170
|
|
|
if (FORCE_ARTICLE_PURGE == 0) { |
|
|
|
|
2171
|
|
|
$purge_unread = get_pref("PURGE_UNREAD_ARTICLES", |
2172
|
|
|
$owner_uid, false); |
2173
|
|
|
} else { |
2174
|
|
|
$purge_unread = true; |
2175
|
|
|
$purge_interval = FORCE_ARTICLE_PURGE; |
2176
|
|
|
} |
2177
|
|
|
|
2178
|
|
|
if (!$purge_unread) { |
2179
|
|
|
$query_limit = " unread = false AND "; |
2180
|
|
|
} else { |
2181
|
|
|
$query_limit = ""; |
2182
|
|
|
} |
2183
|
|
|
|
2184
|
|
|
$purge_interval = (int) $purge_interval; |
2185
|
|
|
|
2186
|
|
|
if (DB_TYPE == "pgsql") { |
|
|
|
|
2187
|
|
|
$sth = $pdo->prepare("DELETE FROM ttrss_user_entries |
2188
|
|
|
USING ttrss_entries |
2189
|
|
|
WHERE ttrss_entries.id = ref_id AND |
2190
|
|
|
marked = false AND |
2191
|
|
|
feed_id = ? AND |
2192
|
|
|
$query_limit |
2193
|
|
|
ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'"); |
2194
|
|
|
$sth->execute([$feed_id]); |
2195
|
|
|
|
2196
|
|
|
} else { |
2197
|
|
|
$sth = $pdo->prepare("DELETE FROM ttrss_user_entries |
2198
|
|
|
USING ttrss_user_entries, ttrss_entries |
2199
|
|
|
WHERE ttrss_entries.id = ref_id AND |
2200
|
|
|
marked = false AND |
2201
|
|
|
feed_id = ? AND |
2202
|
|
|
$query_limit |
2203
|
|
|
ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); |
2204
|
|
|
$sth->execute([$feed_id]); |
2205
|
|
|
|
2206
|
|
|
} |
2207
|
|
|
|
2208
|
|
|
$rows = $sth->rowCount(); |
2209
|
|
|
|
2210
|
|
|
CCache::update($feed_id, $owner_uid); |
2211
|
|
|
|
2212
|
|
|
Debug::log("Purged feed $feed_id ($purge_interval): deleted $rows articles"); |
2213
|
|
|
|
2214
|
|
|
return $rows; |
2215
|
|
|
} |
2216
|
|
|
|
2217
|
|
|
public static function feed_purge_interval($feed_id) { |
2218
|
|
|
|
2219
|
|
|
$pdo = DB::pdo(); |
2220
|
|
|
|
2221
|
|
|
$sth = $pdo->prepare("SELECT purge_interval, owner_uid FROM ttrss_feeds |
2222
|
|
|
WHERE id = ?"); |
2223
|
|
|
$sth->execute([$feed_id]); |
2224
|
|
|
|
2225
|
|
|
if ($row = $sth->fetch()) { |
2226
|
|
|
$purge_interval = $row["purge_interval"]; |
2227
|
|
|
$owner_uid = $row["owner_uid"]; |
2228
|
|
|
|
2229
|
|
|
if ($purge_interval == 0) { |
2230
|
|
|
$purge_interval = get_pref( |
2231
|
|
|
'PURGE_OLD_DAYS', $owner_uid); |
2232
|
|
|
} |
2233
|
|
|
|
2234
|
|
|
return $purge_interval; |
2235
|
|
|
|
2236
|
|
|
} else { |
2237
|
|
|
return -1; |
2238
|
|
|
} |
2239
|
|
|
} |
2240
|
|
|
|
2241
|
|
|
public static function search_to_sql($search, $search_language) { |
2242
|
|
|
|
2243
|
|
|
$keywords = str_getcsv(trim($search), " "); |
2244
|
|
|
$query_keywords = array(); |
2245
|
|
|
$search_words = array(); |
2246
|
|
|
$search_query_leftover = array(); |
2247
|
|
|
|
2248
|
|
|
$pdo = Db::pdo(); |
2249
|
|
|
|
2250
|
|
|
if ($search_language) { |
2251
|
|
|
$search_language = $pdo->quote(mb_strtolower($search_language)); |
2252
|
|
|
} else { |
2253
|
|
|
$search_language = $pdo->quote("english"); |
2254
|
|
|
} |
2255
|
|
|
|
2256
|
|
|
foreach ($keywords as $k) { |
2257
|
|
|
if (strpos($k, "-") === 0) { |
2258
|
|
|
$k = substr($k, 1); |
2259
|
|
|
$not = "NOT"; |
2260
|
|
|
} else { |
2261
|
|
|
$not = ""; |
2262
|
|
|
} |
2263
|
|
|
|
2264
|
|
|
$commandpair = explode(":", mb_strtolower($k), 2); |
2265
|
|
|
|
2266
|
|
|
switch ($commandpair[0]) { |
2267
|
|
|
case "title": |
2268
|
|
|
if ($commandpair[1]) { |
2269
|
|
|
array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE ". |
2270
|
|
|
$pdo->quote('%'.mb_strtolower($commandpair[1]).'%')."))"); |
2271
|
|
|
} else { |
2272
|
|
|
array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') |
2273
|
|
|
OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); |
2274
|
|
|
array_push($search_words, $k); |
2275
|
|
|
} |
2276
|
|
|
break; |
2277
|
|
|
case "author": |
2278
|
|
|
if ($commandpair[1]) { |
2279
|
|
|
array_push($query_keywords, "($not (LOWER(author) LIKE ". |
2280
|
|
|
$pdo->quote('%'.mb_strtolower($commandpair[1]).'%')."))"); |
2281
|
|
|
} else { |
2282
|
|
|
array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') |
2283
|
|
|
OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); |
2284
|
|
|
array_push($search_words, $k); |
2285
|
|
|
} |
2286
|
|
|
break; |
2287
|
|
|
case "note": |
2288
|
|
|
if ($commandpair[1]) { |
2289
|
|
|
if ($commandpair[1] == "true") { |
2290
|
|
|
array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))"); |
2291
|
|
|
} else if ($commandpair[1] == "false") { |
2292
|
|
|
array_push($query_keywords, "($not (note IS NULL OR note = ''))"); |
2293
|
|
|
} else { |
2294
|
|
|
array_push($query_keywords, "($not (LOWER(note) LIKE ". |
2295
|
|
|
$pdo->quote('%'.mb_strtolower($commandpair[1]).'%')."))"); |
2296
|
|
|
} |
2297
|
|
|
} else { |
2298
|
|
|
array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER(".$pdo->quote("%$k%").") |
2299
|
|
|
OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); |
2300
|
|
|
if (!$not) { |
2301
|
|
|
array_push($search_words, $k); |
2302
|
|
|
} |
2303
|
|
|
} |
2304
|
|
|
break; |
2305
|
|
|
case "star": |
2306
|
|
|
|
2307
|
|
|
if ($commandpair[1]) { |
2308
|
|
|
if ($commandpair[1] == "true") { |
2309
|
|
|
array_push($query_keywords, "($not (marked = true))"); |
2310
|
|
|
} else { |
2311
|
|
|
array_push($query_keywords, "($not (marked = false))"); |
2312
|
|
|
} |
2313
|
|
|
} else { |
2314
|
|
|
array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER(".$pdo->quote("%$k%").") |
2315
|
|
|
OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); |
2316
|
|
|
if (!$not) { |
2317
|
|
|
array_push($search_words, $k); |
2318
|
|
|
} |
2319
|
|
|
} |
2320
|
|
|
break; |
2321
|
|
|
case "pub": |
2322
|
|
|
if ($commandpair[1]) { |
2323
|
|
|
if ($commandpair[1] == "true") { |
2324
|
|
|
array_push($query_keywords, "($not (published = true))"); |
2325
|
|
|
} else { |
2326
|
|
|
array_push($query_keywords, "($not (published = false))"); |
2327
|
|
|
} |
2328
|
|
|
|
2329
|
|
|
} else { |
2330
|
|
|
array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') |
2331
|
|
|
OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); |
2332
|
|
|
if (!$not) { |
2333
|
|
|
array_push($search_words, $k); |
2334
|
|
|
} |
2335
|
|
|
} |
2336
|
|
|
break; |
2337
|
|
|
case "unread": |
2338
|
|
|
if ($commandpair[1]) { |
2339
|
|
|
if ($commandpair[1] == "true") { |
2340
|
|
|
array_push($query_keywords, "($not (unread = true))"); |
2341
|
|
|
} else { |
2342
|
|
|
array_push($query_keywords, "($not (unread = false))"); |
2343
|
|
|
} |
2344
|
|
|
|
2345
|
|
|
} else { |
2346
|
|
|
array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER(".$pdo->quote("%$k%").") |
2347
|
|
|
OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); |
2348
|
|
|
if (!$not) { |
2349
|
|
|
array_push($search_words, $k); |
2350
|
|
|
} |
2351
|
|
|
} |
2352
|
|
|
break; |
2353
|
|
|
default: |
2354
|
|
|
if (strpos($k, "@") === 0) { |
2355
|
|
|
|
2356
|
|
|
$user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']); |
2357
|
|
|
$orig_ts = strtotime(substr($k, 1)); |
2358
|
|
|
$k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC')); |
2359
|
|
|
|
2360
|
|
|
//$k = date("Y-m-d", strtotime(substr($k, 1))); |
2361
|
|
|
|
2362
|
|
|
array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')"); |
2363
|
|
|
} else { |
2364
|
|
|
|
2365
|
|
|
if (DB_TYPE == "pgsql") { |
|
|
|
|
2366
|
|
|
$k = mb_strtolower($k); |
2367
|
|
|
array_push($search_query_leftover, $not ? "!$k" : $k); |
2368
|
|
|
} else { |
2369
|
|
|
array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER(".$pdo->quote("%$k%").") |
2370
|
|
|
OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); |
2371
|
|
|
} |
2372
|
|
|
|
2373
|
|
|
if (!$not) { |
2374
|
|
|
array_push($search_words, $k); |
2375
|
|
|
} |
2376
|
|
|
} |
2377
|
|
|
} |
2378
|
|
|
} |
2379
|
|
|
|
2380
|
|
|
if (count($search_query_leftover) > 0) { |
2381
|
|
|
|
2382
|
|
|
if (DB_TYPE == "pgsql") { |
2383
|
|
|
|
2384
|
|
|
// if there's no joiners consider this a "simple" search and |
2385
|
|
|
// concatenate everything with &, otherwise don't try to mess with tsquery syntax |
2386
|
|
|
if (preg_match("/[&|]/", implode(" ", $search_query_leftover))) { |
2387
|
|
|
$tsquery = $pdo->quote(implode(" ", $search_query_leftover)); |
2388
|
|
|
} else { |
2389
|
|
|
$tsquery = $pdo->quote(implode(" & ", $search_query_leftover)); |
2390
|
|
|
} |
2391
|
|
|
|
2392
|
|
|
array_push($query_keywords, |
2393
|
|
|
"(tsvector_combined @@ to_tsquery($search_language, $tsquery))"); |
2394
|
|
|
} |
2395
|
|
|
|
2396
|
|
|
} |
2397
|
|
|
|
2398
|
|
|
$search_query_part = implode("AND", $query_keywords); |
2399
|
|
|
|
2400
|
|
|
return array($search_query_part, $search_words); |
2401
|
|
|
} |
2402
|
|
|
} |
2403
|
|
|
|
2404
|
|
|
|