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