filters.php 37 KB

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