From 49e7e5f052f0c0e1479117b6eecee5131dac1968 Mon Sep 17 00:00:00 2001 From: Curtis Conard Date: Tue, 12 Mar 2024 16:18:36 -0400 Subject: [PATCH] Multiple groups for assets --- CHANGELOG.md | 4 + inc/relation.constant.php | 32 +-- .../update_10.0.x_to_11.0.0/assets.php | 100 +++++++- install/mysql/glpi-empty.sql | 44 +--- src/Api/HL/Controller/AssetController.php | 169 ++++++++++++-- src/Asset/Asset.php | 18 ++ src/CartridgeItem.php | 27 ++- src/CommonDBTM.php | 31 ++- src/Computer.php | 44 +++- src/ConsumableItem.php | 27 ++- src/DBmysql.php | 2 +- src/Dropdown.php | 25 +- src/Features/AssignableAsset.php | 207 ++++++++++++++++- src/Features/Clonable.php | 18 +- src/Link.php | 15 +- src/Monitor.php | 30 ++- src/NetworkEquipment.php | 34 ++- src/Peripheral.php | 30 ++- src/Phone.php | 30 ++- src/Printer.php | 33 ++- src/RuleAsset.php | 10 +- src/Search/Provider/SQLProvider.php | 99 +++++++- src/Software.php | 37 ++- src/Toolbox.php | 2 +- templates/generic_show_form.html.twig | 6 +- tests/DbTestCase.php | 6 +- tests/functional/CartridgeItem.php | 134 +++++++++++ tests/functional/Computer.php | 214 ++++++++++++++++-- tests/functional/ConsumableItem.php | 135 +++++++++++ tests/functional/Glpi/Inventory/Inventory.php | 48 ++-- tests/functional/Link.php | 3 +- tests/functional/Monitor.php | 120 +++++++++- tests/functional/NetworkEquipment.php | 116 ++++++++++ tests/functional/Peripheral.php | 157 +++++++++++++ tests/functional/Phone.php | 157 +++++++++++++ tests/functional/Printer.php | 116 ++++++++++ tests/functional/Search.php | 15 +- tests/functional/Software.php | 116 ++++++++++ tests/units/DB.php | 2 +- tests/units/RuleAsset.php | 4 +- 40 files changed, 2185 insertions(+), 232 deletions(-) create mode 100644 tests/functional/CartridgeItem.php create mode 100644 tests/functional/ConsumableItem.php create mode 100644 tests/functional/Peripheral.php create mode 100644 tests/functional/Phone.php diff --git a/CHANGELOG.md b/CHANGELOG.md index cea7052e0c8a..f1898d6d0ccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ The present file will list all changes made to the project; according to the - External Links `Link or filename` and `File content` fields now use Twig templates instead of a custom tag syntax. - Itemtypes associated with External links are now in the main form rather than a separate tab. - The `Computer_Item` class has been replaced by the `\Glpi\Asset\Asset_PeripheralAsset` class. +- `Group` and `Group in charge` fields for assets may now contain multiple groups. ### Deprecated - Survey URL tags `TICKETCATEGORY_ID` and `TICKETCATEGORY_NAME` are deprecated and replaced by `ITILCATEGORY_ID` and `ITILCATEGORY_NAME` respectively. @@ -135,6 +136,9 @@ The present file will list all changes made to the project; according to the - Specifying the `ranking` of a rule during add/update now triggers `RuleCollection::moveRule` to manage the rankings of other rules to try to keep them valid and in order. - `Lock::getLocksQueryInfosByItemType()` has been made private. - `DBmysql::request()`, `DBmysqlIterator::buildQuery()` and `DBmysqlIterator::execute()` methods signatures changed. +- Any class added to `$CFG_GLPI['directconnect_types']` must now use the `Glpi\Features\AssignableAsset` trait as multi-group support is required. +- For assets, `groups_id` and `groups_id_tech` fields were changed from integers to arrays and are loaded into the `fields` array after `getFromDB`/`getEmpty`. + If reading directly from the DB, you need to query the new linking table `glpi_groups_assets`. #### Deprecated - Usage of `MAIL_SMTPSSL` and `MAIL_SMTPTLS` constants. diff --git a/inc/relation.constant.php b/inc/relation.constant.php index 6e5e421ad4de..6789c53ffcac 100644 --- a/inc/relation.constant.php +++ b/inc/relation.constant.php @@ -722,7 +722,6 @@ 'groups_id_tech', 'groups_id', ], - 'glpi_cartridgeitems' => 'groups_id_tech', 'glpi_certificates' => [ 'groups_id_tech', 'groups_id', @@ -730,16 +729,13 @@ '_glpi_changes_groups' => 'groups_id', 'glpi_changetasks' => 'groups_id_tech', 'glpi_clusters' => 'groups_id_tech', - 'glpi_computers' => [ - 'groups_id_tech', - 'groups_id', - ], - 'glpi_consumableitems' => 'groups_id_tech', 'glpi_databaseinstances' => 'groups_id_tech', 'glpi_domains' => 'groups_id_tech', 'glpi_domainrecords' => 'groups_id_tech', 'glpi_enclosures' => 'groups_id_tech', 'glpi_groups' => 'groups_id', + //FIXME I guess cleanup should be manual, or an actual class is needed. + //'_glpi_groups_assets' => 'groups_id', '_glpi_groups_knowbaseitems' => 'groups_id', '_glpi_groups_problems' => 'groups_id', '_glpi_groups_reminders' => 'groups_id', @@ -749,29 +745,9 @@ 'glpi_items_devicesimcards' => 'groups_id', 'glpi_itilcategories' => 'groups_id', 'glpi_lines' => 'groups_id', - 'glpi_monitors' => [ - 'groups_id_tech', - 'groups_id', - ], - 'glpi_networkequipments' => [ - 'groups_id_tech', - 'groups_id', - ], 'glpi_passivedcequipments' => 'groups_id_tech', 'glpi_pdus' => 'groups_id_tech', - 'glpi_peripherals' => [ - 'groups_id_tech', - 'groups_id', - ], 'glpi_planningexternalevents' => 'groups_id', - 'glpi_phones' => [ - 'groups_id_tech', - 'groups_id', - ], - 'glpi_printers' => [ - 'groups_id_tech', - 'groups_id', - ], 'glpi_problemtasks' => 'groups_id_tech', 'glpi_projects' => 'groups_id', 'glpi_racks' => [ @@ -782,10 +758,6 @@ 'groups_id_tech', 'groups_id', ], - 'glpi_softwares' => [ - 'groups_id_tech', - 'groups_id', - ], 'glpi_tasktemplates' => 'groups_id_tech', 'glpi_tickettasks' => 'groups_id_tech', 'glpi_unmanageds' => [ diff --git a/install/migrations/update_10.0.x_to_11.0.0/assets.php b/install/migrations/update_10.0.x_to_11.0.0/assets.php index 38e1d41d268f..e776b3c94a0c 100644 --- a/install/migrations/update_10.0.x_to_11.0.0/assets.php +++ b/install/migrations/update_10.0.x_to_11.0.0/assets.php @@ -213,11 +213,99 @@ } } -$assignable_asset_rights = [ - 'computer', 'monitor', 'software', 'networking', 'printer', - 'cartridge', 'consumable', 'phone', 'peripheral' +$assignable_assets = [ + 'Computer' => [ + 'table' => 'glpi_computers', + 'rightname' => 'computer' + ], + 'Monitor' => [ + 'table' => 'glpi_monitors', + 'rightname' => 'monitor' + ], + 'Software' => [ + 'table' => 'glpi_softwares', + 'rightname' => 'software' + ], + 'NetworkEquipment' => [ + 'table' => 'glpi_networkequipments', + 'rightname' => 'networking' + ], + 'Printer' => [ + 'table' => 'glpi_printers', + 'rightname' => 'printer' + ], + 'CartridgeItem' => [ + 'table' => 'glpi_cartridgeitems', + 'rightname' => 'cartridge' + ], + 'ConsumableItem' => [ + 'table' => 'glpi_consumableitems', + 'rightname' => 'consumable' + ], + 'Phone' => [ + 'table' => 'glpi_phones', + 'rightname' => 'phone' + ], + 'Peripheral' => [ + 'table' => 'glpi_peripherals', + 'rightname' => 'peripheral' + ] ]; -foreach ($assignable_asset_rights as $rightname) { - $migration->addRight($rightname, READ_ASSIGNED, [$rightname => READ]); - $migration->addRight($rightname, UPDATE_ASSIGNED, [$rightname => UPDATE]); + +if (!$DB->tableExists('glpi_groups_assets')) { + $query = <<doQueryOrDie($query); +} + +foreach ($assignable_assets as $asset_class => $asset) { + $migration->addRight($asset['rightname'], READ_ASSIGNED, [$asset['rightname'] => READ]); + $migration->addRight($asset['rightname'], UPDATE_ASSIGNED, [$asset['rightname'] => UPDATE]); + + // move groups to the new link table + if ($DB->fieldExists($asset['table'], 'groups_id')) { + $DB->insert('glpi_groups_assets', new \Glpi\DBAL\QuerySubQuery([ + 'SELECT' => [ + new \Glpi\DBAL\QueryExpression('NULL', 'id'), + new \Glpi\DBAL\QueryExpression('0', 'type'), + 'id AS items_id', + 'groups_id', + new \Glpi\DBAL\QueryExpression($DB::quoteValue($asset_class), 'itemtype') + ], + 'FROM' => $asset['table'], + 'WHERE' => [ + 'groups_id' => ['>', 0] + ] + ])); + } + if ($DB->fieldExists($asset['table'], 'groups_id_tech')) { + $DB->insert('glpi_groups_assets', new \Glpi\DBAL\QuerySubQuery([ + 'SELECT' => [ + new \Glpi\DBAL\QueryExpression('NULL', 'id'), + new \Glpi\DBAL\QueryExpression('1', 'type'), + 'id AS items_id', + 'groups_id_tech AS groups_id', + new \Glpi\DBAL\QueryExpression($DB::quoteValue($asset_class), 'itemtype') + ], + 'FROM' => $asset['table'], + 'WHERE' => [ + 'groups_id_tech' => ['>', 0] + ] + ])); + } + + $migration->dropKey($asset['table'], 'groups_id'); + $migration->dropKey($asset['table'], 'groups_id_tech'); + $migration->dropField($asset['table'], 'groups_id'); + $migration->dropField($asset['table'], 'groups_id_tech'); } diff --git a/install/mysql/glpi-empty.sql b/install/mysql/glpi-empty.sql index fc90be348884..d8e200f67143 100644 --- a/install/mysql/glpi-empty.sql +++ b/install/mysql/glpi-empty.sql @@ -434,7 +434,6 @@ CREATE TABLE `glpi_cartridgeitems` ( `cartridgeitemtypes_id` int unsigned NOT NULL DEFAULT '0', `manufacturers_id` int unsigned NOT NULL DEFAULT '0', `users_id_tech` int unsigned NOT NULL DEFAULT '0', - `groups_id_tech` int unsigned NOT NULL DEFAULT '0', `is_deleted` tinyint NOT NULL DEFAULT '0', `comment` text, `alarm_threshold` int NOT NULL DEFAULT '10', @@ -452,7 +451,6 @@ CREATE TABLE `glpi_cartridgeitems` ( KEY `cartridgeitemtypes_id` (`cartridgeitemtypes_id`), KEY `is_deleted` (`is_deleted`), KEY `alarm_threshold` (`alarm_threshold`), - KEY `groups_id_tech` (`groups_id_tech`), KEY `date_mod` (`date_mod`), KEY `date_creation` (`date_creation`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; @@ -977,7 +975,6 @@ CREATE TABLE `glpi_computers` ( `contact` varchar(255) DEFAULT NULL, `contact_num` varchar(255) DEFAULT NULL, `users_id_tech` int unsigned NOT NULL DEFAULT '0', - `groups_id_tech` int unsigned NOT NULL DEFAULT '0', `comment` text, `date_mod` timestamp NULL DEFAULT NULL, `autoupdatesystems_id` int unsigned NOT NULL DEFAULT '0', @@ -991,7 +988,6 @@ CREATE TABLE `glpi_computers` ( `is_deleted` tinyint NOT NULL DEFAULT '0', `is_dynamic` tinyint NOT NULL DEFAULT '0', `users_id` int unsigned NOT NULL DEFAULT '0', - `groups_id` int unsigned NOT NULL DEFAULT '0', `states_id` int unsigned NOT NULL DEFAULT '0', `ticket_tco` decimal(20,4) DEFAULT '0.0000', `uuid` varchar(255) DEFAULT NULL, @@ -1006,7 +1002,6 @@ CREATE TABLE `glpi_computers` ( KEY `autoupdatesystems_id` (`autoupdatesystems_id`), KEY `entities_id` (`entities_id`), KEY `manufacturers_id` (`manufacturers_id`), - KEY `groups_id` (`groups_id`), KEY `users_id` (`users_id`), KEY `locations_id` (`locations_id`), KEY `computermodels_id` (`computermodels_id`), @@ -1015,7 +1010,6 @@ CREATE TABLE `glpi_computers` ( KEY `users_id_tech` (`users_id_tech`), KEY `computertypes_id` (`computertypes_id`), KEY `is_deleted` (`is_deleted`), - KEY `groups_id_tech` (`groups_id_tech`), KEY `is_dynamic` (`is_dynamic`), KEY `serial` (`serial`), KEY `otherserial` (`otherserial`), @@ -1322,7 +1316,6 @@ CREATE TABLE `glpi_consumableitems` ( `consumableitemtypes_id` int unsigned NOT NULL DEFAULT '0', `manufacturers_id` int unsigned NOT NULL DEFAULT '0', `users_id_tech` int unsigned NOT NULL DEFAULT '0', - `groups_id_tech` int unsigned NOT NULL DEFAULT '0', `is_deleted` tinyint NOT NULL DEFAULT '0', `comment` text, `alarm_threshold` int NOT NULL DEFAULT '10', @@ -1341,7 +1334,6 @@ CREATE TABLE `glpi_consumableitems` ( KEY `consumableitemtypes_id` (`consumableitemtypes_id`), KEY `is_deleted` (`is_deleted`), KEY `alarm_threshold` (`alarm_threshold`), - KEY `groups_id_tech` (`groups_id_tech`), KEY `date_mod` (`date_mod`), KEY `date_creation` (`date_creation`), KEY `otherserial` (`otherserial`) @@ -4339,7 +4331,6 @@ CREATE TABLE `glpi_monitors` ( `contact` varchar(255) DEFAULT NULL, `contact_num` varchar(255) DEFAULT NULL, `users_id_tech` int unsigned NOT NULL DEFAULT '0', - `groups_id_tech` int unsigned NOT NULL DEFAULT '0', `comment` text, `serial` varchar(255) DEFAULT NULL, `otherserial` varchar(255) DEFAULT NULL, @@ -4361,7 +4352,6 @@ CREATE TABLE `glpi_monitors` ( `is_template` tinyint NOT NULL DEFAULT '0', `template_name` varchar(255) DEFAULT NULL, `users_id` int unsigned NOT NULL DEFAULT '0', - `groups_id` int unsigned NOT NULL DEFAULT '0', `states_id` int unsigned NOT NULL DEFAULT '0', `ticket_tco` decimal(20,4) DEFAULT '0.0000', `is_dynamic` tinyint NOT NULL DEFAULT '0', @@ -4375,7 +4365,6 @@ CREATE TABLE `glpi_monitors` ( KEY `is_global` (`is_global`), KEY `entities_id` (`entities_id`), KEY `manufacturers_id` (`manufacturers_id`), - KEY `groups_id` (`groups_id`), KEY `users_id` (`users_id`), KEY `locations_id` (`locations_id`), KEY `monitormodels_id` (`monitormodels_id`), @@ -4383,7 +4372,6 @@ CREATE TABLE `glpi_monitors` ( KEY `users_id_tech` (`users_id_tech`), KEY `monitortypes_id` (`monitortypes_id`), KEY `is_deleted` (`is_deleted`), - KEY `groups_id_tech` (`groups_id_tech`), KEY `is_dynamic` (`is_dynamic`), KEY `autoupdatesystems_id` (`autoupdatesystems_id`), KEY `serial` (`serial`), @@ -4596,7 +4584,6 @@ CREATE TABLE `glpi_networkequipments` ( `contact` varchar(255) DEFAULT NULL, `contact_num` varchar(255) DEFAULT NULL, `users_id_tech` int unsigned NOT NULL DEFAULT '0', - `groups_id_tech` int unsigned NOT NULL DEFAULT '0', `date_mod` timestamp NULL DEFAULT NULL, `comment` text, `locations_id` int unsigned NOT NULL DEFAULT '0', @@ -4608,7 +4595,6 @@ CREATE TABLE `glpi_networkequipments` ( `is_template` tinyint NOT NULL DEFAULT '0', `template_name` varchar(255) DEFAULT NULL, `users_id` int unsigned NOT NULL DEFAULT '0', - `groups_id` int unsigned NOT NULL DEFAULT '0', `states_id` int unsigned NOT NULL DEFAULT '0', `ticket_tco` decimal(20,4) DEFAULT '0.0000', `is_dynamic` tinyint NOT NULL DEFAULT '0', @@ -4626,7 +4612,6 @@ CREATE TABLE `glpi_networkequipments` ( KEY `entities_id` (`entities_id`), KEY `is_recursive` (`is_recursive`), KEY `manufacturers_id` (`manufacturers_id`), - KEY `groups_id` (`groups_id`), KEY `users_id` (`users_id`), KEY `locations_id` (`locations_id`), KEY `networkequipmentmodels_id` (`networkequipmentmodels_id`), @@ -4636,7 +4621,6 @@ CREATE TABLE `glpi_networkequipments` ( KEY `networkequipmenttypes_id` (`networkequipmenttypes_id`), KEY `is_deleted` (`is_deleted`), KEY `date_mod` (`date_mod`), - KEY `groups_id_tech` (`groups_id_tech`), KEY `is_dynamic` (`is_dynamic`), KEY `serial` (`serial`), KEY `otherserial` (`otherserial`), @@ -5276,7 +5260,6 @@ CREATE TABLE `glpi_peripherals` ( `contact` varchar(255) DEFAULT NULL, `contact_num` varchar(255) DEFAULT NULL, `users_id_tech` int unsigned NOT NULL DEFAULT '0', - `groups_id_tech` int unsigned NOT NULL DEFAULT '0', `comment` text, `serial` varchar(255) DEFAULT NULL, `otherserial` varchar(255) DEFAULT NULL, @@ -5290,7 +5273,6 @@ CREATE TABLE `glpi_peripherals` ( `is_template` tinyint NOT NULL DEFAULT '0', `template_name` varchar(255) DEFAULT NULL, `users_id` int unsigned NOT NULL DEFAULT '0', - `groups_id` int unsigned NOT NULL DEFAULT '0', `states_id` int unsigned NOT NULL DEFAULT '0', `ticket_tco` decimal(20,4) DEFAULT '0.0000', `is_dynamic` tinyint NOT NULL DEFAULT '0', @@ -5304,7 +5286,6 @@ CREATE TABLE `glpi_peripherals` ( KEY `is_global` (`is_global`), KEY `entities_id` (`entities_id`), KEY `manufacturers_id` (`manufacturers_id`), - KEY `groups_id` (`groups_id`), KEY `users_id` (`users_id`), KEY `locations_id` (`locations_id`), KEY `peripheralmodels_id` (`peripheralmodels_id`), @@ -5313,7 +5294,6 @@ CREATE TABLE `glpi_peripherals` ( KEY `peripheraltypes_id` (`peripheraltypes_id`), KEY `is_deleted` (`is_deleted`), KEY `date_mod` (`date_mod`), - KEY `groups_id_tech` (`groups_id_tech`), KEY `is_dynamic` (`is_dynamic`), KEY `autoupdatesystems_id` (`autoupdatesystems_id`), KEY `serial` (`serial`), @@ -5388,7 +5368,6 @@ CREATE TABLE `glpi_phones` ( `contact` varchar(255) DEFAULT NULL, `contact_num` varchar(255) DEFAULT NULL, `users_id_tech` int unsigned NOT NULL DEFAULT '0', - `groups_id_tech` int unsigned NOT NULL DEFAULT '0', `comment` text, `serial` varchar(255) DEFAULT NULL, `otherserial` varchar(255) DEFAULT NULL, @@ -5406,7 +5385,6 @@ CREATE TABLE `glpi_phones` ( `is_template` tinyint NOT NULL DEFAULT '0', `template_name` varchar(255) DEFAULT NULL, `users_id` int unsigned NOT NULL DEFAULT '0', - `groups_id` int unsigned NOT NULL DEFAULT '0', `states_id` int unsigned NOT NULL DEFAULT '0', `ticket_tco` decimal(20,4) DEFAULT '0.0000', `is_dynamic` tinyint NOT NULL DEFAULT '0', @@ -5421,7 +5399,6 @@ CREATE TABLE `glpi_phones` ( KEY `is_global` (`is_global`), KEY `entities_id` (`entities_id`), KEY `manufacturers_id` (`manufacturers_id`), - KEY `groups_id` (`groups_id`), KEY `users_id` (`users_id`), KEY `locations_id` (`locations_id`), KEY `phonemodels_id` (`phonemodels_id`), @@ -5431,7 +5408,6 @@ CREATE TABLE `glpi_phones` ( KEY `phonetypes_id` (`phonetypes_id`), KEY `is_deleted` (`is_deleted`), KEY `date_mod` (`date_mod`), - KEY `groups_id_tech` (`groups_id_tech`), KEY `is_dynamic` (`is_dynamic`), KEY `autoupdatesystems_id` (`autoupdatesystems_id`), KEY `serial` (`serial`), @@ -5528,7 +5504,6 @@ CREATE TABLE `glpi_printers` ( `contact` varchar(255) DEFAULT NULL, `contact_num` varchar(255) DEFAULT NULL, `users_id_tech` int unsigned NOT NULL DEFAULT '0', - `groups_id_tech` int unsigned NOT NULL DEFAULT '0', `serial` varchar(255) DEFAULT NULL, `otherserial` varchar(255) DEFAULT NULL, `have_serial` tinyint NOT NULL DEFAULT '0', @@ -5550,7 +5525,6 @@ CREATE TABLE `glpi_printers` ( `init_pages_counter` int NOT NULL DEFAULT '0', `last_pages_counter` int NOT NULL DEFAULT '0', `users_id` int unsigned NOT NULL DEFAULT '0', - `groups_id` int unsigned NOT NULL DEFAULT '0', `states_id` int unsigned NOT NULL DEFAULT '0', `ticket_tco` decimal(20,4) DEFAULT '0.0000', `is_dynamic` tinyint NOT NULL DEFAULT '0', @@ -5567,7 +5541,6 @@ CREATE TABLE `glpi_printers` ( KEY `entities_id` (`entities_id`), KEY `is_recursive` (`is_recursive`), KEY `manufacturers_id` (`manufacturers_id`), - KEY `groups_id` (`groups_id`), KEY `users_id` (`users_id`), KEY `locations_id` (`locations_id`), KEY `printermodels_id` (`printermodels_id`), @@ -5577,7 +5550,6 @@ CREATE TABLE `glpi_printers` ( KEY `printertypes_id` (`printertypes_id`), KEY `is_deleted` (`is_deleted`), KEY `date_mod` (`date_mod`), - KEY `groups_id_tech` (`groups_id_tech`), KEY `last_pages_counter` (`last_pages_counter`), KEY `is_dynamic` (`is_dynamic`), KEY `serial` (`serial`), @@ -6813,7 +6785,6 @@ CREATE TABLE `glpi_softwares` ( `comment` text, `locations_id` int unsigned NOT NULL DEFAULT '0', `users_id_tech` int unsigned NOT NULL DEFAULT '0', - `groups_id_tech` int unsigned NOT NULL DEFAULT '0', `is_update` tinyint NOT NULL DEFAULT '0', `softwares_id` int unsigned NOT NULL DEFAULT '0', `manufacturers_id` int unsigned NOT NULL DEFAULT '0', @@ -6822,7 +6793,6 @@ CREATE TABLE `glpi_softwares` ( `template_name` varchar(255) DEFAULT NULL, `date_mod` timestamp NULL DEFAULT NULL, `users_id` int unsigned NOT NULL DEFAULT '0', - `groups_id` int unsigned NOT NULL DEFAULT '0', `ticket_tco` decimal(20,4) DEFAULT '0.0000', `is_helpdesk_visible` tinyint NOT NULL DEFAULT '1', `softwarecategories_id` int unsigned NOT NULL DEFAULT '0', @@ -6838,14 +6808,12 @@ CREATE TABLE `glpi_softwares` ( KEY `entities_id` (`entities_id`), KEY `is_recursive` (`is_recursive`), KEY `manufacturers_id` (`manufacturers_id`), - KEY `groups_id` (`groups_id`), KEY `users_id` (`users_id`), KEY `locations_id` (`locations_id`), KEY `users_id_tech` (`users_id_tech`), KEY `softwares_id` (`softwares_id`), KEY `is_deleted` (`is_deleted`), KEY `is_helpdesk_visible` (`is_helpdesk_visible`), - KEY `groups_id_tech` (`groups_id_tech`), KEY `date_creation` (`date_creation`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; @@ -9923,4 +9891,16 @@ CREATE TABLE `glpi_assets_assettypes` ( KEY `date_creation` (`date_creation`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; +DROP TABLE IF EXISTS `glpi_groups_assets`; +CREATE TABLE `glpi_groups_assets` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `groups_id` int unsigned NOT NULL DEFAULT '0', + `itemtype` varchar(255) NOT NULL DEFAULT '', + `items_id` int unsigned NOT NULL DEFAULT '0', + `type` tinyint NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `unicity` (`groups_id`,`itemtype`,`items_id`, `type`), + KEY `item` (`itemtype`, `items_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; + SET FOREIGN_KEY_CHECKS=1; diff --git a/src/Api/HL/Controller/AssetController.php b/src/Api/HL/Controller/AssetController.php index 0f7dd1b613a3..98547a116e69 100644 --- a/src/Api/HL/Controller/AssetController.php +++ b/src/Api/HL/Controller/AssetController.php @@ -42,6 +42,7 @@ use Glpi\Api\HL\Middleware\ResultFormatterMiddleware; use Glpi\Api\HL\Route; use Glpi\Api\HL\Search; +use Glpi\Features\AssignableAsset; use Glpi\Http\JSONResponse; use Glpi\Http\Request; use Glpi\Http\Response; @@ -52,6 +53,7 @@ use Location; use Manufacturer; use Network; +use Software; use State; use User; @@ -387,20 +389,6 @@ class: $model_class, ); } - if (in_array($asset_type, $CFG_GLPI['linkuser_tech_types'], true)) { - $schemas[$schema_name]['properties']['user_tech'] = self::getDropdownTypeSchema( - class: User::class, - field: 'users_id_tech', - full_schema: 'User' - ); - } - if (in_array($asset_type, $CFG_GLPI['linkgroup_tech_types'], true)) { - $schemas[$schema_name]['properties']['group_tech'] = self::getDropdownTypeSchema( - class: Group::class, - field: 'groups_id_tech', - full_schema: 'Group' - ); - } if (in_array($asset_type, $CFG_GLPI['linkuser_types'], true)) { $schemas[$schema_name]['properties']['user'] = self::getDropdownTypeSchema( class: User::class, @@ -408,14 +396,95 @@ class: User::class, full_schema: 'User' ); } - if (in_array($asset_type, $CFG_GLPI['linkgroup_types'], true)) { - $schemas[$schema_name]['properties']['group'] = self::getDropdownTypeSchema( - class: Group::class, - field: 'groups_id', - full_schema: 'Group' + if (in_array($asset_type, $CFG_GLPI['linkuser_tech_types'], true)) { + $schemas[$schema_name]['properties']['user_tech'] = self::getDropdownTypeSchema( + class: User::class, + field: 'users_id_tech', + full_schema: 'User' ); } + if (\Toolbox::hasTrait($asset_type, AssignableAsset::class)) { + // Assignable assets all support multiple groups and the group links are in a separate table + if (in_array($asset_type, $CFG_GLPI['linkgroup_types'], true)) { + $schemas[$schema_name]['properties']['group'] = [ + 'type' => Doc\Schema::TYPE_ARRAY, + 'items' => [ + 'type' => Doc\Schema::TYPE_OBJECT, + 'x-full-schema' => 'Group', + 'x-join' => [ + 'table' => 'glpi_groups', // The table with the desired data + 'fkey' => 'groups_id', + 'field' => 'id', + 'ref-join' => [ + 'table' => 'glpi_groups_assets', + 'fkey' => 'id', + 'field' => 'items_id', + 'condition' => [ + 'itemtype' => $asset_type, + 'type' => 0 // Normal + ] + ] + ], + 'properties' => [ + 'id' => [ + 'type' => Doc\Schema::TYPE_INTEGER, + 'format' => Doc\Schema::FORMAT_INTEGER_INT64, + 'description' => 'ID', + ], + 'name' => ['type' => Doc\Schema::TYPE_STRING], + ] + ] + ]; + } + if (in_array($asset_type, $CFG_GLPI['linkgroup_tech_types'], true)) { + $schemas[$schema_name]['properties']['group_tech'] = [ + 'type' => Doc\Schema::TYPE_ARRAY, + 'items' => [ + 'type' => Doc\Schema::TYPE_OBJECT, + 'x-full-schema' => 'Group', + 'x-join' => [ + 'table' => 'glpi_groups', // The table with the desired data + 'fkey' => 'groups_id', + 'field' => 'id', + 'ref-join' => [ + 'table' => 'glpi_groups_assets', + 'fkey' => 'id', + 'field' => 'items_id', + 'condition' => [ + 'itemtype' => $asset_type, + 'type' => 1 // Tech + ] + ] + ], + 'properties' => [ + 'id' => [ + 'type' => Doc\Schema::TYPE_INTEGER, + 'format' => Doc\Schema::FORMAT_INTEGER_INT64, + 'description' => 'ID', + ], + 'name' => ['type' => Doc\Schema::TYPE_STRING], + ] + ] + ]; + } + } else { + if (in_array($asset_type, $CFG_GLPI['linkgroup_types'], true)) { + $schemas[$schema_name]['properties']['group'] = self::getDropdownTypeSchema( + class: Group::class, + field: 'groups_id', + full_schema: 'Group' + ); + } + if (in_array($asset_type, $CFG_GLPI['linkgroup_tech_types'], true)) { + $schemas[$schema_name]['properties']['group_tech'] = self::getDropdownTypeSchema( + class: Group::class, + field: 'groups_id_tech', + full_schema: 'Group' + ); + } + } + if ($asset->isField('contact')) { $schemas[$schema_name]['properties']['contact'] = ['type' => Doc\Schema::TYPE_STRING]; } @@ -632,7 +701,7 @@ class: Group::class, ]; $schemas['Software'] = [ - 'x-itemtype' => \Software::class, + 'x-itemtype' => Software::class, 'type' => Doc\Schema::TYPE_OBJECT, 'properties' => [ 'id' => [ @@ -650,9 +719,65 @@ class: Group::class, 'parent' => self::getDropdownTypeSchema(class: \Software::class, full_schema: 'Software'), 'is_helpdesk_visible' => ['type' => Doc\Schema::TYPE_BOOLEAN], 'user' => self::getDropdownTypeSchema(class: User::class, full_schema: 'User'), - 'group' => self::getDropdownTypeSchema(class: Group::class, full_schema: 'Group'), + 'group' => [ + 'type' => Doc\Schema::TYPE_ARRAY, + 'items' => [ + 'type' => Doc\Schema::TYPE_OBJECT, + 'x-full-schema' => 'Group', + 'x-join' => [ + 'table' => 'glpi_groups', // The table with the desired data + 'fkey' => 'groups_id', + 'field' => 'id', + 'ref-join' => [ + 'table' => 'glpi_groups_assets', + 'fkey' => 'id', + 'field' => 'items_id', + 'condition' => [ + 'itemtype' => Software::class, + 'type' => 0 // Normal + ] + ] + ], + 'properties' => [ + 'id' => [ + 'type' => Doc\Schema::TYPE_INTEGER, + 'format' => Doc\Schema::FORMAT_INTEGER_INT64, + 'description' => 'ID', + ], + 'name' => ['type' => Doc\Schema::TYPE_STRING], + ] + ] + ], 'user_tech' => self::getDropdownTypeSchema(class: User::class, field: 'users_id_tech', full_schema: 'User'), - 'group_tech' => self::getDropdownTypeSchema(class: Group::class, field: 'groups_id_tech', full_schema: 'Group'), + 'group_tech' => [ + 'type' => Doc\Schema::TYPE_ARRAY, + 'items' => [ + 'type' => Doc\Schema::TYPE_OBJECT, + 'x-full-schema' => 'Group', + 'x-join' => [ + 'table' => 'glpi_groups', // The table with the desired data + 'fkey' => 'groups_id', + 'field' => 'id', + 'ref-join' => [ + 'table' => 'glpi_groups_assets', + 'fkey' => 'id', + 'field' => 'items_id', + 'condition' => [ + 'itemtype' => Software::class, + 'type' => 1 // Tech + ] + ] + ], + 'properties' => [ + 'id' => [ + 'type' => Doc\Schema::TYPE_INTEGER, + 'format' => Doc\Schema::FORMAT_INTEGER_INT64, + 'description' => 'ID', + ], + 'name' => ['type' => Doc\Schema::TYPE_STRING], + ] + ] + ], 'is_deleted' => ['type' => Doc\Schema::TYPE_BOOLEAN], 'is_update' => ['type' => Doc\Schema::TYPE_BOOLEAN], 'is_valid' => ['type' => Doc\Schema::TYPE_BOOLEAN], diff --git a/src/Asset/Asset.php b/src/Asset/Asset.php index a1a2a575a1ad..77bc3c016dcf 100644 --- a/src/Asset/Asset.php +++ b/src/Asset/Asset.php @@ -248,6 +248,15 @@ public function rawSearchOptions() 'field' => 'completename', 'name' => Group::getTypeName(1), 'condition' => ['is_itemgroup' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_NORMAL] + ] + ] + ], 'datatype' => 'dropdown' ]; @@ -295,6 +304,15 @@ public function rawSearchOptions() 'linkfield' => 'groups_id_tech', 'name' => __('Group in charge of the hardware'), 'condition' => ['is_assign' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_NORMAL] + ] + ] + ], 'datatype' => 'dropdown' ]; diff --git a/src/CartridgeItem.php b/src/CartridgeItem.php index 1e0f536ba9e4..fd9bbf5a1e78 100644 --- a/src/CartridgeItem.php +++ b/src/CartridgeItem.php @@ -47,7 +47,10 @@ class CartridgeItem extends CommonDBTM { use AssetImage; - use AssignableAsset; + use AssignableAsset { + prepareInputForAdd as prepareInputForAddAssignableAsset; + prepareInputForUpdate as prepareInputForUpdateAssignableAsset; + } // From CommonDBTM protected static $forward_entity_to = ['Cartridge', 'Infocom']; @@ -89,13 +92,13 @@ public function getPostAdditionalInfosForName() public function prepareInputForAdd($input) { - $input = parent::prepareInputForAdd($input); + $input = $this->prepareInputForAddAssignableAsset($input); return $this->managePictures($input); } public function prepareInputForUpdate($input) { - $input = parent::prepareInputForUpdate($input); + $input = $this->prepareInputForUpdateAssignableAsset($input); return $this->managePictures($input); } @@ -316,9 +319,20 @@ public function rawSearchOptions() 'id' => '49', 'table' => 'glpi_groups', 'field' => 'completename', - 'linkfield' => 'groups_id_tech', + 'linkfield' => 'groups_id', 'name' => __('Group in charge'), 'condition' => ['is_assign' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_TECH] + ] + ] + ], + 'forcegroupby' => true, + 'massiveaction' => false, 'datatype' => 'dropdown' ]; @@ -624,4 +638,9 @@ public static function getIcon() { return Cartridge::getIcon(); } + + public function getGroupTypes(): array + { + return [self::GROUP_TYPE_TECH]; + } } diff --git a/src/CommonDBTM.php b/src/CommonDBTM.php index 6686edc17d08..8bedb4962800 100644 --- a/src/CommonDBTM.php +++ b/src/CommonDBTM.php @@ -38,6 +38,7 @@ use Glpi\DBAL\QueryFunction; use Glpi\DBAL\QueryParam; use Glpi\Event; +use Glpi\Features\AssignableAsset; use Glpi\Features\CacheableListInterface; use Glpi\Plugin\Hooks; use Glpi\RichText\RichText; @@ -1037,6 +1038,19 @@ public function cleanRelationTable() */ global $CFG_GLPI, $DB; + if (Toolbox::hasTrait(static::class, AssignableAsset::class)) { + $DB->delete('glpi_groups_assets', [ + 'itemtype' => static::class, + 'items_id' => $this->getID() + ]); + } + + if (static::class === Group::class) { + $DB->delete('glpi_groups_assets', [ + 'groups_id' => $this->getID() + ]); + } + if (in_array($this->getType(), $CFG_GLPI['agent_types'])) { // Agent does not extends CommonDBConnexity $agent = new Agent(); @@ -3536,11 +3550,18 @@ public function getComments() $this->isField('groups_id') && ($this->getType() != 'Group') ) { - $tmp = Dropdown::getDropdownName("glpi_groups", $this->getField('groups_id')); - if ((strlen($tmp) != 0) && ($tmp != ' ')) { - $toadd[] = ['name' => Group::getTypeName(1), - 'value' => $tmp - ]; + $groups = $this->fields['groups_id']; + if (!is_array($groups)) { + $groups = [$groups]; + } + foreach ($groups as $group) { + $tmp = Dropdown::getDropdownName("glpi_groups", $group); + if ($tmp !== '' && $tmp !== ' ') { + $toadd[] = [ + 'name' => Group::getTypeName(1), + 'value' => $tmp + ]; + } } } diff --git a/src/Computer.php b/src/Computer.php index a8db6b4b6210..7119e2a7494d 100644 --- a/src/Computer.php +++ b/src/Computer.php @@ -45,7 +45,10 @@ class Computer extends CommonDBTM use Glpi\Features\Clonable; use Glpi\Features\Inventoriable; use Glpi\Features\State; - use Glpi\Features\AssignableAsset; + use Glpi\Features\AssignableAsset { + prepareInputForAdd as prepareInputForAddAssignableAsset; + post_updateItem as post_updateItemAssignableAsset; + } // From CommonDBTM public $dohistory = true; @@ -167,6 +170,8 @@ public function post_updateItem($history = true) */ global $CFG_GLPI, $DB; + $this->post_updateItemAssignableAsset($history); + $changes = []; $update_count = count($this->updates ?? []); $input = $this->fields; @@ -185,12 +190,6 @@ public function post_updateItem($history = true) ) { $changes['users_id'] = $input['users_id']; } - if ( - $this->updates[$i] == 'groups_id' - && Entity::getUsedConfig('is_group_autoupdate', $this->getEntityID()) - ) { - $changes['groups_id'] = $input['groups_id']; - } // Update state of attached items if ( ($this->updates[$i] == 'states_id') @@ -207,6 +206,11 @@ public function post_updateItem($history = true) } } + // Group is handled differently since the field was changed to support multiple groups and was therefore moved to a separate table + if (array_key_exists('_groups_id', $this->input) && Entity::getUsedConfig('is_group_autoupdate', $this->getEntityID())) { + $changes['groups_id'] = $this->input['_groups_id']; + } + if (count($changes)) { $update_done = false; $is_input_dynamic = (bool) ($this->input['is_dynamic'] ?? false); @@ -312,13 +316,13 @@ public function post_updateItem($history = true) public function prepareInputForAdd($input) { - if (isset($input["id"]) && ($input["id"] > 0)) { $input["_oldID"] = $input["id"]; } unset($input['id']); unset($input['withtemplate']); + $input = $this->prepareInputForAddAssignableAsset($input); return $input; } @@ -524,6 +528,17 @@ public function rawSearchOptions() 'field' => 'completename', 'name' => Group::getTypeName(1), 'condition' => ['is_itemgroup' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_NORMAL] + ] + ] + ], + 'forcegroupby' => true, + 'massiveaction' => false, 'datatype' => 'dropdown' ]; @@ -575,9 +590,20 @@ public function rawSearchOptions() 'id' => '49', 'table' => 'glpi_groups', 'field' => 'completename', - 'linkfield' => 'groups_id_tech', + 'linkfield' => 'groups_id', 'name' => __('Group in charge'), 'condition' => ['is_assign' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_TECH] + ] + ] + ], + 'forcegroupby' => true, + 'massiveaction' => false, 'datatype' => 'dropdown' ]; diff --git a/src/ConsumableItem.php b/src/ConsumableItem.php index d1e49d53c2bc..eddbf5892bcd 100644 --- a/src/ConsumableItem.php +++ b/src/ConsumableItem.php @@ -50,7 +50,10 @@ class ConsumableItem extends CommonDBTM use Glpi\Features\Clonable; use AssetImage; - use AssignableAsset; + use AssignableAsset { + prepareInputForAdd as prepareInputForAddAssignableAsset; + prepareInputForUpdate as prepareInputForUpdateAssignableAsset; + } // From CommonDBTM protected static $forward_entity_to = ['Consumable', 'Infocom']; @@ -97,13 +100,13 @@ public function getPostAdditionalInfosForName() public function prepareInputForAdd($input) { - $input = parent::prepareInputForAdd($input); + $input = $this->prepareInputForAddAssignableAsset($input); return $this->managePictures($input); } public function prepareInputForUpdate($input) { - $input = parent::prepareInputForUpdate($input); + $input = $this->prepareInputForUpdateAssignableAsset($input); return $this->managePictures($input); } @@ -256,9 +259,20 @@ public function rawSearchOptions() 'id' => '49', 'table' => 'glpi_groups', 'field' => 'completename', - 'linkfield' => 'groups_id_tech', + 'linkfield' => 'groups_id', 'name' => __('Group in charge'), 'condition' => ['is_assign' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_TECH] + ] + ] + ], + 'forcegroupby' => true, + 'massiveaction' => false, 'datatype' => 'dropdown' ]; @@ -484,4 +498,9 @@ public static function getIcon() { return Consumable::getIcon(); } + + public function getGroupTypes(): array + { + return [self::GROUP_TYPE_TECH]; + } } diff --git a/src/DBmysql.php b/src/DBmysql.php index b1a681510c4e..1438546d7a19 100644 --- a/src/DBmysql.php +++ b/src/DBmysql.php @@ -1351,7 +1351,7 @@ public function buildInsert($table, $params) * @since 9.3 * * @param string $table Table name - * @param array $params Query parameters ([field name => field value) + * @param QuerySubQuery|array $params Array of field => value pairs or a QuerySubQuery for INSERT INTO ... SELECT * * @return mysqli_result|boolean Query result handler */ diff --git a/src/Dropdown.php b/src/Dropdown.php index 5487ebe68cc2..fd672b0ad87d 100644 --- a/src/Dropdown.php +++ b/src/Dropdown.php @@ -3394,10 +3394,6 @@ public static function getDropdownValue($post, $json = true) } } - if (Toolbox::hasTrait($post['itemtype'], AssignableAsset::class)) { - $where[] = $post['itemtype']::getAssignableVisiblityCriteria(); - } - $criteria = array_merge( $criteria, [ @@ -3408,6 +3404,10 @@ public static function getDropdownValue($post, $json = true) ] ); + if (Toolbox::hasTrait($post['itemtype'], AssignableAsset::class)) { + $criteria = array_merge($criteria, $post['itemtype']::getAssignableVisiblityCriteria()); + } + $order_field = "$table.$field"; if (isset($post['order']) && !empty($post['order'])) { $order_field = $post['order']; @@ -3869,20 +3869,21 @@ public static function getDropdownFindNum($post, $json = true) $post['page_limit'] = $CFG_GLPI['dropdown_max']; } + $criteria = [ + 'FROM' => $post['table'], + 'WHERE' => $where, + 'ORDER' => $item::getNameField(), + ]; if (Toolbox::hasTrait($post['itemtype'], AssignableAsset::class)) { - $where[] = $post['itemtype']::getAssignableVisiblityCriteria(); + $criteria = array_merge_recursive($criteria, $post['itemtype']::getAssignableVisiblityCriteria()); } $start = (int) (($post['page'] - 1) * $post['page_limit']); $limit = (int) $post['page_limit']; + $criteria['START'] = $start; + $criteria['LIMIT'] = $limit; - $iterator = $DB->request([ - 'FROM' => $post['table'], - 'WHERE' => $where, - 'ORDER' => $item->getNameField(), - 'LIMIT' => $limit, - 'START' => $start - ]); + $iterator = $DB->request($criteria); $results = []; diff --git a/src/Features/AssignableAsset.php b/src/Features/AssignableAsset.php index efe64beb190a..4cfe15cb2fae 100644 --- a/src/Features/AssignableAsset.php +++ b/src/Features/AssignableAsset.php @@ -40,6 +40,9 @@ trait AssignableAsset { + public const GROUP_TYPE_NORMAL = 0; + public const GROUP_TYPE_TECH = 1; + public static function canView() { return Session::haveRightsOr(static::$rightname, [READ, READ_ASSIGNED]); @@ -52,7 +55,7 @@ public function canViewItem() } $is_assigned = $this->fields['users_id_tech'] === $_SESSION['glpiID'] || - in_array((int) ($this->fields['groups_id_tech'] ?? 0), $_SESSION['glpigroups'] ?? [], true); + count(array_intersect($this->fields['groups_id_tech'] ?? [], $_SESSION['glpigroups'] ?? [])) > 0; if (!Session::haveRight(static::$rightname, READ)) { return $is_assigned && Session::haveRight(static::$rightname, READ_ASSIGNED); @@ -74,7 +77,7 @@ public function canUpdateItem() } $is_assigned = $this->fields['users_id_tech'] === $_SESSION['glpiID'] || - in_array((int) ($this->fields['groups_id_tech'] ?? 0), $_SESSION['glpigroups'] ?? [], true); + count(array_intersect($this->fields['groups_id_tech'] ?? [], $_SESSION['glpigroups'] ?? [])) > 0; if (!Session::haveRight(static::$rightname, UPDATE)) { return $is_assigned && Session::haveRight(static::$rightname, UPDATE_ASSIGNED); @@ -87,20 +90,49 @@ public function canUpdateItem() public static function getAssignableVisiblityCriteria() { if (Session::haveRight(static::$rightname, READ)) { - return [new QueryExpression('1')]; + return []; } if (Session::haveRight(static::$rightname, READ_ASSIGNED)) { + $where = [ + 'users_id_tech' => $_SESSION['glpiID'] + ]; + $join = null; + $item = new static(); + if (count($_SESSION['glpigroups']) && in_array(self::GROUP_TYPE_TECH, $item->getGroupTypes(), true)) { + $join = [ + 'LEFT JOIN' => [ + 'glpi_groups_assets' => [ + 'ON' => [ + static::getTable() => 'id', + 'glpi_groups_assets' => 'items_id', [ + 'AND' => [ + 'glpi_groups_assets.itemtype' => static::class, + ] + ] + ], + ], + ] + ]; + $where[] = [ + [ + 'glpi_groups_assets.groups_id' => $_SESSION['glpigroups'], + 'glpi_groups_assets.type' => 1, //Tech + ] + ]; + } $criteria = [ - 'OR' => [ - 'users_id_tech' => $_SESSION['glpiID'], - ], + 'WHERE' => [ + [ + 'OR' => $where, + ] + ] ]; - if (count($_SESSION['glpigroups'])) { - $criteria['OR']['groups_id_tech'] = $_SESSION['glpigroups']; + if ($join !== null) { + $criteria += $join; } - return [$criteria]; + return $criteria; } - return [new QueryExpression('0')]; + return ['WHERE' => [new QueryExpression('0')]]; } /** @@ -118,4 +150,159 @@ public function getRights($interface = 'central') $rights[UPDATE_ASSIGNED] = __('Update assigned'); return $rights; } + + protected function prepareGroupFields(array $input) + { + $fields = ['groups_id', 'groups_id_tech']; + foreach ($fields as $field) { + if (array_key_exists($field, $input)) { + if (!is_array($input[$field])) { + $input[$field] = [$input[$field]]; + } + $input['_' . $field] = array_filter(array_map('intval', $input[$field] ?? []), static fn ($v) => $v > 0); + unset($input[$field]); + } + } + return $input; + } + + public function prepareInputForAdd($input): array|false + { + if ($input === false) { + return false; + } + $input = parent::prepareInputForAdd($input); + return $this->prepareGroupFields($input); + } + + public function prepareInputForUpdate($input): array|false + { + if ($input === false) { + return false; + } + $input = parent::prepareInputForUpdate($input); + return $this->prepareGroupFields($input); + } + + /** + * Update the values in the 'glpi_groups_assets' link table as needed based on the groups set in the 'groups_id' and 'groups_id_tech' fields. + */ + private function updateGroupFields() + { + /** @var \DBmysql $DB */ + global $DB; + + // Find existing links + $existing_links = []; + if (!$this->isNewItem()) { + $it = $DB->request([ + 'SELECT' => ['id', 'groups_id', 'type'], + 'FROM' => 'glpi_groups_assets', + 'WHERE' => [ + 'itemtype' => static::class, + 'items_id' => $this->getID(), + ], + ]); + $existing_links = iterator_to_array($it); + } + + // Group fields are changed to have a '_' prefix to avoid trying to update non-existent fields in the database + $fields = [ + self::GROUP_TYPE_NORMAL => '_groups_id', + self::GROUP_TYPE_TECH => '_groups_id_tech' + ]; + foreach ($fields as $type => $field) { + $existing_for_type = array_column(array_filter($existing_links, static fn($link) => $link['type'] === $type), 'groups_id'); + if (array_key_exists($field, $this->input)) { + $new_links = array_diff($this->input[$field], $existing_for_type); + $old_links = array_diff($existing_for_type, $this->input[$field]); + foreach ($new_links as $group_id) { + $DB->insert('glpi_groups_assets', [ + 'itemtype' => static::class, + 'items_id' => $this->getID(), + 'groups_id' => $group_id, + 'type' => $type, + ]); + } + foreach ($old_links as $group_id) { + $DB->delete('glpi_groups_assets', [ + 'itemtype' => static::class, + 'items_id' => $this->getID(), + 'groups_id' => $group_id, + 'type' => $type, + ]); + } + } + } + + $this->loadGroupFields(); + } + + public function post_addItem() + { + parent::post_addItem(); + $this->updateGroupFields(); + } + + public function post_updateItem($history = true) + { + parent::post_updateItem($history); + $this->updateGroupFields(); + } + + public function getEmpty() + { + if (!parent::getEmpty()) { + return false; + } + $group_fields = ['groups_id', 'groups_id_tech']; + foreach ($group_fields as $field) { + $this->fields[$field] = []; + } + return true; + } + + private function loadGroupFields() + { + /** @var \DBmysql $DB */ + global $DB; + + // Find existing links + $existing_links = []; + if (!$this->isNewItem()) { + $it = $DB->request([ + 'SELECT' => ['id', 'groups_id', 'type'], + 'FROM' => 'glpi_groups_assets', + 'WHERE' => [ + 'itemtype' => static::class, + 'items_id' => $this->getID(), + ], + ]); + $existing_links = iterator_to_array($it); + } + + $group_fields = [ + self::GROUP_TYPE_NORMAL => 'groups_id', + self::GROUP_TYPE_TECH => 'groups_id_tech' + ]; + foreach ($group_fields as $type => $field) { + if (in_array($type, $this->getGroupTypes(), true)) { + $this->fields[$field] = array_column(array_filter($existing_links, static fn($link) => $link['type'] === $type), 'groups_id'); + } + } + } + + public function post_getFromDB() + { + $this->loadGroupFields(); + } + + /** + * Get the types of groups supported by the asset. + * @return array + */ + public function getGroupTypes(): array + { + return [self::GROUP_TYPE_NORMAL, self::GROUP_TYPE_TECH]; + } } diff --git a/src/Features/Clonable.php b/src/Features/Clonable.php index 3062a5eeb8fd..13935204b659 100644 --- a/src/Features/Clonable.php +++ b/src/Features/Clonable.php @@ -154,16 +154,21 @@ private function cloneRelations(CommonDBTM $source, bool $history): void /** * Prepare input datas for cloning the item. - * This empty method is meant to be redefined in objects that need a specific prepareInputForClone logic. - * - * @since 10.0.0 + * The default implementation handles specific cases when the class uses the following trait(s): + * - {@link AssignableAsset} * * @param array $input datas used to add the item * * @return array the modified $input array + * @since 10.0.0 + * */ public function prepareInputForClone($input) { + if (method_exists($this, 'prepareGroupFields')) { + // Toolbox::hasTrait doesn't work to tell PHPStan this method exists even when using generics and assert-if-true + $input = $this->prepareGroupFields($input); + } return $input; } @@ -311,12 +316,17 @@ public function computeCloneName( /** * Post clone logic. - * This empty method is meant to be redefined in objects that need a specific post_clone logic. + * The default implementation handles specific cases when the class uses the following trait(s): + * - {@link AssignableAsset} * * @param $source * @param $history */ public function post_clone($source, $history) { + if (method_exists($this, 'updateGroupFields')) { + // Toolbox::hasTrait doesn't work to tell PHPStan this method exists even when using generics and assert-if-true + $this->updateGroupFields(); + } } } diff --git a/src/Link.php b/src/Link.php index 7ed3889f3cac..df979a4b05ca 100644 --- a/src/Link.php +++ b/src/Link.php @@ -36,6 +36,7 @@ use Glpi\Application\View\TemplateRenderer; use Glpi\ContentTemplates\TemplateManager; use Glpi\DBAL\QueryExpression; +use Glpi\Features\AssignableAsset; use Glpi\Toolbox\URL; /** @@ -438,12 +439,24 @@ public static function generateLinkContents($link, CommonDBTM $item, bool $safe_ 'DOMAIN' => '', 'NETWORK' => $item->isField('networks_id') ? Dropdown::getDropdownName('glpi_networks', $item->getField('networks_id')) : '', 'USER' => $item->isField('users_id') ? Dropdown::getDropdownName('glpi_users', $item->getField('users_id')) : '', - 'GROUP' => $item->isField('groups_id') ? Dropdown::getDropdownName('glpi_groups', $item->getField('groups_id')) : '', 'REALNAME' => $item->isField('realname') ? $item->getField('realname') : '', 'FIRSTNAME' => $item->isField('firstname') ? $item->getField('firstname') : '', 'MODEL' => '', ]; + if (Toolbox::hasTrait($item::class, AssignableAsset::class)) { + if (in_array(0, $item->getGroupTypes(), true)) { + $group_names = array_map(static fn ($group_id) => Dropdown::getDropdownName('glpi_groups', $group_id), $item->fields['groups_id']); + $vars['GROUPS'] = $group_names; + // GROUP - BC for < GLPI 11 + $vars['GROUP'] = count($group_names) > 0 ? array_shift($group_names) : ''; + } + } else { + $vars['GROUPS'] = []; + // GROUP - BC for < GLPI 11 + $vars['GROUP'] = $item->isField('groups_id') ? Dropdown::getDropdownName('glpi_groups', $item->getField('groups_id')) : ''; + } + $item_fields = $item->fields; $item::unsetUndisclosedFields($item_fields); if (count($item_fields)) { diff --git a/src/Monitor.php b/src/Monitor.php index 13ce0f83d919..9cea8a306b50 100644 --- a/src/Monitor.php +++ b/src/Monitor.php @@ -45,7 +45,9 @@ class Monitor extends CommonDBTM use Glpi\Features\Clonable; use Glpi\Features\Inventoriable; use Glpi\Features\State; - use Glpi\Features\AssignableAsset; + use Glpi\Features\AssignableAsset { + prepareInputForAdd as prepareInputForAddAssignableAsset; + } // From CommonDBTM public $dohistory = true; @@ -123,7 +125,6 @@ public function defineTabs($options = []) public function prepareInputForAdd($input) { - if (isset($input["id"]) && ($input["id"] > 0)) { $input["_oldID"] = $input["id"]; } @@ -133,6 +134,7 @@ public function prepareInputForAdd($input) unset($input['id']); unset($input['withtemplate']); + $input = $this->prepareInputForAddAssignableAsset($input); return $input; } @@ -301,6 +303,17 @@ public function rawSearchOptions() 'field' => 'completename', 'name' => Group::getTypeName(1), 'condition' => ['is_itemgroup' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_NORMAL] + ] + ] + ], + 'forcegroupby' => true, + 'massiveaction' => false, 'datatype' => 'dropdown' ]; @@ -424,9 +437,20 @@ public function rawSearchOptions() 'id' => '49', 'table' => 'glpi_groups', 'field' => 'completename', - 'linkfield' => 'groups_id_tech', + 'linkfield' => 'groups_id', 'name' => __('Group in charge'), 'condition' => ['is_assign' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_TECH] + ] + ] + ], + 'forcegroupby' => true, + 'massiveaction' => false, 'datatype' => 'dropdown' ]; diff --git a/src/NetworkEquipment.php b/src/NetworkEquipment.php index c2b350c2216e..4a4f260aa989 100644 --- a/src/NetworkEquipment.php +++ b/src/NetworkEquipment.php @@ -46,7 +46,9 @@ class NetworkEquipment extends CommonDBTM use Glpi\Features\Clonable; use Glpi\Features\Inventoriable; use Glpi\Features\State; - use Glpi\Features\AssignableAsset; + use Glpi\Features\AssignableAsset { + prepareInputForAdd as prepareInputForAddAssignableAsset; + } // From CommonDBTM public $dohistory = true; @@ -148,13 +150,13 @@ public function defineTabs($options = []) public function prepareInputForAdd($input) { - if (isset($input["id"]) && ($input["id"] > 0)) { $input["_oldID"] = $input["id"]; } unset($input['id']); unset($input['withtemplate']); + $input = $this->prepareInputForAddAssignableAsset($input); return $input; } @@ -355,8 +357,19 @@ public function rawSearchOptions() 'table' => 'glpi_groups', 'field' => 'completename', 'name' => Group::getTypeName(1), - 'datatype' => 'dropdown', - 'condition' => ['is_itemgroup' => 1] + 'condition' => ['is_itemgroup' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_NORMAL] + ] + ] + ], + 'forcegroupby' => true, + 'massiveaction' => false, + 'datatype' => 'dropdown' ]; $tab[] = [ @@ -467,9 +480,20 @@ public function rawSearchOptions() 'id' => '49', 'table' => 'glpi_groups', 'field' => 'completename', - 'linkfield' => 'groups_id_tech', + 'linkfield' => 'groups_id', 'name' => __('Group in charge'), 'condition' => ['is_assign' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_TECH] + ] + ] + ], + 'forcegroupby' => true, + 'massiveaction' => false, 'datatype' => 'dropdown' ]; diff --git a/src/Peripheral.php b/src/Peripheral.php index d0f86e0f845f..e60a8a2dc0dd 100644 --- a/src/Peripheral.php +++ b/src/Peripheral.php @@ -45,7 +45,9 @@ class Peripheral extends CommonDBTM use Glpi\Features\Clonable; use Glpi\Features\Inventoriable; use Glpi\Features\State; - use Glpi\Features\AssignableAsset; + use Glpi\Features\AssignableAsset { + prepareInputForAdd as prepareInputForAddAssignableAsset; + } // From CommonDBTM public $dohistory = true; @@ -129,12 +131,12 @@ public function defineTabs($options = []) public function prepareInputForAdd($input) { - if (isset($input["id"]) && ($input["id"] > 0)) { $input["_oldID"] = $input["id"]; } unset($input['id']); unset($input['withtemplate']); + $input = $this->prepareInputForAddAssignableAsset($input); return $input; } @@ -278,6 +280,17 @@ public function rawSearchOptions() 'field' => 'completename', 'name' => Group::getTypeName(1), 'condition' => ['is_itemgroup' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_NORMAL] + ] + ] + ], + 'forcegroupby' => true, + 'massiveaction' => false, 'datatype' => 'dropdown' ]; @@ -337,9 +350,20 @@ public function rawSearchOptions() 'id' => '49', 'table' => 'glpi_groups', 'field' => 'completename', - 'linkfield' => 'groups_id_tech', + 'linkfield' => 'groups_id', 'name' => __('Group in charge'), 'condition' => ['is_assign' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_TECH] + ] + ] + ], + 'forcegroupby' => true, + 'massiveaction' => false, 'datatype' => 'dropdown' ]; diff --git a/src/Phone.php b/src/Phone.php index b94cf5cb6f03..2163f38cea63 100644 --- a/src/Phone.php +++ b/src/Phone.php @@ -45,7 +45,9 @@ class Phone extends CommonDBTM use Glpi\Features\Clonable; use Glpi\Features\Inventoriable; use Glpi\Features\State; - use Glpi\Features\AssignableAsset; + use Glpi\Features\AssignableAsset { + prepareInputForAdd as prepareInputForAddAssignableAsset; + } // From CommonDBTM public $dohistory = true; @@ -136,13 +138,13 @@ public function defineTabs($options = []) public function prepareInputForAdd($input) { - if (isset($input["id"]) && ($input["id"] > 0)) { $input["_oldID"] = $input["id"]; } unset($input['id']); unset($input['withtemplate']); + $input = $this->prepareInputForAddAssignableAsset($input); return $input; } @@ -332,6 +334,17 @@ public function rawSearchOptions() 'field' => 'completename', 'name' => Group::getTypeName(1), 'condition' => ['is_itemgroup' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_NORMAL] + ] + ] + ], + 'forcegroupby' => true, + 'massiveaction' => false, 'datatype' => 'dropdown' ]; @@ -419,9 +432,20 @@ public function rawSearchOptions() 'id' => '49', 'table' => 'glpi_groups', 'field' => 'completename', - 'linkfield' => 'groups_id_tech', + 'linkfield' => 'groups_id', 'name' => __('Group in charge'), 'condition' => ['is_assign' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_TECH] + ] + ] + ], + 'forcegroupby' => true, + 'massiveaction' => false, 'datatype' => 'dropdown' ]; diff --git a/src/Printer.php b/src/Printer.php index df880d6cca1c..5932691a57d4 100644 --- a/src/Printer.php +++ b/src/Printer.php @@ -47,7 +47,10 @@ class Printer extends CommonDBTM use Glpi\Features\Clonable; use Glpi\Features\Inventoriable; use Glpi\Features\State; - use Glpi\Features\AssignableAsset; + use Glpi\Features\AssignableAsset { + prepareInputForAdd as prepareInputForAddAssignableAsset; + prepareInputForUpdate as prepareInputForUpdateAssignableAsset; + } // From CommonDBTM public $dohistory = true; @@ -222,7 +225,6 @@ public function canUnrecurs() public function prepareInputForAdd($input) { - if (isset($input["id"]) && ($input["id"] > 0)) { $input["_oldID"] = $input["id"]; } @@ -240,13 +242,13 @@ public function prepareInputForAdd($input) $input['last_pages_counter'] = $input['init_pages_counter']; } + $input = $this->prepareInputForAddAssignableAsset($input); return $input; } public function prepareInputForUpdate($input) { - if (isset($input['init_pages_counter'])) { $input['init_pages_counter'] = intval($input['init_pages_counter']); } @@ -254,6 +256,7 @@ public function prepareInputForUpdate($input) $input['last_pages_counter'] = intval($input['last_pages_counter']); } + $input = $this->prepareInputForUpdateAssignableAsset($input); return $input; } @@ -440,6 +443,17 @@ public function rawSearchOptions() 'field' => 'completename', 'name' => Group::getTypeName(1), 'condition' => ['is_itemgroup' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_NORMAL] + ] + ] + ], + 'forcegroupby' => true, + 'massiveaction' => false, 'datatype' => 'dropdown' ]; @@ -624,9 +638,20 @@ public function rawSearchOptions() 'id' => '49', 'table' => 'glpi_groups', 'field' => 'completename', - 'linkfield' => 'groups_id_tech', + 'linkfield' => 'groups_id', 'name' => __('Group in charge'), 'condition' => ['is_assign' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_TECH] + ] + ] + ], + 'forcegroupby' => true, + 'massiveaction' => false, 'datatype' => 'dropdown' ]; diff --git a/src/RuleAsset.php b/src/RuleAsset.php index 6397d8acd989..9593b086d48f 100644 --- a/src/RuleAsset.php +++ b/src/RuleAsset.php @@ -236,7 +236,11 @@ public function executeActions($output, $params, array $input = []) foreach ($this->actions as $action) { switch ($action->fields["action_type"]) { case "assign": - $output[$action->fields["field"]] = $action->fields["value"]; + if (in_array($action->fields['field'], ['groups_id', 'groups_id_tech'])) { + $output['_' . $action->fields["field"]] = [$action->fields["value"]]; + } else { + $output[$action->fields["field"]] = $action->fields["value"]; + } break; case "append": @@ -298,7 +302,7 @@ public function executeActions($output, $params, array $input = []) ($action->fields['field'] == 'groups_id') && isset($input['_default_groups_id_of_user']) ) { - $output['groups_id'] = $input['_default_groups_id_of_user']; + $output['_groups_id'] = [$input['_default_groups_id_of_user']]; } break; @@ -307,7 +311,7 @@ public function executeActions($output, $params, array $input = []) ($action->fields['field'] == 'groups_id') && isset($input['_groups_id_of_user']) ) { - $output['groups_id'] = reset($input['_groups_id_of_user']); + $output['_groups_id'] = count($input['_groups_id_of_user']) > 0 ? [reset($input['_groups_id_of_user'])] : []; } break; diff --git a/src/Search/Provider/SQLProvider.php b/src/Search/Provider/SQLProvider.php index b77712c62d36..8b1ce0ba07ff 100644 --- a/src/Search/Provider/SQLProvider.php +++ b/src/Search/Provider/SQLProvider.php @@ -1002,7 +1002,10 @@ public static function getDefaultWhereCriteria(string $itemtype): array if (Toolbox::hasTrait($itemtype, AssignableAsset::class)) { /** @var AssignableAsset $itemtype */ - $criteria[] = $itemtype::getAssignableVisiblityCriteria(); + $visibility_criteria = $itemtype::getAssignableVisiblityCriteria(); + if (count($visibility_criteria)) { + $criteria[] = $visibility_criteria; + } } /* Hook to restrict user right on current itemtype */ @@ -2366,6 +2369,32 @@ public static function getLeftJoinCriteria( } $already_link_tables[] = $tocheck; + // Handle mixed group case for AllAssets and ReservationItem + if ($tocheck === 'glpi_groups' && ($itemtype === \AllAssets::class || $itemtype === \ReservationItem::class)) { + $already_link_tables[] = 'glpi_groups_assets'; + return [ + 'LEFT JOIN' => [ + 'glpi_groups_assets' => [ + 'ON' => [ + 'glpi_groups_assets' => 'items_id', + $rt => 'id', [ + 'AND' => [ + 'glpi_groups_assets.itemtype' => $rt . '_TYPE', // Placeholder to be replaced at the end of the SQL construction during union case handling + 'glpi_groups_assets.type' => 0 + ] + ] + ] + ], + 'glpi_groups' => [ + 'ON' => [ + 'glpi_groups' => 'id', + 'glpi_groups_assets' => 'groups_id' + ] + ] + ] + ]; + } + $specific_leftjoin_criteria = []; // Plugin can override core definition for its type @@ -2999,7 +3028,7 @@ public static function getMetaLeftJoinCriteria(string $from_type, string $to_typ $from_table => 'id', [ 'AND' => [ - "$relation_table_alias." . 'itemtype_asset' => $asset_itemtype, + "$relation_table_alias." . 'itemtype_asset' => $asset_itemtype, "$relation_table_alias." . 'itemtype_peripheral' => $peripheral_itemtype, ] + $deleted_criteria ] @@ -3023,6 +3052,67 @@ public static function getMetaLeftJoinCriteria(string $from_type, string $to_typ return $joins; } + // Hack to support multiple groups for some itemtypes but not others + if ($to_type === 'Group' && Toolbox::hasTrait($from_referencetype, AssignableAsset::class)) { + return [ + 'LEFT JOIN' => [ + 'glpi_groups_assets' => [ + 'ON' => [ + 'glpi_groups_assets' => 'items_id', + $from_table => 'id', + [ + 'AND' => [ + 'glpi_groups_assets.itemtype' => $from_referencetype, + 'glpi_groups_assets.type' => 0 + ] + ] + ] + ], + 'glpi_groups' => [ + 'ON' => [ + 'glpi_groups' => 'id', + 'glpi_groups_assets' => 'groups_id', + [ + 'AND' => $to_entity_restrict_criteria + $to_criteria + ] + ] + ] + ] + ]; + } + + if ($from_referencetype === 'Group' && Toolbox::hasTrait($to_type, AssignableAsset::class)) { + // Need to link to glpi_groups_assets and the target table if they aren't already linked + if (!in_array('glpi_groups_assets', $already_link_tables2, true)) { + $already_link_tables2[] = 'glpi_groups_assets'; + $joins['LEFT JOIN']['glpi_groups_assets'] = [ + 'ON' => [ + 'glpi_groups_assets' => 'groups_id', + $from_table => 'id', + [ + 'AND' => [ + 'glpi_groups_assets.itemtype' => $from_referencetype, + 'glpi_groups_assets.type' => 0 + ] + ] + ] + ]; + } + if (!in_array($to_table_alias, $already_link_tables2, true)) { + $already_link_tables2[] = $to_table_alias; + $joins['LEFT JOIN'][$to_table_join_id] = [ + 'ON' => [ + 'glpi_groups_assets' => 'items_id', + $to_table_alias => 'id', + [ + 'AND' => $to_entity_restrict_criteria + $to_criteria + ] + ] + ]; + } + return $joins; + } + // Generic JOIN $from_obj = getItemForItemtype($from_referencetype); $from_item_obj = null; @@ -4030,6 +4120,11 @@ public static function constructSQL(array &$data) $replace, $tmpquery ); + $tmpquery = str_replace( + $CFG_GLPI["union_search_type"][$data['itemtype']] . '_TYPE', + $ctype, + $tmpquery + ); $tmpquery = str_replace( $CFG_GLPI["union_search_type"][$data['itemtype']], $ctable, diff --git a/src/Software.php b/src/Software.php index bdf574f3f9e8..0a800376580c 100644 --- a/src/Software.php +++ b/src/Software.php @@ -43,7 +43,11 @@ class Software extends CommonDBTM use Glpi\Features\Clonable; use Glpi\Features\TreeBrowse; use AssetImage; - use Glpi\Features\AssignableAsset; + use Glpi\Features\AssignableAsset { + prepareInputForAdd as prepareInputForAddAssignableAsset; + prepareInputForUpdate as prepareInputForUpdateAssignableAsset; + getEmpty as getEmptyAssignableAsset; + } // From CommonDBTM public $dohistory = true; @@ -126,7 +130,7 @@ public function prepareInputForUpdate($input) $input['softwares_id'] = 0; } $input = $this->managePictures($input); - return $input; + return $this->prepareInputForUpdateAssignableAsset($input); } public function prepareInputForAdd($input) @@ -143,7 +147,7 @@ public function prepareInputForAdd($input) $this->handleCategoryRules($input); $input = $this->managePictures($input); - return $input; + return $this->prepareInputForAddAssignableAsset($input); } public function cleanDBonPurge() @@ -211,6 +215,7 @@ public function getEmpty() /** @var array $CFG_GLPI */ global $CFG_GLPI; + $this->getEmptyAssignableAsset(); if (!parent::getEmpty()) { return false; } @@ -403,9 +408,20 @@ public function rawSearchOptions() 'id' => '49', 'table' => Group::getTable(), 'field' => 'completename', - 'linkfield' => 'groups_id_tech', - 'name' => __('Group in charge of the software'), + 'linkfield' => 'groups_id', + 'name' => __('Group in charge'), 'condition' => ['is_assign' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_TECH] + ] + ] + ], + 'forcegroupby' => true, + 'massiveaction' => false, 'datatype' => 'dropdown' ]; @@ -435,6 +451,17 @@ public function rawSearchOptions() 'field' => 'completename', 'name' => Group::getTypeName(1), 'condition' => ['is_itemgroup' => 1], + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_groups_assets', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.type' => self::GROUP_TYPE_NORMAL] + ] + ] + ], + 'forcegroupby' => true, + 'massiveaction' => false, 'datatype' => 'dropdown' ]; diff --git a/src/Toolbox.php b/src/Toolbox.php index d2f5d45ef1f3..f10d0825dcce 100644 --- a/src/Toolbox.php +++ b/src/Toolbox.php @@ -3081,7 +3081,7 @@ public static function isValidWebUrl($url): bool * This function checks the class itself and all parent classes for the trait. * @since 10.0.0 * @param string|object $class The class or object - * @param string $trait The trait + * @param class-string $trait The trait * @return bool True if the class or its parents have the specified trait */ public static function hasTrait($class, string $trait): bool diff --git a/templates/generic_show_form.html.twig b/templates/generic_show_form.html.twig index 66f899fe9ab6..05af644b1649 100644 --- a/templates/generic_show_form.html.twig +++ b/templates/generic_show_form.html.twig @@ -399,7 +399,7 @@ ) }} {% endif %} - {% if item.isField('groups_id_tech') %} + {% if item.isField('groups_id_tech') or (item is usingtrait('Glpi\\Features\\AssignableAsset') and item.GROUP_TYPE_TECH in item.groupTypes()) %} {{ fields.dropdownField( 'Group', 'groups_id_tech', @@ -408,6 +408,7 @@ field_options|merge({ 'entity': item.fields['entities_id'], 'condition': {'is_assign': 1}, + 'multiple': item is usingtrait('Glpi\\Features\\AssignableAsset') ? true : false }) ) }} {% endif %} @@ -541,7 +542,7 @@ ) }} {% endif %} - {% if item.isField('groups_id') %} + {% if item.isField('groups_id') or (item is usingtrait('Glpi\\Features\\AssignableAsset') and item.GROUP_TYPE_NORMAL in item.groupTypes()) %} {{ fields.dropdownField( 'Group', 'groups_id', @@ -550,6 +551,7 @@ field_options|merge({ 'entity': item.fields['entities_id'], 'condition': {'is_itemgroup': 1}, + 'multiple': item is usingtrait('Glpi\\Features\\AssignableAsset') ? true : false }) ) }} {% endif %} diff --git a/tests/DbTestCase.php b/tests/DbTestCase.php index c101baffd279..f51b401ef6b1 100644 --- a/tests/DbTestCase.php +++ b/tests/DbTestCase.php @@ -124,11 +124,13 @@ protected function checkInput(CommonDBTM $object, $id = 0, $input = []) if (count($input)) { foreach ($input as $k => $v) { + $obj_var = var_export($object->fields[$k], true); + $input_var = var_export($v, true); $this->variable($object->fields[$k])->isEqualTo( $v, " - '$k' key current value '{$object->fields[$k]}' (" . gettype($object->fields[$k]) . ") - is not equal to '$v' (" . gettype($v) . ")" + '$k' key current value '{$obj_var}' (" . gettype($object->fields[$k]) . ") + is not equal to '$input_var' (" . gettype($v) . ")" ); } } diff --git a/tests/functional/CartridgeItem.php b/tests/functional/CartridgeItem.php new file mode 100644 index 000000000000..8c2afbe0ea1e --- /dev/null +++ b/tests/functional/CartridgeItem.php @@ -0,0 +1,134 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace tests\units; + +use DbTestCase; + +class CartridgeItem extends DbTestCase +{ + /** + * Test adding an asset with the groups_id and groups_id_tech fields as an array and null. + * Test updating an asset with the groups_id and groups_id_tech fields as an array and null. + * @return void + */ + public function testAddAndUpdateMultipleGroups() + { + $cartridgeitem = $this->createItem(\CartridgeItem::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id_tech' => [3, 4], + ], ['groups_id_tech']); + $cartridgeitems_id_1 = $cartridgeitem->fields['id']; + $this->array($cartridgeitem->fields['groups_id_tech'])->containsValues([3, 4]); + + $cartridgeitem = $this->createItem(\CartridgeItem::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id_tech' => null, + ], ['groups_id_tech']); + $cartridgeitems_id_2 = $cartridgeitem->fields['id']; + $this->array($cartridgeitem->fields['groups_id_tech'])->isEmpty(); + + // Update both assets. Asset 1 will have the groups set to null and asset 2 will have the groups set to an array. + $cartridgeitem->getFromDB($cartridgeitems_id_1); + $this->boolean($cartridgeitem->update([ + 'id' => $cartridgeitems_id_1, + 'groups_id_tech' => null, + ]))->isTrue(); + $this->array($cartridgeitem->fields['groups_id_tech'])->isEmpty(); + + $cartridgeitem->getFromDB($cartridgeitems_id_2); + $this->boolean($cartridgeitem->update([ + 'id' => $cartridgeitems_id_2, + 'groups_id_tech' => [7, 8], + ]))->isTrue(); + $this->array($cartridgeitem->fields['groups_id_tech'])->containsValues([7, 8]); + + // Test updating array to array + $this->boolean($cartridgeitem->update([ + 'id' => $cartridgeitems_id_2, + 'groups_id_tech' => [3, 4], + ]))->isTrue(); + $this->array($cartridgeitem->fields['groups_id_tech'])->containsValues([3, 4]); + } + + /** + * Test the loading asset which still have integer values for groups_id and groups_id_tech (0 for no group). + * The value should be automatically normalized to an array. If the group was '0', the array should be empty. + * @return void + */ + public function testLoadOldItemsSingleGroup() + { + /** @var \DBmysql $DB */ + global $DB; + $cartridgeitem = $this->createItem(\CartridgeItem::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + ]); + $cartridgeitems_id = $cartridgeitem->fields['id']; + + // Manually set the groups_id_tech field to an integer value. + // The update migration should move all the groups to the new table directly for performance reasons (no changes to array, etc) + $DB->delete('glpi_groups_assets', [ + 'itemtype' => 'CartridgeItem', + 'items_id' => $cartridgeitems_id, + ]); + $DB->insert( + 'glpi_groups_assets', + [ + 'itemtype' => 'CartridgeItem', + 'items_id' => $cartridgeitems_id, + 'groups_id' => 2, + 'type' => 1 // Tech + ], + ); + $cartridgeitem->getFromDB($cartridgeitems_id); + $this->array($cartridgeitem->fields['groups_id_tech']) + ->hasSize(1) + ->containsValues([2]); + } + + /** + * An empty asset object should have the groups_id and groups_id_tech fields initialized as an empty array. + * @return void + */ + public function testGetEmptyMultipleGroups() + { + $cartridgeitem = new \CartridgeItem(); + $cartridgeitem->getEmpty(); + $this->array($cartridgeitem->fields['groups_id_tech'])->isEmpty(); + } +} diff --git a/tests/functional/Computer.php b/tests/functional/Computer.php index 8efd50befbd8..25753dde387f 100644 --- a/tests/functional/Computer.php +++ b/tests/functional/Computer.php @@ -114,10 +114,15 @@ public function testUpdate() $this->boolean($printer->getFromDB($printer->getID()))->isTrue(); unset($in['id']); foreach ($in as $k => $v) { - // Check the computer new values - $this->variable($computer->getField($k))->isEqualTo($v); - // Check the printer and test propagation occurs - $this->variable($printer->getField($k))->isEqualTo($v, $k); + $expected = $v; + if (in_array($k, ['groups_id', 'groups_id_tech'], true)) { + // These fields are transformed into arrays + $expected = [$v]; + } + // Check the computer new values + $this->variable($computer->getField($k))->isEqualTo($expected); + // Check the printer and test propagation occurs + $this->variable($printer->getField($k))->isEqualTo($expected, $k); } //reset values @@ -134,10 +139,15 @@ public function testUpdate() $this->boolean($printer->getFromDB($printer->getID()))->isTrue(); unset($in['id']); foreach ($in as $k => $v) { - // Check the computer new values - $this->variable($computer->getField($k))->isEqualTo($v); - // Check the printer and test propagation occurs - $this->variable($printer->getField($k))->isEqualTo($v); + $expected = $v; + if (in_array($k, ['groups_id', 'groups_id_tech'], true)) { + // These fields are transformed into arrays + $expected = $v === 0 ? [] : [$v]; + } + // Check the computer new values + $this->variable($computer->getField($k))->isEqualTo($expected); + // Check the printer and test propagation occurs + $this->variable($printer->getField($k))->isEqualTo($expected); } // Change the computer again @@ -163,10 +173,17 @@ public function testUpdate() $this->boolean($printer->getFromDB($printer->getID()))->isTrue(); unset($in2['id']); foreach ($in2 as $k => $v) { + $expected = $v; + $old_value = $in[$k]; + if (in_array($k, ['groups_id', 'groups_id_tech'], true)) { + // These fields are transformed into arrays + $expected = $v === 0 ? [] : [$v]; + $old_value = $old_value === 0 ? [] : [$old_value]; + } // Check the computer new values - $this->variable($computer->getField($k))->isEqualTo($v); + $this->variable($computer->getField($k))->isEqualTo($expected); // Check the printer and test propagation DOES NOT occurs - $this->variable($printer->getField($k))->isEqualTo($in[$k]); + $this->variable($printer->getField($k))->isEqualTo($old_value); } // Restore configuration @@ -211,10 +228,15 @@ public function testUpdate() $this->boolean($link->getFromDB($link->getID()))->isTrue(); unset($in['id']); foreach ($in as $k => $v) { - // Check the computer new values - $this->variable($computer->getField($k))->isEqualTo($v); - // Check the printer and test propagation occurs - $this->variable($link->getField($k))->isEqualTo($v); + $expected = $v; + if (in_array($k, ['groups_id', 'groups_id_tech'], true)) { + // These fields are transformed into arrays + $expected = [$v]; + } + // Check the computer new values + $this->variable($computer->getField($k))->isEqualTo($expected); + // Check the printer and test propagation occurs + $this->variable($link->getField($k))->isEqualTo($expected); } //reset @@ -248,10 +270,17 @@ public function testUpdate() $this->boolean($link->getFromDB($link->getID()))->isTrue(); unset($in2['id']); foreach ($in2 as $k => $v) { - // Check the computer new values - $this->variable($computer->getField($k))->isEqualTo($v); - // Check the printer and test propagation DOES NOT occurs - $this->variable($link->getField($k))->isEqualTo($in[$k]); + $old_value = $in[$k]; + $expected = $v; + if (in_array($k, ['groups_id', 'groups_id_tech'], true)) { + // These fields are transformed into arrays + $expected = [$v]; + $old_value = [$old_value]; + } + // Check the computer new values + $this->variable($computer->getField($k))->isEqualTo($expected); + // Check the printer and test propagation DOES NOT occurs + $this->variable($link->getField($k))->isEqualTo($old_value); } } @@ -312,10 +341,15 @@ public function testCreateLinks() $this->boolean($printer->getFromDB($printer->getID()))->isTrue(); unset($in['id']); foreach ($in as $k => $v) { - // Check the computer new values - $this->variable($computer->getField($k))->isEqualTo($v); - // Check the printer and test propagation occurs - $this->variable($printer->getField($k))->isEqualTo($v); + $expected = $v; + if (in_array($k, ['groups_id', 'groups_id_tech'], true)) { + // These fields are transformed into arrays + $expected = [$v]; + } + // Check the computer new values + $this->variable($computer->getField($k))->isEqualTo($expected); + // Check the printer and test propagation occurs + $this->variable($printer->getField($k))->isEqualTo($expected); } //create devices @@ -900,4 +934,140 @@ public function testFormatSessionMessageAfterAction( $message = $item->formatSessionMessageAfterAction($raw_message); $this->string($message)->isEqualTo($expected_formatted_message); } + + /** + * Test adding an asset with the groups_id and groups_id_tech fields as an array and null. + * Test updating an asset with the groups_id and groups_id_tech fields as an array and null. + * @return void + */ + public function testAddAndUpdateMultipleGroups() + { + $computer = $this->createItem(\Computer::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id' => [1, 2], + 'groups_id_tech' => [3, 4], + ], ['groups_id', 'groups_id_tech']); + $computers_id_1 = $computer->fields['id']; + $this->array($computer->fields['groups_id'])->containsValues([1, 2]); + $this->array($computer->fields['groups_id_tech'])->containsValues([3, 4]); + + $computer = $this->createItem(\Computer::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id' => null, + 'groups_id_tech' => null, + ], ['groups_id', 'groups_id_tech']); + $computers_id_2 = $computer->fields['id']; + $this->array($computer->fields['groups_id'])->isEmpty(); + $this->array($computer->fields['groups_id_tech'])->isEmpty(); + + // Update both assets. Asset 1 will have the groups set to null and asset 2 will have the groups set to an array. + $computer->getFromDB($computers_id_1); + $this->boolean($computer->update([ + 'id' => $computers_id_1, + 'groups_id' => null, + 'groups_id_tech' => null, + ]))->isTrue(); + $this->array($computer->fields['groups_id'])->isEmpty(); + $this->array($computer->fields['groups_id_tech'])->isEmpty(); + + $computer->getFromDB($computers_id_2); + $this->boolean($computer->update([ + 'id' => $computers_id_2, + 'groups_id' => [5, 6], + 'groups_id_tech' => [7, 8], + ]))->isTrue(); + $this->array($computer->fields['groups_id'])->containsValues([5, 6]); + $this->array($computer->fields['groups_id_tech'])->containsValues([7, 8]); + + // Test updating array to array + $this->boolean($computer->update([ + 'id' => $computers_id_2, + 'groups_id' => [1, 2], + 'groups_id_tech' => [3, 4], + ]))->isTrue(); + $this->array($computer->fields['groups_id'])->containsValues([1, 2]); + $this->array($computer->fields['groups_id_tech'])->containsValues([3, 4]); + } + + /** + * Test the loading of assets which still have integer values for groups_id and groups_id_tech (0 for no group). + * The value should be automatically normalized to an array. If the group was '0', the array should be empty. + * @return void + */ + public function testLoadOldItemsSingleGroup() + { + /** @var \DBmysql $DB */ + global $DB; + $computer = $this->createItem(\Computer::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + ]); + $computers_id = $computer->fields['id']; + + // Manually set the groups_id and groups_id_tech fields to an integer value. + // The update migration should mvoe all the groups to the new table directly for performance reasons (no changes to array, etc) + $DB->delete('glpi_groups_assets', [ + 'itemtype' => 'Computer', + 'items_id' => $computers_id, + ]); + $DB->insert( + 'glpi_groups_assets', + [ + 'itemtype' => 'Computer', + 'items_id' => $computers_id, + 'groups_id' => 1, + 'type' => 0 // Normal + ], + ); + $DB->insert( + 'glpi_groups_assets', + [ + 'itemtype' => 'Computer', + 'items_id' => $computers_id, + 'groups_id' => 2, + 'type' => 1 // Tech + ], + ); + $computer->getFromDB($computers_id); + $this->array($computer->fields['groups_id'])->containsValues([1]); + $this->array($computer->fields['groups_id_tech'])->containsValues([2]); + } + + /** + * An empty asset object should have the groups_id and groups_id_tech fields initialized as an empty array. + * @return void + */ + public function testGetEmptyMultipleGroups() + { + $computer = new \Computer(); + $computer->getEmpty(); + $this->array($computer->fields['groups_id'])->isEmpty(); + $this->array($computer->fields['groups_id_tech'])->isEmpty(); + } + + /** + * Check that adding a computer with groups_id and groups_id_tech as an integer still works (minor BC, mainly for API scripts). + * @return void + */ + public function testAddWithIntGroups() + { + $computer = new \Computer(); + $this->integer($computers_id = $computer->add([ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id' => 1, + 'groups_id_tech' => 2, + ]))->isGreaterThan(0); + // Completely reset computer instance + $computer = new \Computer(); + $computer->getFromDB($computers_id); + $this->array($computer->fields['groups_id']) + ->hasSize(1) + ->containsValues([1]); + $this->array($computer->fields['groups_id_tech']) + ->hasSize(1) + ->containsValues([2]); + } } diff --git a/tests/functional/ConsumableItem.php b/tests/functional/ConsumableItem.php new file mode 100644 index 000000000000..863b14a02a48 --- /dev/null +++ b/tests/functional/ConsumableItem.php @@ -0,0 +1,135 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace tests\units; + +use DbTestCase; + +class ConsumableItem extends DbTestCase +{ + /** + * Test adding an asset with the groups_id and groups_id_tech fields as an array and null. + * Test updating an asset with the groups_id and groups_id_tech fields as an array and null. + * @return void + */ + public function testAddAndUpdateMultipleGroups() + { + $consumableitem = $this->createItem(\ConsumableItem::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id_tech' => [3, 4], + ], ['groups_id_tech']); + $consumableitems_id_1 = $consumableitem->fields['id']; + $this->array($consumableitem->fields['groups_id_tech'])->containsValues([3, 4]); + + $consumableitem = $this->createItem(\ConsumableItem::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id_tech' => null, + ], ['groups_id_tech']); + $consumableitems_id_2 = $consumableitem->fields['id']; + $this->array($consumableitem->fields['groups_id_tech'])->isEmpty(); + + // Update both assets. Asset 1 will have the groups set to null and asset 2 will have the groups set to an array. + $consumableitem->getFromDB($consumableitems_id_1); + $this->boolean($consumableitem->update([ + 'id' => $consumableitems_id_1, + 'groups_id_tech' => null, + ]))->isTrue(); + $this->array($consumableitem->fields['groups_id_tech'])->isEmpty(); + + $consumableitem->getFromDB($consumableitems_id_2); + $this->boolean($consumableitem->update([ + 'id' => $consumableitems_id_2, + 'groups_id_tech' => [7, 8], + ]))->isTrue(); + $this->array($consumableitem->fields['groups_id_tech'])->containsValues([7, 8]); + + // Test updating array to array + $this->boolean($consumableitem->update([ + 'id' => $consumableitems_id_2, + 'groups_id_tech' => [3, 4], + ]))->isTrue(); + $this->array($consumableitem->fields['groups_id_tech'])->containsValues([3, 4]); + } + + /** + * Test the loading asset which still have integer values for groups_id and groups_id_tech (0 for no group). + * The value should be automatically normalized to an array. If the group was '0', the array should be empty. + * @return void + */ + public function testLoadOldItemsSingleGroup() + { + /** @var \DBmysql $DB */ + global $DB; + $consumableitem = $this->createItem(\ConsumableItem::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + ]); + $consumableitems_id = $consumableitem->fields['id']; + + // Manually set the groups_id_tech field to an integer value + // The update migration should move all the groups to the new table directly for performance reasons (no changes to array, etc) + $DB->delete('glpi_groups_assets', [ + 'itemtype' => 'ConsumableItem', + 'items_id' => $consumableitems_id, + ]); + $DB->insert( + 'glpi_groups_assets', + [ + 'itemtype' => 'ConsumableItem', + 'items_id' => $consumableitems_id, + 'groups_id' => 2, + 'type' => 1 // Tech + ], + ); + $consumableitem->getFromDB($consumableitems_id); + + $this->array($consumableitem->fields['groups_id_tech']) + ->hasSize(1) + ->containsValues([2]); + } + + /** + * An empty asset object should have the groups_id and groups_id_tech fields initialized as an empty array. + * @return void + */ + public function testGetEmptyMultipleGroups() + { + $consumableitem = new \ConsumableItem(); + $consumableitem->getEmpty(); + $this->array($consumableitem->fields['groups_id_tech'])->isEmpty(); + } +} diff --git a/tests/functional/Glpi/Inventory/Inventory.php b/tests/functional/Glpi/Inventory/Inventory.php index 33ed8cc75d9a..b8969ece573a 100644 --- a/tests/functional/Glpi/Inventory/Inventory.php +++ b/tests/functional/Glpi/Inventory/Inventory.php @@ -82,7 +82,6 @@ private function checkComputer1($computers_id) 'contact' => 'trasher/root', 'contact_num' => null, 'users_id_tech' => 0, - 'groups_id_tech' => 0, 'comment' => null, 'date_mod' => $computer->fields['date_mod'], 'autoupdatesystems_id' => $autoupdatesystems_id, @@ -96,7 +95,6 @@ private function checkComputer1($computers_id) 'is_deleted' => 0, 'is_dynamic' => 1, 'users_id' => 0, - 'groups_id' => 0, 'states_id' => 0, 'ticket_tco' => '0.0000', 'uuid' => '4c4c4544-0034-3010-8048-b6c04f503732', @@ -104,6 +102,8 @@ private function checkComputer1($computers_id) 'is_recursive' => 0, 'last_inventory_update' => $computer->fields['last_inventory_update'], 'last_boot' => '2020-06-09 07:58:08', + 'groups_id' => [], + 'groups_id_tech' => [], ]; $this->array($computer->fields)->isIdenticalTo($expected); @@ -167,7 +167,6 @@ private function checkComputer1($computers_id) 'contact' => 'trasher/root', 'contact_num' => null, 'users_id_tech' => 0, - 'groups_id_tech' => 0, 'comment' => null, 'serial' => 'ABH55D', 'otherserial' => null, @@ -189,13 +188,14 @@ private function checkComputer1($computers_id) 'is_template' => 0, 'template_name' => null, 'users_id' => 0, - 'groups_id' => 0, 'states_id' => 0, 'ticket_tco' => '0.0000', 'is_dynamic' => 1, 'autoupdatesystems_id' => 0, 'uuid' => null, - 'is_recursive' => 0 + 'is_recursive' => 0, + 'groups_id' => [], + 'groups_id_tech' => [], ]; $this->array($monitor_fields)->isIdenticalTo($expected); @@ -868,7 +868,6 @@ private function checkComputer1($computers_id) 'contact' => 'trasher/root', 'contact_num' => null, 'users_id_tech' => 0, - 'groups_id_tech' => 0, 'serial' => 'MY47L1W1JHEB6', 'otherserial' => null, 'have_serial' => 0, @@ -890,7 +889,6 @@ private function checkComputer1($computers_id) 'init_pages_counter' => 0, 'last_pages_counter' => 0, 'users_id' => 0, - 'groups_id' => 0, 'states_id' => 0, 'ticket_tco' => '0.0000', 'is_dynamic' => 1, @@ -898,7 +896,9 @@ private function checkComputer1($computers_id) 'sysdescr' => null, 'last_inventory_update' => $_SESSION['glpi_currenttime'], 'snmpcredentials_id' => 0, - 'autoupdatesystems_id' => $autoupdatesystems_id + 'autoupdatesystems_id' => $autoupdatesystems_id, + 'groups_id' => [], + 'groups_id_tech' => [], ]; $this->array($printer_fields)->isIdenticalTo($expected); @@ -1277,7 +1277,6 @@ public function testUpdateComputer() 'contact' => 'johan', 'contact_num' => null, 'users_id_tech' => 0, - 'groups_id_tech' => 0, 'comment' => null, 'date_mod' => $computer->fields['date_mod'], 'autoupdatesystems_id' => $autoupdatesystems_id, @@ -1291,7 +1290,6 @@ public function testUpdateComputer() 'is_deleted' => 0, 'is_dynamic' => 1, 'users_id' => 0, - 'groups_id' => 0, 'states_id' => 0, 'ticket_tco' => '0.0000', 'uuid' => '0055ADC9-1D3A-E411-8043-B05D95113232', @@ -1299,6 +1297,8 @@ public function testUpdateComputer() 'is_recursive' => 0, 'last_inventory_update' => $computer->fields['last_inventory_update'], 'last_boot' => "2017-02-20 08:11:53", + 'groups_id' => [], + 'groups_id_tech' => [], ]; $this->array($computer->fields)->isIdenticalTo($expected); @@ -1497,7 +1497,6 @@ public function testUpdateComputer() 'contact' => 'johan', 'contact_num' => null, 'users_id_tech' => 0, - 'groups_id_tech' => 0, 'comment' => null, 'date_mod' => $computer->fields['date_mod'], 'autoupdatesystems_id' => $autoupdatesystems_id, @@ -1511,7 +1510,6 @@ public function testUpdateComputer() 'is_deleted' => 0, 'is_dynamic' => 1, 'users_id' => 0, - 'groups_id' => 0, 'states_id' => 0, 'ticket_tco' => '0.0000', 'uuid' => '0055ADC9-1D3A-E411-8043-B05D95113232', @@ -1519,6 +1517,8 @@ public function testUpdateComputer() 'is_recursive' => 0, 'last_inventory_update' => $computer->fields['last_inventory_update'], 'last_boot' => "2017-02-20 08:11:53", + 'groups_id' => [], + 'groups_id_tech' => [], ]; $this->array($computer->fields)->isIdenticalTo($expected); @@ -1665,7 +1665,6 @@ public function testUpdateComputer() 'contact' => 'johan', 'contact_num' => null, 'users_id_tech' => 0, - 'groups_id_tech' => 0, 'comment' => null, 'date_mod' => $computer->fields['date_mod'], 'autoupdatesystems_id' => $autoupdatesystems_id, @@ -1679,7 +1678,6 @@ public function testUpdateComputer() 'is_deleted' => 0, 'is_dynamic' => 1, 'users_id' => 0, - 'groups_id' => 0, 'states_id' => 0, 'ticket_tco' => '0.0000', 'uuid' => '0055ADC9-1D3A-E411-8043-B05D95113232', @@ -1687,6 +1685,8 @@ public function testUpdateComputer() 'is_recursive' => 0, 'last_inventory_update' => $computer->fields['last_inventory_update'], 'last_boot' => "2017-06-08 07:06:47", + 'groups_id' => [], + 'groups_id_tech' => [], ]; $this->array($computer->fields)->isIdenticalTo($expected); @@ -1934,7 +1934,6 @@ public function testImportNetworkEquipment() 'contact' => 'noc@glpi-project.org', 'contact_num' => null, 'users_id_tech' => 0, - 'groups_id_tech' => 0, 'date_mod' => $equipment->fields['date_mod'], 'comment' => null, 'locations_id' => $locations_id, @@ -1946,7 +1945,6 @@ public function testImportNetworkEquipment() 'is_template' => 0, 'template_name' => null, 'users_id' => 0, - 'groups_id' => 0, 'states_id' => 0, 'ticket_tco' => '0.0000', 'is_dynamic' => 1, @@ -1958,6 +1956,8 @@ public function testImportNetworkEquipment() 'uptime' => '482 days, 05:42:18.50', 'last_inventory_update' => $date_now, 'snmpcredentials_id' => 4, + 'groups_id' => [], + 'groups_id_tech' => [], ]; $this->array($equipment->fields)->isIdenticalTo($expected); @@ -2277,7 +2277,6 @@ public function testImportStackedNetworkEquipment() 'contact' => null, 'contact_num' => null, 'users_id_tech' => 0, - 'groups_id_tech' => 0, 'date_mod' => null, 'comment' => null, 'locations_id' => $locations_id, @@ -2289,7 +2288,6 @@ public function testImportStackedNetworkEquipment() 'is_template' => 0, 'template_name' => null, 'users_id' => 0, - 'groups_id' => 0, 'states_id' => 0, 'ticket_tco' => '0.0000', 'is_dynamic' => 1, @@ -2762,7 +2760,6 @@ public function testImportNetworkEquipmentMultiConnections() 'contact' => null, 'contact_num' => null, 'users_id_tech' => 0, - 'groups_id_tech' => 0, 'date_mod' => null, 'comment' => null, 'locations_id' => $locations_id, @@ -2774,7 +2771,6 @@ public function testImportNetworkEquipmentMultiConnections() 'is_template' => 0, 'template_name' => null, 'users_id' => 0, - 'groups_id' => 0, 'states_id' => 0, 'ticket_tco' => '0.0000', 'is_dynamic' => 1, @@ -3439,7 +3435,6 @@ public function testImportNetworkEquipmentWireless() 'contact' => null, 'contact_num' => null, 'users_id_tech' => 0, - 'groups_id_tech' => 0, 'date_mod' => null, 'comment' => null, 'locations_id' => $locations_id, @@ -3451,7 +3446,6 @@ public function testImportNetworkEquipmentWireless() 'is_template' => 0, 'template_name' => null, 'users_id' => 0, - 'groups_id' => 0, 'states_id' => 0, 'ticket_tco' => '0.0000', 'is_dynamic' => 1, @@ -3818,7 +3812,6 @@ public function testImportNetworkEquipmentWAggregatedPorts() 'contact' => 'noc@glpi-project.org', 'contact_num' => null, 'users_id_tech' => 0, - 'groups_id_tech' => 0, 'date_mod' => $equipment->fields['date_mod'], 'comment' => null, 'locations_id' => $locations_id, @@ -3830,7 +3823,6 @@ public function testImportNetworkEquipmentWAggregatedPorts() 'is_template' => 0, 'template_name' => null, 'users_id' => 0, - 'groups_id' => 0, 'states_id' => 0, 'ticket_tco' => '0.0000', 'is_dynamic' => 1, @@ -3842,6 +3834,8 @@ public function testImportNetworkEquipmentWAggregatedPorts() 'uptime' => '65 days, 20:13:08.93', 'last_inventory_update' => $date_now, 'snmpcredentials_id' => 0, + 'groups_id' => [], + 'groups_id_tech' => [], ]; $this->array($equipment->fields)->isIdenticalTo($expected); @@ -5182,7 +5176,6 @@ public function testImportPhone() 'contact' => 'builder', 'contact_num' => null, 'users_id_tech' => 0, - 'groups_id_tech' => 0, 'comment' => null, 'serial' => 'af8d8fcfa6fa4794', 'otherserial' => 'release-keys', @@ -5200,7 +5193,6 @@ public function testImportPhone() 'is_template' => 0, 'template_name' => null, 'users_id' => 0, - 'groups_id' => 0, 'states_id' => 0, 'ticket_tco' => '0.0000', 'is_dynamic' => 1, @@ -5209,6 +5201,8 @@ public function testImportPhone() 'date_creation' => $phone->fields['date_creation'], 'is_recursive' => 0, 'last_inventory_update' => $phone->fields['last_inventory_update'], + 'groups_id' => [], + 'groups_id_tech' => [], ]; $this->array($phone->fields)->isIdenticalTo($expected); @@ -5536,7 +5530,7 @@ public function testImportPhone() 'locations_id' => 0, 'lines_id' => 0, 'users_id' => 0, - 'groups_id' => 0, + 'groups_id' => [], 'pin' => '', 'pin2' => '', 'puk' => '', diff --git a/tests/functional/Link.php b/tests/functional/Link.php index e6200dc2fbab..5f6284e95184 100644 --- a/tests/functional/Link.php +++ b/tests/functional/Link.php @@ -65,7 +65,8 @@ protected function linkContentProvider(): iterable 'networks_id' => $network->getID(), 'users_id' => getItemByTypeName(\User::class, 'glpi', true), 'computermodels_id' => getItemByTypeName(\ComputerModel::class, '_test_computermodel_1', true), - ] + ], + ['groups_id'] ); // Attach domains diff --git a/tests/functional/Monitor.php b/tests/functional/Monitor.php index 55ac7b8c2bf4..81765a0cf621 100644 --- a/tests/functional/Monitor.php +++ b/tests/functional/Monitor.php @@ -51,7 +51,6 @@ private static function getMonitorFields($id, $date) 'contact' => null, 'contact_num' => null, 'users_id_tech' => 0, - 'groups_id_tech' => 0, 'comment' => null, 'serial' => null, 'otherserial' => null, @@ -73,7 +72,6 @@ private static function getMonitorFields($id, $date) 'is_template' => 0, 'template_name' => null, 'users_id' => 0, - 'groups_id' => 0, 'states_id' => 0, 'ticket_tco' => '0.0000', 'is_dynamic' => 0, @@ -81,6 +79,8 @@ private static function getMonitorFields($id, $date) 'date_creation' => $date, 'is_recursive' => 0, 'uuid' => null, + 'groups_id' => [], + 'groups_id_tech' => [], ]; } @@ -133,4 +133,120 @@ public function testClone() $this->array($clonedMonitor->fields)->isEqualTo($expected); } + + /** + * Test adding an asset with the groups_id and groups_id_tech fields as an array and null. + * Test updating an asset with the groups_id and groups_id_tech fields as an array and null. + * @return void + */ + public function testAddAndUpdateMultipleGroups() + { + $monitor = $this->createItem(\Monitor::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id' => [1, 2], + 'groups_id_tech' => [3, 4], + ], ['groups_id', 'groups_id_tech']); + $monitors_id_1 = $monitor->fields['id']; + $this->array($monitor->fields['groups_id'])->containsValues([1, 2]); + $this->array($monitor->fields['groups_id_tech'])->containsValues([3, 4]); + + $monitor = $this->createItem(\Monitor::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id' => null, + 'groups_id_tech' => null, + ], ['groups_id', 'groups_id_tech']); + $monitors_id_2 = $monitor->fields['id']; + $this->array($monitor->fields['groups_id'])->isEmpty(); + $this->array($monitor->fields['groups_id_tech'])->isEmpty(); + + // Update both assets. Asset 1 will have the groups set to null and asset 2 will have the groups set to an array. + $monitor->getFromDB($monitors_id_1); + $this->boolean($monitor->update([ + 'id' => $monitors_id_1, + 'groups_id' => null, + 'groups_id_tech' => null, + ]))->isTrue(); + $this->array($monitor->fields['groups_id'])->isEmpty(); + $this->array($monitor->fields['groups_id_tech'])->isEmpty(); + + $monitor->getFromDB($monitors_id_2); + $this->boolean($monitor->update([ + 'id' => $monitors_id_2, + 'groups_id' => [5, 6], + 'groups_id_tech' => [7, 8], + ]))->isTrue(); + $this->array($monitor->fields['groups_id'])->containsValues([5, 6]); + $this->array($monitor->fields['groups_id_tech'])->containsValues([7, 8]); + + // Test updating array to array + $this->boolean($monitor->update([ + 'id' => $monitors_id_2, + 'groups_id' => [1, 2], + 'groups_id_tech' => [3, 4], + ]))->isTrue(); + $this->array($monitor->fields['groups_id'])->containsValues([1, 2]); + $this->array($monitor->fields['groups_id_tech'])->containsValues([3, 4]); + } + + /** + * Test the loading assets which still have integer values for groups_id and groups_id_tech (0 for no group). + * The value should be automatically normalized to an array. If the group was '0', the array should be empty. + * @return void + */ + public function testLoadOldItemsSingleGroup() + { + /** @var \DBmysql $DB */ + global $DB; + $monitor = $this->createItem(\Monitor::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + ]); + $monitors_id = $monitor->fields['id']; + + // Manually set the groups_id and groups_id_tech fields to an integer value + // The update migration should mvoe all the groups to the new table directly for performance reasons (no changes to array, etc) + $DB->delete('glpi_groups_assets', [ + 'itemtype' => 'Monitor', + 'items_id' => $monitors_id, + ]); + $DB->insert( + 'glpi_groups_assets', + [ + 'itemtype' => 'Monitor', + 'items_id' => $monitors_id, + 'groups_id' => 1, + 'type' => 0 // Normal + ], + ); + $DB->insert( + 'glpi_groups_assets', + [ + 'itemtype' => 'Monitor', + 'items_id' => $monitors_id, + 'groups_id' => 2, + 'type' => 1 // Tech + ], + ); + $monitor->getFromDB($monitors_id); + $this->array($monitor->fields['groups_id']) + ->hasSize(1) + ->containsValues([1]); + $this->array($monitor->fields['groups_id_tech']) + ->hasSize(1) + ->containsValues([2]); + } + + /** + * An empty asset object should have the groups_id and groups_id_tech fields initialized as an empty array. + * @return void + */ + public function testGetEmptyMultipleGroups() + { + $monitor = new \Monitor(); + $monitor->getEmpty(); + $this->array($monitor->fields['groups_id'])->isEmpty(); + $this->array($monitor->fields['groups_id_tech'])->isEmpty(); + } } diff --git a/tests/functional/NetworkEquipment.php b/tests/functional/NetworkEquipment.php index e1e072ad8e33..a93d75116489 100644 --- a/tests/functional/NetworkEquipment.php +++ b/tests/functional/NetworkEquipment.php @@ -95,4 +95,120 @@ public function testNetEquipmentCRUD() //see if links are dropped $this->integer($netport->countForItem($device))->isIdenticalTo(0); } + + /** + * Test adding an asset with the groups_id and groups_id_tech fields as an array and null. + * Test updating an asset with the groups_id and groups_id_tech fields as an array and null. + * @return void + */ + public function testAddAndUpdateMultipleGroups() + { + $networkequipment = $this->createItem(\NetworkEquipment::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id' => [1, 2], + 'groups_id_tech' => [3, 4], + ], ['groups_id', 'groups_id_tech']); + $networkequipments_id_1 = $networkequipment->fields['id']; + $this->array($networkequipment->fields['groups_id'])->containsValues([1, 2]); + $this->array($networkequipment->fields['groups_id_tech'])->containsValues([3, 4]); + + $networkequipment = $this->createItem(\NetworkEquipment::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id' => null, + 'groups_id_tech' => null, + ], ['groups_id', 'groups_id_tech']); + $networkequipments_id_2 = $networkequipment->fields['id']; + $this->array($networkequipment->fields['groups_id'])->isEmpty(); + $this->array($networkequipment->fields['groups_id_tech'])->isEmpty(); + + // Update both assets. Asset 1 will have the groups set to null and asset 2 will have the groups set to an array. + $networkequipment->getFromDB($networkequipments_id_1); + $this->boolean($networkequipment->update([ + 'id' => $networkequipments_id_1, + 'groups_id' => null, + 'groups_id_tech' => null, + ]))->isTrue(); + $this->array($networkequipment->fields['groups_id'])->isEmpty(); + $this->array($networkequipment->fields['groups_id_tech'])->isEmpty(); + + $networkequipment->getFromDB($networkequipments_id_2); + $this->boolean($networkequipment->update([ + 'id' => $networkequipments_id_2, + 'groups_id' => [5, 6], + 'groups_id_tech' => [7, 8], + ]))->isTrue(); + $this->array($networkequipment->fields['groups_id'])->containsValues([5, 6]); + $this->array($networkequipment->fields['groups_id_tech'])->containsValues([7, 8]); + + // Test updating array to array + $this->boolean($networkequipment->update([ + 'id' => $networkequipments_id_2, + 'groups_id' => [1, 2], + 'groups_id_tech' => [3, 4], + ]))->isTrue(); + $this->array($networkequipment->fields['groups_id'])->containsValues([1, 2]); + $this->array($networkequipment->fields['groups_id_tech'])->containsValues([3, 4]); + } + + /** + * Test the loading asset which still have integer values for groups_id and groups_id_tech (0 for no group). + * The value should be automatically normalized to an array. If the group was '0', the array should be empty. + * @return void + */ + public function testLoadOldItemsSingleGroup() + { + /** @var \DBmysql $DB */ + global $DB; + $networkequipment = $this->createItem(\NetworkEquipment::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + ]); + $networkequipments_id = $networkequipment->fields['id']; + + // Manually set the groups_id and groups_id_tech fields to an integer value + // The update migration should mvoe all the groups to the new table directly for performance reasons (no changes to array, etc) + $DB->delete('glpi_groups_assets', [ + 'itemtype' => 'NetworkEquipment', + 'items_id' => $networkequipments_id, + ]); + $DB->insert( + 'glpi_groups_assets', + [ + 'itemtype' => 'NetworkEquipment', + 'items_id' => $networkequipments_id, + 'groups_id' => 1, + 'type' => 0 // Normal + ], + ); + $DB->insert( + 'glpi_groups_assets', + [ + 'itemtype' => 'NetworkEquipment', + 'items_id' => $networkequipments_id, + 'groups_id' => 2, + 'type' => 1 // Tech + ], + ); + $networkequipment->getFromDB($networkequipments_id); + $this->array($networkequipment->fields['groups_id']) + ->hasSize(1) + ->containsValues([1]); + $this->array($networkequipment->fields['groups_id_tech']) + ->hasSize(1) + ->containsValues([2]); + } + + /** + * An empty asset object should have the groups_id and groups_id_tech fields initialized as an empty array. + * @return void + */ + public function testGetEmptyMultipleGroups() + { + $networkequipment = new \NetworkEquipment(); + $networkequipment->getEmpty(); + $this->array($networkequipment->fields['groups_id'])->isEmpty(); + $this->array($networkequipment->fields['groups_id_tech'])->isEmpty(); + } } diff --git a/tests/functional/Peripheral.php b/tests/functional/Peripheral.php new file mode 100644 index 000000000000..ece612439025 --- /dev/null +++ b/tests/functional/Peripheral.php @@ -0,0 +1,157 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace tests\units; + +use DbTestCase; + +class Peripheral extends DbTestCase +{ + /** + * Test adding an asset with the groups_id and groups_id_tech fields as an array and null. + * Test updating an asset with the groups_id and groups_id_tech fields as an array and null. + * @return void + */ + public function testAddAndUpdateMultipleGroups() + { + $peripheral = $this->createItem(\Peripheral::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id' => [1, 2], + 'groups_id_tech' => [3, 4], + ], ['groups_id', 'groups_id_tech']); + $peripherals_id_1 = $peripheral->fields['id']; + $this->array($peripheral->fields['groups_id'])->containsValues([1, 2]); + $this->array($peripheral->fields['groups_id_tech'])->containsValues([3, 4]); + + $peripheral = $this->createItem(\Peripheral::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id' => null, + 'groups_id_tech' => null, + ], ['groups_id', 'groups_id_tech']); + $peripherals_id_2 = $peripheral->fields['id']; + $this->array($peripheral->fields['groups_id'])->isEmpty(); + $this->array($peripheral->fields['groups_id_tech'])->isEmpty(); + + // Update both assets. Asset 1 will have the groups set to null and asset 2 will have the groups set to an array. + $peripheral->getFromDB($peripherals_id_1); + $this->boolean($peripheral->update([ + 'id' => $peripherals_id_1, + 'groups_id' => null, + 'groups_id_tech' => null, + ]))->isTrue(); + $this->array($peripheral->fields['groups_id'])->isEmpty(); + $this->array($peripheral->fields['groups_id_tech'])->isEmpty(); + + $peripheral->getFromDB($peripherals_id_2); + $this->boolean($peripheral->update([ + 'id' => $peripherals_id_2, + 'groups_id' => [5, 6], + 'groups_id_tech' => [7, 8], + ]))->isTrue(); + $this->array($peripheral->fields['groups_id'])->containsValues([5, 6]); + $this->array($peripheral->fields['groups_id_tech'])->containsValues([7, 8]); + + // Test updating array to array + $this->boolean($peripheral->update([ + 'id' => $peripherals_id_2, + 'groups_id' => [1, 2], + 'groups_id_tech' => [3, 4], + ]))->isTrue(); + $this->array($peripheral->fields['groups_id'])->containsValues([1, 2]); + $this->array($peripheral->fields['groups_id_tech'])->containsValues([3, 4]); + } + + /** + * Test the loading asset which still have integer values for groups_id and groups_id_tech (0 for no group). + * The value should be automatically normalized to an array. If the group was '0', the array should be empty. + * @return void + */ + public function testLoadOldItemsSingleGroup() + { + /** @var \DBmysql $DB */ + global $DB; + $peripheral = $this->createItem(\Peripheral::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + ]); + $peripherals_id = $peripheral->fields['id']; + + // Manually set the groups_id and groups_id_tech fields to an integer value + // The update migration should mvoe all the groups to the new table directly for performance reasons (no changes to array, etc) + $DB->delete('glpi_groups_assets', [ + 'itemtype' => 'Peripheral', + 'items_id' => $peripherals_id, + ]); + $DB->insert( + 'glpi_groups_assets', + [ + 'itemtype' => 'Peripheral', + 'items_id' => $peripherals_id, + 'groups_id' => 1, + 'type' => 0 // Normal + ], + ); + $DB->insert( + 'glpi_groups_assets', + [ + 'itemtype' => 'Peripheral', + 'items_id' => $peripherals_id, + 'groups_id' => 2, + 'type' => 1 // Tech + ], + ); + $peripheral->getFromDB($peripherals_id); + $this->array($peripheral->fields['groups_id']) + ->hasSize(1) + ->containsValues([1]); + $this->array($peripheral->fields['groups_id_tech']) + ->hasSize(1) + ->containsValues([2]); + } + + /** + * An empty asset object should have the groups_id and groups_id_tech fields initialized as an empty array. + * @return void + */ + public function testGetEmptyMultipleGroups() + { + $peripheral = new \Peripheral(); + $peripheral->getEmpty(); + $this->array($peripheral->fields['groups_id'])->isEmpty(); + $this->array($peripheral->fields['groups_id_tech'])->isEmpty(); + } +} diff --git a/tests/functional/Phone.php b/tests/functional/Phone.php new file mode 100644 index 000000000000..6c56996cfb06 --- /dev/null +++ b/tests/functional/Phone.php @@ -0,0 +1,157 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace tests\units; + +use DbTestCase; + +class Phone extends DbTestCase +{ + /** + * Test adding an asset with the groups_id and groups_id_tech fields as an array and null. + * Test updating an asset with the groups_id and groups_id_tech fields as an array and null. + * @return void + */ + public function testAddAndUpdateMultipleGroups() + { + $phone = $this->createItem(\Phone::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id' => [1, 2], + 'groups_id_tech' => [3, 4], + ], ['groups_id', 'groups_id_tech']); + $phones_id_1 = $phone->fields['id']; + $this->array($phone->fields['groups_id'])->containsValues([1, 2]); + $this->array($phone->fields['groups_id_tech'])->containsValues([3, 4]); + + $phone = $this->createItem(\Phone::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id' => null, + 'groups_id_tech' => null, + ], ['groups_id', 'groups_id_tech']); + $phones_id_2 = $phone->fields['id']; + $this->array($phone->fields['groups_id'])->isEmpty(); + $this->array($phone->fields['groups_id_tech'])->isEmpty(); + + // Update both assets. Asset 1 will have the groups set to null and asset 2 will have the groups set to an array. + $phone->getFromDB($phones_id_1); + $this->boolean($phone->update([ + 'id' => $phones_id_1, + 'groups_id' => null, + 'groups_id_tech' => null, + ]))->isTrue(); + $this->array($phone->fields['groups_id'])->isEmpty(); + $this->array($phone->fields['groups_id_tech'])->isEmpty(); + + $phone->getFromDB($phones_id_2); + $this->boolean($phone->update([ + 'id' => $phones_id_2, + 'groups_id' => [5, 6], + 'groups_id_tech' => [7, 8], + ]))->isTrue(); + $this->array($phone->fields['groups_id'])->containsValues([5, 6]); + $this->array($phone->fields['groups_id_tech'])->containsValues([7, 8]); + + // Test updating array to array + $this->boolean($phone->update([ + 'id' => $phones_id_2, + 'groups_id' => [1, 2], + 'groups_id_tech' => [3, 4], + ]))->isTrue(); + $this->array($phone->fields['groups_id'])->containsValues([1, 2]); + $this->array($phone->fields['groups_id_tech'])->containsValues([3, 4]); + } + + /** + * Test the loading asset which still have integer values for groups_id and groups_id_tech (0 for no group). + * The value should be automatically normalized to an array. If the group was '0', the array should be empty. + * @return void + */ + public function testLoadOldItemsSingleGroup() + { + /** @var \DBmysql $DB */ + global $DB; + $phone = $this->createItem(\Phone::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + ]); + $phones_id = $phone->fields['id']; + + // Manually set the groups_id and groups_id_tech fields to an integer value + // The update migration should mvoe all the groups to the new table directly for performance reasons (no changes to array, etc) + $DB->delete('glpi_groups_assets', [ + 'itemtype' => 'Phone', + 'items_id' => $phones_id, + ]); + $DB->insert( + 'glpi_groups_assets', + [ + 'itemtype' => 'Phone', + 'items_id' => $phones_id, + 'groups_id' => 1, + 'type' => 0 // Normal + ], + ); + $DB->insert( + 'glpi_groups_assets', + [ + 'itemtype' => 'Phone', + 'items_id' => $phones_id, + 'groups_id' => 2, + 'type' => 1 // Tech + ], + ); + $phone->getFromDB($phones_id); + $this->array($phone->fields['groups_id']) + ->hasSize(1) + ->containsValues([1]); + $this->array($phone->fields['groups_id_tech']) + ->hasSize(1) + ->containsValues([2]); + } + + /** + * An empty asset object should have the groups_id and groups_id_tech fields initialized as an empty array. + * @return void + */ + public function testGetEmptyMultipleGroups() + { + $phone = new \Phone(); + $phone->getEmpty(); + $this->array($phone->fields['groups_id'])->isEmpty(); + $this->array($phone->fields['groups_id_tech'])->isEmpty(); + } +} diff --git a/tests/functional/Printer.php b/tests/functional/Printer.php index 2b214dbbec30..f847249e72ee 100644 --- a/tests/functional/Printer.php +++ b/tests/functional/Printer.php @@ -167,4 +167,120 @@ public function testDeleteByCriteria() $nb_after = (int)countElementsInTable('glpi_logs', ['itemtype' => 'Printer', 'items_id' => $id]); $this->integer($nb_after)->isidenticalTo($nb_before + 1); } + + /** + * Test adding an asset with the groups_id and groups_id_tech fields as an array and null. + * Test updating an asset with the groups_id and groups_id_tech fields as an array and null. + * @return void + */ + public function testAddAndUpdateMultipleGroups() + { + $printer = $this->createItem(\Printer::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id' => [1, 2], + 'groups_id_tech' => [3, 4], + ], ['groups_id', 'groups_id_tech']); + $printers_id_1 = $printer->fields['id']; + $this->array($printer->fields['groups_id'])->containsValues([1, 2]); + $this->array($printer->fields['groups_id_tech'])->containsValues([3, 4]); + + $printer = $this->createItem(\Printer::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id' => null, + 'groups_id_tech' => null, + ], ['groups_id', 'groups_id_tech']); + $printers_id_2 = $printer->fields['id']; + $this->array($printer->fields['groups_id'])->isEmpty(); + $this->array($printer->fields['groups_id_tech'])->isEmpty(); + + // Update both assets. Asset 1 will have the groups set to null and asset 2 will have the groups set to an array. + $printer->getFromDB($printers_id_1); + $this->boolean($printer->update([ + 'id' => $printers_id_1, + 'groups_id' => null, + 'groups_id_tech' => null, + ]))->isTrue(); + $this->array($printer->fields['groups_id'])->isEmpty(); + $this->array($printer->fields['groups_id_tech'])->isEmpty(); + + $printer->getFromDB($printers_id_2); + $this->boolean($printer->update([ + 'id' => $printers_id_2, + 'groups_id' => [5, 6], + 'groups_id_tech' => [7, 8], + ]))->isTrue(); + $this->array($printer->fields['groups_id'])->containsValues([5, 6]); + $this->array($printer->fields['groups_id_tech'])->containsValues([7, 8]); + + // Test updating array to array + $this->boolean($printer->update([ + 'id' => $printers_id_2, + 'groups_id' => [1, 2], + 'groups_id_tech' => [3, 4], + ]))->isTrue(); + $this->array($printer->fields['groups_id'])->containsValues([1, 2]); + $this->array($printer->fields['groups_id_tech'])->containsValues([3, 4]); + } + + /** + * Test the loading asset which still have integer values for groups_id and groups_id_tech (0 for no group). + * The value should be automatically normalized to an array. If the group was '0', the array should be empty. + * @return void + */ + public function testLoadOldItemsSingleGroup() + { + /** @var \DBmysql $DB */ + global $DB; + $printer = $this->createItem(\Printer::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + ]); + $printers_id = $printer->fields['id']; + + // Manually set the groups_id and groups_id_tech fields to an integer value + // The update migration should mvoe all the groups to the new table directly for performance reasons (no changes to array, etc) + $DB->delete('glpi_groups_assets', [ + 'itemtype' => 'Printer', + 'items_id' => $printers_id, + ]); + $DB->insert( + 'glpi_groups_assets', + [ + 'itemtype' => 'Printer', + 'items_id' => $printers_id, + 'groups_id' => 1, + 'type' => 0 // Normal + ], + ); + $DB->insert( + 'glpi_groups_assets', + [ + 'itemtype' => 'Printer', + 'items_id' => $printers_id, + 'groups_id' => 2, + 'type' => 1 // Tech + ], + ); + $printer->getFromDB($printers_id); + $this->array($printer->fields['groups_id']) + ->hasSize(1) + ->containsValues([1]); + $this->array($printer->fields['groups_id_tech']) + ->hasSize(1) + ->containsValues([2]); + } + + /** + * An empty asset object should have the groups_id and groups_id_tech fields initialized as an empty array. + * @return void + */ + public function testGetEmptyMultipleGroups() + { + $printer = new \Printer(); + $printer->getEmpty(); + $this->array($printer->fields['groups_id'])->isEmpty(); + $this->array($printer->fields['groups_id_tech'])->isEmpty(); + } } diff --git a/tests/functional/Search.php b/tests/functional/Search.php index 841806dd393c..7fab9d855d79 100644 --- a/tests/functional/Search.php +++ b/tests/functional/Search.php @@ -43,6 +43,7 @@ use Document; use Document_Item; use Glpi\Asset\Capacity\HasDocumentsCapacity; +use Glpi\Features\AssignableAsset; use Group_User; use Psr\Log\LogLevel; use Ticket; @@ -807,10 +808,12 @@ public function testIsNotifyComputerGroup() $search_params = ['is_deleted' => 0, 'start' => 0, 'search' => 'Search', - 'criteria' => [0 => ['field' => 'view', - 'searchtype' => 'contains', - 'value' => '' - ] + 'criteria' => [ + 0 => [ + 'field' => 'view', + 'searchtype' => 'contains', + 'value' => '' + ] ], // group is_notify 'metacriteria' => [0 => ['link' => 'AND', @@ -1721,6 +1724,10 @@ public function testAllAssetsFields() $table = getTableForItemType($itemtype); foreach ($needed_fields as $field) { + if (\Toolbox::hasTrait($itemtype, AssignableAsset::class) && str_starts_with($field, 'groups_id')) { + // The group fields don't exist in the main table for these assets + continue; + } $this->boolean($DB->fieldExists($table, $field)) ->isTrue("$table.$field is missing"); } diff --git a/tests/functional/Software.php b/tests/functional/Software.php index eee9e4f2a953..077bbeb6d454 100644 --- a/tests/functional/Software.php +++ b/tests/functional/Software.php @@ -436,4 +436,120 @@ public function testGetSearchOptionsNew() $result = $software->rawSearchOptions(); $this->array($result)->hasSize(61); } + + /** + * Test adding an asset with the groups_id and groups_id_tech fields as an array and null. + * Test updating an asset with the groups_id and groups_id_tech fields as an array and null. + * @return void + */ + public function testAddAndUpdateMultipleGroups() + { + $software = $this->createItem(\Software::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id' => [1, 2], + 'groups_id_tech' => [3, 4], + ], ['groups_id', 'groups_id_tech']); + $softwares_id_1 = $software->fields['id']; + $this->array($software->fields['groups_id'])->containsValues([1, 2]); + $this->array($software->fields['groups_id_tech'])->containsValues([3, 4]); + + $software = $this->createItem(\Software::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + 'groups_id' => null, + 'groups_id_tech' => null, + ], ['groups_id', 'groups_id_tech']); + $softwares_id_2 = $software->fields['id']; + $this->array($software->fields['groups_id'])->isEmpty(); + $this->array($software->fields['groups_id_tech'])->isEmpty(); + + // Update both assets. Asset 1 will have the groups set to null and asset 2 will have the groups set to an array. + $software->getFromDB($softwares_id_1); + $this->boolean($software->update([ + 'id' => $softwares_id_1, + 'groups_id' => null, + 'groups_id_tech' => null, + ]))->isTrue(); + $this->array($software->fields['groups_id'])->isEmpty(); + $this->array($software->fields['groups_id_tech'])->isEmpty(); + + $software->getFromDB($softwares_id_2); + $this->boolean($software->update([ + 'id' => $softwares_id_2, + 'groups_id' => [5, 6], + 'groups_id_tech' => [7, 8], + ]))->isTrue(); + $this->array($software->fields['groups_id'])->containsValues([5, 6]); + $this->array($software->fields['groups_id_tech'])->containsValues([7, 8]); + + // Test updating array to array + $this->boolean($software->update([ + 'id' => $softwares_id_2, + 'groups_id' => [1, 2], + 'groups_id_tech' => [3, 4], + ]))->isTrue(); + $this->array($software->fields['groups_id'])->containsValues([1, 2]); + $this->array($software->fields['groups_id_tech'])->containsValues([3, 4]); + } + + /** + * Test the loading asset which still have integer values for groups_id and groups_id_tech (0 for no group). + * The value should be automatically normalized to an array. If the group was '0', the array should be empty. + * @return void + */ + public function testLoadOldItemsSingleGroup() + { + /** @var \DBmysql $DB */ + global $DB; + $software = $this->createItem(\Software::class, [ + 'name' => __FUNCTION__, + 'entities_id' => $this->getTestRootEntity(true), + ]); + $softwares_id = $software->fields['id']; + + // Manually set the groups_id and groups_id_tech fields to an integer value + // The update migration should mvoe all the groups to the new table directly for performance reasons (no changes to array, etc) + $DB->delete('glpi_groups_assets', [ + 'itemtype' => 'Software', + 'items_id' => $softwares_id, + ]); + $DB->insert( + 'glpi_groups_assets', + [ + 'itemtype' => 'Software', + 'items_id' => $softwares_id, + 'groups_id' => 1, + 'type' => 0 // Normal + ], + ); + $DB->insert( + 'glpi_groups_assets', + [ + 'itemtype' => 'Software', + 'items_id' => $softwares_id, + 'groups_id' => 2, + 'type' => 1 // Tech + ], + ); + $software->getFromDB($softwares_id); + $this->array($software->fields['groups_id']) + ->hasSize(1) + ->containsValues([1]); + $this->array($software->fields['groups_id_tech']) + ->hasSize(1) + ->containsValues([2]); + } + + /** + * An empty asset object should have the groups_id and groups_id_tech fields initialized as an empty array. + * @return void + */ + public function testGetEmptyMultipleGroups() + { + $software = new \Software(); + $software->getEmpty(); + $this->array($software->fields['groups_id'])->isEmpty(); + $this->array($software->fields['groups_id_tech'])->isEmpty(); + } } diff --git a/tests/units/DB.php b/tests/units/DB.php index fe65b57e5c02..3f2b93742bae 100644 --- a/tests/units/DB.php +++ b/tests/units/DB.php @@ -397,7 +397,7 @@ public function testTablesHasItemtype() $excluded_tables = [ 'glpi_assets_assets', 'glpi_assets_assetmodels', 'glpi_assets_assettypes', 'glpi_appliancerelations', 'glpi_oauth_access_tokens', 'glpi_oauth_auth_codes', - 'glpi_oauth_refresh_tokens', 'glpi_stencils', + 'glpi_oauth_refresh_tokens', 'glpi_stencils', 'glpi_groups_assets' ]; //check if each table has a corresponding itemtype diff --git a/tests/units/RuleAsset.php b/tests/units/RuleAsset.php index 2eff6fe4059e..e9e935bfd145 100644 --- a/tests/units/RuleAsset.php +++ b/tests/units/RuleAsset.php @@ -418,7 +418,7 @@ public function testGroupUserAssignFromDefaultUser() ]); $this->integer($computers_id)->isGreaterThan(0); $this->boolean($computer->getFromDB($computers_id))->isTrue(); - $this->integer($computer->getField('groups_id'))->isEqualTo($user->getField('groups_id')); + $this->array($computer->getField('groups_id'))->isEqualTo([$user->getField('groups_id')]); } public function testFirstGroupUserAssignFromUser() @@ -500,6 +500,6 @@ public function testFirstGroupUserAssignFromUser() ]); $this->integer($computers_id)->isGreaterThan(0); $this->boolean($computer->getFromDB($computers_id))->isTrue(); - $this->integer($computer->getField('groups_id'))->isEqualTo($group_id); + $this->array($computer->getField('groups_id'))->isEqualTo([$group_id]); } }