浏览代码

Add a form to create new user queries on the User Queries page (#8623)

* Add form to create user queries
Closes https://github.com/FreshRSS/FreshRSS/issues/6361

Changes proposed in this pull request:

- Add a form to create new user queries on the User Queries page
- Fix the controller to properly handle request data

How to test the feature manually:

1. Open FreshRSS
2. Open Settings
3. Click User queries
4. Create new user query 

* Add tranlation key for title in create new user query
* Fix 'for' conflict with aside in labels
* Fix input widths

* i18n: fr

* make fix-all

* Fix conditions in configureController

* Remove token condition

* Fix ctype_digits condition

* Fix errors

* Fix phpStan error

* Fix syntax and state for checkboxes

* Add new way to create user queries in docs

* Compress image more

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Matheus Roberson 1 周之前
父节点
当前提交
cf5cfeff58

+ 4 - 4
README.fr.md

@@ -228,18 +228,18 @@ Voir le [dépôt dédié à ces extensions](https://github.com/FreshRSS/Extensio
 | Langage | Progression | |
 | - | - | - |
 | Čeština (cs) | ■■■■■■■■・・ 82% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fcs+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Deutsch (de) | ■■■■■■■■■■ 100% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fde+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Deutsch (de) | ■■■■■■■■■・ 99% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fde+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Ελληνικά (el) | ■■■・・・・・・・ 37% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fel+%2F%28TODO%7CDIRTY%29%24%2F) |
 | English (en) | ■■■■■■■■■■ 100% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fen+%2F%28TODO%7CDIRTY%29%24%2F) |
 | English (United States) (en-US) | ■■■■■■■■■■ 100% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fen-US+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Español (es) | ■■■■■■■■■■ 100% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fes+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Español (es) | ■■■■■■■■■・ 99% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fes+%2F%28TODO%7CDIRTY%29%24%2F) |
 | فارسی (fa) | ■■■■■■■■■・ 91% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffa+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Suomi (fi) | ■■■■■■■■■・ 93% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffi+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Français (fr) | ■■■■■■■■■■ 100% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffr+%2F%28TODO%7CDIRTY%29%24%2F) |
 | עברית (he) | ■■■■・・・・・・ 41% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fhe+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Magyar (hu) | ■■■■■■■■■・ 97% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fhu+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Bahasa Indonesia (id) | ■■■■■■■■■・ 90% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fid+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Italiano (it) | ■■■■■■■■■■ 100% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fit+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Italiano (it) | ■■■■■■■■■・ 99% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fit+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 日本語 (ja) | ■■■■■■■■・・ 89% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fja+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 한국어 (ko) | ■■■■■■■■・・ 82% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fko+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Latviešu (lv) | ■■■■■■■・・・ 76% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Flv+%2F%28TODO%7CDIRTY%29%24%2F) |
@@ -251,7 +251,7 @@ Voir le [dépôt dédié à ces extensions](https://github.com/FreshRSS/Extensio
 | Русский (ru) | ■■■■■■■■■・ 97% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fru+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Slovenčina (sk) | ■■■■■■■■・・ 82% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fsk+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Türkçe (tr) | ■■■■■■■■■・ 90% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ftr+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Українська (uk) | ■■■■■■■■■・ 93% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fuk+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Українська (uk) | ■■■■■■■■■・ 92% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fuk+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 简体中文 (zh-CN) | ■■■■■■■■■・ 98% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fzh-CN+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 正體中文 (zh-TW) | ■■■■■■■■・・ 82% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fzh-TW+%2F%28TODO%7CDIRTY%29%24%2F) |
 

+ 4 - 4
README.md

@@ -124,18 +124,18 @@ See the [repository dedicated to those extensions](https://github.com/FreshRSS/E
 | Language | Progress | |
 | - | - | - |
 | Čeština (cs) | ■■■■■■■■・・ 82% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fcs+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Deutsch (de) | ■■■■■■■■■■ 100% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fde+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Deutsch (de) | ■■■■■■■■■・ 99% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fde+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Ελληνικά (el) | ■■■・・・・・・・ 37% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fel+%2F%28TODO%7CDIRTY%29%24%2F) |
 | English (en) | ■■■■■■■■■■ 100% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fen+%2F%28TODO%7CDIRTY%29%24%2F) |
 | English (United States) (en-US) | ■■■■■■■■■■ 100% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fen-US+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Español (es) | ■■■■■■■■■■ 100% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fes+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Español (es) | ■■■■■■■■■・ 99% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fes+%2F%28TODO%7CDIRTY%29%24%2F) |
 | فارسی (fa) | ■■■■■■■■■・ 91% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffa+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Suomi (fi) | ■■■■■■■■■・ 93% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffi+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Français (fr) | ■■■■■■■■■■ 100% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffr+%2F%28TODO%7CDIRTY%29%24%2F) |
 | עברית (he) | ■■■■・・・・・・ 41% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fhe+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Magyar (hu) | ■■■■■■■■■・ 97% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fhu+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Bahasa Indonesia (id) | ■■■■■■■■■・ 90% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fid+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Italiano (it) | ■■■■■■■■■■ 100% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fit+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Italiano (it) | ■■■■■■■■■・ 99% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fit+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 日本語 (ja) | ■■■■■■■■・・ 89% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fja+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 한국어 (ko) | ■■■■■■■■・・ 82% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fko+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Latviešu (lv) | ■■■■■■■・・・ 76% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Flv+%2F%28TODO%7CDIRTY%29%24%2F) |
@@ -147,7 +147,7 @@ See the [repository dedicated to those extensions](https://github.com/FreshRSS/E
 | Русский (ru) | ■■■■■■■■■・ 97% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fru+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Slovenčina (sk) | ■■■■■■■■・・ 82% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fsk+%2F%28TODO%7CDIRTY%29%24%2F) |
 | Türkçe (tr) | ■■■■■■■■■・ 90% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ftr+%2F%28TODO%7CDIRTY%29%24%2F) |
-| Українська (uk) | ■■■■■■■■■・ 93% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fuk+%2F%28TODO%7CDIRTY%29%24%2F) |
+| Українська (uk) | ■■■■■■■■■・ 92% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fuk+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 简体中文 (zh-CN) | ■■■■■■■■■・ 98% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fzh-CN+%2F%28TODO%7CDIRTY%29%24%2F) |
 | 正體中文 (zh-TW) | ■■■■■■■■・・ 82% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fzh-TW+%2F%28TODO%7CDIRTY%29%24%2F) |
 

+ 41 - 14
app/Controllers/configureController.php

@@ -578,32 +578,59 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
 	/**
 	 * This action handles the creation of a user query.
 	 *
-	 * It gets the GET parameters and stores them in the configuration query
-	 * storage. Before it is saved, the unwanted parameters are unset to keep
-	 * lean data.
+	 * It gets the GET or POST parameters and stores them in the configuration query
+	 * storage.
 	 */
 	public function bookmarkQueryAction(): void {
 		if (!Minz_Request::isPost()) {
 			Minz_Error::error(403);
 			return;
 		}
-		$queries = [];
-		foreach (FreshRSS_Context::userConf()->queries as $key => $query) {
-			$queries[$key] = (new FreshRSS_UserQuery($query, FreshRSS_Context::categories(), FreshRSS_Context::labels()))->toArray();
+
+		$queries = FreshRSS_Context::userConf()->queries;
+		$id = count($queries);
+
+		/** @var array{get?:string,name?:string,order?:string,search?:string,state?:int,shareRss?:bool,shareOpml?:bool,description?:string,imageUrl?:string} $params */
+		$params = Minz_Request::paramArray('query') ?: array_filter($_GET, 'is_string', ARRAY_FILTER_USE_KEY);
+		$name = ($params['name'] ?? '') ?: _t('conf.query.number', $id + 1);
+		$queryParams = [];
+
+		if (is_string($params['get'] ?? null)) {
+			$queryParams['get'] = $params['get'];
+		}
+		if (is_string($params['order'] ?? null)) {
+			$queryParams['order'] = $params['order'];
+		}
+		if (is_string($params['search'] ?? null)) {
+			// Search must be as plain text to be XML-encoded or URL-encoded depending on the situation
+			$queryParams['search'] = htmlspecialchars_decode($params['search'], ENT_QUOTES);
 		}
-		$params = array_filter($_GET, 'is_string', ARRAY_FILTER_USE_KEY);
-		unset($params['name']);
-		unset($params['rid']);
-		/** @var array{get?:string,name?:string,order?:string,search?:string,state?:int,url?:string,token?:string,shareRss?:bool,shareOpml?:bool,publishLabelsInsteadOfTags?:bool,description?:string,imageUrl?:string} $params */
-		$params['url'] = Minz_Url::display(['params' => $params]);
-		$params['name'] = _t('conf.query.number', count($queries) + 1);
-		$queries[] = (new FreshRSS_UserQuery($params, FreshRSS_Context::categories(), FreshRSS_Context::labels()))->toArray();
+		if (is_array($params['state'] ?? null)) {
+			$queryParams['state'] = (int)array_sum(array_map('intval', $params['state']));
+		}
+		$queryParams['token'] = FreshRSS_UserQuery::generateToken($name);
+		$queryParams['url'] = Minz_Url::display(['params' => $queryParams]);
+		$queryParams['name'] = $name;
+		if (is_string($params['description'] ?? null)) {
+			$queryParams['description'] = $params['description'];
+		}
+		if (is_string($params['imageUrl'] ?? null)) {
+			$queryParams['imageUrl'] = $params['imageUrl'];
+		}
+		if (ctype_digit($params['shareOpml'] ?? '')) {
+			$queryParams['shareOpml'] = (bool)$params['shareOpml'];
+		}
+		if (ctype_digit($params['shareRss'] ?? '')) {
+			$queryParams['shareRss'] = (bool)$params['shareRss'];
+		}
+
+		$queries[$id] = (new FreshRSS_UserQuery($queryParams, FreshRSS_Context::categories(), FreshRSS_Context::labels()))->toArray();
 
 		FreshRSS_Context::userConf()->queries = $queries;
 		FreshRSS_Context::userConf()->save();
 
 		Minz_Request::good(
-			_t('feedback.conf.query_created', $params['name']),
+			_t('feedback.conf.query_created', $name),
 			[ 'c' => 'configure', 'a' => 'queries' ],
 			showNotification: FreshRSS_Context::userConf()->good_notification_timeout > 0
 		);

+ 1 - 0
app/i18n/cs/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Uživatelské dotazy',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'Tento dotaz již není platný. Odkazovaná kategorie nebo kanál byly odstraněny.',
 		'description' => 'Description',	// TODO
 		'filter' => array(

+ 1 - 0
app/i18n/de/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Benutzerabfragen',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'Diese Abfrage ist nicht länger gültig. Die referenzierte Kategorie oder der Feed ist gelöscht worden.',
 		'description' => 'Beschreibung',
 		'filter' => array(

+ 1 - 0
app/i18n/el/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'User queries',	// TODO
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'This query is no longer valid. The referenced category or feed has been deleted.',	// TODO
 		'description' => 'Description',	// TODO
 		'filter' => array(

+ 1 - 0
app/i18n/en-US/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'User queries',	// IGNORE
+		'create' => 'Create new user query',	// IGNORE
 		'deprecated' => 'This query is no longer valid. The referenced category or feed has been deleted.',	// IGNORE
 		'description' => 'Description',	// IGNORE
 		'filter' => array(

+ 1 - 0
app/i18n/en/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'User queries',
+		'create' => 'Create new user query',
 		'deprecated' => 'This query is no longer valid. The referenced category or feed has been deleted.',
 		'description' => 'Description',
 		'filter' => array(

+ 1 - 0
app/i18n/es/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Vistas de usuario',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'Esta vista ya no es válida. La categoría referenciada o fuente ha sido eliminada.',
 		'description' => 'Descripción',
 		'filter' => array(

+ 1 - 0
app/i18n/fa/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => ' پرس و جوهای کاربر',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => ' این عبارت دیگر معتبر نیست. دسته یا فید ارجاع شده حذف شده است.',
 		'description' => 'توضیحات',
 		'filter' => array(

+ 1 - 0
app/i18n/fi/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Käyttäjän kyselyt',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'Kysely ei enää kelpaa. Siinä käytetty luokka tai syöte on poistettu.',
 		'description' => 'Kuvaus',
 		'filter' => array(

+ 1 - 0
app/i18n/fr/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Filtres utilisateurs',
+		'create' => 'Créer un nouveau filtre utilisateur',
 		'deprecated' => 'Ce filtre n’est plus valide. La catégorie ou le flux concerné a été supprimé.',
 		'description' => 'Description',	// IGNORE
 		'filter' => array(

+ 1 - 0
app/i18n/he/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'שאילתות',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'שאילתה זו אינה בתוקף יותר, הפיד או הקטגוריה לייחוס נמחקו.',
 		'description' => 'Description',	// TODO
 		'filter' => array(

+ 1 - 0
app/i18n/hu/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Felhasználói lekérdezések',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'Ez a lekérdezés már nem érvényes. A hivatkozott kategória vagy hírforrás törölve lett.',
 		'description' => 'Leírás',
 		'filter' => array(

+ 1 - 0
app/i18n/id/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Pencarian Pengguna',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'Pencarian ini tidak valid lagi. Kategori atau umpan yang dirujuk telah dihapus.',
 		'description' => 'Deskripsi',
 		'filter' => array(

+ 1 - 0
app/i18n/it/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Ricerche personali',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'Questa query non è più valida. La categoria o il feed di riferimento non stati cancellati.',
 		'description' => 'Descrizione',
 		'filter' => array(

+ 1 - 0
app/i18n/ja/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'ユーザークエリ',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'このクエリは有効ではありません。参照されているカテゴリやフィードはすでに消去されました。',
 		'description' => '説明',
 		'filter' => array(

+ 1 - 0
app/i18n/ko/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => '사용자 쿼리',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => '이 쿼리는 더 이상 유효하지 않습니다. 해당하는 카테고리나 피드가 삭제되었습니다.',
 		'description' => 'Description',	// TODO
 		'filter' => array(

+ 1 - 0
app/i18n/lv/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Lietotāja pieprasījumi',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'Šis pieprasījums vairs nav derīgs. Norādītā kategorija vai barotne ir dzēsta.',
 		'description' => 'Description',	// TODO
 		'filter' => array(

+ 1 - 0
app/i18n/nl/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Gebruikersquery’s (informatie aanvragen)',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'Deze query (informatie aanvraag) is niet langer geldig. De bedoelde categorie of feed is al verwijderd.',
 		'description' => 'Beschrijving',
 		'filter' => array(

+ 1 - 0
app/i18n/oc/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Filtres utilizaires',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'Aqueste filtre es pas valid. La categoria o lo flux concernit es estat suprimit.',
 		'description' => 'Description',	// TODO
 		'filter' => array(

+ 1 - 0
app/i18n/pl/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Zapisane zapytania',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'To zapytanie nie jest już poprawne. Kategoria lub kanał do którego się odnosi już nie istnieje.',
 		'description' => 'Opis',
 		'filter' => array(

+ 1 - 0
app/i18n/pt-BR/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Consultas do usuário',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'Esta não é mais válida. A categoria ou feed relacionado foi deletado.',
 		'description' => 'Descrição',
 		'filter' => array(

+ 1 - 0
app/i18n/pt-PT/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Consultas do utilizador',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'Esta não é válida. A categoria ou feed relacionado foi apagado.',
 		'description' => 'Description',	// TODO
 		'filter' => array(

+ 1 - 0
app/i18n/ru/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Пользовательские запросы',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'Этот запрос больше не действителен. Связанная категория или лента была удалена.',
 		'description' => 'Описание',
 		'filter' => array(

+ 1 - 0
app/i18n/sk/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Dopyty používateľa',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'Tento dopyt už nie je platný. Kategória alebo kanál boli vymazané.',
 		'description' => 'Description',	// TODO
 		'filter' => array(

+ 1 - 0
app/i18n/tr/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Kullanıcı sorguları',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'Bu sorgu artık geçerli değil. İlgili kategori veya besleme silinmiş.',
 		'description' => 'Açıklama',
 		'filter' => array(

+ 1 - 0
app/i18n/uk/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => 'Користувацькі запити',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => 'Запит більше не чинний. Згадану категорію чи стрічку видалено.',
 		'description' => 'Опис',
 		'filter' => array(

+ 1 - 0
app/i18n/zh-CN/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => '自定义查询',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => '此查询不再有效。相关的分类或订阅源已被删除。',
 		'description' => '描述',
 		'filter' => array(

+ 1 - 0
app/i18n/zh-TW/conf.php

@@ -149,6 +149,7 @@ return array(
 	),
 	'query' => array(
 		'_' => '自定義查詢',
+		'create' => 'Create new user query',	// TODO
 		'deprecated' => '此查詢不再有效。相關的分類或訂閱源已被刪除。',
 		'description' => 'Description',	// TODO
 		'filter' => array(

+ 133 - 1
app/views/configure/queries.phtml

@@ -4,9 +4,141 @@
 	$this->partial('aside_configure');
 ?>
 <main class="post">
+	<h1><?= _t('conf.query') ?></h1>
+	<h2><?= _t('conf.query.create') ?></h2>
+	<form method="post" action="<?= _url('configure', 'bookmarkQuery') ?>" autocomplete="off" data-auto-leave-validation="1">
+		<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
+
+		<div class="form-group">
+			<label class="group-name" for="new_name"><?= _t('conf.query.name') ?></label>
+			<div class="group-controls">
+				<input type="text" class="w50" name="query[name]" id="new_name" value="" />
+			</div>
+		</div>
+		<div class="form-group">
+			<label class="group-name" for="new_description"><?= _t('conf.query.description') ?></label>
+			<div class="group-controls">
+				<input type="text" class="w50" name="query[description]" id="new_description" value="" />
+			</div>
+		</div>
+		<div class="form-group">
+			<label class="group-name" for="new_imageUrl"><?= _t('conf.query.image_url') ?></label>
+			<div class="group-controls">
+				<input type="text" class="w50" name="query[imageUrl]" id="new_imageUrl" value="" />
+			</div>
+		</div>
+
+		<?php if (FreshRSS_Context::systemConf()->api_enabled) { ?>
+			<fieldset>
+				<legend><?= _t('conf.query.share') ?></legend>
+				<div class="form-group">
+					<div class="group-controls">
+						<label class="checkbox" for="new_shareRss">
+							<input type="checkbox" name="query[shareRss]" id="new_shareRss" value="1" />
+							<?= _t('conf.query.filter.shareRss') ?>
+						</label>
+					</div>
+					<div class="group-controls">
+						<label class="checkbox" for="new_shareOpml">
+							<input type="checkbox" name="query[shareOpml]" id="new_shareOpml" value="1" />
+							<?= _t('conf.query.filter.shareOpml') ?>
+						</label>
+					</div>
+					<p class="help"><?= _i('help') ?> <?= _t('conf.query.share.help') ?></a></p>
+					<p class="help"><?= _i('help') ?> <?= _t('conf.query.help') ?></a></p>
+				</div>
+			</fieldset>
+		<?php } else { ?>
+			<div class="form-group">
+				<label class="group-name"><?= _t('conf.query.share.disabled.title') ?></label>
+				<div class="group-controls">
+				<?= _t('conf.query.share.disabled') ?>
+					<p class="help"><?= _i('help') ?> <?= _t('conf.query.help') ?></a></p>
+				</div>
+			</div>
+		<?php } ?>
+
+		<fieldset>
+			<legend><?= _t('conf.query.filter') ?></legend>
+			<div class="form-group">
+				<label class="group-name" for="new_query_search"><?= _t('conf.query.filter.search') ?></label>
+				<div class="group-controls">
+					<input type="text" class="w50" id="new_query_search" name="query[search]" value=""/>
+					<p class="help"><?= _i('help') ?> <?= _t('gen.menu.search_help') ?></a></p>
+				</div>
+			</div>
+			<div class="form-group">
+				<label class="group-name"><?= _t('conf.query.filter.state') ?></label>
+				<div class="group-controls">
+					<label class="checkbox" for="new_show_read">
+					<input type="checkbox" name="query[state][]" id="new_show_read" value="<?= FreshRSS_Entry::STATE_READ ?>" checked="checked" />
+						<?= _t('index.menu.read') ?>
+					</label>
+					<label class="checkbox" for="new_show_not_read">
+						<input type="checkbox" name="query[state][]" id="new_show_not_read" value="<?= FreshRSS_Entry::STATE_NOT_READ ?>" checked="checked" />
+						<?= _t('index.menu.unread') ?>
+					</label>
+					<label class="checkbox" for="new_show_favorite">
+						<input type="checkbox" name="query[state][]" id="new_show_favorite"	value="<?= FreshRSS_Entry::STATE_FAVORITE ?>" checked="checked" />
+						<?= _t('index.menu.starred') ?>
+					</label>
+					<label class="checkbox" for="new_show_not_favorite">
+						<input type="checkbox" name="query[state][]" id="new_show_not_favorite" value="<?= FreshRSS_Entry::STATE_NOT_FAVORITE ?>" checked="checked" />
+						<?= _t('index.menu.non-starred') ?>
+					</label>
+				</div>
+			</div>
+			<div class="form-group">
+				<label class="group-name" for="new_query_get"><?= _t('conf.query.filter.type') ?></label>
+				<div class="group-controls">
+					<select name="query[get]" class="w50" id="new_query_get" size="10">
+						<option value="Z" ><?= _t('conf.query.get_Z') ?></option>
+						<option value="A" ><?= _t('conf.query.get_A') ?></option>
+						<option value="a" ><?= _t('index.feed.title') ?></option>
+						<option value="i" ><?= _t('index.menu.important') ?></option>
+						<option value="s" ><?= _t('index.feed.title_fav') ?></option>
+						<option value="T" ><?= _t('index.menu.mylabels') ?></option>
+						<optgroup label="<?= _t('conf.query.filter.tags') ?>">
+							<?php foreach ($this->tags as $key => $tag): ?>
+								<option value="t_<?= $tag->id() ?>"><?= $tag->name() ?></option>
+							<?php endforeach ?>
+						</optgroup>
+						<optgroup label="<?= _t('conf.query.filter.categories') ?>">
+							<?php foreach ($this->categories as $key => $category): ?>
+								<option value="c_<?= $category->id() ?>" ><?= $category->name() ?></option>
+							<?php endforeach ?>
+						</optgroup>
+						<optgroup label="<?= _t('conf.query.filter.feeds') ?>">
+							<?php foreach ($this->feeds as $key => $feed): ?>
+								<option value="f_<?= $feed->id() ?>" ><?= $feed->name() ?></option>
+							<?php endforeach ?>
+						</optgroup>
+					</select>
+				</div>
+			</div>
+			<div class="form-group">
+				<label class="group-name" for="new_query_order"><?= _t('conf.query.filter.order') ?></label>
+				<div class="group-controls">
+					<select name="query[order]" class="w50" id="new_query_order">
+						<option value=""></option>
+						<option value="DESC" ><?= _t('conf.query.order_desc') ?></option>
+						<option value="ASC" ><?= _t('conf.query.order_asc') ?></option>
+					</select>
+				</div>
+			</div>
+
+			<div class="form-group form-actions">
+				<div class="group-controls">
+					<button type="submit" class="btn btn-important"><?= _t('gen.action.submit') ?></button>
+					<button type="reset" class="btn"><?= _t('gen.action.cancel') ?></button>
+				</div>
+			</div>
+		</fieldset>
+	</form>
+
+
 	<form method="post" action="<?= _url('configure', 'queries') ?>" class="draggableList">
 		<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
-		<h1><?= _t('conf.query') ?></h1>
 		<?php if (count($this->queries) < 1) { ?>
 			<div class="alert alert-warn">
 				<p><?= _t('conf.query.no_queries') ?></p>

二进制
docs/en/img/users/user.queries.form.empty.png


+ 11 - 1
docs/en/users/user_queries.md

@@ -5,7 +5,17 @@
 Read about [the filters](./10_filter.md) to learn the different ways to search and filter
 articles in FreshRSS.
 
-## Bookmark the current query
+## Create an user query
+
+### Bookmark the user query
+
+It is possible to create any search query by filling out the form.
+
+![User queries form](../img/users/user.queries.form.empty.png)
+
+Then click submit to save the query
+
+### Bookmark the current query
 
 Once you have a search query with a filter, it can be saved.