Explorar o código

Add tag management page (#3121)

The new page allows to create, delete and rename tags.

See #3058
Alexis Degrugillier %!s(int64=5) %!d(string=hai) anos
pai
achega
909fdaca31

+ 39 - 1
app/Controllers/tagController.php

@@ -63,7 +63,7 @@ class FreshRSS_tag_Controller extends Minz_ActionController {
 		}
 		if (!$this->ajax) {
 			Minz_Request::forward(array(
-				'c' => 'index',
+				'c' => 'tag',
 				'a' => 'index',
 			), true);
 		}
@@ -77,4 +77,42 @@ class FreshRSS_tag_Controller extends Minz_ActionController {
 		$tagDAO = FreshRSS_Factory::createTagDao();
 		$this->view->tags = $tagDAO->getTagsForEntry($id_entry);
 	}
+
+	public function addAction() {
+		if (!Minz_Request::isPost()) {
+			Minz_Error::error(405);
+		}
+
+		$name = Minz_Request::param('name');
+		$tagDAO = FreshRSS_Factory::createTagDao();
+		if (null === $tagDAO->searchByName($name)) {
+			$tagDAO->addTag(['name' => $name]);
+			Minz_Request::good('feedback.tag.created', ['c' => 'tag', 'a' => 'index'], true);
+		}
+
+		Minz_Request::bad('feedback.tag.name_exists', ['c' => 'tag', 'a' => 'index'], true);
+	}
+
+	public function renameAction() {
+		if (!Minz_Request::isPost()) {
+			Minz_Error::error(405);
+		}
+
+		$name = Minz_Request::param('name');
+		$tagDAO = FreshRSS_Factory::createTagDao();
+		$newTag = $tagDAO->searchByName($name);
+		if (null === $newTag) {
+			$tagDAO->updateTag(Minz_Request::param('id_tag'), ['name' => $name]);
+		} else {
+			$tagDAO->updateEntryTag(Minz_Request::param('id_tag'), $newTag->id());
+			$tagDAO->deleteTag(Minz_Request::param('id_tag'));
+		}
+
+		Minz_Request::good('feedback.tag.renamed', ['c' => 'tag', 'a' => 'index'], true);
+	}
+
+	public function indexAction() {
+		$tagDAO = FreshRSS_Factory::createTagDao();
+		$this->view->tags = $tagDAO->listTags();
+	}
 }

+ 22 - 0
app/Models/TagDAO.php

@@ -148,6 +148,28 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 		}
 	}
 
+	public function updateEntryTag($oldTagId, $newTagId) {
+		$sql = 'DELETE FROM `_entrytag` WHERE EXISTS (SELECT 1 FROM `_entrytag` AS e WHERE e.id_entry = `_entrytag`.id_entry AND e.id_tag = ? AND `_entrytag`.id_tag = ?)';
+		$stm = $this->pdo->prepare($sql);
+
+		if (!($stm && $stm->execute([$newTagId, $oldTagId]))) {
+			$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
+			Minz_Log::error('SQL error moveTag: ' . $info[2]);
+			return false;
+		}
+
+		$sql = 'UPDATE `_entrytag` SET id_tag = ? WHERE id_tag = ?';
+		$stm = $this->pdo->prepare($sql);
+
+		if ($stm && $stm->execute([$newTagId, $oldTagId])) {
+			return $stm->rowCount();
+		} else {
+			$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
+			Minz_Log::error('SQL error moveTag: ' . $info[2]);
+			return false;
+		}
+	}
+
 	public function searchById($id) {
 		$sql = 'SELECT * FROM `_tag` WHERE id=?';
 		$stm = $this->pdo->prepare($sql);

+ 1 - 0
app/i18n/cz/gen.php

@@ -22,6 +22,7 @@ return array(
 		'promote' => 'Promote',	// TODO - Translation
 		'purge' => 'Purge',	// TODO - Translation
 		'remove' => 'Odstranit',
+		'rename' => 'Rename',	// TODO - Translation
 		'see_website' => 'Navštívit WWW stránku',
 		'submit' => 'Odeslat',
 		'truncate' => 'Smazat všechny články',

+ 9 - 0
app/i18n/cz/sub.php

@@ -108,13 +108,22 @@ return array(
 		'import_export' => 'Import / export',	// TODO - Translation
 		'subscription_management' => 'Správa subskripcí',
 		'subscription_tools' => 'Subscription tools',	// TODO - Translation
+		'tag_management' => 'Tags management',	// TODO - Translation
+	),
+	'tag' => array(
+		'name' => 'Name',	// TODO - Translation
+		'new_name' => 'New name',	// TODO - Translation
+		'old_name' => 'Old name',	// TODO - Translation
 	),
 	'title' => array(
 		'_' => 'Správa subskripcí',
 		'add' => 'Add a feed/a category',	// TODO - Translation
 		'add_category' => 'Add a category',	// TODO - Translation
 		'add_feed' => 'Add a feed',	// TODO - Translation
+		'add_tag' => 'Add a tag',	// TODO - Translation
+		'delete_tag' => 'Delete a tag',	// TODO - Translation
 		'feed_management' => 'Správa RSS kanálů',
+		'rename_tag' => 'Rename a tag',	// TODO - Translation
 		'subscription_tools' => 'Subscription tools',	// TODO - Translation
 	),
 );

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

@@ -22,6 +22,7 @@ return array(
 		'promote' => 'Promote',	// TODO - Translation
 		'purge' => 'Purge',	// TODO - Translation
 		'remove' => 'Entfernen',
+		'rename' => 'Rename',	// TODO - Translation
 		'see_website' => 'Webseite ansehen',
 		'submit' => 'Abschicken',
 		'truncate' => 'Alle Artikel löschen',

+ 9 - 0
app/i18n/de/sub.php

@@ -108,13 +108,22 @@ return array(
 		'import_export' => 'Importieren / Exportieren',
 		'subscription_management' => 'Abonnementverwaltung',
 		'subscription_tools' => 'Abonnement-Tools',
+		'tag_management' => 'Tags management',	// TODO - Translation
+	),
+	'tag' => array(
+		'name' => 'Name',	// TODO - Translation
+		'new_name' => 'New name',	// TODO - Translation
+		'old_name' => 'Old name',	// TODO - Translation
 	),
 	'title' => array(
 		'_' => 'Abonnementverwaltung',
 		'add' => 'Add a feed/a category',	// TODO - Translation
 		'add_category' => 'Add a category',	// TODO - Translation
 		'add_feed' => 'Add a feed',	// TODO - Translation
+		'add_tag' => 'Add a tag',	// TODO - Translation
+		'delete_tag' => 'Delete a tag',	// TODO - Translation
 		'feed_management' => 'Verwaltung der RSS-Feeds',
+		'rename_tag' => 'Rename a tag',	// TODO - Translation
 		'subscription_tools' => 'Abonnement-Tools',
 	),
 );

+ 1 - 0
app/i18n/en-us/gen.php

@@ -22,6 +22,7 @@ return array(
 		'promote' => 'Promote',
 		'purge' => 'Purge',
 		'remove' => 'Remove',
+		'rename' => 'Rename',	// TODO - Translation
 		'see_website' => 'See website',
 		'submit' => 'Submit',
 		'truncate' => 'Delete all articles',

+ 9 - 0
app/i18n/en-us/sub.php

@@ -108,13 +108,22 @@ return array(
 		'import_export' => 'Import / export',
 		'subscription_management' => 'Subscription management',
 		'subscription_tools' => 'Subscription tools',
+		'tag_management' => 'Tags management',	// TODO - Translation
+	),
+	'tag' => array(
+		'name' => 'Name',	// TODO - Translation
+		'new_name' => 'New name',	// TODO - Translation
+		'old_name' => 'Old name',	// TODO - Translation
 	),
 	'title' => array(
 		'_' => 'Subscription management',
 		'add' => 'Add a feed or category',
 		'add_category' => 'Add a category',
 		'add_feed' => 'Add a feed',
+		'add_tag' => 'Add a tag',	// TODO - Translation
+		'delete_tag' => 'Delete a tag',	// TODO - Translation
 		'feed_management' => 'RSS feeds management',
+		'rename_tag' => 'Rename a tag',	// TODO - Translation
 		'subscription_tools' => 'Subscription tools',
 	),
 );

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

@@ -22,6 +22,7 @@ return array(
 		'promote' => 'Promote',
 		'purge' => 'Purge',
 		'remove' => 'Remove',
+		'rename' => 'Rename',
 		'see_website' => 'See website',
 		'submit' => 'Submit',
 		'truncate' => 'Delete all articles',

+ 9 - 0
app/i18n/en/sub.php

@@ -108,13 +108,22 @@ return array(
 		'import_export' => 'Import / export',
 		'subscription_management' => 'Subscriptions management',
 		'subscription_tools' => 'Subscription tools',
+		'tag_management' => 'Tags management',
+	),
+	'tag' => array(
+		'name' => 'Name',
+		'new_name' => 'New name',
+		'old_name' => 'Old name',
 	),
 	'title' => array(
 		'_' => 'Subscriptions management',
 		'add' => 'Add a feed/a category',
 		'add_category' => 'Add a category',
 		'add_feed' => 'Add a feed',
+		'add_tag' => 'Add a tag',
+		'delete_tag' => 'Delete a tag',
 		'feed_management' => 'RSS feeds management',
+		'rename_tag' => 'Rename a tag',
 		'subscription_tools' => 'Subscription tools',
 	),
 );

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

@@ -22,6 +22,7 @@ return array(
 		'promote' => 'Promover',
 		'purge' => 'Eliminar',
 		'remove' => 'Borrar',
+		'rename' => 'Rename',	// TODO - Translation
 		'see_website' => 'Ver web',
 		'submit' => 'Enviar',
 		'truncate' => 'Borrar todos los artículos',

+ 9 - 0
app/i18n/es/sub.php

@@ -108,13 +108,22 @@ return array(
 		'import_export' => 'Importar / exportar',
 		'subscription_management' => 'Administración de suscripciones',
 		'subscription_tools' => 'Subscription tools',	// TODO - Translation
+		'tag_management' => 'Tags management',	// TODO - Translation
+	),
+	'tag' => array(
+		'name' => 'Name',	// TODO - Translation
+		'new_name' => 'New name',	// TODO - Translation
+		'old_name' => 'Old name',	// TODO - Translation
 	),
 	'title' => array(
 		'_' => 'Administración de suscripciones',
 		'add' => 'Add a feed/a category',	// TODO - Translation
 		'add_category' => 'Add a category',	// TODO - Translation
 		'add_feed' => 'Add a feed',	// TODO - Translation
+		'add_tag' => 'Add a tag',	// TODO - Translation
+		'delete_tag' => 'Delete a tag',	// TODO - Translation
 		'feed_management' => 'Administración de fuentes RSS',
+		'rename_tag' => 'Rename a tag',	// TODO - Translation
 		'subscription_tools' => 'Subscription tools',	// TODO - Translation
 	),
 );

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

@@ -22,6 +22,7 @@ return array(
 		'promote' => 'Promouvoir',
 		'purge' => 'Purger',
 		'remove' => 'Supprimer',
+		'rename' => 'Renommer',
 		'see_website' => 'Voir le site',
 		'submit' => 'Valider',
 		'truncate' => 'Supprimer tous les articles',

+ 9 - 0
app/i18n/fr/sub.php

@@ -108,13 +108,22 @@ return array(
 		'import_export' => 'Importer / exporter',
 		'subscription_management' => 'Gestion des abonnements',
 		'subscription_tools' => 'Outils d’abonnement',
+		'tag_management' => 'Gestion des étiquettes',
+	),
+	'tag' => array(
+		'name' => 'Nom',
+		'new_name' => 'Ancien nom',
+		'old_name' => 'Nouveau nom',
 	),
 	'title' => array(
 		'_' => 'Gestion des abonnements',
 		'add' => 'Ajouter un flux/une catégorie',
 		'add_category' => 'Ajouter une catégorie',
 		'add_feed' => 'Ajouter un flux',
+		'add_tag' => 'Ajouter une étiquette',
+		'delete_tag' => 'Supprimer une étiquette',
 		'feed_management' => 'Gestion des flux RSS',
+		'rename_tag' => 'Renommer une étiquette',
 		'subscription_tools' => 'Outils d’abonnement',
 	),
 );

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

@@ -22,6 +22,7 @@ return array(
 		'promote' => 'Promote',	// TODO - Translation
 		'purge' => 'Purge',	// TODO - Translation
 		'remove' => 'Remove',	// TODO - Translation
+		'rename' => 'Rename',	// TODO - Translation
 		'see_website' => 'ראו אתר',
 		'submit' => 'אישור',
 		'truncate' => 'מחיקת כל המאמרים',

+ 9 - 0
app/i18n/he/sub.php

@@ -108,13 +108,22 @@ return array(
 		'import_export' => 'יבוא / יצוא ',
 		'subscription_management' => 'ניהול הרשמות',
 		'subscription_tools' => 'Subscription tools',	// TODO - Translation
+		'tag_management' => 'Tags management',	// TODO - Translation
+	),
+	'tag' => array(
+		'name' => 'Name',	// TODO - Translation
+		'new_name' => 'New name',	// TODO - Translation
+		'old_name' => 'Old name',	// TODO - Translation
 	),
 	'title' => array(
 		'_' => 'ניהול הרשמות',
 		'add' => 'Add a feed/a category',	// TODO - Translation
 		'add_category' => 'Add a category',	// TODO - Translation
 		'add_feed' => 'Add a feed',	// TODO - Translation
+		'add_tag' => 'Add a tag',	// TODO - Translation
+		'delete_tag' => 'Delete a tag',	// TODO - Translation
 		'feed_management' => 'ניהול הזנות RSS',
+		'rename_tag' => 'Rename a tag',	// TODO - Translation
 		'subscription_tools' => 'Subscription tools',	// TODO - Translation
 	),
 );

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

@@ -22,6 +22,7 @@ return array(
 		'promote' => 'Promote',	// TODO - Translation
 		'purge' => 'Purge',	// TODO - Translation
 		'remove' => 'Rimuovi',
+		'rename' => 'Rename',	// TODO - Translation
 		'see_website' => 'Vai al sito',
 		'submit' => 'Conferma',
 		'truncate' => 'Cancella tutti gli articoli',

+ 9 - 0
app/i18n/it/sub.php

@@ -108,13 +108,22 @@ return array(
 		'import_export' => 'Importa / esporta',
 		'subscription_management' => 'Gestione sottoscrizioni',
 		'subscription_tools' => 'Subscription tools',	// TODO - Translation
+		'tag_management' => 'Tags management',	// TODO - Translation
+	),
+	'tag' => array(
+		'name' => 'Name',	// TODO - Translation
+		'new_name' => 'New name',	// TODO - Translation
+		'old_name' => 'Old name',	// TODO - Translation
 	),
 	'title' => array(
 		'_' => 'Gestione sottoscrizioni',
 		'add' => 'Add a feed/a category',	// TODO - Translation
 		'add_category' => 'Add a category',	// TODO - Translation
 		'add_feed' => 'Add a feed',	// TODO - Translation
+		'add_tag' => 'Add a tag',	// TODO - Translation
+		'delete_tag' => 'Delete a tag',	// TODO - Translation
 		'feed_management' => 'Gestione RSS feeds',
+		'rename_tag' => 'Rename a tag',	// TODO - Translation
 		'subscription_tools' => 'Subscription tools',	// TODO - Translation
 	),
 );

+ 1 - 0
app/i18n/kr/gen.php

@@ -22,6 +22,7 @@ return array(
 		'promote' => 'Promote',	// TODO - Translation
 		'purge' => 'Purge',	// TODO - Translation
 		'remove' => '삭제',
+		'rename' => 'Rename',	// TODO - Translation
 		'see_website' => '웹사이트 열기',
 		'submit' => '설정 저장',
 		'truncate' => '모든 글 삭제',

+ 9 - 0
app/i18n/kr/sub.php

@@ -108,13 +108,22 @@ return array(
 		'import_export' => '불러오기 / 내보내기',
 		'subscription_management' => '구독 관리',
 		'subscription_tools' => '구독 도구',
+		'tag_management' => 'Tags management',	// TODO - Translation
+	),
+	'tag' => array(
+		'name' => 'Name',	// TODO - Translation
+		'new_name' => 'New name',	// TODO - Translation
+		'old_name' => 'Old name',	// TODO - Translation
 	),
 	'title' => array(
 		'_' => '구독 관리',
 		'add' => 'Add a feed/a category',	// TODO - Translation
 		'add_category' => 'Add a category',	// TODO - Translation
 		'add_feed' => 'Add a feed',	// TODO - Translation
+		'add_tag' => 'Add a tag',	// TODO - Translation
+		'delete_tag' => 'Delete a tag',	// TODO - Translation
 		'feed_management' => 'RSS 피드 관리',
+		'rename_tag' => 'Rename a tag',	// TODO - Translation
 		'subscription_tools' => '구독 도구',
 	),
 );

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

@@ -22,6 +22,7 @@ return array(
 		'promote' => 'Bevorderen',
 		'purge' => 'Zuiveren',
 		'remove' => 'Verwijderen',
+		'rename' => 'Rename',	// TODO - Translation
 		'see_website' => 'Bekijk website',
 		'submit' => 'Opslaan',
 		'truncate' => 'Verwijder alle artikelen',

+ 9 - 0
app/i18n/nl/sub.php

@@ -108,13 +108,22 @@ return array(
 		'import_export' => 'Importeer / exporteer',
 		'subscription_management' => 'Abonnementenbeheer',
 		'subscription_tools' => 'Hulpmiddelen voor abonnementen',
+		'tag_management' => 'Tags management',	// TODO - Translation
+	),
+	'tag' => array(
+		'name' => 'Name',	// TODO - Translation
+		'new_name' => 'New name',	// TODO - Translation
+		'old_name' => 'Old name',	// TODO - Translation
 	),
 	'title' => array(
 		'_' => 'Abonnementenbeheer',
 		'add' => 'Feed of categorie toevoegen',
 		'add_category' => 'Categorie toevoegen',
 		'add_feed' => 'Feed toevoegen',
+		'add_tag' => 'Add a tag',	// TODO - Translation
+		'delete_tag' => 'Delete a tag',	// TODO - Translation
 		'feed_management' => 'RSS-feedbeheer',
+		'rename_tag' => 'Rename a tag',	// TODO - Translation
 		'subscription_tools' => 'Hulpmiddelen voor abonnementen',
 	),
 );

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

@@ -22,6 +22,7 @@ return array(
 		'promote' => 'Promòure',
 		'purge' => 'Purgar',
 		'remove' => 'Levar',
+		'rename' => 'Rename',	// TODO - Translation
 		'see_website' => 'Veire lo site',
 		'submit' => 'Mandar',
 		'truncate' => 'Suprimir totes los articles',

+ 9 - 0
app/i18n/oc/sub.php

@@ -108,13 +108,22 @@ return array(
 		'import_export' => 'Importar / Exportar',
 		'subscription_management' => 'Gestion dels abonaments',
 		'subscription_tools' => 'Aisinas d’abonament',
+		'tag_management' => 'Tags management',	// TODO - Translation
+	),
+	'tag' => array(
+		'name' => 'Name',	// TODO - Translation
+		'new_name' => 'New name',	// TODO - Translation
+		'old_name' => 'Old name',	// TODO - Translation
 	),
 	'title' => array(
 		'_' => 'Gestion dels abonaments',
 		'add' => 'Add a feed/a category',	// TODO - Translation
 		'add_category' => 'Add a category',	// TODO - Translation
 		'add_feed' => 'Add a feed',	// TODO - Translation
+		'add_tag' => 'Add a tag',	// TODO - Translation
+		'delete_tag' => 'Delete a tag',	// TODO - Translation
 		'feed_management' => 'Gestion dels fluxes RSS',
+		'rename_tag' => 'Rename a tag',	// TODO - Translation
 		'subscription_tools' => 'Aisinas d’abonament',
 	),
 );

+ 1 - 0
app/i18n/pt-br/gen.php

@@ -22,6 +22,7 @@ return array(
 		'promote' => 'Promover',
 		'purge' => 'Limpar',
 		'remove' => 'Remover',
+		'rename' => 'Rename',	// TODO - Translation
 		'see_website' => 'Ver o site',
 		'submit' => 'Enviar',
 		'truncate' => 'Deletar todos os artigos',

+ 9 - 0
app/i18n/pt-br/sub.php

@@ -108,13 +108,22 @@ return array(
 		'import_export' => 'Importar / exportar',
 		'subscription_management' => 'Gerenciamento de inscrições',
 		'subscription_tools' => 'Ferramentas de inscrição',
+		'tag_management' => 'Tags management',	// TODO - Translation
+	),
+	'tag' => array(
+		'name' => 'Name',	// TODO - Translation
+		'new_name' => 'New name',	// TODO - Translation
+		'old_name' => 'Old name',	// TODO - Translation
 	),
 	'title' => array(
 		'_' => 'Gerenciamento de inscrições',
 		'add' => 'Add a feed/a category',	// TODO - Translation
 		'add_category' => 'Add a category',	// TODO - Translation
 		'add_feed' => 'Add a feed',	// TODO - Translation
+		'add_tag' => 'Add a tag',	// TODO - Translation
+		'delete_tag' => 'Delete a tag',	// TODO - Translation
 		'feed_management' => 'Gerenciamento dos RSS feeds',
+		'rename_tag' => 'Rename a tag',	// TODO - Translation
 		'subscription_tools' => 'Ferramentas de inscrição',
 	),
 );

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

@@ -22,6 +22,7 @@ return array(
 		'promote' => 'Promote',	// TODO - Translation
 		'purge' => 'Purge',	// TODO - Translation
 		'remove' => 'Remove',	// TODO - Translation
+		'rename' => 'Rename',	// TODO - Translation
 		'see_website' => 'See website',	// TODO - Translation
 		'submit' => 'Submit',	// TODO - Translation
 		'truncate' => 'Delete all articles',	// TODO - Translation

+ 9 - 0
app/i18n/ru/sub.php

@@ -108,13 +108,22 @@ return array(
 		'import_export' => 'Import / export',	// TODO - Translation
 		'subscription_management' => 'Subscriptions management',	// TODO - Translation
 		'subscription_tools' => 'Subscription tools',	// TODO - Translation
+		'tag_management' => 'Tags management',	// TODO - Translation
+	),
+	'tag' => array(
+		'name' => 'Name',	// TODO - Translation
+		'new_name' => 'New name',	// TODO - Translation
+		'old_name' => 'Old name',	// TODO - Translation
 	),
 	'title' => array(
 		'_' => 'Subscriptions management',	// TODO - Translation
 		'add' => 'Add a feed/a category',	// TODO - Translation
 		'add_category' => 'Add a category',	// TODO - Translation
 		'add_feed' => 'Add a feed',	// TODO - Translation
+		'add_tag' => 'Add a tag',	// TODO - Translation
+		'delete_tag' => 'Delete a tag',	// TODO - Translation
 		'feed_management' => 'RSS feeds management',	// TODO - Translation
+		'rename_tag' => 'Rename a tag',	// TODO - Translation
 		'subscription_tools' => 'Subscription tools',	// TODO - Translation
 	),
 );

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

@@ -22,6 +22,7 @@ return array(
 		'promote' => 'Promote',	// TODO - Translation
 		'purge' => 'Purge',	// TODO - Translation
 		'remove' => 'Odstrániť',
+		'rename' => 'Rename',	// TODO - Translation
 		'see_website' => 'Zobraziť webovú stránku',
 		'submit' => 'Poslať',
 		'truncate' => 'Vymazať všetky články',

+ 9 - 0
app/i18n/sk/sub.php

@@ -108,13 +108,22 @@ return array(
 		'import_export' => 'Import / export',
 		'subscription_management' => 'Správa odoberaných kanálov',
 		'subscription_tools' => 'Nástroje na odoberanie kanálov',
+		'tag_management' => 'Tags management',	// TODO - Translation
+	),
+	'tag' => array(
+		'name' => 'Name',	// TODO - Translation
+		'new_name' => 'New name',	// TODO - Translation
+		'old_name' => 'Old name',	// TODO - Translation
 	),
 	'title' => array(
 		'_' => 'Správa odoberaných kanálov',
 		'add' => 'Add a feed/a category',	// TODO - Translation
 		'add_category' => 'Add a category',	// TODO - Translation
 		'add_feed' => 'Add a feed',	// TODO - Translation
+		'add_tag' => 'Add a tag',	// TODO - Translation
+		'delete_tag' => 'Delete a tag',	// TODO - Translation
 		'feed_management' => 'Správa RSS kanálov',
+		'rename_tag' => 'Rename a tag',	// TODO - Translation
 		'subscription_tools' => 'Nástroje na odoberanie kanálov',
 	),
 );

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

@@ -22,6 +22,7 @@ return array(
 		'promote' => 'Promote',	// TODO - Translation
 		'purge' => 'Purge',	// TODO - Translation
 		'remove' => 'Sil',
+		'rename' => 'Rename',	// TODO - Translation
 		'see_website' => 'Siteyi gör',
 		'submit' => 'Onayla',
 		'truncate' => 'Tüm makaleleri sil',

+ 9 - 0
app/i18n/tr/sub.php

@@ -108,13 +108,22 @@ return array(
 		'import_export' => 'İçe / dışa aktar',
 		'subscription_management' => 'Abonelik yönetimi',
 		'subscription_tools' => 'Subscription tools',	// TODO - Translation
+		'tag_management' => 'Tags management',	// TODO - Translation
+	),
+	'tag' => array(
+		'name' => 'Name',	// TODO - Translation
+		'new_name' => 'New name',	// TODO - Translation
+		'old_name' => 'Old name',	// TODO - Translation
 	),
 	'title' => array(
 		'_' => 'Abonelik yönetimi',
 		'add' => 'Add a feed/a category',	// TODO - Translation
 		'add_category' => 'Add a category',	// TODO - Translation
 		'add_feed' => 'Add a feed',	// TODO - Translation
+		'add_tag' => 'Add a tag',	// TODO - Translation
+		'delete_tag' => 'Delete a tag',	// TODO - Translation
 		'feed_management' => 'RSS akış yönetimi',
+		'rename_tag' => 'Rename a tag',	// TODO - Translation
 		'subscription_tools' => 'Subscription tools',	// TODO - Translation
 	),
 );

+ 1 - 0
app/i18n/zh-cn/gen.php

@@ -22,6 +22,7 @@ return array(
 		'promote' => '设为管理员',
 		'purge' => '清理',
 		'remove' => '删除',
+		'rename' => 'Rename',	// TODO - Translation
 		'see_website' => '网站中查看',
 		'submit' => '提交',
 		'truncate' => '删除所有文章',

+ 9 - 0
app/i18n/zh-cn/sub.php

@@ -108,13 +108,22 @@ return array(
 		'import_export' => '导入/导出',
 		'subscription_management' => '订阅管理',
 		'subscription_tools' => '订阅工具',
+		'tag_management' => 'Tags management',	// TODO - Translation
+	),
+	'tag' => array(
+		'name' => 'Name',	// TODO - Translation
+		'new_name' => 'New name',	// TODO - Translation
+		'old_name' => 'Old name',	// TODO - Translation
 	),
 	'title' => array(
 		'_' => '订阅管理',
 		'add' => 'Add a feed/a category',	// TODO - Translation
 		'add_category' => 'Add a category',	// TODO - Translation
 		'add_feed' => 'Add a feed',	// TODO - Translation
+		'add_tag' => 'Add a tag',	// TODO - Translation
+		'delete_tag' => 'Delete a tag',	// TODO - Translation
 		'feed_management' => '订阅源管理',
+		'rename_tag' => 'Rename a tag',	// TODO - Translation
 		'subscription_tools' => '订阅工具',
 	),
 );

+ 4 - 0
app/layout/aside_subscription.phtml

@@ -16,4 +16,8 @@
 	<li class="item<?= Minz_Request::controllerName() === 'subscription' && Minz_Request::actionName() === 'bookmarklet' ? ' active' : '' ?>">
 		<a href="<?= _url('subscription', 'bookmarklet') ?>"><?= _t('sub.menu.subscription_tools') ?></a>
 	</li>
+
+	<li class="item<?= Minz_Request::controllerName() === 'tag' ? ' active' : '' ?>">
+		<a href="<?= _url('tag', 'index') ?>"><?= _t('sub.menu.tag_management') ?></a>
+	</li>
 </ul>

+ 70 - 0
app/views/tag/index.phtml

@@ -0,0 +1,70 @@
+<?php $this->partial('aside_subscription'); ?>
+
+<div class="post">
+	<a href="<?= _url('index', 'index') ?>"><?= _t('gen.action.back_to_rss_feeds') ?></a>
+
+	<h2><?= _t('sub.title.add_tag') ?></h2>
+		<form id="add_tag" method="post" action="<?= _url('tag', 'add') ?>" autocomplete="off">
+		<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
+		<div class="form-group">
+			<label class="group-name" for="category"><?= _t('sub.tag.name') ?></label>
+			<div class="group-controls">
+				<input id="name" name="name" type="text" autocomplete="off"/>
+			</div>
+		</div>
+
+		<div class="form-group form-actions">
+			<div class="group-controls">
+				<button type="submit" class="btn btn-important"><?= _t('gen.action.add') ?></button>
+			</div>
+		</div>
+	</form>
+
+	<h2><?= _t('sub.title.rename_tag') ?></h2>
+	<form id="rename_tag" method="post" action="<?= _url('tag', 'rename') ?>" autocomplete="off">
+		<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
+		<div class="form-group">
+			<label class="group-name" for="category"><?= _t('sub.tag.old_name') ?></label>
+			<div class="group-controls">
+				<select name="id_tag" id="id_tag">
+					<?php foreach ($this->tags as $tag): ?>
+						<option value="<?= $tag->id() ?>"><?= $tag->name() ?></option>
+					<?php endforeach; ?>
+				</select>
+			</div>
+		</div>
+		<div class="form-group">
+			<label class="group-name" for="category"><?= _t('sub.tag.new_name') ?></label>
+			<div class="group-controls">
+				<input id="name" name="name" type="text" autocomplete="off"/>
+			</div>
+		</div>
+
+		<div class="form-group form-actions">
+			<div class="group-controls">
+				<button type="submit" class="btn btn-attention confirm"><?= _t('gen.action.rename') ?></button>
+			</div>
+		</div>
+	</form>
+
+	<h2><?= _t('sub.title.delete_tag') ?></h2>
+	<form id="delete_tag" method="post" action="<?= _url('tag', 'delete') ?>" autocomplete="off">
+		<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
+		<div class="form-group">
+			<label class="group-name" for="category"><?= _t('sub.tag.name') ?></label>
+			<div class="group-controls">
+				<select name="id_tag" id="id_tag">
+					<?php foreach ($this->tags as $tag): ?>
+						<option value="<?= $tag->id() ?>"><?= $tag->name() ?></option>
+					<?php endforeach; ?>
+				</select>
+			</div>
+		</div>
+
+		<div class="form-group form-actions">
+			<div class="group-controls">
+				<button type="submit" class="btn btn-attention confirm"><?= _t('gen.action.remove') ?></button>
+			</div>
+		</div>
+	</form>
+</div>