diff --git a/CHANGELOG.md b/CHANGELOG.md index cdcf090..177d77c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Avoid a per-tag database lookup when rendering the tag column in item lists +- Fix tag associate item ## [2.14.6] - 2026-08-04 diff --git a/composer.json b/composer.json index de2282c..35792c0 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ }, "autoload": { "psr-4": { + "GlpiPlugin\\Tag\\": "src/", "GlpiPlugin\\Tag\\Tests\\": "tests" } }, diff --git a/inc/tagitem.class.php b/inc/tagitem.class.php index f06709c..feb9dde 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 ""; 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(); + } +} diff --git a/tests/TagTestCase.php b/tests/TagTestCase.php index d5a5ae5..e0b2c96 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' => CREATE | UPDATE | PURGE, + 'rights' => $rights, ], [ 'profiles_id' => $user_profile, @@ -72,17 +72,13 @@ 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'], - ], - ); - $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 1c00b69..76fcf2b 100644 --- a/tests/Units/TagItemTest.php +++ b/tests/Units/TagItemTest.php @@ -30,17 +30,20 @@ namespace GlpiPlugin\Tag\Tests\Units; +use Computer; use GlpiPlugin\Tag\Tests\TagTestCase; +use PluginTagTagItem; use Ticket; final class TagItemTest extends TagTestCase { + private const TECH_USER = ['login' => 'tech', 'pass' => 'tech']; + public function testTagsFromTicket(): void { $tagID1 = $this->createTag('TicketTag1'); $tagID2 = $this->createTag('TicketTag2'); - $ticket = new Ticket(); $ticket->add([ 'name' => 'Ticket add Tag', @@ -56,4 +59,22 @@ public function testTagsFromTicket(): void $this->isItemTagged($ticket, $tagID2); } + public function testTagAssociationCreatesLink(): void + { + $this->loginAs(self::TECH_USER); + + $tag = $this->createTag('MyTag', ['Computer']); + $computer = $this->createItem(Computer::class, [ + 'name' => 'Computer to tag', + 'entities_id' => 0, + ]); + + $this->createItem(PluginTagTagItem::class, [ + 'plugin_tag_tags_id' => $tag, + 'itemtype' => Computer::class, + 'items_id' => $computer->getID(), + ]); + + $this->isItemTagged($computer, $tag); + } }
" . __s('Add an item') . "