Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ 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/).

## [UNRELEASED]

### Fixed

- Fix massive action update on CustomAssets

## [1.24.4] - 2026-08-06

### Fixed
Expand Down
12 changes: 5 additions & 7 deletions hook.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ function plugin_fields_install()

$migration = new Migration($version);
if (isCommandLine()) {
echo __('MySQL tables installation', 'fields') . "\n";
echo __s('MySQL tables installation', 'fields') . "\n";
} else {
echo '<center>';
echo "<table class='tab_cadre_fixe'>";
echo '<tr><th>' . __('MySQL tables installation', 'fields') . '<th></tr>';
echo '<tr><th>' . __s('MySQL tables installation', 'fields') . '<th></tr>';

echo "<tr class='tab_bg_1'>";
echo "<td align='center'>";
Expand Down Expand Up @@ -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,
Expand All @@ -124,7 +124,7 @@ function plugin_fields_uninstall()

echo '<center>';
echo "<table class='tab_cadre_fixe'>";
echo '<tr><th>' . __('MySQL tables uninstallation', 'fields') . '<th></tr>';
echo '<tr><th>' . __s('MySQL tables uninstallation', 'fields') . '<th></tr>';

echo "<tr class='tab_bg_1'>";
echo "<td align='center'>";
Expand Down Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion inc/container.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -2097,12 +2097,22 @@ private static function populateData($c_id, CommonDBTM $item)

//managed multi GLPI item dropdown field
if (preg_match('/^dropdown-(?<type>.+)$/', (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'])) { // called from massiveaction
if (isset($_POST[$field['name']])) {
$data[$field['name']] = $_POST[$field['name']];
$has_fields = true;
}
Comment thread
RomainLvr marked this conversation as resolved.
Outdated
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion inc/field.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 CustomAsset, as they are alrady escaped
if (!str_contains($search_string, 'CustomAsset')) {
Comment thread
RomainLvr marked this conversation as resolved.
Outdated
$search_string = str_replace('\\', '\\\\', $search_string);
}

//find field
$iterator = $DB->request([
'SELECT' => [
Expand All @@ -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) . '%'],
],
]);

Expand Down
10 changes: 10 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<psalm
runTaintAnalysis="true"
>
<projectFiles>
<directory name="src"/>
<file name="hook.php"/>
<file name="setup.php"/>
Comment thread
RomainLvr marked this conversation as resolved.
Outdated
</projectFiles>
</psalm>
107 changes: 107 additions & 0 deletions tests/Units/MassiveActionCustomAssetTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

/**
* -------------------------------------------------------------------------
* Fields plugin for GLPI
* -------------------------------------------------------------------------
*
* LICENSE
*
* This file is part of Fields.
*
* Fields 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 2 of the License, or
* (at your option) any later version.
*
* Fields 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 Fields. If not, see <http://www.gnu.org/licenses/>.
* -------------------------------------------------------------------------
* @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);
}
}
Loading
Loading