ILIAS  trunk Revision v13.0_alpha-52-g21c366bbdd4
ParticipantTable.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
23use ILIAS\Test\Results\Data\Factory as ResultsDataFactory;
24use ILIAS\Test\Results\Presentation\Settings as ResultsPresentationSettings;
35use ILIAS\UI\Factory as UIFactory;
37use Psr\Http\Message\ServerRequestInterface;
38
40{
41 private const ID = 'pt';
42 private ?iterable $records = null;
43 private bool $scoring_enabled = false;
44
45 public function __construct(
46 private readonly UIFactory $ui_factory,
47 private readonly \ilUIService $ui_service,
48 private readonly Language $lng,
49 private readonly \ilTestAccess $test_access,
50 private readonly RequestDataCollector $test_request,
51 private readonly \ilTestParticipantAccessFilterFactory $participant_access_filter,
52 private readonly ParticipantRepository $repository,
53 private readonly ResultsDataFactory $results_data_factory,
54 private readonly ResultsPresentationSettings $results_presentation_settings,
55 private readonly \ilObjUser $current_user,
56 private readonly \ilObjTest $test_object,
57 private readonly ParticipantTableActions $table_actions
58 ) {
59 $this->scoring_enabled = $this->test_object->getGlobalSettings()->isManualScoringEnabled();
60 }
61
62 public function execute(URLBuilder $url_builder): ?Modal
63 {
64 return $this->table_actions->execute(...$this->acquireParameters($url_builder));
65 }
66
70 public function getComponents(URLBuilder $url_builder, string $filter_url): array
71 {
72 $summary_information = $this->ui_factory->listing()->property()
73 ->withProperty(
74 $this->lng->txt('tst_stat_result_total_participants'),
75 (string) $this->repository->countParticipants(
76 $this->test_object->getTestId(),
77 null
78 )
79 );
80 $filter = $this->getFilterComponent($filter_url, $this->test_request->getRequest());
81 $table = $this->getTableComponent(
82 $this->test_request->getRequest(),
83 $this->ui_service->filter()->getData($filter)
84 );
85
86 return [
87 $summary_information,
88 $filter,
89 $table->withActions($this->table_actions->getEnabledActions(...$this->acquireParameters($url_builder)))
90 ];
91 }
92
93 public function getTotalRowCount(
94 mixed $additional_viewcontrol_data,
95 mixed $filter_data,
96 mixed $additional_parameters
97 ): ?int {
98 return $this->repository->countParticipants($this->test_object->getTestId(), $filter_data);
99 }
100
101 public function getRows(
102 DataRowBuilder $row_builder,
103 array $visible_column_ids,
105 Order $order,
106 mixed $additional_viewcontrol_data,
107 mixed $filter_data,
108 mixed $additional_parameters
109 ): \Generator {
110 $processing_time = $this->test_object->getProcessingTimeInSeconds();
111 $reset_time_on_new_attempt = $this->test_object->getResetProcessingTime();
112
113 $current_user_timezone = new \DateTimeZone($this->current_user->getTimeZone());
114
116 foreach ($this->getViewControlledRecords($filter_data, $range, $order) as $record) {
117 $total_duration = $record->getTotalDuration($processing_time);
118 $status_of_attempt = $record->getAttemptOverviewInformation()?->getStatusOfAttempt() ?? StatusOfAttempt::NOT_YET_STARTED;
119 $row = [
120 'name' => $record->getDisplayName($this->lng, $this->test_object->getAnonymity()),
121 'login' => $record->getLogin(),
122 'matriculation' => $record->getMatriculation(),
123 'total_time_on_task' => $record->getAttemptOverviewInformation()?->getHumanReadableTotalTimeOnTask() ?? '',
124 'status_of_attempt' => $status_of_attempt->getTranslation($this->lng),
125 'id_of_attempt' => $record->getAttemptOverviewInformation()?->getExamId(),
126 'ip_range' => $record->getClientIpTo() !== '' || $record->getClientIpFrom() !== ''
127 ? sprintf('%s - %s', $record->getClientIpFrom(), $record->getClientIpTo())
128 : '',
129 'total_attempts' => $record->getAttemptOverviewInformation()?->getNrOfAttempts() ?? 0,
130 'extra_time' => $record->getExtraTime() > 0 ? sprintf('%d min', $record->getExtraTime()) : '',
131 'total_duration' => $total_duration > 0 ? sprintf('%d min', $total_duration / 60) : '',
132 'remaining_duration' => sprintf('%d min', $record->getRemainingDuration($processing_time, $reset_time_on_new_attempt) / 60),
133 ];
134
135 if ($this->scoring_enabled) {
136 $row['scoring_finalized'] = $record->isScoringFinalized();
137 }
138
139 $first_access = $record->getAttemptOverviewInformation()?->getStartedDate();
140 if ($first_access !== null) {
141 $row['attempt_started_at'] = $first_access->setTimezone($current_user_timezone);
142 }
143
144 $last_access = $record->getLastAccess();
145 if ($last_access !== null) {
146 $row['last_access'] = $last_access->setTimezone($current_user_timezone);
147 }
148 if ($record->getActiveId() !== null
149 && $this->test_access->checkResultsAccessForActiveId(
150 $record->getActiveId(),
151 $this->test_object->getTestId()
152 ) || $record === null && $this->test_access->checkParticipantsResultsAccess()) {
153 $row['reached_points'] = sprintf(
154 $this->lng->txt('tst_reached_points_of_max'),
155 $record->getAttemptOverviewInformation()?->getReachedPoints(),
156 $record->getAttemptOverviewInformation()?->getAvailablePoints()
157 );
158 $row['nr_of_answered_questions'] = sprintf(
159 $this->lng->txt('tst_answered_questions_of_total'),
160 $record->getAttemptOverviewInformation()?->getNrOfAnsweredQuestions(),
161 $record->getAttemptOverviewInformation()?->getNrOfTotalQuestions()
162 );
163 $row['percent_of_available_points'] = $record->getAttemptOverviewInformation()?->getReachedPointsInPercent() ?? 0.0;
164 }
165
166 if ($status_of_attempt->isFinished()) {
167 $row['test_passed'] = $record->getAttemptOverviewInformation()?->hasPassingMark() ?? false;
168 $row['mark'] = $record->getAttemptOverviewInformation()?->getMark();
169 }
170
171 yield $this->table_actions->onDataRow(
172 $row_builder->buildDataRow(
173 "{$record->getUserId()}_{$record->getActiveId()}",
174 $row
175 ),
176 $record
177 );
178 }
179 }
180
181 private function acquireParameters($url_builder): array
182 {
183 return $url_builder->acquireParameters(
184 [self::ID],
185 ParticipantTableActions::ROW_ID_PARAMETER,
186 ParticipantTableActions::ACTION_PARAMETER,
187 ParticipantTableActions::ACTION_TYPE_PARAMETER
188 );
189 }
190
194 private function getPostLoadFilters(): array
195 {
196 return [
197 'solution' => fn(string $value, Participant $record) =>
198 $value === 'true' ? $record->hasAnsweredQuestionsForScoredAttempt() : !$record->hasAnsweredQuestionsForScoredAttempt(),
199 'status_of_attempt' => fn(array $value, Participant $record) =>
200 (in_array(StatusOfAttempt::NOT_YET_STARTED->value, $value)
201 && $record->getAttemptOverviewInformation()?->getStatusOfAttempt() === null)
202 || in_array($record->getAttemptOverviewInformation()?->getStatusOfAttempt()->value, $value),
203 'test_passed' => fn(string $value, Participant $record) => $value === 'true'
204 ? $record->getAttemptOverviewInformation()?->hasPassingMark() === true
205 : $record->getAttemptOverviewInformation()?->hasPassingMark() !== true,
206 'scoring_finalized' => fn(string $value, Participant $record) => $value === 'true'
207 ? $record->isScoringFinalized() == true
208 : $record->isScoringFinalized() === false
209 ];
210 }
211
215 private function getPostLoadOrderFields(): array
216 {
217 $processing_time = $this->test_object->getProcessingTimeInSeconds();
218 $reset_time_on_new_attempt = $this->test_object->getResetProcessingTime();
219
220 return [
221 'attempt_started_at' => static fn(Participant $a, Participant $b) => $a->getFirstAccess() <=> $b->getFirstAccess(),
222 'total_duration' => static fn(
225 ) => $a->getTotalDuration($processing_time) <=> $b->getTotalDuration($processing_time),
226 'remaining_duration' => static fn(
229 ) => $a->getRemainingDuration($processing_time, $reset_time_on_new_attempt)
230 <=> $b->getRemainingDuration($processing_time, $reset_time_on_new_attempt),
231 'last_access' => static fn(Participant $a, Participant $b) => $a->getLastAccess() <=> $b->getLastAccess(),
232 'status_of_attempt' => fn(
235 ) => strcmp(
236 $a->getAttemptOverviewInformation()?->getStatusOfAttempt()?->getTranslation($this->lng) ?? StatusOfAttempt::NOT_YET_STARTED->getTranslation($this->lng),
237 $b->getAttemptOverviewInformation()?->getStatusOfAttempt()?->getTranslation($this->lng) ?? StatusOfAttempt::NOT_YET_STARTED->getTranslation($this->lng)
238 ),
239 'reached_points' => static fn(
242 ) => $a->getAttemptOverviewInformation()?->getReachedPoints()
243 <=> $b->getAttemptOverviewInformation()?->getReachedPoints(),
244 'nr_of_answered_questions' => static fn(
247 ) => $a->getAttemptOverviewInformation()?->getNrOfAnsweredQuestions()
248 <=> $b->getAttemptOverviewInformation()?->getNrOfAnsweredQuestions(),
249 'percent_of_available_points' => static fn(
252 ) => $a->getAttemptOverviewInformation()?->getReachedPointsInPercent()
253 <=> $b->getAttemptOverviewInformation()?->getReachedPointsInPercent(),
254 'test_passed' => static fn(
257 ) => $a->getAttemptOverviewInformation()?->hasPassingMark()
258 <=> $b->getAttemptOverviewInformation()?->hasPassingMark(),
259 'mark' => static fn(
262 ) => $a->getAttemptOverviewInformation()?->getMark() <=> $b->getAttemptOverviewInformation()?->getMark(),
263 'matriculation' => static fn(
266 ) => $a->getMatriculation() <=> $b->getMatriculation(),
267 'id_of_attempt' => static fn(
270 ) => $a->getAttemptOverviewInformation()?->getExamId() <=> $b->getAttemptOverviewInformation()?->getExamId(),
271 'total_time_on_task' => static fn(
274 ) => $a->getAttemptOverviewInformation()?->getTotalTimeOnTask() <=> $b->getAttemptOverviewInformation()?->getTotalTimeOnTask()
275 ];
276 }
277
278 private function getFilterComponent(string $action, ServerRequestInterface $request): FilterComponent
279 {
280 $filter_inputs = [];
281 $is_input_initially_rendered = [];
282 $field_factory = $this->ui_factory->input()->field();
283
284 foreach ($this->getFilterFields($field_factory) as $filter_id => $filter) {
285 [$filter_inputs[$filter_id], $is_input_initially_rendered[$filter_id]] = $filter;
286 }
287
288 return $this->ui_service->filter()->standard(
289 "participant_filter_{$this->test_request->getRefId()}",
290 $action,
291 $filter_inputs,
292 $is_input_initially_rendered,
293 true,
294 true
295 );
296 }
297
303 private function getFilterFields(FieldFactory $field_factory): array
304 {
305 $yes_no_all_options = [
306 'true' => $this->lng->txt('yes'),
307 'false' => $this->lng->txt('no')
308 ];
309
310 $solution_options = [
311 'false' => $this->lng->txt('without_solution'),
312 'true' => $this->lng->txt('with_solution')
313 ];
314
315 $filters = [
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],
320 ];
321
322 if ($this->test_object->getEnableProcessingTime()) {
323 $filters['extra_time'] = [$field_factory->select($this->lng->txt('extratime'), $yes_no_all_options), true];
324 }
325
326 $filters['status_of_attempt'] = [
327 $field_factory->multiSelect(
328 $this->lng->txt('status_of_attempt'),
329 array_reduce(
330 StatusOfAttempt::cases(),
331 function (array $c, StatusOfAttempt $v): array {
332 $c[$v->value] = $v->getTranslation($this->lng);
333 return $c;
334 },
335 []
336 )
337 ),
338 true
339 ];
340
341 $filters['test_passed'] = [
342 $field_factory->select($this->lng->txt('tst_passed'), $yes_no_all_options),
343 true
344 ];
345
346 if ($this->scoring_enabled) {
347 $filters['scoring_finalized'] = [
348 $field_factory->select($this->lng->txt('finalized_evaluation'), $yes_no_all_options),
349 true
350 ];
351 }
352
353 return $filters;
354 }
355
356 private function getTableComponent(ServerRequestInterface $request, ?array $filter)
357 {
358 return $this->ui_factory
359 ->table()
360 ->data(
361 $this,
362 '',
363 $this->getColumns()
364 )
365 ->withId(self::ID)
366 ->withRequest($request)
367 ->withFilter($filter);
368 }
369
373 private function getColumns(): array
374 {
375 $column_factory = $this->ui_factory->table()->column();
376
377 $columns = [
378 'name' => $column_factory->text($this->lng->txt('name'))
379 ->withIsSortable(!$this->test_object->getAnonymity())
380 ];
381
382 if (!$this->test_object->getAnonymity()) {
383 $columns += [
384 'login' => $column_factory->text($this->lng->txt('login'))->withIsSortable(true),
385 'matriculation' => $column_factory->text($this->lng->txt('matriculation'))
386 ->withIsOptional(true, false)
387 ->withIsSortable(true)
388 ];
389 }
390
391 $columns += [
392 'ip_range' => $column_factory->text($this->lng->txt('client_ip_range'))
393 ->withIsOptional(true, false)
394 ->withIsSortable(true),
395 'attempt_started_at' => $column_factory->date(
396 $this->lng->txt('tst_attempt_started'),
397 $this->current_user->getDateTimeFormat()
398 )->withIsSortable(true),
399 'total_time_on_task' => $column_factory->text($this->lng->txt('working_time'))
400 ->withIsOptional(true, false),
401 'total_attempts' => $column_factory->number($this->lng->txt('total_attempts'))
402 ->withIsOptional(true, false)
403 ->withIsSortable(true),
404 ];
405
406 $columns['status_of_attempt'] = $column_factory->text($this->lng->txt('status_of_attempt'))
407 ->withIsSortable(true);
408
409 if ($this->test_object->getEnableProcessingTime()) {
410 $columns['remaining_duration'] = $column_factory->text($this->lng->txt('remaining_duration'))
411 ->withIsOptional(true);
412 $columns['total_duration'] = $column_factory->text($this->lng->txt('total_duration'))
413 ->withIsOptional(true, false);
414 $columns['extra_time'] = $column_factory->text($this->lng->txt('extratime'))
415 ->withIsOptional(true, false);
416 }
417
418 if ($this->test_object->getMainSettings()->getTestBehaviourSettings()->getExamIdInTestAttemptEnabled()) {
419 $columns['id_of_attempt'] = $column_factory->text($this->lng->txt('exam_id_of_attempt'))
420 ->withIsOptional(true, false)
421 ->withIsSortable(true);
422 }
423
424 if ($this->test_access->checkParticipantsResultsAccess()) {
425 $columns['reached_points'] = $column_factory->text($this->lng->txt('tst_reached_points'))
426 ->withIsSortable(true)
427 ->withOrderingLabels(...$column_factory->number($this->lng->txt('tst_reached_points'))->getOrderingLabels());
428 $columns['nr_of_answered_questions'] = $column_factory->text($this->lng->txt('tst_answered_questions'))
429 ->withIsOptional(true, false)
430 ->withIsSortable(true)
431 ->withOrderingLabels(...$column_factory->number($this->lng->txt('tst_answered_questions'))->getOrderingLabels());
432 $columns['percent_of_available_points'] = $column_factory->number($this->lng->txt('tst_percent_solved'))
433 ->withUnit('%')
434 ->withIsOptional(true, false)
435 ->withIsSortable(true);
436 $columns['test_passed'] = $column_factory->boolean(
437 $this->lng->txt('tst_passed'),
438 $this->ui_factory->symbol()->icon()->custom(
439 'assets/images/standard/icon_checked.svg',
440 $this->lng->txt('yes'),
441 'small'
442 ),
443 $this->ui_factory->symbol()->icon()->custom(
444 'assets/images/standard/icon_unchecked.svg',
445 $this->lng->txt('no'),
446 'small'
447 )
448 )->withIsSortable(true)
449 ->withOrderingLabels(
450 "{$this->lng->txt('tst_passed')}, {$this->lng->txt('no')} {$this->lng->txt('order_option_first')}",
451 "{$this->lng->txt('tst_passed')}, {$this->lng->txt('yes')} {$this->lng->txt('order_option_first')}"
452 );
453 $columns['mark'] = $column_factory->text($this->lng->txt('tst_mark'))
454 ->withIsOptional(true, false)
455 ->withIsSortable(true);
456 }
457 if ($this->scoring_enabled) {
458 $columns['scoring_finalized'] = $column_factory->boolean(
459 $this->lng->txt('finalized_evaluation'),
460 $this->ui_factory->symbol()->icon()->custom(
461 'assets/images/standard/icon_checked.svg',
462 $this->lng->txt('yes'),
463 'small'
464 ),
465 $this->ui_factory->symbol()->icon()->custom(
466 'assets/images/standard/icon_unchecked.svg',
467 $this->lng->txt('no'),
468 'small'
469 )
470 )->withIsOptional(true, false)
471 ->withIsSortable(true);
472 }
473
474 $columns['last_access'] = $column_factory->date(
475 $this->lng->txt('last_access'),
476 $this->current_user->getDateTimeFormat()
477 );
478
479 return $columns;
480 }
481
482 private function loadRecords(?array $filter, Order $order): iterable
483 {
484 if ($this->records !== null) {
485 return $this->records;
486 }
487
488 $records = iterator_to_array(
489 $this->repository->getParticipants(
490 $this->test_object->getTestId(),
491 $filter,
492 null,
493 $order
494 )
495 );
496
497 $this->records = array_filter(
498 $records,
499 fn(Participant $participant) => in_array(
500 $participant->getUserId(),
501 $this->buildAccessFilteredParticipantsList($records)
502 )
503 );
504
505 return $this->records;
506 }
507
513 private function buildAccessFilteredParticipantsList(array $records): array
514 {
515 $manage_access_filter = $this->participant_access_filter
516 ->getManageParticipantsUserFilter($this->test_object->getRefId());
517 $access_results_access_filter = $this->participant_access_filter
518 ->getAccessResultsUserFilter($this->test_object->getRefId());
519 $participant_ids = array_map(
520 fn(Participant $participant) => $participant->getUserId(),
521 $records
522 );
523 return $manage_access_filter($participant_ids) + $access_results_access_filter($participant_ids);
524 }
525
526
530 private function getViewControlledRecords(?array $filter_data, Range $range, Order $order): iterable
531 {
532 return $this->limitRecords(
533 $this->orderRecords(
534 $this->filterRecords(
535 $this->results_data_factory->addAttemptOverviewInformationToParticipants(
536 $this->results_presentation_settings,
537 $this->test_object,
538 $this->loadRecords($filter_data, $order)
539 ),
540 $filter_data
541 ),
542 $order
543 ),
544 $range
545 );
546 }
547
548 private function filterRecords(iterable $records, ?array $filter_data): iterable
549 {
550 foreach ($records as $record) {
551 if ($this->matchFilter($record, $filter_data)) {
552 yield $record;
553 }
554 }
555 }
556
557 private function matchFilter(Participant $record, ?array $filter): bool
558 {
559 if ($filter === null) {
560 return true;
561 }
562
563 $post_load_filters = $this->getPostLoadFilters();
564 $allow = true;
565
566 foreach ($filter as $key => $value) {
567 if (!$value) {
568 continue;
569 }
570
571 $post_load_filter = $post_load_filters[$key] ?? fn() => true;
572 $allow = $allow && $post_load_filter($value, $record);
573 }
574
575 return $allow;
576 }
577
578 private function orderRecords(iterable $records, Order $order): array
579 {
580 $post_load_order_fields = $this->getPostLoadOrderFields();
581 $records = iterator_to_array($records);
582
583 uasort($records, static function (Participant $a, Participant $b) use ($order, $post_load_order_fields) {
584 foreach ($order->get() as $subject => $direction) {
585 $post_load_order_field = $post_load_order_fields[$subject] ?? fn() => 0;
586 $index = $post_load_order_field($a, $b);
587
588 if ($index !== 0) {
589 return $direction === 'DESC' ? $index * -1 : $index;
590 }
591 }
592
593 return 0;
594 });
595
596 return $records;
597 }
598
599 private function limitRecords(array $records, Range $range): array
600 {
601 return array_slice($records, $range->getStart(), $range->getLength());
602 }
603}
Builds a Color from either hex- or rgb values.
Definition: Factory.php:31
Both the subject and the direction need to be specified when expressing an order.
Definition: Order.php:29
A simple class to express a naive range of whole positive numbers.
Definition: Range.php:29
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...
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)
limitRecords(array $records, Range $range)
getTableComponent(ServerRequestInterface $request, ?array $filter)
User class.
Filter service.
return['3gp', '7z', 'ai', 'aif', 'aifc', 'aiff', 'au', 'arw', 'avi', 'backup', 'bak', 'bas', 'bpmn', 'bpmn2', 'bmp', 'bib', 'bibtex', 'bz', 'bz2', 'c', 'c++', 'cc', 'cct', 'cdf', 'cer', 'class', 'cls', 'conf', 'cpp', 'crt', 'crs', 'crw', 'cr2', 'css', 'cst', 'csv', 'cur', 'db', 'dcr', 'des', 'dng', 'doc', 'docx', 'dot', 'dotx', 'dtd', 'dvi', 'el', 'eps', 'epub', 'f', 'f77', 'f90', 'flv', 'for', 'g3', 'gif', 'gl', 'gan', 'ggb', 'gsd', 'gsm', 'gtar', 'gz', 'gzip', 'h', 'hpp', 'htm', 'html', 'htmls', 'ibooks', 'ico', 'ics', 'ini', 'ipynb', 'java', 'jbf', 'jpeg', 'jpg', 'js', 'jsf', 'jso', 'json', 'latex', 'lang', 'less', 'log', 'lsp', 'ltx', 'm1v', 'm2a', 'm2v', 'm3u', 'm4a', 'm4v', 'markdown', 'm', 'mat', 'md', 'mdl', 'mdown', 'mid', 'min', 'midi', 'mobi', 'mod', 'mov', 'movie', 'mp2', 'mp3', 'mp4', 'mpa', 'mpeg', 'mpg', 'mph', 'mpga', 'mpp', 'mpt', 'mpv', 'mpx', 'mv', 'mw', 'mv4', 'nb', 'nbp', 'nef', 'nif', 'niff', 'obj', 'obm', 'odt', 'ods', 'odp', 'odg', 'odf', 'oga', 'ogg', 'ogv', 'old', 'p', 'pas', 'pbm', 'pcl', 'pct', 'pcx', 'pdf', 'pgm', 'pic', 'pict', 'png', 'por', 'pov', 'project', 'properties', 'ppa', 'ppm', 'pps', 'ppsx', 'ppt', 'pptx', 'ppz', 'ps', 'psd', 'pwz', 'qt', 'qtc', 'qti', 'qtif', 'r', 'ra', 'ram', 'rar', 'rast', 'rda', 'rev', 'rexx', 'ris', 'rf', 'rgb', 'rm', 'rmd', 'rmi', 'rmm', 'rmp', 'rt', 'rtf', 'rtx', 'rv', 's', 's3m', 'sav', 'sbs', 'sec', 'sdml', 'sgm', 'sgml', 'smi', 'smil', 'srt', 'sps', 'spv', 'stl', 'svg', 'swa', 'swf', 'swz', 'tar', 'tex', 'texi', 'texinfo', 'text', 'tgz', 'tif', 'tiff', 'ttf', 'txt', 'tmp', 'uvproj', 'vdf', 'vimeo', 'viv', 'vivo', 'vrml', 'vsdx', 'vtt', 'wav', 'webm', 'wmv', 'wmx', 'wmz', 'woff', 'wwd', 'xhtml', 'xif', 'xls', 'xlsx', 'xmind', 'xml', 'xsl', 'xsd', 'zip']
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This describes a standard filter.
Definition: Standard.php:27
This is what a factory for input fields looks like.
Definition: Factory.php:31
This describes commonalities between the different modals.
Definition: Modal.php:35
buildDataRow(string $id, array $record)
$request
@ilCtrl_Calls ilObjLearningSequenceContentGUI: ilObjLearningSequenceConditionsGUI
Definition: ltiauth.php:39
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Participant.php:21
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
global $lng
Definition: privfeed.php:26