From 740191e75ad699c40c459f6690767f4de844717c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Cailly?= Date: Tue, 17 Dec 2024 16:45:26 +0100 Subject: [PATCH 1/3] Allow multiple strategies to be defined at once for certain destination fields --- .../components/form/_form-destination.scss | 190 ++++++++++++++++-- .../Forms/FieldDestinationAssociatedItem.js | 45 +++-- .../Forms/FieldDestinationMultipleConfig.js | 172 ++++++++++++++++ .../CommonITILField/AssigneeFieldTest.php | 53 ++++- .../AssociatedItemsFieldTest.php | 56 +++++- .../CommonITILField/ObserverFieldTest.php | 46 ++++- .../CommonITILField/RequesterFieldTest.php | 46 ++++- .../CommonITILField/ValidationFieldTest.php | 67 +++++- .../Export/FormSerializerDestinationTest.php | 28 +-- .../Glpi/Form/Export/FormSerializerTest.php | 2 +- .../Form/Destination/AbstractConfigField.php | 28 ++- .../CommonITILField/AssigneeField.php | 2 +- .../CommonITILField/AssigneeFieldConfig.php | 11 +- .../CommonITILField/AssociatedItemsField.php | 61 +++--- .../AssociatedItemsFieldConfig.php | 47 ++++- .../CommonITILField/ContentField.php | 5 +- .../CommonITILField/EntityField.php | 15 +- .../CommonITILField/EntityFieldConfig.php | 19 +- .../CommonITILField/ITILActorField.php | 42 ++-- .../CommonITILField/ITILActorFieldConfig.php | 33 ++- .../CommonITILField/ITILCategoryField.php | 15 +- .../ITILCategoryFieldConfig.php | 19 +- .../CommonITILField/ITILFollowupField.php | 15 +- .../ITILFollowupFieldConfig.php | 19 +- .../CommonITILField/ITILTaskField.php | 15 +- .../CommonITILField/ITILTaskFieldConfig.php | 19 +- .../CommonITILField/LocationField.php | 15 +- .../CommonITILField/LocationFieldConfig.php | 19 +- .../CommonITILField/ObserverField.php | 2 +- .../CommonITILField/ObserverFieldConfig.php | 11 +- .../CommonITILField/RequestSourceField.php | 16 +- .../RequestSourceFieldConfig.php | 16 +- .../CommonITILField/RequestTypeField.php | 15 +- .../RequestTypeFieldConfig.php | 19 +- .../CommonITILField/RequesterField.php | 2 +- .../CommonITILField/RequesterFieldConfig.php | 11 +- .../Destination/CommonITILField/SLMField.php | 15 +- .../CommonITILField/SLMFieldConfig.php | 19 +- .../CommonITILField/TemplateField.php | 28 +-- .../CommonITILField/TemplateFieldConfig.php | 19 +- .../CommonITILField/TitleField.php | 10 +- .../CommonITILField/UrgencyField.php | 15 +- .../CommonITILField/UrgencyFieldConfig.php | 20 +- .../CommonITILField/ValidationField.php | 42 ++-- .../CommonITILField/ValidationFieldConfig.php | 44 +++- .../ConfigFieldWithStrategiesInterface.php | 51 +++++ ...face.php => DestinationFieldInterface.php} | 16 +- src/Glpi/Helpdesk/DefaultDataManager.php | 4 +- .../admin/form/form_destination.html.twig | 3 - ...rm_destination_commonitil_config.html.twig | 178 ++++++++++++++-- .../associated_items.html.twig | 124 ++++++------ .../form/itil_config_fields/entity.html.twig | 18 +- .../itil_config_fields/itilactor.html.twig | 26 +-- .../itil_config_fields/itilcategory.html.twig | 18 +- .../itilfollowuptemplate.html.twig | 13 +- .../itiltasktemplate.html.twig | 13 +- .../itil_config_fields/location.html.twig | 18 +- .../request_source.html.twig | 13 +- .../itil_config_fields/request_type.html.twig | 18 +- .../form/itil_config_fields/slm.html.twig | 13 +- .../form/itil_config_fields/urgency.html.twig | 18 +- .../itil_config_fields/validation.html.twig | 19 +- .../associated_items.cy.js | 65 ++++-- tests/cypress/e2e/form/form_destination.cy.js | 51 +++++ 64 files changed, 1514 insertions(+), 573 deletions(-) create mode 100644 js/modules/Forms/FieldDestinationMultipleConfig.js create mode 100644 src/Glpi/Form/Destination/ConfigFieldWithStrategiesInterface.php rename src/Glpi/Form/Destination/{ConfigFieldInterface.php => DestinationFieldInterface.php} (90%) diff --git a/css/includes/components/form/_form-destination.scss b/css/includes/components/form/_form-destination.scss index f3d421fd315..1e440ed26d9 100644 --- a/css/includes/components/form/_form-destination.scss +++ b/css/includes/components/form/_form-destination.scss @@ -31,31 +31,191 @@ * --------------------------------------------------------------------- */ -[data-glpi-specific-values-extra-field-container] { - [data-glpi-specific-values-extra-field-item] { - padding-left: 1px; +[data-glpi-associated-items-specific-values-extra-field] { + [data-glpi-associated-items-specific-values-extra-field-container] { + [data-glpi-associated-items-specific-values-extra-field-item] { + padding-left: 1px; - >span:last-of-type { - flex-grow: 1; + >span:last-of-type { + flex-grow: 1; - .select2-container { - width: 100% !important; + .select2-container { + width: 100% !important; + } } + + &:last-of-type { + [data-glpi-remove-associated-item-button] { + border-bottom-right-radius: var(--tblr-btn-border-radius) !important; + } + } + + &:not(:has([data-glpi-associated-items-items-id-dropdown])) { + .select2-container { + width: 100% !important; + } + } + } + + .input-group[data-glpi-associated-items-specific-values-extra-field-item] { + >span:first-of-type { + .select2-selection { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; + } + } + + >span:not(:first-of-type) { + .select2-selection { + border-radius: 0 !important; + } + } + } + } + + [data-glpi-add-associated-item-button] { + position: relative; + margin-left: 1px; + margin-top: calc(-1 * var(--tblr-border-width)); + border-top-left-radius: 0 !important; + border-top-right-radius: 0 !important; + } +} + +[data-glpi-itildestination-field-configs] { + &:not(:has(> [data-glpi-itildestination-field-config]:nth-child(2))) { + [data-glpi-itildestination-remove-field-config] { + display: none !important; } } - .input-group[data-glpi-specific-values-extra-field-item] { - >span:first-of-type { - .select2-selection { - border-top-right-radius: 0 !important; - border-bottom-right-radius: 0 !important; + &:has(> [data-glpi-itildestination-field-config]:nth-child(2)) { + &:has([data-glpi-itildestination-remove-field-config]) { + [data-glpi-itildestination-field-config] { + &:not(:has([data-glpi-associated-items-specific-values-extra-field]:not(.d-none))) { + .select2-selection { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; + } + } + + &:has([data-glpi-associated-items-specific-values-extra-field]:not(.d-none)) { + &:has([data-glpi-itildestination-field-config-display-condition="specific_values"]) { + >div { + &:first-child { + .select2-selection { + border-top-right-radius: 0 !important; + } + } + } + } + } } } + } - >span:not(:first-of-type) { - .select2-selection { - border-radius: 0 !important; + [data-glpi-itildestination-field-config] { + display: flex; + flex-wrap: wrap; + align-items: stretch; + + &:has(div[data-glpi-itildestination-field-config-display-condition]:not(.d-none)) { + &:not(:has([data-glpi-associated-items-specific-values-extra-field]:not(.d-none))) { + >div { + &:first-child { + .select2-selection { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; + } + } + } } } + + >div { + flex: 1; + + // Fix height issue with multiple select2 + &:has(.select2-selection--multiple) { + >div { + height: 100%; + + >div.btn-group.btn-group-sm { + height: 100%; + } + + .select2-container { + height: 100%; + + .select2-selection { + height: 100%; + } + } + } + } + } + + &:not(:has([data-glpi-associated-items-specific-values-extra-field]:not(.d-none))) { + >div { + &[data-glpi-itildestination-field-config-display-condition] { + margin-left: calc(-1 * var(--tblr-border-width)); + + .select2-selection { + border-top-left-radius: 0 !important; + border-bottom-left-radius: 0 !important; + } + } + } + } + + // Specific layout for associated items + &:has([data-glpi-associated-items-specific-values-extra-field]:not(.d-none)) { + &:has([data-glpi-itildestination-field-config-display-condition="specific_values"]) { + >div { + &:first-child { + .select2-selection { + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; + } + } + } + } + + // Make the strategy select fill the whole line + [data-glpi-associated-items-specific-values-extra-field] { + flex-basis: 100%; + order: 1; + + [data-glpi-associated-items-specific-values-extra-field-item] { + .select2-selection { + border-bottom-right-radius: 0 !important; + border-top-left-radius: 0 !important; + } + + &:not(:last-of-type) { + .select2-selection { + border-bottom-left-radius: 0 !important; + } + } + } + } + + [data-glpi-associated-items-specific-values-extra-field-item] { + margin-top: calc(-1 * var(--tblr-border-width)); + } + } + + [data-glpi-itildestination-remove-field-config] { + border-top-left-radius: 0 !important; + border-bottom-left-radius: 0 !important; + margin-left: calc(-1 * var(--tblr-border-width)); + } + } + + [data-glpi-itildestination-remove-field-config], + [data-glpi-remove-associated-item-button] { + &:hover { + z-index: 99; + } } } diff --git a/js/modules/Forms/FieldDestinationAssociatedItem.js b/js/modules/Forms/FieldDestinationAssociatedItem.js index 87cf9acbd43..6e0e0319b9b 100644 --- a/js/modules/Forms/FieldDestinationAssociatedItem.js +++ b/js/modules/Forms/FieldDestinationAssociatedItem.js @@ -58,12 +58,6 @@ export class GlpiFormFieldDestinationAssociatedItem { */ #specific_values_template; - /** - * Button to add associated items (jquery selector) - * @type {jQuery} - */ - #add_associated_item_button; - /** * @param {jQuery} specific_values_extra_field */ @@ -74,16 +68,32 @@ export class GlpiFormFieldDestinationAssociatedItem { ) { this.#itemtype_name = itemtype_name; this.#items_id_name = items_id_name; - this.#associated_items_container = specific_values_extra_field.find('[data-glpi-specific-values-extra-field-container]'); - this.#specific_values_template = specific_values_extra_field.find('[data-glpi-specific-values-extra-field-template]'); - this.#add_associated_item_button = specific_values_extra_field.find('[data-glpi-add-associated-item-button]'); + this.#associated_items_container = specific_values_extra_field.find('[data-glpi-associated-items-specific-values-extra-field-container]'); + this.#specific_values_template = specific_values_extra_field.find('[data-glpi-associated-items-specific-values-extra-field-template]'); - this.#add_associated_item_button.on('click', () => { - this.#addAssociatedItemField(); - }); + this.#associated_items_container.on('change', `select[name="${this.#items_id_name}"]`, () => this.#handleChanges()); this.#associated_items_container.find('[data-glpi-remove-associated-item-button]') .each((index, button) => this.#registerOnRemoveAssociatedItem($(button))); + + this.#associated_items_container + .find('[data-glpi-associated-items-specific-values-extra-field-item]') + .each((index, field) => this.#initDropdowns($(field))); + } + + #handleChanges() { + const empty_fields = this.#associated_items_container.find(`[data-glpi-associated-items-specific-values-extra-field-item]`) + .filter((index, field) => ($(field).find(`select[name="${this.#items_id_name}"]`).val() ?? "0") === "0"); + + // Always keep one empty field + if (empty_fields.length > 1) { + // Remove fields that are not filled, except the first one + empty_fields.filter((index) => index !== 0) + .closest('[data-glpi-associated-items-specific-values-extra-field-item]') + .remove(); + } else if (empty_fields.length === 0) { + this.#addAssociatedItemField(); + } } #addAssociatedItemField() { @@ -92,7 +102,7 @@ export class GlpiFormFieldDestinationAssociatedItem { this.#associated_items_container.append(template_content); // Get the last item added - const template = this.#associated_items_container.find('[data-glpi-specific-values-extra-field-item]').last(); + const template = this.#associated_items_container.find('[data-glpi-associated-items-specific-values-extra-field-item]').last(); // Initialize dropdowns and register events this.#initDropdowns(template); @@ -101,7 +111,14 @@ export class GlpiFormFieldDestinationAssociatedItem { #registerOnRemoveAssociatedItem(button) { button.on('click', () => { - button.closest('[data-glpi-specific-values-extra-field-item]').remove(); + if ( + (button.closest('[data-glpi-associated-items-specific-values-extra-field-item]') + .find(`select[name="${this.#items_id_name}"]`).val() ?? "0") !== "0" + || this.#associated_items_container.find(`select[name="${this.#items_id_name}"]`) + .filter((index, field) => $(field).val() === "0").length > 1 + ) { + button.closest('[data-glpi-associated-items-specific-values-extra-field-item]').remove(); + } }); } diff --git a/js/modules/Forms/FieldDestinationMultipleConfig.js b/js/modules/Forms/FieldDestinationMultipleConfig.js new file mode 100644 index 00000000000..a757f655501 --- /dev/null +++ b/js/modules/Forms/FieldDestinationMultipleConfig.js @@ -0,0 +1,172 @@ +/** + * --------------------------------------------------------------------- + * + * GLPI - Gestionnaire Libre de Parc Informatique + * + * http://glpi-project.org + * + * @copyright 2015-2024 Teclib' and contributors. + * @copyright 2003-2014 by the INDEPNET Development Team. + * @licence https://www.gnu.org/licenses/gpl-3.0.html + * + * --------------------------------------------------------------------- + * + * LICENSE + * + * This file is part of GLPI. + * + * This program 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 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see . + * + * --------------------------------------------------------------------- + */ + +/* global getUUID, setupAjaxDropdown, setupAdaptDropdown */ + +export class GlpiFormFieldDestinationMultipleConfig { + /** @type {jQuery} */ + #container; + + /** @type {jQuery} */ + #template; + + /** @type {jQuery} */ + #add_button; + + constructor(container) { + this.#container = container; + this.#template = container.find('[data-glpi-itildestination-field-config-template]'); + this.#add_button = container.find('[data-glpi-itildestination-add-field-config]'); + + // Register events + this.#container.find('[data-glpi-itildestination-remove-field-config]') + .on('click', (e) => this.#removeFieldConfig(e.target.closest('[data-glpi-itildestination-field-config]'))); + this.#add_button.on('click', () => this.#addFieldConfig()); + this.#container.find('[data-glpi-itildestination-field-config]') + .each((index, field) => $(field).find('select').first().on('change', (e) => this.#handleStrategyChange(e))); + + // Trigger change event to initialize the display + this.#container.find('[data-glpi-itildestination-field-config]').each((index, field) => { + $(field).find('select').first().trigger('change'); + }); + + this.#handleAddButtonVisibility(); + } + + /** + * Add a new field config + */ + #addFieldConfig() { + const selected_strategies = []; + this.#container.find('[data-glpi-itildestination-field-config]').each((index, field) => { + selected_strategies.push($(field).find('select').first().find('option').filter(':selected').val()); + }); + + const new_config_field = $(this.#template.html()).insertBefore(this.#add_button); + + new_config_field.find('[data-glpi-itildestination-remove-field-config]') + .on('click', (e) => this.#removeFieldConfig(e.target.closest('[data-glpi-itildestination-field-config]'))); + + new_config_field.find('select').find('option').each((index, option) => { + if ($(option).val() != 0 && selected_strategies.includes($(option).val())) { + $(option).prop('disabled', true); + } + }); + + new_config_field.find('select').first().on('change', (e) => this.#handleStrategyChange(e)); + + this.#initDropdowns(new_config_field); + this.#handleAddButtonVisibility(); + } + + /** + * Remove a field config + * @param {jQuery} field + */ + #removeFieldConfig(field) { + field.remove(); + + this.#handleStrategyChange(); + this.#handleAddButtonVisibility(); + } + + #initDropdowns(field) { + field.find("select").each(function () { + const id = $(this).attr("id"); + const config = window.select2_configs[id]; + + if (id !== undefined && config !== undefined) { + // Rename id to ensure it is unique + const uid = getUUID(); + $(this).attr("id", uid); + + // Check if a select2 isn't already initialized + // and if a configuration is available + if ( + $(this).hasClass("select2-hidden-accessible") === false + && config !== undefined + ) { + config.field_id = uid; + if (config.type === "ajax") { + setupAjaxDropdown(config); + } else if (config.type === "adapt") { + setupAdaptDropdown(config); + } + } + } + }); + } + + #handleAddButtonVisibility() { + const count_options = this.#container.find('[data-glpi-itildestination-field-config]') + .find('select').first().find('option').length; + const count_field_configs = this.#container.find('[data-glpi-itildestination-field-config]').length; + + this.#add_button.toggleClass('d-none', count_field_configs >= count_options); + } + + /** + * Handle the change of a strategy + * @param {Event} [event] + */ + #handleStrategyChange(event = null) { + const selected_strategies = []; + this.#container.find('[data-glpi-itildestination-field-config]').each((index, field) => { + selected_strategies.push($(field).find('select').first().find('option').filter(':selected').val()); + }); + + this.#container.find('select').find('option').each((index, option) => { + $(option).prop( + 'disabled', + !option.selected && $(option).val() != 0 && selected_strategies.includes($(option).val()) + ); + }); + + if (event) { + const selected_value = $(event.target).val(); + + // Compute display conditions + $(event.target).closest('[data-glpi-itildestination-field-config]') + .find(`[data-glpi-itildestination-field-config-display-condition]`) + .toggleClass('d-none', true) + .filter(`[data-glpi-itildestination-field-config-display-condition="${selected_value}"]`) + .toggleClass('d-none', false); + + // Compute disabled state of the fields + $(event.target).closest('[data-glpi-itildestination-field-config]') + .find(`[data-glpi-itildestination-field-config-display-condition]`).each((index, field) => { + $(field).find(':input').prop('disabled', $(field).hasClass('d-none')); + }); + } + } +} diff --git a/phpunit/functional/Glpi/Form/Destination/CommonITILField/AssigneeFieldTest.php b/phpunit/functional/Glpi/Form/Destination/CommonITILField/AssigneeFieldTest.php index 68170de249d..7e8afb5f3cf 100644 --- a/phpunit/functional/Glpi/Form/Destination/CommonITILField/AssigneeFieldTest.php +++ b/phpunit/functional/Glpi/Form/Destination/CommonITILField/AssigneeFieldTest.php @@ -62,7 +62,7 @@ public function testAssigneeFromTemplate(): void { $form = $this->createAndGetFormWithMultipleActorsQuestions(); $from_template_config = new AssigneeFieldConfig( - ITILActorFieldStrategy::FROM_TEMPLATE + [ITILActorFieldStrategy::FROM_TEMPLATE] ); // The default GLPI's template doesn't have a predefined value @@ -124,7 +124,7 @@ public function testAssigneeFormFiller(): void { $form = $this->createAndGetFormWithMultipleActorsQuestions(); $form_filler_config = new AssigneeFieldConfig( - ITILActorFieldStrategy::FORM_FILLER + [ITILActorFieldStrategy::FORM_FILLER] ); // The default GLPI's template doesn't have a predefined value @@ -161,7 +161,7 @@ public function testSpecificActors(): void $this->sendFormAndAssertTicketActors( form: $form, config: new AssigneeFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_VALUES, + strategies: [ITILActorFieldStrategy::SPECIFIC_VALUES], specific_itilactors_ids: [ User::getForeignKeyField() . '-' . $user->getID() ] @@ -174,7 +174,7 @@ public function testSpecificActors(): void $this->sendFormAndAssertTicketActors( form: $form, config: new AssigneeFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_VALUES, + strategies: [ITILActorFieldStrategy::SPECIFIC_VALUES], specific_itilactors_ids: [ User::getForeignKeyField() . '-' . $user->getID(), Group::getForeignKeyField() . '-' . $group->getID() @@ -188,7 +188,7 @@ public function testSpecificActors(): void $this->sendFormAndAssertTicketActors( form: $form, config: new AssigneeFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_VALUES, + strategies: [ITILActorFieldStrategy::SPECIFIC_VALUES], specific_itilactors_ids: [ User::getForeignKeyField() . '-' . $user->getID(), Group::getForeignKeyField() . '-' . $group->getID(), @@ -225,7 +225,7 @@ public function testActorsFromSpecificQuestions(): void $this->sendFormAndAssertTicketActors( form: $form, config: new AssigneeFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_ANSWERS, + strategies: [ITILActorFieldStrategy::SPECIFIC_ANSWERS], specific_question_ids: [$this->getQuestionId($form, "Assignee 1")] ), answers: [ @@ -245,7 +245,7 @@ public function testActorsFromSpecificQuestions(): void $this->sendFormAndAssertTicketActors( form: $form, config: new AssigneeFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_ANSWERS, + strategies: [ITILActorFieldStrategy::SPECIFIC_ANSWERS], specific_question_ids: [ $this->getQuestionId($form, "Assignee 1"), $this->getQuestionId($form, "Assignee 2") @@ -272,7 +272,7 @@ public function testActorsFromLastValidQuestion(): void $form = $this->createAndGetFormWithMultipleActorsQuestions(); $last_valid_answer_config = new AssigneeFieldConfig( - ITILActorFieldStrategy::LAST_VALID_ANSWER + [ITILActorFieldStrategy::LAST_VALID_ANSWER] ); $technician_profiles_id = getItemByTypeName('Profile', 'Technician', true); @@ -354,6 +354,43 @@ public function testActorsFromLastValidQuestion(): void ); } + public function testMultipleStrategies(): void + { + // Login is required to assign actors + $this->login(); + + $form = $this->createAndGetFormWithMultipleActorsQuestions(); + $user1 = $this->createItem(User::class, ['name' => 'testMultipleStrategies User 1']); + $user2 = $this->createItem(User::class, ['name' => 'testMultipleStrategies User 2']); + $group = $this->createItem(Group::class, ['name' => 'testMultipleStrategies Group']); + $supplier = $this->createItem(Supplier::class, [ + 'name' => 'testMultipleStrategies Supplier', + 'entities_id' => $this->getTestRootEntity(true) + ]); + + // Set the user as default assignee using predefined fields + $this->createItem(TicketTemplatePredefinedField::class, [ + 'tickettemplates_id' => getItemByTypeName(TicketTemplate::class, "Default", true), + 'num' => 5, // User assignee + 'value' => $user1->getID(), + ]); + + // Multiple strategies: FROM_TEMPLATE and SPECIFIC_VALUES + $this->sendFormAndAssertTicketActors( + form: $form, + config: new AssigneeFieldConfig( + strategies: [ITILActorFieldStrategy::FROM_TEMPLATE, ITILActorFieldStrategy::SPECIFIC_VALUES], + specific_itilactors_ids: [ + User::getForeignKeyField() . '-' . $user2->getID(), + Group::getForeignKeyField() . '-' . $group->getID(), + Supplier::getForeignKeyField() . '-' . $supplier->getID() + ] + ), + answers: [], + expected_actors_ids: [$user1->getID(), $user2->getID(), $group->getID(), $supplier->getID()] + ); + } + private function sendFormAndAssertTicketActors( Form $form, AssigneeFieldConfig $config, diff --git a/phpunit/functional/Glpi/Form/Destination/CommonITILField/AssociatedItemsFieldTest.php b/phpunit/functional/Glpi/Form/Destination/CommonITILField/AssociatedItemsFieldTest.php index 27598b7da30..b1f34a59961 100644 --- a/phpunit/functional/Glpi/Form/Destination/CommonITILField/AssociatedItemsFieldTest.php +++ b/phpunit/functional/Glpi/Form/Destination/CommonITILField/AssociatedItemsFieldTest.php @@ -63,7 +63,7 @@ public function testAssociatedItemsFromSpecificItems(): void $monitors = $this->createMonitors(2); $specific_values = new AssociatedItemsFieldConfig( - strategy: AssociatedItemsFieldStrategy::SPECIFIC_VALUES, + strategies: [AssociatedItemsFieldStrategy::SPECIFIC_VALUES], specific_associated_items: [ Computer::getType() => [ $computers[0]->getID(), @@ -131,7 +131,7 @@ public function testAssociatedItemsFromSpecificAnswers(): void $form = $this->createAndGetFormWithMultipleItemQuestions(); $specific_answers = new AssociatedItemsFieldConfig( - strategy: AssociatedItemsFieldStrategy::SPECIFIC_ANSWERS, + strategies: [AssociatedItemsFieldStrategy::SPECIFIC_ANSWERS], specific_question_ids: [ $this->getQuestionId($form, "Your Computer"), $this->getQuestionId($form, "Your Monitors"), @@ -187,7 +187,7 @@ public function testAssociatedItemsFromLastValidAnswer(): void $form = $this->createAndGetFormWithMultipleItemQuestions(); $last_valid_answer = new AssociatedItemsFieldConfig( - strategy: AssociatedItemsFieldStrategy::LAST_VALID_ANSWER + strategies: [AssociatedItemsFieldStrategy::LAST_VALID_ANSWER] ); // Test with no answers @@ -254,7 +254,7 @@ public function testAssociatedItemsFromAllValidAnswers(): void $form = $this->createAndGetFormWithMultipleItemQuestions(); $all_valid_answers = new AssociatedItemsFieldConfig( - strategy: AssociatedItemsFieldStrategy::ALL_VALID_ANSWERS + strategies: [AssociatedItemsFieldStrategy::ALL_VALID_ANSWERS] ); // Test with no answers @@ -340,6 +340,54 @@ public function testAssociatedItemsFromAllValidAnswers(): void ); } + public function testMultipleStrategies(): void + { + $this->login(); + + // Create computers and monitors + $computers = $this->createComputers(2); + $monitors = $this->createMonitors(2); + + $form = $this->createAndGetFormWithMultipleItemQuestions(); + + // Multiple strategies: SPECIFIC_VALUES and SPECIFIC_ANSWERS + $this->sendFormAndAssertAssociatedItems( + form: $form, + config: new AssociatedItemsFieldConfig( + strategies: [ + AssociatedItemsFieldStrategy::SPECIFIC_VALUES, + AssociatedItemsFieldStrategy::SPECIFIC_ANSWERS + ], + specific_associated_items: [ + Computer::getType() => [ + $computers[0]->getID(), + ], + ], + specific_question_ids: [ + $this->getQuestionId($form, "Your Monitors"), + ] + ), + answers: [ + "Your Computer" => [ + 'Computer_' . $computers[1]->getID(), + ], + "Your Monitors" => [ + 'Monitor_' . $monitors[0]->getID(), + 'Monitor_' . $monitors[1]->getID(), + ], + ], + expected_associated_items: [ + Computer::getType() => [ + $computers[0]->getID() => $computers[0]->getID(), + ], + Monitor::getType() => [ + $monitors[0]->getID() => $monitors[0]->getID(), + $monitors[1]->getID() => $monitors[1]->getID(), + ], + ] + ); + } + private function sendFormAndAssertAssociatedItems( Form $form, AssociatedItemsFieldConfig $config, diff --git a/phpunit/functional/Glpi/Form/Destination/CommonITILField/ObserverFieldTest.php b/phpunit/functional/Glpi/Form/Destination/CommonITILField/ObserverFieldTest.php index 6d1385dd50c..6bc6e445e99 100644 --- a/phpunit/functional/Glpi/Form/Destination/CommonITILField/ObserverFieldTest.php +++ b/phpunit/functional/Glpi/Form/Destination/CommonITILField/ObserverFieldTest.php @@ -61,7 +61,7 @@ public function testObserverFromTemplate(): void { $form = $this->createAndGetFormWithMultipleActorsQuestions(); $from_template_config = new ObserverFieldConfig( - ITILActorFieldStrategy::FROM_TEMPLATE + [ITILActorFieldStrategy::FROM_TEMPLATE] ); // The default GLPI's template doesn't have a predefined location @@ -106,7 +106,7 @@ public function testObserverFormFiller(): void { $form = $this->createAndGetFormWithMultipleActorsQuestions(); $form_filler_config = new ObserverFieldConfig( - ITILActorFieldStrategy::FORM_FILLER + [ITILActorFieldStrategy::FORM_FILLER] ); // The default GLPI's template doesn't have a predefined location @@ -140,7 +140,7 @@ public function testSpecificActors(): void $this->sendFormAndAssertTicketActors( form: $form, config: new ObserverFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_VALUES, + strategies: [ITILActorFieldStrategy::SPECIFIC_VALUES], specific_itilactors_ids: [ User::getForeignKeyField() . '-' . $user->getID() ] @@ -153,7 +153,7 @@ public function testSpecificActors(): void $this->sendFormAndAssertTicketActors( form: $form, config: new ObserverFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_VALUES, + strategies: [ITILActorFieldStrategy::SPECIFIC_VALUES], specific_itilactors_ids: [ User::getForeignKeyField() . '-' . $user->getID(), Group::getForeignKeyField() . '-' . $group->getID() @@ -176,7 +176,7 @@ public function testActorsFromSpecificQuestions(): void $this->sendFormAndAssertTicketActors( form: $form, config: new ObserverFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_ANSWERS, + strategies: [ITILActorFieldStrategy::SPECIFIC_ANSWERS], specific_question_ids: [$this->getQuestionId($form, "Observer 1")] ), answers: [ @@ -195,7 +195,7 @@ public function testActorsFromSpecificQuestions(): void $this->sendFormAndAssertTicketActors( form: $form, config: new ObserverFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_ANSWERS, + strategies: [ITILActorFieldStrategy::SPECIFIC_ANSWERS], specific_question_ids: [ $this->getQuestionId($form, "Observer 1"), $this->getQuestionId($form, "Observer 2") @@ -218,7 +218,7 @@ public function testActorsFromLastValidQuestion(): void { $form = $this->createAndGetFormWithMultipleActorsQuestions(); $last_valid_answer_config = new ObserverFieldConfig( - ITILActorFieldStrategy::LAST_VALID_ANSWER + [ITILActorFieldStrategy::LAST_VALID_ANSWER] ); $user1 = $this->createItem(User::class, ['name' => 'testLocationFromSpecificQuestions User']); @@ -288,6 +288,38 @@ public function testActorsFromLastValidQuestion(): void ); } + public function testMultipleStrategies(): void + { + // Login is required to assign actors + $this->login(); + + $form = $this->createAndGetFormWithMultipleActorsQuestions(); + $user1 = $this->createItem(User::class, ['name' => 'testMultipleStrategies User 1']); + $user2 = $this->createItem(User::class, ['name' => 'testMultipleStrategies User 2']); + $group = $this->createItem(Group::class, ['name' => 'testMultipleStrategies Group']); + + // Set the user as default observer using predefined fields + $this->createItem(TicketTemplatePredefinedField::class, [ + 'tickettemplates_id' => getItemByTypeName(TicketTemplate::class, "Default", true), + 'num' => 66, // User observer + 'value' => $user1->getID(), + ]); + + // Multiple strategies: FROM_TEMPLATE and SPECIFIC_VALUES + $this->sendFormAndAssertTicketActors( + form: $form, + config: new ObserverFieldConfig( + strategies: [ITILActorFieldStrategy::FROM_TEMPLATE, ITILActorFieldStrategy::SPECIFIC_VALUES], + specific_itilactors_ids: [ + User::getForeignKeyField() . '-' . $user2->getID(), + Group::getForeignKeyField() . '-' . $group->getID() + ] + ), + answers: [], + expected_actors_ids: [$user1->getID(), $user2->getID(), $group->getID()] + ); + } + private function sendFormAndAssertTicketActors( Form $form, ObserverFieldConfig $config, diff --git a/phpunit/functional/Glpi/Form/Destination/CommonITILField/RequesterFieldTest.php b/phpunit/functional/Glpi/Form/Destination/CommonITILField/RequesterFieldTest.php index 65fd727ae5a..42265140bd1 100644 --- a/phpunit/functional/Glpi/Form/Destination/CommonITILField/RequesterFieldTest.php +++ b/phpunit/functional/Glpi/Form/Destination/CommonITILField/RequesterFieldTest.php @@ -61,7 +61,7 @@ public function testRequesterFromTemplate(): void { $form = $this->createAndGetFormWithMultipleActorsQuestions(); $from_template_config = new RequesterFieldConfig( - ITILActorFieldStrategy::FROM_TEMPLATE + [ITILActorFieldStrategy::FROM_TEMPLATE] ); // The default GLPI's template doesn't have a predefined location @@ -106,7 +106,7 @@ public function testRequesterFormFiller(): void { $form = $this->createAndGetFormWithMultipleActorsQuestions(); $form_filler_config = new RequesterFieldConfig( - ITILActorFieldStrategy::FORM_FILLER + [ITILActorFieldStrategy::FORM_FILLER] ); // The default GLPI's template doesn't have a predefined location @@ -140,7 +140,7 @@ public function testSpecificActors(): void $this->sendFormAndAssertTicketActors( form: $form, config: new RequesterFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_VALUES, + strategies: [ITILActorFieldStrategy::SPECIFIC_VALUES], specific_itilactors_ids: [ User::getForeignKeyField() . '-' . $user->getID() ] @@ -153,7 +153,7 @@ public function testSpecificActors(): void $this->sendFormAndAssertTicketActors( form: $form, config: new RequesterFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_VALUES, + strategies: [ITILActorFieldStrategy::SPECIFIC_VALUES], specific_itilactors_ids: [ User::getForeignKeyField() . '-' . $user->getID(), Group::getForeignKeyField() . '-' . $group->getID() @@ -176,7 +176,7 @@ public function testActorsFromSpecificQuestions(): void $this->sendFormAndAssertTicketActors( form: $form, config: new RequesterFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_ANSWERS, + strategies: [ITILActorFieldStrategy::SPECIFIC_ANSWERS], specific_question_ids: [$this->getQuestionId($form, "Requester 1")] ), answers: [ @@ -195,7 +195,7 @@ public function testActorsFromSpecificQuestions(): void $this->sendFormAndAssertTicketActors( form: $form, config: new RequesterFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_ANSWERS, + strategies: [ITILActorFieldStrategy::SPECIFIC_ANSWERS], specific_question_ids: [ $this->getQuestionId($form, "Requester 1"), $this->getQuestionId($form, "Requester 2") @@ -218,7 +218,7 @@ public function testActorsFromLastValidQuestion(): void { $form = $this->createAndGetFormWithMultipleActorsQuestions(); $last_valid_answer_config = new RequesterFieldConfig( - ITILActorFieldStrategy::LAST_VALID_ANSWER + [ITILActorFieldStrategy::LAST_VALID_ANSWER] ); $user1 = $this->createItem(User::class, ['name' => 'testLocationFromSpecificQuestions User']); @@ -288,6 +288,38 @@ public function testActorsFromLastValidQuestion(): void ); } + public function testMultipleStrategies(): void + { + // Login is required to assign actors + $this->login(); + + $form = $this->createAndGetFormWithMultipleActorsQuestions(); + $user1 = $this->createItem(User::class, ['name' => 'testMultipleStrategies User 1']); + $user2 = $this->createItem(User::class, ['name' => 'testMultipleStrategies User 2']); + $group = $this->createItem(Group::class, ['name' => 'testMultipleStrategies Group']); + + // Set the user as default requester using predefined fields + $this->createItem(TicketTemplatePredefinedField::class, [ + 'tickettemplates_id' => getItemByTypeName(TicketTemplate::class, "Default", true), + 'num' => 4, // User requester + 'value' => $user1->getID(), + ]); + + // Multiple strategies: FROM_TEMPLATE and SPECIFIC_VALUES + $this->sendFormAndAssertTicketActors( + form: $form, + config: new RequesterFieldConfig( + strategies: [ITILActorFieldStrategy::FROM_TEMPLATE, ITILActorFieldStrategy::SPECIFIC_VALUES], + specific_itilactors_ids: [ + User::getForeignKeyField() . '-' . $user2->getID(), + Group::getForeignKeyField() . '-' . $group->getID() + ] + ), + answers: [], + expected_actors_ids: [$user1->getID(), $user2->getID(), $group->getID()] + ); + } + private function sendFormAndAssertTicketActors( Form $form, RequesterFieldConfig $config, diff --git a/phpunit/functional/Glpi/Form/Destination/CommonITILField/ValidationFieldTest.php b/phpunit/functional/Glpi/Form/Destination/CommonITILField/ValidationFieldTest.php index 9ea4389e179..1f426818357 100644 --- a/phpunit/functional/Glpi/Form/Destination/CommonITILField/ValidationFieldTest.php +++ b/phpunit/functional/Glpi/Form/Destination/CommonITILField/ValidationFieldTest.php @@ -67,7 +67,7 @@ public function testValidationNoValidation(): void $this->sendFormAndAssertValidations( form: $form, config: new ValidationFieldConfig( - strategy: ValidationFieldStrategy::NO_VALIDATION + strategies: [ValidationFieldStrategy::NO_VALIDATION] ), answers: [], expected_validations: [], @@ -78,7 +78,7 @@ public function testValidationNoValidation(): void $this->sendFormAndAssertValidations( form: $form, config: new ValidationFieldConfig( - strategy: ValidationFieldStrategy::NO_VALIDATION, + strategies: [ValidationFieldStrategy::NO_VALIDATION], specific_actors: [ 'users_id-' . $users[0]->getID(), 'users_id-' . $users[1]->getID(), @@ -94,7 +94,7 @@ public function testValidationNoValidation(): void $this->sendFormAndAssertValidations( form: $form, config: new ValidationFieldConfig( - strategy: ValidationFieldStrategy::NO_VALIDATION + strategies: [ValidationFieldStrategy::NO_VALIDATION] ), answers: [ "Assignee" => Group::getForeignKeyField() . '-' . $groups[0]->getID(), @@ -111,7 +111,7 @@ public function testValidationNoValidation(): void $this->sendFormAndAssertValidations( form: $form, config: new ValidationFieldConfig( - strategy: ValidationFieldStrategy::NO_VALIDATION, + strategies: [ValidationFieldStrategy::NO_VALIDATION], specific_actors: [ 'users_id-' . $users[0]->getID(), 'users_id-' . $users[1]->getID(), @@ -142,7 +142,7 @@ public function testValidationSpecificActors(): void $this->sendFormAndAssertValidations( form: $form, config: new ValidationFieldConfig( - strategy: ValidationFieldStrategy::SPECIFIC_ACTORS, + strategies: [ValidationFieldStrategy::SPECIFIC_ACTORS], specific_actors: [ 'users_id-' . $users[0]->getID(), 'users_id-' . $users[1]->getID(), @@ -171,7 +171,7 @@ public function testValidationSpecificActors(): void $this->sendFormAndAssertValidations( form: $form, config: new ValidationFieldConfig( - strategy: ValidationFieldStrategy::SPECIFIC_ACTORS, + strategies: [ValidationFieldStrategy::SPECIFIC_ACTORS], specific_actors: [ 'users_id-' . $users[0]->getID(), 'users_id-' . $users[1]->getID(), @@ -215,7 +215,7 @@ public function testValidationFromSpecificQuestions(): void $this->sendFormAndAssertValidations( form: $form, config: new ValidationFieldConfig( - strategy: ValidationFieldStrategy::SPECIFIC_ANSWERS, + strategies: [ValidationFieldStrategy::SPECIFIC_ANSWERS], specific_question_ids: [$this->getQuestionId($form, "Assignee")] ), answers: [ @@ -238,7 +238,7 @@ public function testValidationFromSpecificQuestions(): void $this->sendFormAndAssertValidations( form: $form, config: new ValidationFieldConfig( - strategy: ValidationFieldStrategy::SPECIFIC_ANSWERS, + strategies: [ValidationFieldStrategy::SPECIFIC_ANSWERS], specific_question_ids: [$this->getQuestionId($form, "GLPI User")] ), answers: [ @@ -258,6 +258,57 @@ public function testValidationFromSpecificQuestions(): void ); } + public function testMultipleStrategies(): void + { + $this->login(); + + $form = $this->createAndGetFormWithMultipleActorsQuestions(); + $users = $this->createAndGetUserActors(); + $groups = $this->createAndGetGroupActors(); + + // Multiple strategies: SPECIFIC_ACTORS and SPECIFIC_ANSWERS + $this->sendFormAndAssertValidations( + form: $form, + config: new ValidationFieldConfig( + strategies: [ValidationFieldStrategy::SPECIFIC_ACTORS, ValidationFieldStrategy::SPECIFIC_ANSWERS], + specific_actors: [ + 'users_id-' . $users[0]->getID(), + 'groups_id-' . $groups[0]->getID(), + ], + specific_question_ids: [ + $this->getQuestionId($form, "Assignee"), + $this->getQuestionId($form, "GLPI User"), + ] + ), + answers: [ + "Assignee" => Group::getForeignKeyField() . '-' . $groups[1]->getID(), + "GLPI User" => [ + 'itemtype' => User::class, + 'items_id' => $users[1]->getID(), + ], + ], + expected_validations: [ + [ + 'itemtype_target' => 'User', + 'items_id_target' => $users[0]->getID(), + ], + [ + 'itemtype_target' => 'User', + 'items_id_target' => $users[1]->getID(), + ], + [ + 'itemtype_target' => 'Group', + 'items_id_target' => $groups[0]->getID(), + ], + [ + 'itemtype_target' => 'Group', + 'items_id_target' => $groups[1]->getID(), + ], + ], + keys_to_be_considered: ['itemtype_target', 'items_id_target'] + ); + } + private function sendFormAndAssertValidations( Form $form, ValidationFieldConfig $config, diff --git a/phpunit/functional/Glpi/Form/Export/FormSerializerDestinationTest.php b/phpunit/functional/Glpi/Form/Export/FormSerializerDestinationTest.php index b112c7bf103..bb138abf1b9 100644 --- a/phpunit/functional/Glpi/Form/Export/FormSerializerDestinationTest.php +++ b/phpunit/functional/Glpi/Form/Export/FormSerializerDestinationTest.php @@ -202,7 +202,7 @@ public static function getFieldDestinationsWithSpecificValueProvider(): iterable yield 'AssociatedItemsField' => [ 'field_key' => AssociatedItemsField::getKey(), 'config_fn' => fn($created_items) => new AssociatedItemsFieldConfig( - strategy: AssociatedItemsFieldStrategy::SPECIFIC_VALUES, + strategies: [AssociatedItemsFieldStrategy::SPECIFIC_VALUES], specific_associated_items: [ \Computer::class => [$created_items[0]->getId()], \Monitor::class => [$created_items[1]->getId()], @@ -260,7 +260,7 @@ public static function getFieldDestinationsWithSpecificValueProvider(): iterable yield 'ValidationField' => [ 'field_key' => ValidationField::getKey(), 'config_fn' => fn($created_items) => new ValidationFieldConfig( - strategy: ValidationFieldStrategy::SPECIFIC_ACTORS, + strategies: [ValidationFieldStrategy::SPECIFIC_ACTORS], specific_actors: [ getForeignKeyFieldForItemType(\User::class) . '-' . $created_items[0]->getId(), getForeignKeyFieldForItemType(\Group::class) . '-' . $created_items[1]->getId(), @@ -284,7 +284,7 @@ public static function getFieldDestinationsWithSpecificValueProvider(): iterable assertEquals( array_diff_key( (new ValidationFieldConfig( - strategy: ValidationFieldStrategy::SPECIFIC_ACTORS, + strategies: [ValidationFieldStrategy::SPECIFIC_ACTORS], specific_actors: [ \User::class => [$created_items[0]->getId()], \Group::class => [$created_items[1]->getId()], @@ -316,7 +316,7 @@ public static function getFieldDestinationsWithSpecificValueProvider(): iterable yield 'RequesterField' => [ 'field_key' => RequesterField::getKey(), 'config_fn' => fn($created_items) => new RequesterFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_VALUES, + strategies: [ITILActorFieldStrategy::SPECIFIC_VALUES], specific_itilactors_ids: [ getForeignKeyFieldForItemType(\User::class) . '-' . $created_items[0]->getId(), getForeignKeyFieldForItemType(\Group::class) . '-' . $created_items[1]->getId(), @@ -347,7 +347,7 @@ public static function getFieldDestinationsWithSpecificValueProvider(): iterable assertEquals( array_diff_key( (new RequesterFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_VALUES, + strategies: [ITILActorFieldStrategy::SPECIFIC_VALUES], specific_itilactors_ids: [ \User::class => [$created_items[0]->getId()], \Group::class => [$created_items[1]->getId()], @@ -364,7 +364,7 @@ public static function getFieldDestinationsWithSpecificValueProvider(): iterable yield 'ObserverField' => [ 'field_key' => ObserverField::getKey(), 'config_fn' => fn($created_items) => new ObserverFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_VALUES, + strategies: [ITILActorFieldStrategy::SPECIFIC_VALUES], specific_itilactors_ids: [ getForeignKeyFieldForItemType(\User::class) . '-' . $created_items[0]->getId(), getForeignKeyFieldForItemType(\Group::class) . '-' . $created_items[1]->getId(), @@ -388,7 +388,7 @@ public static function getFieldDestinationsWithSpecificValueProvider(): iterable assertEquals( array_diff_key( (new ObserverFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_VALUES, + strategies: [ITILActorFieldStrategy::SPECIFIC_VALUES], specific_itilactors_ids: [ \User::class => [$created_items[0]->getId()], \Group::class => [$created_items[1]->getId()], @@ -404,7 +404,7 @@ public static function getFieldDestinationsWithSpecificValueProvider(): iterable yield 'AssigneeField' => [ 'field_key' => AssigneeField::getKey(), 'config_fn' => fn($created_items) => new AssigneeFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_VALUES, + strategies: [ITILActorFieldStrategy::SPECIFIC_VALUES], specific_itilactors_ids: [ getForeignKeyFieldForItemType(\User::class) . '-' . $created_items[0]->getId(), getForeignKeyFieldForItemType(\Group::class) . '-' . $created_items[1]->getId(), @@ -428,7 +428,7 @@ public static function getFieldDestinationsWithSpecificValueProvider(): iterable assertEquals( array_diff_key( (new AssigneeFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_VALUES, + strategies: [ITILActorFieldStrategy::SPECIFIC_VALUES], specific_itilactors_ids: [ \User::class => [$created_items[0]->getId()], \Group::class => [$created_items[1]->getId()], @@ -633,7 +633,7 @@ public static function getFieldDestinationsWithSpecificQuestionProvider(): itera yield 'AssociatedItemsField' => [ 'field_key' => AssociatedItemsField::getKey(), 'config_fn' => fn($questions) => new AssociatedItemsFieldConfig( - strategy: AssociatedItemsFieldStrategy::SPECIFIC_ANSWERS, + strategies: [AssociatedItemsFieldStrategy::SPECIFIC_ANSWERS], specific_question_ids: [ current($questions)->getId(), next($questions)->getId(), @@ -656,7 +656,7 @@ public static function getFieldDestinationsWithSpecificQuestionProvider(): itera yield 'ValidationField' => [ 'field_key' => ValidationField::getKey(), 'config_fn' => fn($questions) => new ValidationFieldConfig( - strategy: ValidationFieldStrategy::SPECIFIC_ANSWERS, + strategies: [ValidationFieldStrategy::SPECIFIC_ANSWERS], specific_question_ids: [ current($questions)->getId(), next($questions)->getId(), @@ -690,7 +690,7 @@ public static function getFieldDestinationsWithSpecificQuestionProvider(): itera yield 'RequesterField' => [ 'field_key' => RequesterField::getKey(), 'config_fn' => fn($questions) => new RequesterFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_ANSWERS, + strategies: [ITILActorFieldStrategy::SPECIFIC_ANSWERS], specific_question_ids: [ current($questions)->getId(), next($questions)->getId(), @@ -713,7 +713,7 @@ public static function getFieldDestinationsWithSpecificQuestionProvider(): itera yield 'ObserverField' => [ 'field_key' => ObserverField::getKey(), 'config_fn' => fn($questions) => new ObserverFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_ANSWERS, + strategies: [ITILActorFieldStrategy::SPECIFIC_ANSWERS], specific_question_ids: [ current($questions)->getId(), next($questions)->getId(), @@ -736,7 +736,7 @@ public static function getFieldDestinationsWithSpecificQuestionProvider(): itera yield 'AssigneeField' => [ 'field_key' => AssigneeField::getKey(), 'config_fn' => fn($questions) => new AssigneeFieldConfig( - strategy: ITILActorFieldStrategy::SPECIFIC_ANSWERS, + strategies: [ITILActorFieldStrategy::SPECIFIC_ANSWERS], specific_question_ids: [ current($questions)->getId(), next($questions)->getId(), diff --git a/phpunit/functional/Glpi/Form/Export/FormSerializerTest.php b/phpunit/functional/Glpi/Form/Export/FormSerializerTest.php index 47fc96774cd..9ee83db5fd9 100644 --- a/phpunit/functional/Glpi/Form/Export/FormSerializerTest.php +++ b/phpunit/functional/Glpi/Form/Export/FormSerializerTest.php @@ -509,7 +509,7 @@ public function testExportAndImportDestinations(): void specific_question_id: $this->getQuestionId($form, "My ITIL Category question") ); $associated_items_config = new AssociatedItemsFieldConfig( - AssociatedItemsFieldStrategy::SPECIFIC_VALUES, + strategies: [AssociatedItemsFieldStrategy::SPECIFIC_VALUES], specific_associated_items: [ Computer::class => [ $computer->getID() diff --git a/src/Glpi/Form/Destination/AbstractConfigField.php b/src/Glpi/Form/Destination/AbstractConfigField.php index 6503544edbd..1d3faab5654 100644 --- a/src/Glpi/Form/Destination/AbstractConfigField.php +++ b/src/Glpi/Form/Destination/AbstractConfigField.php @@ -41,7 +41,7 @@ use Override; use Toolbox; -abstract class AbstractConfigField implements ConfigFieldInterface +abstract class AbstractConfigField implements DestinationFieldInterface { #[Override] final public static function getKey(): string @@ -97,6 +97,32 @@ public static function getAutoConfigKey(): string #[Override] public function prepareInput(array $input): array { + $config_class = $this->getConfigClass(); + + /** + * All strategies are submitted as an array, even if the field can only have one strategy at a time. + * The field should handle this and only use the first strategy if it can only have one. + */ + if (is_subclass_of($config_class, ConfigFieldWithStrategiesInterface::class)) { + /** @var class-string $config_class */ + if ( + $this->canHaveMultipleStrategies() === false + && is_array($input[$this->getKey()][$config_class::getStrategiesInputName()] ?? null) + ) { + $input[$this->getKey()][$config_class::getStrategiesInputName()] = $input[$this->getKey()][$config_class::getStrategiesInputName()][0]; + } + } + return $input; } + + public function getStrategiesForDropdown(): array + { + return []; + } + + public function canHaveMultipleStrategies(): bool + { + return false; + } } diff --git a/src/Glpi/Form/Destination/CommonITILField/AssigneeField.php b/src/Glpi/Form/Destination/CommonITILField/AssigneeField.php index 21cd2d98c9d..0cfa194fa9b 100644 --- a/src/Glpi/Form/Destination/CommonITILField/AssigneeField.php +++ b/src/Glpi/Form/Destination/CommonITILField/AssigneeField.php @@ -76,7 +76,7 @@ public function getConfigClass(): string public function getDefaultConfig(Form $form): AssigneeFieldConfig { return new AssigneeFieldConfig( - ITILActorFieldStrategy::FROM_TEMPLATE, + [ITILActorFieldStrategy::FROM_TEMPLATE], ); } } diff --git a/src/Glpi/Form/Destination/CommonITILField/AssigneeFieldConfig.php b/src/Glpi/Form/Destination/CommonITILField/AssigneeFieldConfig.php index ebdce2125e4..4550c0203d0 100644 --- a/src/Glpi/Form/Destination/CommonITILField/AssigneeFieldConfig.php +++ b/src/Glpi/Form/Destination/CommonITILField/AssigneeFieldConfig.php @@ -54,13 +54,16 @@ public static function listForeignKeysHandlers(ContentSpecificationInterface $co #[Override] public static function jsonDeserialize(array $data): self { - $strategy = ITILActorFieldStrategy::tryFrom($data[self::STRATEGY] ?? ""); - if ($strategy === null) { - $strategy = ITILActorFieldStrategy::FROM_TEMPLATE; + $strategies = array_map( + fn (string $strategy) => ITILActorFieldStrategy::tryFrom($strategy), + $data[self::STRATEGIES] ?? [] + ); + if (empty($strategies)) { + $strategies = [ITILActorFieldStrategy::FROM_TEMPLATE]; } return new self( - strategy: $strategy, + strategies: $strategies, specific_itilactors_ids: $data[self::SPECIFIC_ITILACTORS_IDS] ?? [], specific_question_ids: $data[self::SPECIFIC_QUESTION_IDS] ?? [], ); diff --git a/src/Glpi/Form/Destination/CommonITILField/AssociatedItemsField.php b/src/Glpi/Form/Destination/CommonITILField/AssociatedItemsField.php index 2c447065ef2..33429899a80 100644 --- a/src/Glpi/Form/Destination/CommonITILField/AssociatedItemsField.php +++ b/src/Glpi/Form/Destination/CommonITILField/AssociatedItemsField.php @@ -80,16 +80,9 @@ public function renderConfigForm( // General display options 'options' => $display_options, - // Main config field - 'main_config_field' => [ - 'label' => $this->getLabel(), - 'value' => $config->getStrategy()->value, - 'input_name' => $input_name . "[" . AssociatedItemsFieldConfig::STRATEGY . "]", - 'possible_values' => $this->getMainConfigurationValuesforDropdown(), - ], - // Specific additional config for SPECIFIC_VALUES strategy 'specific_values_extra_field' => [ + 'empty_label' => __("Select an itemtype..."), 'itemtype_aria_label' => __("Select the itemtype of the item to associate..."), 'items_id_aria_label' => __("Select the item to associate..."), 'input_name' => $input_name . "[" . AssociatedItemsFieldConfig::SPECIFIC_ASSOCIATED_ITEMS . "]", @@ -117,22 +110,26 @@ public function applyConfiguratedValueToInputUsingAnswers( throw new InvalidArgumentException("Unexpected config class"); } - // Compute value according to strategy - $associated_items = $config->getStrategy()->getAssociatedItems($config, $answers_set) ?? []; - - $valid_itemtypes = array_keys(CommonITILObject::getAllTypesForHelpdesk()); - foreach ($associated_items as $associated_item) { - // Do not edit input if invalid value was found - if ( - !in_array($associated_item['itemtype'], $valid_itemtypes) - || !is_numeric($associated_item['items_id']) - || $associated_item['items_id'] <= 0 - ) { - continue; + // Compute value according to strategies + foreach ($config->getStrategies() as $strategy) { + $associated_items = $strategy->getAssociatedItems($config, $answers_set); + + if (!empty($associated_items)) { + $valid_itemtypes = array_keys(CommonITILObject::getAllTypesForHelpdesk()); + foreach ($associated_items as $associated_item) { + // Do not edit input if invalid value was found + if ( + !in_array($associated_item['itemtype'], $valid_itemtypes) + || !is_numeric($associated_item['items_id']) + || $associated_item['items_id'] <= 0 + ) { + continue; + } + + // Apply value + $input['items_id'][$associated_item['itemtype']][] = $associated_item['items_id']; + } } - - // Apply value - $input['items_id'][$associated_item['itemtype']][] = $associated_item['items_id']; } return $input; @@ -142,11 +139,11 @@ public function applyConfiguratedValueToInputUsingAnswers( public function getDefaultConfig(Form $form): AssociatedItemsFieldConfig { return new AssociatedItemsFieldConfig( - AssociatedItemsFieldStrategy::LAST_VALID_ANSWER + [AssociatedItemsFieldStrategy::LAST_VALID_ANSWER] ); } - private function getMainConfigurationValuesforDropdown(): array + public function getStrategiesForDropdown(): array { $values = []; foreach (AssociatedItemsFieldStrategy::cases() as $strategies) { @@ -229,6 +226,14 @@ public function prepareInput(array $input): array foreach ($itemtypes as $index => $itemtype) { $item_id = $items_ids[$index]; + // Ensure that itemtype and item_id are valid + if ( + getItemForItemtype($itemtype) === false + || getItemForItemtype($itemtype)->getFromDB($item_id) === false + ) { + continue; + } + if (!isset($result[$itemtype])) { $result[$itemtype] = []; } @@ -241,4 +246,10 @@ public function prepareInput(array $input): array return $input; } + + #[Override] + public function canHaveMultipleStrategies(): bool + { + return true; + } } diff --git a/src/Glpi/Form/Destination/CommonITILField/AssociatedItemsFieldConfig.php b/src/Glpi/Form/Destination/CommonITILField/AssociatedItemsFieldConfig.php index 17dd4b5f634..b4df7be6bdc 100644 --- a/src/Glpi/Form/Destination/CommonITILField/AssociatedItemsFieldConfig.php +++ b/src/Glpi/Form/Destination/CommonITILField/AssociatedItemsFieldConfig.php @@ -35,22 +35,32 @@ namespace Glpi\Form\Destination\CommonITILField; +use CommonITILObject; use Glpi\DBAL\JsonFieldInterface; +use Glpi\Form\Destination\ConfigFieldWithStrategiesInterface; use Glpi\Form\Export\Context\ConfigWithForeignKeysInterface; use Glpi\Form\Export\Context\ForeignKey\ForeignKeyItemsArrayHandler; use Glpi\Form\Export\Context\ForeignKey\QuestionArrayForeignKeyHandler; use Glpi\Form\Export\Specification\ContentSpecificationInterface; use Override; -final class AssociatedItemsFieldConfig implements JsonFieldInterface, ConfigWithForeignKeysInterface +final class AssociatedItemsFieldConfig implements + JsonFieldInterface, + ConfigWithForeignKeysInterface, + ConfigFieldWithStrategiesInterface { // Unique reference to hardcoded names used for serialization and forms input names - public const STRATEGY = 'strategy'; + public const STRATEGIES = 'strategies'; public const SPECIFIC_QUESTION_IDS = 'specific_question_ids'; public const SPECIFIC_ASSOCIATED_ITEMS = 'specific_associated_items'; + /** + * @param array $strategies + * @param array $specific_question_ids + * @param array $specific_associated_items + */ public function __construct( - private AssociatedItemsFieldStrategy $strategy, + private array $strategies, private array $specific_question_ids = [], private array $specific_associated_items = [], ) { @@ -68,13 +78,16 @@ public static function listForeignKeysHandlers(ContentSpecificationInterface $co #[Override] public static function jsonDeserialize(array $data): self { - $strategy = AssociatedItemsFieldStrategy::tryFrom($data[self::STRATEGY] ?? ""); - if ($strategy === null) { - $strategy = AssociatedItemsFieldStrategy::ALL_VALID_ANSWERS; + $strategies = array_map( + fn (string $strategy) => AssociatedItemsFieldStrategy::tryFrom($strategy), + $data[self::STRATEGIES] ?? [] + ); + if (empty($strategies)) { + $strategies = [AssociatedItemsFieldStrategy::ALL_VALID_ANSWERS]; } return new self( - strategy: $strategy, + strategies: $strategies, specific_question_ids: $data[self::SPECIFIC_QUESTION_IDS] ?? [], specific_associated_items: $data[self::SPECIFIC_ASSOCIATED_ITEMS] ?? [], ); @@ -84,15 +97,27 @@ public static function jsonDeserialize(array $data): self public function jsonSerialize(): array { return [ - self::STRATEGY => $this->strategy->value, - self::SPECIFIC_QUESTION_IDS => $this->specific_question_ids, + self::STRATEGIES => array_map( + fn (AssociatedItemsFieldStrategy $strategy) => $strategy->value, + $this->strategies + ), + self::SPECIFIC_QUESTION_IDS => $this->specific_question_ids, self::SPECIFIC_ASSOCIATED_ITEMS => $this->specific_associated_items, ]; } - public function getStrategy(): AssociatedItemsFieldStrategy + #[Override] + public static function getStrategiesInputName(): string + { + return self::STRATEGIES; + } + + /** + * @return array + */ + public function getStrategies(): array { - return $this->strategy; + return $this->strategies; } public function getSpecificQuestionIds(): array diff --git a/src/Glpi/Form/Destination/CommonITILField/ContentField.php b/src/Glpi/Form/Destination/CommonITILField/ContentField.php index e59d5a83e20..f82550af2be 100644 --- a/src/Glpi/Form/Destination/CommonITILField/ContentField.php +++ b/src/Glpi/Form/Destination/CommonITILField/ContentField.php @@ -78,8 +78,10 @@ public function renderConfigForm( {{ fields.textareaField( input_name, value, - label, + '', options|merge({ + 'field_class' : '', + 'no_label' : true, 'enable_richtext' : true, 'enable_images' : false, 'enable_form_tags' : true, @@ -92,7 +94,6 @@ public function renderConfigForm( $twig = TemplateRenderer::getInstance(); return $twig->renderFromStringTemplate($template, [ 'form_id' => $form->fields['id'], - 'label' => $this->getLabel(), 'value' => $config->getValue(), 'input_name' => $input_name . "[" . SimpleValueConfig::VALUE . "]", 'options' => $display_options, diff --git a/src/Glpi/Form/Destination/CommonITILField/EntityField.php b/src/Glpi/Form/Destination/CommonITILField/EntityField.php index cc8302fc055..880e8331785 100644 --- a/src/Glpi/Form/Destination/CommonITILField/EntityField.php +++ b/src/Glpi/Form/Destination/CommonITILField/EntityField.php @@ -79,14 +79,6 @@ public function renderConfigForm( // General display options 'options' => $display_options, - // Main config field - 'main_config_field' => [ - 'label' => $this->getLabel(), - 'value' => $config->getStrategy()->value, - 'input_name' => $input_name . "[" . EntityFieldConfig::STRATEGY . "]", - 'possible_values' => $this->getMainConfigurationValuesforDropdown(), - ], - // Specific additional config for SPECIFIC_VALUE strategy 'specific_value_extra_field' => [ 'aria_label' => __("Select an entity..."), @@ -114,8 +106,11 @@ public function applyConfiguratedValueToInputUsingAnswers( throw new InvalidArgumentException("Unexpected config class"); } + // Only one strategy is allowed + $strategy = current($config->getStrategies()); + // Compute value according to strategy - $entity_id = $config->getStrategy()->getEntityID($config, $answers_set); + $entity_id = $strategy->getEntityID($config, $answers_set); // Do not edit input if invalid value was found if (Entity::getById($entity_id) === false) { @@ -150,7 +145,7 @@ public function getDefaultConfig(Form $form): EntityFieldConfig ); } - private function getMainConfigurationValuesforDropdown(): array + public function getStrategiesForDropdown(): array { $values = []; foreach (EntityFieldStrategy::cases() as $strategies) { diff --git a/src/Glpi/Form/Destination/CommonITILField/EntityFieldConfig.php b/src/Glpi/Form/Destination/CommonITILField/EntityFieldConfig.php index ed074453844..11e6a378a71 100644 --- a/src/Glpi/Form/Destination/CommonITILField/EntityFieldConfig.php +++ b/src/Glpi/Form/Destination/CommonITILField/EntityFieldConfig.php @@ -37,13 +37,17 @@ use Entity; use Glpi\DBAL\JsonFieldInterface; +use Glpi\Form\Destination\ConfigFieldWithStrategiesInterface; use Glpi\Form\Export\Context\ConfigWithForeignKeysInterface; use Glpi\Form\Export\Context\ForeignKey\ForeignKeyHandler; use Glpi\Form\Export\Context\ForeignKey\QuestionForeignKeyHandler; use Glpi\Form\Export\Specification\ContentSpecificationInterface; use Override; -final class EntityFieldConfig implements JsonFieldInterface, ConfigWithForeignKeysInterface +final class EntityFieldConfig implements + JsonFieldInterface, + ConfigWithForeignKeysInterface, + ConfigFieldWithStrategiesInterface { // Unique reference to hardcoded names used for serialization and forms input names public const STRATEGY = 'strategy'; @@ -91,9 +95,18 @@ public function jsonSerialize(): array ]; } - public function getStrategy(): EntityFieldStrategy + #[Override] + public static function getStrategiesInputName(): string + { + return self::STRATEGY; + } + + /** + * @return array + */ + public function getStrategies(): array { - return $this->strategy; + return [$this->strategy]; } public function getSpecificQuestionId(): ?int diff --git a/src/Glpi/Form/Destination/CommonITILField/ITILActorField.php b/src/Glpi/Form/Destination/CommonITILField/ITILActorField.php index f229bb50a17..3ae39cddc61 100644 --- a/src/Glpi/Form/Destination/CommonITILField/ITILActorField.php +++ b/src/Glpi/Form/Destination/CommonITILField/ITILActorField.php @@ -78,14 +78,6 @@ public function renderConfigForm( // General display options 'options' => $display_options, - // Main config field - 'main_config_field' => [ - 'label' => $this->getLabel(), - 'value' => $config->getStrategy()->value, - 'input_name' => $input_name . "[" . ITILActorFieldConfig::STRATEGY . "]", - 'possible_values' => $this->getMainConfigurationValuesforDropdown(), - ], - // Specific additional config for SPECIFIC_VALUES strategy 'specific_value_extra_field' => [ 'aria_label' => __("Select actors..."), @@ -114,20 +106,18 @@ public function applyConfiguratedValueToInputUsingAnswers( throw new InvalidArgumentException("Unexpected config class"); } - // Compute value according to strategy - $itilactors_ids = $config->getStrategy()->getITILActorsIDs( - $this, - $config, - $answers_set - ); - - if (!empty($itilactors_ids)) { - foreach ($itilactors_ids as $itemtype => $ids) { - foreach ($ids as $id) { - $input['_actors'][$this->getActorType()][] = [ - 'itemtype' => $itemtype, - 'items_id' => $id, - ]; + // Compute value according to strategies + foreach ($config->getStrategies() as $strategy) { + $itilactors_ids = $strategy->getITILActorsIDs($this, $config, $answers_set); + + if (!empty($itilactors_ids)) { + foreach ($itilactors_ids as $itemtype => $ids) { + foreach ($ids as $id) { + $input['_actors'][$this->getActorType()][] = [ + 'itemtype' => $itemtype, + 'items_id' => $id, + ]; + } } } } @@ -167,7 +157,7 @@ function ($carry, $value) { return $input; } - private function getMainConfigurationValuesforDropdown(): array + public function getStrategiesForDropdown(): array { $values = []; foreach (ITILActorFieldStrategy::cases() as $strategies) { @@ -187,4 +177,10 @@ function ($carry, $question) { [] ); } + + #[Override] + public function canHaveMultipleStrategies(): bool + { + return true; + } } diff --git a/src/Glpi/Form/Destination/CommonITILField/ITILActorFieldConfig.php b/src/Glpi/Form/Destination/CommonITILField/ITILActorFieldConfig.php index 1d4cbab9d28..04305eb8e60 100644 --- a/src/Glpi/Form/Destination/CommonITILField/ITILActorFieldConfig.php +++ b/src/Glpi/Form/Destination/CommonITILField/ITILActorFieldConfig.php @@ -36,18 +36,27 @@ namespace Glpi\Form\Destination\CommonITILField; use Glpi\DBAL\JsonFieldInterface; +use Glpi\Form\Destination\ConfigFieldWithStrategiesInterface; use Glpi\Form\Export\Context\ConfigWithForeignKeysInterface; use Override; -abstract class ITILActorFieldConfig implements JsonFieldInterface, ConfigWithForeignKeysInterface +abstract class ITILActorFieldConfig implements + JsonFieldInterface, + ConfigWithForeignKeysInterface, + ConfigFieldWithStrategiesInterface { // Unique reference to hardcoded names used for serialization and forms input names - public const STRATEGY = 'strategy'; + public const STRATEGIES = 'strategies'; public const SPECIFIC_ITILACTORS_IDS = 'specific_itilactors_ids'; public const SPECIFIC_QUESTION_IDS = 'specific_question_ids'; + /** + * @param array $strategies + * @param array $specific_itilactors_ids + * @param array $specific_question_ids + */ public function __construct( - private ITILActorFieldStrategy $strategy, + private array $strategies, private array $specific_itilactors_ids = [], private array $specific_question_ids = [], ) { @@ -57,15 +66,25 @@ public function __construct( public function jsonSerialize(): array { return [ - self::STRATEGY => $this->strategy->value, + self::STRATEGIES => array_map( + fn (ITILActorFieldStrategy $strategy) => $strategy->value, + $this->strategies + ), self::SPECIFIC_ITILACTORS_IDS => $this->specific_itilactors_ids, - self::SPECIFIC_QUESTION_IDS => $this->specific_question_ids, + self::SPECIFIC_QUESTION_IDS => $this->specific_question_ids, ]; } - public function getStrategy(): ITILActorFieldStrategy + #[Override] + public static function getStrategiesInputName(): string + { + return self::STRATEGIES; + } + + #[Override] + public function getStrategies(): array { - return $this->strategy; + return $this->strategies; } public function getSpecificITILActorsIds(): ?array diff --git a/src/Glpi/Form/Destination/CommonITILField/ITILCategoryField.php b/src/Glpi/Form/Destination/CommonITILField/ITILCategoryField.php index 28836e29f05..7ed2deae91a 100644 --- a/src/Glpi/Form/Destination/CommonITILField/ITILCategoryField.php +++ b/src/Glpi/Form/Destination/CommonITILField/ITILCategoryField.php @@ -79,14 +79,6 @@ public function renderConfigForm( // General display options 'options' => $display_options, - // Main config field - 'main_config_field' => [ - 'label' => $this->getLabel(), - 'value' => $config->getStrategy()->value, - 'input_name' => $input_name . "[" . ITILCategoryFieldConfig::STRATEGY . "]", - 'possible_values' => $this->getMainConfigurationValuesforDropdown(), - ], - // Specific additional config for SPECIFIC_ANSWER strategy 'specific_value_extra_field' => [ 'empty_label' => __("Select an ITIL category..."), @@ -114,8 +106,11 @@ public function applyConfiguratedValueToInputUsingAnswers( throw new InvalidArgumentException("Unexpected config class"); } + // Only one strategy is allowed + $strategy = current($config->getStrategies()); + // Compute value according to strategy - $itilcategory_id = $config->getStrategy()->getITILCategory($config, $answers_set); + $itilcategory_id = $strategy->getITILCategory($config, $answers_set); // Do not edit input if invalid value was found if (!ITILCategory::getById($itilcategory_id)) { @@ -135,7 +130,7 @@ public function getDefaultConfig(Form $form): ITILCategoryFieldConfig ); } - private function getMainConfigurationValuesforDropdown(): array + public function getStrategiesForDropdown(): array { $values = []; foreach (ITILCategoryFieldStrategy::cases() as $strategies) { diff --git a/src/Glpi/Form/Destination/CommonITILField/ITILCategoryFieldConfig.php b/src/Glpi/Form/Destination/CommonITILField/ITILCategoryFieldConfig.php index 23671cd2f09..f48244b4a7b 100644 --- a/src/Glpi/Form/Destination/CommonITILField/ITILCategoryFieldConfig.php +++ b/src/Glpi/Form/Destination/CommonITILField/ITILCategoryFieldConfig.php @@ -36,6 +36,7 @@ namespace Glpi\Form\Destination\CommonITILField; use Glpi\DBAL\JsonFieldInterface; +use Glpi\Form\Destination\ConfigFieldWithStrategiesInterface; use Glpi\Form\Export\Context\ConfigWithForeignKeysInterface; use Glpi\Form\Export\Context\ForeignKey\ForeignKeyHandler; use Glpi\Form\Export\Context\ForeignKey\QuestionForeignKeyHandler; @@ -43,7 +44,10 @@ use ITILCategory; use Override; -final class ITILCategoryFieldConfig implements JsonFieldInterface, ConfigWithForeignKeysInterface +final class ITILCategoryFieldConfig implements + JsonFieldInterface, + ConfigWithForeignKeysInterface, + ConfigFieldWithStrategiesInterface { // Unique reference to hardcoded names used for serialization and forms input names public const STRATEGY = 'strategy'; @@ -91,9 +95,18 @@ public function jsonSerialize(): array ]; } - public function getStrategy(): ITILCategoryFieldStrategy + #[Override] + public static function getStrategiesInputName(): string + { + return self::STRATEGY; + } + + /** + * @return array + */ + public function getStrategies(): array { - return $this->strategy; + return [$this->strategy]; } public function getSpecificQuestionId(): ?int diff --git a/src/Glpi/Form/Destination/CommonITILField/ITILFollowupField.php b/src/Glpi/Form/Destination/CommonITILField/ITILFollowupField.php index 69d597a9fac..25d988f12c2 100644 --- a/src/Glpi/Form/Destination/CommonITILField/ITILFollowupField.php +++ b/src/Glpi/Form/Destination/CommonITILField/ITILFollowupField.php @@ -77,14 +77,6 @@ public function renderConfigForm( // General display options 'options' => $display_options, - // Main config field - 'main_config_field' => [ - 'label' => $this->getLabel(), - 'value' => $config->getStrategy()->value, - 'input_name' => $input_name . "[" . ITILFollowupFieldConfig::STRATEGY . "]", - 'possible_values' => $this->getMainConfigurationValuesforDropdown(), - ], - // Specific additional config for SPECIFIC_VALUES strategy 'specific_value_extra_field' => [ 'aria_label' => __("Select followup templates..."), @@ -104,8 +96,11 @@ public function applyConfiguratedValueToInputUsingAnswers( throw new InvalidArgumentException("Unexpected config class"); } + // Only one strategy is allowed + $strategy = current($config->getStrategies()); + // Compute value according to strategy - $itilfollowuptemplates_ids = $config->getStrategy()->getITILFollowupTemplatesIDs($config); + $itilfollowuptemplates_ids = $strategy->getITILFollowupTemplatesIDs($config); if (!empty($itilfollowuptemplates_ids)) { $input['_itilfollowuptemplates_id'] = $itilfollowuptemplates_ids; @@ -122,7 +117,7 @@ public function getDefaultConfig(Form $form): ITILFollowupFieldConfig ); } - private function getMainConfigurationValuesforDropdown(): array + public function getStrategiesForDropdown(): array { $values = []; foreach (ITILFollowupFieldStrategy::cases() as $strategies) { diff --git a/src/Glpi/Form/Destination/CommonITILField/ITILFollowupFieldConfig.php b/src/Glpi/Form/Destination/CommonITILField/ITILFollowupFieldConfig.php index 7909f1edce0..f7b053a5dce 100644 --- a/src/Glpi/Form/Destination/CommonITILField/ITILFollowupFieldConfig.php +++ b/src/Glpi/Form/Destination/CommonITILField/ITILFollowupFieldConfig.php @@ -36,13 +36,17 @@ namespace Glpi\Form\Destination\CommonITILField; use Glpi\DBAL\JsonFieldInterface; +use Glpi\Form\Destination\ConfigFieldWithStrategiesInterface; use Glpi\Form\Export\Context\ConfigWithForeignKeysInterface; use Glpi\Form\Export\Context\ForeignKey\ForeignKeyArrayHandler; use Glpi\Form\Export\Specification\ContentSpecificationInterface; use ITILFollowupTemplate; use Override; -final class ITILFollowupFieldConfig implements JsonFieldInterface, ConfigWithForeignKeysInterface +final class ITILFollowupFieldConfig implements + JsonFieldInterface, + ConfigWithForeignKeysInterface, + ConfigFieldWithStrategiesInterface { // Unique reference to hardcoded names used for serialization and forms input names public const STRATEGY = 'strategy'; @@ -86,9 +90,18 @@ public function jsonSerialize(): array ]; } - public function getStrategy(): ITILFollowupFieldStrategy + #[Override] + public static function getStrategiesInputName(): string + { + return self::STRATEGY; + } + + /** + * @return array + */ + public function getStrategies(): array { - return $this->strategy; + return [$this->strategy]; } public function getSpecificITILFollowupTemplatesIds(): ?array diff --git a/src/Glpi/Form/Destination/CommonITILField/ITILTaskField.php b/src/Glpi/Form/Destination/CommonITILField/ITILTaskField.php index 87dacc60c7f..11f943a1070 100644 --- a/src/Glpi/Form/Destination/CommonITILField/ITILTaskField.php +++ b/src/Glpi/Form/Destination/CommonITILField/ITILTaskField.php @@ -77,14 +77,6 @@ public function renderConfigForm( // General display options 'options' => $display_options, - // Main config field - 'main_config_field' => [ - 'label' => $this->getLabel(), - 'value' => $config->getStrategy()->value, - 'input_name' => $input_name . "[" . ITILTaskFieldConfig::STRATEGY . "]", - 'possible_values' => $this->getMainConfigurationValuesforDropdown(), - ], - // Specific additional config for SPECIFIC_VALUES strategy 'specific_value_extra_field' => [ 'aria_label' => __("Select task templates..."), @@ -104,8 +96,11 @@ public function applyConfiguratedValueToInputUsingAnswers( throw new InvalidArgumentException("Unexpected config class"); } + // Only one strategy is allowed + $strategy = current($config->getStrategies()); + // Compute value according to strategy - $tasktemplates_ids = $config->getStrategy()->getTaskTemplatesIDs($config); + $tasktemplates_ids = $strategy->getTaskTemplatesIDs($config); if (!empty($tasktemplates_ids)) { $input['_tasktemplates_id'] = $tasktemplates_ids; @@ -122,7 +117,7 @@ public function getDefaultConfig(Form $form): ITILTaskFieldConfig ); } - private function getMainConfigurationValuesforDropdown(): array + public function getStrategiesForDropdown(): array { $values = []; foreach (ITILTaskFieldStrategy::cases() as $strategies) { diff --git a/src/Glpi/Form/Destination/CommonITILField/ITILTaskFieldConfig.php b/src/Glpi/Form/Destination/CommonITILField/ITILTaskFieldConfig.php index 8f94529366e..5d426181494 100644 --- a/src/Glpi/Form/Destination/CommonITILField/ITILTaskFieldConfig.php +++ b/src/Glpi/Form/Destination/CommonITILField/ITILTaskFieldConfig.php @@ -36,13 +36,17 @@ namespace Glpi\Form\Destination\CommonITILField; use Glpi\DBAL\JsonFieldInterface; +use Glpi\Form\Destination\ConfigFieldWithStrategiesInterface; use Glpi\Form\Export\Context\ConfigWithForeignKeysInterface; use Glpi\Form\Export\Context\ForeignKey\ForeignKeyArrayHandler; use Glpi\Form\Export\Specification\ContentSpecificationInterface; use Override; use TaskTemplate; -final class ITILTaskFieldConfig implements JsonFieldInterface, ConfigWithForeignKeysInterface +final class ITILTaskFieldConfig implements + JsonFieldInterface, + ConfigWithForeignKeysInterface, + ConfigFieldWithStrategiesInterface { // Unique reference to hardcoded names used for serialization and forms input names public const STRATEGY = 'strategy'; @@ -85,9 +89,18 @@ public function jsonSerialize(): array ]; } - public function getStrategy(): ITILTaskFieldStrategy + #[Override] + public static function getStrategiesInputName(): string + { + return self::STRATEGY; + } + + /** + * @return array + */ + public function getStrategies(): array { - return $this->strategy; + return [$this->strategy]; } public function getSpecificTaskTemplatesIds(): ?array diff --git a/src/Glpi/Form/Destination/CommonITILField/LocationField.php b/src/Glpi/Form/Destination/CommonITILField/LocationField.php index f1824ed74f2..3ccd03a105d 100644 --- a/src/Glpi/Form/Destination/CommonITILField/LocationField.php +++ b/src/Glpi/Form/Destination/CommonITILField/LocationField.php @@ -79,14 +79,6 @@ public function renderConfigForm( // General display options 'options' => $display_options, - // Main config field - 'main_config_field' => [ - 'label' => $this->getLabel(), - 'value' => $config->getStrategy()->value, - 'input_name' => $input_name . "[" . LocationFieldConfig::STRATEGY . "]", - 'possible_values' => $this->getMainConfigurationValuesforDropdown(), - ], - // Specific additional config for SPECIFIC_ANSWER strategy 'specific_value_extra_field' => [ 'empty_label' => __("Select a location..."), @@ -114,8 +106,11 @@ public function applyConfiguratedValueToInputUsingAnswers( throw new InvalidArgumentException("Unexpected config class"); } + // Only one strategy is allowed + $strategy = current($config->getStrategies()); + // Compute value according to strategy - $location_id = $config->getStrategy()->getLocationID($config, $answers_set); + $location_id = $strategy->getLocationID($config, $answers_set); // Do not edit input if invalid value was found if (Location::getById($location_id) === false) { @@ -135,7 +130,7 @@ public function getDefaultConfig(Form $form): LocationFieldConfig ); } - private function getMainConfigurationValuesforDropdown(): array + public function getStrategiesForDropdown(): array { $values = []; foreach (LocationFieldStrategy::cases() as $strategies) { diff --git a/src/Glpi/Form/Destination/CommonITILField/LocationFieldConfig.php b/src/Glpi/Form/Destination/CommonITILField/LocationFieldConfig.php index 672c20da695..2573804884f 100644 --- a/src/Glpi/Form/Destination/CommonITILField/LocationFieldConfig.php +++ b/src/Glpi/Form/Destination/CommonITILField/LocationFieldConfig.php @@ -36,6 +36,7 @@ namespace Glpi\Form\Destination\CommonITILField; use Glpi\DBAL\JsonFieldInterface; +use Glpi\Form\Destination\ConfigFieldWithStrategiesInterface; use Glpi\Form\Export\Context\ConfigWithForeignKeysInterface; use Glpi\Form\Export\Context\ForeignKey\ForeignKeyHandler; use Glpi\Form\Export\Context\ForeignKey\QuestionForeignKeyHandler; @@ -43,7 +44,10 @@ use Location; use Override; -final class LocationFieldConfig implements JsonFieldInterface, ConfigWithForeignKeysInterface +final class LocationFieldConfig implements + JsonFieldInterface, + ConfigWithForeignKeysInterface, + ConfigFieldWithStrategiesInterface { // Unique reference to hardcoded names used for serialization and forms input names public const STRATEGY = 'strategy'; @@ -91,9 +95,18 @@ public function jsonSerialize(): array ]; } - public function getStrategy(): LocationFieldStrategy + #[Override] + public static function getStrategiesInputName(): string + { + return self::STRATEGY; + } + + /** + * @return array + */ + public function getStrategies(): array { - return $this->strategy; + return [$this->strategy]; } public function getSpecificQuestionId(): ?int diff --git a/src/Glpi/Form/Destination/CommonITILField/ObserverField.php b/src/Glpi/Form/Destination/CommonITILField/ObserverField.php index 194683a88d7..a2d7810f9c9 100644 --- a/src/Glpi/Form/Destination/CommonITILField/ObserverField.php +++ b/src/Glpi/Form/Destination/CommonITILField/ObserverField.php @@ -76,7 +76,7 @@ public function getConfigClass(): string public function getDefaultConfig(Form $form): ObserverFieldConfig { return new ObserverFieldConfig( - ITILActorFieldStrategy::FROM_TEMPLATE, + [ITILActorFieldStrategy::FROM_TEMPLATE], ); } } diff --git a/src/Glpi/Form/Destination/CommonITILField/ObserverFieldConfig.php b/src/Glpi/Form/Destination/CommonITILField/ObserverFieldConfig.php index 2ba828d2748..86597e26d8f 100644 --- a/src/Glpi/Form/Destination/CommonITILField/ObserverFieldConfig.php +++ b/src/Glpi/Form/Destination/CommonITILField/ObserverFieldConfig.php @@ -54,13 +54,16 @@ public static function listForeignKeysHandlers(ContentSpecificationInterface $co #[Override] public static function jsonDeserialize(array $data): self { - $strategy = ITILActorFieldStrategy::tryFrom($data[self::STRATEGY] ?? ""); - if ($strategy === null) { - $strategy = ITILActorFieldStrategy::FROM_TEMPLATE; + $strategies = array_map( + fn (string $strategy) => ITILActorFieldStrategy::tryFrom($strategy), + $data[self::STRATEGIES] ?? [] + ); + if (empty($strategies)) { + $strategies = [ITILActorFieldStrategy::FROM_TEMPLATE]; } return new self( - strategy: $strategy, + strategies: $strategies, specific_itilactors_ids: $data[self::SPECIFIC_ITILACTORS_IDS] ?? [], specific_question_ids: $data[self::SPECIFIC_QUESTION_IDS] ?? [], ); diff --git a/src/Glpi/Form/Destination/CommonITILField/RequestSourceField.php b/src/Glpi/Form/Destination/CommonITILField/RequestSourceField.php index 3626af28445..97788841cd9 100644 --- a/src/Glpi/Form/Destination/CommonITILField/RequestSourceField.php +++ b/src/Glpi/Form/Destination/CommonITILField/RequestSourceField.php @@ -77,14 +77,6 @@ public function renderConfigForm( // General display options 'options' => $display_options, - // Main config field - 'main_config_field' => [ - 'label' => $this->getLabel(), - 'value' => $config->getStrategy()->value, - 'input_name' => $input_name . "[" . RequestSourceFieldConfig::STRATEGY . "]", - 'possible_values' => $this->getMainConfigurationValuesforDropdown(), - ], - // Specific additional config for SPECIFIC_VALUE strategy 'specific_value_extra_field' => [ 'empty_label' => __("Select a request source..."), @@ -108,11 +100,13 @@ public function applyConfiguratedValueToInputUsingAnswers( throw new InvalidArgumentException("Unexpected config class"); } + // Only one strategy is allowed + $strategy = current($config->getStrategies()); + // Compute value according to strategy - $request_source = $config->getStrategy()->getRequestSource($config, $answers_set); + $request_source = $strategy->getRequestSource($config, $answers_set); // Do not edit input if invalid value was found - $db_values = $DB->request(['FROM' => 'glpi_requesttypes', 'WHERE' => ['is_active' => 1]]); $valid_values = []; foreach ($db_values as $data) { @@ -136,7 +130,7 @@ public function getDefaultConfig(Form $form): RequestSourceFieldConfig ); } - private function getMainConfigurationValuesforDropdown(): array + public function getStrategiesForDropdown(): array { $values = []; foreach (RequestSourceFieldStrategy::cases() as $strategies) { diff --git a/src/Glpi/Form/Destination/CommonITILField/RequestSourceFieldConfig.php b/src/Glpi/Form/Destination/CommonITILField/RequestSourceFieldConfig.php index e39437cdbee..f3a84305b4d 100644 --- a/src/Glpi/Form/Destination/CommonITILField/RequestSourceFieldConfig.php +++ b/src/Glpi/Form/Destination/CommonITILField/RequestSourceFieldConfig.php @@ -36,9 +36,10 @@ namespace Glpi\Form\Destination\CommonITILField; use Glpi\DBAL\JsonFieldInterface; +use Glpi\Form\Destination\ConfigFieldWithStrategiesInterface; use Override; -final class RequestSourceFieldConfig implements JsonFieldInterface +final class RequestSourceFieldConfig implements JsonFieldInterface, ConfigFieldWithStrategiesInterface { // Unique reference to hardcoded names used for serialization and forms input names public const STRATEGY = 'strategy'; @@ -73,9 +74,18 @@ public function jsonSerialize(): array ]; } - public function getStrategy(): RequestSourceFieldStrategy + #[Override] + public static function getStrategiesInputName(): string + { + return self::STRATEGY; + } + + /** + * @return array + */ + public function getStrategies(): array { - return $this->strategy; + return [$this->strategy]; } public function getSpecificRequestSource(): ?int diff --git a/src/Glpi/Form/Destination/CommonITILField/RequestTypeField.php b/src/Glpi/Form/Destination/CommonITILField/RequestTypeField.php index 599594bba92..8b2e469187b 100644 --- a/src/Glpi/Form/Destination/CommonITILField/RequestTypeField.php +++ b/src/Glpi/Form/Destination/CommonITILField/RequestTypeField.php @@ -79,14 +79,6 @@ public function renderConfigForm( // General display options 'options' => $display_options, - // Main config field - 'main_config_field' => [ - 'label' => $this->getLabel(), - 'value' => $config->getStrategy()->value, - 'input_name' => $input_name . "[" . RequestTypeFieldConfig::STRATEGY . "]", - 'possible_values' => $this->getMainConfigurationValuesforDropdown(), - ], - // Specific additional config for SPECIFIC_ANSWER strategy 'specific_value_extra_field' => [ 'empty_label' => __("Select a request type..."), @@ -115,8 +107,11 @@ public function applyConfiguratedValueToInputUsingAnswers( throw new InvalidArgumentException("Unexpected config class"); } + // Only one strategy is allowed + $strategy = current($config->getStrategies()); + // Compute value according to strategy - $request_type = $config->getStrategy()->getRequestType($config, $answers_set); + $request_type = $strategy->getRequestType($config, $answers_set); // Do not edit input if invalid value was found $valid_values = [Ticket::INCIDENT_TYPE, Ticket::DEMAND_TYPE]; @@ -137,7 +132,7 @@ public function getDefaultConfig(Form $form): RequestTypeFieldConfig ); } - private function getMainConfigurationValuesforDropdown(): array + public function getStrategiesForDropdown(): array { $values = []; foreach (RequestTypeFieldStrategy::cases() as $strategies) { diff --git a/src/Glpi/Form/Destination/CommonITILField/RequestTypeFieldConfig.php b/src/Glpi/Form/Destination/CommonITILField/RequestTypeFieldConfig.php index 2c186c229c6..308b523e70c 100644 --- a/src/Glpi/Form/Destination/CommonITILField/RequestTypeFieldConfig.php +++ b/src/Glpi/Form/Destination/CommonITILField/RequestTypeFieldConfig.php @@ -36,12 +36,16 @@ namespace Glpi\Form\Destination\CommonITILField; use Glpi\DBAL\JsonFieldInterface; +use Glpi\Form\Destination\ConfigFieldWithStrategiesInterface; use Glpi\Form\Export\Context\ConfigWithForeignKeysInterface; use Glpi\Form\Export\Context\ForeignKey\QuestionForeignKeyHandler; use Glpi\Form\Export\Specification\ContentSpecificationInterface; use Override; -final class RequestTypeFieldConfig implements JsonFieldInterface, ConfigWithForeignKeysInterface +final class RequestTypeFieldConfig implements + JsonFieldInterface, + ConfigWithForeignKeysInterface, + ConfigFieldWithStrategiesInterface { // Unique reference to hardcoded names used for serialization and forms input names public const STRATEGY = 'strategy'; @@ -88,9 +92,18 @@ public function jsonSerialize(): array ]; } - public function getStrategy(): RequestTypeFieldStrategy + #[Override] + public static function getStrategiesInputName(): string + { + return self::STRATEGY; + } + + /** + * @return array + */ + public function getStrategies(): array { - return $this->strategy; + return [$this->strategy]; } public function getSpecificQuestionId(): ?int diff --git a/src/Glpi/Form/Destination/CommonITILField/RequesterField.php b/src/Glpi/Form/Destination/CommonITILField/RequesterField.php index bf60da84297..789aff36e72 100644 --- a/src/Glpi/Form/Destination/CommonITILField/RequesterField.php +++ b/src/Glpi/Form/Destination/CommonITILField/RequesterField.php @@ -70,7 +70,7 @@ public function getConfigClass(): string public function getDefaultConfig(Form $form): RequesterFieldConfig { return new RequesterFieldConfig( - ITILActorFieldStrategy::FORM_FILLER, + [ITILActorFieldStrategy::FORM_FILLER], ); } diff --git a/src/Glpi/Form/Destination/CommonITILField/RequesterFieldConfig.php b/src/Glpi/Form/Destination/CommonITILField/RequesterFieldConfig.php index 18446ae0ad1..b1bfbffeaa3 100644 --- a/src/Glpi/Form/Destination/CommonITILField/RequesterFieldConfig.php +++ b/src/Glpi/Form/Destination/CommonITILField/RequesterFieldConfig.php @@ -54,13 +54,16 @@ public static function listForeignKeysHandlers(ContentSpecificationInterface $co #[Override] public static function jsonDeserialize(array $data): self { - $strategy = ITILActorFieldStrategy::tryFrom($data[self::STRATEGY] ?? ""); - if ($strategy === null) { - $strategy = ITILActorFieldStrategy::FROM_TEMPLATE; + $strategies = array_map( + fn (string $strategy) => ITILActorFieldStrategy::tryFrom($strategy), + $data[self::STRATEGIES] ?? [] + ); + if (empty($strategies)) { + $strategies = [ITILActorFieldStrategy::FROM_TEMPLATE]; } return new self( - strategy: $strategy, + strategies: $strategies, specific_itilactors_ids: $data[self::SPECIFIC_ITILACTORS_IDS] ?? [], specific_question_ids: $data[self::SPECIFIC_QUESTION_IDS] ?? [], ); diff --git a/src/Glpi/Form/Destination/CommonITILField/SLMField.php b/src/Glpi/Form/Destination/CommonITILField/SLMField.php index eceeab4eaa6..f1dbec74bac 100644 --- a/src/Glpi/Form/Destination/CommonITILField/SLMField.php +++ b/src/Glpi/Form/Destination/CommonITILField/SLMField.php @@ -70,14 +70,6 @@ public function renderConfigForm( // General display options 'options' => $display_options, - // Main config field - 'main_config_field' => [ - 'label' => $this->getLabel(), - 'value' => $config->getStrategy()->value, - 'input_name' => $input_name . "[" . SLMFieldConfig::STRATEGY . "]", - 'possible_values' => $this->getMainConfigurationValuesforDropdown(), - ], - // Specific additional config for SPECIFIC_ANSWER strategy 'specific_value_extra_field' => [ 'slm_class' => $this->getSLMClass(), @@ -99,8 +91,11 @@ public function applyConfiguratedValueToInputUsingAnswers( throw new InvalidArgumentException("Unexpected config class"); } + // Only one strategy is allowed + $strategy = current($config->getStrategies()); + // Compute value according to strategy - $slm_id = $config->getStrategy()->getSLMID($config); + $slm_id = $strategy->getSLMID($config); // Do not edit input if invalid value was found $slm_class = $this->getSLMClass(); @@ -121,7 +116,7 @@ public function getDefaultConfig(Form $form): SLMFieldConfig ); } - private function getMainConfigurationValuesforDropdown(): array + public function getStrategiesForDropdown(): array { $values = []; foreach (SLMFieldStrategy::cases() as $strategies) { diff --git a/src/Glpi/Form/Destination/CommonITILField/SLMFieldConfig.php b/src/Glpi/Form/Destination/CommonITILField/SLMFieldConfig.php index 506d6f8b973..aa23cfbe399 100644 --- a/src/Glpi/Form/Destination/CommonITILField/SLMFieldConfig.php +++ b/src/Glpi/Form/Destination/CommonITILField/SLMFieldConfig.php @@ -36,10 +36,14 @@ namespace Glpi\Form\Destination\CommonITILField; use Glpi\DBAL\JsonFieldInterface; +use Glpi\Form\Destination\ConfigFieldWithStrategiesInterface; use Glpi\Form\Export\Context\ConfigWithForeignKeysInterface; use Override; -abstract class SLMFieldConfig implements JsonFieldInterface, ConfigWithForeignKeysInterface +abstract class SLMFieldConfig implements + JsonFieldInterface, + ConfigWithForeignKeysInterface, + ConfigFieldWithStrategiesInterface { // Unique reference to hardcoded names used for serialization and forms input names public const STRATEGY = 'strategy'; @@ -60,9 +64,18 @@ public function jsonSerialize(): array ]; } - public function getStrategy(): SLMFieldStrategy + #[Override] + public static function getStrategiesInputName(): string + { + return self::STRATEGY; + } + + /** + * @return array + */ + public function getStrategies(): array { - return $this->strategy; + return [$this->strategy]; } public function getSpecificSLMID(): ?int diff --git a/src/Glpi/Form/Destination/CommonITILField/TemplateField.php b/src/Glpi/Form/Destination/CommonITILField/TemplateField.php index 99c7a7e4597..2896499487c 100644 --- a/src/Glpi/Form/Destination/CommonITILField/TemplateField.php +++ b/src/Glpi/Form/Destination/CommonITILField/TemplateField.php @@ -87,14 +87,6 @@ public function renderConfigForm( // General display options 'options' => $display_options, - // Main config field - 'main_config_field' => [ - 'label' => $this->getLabel(), - 'value' => $config->getStrategy()->value, - 'input_name' => $input_name . "[" . TemplateFieldConfig::STRATEGY . "]", - 'possible_values' => $this->getMainConfigurationValuesforDropdown(), - ], - // Specific additional config for CONFIG_SPECIFIC_TEMPLATE 'specific_template_extra_field' => [ 'empty_label' => __("Select a template..."), @@ -107,20 +99,11 @@ public function renderConfigForm( $template = << {{ fields.dropdownArrayField( specific_template_extra_field.input_name, @@ -128,6 +111,8 @@ class="d-none" specific_template_extra_field.possible_values, "", options|merge({ + field_class: '', + mb: '', no_label: true, display_emptychoice: true, emptylabel: specific_template_extra_field.empty_label, @@ -151,8 +136,11 @@ public function applyConfiguratedValueToInputUsingAnswers( throw new InvalidArgumentException("Unexpected config class"); } + // Only one strategy is allowed + $strategy = current($config->getStrategies()); + // Compute value according to strategy - $template_id = $config->getStrategy()->getTemplateID($config, $answers_set); + $template_id = $strategy->getTemplateID($config, $answers_set); // Do not edit the input if invalid value was found if (!$this->itil_template_class::getById($template_id)) { @@ -172,7 +160,7 @@ public function getDefaultConfig(Form $form): TemplateFieldConfig ); } - private function getMainConfigurationValuesforDropdown(): array + public function getStrategiesForDropdown(): array { $values = []; foreach (TemplateFieldStrategy::cases() as $strategies) { diff --git a/src/Glpi/Form/Destination/CommonITILField/TemplateFieldConfig.php b/src/Glpi/Form/Destination/CommonITILField/TemplateFieldConfig.php index da954ffb7eb..4c1799ca7da 100644 --- a/src/Glpi/Form/Destination/CommonITILField/TemplateFieldConfig.php +++ b/src/Glpi/Form/Destination/CommonITILField/TemplateFieldConfig.php @@ -36,13 +36,17 @@ namespace Glpi\Form\Destination\CommonITILField; use Glpi\DBAL\JsonFieldInterface; +use Glpi\Form\Destination\ConfigFieldWithStrategiesInterface; use Glpi\Form\Export\Context\ConfigWithForeignKeysInterface; use Glpi\Form\Export\Context\ForeignKey\ForeignKeyHandler; use Glpi\Form\Export\Specification\ContentSpecificationInterface; use Glpi\Form\Export\Specification\DestinationContentSpecification; use Override; -final class TemplateFieldConfig implements JsonFieldInterface, ConfigWithForeignKeysInterface +final class TemplateFieldConfig implements + JsonFieldInterface, + ConfigWithForeignKeysInterface, + ConfigFieldWithStrategiesInterface { // Unique reference to hardcoded names used for serialization and forms input names public const STRATEGY = 'strategy'; @@ -93,9 +97,18 @@ public function jsonSerialize(): array ]; } - public function getStrategy(): TemplateFieldStrategy + #[Override] + public static function getStrategiesInputName(): string + { + return self::STRATEGY; + } + + /** + * @return array + */ + public function getStrategies(): array { - return $this->strategy; + return [$this->strategy]; } public function getSpecificTemplateID(): ?int diff --git a/src/Glpi/Form/Destination/CommonITILField/TitleField.php b/src/Glpi/Form/Destination/CommonITILField/TitleField.php index 9ec113f3302..f08839747bd 100644 --- a/src/Glpi/Form/Destination/CommonITILField/TitleField.php +++ b/src/Glpi/Form/Destination/CommonITILField/TitleField.php @@ -73,13 +73,13 @@ public function renderConfigForm( $template = << tinymce.on('AddEditor', (e) => { - if (e.editor.id === '{{ input_name ~ '_' ~ rand }}') { + if (e.editor.id === '{{ input_name ~ '_' ~ options.rand }}') { e.editor.on('keydown', (e) => { if (e.keyCode === 13) { e.preventDefault(); @@ -107,7 +106,6 @@ public function renderConfigForm( $twig = TemplateRenderer::getInstance(); return $twig->renderFromStringTemplate($template, [ 'form_id' => $form->fields['id'], - 'label' => $this->getLabel(), 'value' => $config->getValue(), 'input_name' => $input_name . "[" . SimpleValueConfig::VALUE . "]", 'options' => $display_options, diff --git a/src/Glpi/Form/Destination/CommonITILField/UrgencyField.php b/src/Glpi/Form/Destination/CommonITILField/UrgencyField.php index b59d7bf6379..371172f7a9b 100644 --- a/src/Glpi/Form/Destination/CommonITILField/UrgencyField.php +++ b/src/Glpi/Form/Destination/CommonITILField/UrgencyField.php @@ -85,14 +85,6 @@ public function renderConfigForm( // General display options 'options' => $display_options, - // Main config field - 'main_config_field' => [ - 'label' => $this->getLabel(), - 'value' => $config->getStrategy()->value, - 'input_name' => $input_name . "[" . UrgencyFieldConfig::STRATEGY . "]", - 'possible_values' => $this->getMainConfigurationValuesforDropdown(), - ], - // Specific additional config for SPECIFIC_VALUE strategy 'specific_value_extra_field' => [ 'empty_label' => __("Select an urgency level..."), @@ -121,8 +113,11 @@ public function applyConfiguratedValueToInputUsingAnswers( throw new InvalidArgumentException("Unexpected config class"); } + // Only one strategy is allowed + $strategy = current($config->getStrategies()); + // Compute value according to strategy - $urgency = $config->getStrategy()->computeUrgency($config, $answers_set); + $urgency = $strategy->computeUrgency($config, $answers_set); // Do not edit input if invalid value was found $valid_values = array_keys($this->getUrgencyLevels()); @@ -167,7 +162,7 @@ private function getUrgencyLevels(): array return $urgency_levels; } - private function getMainConfigurationValuesforDropdown(): array + public function getStrategiesForDropdown(): array { $values = []; foreach (UrgencyFieldStrategy::cases() as $strategies) { diff --git a/src/Glpi/Form/Destination/CommonITILField/UrgencyFieldConfig.php b/src/Glpi/Form/Destination/CommonITILField/UrgencyFieldConfig.php index fea2038967d..a196f1c883a 100644 --- a/src/Glpi/Form/Destination/CommonITILField/UrgencyFieldConfig.php +++ b/src/Glpi/Form/Destination/CommonITILField/UrgencyFieldConfig.php @@ -36,12 +36,16 @@ namespace Glpi\Form\Destination\CommonITILField; use Glpi\DBAL\JsonFieldInterface; +use Glpi\Form\Destination\ConfigFieldWithStrategiesInterface; use Glpi\Form\Export\Context\ConfigWithForeignKeysInterface; use Glpi\Form\Export\Context\ForeignKey\QuestionForeignKeyHandler; use Glpi\Form\Export\Specification\ContentSpecificationInterface; use Override; -final class UrgencyFieldConfig implements JsonFieldInterface, ConfigWithForeignKeysInterface +final class UrgencyFieldConfig implements + JsonFieldInterface, + ConfigWithForeignKeysInterface, + ConfigFieldWithStrategiesInterface { // Unique reference to hardcoded names used for serialization and forms input names public const STRATEGY = 'strategy'; @@ -88,9 +92,19 @@ public function jsonSerialize(): array ]; } - public function getStrategy(): UrgencyFieldStrategy + #[Override] + public static function getStrategiesInputName(): string + { + return self::STRATEGY; + } + + /** + * @return array + */ + #[Override] + public function getStrategies(): array { - return $this->strategy; + return [$this->strategy]; } public function getSpecificUrgency(): ?int diff --git a/src/Glpi/Form/Destination/CommonITILField/ValidationField.php b/src/Glpi/Form/Destination/CommonITILField/ValidationField.php index 178624eb76a..6a5e8bc79c2 100644 --- a/src/Glpi/Form/Destination/CommonITILField/ValidationField.php +++ b/src/Glpi/Form/Destination/CommonITILField/ValidationField.php @@ -90,14 +90,6 @@ public function renderConfigForm( // General display options 'options' => $display_options, - // Main config field - 'main_config_field' => [ - 'label' => $this->getLabel(), - 'value' => $config->getStrategy()->value, - 'input_name' => $input_name . "[" . ValidationFieldConfig::STRATEGY . "]", - 'possible_values' => $this->getMainConfigurationValuesforDropdown(), - ], - // Specific additional config for SPECIFIC_ACTORS strategy 'specific_values_extra_field' => [ 'values' => $specific_actors, @@ -126,17 +118,19 @@ public function applyConfiguratedValueToInputUsingAnswers( throw new InvalidArgumentException("Unexpected config class"); } - // Compute value according to strategy - $validations = $config->getStrategy()->getValidation($config, $answers_set); - - if (!empty($validations)) { - foreach ($validations as $validation) { - $input['_add_validation'] = 0; - $input['_validation_targets'][] = [ - 'validatortype' => $validation['itemtype'], - 'itemtype_target' => $validation['itemtype'], - 'items_id_target' => $validation['items_id'], - ]; + // Compute value according to strategies + foreach ($config->getStrategies() as $strategy) { + $validations = $strategy->getValidation($config, $answers_set); + + if (!empty($validations)) { + foreach ($validations as $validation) { + $input['_add_validation'] = 0; + $input['_validation_targets'][] = [ + 'validatortype' => $validation['itemtype'], + 'itemtype_target' => $validation['itemtype'], + 'items_id_target' => $validation['items_id'], + ]; + } } } @@ -147,11 +141,11 @@ public function applyConfiguratedValueToInputUsingAnswers( public function getDefaultConfig(Form $form): ValidationFieldConfig { return new ValidationFieldConfig( - ValidationFieldStrategy::NO_VALIDATION + [ValidationFieldStrategy::NO_VALIDATION] ); } - private function getMainConfigurationValuesforDropdown(): array + public function getStrategiesForDropdown(): array { $values = []; foreach (ValidationFieldStrategy::cases() as $strategies) { @@ -243,4 +237,10 @@ public function prepareInput(array $input): array return $input; } + + #[Override] + public function canHaveMultipleStrategies(): bool + { + return true; + } } diff --git a/src/Glpi/Form/Destination/CommonITILField/ValidationFieldConfig.php b/src/Glpi/Form/Destination/CommonITILField/ValidationFieldConfig.php index 4d4c7a28b61..98d243896c4 100644 --- a/src/Glpi/Form/Destination/CommonITILField/ValidationFieldConfig.php +++ b/src/Glpi/Form/Destination/CommonITILField/ValidationFieldConfig.php @@ -36,21 +36,30 @@ namespace Glpi\Form\Destination\CommonITILField; use Glpi\DBAL\JsonFieldInterface; +use Glpi\Form\Destination\ConfigFieldWithStrategiesInterface; use Glpi\Form\Export\Context\ConfigWithForeignKeysInterface; use Glpi\Form\Export\Context\ForeignKey\ForeignKeyItemsArrayHandler; use Glpi\Form\Export\Context\ForeignKey\QuestionArrayForeignKeyHandler; use Glpi\Form\Export\Specification\ContentSpecificationInterface; use Override; -final class ValidationFieldConfig implements JsonFieldInterface, ConfigWithForeignKeysInterface +final class ValidationFieldConfig implements + JsonFieldInterface, + ConfigWithForeignKeysInterface, + ConfigFieldWithStrategiesInterface { // Unique reference to hardcoded names used for serialization and forms input names - public const STRATEGY = 'strategy'; + public const STRATEGIES = 'strategies'; public const SPECIFIC_QUESTION_IDS = 'specific_question_ids'; public const SPECIFIC_ACTORS = 'specific_actors'; + /** + * @param array $strategies + * @param array $specific_question_ids + * @param array $specific_actors + */ public function __construct( - private ValidationFieldStrategy $strategy, + private array $strategies, private array $specific_question_ids = [], private array $specific_actors = [], ) { @@ -68,13 +77,16 @@ public static function listForeignKeysHandlers(ContentSpecificationInterface $co #[Override] public static function jsonDeserialize(array $data): self { - $strategy = ValidationFieldStrategy::tryFrom($data[self::STRATEGY] ?? ""); - if ($strategy === null) { - $strategy = ValidationFieldStrategy::NO_VALIDATION; + $strategies = array_map( + fn (string $strategy) => ValidationFieldStrategy::tryFrom($strategy), + $data[self::STRATEGIES] ?? [] + ); + if (empty($strategies)) { + $strategies = [ValidationFieldStrategy::NO_VALIDATION]; } return new self( - strategy: $strategy, + strategies: $strategies, specific_question_ids: $data[self::SPECIFIC_QUESTION_IDS] ?? [], specific_actors: $data[self::SPECIFIC_ACTORS] ?? [], ); @@ -84,15 +96,27 @@ public static function jsonDeserialize(array $data): self public function jsonSerialize(): array { return [ - self::STRATEGY => $this->strategy->value, + self::STRATEGIES => array_map( + fn (ValidationFieldStrategy $strategy) => $strategy->value, + $this->strategies + ), self::SPECIFIC_QUESTION_IDS => $this->specific_question_ids, self::SPECIFIC_ACTORS => $this->specific_actors, ]; } - public function getStrategy(): ValidationFieldStrategy + #[Override] + public static function getStrategiesInputName(): string + { + return self::STRATEGIES; + } + + /** + * @return array + */ + public function getStrategies(): array { - return $this->strategy; + return $this->strategies; } public function getSpecificQuestionIds(): array diff --git a/src/Glpi/Form/Destination/ConfigFieldWithStrategiesInterface.php b/src/Glpi/Form/Destination/ConfigFieldWithStrategiesInterface.php new file mode 100644 index 00000000000..a9e4482cef1 --- /dev/null +++ b/src/Glpi/Form/Destination/ConfigFieldWithStrategiesInterface.php @@ -0,0 +1,51 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace Glpi\Form\Destination; + +interface ConfigFieldWithStrategiesInterface +{ + /** + * Get strategies input name + */ + public static function getStrategiesInputName(): string; + + /** + * Get actual strategies + * + * @return array + */ + public function getStrategies(): array; +} diff --git a/src/Glpi/Form/Destination/ConfigFieldInterface.php b/src/Glpi/Form/Destination/DestinationFieldInterface.php similarity index 90% rename from src/Glpi/Form/Destination/ConfigFieldInterface.php rename to src/Glpi/Form/Destination/DestinationFieldInterface.php index 3d1916249ee..83e249120b3 100644 --- a/src/Glpi/Form/Destination/ConfigFieldInterface.php +++ b/src/Glpi/Form/Destination/DestinationFieldInterface.php @@ -39,7 +39,7 @@ use Glpi\Form\AnswersSet; use Glpi\Form\Form; -interface ConfigFieldInterface +interface DestinationFieldInterface { /** * Get the unique key used to set/get this field configuration in the @@ -127,4 +127,18 @@ public function getConfigClass(): string; * @return array */ public function prepareInput(array $input): array; + + /** + * Get the possible values for the main configuration dropdown. + * + * @return array + */ + public function getStrategiesForDropdown(): array; + + /** + * Check if this field can have multiple strategies at the same time. + * + * @return bool + */ + public function canHaveMultipleStrategies(): bool; } diff --git a/src/Glpi/Helpdesk/DefaultDataManager.php b/src/Glpi/Helpdesk/DefaultDataManager.php index eca2101a4ff..77cdedf491c 100644 --- a/src/Glpi/Helpdesk/DefaultDataManager.php +++ b/src/Glpi/Helpdesk/DefaultDataManager.php @@ -200,7 +200,7 @@ private function createIncidentForm(): Form // Set last valid answer as observer ObserverField::getKey() => (new ObserverFieldConfig( - strategy: ITILActorFieldStrategy::LAST_VALID_ANSWER, + strategies: [ITILActorFieldStrategy::LAST_VALID_ANSWER], ))->jsonSerialize(), ]; @@ -263,7 +263,7 @@ private function createRequestForm(): void // Set last valid answer as observer ObserverField::getKey() => (new ObserverFieldConfig( - strategy: ITILActorFieldStrategy::LAST_VALID_ANSWER, + strategies: [ITILActorFieldStrategy::LAST_VALID_ANSWER], ))->jsonSerialize(), ]; diff --git a/templates/pages/admin/form/form_destination.html.twig b/templates/pages/admin/form/form_destination.html.twig index 2cd4279c8e2..445aea2ac03 100644 --- a/templates/pages/admin/form/form_destination.html.twig +++ b/templates/pages/admin/form/form_destination.html.twig @@ -149,9 +149,6 @@ import("{{ js_path('js/modules/Forms/DestinationAutoConfigController.js') }}").then((m) => { new m.GlpiFormDestinationAutoConfigController(); }); - import("{{ js_path('js/modules/DynamicDropdownController.js') }}").then((m) => { - new m.DynamicDropdownController(); - }); diff --git a/templates/pages/admin/form/form_destination_commonitil_config.html.twig b/templates/pages/admin/form/form_destination_commonitil_config.html.twig index 4f2e5040d90..4c9149480f1 100644 --- a/templates/pages/admin/form/form_destination_commonitil_config.html.twig +++ b/templates/pages/admin/form/form_destination_commonitil_config.html.twig @@ -31,14 +31,16 @@ # --------------------------------------------------------------------- #} +{% import 'components/form/fields_macros.html.twig' as fields %} + {# @var Glpi\Form\Destination\AbstractCommonITILFormDestination item #} {# @var Glpi\Form\Form\ form #} {# @var array config #}
- {# @var Glpi\Form\Destination\ConfigFieldInterface field #} + {# @var Glpi\Form\Destination\DestinationFieldInterface field #} {% for field in item.getConfigurableFields()|sort((a, b) => a.getWeight() <=> b.getWeight()) %} - + {% set config_field = field.getConfig(form, config) %} {% set extra_options = {} %} {% if field.supportAutoConfiguration() %} {% set use_auto_configuration = field.isAutoConfigurated(config) %} @@ -73,19 +75,171 @@
{% endset %} - {% set extra_options = { - 'insert_content_after_label': auto_configuration_checkbox, + {% set extra_options = extra_options|merge({ 'disabled': use_auto_configuration, - } %} + }) %} {% endif %} -
- {{ field.renderConfigForm( - form, - field.getConfig(form, config), - item.formatConfigInputName(field.getKey()), - {'is_horizontal': false}|merge(extra_options) - )|raw }} + {% set field_container_rand = random() %} + {% set rand = random() %} +
+ {% if config_field is instanceof('Glpi\\Form\\Destination\\ConfigFieldWithStrategiesInterface') %} + {% set label_for = call('Html::cleanId', [ + 'dropdown_' ~ '%s[%s][]'|format( + item.formatConfigInputName(field.getKey()), + config_field.getStrategiesInputName() + ) ~ rand + ]) %} + {% elseif config_field is instanceof('Glpi\\Form\\Destination\\CommonITILField\\SimpleValueConfig') %} + {% set label_for = '%s[%s]_%s'|format( + item.formatConfigInputName(field.getKey()), + constant('Glpi\\Form\\Destination\\CommonITILField\\SimpleValueConfig::VALUE'), + rand + ) %} + {% endif %} + +
+ + {% if field.supportAutoConfiguration() %} + {{ auto_configuration_checkbox }} + {% endif %} +
+
+ {% if config_field is instanceof('Glpi\\Form\\Destination\\ConfigFieldWithStrategiesInterface') %} + {% for strategy in config_field.getStrategies() %} + {# Update the random value for each iteration except the first one, to avoid conflicts with labels #} + {% if not loop.first %} + {% set rand = random() %} + {% endif %} + +
+ {{ fields.dropdownArrayField( + '%s[%s][]'|format( + item.formatConfigInputName(field.getKey()), + config_field.getStrategiesInputName() + ), + strategy.value, + field.getStrategiesForDropdown(), + '', + { + is_horizontal: false, + field_class: '', + no_label: true, + mb: '', + rand: rand, + aria_label: __('Select strategy...'), + }|merge(extra_options) + ) }} + {{ field.renderConfigForm( + form, + config_field, + item.formatConfigInputName(field.getKey()), + { + 'is_horizontal': false, + 'rand': rand, + }|merge(extra_options) + )|raw }} + {% if field.canHaveMultipleStrategies() %} + + {% endif %} +
+ {% endfor %} + {% if field.canHaveMultipleStrategies() %} + + {% endif %} + {% else %} + {{ field.renderConfigForm( + form, + config_field, + item.formatConfigInputName(field.getKey()), + { + 'is_horizontal': true, + 'rand': rand, + }|merge(extra_options) + )|raw }} + {% endif %} +
+ + {% if field.canHaveMultipleStrategies() %} + + {% endif %}
+ + {% endfor %} diff --git a/templates/pages/admin/form/itil_config_fields/associated_items.html.twig b/templates/pages/admin/form/itil_config_fields/associated_items.html.twig index f3258fd9225..fb6bc9b9559 100644 --- a/templates/pages/admin/form/itil_config_fields/associated_items.html.twig +++ b/templates/pages/admin/form/itil_config_fields/associated_items.html.twig @@ -35,54 +35,18 @@ {% set rand = random() %} -{{ fields.dropdownArrayField( - main_config_field.input_name, - main_config_field.value, - main_config_field.possible_values, - main_config_field.label, - options -) }} -
{% set remove_button %} - {% endset %} -
- {% if specific_values_extra_field.associated_items is empty %} - {{ fields.dropdownItemsFromItemtypes( - specific_values_extra_field.input_name, - '', - { - 'init' : true, - 'itemtypes' : specific_values_extra_field.itemtypes, - 'no_label' : true, - 'display_emptychoice': false, - 'itemtype_name' : specific_values_extra_field.input_name ~ '[itemtype][]', - 'items_id_name' : specific_values_extra_field.input_name ~ '[items_id][]', - 'default_itemtype' : specific_values_extra_field.itemtypes|first, - 'width' : '30%', - 'add_field_class' : 'd-flex input-group flex-nowrap', - 'mb' : 'mb-2', - 'add_field_html' : remove_button, - 'add_field_attribs' : { - 'data-glpi-specific-values-extra-field-item': '' - }, - 'aria_label' : specific_values_extra_field.itemtype_aria_label, - 'specific_tags_items_id_dropdown': { - 'aria-label': specific_values_extra_field.items_id_aria_label, - } - } - ) }} - {% endif %} - +
{% for itemtype, items_ids in specific_values_extra_field.associated_items %} {% for items_id in items_ids %} {{ fields.dropdownItemsFromItemtypes( @@ -99,58 +63,81 @@ 'default_items_id' : items_id, 'width' : '30%', 'add_field_class' : 'd-flex input-group flex-nowrap', - 'mb' : 'mb-2', + 'mb' : '', 'add_field_html' : remove_button, 'add_field_attribs' : { - 'data-glpi-specific-values-extra-field-item': '' + 'data-glpi-associated-items-specific-values-extra-field-item': '' }, 'aria_label' : specific_values_extra_field.itemtype_aria_label, 'specific_tags_items_id_dropdown': { 'aria-label': specific_values_extra_field.items_id_aria_label, + 'data-glpi-associated-items-items-id-dropdown': '' } } ) }} {% endfor %} {% endfor %} -
- - -