Просмотр исходного кода

Add search operator `c:` for categories (#7696)

* Add search operator `c:` for categories
fix https://github.com/FreshRSS/FreshRSS/discussions/7692
Allow searching for e.g. `c:23,34`
Alexandre Alapetite 9 месяцев назад
Родитель
Сommit
c8bbf35534

+ 6 - 0
app/Models/Entry.php

@@ -642,6 +642,12 @@ HTML;
 				if ($ok && $filter->getNotFeedIds() !== null) {
 					$ok &= !in_array($this->feedId, $filter->getNotFeedIds(), true);
 				}
+				if ($ok && $filter->getCategoryIds() !== null) {
+					$ok &= in_array($this->feed()?->categoryId(), $filter->getCategoryIds(), true);
+				}
+				if ($ok && $filter->getNotCategoryIds() !== null) {
+					$ok &= !in_array($this->feed()?->categoryId(), $filter->getNotCategoryIds(), true);
+				}
 				if ($ok && $filter->getAuthor() !== null) {
 					foreach ($filter->getAuthor() as $author) {
 						$ok &= stripos(implode(';', $this->authors), $author) !== false;

+ 19 - 0
app/Models/EntryDAO.php

@@ -917,6 +917,25 @@ SQL;
 				$sub_search .= ') ';
 			}
 
+			if ($filter->getCategoryIds() !== null) {
+				$sub_search .= 'AND ' . $alias . 'id_feed IN (SELECT f.id FROM `_feed` f WHERE f.category IN (';
+				foreach ($filter->getCategoryIds() as $category_id) {
+					$sub_search .= '?,';
+					$values[] = $category_id;
+				}
+				$sub_search = rtrim($sub_search, ',');
+				$sub_search .= ')) ';
+			}
+			if ($filter->getNotCategoryIds() !== null) {
+				$sub_search .= 'AND ' . $alias . 'id_feed NOT IN (SELECT f.id FROM `_feed` f WHERE f.category IN (';
+				foreach ($filter->getNotCategoryIds() as $category_id) {
+					$sub_search .= '?,';
+					$values[] = $category_id;
+				}
+				$sub_search = rtrim($sub_search, ',');
+				$sub_search .= ')) ';
+			}
+
 			if ($filter->getLabelIds() !== null) {
 				if ($filter->getLabelIds() === '*') {
 					$sub_search .= 'AND EXISTS (SELECT et.id_tag FROM `_entrytag` et WHERE et.id_entry = ' . $alias . 'id) ';

+ 51 - 0
app/Models/Search.php

@@ -21,6 +21,8 @@ class FreshRSS_Search implements \Stringable {
 	private ?array $entry_ids = null;
 	/** @var list<int>|null */
 	private ?array $feed_ids = null;
+	/** @var list<int>|null */
+	private ?array $category_ids = null;
 	/** @var list<int>|'*'|null */
 	private $label_ids = null;
 	/** @var list<string>|null */
@@ -62,6 +64,8 @@ class FreshRSS_Search implements \Stringable {
 	private ?array $not_entry_ids = null;
 	/** @var list<int>|null */
 	private ?array $not_feed_ids = null;
+	/** @var list<int>|null */
+	private ?array $not_category_ids = null;
 	/** @var list<int>|'*'|null */
 	private $not_label_ids = null;
 	/** @var list<string>|null */
@@ -107,6 +111,7 @@ class FreshRSS_Search implements \Stringable {
 
 		$input = $this->parseNotEntryIds($input);
 		$input = $this->parseNotFeedIds($input);
+		$input = $this->parseNotCategoryIds($input);
 		$input = $this->parseNotLabelIds($input);
 		$input = $this->parseNotLabelNames($input);
 
@@ -121,6 +126,7 @@ class FreshRSS_Search implements \Stringable {
 
 		$input = $this->parseEntryIds($input);
 		$input = $this->parseFeedIds($input);
+		$input = $this->parseCategoryIds($input);
 		$input = $this->parseLabelIds($input);
 		$input = $this->parseLabelNames($input);
 
@@ -165,6 +171,15 @@ class FreshRSS_Search implements \Stringable {
 		return $this->not_feed_ids;
 	}
 
+	/** @return list<int>|null */
+	public function getCategoryIds(): ?array {
+		return $this->category_ids;
+	}
+	/** @return list<int>|null */
+	public function getNotCategoryIds(): ?array {
+		return $this->not_category_ids;
+	}
+
 	/** @return list<int>|'*'|null */
 	public function getLabelIds(): array|string|null {
 		return $this->label_ids;
@@ -420,6 +435,42 @@ class FreshRSS_Search implements \Stringable {
 		return $input;
 	}
 
+	private function parseCategoryIds(string $input): string {
+		if (preg_match_all('/\\bc:(?P<search>[0-9,]*)/', $input, $matches)) {
+			$input = str_replace($matches[0], '', $input);
+			$ids_lists = $matches['search'];
+			$this->category_ids = [];
+			foreach ($ids_lists as $ids_list) {
+				$category_ids = explode(',', $ids_list);
+				$category_ids = self::removeEmptyValues($category_ids);
+				/** @var list<int> $category_ids */
+				$category_ids = array_map('intval', $category_ids);
+				if (!empty($category_ids)) {
+					$this->category_ids = array_merge($this->category_ids, $category_ids);
+				}
+			}
+		}
+		return $input;
+	}
+
+	private function parseNotCategoryIds(string $input): string {
+		if (preg_match_all('/(?<=[\\s(]|^)[!-]c:(?P<search>[0-9,]*)/', $input, $matches)) {
+			$input = str_replace($matches[0], '', $input);
+			$ids_lists = $matches['search'];
+			$this->not_category_ids = [];
+			foreach ($ids_lists as $ids_list) {
+				$category_ids = explode(',', $ids_list);
+				$category_ids = self::removeEmptyValues($category_ids);
+				/** @var list<int> $category_ids */
+				$category_ids = array_map('intval', $category_ids);
+				if (!empty($category_ids)) {
+					$this->not_category_ids = array_merge($this->not_category_ids, $category_ids);
+				}
+			}
+		}
+		return $input;
+	}
+
 	/**
 	 * Parse the search string to find tags (labels) IDs.
 	 */

+ 1 - 0
docs/en/users/10_filter.md

@@ -46,6 +46,7 @@ It is possible to filter articles by their content by inputting a string in the
 You can use the search field to further refine results:
 
 * by feed ID: `f:123` or multiple feed IDs (*or*): `f:123,234,345`
+* by category ID: `c:23` or multiple category IDs (*or*): `c:23,34,45`
 * by author: `author:name` or `author:'composed name'`
 * by title: `intitle:keyword` or `intitle:'composed keyword'`
 * by text (content): `intext:keyword` or `intext:'composed keyword'`

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

@@ -205,6 +205,7 @@ the search field.
 Il est possible d’utiliser le champ de recherche pour raffiner les résultats :
 
 * par ID de flux : `f:123` ou plusieurs flux (*ou*) : `f:123,234,345`
+* by ID de catégorie : `c:23` ou plusieurs catégories (*ou*): `c:23,34,45`
 * par auteur : `author:nom` ou `author:'nom composé'`
 * par titre : `intitle:mot` ou `intitle:'mot composé'`
 * par texte (contenu) : `intext:mot` ou `intext:'mot composé'`

+ 5 - 0
tests/app/Models/SearchTest.php

@@ -366,6 +366,11 @@ class SearchTest extends PHPUnit\Framework\TestCase {
 					' (((e.id_feed IN (?) )) OR ((e.id_feed IN (?) ) OR (e.id_feed IN (?) ))) ',
 				[1, 2, 3, 4, 5, 6, 7]
 			],
+			[
+				'c:1 OR c:2,3',
+				' (e.id_feed IN (SELECT f.id FROM `_feed` f WHERE f.category IN (?)) ) OR (e.id_feed IN (SELECT f.id FROM `_feed` f WHERE f.category IN (?,?)) ) ',
+				[1, 2, 3]
+			],
 			[
 				'#tag Hello OR (author:Alice inurl:example) OR (f:3 intitle:World) OR L:12',
 				" ((TRIM(e.tags) || ' #' LIKE ? AND (e.title LIKE ? OR e.content LIKE ?) )) OR ((e.author LIKE ? AND e.link LIKE ? )) OR" .