19declare(strict_types=1);
26use Psr\Http\Message\ServerRequestInterface;
72 protected readonly UIFactory $ui_factory,
73 protected UIRenderer $ui_renderer,
75 protected readonly ServerRequestInterface $request,
81 $this->prompt = $this->ui_factory->prompt()->standard($this->scoring_url_builder->buildURI());
85 if (!$this->test_access->checkScoreParticipantsAccess()
86 && !$this->test_access->checkScoreParticipantsAccessAnon()
88 \ilObjTestGUI::accessViolationRedirect();
91 if (!$this->
object->getGlobalSettings()->isManualScoringEnabled()) {
93 $this->tpl->setOnScreenMessage(
'failure', $this->
lng->txt(
'manscoring_not_allowed'),
true);
94 $this->
ctrl->redirectByClass([ilRepositoryGUI::class, ilObjTestGUI::class, ilInfoScreenGUI::class]);
100 [$question_id,
$user_id, $attempt_id] = $this->scoring_url_builder->getIdParameters();
105 $this->ui_renderer->render([
109 $response = $this->ui_factory->prompt()->state()->show(
114 $this->test_response->sendAsync(
115 $this->ui_renderer->renderAsync(
$response)
121 ->withRequest($this->request);
122 $formdata = $form->getData();
123 if ($formdata !==
null) {
124 $this->
store($formdata);
126 $this->
lng->txt(
'tst_saved_manscoring_successfully'),
128 $this->scoring->getUserFullName(
$user_id, (
string) $attempt_id)
130 $this->tpl->setOnScreenMessage(
'success', $msg,
true);
132 $anchor = sprintf(
'anchor_%s_%s', $question_id,
$user_id);
133 $url = $this->scoring_url_builder
134 ->withAction(self::CMD_VIEW)
135 ->withFragment($anchor)
136 ->withForceRedirect()
138 $response = $this->ui_factory->prompt()->state()->redirect(
$url);
140 $response = $this->ui_factory->prompt()->state()->show($form);
143 $this->test_response->sendAsync(
144 $this->ui_renderer->renderAsync(
$response)
150 ->withRequest($this->request);
151 $formdata = $form->getData();
152 if ($formdata ===
null) {
153 $this->tpl->setContent($this->
view());
155 $this->
store($formdata);
157 $this->
lng->txt(
'tst_saved_manscoring_successfully'),
159 $this->scoring->getUserFullName(
$user_id, (
string) $attempt_id)
161 $this->tpl->setOnScreenMessage(
'success', $msg,
true);
162 $this->scoring_url_builder->withAction(self::CMD_VIEW)->redirect();
167 $this->scoring->completeScoring(
$user_id,
true);
168 $msg = $this->
lng->txt(
'manscoring_finalized');
169 $this->tpl->setOnScreenMessage(
'success', $msg,
true);
170 $this->scoring_url_builder->withAction(self::CMD_VIEW)->redirect();
174 $this->scoring->completeScoring(
$user_id,
false);
175 $msg = $this->
lng->txt(
'manscoring_finalized_removed');
176 $this->tpl->setOnScreenMessage(
'success', $msg,
true);
177 $this->scoring_url_builder->withAction(self::CMD_VIEW)->redirect();
181 $this->tpl->setContent($this->
view());
185 throw new \Exception(
'no such command/action: ' . $act);
189 protected function view(): string
192 $this->filter_values = $this->filter_service->getData($filter);
194 $sequence = $this->ui_factory->navigation()->sequence(
198 ->withId(
'cs_' . (
string) $this->
object->getRefId())
200 ->withRequest($this->request);
202 return $this->ui_renderer->render([
212 ServerRequestInterface $request,
213 mixed $viewcontrol_values,
217 return $this->scoring->getPositions()
218 ->applyFilters(...$this->
getFilters($filter_values))
219 ->get($viewcontrol_values);
223 ServerRequestInterface $request,
224 mixed $position_data,
225 mixed $viewcontrol_values,
228 if ($position_data === null) {
229 return $this->ui_factory->legacy()->segment(
231 $this->
lng->txt(
'ui_table_no_records')
235 [$usr_active_ids, $question_ids] = $position_data;
236 $usr_active_id = current($usr_active_ids);
237 $question_id = current($question_ids);
239 $attempt_id = $this->scoring->getAttemptUsedForEvaluation($usr_active_id);
240 $question_representation = $this->getQuestionRepresentation($question_id,
true);
241 $user_representation = $this->getUserRepresentation($usr_active_id, $attempt_id);
243 return $this->ui_factory->legacy()->segment(
244 $this->ui_renderer->render(
245 $viewcontrol_values->isUserCentric() ? $user_representation : $question_representation
247 $this->ui_renderer->render(
248 $viewcontrol_values->isSingle()
250 $viewcontrol_values->isUserCentric() ? $question_representation : $user_representation,
251 $this->singleSegmentContent($question_id, $usr_active_id, $attempt_id, $request)
254 $viewcontrol_values->isUserCentric()
255 ? $this->collectForUser($usr_active_id, $question_ids)
256 : $this->collectForQuestion($question_id, $usr_active_ids),
261 ->withSegmentActions(
263 $viewcontrol_values->isUserCentric()
264 ? $this->getSegmentActionsForUser($usr_active_id)
272 $this->ui_factory->input()->viewControl()->mode(
274 ConsecutiveScoringMode::ORIENTATION_USER => $this->
lng->txt(
'mode_user'),
275 ConsecutiveScoringMode::ORIENTATION_QUESTION => $this->lng->txt(
'mode_question'),
278 $this->ui_factory->input()->viewControl()->mode(
280 ConsecutiveScoringMode::MODE_ALL_AT_ONCE => $this->
lng->txt(
'mode_allatonce'),
281 ConsecutiveScoringMode::MODE_ONE_BY_ONE => $this->lng->txt(
'mode_onebyone'),
286 return $this->ui_factory->input()->container()->viewControl()->standard($vcs)
287 ->withAdditionalTransformation(
288 $this->
refinery->custom()->transformation(
297 self::FILTER_USERS => $this->ui_factory->input()->field()->multiselect(
298 $this->
lng->txt(
'tst_man_scoring_userselection'),
299 $this->scoring->getParticipantNames()
301 self::FILTER_QUESTIONS => $this->ui_factory->input()->field()->multiselect(
302 $this->
lng->txt(
'tst_man_scoring_questionselection'),
303 $this->scoring->getQuestionTitles($this->lng)
305 self::FILTER_ANSWERED => $this->ui_factory->input()->field()->select(
306 $this->
lng->txt(
'tst_man_scoring_only_answered'),
308 self::FILTER_ONLY => $this->lng->txt(
'tst_man_scoring_answered_only'),
309 self::FILTER_HIDE => $this->lng->txt(
'tst_man_scoring_answered_hide'),
312 self::FILTER_FINAL => $this->ui_factory->input()->field()->select(
313 $this->
lng->txt(
'tst_man_scoring_finalized'),
315 self::FILTER_ONLY => $this->lng->txt(
'tst_man_scoring_finalized_only'),
316 self::FILTER_HIDE => $this->lng->txt(
'tst_man_scoring_finalized_hide'),
319 self::FILTER_USER_FINAL => $this->ui_factory->input()->field()->select(
320 $this->
lng->txt(
'finalized_evaluation'),
322 self::FILTER_ONLY => $this->lng->txt(
'evaluated_users'),
323 self::FILTER_HIDE => $this->lng->txt(
'not_evaluated_users')
326 self::FILTER_SCOREDBY => $this->ui_factory->input()->field()->multiselect(
327 $this->
lng->txt(
'scored_by'),
328 $this->scoring->getAllFinalizingUserNames()
332 return $this->filter_service->standard(
333 'csfilter_' . (
string) $this->
object->getRefId(),
334 $this->ctrl->getLinkTarget($this, self::CMD_VIEW),
336 array_map(fn() =>
true, $filter),
345 ?array $values_from_filter
347 if ($values_from_filter === null) {
350 $filter_values = array_map(
351 fn($v) => $v ===
'' ?
null : $v,
356 if ($filter_values[self::FILTER_USERS] !==
null) {
357 $ret[] =
static fn(array $uids, array $qids): array => [
358 array_intersect($uids, $filter_values[self::FILTER_USERS]),
363 if ($filter_values[self::FILTER_QUESTIONS] !==
null) {
364 $ret[] =
static fn(array $uids, array $qids): array => [
366 array_intersect($qids, $filter_values[self::FILTER_QUESTIONS])
370 if ($filter_values[self::FILTER_USER_FINAL] !==
null) {
371 $ret[] = fn(array $uids, array $qids): array => [
374 function ($uid) use ($filter_values) {
375 $complete = $this->scoring->isScoringComplete($uid);
376 $filter = $filter_values[self::FILTER_USER_FINAL];
377 return ($complete && $filter === self::FILTER_ONLY)
378 || (!$complete && $filter === self::FILTER_HIDE);
385 if ($filter_values[self::FILTER_ANSWERED] !==
null) {
386 $answered = $this->scoring->getAnsweredQuestionIds();
387 $ret[] =
function (array $uids, array $qids) use ($filter_values, $answered): array {
388 $qids = ($filter_values[self::FILTER_ANSWERED] === self::FILTER_ONLY)
389 ? array_intersect($qids, $answered[current($uids)])
390 : array_diff($qids, $answered[current($uids)]);
391 return [$uids, $qids];
395 if ($filter_values[self::FILTER_FINAL] !==
null) {
396 $finalized = $this->scoring->getFinalizedFeedbackIds();
397 $ret[] =
function (array $uids, array $qids) use ($filter_values, $finalized): array {
398 $qids = ($filter_values[self::FILTER_FINAL] === self::FILTER_ONLY)
399 ? array_intersect($qids, $finalized[current($uids)])
400 : array_diff($qids, $finalized[current($uids)]);
401 return [$uids, $qids];
405 if ($filter_values[self::FILTER_SCOREDBY] !==
null) {
406 $scored_by = $this->scoring->getQidsFinalizedBy($filter_values[self::FILTER_SCOREDBY]);
407 $ret[] =
static fn(array $uids, array $qids): array => [
409 array_intersect($qids, $scored_by),
418 $usr_fullname = $this->scoring->getUserFullName($usr_active_id, (
string) $attempt_id);
419 $usr_avatar = (new \ilUserAvatarResolver((
int) $this->scoring->getUserId($usr_active_id, (
string) $attempt_id)))->getAvatar();
420 $scored_participant_entity =
421 $this->ui_factory->entity()->standard(
425 $this->ui_factory->listing()->property()->withProperty(
426 $this->
lng->txt(
'scored_pass'),
427 (
string) ($attempt_id + 1)
429 $this->
lng->txt(
'usr_manscoring_complete'),
430 $this->scoring->isScoringComplete($usr_active_id) ?
431 $this->lng->txt(
'yes') : $this->lng->txt(
'no')
433 $this->
lng->txt(
'exam_id'),
437 return $scored_participant_entity;
442 $tpl = new \ilTemplate(
'tpl.il_as_tst_manual_scoring_consecutive_question.html',
true,
true,
'components/ILIAS/Test');
443 $question = $this->scoring->getQuestionObject($question_id);
444 $question_text = $question->getQuestion();
447 $tpl->setCurrentBlock(
'expandable_title');
448 $question_title = $question->getTitle();
449 $tpl->setVariable(
'TITLE', $question_title);
451 $tpl->setCurrentBlock(
'question_only');
452 $tpl->setVariable(
'EXPAND_COLLAPSE', $this->
lng->txt(
'expand') .
'/' . $this->lng->txt(
'collapse'));
454 $tpl->setVariable(
'QUESTION', $question_text);
455 $tpl->parseCurrentBlock();
457 return $this->ui_factory->legacy()->content($tpl->get());
464 bool $show_feedback_html =
false,
465 bool $show_grade_btn =
false,
466 bool $show_properties =
false
468 $question_gui = $this->scoring->getUserQuestionGUI($question_id, $usr_active_id, $attempt_id);
469 $question_solution = $question_gui->getSolutionOutput(
472 $graphical_output =
true,
473 $result_output =
true,
474 $show_question_only =
true,
475 $show_feedback =
false,
476 $show_correct_solution =
false,
477 $show_manual_scoring =
true,
478 $show_question_text =
false,
479 $show_inline_feedback =
false
481 $tpl = new \ilTemplate(
'tpl.il_as_tst_manual_scoring_consecutive_answer.html',
true,
true,
'components/ILIAS/Test');
483 $usr_question = $question_gui->getObject();
484 $feedback = $this->scoring->getSingleManualFeedback($question_id, $usr_active_id, $attempt_id);
486 if ($show_properties) {
488 $this->ui_factory->listing()->property()
490 $this->
lng->txt(
'tst_highscore_score'),
492 (
string) $usr_question->getReachedPoints($usr_active_id, $attempt_id),
493 $this->lng->txt(
'tst_manscoring_input_of_max'),
494 (
string) $usr_question->getMaximumPoints()
498 $this->
lng->txt(
'finalized_evaluation'),
499 (
bool) ($feedback[
'finalized_evaluation'] ??
false) ?
500 $this->lng->txt(
'yes') : $this->lng->txt(
'no')
502 $info = $this->getWithFinalizedProperties($feedback,
$info);
503 $tpl->setVariable(
'PROPERTIES', $this->ui_renderer->render(
$info));
506 $tpl->setVariable(
'ANSWER', $question_solution);
507 if (array_key_exists(
'feedback', $feedback) && $show_feedback_html) {
510 $this->
refinery->string()->markdown()->toHTML()->transform($feedback[
'feedback'])
512 } elseif ($show_feedback_html) {
513 $tpl->setVariable(
'FEEDBACK', $this->
lng->txt(
'tst_manscoring_no_feedback'));
515 if ($show_grade_btn) {
518 $this->ui_renderer->render(
519 $this->getSingleFormButton($question_id, $usr_active_id, $attempt_id)
524 return $this->ui_factory->legacy()->content($tpl->get());
527 protected function getScoringForm(
string $action,
int $question_id,
int $usr_active_id,
int $attempt_id):
Form
529 $action = $this->scoring_url_builder
530 ->withAction($action)
531 ->withIdParameters($question_id, $usr_active_id, $attempt_id)
532 ->buildURI()->__toString();
534 $question = $this->scoring->getQuestionObject($question_id);
535 $max_points = $question->getMaximumPoints();
536 $score = $question->getReachedPoints($usr_active_id, $attempt_id);
537 $feedback = $this->scoring->getSingleManualFeedback($question_id, $usr_active_id, $attempt_id);
538 $feedback_final = (bool) ($feedback[
'finalized_evaluation'] ??
false);
539 $feedback_txt = $feedback[
'feedback'] ??
'';
542 $inputs[] = $this->ui_factory->input()->field()->numeric(
543 $this->
lng->txt(
'tst_change_points_for_question')
545 ->withByline($this->
lng->txt(
'tst_manscoring_input_of_max') .
' ' . $max_points)
547 $this->
refinery->custom()->constraint(
548 fn($v) => (
float) $v <= $max_points,
550 $this->
lng->txt(
'tst_manscoring_maxpoints_exceeded_input_alert'),
558 $inputs[] = $this->ui_factory->input()->field()->markdown(
560 $this->
lng->txt(
'set_manual_feedback')
565 $inputs[] = $this->ui_factory->input()->field()->checkbox(
566 $this->
lng->txt(
'finalized_evaluation')
571 $to_int = $this->
refinery->kindlyTo()->int();
572 $inputs[] = $this->ui_factory->input()->field()->group(
574 $this->ui_factory->input()->field()->hidden()
575 ->withAdditionalTransformation($to_int)
576 ->withValue($question_id),
577 $this->ui_factory->input()->field()->hidden()
578 ->withAdditionalTransformation($to_int)
579 ->withValue($usr_active_id),
580 $this->ui_factory->input()->field()->hidden()
581 ->withAdditionalTransformation($to_int)
582 ->withValue($attempt_id)
586 $this->
refinery->custom()->transformation(
589 'usr_active_id' => $values[1],
590 'attempt_id' => $values[2],
595 return $this->ui_factory->input()->container()->form()->standard($action,
$inputs)
596 ->withAdditionalTransformation(
597 $this->
refinery->custom()->transformation(
599 'qid' => $values[3][
'qid'],
600 'usr_active_id' => $values[3][
'usr_active_id'],
601 'attempt' => $values[3][
'attempt_id'],
602 'score' => $values[0],
603 'final' => $values[2] ??
false,
604 'feedback' => $values[1],
605 'max_points' => $max_points,
611 protected function collectForUser(
int $usr_active_id, array $question_ids): array
613 $attempt_id = $this->scoring->getAttemptUsedForEvaluation($usr_active_id);
615 foreach ($question_ids as $question_id) {
617 $this->getQuestionRepresentation($question_id,
true),
618 $this->getUserAnswer($question_id, $usr_active_id, $attempt_id,
true,
true,
true)
620 $panel = $this->ui_factory->panel()->standard(
'', $content);
621 $entries[] = $this->ui_factory->legacy()->content(sprintf(
'<a id="anchor_%s_%s"></a>', $question_id, $usr_active_id));
630 foreach ($usr_active_ids as $usr_active_id) {
631 $attempt_id = $this->scoring->getAttemptUsedForEvaluation($usr_active_id);
633 $this->getUserRepresentation($usr_active_id, $attempt_id),
634 $this->getUserAnswer($question_id, $usr_active_id, $attempt_id,
true,
true,
true)
636 $panel = $this->ui_factory->panel()->standard(
'', $content);
637 $entries[] = $this->ui_factory->legacy()->content(sprintf(
'<a id="anchor_%s_%s"></a>', $question_id, $usr_active_id));
647 ServerRequestInterface $request
649 $form = $this->getScoringForm(self::ACTION_STORE, $question_id, $usr_active_id, $attempt_id);
650 if ($request->getMethod() ===
'POST') {
651 $form = $form->withRequest($request);
654 $feedback_properties = $this->getWithFinalizedProperties(
655 $this->scoring->getSingleManualFeedback($question_id, $usr_active_id, $attempt_id),
656 $this->ui_factory->listing()->property()
659 return $this->ui_factory->layout()->alignment()->horizontal()->evenlyDistributed(
660 $this->getUserAnswer($question_id, $usr_active_id, $attempt_id),
661 $this->ui_factory->panel()->standard(
'', [$form, $feedback_properties])
667 $url = $this->scoring_url_builder
668 ->withAction(self::ACTION_FORM_STATE)
669 ->withIdParameters($question_id, $usr_active_id, $attempt_id)
672 return $this->ui_factory->button()->standard(
673 $this->
lng->txt(
'edit_score'),
674 $this->prompt->getShowSignal(
$url)
680 $done_label =
'set_manscoring_done';
681 $done_action = self::ACTION_SCORING_COMPLETE;
682 if ($this->scoring->isScoringComplete($usr_active_id)) {
683 $done_label =
'set_manscoring_open';
684 $done_action = self::ACTION_SCORING_INCOMPLETE;
686 $btn_done = $this->ui_factory->button()->standard(
687 $this->
lng->txt($done_label),
688 $this->scoring_url_builder
689 ->withAction($done_action)
690 ->withUserId($usr_active_id)
691 ->buildURI()->__toString()
698 $this->scoring->store(
700 $data[
'usr_active_id'],
714 if (array_key_exists(
'finalized_by_usr_id', $feedback) && $feedback[
'finalized_by_usr_id'] !== 0) {
715 $feedback_usr_data = $this->
object->getUserData([$feedback[
'finalized_by_usr_id']])[$feedback[
'finalized_by_usr_id']];
716 $feedback_usr_name = $feedback_usr_data[
'firstname'] .
' ' . $feedback_usr_data[
'lastname'];
718 $this->
lng->txt(
'finalized_by'),
721 $this->
lng->txt(
'finalized_on'),
722 (
string) $feedback[
'finalized_time'],
Builds a Color from either hex- or rgb values.
const TAB_ID_MANUAL_SCORING
getSingleFormButton(int $question_id, int $usr_active_id, int $attempt_id)
singleSegmentContent(int $question_id, int $usr_active_id, int $attempt_id, ServerRequestInterface $request)
__construct(protected readonly \ilCtrlInterface $ctrl, protected readonly \ilGlobalTemplateInterface $tpl, protected readonly \ilTabsGUI $tabs, protected readonly \ilLanguage $lng, protected readonly \ilObjTest $object, protected readonly \ilTestAccess $test_access, protected readonly UIFactory $ui_factory, protected UIRenderer $ui_renderer, protected readonly Refinery $refinery, protected readonly ServerRequestInterface $request, protected readonly ResponseHandler $test_response, protected readonly ConsecutiveScoring $scoring, protected readonly ConsecutiveScoringURLs $scoring_url_builder, protected readonly \ilUIFilterService $filter_service,)
getSegmentActionsForUser(int $usr_active_id)
getUserRepresentation(int $usr_active_id, int $attempt_id)
getAllPositions(ServerRequestInterface $request, mixed $viewcontrol_values, mixed $filter_values)
collectForUser(int $usr_active_id, array $question_ids)
getQuestionRepresentation(int $question_id, bool $show_title=false)
collectForQuestion(int $question_id, array $usr_active_ids)
getSegment(ServerRequestInterface $request, mixed $position_data, mixed $viewcontrol_values, mixed $filter_values)
Receives position data (provided by getAllPositions) and builds a segment.
getFilters(?array $values_from_filter)
getWithFinalizedProperties(array $feedback, PropertyListing $info)
const ACTION_SCORING_COMPLETE
getUserAnswer(int $question_id, int $usr_active_id, int $attempt_id, bool $show_feedback_html=false, bool $show_grade_btn=false, bool $show_properties=false)
const ACTION_SCORING_INCOMPLETE
getScoringForm(string $action, int $question_id, int $usr_active_id, int $attempt_id)
withDetails(PropertyListing|Content ... $details)
static buildExamId($active_id, $pass, $test_obj_id=null)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
The SegmentRetrieval defines available stops for the sequence and builds it's segments.
An entity that renders components to a string output.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
if(!file_exists('../ilias.ini.php'))