From 958ad4a8f0137a47e50eb6c748cc102b298e1f39 Mon Sep 17 00:00:00 2001 From: Keven Clausen Date: Tue, 16 Jun 2026 17:51:47 +0200 Subject: [PATCH 1/3] removing join/leave --- .../Player/class.ilLSLaunchlinksBuilder.php | 7 +- .../class.ilLearningSequenceSetupAgent.php | 6 +- ...enceStreamlinePermissionsDBUpdateSteps.php | 118 ++++++++++++++++++ .../class.ilObjLearningSequenceAccess.php | 5 - .../class.ilObjLearningSequenceGUI.php | 7 +- 5 files changed, 130 insertions(+), 13 deletions(-) create mode 100644 components/ILIAS/LearningSequence/classes/Setup/class.ilLearningSequenceStreamlinePermissionsDBUpdateSteps.php diff --git a/components/ILIAS/LearningSequence/classes/Player/class.ilLSLaunchlinksBuilder.php b/components/ILIAS/LearningSequence/classes/Player/class.ilLSLaunchlinksBuilder.php index 80b65fd879f4..6f694940cf15 100755 --- a/components/ILIAS/LearningSequence/classes/Player/class.ilLSLaunchlinksBuilder.php +++ b/components/ILIAS/LearningSequence/classes/Player/class.ilLSLaunchlinksBuilder.php @@ -25,9 +25,6 @@ */ class ilLSLaunchlinksBuilder { - public const PERM_PARTICIPATE = 'participate'; - public const PERM_UNPARTICIPATE = 'unparticipate'; - public const CMD_STANDARD = ilObjLearningSequenceLearnerGUI::CMD_STANDARD; public const CMD_EXTRO = ilObjLearningSequenceLearnerGUI::CMD_EXTRO; public const CMD_START = ilObjLearningSequenceLearnerGUI::CMD_START; @@ -49,7 +46,7 @@ public function __construct( protected function mayJoin(): bool { - return $this->access->checkAccess(self::PERM_PARTICIPATE, '', $this->lso_ref_id); + return $this->access->checkAccess('read', '', $this->lso_ref_id); } public function currentUserMayUnparticipate(): bool @@ -59,7 +56,7 @@ public function currentUserMayUnparticipate(): bool protected function mayUnparticipate(): bool { - return $this->access->checkAccess(self::PERM_UNPARTICIPATE, '', $this->lso_ref_id); + return $this->isMember() && $this->access->checkAccess('read', '', $this->lso_ref_id); } protected function isMember(): bool diff --git a/components/ILIAS/LearningSequence/classes/Setup/class.ilLearningSequenceSetupAgent.php b/components/ILIAS/LearningSequence/classes/Setup/class.ilLearningSequenceSetupAgent.php index cdff906c33ce..14821e4d7fd6 100755 --- a/components/ILIAS/LearningSequence/classes/Setup/class.ilLearningSequenceSetupAgent.php +++ b/components/ILIAS/LearningSequence/classes/Setup/class.ilLearningSequenceSetupAgent.php @@ -69,6 +69,9 @@ public function getUpdateObjective(?Setup\Config $config = null): Setup\Objectiv new ilDatabaseUpdateStepsExecutedObjective( new LSODropActivationDBUpdateSteps() ), + new ilDatabaseUpdateStepsExecutedObjective( + new ilLearningSequenceStreamlinePermissionsDBUpdateSteps() + ), ); } @@ -89,7 +92,8 @@ public function getStatusObjective(Setup\Metrics\Storage $storage): Setup\Object 'Component LearningSequence', true, new ilDatabaseUpdateStepsMetricsCollectedObjective($storage, new ilLearningSequenceRectifyPostConditionsTableDBUpdateSteps()), - new ilDatabaseUpdateStepsMetricsCollectedObjective($storage, new ilLearningSequenceRegisterNotificationType()) + new ilDatabaseUpdateStepsMetricsCollectedObjective($storage, new ilLearningSequenceRegisterNotificationType()), + new ilDatabaseUpdateStepsMetricsCollectedObjective($storage, new ilLearningSequenceStreamlinePermissionsDBUpdateSteps()) ); } diff --git a/components/ILIAS/LearningSequence/classes/Setup/class.ilLearningSequenceStreamlinePermissionsDBUpdateSteps.php b/components/ILIAS/LearningSequence/classes/Setup/class.ilLearningSequenceStreamlinePermissionsDBUpdateSteps.php new file mode 100644 index 000000000000..917916484210 --- /dev/null +++ b/components/ILIAS/LearningSequence/classes/Setup/class.ilLearningSequenceStreamlinePermissionsDBUpdateSteps.php @@ -0,0 +1,118 @@ +db = $db; + } + + public function step_1(): void + { + $read_ops_id = $this->getOperationId('read'); + $participate_ops_id = $this->getOperationId('participate'); + $unparticipate_ops_id = $this->getOperationId('unparticipate'); + + if ($read_ops_id === null || $participate_ops_id === null || $unparticipate_ops_id === null) { + return; + } + + $res = $this->db->query( + "SELECT pa.rol_id, pa.ref_id, pa.ops_id " . + "FROM rbac_pa pa " . + "JOIN object_reference ref ON ref.ref_id = pa.ref_id " . + "JOIN object_data obj ON obj.obj_id = ref.obj_id " . + "WHERE obj.type = '" . self::TYPE_TITLE . "'" + ); + + while ($row = $this->db->fetchAssoc($res)) { + $serialized = $row['ops_id'] ?? null; + if (!is_string($serialized) || $serialized === '') { + continue; + } + + $ops = @unserialize($serialized, ['allowed_classes' => false]); + if (!is_array($ops)) { + continue; + } + + $ops = array_map('intval', $ops); + $has_old = in_array($participate_ops_id, $ops, true) || in_array($unparticipate_ops_id, $ops, true); + if (!$has_old) { + continue; + } + + $ops[] = $read_ops_id; + $ops = array_values(array_unique(array_diff($ops, [$participate_ops_id, $unparticipate_ops_id]))); + sort($ops); + + $this->db->manipulateF( + 'UPDATE rbac_pa SET ops_id = %s WHERE rol_id = %s AND ref_id = %s', + [\ilDBConstants::T_TEXT, \ilDBConstants::T_INTEGER, \ilDBConstants::T_INTEGER], + [serialize($ops), (int) $row['rol_id'], (int) $row['ref_id']] + ); + } + } + + public function step_2(): void + { + $participate_ops_id = $this->getOperationId('participate'); + $unparticipate_ops_id = $this->getOperationId('unparticipate'); + + if ($participate_ops_id === null && $unparticipate_ops_id === null) { + return; + } + + $ops_ids = array_values(array_filter([(int) $participate_ops_id, (int) $unparticipate_ops_id])); + if ($ops_ids === []) { + return; + } + + $in = implode(',', array_map('intval', $ops_ids)); + $sql = + "DELETE FROM rbac_ta " . + "WHERE typ_id IN (" . + "SELECT obj_id FROM object_data " . + "WHERE type = 'typ' AND title = '" . self::TYPE_TITLE . "'" . + ") " . + "AND ops_id IN (" . $in . ")"; + + $this->db->manipulate($sql); + } + + private function getOperationId(string $operation): ?int + { + $res = $this->db->queryF( + 'SELECT ops_id FROM rbac_operations WHERE operation = %s', + [\ilDBConstants::T_TEXT], + [$operation] + ); + $row = $this->db->fetchAssoc($res); + if (!is_array($row) || !isset($row['ops_id'])) { + return null; + } + return (int) $row['ops_id']; + } +} diff --git a/components/ILIAS/LearningSequence/classes/class.ilObjLearningSequenceAccess.php b/components/ILIAS/LearningSequence/classes/class.ilObjLearningSequenceAccess.php index a7597c60a976..3250db2a68fa 100755 --- a/components/ILIAS/LearningSequence/classes/class.ilObjLearningSequenceAccess.php +++ b/components/ILIAS/LearningSequence/classes/class.ilObjLearningSequenceAccess.php @@ -48,11 +48,6 @@ public static function _getCommands(): array 'cmd' => ilObjLearningSequenceGUI::CMD_SETTINGS, 'permission' => 'write', 'lang_var' => 'settings' - ], - [ - 'cmd' => ilObjLearningSequenceGUI::CMD_UNPARTICIPATE, - 'permission' => 'unparticipate', - 'lang_var' => 'unparticipate' ] ); } diff --git a/components/ILIAS/LearningSequence/classes/class.ilObjLearningSequenceGUI.php b/components/ILIAS/LearningSequence/classes/class.ilObjLearningSequenceGUI.php index d2c237b77efe..e52425a91c18 100755 --- a/components/ILIAS/LearningSequence/classes/class.ilObjLearningSequenceGUI.php +++ b/components/ILIAS/LearningSequence/classes/class.ilObjLearningSequenceGUI.php @@ -659,9 +659,11 @@ protected function afterSave(ilObject $new_object): void public function unparticipate(): void { - if ($this->checkAccess('unparticipate')) { + if ($this->checkAccess('read')) { $usr_id = $this->user->getId(); - $this->getObject()->getLSRoles()->leave($usr_id); + if ($this->getObject()->getLSRoles()->isMember($usr_id)) { + $this->getObject()->getLSRoles()->leave($usr_id); + } } $this->ctrl->redirectByClass('ilObjLearningSequenceLearnerGUI', self::CMD_LEARNER_VIEW); } @@ -880,6 +882,7 @@ function (array $c, ProfileData $v) use ($a_data, $udfs): array { $field_id = $field->getIdentifier(); $c[$v->getId()]['udf_' . $field_id] = (string) $v->getAdditionalFieldByIdentifier($field_id); } + return $c; }, [] ); From 379ebafbc9710c67d29ae99edaaea90b1e7b3a69 Mon Sep 17 00:00:00 2001 From: Keven Clausen Date: Wed, 17 Jun 2026 16:34:41 +0200 Subject: [PATCH 2/3] typing --- ...ass.ilLearningSequenceStreamlinePermissionsDBUpdateSteps.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ILIAS/LearningSequence/classes/Setup/class.ilLearningSequenceStreamlinePermissionsDBUpdateSteps.php b/components/ILIAS/LearningSequence/classes/Setup/class.ilLearningSequenceStreamlinePermissionsDBUpdateSteps.php index 917916484210..3bcd2574fcca 100644 --- a/components/ILIAS/LearningSequence/classes/Setup/class.ilLearningSequenceStreamlinePermissionsDBUpdateSteps.php +++ b/components/ILIAS/LearningSequence/classes/Setup/class.ilLearningSequenceStreamlinePermissionsDBUpdateSteps.php @@ -20,7 +20,7 @@ class ilLearningSequenceStreamlinePermissionsDBUpdateSteps implements \ilDatabaseUpdateSteps { - private const TYPE_TITLE = 'lso'; + private const string TYPE_TITLE = 'lso'; protected \ilDBInterface $db; From eff585d745447dea5792493440fed192e78ebfb1 Mon Sep 17 00:00:00 2001 From: Keven Clausen Date: Fri, 19 Jun 2026 06:44:31 +0200 Subject: [PATCH 3/3] Make step_1() require only the RBAC read operation and migrate permissions if either legacy op (participate/unparticipate) exists. Throw if read is missing to avoid silently skipping a broken upgrade. --- ...enceStreamlinePermissionsDBUpdateSteps.php | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/components/ILIAS/LearningSequence/classes/Setup/class.ilLearningSequenceStreamlinePermissionsDBUpdateSteps.php b/components/ILIAS/LearningSequence/classes/Setup/class.ilLearningSequenceStreamlinePermissionsDBUpdateSteps.php index 3bcd2574fcca..d35f72ef0034 100644 --- a/components/ILIAS/LearningSequence/classes/Setup/class.ilLearningSequenceStreamlinePermissionsDBUpdateSteps.php +++ b/components/ILIAS/LearningSequence/classes/Setup/class.ilLearningSequenceStreamlinePermissionsDBUpdateSteps.php @@ -35,7 +35,17 @@ public function step_1(): void $participate_ops_id = $this->getOperationId('participate'); $unparticipate_ops_id = $this->getOperationId('unparticipate'); - if ($read_ops_id === null || $participate_ops_id === null || $unparticipate_ops_id === null) { + if ($read_ops_id === null) { + throw new \RuntimeException('Cannot migrate learning sequence permissions: RBAC operation "read" not found.'); + } + + $old_ops_ids = array_values( + array_filter([ + $participate_ops_id, + $unparticipate_ops_id + ]) + ); + if ($old_ops_ids === []) { return; } @@ -59,13 +69,19 @@ public function step_1(): void } $ops = array_map('intval', $ops); - $has_old = in_array($participate_ops_id, $ops, true) || in_array($unparticipate_ops_id, $ops, true); + $has_old = false; + foreach ($old_ops_ids as $old_ops_id) { + if (in_array((int) $old_ops_id, $ops, true)) { + $has_old = true; + break; + } + } if (!$has_old) { continue; } $ops[] = $read_ops_id; - $ops = array_values(array_unique(array_diff($ops, [$participate_ops_id, $unparticipate_ops_id]))); + $ops = array_values(array_unique(array_diff($ops, array_map('intval', $old_ops_ids)))); sort($ops); $this->db->manipulateF(