ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ConfigurationGUI.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
23use ILIAS\User\RedirectOnMissingWrite;
27use ILIAS\UI\Factory as UIFactory;
28use ILIAS\UI\Renderer as UIRenderer;
31use ILIAS\UI\Component\Modal\RoundTrip as RoundTripModal;
32use ILIAS\UI\Component\Modal\Interruptive as InterruptiveModal;
33use ILIAS\UI\Component\Listing\Descriptive as DescriptiveListing;
34use ILIAS\UI\Component\Table\Data as DataTable;
42use ILIAS\Refinery\Factory as Refinery;
44use ILIAS\HTTP\Services as HttpService;
46use Psr\Http\Message\ServerRequestInterface;
47
49{
50 use RedirectOnMissingWrite;
51
52 private const string CHANGED_ATTRIBUTES_PARAMETER = 'ca';
53
54 private const string ACTION_EDIT = 'edit';
55 private const string ACTION_DELETE = 'delete';
56
57 private readonly URLBuilder $url_builder;
60
61 private array $available_fields;
62
63 public function __construct(
64 private readonly Language $lng,
65 private readonly \ilCtrl $ctrl,
66 private readonly \ilAppEventHandler $event,
67 private readonly \ilAccess $access,
68 private readonly \ilToolbarGUI $toolbar,
69 private readonly \ilGlobalTemplateInterface $tpl,
70 private readonly UIFactory $ui_factory,
71 private readonly UIRenderer $ui_renderer,
72 private readonly Refinery $refinery,
73 private readonly ServerRequestInterface $request,
74 private readonly RequestWrapper $request_wrapper,
75 private readonly RequestWrapper $post_wrapper,
76 private readonly HttpService $http,
77 private readonly array $available_change_listeners,
78 private readonly ConfigurationRepository $repository
79 ) {
80 $this->available_fields = $this->repository->get();
81
82 $url_builder = new URLBuilder(
83 new URI(
84 ILIAS_HTTP_PATH . '/' . $this->ctrl->getLinkTargetByClass(
85 [\ilAdministrationGUI::class, \ilObjUserFolderGUI::class, self::class],
86 'action'
87 )
88 )
89 );
90 [
95 ['profile', 'fields'],
96 'table_action',
97 'field'
98 );
99 }
100
101 public function executeCommand(): void
102 {
103 $this->redirectOnMissingWrite($this->access, $this->ctrl, $this->tpl, $this->lng);
104 $cmd = $this->ctrl->getCmd() . 'Cmd';
105 $this->$cmd();
106 }
107
108 public function showCmd(?RoundTripModal $modal = null): void
109 {
110 if (!$this->repository->hasMigrationBeenRun()) {
111 $this->tpl->setOnScreenMessage('info', $this->lng->txt('missing_migration'));
112 return;
113 }
114
115 $create_modal = $this->buildCreateModal();
116 $this->toolbar->addComponent(
117 $this->ui_factory->button()->standard(
118 $this->lng->txt('add_user_defined_field'),
119 $create_modal->getShowSignal()
120 )
121 );
122 $content = [
123 $create_modal,
124 $this->buildTable()
125 ];
126
127 if ($modal !== null) {
128 $content[] = $modal;
129 }
130
131 $this->tpl->setContent(
132 $this->ui_renderer->render($content)
133 );
134 }
135
136 public function actionCmd(): void
137 {
138 $action = $this->request_wrapper->retrieve(
139 $this->action_token->getName(),
140 $this->refinery->kindlyTo()->string()
141 );
142 $this->http->saveResponse(
143 $this->http->response()->withBody(
145 $this->ui_renderer->renderAsync(
146 $this->buildActionModal($action)
147 )
148 )
149 )
150 );
151 $this->http->sendResponse();
152 $this->http->close();
153 }
154
155 public function saveCmd(): void
156 {
157 $field = $this->repository->getByIdentifier(
159 );
160 $modal = $this->buildEditModal($field)->withRequest($this->request);
161 $data = $modal->getData();
162 if ($data === null) {
163 $this->showCmd($modal->withOnLoad($modal->getShowSignal()));
164 return;
165 }
166
167 $listeners_to_notify = $this->getListenersToNotifyByChangedValues($field, $data['field']);
168 if ($listeners_to_notify !== []) {
169 $this->showChangeListenerConfirmationModal($listeners_to_notify, $data['field']);
170 return;
171 }
172
173 $this->storeField($data['field']);
174 $this->ctrl->redirectByClass(
175 [\ilAdministrationGUI::class, \ilObjUserFolderGUI::class, self::class],
176 'show'
177 );
178 }
179
180 public function createCmd(): void
181 {
182 $modal = $this->buildCreateModal()->withRequest($this->request);
183 $data = $modal->getData();
184 if ($data === null) {
185 $this->showCmd($modal->withOnLoad($modal->getShowSignal()));
186 return;
187 }
188
189 $listeners_to_notify = $this->getListenersToNotifyByChangedValues(
190 $this->repository->getUnspecifiedCustomField(),
191 $data['field']
192 );
193
194 if ($listeners_to_notify !== []) {
195 $this->showChangeListenerConfirmationModal($listeners_to_notify, $data['field']);
196 return;
197 }
198
199 $this->storeField($data['field']);
200 $this->showCmd();
201 }
202
203 public function saveAfterListenerConfirmationCmd(): void
204 {
205 $field = $this->repository->getByIdentifier(
207 );
208
211 $field,
213 ),
214 $field,
215 )->withRequest($this->request)->getData();
216
217 if ($data === null || $data['field']->isRequired() && !$data['field']->isVisibleInRegistration()) {
218 $this->showCmd();
219 return;
220 }
221
222 $this->storeField($data['field']);
223 $this->event->raise(
224 'components/ILIAS/User',
225 'onUserFieldAttributesChanged',
226 $field->getChangedAttributes($data['field'])
227 );
228 $this->showCmd();
229 }
230
231 public function deleteCmd(): void
232 {
233 $identifier = $this->post_wrapper->retrieve(
234 'interruptive_items',
235 $this->refinery->byTrying([
236 $this->refinery->kindlyTo()->listOf(
237 $this->refinery->kindlyTo()->string()
238 ),
239 $this->refinery->always(null)
240 ])
241 );
242 if ($identifier === null) {
243 $this->showCmd();
244 }
245 $this->repository->deleteCustomField(
246 $this->repository->getByIdentifier($identifier[0])
247 );
248 $this->available_fields = $this->repository->get();
249 $this->tpl->setOnScreenMessage('success', $this->lng->txt('udf_field_deleted'), true);
250 $this->showCmd();
251 }
252
253 public function getRows(
254 DataRowBuilder $row_builder,
255 array $visible_column_ids,
257 Order $order,
258 ?array $filter_data,
259 ?array $additional_parameters
260 ): \Generator {
261 $this->orderRows($order);
262 foreach ($this->available_fields as $field) {
263 yield $field->getTableRow(
264 $row_builder,
265 $this->lng,
266 $this->ui_factory,
267 $this->ui_renderer
268 );
269 }
270 }
271
272 public function getTotalRowCount(
273 ?array $filter_data,
274 ?array $additional_parameters
275 ): ?int {
276 return count($this->available_fields);
277 }
278
279 private function buildTable(): DataTable
280 {
281 return $this->ui_factory->table()->data(
282 $this,
283 $this->lng->txt('profile_fields'),
284 $this->getColumns()
285 )->withActions(
286 $this->getActions()
287 )->withRequest($this->request);
288 }
289
290 private function getColumns(): array
291 {
292 $cf = $this->ui_factory->table()->column();
293 return [
294 'field' => $cf->text($this->lng->txt('user_field'))->withIsSortable(true),
295 'type' => $cf->text($this->lng->txt('type'))
296 ->withIsOptional(true, true)
297 ->withIsSortable(true),
298 'section' => $cf->text($this->lng->txt('profile_section'))
299 ->withIsOptional(true, false)
300 ->withIsSortable(true),
301 'access' => $cf->text($this->lng->txt('access'))->withIsSortable(false),
302 'required' => $cf->boolean(
303 $this->lng->txt(
304 PropertyAttributes::Required->value
305 ),
306 $this->ui_factory->symbol()->glyph()->checked(),
307 $this->ui_factory->symbol()->glyph()->unchecked()
308 )->withIsSortable(true),
309 'export' => $cf->boolean(
310 $this->lng->txt(
311 PropertyAttributes::Export->value
312 ),
313 $this->ui_factory->symbol()->glyph()->checked(),
314 $this->ui_factory->symbol()->glyph()->unchecked()
315 )->withIsSortable(true),
316 'searchable' => $cf->boolean(
317 $this->lng->txt(
318 PropertyAttributes::Searchable->value
319 ),
320 $this->ui_factory->symbol()->glyph()->checked(),
321 $this->ui_factory->symbol()->glyph()->unchecked()
322 )->withIsSortable(true),
323 'available_in_certificates' => $cf->boolean(
324 $this->lng->txt(
325 PropertyAttributes::AvailableInCertificates->value
326 ),
327 $this->ui_factory->symbol()->glyph()->checked(),
328 $this->ui_factory->symbol()->glyph()->unchecked()
329 )->withIsSortable(true)
330 ];
331 }
332
333 private function getActions(): array
334 {
335 return [
336 self::ACTION_EDIT => $this->ui_factory->table()->action()->single(
337 $this->lng->txt('edit_field'),
338 $this->url_builder->withParameter(
339 $this->action_token,
340 self::ACTION_EDIT
341 ),
342 $this->field_id_token
343 )->withAsync(true),
344 self::ACTION_DELETE => $this->ui_factory->table()->action()->single(
345 $this->lng->txt('delete'),
346 $this->url_builder->withParameter(
347 $this->action_token,
348 self::ACTION_DELETE
349 ),
350 $this->field_id_token
351 )->withAsync(true)
352 ];
353 }
354
355 private function orderRows(Order $order): void
356 {
357 $order_array = $order->get();
358 $key = array_key_first($order_array);
359 $factor = array_shift($order_array) === 'ASC' ? 1 : -1;
360 if ($key === 'field') {
361 usort(
362 $this->available_fields,
363 fn(Field $v1, Field $v2): int =>
364 $factor * ($this->lng->txt($v1->getLabel($this->lng)) <=> $this->lng->txt($v2->getLabel($this->lng)))
365 );
366 return;
367 }
368
369 if ($key === 'type') {
370 usort(
371 $this->available_fields,
372 fn(Field $v1, Field $v2): int =>
373 $factor * ($this->lng->txt($v1->isCustom() ? 'custom' : 'default') <=> $this->lng->txt($v2->isCustom() ? 'custom' : 'default'))
374 );
375 return;
376 }
377
378 if ($key === 'section') {
379 usort(
380 $this->available_fields,
381 fn(Field $v1, Field $v2): int =>
382 $factor * ($this->lng->txt($v1->getSection()->value) <=> $this->lng->txt($v2->getSection()->value))
383 );
384 return;
385 }
386
387 if ($key === 'export') {
388 usort(
389 $this->available_fields,
390 fn(Field $v1, Field $v2): int =>
391 $factor * ($v1->export() <=> $v2->export())
392 );
393 return;
394 }
395
396 if ($key === 'required') {
397 usort(
398 $this->available_fields,
399 fn(Field $v1, Field $v2): int =>
400 $factor * ($v1->isRequired() <=> $v2->isRequired())
401 );
402 return;
403 }
404
405 if ($key === 'searchable') {
406 usort(
407 $this->available_fields,
408 fn(Field $v1, Field $v2): int =>
409 $factor * ($v1->isSearchable() <=> $v2->isSearchable())
410 );
411 return;
412 }
413
414 if ($key === 'available_in_certificates') {
415 usort(
416 $this->available_fields,
417 fn(Field $v1, Field $v2): int =>
418 $factor * ($v1->isAvailableInCertificates() <=> $v2->isAvailableInCertificates())
419 );
420 return;
421 }
422 }
423
424 private function buildActionModal(
425 ?string $action
426 ): Modal|MessageBox {
427 $field = $this->repository->getByIdentifier(
428 $this->retrieveIdentifierFromQuery()
429 );
430 return match ($action) {
431 self::ACTION_EDIT => $this->buildEditModal($field),
432 self::ACTION_DELETE => $this->buildDeleteConfirmationModal($field),
433 default => $this->ui_factory->messageBox()->failure(
434 $this->lng->txt('msg_cancel')
435 )
436 };
437 }
438
439 private function buildEditModal(
440 Field $field
441 ): RoundTripModal {
442 $identifier = $this->retrieveIdentifierFromQuery();
443 $this->ctrl->setParameterByClass(self::class, $this->field_id_token->getName(), $identifier);
444 return $this->ui_factory->modal()->roundtrip(
445 "{$this->lng->txt('edit_field')}: {$field->getLabel($this->lng)}",
446 null,
447 $field->getEditForm(
448 $this->lng,
449 $this->ui_factory->input()->field(),
450 $this->refinery,
451 $this->repository->getCustomFieldTypes(),
452 array_filter(
453 $this->available_fields,
454 static fn(Field $v): bool => $v->isCustom()
455 )
456 ),
457 $this->ctrl->getFormActionByClass(
458 [\ilAdministrationGUI::class, \ilObjUserFolderGUI::class, self::class],
459 'save'
460 )
461 );
462 }
463
464 private function buildCreateModal(): RoundTripModal
465 {
466 return $this->ui_factory->modal()->roundtrip(
467 $this->lng->txt('add_user_defined_field'),
468 null,
469 $this->repository->getUnspecifiedCustomField()->getCreateCustomFieldForm(
470 $this->lng,
471 $this->ui_factory->input()->field(),
472 $this->refinery,
473 $this->repository->getCustomFieldTypes(),
474 array_filter(
475 $this->available_fields,
476 static fn(Field $v): bool => $v->isCustom()
477 )
478 ),
479 $this->ctrl->getFormActionByClass(
480 [\ilAdministrationGUI::class, \ilObjUserFolderGUI::class, self::class],
481 'create'
482 )
483 );
484 }
485
487 array $listeners_to_notify,
488 Field $new
489 ): void {
490 $this->setChangedAttributesParameter($listeners_to_notify);
491 $modal = $this->buildChangeListenerConfirmationModal(
492 $listeners_to_notify,
493 $new
494 );
495 $this->showCmd($modal->withOnLoad($modal->getShowSignal()));
496 }
497
499 array $listeners_to_notify,
500 Field $field
501 ): RoundTripModal {
502 return $this->ui_factory->modal()->roundtrip(
503 $this->lng->txt('usr_field_change_components_listening'),
504 $this->ui_factory->messageBox()->confirmation(
505 $this->ui_renderer->render(
506 $this->buildListingOfListeners($listeners_to_notify, $field->getLabel($this->lng))
507 )
508 ),
509 $field->getHiddenForm(
510 $this->lng,
511 $this->ui_factory->input()->field(),
512 $this->refinery,
513 $this->repository->getCustomFieldTypes(),
514 array_filter(
515 $this->available_fields,
516 static fn(Field $v): bool => $v->isCustom()
517 )
518 ),
519 $this->ctrl->getFormActionByClass(
520 [\ilAdministrationGUI::class, \ilObjUserFolderGUI::class, self::class],
521 'saveAfterListenerConfirmation'
522 )
523 );
524 }
525
527 Field $field
528 ): InterruptiveModal {
529 return $this->ui_factory->modal()->interruptive(
530 $this->lng->txt('confirm'),
531 $this->lng->txt('udf_delete_sure'),
532 $this->ctrl->getFormActionByClass(
533 [\ilAdministrationGUI::class, \ilObjUserFolderGUI::class, self::class],
534 'delete'
535 )
536 )->withAffectedItems([
537 $this->ui_factory->modal()->interruptiveItem()->standard(
538 $field->getIdentifier(),
539 $field->getLabel($this->lng)
540 )
541 ]);
542 }
543
544 private function retrieveIdentifierFromQuery(): string
545 {
546 $identifier = $this->request_wrapper->retrieve(
547 $this->field_id_token->getName(),
548 $this->refinery->byTrying([
549 $this->refinery->kindlyTo()->string(),
550 $this->refinery->kindlyTo()->listOf(
551 $this->refinery->kindlyTo()->string()
552 )
553 ])
554 );
555
556 if (is_array($identifier)) {
557 return $identifier[0];
558 }
559 return $identifier;
560 }
561
562 private function buildListingOfListeners(
563 array $listeners_to_notify,
564 string $field_name
565 ): DescriptiveListing {
566 return $this->ui_factory->listing()->descriptive(
567 array_reduce(
568 $listeners_to_notify,
569 function (array $c, UserFieldAttributesChangeListener $v) use ($field_name): array {
570 $c[$v->getComponentName()] = $v->getDescriptionForField(
571 $this->lng,
572 $field_name,
573 $this->lng->txt($v->isInterestedInAttribute()->value)
574 );
575 return $c;
576 },
577 []
578 )
579 );
580 }
581
583 Field $old_field,
584 Field $new_field
585 ): array {
586 return array_reduce(
587 $this->available_change_listeners,
588 static function (
589 array $c,
590 string $listener_class
591 ) use ($old_field, $new_field): array {
592 $listener = new $listener_class();
593 $field_definition_class = $listener->isInterestedInField();
594
595 if ($old_field->getIdentifier() === (new $field_definition_class())->getIdentifier()
596 && $old_field->retrieveValueByPropertyAttribute($listener->isInterestedInAttribute())
597 !== $new_field->retrieveValueByPropertyAttribute($listener->isInterestedInAttribute())) {
598 $c[] = $listener;
599 }
600
601 return $c;
602 },
603 []
604 );
605 }
606
612 Field $field,
613 array $attributes
614 ): array {
615 return array_reduce(
616 $this->available_change_listeners,
617 function (
618 array $c,
619 string $listener_class
620 ) use ($field, $attributes): array {
621 $listener = new $listener_class();
622 if ($field->getIdentifier() === $this->repository->getByClass(
623 $listener->isInterestedInField()
624 )->getIdentifier()
625 && in_array($listener->isInterestedInAttribute(), $attributes)) {
626 $c[] = $listener;
627 }
628
629 return $c;
630 },
631 []
632 );
633 }
634
635 private function setChangedAttributesParameter(array $listeners_to_notify): void
636 {
637 $this->ctrl->setParameterByClass(
638 self::class,
639 self::CHANGED_ATTRIBUTES_PARAMETER,
640 implode(
641 ',',
642 array_map(
643 fn(UserFieldAttributesChangeListener $v): string => $v->isInterestedInAttribute()->value,
644 $listeners_to_notify
645 )
646 )
647 );
648 }
649
650 private function retrieveChangedAttributesFromQuery(): array
651 {
652 if (!$this->request_wrapper->has(self::CHANGED_ATTRIBUTES_PARAMETER)) {
653 return [];
654 }
655
656 return $this->request_wrapper->retrieve(
657 self::CHANGED_ATTRIBUTES_PARAMETER,
658 $this->refinery->custom()->transformation(
659 fn(string $v): array => array_reduce(
660 explode(',', $v),
661 static function (array $c, string $v): array {
662 $a = PropertyAttributes::tryFrom($v);
663 if ($a !== null) {
664 $c[] = $a;
665 }
666 return $c;
667 },
668 []
669 )
670 )
671 );
672 }
673
674 private function storeField(Field $field): void
675 {
676 $this->repository->storeConfiguration($field);
677 $this->available_fields = $this->repository->get();
679 $this->tpl->setOnScreenMessage('success', $this->lng->txt('usr_settings_saved'), true);
680 }
681}
Builds a Color from either hex- or rgb values.
Definition: Factory.php:31
Builds data types.
Definition: Factory.php:36
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
The scope of this class is split ilias-conform URI's into components.
Definition: URI.php:35
Stream factory which enables the user to create streams without the knowledge of the concrete class.
Definition: Streams.php:32
static ofString(string $string)
Creates a new stream with an initial value.
Definition: Streams.php:41
Class Services.
Definition: Services.php:38
acquireParameters(array $namespace, string ... $names)
Definition: URLBuilder.php:138
setChangedAttributesParameter(array $listeners_to_notify)
getListenersToNotifyByChangedValues(Field $old_field, Field $new_field)
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....
buildChangeListenerConfirmationModal(array $listeners_to_notify, Field $field)
getListenersToNotifyByInterests(Field $field, array $attributes)
showChangeListenerConfirmationModal(array $listeners_to_notify, Field $new)
buildListingOfListeners(array $listeners_to_notify, string $field_name)
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 Language $lng, private readonly \ilCtrl $ctrl, private readonly \ilAppEventHandler $event, private readonly \ilAccess $access, private readonly \ilToolbarGUI $toolbar, private readonly \ilGlobalTemplateInterface $tpl, private readonly UIFactory $ui_factory, private readonly UIRenderer $ui_renderer, private readonly Refinery $refinery, private readonly ServerRequestInterface $request, private readonly RequestWrapper $request_wrapper, private readonly RequestWrapper $post_wrapper, private readonly HttpService $http, private readonly array $available_change_listeners, private readonly ConfigurationRepository $repository)
getLabel(Language $lng)
Definition: Field.php:73
retrieveValueByPropertyAttribute(PropertyAttributes $attribute)
Definition: Field.php:299
getEditForm(Language $lng, FieldFactory $ff, Refinery $refinery, array $custom_field_types, array $available_custom_fields)
Definition: Field.php:188
Class ilAccessHandler Checks access for ILIAS objects.
Class ilAdministratioGUI.
Global event handler.
Class ilCtrl provides processing control methods.
static _reset()
Reset all.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$http
Definition: deliver.php:30
$c
Definition: deliver.php:25
return['delivery_method'=> 'php',]
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Interface RequestWrapper.
This describes commonalities between the different modals.
Definition: Modal.php:35
This describes a Data Table.
Definition: Data.php:31
An entity that renders components to a string output.
Definition: Renderer.php:31
static http()
Fetches the global http state from ILIAS.
modal(string $title="", string $cancel_label="")
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
global $lng
Definition: privfeed.php:31