From 16c57231034cf60fb88a0ea6580f685a0fb3aba2 Mon Sep 17 00:00:00 2001 From: Matheus Zych Date: Tue, 13 Jan 2026 13:20:09 +0100 Subject: [PATCH] Test: Add Org-Unit Filter to Manual Scoring See: https://mantis.ilias.de/view.php?id=46659 When org unit position access is active for a test, the manual scoring view listed all participants. PositionsFactory now injects ilAccess and filters the participant set to users the current grader may score via ilOrgUnitOperation::OP_SCORE_PARTICIPANTS. Participant queries in ilObjTest also use a stable ORDER BY (lastname, firstname, active_id). --- .../ILIAS/Test/classes/class.ilObjTest.php | 4 +-- components/ILIAS/Test/src/GUIFactory.php | 4 +-- .../src/Scoring/Manual/ConsecutiveScoring.php | 33 +++++++++++++------ .../src/Scoring/Manual/PositionsFactory.php | 21 ++++++++++-- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/components/ILIAS/Test/classes/class.ilObjTest.php b/components/ILIAS/Test/classes/class.ilObjTest.php index 075ca95eed23..95ed56acaa30 100755 --- a/components/ILIAS/Test/classes/class.ilObjTest.php +++ b/components/ILIAS/Test/classes/class.ilObjTest.php @@ -4297,7 +4297,7 @@ public function getTestParticipants(): array LEFT JOIN usr_data ON tst_active.user_fi = usr_data.usr_id WHERE tst_active.test_fi = %s - ORDER BY usr_data.lastname + ORDER BY usr_data.lastname, usr_data.firstname, tst_active.active_id "; $result = $this->db->queryF( $query, @@ -4321,7 +4321,7 @@ public function getTestParticipants(): array LEFT JOIN usr_data ON tst_active.user_fi = usr_data.usr_id WHERE tst_active.test_fi = %s - ORDER BY usr_data.lastname + ORDER BY usr_data.lastname, usr_data.firstname, tst_active.active_id "; $result = $this->db->queryF( $query, diff --git a/components/ILIAS/Test/src/GUIFactory.php b/components/ILIAS/Test/src/GUIFactory.php index 49fc2e305a33..ae478334cd68 100644 --- a/components/ILIAS/Test/src/GUIFactory.php +++ b/components/ILIAS/Test/src/GUIFactory.php @@ -76,7 +76,6 @@ public function __construct( $this->internal['manscoring.testscoring']($test_obj), $this->test_dic['scoring.manual.done_helper'], $this->global_dic['ilUser'], - $this->internal['test.access']($test_obj), $this->test_dic['participant.repository'], $this->global_dic['lng'], ); @@ -84,7 +83,8 @@ public function __construct( $this->internal['manscoring.positionsfactory'] = fn(\ilObjTest $test_obj): PositionsFactory => new PositionsFactory( $test_obj, - $this->test_dic['question.general_properties.repository'] + $this->test_dic['question.general_properties.repository'], + $this->test_dic['participant.access_filter.factory'] ); $this->internal['manscoring.testscoring'] = fn(\ilObjTest $test_obj): TestScoring => diff --git a/components/ILIAS/Test/src/Scoring/Manual/ConsecutiveScoring.php b/components/ILIAS/Test/src/Scoring/Manual/ConsecutiveScoring.php index 3bac8f205a12..6d2d13de03e5 100644 --- a/components/ILIAS/Test/src/Scoring/Manual/ConsecutiveScoring.php +++ b/components/ILIAS/Test/src/Scoring/Manual/ConsecutiveScoring.php @@ -29,6 +29,11 @@ class ConsecutiveScoring { + /** + * @var int[] + */ + private array $anon_only_participant_ids; + public function __construct( private readonly Positions $positions, private readonly \ilObjTest $object, @@ -37,10 +42,10 @@ public function __construct( private TestScoring $scorer, private TestManScoringDoneHelper $scoring_done_helper, private \ilObjUser $current_user, - private readonly \ilTestAccess $test_access, private readonly ParticipantRepository $participant_repository, private readonly Language $lng, ) { + $this->anon_only_participant_ids = $this->object->getAnonOnlyParticipantIds(); } public function getPositions(): Positions @@ -92,13 +97,11 @@ public function getUserFullName( int $usr_active_id, string $attempt ): string { - if ($this->object->getAnonymity() - || !$this->test_access->checkScoreParticipantsAccess() - ) { + $participant = $this->participant_repository->getParticipantByActiveId($this->object->getTestId(), $usr_active_id); + + if ($this->shouldUsePseudonymousParticipantLabel($participant->getUserId())) { return \ilObjTest::buildExamId($usr_active_id, $attempt, $this->object->getId()); } - - $participant = $this->participant_repository->getParticipantByActiveId($this->object->getTestId(), $usr_active_id); $importname = $participant->getImportname(); $user_id = $participant->getUserId(); if ($user_id === ANONYMOUS_USER_ID && $importname !== null && $importname !== '') { @@ -119,12 +122,22 @@ public function getUserId( int $usr_active_id, string $attempt, ): string { - if ($this->object->getAnonymity() - || !$this->test_access->checkScoreParticipantsAccess() - ) { + $user_id = $this->object->_getUserIdFromActiveId($usr_active_id); + + if ($this->shouldUsePseudonymousParticipantLabel($user_id)) { return \ilObjTest::buildExamId($usr_active_id, $attempt, $this->object->getId()); } - return (string) $this->object->_getUserIdFromActiveId($usr_active_id); + + return (string) $user_id; + } + + private function shouldUsePseudonymousParticipantLabel(int $user_id): bool + { + if ($this->object->getAnonymity()) { + return true; + } + + return in_array($user_id, $this->anon_only_participant_ids, true); } public function getSingleManualFeedback(int $qid, int $usr_active_id, int $attempt_id): array diff --git a/components/ILIAS/Test/src/Scoring/Manual/PositionsFactory.php b/components/ILIAS/Test/src/Scoring/Manual/PositionsFactory.php index 54d3ebb75774..a420922d4198 100644 --- a/components/ILIAS/Test/src/Scoring/Manual/PositionsFactory.php +++ b/components/ILIAS/Test/src/Scoring/Manual/PositionsFactory.php @@ -26,7 +26,8 @@ class PositionsFactory { public function __construct( private readonly \ilObjTest $test_obj, - private readonly GeneralQuestionPropertiesRepository $question_repo + private readonly GeneralQuestionPropertiesRepository $question_repo, + private readonly \ilTestParticipantAccessFilterFactory $participant_access_filter_factory ) { } @@ -35,7 +36,10 @@ public function get(): Positions $user_questions = []; $user_attempts = []; $question_properties = []; - foreach (array_keys($this->test_obj->getTestParticipants()) as $usr_active_id) { + + $test_participants = $this->filterParticipantsByAccessOfCurrentUser($this->test_obj->getTestParticipants()); + + foreach (array_keys($test_participants) as $usr_active_id) { $attempt = \ilObjTest::_getResultPass($usr_active_id); $user_attempts[$usr_active_id] = $attempt; $user_questions[$usr_active_id] = $this->test_obj->isRandomTest() @@ -73,4 +77,17 @@ public function get(): Positions $question_properties ); } + + private function filterParticipantsByAccessOfCurrentUser(array $test_participants): array + { + $allowed_user_ids = $this->participant_access_filter_factory + ->getScoreParticipantsUserFilter($this->test_obj->getRefId())( + array_column($test_participants, 'usr_id') + ); + + return array_filter( + $test_participants, + static fn(array $participant): bool => in_array($participant['usr_id'], $allowed_user_ids, true) + ); + } }