19 declare(strict_types=1);
41 private const ID =
'pt';
52 private readonly ResultsDataFactory $results_data_factory,
54 private readonly \
ilObjUser $current_user,
72 $this->test_request->getRequest(),
73 $this->ui_service->filter()->getData($filter)
78 $table->withActions($this->table_actions->getEnabledActions(...$this->acquireParameters($url_builder)))
84 return $this->
repository->countParticipants($this->test_object->getTestId(), $filter_data);
89 array $visible_column_ids,
93 ?array $additional_parameters
95 $processing_time = $this->test_object->getProcessingTimeInSeconds();
96 $reset_time_on_new_attempt = $this->test_object->getResetProcessingTime();
98 $current_user_timezone = new \DateTimeZone($this->current_user->getTimeZone());
102 $total_duration = $record->getTotalDuration($processing_time);
103 $status_of_attempt = $record->getAttemptOverviewInformation()?->getStatusOfAttempt() ?? StatusOfAttempt::NOT_YET_STARTED;
106 'name' => $this->test_object->buildName($record->getUserId(), $record->getLastname(), $record->getFirstname()),
107 'login' => $record->getLogin(),
108 'matriculation' => $record->getMatriculation(),
109 'total_time_on_task' => $record->getAttemptOverviewInformation()?->getHumanReadableTotalTimeOnTask() ??
'',
110 'status_of_attempt' => $this->
lng->txt($status_of_attempt->value),
111 'id_of_attempt' => $record->getAttemptOverviewInformation()?->getExamId(),
112 'ip_range' => $record->getClientIpTo() !==
'' || $record->getClientIpFrom() !==
'' 113 ? sprintf(
'%s - %s', $record->getClientIpFrom(), $record->getClientIpTo())
115 'total_attempts' => $record->getAttemptOverviewInformation()?->getNrOfAttempts() ?? 0,
116 'extra_time' => $record->getExtraTime() > 0 ? sprintf(
'%d min', $record->getExtraTime()) :
'',
117 'total_duration' => $total_duration > 0 ? sprintf(
'%d min', $total_duration / 60) :
'',
118 'remaining_duration' => sprintf(
'%d min', $record->getRemainingDuration($processing_time, $reset_time_on_new_attempt) / 60),
121 $first_access = $record->getAttemptOverviewInformation()?->getStartedDate();
122 if ($first_access !==
null) {
123 $row[
'attempt_started_at'] = $first_access->setTimezone($current_user_timezone);
126 $last_access = $record->getLastAccess();
127 if ($last_access !==
null) {
128 $row[
'last_access'] = $last_access->setTimezone($current_user_timezone);
130 if ($record->getActiveId() !==
null 131 && $this->test_access->checkResultsAccessForActiveId(
132 $record->getActiveId(),
133 $this->test_object->getTestId()
134 ) || $record ===
null && $this->test_access->checkParticipantsResultsAccess()) {
135 $row[
'reached_points'] = sprintf(
136 $this->
lng->txt(
'tst_reached_points_of_max'),
137 $record->getAttemptOverviewInformation()?->getReachedPoints(),
138 $record->getAttemptOverviewInformation()?->getAvailablePoints()
140 $row[
'nr_of_answered_questions'] = sprintf(
141 $this->
lng->txt(
'tst_answered_questions_of_total'),
142 $record->getAttemptOverviewInformation()?->getNrOfAnsweredQuestions(),
143 $record->getAttemptOverviewInformation()?->getNrOfTotalQuestions()
145 $row[
'percent_of_available_points'] = $record->getAttemptOverviewInformation()?->getReachedPointsInPercent();
148 if ($status_of_attempt->isFinished()) {
149 $row[
'test_passed'] = $record->getAttemptOverviewInformation()?->hasPassingMark() ??
false;
150 $row[
'mark'] = $record->getAttemptOverviewInformation()?->getMark();
153 yield $this->table_actions->onDataRow(
154 $row_builder->
buildDataRow((
string) $record->getUserId(), $row),
162 return $url_builder->acquireParameters(
176 'solution' => fn(
string $value,
Participant $record) =>
178 'status_of_attempt' => fn(
string $value,
Participant $record) =>
181 'test_passed' => fn(
string $value,
Participant $record) => $value ===
'true' 192 $processing_time = $this->test_object->getProcessingTimeInSeconds();
193 $reset_time_on_new_attempt = $this->test_object->getResetProcessingTime();
197 'total_duration' =>
static fn(
201 'remaining_duration' =>
static fn(
207 'status_of_attempt' =>
static fn(
212 'reached_points' =>
static fn(
217 'nr_of_answered_questions' =>
static fn(
222 'percent_of_available_points' =>
static fn(
227 'test_passed' =>
static fn(
236 'matriculation' =>
static fn(
240 'id_of_attempt' =>
static fn(
250 $is_input_initially_rendered = [];
251 $field_factory = $this->ui_factory->input()->field();
253 foreach ($this->
getFilterFields($field_factory) as $filter_id => $filter) {
254 [$filter_inputs[$filter_id], $is_input_initially_rendered[$filter_id]] = $filter;
257 return $this->ui_service->filter()->standard(
258 'participant_filter',
261 $is_input_initially_rendered,
274 $yes_no_all_options = [
275 'true' => $this->
lng->txt(
'yes'),
276 'false' => $this->
lng->txt(
'no')
279 $solution_options = [
280 'false' => $this->
lng->txt(
'without_solution'),
281 'true' => $this->
lng->txt(
'with_solution')
284 $status_of_attempt_options = [
285 StatusOfAttempt::NOT_YET_STARTED->value => $this->
lng->txt(StatusOfAttempt::NOT_YET_STARTED->value),
286 StatusOfAttempt::RUNNING->value => $this->
lng->txt(StatusOfAttempt::RUNNING->value),
287 StatusOfAttempt::FINISHED_BY_UNKNOWN->value => $this->
lng->txt(StatusOfAttempt::FINISHED_BY_UNKNOWN->value),
288 StatusOfAttempt::FINISHED_BY_ADMINISTRATOR->value => $this->
lng->txt(StatusOfAttempt::FINISHED_BY_ADMINISTRATOR->value),
289 StatusOfAttempt::FINISHED_BY_CRONJOB->value => $this->
lng->txt(StatusOfAttempt::FINISHED_BY_CRONJOB->value),
290 StatusOfAttempt::FINISHED_BY_DURATION->value => $this->
lng->txt(StatusOfAttempt::FINISHED_BY_DURATION->value),
291 StatusOfAttempt::FINISHED_BY_PARTICIPANT->value => $this->
lng->txt(StatusOfAttempt::FINISHED_BY_PARTICIPANT->value),
295 'name' => [$field_factory->text($this->
lng->txt(
'name')),
true],
296 'login' => [$field_factory->text($this->lng->txt(
'login')),
true],
297 'ip_range' => [$field_factory->text($this->lng->txt(
'client_ip_range')),
true],
298 'solution' => [$field_factory->select($this->lng->txt(
'solutions'), $solution_options),
true],
301 if ($this->test_object->getEnableProcessingTime()) {
302 $filters[
'extra_time'] = [$field_factory->select($this->
lng->txt(
'extratime'), $yes_no_all_options),
true];
305 $filters[
'status_of_attempt'] = [
306 $field_factory->select($this->
lng->txt(
'status_of_attempt'), $status_of_attempt_options),
310 $filters[
'test_passed'] = [
311 $field_factory->select($this->
lng->txt(
'tst_passed'), $yes_no_all_options),
320 return $this->ui_factory
324 $this->
lng->txt(
'list_of_participants'),
328 ->withRequest($request)
329 ->withFilter($filter);
337 $column_factory = $this->ui_factory->table()->column();
340 'name' => $column_factory->text($this->
lng->txt(
'name'))
341 ->withIsSortable(!$this->test_object->getAnonymity())
343 if (!$this->test_object->getAnonymity()) {
344 $columns[
'login'] = $column_factory->text($this->
lng->txt(
'login'))->withIsSortable(
true);
348 'matriculation' => $column_factory->text($this->
lng->txt(
'matriculation'))
349 ->withIsOptional(
true,
false)
350 ->withIsSortable(
true),
351 'ip_range' => $column_factory->text($this->
lng->txt(
'client_ip_range'))
352 ->withIsOptional(
true,
false)
353 ->withIsSortable(
true),
354 'attempt_started_at' => $column_factory->date(
355 $this->
lng->txt(
'tst_attempt_started'),
356 $this->current_user->getDateTimeFormat()
357 )->withIsSortable(
true),
358 'total_time_on_task' => $column_factory->text($this->
lng->txt(
'working_time'))
359 ->withIsOptional(
true,
false),
360 'total_attempts' => $column_factory->number($this->
lng->txt(
'total_attempts'))
361 ->withIsOptional(
true,
false)
362 ->withIsSortable(
true),
365 if ($this->test_object->getEnableProcessingTime()) {
366 $columns[
'extra_time'] = $column_factory->text($this->
lng->txt(
'extratime'))
367 ->withIsOptional(
true,
false);
368 $columns[
'total_duration'] = $column_factory->text($this->
lng->txt(
'total_duration'))
369 ->withIsOptional(
true,
false);
370 $columns[
'remaining_duration'] = $column_factory->text($this->
lng->txt(
'remaining_duration'))
371 ->withIsOptional(
true);
374 $columns[
'status_of_attempt'] = $column_factory->text($this->
lng->txt(
'status_of_attempt'))
375 ->withIsSortable(
true);
377 if ($this->test_object->getMainSettings()->getTestBehaviourSettings()->getExamIdInTestAttemptEnabled()) {
378 $columns[
'id_of_attempt'] = $column_factory->text($this->
lng->txt(
'exam_id_of_attempt'))
379 ->withIsOptional(
true,
false)
380 ->withIsSortable(
true);
383 if ($this->test_access->checkParticipantsResultsAccess()) {
384 $columns[
'reached_points'] = $column_factory->text($this->
lng->txt(
'tst_reached_points'))
385 ->withIsSortable(
true);
386 $columns[
'nr_of_answered_questions'] = $column_factory->text($this->
lng->txt(
'tst_answered_questions'))
387 ->withIsOptional(
true,
false)
388 ->withIsSortable(
true);
389 $columns[
'percent_of_available_points'] = $column_factory->number($this->
lng->txt(
'tst_percent_solved'))
391 ->withIsOptional(
true,
false)
392 ->withIsSortable(
true);
393 $columns[
'test_passed'] = $column_factory->boolean(
394 $this->
lng->txt(
'tst_passed'),
395 $this->ui_factory->symbol()->icon()->custom(
396 'assets/images/standard/icon_checked.svg',
397 $this->
lng->txt(
'yes'),
400 $this->ui_factory->symbol()->icon()->custom(
401 'assets/images/standard/icon_unchecked.svg',
402 $this->
lng->txt(
'no'),
405 )->withIsSortable(
true)
406 ->withOrderingLabels(
407 "{$this->lng->txt('tst_passed')}, {$this->lng->txt('yes')} {$this->lng->txt('order_option_first')}",
408 "{$this->lng->txt('tst_passed')}, {$this->lng->txt('no')} {$this->lng->txt('order_option_first')}" 410 $columns[
'mark'] = $column_factory->text($this->
lng->txt(
'tst_mark'))
411 ->withIsOptional(
true,
false)
412 ->withIsSortable(
true);
415 $columns[
'last_access'] = $column_factory->date(
416 $this->
lng->txt(
'last_access'),
417 $this->current_user->getDateTimeFormat()
425 if ($this->records !==
null) {
429 $records = iterator_to_array(
431 $this->test_object->getTestId(),
438 $access_filter = $this->participant_access_filter->getManageParticipantsUserFilter($this->test_object->getRefId());
439 $filtered_user_ids = $access_filter(
array_map(
444 $this->records = array_filter(
461 $this->results_data_factory->addAttemptOverviewInformationToParticipants(
462 $this->results_presentation_settings,
464 $this->loadRecords($filter_data, $order)
474 private function filterRecords(iterable $records, ?array $filter_data): iterable
476 foreach ($records as $record) {
485 if ($filter ===
null) {
492 foreach ($filter as $key => $value) {
497 $post_load_filter = $post_load_filters[$key] ?? fn() =>
true;
498 $allow = $allow && $post_load_filter($value, $record);
507 $records = iterator_to_array($records);
510 foreach ($order->
get() as $subject => $direction) {
511 $post_load_order_field = $post_load_order_fields[$subject] ?? fn() => 0;
512 $index = $post_load_order_field($a, $b);
515 return $direction ===
'DESC' ? $index * -1 : $index;
This describes commonalities between the different modals.
getAttemptOverviewInformation()
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
repository()
description: > Example for rendering a repository card
getTotalDuration(?int $processing_time)
matchFilter(Participant $record, ?array $filter)
getViewControlledRecords(?array $filter_data, Range $range, Order $order)
getComponents(URLBuilder $url_builder, string $filter_url)
Both the subject and the direction need to be specified when expressing an order. ...
buildDataRow(string $id, array $record)
limitRecords(array $records, Range $range)
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
getFilterFields(FieldFactory $field_factory)
getRows(DataRowBuilder $row_builder, array $visible_column_ids, Range $range, Order $order, ?array $filter_data, ?array $additional_parameters)
This is called by the table to retrieve rows; map data-records to rows using the $row_builder e...
loadRecords(?array $filter, Order $order)
const ACTION_TYPE_PARAMETER
sortRecords(iterable $records, Order $order)
getTableComponent(ServerRequestInterface $request, ?array $filter)
hasAnsweredQuestionsForScoredAttempt()
getTotalRowCount(?array $filter_data, ?array $additional_parameters)
Mainly for the purpose of pagination-support, it is important to know about the total number of recor...
__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)
acquireParameters($url_builder)
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
filterRecords(iterable $records, ?array $filter_data)
A simple class to express a naive range of whole positive numbers.
execute(URLBuilder $url_builder)
getRemainingDuration(int $processing_time, bool $reset_time_on_new_attempt)
getFilterComponent(string $action, ServerRequestInterface $request)