]> git.wh0rd.org Git - tt-rss.git/blob - classes/pref/filters.php
7049630b1dad32ba4331d8239587c78c5b85d5c6
[tt-rss.git] / classes / pref / filters.php
1 <?php
2 class Pref_Filters extends Handler_Protected {
3
4         function csrf_ignore($method) {
5                 $csrf_ignored = array("index", "getfiltertree", "edit", "newfilter", "newrule",
6                         "newaction", "savefilterorder");
7
8                 return array_search($method, $csrf_ignored) !== false;
9         }
10
11         function filtersortreset() {
12                 $sth = $this->pdo->prepare("UPDATE ttrss_filters2
13                                 SET order_id = 0 WHERE owner_uid = ?");
14                 $sth->execute([$_SESSION['uid']]);
15                 return;
16         }
17
18         function savefilterorder() {
19                 $data = json_decode($_POST['payload'], true);
20
21                 #file_put_contents("/tmp/saveorder.json", $_POST['payload']);
22                 #$data = json_decode(file_get_contents("/tmp/saveorder.json"), true);
23
24                 if (!is_array($data['items']))
25                         $data['items'] = json_decode($data['items'], true);
26
27                 $index = 0;
28
29                 if (is_array($data) && is_array($data['items'])) {
30
31                         $sth = $this->pdo->prepare("UPDATE ttrss_filters2 SET
32                                                 order_id = ? WHERE id = ? AND
33                                                 owner_uid = ?");
34
35                         foreach ($data['items'][0]['items'] as $item) {
36                                 $filter_id = (int) str_replace("FILTER:", "", $item['_reference']);
37
38                                 if ($filter_id > 0) {
39                                         $sth->execute([$index, $filter_id, $_SESSION['uid']]);
40                                         ++$index;
41                                 }
42                         }
43                 }
44
45                 return;
46         }
47
48         function testFilterDo() {
49                 $offset = (int) $_REQUEST["offset"];
50                 $limit = (int) $_REQUEST["limit"];
51
52                 $filter = array();
53
54                 $filter["enabled"] = true;
55                 $filter["match_any_rule"] = sql_bool_to_bool(
56                         checkbox_to_sql_bool($_REQUEST["match_any_rule"]));
57                 $filter["inverse"] = sql_bool_to_bool(
58                         checkbox_to_sql_bool($_REQUEST["inverse"]));
59
60                 $filter["rules"] = array();
61                 $filter["actions"] = array("dummy-action");
62
63                 $res = $this->pdo->query("SELECT id,name FROM ttrss_filter_types");
64
65                 $filter_types = array();
66                 while ($line = $res->fetch()) {
67                         $filter_types[$line["id"]] = $line["name"];
68                 }
69
70                 $scope_qparts = array();
71
72                 $rctr = 0;
73                 foreach ($_REQUEST["rule"] AS $r) {
74                         $rule = json_decode($r, true);
75
76                         if ($rule && $rctr < 5) {
77                                 $rule["type"] = $filter_types[$rule["filter_type"]];
78                                 unset($rule["filter_type"]);
79
80                                 $scope_inner_qparts = [];
81                                 foreach ($rule["feed_id"] as $feed_id) {
82
83                     if (strpos($feed_id, "CAT:") === 0) {
84                         $cat_id = (int) substr($feed_id, 4);
85                         array_push($scope_inner_qparts, "cat_id = " . $this->pdo->quote($cat_id));
86                     } else if ($feed_id > 0) {
87                         array_push($scope_inner_qparts, "feed_id = " . $this->pdo->quote($feed_id));
88                     }
89                 }
90
91                 if (count($scope_inner_qparts) > 0) {
92                                     array_push($scope_qparts, "(" . implode(" OR ", $scope_inner_qparts) . ")");
93                 }
94
95                                 array_push($filter["rules"], $rule);
96
97                                 ++$rctr;
98                         } else {
99                                 break;
100                         }
101                 }
102
103                 if (count($scope_qparts) == 0) $scope_qparts = ["true"];
104
105                 $glue = $filter['match_any_rule'] ? " OR " :  " AND ";
106                 $scope_qpart = join($glue, $scope_qparts);
107
108                 if (!$scope_qpart) $scope_qpart = "true";
109
110                 $rv = array();
111
112                 //while ($found < $limit && $offset < $limit * 1000 && time() - $started < ini_get("max_execution_time") * 0.7) {
113
114                         $sth = $this->pdo->prepare("SELECT ttrss_entries.id,
115                                         ttrss_entries.title,
116                                         ttrss_feeds.id AS feed_id,
117                                         ttrss_feeds.title AS feed_title,
118                                         ttrss_feed_categories.id AS cat_id,
119                                         content,
120                                         date_entered,
121                                         link,
122                                         author,
123                                         tag_cache
124                                 FROM
125                                         ttrss_entries, ttrss_user_entries
126                                                 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)
127                                                 LEFT JOIN ttrss_feed_categories ON (ttrss_feeds.cat_id = ttrss_feed_categories.id)
128                                 WHERE
129                                         ref_id = ttrss_entries.id AND
130                                         ($scope_qpart) AND
131                                         ttrss_user_entries.owner_uid = ?
132                                 ORDER BY date_entered DESC LIMIT ?OFFSET ?");
133
134                         $sth->execute([$_SESSION['uid'], $limit, $offset]);;
135
136                         while ($line = $sth->fetch()) {
137
138                                 $rc = RSSUtils::get_article_filters(array($filter), $line['title'], $line['content'], $line['link'],
139                                         $line['author'], explode(",", $line['tag_cache']));
140
141                                 if (count($rc) > 0) {
142
143                                         $line["content_preview"] = truncate_string(strip_tags($line["content"]), 200, '&hellip;');
144
145                                         foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
146                                                 $line = $p->hook_query_headlines($line, 100);
147                                         }
148
149                                         $content_preview = $line["content_preview"];
150
151                                         $tmp = "<tr style='margin-top : 5px'>";
152
153                                         #$tmp .= "<td width='5%' align='center'><input dojoType=\"dijit.form.CheckBox\"
154                                         #       checked=\"1\" disabled=\"1\" type=\"checkbox\"></td>";
155
156                                         $id = $line['id'];
157                                         $tmp .= "<td width='5%' align='center'><img style='cursor : pointer' title='".__("Preview article")."'
158                                                 src='images/information.png' onclick='openArticlePopup($id)'></td><td>";
159
160                                         /*foreach ($filter['rules'] as $rule) {
161                                                 $reg_exp = str_replace('/', '\/', $rule["reg_exp"]);
162
163                                                 $line["title"] = preg_replace("/($reg_exp)/i",
164                                                         "<span class=\"highlight\">$1</span>", $line["title"]);
165
166                                                 $content_preview = preg_replace("/($reg_exp)/i",
167                                                         "<span class=\"highlight\">$1</span>", $content_preview);
168                                         }*/
169
170                                         $tmp .= "<strong>" . $line["title"] . "</strong><br/>";
171                                         $tmp .= $line['feed_title'] . ", " . mb_substr($line["date_entered"], 0, 16);
172                                         $tmp .= "<div class='insensitive'>" . $content_preview . "</div>";
173                                         $tmp .= "</td></tr>";
174
175                                         array_push($rv, $tmp);
176
177                                         /*array_push($rv, array("title" => $line["title"],
178                                                 "content" => $content_preview,
179                                                 "date" => $line["date_entered"],
180                                                 "feed" => $line["feed_title"])); */
181
182                                 }
183                         }
184
185                         //$offset += $limit;
186                 //}
187
188                 /*if ($found == 0) {
189                         print "<tr><td align='center'>" .
190                                 __("No recent articles matching this filter have been found.");
191                 }*/
192
193                 print json_encode($rv);
194         }
195
196         function testFilter() {
197
198                 if (isset($_REQUEST["offset"])) return $this->testFilterDo();
199
200                 //print __("Articles matching this filter:");
201
202                 print "<div><img id='prefFilterLoadingIndicator' src='images/indicator_tiny.gif'>&nbsp;<span id='prefFilterProgressMsg'>Looking for articles...</span></div>";
203
204                 print "<br/><div class=\"filterTestHolder\">";
205                 print "<table width=\"100%\" cellspacing=\"0\" id=\"prefFilterTestResultList\">";
206                 print "</table></div>";
207
208                 print "<div style='text-align : center'>";
209                 print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('filterTestDlg').hide()\">".
210                         __('Close this window')."</button>";
211                 print "</div>";
212
213         }
214
215         private function getfilterrules_concise($filter_id) {
216                 $sth = $this->pdo->prepare("SELECT reg_exp,
217                         inverse,
218                         match_on,
219                         feed_id,
220                         cat_id,
221                         cat_filter,
222                         ttrss_filter_types.description AS field
223                         FROM
224                                 ttrss_filters2_rules, ttrss_filter_types
225                         WHERE
226                                 filter_id = ? AND filter_type = ttrss_filter_types.id
227                         ORDER BY reg_exp");
228                 $sth->execute([$filter_id]);
229
230                 $rv = "";
231
232                 while ($line = $sth->fetch()) {
233
234                     if ($line["match_on"]) {
235                         $feeds = json_decode($line["match_on"], true);
236                         $feeds_fmt = [];
237
238                 foreach ($feeds as $feed_id) {
239
240                     if (strpos($feed_id, "CAT:") === 0) {
241                         $feed_id = (int)substr($feed_id, 4);
242                         array_push($feeds_fmt, Feeds::getCategoryTitle($feed_id));
243                     } else {
244                         if ($feed_id)
245                             array_push($feeds_fmt, Feeds::getFeedTitle((int)$feed_id));
246                         else
247                             array_push($feeds_fmt, __("All feeds"));
248                     }
249                 }
250
251                 $where = implode(", ", $feeds_fmt);
252
253             } else {
254
255                 $where = sql_bool_to_bool($line["cat_filter"]) ?
256                     Feeds::getCategoryTitle($line["cat_id"]) :
257                     ($line["feed_id"] ?
258                         Feeds::getFeedTitle($line["feed_id"]) : __("All feeds"));
259             }
260
261 #                       $where = $line["cat_id"] . "/" . $line["feed_id"];
262
263                         $inverse = sql_bool_to_bool($line["inverse"]) ? "inverse" : "";
264
265                         $rv .= "<span class='$inverse'>" . T_sprintf("%s on %s in %s %s",
266                                 htmlspecialchars($line["reg_exp"]),
267                                 $line["field"],
268                                 $where,
269                                 sql_bool_to_bool($line["inverse"]) ? __("(inverse)") : "") . "</span>";
270                 }
271
272                 return $rv;
273         }
274
275         function getfiltertree() {
276                 $root = array();
277                 $root['id'] = 'root';
278                 $root['name'] = __('Filters');
279                 $root['items'] = array();
280
281                 $filter_search = $_SESSION["prefs_filter_search"];
282
283                 $sth = $this->pdo->prepare("SELECT *,
284                         (SELECT action_param FROM ttrss_filters2_actions
285                                 WHERE filter_id = ttrss_filters2.id ORDER BY id LIMIT 1) AS action_param,
286                         (SELECT action_id FROM ttrss_filters2_actions
287                                 WHERE filter_id = ttrss_filters2.id ORDER BY id LIMIT 1) AS action_id,
288                         (SELECT description FROM ttrss_filter_actions
289                                 WHERE id = (SELECT action_id FROM ttrss_filters2_actions
290                                         WHERE filter_id = ttrss_filters2.id ORDER BY id LIMIT 1)) AS action_name,
291                         (SELECT reg_exp FROM ttrss_filters2_rules
292                                 WHERE filter_id = ttrss_filters2.id ORDER BY id LIMIT 1) AS reg_exp
293                         FROM ttrss_filters2 WHERE
294                         owner_uid = ? ORDER BY order_id, title");
295                 $sth->execute([$_SESSION['uid']]);
296
297                 $folder = array();
298                 $folder['items'] = array();
299
300                 while ($line = $sth->fetch()) {
301
302                         $name = $this->getFilterName($line["id"]);
303
304                         $match_ok = false;
305                         if ($filter_search) {
306                                 $rules_sth = $this->pdo->prepare("SELECT reg_exp 
307                                         FROM ttrss_filters2_rules WHERE filter_id = ?");
308                                 $rules_sth->execute([$line['id']]);
309
310                                 while ($rule_line = $rules_sth->fetch()) {
311                                         if (mb_strpos($rule_line['reg_exp'], $filter_search) !== false) {
312                                                 $match_ok = true;
313                                                 break;
314                                         }
315                                 }
316                         }
317
318                         if ($line['action_id'] == 7) {
319                                 $label_sth = $this->pdo->prepare("SELECT fg_color, bg_color
320                                         FROM ttrss_labels2 WHERE caption = ? AND
321                                                 owner_uid = ?");
322                                 $label_sth->execute([$line['action_param'], $_SESSION['uid']]);
323
324                                 if ($label_row = $label_sth->fetch()) {
325                                         $fg_color = $label_row["fg_color"];
326                                         $bg_color = $label_row["bg_color"];
327
328                                         $name[1] = "<span class=\"labelColorIndicator\" id=\"label-editor-indicator\" style='color : $fg_color; background-color : $bg_color; margin-right : 4px'>&alpha;</span>" . $name[1];
329                                 }
330                         }
331
332                         $filter = array();
333                         $filter['id'] = 'FILTER:' . $line['id'];
334                         $filter['bare_id'] = $line['id'];
335                         $filter['name'] = $name[0];
336                         $filter['param'] = $name[1];
337                         $filter['checkbox'] = false;
338                         $filter['enabled'] = sql_bool_to_bool($line["enabled"]);
339                         $filter['rules'] = $this->getfilterrules_concise($line['id']);
340
341                         if (!$filter_search || $match_ok) {
342                                 array_push($folder['items'], $filter);
343                         }
344                 }
345
346                 $root['items'] = $folder['items'];
347
348                 $fl = array();
349                 $fl['identifier'] = 'id';
350                 $fl['label'] = 'name';
351                 $fl['items'] = array($root);
352
353                 print json_encode($fl);
354                 return;
355         }
356
357         function edit() {
358
359                 $filter_id = $_REQUEST["id"];
360
361                 $sth = $this->pdo->prepare("SELECT * FROM ttrss_filters2 
362                         WHERE id = ? AND owner_uid = ?");
363                 $sth->execute([$filter_id, $_SESSION['uid']]);
364
365                 if ($row = $sth->fetch()) {
366
367                         $enabled = sql_bool_to_bool($row["enabled"]);
368                         $match_any_rule = sql_bool_to_bool($row["match_any_rule"]);
369                         $inverse = sql_bool_to_bool($row["inverse"]);
370                         $title = htmlspecialchars($row["title"]);
371
372                         print "<form id=\"filter_edit_form\" onsubmit='return false'>";
373
374                         print_hidden("op", "pref-filters");
375                         print_hidden("id", "$filter_id");
376                         print_hidden("method", "editSave");
377                         print_hidden("csrf_token", $_SESSION['csrf_token']);
378
379                         print "<div class=\"dlgSec\">".__("Caption")."</div>";
380
381                         print "<input required=\"true\" dojoType=\"dijit.form.ValidationTextBox\" style=\"width : 20em;\" name=\"title\" value=\"$title\">";
382
383                         print "</div>";
384
385                         print "<div class=\"dlgSec\">".__("Match")."</div>";
386
387                         print "<div dojoType=\"dijit.Toolbar\">";
388
389                         print "<div dojoType=\"dijit.form.DropDownButton\">".
390                                 "<span>" . __('Select')."</span>";
391                         print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
392                         print "<div onclick=\"dijit.byId('filterEditDlg').selectRules(true)\"
393                         dojoType=\"dijit.MenuItem\">".__('All')."</div>";
394                         print "<div onclick=\"dijit.byId('filterEditDlg').selectRules(false)\"
395                         dojoType=\"dijit.MenuItem\">".__('None')."</div>";
396                         print "</div></div>";
397
398                         print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').addRule()\">".
399                                 __('Add')."</button> ";
400
401                         print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').deleteRule()\">".
402                                 __('Delete')."</button> ";
403
404                         print "</div>";
405
406                         print "<ul id='filterDlg_Matches'>";
407
408                         $rules_sth = $this->pdo->prepare("SELECT * FROM ttrss_filters2_rules
409                                   WHERE filter_id = ? ORDER BY reg_exp, id");
410                         $rules_sth->execute([$filter_id]);
411
412                         while ($line = $rules_sth->fetch()) {
413                                 if ($line["match_on"]) {
414                                         $line["feed_id"] = json_decode($line["match_on"], true);
415                                 } else {
416                                         if (sql_bool_to_bool($line["cat_filter"])) {
417                                                 $feed_id = "CAT:" . (int)$line["cat_id"];
418                                         } else {
419                                                 $feed_id = (int)$line["feed_id"];
420                                         }
421
422                                         $line["feed_id"] = ["" . $feed_id]; // set item type to string for in_array()
423                                 }
424
425                                 unset($line["cat_filter"]);
426                                 unset($line["cat_id"]);
427                                 unset($line["filter_id"]);
428                                 unset($line["id"]);
429                                 if (!sql_bool_to_bool($line["inverse"])) unset($line["inverse"]);
430                                 unset($line["match_on"]);
431
432                                 $data = htmlspecialchars(json_encode($line));
433
434                                 print "<li><input dojoType='dijit.form.CheckBox' type='checkbox' onclick='toggleSelectListRow2(this)'>".
435                                         "<span onclick=\"dijit.byId('filterEditDlg').editRule(this)\">".$this->getRuleName($line)."</span>".
436                                         "<input type='hidden' name='rule[]' value=\"$data\"/></li>";
437                         }
438
439                         print "</ul>";
440
441                         print "</div>";
442
443                         print "<div class=\"dlgSec\">".__("Apply actions")."</div>";
444
445                         print "<div dojoType=\"dijit.Toolbar\">";
446
447                         print "<div dojoType=\"dijit.form.DropDownButton\">".
448                                 "<span>" . __('Select')."</span>";
449                         print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
450                         print "<div onclick=\"dijit.byId('filterEditDlg').selectActions(true)\"
451                         dojoType=\"dijit.MenuItem\">".__('All')."</div>";
452                         print "<div onclick=\"dijit.byId('filterEditDlg').selectActions(false)\"
453                         dojoType=\"dijit.MenuItem\">".__('None')."</div>";
454                         print "</div></div>";
455
456                         print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').addAction()\">".
457                                 __('Add')."</button> ";
458
459                         print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').deleteAction()\">".
460                                 __('Delete')."</button> ";
461
462                         print "</div>";
463
464                         print "<ul id='filterDlg_Actions'>";
465
466                         $actions_sth = $this->pdo->prepare("SELECT * FROM ttrss_filters2_actions
467                                 WHERE filter_id = ? ORDER BY id");
468                         $actions_sth->execute([$filter_id]);
469
470                         while ($line = $actions_sth->fetch()) {
471                                 $line["action_param_label"] = $line["action_param"];
472
473                                 unset($line["filter_id"]);
474                                 unset($line["id"]);
475
476                                 $data = htmlspecialchars(json_encode($line));
477
478                                 print "<li><input dojoType='dijit.form.CheckBox' type='checkbox' onclick='toggleSelectListRow2(this)'>".
479                                         "<span onclick=\"dijit.byId('filterEditDlg').editAction(this)\">".$this->getActionName($line)."</span>".
480                                         "<input type='hidden' name='action[]' value=\"$data\"/></li>";
481                         }
482
483                         print "</ul>";
484
485                         print "</div>";
486
487                         if ($enabled) {
488                                 $checked = "checked=\"1\"";
489                         } else {
490                                 $checked = "";
491                         }
492
493                         print "<input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" name=\"enabled\" id=\"enabled\" $checked>
494                                 <label for=\"enabled\">".__('Enabled')."</label>";
495
496                         if ($match_any_rule) {
497                                 $checked = "checked=\"1\"";
498                         } else {
499                                 $checked = "";
500                         }
501
502                         print "<br/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" name=\"match_any_rule\" id=\"match_any_rule\" $checked>
503                                 <label for=\"match_any_rule\">".__('Match any rule')."</label>";
504
505                         if ($inverse) {
506                                 $checked = "checked=\"1\"";
507                         } else {
508                                 $checked = "";
509                         }
510
511                         print "<br/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" name=\"inverse\" id=\"inverse\" $checked>
512                                 <label for=\"inverse\">".__('Inverse matching')."</label>";
513
514                         print "<p/>";
515
516                         print "<div class=\"dlgButtons\">";
517
518                         print "<div style=\"float : left\">";
519                         print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').removeFilter()\">".
520                                 __('Remove')."</button>";
521                         print "</div>";
522
523                         print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').test()\">".
524                                 __('Test')."</button> ";
525
526                         print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').execute()\">".
527                                 __('Save')."</button> ";
528
529                         print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').hide()\">".
530                                 __('Cancel')."</button>";
531
532                         print "</div>";
533                         
534                 }
535         }
536
537         private function getRuleName($rule) {
538                 if (!$rule) $rule = json_decode($_REQUEST["rule"], true);
539
540                 $feeds = $rule["feed_id"];
541                 $feeds_fmt = [];
542
543                 if (!is_array($feeds)) $feeds = [$feeds];
544
545                 foreach ($feeds as $feed_id) {
546
547             if (strpos($feed_id, "CAT:") === 0) {
548                 $feed_id = (int)substr($feed_id, 4);
549                 array_push($feeds_fmt, Feeds::getCategoryTitle($feed_id));
550             } else {
551                 if ($feed_id)
552                     array_push($feeds_fmt, Feeds::getFeedTitle((int)$feed_id));
553                 else
554                     array_push($feeds_fmt, __("All feeds"));
555             }
556         }
557
558         $feed = implode(", ", $feeds_fmt);
559
560                 $sth = $this->pdo->prepare("SELECT description FROM ttrss_filter_types
561                         WHERE id = ?");
562                 $sth->execute([(int)$rule["filter_type"]]);
563
564                 if ($row = $sth->fetch()) {
565                         $filter_type = $row["description"];
566                 } else {
567                         $filter_type = "?UNKNOWN?";
568                 }
569
570                 $inverse = isset($rule["inverse"]) ? "inverse" : "";
571
572                 return "<span class='filterRule $inverse'>" .
573                         T_sprintf("%s on %s in %s %s", htmlspecialchars($rule["reg_exp"]),
574                         $filter_type, $feed, isset($rule["inverse"]) ? __("(inverse)") : "") . "</span>";
575         }
576
577         function printRuleName() {
578                 print $this->getRuleName(json_decode($_REQUEST["rule"], true));
579         }
580
581         private function getActionName($action) {
582                 $sth = $this->pdo->prepare("SELECT description FROM
583                         ttrss_filter_actions WHERE id = ?");
584                 $sth->execute([(int)$action["action_id"]]);
585
586                 $title = "";
587
588                 if ($row = $sth->fetch()) {
589
590                         $title = __($row["description"]);
591
592                         if ($action["action_id"] == 4 || $action["action_id"] == 6 ||
593                                 $action["action_id"] == 7)
594                                 $title .= ": " . $action["action_param"];
595
596                         if ($action["action_id"] == 9) {
597                                 list ($pfclass, $pfaction) = explode(":", $action["action_param"]);
598
599                                 $filter_actions = PluginHost::getInstance()->get_filter_actions();
600
601                                 foreach ($filter_actions as $fclass => $factions) {
602                                         foreach ($factions as $faction) {
603                                                 if ($pfaction == $faction["action"] && $pfclass == $fclass) {
604                                                         $title .= ": " . $fclass . ": " . $faction["description"];
605                                                         break;
606                                                 }
607                                         }
608                                 }
609                         }
610                 }
611
612                 return $title;
613         }
614
615         function printActionName() {
616                 print $this->getActionName(json_decode($_REQUEST["action"], true));
617         }
618
619         function editSave() {
620                 if ($_REQUEST["savemode"] && $_REQUEST["savemode"] == "test") {
621                         return $this->testFilter();
622                 }
623
624                 $filter_id = $_REQUEST["id"];
625                 $enabled = checkbox_to_sql_bool($_REQUEST["enabled"]);
626                 $match_any_rule = checkbox_to_sql_bool($_REQUEST["match_any_rule"]);
627                 $inverse = checkbox_to_sql_bool($_REQUEST["inverse"]);
628                 $title = $_REQUEST["title"];
629
630                 $this->pdo->beginTransaction();
631
632                 $sth = $this->pdo->prepare("UPDATE ttrss_filters2 SET enabled = ?,
633                         match_any_rule = ?,
634                         inverse = ?,
635                         title = ?
636                         WHERE id = ? AND owner_uid = ?");
637
638                 $sth->execute([$enabled, $match_any_rule, $inverse, $title, $filter_id, $_SESSION['uid']]);
639
640                 $this->saveRulesAndActions($filter_id);
641
642                 $this->pdo->commit();
643         }
644
645         function remove() {
646
647                 $ids = explode(",", $_REQUEST["ids"]);
648                 $ids_qmarks = arr_qmarks($ids);
649
650                 $sth = $this->pdo->prepare("DELETE FROM ttrss_filters2 WHERE id IN ($ids_qmarks) 
651                         AND owner_uid = ?");
652                 $sth->execute(array_merge($ids, [$_SESSION['uid']]));
653         }
654
655         private function saveRulesAndActions($filter_id)
656         {
657
658                 $sth = $this->pdo->prepare("DELETE FROM ttrss_filters2_rules WHERE filter_id = ?");
659                 $sth->execute([$filter_id]);
660
661                 $sth = $this->pdo->prepare("DELETE FROM ttrss_filters2_actions WHERE filter_id = ?");
662                 $sth->execute([$filter_id]);
663
664                 if (!is_array($_REQUEST["rule"])) $_REQUEST["rule"] = [];
665                 if (!is_array($_REQUEST["action"])) $_REQUEST["action"] = [];
666                 
667                 if ($filter_id) {
668                         /* create rules */
669
670                         $rules = array();
671                         $actions = array();
672
673                         foreach ($_REQUEST["rule"] as $rule) {
674                                 $rule = json_decode($rule, true);
675                                 unset($rule["id"]);
676
677                                 if (array_search($rule, $rules) === false) {
678                                         array_push($rules, $rule);
679                                 }
680                         }
681
682                         foreach ($_REQUEST["action"] as $action) {
683                                 $action = json_decode($action, true);
684                                 unset($action["id"]);
685
686                                 if (array_search($action, $actions) === false) {
687                                         array_push($actions, $action);
688                                 }
689                         }
690
691                         $rsth = $this->pdo->prepare("INSERT INTO ttrss_filters2_rules
692                                                 (filter_id, reg_exp,filter_type,feed_id,cat_id,match_on,inverse) VALUES
693                                                 (?, ?, ?, NULL, NULL, ?, ?)");
694
695                         foreach ($rules as $rule) {
696                                 if ($rule) {
697
698                                         $reg_exp = trim($rule["reg_exp"]);
699                                         $inverse = isset($rule["inverse"]) ? "true" : "false";
700
701                                         $filter_type = (int)trim($rule["filter_type"]);
702                                         $match_on = json_encode($rule["feed_id"]);
703
704                                         $rsth->execute([$filter_id, $reg_exp, $filter_type, $match_on, $inverse]);
705                                 }
706                         }
707
708                         $asth = $this->pdo->prepare("INSERT INTO ttrss_filters2_actions
709                                                 (filter_id, action_id, action_param) VALUES
710                                                 (?, ?, ?)");
711
712                         foreach ($actions as $action) {
713                                 if ($action) {
714
715                                         $action_id = (int)$action["action_id"];
716                                         $action_param = $action["action_param"];
717                                         $action_param_label = $action["action_param_label"];
718
719                                         if ($action_id == 7) {
720                                                 $action_param = $action_param_label;
721                                         }
722
723                                         if ($action_id == 6) {
724                                                 $action_param = (int)str_replace("+", "", $action_param);
725                                         }
726
727                                         $asth->execute([$filter_id, $action_id, $action_param]);
728                                 }
729                         }
730                 }
731         }
732
733         function add() {
734                 if ($_REQUEST["savemode"] && $_REQUEST["savemode"] == "test") {
735                         return $this->testFilter();
736                 }
737
738                 $enabled = checkbox_to_sql_bool($_REQUEST["enabled"]);
739                 $match_any_rule = checkbox_to_sql_bool($_REQUEST["match_any_rule"]);
740                 $title = $_REQUEST["title"];
741                 $inverse = checkbox_to_sql_bool($_REQUEST["inverse"]);
742
743                 $this->pdo->beginTransaction();
744
745                 /* create base filter */
746
747                 $sth = $this->pdo->prepare("INSERT INTO ttrss_filters2
748                         (owner_uid, match_any_rule, enabled, title, inverse) VALUES
749                         (?, ?, ?, ?, ?)");
750
751                 $sth->execute([$_SESSION['uid'], $match_any_rule, $enabled, $title, $inverse]);
752
753                 $sth = $this->pdo->prepare("SELECT MAX(id) AS id FROM ttrss_filters2
754                         WHERE owner_uid = ?");
755                 $sth->execute([$_SESSION['uid']]);
756
757                 if ($row = $sth->fetch()) {
758                         $filter_id = $row['id'];
759                         $this->saveRulesAndActions($filter_id);
760                 }
761
762                 $this->pdo->commit();
763         }
764
765         function index() {
766
767                 $sort = $_REQUEST["sort"];
768
769                 if (!$sort || $sort == "undefined") {
770                         $sort = "reg_exp";
771                 }
772
773                 $filter_search = $_REQUEST["search"];
774
775                 if (array_key_exists("search", $_REQUEST)) {
776                         $_SESSION["prefs_filter_search"] = $filter_search;
777                 } else {
778                         $filter_search = $_SESSION["prefs_filter_search"];
779                 }
780
781                 print "<div id=\"pref-filter-wrap\" dojoType=\"dijit.layout.BorderContainer\" gutters=\"false\">";
782                 print "<div id=\"pref-filter-header\" dojoType=\"dijit.layout.ContentPane\" region=\"top\">";
783                 print "<div id=\"pref-filter-toolbar\" dojoType=\"dijit.Toolbar\">";
784
785                 $filter_search = $_REQUEST["search"];
786
787                 if (array_key_exists("search", $_REQUEST)) {
788                         $_SESSION["prefs_filter_search"] = $filter_search;
789                 } else {
790                         $filter_search = $_SESSION["prefs_filter_search"];
791                 }
792
793                 print "<div style='float : right; padding-right : 4px;'>
794                         <input dojoType=\"dijit.form.TextBox\" id=\"filter_search\" size=\"20\" type=\"search\"
795                                 value=\"$filter_search\">
796                         <button dojoType=\"dijit.form.Button\" onclick=\"updateFilterList()\">".
797                                 __('Search')."</button>
798                         </div>";
799
800                 print "<div dojoType=\"dijit.form.DropDownButton\">".
801                                 "<span>" . __('Select')."</span>";
802                 print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
803                 print "<div onclick=\"dijit.byId('filterTree').model.setAllChecked(true)\"
804                         dojoType=\"dijit.MenuItem\">".__('All')."</div>";
805                 print "<div onclick=\"dijit.byId('filterTree').model.setAllChecked(false)\"
806                         dojoType=\"dijit.MenuItem\">".__('None')."</div>";
807                 print "</div></div>";
808
809                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return quickAddFilter()\">".
810                         __('Create filter')."</button> ";
811
812                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return joinSelectedFilters()\">".
813                         __('Combine')."</button> ";
814
815                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return editSelectedFilter()\">".
816                         __('Edit')."</button> ";
817
818                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return resetFilterOrder()\">".
819                         __('Reset sort order')."</button> ";
820
821
822                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return removeSelectedFilters()\">".
823                         __('Remove')."</button> ";
824
825                 if (defined('_ENABLE_FEED_DEBUGGING')) {
826                         print "<button dojoType=\"dijit.form.Button\" onclick=\"rescore_all_feeds()\">".
827                                 __('Rescore articles')."</button> ";
828                 }
829
830                 print "</div>"; # toolbar
831                 print "</div>"; # toolbar-frame
832                 print "<div id=\"pref-filter-content\" dojoType=\"dijit.layout.ContentPane\" region=\"center\">";
833
834                 print "<div id=\"filterlistLoading\">
835                 <img src='images/indicator_tiny.gif'>".
836                  __("Loading, please wait...")."</div>";
837
838                 print "<div dojoType=\"fox.PrefFilterStore\" jsId=\"filterStore\"
839                         url=\"backend.php?op=pref-filters&method=getfiltertree\">
840                 </div>
841                 <div dojoType=\"lib.CheckBoxStoreModel\" jsId=\"filterModel\" store=\"filterStore\"
842                         query=\"{id:'root'}\" rootId=\"root\" rootLabel=\"Filters\"
843                         childrenAttrs=\"items\" checkboxStrict=\"false\" checkboxAll=\"false\">
844                 </div>
845                 <div dojoType=\"fox.PrefFilterTree\" id=\"filterTree\"
846                         dndController=\"dijit.tree.dndSource\"
847                         betweenThreshold=\"5\"
848                         model=\"filterModel\" openOnClick=\"true\">
849                 <script type=\"dojo/method\" event=\"onLoad\" args=\"item\">
850                         Element.hide(\"filterlistLoading\");
851                 </script>
852                 <script type=\"dojo/method\" event=\"onClick\" args=\"item\">
853                         var id = String(item.id);
854                         var bare_id = id.substr(id.indexOf(':')+1);
855
856                         if (id.match('FILTER:')) {
857                                 editFilter(bare_id);
858                         }
859                 </script>
860
861                 </div>";
862
863                 print "</div>"; #pane
864
865                 PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB,
866                         "hook_prefs_tab", "prefFilters");
867
868                 print "</div>"; #container
869
870         }
871
872         function newfilter() {
873
874                 print "<form name='filter_new_form' id='filter_new_form'>";
875
876                 print_hidden("op", "pref-filters");
877                 print_hidden("method", "add");
878                 print_hidden("csrf_token", $_SESSION['csrf_token']);
879
880                 print "<div class=\"dlgSec\">".__("Caption")."</div>";
881
882                 print "<input required=\"true\" dojoType=\"dijit.form.ValidationTextBox\" style=\"width : 20em;\" name=\"title\" value=\"\">";
883
884                 print "<div class=\"dlgSec\">".__("Match")."</div>";
885
886                 print "<div dojoType=\"dijit.Toolbar\">";
887
888                 print "<div dojoType=\"dijit.form.DropDownButton\">".
889                                 "<span>" . __('Select')."</span>";
890                 print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
891                 print "<div onclick=\"dijit.byId('filterEditDlg').selectRules(true)\"
892                         dojoType=\"dijit.MenuItem\">".__('All')."</div>";
893                 print "<div onclick=\"dijit.byId('filterEditDlg').selectRules(false)\"
894                         dojoType=\"dijit.MenuItem\">".__('None')."</div>";
895                 print "</div></div>";
896
897                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').addRule()\">".
898                         __('Add')."</button> ";
899
900                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').deleteRule()\">".
901                         __('Delete')."</button> ";
902
903                 print "</div>";
904
905                 print "<ul id='filterDlg_Matches'>";
906 #               print "<li>No rules</li>";
907                 print "</ul>";
908
909                 print "</div>";
910
911                 print "<div class=\"dlgSec\">".__("Apply actions")."</div>";
912
913                 print "<div dojoType=\"dijit.Toolbar\">";
914
915                 print "<div dojoType=\"dijit.form.DropDownButton\">".
916                                 "<span>" . __('Select')."</span>";
917                 print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
918                 print "<div onclick=\"dijit.byId('filterEditDlg').selectActions(true)\"
919                         dojoType=\"dijit.MenuItem\">".__('All')."</div>";
920                 print "<div onclick=\"dijit.byId('filterEditDlg').selectActions(false)\"
921                         dojoType=\"dijit.MenuItem\">".__('None')."</div>";
922                 print "</div></div>";
923
924                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').addAction()\">".
925                         __('Add')."</button> ";
926
927                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').deleteAction()\">".
928                         __('Delete')."</button> ";
929
930                 print "</div>";
931
932                 print "<ul id='filterDlg_Actions'>";
933 #               print "<li>No actions</li>";
934                 print "</ul>";
935
936 /*              print "<div class=\"dlgSec\">".__("Options")."</div>";
937                 print "<div class=\"dlgSecCont\">"; */
938
939                 print "<input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" name=\"enabled\" id=\"enabled\" checked=\"1\">
940                                 <label for=\"enabled\">".__('Enabled')."</label>";
941
942                 print "<br/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" name=\"match_any_rule\" id=\"match_any_rule\">
943                                 <label for=\"match_any_rule\">".__('Match any rule')."</label>";
944
945                 print "<br/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" name=\"inverse\" id=\"inverse\">
946                                 <label for=\"inverse\">".__('Inverse matching')."</label>";
947
948 //              print "</div>";
949
950                 print "<div class=\"dlgButtons\">";
951
952                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').test()\">".
953                         __('Test')."</button> ";
954
955                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').execute()\">".
956                         __('Create')."</button> ";
957
958                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').hide()\">".
959                         __('Cancel')."</button>";
960
961                 print "</div>";
962
963         }
964
965         function newrule() {
966                 $rule = json_decode($_REQUEST["rule"], true);
967
968                 if ($rule) {
969                         $reg_exp = htmlspecialchars($rule["reg_exp"]);
970                         $filter_type = $rule["filter_type"];
971                         $feed_id = $rule["feed_id"];
972                         $inverse_checked = isset($rule["inverse"]) ? "checked" : "";
973                 } else {
974                         $reg_exp = "";
975                         $filter_type = 1;
976                         $feed_id = ["0"];
977                         $inverse_checked = "";
978                 }
979
980                 print "<form name='filter_new_rule_form' id='filter_new_rule_form'>";
981
982                 $res = $this->pdo->query("SELECT id,description
983                         FROM ttrss_filter_types WHERE id != 5 ORDER BY description");
984
985                 $filter_types = array();
986
987                 while ($line = $res->fetch()) {
988                         $filter_types[$line["id"]] = __($line["description"]);
989                 }
990
991                 print "<div class=\"dlgSec\">".__("Match")."</div>";
992
993                 print "<div class=\"dlgSecCont\">";
994
995                 print "<input dojoType=\"dijit.form.ValidationTextBox\"
996                          required=\"true\" id=\"filterDlg_regExp\"
997                          style=\"font-size : 16px; width : 20em;\"
998                          name=\"reg_exp\" value=\"$reg_exp\"/>";
999
1000                 print "<hr/>";
1001                 print "<input id=\"filterDlg_inverse\" dojoType=\"dijit.form.CheckBox\"
1002                          name=\"inverse\" $inverse_checked/>";
1003                 print "<label for=\"filterDlg_inverse\">".__("Inverse regular expression matching")."</label>";
1004
1005                 print "<hr/>" .  __("on field") . " ";
1006                 print_select_hash("filter_type", $filter_type, $filter_types,
1007                         'dojoType="dijit.form.Select"');
1008
1009                 print "<hr/>";
1010
1011                 print __("in") . " ";
1012
1013                 print "<span id='filterDlg_feeds'>";
1014                 print_feed_multi_select("feed_id",
1015                         $feed_id,
1016                         'dojoType="dijit.form.MultiSelect"');
1017                 print "</span>";
1018
1019                 print "</div>";
1020
1021                 print "<div class=\"dlgButtons\">";
1022
1023                 print "<div style=\"float : left\">
1024                         <a class=\"visibleLink\" target=\"_blank\" href=\"http://tt-rss.org/wiki/ContentFilters\">".__("Wiki: Filters")."</a>
1025                 </div>";
1026
1027
1028                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterNewRuleDlg').execute()\">".
1029                         ($rule ? __("Save rule") : __('Add rule'))."</button> ";
1030
1031                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterNewRuleDlg').hide()\">".
1032                         __('Cancel')."</button>";
1033
1034                 print "</div>";
1035
1036                 print "</form>";
1037         }
1038
1039         function newaction() {
1040                 $action = json_decode($_REQUEST["action"], true);
1041
1042                 if ($action) {
1043                         $action_param = $action["action_param"];
1044                         $action_id = (int)$action["action_id"];
1045                 } else {
1046                         $action_param = "";
1047                         $action_id = 0;
1048                 }
1049
1050                 print "<form name='filter_new_action_form' id='filter_new_action_form'>";
1051
1052                 print "<div class=\"dlgSec\">".__("Perform Action")."</div>";
1053
1054                 print "<div class=\"dlgSecCont\">";
1055
1056                 print "<select name=\"action_id\" dojoType=\"dijit.form.Select\"
1057                         onchange=\"filterDlgCheckAction(this)\">";
1058
1059                 $res = $this->pdo->query("SELECT id,description FROM ttrss_filter_actions
1060                         ORDER BY name");
1061
1062                 while ($line = $res->fetch()) {
1063                         $is_selected = ($line["id"] == $action_id) ? "selected='1'" : "";
1064                         printf("<option $is_selected value='%d'>%s</option>", $line["id"], __($line["description"]));
1065                 }
1066
1067                 print "</select>";
1068
1069                 $param_box_hidden = ($action_id == 7 || $action_id == 4 || $action_id == 6 || $action_id == 9) ?
1070                         "" : "display : none";
1071
1072                 $param_hidden = ($action_id == 4 || $action_id == 6) ?
1073                         "" : "display : none";
1074
1075                 $label_param_hidden = ($action_id == 7) ?       "" : "display : none";
1076                 $plugin_param_hidden = ($action_id == 9) ?      "" : "display : none";
1077
1078                 print "<span id=\"filterDlg_paramBox\" style=\"$param_box_hidden\">";
1079                 print " ";
1080                 //print " " . __("with parameters:") . " ";
1081                 print "<input dojoType=\"dijit.form.TextBox\"
1082                         id=\"filterDlg_actionParam\" style=\"$param_hidden\"
1083                         name=\"action_param\" value=\"$action_param\">";
1084
1085                 print_label_select("action_param_label", $action_param,
1086                         "id=\"filterDlg_actionParamLabel\" style=\"$label_param_hidden\"
1087                         dojoType=\"dijit.form.Select\"");
1088
1089                 $filter_actions = PluginHost::getInstance()->get_filter_actions();
1090                 $filter_action_hash = array();
1091
1092                 foreach ($filter_actions as $fclass => $factions) {
1093                         foreach ($factions as $faction) {
1094
1095                                 $filter_action_hash[$fclass . ":" . $faction["action"]] =
1096                                         $fclass . ": " . $faction["description"];
1097                         }
1098                 }
1099
1100                 if (count($filter_action_hash) == 0) {
1101                         $filter_plugin_disabled = "disabled";
1102
1103                         $filter_action_hash["no-data"] = __("No actions available");
1104
1105                 } else {
1106                         $filter_plugin_disabled = "";
1107                 }
1108
1109                 print_select_hash("filterDlg_actionParamPlugin", $action_param, $filter_action_hash,
1110                         "style=\"$plugin_param_hidden\" dojoType=\"dijit.form.Select\" $filter_plugin_disabled",
1111                         "action_param_plugin");
1112
1113                 print "</span>";
1114
1115                 print "&nbsp;"; // tiny layout hack
1116
1117                 print "</div>";
1118
1119                 print "<div class=\"dlgButtons\">";
1120
1121                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterNewActionDlg').execute()\">".
1122                         ($action ? __("Save action") : __('Add action'))."</button> ";
1123
1124                 print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterNewActionDlg').hide()\">".
1125                         __('Cancel')."</button>";
1126
1127                 print "</div>";
1128
1129                 print "</form>";
1130         }
1131
1132         private function getFilterName($id) {
1133
1134                 $sth = $this->pdo->prepare(
1135                         "SELECT title,match_any_rule,COUNT(DISTINCT r.id) AS num_rules,COUNT(DISTINCT a.id) AS num_actions
1136                                 FROM ttrss_filters2 AS f LEFT JOIN ttrss_filters2_rules AS r
1137                                         ON (r.filter_id = f.id)
1138                                                 LEFT JOIN ttrss_filters2_actions AS a
1139                                                         ON (a.filter_id = f.id) WHERE f.id = ? GROUP BY f.title, f.match_any_rule");
1140                 $sth->execute([$id]);
1141
1142                 if ($row = $sth->fetch()) {
1143
1144                         $title = $row["title"];
1145                         $num_rules = $row["num_rules"];
1146                         $num_actions = $row["num_actions"];
1147                         $match_any_rule = sql_bool_to_bool($row["match_any_rule"]);
1148
1149                         if (!$title) $title = __("[No caption]");
1150
1151                         $title = sprintf(_ngettext("%s (%d rule)", "%s (%d rules)", (int) $num_rules), $title, $num_rules);
1152
1153                         $sth = $this->pdo->prepare("SELECT * FROM ttrss_filters2_actions 
1154                                 WHERE filter_id = ? ORDER BY id LIMIT 1");
1155                         $sth->execute([$id]);
1156
1157                         $actions = "";
1158
1159                         if ($line = $sth->fetch()) {
1160                                 $actions = $this->getActionName($line);
1161
1162                                 $num_actions -= 1;
1163                         }
1164
1165                         if ($match_any_rule) $title .= " (" . __("matches any rule") . ")";
1166
1167                         if ($num_actions > 0)
1168                                 $actions = sprintf(_ngettext("%s (+%d action)", "%s (+%d actions)", (int) $num_actions), $actions, $num_actions);
1169
1170                         return [$title, $actions];
1171                 }
1172
1173                 return [];
1174         }
1175
1176         function join() {
1177                 $ids = explode(",", $_REQUEST["ids"]);
1178
1179                 if (count($ids) > 1) {
1180                         $base_id = array_shift($ids);
1181                         $ids_qmarks = arr_qmarks($ids);
1182
1183                         $this->pdo->beginTransaction();
1184
1185                         $sth = $this->pdo->prepare("UPDATE ttrss_filters2_rules
1186                                 SET filter_id = ? WHERE filter_id IN ($ids_qmarks)");
1187                         $sth->execute(array_merge([$base_id], $ids));
1188
1189                         $sth = $this->pdo->prepare("UPDATE ttrss_filters2_actions
1190                                 SET filter_id = ? WHERE filter_id IN ($ids_qmarks)");
1191                         $sth->execute(array_merge([$base_id], $ids));
1192
1193                         $sth = $this->pdo->prepare("DELETE FROM ttrss_filters2 WHERE id IN ($ids_qmarks)");
1194                         $sth->execute($ids);
1195
1196                         $sth = $this->pdo->prepare("UPDATE ttrss_filters2 SET match_any_rule = true WHERE id = ?");
1197                         $sth->execute([$base_id]);
1198
1199                         $this->pdo->commit();
1200
1201                         $this->optimizeFilter($base_id);
1202
1203                 }
1204         }
1205
1206         private function optimizeFilter($id) {
1207
1208                 $this->pdo->beginTransaction();
1209
1210                 $sth = $this->pdo->prepare("SELECT * FROM ttrss_filters2_actions
1211                         WHERE filter_id = ?");
1212                 $sth->execute([$id]);
1213
1214                 $tmp = array();
1215                 $dupe_ids = array();
1216
1217                 while ($line = $sth->fetch()) {
1218                         $id = $line["id"];
1219                         unset($line["id"]);
1220
1221                         if (array_search($line, $tmp) === false) {
1222                                 array_push($tmp, $line);
1223                         } else {
1224                                 array_push($dupe_ids, $id);
1225                         }
1226                 }
1227
1228                 if (count($dupe_ids) > 0) {
1229                         $ids_str = join(",", $dupe_ids);
1230
1231                         $this->pdo->query("DELETE FROM ttrss_filters2_actions WHERE id IN ($ids_str)");
1232                 }
1233
1234                 $sth = $this->pdo->prepare("SELECT * FROM ttrss_filters2_rules
1235                         WHERE filter_id = ?");
1236                 $sth->execute([$id]);
1237
1238                 $tmp = array();
1239                 $dupe_ids = array();
1240
1241                 while ($line = $sth->fetch()) {
1242                         $id = $line["id"];
1243                         unset($line["id"]);
1244
1245                         if (array_search($line, $tmp) === false) {
1246                                 array_push($tmp, $line);
1247                         } else {
1248                                 array_push($dupe_ids, $id);
1249                         }
1250                 }
1251
1252                 if (count($dupe_ids) > 0) {
1253                         $ids_str = join(",", $dupe_ids);
1254
1255                         $this->pdo->query("DELETE FROM ttrss_filters2_rules WHERE id IN ($ids_str)");
1256                 }
1257
1258                 $this->pdo->commit();
1259         }
1260 }