From 7ceb4554e5598321a01b1ae9c3b72b874d964e99 Mon Sep 17 00:00:00 2001
From: Stanislas Kita <7335054+stonebuzz@users.noreply.github.com>
Date: Fri, 2 Oct 2026 15:06:09 +0200
Subject: [PATCH 1/2] Fix: apply missing checks on block and field handling
---
CHANGELOG.md | 14 +
composer.json | 4 +-
front/container.form.php | 10 +-
front/field.form.php | 6 +-
hook.php | 82 -----
inc/container.class.php | 204 ++++++++++-
inc/field.class.php | 44 ++-
inc/inventory.class.php | 128 -------
setup.php | 16 +-
tests/FieldTestCase.php | 2 +-
.../Units/ContainerGlpiItemReferenceTest.php | 177 +++++++++
tests/Units/ContainerItemtypesTest.php | 137 +++++++
tests/Units/ContainerReadonlyValuesTest.php | 336 ++++++++++++++++++
tests/Units/ExportBlockAsYamlTest.php | 128 +++++++
tests/bootstrap.php | 4 +
15 files changed, 1029 insertions(+), 263 deletions(-)
delete mode 100644 inc/inventory.class.php
create mode 100644 tests/Units/ContainerGlpiItemReferenceTest.php
create mode 100644 tests/Units/ContainerItemtypesTest.php
create mode 100644 tests/Units/ContainerReadonlyValuesTest.php
create mode 100644 tests/Units/ExportBlockAsYamlTest.php
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 99722e8c..67c6720f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +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/).
+## [Unreleased]
+
+### Fixed
+
+- Fix blocks export, block deletion and read-only fields not applying expected checks.
+- Fix read-only status overrides being resolved with the previous status instead of the submitted one.
+- Fix read-only fields of "Insertion in form" blocks being overwritable from the item form.
+- Fix block's associated item types and type being changeable after creation.
+- Remove obsolete FusionInventory integration.
+
+### Changed
+
+- "GLPI item" field now rejects a new reference to an item the current user cannot read, instead of silently clearing it. Automated writes (CLI, cron, inventory) are not affected.
+
## [1.21.30] 2026-09-11
- Fix additional fields being saved on an item the user is not allowed to update.
diff --git a/composer.json b/composer.json
index 483e9407..ad1e9d65 100644
--- a/composer.json
+++ b/composer.json
@@ -1,12 +1,12 @@
{
"require": {
- "php": ">=7.4",
- "symfony/yaml": "^5.4"
+ "php": ">=7.4"
},
"require-dev": {
"glpi-project/tools": "^0.8"
},
"config": {
+ "autoloader-suffix": "PluginFields",
"optimize-autoloader": true,
"platform": {
"php": "7.4.0"
diff --git a/front/container.form.php b/front/container.form.php
index 92a8e314..f4c04992 100644
--- a/front/container.form.php
+++ b/front/container.form.php
@@ -45,12 +45,14 @@
$container->check($_POST['id'], DELETE);
$ok = $container->delete($_POST);
Html::redirect(PLUGINFIELDS_WEB_DIR . '/front/container.php');
-} elseif (isset($_REQUEST['purge'])) {
- $container->check($_REQUEST['id'], PURGE);
- $container->delete($_REQUEST, true);
+} elseif (isset($_POST['purge'])) {
+ $container->check($_POST['id'], PURGE);
+ $container->delete($_POST, true);
Html::redirect(PLUGINFIELDS_WEB_DIR . '/front/container.php');
} elseif (isset($_POST['update'])) {
$container->check($_POST['id'], UPDATE);
+ // structural fields drive generated classes and tables; only migrations may change them
+ unset($_POST['itemtypes'], $_POST['type'], $_POST['subtype']);
$container->update($_POST);
Html::back();
} elseif (isset($_POST['update_fields_values'])) {
@@ -66,7 +68,7 @@
Html::displayRightError();
}
- $container->updateFieldsValues($_REQUEST, $_REQUEST['itemtype'], false);
+ $container->updateFieldsValues(PluginFieldsContainer::removeReadonlyValues($_REQUEST, $item), $_REQUEST['itemtype'], false);
}
Html::back();
} else {
diff --git a/front/field.form.php b/front/field.form.php
index da7323ea..54d7b3a5 100644
--- a/front/field.form.php
+++ b/front/field.form.php
@@ -46,9 +46,9 @@
$field->check($_POST['id'], DELETE);
$field->delete($_POST);
Html::back();
-} elseif (isset($_REQUEST['purge'])) {
- $field->check($_REQUEST['id'], PURGE);
- $field->delete($_REQUEST, true);
+} elseif (isset($_POST['purge'])) {
+ $field->check($_POST['id'], PURGE);
+ $field->delete($_POST, true);
$field->redirectToList();
} elseif (isset($_POST['update'])) {
$field->check($_POST['id'], UPDATE);
diff --git a/hook.php b/hook.php
index 7f997ab4..28750fb3 100644
--- a/hook.php
+++ b/hook.php
@@ -231,88 +231,6 @@ function plugin_fields_MassiveActionsFieldsDisplay($options = [])
return false;
}
-
-/**** RULES ENGINE ****/
-
-/**
- *
- * Actions for rules
- * @since 0.84
- * @param array $params input data
- * @return array an array of actions
- */
-function plugin_fields_getRuleActions($params = [])
-{
- $actions = [];
-
- switch ($params['rule_itemtype']) {
- case 'PluginFusioninventoryTaskpostactionRule':
- $options = PluginFieldsContainer::getAddSearchOptions('Computer');
- foreach ($options as $option) {
- $actions[$option['linkfield']]['name'] = $option['name'];
- $actions[$option['linkfield']]['type'] = $option['pfields_type'];
- if ($option['pfields_type'] == 'dropdown') {
- $actions[$option['linkfield']]['table'] = $option['table'];
- }
- }
-
- break;
- }
-
- return $actions;
-}
-
-
-function plugin_fields_rule_matched($params = [])
-{
- /** @var DBmysql $DB */
- global $DB;
-
- $container = new PluginFieldsContainer();
-
- if (class_exists('PluginFusioninventoryAgent') && $params['sub_type'] == 'PluginFusioninventoryTaskpostactionRule') {
- $agent = new PluginFusioninventoryAgent();
-
- if (isset($params['input']['plugin_fusioninventory_agents_id'])) {
- foreach ($params['output'] as $field => $value) {
- // check if current field is in a tab container
- $iterator = $DB->request([
- 'SELECT' => 'glpi_plugin_fields_containers.id',
- 'FROM' => 'glpi_plugin_fields_containers',
- 'LEFT JOIN' => [
- 'glpi_plugin_fields_fields' => [
- 'FKEY' => [
- 'glpi_plugin_fields_containers' => 'id',
- 'glpi_plugin_fields_fields' => 'plugin_fields_containers_id',
- ],
- ],
- ],
- 'WHERE' => [
- 'glpi_plugin_fields_fields.name' => $field,
- ],
- ]);
- if (count($iterator) > 0) {
- $data = $iterator->current();
-
- //retrieve computer
- $agents_id = $params['input']['plugin_fusioninventory_agents_id'];
- $agent->getFromDB($agents_id);
-
- // update current field
- $container->updateFieldsValues(
- [
- 'plugin_fields_containers_id' => $data['id'],
- $field => $value,
- 'items_id' => $agent->fields['computers_id'],
- ],
- Computer::getType(),
- );
- }
- }
- }
- }
-}
-
function plugin_fields_giveItem($itemtype, $ID, $data, $num)
{
$searchopt = &Search::getOptions($itemtype);
diff --git a/inc/container.class.php b/inc/container.class.php
index b7540a86..8bdff01d 100644
--- a/inc/container.class.php
+++ b/inc/container.class.php
@@ -174,12 +174,12 @@ public static function installUserData(Migration $migration, $version)
foreach ($itemtypes as $itemtype) {
$sysname = self::getSystemName($itemtype, $container['name']);
- $class_filename = $sysname . '.class.php';
+ $class_filename = basename($sysname) . '.class.php';
if (file_exists(PLUGINFIELDS_DIR . "/inc/$class_filename")) {
unlink(PLUGINFIELDS_DIR . "/inc/$class_filename");
}
- $injclass_filename = $sysname . 'injection.class.php';
+ $injclass_filename = basename($sysname) . 'injection.class.php';
if (file_exists(PLUGINFIELDS_DIR . "/inc/$injclass_filename")) {
unlink(PLUGINFIELDS_DIR . "/inc/$injclass_filename");
}
@@ -538,6 +538,35 @@ public function defineTabs($options = [])
return $ong;
}
+ public function prepareInputForUpdate($input)
+ {
+ if (isset($input['itemtypes'])) {
+ $itemtypes = is_array($input['itemtypes'])
+ ? $input['itemtypes']
+ : PluginFieldsToolbox::decodeJSONItemtypes((string) $input['itemtypes']);
+ if (!is_array($itemtypes) || $itemtypes === [] || !$this->areValidItemtypeNames($itemtypes)) {
+ Session::AddMessageAfterRedirect(__('At least one selected object is not a valid element type', 'fields'), false, ERROR);
+
+ return false;
+ }
+
+ $input['itemtypes'] = json_encode(array_values($itemtypes));
+ }
+
+ return $input;
+ }
+
+ private function areValidItemtypeNames(array $itemtypes): bool
+ {
+ foreach ($itemtypes as $itemtype) {
+ if (!is_string($itemtype) || preg_match('/^[A-Za-z_][A-Za-z0-9_\\\\]*$/', $itemtype) !== 1) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
public function prepareInputForAdd($input)
{
if (!isset($input['itemtypes'])) {
@@ -557,6 +586,12 @@ public function prepareInputForAdd($input)
$input['itemtypes'] = [$input['itemtypes']];
}
+ if (!$this->areValidItemtypeNames($input['itemtypes'])) {
+ Session::AddMessageAfterRedirect(__('At least one selected object is not a valid element type', 'fields'), false, ERROR);
+
+ return false;
+ }
+
if ($input['type'] === 'dom') {
//check for already exist dom container with this itemtype
$found = $this->find(['type' => 'dom']);
@@ -741,8 +776,8 @@ public function pre_deleteItem()
foreach (json_decode($this->fields['itemtypes']) as $itemtype) {
$classname = self::getClassname($itemtype, $this->fields['name']);
$sysname = self::getSystemName($itemtype, $this->fields['name']);
- $class_filename = $sysname . '.class.php';
- $injection_filename = $sysname . 'injection.class.php';
+ $class_filename = basename($sysname) . '.class.php';
+ $injection_filename = basename($sysname) . 'injection.class.php';
//delete fields
$field_obj = new PluginFieldsField();
@@ -1526,6 +1561,8 @@ public static function validateValues($data, $itemtype, $massiveaction)
$valid = true;
$empty_errors = [];
$number_errors = [];
+ $url_errors = [];
+ $reference_errors = [];
$container = new self();
$container->getFromDB($data['plugin_fields_containers_id']);
@@ -1555,6 +1592,8 @@ public static function validateValues($data, $itemtype, $massiveaction)
}
}
+ $stored_values = self::getStoredValues($container, $itemtype, (int) ($data['items_id'] ?? 0));
+
foreach ($fields as $field) {
if (!$field['is_active']) {
continue;
@@ -1571,13 +1610,19 @@ public static function validateValues($data, $itemtype, $massiveaction)
$itemtype_key = sprintf('itemtype_%s', $name);
$items_id_key = sprintf('items_id_%s', $name);
- if (
- isset($data[$itemtype_key], $data[$items_id_key])
- && is_a($data[$itemtype_key], CommonDBTM::class, true)
- && $data[$items_id_key] > 0
- ) {
- $glpi_item = new $data[$itemtype_key]();
- $value = $glpi_item->getFromDB($data[$items_id_key]) ? $data[$items_id_key] : null;
+ $is_unchanged_reference = isset($stored_values[$itemtype_key], $stored_values[$items_id_key], $data[$itemtype_key], $data[$items_id_key])
+ && $stored_values[$itemtype_key] === $data[$itemtype_key]
+ && (int) $stored_values[$items_id_key] === (int) $data[$items_id_key];
+ if (!$is_unchanged_reference && isset($data[$items_id_key]) && (int) $data[$items_id_key] > 0) {
+ $value = self::isValidItemReference($field, $data[$itemtype_key] ?? null, (int) $data[$items_id_key])
+ ? (int) $data[$items_id_key]
+ : null;
+ if ($value === null) {
+ $field['itemtype'] = PluginFieldsField::getType();
+ $reference_errors[] = PluginFieldsLabelTranslation::getLabelFor($field);
+ $valid = false;
+ continue;
+ }
}
} elseif (isset($data[$name])) {
$value = $data[$name];
@@ -1631,9 +1676,135 @@ public static function validateValues($data, $itemtype, $massiveaction)
' : ' . implode(', ', $url_errors), false, ERROR);
}
+ if ($reference_errors !== []) {
+ Session::AddMessageAfterRedirect(__('Some item fields reference an invalid item', 'fields')
+ . ' : ' . implode(', ', $reference_errors), false, ERROR);
+ }
+
return $valid;
}
+ /**
+ * Status of the item for this request: the submitted one when present, otherwise the persisted one.
+ */
+ private static function getStatusValue(CommonDBTM $item): ?int
+ {
+ $status_field_name = PluginFieldsStatusOverride::getStatusFieldName($item->getType());
+ foreach ([$item->input, $item->fields] as $source) {
+ if (array_key_exists($status_field_name, $source) && $source[$status_field_name] !== '') {
+ return (int) $source[$status_field_name];
+ }
+ }
+
+ return null;
+ }
+
+ public static function removeReadonlyValues(array $data, CommonDBTM $item): array
+ {
+ $container_id = (int) $data['plugin_fields_containers_id'];
+ $fields = (new PluginFieldsField())->find([
+ 'plugin_fields_containers_id' => $container_id,
+ 'is_active' => 1,
+ ]);
+
+ $status_value = self::getStatusValue($item);
+ $status_overrides = $status_value !== null
+ ? PluginFieldsStatusOverride::getOverridesForItemtypeAndStatus($container_id, $item->getType(), $status_value)
+ : [];
+ foreach ($status_overrides as $status_override) {
+ if (isset($fields[$status_override['plugin_fields_fields_id']])) {
+ $fields[$status_override['plugin_fields_fields_id']]['is_readonly'] = $status_override['is_readonly'];
+ }
+ }
+
+ $container = new self();
+ $container->getFromDB($container_id);
+
+ $stored_values = self::getStoredValues($container, $item->getType(), (int) $item->getID());
+
+ foreach ($fields as $field) {
+ if (!$field['is_readonly']) {
+ continue;
+ }
+
+ $dropdown_key = sprintf('plugin_fields_%sdropdowns_id', $field['name']);
+ unset($data[sprintf('_%s_defined', $field['name'])], $data[sprintf('_%s_defined', $dropdown_key)]);
+ foreach ([
+ $field['name'],
+ $dropdown_key,
+ sprintf('itemtype_%s', $field['name']),
+ sprintf('items_id_%s', $field['name']),
+ ] as $input_key) {
+ unset($data[$input_key]);
+ if (array_key_exists($input_key, $stored_values)) {
+ $data[$input_key] = $field['multiple']
+ ? json_decode((string) $stored_values[$input_key], true)
+ : $stored_values[$input_key];
+ }
+ }
+
+ if ($item->isNewItem()) {
+ $data += self::getDefaultInput($field);
+ }
+ }
+
+ return $data;
+ }
+
+ private static function getDefaultInput(array $field): array
+ {
+ $default = PluginFieldsField::getDefaultValue($field);
+ if ($default === null) {
+ return [];
+ }
+
+ $input_key = $field['type'] === 'dropdown'
+ ? sprintf('plugin_fields_%sdropdowns_id', $field['name'])
+ : $field['name'];
+
+ if (!$field['multiple']) {
+ return [$input_key => $default];
+ }
+
+ $decoded = json_decode((string) $default, true);
+
+ return is_array($decoded) && $decoded !== [] ? [$input_key => $decoded] : [];
+ }
+
+ private static function getStoredValues(self $container, string $itemtype, int $items_id): array
+ {
+ if ($items_id <= 0 || $container->isNewItem()) {
+ return [];
+ }
+
+ $values_obj = (new DbUtils())->getItemForItemtype(self::getClassname($itemtype, $container->fields['name']));
+ if ($values_obj === false || !$values_obj->getFromDBByCrit(['items_id' => $items_id])) {
+ return [];
+ }
+
+ return $values_obj->fields;
+ }
+
+ private static function isValidItemReference(array $field, $itemtype, int $items_id): bool
+ {
+ $allowed_itemtypes = json_decode((string) $field['allowed_values'], true);
+ if (
+ !is_string($itemtype)
+ || !is_a($itemtype, CommonDBTM::class, true)
+ || !is_array($allowed_itemtypes)
+ || !in_array($itemtype, $allowed_itemtypes, true)
+ ) {
+ return false;
+ }
+
+ $item = new $itemtype();
+
+ // Automated writes (CLI, cron, inventory) run without a user whose rights could be checked
+ return Session::getLoginUserID() === false || Session::isInventory()
+ ? $item->getFromDB($items_id)
+ : $item->can($items_id, READ);
+ }
+
public static function findContainer($itemtype, $type = 'tab', $subtype = '')
{
$condition = [
@@ -1800,6 +1971,9 @@ public static function preItem(CommonDBTM $item)
}
if (false !== ($data = self::populateData($c_id, $item))) {
+ // read-only fields keep their stored value, or their default on creation
+ $data = self::removeReadonlyValues($data, $item);
+
if (self::validateValues($data, $item::getType(), isset($_REQUEST['massiveaction'])) === false) {
$item->input = [];
@@ -1842,13 +2016,7 @@ private static function populateData($c_id, CommonDBTM $item)
}
// Add status so it can be used with status overrides
- $status_field_name = PluginFieldsStatusOverride::getStatusFieldName($item->getType());
- $data[$status_field_name] = null;
- if (array_key_exists($status_field_name, $item->input) && $item->input[$status_field_name] !== '') {
- $data[$status_field_name] = (int) $item->input[$status_field_name];
- } elseif (array_key_exists($status_field_name, $item->fields) && $item->fields[$status_field_name] !== '') {
- $data[$status_field_name] = (int) $item->fields[$status_field_name];
- }
+ $data[PluginFieldsStatusOverride::getStatusFieldName($item->getType())] = self::getStatusValue($item);
$has_fields = false;
foreach ($fields as $field) {
diff --git a/inc/field.class.php b/inc/field.class.php
index 659ae463..beca1b7b 100644
--- a/inc/field.class.php
+++ b/inc/field.class.php
@@ -998,6 +998,9 @@ public static function showForTab($params)
//JS to trigger any change and check if container need to be display or not
$ajax_url = Plugin::getWebDir('fields') . '/ajax/container.php';
$items_id = !$item->isNewItem() ? $item->getID() : 0;
+ $js_itemtype = json_encode($item::getType(), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
+ $js_type = json_encode($type, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
+ $js_subtype = json_encode($subtype, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
echo Html::scriptBlock(
<<.
- * -------------------------------------------------------------------------
- * @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
- * -------------------------------------------------------------------------
- */
-
-class PluginFieldsInventory extends CommonDBTM
-{
- public static function updateInventory($params = [])
- {
- if (
- !empty($params)
- && isset($params['inventory_data']) && !empty($params['inventory_data'])
- ) {
- $availaibleItemType = ['Computer', 'Printer', 'NetworkEquipment'];
- foreach (array_keys($params['inventory_data']) as $itemtype) {
- if (in_array($itemtype, $availaibleItemType)) {
- $items_id = 0;
- //retrieve items id switch itemtype
- switch ($itemtype) {
- case Computer::getType():
- $items_id = $params['computers_id'];
- break;
-
- case NetworkEquipment::getType():
- $items_id = $params['networkequipments_id'];
- break;
-
- case Printer::getType():
- $items_id = $params['printers_id'];
- break;
- }
-
- if (class_exists('PluginFusioninventoryInventoryComputerComputer')) {
- if ($itemtype == Computer::getType()) {
- //load inventory from DB because
- //FI not update XML file if computer is not update
- $db_info = new PluginFusioninventoryInventoryComputerComputer();
- if ($db_info->getFromDBByCrit(['computers_id' => $items_id])) {
- $arrayinventory = unserialize(gzuncompress($db_info->fields['serialized_inventory']));
- if (isset($arrayinventory['custom'])) {
- self::updateFields($arrayinventory['custom']['container'], $itemtype, $items_id);
- }
- }
- } else {
- //Load XML file because FI always update XML file and don't store inventory into DB
- $file = self::loadXMLFile($itemtype, $items_id);
- if (
- $file !== false
- && class_exists('PluginFusioninventoryFormatconvert')
- ) {
- $arrayinventory = PluginFusioninventoryFormatconvert::XMLtoArray($file);
- if (isset($arrayinventory['CUSTOM'])) {
- self::updateFields($arrayinventory['CUSTOM']['CONTAINER'], $itemtype, $items_id);
- }
- }
- }
- }
- }
- }
- }
- }
-
- public static function updateFields($containersData, $itemtype, $items_id)
- {
- if (isset($containersData['ID'])) {
- // $containersData contains only one element, encapsulate it into an array
- $containersData = [$containersData];
- }
- foreach ($containersData as $key => $containerData) {
- $container = new PluginFieldsContainer();
- $container->getFromDB($containerData['ID']);
- $data = [];
- $data['items_id'] = $items_id;
- $data['itemtype'] = $itemtype;
- $data['plugin_fields_containers_id'] = $containerData['ID'];
- foreach ($containerData['FIELDS'] as $key => $value) {
- $data[strtolower($key)] = $value;
- }
- $container->updateFieldsValues($data, $itemtype, false);
- }
- }
-
- public static function loadXMLFile($itemtype, $items_id)
- {
- $pxml = false;
- $folder = substr($items_id, 0, -1);
- if (empty($folder)) {
- $folder = '0';
- }
-
- //Check if the file exists with the .xml extension (new format)
- /** @phpstan-ignore-next-line */
- $file = PLUGIN_FUSIONINVENTORY_XML_DIR . strtolower($itemtype) . '/' . $folder . '/' . $items_id;
- if (file_exists($file . '.xml')) {
- $file .= '.xml';
- } elseif (!file_exists($file)) {
- return false;
- }
- $pxml = simplexml_load_file($file, 'SimpleXMLElement', LIBXML_NOCDATA);
-
- return $pxml;
- }
-}
diff --git a/setup.php b/setup.php
index 4f5a8ea9..887cca5e 100644
--- a/setup.php
+++ b/setup.php
@@ -107,15 +107,6 @@ function plugin_init_fields()
}
}
- if (Plugin::isPluginActive('fusioninventory')) {
- $PLUGIN_HOOKS['fusioninventory_inventory']['fields']
- = ['PluginFieldsInventory', 'updateInventory'];
- }
-
- // complete rule engine
- $PLUGIN_HOOKS['use_rules']['fields'] = ['PluginFusioninventoryTaskpostactionRule'];
- $PLUGIN_HOOKS['rule_matched']['fields'] = 'plugin_fields_rule_matched';
-
if (isset($_SESSION['glpiactiveentities'])) {
// add link in plugin page
$PLUGIN_HOOKS['config_page']['fields'] = 'front/container.php';
@@ -312,6 +303,13 @@ function plugin_fields_exportBlockAsYaml($container_id = null)
$containers = $container_obj->find($where);
foreach ($containers as $container) {
+ if (
+ !Session::haveAccessToEntity((int) $container['entities_id'], (bool) $container['is_recursive'])
+ || PluginFieldsProfile::getRightOnContainer((int) ($_SESSION['glpiactiveprofile']['id'] ?? 0), (int) $container['id']) < READ
+ ) {
+ continue;
+ }
+
$itemtypes = (strlen($container['itemtypes']) > 0)
? json_decode($container['itemtypes'], true)
: [];
diff --git a/tests/FieldTestCase.php b/tests/FieldTestCase.php
index af81756c..ac248dee 100644
--- a/tests/FieldTestCase.php
+++ b/tests/FieldTestCase.php
@@ -87,7 +87,7 @@ public function createField(array $inputs): PluginFieldsField
// Re-login to ensure we are logged in
$this->login();
- $field = $this->createItem(PluginFieldsField::class, $inputs, ['allowed_values']);
+ $field = $this->createItem(PluginFieldsField::class, $inputs, ['allowed_values', 'default_value']);
self::$createdFields[] = $field;
// Re-initialize fields plugin to register new field logic
diff --git a/tests/Units/ContainerGlpiItemReferenceTest.php b/tests/Units/ContainerGlpiItemReferenceTest.php
new file mode 100644
index 00000000..8b451ba8
--- /dev/null
+++ b/tests/Units/ContainerGlpiItemReferenceTest.php
@@ -0,0 +1,177 @@
+.
+ * -------------------------------------------------------------------------
+ * @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 Computer;
+use Entity;
+use DbTestCase;
+use GLPITestCase;
+use GlpiPlugin\Field\Tests\FieldTestTrait;
+use Location;
+use PluginFieldsContainer;
+
+require_once __DIR__ . '/../FieldTestCase.php';
+
+final class ContainerGlpiItemReferenceTest extends DbTestCase
+{
+ use FieldTestTrait;
+
+ private PluginFieldsContainer $container;
+
+ private string $field_name;
+
+ private Computer $holder;
+
+ private Computer $root_target;
+
+ private Computer $child_target;
+
+ private int $root_entity_id;
+
+ public function setUp(): void
+ {
+ GLPITestCase::setUp();
+ $this->login();
+
+ $this->root_entity_id = 0;
+ $child_entity_id = $this->createItem(Entity::class, [
+ 'name' => 'Entity ' . $this->getUniqueString(),
+ 'entities_id' => 0,
+ ])->getID();
+ $this->setEntity($this->root_entity_id, true);
+
+ $this->container = $this->createFieldContainer([
+ 'label' => 'Item reference',
+ 'type' => 'tab',
+ 'itemtypes' => [Computer::class],
+ 'is_active' => 1,
+ 'entities_id' => $this->root_entity_id,
+ 'is_recursive' => 1,
+ ]);
+ $field = $this->createField([
+ 'label' => 'Linked computer',
+ 'type' => 'glpi_item',
+ PluginFieldsContainer::getForeignKeyField() => $this->container->getID(),
+ 'ranking' => 1,
+ 'is_active' => 1,
+ 'is_readonly' => 0,
+ 'allowed_values' => [Computer::class],
+ ]);
+ $this->field_name = $field->fields['name'];
+
+ $this->holder = $this->createComputer($this->root_entity_id);
+ $this->root_target = $this->createComputer($this->root_entity_id);
+ $this->child_target = $this->createComputer($child_entity_id);
+ }
+
+ public function tearDown(): void
+ {
+ $this->tearDownFieldTest();
+ GLPITestCase::tearDown();
+ }
+
+ public function testValidReferenceIsAccepted(): void
+ {
+ $this->assertTrue($this->validate(Computer::class, $this->root_target->getID()));
+ }
+
+ public function testUnknownItemIsRejected(): void
+ {
+ $this->assertFalse($this->validate(Computer::class, 999999));
+ $this->assertReferenceError();
+ }
+
+ public function testItemtypeOutsideAllowedValuesIsRejected(): void
+ {
+ $location = $this->createItem(Location::class, [
+ 'name' => 'Location ' . $this->getUniqueString(),
+ 'entities_id' => $this->root_entity_id,
+ ]);
+
+ $this->assertFalse($this->validate(Location::class, $location->getID()));
+ $this->assertReferenceError();
+ }
+
+ public function testReferenceToItemWithoutReadRightIsRejected(): void
+ {
+ $this->setEntity($this->root_entity_id, false);
+
+ $this->assertFalse($this->validate(Computer::class, $this->child_target->getID()));
+ $this->assertReferenceError();
+ }
+
+ public function testUnchangedReferenceStaysAcceptedWithoutReadRight(): void
+ {
+ $this->assertTrue($this->container->updateFieldsValues(
+ $this->buildData(Computer::class, $this->child_target->getID()),
+ Computer::class,
+ false,
+ ));
+
+ $this->setEntity($this->root_entity_id, false);
+
+ $this->assertTrue($this->validate(Computer::class, $this->child_target->getID()));
+ }
+
+ private function validate(string $itemtype, int $items_id): bool
+ {
+ return PluginFieldsContainer::validateValues($this->buildData($itemtype, $items_id), Computer::class, false);
+ }
+
+ private function buildData(string $itemtype, int $items_id): array
+ {
+ return [
+ 'plugin_fields_containers_id' => $this->container->getID(),
+ 'itemtype' => Computer::class,
+ 'items_id' => $this->holder->getID(),
+ 'itemtype_' . $this->field_name => $itemtype,
+ 'items_id_' . $this->field_name => $items_id,
+ ];
+ }
+
+ private function assertReferenceError(): void
+ {
+ $this->hasSessionMessages(ERROR, ['Some item fields reference an invalid item : Linked computer']);
+ }
+
+ private function createComputer(int $entities_id): Computer
+ {
+ $computer = $this->createItem(Computer::class, [
+ 'name' => 'Computer ' . $this->getUniqueString(),
+ 'entities_id' => $entities_id,
+ ]);
+ $this->assertInstanceOf(Computer::class, $computer);
+
+ return $computer;
+ }
+}
diff --git a/tests/Units/ContainerItemtypesTest.php b/tests/Units/ContainerItemtypesTest.php
new file mode 100644
index 00000000..d9c44a50
--- /dev/null
+++ b/tests/Units/ContainerItemtypesTest.php
@@ -0,0 +1,137 @@
+.
+ * -------------------------------------------------------------------------
+ * @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 Computer;
+use DbTestCase;
+use GLPITestCase;
+use GlpiPlugin\Field\Tests\FieldTestTrait;
+use Monitor;
+use PluginFieldsContainer;
+use PluginFieldsToolbox;
+
+require_once __DIR__ . '/../FieldTestCase.php';
+
+final class ContainerItemtypesTest extends DbTestCase
+{
+ use FieldTestTrait;
+
+ public function setUp(): void
+ {
+ GLPITestCase::setUp();
+ $this->login();
+ }
+
+ public function tearDown(): void
+ {
+ $this->tearDownFieldTest();
+ GLPITestCase::tearDown();
+ }
+
+ public static function provideMalformedItemtypes(): iterable
+ {
+ yield 'path separator' => ['itemtype' => '../Computer'];
+ yield 'leading digit' => ['itemtype' => '1Computer'];
+ yield 'whitespace' => ['itemtype' => 'Computer Model'];
+ }
+
+ /**
+ * @dataProvider provideMalformedItemtypes
+ */
+ public function testAddWithMalformedItemtypeIsRejected(string $itemtype): void
+ {
+ $container = new PluginFieldsContainer();
+ $result = $container->add([
+ 'label' => 'Malformed itemtype',
+ 'type' => 'tab',
+ 'itemtypes' => [$itemtype],
+ 'is_active' => 1,
+ 'entities_id' => 0,
+ 'is_recursive' => 1,
+ ]);
+
+ $this->assertFalse($result);
+ $this->hasSessionMessages(ERROR, ['At least one selected object is not a valid element type']);
+ }
+
+ /**
+ * @dataProvider provideMalformedItemtypes
+ */
+ public function testUpdateWithMalformedItemtypeIsRejected(string $itemtype): void
+ {
+ $container = $this->createFieldContainer([
+ 'label' => 'Upd malformed',
+ 'type' => 'tab',
+ 'itemtypes' => [Computer::class],
+ 'is_active' => 1,
+ 'entities_id' => 0,
+ 'is_recursive' => 1,
+ ]);
+ $original_itemtypes = $container->fields['itemtypes'];
+
+ $result = $container->update([
+ 'id' => $container->getID(),
+ 'itemtypes' => json_encode([$itemtype]),
+ ]);
+
+ $this->assertFalse($result);
+ $this->hasSessionMessages(ERROR, ['At least one selected object is not a valid element type']);
+ $container->getFromDB($container->getID());
+ $this->assertSame($original_itemtypes, $container->fields['itemtypes']);
+ }
+
+ public function testUpdateWithValidItemtypesReencodesThem(): void
+ {
+ $container = $this->createFieldContainer([
+ 'label' => 'Upd valid',
+ 'type' => 'tab',
+ 'itemtypes' => [Computer::class],
+ 'is_active' => 1,
+ 'entities_id' => 0,
+ 'is_recursive' => 1,
+ ]);
+
+ $result = $container->update([
+ 'id' => $container->getID(),
+ 'itemtypes' => json_encode([Computer::class, Monitor::class]),
+ ]);
+
+ $this->assertTrue($result);
+ $container->getFromDB($container->getID());
+ $this->assertSame(
+ [Computer::class, Monitor::class],
+ PluginFieldsToolbox::decodeJSONItemtypes($container->fields['itemtypes']),
+ );
+ }
+}
diff --git a/tests/Units/ContainerReadonlyValuesTest.php b/tests/Units/ContainerReadonlyValuesTest.php
new file mode 100644
index 00000000..94491f1d
--- /dev/null
+++ b/tests/Units/ContainerReadonlyValuesTest.php
@@ -0,0 +1,336 @@
+.
+ * -------------------------------------------------------------------------
+ * @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 Computer;
+use Entity;
+use DbTestCase;
+use GLPITestCase;
+use GlpiPlugin\Field\Tests\FieldTestTrait;
+use PluginFieldsContainer;
+use PluginFieldsDropdown;
+use PluginFieldsField;
+use PluginFieldsStatusOverride;
+use State;
+
+require_once __DIR__ . '/../FieldTestCase.php';
+
+final class ContainerReadonlyValuesTest extends DbTestCase
+{
+ use FieldTestTrait;
+
+ private int $entity_id;
+
+ public function setUp(): void
+ {
+ GLPITestCase::setUp();
+ $this->login();
+ $this->entity_id = 0;
+ $this->setEntity($this->entity_id, true);
+ }
+
+ public function tearDown(): void
+ {
+ $this->tearDownFieldTest();
+ GLPITestCase::tearDown();
+ }
+
+ public function testReadonlyValueIsReplacedByStoredValue(): void
+ {
+ $container = $this->createContainer('tab');
+ $readonly_name = $this->createTextField($container, 'Locked', 1);
+ $editable_name = $this->createTextField($container, 'Editable', 0);
+ $computer = $this->createComputer();
+
+ $this->storeValues($container, $computer, [$readonly_name => 'stored']);
+
+ $data = PluginFieldsContainer::removeReadonlyValues(
+ $this->buildData($container, $computer, [$readonly_name => 'hacked', $editable_name => 'new']),
+ $computer,
+ );
+
+ $this->assertSame('stored', $data[$readonly_name]);
+ $this->assertSame('new', $data[$editable_name]);
+ }
+
+ public function testReadonlyValueWithoutStoredRowIsDropped(): void
+ {
+ $container = $this->createContainer('tab');
+ $readonly_name = $this->createTextField($container, 'Locked', 1);
+ $editable_name = $this->createTextField($container, 'Editable', 0);
+ $computer = $this->createComputer();
+
+ $data = PluginFieldsContainer::removeReadonlyValues(
+ $this->buildData($container, $computer, [
+ $readonly_name => 'hacked',
+ '_' . $readonly_name . '_defined' => 1,
+ $editable_name => 'new',
+ ]),
+ $computer,
+ );
+
+ $this->assertArrayNotHasKey($readonly_name, $data);
+ $this->assertArrayNotHasKey('_' . $readonly_name . '_defined', $data);
+ $this->assertSame('new', $data[$editable_name]);
+ }
+
+ public function testStatusOverrideTogglesReadonly(): void
+ {
+ $container = $this->createContainer('dom');
+ $field_name = $this->createTextField($container, 'Locked when stocked', 0);
+ $state = $this->createItem(State::class, ['name' => 'State ' . $this->getUniqueString(), 'entities_id' => $this->entity_id]);
+ $computer = $this->createComputer();
+
+ $this->createItem(PluginFieldsStatusOverride::class, [
+ PluginFieldsContainer::getForeignKeyField() => $container->getID(),
+ 'plugin_fields_fields_id' => $this->fieldIdByName($field_name),
+ 'itemtype' => Computer::class,
+ 'states' => [$state->getID()],
+ 'is_readonly' => 1,
+ 'mandatory' => 0,
+ ], ['states', PluginFieldsContainer::getForeignKeyField()]);
+ $this->storeValues($container, $computer, [$field_name => 'stored']);
+
+ $data = PluginFieldsContainer::removeReadonlyValues(
+ $this->buildData($container, $computer, [$field_name => 'submitted']),
+ $computer,
+ );
+ $this->assertSame('submitted', $data[$field_name]);
+
+ $this->updateItem(Computer::class, $computer->getID(), ['states_id' => $state->getID()]);
+ $this->assertTrue($computer->getFromDB($computer->getID()));
+
+ $data = PluginFieldsContainer::removeReadonlyValues(
+ $this->buildData($container, $computer, [$field_name => 'submitted']),
+ $computer,
+ );
+ $this->assertSame('stored', $data[$field_name]);
+ }
+
+ public function testStatusOverrideUsesSubmittedStatusOnUpdate(): void
+ {
+ $container = $this->createContainer('dom');
+ $field_name = $this->createTextField($container, 'Locked when stocked', 0);
+ $state = $this->createItem(State::class, ['name' => 'State ' . $this->getUniqueString(), 'entities_id' => $this->entity_id]);
+ $computer = $this->createComputer();
+ $this->createReadonlyOverride($container, $field_name, $state);
+ $this->storeValues($container, $computer, [$field_name => 'stored']);
+
+ $computer->input = ['states_id' => $state->getID()];
+ $data = PluginFieldsContainer::removeReadonlyValues(
+ $this->buildData($container, $computer, [$field_name => 'submitted']),
+ $computer,
+ );
+ $this->assertSame('stored', $data[$field_name]);
+
+ $this->updateItem(Computer::class, $computer->getID(), ['states_id' => $state->getID()]);
+ $this->assertTrue($computer->getFromDB($computer->getID()));
+ $computer->input = ['states_id' => 0];
+
+ $data = PluginFieldsContainer::removeReadonlyValues(
+ $this->buildData($container, $computer, [$field_name => 'submitted']),
+ $computer,
+ );
+ $this->assertSame('submitted', $data[$field_name]);
+ }
+
+ public function testStatusOverrideAppliesOnItemCreation(): void
+ {
+ $container = $this->createContainer('dom');
+ $field_name = $this->createTextField($container, 'Locked when stocked', 0, 'default');
+ $state = $this->createItem(State::class, ['name' => 'State ' . $this->getUniqueString(), 'entities_id' => $this->entity_id]);
+ $this->createReadonlyOverride($container, $field_name, $state);
+
+ $computer = new Computer();
+ $computer->input = ['states_id' => $state->getID()];
+
+ $data = PluginFieldsContainer::removeReadonlyValues(
+ [$field_name => 'hacked', 'plugin_fields_containers_id' => $container->getID(), 'itemtype' => Computer::class, 'items_id' => 0],
+ $computer,
+ );
+ $this->assertSame('default', $data[$field_name]);
+ }
+
+ public function testReadonlyMultipleDropdownIsRestoredAsArray(): void
+ {
+ $container = $this->createContainer('tab');
+ $field = $this->createField([
+ 'label' => 'Tags',
+ 'type' => 'dropdown',
+ 'multiple' => 1,
+ 'default_value' => [],
+ PluginFieldsContainer::getForeignKeyField() => $container->getID(),
+ 'ranking' => 1,
+ 'is_active' => 1,
+ 'is_readonly' => 1,
+ ], ['default_value']);
+ $dropdown_key = sprintf('plugin_fields_%sdropdowns_id', $field->fields['name']);
+ $dropdown_class = PluginFieldsDropdown::getClassname($field->fields['name']);
+ $stored_ids = [
+ $this->createItem($dropdown_class, ['name' => 'A'])->getID(),
+ $this->createItem($dropdown_class, ['name' => 'B'])->getID(),
+ ];
+ $other_id = $this->createItem($dropdown_class, ['name' => 'C'])->getID();
+ $computer = $this->createComputer();
+
+ $this->storeValues($container, $computer, [$dropdown_key => $stored_ids]);
+
+ $data = PluginFieldsContainer::removeReadonlyValues(
+ $this->buildData($container, $computer, [$dropdown_key => [$other_id], '_' . $dropdown_key . '_defined' => 1]),
+ $computer,
+ );
+
+ $this->assertSame($stored_ids, $data[$dropdown_key]);
+ $this->assertArrayNotHasKey('_' . $dropdown_key . '_defined', $data);
+ }
+
+ public function testDomBlockReadonlyFieldCannotBeOverwrittenFromItemForm(): void
+ {
+ $container = $this->createContainer('dom');
+ $readonly_name = $this->createTextField($container, 'Locked', 1);
+ $editable_name = $this->createTextField($container, 'Editable', 0);
+ $computer = $this->createComputer();
+
+ $this->storeValues($container, $computer, [$readonly_name => 'stored', $editable_name => 'old']);
+
+ $this->updateItem(Computer::class, $computer->getID(), [
+ 'name' => 'Renamed',
+ $readonly_name => 'hacked',
+ $editable_name => 'new',
+ ], [$readonly_name, $editable_name]);
+
+ $values = $this->getStoredValues($container, $computer);
+ $this->assertSame('stored', $values[$readonly_name]);
+ $this->assertSame('new', $values[$editable_name]);
+ }
+
+ public function testDomBlockReadonlyFieldTakesItsDefaultOnItemCreation(): void
+ {
+ $container = $this->createContainer('dom');
+ $readonly_name = $this->createTextField($container, 'Locked', 1, 'locked default');
+ $editable_name = $this->createTextField($container, 'Editable', 0);
+
+ $computer = $this->createItem(Computer::class, [
+ 'name' => 'Computer ' . $this->getUniqueString(),
+ 'entities_id' => $this->entity_id,
+ $readonly_name => 'hacked',
+ $editable_name => 'new',
+ ], [$readonly_name, $editable_name]);
+ $this->assertInstanceOf(Computer::class, $computer);
+
+ $values = $this->getStoredValues($container, $computer);
+ $this->assertSame('locked default', $values[$readonly_name]);
+ $this->assertSame('new', $values[$editable_name]);
+ }
+
+ private function createContainer(string $type): PluginFieldsContainer
+ {
+ return $this->createFieldContainer([
+ 'label' => 'Readonly ' . $type,
+ 'type' => $type,
+ 'itemtypes' => [Computer::class],
+ 'is_active' => 1,
+ 'entities_id' => $this->entity_id,
+ 'is_recursive' => 1,
+ ]);
+ }
+
+ private function createTextField(PluginFieldsContainer $container, string $label, int $is_readonly, string $default_value = ''): string
+ {
+ $field = $this->createField([
+ 'label' => $label,
+ 'type' => 'text',
+ PluginFieldsContainer::getForeignKeyField() => $container->getID(),
+ 'ranking' => 1,
+ 'is_active' => 1,
+ 'is_readonly' => $is_readonly,
+ 'default_value' => $default_value,
+ ]);
+
+ return $field->fields['name'];
+ }
+
+ private function createReadonlyOverride(PluginFieldsContainer $container, string $field_name, State $state): void
+ {
+ $this->createItem(PluginFieldsStatusOverride::class, [
+ PluginFieldsContainer::getForeignKeyField() => $container->getID(),
+ 'plugin_fields_fields_id' => $this->fieldIdByName($field_name),
+ 'itemtype' => Computer::class,
+ 'states' => [$state->getID()],
+ 'is_readonly' => 1,
+ 'mandatory' => 0,
+ ], ['states', PluginFieldsContainer::getForeignKeyField()]);
+ }
+
+ private function fieldIdByName(string $name): int
+ {
+ $field = new PluginFieldsField();
+ $this->assertTrue($field->getFromDBByCrit(['name' => $name]));
+
+ return $field->getID();
+ }
+
+ private function createComputer(): Computer
+ {
+ $computer = $this->createItem(Computer::class, [
+ 'name' => 'Computer ' . $this->getUniqueString(),
+ 'entities_id' => $this->entity_id,
+ ]);
+ $this->assertInstanceOf(Computer::class, $computer);
+
+ return $computer;
+ }
+
+ private function buildData(PluginFieldsContainer $container, Computer $computer, array $values): array
+ {
+ return $values + [
+ 'plugin_fields_containers_id' => $container->getID(),
+ 'itemtype' => Computer::class,
+ 'items_id' => $computer->getID(),
+ ];
+ }
+
+ private function storeValues(PluginFieldsContainer $container, Computer $computer, array $values): void
+ {
+ $this->assertTrue($container->updateFieldsValues($this->buildData($container, $computer, $values), Computer::class, false));
+ }
+
+ private function getStoredValues(PluginFieldsContainer $container, Computer $computer): array
+ {
+ $classname = PluginFieldsContainer::getClassname(Computer::class, $container->fields['name']);
+ $values_obj = new $classname();
+ $this->assertTrue($values_obj->getFromDBByCrit(['items_id' => $computer->getID()]));
+
+ return $values_obj->fields;
+ }
+}
diff --git a/tests/Units/ExportBlockAsYamlTest.php b/tests/Units/ExportBlockAsYamlTest.php
new file mode 100644
index 00000000..2e1dd070
--- /dev/null
+++ b/tests/Units/ExportBlockAsYamlTest.php
@@ -0,0 +1,128 @@
+.
+ * -------------------------------------------------------------------------
+ * @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 Computer;
+use Entity;
+use DbTestCase;
+use GLPITestCase;
+use GlpiPlugin\Field\Tests\FieldTestTrait;
+use PluginFieldsContainer;
+use PluginFieldsProfile;
+
+require_once __DIR__ . '/../FieldTestCase.php';
+
+final class ExportBlockAsYamlTest extends DbTestCase
+{
+ use FieldTestTrait;
+
+ private int $root_entity_id;
+
+ private int $child_entity_id;
+
+ public function setUp(): void
+ {
+ GLPITestCase::setUp();
+ $this->login();
+ $this->root_entity_id = 0;
+ $this->child_entity_id = $this->createItem(Entity::class, [
+ 'name' => 'Entity ' . $this->getUniqueString(),
+ 'entities_id' => 0,
+ ])->getID();
+ $this->setEntity($this->root_entity_id, true);
+ }
+
+ public function tearDown(): void
+ {
+ $this->tearDownFieldTest();
+ GLPITestCase::tearDown();
+ }
+
+ public function testVisibleBlockIsExported(): void
+ {
+ $container = $this->createContainer($this->root_entity_id);
+
+ $this->assertTrue(plugin_fields_exportBlockAsYaml($container->getID()));
+ $this->assertStringContainsString(
+ $container->getID() . '-' . Computer::class,
+ (string) file_get_contents(GLPI_TMP_DIR . '/fields_conf.yaml'),
+ );
+ }
+
+ public function testBlockWithoutProfileAccessIsOmitted(): void
+ {
+ $container = $this->createContainer($this->root_entity_id);
+
+ $profile_right = new PluginFieldsProfile();
+ $this->assertTrue($profile_right->getFromDBByCrit([
+ 'profiles_id' => $_SESSION['glpiactiveprofile']['id'],
+ 'plugin_fields_containers_id' => $container->getID(),
+ ]));
+ $this->updateItem(PluginFieldsProfile::class, $profile_right->getID(), ['right' => 0]);
+
+ $this->assertFalse(plugin_fields_exportBlockAsYaml($container->getID()));
+ }
+
+ public function testBlockOutsideActiveEntitiesIsOmitted(): void
+ {
+ $container = $this->createContainer($this->child_entity_id);
+
+ $this->setEntity($this->root_entity_id, false);
+ $this->assertFalse(plugin_fields_exportBlockAsYaml($container->getID()));
+
+ $this->setEntity($this->child_entity_id, false);
+ $this->assertTrue(plugin_fields_exportBlockAsYaml($container->getID()));
+ }
+
+ private function createContainer(int $entities_id): PluginFieldsContainer
+ {
+ $container = $this->createFieldContainer([
+ 'label' => 'Export',
+ 'type' => 'tab',
+ 'itemtypes' => [Computer::class],
+ 'is_active' => 1,
+ 'entities_id' => $entities_id,
+ 'is_recursive' => 0,
+ ]);
+ $this->createField([
+ 'label' => 'Exported text',
+ 'type' => 'text',
+ PluginFieldsContainer::getForeignKeyField() => $container->getID(),
+ 'ranking' => 1,
+ 'is_active' => 1,
+ 'is_readonly' => 0,
+ ]);
+
+ return $container;
+ }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 5ebb18d7..c397b3a0 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -39,6 +39,10 @@
define('TU_PASS', 'glpi');
define('GLPI_LOG_LVL', 'DEBUG');
+if (!is_dir(GLPI_LOG_DIR)) {
+ mkdir(GLPI_LOG_DIR, 0755, true);
+}
+
require GLPI_ROOT . '/inc/includes.php';
Plugin::load('fields', true);
From 7b47168790c2329c1f160d218dbd01e65dc915db Mon Sep 17 00:00:00 2001
From: Stanislas Kita <7335054+stonebuzz@users.noreply.github.com>
Date: Fri, 2 Oct 2026 15:57:35 +0200
Subject: [PATCH 2/2] fix
---
composer.lock | 456 +++++++++++---------------
front/export_to_yaml.php | 48 ---
inc/container.class.php | 14 -
setup.php | 117 -------
tests/Units/ExportBlockAsYamlTest.php | 128 --------
5 files changed, 190 insertions(+), 573 deletions(-)
delete mode 100644 front/export_to_yaml.php
delete mode 100644 tests/Units/ExportBlockAsYamlTest.php
diff --git a/composer.lock b/composer.lock
index eb35804e..7efcf726 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,247 +4,21 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "8fd001d971e6381c8d6d7f4af8e2913c",
- "packages": [
- {
- "name": "symfony/deprecation-contracts",
- "version": "v2.5.4",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/deprecation-contracts.git",
- "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918",
- "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918",
- "shasum": ""
- },
- "require": {
- "php": ">=7.1"
- },
- "type": "library",
- "extra": {
- "thanks": {
- "url": "https://github.com/symfony/contracts",
- "name": "symfony/contracts"
- },
- "branch-alias": {
- "dev-main": "2.5-dev"
- }
- },
- "autoload": {
- "files": [
- "function.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "A generic function and convention to trigger deprecation notices",
- "homepage": "https://symfony.com",
- "support": {
- "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2024-09-25T14:11:13+00:00"
- },
- {
- "name": "symfony/polyfill-ctype",
- "version": "v1.33.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
- "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
- "shasum": ""
- },
- "require": {
- "php": ">=7.2"
- },
- "provide": {
- "ext-ctype": "*"
- },
- "suggest": {
- "ext-ctype": "For best performance"
- },
- "type": "library",
- "extra": {
- "thanks": {
- "url": "https://github.com/symfony/polyfill",
- "name": "symfony/polyfill"
- }
- },
- "autoload": {
- "files": [
- "bootstrap.php"
- ],
- "psr-4": {
- "Symfony\\Polyfill\\Ctype\\": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Gert de Pagter",
- "email": "BackEndTea@gmail.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony polyfill for ctype functions",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "ctype",
- "polyfill",
- "portable"
- ],
- "support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://github.com/nicolas-grekas",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2024-09-09T11:45:10+00:00"
- },
- {
- "name": "symfony/yaml",
- "version": "v5.4.45",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/yaml.git",
- "reference": "a454d47278cc16a5db371fe73ae66a78a633371e"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/a454d47278cc16a5db371fe73ae66a78a633371e",
- "reference": "a454d47278cc16a5db371fe73ae66a78a633371e",
- "shasum": ""
- },
- "require": {
- "php": ">=7.2.5",
- "symfony/deprecation-contracts": "^2.1|^3",
- "symfony/polyfill-ctype": "^1.8"
- },
- "conflict": {
- "symfony/console": "<5.3"
- },
- "require-dev": {
- "symfony/console": "^5.3|^6.0"
- },
- "suggest": {
- "symfony/console": "For validating YAML files using the lint command"
- },
- "bin": [
- "Resources/bin/yaml-lint"
- ],
- "type": "library",
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Yaml\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Loads and dumps YAML files",
- "homepage": "https://symfony.com",
- "support": {
- "source": "https://github.com/symfony/yaml/tree/v5.4.45"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2024-09-25T14:11:13+00:00"
- }
- ],
+ "content-hash": "ce5f749951bc7e46686362673da6c64f",
+ "packages": [],
"packages-dev": [
{
"name": "glpi-project/tools",
- "version": "0.8.3",
+ "version": "0.8.4",
"source": {
"type": "git",
"url": "https://github.com/glpi-project/tools.git",
- "reference": "8ea2a7d4702a858f4b0360ba7d4f1841a5e77026"
+ "reference": "2791387652f570c0f676a372ad74c4dade65c937"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/glpi-project/tools/zipball/8ea2a7d4702a858f4b0360ba7d4f1841a5e77026",
- "reference": "8ea2a7d4702a858f4b0360ba7d4f1841a5e77026",
+ "url": "https://api.github.com/repos/glpi-project/tools/zipball/2791387652f570c0f676a372ad74c4dade65c937",
+ "reference": "2791387652f570c0f676a372ad74c4dade65c937",
"shasum": ""
},
"require": {
@@ -287,7 +61,7 @@
"issues": "https://github.com/glpi-project/tools/issues",
"source": "https://github.com/glpi-project/tools"
},
- "time": "2025-10-14T10:26:06+00:00"
+ "time": "2026-09-09T13:56:33+00:00"
},
{
"name": "psr/container",
@@ -436,18 +210,168 @@
],
"time": "2024-11-06T11:30:55+00:00"
},
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v2.5.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918",
+ "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "2.5-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-25T14:11:13+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.37.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "141046a8f9477948ff284fa65be2095baafb94f2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2",
+ "reference": "141046a8f9477948ff284fa65be2095baafb94f2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-ctype": "*"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-04-10T16:19:22+00:00"
+ },
{
"name": "symfony/polyfill-intl-grapheme",
- "version": "v1.33.0",
+ "version": "v1.43.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
- "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70"
+ "reference": "5cac8f4db97208823aa353f59a01faad2da52c75"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70",
- "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5cac8f4db97208823aa353f59a01faad2da52c75",
+ "reference": "5cac8f4db97208823aa353f59a01faad2da52c75",
"shasum": ""
},
"require": {
@@ -496,7 +420,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0"
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.43.0"
},
"funding": [
{
@@ -516,20 +440,20 @@
"type": "tidelift"
}
],
- "time": "2025-06-27T09:58:17+00:00"
+ "time": "2026-09-25T15:46:47+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
- "version": "v1.33.0",
+ "version": "v1.43.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
- "reference": "3833d7255cc303546435cb650316bff708a1c75c"
+ "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
- "reference": "3833d7255cc303546435cb650316bff708a1c75c",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/ebd57ccc7edb93d632e184efbe7693d2517bab3b",
+ "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b",
"shasum": ""
},
"require": {
@@ -581,7 +505,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0"
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.43.0"
},
"funding": [
{
@@ -601,20 +525,20 @@
"type": "tidelift"
}
],
- "time": "2024-09-09T11:45:10+00:00"
+ "time": "2026-09-23T22:02:09+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.33.0",
+ "version": "v1.43.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
+ "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
- "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/4d5d331bf731e0aedbc6b2f5e61135b7b7996a72",
+ "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72",
"shasum": ""
},
"require": {
@@ -666,7 +590,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.0"
},
"funding": [
{
@@ -686,11 +610,11 @@
"type": "tidelift"
}
],
- "time": "2024-12-23T08:48:59+00:00"
+ "time": "2026-09-24T15:19:04+00:00"
},
{
"name": "symfony/polyfill-php73",
- "version": "v1.33.0",
+ "version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php73.git",
@@ -746,7 +670,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0"
+ "source": "https://github.com/symfony/polyfill-php73/tree/v1.37.0"
},
"funding": [
{
@@ -770,16 +694,16 @@
},
{
"name": "symfony/polyfill-php80",
- "version": "v1.33.0",
+ "version": "v1.43.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
- "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
+ "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
- "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74",
+ "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74",
"shasum": ""
},
"require": {
@@ -830,7 +754,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.43.0"
},
"funding": [
{
@@ -850,20 +774,20 @@
"type": "tidelift"
}
],
- "time": "2025-01-02T08:10:11+00:00"
+ "time": "2026-09-25T15:50:05+00:00"
},
{
"name": "symfony/polyfill-php81",
- "version": "v1.33.0",
+ "version": "v1.38.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
- "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
+ "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
- "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
+ "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7",
+ "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7",
"shasum": ""
},
"require": {
@@ -910,7 +834,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0"
+ "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1"
},
"funding": [
{
@@ -930,7 +854,7 @@
"type": "tidelift"
}
],
- "time": "2024-09-09T11:45:10+00:00"
+ "time": "2026-05-26T12:45:58+00:00"
},
{
"name": "symfony/service-contracts",
@@ -1194,5 +1118,5 @@
"platform-overrides": {
"php": "7.4.0"
},
- "plugin-api-version": "2.9.0"
+ "plugin-api-version": "2.6.0"
}
diff --git a/front/export_to_yaml.php b/front/export_to_yaml.php
deleted file mode 100644
index 16e8afb7..00000000
--- a/front/export_to_yaml.php
+++ /dev/null
@@ -1,48 +0,0 @@
-.
- * -------------------------------------------------------------------------
- * @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
- * -------------------------------------------------------------------------
- */
-
-include('../../../inc/includes.php');
-include('../hook.php');
-
-Session::checkRight('config', READ);
-
-$ID = null;
-if (isset($_GET['id'])) {
- $ID = $_GET['id'];
-}
-
-if (plugin_fields_exportBlockAsYaml($ID)) {
- $filename = 'fields_conf.yaml';
- $path = GLPI_TMP_DIR . '/fields_conf.yaml';
- Toolbox::sendFile($path, $filename, 'text/yaml');
-} else {
- Session::addMessageAfterRedirect('No data to export', false, INFO);
- Html::back();
-}
diff --git a/inc/container.class.php b/inc/container.class.php
index 8bdff01d..3a84496d 100644
--- a/inc/container.class.php
+++ b/inc/container.class.php
@@ -854,20 +854,6 @@ public static function getTypeName($nb = 0)
public function showForm($ID, $options = [])
{
$this->initForm($ID, $options);
-
- if (!$this->isNewID($ID)) {
- $btn_url = Plugin::getWebDir('fields') . '/front/export_to_yaml.php?id=' . $ID;
- $btn_label = __('Export to YAML', 'fields');
- $export_btn = <<
-
-
-HTML;
- $options['header_toolbar'] = [$export_btn];
- }
-
$this->showFormHeader($options);
$rand = mt_rand();
diff --git a/setup.php b/setup.php
index 887cca5e..788db2a9 100644
--- a/setup.php
+++ b/setup.php
@@ -278,120 +278,3 @@ function plugin_fields_checkFiles()
}
}
-
-function plugin_fields_exportBlockAsYaml($container_id = null)
-{
- /** @var DBmysql $DB */
- global $DB;
-
- $yaml_conf = [
- 'container' => [],
- ];
-
- if (
- isset($_SESSION['glpiactiveentities'])
- && Session::getLoginUserID()
- && Plugin::isPluginActive('fields')
- ) {
- if ($DB->tableExists(PluginFieldsContainer::getTable())) {
- $where = [];
- $where['is_active'] = true;
- if ($container_id != null) {
- $where['id'] = $container_id;
- }
- $container_obj = new PluginFieldsContainer();
- $containers = $container_obj->find($where);
-
- foreach ($containers as $container) {
- if (
- !Session::haveAccessToEntity((int) $container['entities_id'], (bool) $container['is_recursive'])
- || PluginFieldsProfile::getRightOnContainer((int) ($_SESSION['glpiactiveprofile']['id'] ?? 0), (int) $container['id']) < READ
- ) {
- continue;
- }
-
- $itemtypes = (strlen($container['itemtypes']) > 0)
- ? json_decode($container['itemtypes'], true)
- : [];
-
- foreach ($itemtypes as $itemtype) {
- $fields_obj = new PluginFieldsField();
- // to get translation
- $container['itemtype'] = PluginFieldsContainer::getType();
- $yaml_conf['container'][$container['id'] . '-' . $itemtype] = [
- 'id' => (int) $container['id'],
- 'name' => PluginFieldsLabelTranslation::getLabelFor($container),
- 'itemtype' => $itemtype,
- 'type' => $container['type'],
- 'subtype' => $container['subtype'],
- 'fields' => [],
- ];
- $fields = $fields_obj->find(['plugin_fields_containers_id' => $container['id'],
- 'is_active' => true,
- 'is_readonly' => false,
- ]);
- if (count($fields)) {
- foreach ($fields as $field) {
- $tmp_field = [];
- $tmp_field['id'] = (int) $field['id'];
-
- //to get translation
- $field['itemtype'] = PluginFieldsField::getType();
- $tmp_field['label'] = PluginFieldsLabelTranslation::getLabelFor($field);
- $tmp_field['xml_node'] = strtoupper($field['name']);
- $tmp_field['type'] = $field['type'];
- $tmp_field['ranking'] = $field['ranking'];
- $tmp_field['default_value'] = $field['default_value'];
- $tmp_field['mandatory'] = $field['mandatory'];
- $tmp_field['possible_value'] = '';
-
- switch ($field['type']) {
- case 'dropdown':
- $obj = new $itemtype();
- $obj->getEmpty();
-
- $dropdown_itemtype = PluginFieldsDropdown::getClassname($field['name']);
- $tmp_field['xml_node'] = strtoupper(getForeignKeyFieldForItemType($dropdown_itemtype));
-
- $dropdown_obj = new $dropdown_itemtype();
- $dropdown_datas = $dropdown_obj->find();
- $datas = [];
- foreach ($dropdown_datas as $value) {
- $items = [];
- $items['id'] = (int) $value['id'];
- $items['value'] = $value['name'];
- $datas[] = $items;
- }
- $tmp_field['possible_value'] = $datas;
- break;
- case 'yesno':
- $datas = [];
- $datas['0']['id'] = 0;
- $datas['0']['value'] = __('No');
- $datas['1']['id'] = 1;
- $datas['1']['value'] = __('Yes');
- $tmp_field['possible_value'] = $datas;
- break;
- case 'dropdownuser':
- $datas = Dropdown::getDropdownUsers(['is_active' => 1, 'is_deleted' => 0], false);
- $tmp_field['possible_value'] = $datas['results'];
- break;
- }
- $yaml_conf['container'][$container['id'] . '-' . $itemtype]['fields'][] = $tmp_field;
- }
- }
- }
- }
- }
- }
-
- if (!empty($yaml_conf['container'])) {
- $dump = Yaml::dump($yaml_conf, 10);
- $filename = GLPI_TMP_DIR . '/fields_conf.yaml';
- file_put_contents($filename, $dump);
-
- return true;
- }
-
- return false;
-}
diff --git a/tests/Units/ExportBlockAsYamlTest.php b/tests/Units/ExportBlockAsYamlTest.php
deleted file mode 100644
index 2e1dd070..00000000
--- a/tests/Units/ExportBlockAsYamlTest.php
+++ /dev/null
@@ -1,128 +0,0 @@
-.
- * -------------------------------------------------------------------------
- * @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 Computer;
-use Entity;
-use DbTestCase;
-use GLPITestCase;
-use GlpiPlugin\Field\Tests\FieldTestTrait;
-use PluginFieldsContainer;
-use PluginFieldsProfile;
-
-require_once __DIR__ . '/../FieldTestCase.php';
-
-final class ExportBlockAsYamlTest extends DbTestCase
-{
- use FieldTestTrait;
-
- private int $root_entity_id;
-
- private int $child_entity_id;
-
- public function setUp(): void
- {
- GLPITestCase::setUp();
- $this->login();
- $this->root_entity_id = 0;
- $this->child_entity_id = $this->createItem(Entity::class, [
- 'name' => 'Entity ' . $this->getUniqueString(),
- 'entities_id' => 0,
- ])->getID();
- $this->setEntity($this->root_entity_id, true);
- }
-
- public function tearDown(): void
- {
- $this->tearDownFieldTest();
- GLPITestCase::tearDown();
- }
-
- public function testVisibleBlockIsExported(): void
- {
- $container = $this->createContainer($this->root_entity_id);
-
- $this->assertTrue(plugin_fields_exportBlockAsYaml($container->getID()));
- $this->assertStringContainsString(
- $container->getID() . '-' . Computer::class,
- (string) file_get_contents(GLPI_TMP_DIR . '/fields_conf.yaml'),
- );
- }
-
- public function testBlockWithoutProfileAccessIsOmitted(): void
- {
- $container = $this->createContainer($this->root_entity_id);
-
- $profile_right = new PluginFieldsProfile();
- $this->assertTrue($profile_right->getFromDBByCrit([
- 'profiles_id' => $_SESSION['glpiactiveprofile']['id'],
- 'plugin_fields_containers_id' => $container->getID(),
- ]));
- $this->updateItem(PluginFieldsProfile::class, $profile_right->getID(), ['right' => 0]);
-
- $this->assertFalse(plugin_fields_exportBlockAsYaml($container->getID()));
- }
-
- public function testBlockOutsideActiveEntitiesIsOmitted(): void
- {
- $container = $this->createContainer($this->child_entity_id);
-
- $this->setEntity($this->root_entity_id, false);
- $this->assertFalse(plugin_fields_exportBlockAsYaml($container->getID()));
-
- $this->setEntity($this->child_entity_id, false);
- $this->assertTrue(plugin_fields_exportBlockAsYaml($container->getID()));
- }
-
- private function createContainer(int $entities_id): PluginFieldsContainer
- {
- $container = $this->createFieldContainer([
- 'label' => 'Export',
- 'type' => 'tab',
- 'itemtypes' => [Computer::class],
- 'is_active' => 1,
- 'entities_id' => $entities_id,
- 'is_recursive' => 0,
- ]);
- $this->createField([
- 'label' => 'Exported text',
- 'type' => 'text',
- PluginFieldsContainer::getForeignKeyField() => $container->getID(),
- 'ranking' => 1,
- 'is_active' => 1,
- 'is_readonly' => 0,
- ]);
-
- return $container;
- }
-}