Passed
Push — master ( 678db7...164b32 )
by Cody
06:12 queued 03:06
created

Feeds::format_headlines_list()   F

Complexity

Conditions 62

Size

Total Lines 347
Code Lines 208

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 62
eloc 208
c 1
b 0
f 0
nop 11
dl 0
loc 347
rs 3.3333

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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