From 75b420431da068dfd864c8d1a00d5d7ab87a3982 Mon Sep 17 00:00:00 2001 From: Lainow Date: Thu, 12 Feb 2026 10:18:02 +0100 Subject: [PATCH 1/9] . --- ajax/tree_dropdown_children.php | 147 ++++++++++++ .../TreeCascadeDropdownQuestion.php | 220 ++++++++++++++++++ src/Service/ConfigManager.php | 2 + templates/tree_cascade_dropdown.html.twig | 208 +++++++++++++++++ 4 files changed, 577 insertions(+) create mode 100644 ajax/tree_dropdown_children.php create mode 100644 src/Model/QuestionType/TreeCascadeDropdownQuestion.php create mode 100644 templates/tree_cascade_dropdown.html.twig diff --git a/ajax/tree_dropdown_children.php b/ajax/tree_dropdown_children.php new file mode 100644 index 0000000..81cb3cf --- /dev/null +++ b/ajax/tree_dropdown_children.php @@ -0,0 +1,147 @@ + $parent_id]; +if (!empty($condition_param)) { + $where_condition = array_merge($where_condition, $condition_param); +} + +$count_result = $DB->request([ + 'COUNT' => 'cpt', + 'FROM' => $table, + 'WHERE' => $where_condition, +])->current(); + +if (!$count_result || $count_result['cpt'] == 0) { + exit; +} + +$rand_value = random_int(1000000, 9999999); +$temp_field_name = 'temp_tree_child_' . $rand_value; + +$twig = TemplateRenderer::getInstance(); +echo $twig->renderFromStringTemplate(<< +{{ fields.dropdownField( + itemtype, + temp_field_name, + '', + '', + { + 'init' : true, + 'no_label' : true, + 'right' : 'all', + 'width' : '100%', + 'mb' : '', + 'comments' : false, + 'addicon' : false, + 'aria_label' : aria_label, + 'nochecklimit' : true, + 'display_emptychoice': true, + 'rand' : rand_value, + 'condition' : {(foreign_key): parent_id}|merge(condition_param), + } +) }} + + + +TWIG, [ + 'itemtype' => $itemtype, + 'temp_field_name' => $temp_field_name, + 'final_field_name' => $final_field_name, + 'aria_label' => $aria_label, + 'rand_value' => $rand_value, + 'parent_id' => $parent_id, + 'foreign_key' => $foreign_key, + 'condition_param' => $condition_param, + 'root_doc' => $CFG_GLPI['root_doc'], +]); diff --git a/src/Model/QuestionType/TreeCascadeDropdownQuestion.php b/src/Model/QuestionType/TreeCascadeDropdownQuestion.php new file mode 100644 index 0000000..3df623e --- /dev/null +++ b/src/Model/QuestionType/TreeCascadeDropdownQuestion.php @@ -0,0 +1,220 @@ +itemtype_aria_label = __('Select a dropdown type'); + $this->items_id_aria_label = __('Select a dropdown item'); + } + + #[Override] + public function getAllowedItemtypes(): array + { + $dropdown_itemtypes = Dropdown::getStandardDropdownItemTypes(check_rights: false); + + array_walk_recursive($dropdown_itemtypes, function (&$value, $key) { + $value = $key; + }); + + return $dropdown_itemtypes; + } + #[Override] + public function getName(): string + { + return __('Tree Cascade Dropdown', 'advancedforms'); + } + + #[Override] + public function getIcon(): string + { + return 'ti ti-sitemap'; + } + + #[Override] + public function getWeight(): int + { + return 30; + } + + #[Override] + public function getDropdownRestrictionParams(?Question $question): array + { + return parent::getDropdownRestrictionParams($question); + } + + #[Override] + public function renderEndUserTemplate(Question $question): string + { + global $CFG_GLPI; + + $itemtype = $this->getDefaultValueItemtype($question); + if (!is_a($itemtype, CommonTreeDropdown::class, true)) { + return parent::renderEndUserTemplate($question); + } + + $default_items_id = $this->getDefaultValueItemId($question); + $aria_label = $this->items_id_aria_label; + + $tree_table = $itemtype::getTable(); + $foreign_key = $itemtype::getForeignKeyField(); + + $rand_tree = random_int(1000000, 9999999); + $final_items_id_name = $question->getEndUserInputName() . '[items_id]'; + $level2_container = 'level2_container_' . $rand_tree; + + $dropdown_restriction_params = $this->getDropdownRestrictionParams($question); + + $ancestor_chain = $this->buildAncestorChain($itemtype, $default_items_id); + + $twig = TemplateRenderer::getInstance(); + return $twig->render( + '@advancedforms/tree_cascade_dropdown.html.twig', + [ + 'question' => $question, + 'itemtype' => $itemtype, + 'tree_table' => $tree_table, + 'foreign_key' => $foreign_key, + 'default_items_id' => $default_items_id, + 'aria_label' => $aria_label, + 'rand_tree' => $rand_tree, + 'final_items_id_name' => $final_items_id_name, + 'level2_container' => $level2_container, + 'dropdown_restriction_params' => $dropdown_restriction_params['WHERE'] ?? [], + 'root_doc' => $CFG_GLPI['root_doc'], + 'ancestor_chain' => $ancestor_chain, + ] + ); + } + + /** + * @param class-string $itemtype + */ + private function buildAncestorChain(string $itemtype, int $items_id): array + { + if ($items_id <= 0) { + return []; + } + + $item = new $itemtype(); + if (!$item->getFromDB($items_id)) { + return []; + } + + $foreign_key = $itemtype::getForeignKeyField(); + $chain = []; + $current = $item; + + while ($current !== null) { + array_unshift($chain, [ + 'id' => (int) $current->fields['id'], + 'parent_id' => (int) $current->fields[$foreign_key], + 'level' => (int) $current->fields['level'], + ]); + + $parent_id = (int) $current->fields[$foreign_key]; + if ($parent_id <= 0) { + break; + } + + $parent = new $itemtype(); + if (!$parent->getFromDB($parent_id)) { + break; + } + $current = $parent; + } + + return $chain; + } + + #[Override] + public function prepareEndUserAnswer(Question $question, mixed $answer): mixed + { + $question->fields['type'] = QuestionTypeItemDropdown::class; + + return parent::prepareEndUserAnswer($question, $answer); + } + + #[Override] + public function getTargetQuestionType(array $rawData): string + { + return \Glpi\Form\QuestionType\QuestionTypeItemDropdown::class; + } + + #[Override] + public function getConfigDescription(): string + { + return __('Enable tree cascade dropdown question type (recursive dropdown for hierarchical data)', 'advancedforms'); + } + + #[Override] + public static function getConfigKey(): string + { + return 'enable_tree_cascade_dropdown'; + } + + #[Override] + public function getConfigTitle(): string + { + return $this->getName(); + } + + #[Override] + public function getConfigIcon(): string + { + return $this->getIcon(); + } +} diff --git a/src/Service/ConfigManager.php b/src/Service/ConfigManager.php index 7ce6be8..22aa042 100644 --- a/src/Service/ConfigManager.php +++ b/src/Service/ConfigManager.php @@ -42,6 +42,7 @@ use GlpiPlugin\Advancedforms\Model\QuestionType\HostnameQuestion; use GlpiPlugin\Advancedforms\Model\QuestionType\IpAddressQuestion; use GlpiPlugin\Advancedforms\Model\QuestionType\LdapQuestion; +use GlpiPlugin\Advancedforms\Model\QuestionType\TreeCascadeDropdownQuestion; final class ConfigManager { @@ -64,6 +65,7 @@ public function getConfigurableQuestionTypes(): array new HostnameQuestion(), new HiddenQuestion(), new LdapQuestion(), + new TreeCascadeDropdownQuestion(), ]; } diff --git a/templates/tree_cascade_dropdown.html.twig b/templates/tree_cascade_dropdown.html.twig new file mode 100644 index 0000000..93c1701 --- /dev/null +++ b/templates/tree_cascade_dropdown.html.twig @@ -0,0 +1,208 @@ +{# + # ------------------------------------------------------------------------- + # advancedforms plugin for GLPI + # ------------------------------------------------------------------------- + # + # MIT License + # + # Permission is hereby granted, free of charge, to any person obtaining a copy + # of this software and associated documentation files (the "Software"), to deal + # in the Software without restriction, including without limitation the rights + # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + # copies of the Software, and to permit persons to whom the Software is + # furnished to do so, subject to the following conditions: + # + # The above copyright notice and this permission notice shall be included in all + # copies or substantial portions of the Software. + # + # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + # SOFTWARE. + # ------------------------------------------------------------------------- + # @copyright Copyright (C) 2025 by the advancedforms plugin team. + # @license MIT https://opensource.org/licenses/mit-license.php + # @link https://github.com/pluginsGLPI/advancedforms + # ------------------------------------------------------------------------- + #} + +{% import 'components/form/fields_macros.html.twig' as fields %} + +{{ fields.hiddenField(question.getEndUserInputName() ~ '[itemtype]', itemtype) }} +{{ fields.hiddenField(final_items_id_name, default_items_id) }} + +{% if ancestor_chain|length > 0 %} + {# Pre-filled mode: render each ancestor level with its value pre-selected #} + {% for i, node in ancestor_chain %} + {% set is_last = loop.last %} + {% set level_rand = rand_tree + i %} + {% set temp_name = 'temp_tree_level_' ~ level_rand %} + {% set next_container_id = 'next_level_' ~ level_rand %} + + {% if i == 0 %} + {# Level 1: root items #} +
+ {{ fields.dropdownField( + itemtype, + temp_name, + node.id, + '', + { + 'init' : true, + 'no_label' : true, + 'right' : 'all', + 'width' : '100%', + 'mb' : '', + 'comments' : false, + 'addicon' : false, + 'aria_label' : aria_label, + 'nochecklimit' : true, + 'display_emptychoice': true, + 'rand' : level_rand, + 'condition' : {(tree_table ~ '.level'): 1}|merge(dropdown_restriction_params), + } + ) }} +
+ {% else %} + {# Child levels: show children of the previous node #} +
+ {{ fields.dropdownField( + itemtype, + temp_name, + node.id, + '', + { + 'init' : true, + 'no_label' : true, + 'right' : 'all', + 'width' : '100%', + 'mb' : '', + 'comments' : false, + 'addicon' : false, + 'aria_label' : aria_label, + 'nochecklimit' : true, + 'display_emptychoice': true, + 'rand' : level_rand, + 'condition' : {(foreign_key): node.parent_id}|merge(dropdown_restriction_params), + } + ) }} +
+ {% endif %} + + + + {% if is_last %} + {# Last pre-filled level: auto-load children if they exist #} +
+ + + {% endif %} + {% endfor %} + +{% else %} + {# No default value: single level 1 dropdown with AJAX cascade #} + {% set temp_level1_name = 'temp_tree_level1_' ~ rand_tree %} +
+ {{ fields.dropdownField( + itemtype, + temp_level1_name, + 0, + '', + { + 'init' : true, + 'no_label' : true, + 'right' : 'all', + 'width' : '100%', + 'mb' : '', + 'comments' : false, + 'addicon' : false, + 'aria_label' : aria_label, + 'nochecklimit' : true, + 'display_emptychoice': true, + 'rand' : rand_tree, + 'condition' : {(tree_table ~ '.level'): 1}|merge(dropdown_restriction_params), + } + ) }} +
+ + + +
+ + {% do call('Ajax::updateItemOnSelectEvent', [ + 'dropdown_' ~ temp_level1_name ~ rand_tree, + level2_container, + root_doc ~ '/plugins/advancedforms/ajax/tree_dropdown_children.php', + { + 'itemtype': itemtype, + 'parent_id': '__VALUE__', + 'field_name': final_items_id_name, + 'aria_label': aria_label, + 'condition': dropdown_restriction_params, + } + ]) %} +{% endif %} From b9d1b42488b1ac0ecd5f68ea28d2dfae2295b555 Mon Sep 17 00:00:00 2001 From: Lainow Date: Thu, 12 Feb 2026 10:21:05 +0100 Subject: [PATCH 2/9] . --- templates/tree_cascade_dropdown.html.twig | 3 --- 1 file changed, 3 deletions(-) diff --git a/templates/tree_cascade_dropdown.html.twig b/templates/tree_cascade_dropdown.html.twig index 93c1701..e573536 100644 --- a/templates/tree_cascade_dropdown.html.twig +++ b/templates/tree_cascade_dropdown.html.twig @@ -184,9 +184,6 @@ $('#dropdown_{{ temp_level1_name }}{{ rand_tree }}').on('change', function() { var value = $(this).val(); $('input[name="{{ final_items_id_name }}"]').val(value); - var $wrapper = $(this).closest('.af-tree-level-wrapper'); - $wrapper.nextAll('.af-tree-level-wrapper, .af-tree-next-container').remove(); - $('#{{ level2_container }}').empty(); }); }); From de2cad4e6da6b0b5bb80d1538b8a7cc41e54e99c Mon Sep 17 00:00:00 2001 From: Lainow Date: Thu, 12 Feb 2026 11:14:44 +0100 Subject: [PATCH 3/9] . --- ajax/tree_dropdown_children.php | 85 ++++++++++--------- .../TreeCascadeDropdownQuestion.php | 50 ++++++++++- templates/tree_cascade_dropdown.html.twig | 58 ++++++------- 3 files changed, 123 insertions(+), 70 deletions(-) diff --git a/ajax/tree_dropdown_children.php b/ajax/tree_dropdown_children.php index 81cb3cf..8fbc093 100644 --- a/ajax/tree_dropdown_children.php +++ b/ajax/tree_dropdown_children.php @@ -58,54 +58,64 @@ $foreign_key = $itemtype::getForeignKeyField(); $table = $itemtype::getTable(); -$where_condition = [$foreign_key => $parent_id]; +$where = [$foreign_key => $parent_id]; if (!empty($condition_param)) { - $where_condition = array_merge($where_condition, $condition_param); + $where = array_merge($where, $condition_param); } -$count_result = $DB->request([ - 'COUNT' => 'cpt', - 'FROM' => $table, - 'WHERE' => $where_condition, -])->current(); +$entity_restrict = getEntitiesRestrictCriteria($table); +if (!empty($entity_restrict)) { + $where = array_merge($where, $entity_restrict); +} + +$item_check = new $itemtype(); +if ($item_check->isField('is_deleted')) { + $where['is_deleted'] = 0; +} + +$children = []; +$iterator = $DB->request([ + 'SELECT' => ['id', 'name'], + 'FROM' => $table, + 'WHERE' => $where, + 'ORDER' => 'name ASC', +]); + +foreach ($iterator as $row) { + $children[] = ['id' => (int) $row['id'], 'name' => $row['name']]; +} -if (!$count_result || $count_result['cpt'] == 0) { +if (empty($children)) { exit; } $rand_value = random_int(1000000, 9999999); -$temp_field_name = 'temp_tree_child_' . $rand_value; +$select_id = 'tree_cascade_child_' . $rand_value; $twig = TemplateRenderer::getInstance(); echo $twig->renderFromStringTemplate(<< -{{ fields.dropdownField( - itemtype, - temp_field_name, - '', - '', - { - 'init' : true, - 'no_label' : true, - 'right' : 'all', - 'width' : '100%', - 'mb' : '', - 'comments' : false, - 'addicon' : false, - 'aria_label' : aria_label, - 'nochecklimit' : true, - 'display_emptychoice': true, - 'rand' : rand_value, - 'condition' : {(foreign_key): parent_id}|merge(condition_param), - } -) }} + TWIG, [ - 'itemtype' => $itemtype, - 'temp_field_name' => $temp_field_name, + 'select_id' => $select_id, + 'children' => $children, 'final_field_name' => $final_field_name, 'aria_label' => $aria_label, - 'rand_value' => $rand_value, - 'parent_id' => $parent_id, - 'foreign_key' => $foreign_key, - 'condition_param' => $condition_param, + 'itemtype' => $itemtype, 'root_doc' => $CFG_GLPI['root_doc'], + 'condition_param' => $condition_param, + 'ajax_limit_count' => (int) $CFG_GLPI['ajax_limit_count'], ]); diff --git a/src/Model/QuestionType/TreeCascadeDropdownQuestion.php b/src/Model/QuestionType/TreeCascadeDropdownQuestion.php index 3df623e..e45c52d 100644 --- a/src/Model/QuestionType/TreeCascadeDropdownQuestion.php +++ b/src/Model/QuestionType/TreeCascadeDropdownQuestion.php @@ -117,8 +117,9 @@ public function renderEndUserTemplate(Question $question): string $level2_container = 'level2_container_' . $rand_tree; $dropdown_restriction_params = $this->getDropdownRestrictionParams($question); + $restriction_where = $dropdown_restriction_params['WHERE'] ?? []; - $ancestor_chain = $this->buildAncestorChain($itemtype, $default_items_id); + $ancestor_chain = $this->buildAncestorChain($itemtype, $default_items_id, $restriction_where); $twig = TemplateRenderer::getInstance(); return $twig->render( @@ -133,9 +134,10 @@ public function renderEndUserTemplate(Question $question): string 'rand_tree' => $rand_tree, 'final_items_id_name' => $final_items_id_name, 'level2_container' => $level2_container, - 'dropdown_restriction_params' => $dropdown_restriction_params['WHERE'] ?? [], + 'dropdown_restriction_params' => $restriction_where, 'root_doc' => $CFG_GLPI['root_doc'], 'ancestor_chain' => $ancestor_chain, + 'ajax_limit_count' => (int) $CFG_GLPI['ajax_limit_count'], ] ); } @@ -143,7 +145,7 @@ public function renderEndUserTemplate(Question $question): string /** * @param class-string $itemtype */ - private function buildAncestorChain(string $itemtype, int $items_id): array + private function buildAncestorChain(string $itemtype, int $items_id, array $extra_conditions = []): array { if ($items_id <= 0) { return []; @@ -154,7 +156,11 @@ private function buildAncestorChain(string $itemtype, int $items_id): array return []; } + /** @var \DBmysql $DB */ + global $DB; + $foreign_key = $itemtype::getForeignKeyField(); + $table = $itemtype::getTable(); $chain = []; $current = $item; @@ -177,6 +183,44 @@ private function buildAncestorChain(string $itemtype, int $items_id): array $current = $parent; } + $entity_restrict = getEntitiesRestrictCriteria($table); + $has_is_deleted = $item->isField('is_deleted'); + + foreach ($chain as &$node) { + $where = []; + if ($node['level'] === 1) { + $where[$table . '.level'] = 1; + } else { + $where[$foreign_key] = $node['parent_id']; + } + + if (!empty($entity_restrict)) { + $where = array_merge($where, $entity_restrict); + } + + if (!empty($extra_conditions)) { + $where = array_merge($where, $extra_conditions); + } + + if ($has_is_deleted) { + $where['is_deleted'] = 0; + } + + $siblings = []; + $iterator = $DB->request([ + 'SELECT' => ['id', 'name'], + 'FROM' => $table, + 'WHERE' => $where, + 'ORDER' => 'name ASC', + ]); + + foreach ($iterator as $row) { + $siblings[] = ['id' => (int) $row['id'], 'name' => $row['name']]; + } + + $node['siblings'] = $siblings; + } + return $chain; } diff --git a/templates/tree_cascade_dropdown.html.twig b/templates/tree_cascade_dropdown.html.twig index e573536..6185800 100644 --- a/templates/tree_cascade_dropdown.html.twig +++ b/templates/tree_cascade_dropdown.html.twig @@ -39,11 +39,11 @@ {% for i, node in ancestor_chain %} {% set is_last = loop.last %} {% set level_rand = rand_tree + i %} - {% set temp_name = 'temp_tree_level_' ~ level_rand %} - {% set next_container_id = 'next_level_' ~ level_rand %} {% if i == 0 %} - {# Level 1: root items #} + {# Level 1: root items - use standard dropdown #} + {% set temp_name = 'temp_tree_level_' ~ level_rand %} + {% set selector_id = 'dropdown_' ~ temp_name ~ level_rand %}
{{ fields.dropdownField( itemtype, @@ -67,41 +67,41 @@ ) }}
{% else %} - {# Child levels: show children of the previous node #} + {# Child levels: custom select showing only item name #} + {% set selector_id = 'tree_cascade_level_' ~ level_rand %}
- {{ fields.dropdownField( - itemtype, - temp_name, - node.id, - '', - { - 'init' : true, - 'no_label' : true, - 'right' : 'all', - 'width' : '100%', - 'mb' : '', - 'comments' : false, - 'addicon' : false, - 'aria_label' : aria_label, - 'nochecklimit' : true, - 'display_emptychoice': true, - 'rand' : level_rand, - 'condition' : {(foreign_key): node.parent_id}|merge(dropdown_restriction_params), - } - ) }} +
+ {% endif %} -
+
{% do call('Ajax::updateItemOnSelectEvent', [ 'dropdown_' ~ temp_level1_name ~ rand_tree, From 390fa10c09dba0f467f97406eef41341cf02c74c Mon Sep 17 00:00:00 2001 From: Lainow Date: Thu, 12 Feb 2026 11:26:47 +0100 Subject: [PATCH 4/9] Implement Tree Cascade Question Type for Location and ITILCategory --- .../QuestionType/TreeCascadeDropdownQuestion.php | 13 ++++++------- templates/tree_cascade_dropdown.html.twig | 6 +++--- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Model/QuestionType/TreeCascadeDropdownQuestion.php b/src/Model/QuestionType/TreeCascadeDropdownQuestion.php index e45c52d..d77d353 100644 --- a/src/Model/QuestionType/TreeCascadeDropdownQuestion.php +++ b/src/Model/QuestionType/TreeCascadeDropdownQuestion.php @@ -64,13 +64,12 @@ public function __construct() #[Override] public function getAllowedItemtypes(): array { - $dropdown_itemtypes = Dropdown::getStandardDropdownItemTypes(check_rights: false); - - array_walk_recursive($dropdown_itemtypes, function (&$value, $key) { - $value = $key; - }); - - return $dropdown_itemtypes; + return [ + 'Ticket' => [ + \Location::class, + \ITILCategory::class, + ] + ]; } #[Override] public function getName(): string diff --git a/templates/tree_cascade_dropdown.html.twig b/templates/tree_cascade_dropdown.html.twig index 6185800..6de3961 100644 --- a/templates/tree_cascade_dropdown.html.twig +++ b/templates/tree_cascade_dropdown.html.twig @@ -69,7 +69,7 @@ {% else %} {# Child levels: custom select showing only item name #} {% set selector_id = 'tree_cascade_level_' ~ level_rand %} -
+
+
- {% endif %} {% if is_last %} - {# Last pre-filled level: auto-load children if they exist #} -
- - +
{% endif %} {% endfor %} {% else %} - {# No default value: single level 1 dropdown with AJAX cascade #} {% set temp_level1_name = 'temp_tree_level1_' ~ rand_tree %} + {% set selector_id = 'dropdown_' ~ temp_level1_name ~ rand_tree %}
{{ fields.dropdownField( itemtype, @@ -179,27 +133,19 @@ ) }}
+
+ - -
- - {% do call('Ajax::updateItemOnSelectEvent', [ - 'dropdown_' ~ temp_level1_name ~ rand_tree, - level2_container, - root_doc ~ '/plugins/advancedforms/ajax/tree_dropdown_children.php', - { - 'itemtype': itemtype, - 'parent_id': '__VALUE__', - 'field_name': final_items_id_name, - 'aria_label': aria_label, - 'condition': dropdown_restriction_params, - } - ]) %} {% endif %} diff --git a/templates/tree_cascade_dropdown_children.html.twig b/templates/tree_cascade_dropdown_children.html.twig new file mode 100644 index 0000000..a9bb1f1 --- /dev/null +++ b/templates/tree_cascade_dropdown_children.html.twig @@ -0,0 +1,47 @@ +{# + # ------------------------------------------------------------------------- + # advancedforms plugin for GLPI + # ------------------------------------------------------------------------- + # + # MIT License + # + # Permission is hereby granted, free of charge, to any person obtaining a copy + # of this software and associated documentation files (the "Software"), to deal + # in the Software without restriction, including without limitation the rights + # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + # copies of the Software, and to permit persons to whom the Software is + # furnished to do so, subject to the following conditions: + # + # The above copyright notice and this permission notice shall be included in all + # copies or substantial portions of the Software. + # + # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + # SOFTWARE. + # ------------------------------------------------------------------------- + # @copyright Copyright (C) 2025 by the advancedforms plugin team. + # @license MIT https://opensource.org/licenses/mit-license.php + # @link https://github.com/pluginsGLPI/advancedforms + # ------------------------------------------------------------------------- + #} + +
+ +
From 8b3cfde5934b435f590e312b402c06e630fe0a63 Mon Sep 17 00:00:00 2001 From: Lainow Date: Thu, 12 Feb 2026 17:05:58 +0100 Subject: [PATCH 8/9] Fix lint --- src/Controller/TreeDropdownChildrenController.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Controller/TreeDropdownChildrenController.php b/src/Controller/TreeDropdownChildrenController.php index e416b50..8e5e00f 100644 --- a/src/Controller/TreeDropdownChildrenController.php +++ b/src/Controller/TreeDropdownChildrenController.php @@ -33,6 +33,7 @@ namespace GlpiPlugin\Advancedforms\Controller; +use DBmysql; use CommonTreeDropdown; use Glpi\Application\View\TemplateRenderer; use Glpi\Controller\AbstractController; @@ -66,7 +67,7 @@ public function __invoke(Request $request): Response return new Response('', Response::HTTP_OK); } - /** @var \DBmysql $DB */ + /** @var DBmysql $DB */ global $DB; $foreign_key = $itemtype::getForeignKeyField(); @@ -96,10 +97,17 @@ public function __invoke(Request $request): Response ]); foreach ($iterator as $row) { - $children[] = ['id' => (int) $row['id'], 'name' => $row['name']]; + if (!is_array($row)) { + continue; + } + + $children[] = [ + 'id' => $row['id'], + 'name' => $row['name'], + ]; } - if (empty($children)) { + if ($children === []) { return new Response('', Response::HTTP_OK); } From 624961c94f225799113a9ecf3116c3dde32ab1c0 Mon Sep 17 00:00:00 2001 From: Lainow Date: Thu, 12 Feb 2026 17:22:01 +0100 Subject: [PATCH 9/9] Use module --- .../AfTreeCascadeDropdown.js} | 2 +- setup.php | 4 +++- templates/tree_cascade_dropdown.html.twig | 6 ++++-- 3 files changed, 8 insertions(+), 4 deletions(-) rename public/js/{tree_cascade_dropdown.js => modules/AfTreeCascadeDropdown.js} (99%) diff --git a/public/js/tree_cascade_dropdown.js b/public/js/modules/AfTreeCascadeDropdown.js similarity index 99% rename from public/js/tree_cascade_dropdown.js rename to public/js/modules/AfTreeCascadeDropdown.js index 1d16f1a..36fb1f8 100644 --- a/public/js/tree_cascade_dropdown.js +++ b/public/js/modules/AfTreeCascadeDropdown.js @@ -29,7 +29,7 @@ * ------------------------------------------------------------------------- */ -window.AfTreeCascadeDropdown = class AfTreeCascadeDropdown { +export class AfTreeCascadeDropdown { /** * @param {Object} options * @param {string} options.selector_id - The ID of the select element to bind diff --git a/setup.php b/setup.php index a4b6d9e..bdab5bb 100644 --- a/setup.php +++ b/setup.php @@ -31,6 +31,7 @@ * ------------------------------------------------------------------------- */ +use Glpi\Application\ImportMapGenerator; use Glpi\Plugin\HookManager; use GlpiPlugin\Advancedforms\Service\InitManager; @@ -60,7 +61,8 @@ function plugin_init_advancedforms(): void $hook_manager = new HookManager('advancedforms'); $hook_manager->registerCSSFile('css/advancedforms.css'); $hook_manager->registerJavascriptFile('js/advancedforms.js'); - $hook_manager->registerJavascriptFile('js/tree_cascade_dropdown.js'); + + ImportMapGenerator::getInstance()->registerModulesPath('advancedforms', '/public/js/modules'); InitManager::getInstance()->init(); } diff --git a/templates/tree_cascade_dropdown.html.twig b/templates/tree_cascade_dropdown.html.twig index ed0e0b9..d626484 100644 --- a/templates/tree_cascade_dropdown.html.twig +++ b/templates/tree_cascade_dropdown.html.twig @@ -84,7 +84,8 @@
{% endif %} -