";
echo "";
@@ -111,7 +111,7 @@ function plugin_fields_uninstall()
{
if (!class_exists('PluginFieldsProfile')) {
Session::addMessageAfterRedirect(
- __("The plugin can't be uninstalled when the plugin is disabled", 'fields'),
+ __s("The plugin can't be uninstalled when the plugin is disabled", 'fields'),
true,
WARNING,
true,
@@ -124,7 +124,7 @@ function plugin_fields_uninstall()
echo '';
echo "";
- echo '| ' . __('MySQL tables uninstallation', 'fields') . ' | | ';
+ echo '| ' . __s('MySQL tables uninstallation', 'fields') . ' | | ';
echo "";
echo "| ";
@@ -227,9 +227,7 @@ function plugin_fields_MassiveActionsFieldsDisplay($options = [])
);
}
- PluginFieldsField::showSingle($options['itemtype'], $options['options'], true);
-
- return true;
+ return PluginFieldsField::showSingle($options['itemtype'], $options['options'], true);
}
// Need to return false on non display item
diff --git a/inc/container.class.php b/inc/container.class.php
index 0c0b2ebb..f98e3526 100644
--- a/inc/container.class.php
+++ b/inc/container.class.php
@@ -2097,12 +2097,23 @@ private static function populateData($c_id, CommonDBTM $item)
//managed multi GLPI item dropdown field
if (preg_match('/^dropdown-(?.+)$/', (string) $field['type'], $match) === 1) {
+ $defined_key = '_' . $field['name'] . '_defined';
//values are defined by user
if (isset($item->input[$field['name']])) {
$data[$field['name']] = $item->input[$field['name']];
$has_fields = true;
- } else { //multi dropdown is empty or has been emptied
+ } elseif (
+ isset($item->input[$defined_key])
+ && $item->input[$defined_key]
+ ) { //multi dropdown is empty or has been emptied
$data[$field['name']] = [];
+ $has_fields = true;
+ } elseif (
+ isset($_REQUEST['massiveaction'])
+ && isset($_POST[$field['name']])
+ ) { // called from massiveaction
+ $data[$field['name']] = $_POST[$field['name']];
+ $has_fields = true;
}
}
}
diff --git a/inc/field.class.php b/inc/field.class.php
index 07f25d69..ce9b3e24 100644
--- a/inc/field.class.php
+++ b/inc/field.class.php
@@ -1361,6 +1361,14 @@ public static function showSingle($itemtype, $searchOption, $massiveaction = fal
(string) $searchOption['linkfield'],
);
+ // itemtype is stored in a JSON array, so entry is surrounded by double quotes
+ $search_string = json_encode($itemtype);
+ // Backslashes must be doubled in LIKE clause according to MySQL documentation
+ // But do not escape backslashes for namespaced itemtypes, as they are already escaped
+ if (!str_contains((string) $itemtype, '\\')) {
+ $search_string = str_replace('\\', '\\\\', $search_string);
+ }
+
//find field
$iterator = $DB->request([
'SELECT' => [
@@ -1381,7 +1389,7 @@ public static function showSingle($itemtype, $searchOption, $massiveaction = fal
],
'WHERE' => [
'fields.name' => $cleaned_linkfield,
- 'containers.itemtypes' => ['LIKE', sprintf('%%%s%%', $itemtype)],
+ 'containers.itemtypes' => ['LIKE', '%' . $DB->escape($search_string) . '%'],
],
]);
diff --git a/tests/Units/MassiveActionCustomAssetTest.php b/tests/Units/MassiveActionCustomAssetTest.php
new file mode 100644
index 00000000..7944d159
--- /dev/null
+++ b/tests/Units/MassiveActionCustomAssetTest.php
@@ -0,0 +1,107 @@
+.
+ * -------------------------------------------------------------------------
+ * @copyright Copyright (C) 2013-2023 by Fields plugin team.
+ * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html
+ * @link https://github.com/pluginsGLPI/fields
+ * -------------------------------------------------------------------------
+ */
+
+declare(strict_types=1);
+
+namespace GlpiPlugin\Field\Tests\Units;
+
+use Glpi\Tests\DbTestCase;
+use Glpi\Tests\GLPITestCase;
+use GlpiPlugin\Field\Tests\FieldTestTrait;
+use PluginFieldsContainer;
+use PluginFieldsField;
+use Search;
+
+require_once __DIR__ . '/../FieldTestCase.php';
+
+/**
+ * Reproduces the bug where the massive action "update" widget for a Fields
+ * plugin field is never rendered for CustomAsset itemtypes (namespaced
+ * classes like Glpi\CustomAsset\XxxAsset), because PluginFieldsField::showSingle()
+ * builds a LIKE query against the un-escaped itemtype string.
+ */
+final class MassiveActionCustomAssetTest extends DbTestCase
+{
+ use FieldTestTrait;
+
+ public function setUp(): void
+ {
+ GLPITestCase::setUp();
+ $this->login();
+ }
+
+ public function tearDown(): void
+ {
+ $this->tearDownFieldTest();
+ GLPITestCase::tearDown();
+ }
+
+ public function testShowSingleDisplaysFieldForCustomAsset(): void
+ {
+ $definition = $this->initAssetDefinition('so' . substr((string) $this->getUniqueString(), 0, 6));
+ $asset_class = $definition->getAssetClassName();
+
+ $container = $this->createFieldContainer([
+ 'label' => 'F',
+ 'type' => 'tab',
+ 'itemtypes' => [$asset_class],
+ 'is_active' => 1,
+ 'entities_id' => 0,
+ 'is_recursive' => 1,
+ ]);
+
+ $field = $this->createField([
+ 'label' => 'Custom Asset Field',
+ 'type' => 'text',
+ PluginFieldsContainer::getForeignKeyField() => $container->getID(),
+ 'ranking' => 1,
+ 'is_active' => 1,
+ 'is_readonly' => 0,
+ ]);
+ $field_name = $field->fields['name'];
+
+ $search_option = null;
+ foreach (Search::getOptions($asset_class) as $so) {
+ if (($so['linkfield'] ?? null) === $field_name) {
+ $search_option = $so;
+ break;
+ }
+ }
+
+ $this->assertIsArray($search_option, 'search option not found for plugin field on custom asset');
+
+ ob_start();
+ $result = PluginFieldsField::showSingle($asset_class, $search_option, true);
+ $html = ob_get_clean();
+
+ $this->assertTrue($result, 'showSingle() should find the field container for a CustomAsset itemtype');
+ $this->assertStringContainsString($field_name, $html);
+ }
+}
diff --git a/tests/Units/MassiveActionGlpiItemDropdownTest.php b/tests/Units/MassiveActionGlpiItemDropdownTest.php
new file mode 100644
index 00000000..12663d61
--- /dev/null
+++ b/tests/Units/MassiveActionGlpiItemDropdownTest.php
@@ -0,0 +1,193 @@
+.
+ * -------------------------------------------------------------------------
+ * @copyright Copyright (C) 2013-2023 by Fields plugin team.
+ * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html
+ * @link https://github.com/pluginsGLPI/fields
+ * -------------------------------------------------------------------------
+ */
+
+declare(strict_types=1);
+
+namespace GlpiPlugin\Field\Tests\Units;
+
+use PluginFieldsField;
+use Glpi\Tests\DbTestCase;
+use Glpi\Tests\GLPITestCase;
+use GlpiPlugin\Field\Tests\FieldTestTrait;
+use Location;
+use MassiveAction;
+use PluginFieldsContainer;
+use ReflectionClass;
+use Glpi\Search\SearchOption;
+
+require_once __DIR__ . '/../FieldTestCase.php';
+
+/**
+ * Reproduces the bug where a massive action "update" of one field wipes a
+ * sibling "dropdown-" field (e.g. a multi-select referencing
+ * Location) in the same container, because PluginFieldsContainer::populateData()
+ * unconditionally blanks it to [] whenever it's absent from the current
+ * request's input - unlike the plain "dropdown" type, which is guarded
+ * against this (fixed in #795/#974).
+ */
+final class MassiveActionGlpiItemDropdownTest extends DbTestCase
+{
+ use FieldTestTrait;
+
+ public function setUp(): void
+ {
+ GLPITestCase::setUp();
+ $this->login();
+ }
+
+ public function tearDown(): void
+ {
+ unset($_REQUEST['massiveaction'], $_POST);
+ $this->tearDownFieldTest();
+ GLPITestCase::tearDown();
+ }
+
+ private function buildMassiveAction(array $post, string $itemtype, int $id): MassiveAction
+ {
+ $ref = new ReflectionClass(MassiveAction::class);
+ $ma = $ref->newInstanceWithoutConstructor();
+ $ma->POST = $post;
+
+ $set = static function (string $name, $value) use ($ref, $ma): void {
+ $prop = $ref->getProperty($name);
+ $prop->setValue($ma, $value);
+ };
+
+ $set('action', 'update');
+ $set('done', []);
+ $set('nb_done', 0);
+ $set('current_itemtype', null);
+ $set('remainings', [$itemtype => [$id => $id]]);
+ $set('results', ['ok' => 0, 'noaction' => 0, 'ko' => 0, 'noright' => 0, 'messages' => []]);
+ $set('start_time', microtime(true));
+
+ return $ma;
+ }
+
+ public function testMassiveUpdateOfOtherFieldDoesNotWipeGlpiItemDropdown(): void
+ {
+ $definition = $this->initAssetDefinition('so' . substr((string) $this->getUniqueString(), 0, 6));
+ $asset_class = $definition->getAssetClassName();
+
+ $container = $this->createFieldContainer([
+ 'label' => 'F',
+ 'type' => 'tab',
+ 'itemtypes' => [$asset_class],
+ 'is_active' => 1,
+ 'entities_id' => 0,
+ 'is_recursive' => 1,
+ ]);
+
+ $glpi_item_field = $this->createItem(PluginFieldsField::class, [
+ 'label' => 'Locations',
+ 'type' => 'dropdown-' . Location::class,
+ 'multiple' => 1,
+ 'default_value' => [],
+ PluginFieldsContainer::getForeignKeyField() => $container->getID(),
+ 'ranking' => 1,
+ 'is_active' => 1,
+ 'is_readonly' => 0,
+ ], ['allowed_values', 'question_types', 'default_value']);
+ $glpi_item_field_name = $glpi_item_field->fields['name'];
+
+ $text_field = $this->createField([
+ 'label' => 'Other Field',
+ 'type' => 'text',
+ PluginFieldsContainer::getForeignKeyField() => $container->getID(),
+ 'ranking' => 2,
+ 'is_active' => 1,
+ 'is_readonly' => 0,
+ ]);
+ $text_field_name = $text_field->fields['name'];
+
+ $location1 = $this->createItem(Location::class, ['name' => $this->getUniqueString(), 'entities_id' => 0]);
+ $location2 = $this->createItem(Location::class, ['name' => $this->getUniqueString(), 'entities_id' => 0]);
+
+ $entities_id = reset($_SESSION['glpiactiveentities']);
+ $asset = $this->createItem($asset_class, ['name' => 'Test asset', 'entities_id' => $entities_id]);
+
+ $so = SearchOption::getOptionsForItemtype($asset_class);
+ $glpi_item_so_index = null;
+ $text_so_index = null;
+ foreach ($so as $idx => $opt) {
+ if (($opt['linkfield'] ?? null) === $glpi_item_field_name || ($opt['field'] ?? null) === $glpi_item_field_name) {
+ $glpi_item_so_index = $idx;
+ }
+
+ if (($opt['linkfield'] ?? null) === $text_field_name) {
+ $text_so_index = $idx;
+ }
+ }
+
+ $this->assertNotNull($glpi_item_so_index, 'search option index not found for dropdown-Location field');
+ $this->assertNotNull($text_so_index, 'search option index not found for text field');
+
+ $_REQUEST['massiveaction'] = 1;
+
+ // --- First massive action: set the multi-select Location field ---
+ $_POST = [
+ 'common_options' => [$asset_class . ':' . $glpi_item_so_index => $asset_class . ':' . $glpi_item_so_index],
+ 'search_options' => [$asset_class => $glpi_item_so_index],
+ 'id_field' => $asset_class . ':' . $glpi_item_so_index,
+ 'field' => $glpi_item_field_name,
+ $glpi_item_field_name => [$location1->getID(), $location2->getID()],
+ ];
+ $ma1 = $this->buildMassiveAction($_POST, $asset_class, $asset->getID());
+ MassiveAction::processMassiveActionsForOneItemtype($ma1, $asset, [$asset->getID()]);
+
+ $container_classname = PluginFieldsContainer::getClassname($asset_class, $container->fields['name']);
+ $container_item = new $container_classname();
+ $this->assertTrue($container_item->getFromDBByCrit(['items_id' => $asset->getID()]));
+ $this->assertSame(
+ [$location1->getID(), $location2->getID()],
+ json_decode($container_item->fields[$glpi_item_field_name], true),
+ 'dropdown-Location value not correctly stored after first massive action',
+ );
+
+ // --- Second massive action: update the sibling text field only ---
+ $_POST = [
+ 'common_options' => [$asset_class . ':' . $text_so_index => $asset_class . ':' . $text_so_index],
+ 'search_options' => [$asset_class => $text_so_index],
+ 'id_field' => $asset_class . ':' . $text_so_index,
+ 'field' => $text_field_name,
+ $text_field_name => 'hello',
+ ];
+ $ma2 = $this->buildMassiveAction($_POST, $asset_class, $asset->getID());
+ MassiveAction::processMassiveActionsForOneItemtype($ma2, $asset, [$asset->getID()]);
+
+ $container_item2 = new $container_classname();
+ $this->assertTrue($container_item2->getFromDBByCrit(['items_id' => $asset->getID()]));
+ $this->assertSame(
+ [$location1->getID(), $location2->getID()],
+ json_decode($container_item2->fields[$glpi_item_field_name], true),
+ 'Massive-updating the sibling text field must not wipe the previously set dropdown-Location value',
+ );
+ }
+}
diff --git a/tests/Units/MassiveActionMultipleDropdownTest.php b/tests/Units/MassiveActionMultipleDropdownTest.php
new file mode 100644
index 00000000..7a6fbe50
--- /dev/null
+++ b/tests/Units/MassiveActionMultipleDropdownTest.php
@@ -0,0 +1,148 @@
+.
+ * -------------------------------------------------------------------------
+ * @copyright Copyright (C) 2013-2023 by Fields plugin team.
+ * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html
+ * @link https://github.com/pluginsGLPI/fields
+ * -------------------------------------------------------------------------
+ */
+
+declare(strict_types=1);
+
+namespace GlpiPlugin\Field\Tests\Units;
+
+use PluginFieldsField;
+use Glpi\Tests\DbTestCase;
+use Glpi\Tests\GLPITestCase;
+use GlpiPlugin\Field\Tests\FieldTestTrait;
+use PluginFieldsContainer;
+use PluginFieldsDropdown;
+
+require_once __DIR__ . '/../FieldTestCase.php';
+
+/**
+ * Reproduces the bug where a massive action "update" of one field wipes out
+ * a previously mass-updated multiple-value dropdown field in the same
+ * container, because PluginFieldsContainer::populateData() re-evaluates
+ * every field of the container on every update, not just the field being
+ * edited.
+ */
+final class MassiveActionMultipleDropdownTest extends DbTestCase
+{
+ use FieldTestTrait;
+
+ public function setUp(): void
+ {
+ GLPITestCase::setUp();
+ $this->login();
+ }
+
+ public function tearDown(): void
+ {
+ unset($_REQUEST['massiveaction']);
+ $this->tearDownFieldTest();
+ GLPITestCase::tearDown();
+ }
+
+ public function testMassiveUpdateOfOtherFieldDoesNotWipeMultipleDropdown(): void
+ {
+ $definition = $this->initAssetDefinition('so' . substr((string) $this->getUniqueString(), 0, 6));
+ $asset_class = $definition->getAssetClassName();
+
+ $container = $this->createFieldContainer([
+ 'label' => 'F',
+ 'type' => 'tab',
+ 'itemtypes' => [$asset_class],
+ 'is_active' => 1,
+ 'entities_id' => 0,
+ 'is_recursive' => 1,
+ ]);
+
+ // Multiple-value Fields dropdown (e.g. "Transmission")
+ $dropdown_field = $this->createItem(PluginFieldsField::class, [
+ 'label' => 'Transmission',
+ 'type' => 'dropdown',
+ 'multiple' => 1,
+ 'default_value' => [],
+ PluginFieldsContainer::getForeignKeyField() => $container->getID(),
+ 'ranking' => 1,
+ 'is_active' => 1,
+ 'is_readonly' => 0,
+ ], ['allowed_values', 'question_types', 'default_value']);
+ $dropdown_field_name = $dropdown_field->fields['name'];
+ $multiple_key = 'plugin_fields_' . $dropdown_field_name . 'dropdowns_id';
+
+ // A plain sibling field in the SAME container
+ $text_field = $this->createField([
+ 'label' => 'Other Field',
+ 'type' => 'text',
+ PluginFieldsContainer::getForeignKeyField() => $container->getID(),
+ 'ranking' => 2,
+ 'is_active' => 1,
+ 'is_readonly' => 0,
+ ]);
+ $text_field_name = $text_field->fields['name'];
+
+ // Two allowed dropdown values
+ $dropdown_class = PluginFieldsDropdown::getClassname($dropdown_field_name);
+ $value1 = $this->createItem($dropdown_class, ['name' => 'Manual', 'entities_id' => 0]);
+ $value2 = $this->createItem($dropdown_class, ['name' => 'Automatic', 'entities_id' => 0]);
+
+ $asset = $this->createItem($asset_class, ['name' => 'Test asset', 'entities_id' => 0]);
+
+ $_REQUEST['massiveaction'] = 1;
+
+ // First massive action: set the multiple dropdown field
+ $item = new $asset_class();
+ $this->assertTrue($item->update([
+ 'id' => $asset->getID(),
+ 'c_id' => $container->getID(),
+ $multiple_key => [$value1->getID(), $value2->getID()],
+ ]));
+
+ $container_classname = PluginFieldsContainer::getClassname($asset_class, $container->fields['name']);
+ $container_item = new $container_classname();
+ $this->assertTrue($container_item->getFromDBByCrit(['items_id' => $asset->getID()]));
+ $this->assertSame(
+ [$value1->getID(), $value2->getID()],
+ json_decode($container_item->fields[$multiple_key], true),
+ );
+
+ // Second massive action: update the sibling text field only
+ $item2 = new $asset_class();
+ $this->assertTrue($item2->update([
+ 'id' => $asset->getID(),
+ 'c_id' => $container->getID(),
+ $text_field_name => 'hello',
+ ]));
+
+ $container_item2 = new $container_classname();
+ $this->assertTrue($container_item2->getFromDBByCrit(['items_id' => $asset->getID()]));
+ $this->assertSame(
+ [$value1->getID(), $value2->getID()],
+ json_decode($container_item2->fields[$multiple_key], true),
+ 'Massive-updating the sibling text field must not wipe the previously set multiple dropdown value',
+ );
+ }
+}
diff --git a/tests/Units/MassiveActionRealFlowTest.php b/tests/Units/MassiveActionRealFlowTest.php
new file mode 100644
index 00000000..488809a2
--- /dev/null
+++ b/tests/Units/MassiveActionRealFlowTest.php
@@ -0,0 +1,193 @@
+.
+ * -------------------------------------------------------------------------
+ * @copyright Copyright (C) 2013-2023 by Fields plugin team.
+ * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html
+ * @link https://github.com/pluginsGLPI/fields
+ * -------------------------------------------------------------------------
+ */
+
+declare(strict_types=1);
+
+namespace GlpiPlugin\Field\Tests\Units;
+
+use PluginFieldsField;
+use Glpi\Tests\DbTestCase;
+use Glpi\Tests\GLPITestCase;
+use GlpiPlugin\Field\Tests\FieldTestTrait;
+use MassiveAction;
+use PluginFieldsContainer;
+use PluginFieldsDropdown;
+use ReflectionClass;
+use Glpi\Search\SearchOption;
+
+require_once __DIR__ . '/../FieldTestCase.php';
+
+/**
+ * Reproduces the exact core MassiveAction::processMassiveActionsForOneItemtype()
+ * code path (the one producing "Array to string conversion" warnings) for a
+ * CustomAsset with a Fields-plugin multiple dropdown, then a second massive
+ * action on a sibling field, checking whether the dropdown value survives.
+ */
+final class MassiveActionRealFlowTest extends DbTestCase
+{
+ use FieldTestTrait;
+
+ public function setUp(): void
+ {
+ GLPITestCase::setUp();
+ $this->login();
+ }
+
+ public function tearDown(): void
+ {
+ unset($_REQUEST['massiveaction'], $_POST);
+ $this->tearDownFieldTest();
+ GLPITestCase::tearDown();
+ }
+
+ private function buildMassiveAction(array $post, string $itemtype, int $id): MassiveAction
+ {
+ $ref = new ReflectionClass(MassiveAction::class);
+ $ma = $ref->newInstanceWithoutConstructor();
+ $ma->POST = $post;
+
+ $set = static function (string $name, $value) use ($ref, $ma): void {
+ $prop = $ref->getProperty($name);
+ $prop->setValue($ma, $value);
+ };
+
+ $set('action', 'update');
+ $set('done', []);
+ $set('nb_done', 0);
+ $set('current_itemtype', null);
+ $set('remainings', [$itemtype => [$id => $id]]);
+ $set('results', ['ok' => 0, 'noaction' => 0, 'ko' => 0, 'noright' => 0, 'messages' => []]);
+ $set('start_time', microtime(true));
+
+ return $ma;
+ }
+
+ public function testMassiveUpdateOfOtherFieldDoesNotWipeMultipleDropdown(): void
+ {
+ $definition = $this->initAssetDefinition('so' . substr((string) $this->getUniqueString(), 0, 6));
+ $asset_class = $definition->getAssetClassName();
+
+ $container = $this->createFieldContainer([
+ 'label' => 'F',
+ 'type' => 'tab',
+ 'itemtypes' => [$asset_class],
+ 'is_active' => 1,
+ 'entities_id' => 0,
+ 'is_recursive' => 1,
+ ]);
+
+ $dropdown_field = $this->createItem(PluginFieldsField::class, [
+ 'label' => 'Transmission',
+ 'type' => 'dropdown',
+ 'multiple' => 1,
+ 'default_value' => [],
+ PluginFieldsContainer::getForeignKeyField() => $container->getID(),
+ 'ranking' => 1,
+ 'is_active' => 1,
+ 'is_readonly' => 0,
+ ], ['allowed_values', 'question_types', 'default_value']);
+ $dropdown_field_name = $dropdown_field->fields['name'];
+ $multiple_key = 'plugin_fields_' . $dropdown_field_name . 'dropdowns_id';
+
+ $text_field = $this->createField([
+ 'label' => 'Other Field',
+ 'type' => 'text',
+ PluginFieldsContainer::getForeignKeyField() => $container->getID(),
+ 'ranking' => 2,
+ 'is_active' => 1,
+ 'is_readonly' => 0,
+ ]);
+ $text_field_name = $text_field->fields['name'];
+
+ $dropdown_class = PluginFieldsDropdown::getClassname($dropdown_field_name);
+ $value1 = $this->createItem($dropdown_class, ['name' => 'Manual', 'entities_id' => 0]);
+ $value2 = $this->createItem($dropdown_class, ['name' => 'Automatic', 'entities_id' => 0]);
+
+ $entities_id = reset($_SESSION['glpiactiveentities']);
+ $asset = $this->createItem($asset_class, ['name' => 'Test asset', 'entities_id' => $entities_id]);
+
+ $so = SearchOption::getOptionsForItemtype($asset_class);
+ $dropdown_so_index = null;
+ $text_so_index = null;
+ foreach ($so as $idx => $opt) {
+ if (($opt['linkfield'] ?? null) === $multiple_key || ($opt['field'] ?? null) === $multiple_key) {
+ $dropdown_so_index = $idx;
+ }
+
+ if (($opt['linkfield'] ?? null) === $text_field_name) {
+ $text_so_index = $idx;
+ }
+ }
+
+ $this->assertNotNull($dropdown_so_index, 'search option index not found for dropdown field');
+ $this->assertNotNull($text_so_index, 'search option index not found for text field');
+
+ $_REQUEST['massiveaction'] = 1;
+
+ // --- First massive action: update the multiple dropdown field ---
+ $_POST = [
+ 'common_options' => [$asset_class . ':' . $dropdown_so_index => $asset_class . ':' . $dropdown_so_index],
+ 'search_options' => [$asset_class => $dropdown_so_index],
+ 'id_field' => $asset_class . ':' . $dropdown_so_index,
+ 'field' => $multiple_key,
+ $multiple_key => [$value1->getID(), $value2->getID()],
+ ];
+ $ma1 = $this->buildMassiveAction($_POST, $asset_class, $asset->getID());
+ MassiveAction::processMassiveActionsForOneItemtype($ma1, $asset, [$asset->getID()]);
+
+ $container_classname = PluginFieldsContainer::getClassname($asset_class, $container->fields['name']);
+ $container_item = new $container_classname();
+ $this->assertTrue($container_item->getFromDBByCrit(['items_id' => $asset->getID()]));
+ $this->assertSame(
+ [$value1->getID(), $value2->getID()],
+ json_decode($container_item->fields[$multiple_key], true),
+ 'dropdown value not correctly stored after first massive action',
+ );
+
+ // --- Second massive action: update the sibling text field only ---
+ $_POST = [
+ 'common_options' => [$asset_class . ':' . $text_so_index => $asset_class . ':' . $text_so_index],
+ 'search_options' => [$asset_class => $text_so_index],
+ 'id_field' => $asset_class . ':' . $text_so_index,
+ 'field' => $text_field_name,
+ $text_field_name => 'hello',
+ ];
+ $ma2 = $this->buildMassiveAction($_POST, $asset_class, $asset->getID());
+ MassiveAction::processMassiveActionsForOneItemtype($ma2, $asset, [$asset->getID()]);
+
+ $container_item2 = new $container_classname();
+ $this->assertTrue($container_item2->getFromDBByCrit(['items_id' => $asset->getID()]));
+ $this->assertSame(
+ [$value1->getID(), $value2->getID()],
+ json_decode($container_item2->fields[$multiple_key], true),
+ 'Massive-updating the sibling text field must not wipe the previously set multiple dropdown value',
+ );
+ }
+}
| |