aboutsummaryrefslogtreecommitdiff
path: root/app/Models
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2021-07-24 19:32:43 +0200
committerGravatar GitHub <noreply@github.com> 2021-07-24 19:32:43 +0200
commit705be9a6a1608ff195c0f24004d07cb8823fa6de (patch)
tree998bf3c93771e94fe470401d3b7a40a662e4e4d0 /app/Models
parent247dfa6c1ba0f60b7cca63141c94d5754b2bd7e5 (diff)
Search labels (#3709)
* Search labels #fix https://github.com/FreshRSS/FreshRSS/issues/3704 * Documentation * Allow list without quotes * Allow boolean AND searches * Allow searching any label * fix labels alias
Diffstat (limited to 'app/Models')
-rw-r--r--app/Models/EntryDAO.php102
-rw-r--r--app/Models/Search.php149
2 files changed, 220 insertions, 31 deletions
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index 046dc4afc..86f3ac040 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -732,25 +732,6 @@ SQL;
}
$sub_search = '';
- if ($filter->getFeedIds()) {
- $sub_search .= 'AND ' . $alias . 'id_feed IN (';
- foreach ($filter->getFeedIds() as $feed_id) {
- $sub_search .= '?,';
- $values[] = $feed_id;
- }
- $sub_search = rtrim($sub_search, ',');
- $sub_search .= ') ';
- }
- if ($filter->getNotFeedIds()) {
- $sub_search .= 'AND ' . $alias . 'id_feed NOT IN (';
- foreach ($filter->getNotFeedIds() as $feed_id) {
- $sub_search .= '?,';
- $values[] = $feed_id;
- }
- $sub_search = rtrim($sub_search, ',');
- $sub_search .= ') ';
- }
-
if ($filter->getMinDate()) {
$sub_search .= 'AND ' . $alias . 'id >= ? ';
$values[] = "{$filter->getMinDate()}000000";
@@ -800,6 +781,83 @@ SQL;
$sub_search .= ') ';
}
+ if ($filter->getFeedIds()) {
+ foreach ($filter->getFeedIds() as $feed_ids) {
+ $sub_search .= 'AND ' . $alias . 'id_feed IN (';
+ foreach ($feed_ids as $feed_id) {
+ $sub_search .= '?,';
+ $values[] = $feed_id;
+ }
+ $sub_search = rtrim($sub_search, ',');
+ $sub_search .= ') ';
+ }
+ }
+ if ($filter->getNotFeedIds()) {
+ foreach ($filter->getNotFeedIds() as $feed_ids) {
+ $sub_search .= 'AND ' . $alias . 'id_feed NOT IN (';
+ foreach ($feed_ids as $feed_id) {
+ $sub_search .= '?,';
+ $values[] = $feed_id;
+ }
+ $sub_search = rtrim($sub_search, ',');
+ $sub_search .= ') ';
+ }
+ }
+
+ if ($filter->getLabelIds()) {
+ foreach ($filter->getLabelIds() as $label_ids) {
+ if ($label_ids === '*') {
+ $sub_search .= 'AND EXISTS (SELECT et.id_tag FROM `_entrytag` et WHERE et.id_entry = ' . $alias . 'id) ';
+ } else {
+ $sub_search .= 'AND ' . $alias . 'id IN (SELECT et.id_entry FROM `_entrytag` et WHERE et.id_tag IN (';
+ foreach ($label_ids as $label_id) {
+ $sub_search .= '?,';
+ $values[] = $label_id;
+ }
+ $sub_search = rtrim($sub_search, ',');
+ $sub_search .= ')) ';
+ }
+ }
+ }
+ if ($filter->getNotLabelIds()) {
+ foreach ($filter->getNotLabelIds() as $label_ids) {
+ if ($label_ids === '*') {
+ $sub_search .= 'AND NOT EXISTS (SELECT et.id_tag FROM `_entrytag` et WHERE et.id_entry = ' . $alias . 'id) ';
+ } else {
+ $sub_search .= 'AND ' . $alias . 'id NOT IN (SELECT et.id_entry FROM `_entrytag` et WHERE et.id_tag IN (';
+ foreach ($label_ids as $label_id) {
+ $sub_search .= '?,';
+ $values[] = $label_id;
+ }
+ $sub_search = rtrim($sub_search, ',');
+ $sub_search .= ')) ';
+ }
+ }
+ }
+
+ if ($filter->getLabelNames()) {
+ foreach ($filter->getLabelNames() as $label_names) {
+ $sub_search .= 'AND ' . $alias . 'id IN (SELECT et.id_entry FROM `_entrytag` et, `_tag` t WHERE et.id_tag = t.id AND t.name IN (';
+ foreach ($label_names as $label_name) {
+ $sub_search .= '?,';
+ $values[] = $label_name;
+ }
+ $sub_search = rtrim($sub_search, ',');
+ $sub_search .= ')) ';
+ }
+ }
+ if ($filter->getNotLabelNames()) {
+ foreach ($filter->getNotLabelNames() as $label_names) {
+ $sub_search .= 'AND ' . $alias . 'id NOT IN (SELECT et.id_entry FROM `_entrytag` et, `_tag` t WHERE et.id_tag = t.id AND t.name IN (';
+ foreach ($label_names as $label_name) {
+ $sub_search .= '?,';
+ $values[] = $label_name;
+ }
+ $sub_search = rtrim($sub_search, ',');
+ $sub_search .= ')) ';
+ }
+ }
+
if ($filter->getAuthor()) {
foreach ($filter->getAuthor() as $author) {
$sub_search .= 'AND ' . $alias . 'author LIKE ? ';
@@ -913,14 +971,14 @@ SQL;
$where .= 'e.id_feed=? ';
$values[] = intval($id);
break;
- case 't': //Tag
+ case 't': //Tag (label)
$where .= 'et.id_tag=? ';
$values[] = intval($id);
break;
- case 'T': //Any tag
+ case 'T': //Any tag (label)
$where .= '1=1 ';
break;
- case 'ST': //Starred or tagged
+ case 'ST': //Starred or tagged (label)
$where .= 'e.is_favorite=1 OR EXISTS (SELECT et2.id_tag FROM `_entrytag` et2 WHERE et2.id_entry = e.id) ';
break;
default:
diff --git a/app/Models/Search.php b/app/Models/Search.php
index 23d1024aa..20c4f540a 100644
--- a/app/Models/Search.php
+++ b/app/Models/Search.php
@@ -15,6 +15,8 @@ class FreshRSS_Search {
// The following properties are extracted from the raw input
private $feed_ids;
+ private $label_ids;
+ private $label_names;
private $intitle;
private $min_date;
private $max_date;
@@ -26,6 +28,8 @@ class FreshRSS_Search {
private $search;
private $not_feed_ids;
+ private $not_label_ids;
+ private $not_label_names;
private $not_intitle;
private $not_min_date;
private $not_max_date;
@@ -45,6 +49,8 @@ class FreshRSS_Search {
$input = preg_replace('/:&quot;(.*?)&quot;/', ':"\1"', $input);
$input = $this->parseNotFeedIds($input);
+ $input = $this->parseNotLabelIds($input);
+ $input = $this->parseNotLabelNames($input);
$input = $this->parseNotPubdateSearch($input);
$input = $this->parseNotDateSearch($input);
@@ -55,6 +61,8 @@ class FreshRSS_Search {
$input = $this->parseNotTagsSearch($input);
$input = $this->parseFeedIds($input);
+ $input = $this->parseLabelIds($input);
+ $input = $this->parseLabelNames($input);
$input = $this->parsePubdateSearch($input);
$input = $this->parseDateSearch($input);
@@ -83,6 +91,19 @@ class FreshRSS_Search {
return $this->not_feed_ids;
}
+ public function getLabelIds() {
+ return $this->label_ids;
+ }
+ public function getNotlabelIds() {
+ return $this->not_label_ids;
+ }
+ public function getLabelNames() {
+ return $this->label_names;
+ }
+ public function getNotlabelNames() {
+ return $this->not_label_names;
+ }
+
public function getIntitle() {
return $this->intitle;
}
@@ -175,12 +196,15 @@ class FreshRSS_Search {
*/
private function parseFeedIds($input) {
if (preg_match_all('/\bf:(?P<search>[0-9,]*)/', $input, $matches)) {
- $ids_lists = $matches['search'];
$input = str_replace($matches[0], '', $input);
- $ids_lists = self::removeEmptyValues($ids_lists);
- if (!empty($ids_lists[0])) {
- $this->feed_ids = explode(',', $ids_lists[0]);
- array_filter($this->feed_ids, function($v) { $v != ''; });
+ $ids_lists = $matches['search'];
+ $this->feed_ids = [];
+ foreach ($ids_lists as $ids_list) {
+ $feed_ids = explode(',', $ids_list);
+ $feed_ids = self::removeEmptyValues($feed_ids);
+ if (!empty($feed_ids)) {
+ $this->feed_ids[] = $feed_ids;
+ }
}
}
return $input;
@@ -188,12 +212,119 @@ class FreshRSS_Search {
private function parseNotFeedIds($input) {
if (preg_match_all('/[!-]f:(?P<search>[0-9,]*)/', $input, $matches)) {
+ $input = str_replace($matches[0], '', $input);
+ $ids_lists = $matches['search'];
+ $this->not_feed_ids = [];
+ foreach ($ids_lists as $ids_list) {
+ $feed_ids = explode(',', $ids_list);
+ $feed_ids = self::removeEmptyValues($feed_ids);
+ if (!empty($feed_ids)) {
+ $this->not_feed_ids[] = $feed_ids;
+ }
+ }
+ }
+ return $input;
+ }
+
+ /**
+ * Parse the search string to find tags (labels) IDs.
+ *
+ * @param string $input
+ * @return string
+ */
+ private function parseLabelIds($input) {
+ if (preg_match_all('/\b[lL]:(?P<search>[0-9,]+|[*])/', $input, $matches)) {
+ $input = str_replace($matches[0], '', $input);
+ $ids_lists = $matches['search'];
+ $this->label_ids = [];
+ foreach ($ids_lists as $ids_list) {
+ if ($ids_list === '*') {
+ $this->label_ids[] = '*';
+ break;
+ }
+ $label_ids = explode(',', $ids_list);
+ $label_ids = self::removeEmptyValues($label_ids);
+ if (!empty($label_ids)) {
+ $this->label_ids[] = $label_ids;
+ }
+ }
+ }
+ return $input;
+ }
+
+ private function parseNotLabelIds($input) {
+ if (preg_match_all('/[!-][lL]:(?P<search>[0-9,]+|[*])/', $input, $matches)) {
+ $input = str_replace($matches[0], '', $input);
$ids_lists = $matches['search'];
+ $this->not_label_ids = [];
+ foreach ($ids_lists as $ids_list) {
+ if ($ids_list === '*') {
+ $this->not_label_ids[] = '*';
+ break;
+ }
+ $label_ids = explode(',', $ids_list);
+ $label_ids = self::removeEmptyValues($label_ids);
+ if (!empty($label_ids)) {
+ $this->not_label_ids[] = $label_ids;
+ }
+ }
+ }
+ return $input;
+ }
+
+ /**
+ * Parse the search string to find tags (labels) names.
+ *
+ * @param string $input
+ * @return string
+ */
+ private function parseLabelNames($input) {
+ $names_lists = [];
+ if (preg_match_all('/\blabels?:(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) {
+ $names_lists = $matches['search'];
$input = str_replace($matches[0], '', $input);
- $ids_lists = self::removeEmptyValues($ids_lists);
- if (!empty($ids_lists[0])) {
- $this->not_feed_ids = explode(',', $ids_lists[0]);
- array_filter($this->not_feed_ids, function($v) { $v != ''; });
+ }
+ if (preg_match_all('/\blabels?:(?P<search>[^\s"]*)/', $input, $matches)) {
+ $names_lists = array_merge($names_lists, $matches['search']);
+ $input = str_replace($matches[0], '', $input);
+ }
+ if (!empty($names_lists)) {
+ $this->label_names = [];
+ foreach ($names_lists as $names_list) {
+ $names_array = explode(',', $names_list);
+ $names_array = self::removeEmptyValues($names_array);
+ if (!empty($names_array)) {
+ $this->label_names[] = $names_array;
+ }
+ }
+ }
+ return $input;
+ }
+
+ /**
+ * Parse the search string to find tags (labels) names to exclude.
+ *
+ * @param string $input
+ * @return string
+ */
+ private function parseNotLabelNames($input) {
+ $names_lists = [];
+ if (preg_match_all('/[!-]labels?:(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) {
+ $names_lists = $matches['search'];
+ $input = str_replace($matches[0], '', $input);
+ }
+ if (preg_match_all('/[!-]labels?:(?P<search>[^\s"]*)/', $input, $matches)) {
+ $names_lists = array_merge($names_lists, $matches['search']);
+ $input = str_replace($matches[0], '', $input);
+ }
+ if (!empty($names_lists)) {
+ $this->not_label_names = [];
+ foreach ($names_lists as $names_list) {
+ $names_array = explode(',', $names_list);
+ $names_array = self::removeEmptyValues($names_array);
+ if (!empty($names_array)) {
+ $this->not_label_names[] = $names_array;
+ }
}
}
return $input;