From bdcf5238920ef9ab2b606413540dbd638d607fc2 Mon Sep 17 00:00:00 2001 From: Lainow Date: Wed, 27 May 2026 14:47:17 +0200 Subject: [PATCH 01/15] Change config UI --- src/Config.php | 107 +++++++----- src/Controller.php | 22 --- templates/config.html.twig | 337 ++++++------------------------------- 3 files changed, 113 insertions(+), 353 deletions(-) diff --git a/src/Config.php b/src/Config.php index 8e900ca..adf9377 100644 --- a/src/Config.php +++ b/src/Config.php @@ -47,6 +47,7 @@ class Config extends CommonDBTM { public $dohistory = true; public static $rightname = 'config'; + public const CONFIG_PARENT = \Entity::CONFIG_PARENT; public static function getMenuName(): string { return __('More options', 'moreoptions'); @@ -105,10 +106,8 @@ public static function preItemUpdate(CommonDBTM $item): CommonDBTM } foreach (self::getItilConfigFields() as $field) { - if (!isset($item->input[$field])) { - $item->input[$field] = 0; - } elseif ($item->input[$field] == 'on') { - $item->input[$field] = 1; + if (isset($item->input[$field])) { + $item->input[$field] = (int) $item->input[$field]; } } @@ -121,7 +120,6 @@ public static function preItemUpdate(CommonDBTM $item): CommonDBTM public static function getItilConfigFields(): array { return [ - 'use_parent_entity', 'take_item_group_ticket', 'take_item_group_change', 'take_item_group_problem', @@ -153,6 +151,12 @@ public static function getItilConfigFields(): array 'mandatory_task_duration', 'mandatory_task_user', 'mandatory_task_group', + 'take_requester_group_ticket', + 'take_requester_group_change', + 'take_requester_group_problem', + 'take_technician_group_ticket', + 'take_technician_group_change', + 'take_technician_group_problem', ]; } @@ -175,24 +179,31 @@ public static function showForEntity(Entity $item): void 'entities_id' => $item->getID(), ]); - // Get effective configuration to show which entity's config is actually used - $effectiveConfig = self::getConfig($item->getID(), true); - $parentEntityInfo = null; - - if (($moconfig->fields['use_parent_entity'] ?? false) && ($effectiveConfig->fields['entities_id'] != $item->getID())) { - $parentEntity = new Entity(); - if ($parentEntity->getFromDB($effectiveConfig->fields['entities_id'])) { - $parentEntityInfo = $parentEntity->getName(); + $inheritance_labels = []; + if ($item->getID() > 0) { + $parentConfig = self::getConfig($item->fields['entities_id'], true); + foreach (self::getItilConfigFields() as $field) { + $inheritance_labels[$field] = self::getInheritedValueBadge($parentConfig->fields[$field] ?? 0); + } + foreach ([ + 'take_requester_group_ticket', + 'take_requester_group_change', + 'take_requester_group_problem', + 'take_technician_group_ticket', + 'take_technician_group_change', + 'take_technician_group_problem', + ] as $field) { + $inheritance_labels[$field] = self::getInheritedValueBadgeForActorGroup($parentConfig->fields[$field] ?? 0); } } TemplateRenderer::getInstance()->display( '@moreoptions/config.html.twig', [ - 'item' => $moconfig, - 'dropdown_options' => self::getSelectableActorGroup(), - 'parent_entity_info' => $parentEntityInfo, - 'params' => [ + 'item' => $moconfig, + 'dropdown_options' => self::getSelectableActorGroup(), + 'inheritance_labels' => $inheritance_labels, + 'params' => [ 'canedit' => true, ], ], @@ -204,11 +215,26 @@ public static function getIcon(): string return "ti ti-send"; } + private static function getInheritedValueBadge(mixed $value): string + { + $text = match ((int) $value) { + 1 => __('Yes'), + default => __('No'), + }; + return Entity::inheritedValue(htmlescape($text), false, false); + } + + private static function getInheritedValueBadgeForActorGroup(mixed $value): string + { + $options = self::getSelectableActorGroup(); + $text = $options[(int) $value] ?? __('No'); + return Entity::inheritedValue(htmlescape($text), false, false); + } + public static function addConfig(CommonDBTM $item): void { $moconfig = new self(); $moconfig->add([ - 'is_active' => 0, 'entities_id' => $item->getID(), ]); } @@ -222,7 +248,6 @@ public static function addConfig(CommonDBTM $item): void */ public static function getConfig(?int $entityId = null, bool $useInheritance = true): self { - // Use current entity if not specified if ($entityId === null) { $entityId = Session::getActiveEntity(); } @@ -232,12 +257,13 @@ public static function getConfig(?int $entityId = null, bool $useInheritance = t 'entities_id' => $entityId, ]); - // If inheritance is enabled, use_parent_entity is set, and we're not at root entity - if ($useInheritance && ($moconfig->fields['use_parent_entity'] ?? false) && $entityId > 0) { + if ($useInheritance && $entityId > 0) { $entity = new Entity(); if ($entity->getFromDB($entityId)) { - $parentId = $entity->fields['entities_id']; - return self::getConfig($parentId, true); + $parentConfig = self::getConfig($entity->fields['entities_id'], true); + foreach (self::getItilConfigFields() as $field) { + $moconfig->fields[$field] = $parentConfig->fields[$field] ?? 0; + } } } @@ -253,18 +279,16 @@ public static function install(Migration $migration): void $migration->displayMessage("Installing $table"); $query = "CREATE TABLE IF NOT EXISTS `$table` ( `id` int unsigned NOT NULL AUTO_INCREMENT, - `is_active` tinyint NOT NULL DEFAULT '1', `entities_id` int unsigned NOT NULL DEFAULT '0', - `use_parent_entity` tinyint NOT NULL DEFAULT '0', `take_item_group_ticket` tinyint NOT NULL DEFAULT '-2', `take_item_group_change` tinyint NOT NULL DEFAULT '0', `take_item_group_problem` tinyint NOT NULL DEFAULT '0', - `take_requester_group_ticket` int unsigned NOT NULL DEFAULT '0', - `take_requester_group_change` int unsigned NOT NULL DEFAULT '0', - `take_requester_group_problem` int unsigned NOT NULL DEFAULT '0', - `take_technician_group_ticket` int unsigned NOT NULL DEFAULT '0', - `take_technician_group_change` int unsigned NOT NULL DEFAULT '0', - `take_technician_group_problem` int unsigned NOT NULL DEFAULT '0', + `take_requester_group_ticket` tinyint NOT NULL DEFAULT '0', + `take_requester_group_change` tinyint NOT NULL DEFAULT '0', + `take_requester_group_problem` tinyint NOT NULL DEFAULT '0', + `take_technician_group_ticket` tinyint NOT NULL DEFAULT '0', + `take_technician_group_change` tinyint NOT NULL DEFAULT '0', + `take_technician_group_problem` tinyint NOT NULL DEFAULT '0', `prevent_closure_ticket` tinyint NOT NULL DEFAULT '0', `prevent_closure_change` tinyint NOT NULL DEFAULT '0', `prevent_closure_problem` tinyint NOT NULL DEFAULT '0', @@ -294,22 +318,23 @@ public static function install(Migration $migration): void `mandatory_task_user` tinyint NOT NULL DEFAULT '0', `mandatory_task_group` tinyint NOT NULL DEFAULT '0', PRIMARY KEY (`id`), - KEY `entities_id` (`entities_id`), - KEY `is_active` (`is_active`) + KEY `entities_id` (`entities_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; "; $DB->doQuery($query); } - // Migration: Add use_parent_entity column if it doesn't exist - if (!$DB->fieldExists($table, 'use_parent_entity')) { - $migration->displayMessage("Adding use_parent_entity field to $table"); - $migration->addField($table, 'use_parent_entity', 'tinyint', [ - 'after' => 'entities_id', - 'value' => 0, - 'nodefault' => false, - ]); + foreach ([ + 'take_requester_group_ticket', + 'take_requester_group_change', + 'take_requester_group_problem', + 'take_technician_group_ticket', + 'take_technician_group_change', + 'take_technician_group_problem', + ] as $field) { + $migration->changeField($table, $field, $field, 'tinyint', ['value' => 0]); } + $migration->executeMigration(); $entities = new Entity(); foreach ($entities->find() as $entity) { diff --git a/src/Controller.php b/src/Controller.php index f18da82..6ce8f6e 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -86,10 +86,6 @@ public static function useConfig(CommonDBTM $item): void } $moconfig = Config::getConfig(); - if ($moconfig->fields['is_active'] != 1) { - return; - } - switch ($item) { case $item instanceof Ticket_User: if ($item->fields['type'] == \CommonITILActor::REQUESTER) { @@ -132,9 +128,6 @@ public static function useConfig(CommonDBTM $item): void public static function addItemGroups(CommonDBTM $item): void { $conf = Config::getConfig(); - if ($conf->fields['is_active'] != 1) { - return; - } // Mapping of item types to their configuration fields and group classes $itemMappings = [ @@ -311,9 +304,6 @@ public static function beforeCloseITILObject(CommonDBTM $item): void public static function preventClosure(CommonDBTM $item): bool { $conf = Config::getConfig(); - if ($conf->fields['is_active'] != 1) { - return true; - } $tasks = []; @@ -349,9 +339,6 @@ public static function preventClosure(CommonDBTM $item): bool public static function requireFieldsToClose(CommonDBTM $item, bool $is_solution = false): bool { $conf = Config::getConfig(); - if ($conf->fields['is_active'] != 1) { - return true; - } $message = ''; $itemtype = get_class($item); @@ -447,9 +434,6 @@ public static function requireFieldsToClose(CommonDBTM $item, bool $is_solution public static function checkTaskRequirements(CommonDBTM $item): CommonDBTM { $conf = Config::getConfig(); - if ($conf->fields['is_active'] != 1) { - return $item; - } $message = ''; if ($conf->fields['mandatory_task_category'] == 1) { @@ -488,9 +472,6 @@ public static function checkTaskRequirements(CommonDBTM $item): CommonDBTM public static function updateItemActors(CommonITILObject $item): CommonITILObject { $conf = Config::getConfig(); - if ($conf->fields['is_active'] != 1) { - return $item; - } switch (get_class($item)) { case 'Ticket': @@ -557,9 +538,6 @@ public static function updateItemActors(CommonITILObject $item): CommonITILObjec public static function assignTechnicianFromTask(\CommonITILTask $item): void { $conf = Config::getConfig(Session::getActiveEntity()); - if ($conf->fields['is_active'] != 1) { - return; - } // Check if a technician is assigned to the task if (empty($item->fields['users_id_tech'])) { diff --git a/templates/config.html.twig b/templates/config.html.twig index 7d088c6..984a9b8 100644 --- a/templates/config.html.twig +++ b/templates/config.html.twig @@ -33,10 +33,13 @@ #} {% import 'components/form/fields_macros.html.twig' as fields %} -{% import 'components/alerts_macros.html.twig' as alerts %} {# Layout options for fields #} {% set field_options = {'label_class': 'col-8', 'input_class': 'col-4', 'field_class': 'col-6', 'full_width': false} %} +{% set inherit_option = item.fields['entities_id'] != 0 ? {(constant('Entity::CONFIG_PARENT')): __('Inheritance of the parent entity')} : {} %} +{% set yes_no_options = inherit_option + {0: __('No'), 1: __('Yes')} %} +{% set dropdown_options_with_inherit = inherit_option + dropdown_options %} +{% set cell_options = {'no_label': true, 'field_class': 'col-12'} %}
@@ -53,194 +56,110 @@ {{ __('Itil items') }}
- {{ fields.checkboxField( - 'is_active', - item.fields['is_active'], - __('Active on this entity', 'moreoptions') - ) }} - - {% if item.fields['entities_id'] != 0 %} - {{ fields.checkboxField( - 'use_parent_entity', - item.fields['use_parent_entity'], - __('Use parent entity configuration', 'moreoptions'), - {'helper': __('When enabled, this entity will inherit configuration from its parent entity', 'moreoptions')} - ) }} - - {% if parent_entity_info %} - {{ alerts.alert_info(__('Configuration is inherited from parent entity: %s', 'moreoptions')|format(parent_entity_info)) }} - {% endif %} - {% endif %} -
- +
- - - - - + + + - - - - + + + - - - - + + + - - - - + + + - - - - + + + - - - - + + +
  {{ __('Ticket') }} {{ __('Change') }} {{ __('Problem') }}{{ __('All') }}
{{ __('Take the group of associated item', 'moreoptions') }}{{ fields.dropdownArrayField('take_item_group_ticket', item.fields['take_item_group_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['take_item_group_ticket']|default(null)})) }}{{ fields.dropdownArrayField('take_item_group_change', item.fields['take_item_group_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['take_item_group_change']|default(null)})) }}{{ fields.dropdownArrayField('take_item_group_problem', item.fields['take_item_group_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['take_item_group_problem']|default(null)})) }}
{{ __('Take the requester group', 'moreoptions') }} - {{ fields.dropdownArrayField( - 'take_requester_group_ticket', - item.fields['take_requester_group_ticket'], - dropdown_options, - '', - field_options|merge({'field_class': 'col-12', 'no_label': true}) - ) }} - - {{ fields.dropdownArrayField( - 'take_requester_group_change', - item.fields['take_requester_group_change'], - dropdown_options, - '', - field_options|merge({'field_class': 'col-12', 'no_label': true}) - ) }} - - {{ fields.dropdownArrayField( - 'take_requester_group_problem', - item.fields['take_requester_group_problem'], - dropdown_options, - '', - field_options|merge({'field_class': 'col-12', 'no_label': true}) - ) }} - {{ fields.dropdownArrayField('take_requester_group_ticket', item.fields['take_requester_group_ticket'], dropdown_options_with_inherit, '', cell_options|merge({'add_field_html': inheritance_labels['take_requester_group_ticket']|default(null)})) }}{{ fields.dropdownArrayField('take_requester_group_change', item.fields['take_requester_group_change'], dropdown_options_with_inherit, '', cell_options|merge({'add_field_html': inheritance_labels['take_requester_group_change']|default(null)})) }}{{ fields.dropdownArrayField('take_requester_group_problem', item.fields['take_requester_group_problem'], dropdown_options_with_inherit, '', cell_options|merge({'add_field_html': inheritance_labels['take_requester_group_problem']|default(null)})) }}
{{ __('Take the technician group', 'moreoptions') }} - {{ fields.dropdownArrayField( - 'take_technician_group_ticket', - item.fields['take_technician_group_ticket'], - dropdown_options, - '', - field_options|merge({'field_class': 'col-12', 'no_label': true}) - ) }} - - {{ fields.dropdownArrayField( - 'take_technician_group_change', - item.fields['take_technician_group_change'], - dropdown_options, - '', - field_options|merge({'field_class': 'col-12', 'no_label': true}) - ) }} - - {{ fields.dropdownArrayField( - 'take_technician_group_problem', - item.fields['take_technician_group_problem'], - dropdown_options, - '', - field_options|merge({'field_class': 'col-12', 'no_label': true}) - ) }} - {{ fields.dropdownArrayField('take_technician_group_ticket', item.fields['take_technician_group_ticket'], dropdown_options_with_inherit, '', cell_options|merge({'add_field_html': inheritance_labels['take_technician_group_ticket']|default(null)})) }}{{ fields.dropdownArrayField('take_technician_group_change', item.fields['take_technician_group_change'], dropdown_options_with_inherit, '', cell_options|merge({'add_field_html': inheritance_labels['take_technician_group_change']|default(null)})) }}{{ fields.dropdownArrayField('take_technician_group_problem', item.fields['take_technician_group_problem'], dropdown_options_with_inherit, '', cell_options|merge({'add_field_html': inheritance_labels['take_technician_group_problem']|default(null)})) }}
{{ __('Prevent closure with tasks in To Do status', 'moreoptions') }}{{ fields.dropdownArrayField('prevent_closure_ticket', item.fields['prevent_closure_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['prevent_closure_ticket']|default(null)})) }}{{ fields.dropdownArrayField('prevent_closure_change', item.fields['prevent_closure_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['prevent_closure_change']|default(null)})) }}{{ fields.dropdownArrayField('prevent_closure_problem', item.fields['prevent_closure_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['prevent_closure_problem']|default(null)})) }}
{{ __('Assign technical manager when changing category', 'moreoptions') }}{{ fields.dropdownArrayField('assign_technical_manager_when_changing_category_ticket', item.fields['assign_technical_manager_when_changing_category_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technical_manager_when_changing_category_ticket']|default(null)})) }}{{ fields.dropdownArrayField('assign_technical_manager_when_changing_category_change', item.fields['assign_technical_manager_when_changing_category_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technical_manager_when_changing_category_change']|default(null)})) }}{{ fields.dropdownArrayField('assign_technical_manager_when_changing_category_problem', item.fields['assign_technical_manager_when_changing_category_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technical_manager_when_changing_category_problem']|default(null)})) }}
{{ __('Assign technical group when changing category', 'moreoptions') }}{{ fields.dropdownArrayField('assign_technical_group_when_changing_category_ticket', item.fields['assign_technical_group_when_changing_category_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technical_group_when_changing_category_ticket']|default(null)})) }}{{ fields.dropdownArrayField('assign_technical_group_when_changing_category_change', item.fields['assign_technical_group_when_changing_category_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technical_group_when_changing_category_change']|default(null)})) }}{{ fields.dropdownArrayField('assign_technical_group_when_changing_category_problem', item.fields['assign_technical_group_when_changing_category_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technical_group_when_changing_category_problem']|default(null)})) }}
- {{ __('Solve and Close Items') }} + {{ __('Mandatory fields to Solve and Close ITILs', 'moreoptions') }}
- {{ alerts.alert_danger(__('The options below relate to the mandatory fields for resolving and closing ITIL items.', 'moreoptions')) }} - - +
- - - - - + + + - - - - + + + - - - - + + + - - - - + + + - - - - + + +
  {{ __('Ticket') }} {{ __('Change') }} {{ __('Problem') }}{{ __('All') }}
{{ __('Technician') }}{{ fields.dropdownArrayField('require_technician_to_close_ticket', item.fields['require_technician_to_close_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_technician_to_close_ticket']|default(null)})) }}{{ fields.dropdownArrayField('require_technician_to_close_change', item.fields['require_technician_to_close_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_technician_to_close_change']|default(null)})) }}{{ fields.dropdownArrayField('require_technician_to_close_problem', item.fields['require_technician_to_close_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_technician_to_close_problem']|default(null)})) }}
{{ __('Technicians group') }}{{ fields.dropdownArrayField('require_technicians_group_to_close_ticket', item.fields['require_technicians_group_to_close_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_technicians_group_to_close_ticket']|default(null)})) }}{{ fields.dropdownArrayField('require_technicians_group_to_close_change', item.fields['require_technicians_group_to_close_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_technicians_group_to_close_change']|default(null)})) }}{{ fields.dropdownArrayField('require_technicians_group_to_close_problem', item.fields['require_technicians_group_to_close_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_technicians_group_to_close_problem']|default(null)})) }}
{{ __('Category') }}{{ fields.dropdownArrayField('require_category_to_close_ticket', item.fields['require_category_to_close_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_category_to_close_ticket']|default(null)})) }}{{ fields.dropdownArrayField('require_category_to_close_change', item.fields['require_category_to_close_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_category_to_close_change']|default(null)})) }}{{ fields.dropdownArrayField('require_category_to_close_problem', item.fields['require_category_to_close_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_category_to_close_problem']|default(null)})) }}
{{ __('Location') }}{{ fields.dropdownArrayField('require_location_to_close_ticket', item.fields['require_location_to_close_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_location_to_close_ticket']|default(null)})) }}{{ fields.dropdownArrayField('require_location_to_close_change', item.fields['require_location_to_close_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_location_to_close_change']|default(null)})) }}{{ fields.dropdownArrayField('require_location_to_close_problem', item.fields['require_location_to_close_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_location_to_close_problem']|default(null)})) }}
{{ __('Solution') }}{{ fields.dropdownArrayField('require_solution_to_close_ticket', item.fields['require_solution_to_close_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_solution_to_close_ticket']|default(null)})) }}{{ fields.dropdownArrayField('require_solution_to_close_change', item.fields['require_solution_to_close_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_solution_to_close_change']|default(null)})) }}{{ fields.dropdownArrayField('require_solution_to_close_problem', item.fields['require_solution_to_close_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_solution_to_close_problem']|default(null)})) }}
- {{ __('Tasks') }} + {{ __('Mandatory fields for Tasks creation', 'moreoptions') }}
- {{ alerts.alert_danger(__('The options below relate to the mandatory fields for creating a task.', 'moreoptions')) }} - - +
@@ -251,10 +170,10 @@ - - - - + + + +
{{ __('Category') }}
{{ fields.dropdownArrayField('mandatory_task_category', item.fields['mandatory_task_category'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['mandatory_task_category']|default(null)})) }}{{ fields.dropdownArrayField('mandatory_task_duration', item.fields['mandatory_task_duration'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['mandatory_task_duration']|default(null)})) }}{{ fields.dropdownArrayField('mandatory_task_user', item.fields['mandatory_task_user'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['mandatory_task_user']|default(null)})) }}{{ fields.dropdownArrayField('mandatory_task_group', item.fields['mandatory_task_group'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['mandatory_task_group']|default(null)})) }}
@@ -266,166 +185,4 @@
{{ include('components/form/buttons.html.twig') }} - - - \ No newline at end of file + \ No newline at end of file From cd2851c4a1fbcdf31e3e32405e531527e9c9ca65 Mon Sep 17 00:00:00 2001 From: Lainow Date: Wed, 27 May 2026 16:43:20 +0200 Subject: [PATCH 02/15] Fix tests and inheritance --- src/Config.php | 29 +++++++++---- tests/Units/ConfigTest.php | 84 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 8 deletions(-) diff --git a/src/Config.php b/src/Config.php index adf9377..52cac93 100644 --- a/src/Config.php +++ b/src/Config.php @@ -234,9 +234,14 @@ private static function getInheritedValueBadgeForActorGroup(mixed $value): strin public static function addConfig(CommonDBTM $item): void { $moconfig = new self(); - $moconfig->add([ - 'entities_id' => $item->getID(), - ]); + $entity_id = $item->getID(); + $data = ['entities_id' => $entity_id]; + if ($entity_id > 0) { + foreach (self::getItilConfigFields() as $field) { + $data[$field] = self::CONFIG_PARENT; + } + } + $moconfig->add($data); } /** @@ -262,7 +267,9 @@ public static function getConfig(?int $entityId = null, bool $useInheritance = t if ($entity->getFromDB($entityId)) { $parentConfig = self::getConfig($entity->fields['entities_id'], true); foreach (self::getItilConfigFields() as $field) { - $moconfig->fields[$field] = $parentConfig->fields[$field] ?? 0; + if (($moconfig->fields[$field] ?? 0) == self::CONFIG_PARENT) { + $moconfig->fields[$field] = $parentConfig->fields[$field] ?? 0; + } } } } @@ -334,14 +341,20 @@ public static function install(Migration $migration): void ] as $field) { $migration->changeField($table, $field, $field, 'tinyint', ['value' => 0]); } - $migration->executeMigration(); $entities = new Entity(); foreach ($entities->find() as $entity) { if (is_array($entity) && isset($entity['id'])) { - $data = [ - 'entities_id' => $entity['id'], - ]; + $entity_id = (int) $entity['id']; + if ($DB->numrows($DB->request(['FROM' => self::getTable(), 'WHERE' => ['entities_id' => $entity_id]])) > 0) { + continue; + } + $data = ['entities_id' => $entity_id]; + if ($entity_id > 0) { + foreach (self::getItilConfigFields() as $field) { + $data[$field] = self::CONFIG_PARENT; + } + } $DB->insert( self::getTable(), $data, diff --git a/tests/Units/ConfigTest.php b/tests/Units/ConfigTest.php index 824d39b..49cc775 100644 --- a/tests/Units/ConfigTest.php +++ b/tests/Units/ConfigTest.php @@ -1974,4 +1974,88 @@ public function testAssignTechnicianFromProblemTask(): void $this->assertEquals($tech_id, $assignedUser['users_id']); $this->assertEquals(\CommonITILActor::ASSIGN, $assignedUser['type']); } + + /** + * Test that CONFIG_PARENT values are resolved through multiple entity levels. + * + * Hierarchy: Root(0) → A → B → C + * + * Root : take_item_group_ticket=1, take_requester_group_ticket=2 + * A : take_item_group_ticket=CONFIG_PARENT, take_requester_group_ticket=1 (own) + * B : take_item_group_ticket=0 (own), take_requester_group_ticket=CONFIG_PARENT + * C : take_item_group_ticket=CONFIG_PARENT, take_requester_group_ticket=CONFIG_PARENT + * + * Expected resolved values: + * A effective: take_item_group_ticket=1 (from root), take_requester_group_ticket=1 (own) + * B effective: take_item_group_ticket=0 (own), take_requester_group_ticket=1 (from A) + * C effective: take_item_group_ticket=0 (from B), take_requester_group_ticket=1 (from B→A) + */ + public function testMultiLevelInheritanceResolvesConfigParentThroughChain(): void + { + $this->initEntitySession(); + + $entity_a = $this->createItem( + \Entity::class, + ['name' => 'Inheritance Test A', 'entities_id' => 0], + ['name'], + ); + $this->clearLogEntriesContaining('glpiactiveentities_string'); + + $entity_b = $this->createItem( + \Entity::class, + ['name' => 'Inheritance Test B', 'entities_id' => $entity_a->getID()], + ['name', 'entities_id'], + ); + $this->clearLogEntriesContaining('glpiactiveentities_string'); + + $entity_c = $this->createItem( + \Entity::class, + ['name' => 'Inheritance Test C', 'entities_id' => $entity_b->getID()], + ['name', 'entities_id'], + ); + $this->clearLogEntriesContaining('glpiactiveentities_string'); + + // Root: explicit values + $root_conf = Config::getConfig(0, false); + $this->updateItem(Config::class, $root_conf->getID(), [ + 'take_item_group_ticket' => 1, + 'take_requester_group_ticket' => 2, + ]); + + // A: inherit take_item_group_ticket from root, own take_requester_group_ticket=1 + $conf_a = Config::getConfig($entity_a->getID(), false); + $this->updateItem(Config::class, $conf_a->getID(), [ + 'take_item_group_ticket' => Config::CONFIG_PARENT, + 'take_requester_group_ticket' => 1, + ]); + + // B: own take_item_group_ticket=0, inherit take_requester_group_ticket from A + $conf_b = Config::getConfig($entity_b->getID(), false); + $this->updateItem(Config::class, $conf_b->getID(), [ + 'take_item_group_ticket' => 0, + 'take_requester_group_ticket' => Config::CONFIG_PARENT, + ]); + + // C: inherit everything + $conf_c = Config::getConfig($entity_c->getID(), false); + $this->updateItem(Config::class, $conf_c->getID(), [ + 'take_item_group_ticket' => Config::CONFIG_PARENT, + 'take_requester_group_ticket' => Config::CONFIG_PARENT, + ]); + + // Entity A: CONFIG_PARENT resolves to root's value + $resolved_a = Config::getConfig($entity_a->getID()); + $this->assertEquals(1, $resolved_a->fields['take_item_group_ticket'], 'A should inherit take_item_group_ticket=1 from root'); + $this->assertEquals(1, $resolved_a->fields['take_requester_group_ticket'], 'A should keep its own take_requester_group_ticket=1'); + + // Entity B: own value wins over parent, CONFIG_PARENT resolves through A + $resolved_b = Config::getConfig($entity_b->getID()); + $this->assertEquals(0, $resolved_b->fields['take_item_group_ticket'], 'B should keep its own take_item_group_ticket=0'); + $this->assertEquals(1, $resolved_b->fields['take_requester_group_ticket'], 'B should inherit take_requester_group_ticket=1 from A'); + + // Entity C: CONFIG_PARENT resolves to B's effective values (not root's) + $resolved_c = Config::getConfig($entity_c->getID()); + $this->assertEquals(0, $resolved_c->fields['take_item_group_ticket'], 'C should inherit take_item_group_ticket=0 from B (not root=1)'); + $this->assertEquals(1, $resolved_c->fields['take_requester_group_ticket'], 'C should inherit take_requester_group_ticket=1 through B→A'); + } } From 717346cfed32431a594682df6a93c2387be4f36e Mon Sep 17 00:00:00 2001 From: Lainow Date: Fri, 29 May 2026 10:45:20 +0200 Subject: [PATCH 03/15] Fix tests --- src/Config.php | 10 +++-- tests/Units/ConfigTest.php | 84 +++++++++----------------------------- 2 files changed, 26 insertions(+), 68 deletions(-) diff --git a/src/Config.php b/src/Config.php index 52cac93..3188ca7 100644 --- a/src/Config.php +++ b/src/Config.php @@ -265,7 +265,7 @@ public static function getConfig(?int $entityId = null, bool $useInheritance = t if ($useInheritance && $entityId > 0) { $entity = new Entity(); if ($entity->getFromDB($entityId)) { - $parentConfig = self::getConfig($entity->fields['entities_id'], true); + $parentConfig = self::getConfig((int) $entity->fields['entities_id'], true); foreach (self::getItilConfigFields() as $field) { if (($moconfig->fields[$field] ?? 0) == self::CONFIG_PARENT) { $moconfig->fields[$field] = $parentConfig->fields[$field] ?? 0; @@ -287,7 +287,7 @@ public static function install(Migration $migration): void $query = "CREATE TABLE IF NOT EXISTS `$table` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `entities_id` int unsigned NOT NULL DEFAULT '0', - `take_item_group_ticket` tinyint NOT NULL DEFAULT '-2', + `take_item_group_ticket` tinyint NOT NULL DEFAULT '0', `take_item_group_change` tinyint NOT NULL DEFAULT '0', `take_item_group_problem` tinyint NOT NULL DEFAULT '0', `take_requester_group_ticket` tinyint NOT NULL DEFAULT '0', @@ -339,9 +339,13 @@ public static function install(Migration $migration): void 'take_technician_group_change', 'take_technician_group_problem', ] as $field) { - $migration->changeField($table, $field, $field, 'tinyint', ['value' => 0]); + if ($DB->fieldExists($table, $field)) { + $migration->changeField($table, $field, $field, 'bool', ['value' => '0']); + } } + $migration->executeMigration(); + $entities = new Entity(); foreach ($entities->find() as $entity) { if (is_array($entity) && isset($entity['id'])) { diff --git a/tests/Units/ConfigTest.php b/tests/Units/ConfigTest.php index 49cc775..a379b62 100644 --- a/tests/Units/ConfigTest.php +++ b/tests/Units/ConfigTest.php @@ -49,7 +49,6 @@ public function testTaskMandatoryField(): void $conf = $this->getCurrentConfig(); $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, 'mandatory_task_category' => 1, 'mandatory_task_duration' => 1, @@ -189,7 +188,6 @@ public function testTicketMandatoryFieldsBeforeCloseTicket(): void // Configure mandatory fields before closing $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, 'require_technician_to_close_ticket' => 1, 'require_technicians_group_to_close_ticket' => 1, @@ -317,7 +315,6 @@ public function testCannotAddSolutionWhenMissingMandatoryFields(): void // Configure mandatory fields before closing (which impacts solutions too) $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, 'require_technician_to_close_ticket' => 1, 'require_category_to_close_ticket' => 1, @@ -405,7 +402,6 @@ public function testChangeMandatoryFieldsBeforeCloseChange(): void // Configure mandatory fields before closing $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, 'require_technician_to_close_change' => 1, 'require_technicians_group_to_close_change' => 1, @@ -534,7 +530,6 @@ public function testProblemMandatoryFieldsBeforeCloseProblem(): void // Configure mandatory fields before closing $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, 'require_technician_to_close_problem' => 1, 'require_technicians_group_to_close_problem' => 1, @@ -661,7 +656,6 @@ public function testTakeTheRequesterGroup(): void // Configure to take all groups of the requester $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, 'take_requester_group_ticket' => 2, // All ]); @@ -736,7 +730,6 @@ public function testTakeTheRequesterGroup(): void $config = new Config(); // Configurer pour ne prendre que le groupe principal du demandeur $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, 'take_requester_group_ticket' => 1, // Default ]); @@ -783,7 +776,6 @@ public function testTakeTheRequesterGroup(): void // Reset config // Réinitialiser la configuration $resetResult = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, 'take_requester_group_ticket' => 0, // Default ]); @@ -799,7 +791,6 @@ public function testTakeTheTechnicianGroup(): void // Setup to take all groups of the technician $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, 'take_technician_group_ticket' => 2, // All ]); @@ -873,7 +864,6 @@ public function testTakeTheTechnicianGroup(): void // Setup to take only the main group of the technician $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, 'take_technician_group_ticket' => 1, // Default ]); @@ -927,7 +917,6 @@ public function testTakeItemGroups(): void // Setup to take the groups of the items $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, 'take_item_group_ticket' => 1, ]); @@ -1006,7 +995,6 @@ public function testUpdateTicketActorsOnCategoryChange(): void // Configure to assign technical manager and group when changing category $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, 'assign_technical_manager_when_changing_category_ticket' => 1, 'assign_technical_group_when_changing_category_ticket' => 1, @@ -1100,7 +1088,6 @@ public function testUpdateChangeActorsOnCategoryChange(): void // Configure to assign technical manager and group when changing category $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, 'assign_technical_manager_when_changing_category_change' => 1, 'assign_technical_group_when_changing_category_change' => 1, @@ -1194,7 +1181,6 @@ public function testUpdateProblemActorsOnCategoryChange(): void // Configure to assign technical manager and group when changing category $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, 'assign_technical_manager_when_changing_category_problem' => 1, 'assign_technical_group_when_changing_category_problem' => 1, @@ -1288,7 +1274,6 @@ public function testUpdateActorsDisabledConfiguration(): void // Ensure configuration is disabled $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, 'assign_technical_manager_when_changing_category_ticket' => 0, 'assign_technical_group_when_changing_category_ticket' => 0, @@ -1365,59 +1350,50 @@ public function testUpdateActorsDisabledConfiguration(): void */ public function testParentEntityConfigInheritance(): void { - $this->initEntitySession(); + $this->login(); - $parent_entity_id = false; - // Create child entity + // Create child entity under root (entities_id=0) $child_entity = $this->createItem( \Entity::class, [ 'name' => 'Child Entity Test', - 'entities_id' => 0, // Parent entity as parent + 'entities_id' => 0, ], - ['name'], // Entity uses 'completename' not 'name' + ['name'], ); $child_entity_id = $child_entity->getID(); $this->clearLogEntriesContaining('glpiactiveentities_string'); - // Configure parent entity with specific settings + // Configure root entity with specific settings $conf = Config::getConfig(0, false); $this->updateItem( Config::class, $conf->getID(), [ - 'entities_id' => $parent_entity_id, - 'is_active' => true, - 'use_parent_entity' => false, // This is the source config - 'take_item_group_ticket' => true, - 'prevent_closure_ticket' => true, - 'require_technician_to_close_ticket' => true, - 'mandatory_task_category' => true, + 'take_item_group_ticket' => 1, + 'prevent_closure_ticket' => 1, + 'require_technician_to_close_ticket' => 1, + 'mandatory_task_category' => 1, ], ); - // Configure child entity to use parent configuration + // Configure child entity to inherit from parent (CONFIG_PARENT) $this->assertIsInt($child_entity_id); $child_conf = Config::getConfig($child_entity_id, false); $this->updateItem( Config::class, $child_conf->getID(), [ - 'entities_id' => $child_entity_id, - 'is_active' => 1, - 'use_parent_entity' => 1, // Enable inheritance - 'take_item_group_ticket' => 0, // These values should be ignored - 'prevent_closure_ticket' => 0, - 'require_technician_to_close_ticket' => 0, - 'mandatory_task_category' => 0, + 'take_item_group_ticket' => Config::CONFIG_PARENT, + 'prevent_closure_ticket' => Config::CONFIG_PARENT, + 'require_technician_to_close_ticket' => Config::CONFIG_PARENT, + 'mandatory_task_category' => Config::CONFIG_PARENT, ], ); - // Test effective configuration for child entity + // Test effective configuration for child entity — should inherit root values $effective_config = Config::getConfig($child_entity_id, true); - // Should return parent config - $this->assertEquals($parent_entity_id, $effective_config->fields['entities_id']); $this->assertEquals(1, $effective_config->fields['take_item_group_ticket']); $this->assertEquals(1, $effective_config->fields['prevent_closure_ticket']); $this->assertEquals(1, $effective_config->fields['require_technician_to_close_ticket']); @@ -1474,8 +1450,6 @@ public function testMultiLevelParentEntityConfigInheritance(): void $grandparent_conf->getID(), [ 'entities_id' => $grandparent_entity_id, - 'is_active' => 1, - 'use_parent_entity' => 0, // This is the source config 'take_item_group_ticket' => 1, 'prevent_closure_ticket' => 1, 'require_technician_to_close_ticket' => 1, @@ -1490,8 +1464,6 @@ public function testMultiLevelParentEntityConfigInheritance(): void $parent_conf->getID(), [ 'entities_id' => $parent_entity_id, - 'is_active' => 1, - 'use_parent_entity' => 1, // Cascade to grandparent 'take_item_group_ticket' => 0, // Should be ignored 'prevent_closure_ticket' => 0, 'require_technician_to_close_ticket' => 0, @@ -1506,8 +1478,6 @@ public function testMultiLevelParentEntityConfigInheritance(): void $child_conf->getID(), [ 'entities_id' => $child_entity_id, - 'is_active' => 1, - 'use_parent_entity' => 1, // Should cascade to grandparent 'take_item_group_ticket' => 0, // Should be ignored 'prevent_closure_ticket' => 0, 'require_technician_to_close_ticket' => 0, @@ -1525,7 +1495,7 @@ public function testMultiLevelParentEntityConfigInheritance(): void } /** - * Test that child entity without use_parent_entity uses its own config + * Test that child entity without inheritance uses its own config */ public function testChildEntityWithoutInheritanceUsesOwnConfig(): void { @@ -1562,8 +1532,6 @@ public function testChildEntityWithoutInheritanceUsesOwnConfig(): void $parent_conf->getID(), [ 'entities_id' => $parent_entity_id, - 'is_active' => 1, - 'use_parent_entity' => 0, 'take_item_group_ticket' => 1, 'prevent_closure_ticket' => 1, ], @@ -1577,8 +1545,6 @@ public function testChildEntityWithoutInheritanceUsesOwnConfig(): void $child_conf->getID(), [ 'entities_id' => $child_entity_id, - 'is_active' => 1, - 'use_parent_entity' => 0, // NO inheritance 'take_item_group_ticket' => 0, // Different from parent 'prevent_closure_ticket' => 0, ], @@ -1619,8 +1585,6 @@ public function testGetEffectiveConfigUsesCurrentSession(): void $test_conf->getID(), [ 'entities_id' => $test_entity_id, - 'is_active' => 1, - 'use_parent_entity' => 0, 'take_item_group_ticket' => 1, ], ); @@ -1656,8 +1620,6 @@ public function testRootEntityCannotInheritFromParent(): void Config::class, [ 'entities_id' => 0, - 'is_active' => 1, - 'use_parent_entity' => 1, // This should be ignored for root entity 'take_item_group_ticket' => 1, ], ); @@ -1668,7 +1630,6 @@ public function testRootEntityCannotInheritFromParent(): void Config::class, $root_config->getID(), [ - 'use_parent_entity' => 1, // This should be ignored 'take_item_group_ticket' => 1, ], ); @@ -1725,8 +1686,6 @@ public function testControllerUsesEffectiveConfigWithInheritance(): void $parent_conf->getID(), [ 'entities_id' => $parent_entity_id, - 'is_active' => 1, - 'use_parent_entity' => 0, 'mandatory_task_category' => 1, // Enable mandatory task category 'mandatory_task_duration' => 1, ], @@ -1740,8 +1699,6 @@ public function testControllerUsesEffectiveConfigWithInheritance(): void $child_conf->getID(), [ 'entities_id' => $child_entity_id, - 'is_active' => 1, - 'use_parent_entity' => 1, // Inherit from parent 'mandatory_task_category' => 0, // Should be ignored 'mandatory_task_duration' => 0, ], @@ -1787,7 +1744,6 @@ public function testAssignTechnicianFromTask(): void $conf = $this->getCurrentConfig(); $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, ]); $this->assertTrue($result); @@ -1852,7 +1808,6 @@ public function testAssignTechnicianFromChangeTask(): void $conf = $this->getCurrentConfig(); $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, ]); $this->assertTrue($result); @@ -1917,7 +1872,6 @@ public function testAssignTechnicianFromProblemTask(): void $conf = $this->getCurrentConfig(); $result = $this->updateTestConfig($conf, [ - 'is_active' => 1, 'entities_id' => 0, ]); $this->assertTrue($result); @@ -1992,7 +1946,7 @@ public function testAssignTechnicianFromProblemTask(): void */ public function testMultiLevelInheritanceResolvesConfigParentThroughChain(): void { - $this->initEntitySession(); + $this->login(); $entity_a = $this->createItem( \Entity::class, @@ -2004,14 +1958,14 @@ public function testMultiLevelInheritanceResolvesConfigParentThroughChain(): voi $entity_b = $this->createItem( \Entity::class, ['name' => 'Inheritance Test B', 'entities_id' => $entity_a->getID()], - ['name', 'entities_id'], + ['name'], ); $this->clearLogEntriesContaining('glpiactiveentities_string'); $entity_c = $this->createItem( \Entity::class, ['name' => 'Inheritance Test C', 'entities_id' => $entity_b->getID()], - ['name', 'entities_id'], + ['name'], ); $this->clearLogEntriesContaining('glpiactiveentities_string'); From 7b7c868811a5fcb9a422437616b32282271c387a Mon Sep 17 00:00:00 2001 From: Lainow Date: Fri, 29 May 2026 11:04:09 +0200 Subject: [PATCH 04/15] Fix lint --- src/Config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Config.php b/src/Config.php index 3188ca7..de7fdd9 100644 --- a/src/Config.php +++ b/src/Config.php @@ -350,7 +350,7 @@ public static function install(Migration $migration): void foreach ($entities->find() as $entity) { if (is_array($entity) && isset($entity['id'])) { $entity_id = (int) $entity['id']; - if ($DB->numrows($DB->request(['FROM' => self::getTable(), 'WHERE' => ['entities_id' => $entity_id]])) > 0) { + if (countElementsInTable(self::getTable(), ['entities_id' => $entity_id]) > 0) { continue; } $data = ['entities_id' => $entity_id]; From 1fc84cffd3278442b18e13613c13dadb5279319e Mon Sep 17 00:00:00 2001 From: Lainow Date: Thu, 4 Jun 2026 09:44:17 +0200 Subject: [PATCH 05/15] Fix lint --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 432426a..97bde67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,4 +5,5 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [UNRELEASE] +## [unreleased] + From b64c765f601bb27f33a0a9bbe3a88fbf87ccf7e6 Mon Sep 17 00:00:00 2001 From: Lainow Date: Mon, 22 Jun 2026 14:08:34 +0200 Subject: [PATCH 06/15] Implementy suggestion and update tests --- src/Config.php | 15 ++- tests/Units/ConfigTest.php | 216 ++++++++++++++++++++++++++++++++++--- 2 files changed, 215 insertions(+), 16 deletions(-) diff --git a/src/Config.php b/src/Config.php index de7fdd9..c8e3a71 100644 --- a/src/Config.php +++ b/src/Config.php @@ -151,6 +151,15 @@ public static function getItilConfigFields(): array 'mandatory_task_duration', 'mandatory_task_user', 'mandatory_task_group', + ]; + } + + /** + * @return array + */ + private static function getActorGroupConfigFields(): array + { + return [ 'take_requester_group_ticket', 'take_requester_group_change', 'take_requester_group_problem', @@ -266,7 +275,11 @@ public static function getConfig(?int $entityId = null, bool $useInheritance = t $entity = new Entity(); if ($entity->getFromDB($entityId)) { $parentConfig = self::getConfig((int) $entity->fields['entities_id'], true); - foreach (self::getItilConfigFields() as $field) { + $allFields = array_merge( + self::getItilConfigFields(), + self::getActorGroupConfigFields(), + ); + foreach ($allFields as $field) { if (($moconfig->fields[$field] ?? 0) == self::CONFIG_PARENT) { $moconfig->fields[$field] = $parentConfig->fields[$field] ?? 0; } diff --git a/tests/Units/ConfigTest.php b/tests/Units/ConfigTest.php index a379b62..335d62e 100644 --- a/tests/Units/ConfigTest.php +++ b/tests/Units/ConfigTest.php @@ -1405,7 +1405,7 @@ public function testParentEntityConfigInheritance(): void */ public function testMultiLevelParentEntityConfigInheritance(): void { - $this->initEntitySession(); + $this->login(); // Create grandparent entity (level 1) $grandparent_entity = $this->createItem( \Entity::class, @@ -1456,7 +1456,7 @@ public function testMultiLevelParentEntityConfigInheritance(): void ], ); - // Configure parent entity to use parent configuration (cascade) + // Configure parent entity to inherit from grandparent $this->assertIsInt($parent_entity_id); $parent_conf = Config::getConfig($parent_entity_id, false); $this->updateItem( @@ -1464,13 +1464,13 @@ public function testMultiLevelParentEntityConfigInheritance(): void $parent_conf->getID(), [ 'entities_id' => $parent_entity_id, - 'take_item_group_ticket' => 0, // Should be ignored - 'prevent_closure_ticket' => 0, - 'require_technician_to_close_ticket' => 0, + 'take_item_group_ticket' => Config::CONFIG_PARENT, + 'prevent_closure_ticket' => Config::CONFIG_PARENT, + 'require_technician_to_close_ticket' => Config::CONFIG_PARENT, ], ); - // Configure child entity to use parent configuration + // Configure child entity to inherit from parent (which itself inherits from grandparent) $this->assertIsInt($child_entity_id); $child_conf = Config::getConfig($child_entity_id, false); $this->updateItem( @@ -1478,20 +1478,18 @@ public function testMultiLevelParentEntityConfigInheritance(): void $child_conf->getID(), [ 'entities_id' => $child_entity_id, - 'take_item_group_ticket' => 0, // Should be ignored - 'prevent_closure_ticket' => 0, - 'require_technician_to_close_ticket' => 0, + 'take_item_group_ticket' => Config::CONFIG_PARENT, + 'prevent_closure_ticket' => Config::CONFIG_PARENT, + 'require_technician_to_close_ticket' => Config::CONFIG_PARENT, ], ); - // Test effective configuration for child entity (should cascade to grandparent) + // Child should cascade through parent to resolve grandparent's values $effective_config = Config::getConfig($child_entity_id, true); - // The cascade should find a config with the expected values - // Note: Values may be -2 (CONFIG_PARENT) if not fully resolved, or 0 if inherited default - $this->assertContains($effective_config->fields['take_item_group_ticket'], [0, 1, -2]); - $this->assertContains($effective_config->fields['prevent_closure_ticket'], [0, 1, -2]); - $this->assertContains($effective_config->fields['require_technician_to_close_ticket'], [0, 1, -2]); + $this->assertEquals(1, $effective_config->fields['take_item_group_ticket']); + $this->assertEquals(1, $effective_config->fields['prevent_closure_ticket']); + $this->assertEquals(1, $effective_config->fields['require_technician_to_close_ticket']); } /** @@ -2012,4 +2010,192 @@ public function testMultiLevelInheritanceResolvesConfigParentThroughChain(): voi $this->assertEquals(0, $resolved_c->fields['take_item_group_ticket'], 'C should inherit take_item_group_ticket=0 from B (not root=1)'); $this->assertEquals(1, $resolved_c->fields['take_requester_group_ticket'], 'C should inherit take_requester_group_ticket=1 through B→A'); } + + /** + * Test that a child entity resolves grandparent values through a full CONFIG_PARENT chain. + * + * Hierarchy: Grandparent (explicit) → Parent (CONFIG_PARENT) → Child (CONFIG_PARENT) + * Expected: child gets grandparent's values, not CONFIG_PARENT sentinel (-2). + */ + public function testGrandparentToParentToChildAllInherit(): void + { + $this->login(); + + $grandparent = $this->createItem( + \Entity::class, + ['name' => 'GP2P2C Grandparent', 'entities_id' => 0], + ['name'], + ); + $this->clearLogEntriesContaining('glpiactiveentities_string'); + + $parent = $this->createItem( + \Entity::class, + ['name' => 'GP2P2C Parent', 'entities_id' => $grandparent->getID()], + ['name'], + ); + $this->clearLogEntriesContaining('glpiactiveentities_string'); + + $child = $this->createItem( + \Entity::class, + ['name' => 'GP2P2C Child', 'entities_id' => $parent->getID()], + ['name'], + ); + $this->clearLogEntriesContaining('glpiactiveentities_string'); + + $gp_conf = Config::getConfig($grandparent->getID(), false); + $this->updateItem(Config::class, $gp_conf->getID(), [ + 'take_item_group_ticket' => 1, + 'prevent_closure_ticket' => 1, + 'mandatory_task_category' => 1, + ]); + + $parent_conf = Config::getConfig($parent->getID(), false); + $this->updateItem(Config::class, $parent_conf->getID(), [ + 'take_item_group_ticket' => Config::CONFIG_PARENT, + 'prevent_closure_ticket' => Config::CONFIG_PARENT, + 'mandatory_task_category' => Config::CONFIG_PARENT, + ]); + + $child_conf = Config::getConfig($child->getID(), false); + $this->updateItem(Config::class, $child_conf->getID(), [ + 'take_item_group_ticket' => Config::CONFIG_PARENT, + 'prevent_closure_ticket' => Config::CONFIG_PARENT, + 'mandatory_task_category' => Config::CONFIG_PARENT, + ]); + + $resolved = Config::getConfig($child->getID()); + + $this->assertEquals(1, $resolved->fields['take_item_group_ticket'], + 'Child should resolve to grandparent value (1) through the full CONFIG_PARENT chain'); + $this->assertEquals(1, $resolved->fields['prevent_closure_ticket'], + 'Child should resolve to grandparent value (1) through the full CONFIG_PARENT chain'); + $this->assertEquals(1, $resolved->fields['mandatory_task_category'], + 'Child should resolve to grandparent value (1) through the full CONFIG_PARENT chain'); + } + + /** + * Test that a child using CONFIG_PARENT gets the parent's own value, not the grandparent's, + * when the parent has an explicit override. + * + * Hierarchy: Grandparent (value=1) → Parent (own value=2) → Child (CONFIG_PARENT) + * Expected: child gets 2 (parent's override), not 1 (grandparent's value). + */ + public function testChildInheritsParentOverrideNotGrandparent(): void + { + $this->login(); + + $grandparent = $this->createItem( + \Entity::class, + ['name' => 'Override GP', 'entities_id' => 0], + ['name'], + ); + $this->clearLogEntriesContaining('glpiactiveentities_string'); + + $parent = $this->createItem( + \Entity::class, + ['name' => 'Override Parent', 'entities_id' => $grandparent->getID()], + ['name'], + ); + $this->clearLogEntriesContaining('glpiactiveentities_string'); + + $child = $this->createItem( + \Entity::class, + ['name' => 'Override Child', 'entities_id' => $parent->getID()], + ['name'], + ); + $this->clearLogEntriesContaining('glpiactiveentities_string'); + + $gp_conf = Config::getConfig($grandparent->getID(), false); + $this->updateItem(Config::class, $gp_conf->getID(), [ + 'take_item_group_ticket' => 1, + 'take_requester_group_ticket' => 1, + ]); + + // Parent overrides with different explicit values + $parent_conf = Config::getConfig($parent->getID(), false); + $this->updateItem(Config::class, $parent_conf->getID(), [ + 'take_item_group_ticket' => 2, + 'take_requester_group_ticket' => 0, + ]); + + $child_conf = Config::getConfig($child->getID(), false); + $this->updateItem(Config::class, $child_conf->getID(), [ + 'take_item_group_ticket' => Config::CONFIG_PARENT, + 'take_requester_group_ticket' => Config::CONFIG_PARENT, + ]); + + $resolved = Config::getConfig($child->getID()); + + $this->assertEquals(2, $resolved->fields['take_item_group_ticket'], + 'Child should inherit parent override (2), not grandparent value (1)'); + $this->assertEquals(0, $resolved->fields['take_requester_group_ticket'], + 'Child should inherit parent override (0), not grandparent value (1)'); + } + + /** + * Test that different fields in the same entity can be resolved at different levels + * of a three-level hierarchy. + * + * Hierarchy: Grandparent → Parent → Child + * - take_item_group_ticket: GP=1, Parent=CONFIG_PARENT, Child=CONFIG_PARENT → child gets 1 + * - take_requester_group_ticket: GP=2, Parent=0 (own), Child=CONFIG_PARENT → child gets 0 + * - prevent_closure_ticket: GP=1, Parent=1 (explicit), Child=0 (own) → child keeps 0 + */ + public function testMixedFieldInheritanceThroughThreeLevels(): void + { + $this->login(); + + $grandparent = $this->createItem( + \Entity::class, + ['name' => 'Mixed GP', 'entities_id' => 0], + ['name'], + ); + $this->clearLogEntriesContaining('glpiactiveentities_string'); + + $parent = $this->createItem( + \Entity::class, + ['name' => 'Mixed Parent', 'entities_id' => $grandparent->getID()], + ['name'], + ); + $this->clearLogEntriesContaining('glpiactiveentities_string'); + + $child = $this->createItem( + \Entity::class, + ['name' => 'Mixed Child', 'entities_id' => $parent->getID()], + ['name'], + ); + $this->clearLogEntriesContaining('glpiactiveentities_string'); + + $gp_conf = Config::getConfig($grandparent->getID(), false); + $this->updateItem(Config::class, $gp_conf->getID(), [ + 'take_item_group_ticket' => 1, + 'take_requester_group_ticket' => 2, + 'prevent_closure_ticket' => 1, + ]); + + // Parent inherits field_a, overrides field_b, sets field_c explicitly + $parent_conf = Config::getConfig($parent->getID(), false); + $this->updateItem(Config::class, $parent_conf->getID(), [ + 'take_item_group_ticket' => Config::CONFIG_PARENT, // inherits GP's 1 + 'take_requester_group_ticket' => 0, // own override of GP's 2 + 'prevent_closure_ticket' => 1, // same as GP but explicit + ]); + + // Child inherits field_a and field_b from parent, owns field_c + $child_conf = Config::getConfig($child->getID(), false); + $this->updateItem(Config::class, $child_conf->getID(), [ + 'take_item_group_ticket' => Config::CONFIG_PARENT, // cascades to GP's 1 + 'take_requester_group_ticket' => Config::CONFIG_PARENT, // parent's override: 0 + 'prevent_closure_ticket' => 0, // own explicit value + ]); + + $resolved = Config::getConfig($child->getID()); + + $this->assertEquals(1, $resolved->fields['take_item_group_ticket'], + 'Child should resolve to 1 from grandparent (parent also inherits this field)'); + $this->assertEquals(0, $resolved->fields['take_requester_group_ticket'], + 'Child should get parent override (0), not grandparent value (2)'); + $this->assertEquals(0, $resolved->fields['prevent_closure_ticket'], + 'Child should keep its own explicit value (0), ignoring parent and grandparent'); + } } From f652e8fb06ee767ac0ece4c7aeb1057298fddad6 Mon Sep 17 00:00:00 2001 From: Lainow Date: Mon, 22 Jun 2026 14:21:54 +0200 Subject: [PATCH 07/15] FIx lint --- tests/Units/ConfigTest.php | 56 +++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/tests/Units/ConfigTest.php b/tests/Units/ConfigTest.php index 335d62e..0a9371b 100644 --- a/tests/Units/ConfigTest.php +++ b/tests/Units/ConfigTest.php @@ -2065,12 +2065,21 @@ public function testGrandparentToParentToChildAllInherit(): void $resolved = Config::getConfig($child->getID()); - $this->assertEquals(1, $resolved->fields['take_item_group_ticket'], - 'Child should resolve to grandparent value (1) through the full CONFIG_PARENT chain'); - $this->assertEquals(1, $resolved->fields['prevent_closure_ticket'], - 'Child should resolve to grandparent value (1) through the full CONFIG_PARENT chain'); - $this->assertEquals(1, $resolved->fields['mandatory_task_category'], - 'Child should resolve to grandparent value (1) through the full CONFIG_PARENT chain'); + $this->assertEquals( + 1, + $resolved->fields['take_item_group_ticket'], + 'Child should resolve to grandparent value (1) through the full CONFIG_PARENT chain', + ); + $this->assertEquals( + 1, + $resolved->fields['prevent_closure_ticket'], + 'Child should resolve to grandparent value (1) through the full CONFIG_PARENT chain', + ); + $this->assertEquals( + 1, + $resolved->fields['mandatory_task_category'], + 'Child should resolve to grandparent value (1) through the full CONFIG_PARENT chain', + ); } /** @@ -2126,10 +2135,16 @@ public function testChildInheritsParentOverrideNotGrandparent(): void $resolved = Config::getConfig($child->getID()); - $this->assertEquals(2, $resolved->fields['take_item_group_ticket'], - 'Child should inherit parent override (2), not grandparent value (1)'); - $this->assertEquals(0, $resolved->fields['take_requester_group_ticket'], - 'Child should inherit parent override (0), not grandparent value (1)'); + $this->assertEquals( + 2, + $resolved->fields['take_item_group_ticket'], + 'Child should inherit parent override (2), not grandparent value (1)', + ); + $this->assertEquals( + 0, + $resolved->fields['take_requester_group_ticket'], + 'Child should inherit parent override (0), not grandparent value (1)', + ); } /** @@ -2191,11 +2206,20 @@ public function testMixedFieldInheritanceThroughThreeLevels(): void $resolved = Config::getConfig($child->getID()); - $this->assertEquals(1, $resolved->fields['take_item_group_ticket'], - 'Child should resolve to 1 from grandparent (parent also inherits this field)'); - $this->assertEquals(0, $resolved->fields['take_requester_group_ticket'], - 'Child should get parent override (0), not grandparent value (2)'); - $this->assertEquals(0, $resolved->fields['prevent_closure_ticket'], - 'Child should keep its own explicit value (0), ignoring parent and grandparent'); + $this->assertEquals( + 1, + $resolved->fields['take_item_group_ticket'], + 'Child should resolve to 1 from grandparent (parent also inherits this field)', + ); + $this->assertEquals( + 0, + $resolved->fields['take_requester_group_ticket'], + 'Child should get parent override (0), not grandparent value (2)', + ); + $this->assertEquals( + 0, + $resolved->fields['prevent_closure_ticket'], + 'Child should keep its own explicit value (0), ignoring parent and grandparent', + ); } } From 7f59755e8e4604c050c50bb9248d24a9799cc1b5 Mon Sep 17 00:00:00 2001 From: Lainow Date: Mon, 29 Jun 2026 14:57:25 +0200 Subject: [PATCH 08/15] Implements suggestions and fix tests --- src/Config.php | 4 +- tests/Units/ConfigTest.php | 123 +++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 2 deletions(-) diff --git a/src/Config.php b/src/Config.php index c8e3a71..f559c0e 100644 --- a/src/Config.php +++ b/src/Config.php @@ -246,7 +246,7 @@ public static function addConfig(CommonDBTM $item): void $entity_id = $item->getID(); $data = ['entities_id' => $entity_id]; if ($entity_id > 0) { - foreach (self::getItilConfigFields() as $field) { + foreach (array_merge(self::getItilConfigFields(), self::getActorGroupConfigFields()) as $field) { $data[$field] = self::CONFIG_PARENT; } } @@ -368,7 +368,7 @@ public static function install(Migration $migration): void } $data = ['entities_id' => $entity_id]; if ($entity_id > 0) { - foreach (self::getItilConfigFields() as $field) { + foreach (array_merge(self::getItilConfigFields(), self::getActorGroupConfigFields()) as $field) { $data[$field] = self::CONFIG_PARENT; } } diff --git a/tests/Units/ConfigTest.php b/tests/Units/ConfigTest.php index 0a9371b..6ae5516 100644 --- a/tests/Units/ConfigTest.php +++ b/tests/Units/ConfigTest.php @@ -2147,6 +2147,129 @@ public function testChildInheritsParentOverrideNotGrandparent(): void ); } + /** + * Test that addConfig() initializes actor group fields to CONFIG_PARENT for non-root entities, + * and that those values are then resolved correctly through the inheritance chain. + * + * This test will FAIL if getActorGroupConfigFields() is missing from addConfig(). + */ + public function testActorGroupFieldsInheritFromParent(): void + { + $this->login(); + + $parent_entity = $this->createItem( + \Entity::class, + ['name' => 'Actor Group Parent Entity', 'entities_id' => 0], + ['name'], + ); + $this->clearLogEntriesContaining('glpiactiveentities_string'); + + $child_entity = $this->createItem( + \Entity::class, + ['name' => 'Actor Group Child Entity', 'entities_id' => $parent_entity->getID()], + ['name'], + ); + $this->clearLogEntriesContaining('glpiactiveentities_string'); + + // addConfig() must have stored CONFIG_PARENT for actor group fields on the child — verify raw DB value + $child_conf_raw = Config::getConfig($child_entity->getID(), false); + foreach (['take_requester_group_ticket', 'take_requester_group_change', 'take_requester_group_problem', + 'take_technician_group_ticket', 'take_technician_group_change', 'take_technician_group_problem'] as $field) { + $this->assertEquals( + Config::CONFIG_PARENT, + $child_conf_raw->fields[$field], + "addConfig() must initialize $field to CONFIG_PARENT for non-root entities", + ); + } + + // Configure parent with explicit actor group values + $parent_conf = Config::getConfig($parent_entity->getID(), false); + $this->updateItem(Config::class, $parent_conf->getID(), [ + 'take_requester_group_ticket' => 2, + 'take_technician_group_ticket' => 1, + 'take_requester_group_change' => 1, + 'take_technician_group_change' => 2, + 'take_requester_group_problem' => 2, + 'take_technician_group_problem' => 1, + ]); + + // With inheritance, child must resolve to parent's values + $resolved = Config::getConfig($child_entity->getID(), true); + + $this->assertEquals(2, $resolved->fields['take_requester_group_ticket'], 'Child should inherit take_requester_group_ticket=2 from parent'); + $this->assertEquals(1, $resolved->fields['take_technician_group_ticket'], 'Child should inherit take_technician_group_ticket=1 from parent'); + $this->assertEquals(1, $resolved->fields['take_requester_group_change'], 'Child should inherit take_requester_group_change=1 from parent'); + $this->assertEquals(2, $resolved->fields['take_technician_group_change'], 'Child should inherit take_technician_group_change=2 from parent'); + $this->assertEquals(2, $resolved->fields['take_requester_group_problem'], 'Child should inherit take_requester_group_problem=2 from parent'); + $this->assertEquals(1, $resolved->fields['take_technician_group_problem'], 'Child should inherit take_technician_group_problem=1 from parent'); + } + + /** + * Test that child entity fields set to CONFIG_PARENT propagate correctly to + * Controller::checkTaskRequirements, asserting both the blocked outcome (empty + * mandatory fields) and the unblocked outcome (filled mandatory fields). + */ + public function testCheckTaskRequirementsWithInheritedConfig(): void + { + $this->login(); + + $parent_entity = $this->createItem( + \Entity::class, + ['name' => 'Task Requirements Parent', 'entities_id' => 0], + ['name'], + ); + $this->clearLogEntriesContaining('glpiactiveentities_string'); + + $child_entity = $this->createItem( + \Entity::class, + ['name' => 'Task Requirements Child', 'entities_id' => $parent_entity->getID()], + ['name'], + ); + $this->clearLogEntriesContaining('glpiactiveentities_string'); + + // Parent enables all mandatory task fields + $parent_conf = Config::getConfig($parent_entity->getID(), false); + $this->updateItem(Config::class, $parent_conf->getID(), [ + 'mandatory_task_category' => 1, + 'mandatory_task_duration' => 1, + 'mandatory_task_user' => 1, + 'mandatory_task_group' => 1, + ]); + + // Child inherits all mandatory task fields from parent via CONFIG_PARENT + $child_conf = Config::getConfig($child_entity->getID(), false); + $this->updateItem(Config::class, $child_conf->getID(), [ + 'mandatory_task_category' => Config::CONFIG_PARENT, + 'mandatory_task_duration' => Config::CONFIG_PARENT, + 'mandatory_task_user' => Config::CONFIG_PARENT, + 'mandatory_task_group' => Config::CONFIG_PARENT, + ]); + + $original_entity = $_SESSION['glpiactive_entity']; + $_SESSION['glpiactive_entity'] = $child_entity->getID(); + + // Blocked: missing all mandatory fields — inherited config from parent must block creation + $task_empty = new \TicketTask(); + $task_empty->input = ['content' => 'Test task missing fields']; + \GlpiPlugin\Moreoptions\Controller::checkTaskRequirements($task_empty); + $this->assertFalse($task_empty->input, 'Task with missing mandatory fields should be blocked (input=false) via inherited config'); + $this->clearSessionMessages(); + + // Unblocked: all mandatory fields filled — inherited config must allow creation + $task_filled = new \TicketTask(); + $task_filled->input = [ + 'content' => 'Test task with all fields', + 'taskcategories_id' => 1, + 'actiontime' => 3600, + 'users_id_tech' => 1, + 'groups_id_tech' => 1, + ]; + \GlpiPlugin\Moreoptions\Controller::checkTaskRequirements($task_filled); + $this->assertNotFalse($task_filled->input, 'Task with all mandatory fields filled should not be blocked'); + + $_SESSION['glpiactive_entity'] = $original_entity; + } + /** * Test that different fields in the same entity can be resolved at different levels * of a three-level hierarchy. From 6e761b919fa8750f26222b3ffb051669e76adf80 Mon Sep 17 00:00:00 2001 From: Lainow Date: Mon, 29 Jun 2026 15:01:42 +0200 Subject: [PATCH 09/15] Fix php cs --- tests/Units/ConfigTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Units/ConfigTest.php b/tests/Units/ConfigTest.php index 6ae5516..f51cda7 100644 --- a/tests/Units/ConfigTest.php +++ b/tests/Units/ConfigTest.php @@ -2174,7 +2174,7 @@ public function testActorGroupFieldsInheritFromParent(): void // addConfig() must have stored CONFIG_PARENT for actor group fields on the child — verify raw DB value $child_conf_raw = Config::getConfig($child_entity->getID(), false); foreach (['take_requester_group_ticket', 'take_requester_group_change', 'take_requester_group_problem', - 'take_technician_group_ticket', 'take_technician_group_change', 'take_technician_group_problem'] as $field) { + 'take_technician_group_ticket', 'take_technician_group_change', 'take_technician_group_problem'] as $field) { $this->assertEquals( Config::CONFIG_PARENT, $child_conf_raw->fields[$field], From a14bb8a867915d32011a8db0507baa31d5296e21 Mon Sep 17 00:00:00 2001 From: Lainow Date: Tue, 30 Jun 2026 10:11:35 +0200 Subject: [PATCH 10/15] Implements suggestions --- src/Config.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Config.php b/src/Config.php index f559c0e..ed435e6 100644 --- a/src/Config.php +++ b/src/Config.php @@ -345,12 +345,7 @@ public static function install(Migration $migration): void } foreach ([ - 'take_requester_group_ticket', - 'take_requester_group_change', - 'take_requester_group_problem', - 'take_technician_group_ticket', - 'take_technician_group_change', - 'take_technician_group_problem', + self::getActorGroupConfigFields() ] as $field) { if ($DB->fieldExists($table, $field)) { $migration->changeField($table, $field, $field, 'bool', ['value' => '0']); From 09f4322d080897c3b98b26d1199490df3a135f5f Mon Sep 17 00:00:00 2001 From: Lainow Date: Tue, 30 Jun 2026 11:23:16 +0200 Subject: [PATCH 11/15] Fix lints --- src/Config.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Config.php b/src/Config.php index ed435e6..f82cca9 100644 --- a/src/Config.php +++ b/src/Config.php @@ -344,9 +344,7 @@ public static function install(Migration $migration): void $DB->doQuery($query); } - foreach ([ - self::getActorGroupConfigFields() - ] as $field) { + foreach (self::getActorGroupConfigFields() as $field) { if ($DB->fieldExists($table, $field)) { $migration->changeField($table, $field, $field, 'bool', ['value' => '0']); } From 3ab0e34302e4e004e01edb6f3c24cd62e0d66f45 Mon Sep 17 00:00:00 2001 From: Samuel Launay <107540223+Lainow@users.noreply.github.com> Date: Wed, 1 Jul 2026 11:47:20 +0200 Subject: [PATCH 12/15] Update src/Config.php Co-authored-by: Romain B. <8530352+Rom1-B@users.noreply.github.com> --- src/Config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Config.php b/src/Config.php index f82cca9..0c4cccc 100644 --- a/src/Config.php +++ b/src/Config.php @@ -105,7 +105,7 @@ public static function preItemUpdate(CommonDBTM $item): CommonDBTM return $item; } - foreach (self::getItilConfigFields() as $field) { + foreach (array_merge(self::getItilConfigFields(), self::getActorGroupConfigFields()) as $field) { if (isset($item->input[$field])) { $item->input[$field] = (int) $item->input[$field]; } From fca801833df38c4d313656f79d5bbf311c1962d4 Mon Sep 17 00:00:00 2001 From: Samuel Launay <107540223+Lainow@users.noreply.github.com> Date: Wed, 1 Jul 2026 11:47:34 +0200 Subject: [PATCH 13/15] Update src/Controller.php Co-authored-by: Romain B. <8530352+Rom1-B@users.noreply.github.com> --- src/Controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller.php b/src/Controller.php index 6ce8f6e..6c7bf35 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -537,7 +537,7 @@ public static function updateItemActors(CommonITILObject $item): CommonITILObjec */ public static function assignTechnicianFromTask(\CommonITILTask $item): void { - $conf = Config::getConfig(Session::getActiveEntity()); + $conf = Config::getConfig(); // Check if a technician is assigned to the task if (empty($item->fields['users_id_tech'])) { From 02af14b7dc0475a6ff7077c9d3f470ca46ec693c Mon Sep 17 00:00:00 2001 From: Lainow Date: Wed, 1 Jul 2026 12:04:05 +0200 Subject: [PATCH 14/15] Implement suggestions --- CHANGELOG.md | 1 + src/Config.php | 27 ++++++++++++------ src/Controller.php | 6 ++-- templates/config.html.twig | 6 ++++ tests/Units/ConfigTest.php | 56 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97bde67..791fb1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,3 +7,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] + diff --git a/src/Config.php b/src/Config.php index 0c4cccc..c5122cc 100644 --- a/src/Config.php +++ b/src/Config.php @@ -151,6 +151,9 @@ public static function getItilConfigFields(): array 'mandatory_task_duration', 'mandatory_task_user', 'mandatory_task_group', + 'assign_technician_from_task_ticket', + 'assign_technician_from_task_change', + 'assign_technician_from_task_problem', ]; } @@ -194,14 +197,7 @@ public static function showForEntity(Entity $item): void foreach (self::getItilConfigFields() as $field) { $inheritance_labels[$field] = self::getInheritedValueBadge($parentConfig->fields[$field] ?? 0); } - foreach ([ - 'take_requester_group_ticket', - 'take_requester_group_change', - 'take_requester_group_problem', - 'take_technician_group_ticket', - 'take_technician_group_change', - 'take_technician_group_problem', - ] as $field) { + foreach (self::getActorGroupConfigFields() as $field) { $inheritance_labels[$field] = self::getInheritedValueBadgeForActorGroup($parentConfig->fields[$field] ?? 0); } } @@ -337,6 +333,9 @@ public static function install(Migration $migration): void `mandatory_task_duration` tinyint NOT NULL DEFAULT '0', `mandatory_task_user` tinyint NOT NULL DEFAULT '0', `mandatory_task_group` tinyint NOT NULL DEFAULT '0', + `assign_technician_from_task_ticket` tinyint NOT NULL DEFAULT '0', + `assign_technician_from_task_change` tinyint NOT NULL DEFAULT '0', + `assign_technician_from_task_problem` tinyint NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `entities_id` (`entities_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; @@ -350,6 +349,18 @@ public static function install(Migration $migration): void } } + foreach ( + [ + 'assign_technician_from_task_ticket', + 'assign_technician_from_task_change', + 'assign_technician_from_task_problem', + ] as $field + ) { + if (!$DB->fieldExists($table, $field)) { + $migration->addField($table, $field, 'bool', ['value' => '0']); + } + } + $migration->executeMigration(); $entities = new Entity(); diff --git a/src/Controller.php b/src/Controller.php index 6c7bf35..7398eb4 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -549,7 +549,7 @@ public static function assignTechnicianFromTask(\CommonITILTask $item): void // Determine the parent ITIL object and user link class based on task type switch ($item::class) { case TicketTask::class: - if (empty($item->fields['tickets_id'])) { + if ($conf->fields['assign_technician_from_task_ticket'] != 1 || empty($item->fields['tickets_id'])) { return; } $itilObject = new Ticket(); @@ -559,7 +559,7 @@ public static function assignTechnicianFromTask(\CommonITILTask $item): void break; case ChangeTask::class: - if (empty($item->fields['changes_id'])) { + if ($conf->fields['assign_technician_from_task_change'] != 1 || empty($item->fields['changes_id'])) { return; } $itilObject = new Change(); @@ -569,7 +569,7 @@ public static function assignTechnicianFromTask(\CommonITILTask $item): void break; case ProblemTask::class: - if (empty($item->fields['problems_id'])) { + if ($conf->fields['assign_technician_from_task_problem'] != 1 || empty($item->fields['problems_id'])) { return; } $itilObject = new Problem(); diff --git a/templates/config.html.twig b/templates/config.html.twig index 984a9b8..9ff7a88 100644 --- a/templates/config.html.twig +++ b/templates/config.html.twig @@ -103,6 +103,12 @@ {{ fields.dropdownArrayField('assign_technical_group_when_changing_category_change', item.fields['assign_technical_group_when_changing_category_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technical_group_when_changing_category_change']|default(null)})) }} {{ fields.dropdownArrayField('assign_technical_group_when_changing_category_problem', item.fields['assign_technical_group_when_changing_category_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technical_group_when_changing_category_problem']|default(null)})) }} + + {{ __('Assign technician from task to parent item', 'moreoptions') }} + {{ fields.dropdownArrayField('assign_technician_from_task_ticket', item.fields['assign_technician_from_task_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technician_from_task_ticket']|default(null)})) }} + {{ fields.dropdownArrayField('assign_technician_from_task_change', item.fields['assign_technician_from_task_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technician_from_task_change']|default(null)})) }} + {{ fields.dropdownArrayField('assign_technician_from_task_problem', item.fields['assign_technician_from_task_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technician_from_task_problem']|default(null)})) }} + diff --git a/tests/Units/ConfigTest.php b/tests/Units/ConfigTest.php index f51cda7..b22dbf2 100644 --- a/tests/Units/ConfigTest.php +++ b/tests/Units/ConfigTest.php @@ -1743,6 +1743,7 @@ public function testAssignTechnicianFromTask(): void $result = $this->updateTestConfig($conf, [ 'entities_id' => 0, + 'assign_technician_from_task_ticket' => 1, ]); $this->assertTrue($result); @@ -1807,6 +1808,7 @@ public function testAssignTechnicianFromChangeTask(): void $result = $this->updateTestConfig($conf, [ 'entities_id' => 0, + 'assign_technician_from_task_change' => 1, ]); $this->assertTrue($result); @@ -1871,6 +1873,7 @@ public function testAssignTechnicianFromProblemTask(): void $result = $this->updateTestConfig($conf, [ 'entities_id' => 0, + 'assign_technician_from_task_problem' => 1, ]); $this->assertTrue($result); @@ -1927,6 +1930,59 @@ public function testAssignTechnicianFromProblemTask(): void $this->assertEquals(\CommonITILActor::ASSIGN, $assignedUser['type']); } + public function testAssignTechnicianFromTaskDisabledByConfig(): void + { + $this->login(); + + $conf = $this->getCurrentConfig(); + + $result = $this->updateTestConfig($conf, [ + 'entities_id' => 0, + 'assign_technician_from_task_ticket' => 0, + ]); + $this->assertTrue($result); + + $tech = $this->createItem( + \User::class, + [ + 'name' => 'tech_from_task_disabled', + 'password' => 'tech_from_task_disabled', + 'password2' => 'tech_from_task_disabled', + '_profiles_id' => 4, + ], + ['password', 'password2'], + ); + $tech_id = $tech->getID(); + + $ticket = $this->createItem( + \Ticket::class, + [ + 'name' => 'Test ticket for disabled task assignment', + 'content' => 'Test content', + ], + ); + $ticket_id = $ticket->getID(); + + $this->createItem( + \TicketTask::class, + [ + 'tickets_id' => $ticket_id, + 'content' => 'Test task', + 'users_id_tech' => $tech_id, + 'actiontime' => 3600, + 'state' => \Planning::TODO, + ], + ); + + $ticket_user = new \Ticket_User(); + $assigned_users = $ticket_user->find([ + 'tickets_id' => $ticket_id, + 'users_id' => $tech_id, + 'type' => \CommonITILActor::ASSIGN, + ]); + $this->assertCount(0, $assigned_users); + } + /** * Test that CONFIG_PARENT values are resolved through multiple entity levels. * From 4a9dd6d84579a8cf71a5fa5c24f3510d19716079 Mon Sep 17 00:00:00 2001 From: Lainow Date: Wed, 1 Jul 2026 15:28:26 +0200 Subject: [PATCH 15/15] Implement uggestions --- src/Config.php | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Config.php b/src/Config.php index c5122cc..4b42f30 100644 --- a/src/Config.php +++ b/src/Config.php @@ -105,7 +105,7 @@ public static function preItemUpdate(CommonDBTM $item): CommonDBTM return $item; } - foreach (array_merge(self::getItilConfigFields(), self::getActorGroupConfigFields()) as $field) { + foreach (self::getAllConfigFields() as $field) { if (isset($item->input[$field])) { $item->input[$field] = (int) $item->input[$field]; } @@ -172,6 +172,14 @@ private static function getActorGroupConfigFields(): array ]; } + /** + * @return array + */ + private static function getAllConfigFields(): array + { + return array_merge(self::getItilConfigFields(), self::getActorGroupConfigFields()); + } + /** * @return array */ @@ -242,7 +250,7 @@ public static function addConfig(CommonDBTM $item): void $entity_id = $item->getID(); $data = ['entities_id' => $entity_id]; if ($entity_id > 0) { - foreach (array_merge(self::getItilConfigFields(), self::getActorGroupConfigFields()) as $field) { + foreach (self::getAllConfigFields() as $field) { $data[$field] = self::CONFIG_PARENT; } } @@ -271,11 +279,7 @@ public static function getConfig(?int $entityId = null, bool $useInheritance = t $entity = new Entity(); if ($entity->getFromDB($entityId)) { $parentConfig = self::getConfig((int) $entity->fields['entities_id'], true); - $allFields = array_merge( - self::getItilConfigFields(), - self::getActorGroupConfigFields(), - ); - foreach ($allFields as $field) { + foreach (self::getAllConfigFields() as $field) { if (($moconfig->fields[$field] ?? 0) == self::CONFIG_PARENT) { $moconfig->fields[$field] = $parentConfig->fields[$field] ?? 0; } @@ -372,7 +376,7 @@ public static function install(Migration $migration): void } $data = ['entities_id' => $entity_id]; if ($entity_id > 0) { - foreach (array_merge(self::getItilConfigFields(), self::getActorGroupConfigFields()) as $field) { + foreach (self::getAllConfigFields() as $field) { $data[$field] = self::CONFIG_PARENT; } }