diff --git a/CHANGELOG.md b/CHANGELOG.md
index 93358c1..cdcf090 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
+## [Unreleased]
+
+### Fixed
+
+- Avoid a per-tag database lookup when rendering the tag column in item lists
+
## [2.14.6] - 2026-08-04
### Fixed
diff --git a/hook.php b/hook.php
index 1640651..e4600c7 100644
--- a/hook.php
+++ b/hook.php
@@ -99,6 +99,9 @@ function plugin_tag_getAddSearchOptionsNew($itemtype)
'searchtype' => ['equals','notequals','contains'],
'massiveaction' => false,
'forcegroupby' => true,
+ // Fetch the color alongside the name in the same grouped query,
+ // avoiding a getFromDB() call per tag per row in plugin_tag_giveItem().
+ 'additionalfields' => ['color'],
'joinparams' => [
'beforejoin' => [
'table' => 'glpi_plugin_tag_tagitems',
@@ -133,6 +136,7 @@ function plugin_tag_getAddSearchOptionsNew($itemtype)
'massiveaction' => false,
'forcegroupby' => true,
'usehaving' => true,
+ 'additionalfields' => ['color'],
'joinparams' => [
'condition' => "AND 1=1", // to force distinct complex id than the previous option
'beforejoin' => [
@@ -163,7 +167,15 @@ function plugin_tag_giveItem($type, $field, $data, $num, $linkfield = "")
$separator = '';
foreach ($data[$num] as $tag) {
if (isset($tag['id']) && isset($tag['name'])) {
- $out .= PluginTagTag::getSingleTag($tag['id'], $separator);
+ $color = $tag['color'] ?: '#DDDDDD';
+ $textcolor = idealTextColor($color);
+ $out .= sprintf(
+ "%s%s",
+ htmlentities((string) $color, ENT_QUOTES, "UTF-8"),
+ $textcolor,
+ $separator,
+ htmlentities((string) $tag['name'], ENT_QUOTES, "UTF-8"),
+ );
//For export (CSV, PDF) of GLPI core
$separator = ', ';
}
diff --git a/tests/Units/TagTest.php b/tests/Units/TagTest.php
index 25ea623..e055009 100644
--- a/tests/Units/TagTest.php
+++ b/tests/Units/TagTest.php
@@ -96,6 +96,35 @@ public function testAddHavingEscapesValue(): void
);
}
+ public function testGiveItemRendersColorFromSearchDataWithoutDbLookup(): void
+ {
+ // Tag ids intentionally do not exist in DB: if plugin_tag_giveItem()
+ // ever falls back to a getFromDB() call per tag, this fails because
+ // the lookup returns nothing and the fallback color/empty name show up instead.
+ $data = [
+ 0 => [
+ 'count' => 2,
+ 0 => [
+ 'id' => 999998,
+ 'name' => 'First tag',
+ 'color' => '#123456',
+ ],
+ 1 => [
+ 'id' => 999999,
+ 'name' => 'Second tag',
+ 'color' => '#abcdef',
+ ],
+ ],
+ ];
+
+ $out = plugin_tag_giveItem('Ticket', PluginTagTag::S_OPTION, $data, 0);
+
+ $this->assertStringContainsString('#123456', $out);
+ $this->assertStringContainsString('First tag', $out);
+ $this->assertStringContainsString('#abcdef', $out);
+ $this->assertStringContainsString('Second tag', $out);
+ }
+
public function testUpdateAcceptsScalarTypeMenu(): void
{
$tag = $this->createItem(