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),
346 $filter_values = array_map(fn($v) => $v ===
'' ?
null : $v, $filter_values);
349 if ($filter_values[self::FILTER_USERS] !==
null) {
350 $ret[] =
static fn(array $uids, array $qids): array => [
351 array_intersect($uids, $filter_values[self::FILTER_USERS]),
356 if ($filter_values[self::FILTER_QUESTIONS] !==
null) {
357 $ret[] =
static fn(array $uids, array $qids): array => [
359 array_intersect($qids, $filter_values[self::FILTER_QUESTIONS])
363 if ($filter_values[self::FILTER_USER_FINAL] !==
null) {
364 $ret[] = fn(array $uids, array $qids): array => [
367 function ($uid) use ($filter_values) {
368 $complete = $this->scoring->isScoringComplete($uid);
369 $filter = $filter_values[self::FILTER_USER_FINAL];
370 return ($complete && $filter === self::FILTER_ONLY)
371 || (!$complete && $filter === self::FILTER_HIDE);
378 if ($filter_values[self::FILTER_ANSWERED] !==
null) {
379 $answered = $this->scoring->getAnsweredQuestionIds();
380 $ret[] =
function (array $uids, array $qids) use ($filter_values, $answered): array {
381 $qids = ($filter_values[self::FILTER_ANSWERED] === self::FILTER_ONLY)
382 ? array_intersect($qids, $answered[current($uids)])
383 : array_diff($qids, $answered[current($uids)]);
384 return [$uids, $qids];
388 if ($filter_values[self::FILTER_FINAL] !==
null) {
389 $finalized = $this->scoring->getFinalizedFeedbackIds();
390 $ret[] =
function (array $uids, array $qids) use ($filter_values, $finalized): array {
391 $qids = ($filter_values[self::FILTER_FINAL] === self::FILTER_ONLY)
392 ? array_intersect($qids, $finalized[current($uids)])
393 : array_diff($qids, $finalized[current($uids)]);
394 return [$uids, $qids];
398 if ($filter_values[self::FILTER_SCOREDBY] !==
null) {
399 $scored_by = $this->scoring->getQidsFinalizedBy($filter_values[self::FILTER_SCOREDBY]);
400 $ret[] =
static fn(array $uids, array $qids): array => [
402 array_intersect($qids, $scored_by),
411 $usr_fullname = $this->scoring->getUserFullName($usr_active_id, (
string) $attempt_id);
412 $usr_avatar = (new \ilUserAvatarResolver((
int) $this->scoring->getUserId($usr_active_id, (
string) $attempt_id)))->getAvatar();
413 $scored_participant_entity =
414 $this->ui_factory->entity()->standard(
418 $this->ui_factory->listing()->property()->withProperty(
419 $this->
lng->txt(
'scored_pass'),
420 (
string) ($attempt_id + 1)
422 $this->
lng->txt(
'usr_manscoring_complete'),
423 $this->scoring->isScoringComplete($usr_active_id) ?
424 $this->lng->txt(
'yes') : $this->lng->txt(
'no')
426 $this->
lng->txt(
'exam_id'),
430 return $scored_participant_entity;
435 $tpl = new \ilTemplate(
'tpl.il_as_tst_manual_scoring_consecutive_question.html',
true,
true,
'components/ILIAS/Test');
436 $question = $this->scoring->getQuestionObject($question_id);
437 $question_text = $question->getQuestion();
440 $tpl->setCurrentBlock(
'expandable_title');
441 $question_title = $question->getTitle();
442 $tpl->setVariable(
'TITLE', $question_title);
444 $tpl->setCurrentBlock(
'question_only');
445 $tpl->setVariable(
'EXPAND_COLLAPSE', $this->
lng->txt(
'expand') .
'/' . $this->lng->txt(
'collapse'));
447 $tpl->setVariable(
'QUESTION', $question_text);
448 $tpl->parseCurrentBlock();
450 return $this->ui_factory->legacy()->content($tpl->get());
457 bool $show_feedback_html =
false,
458 bool $show_grade_btn =
false,
459 bool $show_properties =
false
461 $question_gui = $this->scoring->getUserQuestionGUI($question_id, $usr_active_id, $attempt_id);
462 $question_solution = $question_gui->getSolutionOutput(
465 $graphical_output =
true,
466 $result_output =
true,
467 $show_question_only =
true,
468 $show_feedback =
false,
469 $show_correct_solution =
false,
470 $show_manual_scoring =
true,
471 $show_question_text =
false,
472 $show_inline_feedback =
false
474 $tpl = new \ilTemplate(
'tpl.il_as_tst_manual_scoring_consecutive_answer.html',
true,
true,
'components/ILIAS/Test');
476 $usr_question = $question_gui->getObject();
477 $feedback = $this->scoring->getSingleManualFeedback($question_id, $usr_active_id, $attempt_id);
479 if ($show_properties) {
481 $this->ui_factory->listing()->property()
483 $this->
lng->txt(
'tst_highscore_score'),
485 (
string) $usr_question->getReachedPoints($usr_active_id, $attempt_id),
486 $this->lng->txt(
'tst_manscoring_input_of_max'),
487 (
string) $usr_question->getMaximumPoints()
491 $this->
lng->txt(
'finalized_evaluation'),
492 (
bool) ($feedback[
'finalized_evaluation'] ??
false) ?
493 $this->lng->txt(
'yes') : $this->lng->txt(
'no')
495 $info = $this->getWithFinalizedProperties($feedback,
$info);
496 $tpl->setVariable(
'PROPERTIES', $this->ui_renderer->render(
$info));
499 $tpl->setVariable(
'ANSWER', $question_solution);
500 if (array_key_exists(
'feedback', $feedback) && $show_feedback_html) {
503 $this->
refinery->string()->markdown()->toHTML()->transform($feedback[
'feedback'])
505 } elseif ($show_feedback_html) {
506 $tpl->setVariable(
'FEEDBACK', $this->
lng->txt(
'tst_manscoring_no_feedback'));
508 if ($show_grade_btn) {
511 $this->ui_renderer->render(
512 $this->getSingleFormButton($question_id, $usr_active_id, $attempt_id)
517 return $this->ui_factory->legacy()->content($tpl->get());
520 protected function getScoringForm(
string $action,
int $question_id,
int $usr_active_id,
int $attempt_id):
Form
522 $action = $this->scoring_url_builder
523 ->withAction($action)
524 ->withIdParameters($question_id, $usr_active_id, $attempt_id)
525 ->buildURI()->__toString();
527 $question = $this->scoring->getQuestionObject($question_id);
528 $max_points = $question->getMaximumPoints();
529 $score = $question->getReachedPoints($usr_active_id, $attempt_id);
530 $feedback = $this->scoring->getSingleManualFeedback($question_id, $usr_active_id, $attempt_id);
531 $feedback_final = (bool) ($feedback[
'finalized_evaluation'] ??
false);
532 $feedback_txt = $feedback[
'feedback'] ??
'';
535 $inputs[] = $this->ui_factory->input()->field()->numeric(
536 $this->
lng->txt(
'tst_change_points_for_question')
538 ->withByline($this->
lng->txt(
'tst_manscoring_input_of_max') .
' ' . $max_points)
540 $this->
refinery->custom()->constraint(
541 fn($v) => (
float) $v <= $max_points,
543 $this->
lng->txt(
'tst_manscoring_maxpoints_exceeded_input_alert'),
551 $inputs[] = $this->ui_factory->input()->field()->markdown(
553 $this->
lng->txt(
'set_manual_feedback')
558 $inputs[] = $this->ui_factory->input()->field()->checkbox(
559 $this->
lng->txt(
'finalized_evaluation')
564 $to_int = $this->
refinery->kindlyTo()->int();
565 $inputs[] = $this->ui_factory->input()->field()->group(
567 $this->ui_factory->input()->field()->hidden()
568 ->withAdditionalTransformation($to_int)
569 ->withValue($question_id),
570 $this->ui_factory->input()->field()->hidden()
571 ->withAdditionalTransformation($to_int)
572 ->withValue($usr_active_id),
573 $this->ui_factory->input()->field()->hidden()
574 ->withAdditionalTransformation($to_int)
575 ->withValue($attempt_id)
579 $this->
refinery->custom()->transformation(
582 'usr_active_id' => $values[1],
583 'attempt_id' => $values[2],
588 return $this->ui_factory->input()->container()->form()->standard($action,
$inputs)
589 ->withAdditionalTransformation(
590 $this->
refinery->custom()->transformation(
592 'qid' => $values[3][
'qid'],
593 'usr_active_id' => $values[3][
'usr_active_id'],
594 'attempt' => $values[3][
'attempt_id'],
595 'score' => $values[0],
596 'final' => $values[2] ??
false,
597 'feedback' => $values[1],
598 'max_points' => $max_points,
604 protected function collectForUser(
int $usr_active_id, array $question_ids): array
606 $attempt_id = $this->scoring->getAttemptUsedForEvaluation($usr_active_id);
608 foreach ($question_ids as $question_id) {
610 $this->getQuestionRepresentation($question_id,
true),
611 $this->getUserAnswer($question_id, $usr_active_id, $attempt_id,
true,
true,
true)
613 $panel = $this->ui_factory->panel()->standard(
'', $content);
614 $entries[] = $this->ui_factory->legacy()->content(sprintf(
'<a id="anchor_%s_%s"></a>', $question_id, $usr_active_id));
623 foreach ($usr_active_ids as $usr_active_id) {
624 $attempt_id = $this->scoring->getAttemptUsedForEvaluation($usr_active_id);
626 $this->getUserRepresentation($usr_active_id, $attempt_id),
627 $this->getUserAnswer($question_id, $usr_active_id, $attempt_id,
true,
true,
true)
629 $panel = $this->ui_factory->panel()->standard(
'', $content);
630 $entries[] = $this->ui_factory->legacy()->content(sprintf(
'<a id="anchor_%s_%s"></a>', $question_id, $usr_active_id));
640 ServerRequestInterface $request
642 $form = $this->getScoringForm(self::ACTION_STORE, $question_id, $usr_active_id, $attempt_id);
643 if ($request->getMethod() ===
'POST') {
644 $form = $form->withRequest($request);
647 $feedback_properties = $this->getWithFinalizedProperties(
648 $this->scoring->getSingleManualFeedback($question_id, $usr_active_id, $attempt_id),
649 $this->ui_factory->listing()->property()
652 return $this->ui_factory->layout()->alignment()->horizontal()->evenlyDistributed(
653 $this->getUserAnswer($question_id, $usr_active_id, $attempt_id),
654 $this->ui_factory->panel()->standard(
'', [$form, $feedback_properties])
660 $url = $this->scoring_url_builder
661 ->withAction(self::ACTION_FORM_STATE)
662 ->withIdParameters($question_id, $usr_active_id, $attempt_id)
665 return $this->ui_factory->button()->standard(
666 $this->
lng->txt(
'edit_score'),
667 $this->prompt->getShowSignal(
$url)
673 $done_label =
'set_manscoring_done';
674 $done_action = self::ACTION_SCORING_COMPLETE;
675 if ($this->scoring->isScoringComplete($usr_active_id)) {
676 $done_label =
'set_manscoring_open';
677 $done_action = self::ACTION_SCORING_INCOMPLETE;
679 $btn_done = $this->ui_factory->button()->standard(
680 $this->
lng->txt($done_label),
681 $this->scoring_url_builder
682 ->withAction($done_action)
683 ->withUserId($usr_active_id)
684 ->buildURI()->__toString()
691 $this->scoring->store(
693 $data[
'usr_active_id'],
707 if (array_key_exists(
'finalized_by_usr_id', $feedback) && $feedback[
'finalized_by_usr_id'] !== 0) {
708 $feedback_usr_data = $this->
object->getUserData([$feedback[
'finalized_by_usr_id']])[$feedback[
'finalized_by_usr_id']];
709 $feedback_usr_name = $feedback_usr_data[
'firstname'] .
' ' . $feedback_usr_data[
'lastname'];
711 $this->
lng->txt(
'finalized_by'),
714 $this->
lng->txt(
'finalized_on'),
715 (
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)
getFilters(array $filter_values)
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.
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'))