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' => $this->test_object->buildName($record->getUserId(), $record->getFirstname(), $record->getLastname()),
112 'login' => $record->getLogin(),
113 'matriculation' => $record->getMatriculation(),
114 'total_time_on_task' => $record->getAttemptOverviewInformation()?->getHumanReadableTotalTimeOnTask() ??
'',
115 'status_of_attempt' => $this->
lng->txt($status_of_attempt->value),
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(
163 $row_builder->
buildDataRow((
string) $record->getUserId(), $row),
171 return $url_builder->acquireParameters(
173 ParticipantTableActions::ROW_ID_PARAMETER,
174 ParticipantTableActions::ACTION_PARAMETER,
175 ParticipantTableActions::ACTION_TYPE_PARAMETER
185 'solution' => fn(
string $value,
Participant $record) =>
187 'status_of_attempt' => fn(
string $value,
Participant $record) =>
190 'test_passed' => fn(
string $value,
Participant $record) => $value ===
'true'
193 'scoring_finalized' => fn(
string $value,
Participant $record) => $value ===
'true'
204 $processing_time = $this->test_object->getProcessingTimeInSeconds();
205 $reset_time_on_new_attempt = $this->test_object->getResetProcessingTime();
209 'total_duration' =>
static fn(
212 ) =>
$a->getTotalDuration($processing_time) <=>
$b->getTotalDuration($processing_time),
213 'remaining_duration' =>
static fn(
216 ) =>
$a->getRemainingDuration($processing_time, $reset_time_on_new_attempt)
217 <=>
$b->getRemainingDuration($processing_time, $reset_time_on_new_attempt),
219 'status_of_attempt' =>
static fn(
222 ) =>
$a->getAttemptOverviewInformation()?->getStatusOfAttempt()
223 <=>
$b->getAttemptOverviewInformation()?->getStatusOfAttempt(),
224 'reached_points' =>
static fn(
227 ) =>
$a->getAttemptOverviewInformation()?->getReachedPoints()
228 <=>
$b->getAttemptOverviewInformation()?->getReachedPoints(),
229 'nr_of_answered_questions' =>
static fn(
232 ) =>
$a->getAttemptOverviewInformation()?->getNrOfAnsweredQuestions()
233 <=>
$b->getAttemptOverviewInformation()?->getNrOfAnsweredQuestions(),
234 'percent_of_available_points' =>
static fn(
237 ) =>
$a->getAttemptOverviewInformation()?->getReachedPointsInPercent()
238 <=>
$b->getAttemptOverviewInformation()?->getReachedPointsInPercent(),
239 'test_passed' =>
static fn(
242 ) =>
$a->getAttemptOverviewInformation()?->hasPassingMark()
243 <=>
$b->getAttemptOverviewInformation()?->hasPassingMark(),
247 ) =>
$a->getAttemptOverviewInformation()?->getMark() <=>
$b->getAttemptOverviewInformation()?->getMark(),
248 'matriculation' =>
static fn(
251 ) =>
$a->getMatriculation() <=>
$b->getMatriculation(),
252 'id_of_attempt' =>
static fn(
255 ) =>
$a->getAttemptOverviewInformation()?->getExamId() <=>
$b->getAttemptOverviewInformation()?->getExamId(),
256 'total_time_on_task' =>
static fn(
259 ) =>
$a->getAttemptOverviewInformation()?->getTotalTimeOnTask() <=>
$b->getAttemptOverviewInformation()?->getTotalTimeOnTask()
263 private function getFilterComponent(
string $action, ServerRequestInterface $request): FilterComponent
266 $is_input_initially_rendered = [];
267 $field_factory = $this->ui_factory->input()->field();
269 foreach ($this->getFilterFields($field_factory) as $filter_id => $filter) {
270 [$filter_inputs[$filter_id], $is_input_initially_rendered[$filter_id]] = $filter;
273 return $this->ui_service->filter()->standard(
274 "participant_filter_{$this->test_request->getRefId()}",
277 $is_input_initially_rendered,
290 $yes_no_all_options = [
291 'true' => $this->
lng->txt(
'yes'),
292 'false' => $this->
lng->txt(
'no')
295 $solution_options = [
296 'false' => $this->
lng->txt(
'without_solution'),
297 'true' => $this->
lng->txt(
'with_solution')
300 $status_of_attempt_options = [
301 StatusOfAttempt::NOT_YET_STARTED->value => $this->
lng->txt(StatusOfAttempt::NOT_YET_STARTED->value),
302 StatusOfAttempt::RUNNING->value => $this->
lng->txt(StatusOfAttempt::RUNNING->value),
303 StatusOfAttempt::FINISHED_BY_UNKNOWN->value => $this->
lng->txt(StatusOfAttempt::FINISHED_BY_UNKNOWN->value),
304 StatusOfAttempt::FINISHED_BY_ADMINISTRATOR->value => $this->
lng->txt(StatusOfAttempt::FINISHED_BY_ADMINISTRATOR->value),
305 StatusOfAttempt::FINISHED_BY_CRONJOB->value => $this->
lng->txt(StatusOfAttempt::FINISHED_BY_CRONJOB->value),
306 StatusOfAttempt::FINISHED_BY_DURATION->value => $this->
lng->txt(StatusOfAttempt::FINISHED_BY_DURATION->value),
307 StatusOfAttempt::FINISHED_BY_PARTICIPANT->value => $this->
lng->txt(StatusOfAttempt::FINISHED_BY_PARTICIPANT->value),
311 'name' => [$field_factory->text($this->
lng->txt(
'name')),
true],
312 'login' => [$field_factory->text($this->
lng->txt(
'login')),
true],
313 'ip_range' => [$field_factory->text($this->
lng->txt(
'client_ip_range')),
true],
314 'solution' => [$field_factory->select($this->
lng->txt(
'solutions'), $solution_options),
true],
317 if ($this->test_object->getEnableProcessingTime()) {
318 $filters[
'extra_time'] = [$field_factory->select($this->
lng->txt(
'extratime'), $yes_no_all_options),
true];
321 $filters[
'status_of_attempt'] = [
322 $field_factory->select($this->
lng->txt(
'status_of_attempt'), $status_of_attempt_options),
326 $filters[
'test_passed'] = [
327 $field_factory->select($this->
lng->txt(
'tst_passed'), $yes_no_all_options),
331 if ($this->scoring_enabled) {
332 $filters[
'scoring_finalized'] = [
333 $field_factory->select($this->
lng->txt(
'finalized_evaluation'), $yes_no_all_options),
343 return $this->ui_factory
347 $this->
lng->txt(
'list_of_participants'),
351 ->withRequest($request)
352 ->withFilter($filter);
360 $column_factory = $this->ui_factory->table()->column();
363 'name' => $column_factory->text($this->
lng->txt(
'name'))
364 ->withIsSortable(!$this->test_object->getAnonymity())
366 if (!$this->test_object->getAnonymity()) {
367 $columns[
'login'] = $column_factory->text($this->
lng->txt(
'login'))->withIsSortable(
true);
371 'matriculation' => $column_factory->text($this->
lng->txt(
'matriculation'))
372 ->withIsOptional(
true,
false)
373 ->withIsSortable(
true),
374 'ip_range' => $column_factory->text($this->
lng->txt(
'client_ip_range'))
375 ->withIsOptional(
true,
false)
376 ->withIsSortable(
true),
377 'attempt_started_at' => $column_factory->date(
378 $this->
lng->txt(
'tst_attempt_started'),
379 $this->current_user->getDateTimeFormat()
380 )->withIsSortable(
true),
381 'total_time_on_task' => $column_factory->text($this->
lng->txt(
'working_time'))
382 ->withIsOptional(
true,
false),
383 'total_attempts' => $column_factory->number($this->
lng->txt(
'total_attempts'))
384 ->withIsOptional(
true,
false)
385 ->withIsSortable(
true),
388 $columns[
'status_of_attempt'] = $column_factory->text($this->
lng->txt(
'status_of_attempt'))
389 ->withIsSortable(
true);
391 if ($this->test_object->getEnableProcessingTime()) {
392 $columns[
'remaining_duration'] = $column_factory->text($this->
lng->txt(
'remaining_duration'))
393 ->withIsOptional(
true);
394 $columns[
'total_duration'] = $column_factory->text($this->
lng->txt(
'total_duration'))
395 ->withIsOptional(
true,
false);
396 $columns[
'extra_time'] = $column_factory->text($this->
lng->txt(
'extratime'))
397 ->withIsOptional(
true,
false);
400 if ($this->test_object->getMainSettings()->getTestBehaviourSettings()->getExamIdInTestAttemptEnabled()) {
401 $columns[
'id_of_attempt'] = $column_factory->text($this->
lng->txt(
'exam_id_of_attempt'))
402 ->withIsOptional(
true,
false)
403 ->withIsSortable(
true);
406 if ($this->test_access->checkParticipantsResultsAccess()) {
407 $columns[
'reached_points'] = $column_factory->text($this->
lng->txt(
'tst_reached_points'))
408 ->withIsSortable(
true);
409 $columns[
'nr_of_answered_questions'] = $column_factory->text($this->
lng->txt(
'tst_answered_questions'))
410 ->withIsOptional(
true,
false)
411 ->withIsSortable(
true);
412 $columns[
'percent_of_available_points'] = $column_factory->number($this->
lng->txt(
'tst_percent_solved'))
414 ->withIsOptional(
true,
false)
415 ->withIsSortable(
true);
416 $columns[
'test_passed'] = $column_factory->boolean(
417 $this->
lng->txt(
'tst_passed'),
418 $this->ui_factory->symbol()->icon()->custom(
419 'assets/images/standard/icon_checked.svg',
420 $this->lng->txt(
'yes'),
423 $this->ui_factory->symbol()->icon()->custom(
424 'assets/images/standard/icon_unchecked.svg',
425 $this->lng->txt(
'no'),
428 )->withIsSortable(
true)
429 ->withOrderingLabels(
430 "{$this->lng->txt('tst_passed')}, {$this->lng->txt('yes')} {$this->lng->txt('order_option_first')}",
431 "{$this->lng->txt('tst_passed')}, {$this->lng->txt('no')} {$this->lng->txt('order_option_first')}"
433 $columns[
'mark'] = $column_factory->text($this->
lng->txt(
'tst_mark'))
434 ->withIsOptional(
true,
false)
435 ->withIsSortable(
true);
437 if ($this->scoring_enabled) {
438 $columns[
'scoring_finalized'] = $column_factory->boolean(
439 $this->
lng->txt(
'finalized_evaluation'),
440 $this->ui_factory->symbol()->icon()->custom(
441 'assets/images/standard/icon_checked.svg',
442 $this->lng->txt(
'yes'),
445 $this->ui_factory->symbol()->icon()->custom(
446 'assets/images/standard/icon_unchecked.svg',
447 $this->lng->txt(
'no'),
450 )->withIsOptional(
true,
false)
451 ->withIsSortable(
true);
454 $columns[
'last_access'] = $column_factory->date(
455 $this->
lng->txt(
'last_access'),
456 $this->current_user->getDateTimeFormat()
464 if ($this->records !==
null) {
465 return $this->records;
468 $records = iterator_to_array(
470 $this->test_object->getTestId(),
477 $this->records = array_filter(
481 $this->buildAccessFilteredParticipantsList($records)
485 return $this->records;
495 $manage_access_filter = $this->participant_access_filter
496 ->getManageParticipantsUserFilter($this->test_object->getRefId());
497 $access_results_access_filter = $this->participant_access_filter
498 ->getAccessResultsUserFilter($this->test_object->getRefId());
499 $participant_ids = array_map(
503 return $manage_access_filter($participant_ids) + $access_results_access_filter($participant_ids);
512 return $this->limitRecords(
514 $this->filterRecords(
515 $this->results_data_factory->addAttemptOverviewInformationToParticipants(
516 $this->results_presentation_settings,
518 $this->loadRecords($filter_data, $order)
528 private function filterRecords(iterable $records, ?array $filter_data): iterable
530 foreach ($records as $record) {
531 if ($this->matchFilter($record, $filter_data)) {
539 if ($filter ===
null) {
543 $post_load_filters = $this->getPostLoadFilters();
546 foreach ($filter as $key => $value) {
551 $post_load_filter = $post_load_filters[$key] ?? fn() =>
true;
552 $allow = $allow && $post_load_filter($value, $record);
560 $post_load_order_fields = $this->getPostLoadOrderFields();
561 $records = iterator_to_array($records);
564 foreach ($order->
get() as $subject => $direction) {
565 $post_load_order_field = $post_load_order_fields[$subject] ?? fn() => 0;
566 $index = $post_load_order_field($a, $b);
569 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