]> git.wh0rd.org Git - tt-rss.git/blob - classes/pref/feeds.php
Revert "import_export: do not use DOMDocument->loadXML in static context"
[tt-rss.git] / classes / pref / feeds.php
1 <?php
2 class Pref_Feeds extends Handler_Protected {
3         public static $feed_languages = array("English", "Danish", "Dutch", "Finnish", "French", "German", "Hungarian", "Italian", "Norwegian",
4                 "Portuguese", "Russian", "Spanish", "Swedish", "Turkish", "Simple");
5
6         function csrf_ignore($method) {
7                 $csrf_ignored = array("index", "getfeedtree", "add", "editcats", "editfeed",
8                         "savefeedorder", "uploadicon", "feedswitherrors", "inactivefeeds",
9                         "batchsubscribe");
10
11                 return array_search($method, $csrf_ignored) !== false;
12         }
13
14         function batch_edit_cbox($elem, $label = false) {
15                 print "<input type=\"checkbox\" title=\"".__("Check to enable field")."\"
16                         onchange=\"dijit.byId('feedEditDlg').toggleField(this, '$elem', '$label')\">";
17         }
18
19         function renamecat() {
20                 $title = clean($_REQUEST['title']);
21                 $id = clean($_REQUEST['id']);
22
23                 if ($title) {
24                         $sth = $this->pdo->prepare("UPDATE ttrss_feed_categories SET
25                                 title = ? WHERE id = ? AND owner_uid = ?");
26                         $sth->execute([$title, $id, $_SESSION['uid']]);
27                 }
28         }
29
30         private function get_category_items($cat_id) {
31
32                 if (clean($_REQUEST['mode']) != 2)
33                         $search = $_SESSION["prefs_feed_search"];
34                 else
35                         $search = "";
36
37                 // first one is set by API
38                 $show_empty_cats = clean($_REQUEST['force_show_empty']) ||
39                         (clean($_REQUEST['mode']) != 2 && !$search);
40
41                 $items = array();
42
43                 $sth = $this->pdo->prepare("SELECT id, title FROM ttrss_feed_categories
44                                 WHERE owner_uid = ? AND parent_cat = ? ORDER BY order_id, title");
45                 $sth->execute([$_SESSION['uid'], $cat_id]);
46
47                 while ($line = $sth->fetch()) {
48
49                         $cat = array();
50                         $cat['id'] = 'CAT:' . $line['id'];
51                         $cat['bare_id'] = (int)$line['id'];
52                         $cat['name'] = $line['title'];
53                         $cat['items'] = array();
54                         $cat['checkbox'] = false;
55                         $cat['type'] = 'category';
56                         $cat['unread'] = 0;
57                         $cat['child_unread'] = 0;
58                         $cat['auxcounter'] = 0;
59                         $cat['parent_id'] = $cat_id;
60
61                         $cat['items'] = $this->get_category_items($line['id']);
62
63                         $num_children = $this->calculate_children_count($cat);
64                         $cat['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', (int) $num_children), $num_children);
65
66                         if ($num_children > 0 || $show_empty_cats)
67                                 array_push($items, $cat);
68
69                 }
70
71                 $fsth = $this->pdo->prepare("SELECT id, title, last_error,
72                         ".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated
73                         FROM ttrss_feeds
74                         WHERE cat_id = :cat AND 
75                         owner_uid = :uid AND
76                         (:search = '' OR (LOWER(title) LIKE :search OR LOWER(feed_url) LIKE :search))
77                         ORDER BY order_id, title");
78
79                 $fsth->execute([":cat" => $cat_id, ":uid" => $_SESSION['uid'], ":search" => $search ? "%$search%" : ""]);
80
81                 while ($feed_line = $fsth->fetch()) {
82                         $feed = array();
83                         $feed['id'] = 'FEED:' . $feed_line['id'];
84                         $feed['bare_id'] = (int)$feed_line['id'];
85                         $feed['auxcounter'] = 0;
86                         $feed['name'] = $feed_line['title'];
87                         $feed['checkbox'] = false;
88                         $feed['unread'] = 0;
89                         $feed['error'] = $feed_line['last_error'];
90                         $feed['icon'] = Feeds::getFeedIcon($feed_line['id']);
91                         $feed['param'] = make_local_datetime(
92                                 $feed_line['last_updated'], true);
93
94                         array_push($items, $feed);
95                 }
96
97                 return $items;
98         }
99
100         function getfeedtree() {
101                 print json_encode($this->makefeedtree());
102         }
103
104         function makefeedtree() {
105
106                 if (clean($_REQUEST['mode']) != 2)
107                         $search = $_SESSION["prefs_feed_search"];
108                 else
109                         $search = "";
110
111                 $root = array();
112                 $root['id'] = 'root';
113                 $root['name'] = __('Feeds');
114                 $root['items'] = array();
115                 $root['type'] = 'category';
116
117                 $enable_cats = get_pref('ENABLE_FEED_CATS');
118
119                 if (clean($_REQUEST['mode']) == 2) {
120
121                         if ($enable_cats) {
122                                 $cat = $this->feedlist_init_cat(-1);
123                         } else {
124                                 $cat['items'] = array();
125                         }
126
127                         foreach (array(-4, -3, -1, -2, 0, -6) as $i) {
128                                 array_push($cat['items'], $this->feedlist_init_feed($i));
129                         }
130
131                         /* Plugin feeds for -1 */
132
133                         $feeds = PluginHost::getInstance()->get_feeds(-1);
134
135                         if ($feeds) {
136                                 foreach ($feeds as $feed) {
137                                         $feed_id = PluginHost::pfeed_to_feed_id($feed['id']);
138
139                                         $item = array();
140                                         $item['id'] = 'FEED:' . $feed_id;
141                                         $item['bare_id'] = (int)$feed_id;
142                                         $item['auxcounter'] = 0;
143                                         $item['name'] = $feed['title'];
144                                         $item['checkbox'] = false;
145                                         $item['error'] = '';
146                                         $item['icon'] = $feed['icon'];
147
148                                         $item['param'] = '';
149                                         $item['unread'] = 0; //$feed['sender']->get_unread($feed['id']);
150                                         $item['type'] = 'feed';
151
152                                         array_push($cat['items'], $item);
153                                 }
154                         }
155
156                         if ($enable_cats) {
157                                 array_push($root['items'], $cat);
158                         } else {
159                                 $root['items'] = array_merge($root['items'], $cat['items']);
160                         }
161
162                         $sth = $this->pdo->prepare("SELECT * FROM
163                                 ttrss_labels2 WHERE owner_uid = ? ORDER by caption");
164                         $sth->execute([$_SESSION['uid']]);
165
166                         if (get_pref('ENABLE_FEED_CATS')) {
167                                 $cat = $this->feedlist_init_cat(-2);
168                         } else {
169                                 $cat['items'] = array();
170                         }
171
172                         $num_labels = 0;
173                         while ($line = $sth->fetch()) {
174                                 ++$num_labels;
175
176                                 $label_id = Labels::label_to_feed_id($line['id']);
177
178                                 $feed = $this->feedlist_init_feed($label_id, false, 0);
179
180                                 $feed['fg_color'] = $line['fg_color'];
181                                 $feed['bg_color'] = $line['bg_color'];
182
183                                 array_push($cat['items'], $feed);
184                         }
185
186                         if ($num_labels) {
187                                 if ($enable_cats) {
188                                         array_push($root['items'], $cat);
189                                 } else {
190                                         $root['items'] = array_merge($root['items'], $cat['items']);
191                                 }
192                         }
193                 }
194
195                 if ($enable_cats) {
196                         $show_empty_cats = clean($_REQUEST['force_show_empty']) ||
197                                 (clean($_REQUEST['mode']) != 2 && !$search);
198
199                         $sth = $this->pdo->prepare("SELECT id, title FROM ttrss_feed_categories
200                                 WHERE owner_uid = ? AND parent_cat IS NULL ORDER BY order_id, title");
201                         $sth->execute([$_SESSION['uid']]);
202
203                         while ($line = $sth->fetch()) {
204                                 $cat = array();
205                                 $cat['id'] = 'CAT:' . $line['id'];
206                                 $cat['bare_id'] = (int)$line['id'];
207                                 $cat['auxcounter'] = 0;
208                                 $cat['name'] = $line['title'];
209                                 $cat['items'] = array();
210                                 $cat['checkbox'] = false;
211                                 $cat['type'] = 'category';
212                                 $cat['unread'] = 0;
213                                 $cat['child_unread'] = 0;
214
215                                 $cat['items'] = $this->get_category_items($line['id']);
216
217                                 $num_children = $this->calculate_children_count($cat);
218                                 $cat['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', (int) $num_children), $num_children);
219
220                                 if ($num_children > 0 || $show_empty_cats)
221                                         array_push($root['items'], $cat);
222
223                                 $root['param'] += count($cat['items']);
224                         }
225
226                         /* Uncategorized is a special case */
227
228                         $cat = array();
229                         $cat['id'] = 'CAT:0';
230                         $cat['bare_id'] = 0;
231                         $cat['auxcounter'] = 0;
232                         $cat['name'] = __("Uncategorized");
233                         $cat['items'] = array();
234                         $cat['type'] = 'category';
235                         $cat['checkbox'] = false;
236                         $cat['unread'] = 0;
237                         $cat['child_unread'] = 0;
238
239                         $fsth = $this->pdo->prepare("SELECT id, title,last_error,
240                                 ".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated
241                                 FROM ttrss_feeds
242                                 WHERE cat_id IS NULL AND 
243                                 owner_uid = :uid AND
244                                 (:search = '' OR (LOWER(title) LIKE :search OR LOWER(feed_url) LIKE :search))
245                                 ORDER BY order_id, title");
246                         $fsth->execute([":uid" => $_SESSION['uid'], ":search" => $search ? "%$search%" : ""]);
247
248                         while ($feed_line = $fsth->fetch()) {
249                                 $feed = array();
250                                 $feed['id'] = 'FEED:' . $feed_line['id'];
251                                 $feed['bare_id'] = (int)$feed_line['id'];
252                                 $feed['auxcounter'] = 0;
253                                 $feed['name'] = $feed_line['title'];
254                                 $feed['checkbox'] = false;
255                                 $feed['error'] = $feed_line['last_error'];
256                                 $feed['icon'] = Feeds::getFeedIcon($feed_line['id']);
257                                 $feed['param'] = make_local_datetime(
258                                         $feed_line['last_updated'], true);
259                                 $feed['unread'] = 0;
260                                 $feed['type'] = 'feed';
261
262                                 array_push($cat['items'], $feed);
263                         }
264
265                         $cat['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', count($cat['items'])), count($cat['items']));
266
267                         if (count($cat['items']) > 0 || $show_empty_cats)
268                                 array_push($root['items'], $cat);
269
270                         $num_children = $this->calculate_children_count($root);
271                         $root['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', (int) $num_children), $num_children);
272
273                 } else {
274                         $fsth = $this->pdo->prepare("SELECT id, title, last_error,
275                                 ".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated
276                                 FROM ttrss_feeds
277                                 WHERE owner_uid = :uid AND
278                                 (:search = '' OR (LOWER(title) LIKE :search OR LOWER(feed_url) LIKE :search))
279                                 ORDER BY order_id, title");
280                         $fsth->execute([":uid" => $_SESSION['uid'], ":search" => $search ? "%$search%" : ""]);
281
282                         while ($feed_line = $fsth->fetch()) {
283                                 $feed = array();
284                                 $feed['id'] = 'FEED:' . $feed_line['id'];
285                                 $feed['bare_id'] = (int)$feed_line['id'];
286                                 $feed['auxcounter'] = 0;
287                                 $feed['name'] = $feed_line['title'];
288                                 $feed['checkbox'] = false;
289                                 $feed['error'] = $feed_line['last_error'];
290                                 $feed['icon'] = Feeds::getFeedIcon($feed_line['id']);
291                                 $feed['param'] = make_local_datetime(
292                                         $feed_line['last_updated'], true);
293                                 $feed['unread'] = 0;
294                                 $feed['type'] = 'feed';
295
296                                 array_push($root['items'], $feed);
297                         }
298
299                         $root['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', count($cat['items'])), count($cat['items']));
300                 }
301
302                 $fl = array();
303                 $fl['identifier'] = 'id';
304                 $fl['label'] = 'name';
305
306                 if (clean($_REQUEST['mode']) != 2) {
307                         $fl['items'] = array($root);
308                 } else {
309                         $fl['items'] = $root['items'];
310                 }
311
312                 return $fl;
313         }
314
315         function catsortreset() {
316                 $sth = $this->pdo->prepare("UPDATE ttrss_feed_categories
317                                 SET order_id = 0 WHERE owner_uid = ?");
318                 $sth->execute([$_SESSION['uid']]);
319         }
320
321         function feedsortreset() {
322                 $sth = $this->pdo->prepare("UPDATE ttrss_feeds
323                                 SET order_id = 0 WHERE owner_uid = ?");
324                 $sth->execute([$_SESSION['uid']]);
325         }
326
327         private function process_category_order(&$data_map, $item_id, $parent_id = false, $nest_level = 0) {
328                 $debug = isset($_REQUEST["debug"]);
329
330                 $prefix = "";
331                 for ($i = 0; $i < $nest_level; $i++)
332                         $prefix .= "   ";
333
334                 if ($debug) _debug("$prefix C: $item_id P: $parent_id");
335
336                 $bare_item_id = substr($item_id, strpos($item_id, ':')+1);
337
338                 if ($item_id != 'root') {
339                         if ($parent_id && $parent_id != 'root') {
340                                 $parent_bare_id = substr($parent_id, strpos($parent_id, ':')+1);
341                                 $parent_qpart = $parent_bare_id;
342                         } else {
343                                 $parent_qpart = null;
344                         }
345
346                         $sth = $this->pdo->prepare("UPDATE ttrss_feed_categories
347                                 SET parent_cat = ? WHERE id = ? AND
348                                 owner_uid = ?");
349                         $sth->execute([$parent_qpart, $bare_item_id, $_SESSION['uid']]);
350                 }
351
352                 $order_id = 1;
353
354                 $cat = $data_map[$item_id];
355
356                 if ($cat && is_array($cat)) {
357                         foreach ($cat as $item) {
358                                 $id = $item['_reference'];
359                                 $bare_id = substr($id, strpos($id, ':')+1);
360
361                                 if ($debug) _debug("$prefix [$order_id] $id/$bare_id");
362
363                                 if ($item['_reference']) {
364
365                                         if (strpos($id, "FEED") === 0) {
366
367                                                 $cat_id = ($item_id != "root") ? $bare_item_id : null;
368
369                                                 $sth = $this->pdo->prepare("UPDATE ttrss_feeds
370                                                         SET order_id = ?, cat_id = ?
371                                                         WHERE id = ? AND owner_uid = ?");
372
373                                                 $sth->execute([$order_id, $cat_id ? $cat_id : null, $bare_id, $_SESSION['uid']]);
374
375                                         } else if (strpos($id, "CAT:") === 0) {
376                                                 $this->process_category_order($data_map, $item['_reference'], $item_id,
377                                                         $nest_level+1);
378
379                                                 $sth = $this->pdo->prepare("UPDATE ttrss_feed_categories
380                                                                 SET order_id = ? WHERE id = ? AND
381                                                                 owner_uid = ?");
382                                                 $sth->execute([$order_id, $bare_id, $_SESSION['uid']]);
383                                         }
384                                 }
385
386                                 ++$order_id;
387                         }
388                 }
389         }
390
391         function savefeedorder() {
392                 $data = json_decode(clean($_POST['payload']), true);
393
394                 #file_put_contents("/tmp/saveorder.json", clean($_POST['payload']));
395                 #$data = json_decode(file_get_contents("/tmp/saveorder.json"), true);
396
397                 if (!is_array($data['items']))
398                         $data['items'] = json_decode($data['items'], true);
399
400 #               print_r($data['items']);
401
402                 if (is_array($data) && is_array($data['items'])) {
403 #                       $cat_order_id = 0;
404
405                         $data_map = array();
406                         $root_item = false;
407
408                         foreach ($data['items'] as $item) {
409
410 #                               if ($item['id'] != 'root') {
411                                         if (is_array($item['items'])) {
412                                                 if (isset($item['items']['_reference'])) {
413                                                         $data_map[$item['id']] = array($item['items']);
414                                                 } else {
415                                                         $data_map[$item['id']] = $item['items'];
416                                                 }
417                                         }
418                                 if ($item['id'] == 'root') {
419                                         $root_item = $item['id'];
420                                 }
421                         }
422
423                         $this->process_category_order($data_map, $root_item);
424                 }
425         }
426
427         function removeicon() {
428                 $feed_id = clean($_REQUEST["feed_id"]);
429
430                 $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
431                         WHERE id = ? AND owner_uid = ?");
432                 $sth->execute([$feed_id, $_SESSION['uid']]);
433
434                 if ($row = $sth->fetch()) {
435                         @unlink(ICONS_DIR . "/$feed_id.ico");
436
437                         $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET favicon_avg_color = NULL
438                                 where id = ?");
439                         $sth->execute([$feed_id]);
440                 }
441         }
442
443         function uploadicon() {
444                 header("Content-type: text/html");
445
446                 if (is_uploaded_file($_FILES['icon_file']['tmp_name'])) {
447                         $tmp_file = tempnam(CACHE_DIR . '/upload', 'icon');
448
449                         $result = move_uploaded_file($_FILES['icon_file']['tmp_name'],
450                                 $tmp_file);
451
452                         if (!$result) {
453                                 return;
454                         }
455                 } else {
456                         return;
457                 }
458
459                 $icon_file = $tmp_file;
460                 $feed_id = clean($_REQUEST["feed_id"]);
461
462                 if (is_file($icon_file) && $feed_id) {
463                         if (filesize($icon_file) < 65535) {
464
465                                 $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
466                                         WHERE id = ? AND owner_uid = ?");
467                                 $sth->execute([$feed_id, $_SESSION['uid']]);
468
469                                 if ($row = $sth->fetch()) {
470                                         @unlink(ICONS_DIR . "/$feed_id.ico");
471                                         if (rename($icon_file, ICONS_DIR . "/$feed_id.ico")) {
472
473                                                 $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET
474                                                         favicon_avg_color = ''
475                                                         WHERE id = ?");
476                                                 $sth->execute([$feed_id]);
477
478                                                 $rc = 0;
479                                         }
480                                 } else {
481                                         $rc = 2;
482                                 }
483                         } else {
484                                 $rc = 1;
485                         }
486                 } else {
487                         $rc = 2;
488                 }
489
490                 @unlink($icon_file);
491
492                 print "<script type=\"text/javascript\">";
493                 print "parent.uploadIconHandler($rc);";
494                 print "</script>";
495                 return;
496         }
497
498         function editfeed() {
499                 global $purge_intervals;
500                 global $update_intervals;
501
502
503                 $feed_id = clean($_REQUEST["id"]);
504
505                 $sth = $this->pdo->prepare("SELECT * FROM ttrss_feeds WHERE id = ? AND
506                                 owner_uid = ?");
507                 $sth->execute([$feed_id, $_SESSION['uid']]);
508
509                 if ($row = $sth->fetch()) {
510                         print '<div dojoType="dijit.layout.TabContainer" style="height : 450px">
511                         <div dojoType="dijit.layout.ContentPane" title="'.__('General').'">';
512
513                         $auth_pass_encrypted = $row["auth_pass_encrypted"];
514
515                         $title = htmlspecialchars($row["title"]);
516
517                         print_hidden("id", "$feed_id");
518                         print_hidden("op", "pref-feeds");
519                         print_hidden("method", "editSave");
520
521                         print "<div class=\"dlgSec\">".__("Feed")."</div>";
522                         print "<div class=\"dlgSecCont\">";
523
524                         /* Title */
525
526                         print "<input dojoType=\"dijit.form.ValidationTextBox\" required=\"1\"
527                         placeHolder=\"".__("Feed Title")."\"
528                         style=\"font-size : 16px; width: 20em\" name=\"title\" value=\"$title\">";
529
530                         /* Feed URL */
531
532                         $feed_url = htmlspecialchars($row["feed_url"]);
533
534                         print "<hr/>";
535
536                         print __('URL:') . " ";
537                         print "<input dojoType=\"dijit.form.ValidationTextBox\" required=\"1\"
538                         placeHolder=\"".__("Feed URL")."\"
539                         regExp='^(http|https)://.*' style=\"width : 20em\"
540                         name=\"feed_url\" value=\"$feed_url\">";
541
542                         $last_error = $row["last_error"];
543
544                         if ($last_error) {
545                                 print "&nbsp;<img src=\"images/error.png\" alt=\"(error)\"
546                                 style=\"vertical-align : middle\"
547                                 title=\"".htmlspecialchars($last_error)."\">";
548
549                         }
550
551                         /* Category */
552
553                         if (get_pref('ENABLE_FEED_CATS')) {
554
555                                 $cat_id = $row["cat_id"];
556
557                                 print "<hr/>";
558
559                                 print __('Place in category:') . " ";
560
561                                 print_feed_cat_select("cat_id", $cat_id,
562                                         'dojoType="dijit.form.Select"');
563                         }
564
565                         /* FTS Stemming Language */
566
567                         if (DB_TYPE == "pgsql") {
568                                 $feed_language = $row["feed_language"];
569
570                                 print "<hr/>";
571
572                                 print __('Language:') . " ";
573                                 print_select("feed_language", $feed_language, $this::$feed_languages,
574                                         'dojoType="dijit.form.Select"');
575                         }
576
577                         print "</div>";
578
579                         print "<div class=\"dlgSec\">".__("Update")."</div>";
580                         print "<div class=\"dlgSecCont\">";
581
582                         /* Update Interval */
583
584                         $update_interval = $row["update_interval"];
585
586                         print_select_hash("update_interval", $update_interval, $update_intervals,
587                                 'dojoType="dijit.form.Select"');
588
589                         /* Purge intl */
590
591                         $purge_interval = $row["purge_interval"];
592
593                         print "<hr/>";
594                         print __('Article purging:') . " ";
595
596                         print_select_hash("purge_interval", $purge_interval, $purge_intervals,
597                                 'dojoType="dijit.form.Select" ' .
598                                 ((FORCE_ARTICLE_PURGE == 0) ? "" : 'disabled="1"'));
599
600                         print "</div>";
601
602                         $auth_login = htmlspecialchars($row["auth_login"]);
603                         $auth_pass = $row["auth_pass"];
604
605                         if ($auth_pass_encrypted && function_exists("mcrypt_decrypt")) {
606                                 require_once "crypt.php";
607                                 $auth_pass = decrypt_string($auth_pass);
608                         }
609
610                         $auth_pass = htmlspecialchars($auth_pass);
611                         $auth_enabled = $auth_login !== '' || $auth_pass !== '';
612
613                         $auth_style = $auth_enabled ? '' : 'display: none';
614                         print "<div id='feedEditDlg_loginContainer' style='$auth_style'>";
615                         print "<div class=\"dlgSec\">".__("Authentication")."</div>";
616                         print "<div class=\"dlgSecCont\">";
617
618                         print "<input dojoType=\"dijit.form.TextBox\" id=\"feedEditDlg_login\"
619                         placeHolder=\"".__("Login")."\"
620                         autocomplete=\"new-password\"
621                         name=\"auth_login\" value=\"$auth_login\"><hr/>";
622
623
624                         print "<input dojoType=\"dijit.form.TextBox\" type=\"password\" name=\"auth_pass\"
625                         autocomplete=\"new-password\"
626                         placeHolder=\"".__("Password")."\"
627                         value=\"$auth_pass\">";
628
629                         print "<div dojoType=\"dijit.Tooltip\" connectId=\"feedEditDlg_login\" position=\"below\">
630                         ".__('<b>Hint:</b> you need to fill in your login information if your feed requires authentication, except for Twitter feeds.')."
631                         </div>";
632
633                         print "</div></div>";
634
635                         $auth_checked = $auth_enabled ? 'checked' : '';
636                         print "<div style=\"clear : both\">
637                                 <input type=\"checkbox\" $auth_checked name=\"need_auth\" dojoType=\"dijit.form.CheckBox\" id=\"feedEditDlg_loginCheck\"
638                                                 onclick='checkboxToggleElement(this, \"feedEditDlg_loginContainer\")'>
639                                         <label for=\"feedEditDlg_loginCheck\">".
640                                 __('This feed requires authentication.')."</div>";
641
642                         print '</div><div dojoType="dijit.layout.ContentPane" title="'.__('Options').'">';
643
644                         //print "<div class=\"dlgSec\">".__("Options")."</div>";
645                         print "<div class=\"dlgSecSimple\">";
646
647                         $private = $row["private"];
648
649                         if ($private) {
650                                 $checked = "checked=\"1\"";
651                         } else {
652                                 $checked = "";
653                         }
654
655                         print "<input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" name=\"private\" id=\"private\"
656                         $checked>&nbsp;<label for=\"private\">".__('Hide from Popular feeds')."</label>";
657
658                         $include_in_digest = $row["include_in_digest"];
659
660                         if ($include_in_digest) {
661                                 $checked = "checked=\"1\"";
662                         } else {
663                                 $checked = "";
664                         }
665
666                         print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"include_in_digest\"
667                         name=\"include_in_digest\"
668                         $checked>&nbsp;<label for=\"include_in_digest\">".__('Include in e-mail digest')."</label>";
669
670
671                         $always_display_enclosures = $row["always_display_enclosures"];
672
673                         if ($always_display_enclosures) {
674                                 $checked = "checked";
675                         } else {
676                                 $checked = "";
677                         }
678
679                         print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"always_display_enclosures\"
680                         name=\"always_display_enclosures\"
681                         $checked>&nbsp;<label for=\"always_display_enclosures\">".__('Always display image attachments')."</label>";
682
683                         $hide_images = $row["hide_images"];
684
685                         if ($hide_images) {
686                                 $checked = "checked=\"1\"";
687                         } else {
688                                 $checked = "";
689                         }
690
691                         print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"hide_images\"
692                 name=\"hide_images\"
693                         $checked>&nbsp;<label for=\"hide_images\">".
694                                 __('Do not embed images')."</label>";
695
696                         $cache_images = $row["cache_images"];
697
698                         if ($cache_images) {
699                                 $checked = "checked=\"1\"";
700                         } else {
701                                 $checked = "";
702                         }
703
704                         print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"cache_images\"
705                 name=\"cache_images\"
706                         $checked>&nbsp;<label for=\"cache_images\">".
707                                 __('Cache media')."</label>";
708
709                         $mark_unread_on_update = $row["mark_unread_on_update"];
710
711                         if ($mark_unread_on_update) {
712                                 $checked = "checked";
713                         } else {
714                                 $checked = "";
715                         }
716
717                         print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"mark_unread_on_update\"
718                         name=\"mark_unread_on_update\"
719                         $checked>&nbsp;<label for=\"mark_unread_on_update\">".__('Mark updated articles as unread')."</label>";
720
721                         print "</div>";
722
723                         print '</div><div dojoType="dijit.layout.ContentPane" title="'.__('Icon').'">';
724
725                         /* Icon */
726
727                         print "<div class=\"dlgSecSimple\">";
728
729                         print "<img class=\"feedIcon\" src=\"".Feeds::getFeedIcon($feed_id)."\">";
730
731                         print "<iframe name=\"icon_upload_iframe\"
732                                 style=\"width: 400px; height: 100px; display: none;\"></iframe>";
733
734                         print "<form style='display : block' target=\"icon_upload_iframe\"
735                         enctype=\"multipart/form-data\" method=\"POST\"
736                         action=\"backend.php\">
737                         <label class=\"dijitButton\">".__("Choose file...")."
738                                 <input style=\"display: none\" id=\"icon_file\" size=\"10\" name=\"icon_file\" type=\"file\">
739                         </label>
740                         <input type=\"hidden\" name=\"op\" value=\"pref-feeds\">
741                         <input type=\"hidden\" name=\"feed_id\" value=\"$feed_id\">
742                         <input type=\"hidden\" name=\"method\" value=\"uploadicon\">                    
743                         <button class=\"\" dojoType=\"dijit.form.Button\" onclick=\"return uploadFeedIcon();\"
744                                 type=\"submit\">".__('Replace')."</button>
745                         <button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick=\"return removeFeedIcon($feed_id);\"
746                                 type=\"submit\">".__('Remove')."</button>
747                         </form>";
748
749                         print "</div>";
750
751                         print '</div><div dojoType="dijit.layout.ContentPane" title="'.__('Plugins').'">';
752
753                         PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_EDIT_FEED,
754                                 "hook_prefs_edit_feed", $feed_id);
755
756
757                         print "</div></div>";
758
759                         $title = htmlspecialchars($title, ENT_QUOTES);
760
761                         print "<div class='dlgButtons'>
762                         <div style=\"float : left\">
763                         <button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick='return unsubscribeFeed($feed_id, \"$title\")'>".
764                                 __('Unsubscribe')."</button>";
765
766                         print "</div>";
767
768                         print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('feedEditDlg').execute()\">".__('Save')."</button>
769                                 <button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('feedEditDlg').hide()\">".__('Cancel')."</button>
770                                 </div>";
771                 }
772         }
773
774         function editfeeds() {
775                 global $purge_intervals;
776                 global $update_intervals;
777
778                 $feed_ids = clean($_REQUEST["ids"]);
779
780                 print_notice("Enable the options you wish to apply using checkboxes on the right:");
781
782                 print "<p>";
783
784                 print_hidden("ids", "$feed_ids");
785                 print_hidden("op", "pref-feeds");
786                 print_hidden("method", "batchEditSave");
787
788                 print "<div class=\"dlgSec\">".__("Feed")."</div>";
789                 print "<div class=\"dlgSecCont\">";
790
791                 /* Category */
792
793                 if (get_pref('ENABLE_FEED_CATS')) {
794
795                         print __('Place in category:') . " ";
796
797                         print_feed_cat_select("cat_id", false,
798                                 'disabled="1" dojoType="dijit.form.Select"');
799
800                         $this->batch_edit_cbox("cat_id");
801
802                 }
803
804                 /* FTS Stemming Language */
805
806                 if (DB_TYPE == "pgsql") {
807                         print "<hr/>";
808
809                         print __('Language:') . " ";
810                         print_select("feed_language", "", $this::$feed_languages,
811                                 'disabled="1" dojoType="dijit.form.Select"');
812
813                         $this->batch_edit_cbox("feed_language");
814                 }
815
816                 print "</div>";
817
818                 print "<div class=\"dlgSec\">".__("Update")."</div>";
819                 print "<div class=\"dlgSecCont\">";
820
821                 /* Update Interval */
822
823                 print_select_hash("update_interval", "", $update_intervals,
824                         'disabled="1" dojoType="dijit.form.Select"');
825
826                 $this->batch_edit_cbox("update_interval");
827
828                 /* Purge intl */
829
830                 if (FORCE_ARTICLE_PURGE == 0) {
831
832                         print "<br/>";
833
834                         print __('Article purging:') . " ";
835
836                         print_select_hash("purge_interval", "", $purge_intervals,
837                                 'disabled="1" dojoType="dijit.form.Select"');
838
839                         $this->batch_edit_cbox("purge_interval");
840                 }
841
842                 print "</div>";
843                 print "<div class=\"dlgSec\">".__("Authentication")."</div>";
844                 print "<div class=\"dlgSecCont\">";
845
846                 print "<input dojoType=\"dijit.form.TextBox\"
847                         placeHolder=\"".__("Login")."\" disabled=\"1\"
848                         autocomplete=\"new-password\"
849                         name=\"auth_login\" value=\"\">";
850
851                 $this->batch_edit_cbox("auth_login");
852
853                 print "<hr/> <input dojoType=\"dijit.form.TextBox\" type=\"password\" name=\"auth_pass\"
854                         autocomplete=\"new-password\"
855                         placeHolder=\"".__("Password")."\" disabled=\"1\"
856                         value=\"\">";
857
858                 $this->batch_edit_cbox("auth_pass");
859
860                 print "</div>";
861                 print "<div class=\"dlgSec\">".__("Options")."</div>";
862                 print "<div class=\"dlgSecCont\">";
863
864                 print "<input disabled=\"1\" type=\"checkbox\" name=\"private\" id=\"private\"
865                         dojoType=\"dijit.form.CheckBox\">&nbsp;<label id=\"private_l\" class='insensitive' for=\"private\">".__('Hide from Popular feeds')."</label>";
866
867                 print "&nbsp;"; $this->batch_edit_cbox("private", "private_l");
868
869                 print "<br/><input disabled=\"1\" type=\"checkbox\" id=\"include_in_digest\"
870                         name=\"include_in_digest\"
871                         dojoType=\"dijit.form.CheckBox\">&nbsp;<label id=\"include_in_digest_l\" class='insensitive' for=\"include_in_digest\">".__('Include in e-mail digest')."</label>";
872
873                 print "&nbsp;"; $this->batch_edit_cbox("include_in_digest", "include_in_digest_l");
874
875                 print "<br/><input disabled=\"1\" type=\"checkbox\" id=\"always_display_enclosures\"
876                         name=\"always_display_enclosures\"
877                         dojoType=\"dijit.form.CheckBox\">&nbsp;<label id=\"always_display_enclosures_l\" class='insensitive' for=\"always_display_enclosures\">".__('Always display image attachments')."</label>";
878
879                 print "&nbsp;"; $this->batch_edit_cbox("always_display_enclosures", "always_display_enclosures_l");
880
881                 print "<br/><input disabled=\"1\" type=\"checkbox\" id=\"hide_images\"
882                         name=\"hide_images\"
883                         dojoType=\"dijit.form.CheckBox\">&nbsp;<label class='insensitive' id=\"hide_images_l\"
884                         for=\"hide_images\">".
885                 __('Do not embed images')."</label>";
886
887                 print "&nbsp;"; $this->batch_edit_cbox("hide_images", "hide_images_l");
888
889                 print "<br/><input disabled=\"1\" type=\"checkbox\" id=\"cache_images\"
890                         name=\"cache_images\"
891                         dojoType=\"dijit.form.CheckBox\">&nbsp;<label class='insensitive' id=\"cache_images_l\"
892                         for=\"cache_images\">".
893                 __('Cache media')."</label>";
894
895                 print "&nbsp;"; $this->batch_edit_cbox("cache_images", "cache_images_l");
896
897                 print "<br/><input disabled=\"1\" type=\"checkbox\" id=\"mark_unread_on_update\"
898                         name=\"mark_unread_on_update\"
899                         dojoType=\"dijit.form.CheckBox\">&nbsp;<label id=\"mark_unread_on_update_l\" class='insensitive' for=\"mark_unread_on_update\">".__('Mark updated articles as unread')."</label>";
900
901                 print "&nbsp;"; $this->batch_edit_cbox("mark_unread_on_update", "mark_unread_on_update_l");
902
903                 print "</div>";
904
905                 print "<div class='dlgButtons'>
906                         <button dojoType=\"dijit.form.Button\"
907                                 onclick=\"return dijit.byId('feedEditDlg').execute()\">".
908                                 __('Save')."</button>
909                         <button dojoType=\"dijit.form.Button\"
910                         onclick=\"return dijit.byId('feedEditDlg').hide()\">".
911                                 __('Cancel')."</button>
912                         </div>";
913
914                 return;
915         }
916
917         function batchEditSave() {
918                 return $this->editsaveops(true);
919         }
920
921         function editSave() {
922                 return $this->editsaveops(false);
923         }
924
925         function editsaveops($batch) {
926
927                 $feed_title = trim(clean($_POST["title"]));
928                 $feed_url = trim(clean($_POST["feed_url"]));
929                 $upd_intl = (int) clean($_POST["update_interval"]);
930                 $purge_intl = (int) clean($_POST["purge_interval"]);
931                 $feed_id = (int) clean($_POST["id"]); /* editSave */
932                 $feed_ids = explode(",", clean($_POST["ids"])); /* batchEditSave */
933                 $cat_id = (int) clean($_POST["cat_id"]);
934                 $auth_login = trim(clean($_POST["auth_login"]));
935                 $auth_pass = trim(clean($_POST["auth_pass"]));
936                 $private = checkbox_to_sql_bool(clean($_POST["private"]));
937                 $include_in_digest = checkbox_to_sql_bool(
938                         clean($_POST["include_in_digest"]));
939                 $cache_images = checkbox_to_sql_bool(
940                         clean($_POST["cache_images"]));
941                 $hide_images = checkbox_to_sql_bool(
942                         clean($_POST["hide_images"]));
943                 $always_display_enclosures = checkbox_to_sql_bool(
944                         clean($_POST["always_display_enclosures"]));
945
946                 $mark_unread_on_update = checkbox_to_sql_bool(
947                         clean($_POST["mark_unread_on_update"]));
948
949                 $feed_language = trim(clean($_POST["feed_language"]));
950
951                 if (!$batch) {
952                         if (clean($_POST["need_auth"]) !== 'on') {
953                                 $auth_login = '';
954                                 $auth_pass = '';
955                         }
956
957                         $sth = $this->pdo->prepare("SELECT feed_url FROM ttrss_feeds WHERE id = ?");
958                         $sth->execute([$feed_id]);
959                         $row = $sth->fetch();
960                         $orig_feed_url = $row["feed_url"];
961
962                         $reset_basic_info = $orig_feed_url != $feed_url;
963
964                         $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET
965                                 cat_id = :cat_id,
966                                 title = :title, 
967                                 feed_url = :feed_url,
968                                 update_interval = :upd_intl,
969                                 purge_interval = :purge_intl,
970                                 auth_login = :auth_login,
971                                 auth_pass = :auth_pass,
972                                 auth_pass_encrypted = false,
973                                 private = :private,
974                                 cache_images = :cache_images,
975                                 hide_images = :hide_images,
976                                 include_in_digest = :include_in_digest,
977                                 always_display_enclosures = :always_display_enclosures,
978                                 mark_unread_on_update = :mark_unread_on_update,
979                                 feed_language = :feed_language
980                         WHERE id = :id AND owner_uid = :uid");
981
982                         $sth->execute([":title" => $feed_title,
983                                         ":cat_id" => $cat_id ? $cat_id : null,
984                                         ":feed_url" => $feed_url,
985                                         ":upd_intl" => $upd_intl,
986                                         ":purge_intl" => $purge_intl,
987                                         ":auth_login" => $auth_login,
988                                         ":auth_pass" => $auth_pass,
989                                         ":private" => (int)$private,
990                                         ":cache_images" => (int)$cache_images,
991                                         ":hide_images" => (int)$hide_images,
992                                         ":include_in_digest" => (int)$include_in_digest,
993                                         ":always_display_enclosures" => (int)$always_display_enclosures,
994                                         ":mark_unread_on_update" => (int)$mark_unread_on_update,
995                                         ":feed_language" => $feed_language,
996                                         ":id" => $feed_id,
997                                         ":uid" => $_SESSION['uid']]);
998
999                         if ($reset_basic_info) {
1000                                 RSSUtils::set_basic_feed_info($feed_id);
1001                         }
1002
1003                         PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_SAVE_FEED,
1004                                 "hook_prefs_save_feed", $feed_id);
1005
1006                 } else {
1007                         $feed_data = array();
1008
1009                         foreach (array_keys($_POST) as $k) {
1010                                 if ($k != "op" && $k != "method" && $k != "ids") {
1011                                         $feed_data[$k] = clean($_POST[$k]);
1012                                 }
1013                         }
1014
1015                         $this->pdo->beginTransaction();
1016
1017                         $feed_ids_qmarks = arr_qmarks($feed_ids);
1018
1019                         foreach (array_keys($feed_data) as $k) {
1020
1021                                 $qpart = "";
1022
1023                                 switch ($k) {
1024                                         case "title":
1025                                                 $qpart = "title = " . $this->pdo->quote($feed_title);
1026                                                 break;
1027
1028                                         case "feed_url":
1029                                                 $qpart = "feed_url = " . $this->pdo->quote($feed_url);
1030                                                 break;
1031
1032                                         case "update_interval":
1033                                                 $qpart = "update_interval = " . $this->pdo->quote($upd_intl);
1034                                                 break;
1035
1036                                         case "purge_interval":
1037                                                 $qpart = "purge_interval =" . $this->pdo->quote($purge_intl);
1038                                                 break;
1039
1040                                         case "auth_login":
1041                                                 $qpart = "auth_login = " . $this->pdo->quote($auth_login);
1042                                                 break;
1043
1044                                         case "auth_pass":
1045                                                 $qpart = "auth_pass =" . $this->pdo->quote($auth_pass). ", auth_pass_encrypted = false";
1046                                                 break;
1047
1048                                         case "private":
1049                                                 $qpart = "private = " . $this->pdo->quote($private);
1050                                                 break;
1051
1052                                         case "include_in_digest":
1053                                                 $qpart = "include_in_digest = " . $this->pdo->quote($include_in_digest);
1054                                                 break;
1055
1056                                         case "always_display_enclosures":
1057                                                 $qpart = "always_display_enclosures = " . $this->pdo->quote($always_display_enclosures);
1058                                                 break;
1059
1060                                         case "mark_unread_on_update":
1061                                                 $qpart = "mark_unread_on_update = " . $this->pdo->quote($mark_unread_on_update);
1062                                                 break;
1063
1064                                         case "cache_images":
1065                                                 $qpart = "cache_images = " . $this->pdo->quote($cache_images);
1066                                                 break;
1067
1068                                         case "hide_images":
1069                                                 $qpart = "hide_images = " . $this->pdo->quote($hide_images);
1070                                                 break;
1071
1072                                         case "cat_id":
1073                                                 if (get_pref('ENABLE_FEED_CATS')) {
1074                                                         if ($cat_id) {
1075                                                                 $qpart = "cat_id = " . $this->pdo->quote($cat_id);
1076                                                         } else {
1077                                                                 $qpart = 'cat_id = NULL';
1078                                                         }
1079                                                 } else {
1080                                                         $qpart = "";
1081                                                 }
1082
1083                                                 break;
1084
1085                                         case "feed_language":
1086                                                 $qpart = "feed_language = " . $this->pdo->quote($feed_language);
1087                                                 break;
1088
1089                                 }
1090
1091                                 if ($qpart) {
1092                                         $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET $qpart WHERE id IN ($feed_ids_qmarks)
1093                                                 AND owner_uid = ?");
1094                                         $sth->execute(array_merge($feed_ids, [$_SESSION['uid']]));
1095                                 }
1096                         }
1097
1098                         $this->pdo->commit();
1099                 }
1100                 return;
1101         }
1102
1103         function remove() {
1104
1105                 $ids = explode(",", clean($_REQUEST["ids"]));
1106
1107                 foreach ($ids as $id) {
1108                         Pref_Feeds::remove_feed($id, $_SESSION["uid"]);
1109                 }
1110
1111                 return;
1112         }
1113
1114         function removeCat() {
1115                 $ids = explode(",", clean($_REQUEST["ids"]));
1116                 foreach ($ids as $id) {
1117                         $this->remove_feed_category($id, $_SESSION["uid"]);
1118                 }
1119         }
1120
1121         function addCat() {
1122                 $feed_cat = trim(clean($_REQUEST["cat"]));
1123
1124                 add_feed_category($feed_cat);
1125         }
1126
1127         function index() {
1128
1129                 print "<div dojoType=\"dijit.layout.AccordionContainer\" region=\"center\">";
1130                 print "<div id=\"pref-feeds-feeds\" dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Feeds')."\">";
1131
1132                 $sth = $this->pdo->prepare("SELECT COUNT(id) AS num_errors
1133                         FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?");
1134                 $sth->execute([$_SESSION['uid']]);
1135
1136                 if ($row = $sth->fetch()) {
1137                         $num_errors = $row["num_errors"];
1138                 } else {
1139                         $num_errors = 0;
1140                 }
1141
1142                 if ($num_errors > 0) {
1143
1144                         $error_button = "<button dojoType=\"dijit.form.Button\"
1145                                         onclick=\"showFeedsWithErrors()\" id=\"errorButton\">" .
1146                                 __("Feeds with errors") . "</button>";
1147                 }
1148
1149                 $inactive_button = "<button dojoType=\"dijit.form.Button\"
1150                                 id=\"pref_feeds_inactive_btn\"
1151                                 style=\"display : none\"
1152                                 onclick=\"showInactiveFeeds()\">" .
1153                                 __("Inactive feeds") . "</button>";
1154
1155                 $feed_search = clean($_REQUEST["search"]);
1156
1157                 if (array_key_exists("search", $_REQUEST)) {
1158                         $_SESSION["prefs_feed_search"] = $feed_search;
1159                 } else {
1160                         $feed_search = $_SESSION["prefs_feed_search"];
1161                 }
1162
1163                 print '<div dojoType="dijit.layout.BorderContainer" gutters="false">';
1164
1165                 print "<div region='top' dojoType=\"dijit.Toolbar\">"; #toolbar
1166
1167                 print "<div style='float : right; padding-right : 4px;'>
1168                         <input dojoType=\"dijit.form.TextBox\" id=\"feed_search\" size=\"20\" type=\"search\"
1169                                 value=\"$feed_search\">
1170                         <button dojoType=\"dijit.form.Button\" onclick=\"updateFeedList()\">".
1171                                 __('Search')."</button>
1172                         </div>";
1173
1174                 print "<div dojoType=\"dijit.form.DropDownButton\">".
1175                                 "<span>" . __('Select')."</span>";
1176                 print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
1177                 print "<div onclick=\"dijit.byId('feedTree').model.setAllChecked(true)\"
1178                         dojoType=\"dijit.MenuItem\">".__('All')."</div>";
1179                 print "<div onclick=\"dijit.byId('feedTree').model.setAllChecked(false)\"
1180                         dojoType=\"dijit.MenuItem\">".__('None')."</div>";
1181                 print "</div></div>";
1182
1183                 print "<div dojoType=\"dijit.form.DropDownButton\">".
1184                                 "<span>" . __('Feeds')."</span>";
1185                 print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
1186                 print "<div onclick=\"quickAddFeed()\"
1187                         dojoType=\"dijit.MenuItem\">".__('Subscribe to feed')."</div>";
1188                 print "<div onclick=\"editSelectedFeed()\"
1189                         dojoType=\"dijit.MenuItem\">".__('Edit selected feeds')."</div>";
1190                 print "<div onclick=\"resetFeedOrder()\"
1191                         dojoType=\"dijit.MenuItem\">".__('Reset sort order')."</div>";
1192                 print "<div onclick=\"batchSubscribe()\"
1193                         dojoType=\"dijit.MenuItem\">".__('Batch subscribe')."</div>";
1194                 print "<div dojoType=\"dijit.MenuItem\" onclick=\"removeSelectedFeeds()\">"
1195                         .__('Unsubscribe')."</div> ";
1196                 print "</div></div>";
1197
1198                 if (get_pref('ENABLE_FEED_CATS')) {
1199                         print "<div dojoType=\"dijit.form.DropDownButton\">".
1200                                         "<span>" . __('Categories')."</span>";
1201                         print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
1202                         print "<div onclick=\"createCategory()\"
1203                                 dojoType=\"dijit.MenuItem\">".__('Add category')."</div>";
1204                         print "<div onclick=\"resetCatOrder()\"
1205                                 dojoType=\"dijit.MenuItem\">".__('Reset sort order')."</div>";
1206                         print "<div onclick=\"removeSelectedCategories()\"
1207                                 dojoType=\"dijit.MenuItem\">".__('Remove selected')."</div>";
1208                         print "</div></div>";
1209
1210                 }
1211
1212                 print $error_button;
1213                 print $inactive_button;
1214
1215                 print "</div>"; # toolbar
1216
1217                 //print '</div>';
1218                 print '<div dojoType="dijit.layout.ContentPane" region="center">';
1219
1220                 print "<div id=\"feedlistLoading\">
1221                 <img src='images/indicator_tiny.gif'>".
1222                  __("Loading, please wait...")."</div>";
1223
1224                 print "<div dojoType=\"fox.PrefFeedStore\" jsId=\"feedStore\"
1225                         url=\"backend.php?op=pref-feeds&method=getfeedtree\">
1226                 </div>
1227                 <div dojoType=\"lib.CheckBoxStoreModel\" jsId=\"feedModel\" store=\"feedStore\"
1228                 query=\"{id:'root'}\" rootId=\"root\" rootLabel=\"Feeds\"
1229                         childrenAttrs=\"items\" checkboxStrict=\"false\" checkboxAll=\"false\">
1230                 </div>
1231                 <div dojoType=\"fox.PrefFeedTree\" id=\"feedTree\"
1232                         dndController=\"dijit.tree.dndSource\"
1233                         betweenThreshold=\"5\"
1234                         autoExpand='true'
1235                         model=\"feedModel\" openOnClick=\"false\">
1236                 <script type=\"dojo/method\" event=\"onClick\" args=\"item\">
1237                         var id = String(item.id);
1238                         var bare_id = id.substr(id.indexOf(':')+1);
1239
1240                         if (id.match('FEED:')) {
1241                                 editFeed(bare_id);
1242                         } else if (id.match('CAT:')) {
1243                                 editCat(bare_id, item);
1244                         }
1245                 </script>
1246                 <script type=\"dojo/method\" event=\"onLoad\" args=\"item\">
1247                         Element.hide(\"feedlistLoading\");
1248
1249                         checkInactiveFeeds();
1250                 </script>
1251                 </div>";
1252
1253 #               print "<div dojoType=\"dijit.Tooltip\" connectId=\"feedTree\" position=\"below\">
1254 #                       ".__('<b>Hint:</b> you can drag feeds and categories around.')."
1255 #                       </div>";
1256
1257                 print '</div>';
1258                 print '</div>';
1259
1260                 print "</div>"; # feeds pane
1261
1262                 print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('OPML')."\">";
1263
1264                 print "<p>" . __("Using OPML you can export and import your feeds, filters, labels and Tiny Tiny RSS settings.") .
1265                         __("Only main settings profile can be migrated using OPML.") . "</p>";
1266
1267                 print "<iframe id=\"upload_iframe\"
1268                         name=\"upload_iframe\" onload=\"opmlImportComplete(this)\"
1269                         style=\"width: 400px; height: 100px; display: none;\"></iframe>";
1270
1271                 print "<form  name=\"opml_form\" style='display : block' target=\"upload_iframe\"
1272                         enctype=\"multipart/form-data\" method=\"POST\"
1273                         action=\"backend.php\">
1274                         <label class=\"dijitButton\">".__("Choose file...")."
1275                                 <input style=\"display : none\" id=\"opml_file\" name=\"opml_file\" type=\"file\">&nbsp;
1276                         </label>
1277                         <input type=\"hidden\" name=\"op\" value=\"dlg\">
1278                         <input type=\"hidden\" name=\"method\" value=\"importOpml\">
1279                         <button dojoType=\"dijit.form.Button\" onclick=\"return opmlImport();\" type=\"submit\">" .
1280                         __('Import my OPML') . "</button>";
1281
1282                 print "<hr>";
1283
1284                 $opml_export_filename = "TinyTinyRSS_".date("Y-m-d").".opml";
1285
1286                 print "<p>" . __('Filename:') .
1287             " <input class=\"input input-text\" type=\"text\" id=\"filename\" value=\"$opml_export_filename\" />&nbsp;" .
1288                                 __('Include settings') . "<input type=\"checkbox\" id=\"settings\" checked=\"1\"/>";
1289
1290                 print "</p><button dojoType=\"dijit.form.Button\"
1291                         onclick=\"gotoExportOpml(document.opml_form.filename.value, document.opml_form.settings.checked)\" >" .
1292               __('Export OPML') . "</button></p></form>";
1293
1294                 print "<hr>";
1295
1296                 print "<p>" . __('Your OPML can be published publicly and can be subscribed by anyone who knows the URL below.') . "</p>";
1297
1298                 print_warning("Published OPML does not include your Tiny Tiny RSS settings, feeds that require authentication or feeds hidden from Popular feeds.");
1299
1300                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return displayDlg('".__("Public OPML URL")."','pubOPMLUrl')\">".
1301                         __('Display published OPML URL')."</button> ";
1302
1303                 PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION,
1304                         "hook_prefs_tab_section", "prefFeedsOPML");
1305
1306                 print "</div>"; # pane
1307
1308                 if (strpos($_SERVER['HTTP_USER_AGENT'], "Firefox") !== false) {
1309
1310                         print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Firefox integration')."\">";
1311
1312                         print_notice(__('This Tiny Tiny RSS site can be used as a Firefox Feed Reader by clicking the link below.'));
1313
1314                         print "<p>";
1315
1316                         print "<button onclick='window.navigator.registerContentHandler(" .
1317                       "\"application/vnd.mozilla.maybe.feed\", " .
1318                       "\"" . $this->subscribe_to_feed_url() . "\", " . " \"Tiny Tiny RSS\")'>" .
1319                                                          __('Click here to register this site as a feed reader.') .
1320                                 "</button>";
1321
1322                         print "</p>";
1323
1324                         print "</div>"; # pane
1325                 }
1326
1327                 print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Published & shared articles / Generated feeds')."\">";
1328
1329                 print "<p>" . __('Published articles are exported as a public RSS feed and can be subscribed by anyone who knows the URL specified below.') . "</p>";
1330
1331                 $rss_url = '-2::' . htmlspecialchars(get_self_url_prefix() .
1332                                 "/public.php?op=rss&id=-2&view-mode=all_articles");;
1333
1334                 print "<p>";
1335
1336                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return displayDlg('".__("View as RSS")."','generatedFeed', '$rss_url')\">".
1337                         __('Display URL')."</button> ";
1338
1339                 print "<button class=\"warning\" dojoType=\"dijit.form.Button\" onclick=\"return clearFeedAccessKeys()\">".
1340                         __('Clear all generated URLs')."</button> ";
1341
1342                 print "</p>";
1343
1344                 PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION,
1345                         "hook_prefs_tab_section", "prefFeedsPublishedGenerated");
1346
1347                 print "</div>"; #pane
1348
1349                 PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB,
1350                         "hook_prefs_tab", "prefFeeds");
1351
1352                 print "</div>"; #container
1353         }
1354
1355         private function feedlist_init_cat($cat_id) {
1356                 $obj = array();
1357                 $cat_id = (int) $cat_id;
1358
1359                 if ($cat_id > 0) {
1360                         $cat_unread = CCache::find($cat_id, $_SESSION["uid"], true);
1361                 } else if ($cat_id == 0 || $cat_id == -2) {
1362                         $cat_unread = Feeds::getCategoryUnread($cat_id);
1363                 }
1364
1365                 $obj['id'] = 'CAT:' . $cat_id;
1366                 $obj['items'] = array();
1367                 $obj['name'] = Feeds::getCategoryTitle($cat_id);
1368                 $obj['type'] = 'category';
1369                 $obj['unread'] = (int) $cat_unread;
1370                 $obj['bare_id'] = $cat_id;
1371
1372                 return $obj;
1373         }
1374
1375         private function feedlist_init_feed($feed_id, $title = false, $unread = false, $error = '', $updated = '') {
1376                 $obj = array();
1377                 $feed_id = (int) $feed_id;
1378
1379                 if (!$title)
1380                         $title = Feeds::getFeedTitle($feed_id, false);
1381
1382                 if ($unread === false)
1383                         $unread = getFeedUnread($feed_id, false);
1384
1385                 $obj['id'] = 'FEED:' . $feed_id;
1386                 $obj['name'] = $title;
1387                 $obj['unread'] = (int) $unread;
1388                 $obj['type'] = 'feed';
1389                 $obj['error'] = $error;
1390                 $obj['updated'] = $updated;
1391                 $obj['icon'] = Feeds::getFeedIcon($feed_id);
1392                 $obj['bare_id'] = $feed_id;
1393                 $obj['auxcounter'] = 0;
1394
1395                 return $obj;
1396         }
1397
1398         function inactiveFeeds() {
1399
1400                 if (DB_TYPE == "pgsql") {
1401                         $interval_qpart = "NOW() - INTERVAL '3 months'";
1402                 } else {
1403                         $interval_qpart = "DATE_SUB(NOW(), INTERVAL 3 MONTH)";
1404                 }
1405
1406                 $sth = $this->pdo->prepare("SELECT ttrss_feeds.title, ttrss_feeds.site_url,
1407                                 ttrss_feeds.feed_url, ttrss_feeds.id, MAX(updated) AS last_article
1408                         FROM ttrss_feeds, ttrss_entries, ttrss_user_entries WHERE
1409                                 (SELECT MAX(updated) FROM ttrss_entries, ttrss_user_entries WHERE
1410                                         ttrss_entries.id = ref_id AND
1411                                                 ttrss_user_entries.feed_id = ttrss_feeds.id) < $interval_qpart
1412                         AND ttrss_feeds.owner_uid = ? AND
1413                                 ttrss_user_entries.feed_id = ttrss_feeds.id AND
1414                                 ttrss_entries.id = ref_id
1415                         GROUP BY ttrss_feeds.title, ttrss_feeds.id, ttrss_feeds.site_url, ttrss_feeds.feed_url
1416                         ORDER BY last_article");
1417                 $sth->execute([$_SESSION['uid']]);
1418
1419                 print "<p" .__("These feeds have not been updated with new content for 3 months (oldest first):") . "</p>";
1420
1421                 print "<div dojoType=\"dijit.Toolbar\">";
1422                 print "<div dojoType=\"dijit.form.DropDownButton\">".
1423                                 "<span>" . __('Select')."</span>";
1424                 print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
1425                 print "<div onclick=\"selectTableRows('prefInactiveFeedList', 'all')\"
1426                         dojoType=\"dijit.MenuItem\">".__('All')."</div>";
1427                 print "<div onclick=\"selectTableRows('prefInactiveFeedList', 'none')\"
1428                         dojoType=\"dijit.MenuItem\">".__('None')."</div>";
1429                 print "</div></div>";
1430                 print "</div>"; #toolbar
1431
1432                 print "<div class=\"inactiveFeedHolder\">";
1433
1434                 print "<table width=\"100%\" cellspacing=\"0\" id=\"prefInactiveFeedList\">";
1435
1436                 $lnum = 1;
1437
1438                 while ($line = $sth->fetch()) {
1439
1440                         $feed_id = $line["id"];
1441                         $this_row_id = "id=\"FUPDD-$feed_id\"";
1442
1443                         # class needed for selectTableRows()
1444                         print "<tr class=\"placeholder\" $this_row_id>";
1445
1446                         # id needed for selectTableRows()
1447                         print "<td width='5%' align='center'><input
1448                                 onclick='toggleSelectRow2(this);' dojoType=\"dijit.form.CheckBox\"
1449                                 type=\"checkbox\" id=\"FUPDC-$feed_id\"></td>";
1450                         print "<td>";
1451
1452                         print "<a class=\"visibleLink\" href=\"#\" ".
1453                                 "title=\"".__("Click to edit feed")."\" ".
1454                                 "onclick=\"editFeed(".$line["id"].")\">".
1455                                 htmlspecialchars($line["title"])."</a>";
1456
1457                         print "</td><td class=\"insensitive\" align='right'>";
1458                         print make_local_datetime($line['last_article'], false);
1459                         print "</td>";
1460                         print "</tr>";
1461
1462                         ++$lnum;
1463                 }
1464
1465                 print "</table>";
1466                 print "</div>";
1467
1468                 print "<div class='dlgButtons'>";
1469                 print "<div style='float : left'>";
1470                 print "<button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('inactiveFeedsDlg').removeSelected()\">"
1471                         .__('Unsubscribe from selected feeds')."</button> ";
1472                 print "</div>";
1473
1474                 print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('inactiveFeedsDlg').hide()\">".
1475                         __('Close this window')."</button>";
1476
1477                 print "</div>";
1478
1479         }
1480
1481         function feedsWithErrors() {
1482                 $sth = $this->pdo->prepare("SELECT id,title,feed_url,last_error,site_url
1483                         FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?");
1484                 $sth->execute([$_SESSION['uid']]);
1485
1486                 print "<div dojoType=\"dijit.Toolbar\">";
1487                 print "<div dojoType=\"dijit.form.DropDownButton\">".
1488                                 "<span>" . __('Select')."</span>";
1489                 print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
1490                 print "<div onclick=\"selectTableRows('prefErrorFeedList', 'all')\"
1491                         dojoType=\"dijit.MenuItem\">".__('All')."</div>";
1492                 print "<div onclick=\"selectTableRows('prefErrorFeedList', 'none')\"
1493                         dojoType=\"dijit.MenuItem\">".__('None')."</div>";
1494                 print "</div></div>";
1495                 print "</div>"; #toolbar
1496
1497                 print "<div class=\"inactiveFeedHolder\">";
1498
1499                 print "<table width=\"100%\" cellspacing=\"0\" id=\"prefErrorFeedList\">";
1500
1501                 $lnum = 1;
1502
1503                 while ($line = $sth->fetch()) {
1504
1505                         $feed_id = $line["id"];
1506                         $this_row_id = "id=\"FERDD-$feed_id\"";
1507
1508                         # class needed for selectTableRows()
1509                         print "<tr class=\"placeholder\" $this_row_id>";
1510
1511                         # id needed for selectTableRows()
1512                         print "<td width='5%' align='center'><input
1513                                 onclick='toggleSelectRow2(this);' dojoType=\"dijit.form.CheckBox\"
1514                                 type=\"checkbox\" id=\"FERDC-$feed_id\"></td>";
1515                         print "<td>";
1516
1517                         print "<a class=\"visibleLink\" href=\"#\" ".
1518                                 "title=\"".__("Click to edit feed")."\" ".
1519                                 "onclick=\"editFeed(".$line["id"].")\">".
1520                                 htmlspecialchars($line["title"])."</a>: ";
1521
1522                         print "<span class=\"insensitive\">";
1523                         print htmlspecialchars($line["last_error"]);
1524                         print "</span>";
1525
1526                         print "</td>";
1527                         print "</tr>";
1528
1529                         ++$lnum;
1530                 }
1531
1532                 print "</table>";
1533                 print "</div>";
1534
1535                 print "<div class='dlgButtons'>";
1536                 print "<div style='float : left'>";
1537                 print "<button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('errorFeedsDlg').removeSelected()\">"
1538                         .__('Unsubscribe from selected feeds')."</button> ";
1539                 print "</div>";
1540
1541                 print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('errorFeedsDlg').hide()\">".
1542                         __('Close this window')."</button>";
1543
1544                 print "</div>";
1545         }
1546
1547         private function remove_feed_category($id, $owner_uid) {
1548
1549                 $sth = $this->pdo->prepare("DELETE FROM ttrss_feed_categories
1550                         WHERE id = ? AND owner_uid = ?");
1551                 $sth->execute([$id, $owner_uid]);
1552
1553                 CCache::remove($id, $owner_uid, true);
1554         }
1555
1556         static function remove_feed($id, $owner_uid) {
1557                 $debug = isset($_REQUEST["debug"]);
1558
1559                 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_UNSUBSCRIBE_FEED) as $p) {
1560                         if( ! $p->hook_unsubscribe_feed($id, $owner_uid)){
1561                                         if($debug) _debug("Feed not removed due to Error in Plugin. (HOOK_UNSUBSCRIBE_FEED)");
1562                                         return;
1563                         }
1564                 }
1565
1566                 $pdo = Db::pdo();
1567
1568                 if ($id > 0) {
1569                         $pdo->beginTransaction();
1570
1571                         /* save starred articles in Archived feed */
1572
1573                         /* prepare feed if necessary */
1574
1575                         $sth = $pdo->prepare("SELECT feed_url FROM ttrss_feeds WHERE id = ?
1576                                 AND owner_uid = ?");
1577                         $sth->execute([$id, $owner_uid]);
1578
1579                         if ($row = $sth->fetch()) {
1580                                 $feed_url = $row["feed_url"];
1581
1582                                 $sth = $pdo->prepare("SELECT id FROM ttrss_archived_feeds
1583                                         WHERE feed_url = ? AND owner_uid = ?");
1584                                 $sth->execute([$feed_url, $owner_uid]);
1585
1586                                 if ($row = $sth->fetch()) {
1587                                         $archive_id = $row["id"];
1588                                 } else {
1589                                         $res = $pdo->query("SELECT MAX(id) AS id FROM ttrss_archived_feeds");
1590                                         $row = $res->fetch();
1591
1592                                         $new_feed_id = (int)$row['id'] + 1;
1593
1594                                         $sth = $pdo->prepare("INSERT INTO ttrss_archived_feeds
1595                                                 (id, owner_uid, title, feed_url, site_url)
1596                                                         SELECT ?, owner_uid, title, feed_url, site_url from ttrss_feeds
1597                                                         WHERE id = ?");
1598                                         $sth->execute([$new_feed_id, $id]);
1599
1600                                         $archive_id = $new_feed_id;
1601                                 }
1602
1603                                 $sth = $pdo->prepare("UPDATE ttrss_user_entries SET feed_id = NULL,
1604                                         orig_feed_id = ? WHERE feed_id = ? AND
1605                                                 marked = true AND owner_uid = ?");
1606
1607                                 $sth->execute([$archive_id, $id, $owner_uid]);
1608
1609                                 /* Remove access key for the feed */
1610
1611                                 $sth = $pdo->prepare("DELETE FROM ttrss_access_keys WHERE
1612                                         feed_id = ? AND owner_uid = ?");
1613                                 $sth->execute([$id, $owner_uid]);
1614
1615                                 /* remove the feed */
1616
1617                                 $sth = $pdo->prepare("DELETE FROM ttrss_feeds
1618                                         WHERE id = ? AND owner_uid = ?");
1619                                 $sth->execute([$id, $owner_uid]);
1620                         }
1621
1622                         $pdo->commit();
1623
1624                         if (file_exists(ICONS_DIR . "/$id.ico")) {
1625                                 unlink(ICONS_DIR . "/$id.ico");
1626                         }
1627
1628                         CCache::remove($id, $owner_uid);
1629
1630                 } else {
1631                         Labels::remove(Labels::feed_to_label_id($id), $owner_uid);
1632                         //CCache::remove($id, $owner_uid); don't think labels are cached
1633                 }
1634         }
1635
1636         function batchSubscribe() {
1637                 print_hidden("op", "pref-feeds");
1638                 print_hidden("method", "batchaddfeeds");
1639
1640                 print "<table width='100%'><tr><td>
1641                         ".__("Add one valid RSS feed per line (no feed detection is done)")."
1642                 </td><td align='right'>";
1643                 if (get_pref('ENABLE_FEED_CATS')) {
1644                         print __('Place in category:') . " ";
1645                         print_feed_cat_select("cat", false, 'dojoType="dijit.form.Select"');
1646                 }
1647                 print "</td></tr><tr><td colspan='2'>";
1648                 print "<textarea
1649                         style='font-size : 12px; width : 98%; height: 200px;'
1650                         placeHolder=\"".__("Feeds to subscribe, One per line")."\"
1651                         dojoType=\"dijit.form.SimpleTextarea\" required=\"1\" name=\"feeds\"></textarea>";
1652
1653                 print "</td></tr><tr><td colspan='2'>";
1654
1655                 print "<div id='feedDlg_loginContainer' style='display : none'>
1656                                 " .
1657                                 " <input dojoType=\"dijit.form.TextBox\" name='login'\"
1658                                         placeHolder=\"".__("Login")."\"
1659                                         style=\"width : 10em;\"> ".
1660                                 " <input
1661                                         placeHolder=\"".__("Password")."\"
1662                                         dojoType=\"dijit.form.TextBox\" type='password'
1663                                         autocomplete=\"new-password\"
1664                                         style=\"width : 10em;\" name='pass'\">".
1665                                 "</div>";
1666
1667                 print "</td></tr><tr><td colspan='2'>";
1668
1669                 print "<div style=\"clear : both\">
1670                         <input type=\"checkbox\" name=\"need_auth\" dojoType=\"dijit.form.CheckBox\" id=\"feedDlg_loginCheck\"
1671                                         onclick='checkboxToggleElement(this, \"feedDlg_loginContainer\")'>
1672                                 <label for=\"feedDlg_loginCheck\">".
1673                                 __('Feeds require authentication.')."</div>";
1674
1675                 print "</form>";
1676
1677                 print "</td></tr></table>";
1678
1679                 print "<div class=\"dlgButtons\">
1680                         <button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('batchSubDlg').execute()\">".__('Subscribe')."</button>
1681                         <button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('batchSubDlg').hide()\">".__('Cancel')."</button>
1682                         </div>";
1683         }
1684
1685         function batchAddFeeds() {
1686                 $cat_id = clean($_REQUEST['cat']);
1687                 $feeds = explode("\n", clean($_REQUEST['feeds']));
1688                 $login = clean($_REQUEST['login']);
1689                 $pass = trim(clean($_REQUEST['pass']));
1690
1691                 foreach ($feeds as $feed) {
1692                         $feed = trim($feed);
1693
1694                         if (validate_feed_url($feed)) {
1695
1696                                 $this->pdo->beginTransaction();
1697
1698                                 $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
1699                                                 WHERE feed_url = ? AND owner_uid = ?");
1700                                 $sth->execute([$feed, $_SESSION['uid']]);
1701
1702                                 if (!$sth->fetch()) {
1703                                         $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds
1704                                                         (owner_uid,feed_url,title,cat_id,auth_login,auth_pass,update_method,auth_pass_encrypted)
1705                                                 VALUES (?, ?, '[Unknown]', ?, ?, ?, 0, false)");
1706
1707                                         $sth->execute([$_SESSION['uid'], $feed, $cat_id ? $cat_id : null, $login, $pass]);
1708                                 }
1709
1710                                 $this->pdo->commit();
1711                         }
1712                 }
1713         }
1714
1715         function regenOPMLKey() {
1716                 $this->update_feed_access_key('OPML:Publish',
1717                 false, $_SESSION["uid"]);
1718
1719                 $new_link = Opml::opml_publish_url();
1720
1721                 print json_encode(array("link" => $new_link));
1722         }
1723
1724         function regenFeedKey() {
1725                 $feed_id = clean($_REQUEST['id']);
1726                 $is_cat = clean($_REQUEST['is_cat']) == "true";
1727
1728                 $new_key = $this->update_feed_access_key($feed_id, $is_cat);
1729
1730                 print json_encode(array("link" => $new_key));
1731         }
1732
1733
1734         private function update_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1735                 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1736
1737                 // clear old value and generate new one
1738                 $sth = $this->pdo->prepare("DELETE FROM ttrss_access_keys
1739                         WHERE feed_id = ? AND is_cat = ? AND owner_uid = ?");
1740                 $sth->execute([$feed_id, $is_cat, $owner_uid]);
1741
1742                 return get_feed_access_key($feed_id, $is_cat, $owner_uid);
1743         }
1744
1745         // Silent
1746         function clearKeys() {
1747                 $sth = $this->pdo->prepare("DELETE FROM ttrss_access_keys WHERE
1748                         owner_uid = ?");
1749                 $sth->execute([$_SESSION['uid']]);
1750         }
1751
1752         private function calculate_children_count($cat) {
1753                 $c = 0;
1754
1755                 foreach ($cat['items'] as $child) {
1756                         if ($child['type'] == 'category') {
1757                                 $c += $this->calculate_children_count($child);
1758                         } else {
1759                                 $c += 1;
1760                         }
1761                 }
1762
1763                 return $c;
1764         }
1765
1766         function getinactivefeeds() {
1767                 if (DB_TYPE == "pgsql") {
1768                         $interval_qpart = "NOW() - INTERVAL '3 months'";
1769                 } else {
1770                         $interval_qpart = "DATE_SUB(NOW(), INTERVAL 3 MONTH)";
1771                 }
1772
1773                 $sth = $this->pdo->prepare("SELECT COUNT(id) AS num_inactive FROM ttrss_feeds WHERE
1774                                 (SELECT MAX(updated) FROM ttrss_entries, ttrss_user_entries WHERE
1775                                         ttrss_entries.id = ref_id AND
1776                                                 ttrss_user_entries.feed_id = ttrss_feeds.id) < $interval_qpart AND
1777                           ttrss_feeds.owner_uid = ?");
1778                 $sth->execute([$_SESSION['uid']]);
1779
1780                 if ($row = $sth->fetch()) {
1781                         print (int)$row["num_inactive"];
1782                 }
1783         }
1784
1785         static function subscribe_to_feed_url() {
1786                 $url_path = get_self_url_prefix() .
1787                         "/public.php?op=subscribe&feed_url=%s";
1788                 return $url_path;
1789         }
1790
1791 }