diff --git a/inc/destinationfield.class.php b/inc/destinationfield.class.php index 831f2081..8b881823 100644 --- a/inc/destinationfield.class.php +++ b/inc/destinationfield.class.php @@ -131,13 +131,25 @@ public function applyConfiguratedValueToInputUsingAnswers( $field_name = $field->fields['name']; } + $raw_answer = $answer->getRawAnswer(); + $is_multiple = (bool) $field->fields['multiple']; + if (!$is_multiple && $question->getQuestionType() instanceof PluginFieldsQuestionType) { + $extra = json_decode($question->fields['extra_data'] ?? '{}', true) ?? []; + $is_multiple = (bool) ($extra[PluginFieldsQuestionTypeExtraDataConfig::IS_MULTIPLE] ?? false); + } + if ($field->fields['type'] == 'glpi_item') { - $input[sprintf('itemtype_%s', $field_name)] = $answer->getRawAnswer()['itemtype']; - $input[sprintf('items_id_%s', $field_name)] = $answer->getRawAnswer()['items_id']; - } elseif ($field->fields['type'] == 'dropdown') { - $input[$field_name] = $answer->getRawAnswer()['items_id']; + $input[sprintf('itemtype_%s', $field_name)] = $raw_answer['itemtype']; + $input[sprintf('items_id_%s', $field_name)] = $raw_answer['items_id']; + } elseif (str_starts_with((string) $field->fields['type'], 'dropdown')) { + $items_id = $raw_answer['items_id'] ?? $raw_answer; + if ($is_multiple && is_array($items_id)) { + $input[$field_name] = json_encode(array_values(array_map(intval(...), $items_id))); + } else { + $input[$field_name] = $items_id; + } } else { - $input[$field_name] = $value ?? $answer->getRawAnswer(); + $input[$field_name] = $value ?? $raw_answer; } } } diff --git a/inc/questiontype.class.php b/inc/questiontype.class.php index e40963d0..39c4c410 100644 --- a/inc/questiontype.class.php +++ b/inc/questiontype.class.php @@ -32,6 +32,7 @@ use Glpi\DBAL\JsonFieldInterface; use Glpi\Form\Condition\ConditionHandler\ItemAsTextConditionHandler; use Glpi\Form\Condition\ConditionHandler\ItemConditionHandler; +use Glpi\Form\Condition\ConditionHandler\MultipleChoiceFromValuesConditionHandler; use Glpi\Form\Form; use Glpi\Form\Migration\FormQuestionDataConverterInterface; use Glpi\Form\Question; @@ -85,6 +86,41 @@ public function formatDefaultValueForDB(mixed $value): string return json_encode($value); } + #[Override] + public function prepareEndUserAnswer(Question $question, mixed $answer): mixed + { + if ($this->isMultipleForQuestion($question) && (isset($answer['items_id']) && is_array($answer['items_id']))) { + $answer['items_id'] = array_values(array_map(intval(...), $answer['items_id'])); + } + + return $answer; + } + + #[Override] + public function renderAdministrationOptionsTemplate(?Question $question): string + { + $is_multiple = $this->isMultipleForQuestion($question); + + $template = << + + +TWIG; + + $twig = TemplateRenderer::getInstance(); + return $twig->renderFromStringTemplate($template, [ + 'is_multiple' => $is_multiple, + 'label' => __('Allow multiple options'), + ]); + } + #[Override] public function validateExtraDataInput(array $input): bool { @@ -134,6 +170,9 @@ public function renderAdministrationTemplate(?Question $question): string $default_value = json_decode($question->fields['default_value'], true); } + $field_data = $current_field->fields; + $field_data['multiple'] = $this->isMultipleForQuestion($question) ? 1 : 0; + $twig = TemplateRenderer::getInstance(); return $twig->render('@fields/question_type_administration.html.twig', [ 'question' => $question, @@ -141,7 +180,7 @@ public function renderAdministrationTemplate(?Question $question): string 'selected_field_id' => $current_field_id, 'available_fields' => $available_fields, 'item' => new Form(), - 'field' => $current_field->fields, + 'field' => $field_data, ]); } @@ -181,10 +220,13 @@ public function renderEndUserTemplate(Question $question): string } } + $field_data = $current_field->fields; + $field_data['multiple'] = $this->isMultipleForQuestion($question) ? 1 : 0; + $twig = TemplateRenderer::getInstance(); return $twig->render('@fields/question_type_end_user.html.twig', [ 'question' => $question, - 'field' => $current_field->fields, + 'field' => $field_data, 'default_value' => $default_value, 'item' => new Form(), 'itemtype' => $itemtype, @@ -312,7 +354,6 @@ public function getConditionHandlers( return parent::getConditionHandlers($question_config); } - // If the question is configured with a dropdown field, we add condition handlers to handle item and item as text conditions on the dropdown options $field = PluginFieldsField::getById($question_config->getFieldId()); if ($field && str_starts_with((string) $field->fields['type'], 'dropdown')) { if ($field->fields['type'] == 'dropdown') { @@ -330,11 +371,29 @@ public function getConditionHandlers( new ItemAsTextConditionHandler($itemtype), ], ); + + if ($field->fields['multiple'] || $question_config->isMultiple()) { + $condition_handlers[] = new MultipleChoiceFromValuesConditionHandler( + $this->getDropdownValuesForCondition($itemtype), + ); + } } return $condition_handlers; } + private function getDropdownValuesForCondition(string $itemtype): array + { + $values = []; + $item = new $itemtype(); + $rows = $item->find([], 'name'); + foreach ($rows as $row) { + $values[(string) $row['id']] = $row['name']; + } + + return $values; + } + /** * Retrieve the default value block from the question's extra data * @@ -375,6 +434,31 @@ public function getDefaultValueFieldId(?Question $question): ?int return $config->getFieldId(); } + private function getFieldForQuestion(Question $question): ?PluginFieldsField + { + $field_id = $this->getDefaultValueFieldId($question); + if ($field_id === null) { + return null; + } + return PluginFieldsField::getById($field_id) ?: null; + } + + private function isMultipleForQuestion(?Question $question): bool + { + if (!$question instanceof Question) { + return false; + } + + /** @var ?PluginFieldsQuestionTypeExtraDataConfig $config */ + $config = $this->getExtraDataConfig(json_decode($question->fields['extra_data'], true) ?? []); + if ($config !== null && $config->isMultiple()) { + return true; + } + + $field = $this->getFieldForQuestion($question); + return $field !== null && (bool) $field->fields['multiple']; + } + private function getAvailableBlocks(): array { $field_container = new PluginFieldsContainer(); @@ -391,7 +475,7 @@ private function getAvailableBlocks(): array $result = $field_container->find([ 'is_active' => 1, - 'type' => 'dom', + 'type' => ['dom', 'tab'], 'OR' => [ ['itemtypes' => ['LIKE', '%\"Ticket\"%']], ['itemtypes' => ['LIKE', '%\"Change\"%']], diff --git a/inc/questiontypeextradataconfig.class.php b/inc/questiontypeextradataconfig.class.php index ce07e6aa..65faf7fc 100644 --- a/inc/questiontypeextradataconfig.class.php +++ b/inc/questiontypeextradataconfig.class.php @@ -37,9 +37,12 @@ class PluginFieldsQuestionTypeExtraDataConfig implements JsonFieldInterface public const FIELD_ID = "field_id"; + public const IS_MULTIPLE = "is_multiple"; + public function __construct( private readonly ?int $block_id = null, private readonly ?int $field_id = null, + private readonly bool $is_multiple = false, ) {} #[Override] @@ -48,6 +51,7 @@ public static function jsonDeserialize(array $data): self return new self( block_id: $data[self::BLOCK_ID] ?? null, field_id: $data[self::FIELD_ID] ?? null, + is_multiple: (bool) ($data[self::IS_MULTIPLE] ?? false), ); } @@ -57,6 +61,7 @@ public function jsonSerialize(): array return [ self::BLOCK_ID => $this->block_id, self::FIELD_ID => $this->field_id, + self::IS_MULTIPLE => $this->is_multiple, ]; } @@ -69,4 +74,9 @@ public function getFieldId(): ?int { return $this->field_id; } + + public function isMultiple(): bool + { + return $this->is_multiple; + } } diff --git a/templates/fields.html.twig b/templates/fields.html.twig index 44a3c5a4..7f525dd7 100644 --- a/templates/fields.html.twig +++ b/templates/fields.html.twig @@ -142,6 +142,10 @@ {% set dropdown_options = {'entity': item.getEntityID()} %} {% if field['multiple'] %} {% set dropdown_options = dropdown_options|merge({'multiple': true}) %} + {% if value is iterable %} + {% set dropdown_options = dropdown_options|merge({'values': value}) %} + {% set value = '' %} + {% endif %} {% endif %} {% if item.isRecursive() %} {% set dropdown_options = dropdown_options|merge({'entity_sons': true}) %} @@ -168,6 +172,10 @@ {% endif %} {% if field['multiple'] %} {% set dropdown_options = dropdown_options|merge({'multiple': true}) %} + {% if value is iterable %} + {% set dropdown_options = dropdown_options|merge({'values': value}) %} + {% set value = '' %} + {% endif %} {% endif %} {{ macros.dropdownField(field['dropdown_class'], input_name, value, label, field_options|merge(dropdown_options|default({}))) }}