Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

<<<<<<< HEAD
## [Unreleased]

### Fixed

- Remove redundant variable initialization in the question type AJAX controller
=======
## [UNRELEASED]

### Fixed

- Fix form submission error when a multiple dropdown field question is left empty
- Fix search criterion value on multiple dropdown fields being reset after a search, which emptied the criterion when sorting or paging results
>>>>>>> be73dd9 (Fix form submission error when a multiple dropdown field question is empty (#1323))

## [1.25.1] - 2026-10-28

Expand Down
10 changes: 9 additions & 1 deletion inc/questiontype.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,18 @@
case 'date':
return (string) $answer;
case 'dropdown':
<<<<<<< HEAD

Check failure on line 206 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.5 - mariadb:10.11 / Continuous integration

Syntax error, unexpected T_SL on line 206

Check failure on line 206 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.3 - mysql:8.0 / Continuous integration

Syntax error, unexpected T_SL on line 206
$answer = self::extractDropdownAnswerIds($answer);
=======
// An empty multiple dropdown is submitted as an empty string
$ids = array_filter(
array_map(intval(...), (array) ($answer['items_id'] ?? [])),

Check failure on line 211 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.5 - mariadb:10.11 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE, expecting '-' or T_STRING or T_VARIABLE or T_NUM_STRING on line 211

Check failure on line 211 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.3 - mysql:8.0 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE, expecting '-' or T_STRING or T_VARIABLE or T_NUM_STRING on line 211
fn(int $id) => $id > 0,
);
>>>>>>> be73dd9 (Fix form submission error when a multiple dropdown field question is empty (#1323))

$itemtype = PluginFieldsDropdown::getClassname($current_field->fields['name']);

Check failure on line 216 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.5 - mariadb:10.11 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 216

Check failure on line 216 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.3 - mysql:8.0 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 216
return implode(', ', array_map(fn($opt_id) => $itemtype::getById($opt_id)?->fields['name'] ?? '', $answer));
return implode(', ', array_map(fn(int $opt_id) => $itemtype::getById($opt_id)?->fields['name'] ?? '', $ids));
case 'yesno':
return $answer ? __('Yes') : __('No');
case 'datetime':
Expand All @@ -222,11 +230,11 @@
return '';
}

return $item->fields['name'];

Check failure on line 233 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.5 - mariadb:10.11 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 233

Check failure on line 233 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.3 - mysql:8.0 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 233
}

if (str_starts_with((string) $current_field->fields['type'], 'dropdown-')) {

Check failure on line 236 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.5 - mariadb:10.11 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 236

Check failure on line 236 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.3 - mysql:8.0 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 236
$itemtype = substr((string) $current_field->fields['type'], strlen('dropdown-'));

Check failure on line 237 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.5 - mariadb:10.11 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 237

Check failure on line 237 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.3 - mysql:8.0 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 237
if (!getItemForItemtype($itemtype)) {
return '';
}
Expand All @@ -237,7 +245,7 @@
foreach ($answer as $items_id) {
$item = $itemtype::getById((int) $items_id);
if ($item) {
$names[] = $item->fields['name'];

Check failure on line 248 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.5 - mariadb:10.11 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 248

Check failure on line 248 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.3 - mysql:8.0 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 248
}
}

Expand Down Expand Up @@ -279,17 +287,17 @@
}

$block = new PluginFieldsContainer();
if (!$block->getFromDB($values['blocks_field'])) {

Check failure on line 290 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.5 - mariadb:10.11 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 290

Check failure on line 290 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.3 - mysql:8.0 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 290
return null;
}

$field = new PluginFieldsField();
if (!$field->getFromDBByCrit(['name' => $values['dropdown_fields_field']])) {

Check failure on line 295 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.5 - mariadb:10.11 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 295

Check failure on line 295 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.3 - mysql:8.0 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 295
return null;
}

return [
'block_id' => $block->getID(),

Check failure on line 300 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.5 - mariadb:10.11 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 300

Check failure on line 300 in inc/questiontype.class.php

View workflow job for this annotation

GitHub Actions / GLPI 12.0.x - php:8.3 - mysql:8.0 / Continuous integration

Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 300
'field_id' => $field->getID(),
];
}
Expand Down
200 changes: 200 additions & 0 deletions tests/Units/FieldQuestionTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,206 @@ public function testFormatRawAnswerSupportsLegacyItemsIdKey(): void
{
$this->login();

<<<<<<< HEAD
=======
// Arrange: create a multiple "dropdown-Location" field
$this->fields['locations'] = $this->createField([
'label' => 'Locations',
'type' => 'dropdown-' . Location::class,
'multiple' => 1,
'default_value' => [],
PluginFieldsContainer::getForeignKeyField() => $this->block->getID(),
'ranking' => 2,
'is_active' => 1,
], ['default_value']);

$location1 = $this->createItem(Location::class, [
'name' => __FUNCTION__ . ' 1',
'entities_id' => $this->getTestRootEntity(true),
]);
$location2 = $this->createItem(Location::class, [
'name' => __FUNCTION__ . ' 2',
'entities_id' => $this->getTestRootEntity(true),
]);

$builder = new FormBuilder("My form");
$builder->addQuestion(
"Locations question",
PluginFieldsQuestionType::class,
extra_data: json_encode($this->getFieldExtraDataConfig('locations')),
);
$form = $this->createForm($builder);
$question = Question::getById($this->getQuestionId($form, "Locations question"));

try {
// Act: format the answer as submitted by the end user template
$formatted = (new PluginFieldsQuestionType())->formatRawAnswer([
'itemtype' => Location::class,
'items_id' => [(string) $location1->getID(), (string) $location2->getID()],
], $question);

// Assert: both location names are displayed
$this->assertEquals(
$location1->fields['name'] . ', ' . $location2->fields['name'],
$formatted,
);
} finally {
// delete locations for another run
$location1->delete($location1->fields, true);
$location2->delete($location2->fields, true);
}
}

public function testFormatRawAnswerForMultipleDropdown(): void
{
$this->login();

// Arrange: create a multiple plugin dropdown field
$this->fields['multiple_dropdown'] = $this->createField([
'label' => 'Multiple dropdown',
'type' => 'dropdown',
'multiple' => 1,
'default_value' => [],
PluginFieldsContainer::getForeignKeyField() => $this->block->getID(),
'ranking' => 2,
'is_active' => 1,
], ['default_value']);

$dropdown_class = PluginFieldsDropdown::getClassname($this->fields['multiple_dropdown']->fields['name']);
$value1 = $this->createItem($dropdown_class, ['name' => 'Value 1', 'entities_id' => 0]);
$value2 = $this->createItem($dropdown_class, ['name' => 'Value 2', 'entities_id' => 0]);

$builder = new FormBuilder("My form");
$builder->addQuestion(
"Multiple dropdown question",
PluginFieldsQuestionType::class,
extra_data: json_encode($this->getFieldExtraDataConfig('multiple_dropdown')),
);
$form = $this->createForm($builder);
$question = Question::getById($this->getQuestionId($form, "Multiple dropdown question"));
$question_type = new PluginFieldsQuestionType();

// Act & Assert: an empty selection is submitted as an empty string
$this->assertEquals('', $question_type->formatRawAnswer([
'itemtype' => $dropdown_class,
'items_id' => '',
], $question));

// Act & Assert: selected values are displayed
$this->assertEquals('Value 1, Value 2', $question_type->formatRawAnswer([
'itemtype' => $dropdown_class,
'items_id' => [(string) $value1->getID(), (string) $value2->getID()],
], $question));
}

public function testFieldsQuestionEditorRenderingForMultipleDropdownWithStringKeyedDefaultValue(): void
{
$this->login();

// Arrange: create a multiple "dropdown-Location" field
$this->fields['locations'] = $this->createField([
'label' => 'Locations',
'type' => 'dropdown-' . Location::class,
'multiple' => 1,
'default_value' => [],
PluginFieldsContainer::getForeignKeyField() => $this->block->getID(),
'ranking' => 2,
'is_active' => 1,
], ['default_value']);

$location1 = $this->createItem(Location::class, [
'name' => __FUNCTION__ . ' 1',
'entities_id' => $this->getTestRootEntity(true),
]);
$location2 = $this->createItem(Location::class, [
'name' => __FUNCTION__ . ' 2',
'entities_id' => $this->getTestRootEntity(true),
]);

// Default value with string keys, as they would be passed as named parameters if not reindexed
$builder = new FormBuilder("My form");
$builder->addQuestion(
"Locations question",
PluginFieldsQuestionType::class,
default_value: [
'itemtype' => Location::class,
'items_id' => [
'first' => $location1->getID(),
'second' => $location2->getID(),
],
],
extra_data: json_encode($this->getFieldExtraDataConfig('locations')),
);
$form = $this->createForm($builder);

try {
// Act: render form editor
$crawler = $this->renderFormEditor($form);

// Assert: both locations are rendered as the default value
$selected_values = $crawler
->filter('.form-editor-container [data-glpi-form-editor-question] input[name^="default_value[items_id]"]')
->each(fn($input) => (int) $input->attr('value'));
$this->assertEqualsCanonicalizing(
[$location1->getID(), $location2->getID()],
$selected_values,
);
} finally {
// delete locations for another run
$location1->delete($location1->fields, true);
$location2->delete($location2->fields, true);
}
}

public static function cloneDefaultValueProvider(): iterable
{
yield 'text' => ['text', 'hello'];
yield 'textarea' => ['textarea', "first line\nsecond line"];
yield 'number' => ['number', '42'];
yield 'url' => ['url', 'https://glpi-project.org'];
yield 'date' => ['date', '2026-10-15'];
yield 'json-like text' => ['text', '[1,2]'];
}

#[DataProvider('cloneDefaultValueProvider')]
public function testCloneKeepsScalarDefaultValue(string $type, string $default_value): void
{
$this->login();

// Arrange: create a form with a question using a scalar field
$this->fields['scalar'] = $this->createField([
'label' => 'Scalar field',
'type' => $type,
PluginFieldsContainer::getForeignKeyField() => $this->block->getID(),
'ranking' => 2,
'is_active' => 1,
]);
$builder = new FormBuilder("My form");
$builder->addQuestion(
"Scalar question",
PluginFieldsQuestionType::class,
default_value: $default_value,
extra_data: json_encode($this->getFieldExtraDataConfig('scalar')),
);
$form = $this->createForm($builder);

// Act: clone the form, then clone the clone
$clone = Form::getById($form->clone());
$clone_of_clone = Form::getById($clone->clone());

// Assert: the default value is encoded only once in each form
foreach ([$form, $clone, $clone_of_clone] as $current_form) {
$question = Question::getById($this->getQuestionId($current_form, "Scalar question"));
$this->assertSame(json_encode($default_value), $question->fields['default_value']);
}
}

public function testCloneKeepsDropdownDefaultValue(): void
{
$this->login();

// Arrange: create a form with a dropdown question having a default value
>>>>>>> be73dd9 (Fix form submission error when a multiple dropdown field question is empty (#1323))
$itemtype = PluginFieldsDropdown::getClassname($this->fields['dropdown']->fields['name']);
$item = $this->createItem($itemtype, ['name' => 'Legacy Option']);

Expand Down
Loading