diff --git a/CHANGELOG.md b/CHANGELOG.md index 41cc544a..d98d43e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Prevent the tag field from appearing in the satisfaction survey. - Fix display translations - Removed tag rights during plugin uninstall +- Fix tag dropdown to allow tag creation directly from the field ## [2.14.3] - 2025-12-22 diff --git a/inc/destinationfield.class.php b/inc/destinationfield.class.php index 9604144f..06edaffa 100644 --- a/inc/destinationfield.class.php +++ b/inc/destinationfield.class.php @@ -62,7 +62,7 @@ public function renderConfigForm( throw new InvalidArgumentException("Unexpected config class"); } - [$available_tags, $available_tags_color] = (new PluginTagQuestionType())->getAvailableTags(); + [$available_tags_color, $condition] = (new PluginTagQuestionType())->getAvailableTags(); $twig = TemplateRenderer::getInstance(); return $twig->render('@tag/destinationfield.html.twig', [ @@ -77,8 +77,8 @@ public function renderConfigForm( 'specific_values_extra_field' => [ 'input_name' => $input_name . "[" . PluginTagDestinationFieldConfig::SPECIFIC_TAG_IDS . "]", 'selected_tags' => $config->getSpecificTagIDs() ?? [], - 'available_tags' => $available_tags, 'tags_color' => $available_tags_color, + 'condition' => $condition, ], // Specific additional config for SPECIFIC_ANSWERS strategy diff --git a/inc/questiontype.class.php b/inc/questiontype.class.php index a288eb6e..8178de41 100644 --- a/inc/questiontype.class.php +++ b/inc/questiontype.class.php @@ -79,13 +79,13 @@ public function formatRawAnswer(mixed $answer, Question $question): string #[Override] public function renderAdministrationTemplate(?Question $question): string { - [$available_tags, $available_tags_color] = $this->getAvailableTags(); + [$available_tags_color, $condition] = $this->getAvailableTags(); $twig = TemplateRenderer::getInstance(); return $twig->render('@tag/question_dropdown.html.twig', [ 'input_name' => 'default_value', 'selected_tags' => empty($question?->fields['default_value']) ? [] : explode(',', (string) $question->fields['default_value']), - 'available_tags' => $available_tags, + 'condition' => $condition, 'tags_color' => $available_tags_color, 'dropdown_params' => [ 'no_label' => true, @@ -97,13 +97,13 @@ public function renderAdministrationTemplate(?Question $question): string #[Override] public function renderEndUserTemplate(Question $question): string { - [$available_tags, $available_tags_color] = $this->getAvailableTags($question->getForm()); + [$available_tags_color, $condition] = $this->getAvailableTags($question->getForm()); $twig = TemplateRenderer::getInstance(); return $twig->render('@tag/question_dropdown.html.twig', [ 'input_name' => $question->getEndUserInputName(), 'selected_tags' => empty($question?->fields['default_value']) ? [] : explode(',', (string) $question->fields['default_value']), - 'available_tags' => $available_tags, + 'condition' => $condition, 'tags_color' => $available_tags_color, 'show_search_tooltip' => false, 'dropdown_params' => [ @@ -142,9 +142,8 @@ public function getAvailableTags(?Form $form = null): array } $tag = new PluginTagTag(); - $available_tags = []; $available_tags_color = []; - $result = $tag->find([ + $condition = [ 'is_active' => 1, 'OR' => [ ['type_menu' => ['LIKE', '%\"Ticket\"%']], @@ -154,12 +153,13 @@ public function getAvailableTags(?Form $form = null): array ['type_menu' => ''], ['type_menu' => 'NULL'], ], - ] + getEntitiesRestrictCriteria('', '', $active_entities_ids, true), 'name'); + ] + getEntitiesRestrictCriteria('', '', $active_entities_ids, true); + $result = $tag->find($condition, 'name'); + foreach ($result as $id => $data) { - $available_tags[$id] = $data['name']; $available_tags_color[$id] = $data['color'] ?: '#DDDDDD'; } - return [$available_tags, $available_tags_color]; + return [$available_tags_color, $condition]; } } diff --git a/inc/tag.class.php b/inc/tag.class.php index 664b86d3..3c92cb28 100644 --- a/inc/tag.class.php +++ b/inc/tag.class.php @@ -660,7 +660,6 @@ public static function showTagDropdown($params = []) $available_tags = $tag->find($where, 'name'); foreach ($available_tags as $tag_data) { - $available_tags[$tag_data['id']] = $tag_data['name']; $available_tags_color[$tag_data['id']] = $tag_data['color'] ?: '#DDDDDD'; } @@ -705,7 +704,7 @@ public static function showTagDropdown($params = []) TemplateRenderer::getInstance()->display('@tag/tag_dropdown.html.twig', [ 'extra_class' => $extra_class ?? '', 'selected_tags' => $selected_tags, - 'available_tags' => $available_tags, + 'condition' => $where, 'tags_color' => $available_tags_color ?? [], 'rand' => $rand, 'token_creation' => $token_creation, diff --git a/public/js/common.js b/public/js/common.js index 95ba833c..b36a48ed 100644 --- a/public/js/common.js +++ b/public/js/common.js @@ -52,37 +52,3 @@ var idealTextColor = function(hexTripletColor) { var bgDelta = (components.R * 0.299) + (components.G * 0.587) + (components.B * 0.114); return ((255 - bgDelta) < nThreshold) ? "#000000" : "#E6E6E6"; }; - -var formatOptionSelection = function(option, container) { - var color = (typeof option.color !== 'undefined' && option.color !== '') - ? option.color - : '#DDDDDD'; - var invertedcolor = idealTextColor(color); - - $(container) - .css("background-color", color) - .css("border-color", invertedcolor) - .css("color", invertedcolor) - .children('.select2-selection__choice__remove') - .css("color", invertedcolor); - - var _elt = $(''); - _elt.html(escapeMarkupText(option.text)); - return _elt; -}; - -var formatOptionResult = function(option) { - - var color = (typeof option.color !== 'undefined' && option.color !== '') - ? option.color - : '#DDDDDD'; - var invertedcolor = idealTextColor(color); - - var template = ` - - ${option.text} - - `; - - return $(template); -}; diff --git a/public/js/modules/TagDropdownColorizer.js b/public/js/modules/TagDropdownColorizer.js deleted file mode 100644 index 38aa03ca..00000000 --- a/public/js/modules/TagDropdownColorizer.js +++ /dev/null @@ -1,96 +0,0 @@ -/** - * ------------------------------------------------------------------------- - * Tag plugin for GLPI - * ------------------------------------------------------------------------- - * - * LICENSE - * - * This file is part of Tag. - * - * Tag is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Tag is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Tag. If not, see . - * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2014-2023 by Teclib'. - * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html - * @link https://github.com/pluginsGLPI/tag - * ------------------------------------------------------------------------- - */ - -export class GlpiPluginTagTagDropdownColorizer { - constructor(tagsColor, selector, $container) { - this.tagsColor = tagsColor; - this.selector = selector; - this.$container = $container; - - this.init(); - } - - isDark(hexColor) { - if (!hexColor) return false; - hexColor = hexColor.replace('#', ''); - const r = parseInt(hexColor.substr(0, 2), 16); - const g = parseInt(hexColor.substr(2, 2), 16); - const b = parseInt(hexColor.substr(4, 2), 16); - const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; - return luminance < 0.5; - } - - applyTagColors($select) { - const selectedIds = $select.find('option:selected').map(function() { - return $(this).val(); - }).get(); - - const $container = $select.nextAll('.select2').find('.select2-selection__rendered'); - $container.find('.select2-selection__choice').each((index, element) => { - const id = selectedIds[index]; - const color = this.tagsColor[id]; - if (color) { - $(element).css('background-color', color); - $(element).css('color', this.isDark(color) ? '#eeeeee' : ''); - - // Also style the remove button for better visibility - $(element).find('.select2-selection__choice__remove').css('color', this.isDark(color) ? '#eeeeee' : ''); - } - }); - } - - init() { - const $select = this.$container.find(this.selector); - - $select.each((index, element) => { - this.applyTagColors($(element)); - }); - - $select.on('change select2:select select2:unselect', (event) => { - this.applyTagColors($(event.target)); - }); - - $select.on('select2:open', () => { - setTimeout(() => { - $('.select2-results__option').each((index, element) => { - const matches = element.id.match(/result-[^-]+-(\d+)$/); - if (matches && matches[1]) { - const color = this.tagsColor[matches[1]]; - // Cible uniquement le span SANS la classe select2-rendered__match - $(element).find('span:not(.select2-rendered__match)').css({ - 'background-color': color ? color : '', - 'padding': color ? '2px' : '', - 'color': (color && this.isDark(color)) ? '#fff' : '', - 'border-radius': '2px' - }); - } - }); - }, 0); - }); - } -} diff --git a/templates/destinationfield.html.twig b/templates/destinationfield.html.twig index 98ac115f..ab5ad8bf 100644 --- a/templates/destinationfield.html.twig +++ b/templates/destinationfield.html.twig @@ -34,9 +34,9 @@ {% include "@tag/dropdown.html.twig" with { 'input_name' : specific_values_extra_field.input_name, 'selected_tags' : specific_values_extra_field.selected_tags, - 'available_tags' : specific_values_extra_field.available_tags, 'tags_color' : specific_values_extra_field.tags_color, 'show_search_tooltip': show_search_tooltip|default(true), + 'condition' : condition, 'dropdown_params' : { 'no_label' : true, 'mb' : '', diff --git a/templates/dropdown.html.twig b/templates/dropdown.html.twig index 44220e5a..5c398806 100644 --- a/templates/dropdown.html.twig +++ b/templates/dropdown.html.twig @@ -39,18 +39,14 @@ 'values' : selected_tags, 'multiple': true, 'mb' : '', - 'add_data_attributes': { - 'glpi-plugin-tag-dropdown-uuid': rand + 'condition': condition|default([]), + 'templateResult': 'formatOptionResult', + 'templateSelection': 'formatOptionSelection', + 'specific_tags': { + 'data-glpi-plugin-tag-dropdown-uuid': rand } }|merge(dropdown_params|default({})) %} -{% set field %} - {% do call('Dropdown::showFromArray', [input_name, available_tags, { - 'value': null, - 'rand': rand, - }|merge(options)]) %} -{% endset %} - {% set tooltip_parts = [] %} {% if call('PluginTagTag::canCreate') and show_search_tooltip|default(true) %} @@ -85,25 +81,57 @@ {% endif %} {% endif %} -{{ fields.field( + + +{{ fields.dropdownField( + 'PluginTagTag', input_name, - field ~ tooltip, + selected_tags, __('Tags', 'tag'), - options|merge({'id': 'dropdown_' ~ input_name|replace({'[': '_', ']': '_'}) ~ rand}) + options|merge({ + 'rand': rand, + 'add_field_html': tooltip, + }) ) }} - - {% if show_save_button is defined and show_save_button %} diff --git a/templates/tag_dropdown.html.twig b/templates/tag_dropdown.html.twig index eb1260f6..b242c316 100644 --- a/templates/tag_dropdown.html.twig +++ b/templates/tag_dropdown.html.twig @@ -81,9 +81,9 @@ {% include "@tag/dropdown.html.twig" with { 'input_name' : '_plugin_tag_tag_values', 'selected_tags' : selected_tags, - 'available_tags' : available_tags, 'tags_color' : tags_color, 'rand' : rand, + 'condition' : condition, 'dropdown_params': { 'id' : 'tag_select_', 'disabled' : readOnly,