From d6e1c357f1da692295852a04d5e757a0c1aa11da Mon Sep 17 00:00:00 2001 From: Herafia Date: Fri, 21 Aug 2026 15:12:21 +0200 Subject: [PATCH 01/12] Fix tag associate item --- ajax/add_item_to_tag.php | 72 ++++++++++++++++++ inc/tagitem.class.php | 2 +- tests/TagTestCase.php | 8 +- tests/Units/TagItemTest.php | 141 +++++++++++++++++++++++++++++------- 4 files changed, 191 insertions(+), 32 deletions(-) create mode 100644 ajax/add_item_to_tag.php diff --git a/ajax/add_item_to_tag.php b/ajax/add_item_to_tag.php new file mode 100644 index 0000000..be876fb --- /dev/null +++ b/ajax/add_item_to_tag.php @@ -0,0 +1,72 @@ +. + * ------------------------------------------------------------------------- + * @copyright Copyright (C) 2014-2026 by Teclib'. + * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + * @link https://github.com/pluginsGLPI/tag + * ------------------------------------------------------------------------- + */ + + +Session::checkLoginUser(); + +if (!isset($_POST['plugin_tag_tags_id'], $_POST['itemtype'], $_POST['items_id'])) { + http_response_code(400); + exit; +} + +$tag = new PluginTagTag(); +if (!$tag->getFromDB($_POST['plugin_tag_tags_id']) || !$tag->can($tag->getID(), UPDATE)) { + http_response_code(403); + exit; +} + +$itemtype = $_POST['itemtype']; +if (!is_a($itemtype, CommonDBTM::class, true) || !PluginTagTag::canItemtype($itemtype)) { + http_response_code(400); + exit; +} + +$item = new $itemtype(); +if (!$item->getFromDB($_POST['items_id']) || !$item->canUpdateItem()) { + http_response_code(403); + exit; +} + +$tag_item = new PluginTagTagItem(); +$found = $tag_item->find([ + 'plugin_tag_tags_id' => $tag->getID(), + 'items_id' => $item->getID(), + 'itemtype' => $itemtype, +]); + +if (count($found) === 0) { + $tag_item->add([ + 'plugin_tag_tags_id' => $tag->getID(), + 'items_id' => $item->getID(), + 'itemtype' => $itemtype, + ]); +} + +Html::back(); diff --git a/inc/tagitem.class.php b/inc/tagitem.class.php index 1b2029c..0cc4266 100644 --- a/inc/tagitem.class.php +++ b/inc/tagitem.class.php @@ -197,7 +197,7 @@ public static function showForTag(PluginTagTag $tag) if ($canedit) { echo "
"; echo "
"; + action='" . plugin_tag_geturl() . "/ajax/add_item_to_tag.php'>"; echo ""; echo ""; diff --git a/tests/TagTestCase.php b/tests/TagTestCase.php index 8018c05..d48b261 100644 --- a/tests/TagTestCase.php +++ b/tests/TagTestCase.php @@ -46,7 +46,7 @@ protected function logOut() $_SESSION['glpi_currenttime'] = $ctime; } - public function loginAs(array $credentials): int + public function loginAs(array $credentials, int $rights = CREATE | UPDATE | PURGE): int { global $DB; @@ -59,7 +59,7 @@ public function loginAs(array $credentials): int $DB->update( 'glpi_profilerights', [ - 'rights' => READ | CREATE | UPDATE | PURGE, + 'rights' => $rights, ], [ 'profiles_id' => $user_profile, @@ -72,14 +72,14 @@ public function loginAs(array $credentials): int return $user->getID(); } - public function createTag(string $tagName): int + public function createTag(string $tagName, array $typeMenu = ['Ticket']): int { $tag = new PluginTagTag(); $tag->add( [ 'name' => $tagName, 'is_active' => 1, - 'type_menu' => ['Ticket'], + 'type_menu' => $typeMenu, ], ); $this->assertGreaterThan(0, $tag->getID()); diff --git a/tests/Units/TagItemTest.php b/tests/Units/TagItemTest.php index 0a27e12..7ae46b6 100644 --- a/tests/Units/TagItemTest.php +++ b/tests/Units/TagItemTest.php @@ -30,16 +30,19 @@ namespace GlpiPlugin\Tag\Tests\Units; +use Computer; use GlpiPlugin\Tag\Tests\TagTestCase; -use PluginTagTag; +use PluginTagTagItem; use Ticket; final class TagItemTest extends TagTestCase { + private const TECH_USER = ['login' => 'tech', 'pass' => 'tech']; + + private const SELF_SERVICE_USER = ['login' => 'post-only', 'pass' => 'postonly']; + public function testTagsFromTicket(): void { - $this->login(); - $tagID1 = $this->createTag('TicketTag1'); $tagID2 = $this->createTag('TicketTag2'); @@ -59,36 +62,120 @@ public function testTagsFromTicket(): void $this->isItemTagged($ticket, $tagID2); } - public function testTagOutOfEntityScopeIsNotLinked(): void + public function testAddItemToTagViaAjaxSucceeds(): void { - $this->login(); - - $out_of_scope_entity = getItemByTypeName('Entity', '_test_child_2', true); - $tag = new PluginTagTag(); - $tag->add([ - 'name' => 'OutOfScopeTag', - 'is_active' => 1, - 'type_menu' => ['Ticket'], - 'entities_id' => $out_of_scope_entity, - 'is_recursive' => 0, + $this->loginAs(self::TECH_USER); + + $tagID = $this->createTag('AddItemTag'); + $ticket = $this->createItem(Ticket::class, [ + 'name' => 'Ticket to tag', + 'content' => 'Ticket to tag', ]); - $tagID = $tag->getID(); - $this->assertGreaterThan(0, $tagID); - $this->setEntity('_test_child_1', false); + $_POST['plugin_tag_tags_id'] = $tagID; + $_POST['itemtype'] = Ticket::class; + $_POST['items_id'] = $ticket->getID(); - $ticket = new Ticket(); - $ticket->add([ - 'name' => 'Ticket out of scope tag', - 'content' => 'Ticket out of scope tag', - 'entities_id' => getItemByTypeName('Entity', '_test_child_1', true), - '_plugin_tag_tag_process_form' => 1, - '_plugin_tag_tag_values' => [ - $tagID, - ], + $this->callAjax('plugins/tag/ajax/add_item_to_tag.php'); + + $this->isItemTagged($ticket, $tagID); + } + + public function testAddItemToTagViaAjaxIsIdempotent(): void + { + $this->loginAs(self::TECH_USER); + + $tagID = $this->createTag('AddItemTagTwice'); + $ticket = $this->createItem(Ticket::class, [ + 'name' => 'Ticket to tag twice', + 'content' => 'Ticket to tag twice', + ]); + + $_POST['plugin_tag_tags_id'] = $tagID; + $_POST['itemtype'] = Ticket::class; + $_POST['items_id'] = $ticket->getID(); + + $this->callAjax('plugins/tag/ajax/add_item_to_tag.php'); + $this->callAjax('plugins/tag/ajax/add_item_to_tag.php'); + + $tagItem = new PluginTagTagItem(); + $links = $tagItem->find([ + 'plugin_tag_tags_id' => $tagID, + 'itemtype' => Ticket::class, + 'items_id' => $ticket->getID(), + ]); + $this->assertCount(1, $links); + } + + public function testAddItemToTagViaAjaxFailsForUnknownTag(): void + { + $this->loginAs(self::TECH_USER); + + $ticket = $this->createItem(Ticket::class, [ + 'name' => 'Ticket unknown tag', + 'content' => 'Ticket unknown tag', ]); - $this->assertGreaterThan(0, $ticket->getID()); + + $_POST['plugin_tag_tags_id'] = 999999; + $_POST['itemtype'] = Ticket::class; + $_POST['items_id'] = $ticket->getID(); + + $this->callAjax('plugins/tag/ajax/add_item_to_tag.php'); + + $this->isItemNotTagged($ticket, 999999); + } + + public function testAddItemToTagViaAjaxFailsWhenUserLacksTagUpdateRight(): void + { + $this->loginAs(self::TECH_USER); + + $tagID = $this->createTag('ReadOnlyTag'); + $ticket = $this->createItem(Ticket::class, [ + 'name' => 'Ticket read only tag', + 'content' => 'Ticket read only tag', + ]); + + $this->loginAs(self::TECH_USER, READ); + + $_POST['plugin_tag_tags_id'] = $tagID; + $_POST['itemtype'] = Ticket::class; + $_POST['items_id'] = $ticket->getID(); + + $this->callAjax('plugins/tag/ajax/add_item_to_tag.php'); $this->isItemNotTagged($ticket, $tagID); } + + public function testAddItemToTagViaAjaxFailsWhenUserLacksItemUpdateRight(): void + { + $this->loginAs(self::TECH_USER); + $tagID = $this->createTag('ComputerTag', ['Computer']); + $computer = $this->createItem(Computer::class, [ + 'name' => 'Computer to tag', + 'entities_id' => 0, + ]); + + $this->loginAs(self::SELF_SERVICE_USER); + + $_POST['plugin_tag_tags_id'] = $tagID; + $_POST['itemtype'] = Computer::class; + $_POST['items_id'] = $computer->getID(); + + $this->callAjax('plugins/tag/ajax/add_item_to_tag.php'); + + $this->isItemNotTagged($computer, $tagID); + } + + private function callAjax(string $path): void + { + ob_start(); + try { + include GLPI_ROOT . '/' . $path; + } catch (\Exception $e) { + ob_end_clean(); + throw $e; + } + ob_end_clean(); + } + } From 35457c40506f7af3463b03ebe6f50ade768e7755 Mon Sep 17 00:00:00 2001 From: Herafia Date: Fri, 21 Aug 2026 15:25:47 +0200 Subject: [PATCH 02/12] fix --- ajax/add_item_to_tag.php | 14 ++++++-------- tests/Units/TagItemTest.php | 19 +++++++++++-------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/ajax/add_item_to_tag.php b/ajax/add_item_to_tag.php index be876fb..1ab6624 100644 --- a/ajax/add_item_to_tag.php +++ b/ajax/add_item_to_tag.php @@ -28,30 +28,28 @@ * ------------------------------------------------------------------------- */ +use Glpi\Exception\Http\BadRequestHttpException; +use Glpi\Exception\Http\AccessDeniedHttpException; Session::checkLoginUser(); if (!isset($_POST['plugin_tag_tags_id'], $_POST['itemtype'], $_POST['items_id'])) { - http_response_code(400); - exit; + throw new BadRequestHttpException(__s('Missing parameters', 'tag')); } $tag = new PluginTagTag(); if (!$tag->getFromDB($_POST['plugin_tag_tags_id']) || !$tag->can($tag->getID(), UPDATE)) { - http_response_code(403); - exit; + throw new AccessDeniedHttpException(__s('You do not have permission to update this tag', 'tag')); } $itemtype = $_POST['itemtype']; if (!is_a($itemtype, CommonDBTM::class, true) || !PluginTagTag::canItemtype($itemtype)) { - http_response_code(400); - exit; + throw new BadRequestHttpException(__s('Invalid item type', 'tag')); } $item = new $itemtype(); if (!$item->getFromDB($_POST['items_id']) || !$item->canUpdateItem()) { - http_response_code(403); - exit; + throw new AccessDeniedHttpException(__s('You do not have permission to update this item', 'tag')); } $tag_item = new PluginTagTagItem(); diff --git a/tests/Units/TagItemTest.php b/tests/Units/TagItemTest.php index 7ae46b6..8d3e2f4 100644 --- a/tests/Units/TagItemTest.php +++ b/tests/Units/TagItemTest.php @@ -30,11 +30,16 @@ namespace GlpiPlugin\Tag\Tests\Units; +use Glpi\Exception\Http\AccessDeniedHttpException; +use Exception; use Computer; use GlpiPlugin\Tag\Tests\TagTestCase; use PluginTagTagItem; use Ticket; +use function Safe\ob_end_clean; +use function Safe\ob_start; + final class TagItemTest extends TagTestCase { private const TECH_USER = ['login' => 'tech', 'pass' => 'tech']; @@ -120,9 +125,8 @@ public function testAddItemToTagViaAjaxFailsForUnknownTag(): void $_POST['itemtype'] = Ticket::class; $_POST['items_id'] = $ticket->getID(); + $this->expectException(AccessDeniedHttpException::class); $this->callAjax('plugins/tag/ajax/add_item_to_tag.php'); - - $this->isItemNotTagged($ticket, 999999); } public function testAddItemToTagViaAjaxFailsWhenUserLacksTagUpdateRight(): void @@ -141,9 +145,8 @@ public function testAddItemToTagViaAjaxFailsWhenUserLacksTagUpdateRight(): void $_POST['itemtype'] = Ticket::class; $_POST['items_id'] = $ticket->getID(); + $this->expectException(AccessDeniedHttpException::class); $this->callAjax('plugins/tag/ajax/add_item_to_tag.php'); - - $this->isItemNotTagged($ticket, $tagID); } public function testAddItemToTagViaAjaxFailsWhenUserLacksItemUpdateRight(): void @@ -161,9 +164,8 @@ public function testAddItemToTagViaAjaxFailsWhenUserLacksItemUpdateRight(): void $_POST['itemtype'] = Computer::class; $_POST['items_id'] = $computer->getID(); + $this->expectException(AccessDeniedHttpException::class); $this->callAjax('plugins/tag/ajax/add_item_to_tag.php'); - - $this->isItemNotTagged($computer, $tagID); } private function callAjax(string $path): void @@ -171,10 +173,11 @@ private function callAjax(string $path): void ob_start(); try { include GLPI_ROOT . '/' . $path; - } catch (\Exception $e) { + } catch (Exception $exception) { ob_end_clean(); - throw $e; + throw $exception; } + ob_end_clean(); } From e050af658901e9ffdb31a7c0dd91b6baddb41541 Mon Sep 17 00:00:00 2001 From: Herafia Date: Fri, 21 Aug 2026 15:42:25 +0200 Subject: [PATCH 03/12] fix --- CHANGELOG.md | 1 + tests/Units/TagItemTest.php | 122 +++--------------------------------- 2 files changed, 11 insertions(+), 112 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f858c27..ef4626a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Avoid a per-tag database lookup when rendering the tag column in item lists - Ignore submitted tags that are not visible in the user entities when saving an item +- Fix tag associate item ## [2.14.6] - 2026-08-04 diff --git a/tests/Units/TagItemTest.php b/tests/Units/TagItemTest.php index 8d3e2f4..a8a0a07 100644 --- a/tests/Units/TagItemTest.php +++ b/tests/Units/TagItemTest.php @@ -30,28 +30,21 @@ namespace GlpiPlugin\Tag\Tests\Units; -use Glpi\Exception\Http\AccessDeniedHttpException; -use Exception; use Computer; use GlpiPlugin\Tag\Tests\TagTestCase; +use PluginTagTag; use PluginTagTagItem; use Ticket; -use function Safe\ob_end_clean; -use function Safe\ob_start; - final class TagItemTest extends TagTestCase { private const TECH_USER = ['login' => 'tech', 'pass' => 'tech']; - private const SELF_SERVICE_USER = ['login' => 'post-only', 'pass' => 'postonly']; - public function testTagsFromTicket(): void { $tagID1 = $this->createTag('TicketTag1'); $tagID2 = $this->createTag('TicketTag2'); - $ticket = new Ticket(); $ticket->add([ 'name' => 'Ticket add Tag', @@ -67,118 +60,23 @@ public function testTagsFromTicket(): void $this->isItemTagged($ticket, $tagID2); } - public function testAddItemToTagViaAjaxSucceeds(): void - { - $this->loginAs(self::TECH_USER); - - $tagID = $this->createTag('AddItemTag'); - $ticket = $this->createItem(Ticket::class, [ - 'name' => 'Ticket to tag', - 'content' => 'Ticket to tag', - ]); - - $_POST['plugin_tag_tags_id'] = $tagID; - $_POST['itemtype'] = Ticket::class; - $_POST['items_id'] = $ticket->getID(); - - $this->callAjax('plugins/tag/ajax/add_item_to_tag.php'); - - $this->isItemTagged($ticket, $tagID); - } - - public function testAddItemToTagViaAjaxIsIdempotent(): void + public function testTagAssociationCreatesLink(): void { $this->loginAs(self::TECH_USER); - $tagID = $this->createTag('AddItemTagTwice'); - $ticket = $this->createItem(Ticket::class, [ - 'name' => 'Ticket to tag twice', - 'content' => 'Ticket to tag twice', - ]); - - $_POST['plugin_tag_tags_id'] = $tagID; - $_POST['itemtype'] = Ticket::class; - $_POST['items_id'] = $ticket->getID(); - - $this->callAjax('plugins/tag/ajax/add_item_to_tag.php'); - $this->callAjax('plugins/tag/ajax/add_item_to_tag.php'); - - $tagItem = new PluginTagTagItem(); - $links = $tagItem->find([ - 'plugin_tag_tags_id' => $tagID, - 'itemtype' => Ticket::class, - 'items_id' => $ticket->getID(), - ]); - $this->assertCount(1, $links); - } - - public function testAddItemToTagViaAjaxFailsForUnknownTag(): void - { - $this->loginAs(self::TECH_USER); - - $ticket = $this->createItem(Ticket::class, [ - 'name' => 'Ticket unknown tag', - 'content' => 'Ticket unknown tag', - ]); - - $_POST['plugin_tag_tags_id'] = 999999; - $_POST['itemtype'] = Ticket::class; - $_POST['items_id'] = $ticket->getID(); - - $this->expectException(AccessDeniedHttpException::class); - $this->callAjax('plugins/tag/ajax/add_item_to_tag.php'); - } - - public function testAddItemToTagViaAjaxFailsWhenUserLacksTagUpdateRight(): void - { - $this->loginAs(self::TECH_USER); - - $tagID = $this->createTag('ReadOnlyTag'); - $ticket = $this->createItem(Ticket::class, [ - 'name' => 'Ticket read only tag', - 'content' => 'Ticket read only tag', - ]); - - $this->loginAs(self::TECH_USER, READ); - - $_POST['plugin_tag_tags_id'] = $tagID; - $_POST['itemtype'] = Ticket::class; - $_POST['items_id'] = $ticket->getID(); - - $this->expectException(AccessDeniedHttpException::class); - $this->callAjax('plugins/tag/ajax/add_item_to_tag.php'); - } - - public function testAddItemToTagViaAjaxFailsWhenUserLacksItemUpdateRight(): void - { - $this->loginAs(self::TECH_USER); - $tagID = $this->createTag('ComputerTag', ['Computer']); + $tag = $this->createTag('MyTag', ['Computer']); $computer = $this->createItem(Computer::class, [ 'name' => 'Computer to tag', 'entities_id' => 0, ]); - $this->loginAs(self::SELF_SERVICE_USER); - - $_POST['plugin_tag_tags_id'] = $tagID; - $_POST['itemtype'] = Computer::class; - $_POST['items_id'] = $computer->getID(); - - $this->expectException(AccessDeniedHttpException::class); - $this->callAjax('plugins/tag/ajax/add_item_to_tag.php'); - } - - private function callAjax(string $path): void - { - ob_start(); - try { - include GLPI_ROOT . '/' . $path; - } catch (Exception $exception) { - ob_end_clean(); - throw $exception; - } + $tagItem = new PluginTagTagItem(); + $tagItem->add([ + 'plugin_tag_tags_id' => $tag, + 'itemtype' => Computer::class, + 'items_id' => $computer->getID(), + ]); - ob_end_clean(); + $this->isItemTagged($computer, $tag); } - } From fb8b05616b4af7269ea258af5eb90a2164a42087 Mon Sep 17 00:00:00 2001 From: Herafia Date: Fri, 21 Aug 2026 15:46:37 +0200 Subject: [PATCH 04/12] rector --- tests/Units/TagItemTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Units/TagItemTest.php b/tests/Units/TagItemTest.php index a8a0a07..2f8b203 100644 --- a/tests/Units/TagItemTest.php +++ b/tests/Units/TagItemTest.php @@ -32,7 +32,6 @@ use Computer; use GlpiPlugin\Tag\Tests\TagTestCase; -use PluginTagTag; use PluginTagTagItem; use Ticket; From c9a3c3406089e282a15ef1c2771f242dba5ad293 Mon Sep 17 00:00:00 2001 From: Herafia Date: Mon, 24 Aug 2026 11:49:09 +0200 Subject: [PATCH 05/12] refracto --- ajax/add_item_to_tag.php | 70 --------------------- composer.json | 1 + inc/tagitem.class.php | 2 +- src/Controller/TagItemController.php | 91 ++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 71 deletions(-) delete mode 100644 ajax/add_item_to_tag.php create mode 100644 src/Controller/TagItemController.php diff --git a/ajax/add_item_to_tag.php b/ajax/add_item_to_tag.php deleted file mode 100644 index 1ab6624..0000000 --- a/ajax/add_item_to_tag.php +++ /dev/null @@ -1,70 +0,0 @@ -. - * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2014-2026 by Teclib'. - * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html - * @link https://github.com/pluginsGLPI/tag - * ------------------------------------------------------------------------- - */ - -use Glpi\Exception\Http\BadRequestHttpException; -use Glpi\Exception\Http\AccessDeniedHttpException; - -Session::checkLoginUser(); - -if (!isset($_POST['plugin_tag_tags_id'], $_POST['itemtype'], $_POST['items_id'])) { - throw new BadRequestHttpException(__s('Missing parameters', 'tag')); -} - -$tag = new PluginTagTag(); -if (!$tag->getFromDB($_POST['plugin_tag_tags_id']) || !$tag->can($tag->getID(), UPDATE)) { - throw new AccessDeniedHttpException(__s('You do not have permission to update this tag', 'tag')); -} - -$itemtype = $_POST['itemtype']; -if (!is_a($itemtype, CommonDBTM::class, true) || !PluginTagTag::canItemtype($itemtype)) { - throw new BadRequestHttpException(__s('Invalid item type', 'tag')); -} - -$item = new $itemtype(); -if (!$item->getFromDB($_POST['items_id']) || !$item->canUpdateItem()) { - throw new AccessDeniedHttpException(__s('You do not have permission to update this item', 'tag')); -} - -$tag_item = new PluginTagTagItem(); -$found = $tag_item->find([ - 'plugin_tag_tags_id' => $tag->getID(), - 'items_id' => $item->getID(), - 'itemtype' => $itemtype, -]); - -if (count($found) === 0) { - $tag_item->add([ - 'plugin_tag_tags_id' => $tag->getID(), - 'items_id' => $item->getID(), - 'itemtype' => $itemtype, - ]); -} - -Html::back(); diff --git a/composer.json b/composer.json index a308b53..2a77420 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ }, "autoload": { "psr-4": { + "GlpiPlugin\\Tag\\": "src/", "GlpiPlugin\\Tag\\Tests\\": "tests" } }, diff --git a/inc/tagitem.class.php b/inc/tagitem.class.php index 0cc4266..382f6f9 100644 --- a/inc/tagitem.class.php +++ b/inc/tagitem.class.php @@ -197,7 +197,7 @@ public static function showForTag(PluginTagTag $tag) if ($canedit) { echo "
"; echo ""; + action='/plugins/tag/associate'>"; echo "
" . __s('Add an item') . "
"; echo ""; diff --git a/src/Controller/TagItemController.php b/src/Controller/TagItemController.php new file mode 100644 index 0000000..c9013d3 --- /dev/null +++ b/src/Controller/TagItemController.php @@ -0,0 +1,91 @@ +. + * ------------------------------------------------------------------------- + * @copyright Copyright (C) 2014-2026 by Teclib'. + * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + * @link https://github.com/pluginsGLPI/tag + * ------------------------------------------------------------------------- + */ + +namespace GlpiPlugin\Tag\Controller; + +use CommonDBTM; +use Glpi\Controller\GenericFormController; +use Glpi\Exception\Http\AccessDeniedHttpException; +use Glpi\Exception\Http\BadRequestHttpException; +use Html; +use PluginTagTag; +use PluginTagTagItem; +use Session; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; + +final class TagItemController extends GenericFormController +{ + #[Route('/associate', methods: ['POST'])] + public function associate(Request $request): Response + { + Session::checkLoginUser(); + + $tag_id = $request->request->getInt('plugin_tag_tags_id'); + $itemtype = $request->request->get('itemtype'); + $item_id = $request->request->getInt('items_id'); + + if (!$tag_id || !$itemtype || !$item_id) { + throw new BadRequestHttpException(__s('Missing parameters', 'tag')); + } + + $tag = new PluginTagTag(); + if (!$tag->getFromDB($tag_id) || !$tag->can($tag_id, UPDATE)) { + throw new AccessDeniedHttpException(__s('You do not have permission to update this tag', 'tag')); + } + + if (!is_a($itemtype, CommonDBTM::class, true) || !PluginTagTag::canItemtype($itemtype)) { + throw new BadRequestHttpException(__s('Invalid item type', 'tag')); + } + + $item = new $itemtype(); + if (!$item->getFromDB($item_id) || !$item->canUpdateItem()) { + throw new AccessDeniedHttpException(__s('You do not have permission to update this item', 'tag')); + } + + $tag_item = new PluginTagTagItem(); + $found = $tag_item->find([ + 'plugin_tag_tags_id' => $tag_id, + 'items_id' => $item_id, + 'itemtype' => $itemtype, + ]); + + if (count($found) === 0) { + $tag_item->add([ + 'plugin_tag_tags_id' => $tag_id, + 'items_id' => $item_id, + 'itemtype' => $itemtype, + ]); + } + + Html::back(); + } +} From 48e5a795fc28d84df3e1903bc232945f4d208d96 Mon Sep 17 00:00:00 2001 From: Herafia Date: Mon, 24 Aug 2026 14:41:37 +0200 Subject: [PATCH 06/12] fix --- tests/TagTestCase.php | 14 +++++--------- tests/Units/TagItemTest.php | 3 +-- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/tests/TagTestCase.php b/tests/TagTestCase.php index d48b261..e0b2c96 100644 --- a/tests/TagTestCase.php +++ b/tests/TagTestCase.php @@ -74,15 +74,11 @@ public function loginAs(array $credentials, int $rights = CREATE | UPDATE | PURG public function createTag(string $tagName, array $typeMenu = ['Ticket']): int { - $tag = new PluginTagTag(); - $tag->add( - [ - 'name' => $tagName, - 'is_active' => 1, - 'type_menu' => $typeMenu, - ], - ); - $this->assertGreaterThan(0, $tag->getID()); + $tag = $this->createItem(PluginTagTag::class, [ + 'name' => $tagName, + 'is_active' => 1, + 'type_menu' => $typeMenu, + ], ['type_menu']); return $tag->getID(); } diff --git a/tests/Units/TagItemTest.php b/tests/Units/TagItemTest.php index 2f8b203..76fcf2b 100644 --- a/tests/Units/TagItemTest.php +++ b/tests/Units/TagItemTest.php @@ -69,8 +69,7 @@ public function testTagAssociationCreatesLink(): void 'entities_id' => 0, ]); - $tagItem = new PluginTagTagItem(); - $tagItem->add([ + $this->createItem(PluginTagTagItem::class, [ 'plugin_tag_tags_id' => $tag, 'itemtype' => Computer::class, 'items_id' => $computer->getID(), From 07d4132d7dc68097b806af99df355a656825245d Mon Sep 17 00:00:00 2001 From: Herafia Date: Fri, 28 Aug 2026 15:52:42 +0200 Subject: [PATCH 07/12] fix --- src/Controller/TagItemController.php | 3 ++- tests/Units/TagItemTest.php | 12 ++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Controller/TagItemController.php b/src/Controller/TagItemController.php index c9013d3..4e8cf56 100644 --- a/src/Controller/TagItemController.php +++ b/src/Controller/TagItemController.php @@ -34,6 +34,7 @@ use Glpi\Controller\GenericFormController; use Glpi\Exception\Http\AccessDeniedHttpException; use Glpi\Exception\Http\BadRequestHttpException; +use Glpi\Http\RedirectResponse; use Html; use PluginTagTag; use PluginTagTagItem; @@ -86,6 +87,6 @@ public function associate(Request $request): Response ]); } - Html::back(); + return new RedirectResponse(Html::getBackUrl()); } } diff --git a/tests/Units/TagItemTest.php b/tests/Units/TagItemTest.php index 76fcf2b..de9a90c 100644 --- a/tests/Units/TagItemTest.php +++ b/tests/Units/TagItemTest.php @@ -31,8 +31,9 @@ namespace GlpiPlugin\Tag\Tests\Units; use Computer; +use GlpiPlugin\Tag\Controller\TagItemController; use GlpiPlugin\Tag\Tests\TagTestCase; -use PluginTagTagItem; +use Symfony\Component\HttpFoundation\Request; use Ticket; final class TagItemTest extends TagTestCase @@ -69,12 +70,15 @@ public function testTagAssociationCreatesLink(): void 'entities_id' => 0, ]); - $this->createItem(PluginTagTagItem::class, [ + $controller = new TagItemController(); + $request = Request::create('/plugins/tag/associate', 'POST', [ 'plugin_tag_tags_id' => $tag, - 'itemtype' => Computer::class, - 'items_id' => $computer->getID(), + 'itemtype' => Computer::class, + 'items_id' => $computer->getID(), ]); + $controller->associate($request); + $this->isItemTagged($computer, $tag); } } From c80e113e78fb671baa991865dc7b6c748a254712 Mon Sep 17 00:00:00 2001 From: Laura <35998899+Herafia@users.noreply.github.com> Date: Fri, 4 Sep 2026 16:15:47 +0200 Subject: [PATCH 08/12] Update inc/tagitem.class.php Co-authored-by: Stanislas --- inc/tagitem.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/tagitem.class.php b/inc/tagitem.class.php index 382f6f9..e1c584a 100644 --- a/inc/tagitem.class.php +++ b/inc/tagitem.class.php @@ -197,7 +197,7 @@ public static function showForTag(PluginTagTag $tag) if ($canedit) { echo "
"; echo ""; + action='" . Toolbox::getItemTypeFormURL('PluginTagTagItem') . "'>"; echo "
" . __s('Add an item') . "
"; echo ""; From 9e5eca520a35642b3d59aa2a4da43f128f21120e Mon Sep 17 00:00:00 2001 From: Laura <35998899+Herafia@users.noreply.github.com> Date: Fri, 4 Sep 2026 16:16:01 +0200 Subject: [PATCH 09/12] Update inc/tagitem.class.php Co-authored-by: Stanislas --- inc/tagitem.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/tagitem.class.php b/inc/tagitem.class.php index e1c584a..8b35b1b 100644 --- a/inc/tagitem.class.php +++ b/inc/tagitem.class.php @@ -223,7 +223,7 @@ public static function showForTag(PluginTagTag $tag) ]); echo ""; echo "
" . __s('Add an item') . "
"; echo sprintf("", $instID); - echo ""; + echo ""; echo "
"; From dedebb1faab326f01ef5f5bce28f263d2178c2f1 Mon Sep 17 00:00:00 2001 From: Laura <35998899+Herafia@users.noreply.github.com> Date: Fri, 4 Sep 2026 16:16:10 +0200 Subject: [PATCH 10/12] Update src/Controller/TagItemController.php Co-authored-by: Stanislas --- src/Controller/TagItemController.php | 62 ++++++++++++++-------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/Controller/TagItemController.php b/src/Controller/TagItemController.php index 4e8cf56..4f57bf0 100644 --- a/src/Controller/TagItemController.php +++ b/src/Controller/TagItemController.php @@ -46,47 +46,49 @@ final class TagItemController extends GenericFormController { #[Route('/associate', methods: ['POST'])] - public function associate(Request $request): Response +public function associate(Request $request): Response { - Session::checkLoginUser(); + if ($request->query->getInt('associate') === 1) { + Session::checkLoginUser(); - $tag_id = $request->request->getInt('plugin_tag_tags_id'); - $itemtype = $request->request->get('itemtype'); - $item_id = $request->request->getInt('items_id'); + $tag_id = $request->request->getInt('plugin_tag_tags_id'); + $itemtype = $request->request->get('itemtype'); + $item_id = $request->request->getInt('items_id'); - if (!$tag_id || !$itemtype || !$item_id) { - throw new BadRequestHttpException(__s('Missing parameters', 'tag')); - } - - $tag = new PluginTagTag(); - if (!$tag->getFromDB($tag_id) || !$tag->can($tag_id, UPDATE)) { - throw new AccessDeniedHttpException(__s('You do not have permission to update this tag', 'tag')); - } + if (!$tag_id || !$itemtype || !$item_id) { + throw new BadRequestHttpException(__s('Missing parameters', 'tag')); + } - if (!is_a($itemtype, CommonDBTM::class, true) || !PluginTagTag::canItemtype($itemtype)) { - throw new BadRequestHttpException(__s('Invalid item type', 'tag')); - } + $tag = new PluginTagTag(); + if (!$tag->getFromDB($tag_id) || !$tag->can($tag_id, UPDATE)) { + throw new AccessDeniedHttpException(__s('You do not have permission to update this tag', 'tag')); + } - $item = new $itemtype(); - if (!$item->getFromDB($item_id) || !$item->canUpdateItem()) { - throw new AccessDeniedHttpException(__s('You do not have permission to update this item', 'tag')); - } + if (!is_a($itemtype, CommonDBTM::class, true) || !PluginTagTag::canItemtype($itemtype)) { + throw new BadRequestHttpException(__s('Invalid item type', 'tag')); + } - $tag_item = new PluginTagTagItem(); - $found = $tag_item->find([ - 'plugin_tag_tags_id' => $tag_id, - 'items_id' => $item_id, - 'itemtype' => $itemtype, - ]); + $item = new $itemtype(); + if (!$item->getFromDB($item_id) || !$item->canUpdateItem()) { + throw new AccessDeniedHttpException(__s('You do not have permission to update this item', 'tag')); + } - if (count($found) === 0) { - $tag_item->add([ + $tag_item = new PluginTagTagItem(); + $found = $tag_item->find([ 'plugin_tag_tags_id' => $tag_id, 'items_id' => $item_id, 'itemtype' => $itemtype, ]); - } - return new RedirectResponse(Html::getBackUrl()); + if (count($found) === 0) { + $tag_item->add([ + 'plugin_tag_tags_id' => $tag_id, + 'items_id' => $item_id, + 'itemtype' => $itemtype, + ]); + } + + return new RedirectResponse(Html::getBackUrl()); + } } } From 242c98c5462ba3dbbaf6f22287d2c9b99eba8692 Mon Sep 17 00:00:00 2001 From: Laura <35998899+Herafia@users.noreply.github.com> Date: Fri, 4 Sep 2026 16:16:19 +0200 Subject: [PATCH 11/12] Update src/Controller/TagItemController.php Co-authored-by: Stanislas --- src/Controller/TagItemController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/TagItemController.php b/src/Controller/TagItemController.php index 4f57bf0..fa5f1bb 100644 --- a/src/Controller/TagItemController.php +++ b/src/Controller/TagItemController.php @@ -45,7 +45,7 @@ final class TagItemController extends GenericFormController { - #[Route('/associate', methods: ['POST'])] + #[ItemtypeFormRoute(PluginTagTagItem::class)] public function associate(Request $request): Response { if ($request->query->getInt('associate') === 1) { From 123a2a6716fb842a78b790efa692d86b0380a4bd Mon Sep 17 00:00:00 2001 From: Herafia Date: Fri, 4 Sep 2026 16:45:51 +0200 Subject: [PATCH 12/12] fix --- src/Controller/TagItemController.php | 64 ++++++++++++++-------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/src/Controller/TagItemController.php b/src/Controller/TagItemController.php index fa5f1bb..6a7a571 100644 --- a/src/Controller/TagItemController.php +++ b/src/Controller/TagItemController.php @@ -39,56 +39,54 @@ use PluginTagTag; use PluginTagTagItem; use Session; +use Glpi\Routing\Attribute\ItemtypeFormRoute; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Attribute\Route; final class TagItemController extends GenericFormController { #[ItemtypeFormRoute(PluginTagTagItem::class)] -public function associate(Request $request): Response + public function associate(Request $request): Response { - if ($request->query->getInt('associate') === 1) { - Session::checkLoginUser(); + Session::checkLoginUser(); - $tag_id = $request->request->getInt('plugin_tag_tags_id'); - $itemtype = $request->request->get('itemtype'); - $item_id = $request->request->getInt('items_id'); + $tag_id = $request->request->getInt('plugin_tag_tags_id'); + $itemtype = $request->request->get('itemtype'); + $item_id = $request->request->getInt('items_id'); - if (!$tag_id || !$itemtype || !$item_id) { - throw new BadRequestHttpException(__s('Missing parameters', 'tag')); - } + if (!$tag_id || !$itemtype || !$item_id) { + throw new BadRequestHttpException(__s('Missing parameters', 'tag')); + } + + $tag = new PluginTagTag(); + if (!$tag->getFromDB($tag_id) || !$tag->can($tag_id, UPDATE)) { + throw new AccessDeniedHttpException(__s('You do not have permission to update this tag', 'tag')); + } - $tag = new PluginTagTag(); - if (!$tag->getFromDB($tag_id) || !$tag->can($tag_id, UPDATE)) { - throw new AccessDeniedHttpException(__s('You do not have permission to update this tag', 'tag')); - } + if (!is_a($itemtype, CommonDBTM::class, true) || !PluginTagTag::canItemtype($itemtype)) { + throw new BadRequestHttpException(__s('Invalid item type', 'tag')); + } - if (!is_a($itemtype, CommonDBTM::class, true) || !PluginTagTag::canItemtype($itemtype)) { - throw new BadRequestHttpException(__s('Invalid item type', 'tag')); - } + $item = new $itemtype(); + if (!$item->getFromDB($item_id) || !$item->canUpdateItem()) { + throw new AccessDeniedHttpException(__s('You do not have permission to update this item', 'tag')); + } - $item = new $itemtype(); - if (!$item->getFromDB($item_id) || !$item->canUpdateItem()) { - throw new AccessDeniedHttpException(__s('You do not have permission to update this item', 'tag')); - } + $tag_item = new PluginTagTagItem(); + $found = $tag_item->find([ + 'plugin_tag_tags_id' => $tag_id, + 'items_id' => $item_id, + 'itemtype' => $itemtype, + ]); - $tag_item = new PluginTagTagItem(); - $found = $tag_item->find([ + if (count($found) === 0) { + $tag_item->add([ 'plugin_tag_tags_id' => $tag_id, 'items_id' => $item_id, 'itemtype' => $itemtype, ]); - - if (count($found) === 0) { - $tag_item->add([ - 'plugin_tag_tags_id' => $tag_id, - 'items_id' => $item_id, - 'itemtype' => $itemtype, - ]); - } - - return new RedirectResponse(Html::getBackUrl()); } + + return new RedirectResponse(Html::getBackUrl()); } }