From a3680a385d1042db55a819f0883424457e4f57a0 Mon Sep 17 00:00:00 2001 From: Matheus Zych Date: Thu, 9 Jul 2026 07:44:23 +0200 Subject: [PATCH 1/3] ItemGroup: Fix itgr_data Column Defaults See: https://mantis.ilias.de/view.php?id=31861 See: https://mantis.ilias.de/view.php?id=48043 See: https://mantis.ilias.de/view.php?id=48101 After the item group display migration, `hide_title` and `behaviour` in `itgr_data` need a default of `MIGRATED_MARKER` (-1). Setup step 4 now sets these column defaults and exposes the marker constant for reuse. --- .../Setup/class.ilItemGroupDBUpdateSteps.php | 15 +++++++++++++++ .../Setup/class.ilItemGroupDisplayMigration.php | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDBUpdateSteps.php b/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDBUpdateSteps.php index 2687bcae5496..6471e6b5f813 100755 --- a/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDBUpdateSteps.php +++ b/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDBUpdateSteps.php @@ -75,4 +75,19 @@ public function step_3(): void ]); } } + + public function step_4(): void + { + if ($this->db->tableColumnExists('itgr_data', 'hide_title')) { + $this->db->modifyTableColumn('itgr_data', 'hide_title', [ + 'default' => ilItemGroupDisplayMigration::MIGRATED_MARKER, + ]); + } + + if ($this->db->tableColumnExists('itgr_data', 'behaviour')) { + $this->db->modifyTableColumn('itgr_data', 'behaviour', [ + 'default' => ilItemGroupDisplayMigration::MIGRATED_MARKER, + ]); + } + } } diff --git a/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDisplayMigration.php b/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDisplayMigration.php index f22fabe7cf1d..43baeec2b750 100644 --- a/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDisplayMigration.php +++ b/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDisplayMigration.php @@ -30,7 +30,7 @@ class ilItemGroupDisplayMigration implements Migration { - private const MIGRATED_MARKER = -1; + public const MIGRATED_MARKER = -1; private ilDBInterface $db; From 07960a9caa73be99ea10f3b87a35b89effa0dd16 Mon Sep 17 00:00:00 2001 From: Matheus Zych Date: Mon, 31 Aug 2026 14:11:45 +0200 Subject: [PATCH 2/3] ItemGroup: Drop Legacy itgr_data Display Columns See: https://mantis.ilias.de/view.php?id=31861 See: https://mantis.ilias.de/view.php?id=48043 See: https://mantis.ilias.de/view.php?id=48101 After migrating `hide_title` and `behaviour` to `display` and `toggleable_initially`, drop the leftover columns from `itgr_data` via `ilItemGroupDataTableColumnMigration` instead of a database update step. `ilItemGroupDisplayMigration` now no-ops when the legacy columns are already gone, and the dataset no longer exports them. --- .../ItemGroup/classes/Setup/class.Agent.php | 19 ++-- ...ss.ilItemGroupDataTableColumnMigration.php | 93 +++++++++++++++++++ .../class.ilItemGroupDisplayMigration.php | 15 +++ 3 files changed, 119 insertions(+), 8 deletions(-) create mode 100644 components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDataTableColumnMigration.php diff --git a/components/ILIAS/ItemGroup/classes/Setup/class.Agent.php b/components/ILIAS/ItemGroup/classes/Setup/class.Agent.php index 2c3737c26f70..fe02a23b5e59 100755 --- a/components/ILIAS/ItemGroup/classes/Setup/class.Agent.php +++ b/components/ILIAS/ItemGroup/classes/Setup/class.Agent.php @@ -16,24 +16,27 @@ * *********************************************************************/ +declare(strict_types=1); + namespace ILIAS\ItemGroup\Setup; -use ILIAS\Setup; +use ILIAS\Setup\Agent\NullAgent; +use ILIAS\Setup\Config; +use ILIAS\Setup\Objective; +use ilDatabaseUpdateStepsExecutedObjective; -/** - * @author Alexander Killing - */ -class Agent extends Setup\Agent\NullAgent +class Agent extends NullAgent { - public function getUpdateObjective(?Setup\Config $config = null): Setup\Objective + public function getUpdateObjective(?Config $config = null): Objective { - return new \ilDatabaseUpdateStepsExecutedObjective(new ilItemGroupDBUpdateSteps()); + return new ilDatabaseUpdateStepsExecutedObjective(new ilItemGroupDBUpdateSteps()); } public function getMigrations(): array { return [ - new ilItemGroupDisplayMigration() + new ilItemGroupDisplayMigration(), + new ilItemGroupDataTableColumnMigration(), ]; } } diff --git a/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDataTableColumnMigration.php b/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDataTableColumnMigration.php new file mode 100644 index 000000000000..4dd0ba53edac --- /dev/null +++ b/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDataTableColumnMigration.php @@ -0,0 +1,93 @@ +db = $environment->getResource(Environment::RESOURCE_DATABASE); + } + + public function step(Environment $environment): void + { + if ($this->isDataMigrationRequired()) { + return; + } + + if ($this->db->tableColumnExists('itgr_data', 'hide_title')) { + $this->db->dropTableColumn('itgr_data', 'hide_title'); + } + + if ($this->db->tableColumnExists('itgr_data', 'behaviour')) { + $this->db->dropTableColumn('itgr_data', 'behaviour'); + } + } + + public function getRemainingAmountOfSteps(): int + { + if ($this->isDataMigrationRequired()) { + throw new RuntimeException(sprintf('The %s migration must be executed first.', ilItemGroupDisplayMigration::class)); + } + + return (int) ( + $this->db->tableColumnExists('itgr_data', 'hide_title') + || $this->db->tableColumnExists('itgr_data', 'behaviour') + ); + } + + private function isDataMigrationRequired(): bool + { + $result = $this->db->queryF( + 'SELECT COUNT(id) AS cnt FROM itgr_data WHERE hide_title <> %s AND behaviour <> %s', + [ilDBConstants::T_INTEGER, ilDBConstants::T_INTEGER], + [ilItemGroupDisplayMigration::MIGRATED_MARKER, ilItemGroupDisplayMigration::MIGRATED_MARKER] + ); + + return ($this->db->fetchObject($result)?->cnt ?? 0) > 0; + } +} diff --git a/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDisplayMigration.php b/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDisplayMigration.php index 43baeec2b750..69c35766aebc 100644 --- a/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDisplayMigration.php +++ b/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDisplayMigration.php @@ -58,6 +58,10 @@ public function prepare(Environment $environment): void public function step(Environment $environment): void { + if (!$this->columnsExist()) { + return; + } + $result = $this->db->queryF( 'SELECT id, hide_title, behaviour FROM itgr_data WHERE hide_title <> %s AND behaviour <> %s LIMIT 1', [ilDBConstants::T_INTEGER, ilDBConstants::T_INTEGER], @@ -87,6 +91,10 @@ public function step(Environment $environment): void public function getRemainingAmountOfSteps(): int { + if (!$this->columnsExist()) { + return 0; + } + $result = $this->db->queryF( 'SELECT COUNT(id) AS cnt FROM itgr_data WHERE hide_title <> %s AND behaviour <> %s', [ilDBConstants::T_INTEGER, ilDBConstants::T_INTEGER], @@ -120,4 +128,11 @@ private function mapLegacyValues(int $hide_title, int $behaviour): array ] }; } + + private function columnsExist(): bool + { + return + $this->db->tableColumnExists('itgr_data', 'hide_title') + && $this->db->tableColumnExists('itgr_data', 'behaviour'); + } } From 1ac1d8a5002185cf5c02b096f0c9be07f3d89a1f Mon Sep 17 00:00:00 2001 From: Matheus Zych Date: Mon, 14 Sep 2026 15:31:57 +0200 Subject: [PATCH 3/3] ItemGroup: Fix Dataset Import/Export See: https://mantis.ilias.de/view.php?id=31861 See: https://mantis.ilias.de/view.php?id=48043 See: https://mantis.ilias.de/view.php?id=48101 After replacing `hide_title` and `behaviour` with `display` and `toggleable_initially`, export still used the legacy fields. Add dataset version `10.12` and exporter schema `12.0` for the new columns, and map both legacy and current records onto the item group display settings on import. --- .../classes/class.ilItemGroupDataSet.php | 232 ++++++++++-------- .../classes/class.ilItemGroupExporter.php | 4 + 2 files changed, 136 insertions(+), 100 deletions(-) diff --git a/components/ILIAS/ItemGroup/classes/class.ilItemGroupDataSet.php b/components/ILIAS/ItemGroup/classes/class.ilItemGroupDataSet.php index 08855662b47b..c774bae3a701 100755 --- a/components/ILIAS/ItemGroup/classes/class.ilItemGroupDataSet.php +++ b/components/ILIAS/ItemGroup/classes/class.ilItemGroupDataSet.php @@ -16,96 +16,100 @@ * *********************************************************************/ -/** - * Item group data set class - * @author Alexander Killing - */ +declare(strict_types=1); + class ilItemGroupDataSet extends ilDataSet { protected ilObjItemGroup $current_obj; public function getSupportedVersions(): array { - return array("4.3.0", "5.3.0"); + return ['4.3.0', '5.3.0', '10.12']; } public function getXmlNamespace(string $a_entity, string $a_schema_version): string { - return "https://www.ilias.de/xml/Modules/ItemGroup/" . $a_entity; + return "https://www.ilias.de/xml/Modules/ItemGroup/{$a_entity}"; } protected function getTypes(string $a_entity, string $a_version): array { - if ($a_entity == "itgr") { - switch ($a_version) { - case "4.3.0": - return array( - "Id" => "integer", - "Title" => "text", - "Description" => "text"); - case "5.3.0": - return array( - "Id" => "integer", - "HideTitle" => "integer", - "Behaviour" => "integer", - "Title" => "text", - "Description" => "text"); - } - } - - if ($a_entity == "itgr_item") { - switch ($a_version) { - case "4.3.0": - case "5.3.0": - return array( - "ItemGroupId" => "integer", - "ItemId" => "text" - ); - } - } - return []; + return match ($a_entity) { + 'itgr' => match ($a_version) { + '4.3.0' => [ + 'Id' => ilDBConstants::T_INTEGER, + 'Title' => ilDBConstants::T_TEXT, + 'Description' => ilDBConstants::T_TEXT, + ], + '5.3.0' => [ + 'Id' => ilDBConstants::T_INTEGER, + 'HideTitle' => ilDBConstants::T_INTEGER, + 'Behaviour' => ilDBConstants::T_INTEGER, + 'ListPresentation' => ilDBConstants::T_TEXT, + 'TileSize' => ilDBConstants::T_INTEGER, + 'Title' => ilDBConstants::T_TEXT, + 'Description' => ilDBConstants::T_TEXT, + ], + '10.12' => [ + 'Id' => ilDBConstants::T_INTEGER, + 'Display' => ilDBConstants::T_TEXT, + 'ToggleableInitially' => ilDBConstants::T_TEXT, + 'ListPresentation' => ilDBConstants::T_TEXT, + 'TileSize' => ilDBConstants::T_INTEGER, + 'Title' => ilDBConstants::T_TEXT, + 'Description' => ilDBConstants::T_TEXT, + ], + default => [], + }, + 'itgr_item' => match ($a_version) { + '4.3.0', '5.3.0', '10.12' => [ + 'ItemGroupId' => ilDBConstants::T_INTEGER, + 'ItemId' => ilDBConstants::T_TEXT, + ], + default => [], + }, + }; } public function readData(string $a_entity, string $a_version, array $a_ids): void { - $ilDB = $this->db; - - if ($a_entity == "itgr") { - switch ($a_version) { - case "4.3.0": - $this->getDirectDataFromQuery("SELECT obj_id id, title, description " . - " FROM object_data " . - "WHERE " . - $ilDB->in("obj_id", $a_ids, false, "integer")); - break; - case "5.3.0": - $this->getDirectDataFromQuery("SELECT obj_id id, title, description, hide_title, behaviour " . - " FROM object_data JOIN itgr_data ON (object_data.obj_id = itgr_data.id)" . - "WHERE " . - $ilDB->in("obj_id", $a_ids, false, "integer")); - break; - } - } - - if ($a_entity == "itgr_item") { - switch ($a_version) { - case "4.3.0": - case "5.3.0": - $this->getDirectDataFromQuery($q = "SELECT item_group_id itgr_id, item_ref_id item_id" . - " FROM item_group_item " . - "WHERE " . - $ilDB->in("item_group_id", $a_ids, false, "integer")); - break; - } - } + $in_obj_id = $this->db->in('obj_id', $a_ids, false, ilDBConstants::T_INTEGER); + + match ($a_entity) { + 'itgr' => match ($a_version) { + '4.3.0' => $this->getDirectDataFromQuery( + "SELECT obj_id id, title, description, list_presentation, tile_size FROM object_data + WHERE {$in_obj_id}" + ), + '5.3.0' => $this->getDirectDataFromQuery( + "SELECT obj_id id, title, description, hide_title, behaviour, list_presentation, tile_size FROM object_data + INNER JOIN itgr_data ON object_data.obj_id = itgr_data.id + WHERE {$in_obj_id}" + ), + '10.12' => $this->getDirectDataFromQuery( + "SELECT obj_id id, title, description, list_presentation, tile_size, display, toggleable_initially FROM object_data + INNER JOIN itgr_data ON object_data.obj_id = itgr_data.id + WHERE {$in_obj_id}" + ), + default => '', + }, + 'itgr_item' => match ($a_version) { + '4.3.0', '5.3.0', '10.12' => $this->getDirectDataFromQuery( + "SELECT item_group_id itgr_id, item_ref_id item_id FROM item_group_item + WHERE {$this->db->in('item_group_id', $a_ids, false, ilDBConstants::T_INTEGER)}" + ), + default => null, + }, + default => null, + }; } public function getXmlRecord(string $a_entity, string $a_version, array $a_set): array { - if ($a_entity == "itgr_item") { - // make ref id an object id - $a_set["ItemId"] = ilObject::_lookupObjId($a_set["ItemId"]); + if ($a_entity === 'itgr_item') { + $a_set['ItemId'] = ilObject::_lookupObjId($a_set['ItemId']); } + return $a_set; } @@ -115,14 +119,10 @@ protected function getDependencies( ?array $a_rec = null, ?array $a_ids = null ): array { - switch ($a_entity) { - case "itgr": - return array( - "itgr_item" => array("ids" => $a_rec["Id"] ?? []) - ); - } - - return []; + return match ($a_entity) { + 'itgr' => ['itgr_item' => ['ids' => $a_rec['Id'] ?? []]], + default => [], + }; } public function importRecord( @@ -133,45 +133,77 @@ public function importRecord( string $a_schema_version ): void { $a_rec = $this->stripTags($a_rec); + switch ($a_entity) { - case "itgr": - if ($new_id = $a_mapping->getMapping('components/ILIAS/Container', 'objs', $a_rec['Id'])) { + case 'itgr': + $new_id = (int) ($a_mapping->getMapping('components/ILIAS/Container', 'objs', $a_rec['Id']) ?? 0); + + if ($new_id !== 0) { /** @var ilObjItemGroup $newObj */ $newObj = ilObjectFactory::getInstanceByObjId($new_id, false); } else { $newObj = new ilObjItemGroup(); - $newObj->setType("itgr"); + $newObj->setType('itgr'); $newObj->create(true); } - $newObj->setTitle($a_rec["Title"]); - $newObj->setDescription($a_rec["Description"]); - $newObj->setDisplay( - $a_rec["HideTitle"] === "1" - ? ilItemGroupAR::DISPLAY_WITHOUT_TITLE - : ilItemGroupAR::DISPLAY_WITH_TITLE - ); - $newObj->setDisplayWithTitleAndToggleableInitially( - $a_rec["Behaviour"] === "1" - ? ilItemGroupAR::DISPLAY_WITH_TITLE_AND_TOGGLEABLE_INITIALLY_OPEN - : ilItemGroupAR::DISPLAY_WITH_TITLE_AND_TOGGLEABLE_INITIALLY_CLOSED - ); + $newObj->setTitle($a_rec['Title']); + $newObj->setDescription($a_rec['Description']); + $newObj->setListPresentation((string) ($a_rec['ListPresentation'] ?? '')); + $newObj->setTileSize((int) ($a_rec['TileSize'] ?? 0)); + $newObj->setDisplay($this->resolveDisplay($a_rec)); + $newObj->setDisplayWithTitleAndToggleableInitially($this->resolveBehaviour($a_rec)); $newObj->update(); $this->current_obj = $newObj; - $a_mapping->addMapping("components/ILIAS/ItemGroup", "itgr", $a_rec["Id"], $newObj->getId()); - + $a_mapping->addMapping('components/ILIAS/ItemGroup', 'itgr', $a_rec['Id'], (string) $newObj->getId()); break; - case "itgr_item": - if ($obj_id = $a_mapping->getMapping('components/ILIAS/Container', 'objs', $a_rec['ItemId'])) { - $ref_id = current(ilObject::_getAllReferences($obj_id)); - $itgri = new ilItemGroupItems(); - $itgri->setItemGroupId($this->current_obj->getId()); - $itgri->read(); - $itgri->addItem($ref_id); - $itgri->update(); + case 'itgr_item': + $obj_id = (int) ($a_mapping->getMapping('components/ILIAS/Container', 'objs', $a_rec['ItemId']) ?? 0); + + if ($obj_id === 0) { + break; } + + $ref_id = current(ilObject::_getAllReferences($obj_id)); + $itgri = new ilItemGroupItems(); + $itgri->setItemGroupId($this->current_obj->getId()); + $itgri->read(); + $itgri->addItem($ref_id); + $itgri->update(); break; } } + + private function resolveDisplay(array $a_set): string + { + if (isset($a_set['HideTitle'])) { + return match ($a_set['HideTitle']) { + '1' => ilItemGroupAR::DISPLAY_WITHOUT_TITLE, + default => ilItemGroupAR::DISPLAY_WITH_TITLE, + }; + } + + $display = $a_set['Display'] ?? ''; + return match ($display) { + ilItemGroupAR::DISPLAY_WITHOUT_TITLE, ilItemGroupAR::DISPLAY_WITH_TITLE_AND_TOGGLEABLE => $display, + default => ilItemGroupAR::DISPLAY_WITH_TITLE, + }; + } + + private function resolveBehaviour(array $a_set): string + { + if (isset($a_set['Behaviour'])) { + return match ($a_set['Behaviour']) { + '1' => ilItemGroupAR::DISPLAY_WITH_TITLE_AND_TOGGLEABLE_INITIALLY_OPEN, + default => ilItemGroupAR::DISPLAY_WITH_TITLE_AND_TOGGLEABLE_INITIALLY_CLOSED, + }; + } + + $toggleable_initially = $a_set['ToggleableInitially'] ?? ''; + return match ($toggleable_initially) { + ilItemGroupAR::DISPLAY_WITH_TITLE_AND_TOGGLEABLE_INITIALLY_OPEN => $toggleable_initially, + default => ilItemGroupAR::DISPLAY_WITH_TITLE_AND_TOGGLEABLE_INITIALLY_CLOSED, + }; + } } diff --git a/components/ILIAS/ItemGroup/classes/class.ilItemGroupExporter.php b/components/ILIAS/ItemGroup/classes/class.ilItemGroupExporter.php index 9ce46b35ed06..d2b484797345 100755 --- a/components/ILIAS/ItemGroup/classes/class.ilItemGroupExporter.php +++ b/components/ILIAS/ItemGroup/classes/class.ilItemGroupExporter.php @@ -43,6 +43,10 @@ public function getXmlRepresentation( public function getValidSchemaVersions(string $a_entity): array { return array( + "10.12" => array( + "uses_dataset" => true, + "min" => "10.12", + "max" => ""), "5.3.0" => array( "namespace" => "https://www.ilias.de/Modules/ItemGroup/itgr/5_3", "xsd_file" => "ilias_itgr_5_3.xsd",