Browse Source

Implement negation for searching by date intervals (#2869)

* Implement negation for searching by date intervals

#fix https://github.com/FreshRSS/FreshRSS/issues/2866
Allow searching for e.g. `!date:P1W` to exlude all articles newer than
one week.
More generally, allows exclusion on some date intervals.

* Fix OR
Alexandre Alapetite 6 years ago
parent
commit
61c8026ac9
4 changed files with 76 additions and 4 deletions
  1. 32 0
      app/Models/EntryDAO.php
  2. 42 2
      app/Models/Search.php
  3. 1 1
      docs/en/users/03_Main_view.md
  4. 1 1
      docs/fr/users/03_Main_view.md

+ 32 - 0
app/Models/EntryDAO.php

@@ -720,6 +720,38 @@ SQL;
 					$values[] = $filter->getMaxPubdate();
 				}
 
+				//Negation of date intervals must be combined by OR
+				if ($filter->getNotMinDate() || $filter->getNotMaxDate()) {
+					$sub_search .= 'AND (';
+					if ($filter->getNotMinDate()) {
+						$sub_search .= $alias . 'id < ?';
+						$values[] = "{$filter->getNotMinDate()}000000";
+						if ($filter->getNotMaxDate()) {
+							$sub_search .= ' OR ';
+						}
+					}
+					if ($filter->getNotMaxDate()) {
+						$sub_search .= $alias . 'id > ?';
+						$values[] = "{$filter->getNotMaxDate()}000000";
+					}
+					$sub_search .= ') ';
+				}
+				if ($filter->getNotMinPubdate() || $filter->getNotMaxPubdate()) {
+					$sub_search .= 'AND (';
+					if ($filter->getNotMinPubdate()) {
+						$sub_search .= $alias . 'date < ?';
+						$values[] = $filter->getNotMinPubdate();
+						if ($filter->getNotMaxPubdate()) {
+							$sub_search .= ' OR ';
+						}
+					}
+					if ($filter->getNotMaxPubdate()) {
+						$sub_search .= $alias . 'date > ?';
+						$values[] = $filter->getNotMaxPubdate();
+					}
+					$sub_search .= ') ';
+				}
+
 				if ($filter->getAuthor()) {
 					foreach ($filter->getAuthor() as $author) {
 						$sub_search .= 'AND ' . $alias . 'author LIKE ? ';

+ 42 - 2
app/Models/Search.php

@@ -24,6 +24,10 @@ class FreshRSS_Search {
 	private $search;
 
 	private $not_intitle;
+	private $not_min_date;
+	private $not_max_date;
+	private $not_min_pubdate;
+	private $not_max_pubdate;
 	private $not_inurl;
 	private $not_author;
 	private $not_tags;
@@ -37,6 +41,9 @@ class FreshRSS_Search {
 
 		$input = preg_replace('/:&quot;(.*?)&quot;/', ':"\1"', $input);
 
+		$input = $this->parseNotPubdateSearch($input);
+		$input = $this->parseNotDateSearch($input);
+
 		$input = $this->parseNotIntitleSearch($input);
 		$input = $this->parseNotAuthorSearch($input);
 		$input = $this->parseNotInurlSearch($input);
@@ -72,7 +79,9 @@ class FreshRSS_Search {
 	public function getMinDate() {
 		return $this->min_date;
 	}
-
+	public function getNotMinDate() {
+		return $this->not_min_date;
+	}
 	public function setMinDate($value) {
 		return $this->min_date = $value;
 	}
@@ -80,7 +89,9 @@ class FreshRSS_Search {
 	public function getMaxDate() {
 		return $this->max_date;
 	}
-
+	public function getNotMaxDate() {
+		return $this->not_max_date;
+	}
 	public function setMaxDate($value) {
 		return $this->max_date = $value;
 	}
@@ -88,10 +99,16 @@ class FreshRSS_Search {
 	public function getMinPubdate() {
 		return $this->min_pubdate;
 	}
+	public function getNotMinPubdate() {
+		return $this->not_min_pubdate;
+	}
 
 	public function getMaxPubdate() {
 		return $this->max_pubdate;
 	}
+	public function getNotMaxPubdate() {
+		return $this->not_max_pubdate;
+	}
 
 	public function getInurl() {
 		return $this->inurl;
@@ -257,6 +274,18 @@ class FreshRSS_Search {
 		return $input;
 	}
 
+	private function parseNotDateSearch($input) {
+		if (preg_match_all('/[!-]date:(?P<search>[^\s]*)/', $input, $matches)) {
+			$input = str_replace($matches[0], '', $input);
+			$dates = self::removeEmptyValues($matches['search']);
+			if (!empty($dates[0])) {
+				list($this->not_min_date, $this->not_max_date) = parseDateInterval($dates[0]);
+			}
+		}
+		return $input;
+	}
+
+
 	/**
 	 * Parse the search string to find pubdate keyword and the search related
 	 * to it.
@@ -276,6 +305,17 @@ class FreshRSS_Search {
 		return $input;
 	}
 
+	private function parseNotPubdateSearch($input) {
+		if (preg_match_all('/[!-]pubdate:(?P<search>[^\s]*)/', $input, $matches)) {
+			$input = str_replace($matches[0], '', $input);
+			$dates = self::removeEmptyValues($matches['search']);
+			if (!empty($dates[0])) {
+				list($this->not_min_pubdate, $this->not_max_pubdate) = parseDateInterval($dates[0]);
+			}
+		}
+		return $input;
+	}
+
 	/**
 	 * Parse the search string to find tags keyword (# followed by a word)
 	 * and the search related to it.

+ 1 - 1
docs/en/users/03_Main_view.md

@@ -200,7 +200,7 @@ You can use the search field to further refine results:
 Be careful not to enter a space between the operator and the search value.
 
 Some operators can be used negatively, to exclude articles, with the same syntax as above, but prefixed by a `!` or `-`:
-`-author:name`, `-intitle:keyword`, `-inurl:keyword`, `-#tag`, `!keyword`.
+`-author:name`, `-intitle:keyword`, `-inurl:keyword`, `-#tag`, `!keyword`, `!date:2019`, `!date:P1W`, `!pubdate:P3d/`.
 
 It is also possible to combine keywords to create a more precise filter. For example, you can enter multiple instances of `author:`, `intitle:`, `inurl:`, `#`, and free-text.
 

+ 1 - 1
docs/fr/users/03_Main_view.md

@@ -251,7 +251,7 @@ recherchée.
 
 Certains opérateurs peuvent être utilisé négativement, pour exclure des
 articles, avec la même syntaxe que ci-dessus, mais préfixé par `!` ou `-`
-:`-author:nom`, `-intitle:mot`, `-inurl:mot`, `-#tag`, `!mot`.
+:`-author:nom`, `-intitle:mot`, `-inurl:mot`, `-#tag`, `!mot`, `!date:2019`, `!date:P1W`, `!pubdate:P3d/`.
 
 Il est également possible de combiner les mots-clefs pour faire un filtrage
 encore plus précis, et il est autorisé d’avoir plusieurs instances de :