19declare(strict_types=1);
37use Psr\Http\Message\ServerRequestInterface;
41 private const ID =
'pt';
46 private readonly UIFactory $ui_factory,
53 private readonly ResultsDataFactory $results_data_factory,
54 private readonly ResultsPresentationSettings $results_presentation_settings,
55 private readonly \
ilObjUser $current_user,
59 $this->scoring_enabled = $this->test_object->getGlobalSettings()->isManualScoringEnabled();
74 $this->test_request->getRequest(),
75 $this->ui_service->filter()->getData($filter)
80 $table->withActions($this->table_actions->getEnabledActions(...$this->acquireParameters($url_builder)))
85 mixed $additional_viewcontrol_data,
87 mixed $additional_parameters
89 return $this->
repository->countParticipants($this->test_object->getTestId(), $filter_data);
92 public function getRows(
94 array $visible_column_ids,
97 mixed $additional_viewcontrol_data,
99 mixed $additional_parameters
101 $processing_time = $this->test_object->getProcessingTimeInSeconds();
102 $reset_time_on_new_attempt = $this->test_object->getResetProcessingTime();
104 $current_user_timezone = new \DateTimeZone($this->current_user->getTimeZone());
107 foreach ($this->getViewControlledRecords($filter_data,
$range, $order) as $record) {
108 $total_duration = $record->getTotalDuration($processing_time);
109 $status_of_attempt = $record->getAttemptOverviewInformation()?->getStatusOfAttempt() ?? StatusOfAttempt::NOT_YET_STARTED;
111 'name' => $record->getDisplayName($this->
lng, $this->test_object->getAnonymity()),
112 'login' => $record->getLogin(),
113 'matriculation' => $record->getMatriculation(),
114 'total_time_on_task' => $record->getAttemptOverviewInformation()?->getHumanReadableTotalTimeOnTask() ??
'',
115 'status_of_attempt' => $status_of_attempt->getTranslation($this->
lng),
116 'id_of_attempt' => $record->getAttemptOverviewInformation()?->getExamId(),
117 'ip_range' => $record->getClientIpTo() !==
'' || $record->getClientIpFrom() !==
''
118 ? sprintf(
'%s - %s', $record->getClientIpFrom(), $record->getClientIpTo())
120 'total_attempts' => $record->getAttemptOverviewInformation()?->getNrOfAttempts() ?? 0,
121 'extra_time' => $record->getExtraTime() > 0 ? sprintf(
'%d min', $record->getExtraTime()) :
'',
122 'total_duration' => $total_duration > 0 ? sprintf(
'%d min', $total_duration / 60) :
'',
123 'remaining_duration' => sprintf(
'%d min', $record->getRemainingDuration($processing_time, $reset_time_on_new_attempt) / 60),
126 if ($this->scoring_enabled) {
127 $row[
'scoring_finalized'] = $record->isScoringFinalized();
130 $first_access = $record->getAttemptOverviewInformation()?->getStartedDate();
131 if ($first_access !==
null) {
132 $row[
'attempt_started_at'] = $first_access->setTimezone($current_user_timezone);
135 $last_access = $record->getLastAccess();
136 if ($last_access !==
null) {
137 $row[
'last_access'] = $last_access->setTimezone($current_user_timezone);
139 if ($record->getActiveId() !==
null
140 && $this->test_access->checkResultsAccessForActiveId(
141 $record->getActiveId(),
142 $this->test_object->getTestId()
143 ) || $record ===
null && $this->test_access->checkParticipantsResultsAccess()) {
144 $row[
'reached_points'] = sprintf(
145 $this->
lng->txt(
'tst_reached_points_of_max'),
146 $record->getAttemptOverviewInformation()?->getReachedPoints(),
147 $record->getAttemptOverviewInformation()?->getAvailablePoints()
149 $row[
'nr_of_answered_questions'] = sprintf(
150 $this->
lng->txt(
'tst_answered_questions_of_total'),
151 $record->getAttemptOverviewInformation()?->getNrOfAnsweredQuestions(),
152 $record->getAttemptOverviewInformation()?->getNrOfTotalQuestions()
154 $row[
'percent_of_available_points'] = $record->getAttemptOverviewInformation()?->getReachedPointsInPercent();
157 if ($status_of_attempt->isFinished()) {
158 $row[
'test_passed'] = $record->getAttemptOverviewInformation()?->hasPassingMark() ??
false;
159 $row[
'mark'] = $record->getAttemptOverviewInformation()?->getMark();
162 yield $this->table_actions->onDataRow(
164 "{$record->getUserId()}_{$record->getActiveId()}",
174 return $url_builder->acquireParameters(
176 ParticipantTableActions::ROW_ID_PARAMETER,
177 ParticipantTableActions::ACTION_PARAMETER,
178 ParticipantTableActions::ACTION_TYPE_PARAMETER
188 'solution' => fn(
string $value,
Participant $record) =>
190 'status_of_attempt' => fn(
string $value,
Participant $record) =>
193 'test_passed' => fn(
string $value,
Participant $record) => $value ===
'true'
196 'scoring_finalized' => fn(
string $value,
Participant $record) => $value ===
'true'
207 $processing_time = $this->test_object->getProcessingTimeInSeconds();
208 $reset_time_on_new_attempt = $this->test_object->getResetProcessingTime();
212 'total_duration' =>
static fn(
215 ) =>
$a->getTotalDuration($processing_time) <=>
$b->getTotalDuration($processing_time),
216 'remaining_duration' =>
static fn(
219 ) =>
$a->getRemainingDuration($processing_time, $reset_time_on_new_attempt)
220 <=>
$b->getRemainingDuration($processing_time, $reset_time_on_new_attempt),
222 'status_of_attempt' => fn(
226 $a->getAttemptOverviewInformation()?->getStatusOfAttempt()?->getTranslation($this->lng) ?? StatusOfAttempt::NOT_YET_STARTED->getTranslation($this->lng),
227 $b->getAttemptOverviewInformation()?->getStatusOfAttempt()?->getTranslation($this->lng) ?? StatusOfAttempt::NOT_YET_STARTED->getTranslation($this->lng)
229 'reached_points' =>
static fn(
232 ) =>
$a->getAttemptOverviewInformation()?->getReachedPoints()
233 <=>
$b->getAttemptOverviewInformation()?->getReachedPoints(),
234 'nr_of_answered_questions' =>
static fn(
237 ) =>
$a->getAttemptOverviewInformation()?->getNrOfAnsweredQuestions()
238 <=>
$b->getAttemptOverviewInformation()?->getNrOfAnsweredQuestions(),
239 'percent_of_available_points' =>
static fn(
242 ) =>
$a->getAttemptOverviewInformation()?->getReachedPointsInPercent()
243 <=>
$b->getAttemptOverviewInformation()?->getReachedPointsInPercent(),
244 'test_passed' =>
static fn(
247 ) =>
$a->getAttemptOverviewInformation()?->hasPassingMark()
248 <=>
$b->getAttemptOverviewInformation()?->hasPassingMark(),
252 ) =>
$a->getAttemptOverviewInformation()?->getMark() <=>
$b->getAttemptOverviewInformation()?->getMark(),
253 'matriculation' =>
static fn(
256 ) =>
$a->getMatriculation() <=>
$b->getMatriculation(),
257 'id_of_attempt' =>
static fn(
260 ) =>
$a->getAttemptOverviewInformation()?->getExamId() <=>
$b->getAttemptOverviewInformation()?->getExamId(),
261 'total_time_on_task' =>
static fn(
264 ) =>
$a->getAttemptOverviewInformation()?->getTotalTimeOnTask() <=>
$b->getAttemptOverviewInformation()?->getTotalTimeOnTask()
268 private function getFilterComponent(
string $action, ServerRequestInterface $request): FilterComponent
271 $is_input_initially_rendered = [];
272 $field_factory = $this->ui_factory->input()->field();
274 foreach ($this->getFilterFields($field_factory) as $filter_id => $filter) {
275 [$filter_inputs[$filter_id], $is_input_initially_rendered[$filter_id]] = $filter;
278 return $this->ui_service->filter()->standard(
279 "participant_filter_{$this->test_request->getRefId()}",
282 $is_input_initially_rendered,
295 $yes_no_all_options = [
296 'true' => $this->
lng->txt(
'yes'),
297 'false' => $this->
lng->txt(
'no')
300 $solution_options = [
301 'false' => $this->
lng->txt(
'without_solution'),
302 'true' => $this->
lng->txt(
'with_solution')
305 $status_of_attempt_options = [
306 StatusOfAttempt::NOT_YET_STARTED->value => $this->
lng->txt(StatusOfAttempt::NOT_YET_STARTED->value),
307 StatusOfAttempt::RUNNING->value => $this->
lng->txt(StatusOfAttempt::RUNNING->value),
308 StatusOfAttempt::FINISHED_BY_UNKNOWN->value => $this->
lng->txt(StatusOfAttempt::FINISHED_BY_UNKNOWN->value),
309 StatusOfAttempt::FINISHED_BY_ADMINISTRATOR->value => $this->
lng->txt(StatusOfAttempt::FINISHED_BY_ADMINISTRATOR->value),
310 StatusOfAttempt::FINISHED_BY_CRONJOB->value => $this->
lng->txt(StatusOfAttempt::FINISHED_BY_CRONJOB->value),
311 StatusOfAttempt::FINISHED_BY_DURATION->value => $this->
lng->txt(StatusOfAttempt::FINISHED_BY_DURATION->value),
312 StatusOfAttempt::FINISHED_BY_PARTICIPANT->value => $this->
lng->txt(StatusOfAttempt::FINISHED_BY_PARTICIPANT->value),
316 'name' => [$field_factory->text($this->
lng->txt(
'name')),
true],
317 'login' => [$field_factory->text($this->
lng->txt(
'login')),
true],
318 'ip_range' => [$field_factory->text($this->
lng->txt(
'client_ip_range')),
true],
319 'solution' => [$field_factory->select($this->
lng->txt(
'solutions'), $solution_options),
true],
322 if ($this->test_object->getEnableProcessingTime()) {
323 $filters[
'extra_time'] = [$field_factory->select($this->
lng->txt(
'extratime'), $yes_no_all_options),
true];
326 $filters[
'status_of_attempt'] = [
327 $field_factory->select($this->
lng->txt(
'status_of_attempt'), $status_of_attempt_options),
331 $filters[
'test_passed'] = [
332 $field_factory->select($this->
lng->txt(
'tst_passed'), $yes_no_all_options),
336 if ($this->scoring_enabled) {
337 $filters[
'scoring_finalized'] = [
338 $field_factory->select($this->
lng->txt(
'finalized_evaluation'), $yes_no_all_options),
348 return $this->ui_factory
352 $this->
lng->txt(
'list_of_participants'),
356 ->withRequest($request)
357 ->withFilter($filter);
365 $column_factory = $this->ui_factory->table()->column();
368 'name' => $column_factory->text($this->
lng->txt(
'name'))
369 ->withIsSortable(!$this->test_object->getAnonymity())
371 if (!$this->test_object->getAnonymity()) {
372 $columns[
'login'] = $column_factory->text($this->
lng->txt(
'login'))->withIsSortable(
true);
376 'matriculation' => $column_factory->text($this->
lng->txt(
'matriculation'))
377 ->withIsOptional(
true,
false)
378 ->withIsSortable(
true),
379 'ip_range' => $column_factory->text($this->
lng->txt(
'client_ip_range'))
380 ->withIsOptional(
true,
false)
381 ->withIsSortable(
true),
382 'attempt_started_at' => $column_factory->date(
383 $this->
lng->txt(
'tst_attempt_started'),
384 $this->current_user->getDateTimeFormat()
385 )->withIsSortable(
true),
386 'total_time_on_task' => $column_factory->text($this->
lng->txt(
'working_time'))
387 ->withIsOptional(
true,
false),
388 'total_attempts' => $column_factory->number($this->
lng->txt(
'total_attempts'))
389 ->withIsOptional(
true,
false)
390 ->withIsSortable(
true),
393 $columns[
'status_of_attempt'] = $column_factory->text($this->
lng->txt(
'status_of_attempt'))
394 ->withIsSortable(
true);
396 if ($this->test_object->getEnableProcessingTime()) {
397 $columns[
'remaining_duration'] = $column_factory->text($this->
lng->txt(
'remaining_duration'))
398 ->withIsOptional(
true);
399 $columns[
'total_duration'] = $column_factory->text($this->
lng->txt(
'total_duration'))
400 ->withIsOptional(
true,
false);
401 $columns[
'extra_time'] = $column_factory->text($this->
lng->txt(
'extratime'))
402 ->withIsOptional(
true,
false);
405 if ($this->test_object->getMainSettings()->getTestBehaviourSettings()->getExamIdInTestAttemptEnabled()) {
406 $columns[
'id_of_attempt'] = $column_factory->text($this->
lng->txt(
'exam_id_of_attempt'))
407 ->withIsOptional(
true,
false)
408 ->withIsSortable(
true);
411 if ($this->test_access->checkParticipantsResultsAccess()) {
412 $columns[
'reached_points'] = $column_factory->text($this->
lng->txt(
'tst_reached_points'))
413 ->withIsSortable(
true)
414 ->withOrderingLabels(...$column_factory->number($this->lng->txt(
'tst_reached_points'))->getOrderingLabels());
415 $columns[
'nr_of_answered_questions'] = $column_factory->text($this->
lng->txt(
'tst_answered_questions'))
416 ->withIsOptional(
true,
false)
417 ->withIsSortable(
true)
418 ->withOrderingLabels(...$column_factory->number($this->lng->txt(
'tst_answered_questions'))->getOrderingLabels());
419 $columns[
'percent_of_available_points'] = $column_factory->number($this->
lng->txt(
'tst_percent_solved'))
421 ->withIsOptional(
true,
false)
422 ->withIsSortable(
true);
423 $columns[
'test_passed'] = $column_factory->boolean(
424 $this->
lng->txt(
'tst_passed'),
425 $this->ui_factory->symbol()->icon()->custom(
426 'assets/images/standard/icon_checked.svg',
427 $this->lng->txt(
'yes'),
430 $this->ui_factory->symbol()->icon()->custom(
431 'assets/images/standard/icon_unchecked.svg',
432 $this->lng->txt(
'no'),
435 )->withIsSortable(
true)
436 ->withOrderingLabels(
437 "{$this->lng->txt('tst_passed')}, {$this->lng->txt('no')} {$this->lng->txt('order_option_first')}",
438 "{$this->lng->txt('tst_passed')}, {$this->lng->txt('yes')} {$this->lng->txt('order_option_first')}"
440 $columns[
'mark'] = $column_factory->text($this->
lng->txt(
'tst_mark'))
441 ->withIsOptional(
true,
false)
442 ->withIsSortable(
true);
444 if ($this->scoring_enabled) {
445 $columns[
'scoring_finalized'] = $column_factory->boolean(
446 $this->
lng->txt(
'finalized_evaluation'),
447 $this->ui_factory->symbol()->icon()->custom(
448 'assets/images/standard/icon_checked.svg',
449 $this->lng->txt(
'yes'),
452 $this->ui_factory->symbol()->icon()->custom(
453 'assets/images/standard/icon_unchecked.svg',
454 $this->lng->txt(
'no'),
457 )->withIsOptional(
true,
false)
458 ->withIsSortable(
true);
461 $columns[
'last_access'] = $column_factory->date(
462 $this->
lng->txt(
'last_access'),
463 $this->current_user->getDateTimeFormat()
471 if ($this->records !==
null) {
472 return $this->records;
475 $records = iterator_to_array(
477 $this->test_object->getTestId(),
484 $this->records = array_filter(
488 $this->buildAccessFilteredParticipantsList($records)
492 return $this->records;
502 $manage_access_filter = $this->participant_access_filter
503 ->getManageParticipantsUserFilter($this->test_object->getRefId());
504 $access_results_access_filter = $this->participant_access_filter
505 ->getAccessResultsUserFilter($this->test_object->getRefId());
506 $participant_ids = array_map(
510 return $manage_access_filter($participant_ids) + $access_results_access_filter($participant_ids);
519 return $this->limitRecords(
521 $this->filterRecords(
522 $this->results_data_factory->addAttemptOverviewInformationToParticipants(
523 $this->results_presentation_settings,
525 $this->loadRecords($filter_data, $order)
535 private function filterRecords(iterable $records, ?array $filter_data): iterable
537 foreach ($records as $record) {
538 if ($this->matchFilter($record, $filter_data)) {
546 if ($filter ===
null) {
550 $post_load_filters = $this->getPostLoadFilters();
553 foreach ($filter as $key => $value) {
558 $post_load_filter = $post_load_filters[$key] ?? fn() =>
true;
559 $allow = $allow && $post_load_filter($value, $record);
567 $post_load_order_fields = $this->getPostLoadOrderFields();
568 $records = iterator_to_array($records);
571 foreach ($order->
get() as $subject => $direction) {
572 $post_load_order_field = $post_load_order_fields[$subject] ?? fn() => 0;
573 $index = $post_load_order_field($a, $b);
576 return $direction ===
'DESC' ? $index * -1 : $index;
Builds a Color from either hex- or rgb values.
Both the subject and the direction need to be specified when expressing an order.
A simple class to express a naive range of whole positive numbers.
getTotalRowCount(mixed $additional_viewcontrol_data, mixed $filter_data, mixed $additional_parameters)
Mainly for the purpose of pagination-support, it is important to know about the total number of recor...
buildAccessFilteredParticipantsList(array $records)
getFilterFields(FieldFactory $field_factory)
getComponents(URLBuilder $url_builder, string $filter_url)
filterRecords(iterable $records, ?array $filter_data)
getFilterComponent(string $action, ServerRequestInterface $request)
orderRecords(iterable $records, Order $order)
getViewControlledRecords(?array $filter_data, Range $range, Order $order)
__construct(private readonly UIFactory $ui_factory, private readonly \ilUIService $ui_service, private readonly Language $lng, private readonly \ilTestAccess $test_access, private readonly RequestDataCollector $test_request, private readonly \ilTestParticipantAccessFilterFactory $participant_access_filter, private readonly ParticipantRepository $repository, private readonly ResultsDataFactory $results_data_factory, private readonly ResultsPresentationSettings $results_presentation_settings, private readonly \ilObjUser $current_user, private readonly \ilObjTest $test_object, private readonly ParticipantTableActions $table_actions)
loadRecords(?array $filter, Order $order)
matchFilter(Participant $record, ?array $filter)
execute(URLBuilder $url_builder)
limitRecords(array $records, Range $range)
acquireParameters($url_builder)
getTableComponent(ServerRequestInterface $request, ?array $filter)
hasAnsweredQuestionsForScoredAttempt()
getAttemptOverviewInformation()
return['delivery_method'=> 'php',]
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This describes commonalities between the different modals.
buildDataRow(string $id, array $record)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples