From 979ede35f9bf8c4a230f9924a163945eb0885b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Jou=C3=9Fen?= Date: Thu, 11 Dec 2025 17:27:47 +0100 Subject: [PATCH] BookingPool: Refactor Participant And Schedule Tables The participant and schedule tables were still using older UI patterns and had inconsistent action handling, which caused problems when removing participants and deleting schedules. This change migrates both tables to the new KS table elements, unifies table actions, updates language entries and filtering, and single and bulk delete actions. --- .../class.ilBookingParticipantGUI.php | 163 +++++++++---- .../Schedule/class.ilBookingScheduleGUI.php | 185 ++++++++++----- .../BookingManager/src/Common/Table/Table.php | 17 ++ .../src/Common/Table/TableAction.php | 57 +++++ .../Common/Table/TableActionExecutorTrait.php | 20 ++ .../Common/Table/TableActionModalTrait.php | 111 +++++++++ .../src/Common/Table/TableActions.php | 128 ++++++++++ .../ILIAS/BookingManager/src/HttpService.php | 83 +++++++ .../src/Participant/ParticipantRepository.php | 34 +++ .../src/Participant/ParticipantTable.php | 224 ++++++++++++++++++ ...rticipantTableBookForParticipantAction.php | 112 +++++++++ .../ParticipantTableDeleteAction.php | 169 +++++++++++++ .../ParticipantTableEditBookingAction.php | 136 +++++++++++ .../src/Schedule/ScheduleTable.php | 156 ++++++++++++ .../Schedule/ScheduleTableDeleteAction.php | 145 ++++++++++++ .../src/Schedule/ScheduleTableEditAction.php | 107 +++++++++ lang/ilias_de.lang | 8 + lang/ilias_en.lang | 8 + 18 files changed, 1763 insertions(+), 100 deletions(-) create mode 100644 components/ILIAS/BookingManager/src/Common/Table/Table.php create mode 100644 components/ILIAS/BookingManager/src/Common/Table/TableAction.php create mode 100644 components/ILIAS/BookingManager/src/Common/Table/TableActionExecutorTrait.php create mode 100644 components/ILIAS/BookingManager/src/Common/Table/TableActionModalTrait.php create mode 100644 components/ILIAS/BookingManager/src/Common/Table/TableActions.php create mode 100644 components/ILIAS/BookingManager/src/HttpService.php create mode 100644 components/ILIAS/BookingManager/src/Participant/ParticipantRepository.php create mode 100644 components/ILIAS/BookingManager/src/Participant/ParticipantTable.php create mode 100644 components/ILIAS/BookingManager/src/Participant/ParticipantTableBookForParticipantAction.php create mode 100644 components/ILIAS/BookingManager/src/Participant/ParticipantTableDeleteAction.php create mode 100644 components/ILIAS/BookingManager/src/Participant/ParticipantTableEditBookingAction.php create mode 100644 components/ILIAS/BookingManager/src/Schedule/ScheduleTable.php create mode 100644 components/ILIAS/BookingManager/src/Schedule/ScheduleTableDeleteAction.php create mode 100644 components/ILIAS/BookingManager/src/Schedule/ScheduleTableEditAction.php diff --git a/components/ILIAS/BookingManager/Participants/class.ilBookingParticipantGUI.php b/components/ILIAS/BookingManager/Participants/class.ilBookingParticipantGUI.php index 8a19294fa671..8e3cde5a0ec5 100755 --- a/components/ILIAS/BookingManager/Participants/class.ilBookingParticipantGUI.php +++ b/components/ILIAS/BookingManager/Participants/class.ilBookingParticipantGUI.php @@ -16,6 +16,19 @@ * *********************************************************************/ +use ILIAS\BookingManager\HttpService; +use ILIAS\BookingManager\Participant\ParticipantRepository; +use ILIAS\BookingManager\Participant\ParticipantTable; +use ILIAS\BookingManager\Participant\ParticipantTableBookForParticipantAction; +use ILIAS\BookingManager\Participant\ParticipantTableDeleteAction; +use ILIAS\BookingManager\Participant\ParticipantTableEditBookingAction; +use ILIAS\BookingManager\Common\Table\TableActions; +use ILIAS\Data\Factory; +use ILIAS\Refinery\Factory as Refinery; +use ILIAS\UI\Factory as UIFactory; +use ILIAS\UI\Renderer as UIRenderer; +use ILIAS\UI\URLBuilder; + /** * Class ilBookingParticipantGUI * @author Jesús López @@ -23,8 +36,6 @@ */ class ilBookingParticipantGUI { - public const FILTER_ACTION_APPLY = 1; - public const FILTER_ACTION_RESET = 2; public const PARTICIPANT_VIEW = 1; protected \ILIAS\BookingManager\Access\AccessManager $access; protected \ILIAS\BookingManager\StandardGUIRequest $book_request; @@ -37,12 +48,20 @@ class ilBookingParticipantGUI protected int $ref_id; protected int $pool_id; + private readonly Refinery $refinery; + private readonly UIFactory $ui_factory; + private readonly UIRenderer $ui_renderer; + private readonly HttpService $http_service; + private readonly ilUIService $ui_service; + private readonly Factory $data_factory; + private readonly ParticipantRepository $participant_repository; + public function __construct( ilObjBookingPoolGUI $a_parent_obj ) { global $DIC; - $this->tpl = $DIC["tpl"]; + $this->tpl = $DIC->ui()->mainTemplate(); $this->tabs = $DIC->tabs(); $this->ctrl = $DIC->ctrl(); $this->lng = $DIC->language(); @@ -52,7 +71,15 @@ public function __construct( ->internal() ->gui() ->standardRequest(); - + $this->refinery = $DIC->refinery(); + $this->ui_factory = $DIC->ui()->factory(); + $this->ui_renderer = $DIC->ui()->renderer(); + $this->http_service = new HttpService($DIC->http(), $this->refinery); + $this->ui_service = $DIC->uiService(); + $this->data_factory = new Factory(); + $this->participant_repository = new ParticipantRepository( + $DIC->database() + ); $this->ref_id = $a_parent_obj->getRefId(); $this->pool_id = $a_parent_obj->getObject()->getId(); @@ -90,28 +117,39 @@ public function executeCommand(): void } } + public function executeTableAction(): void + { + $this + ->configureParticipantTable() + ->execute($this->getTableActionUrlBuilder()); + + $this->render(); + } + /** * Render list of booking participants. - * uses ilBookingParticipantsTableGUI */ public function render(): void { - if ($this->access->canManageParticipants($this->ref_id)) { - ilRepositorySearchGUI::fillAutoCompleteToolbar( - $this, - $this->toolbar, - array( - 'auto_complete_name' => $this->lng->txt('user'), - 'submit_name' => $this->lng->txt('add'), - 'add_search' => true, - 'add_from_container' => $this->ref_id - ) - ); - - $table = new ilBookingParticipantsTableGUI($this, 'render', $this->ref_id, $this->pool_id); - - $this->tpl->setContent($table->getHTML()); + if (!$this->access->canManageParticipants($this->ref_id)) { + return; } + ilRepositorySearchGUI::fillAutoCompleteToolbar( + $this, + $this->toolbar, + array( + 'auto_complete_name' => $this->lng->txt('user'), + 'submit_name' => $this->lng->txt('add'), + 'add_search' => true, + 'add_from_container' => $this->ref_id + ) + ); + + $this->tpl->setContent( + $this->ui_renderer->render( + $this->configureParticipantTable()->getComponents($this->getTableActionUrlBuilder()) + ) + ); } public function addUserFromAutoCompleteObject(): bool @@ -164,30 +202,6 @@ public function addParticipantObject( return true; } - public function applyParticipantsFilter(): void - { - $this->applyFilterAction(self::FILTER_ACTION_APPLY); - } - - public function resetParticipantsFilter(): void - { - $this->applyFilterAction(self::FILTER_ACTION_RESET); - } - - protected function applyFilterAction( - int $a_filter_action - ): void { - $table = new ilBookingParticipantsTableGUI($this, 'render', $this->ref_id, $this->pool_id); - $table->resetOffset(); - if ($a_filter_action === self::FILTER_ACTION_RESET) { - $table->resetFilter(); - } else { - $table->writeFilterToSession(); - } - - $this->render(); - } - public function assignObjects(): void { $this->tabs->clearTargets(); @@ -197,4 +211,65 @@ public function assignObjects(): void $this->tpl->setContent($table->getHTML()); } + + private function configureParticipantTable(): ParticipantTable + { + return new ParticipantTable( + $this->ui_factory, + $this->lng, + new TableActions( + $this->ctrl, + $this->lng, + $this->tpl, + $this->ui_factory, + $this->ui_renderer, + $this->refinery, + $this->http_service, + [ + ParticipantTableBookForParticipantAction::ACTION_ID => new ParticipantTableBookForParticipantAction( + $this->ui_factory, + $this->lng, + $this->access, + $this->ctrl, + $this->http_service, + $this->ref_id, + $this->pool_id + ), + ParticipantTableEditBookingAction::ACTION_ID => new ParticipantTableEditBookingAction( + $this->ui_factory, + $this->lng, + $this->access, + $this->ctrl, + $this->http_service, + $this->ref_id, + $this->pool_id + ), + ParticipantTableDeleteAction::ACTION_ID => new ParticipantTableDeleteAction( + $this->ui_factory, + $this->lng, + $this->access, + $this->tpl, + $this->http_service, + $this->participant_repository, + $this->ref_id, + $this->pool_id + ), + ] + ), + $this->http_service, + $this->ui_service, + $this->pool_id, + $this->http_service->getRequest() + ); + } + + private function getTableActionUrlBuilder(): URLBuilder + { + return new URLBuilder($this->data_factory->uri( + ILIAS_HTTP_PATH . '/' . $this->ctrl->getLinkTargetByClass( + self::class, + 'executeTableAction' + ) + )); + } } diff --git a/components/ILIAS/BookingManager/Schedule/class.ilBookingScheduleGUI.php b/components/ILIAS/BookingManager/Schedule/class.ilBookingScheduleGUI.php index c1eebbaee03a..504eba492711 100755 --- a/components/ILIAS/BookingManager/Schedule/class.ilBookingScheduleGUI.php +++ b/components/ILIAS/BookingManager/Schedule/class.ilBookingScheduleGUI.php @@ -16,6 +16,19 @@ * *********************************************************************/ +use ILIAS\BookingManager\HttpService; +use ILIAS\BookingManager\Schedule\ScheduleManager; +use ILIAS\BookingManager\Schedule\ScheduleTable; +use ILIAS\BookingManager\Common\Table\TableActions; +use ILIAS\BookingManager\Schedule\ScheduleTableDeleteAction; +use ILIAS\BookingManager\Schedule\ScheduleTableEditAction; +use ILIAS\BookingManager\Service as BookingManager; +use ILIAS\Data\Factory; +use ILIAS\Refinery\Factory as Refinery; +use ILIAS\UI\Factory as UIFactory; +use ILIAS\UI\Renderer as UIRenderer; +use ILIAS\UI\URLBuilder; + /** * Class ilBookingScheduleGUI * @@ -35,6 +48,14 @@ class ilBookingScheduleGUI protected int $schedule_id; protected int $ref_id; + private readonly Refinery $refinery; + private readonly UIFactory $ui_factory; + private readonly UIRenderer $ui_renderer; + private readonly HttpService $http_service; + private readonly BookingManager $booking_manager; + private readonly ilToolbarGUI $toolbar; + private readonly Factory $data_factory; + public function __construct( ilObjBookingPoolGUI $a_parent_obj ) { @@ -47,6 +68,14 @@ public function __construct( $this->access = $DIC->bookingManager()->internal()->domain()->access(); $this->help = $DIC["ilHelp"]; $this->obj_data_cache = $DIC["ilObjDataCache"]; + $this->refinery = $DIC->refinery(); + $this->ui_factory = $DIC->ui()->factory(); + $this->ui_renderer = $DIC->ui()->renderer(); + $this->http_service = new HttpService($DIC->http(), $this->refinery); + $this->booking_manager = $DIC->bookingManager(); + $this->toolbar = $DIC->toolbar(); + $this->data_factory = new Factory(); + $this->ref_id = $a_parent_obj->getRefId(); $this->book_request = $DIC->bookingManager() ->internal() @@ -71,37 +100,55 @@ public function executeCommand(): void switch ($next_class) { default: $cmd = $ilCtrl->getCmd("render"); - $this->$cmd(); + if (method_exists($this, $cmd)) { + $this->$cmd(); + } break; } } - /** - * Render list of booking schedules - * uses ilBookingSchedulesTableGUI - */ + public function executeTableAction(): void + { + $pool_id = $this->obj_data_cache->lookupObjId($this->ref_id); + $schedule_manager = $this->booking_manager + ->internal() + ->domain() + ->schedules($pool_id); + + $this + ->configureScheduleTable($schedule_manager) + ->execute($this->getTableActionUrlBuilder()); + + $this->ctrl->redirectByClass( + ilBookingScheduleGUI::class, + 'render' + ); + } + public function render(): void { - $tpl = $this->tpl; - $lng = $this->lng; - $ilCtrl = $this->ctrl; - $table = new ilBookingSchedulesTableGUI($this, 'render', $this->ref_id); + $pool_id = $this->obj_data_cache->lookupObjId($this->ref_id); + $schedule_manager = $this->booking_manager + ->internal() + ->domain() + ->schedules($pool_id); - $bar = ""; - if ($this->access->canManageSettings($this->ref_id)) { - // if we have schedules but no objects - show info - if (count($table->getData())) { - if (!count(ilBookingObject::getList(ilObject::_lookupObjId($this->ref_id)))) { - $this->tpl->setOnScreenMessage('info', $lng->txt("book_type_warning")); - } - } + $this->checkForInfoMessageAboutMissingBookableItems($schedule_manager, $pool_id); - $bar = new ilToolbarGUI(); - $bar->addButton($lng->txt('book_add_schedule'), $ilCtrl->getLinkTarget($this, 'create')); - $bar = $bar->getHTML(); + if ($this->access->canManageSettings($this->ref_id)) { + $this->toolbar->addComponent( + $this->ui_factory->button()->standard( + $this->lng->txt('book_add_schedule'), + $this->ctrl->getLinkTarget($this, 'create') + ) + ); } - $tpl->setContent($bar . $table->getHTML()); + $this->tpl->setContent( + $this->ui_renderer->render( + $this->configureScheduleTable($schedule_manager)->getComponents($this->getTableActionUrlBuilder()) + ) + ); } /** @@ -258,8 +305,8 @@ public function save(): void $this->formToObject($form, $obj); $obj->save(); - $this->tpl->setOnScreenMessage('success', $lng->txt("book_schedule_added")); - $this->render(); + $this->tpl->setOnScreenMessage('success', $lng->txt("book_schedule_added"), true); + $this->ctrl->redirect($this, 'render'); } else { $form->setValuesByPost(); $tpl->setContent($form->getHTML()); @@ -277,8 +324,8 @@ public function update(): void $this->formToObject($form, $obj); $obj->update(); - $this->tpl->setOnScreenMessage('success', $lng->txt("book_schedule_updated")); - $this->render(); + $this->tpl->setOnScreenMessage('success', $lng->txt("book_schedule_updated"), true); + $this->ctrl->redirect($this, 'render'); } else { $form->setValuesByPost(); $tpl->setContent($form->getHTML()); @@ -342,43 +389,69 @@ protected function formToObject( $schedule->setDefinitionBySlots($days); } - /** - * Confirm delete - */ - public function confirmDelete(): void + private function configureScheduleTable(ScheduleManager $schedule_manager): ScheduleTable { - $ilCtrl = $this->ctrl; - $lng = $this->lng; - $tpl = $this->tpl; - $ilHelp = $this->help; - - $ilHelp->setSubScreenId("delete"); - - - $conf = new ilConfirmationGUI(); - $conf->setFormAction($ilCtrl->getFormAction($this)); - $conf->setHeaderText($lng->txt('book_confirm_delete')); - - $type = new ilBookingSchedule($this->schedule_id); - $conf->addItem('schedule_id', $this->schedule_id, $type->getTitle()); - $conf->setConfirm($lng->txt('delete'), 'delete'); - $conf->setCancel($lng->txt('cancel'), 'render'); - - $tpl->setContent($conf->getHTML()); + return new ScheduleTable( + $this->ui_factory, + $this->lng, + new TableActions( + $this->ctrl, + $this->lng, + $this->tpl, + $this->ui_factory, + $this->ui_renderer, + $this->refinery, + $this->http_service, + [ + ScheduleTableEditAction::ACTION_ID => new ScheduleTableEditAction( + $this->ui_factory, + $this->lng, + $this->access, + $this->ctrl, + $this->http_service, + $this->ref_id + ), + ScheduleTableDeleteAction::ACTION_ID => new ScheduleTableDeleteAction( + $this->ui_factory, + $this->lng, + $this->access, + $this->tpl, + $this->http_service, + $schedule_manager, + $this->ref_id + ), + ] + ), + $schedule_manager, + $this->http_service + ); } /** - * Delete schedule + * @param ScheduleManager $schedule_manager + * @param int $pool_id + * + * @return void */ - public function delete(): void + private function checkForInfoMessageAboutMissingBookableItems(ScheduleManager $schedule_manager, int $pool_id): void { - $ilCtrl = $this->ctrl; - $lng = $this->lng; - - $obj = new ilBookingSchedule($this->schedule_id); - $obj->delete(); + $schedule_data = $schedule_manager->getScheduleData(); + if (count($schedule_data) > 0) { + if (!count(ilBookingObject::getList($pool_id))) { + $this->tpl->setOnScreenMessage('info', $this->lng->txt("book_type_warning")); + } + } + } - $this->tpl->setOnScreenMessage('success', $lng->txt('book_schedule_deleted'), true); - $ilCtrl->redirect($this, 'render'); + private function getTableActionUrlBuilder(): URLBuilder + { + // Use current request URI so it can read existing parameters + // This ensures tokens work correctly when actions are rendered + return new URLBuilder($this->data_factory->uri( + ILIAS_HTTP_PATH . '/' . $this->ctrl->getLinkTargetByClass( + self::class, + 'executeTableAction' + ) + )); } } diff --git a/components/ILIAS/BookingManager/src/Common/Table/Table.php b/components/ILIAS/BookingManager/src/Common/Table/Table.php new file mode 100644 index 000000000000..759d45e97562 --- /dev/null +++ b/components/ILIAS/BookingManager/src/Common/Table/Table.php @@ -0,0 +1,17 @@ + + */ + public function getComponents(URLBuilder $url_builder): array; +} diff --git a/components/ILIAS/BookingManager/src/Common/Table/TableAction.php b/components/ILIAS/BookingManager/src/Common/Table/TableAction.php new file mode 100644 index 000000000000..7fd2d83f23d2 --- /dev/null +++ b/components/ILIAS/BookingManager/src/Common/Table/TableAction.php @@ -0,0 +1,57 @@ +table_actions->execute(...$this->acquireParameters($url_builder)); + } + + abstract protected function acquireParameters(URLBuilder $url_builder): array; +} diff --git a/components/ILIAS/BookingManager/src/Common/Table/TableActionModalTrait.php b/components/ILIAS/BookingManager/src/Common/Table/TableActionModalTrait.php new file mode 100644 index 000000000000..be2e0174391f --- /dev/null +++ b/components/ILIAS/BookingManager/src/Common/Table/TableActionModalTrait.php @@ -0,0 +1,111 @@ +http_service->resolveRowParameter($action_type_token->getName())) { + self::SUBMIT_MODAL_ACTION => $this->submit($url_builder, $row_id_token, $action_token, $action_type_token), + default => $this->showModal($url_builder, $row_id_token, $action_token, $action_type_token), + }; + } + + protected function showModal( + URLBuilder $url_builder, + URLBuilderToken $row_id_token, + URLBuilderToken $action_token, + URLBuilderToken $action_type_token, + ): ?Modal { + $selected_ids = $this->http_service->resolveRowParameters($row_id_token->getName()); + + $selected_records = $selected_ids === [] ? [] : array_filter( + $this->resolveRecords($selected_ids === 'ALL_OBJECTS' ? [] : $selected_ids), + static fn(array $record): bool => !isset($record['is_used']) ? true : !$record['is_used'] + ); + + return $this->getModal( + $url_builder + ->withParameter($row_id_token, $selected_ids) + ->withParameter($action_token, $this->getActionId()) + ->withParameter($action_type_token, self::SUBMIT_MODAL_ACTION), + $selected_records, + false + ); + } + + protected function submit( + URLBuilder $url_builder, + URLBuilderToken $row_id_token, + URLBuilderToken $action_token, + URLBuilderToken $action_type_token, + ): ?Modal { + $selected_ids = $this->http_service->resolveRowParameters($row_id_token->getName()); + + if ($selected_ids === []) { + $this->showErrorMessage($this->getSelectionErrorMessage()); + return null; + } + + $selected_records = array_filter( + $this->resolveRecords($selected_ids === 'ALL_OBJECTS' ? [] : $selected_ids), + static fn(array $record): bool => !isset($record['is_used']) ? true : !$record['is_used'] + ); + + return $this->onSubmit( + $url_builder + ->withParameter($row_id_token, $selected_ids) + ->withParameter($action_token, $this->getActionId()) + ->withParameter($action_type_token, self::SUBMIT_MODAL_ACTION), + $selected_records, + false + ); + } + + protected function showErrorMessage(string $message): void + { + $this->tpl->setOnScreenMessage(\ilGlobalTemplateInterface::MESSAGE_TYPE_FAILURE, $message, true); + } + + protected function showSuccessMessage(string $message): void + { + $this->tpl->setOnScreenMessage(\ilGlobalTemplateInterface::MESSAGE_TYPE_SUCCESS, $message, true); + } + + /** + * @param list $selected_records + */ + abstract protected function getModal( + URLBuilder $url_builder, + array $selected_records, + bool $all_records_selected + ): ?Modal; + + /** + * @param list $selected_records + */ + abstract protected function onSubmit( + URLBuilder $url_builder, + array $selected_records, + bool $all_records_selected + ): ?Modal; + + abstract protected function resolveRecords(array $selected_ids): array; +} diff --git a/components/ILIAS/BookingManager/src/Common/Table/TableActions.php b/components/ILIAS/BookingManager/src/Common/Table/TableActions.php new file mode 100644 index 000000000000..d8bfed52b78f --- /dev/null +++ b/components/ILIAS/BookingManager/src/Common/Table/TableActions.php @@ -0,0 +1,128 @@ + $actions + */ + public function __construct( + protected readonly ilCtrlInterface $ctrl, + protected readonly ilLanguage $lng, + protected readonly ilGlobalTemplateInterface $tpl, + protected readonly UIFactory $ui_factory, + protected readonly UIRenderer $ui_renderer, + protected readonly Refinery $refinery, + protected readonly HttpService $http_service, + protected readonly array $actions + ) { + } + + public function getEnabledActions( + URLBuilder $url_builder, + URLBuilderToken $row_id_token, + URLBuilderToken $action_token, + URLBuilderToken $action_type_token + ): array { + return array_filter( + array_map( + function (TableAction $action) use ( + $url_builder, + $row_id_token, + $action_token, + $action_type_token + ): ?Action { + if (!$action->isAvailable()) { + return null; + } + + return $action->getTableAction( + $url_builder, + $row_id_token, + $action_token, + $action_type_token + ); + }, + $this->actions + ) + ); + } + + public function execute( + URLBuilder $url_builder, + URLBuilderToken $row_id_token, + URLBuilderToken $action_token, + URLBuilderToken $action_type_token + ): ?Modal { + if (!$this->http_service->has($action_token->getName())) { + return null; + } + + $action_id = $this->http_service->resolveRowParameter($action_token->getName()); + + if ($action_id === null || !isset($this->actions[$action_id])) { + return null; + } + + $action = $this->actions[$action_id]; + $response = $action->onExecute( + $url_builder, + $row_id_token, + $action_token, + $action_type_token + ); + + if ($response instanceof Modal) { + $this->http_service->sendAsync( + $this->ui_renderer->renderAsync( + $response + ) + ); + } + + return null; + } + + public function onDataRow(DataRow $row, mixed $record): DataRow + { + return array_reduce( + array_keys($this->actions), + fn(DataRow $c, string $v): DataRow => $this->actions[$v]->allowActionForRecord($record) + ? $c + : $c->withDisabledAction($v), + $row + ); + } +} diff --git a/components/ILIAS/BookingManager/src/HttpService.php b/components/ILIAS/BookingManager/src/HttpService.php new file mode 100644 index 000000000000..b379b2bf04db --- /dev/null +++ b/components/ILIAS/BookingManager/src/HttpService.php @@ -0,0 +1,83 @@ +http->request(); + } + + public function getRefId(): int + { + return $this->get('ref_id', $this->refinery->kindlyTo()->int()); + } + + public function resolveRowParameter(string $key): string|int + { + return $this->get($key, $this->refinery->byTrying([ + $this->refinery->kindlyTo()->int(), + $this->refinery->kindlyTo()->string(), + $this->refinery->custom()->transformation(fn(array $v): string|int => $v[0]) + ])); + } + + public function resolveRowParameters(string $key): array|string + { + return $this->get($key, $this->refinery->custom()->transformation( + static fn(array|string $value): array|string => $value === 'ALL_OBJECTS' || $value[0] === 'ALL_OBJECTS' + ? 'ALL_OBJECTS' + : array_map('intval', $value) + )) ?? []; + } + + public function get(string $key, Transformation $t): mixed + { + + $wrapper = $this->http->wrapper(); + if ($wrapper->post()->has($key)) { + return $wrapper->post()->retrieve($key, $t); + } + if ($wrapper->query()->has($key)) { + return $wrapper->query()->retrieve($key, $t); + } + return null; + } + + /** + * @param Stream|string|mixed $response + */ + public function sendAsync(mixed $response): void + { + if (is_string($response)) { + $response = Streams::ofString($response); + } elseif (is_resource($response)) { + $response = Streams::ofResource($response); + } + + $this->http->saveResponse( + $this->http->response()->withBody($response) + ); + $this->http->sendResponse(); + $this->http->close(); + } + + public function has(string $key): bool + { + return $this->http->wrapper()->query()->has($key) || $this->http->wrapper()->post()->has($key); + } +} diff --git a/components/ILIAS/BookingManager/src/Participant/ParticipantRepository.php b/components/ILIAS/BookingManager/src/Participant/ParticipantRepository.php new file mode 100644 index 000000000000..b4d48236104a --- /dev/null +++ b/components/ILIAS/BookingManager/src/Participant/ParticipantRepository.php @@ -0,0 +1,34 @@ +database->manipulateF( + "DELETE booking_reservation FROM booking_reservation + INNER JOIN booking_object ON booking_object.booking_object_id = booking_reservation.object_id + WHERE booking_reservation.user_id = %s AND booking_object.pool_id = %s;", + [ilDBConstants::T_INTEGER, ilDBConstants::T_INTEGER], + [$user_id, $pool_id] + ); + + $this->database->manipulateF( + "DELETE FROM booking_member WHERE user_id = %s AND booking_pool_id = %s;", + [ilDBConstants::T_INTEGER, ilDBConstants::T_INTEGER], + [$user_id, $pool_id] + ); + + return true; + } + +} diff --git a/components/ILIAS/BookingManager/src/Participant/ParticipantTable.php b/components/ILIAS/BookingManager/src/Participant/ParticipantTable.php new file mode 100644 index 000000000000..3388b972626c --- /dev/null +++ b/components/ILIAS/BookingManager/src/Participant/ParticipantTable.php @@ -0,0 +1,224 @@ +, obj_count: int, object_ids: array} + */ + public function __construct( + private readonly UIFactory $ui_factory, + private readonly ilLanguage $lng, + private readonly TableActions $table_actions, + private readonly HttpService $http_service, + private readonly ilUIService $ui_service, + private readonly int $pool_id, + private readonly ServerRequestInterface $request + ) { + } + + /** + * @return array<\ILIAS\UI\Component\Component> + */ + public function getComponents(URLBuilder $url_builder): array + { + $filter = $this->getFilterComponent($url_builder->buildURI()->__toString()); + + $table = $this->ui_factory->table()->data( + $this, + $this->lng->txt('participants'), + $this->getColumns() + ) + ->withActions( + $this->table_actions->getEnabledActions(...$this->acquireParameters($url_builder)) + ) + ->withRequest($this->request) + ->withId(self::ID) + ->withFilter($this->ui_service->filter()->getData($filter)); + + return [ + $filter, + $table + ]; + } + + public function getTotalRowCount( + mixed $additional_viewcontrol_data, + mixed $filter_data, + mixed $additional_parameters + ): ?int { + $data = $this->loadRecords($filter_data); + return count($data); + } + + public function getRows( + DataRowBuilder $row_builder, + array $visible_column_ids, + Range $range, + Order $order, + mixed $additional_viewcontrol_data, + mixed $filter_data, + mixed $additional_parameters + ): Generator { + $data = $this->loadRecords($filter_data); + + $order_data = $order->get(); + if (!empty($order_data)) { + $order_field = array_keys($order_data)[0]; + $order_direction = $order_data[$order_field]; + + usort($data, function ($a, $b) use ($order_field, $order_direction) { + $a_val = $a[$order_field] ?? ''; + $b_val = $b[$order_field] ?? ''; + + $result = $a_val <=> $b_val; + return $order_direction === Order::ASC ? $result : -$result; + }); + } + + $offset = $range->getStart(); + $length = $range->getLength(); + $data = array_slice($data, $offset, $length); + + foreach ($data as $record) { + $bookable_items = implode(', ', $record['object_title'] ?? []); + + yield $this->table_actions->onDataRow( + $row_builder->buildDataRow( + (string) $record['user_id'], + [ + 'name' => $record['name'] ?? '', + 'bookable_item' => $bookable_items, + ] + ), + $record + ); + } + } + + /** + * @return array + */ + private function getColumns(): array + { + return [ + 'name' => $this->ui_factory->table()->column()->text($this->lng->txt('name')) + ->withIsSortable(true), + 'bookable_item' => $this->ui_factory->table()->column()->text($this->lng->txt('book_bobj')) + ->withIsSortable(false), + ]; + } + + private function loadRecords(?array $filter_data): array + { + $filter = []; + if (isset($filter_data['bookable_item_id']) && $filter_data['bookable_item_id'] !== '') { + $filter['object'] = (int) $filter_data['bookable_item_id']; + } + if (isset($filter_data['bookable_item_title']) && $filter_data['bookable_item_title'] !== '') { + $filter['title'] = (string) $filter_data['bookable_item_title']; + } + if (isset($filter_data['participant_id']) && $filter_data['participant_id'] !== '') { + $filter['user_id'] = (int) $filter_data['participant_id']; + } + + $filter_object = isset($filter['object']) ? (int) $filter['object'] : null; + if ($filter_object === -1) { + return array_filter( + \ilBookingParticipant::getList($this->pool_id, $filter), + static fn(array $item): bool => ($item['obj_count'] ?? 0) === 0 + ); + } + + return \ilBookingParticipant::getList($this->pool_id, $filter, $filter_object); + } + + private function getFilterComponent(string $action): FilterComponent + { + $field_factory = $this->ui_factory->input()->field(); + + // Bookable Item dropdown + $bookable_items = []; + foreach (\ilBookingObject::getList($this->pool_id) as $item) { + $bookable_items[$item['booking_object_id']] = $item['title']; + } + + $filter_inputs = [ + 'bookable_item_id' => $field_factory->select( + $this->lng->txt('book_bobj'), + array_replace(['-1' => $this->lng->txt('book_no_objects')], $bookable_items) + ), + 'bookable_item_title' => $field_factory->text( + $this->lng->txt('book_bobj') . ' ' . $this->lng->txt('title') . '/' . $this->lng->txt('description') + ), + 'participant_id' => $field_factory->select( + $this->lng->txt('book_participant'), + \ilBookingParticipant::getUserFilter($this->pool_id) + ), + ]; + + return $this->ui_service->filter()->standard( + 'participant_filter_' . $this->pool_id, + $action, + $filter_inputs, + array_fill(0, count($filter_inputs), true), + true, + true + ); + } + + /** + * @return array{URLBuilder, URLBuilderToken, URLBuilderToken, URLBuilderToken} + */ + protected function acquireParameters(URLBuilder $url_builder): array + { + return $url_builder->acquireParameters( + [self::ID], + self::ROW_ID_PARAMETER, + self::ACTION_PARAMETER, + self::ACTION_TYPE_PARAMETER + ); + } +} diff --git a/components/ILIAS/BookingManager/src/Participant/ParticipantTableBookForParticipantAction.php b/components/ILIAS/BookingManager/src/Participant/ParticipantTableBookForParticipantAction.php new file mode 100644 index 000000000000..3127326fb4f6 --- /dev/null +++ b/components/ILIAS/BookingManager/src/Participant/ParticipantTableBookForParticipantAction.php @@ -0,0 +1,112 @@ +, obj_count: int, object_ids: array} + * @implements TableAction + */ +class ParticipantTableBookForParticipantAction implements TableAction +{ + public const ACTION_ID = 'book_for_participant'; + + public function __construct( + private readonly UIFactory $ui_factory, + private readonly ilLanguage $lng, + private readonly AccessManager $access, + private readonly ilCtrlInterface $ctrl, + private readonly HttpService $http_service, + private readonly int $ref_id, + private readonly int $pool_id + ) { + } + + public function getActionId(): string + { + return self::ACTION_ID; + } + + public function isAvailable(): bool + { + return $this->access->canManageParticipants($this->ref_id); + } + + public function getTableAction( + URLBuilder $url_builder, + URLBuilderToken $row_id_token, + URLBuilderToken $action_token, + URLBuilderToken $action_type_token + ): Action { + return $this->ui_factory->table()->action()->single( + $this->lng->txt('book_assign_object'), + $url_builder + ->withParameter($action_token, self::ACTION_ID) + ->withParameter($action_type_token, 'book'), + $row_id_token + ); + } + + public function onExecute( + URLBuilder $url_builder, + URLBuilderToken $row_id_token, + URLBuilderToken $action_token, + URLBuilderToken $action_type_token + ): mixed { + $user_id = $this->http_service->resolveRowParameter($row_id_token->getName()); + + $this->ctrl->setParameterByClass( + \ilBookingParticipantGUI::class, + 'bkusr', + (string) $user_id + ); + $this->ctrl->redirectByClass( + \ilBookingParticipantGUI::class, + 'assignObjects' + ); + + return null; + } + + /** + * @param ParticipantRecord $record + */ + public function allowActionForRecord(mixed $record): bool + { + $obj_count = (int) ($record['obj_count'] ?? 0); + $total_objects = \ilBookingObject::getNumberOfObjectsForPool($this->pool_id); + return $obj_count < $total_objects; + } + + public function getSelectionErrorMessage(): ?string + { + return null; + } +} diff --git a/components/ILIAS/BookingManager/src/Participant/ParticipantTableDeleteAction.php b/components/ILIAS/BookingManager/src/Participant/ParticipantTableDeleteAction.php new file mode 100644 index 000000000000..b872914d8785 --- /dev/null +++ b/components/ILIAS/BookingManager/src/Participant/ParticipantTableDeleteAction.php @@ -0,0 +1,169 @@ +, obj_count: int, object_ids: array} + * @implements TableAction + */ +class ParticipantTableDeleteAction implements TableAction +{ + use TableActionModalTrait; + + public const ACTION_ID = 'delete'; + + public function __construct( + private readonly UIFactory $ui_factory, + private readonly ilLanguage $lng, + private readonly AccessManager $access, + private readonly ilGlobalTemplateInterface $tpl, + private readonly HttpService $http_service, + private readonly ParticipantRepository $participant_repository, + private readonly int $ref_id, + private readonly int $pool_id + ) { + } + + public function getActionId(): string + { + return self::ACTION_ID; + } + + public function isAvailable(): bool + { + return $this->access->canManageParticipants($this->ref_id); + } + + public function getTableAction( + URLBuilder $url_builder, + URLBuilderToken $row_id_token, + URLBuilderToken $action_token, + URLBuilderToken $action_type_token + ): Action { + return $this->ui_factory->table()->action()->standard( + $this->lng->txt('book_remove_participants'), + $url_builder + ->withParameter($action_token, self::ACTION_ID) + ->withParameter($action_type_token, self::SHOW_MODAL_ACTION), + $row_id_token + )->withAsync(); + } + + /** + * @param ParticipantRecord $record + */ + public function allowActionForRecord(mixed $record): bool + { + return true; + } + + public function getModal( + URLBuilder $url_builder, + array $selected_records, + bool $all_records_selected + ): ?Modal { + return $this->ui_factory->modal()->interruptive( + $this->lng->txt('confirm'), + $this->lng->txt('book_confirm_remove_participant'), + $url_builder->buildURI()->__toString() + )->withAffectedItems( + array_map( + fn(array $record): InterruptiveItem => $this->ui_factory->modal()->interruptiveItem()->standard( + (string) $record['user_id'], + (string) ($record['name'] ?? '') + ), + $selected_records + ) + )->withActionButtonLabel($this->lng->txt('remove')); + } + + public function onSubmit( + URLBuilder $url_builder, + array $selected_records, + bool $all_records_selected + ): ?Modal { + if (!$this->access->canManageParticipants($this->ref_id)) { + $this->tpl->setOnScreenMessage( + \ilGlobalTemplateInterface::MESSAGE_TYPE_FAILURE, + $this->lng->txt('no_permission'), + true + ); + return null; + } + + foreach ($selected_records as $record) { + $this->deleteParticipant((int) $record['user_id']); + } + + $this->tpl->setOnScreenMessage( + \ilGlobalTemplateInterface::MESSAGE_TYPE_SUCCESS, + $this->lng->txt('book_participant_removed'), + true + ); + + return null; + } + + public function getSelectionErrorMessage(): ?string + { + return $this->lng->txt('book_table_no_valid_selection'); + } + + protected function resolveRecords(array $selected_ids): array + { + $all_participants = \ilBookingParticipant::getList($this->pool_id); + + if ($selected_ids === []) { + return array_values($all_participants); + } + + $records = []; + foreach ($selected_ids as $user_id) { + $key = $this->pool_id . '_' . $user_id; + if (isset($all_participants[$key])) { + $records[] = $all_participants[$key]; + } + } + + return $records; + } + + private function deleteParticipant(int $user_id): void + { + $this->participant_repository->delete($user_id, $this->pool_id); + } +} diff --git a/components/ILIAS/BookingManager/src/Participant/ParticipantTableEditBookingAction.php b/components/ILIAS/BookingManager/src/Participant/ParticipantTableEditBookingAction.php new file mode 100644 index 000000000000..e17d04fae47b --- /dev/null +++ b/components/ILIAS/BookingManager/src/Participant/ParticipantTableEditBookingAction.php @@ -0,0 +1,136 @@ +, obj_count: int, object_ids: array} + * @implements TableAction + */ +class ParticipantTableEditBookingAction implements TableAction +{ + public const ACTION_ID = 'edit_booking'; + + public function __construct( + private readonly UIFactory $ui_factory, + private readonly ilLanguage $lng, + private readonly AccessManager $access, + private readonly ilCtrlInterface $ctrl, + private readonly HttpService $http_service, + private readonly int $ref_id, + private readonly int $pool_id + ) { + } + + public function getActionId(): string + { + return self::ACTION_ID; + } + + public function isAvailable(): bool + { + return $this->access->canManageParticipants($this->ref_id); + } + + public function getTableAction( + URLBuilder $url_builder, + URLBuilderToken $row_id_token, + URLBuilderToken $action_token, + URLBuilderToken $action_type_token + ): Action { + return $this->ui_factory->table()->action()->single( + $this->lng->txt('book_deassign'), + $url_builder + ->withParameter($action_token, self::ACTION_ID) + ->withParameter($action_type_token, 'edit'), + $row_id_token + ); + } + + public function onExecute( + URLBuilder $url_builder, + URLBuilderToken $row_id_token, + URLBuilderToken $action_token, + URLBuilderToken $action_type_token + ): mixed { + $user_id = $this->http_service->resolveRowParameter($row_id_token->getName()); + + $bp = new \ilObjBookingPool($this->pool_id, false); + $obj_count = $this->getObjectCountForUser((int) $user_id); + + if ($obj_count === 1 && $bp->getScheduleType() === \ilObjBookingPool::TYPE_NO_SCHEDULE) { + // Single object, no schedule - direct deassign + $object_ids = $this->getObjectIdsForUser((int) $user_id); + if (!empty($object_ids)) { + $this->ctrl->setParameterByClass('ilbookingreservationsgui', 'bkusr', (string) $user_id); + $this->ctrl->setParameterByClass('ilbookingreservationsgui', 'object_id', (string) $object_ids[0]); + $this->ctrl->setParameterByClass('ilbookingreservationsgui', 'part_view', \ilBookingParticipantGUI::PARTICIPANT_VIEW); + $this->ctrl->redirectByClass('ilbookingreservationsgui', 'rsvConfirmCancelUser'); + } + } else { + // Multiple objects or schedule - show log + $this->ctrl->setParameterByClass('ilbookingreservationsgui', 'user_id', (string) $user_id); + $this->ctrl->redirectByClass('ilbookingreservationsgui', 'log'); + } + + return null; + } + + /** + * @param ParticipantRecord $record + */ + public function allowActionForRecord(mixed $record): bool + { + $obj_count = (int) ($record['obj_count'] ?? 0); + return $obj_count > 0; + } + + public function getSelectionErrorMessage(): ?string + { + return null; + } + + private function getObjectCountForUser(int $user_id): int + { + $data = \ilBookingParticipant::getList($this->pool_id, ['user_id' => $user_id]); + $user_data = $data[$this->pool_id . '_' . $user_id] ?? null; + return (int) ($user_data['obj_count'] ?? 0); + } + + /** + * @return array + */ + private function getObjectIdsForUser(int $user_id): array + { + $data = \ilBookingParticipant::getList($this->pool_id, ['user_id' => $user_id]); + $user_data = $data[$this->pool_id . '_' . $user_id] ?? null; + return $user_data['object_ids'] ?? []; + } +} diff --git a/components/ILIAS/BookingManager/src/Schedule/ScheduleTable.php b/components/ILIAS/BookingManager/src/Schedule/ScheduleTable.php new file mode 100644 index 000000000000..0c0261a603dd --- /dev/null +++ b/components/ILIAS/BookingManager/src/Schedule/ScheduleTable.php @@ -0,0 +1,156 @@ + + */ + public function getComponents(URLBuilder $url_builder): array + { + return [ + $this->ui_factory->table()->data( + $this, + $this->lng->txt('book_schedules'), + $this->getColumns() + ) + ->withActions( + $this->table_actions->getEnabledActions(...$this->acquireParameters($url_builder)) + ) + ->withRequest($this->http_service->getRequest()) + ->withId(self::ID) + ]; + } + + public function getTotalRowCount( + mixed $additional_viewcontrol_data, + mixed $filter_data, + mixed $additional_parameters + ): ?int { + $data = $this->schedule_manager->getScheduleData(); + return count($data); + } + + public function getRows( + DataRowBuilder $row_builder, + array $visible_column_ids, + Range $range, + Order $order, + mixed $additional_viewcontrol_data, + mixed $filter_data, + mixed $additional_parameters + ): \Generator { + $data = $this->schedule_manager->getScheduleData(); + + // Apply sorting if needed + $order_data = $order->get(); + if (!empty($order_data)) { + $order_field = array_keys($order_data)[0]; + $order_direction = $order_data[$order_field]; + + usort($data, function ($a, $b) use ($order_field, $order_direction) { + $a_val = $a[$order_field] ?? ''; + $b_val = $b[$order_field] ?? ''; + + $result = $a_val <=> $b_val; + return $order_direction === Order::ASC ? $result : -$result; + }); + } + + // Apply range (pagination) + $offset = $range->getStart(); + $length = $range->getLength(); + $data = array_slice($data, $offset, $length); + + foreach ($data as $record) { + $is_used = (bool) ($record['is_used'] ?? false); + + yield $this->table_actions->onDataRow( + $row_builder->buildDataRow( + (string) $record['booking_schedule_id'], + [ + 'title' => $record['title'] ?? '', + 'is_used' => $is_used, + ] + ), + $record + ); + } + } + + /** + * @return array + */ + private function getColumns(): array + { + return [ + 'title' => $this->ui_factory->table()->column()->text($this->lng->txt('title')) + ->withIsSortable(true), + 'is_used' => $this->ui_factory->table()->column()->boolean( + $this->lng->txt('book_is_used'), + $this->lng->txt('yes'), + $this->lng->txt('no') + ) + ->withIsSortable(true), + ]; + } + + /** + * @return array{URLBuilder, URLBuilderToken, URLBuilderToken, URLBuilderToken} + */ + protected function acquireParameters(URLBuilder $url_builder): array + { + return $url_builder->acquireParameters( + [self::ID], + self::ROW_ID_PARAMETER, + self::ACTION_PARAMETER, + self::ACTION_TYPE_PARAMETER + ); + } +} diff --git a/components/ILIAS/BookingManager/src/Schedule/ScheduleTableDeleteAction.php b/components/ILIAS/BookingManager/src/Schedule/ScheduleTableDeleteAction.php new file mode 100644 index 000000000000..0d6f813fb348 --- /dev/null +++ b/components/ILIAS/BookingManager/src/Schedule/ScheduleTableDeleteAction.php @@ -0,0 +1,145 @@ + + */ +class ScheduleTableDeleteAction implements TableAction +{ + use TableActionModalTrait; + + public const string ACTION_ID = 'delete'; + + public function __construct( + private readonly UIFactory $ui_factory, + private readonly ilLanguage $lng, + private readonly AccessManager $access, + private readonly ilGlobalTemplateInterface $tpl, + private readonly HttpService $http_service, + private readonly ScheduleManager $schedule_manager, + private readonly int $ref_id, + ) { + } + + public function getActionId(): string + { + return self::ACTION_ID; + } + + public function isAvailable(): bool + { + return $this->access->canManageSettings($this->ref_id); + } + + public function getTableAction( + URLBuilder $url_builder, + URLBuilderToken $row_id_token, + URLBuilderToken $action_token, + URLBuilderToken $action_type_token + ): Action { + return $this->ui_factory->table()->action()->standard( + $this->lng->txt('delete'), + $url_builder + ->withParameter($action_token, self::ACTION_ID) + ->withParameter($action_type_token, self::SHOW_MODAL_ACTION), + $row_id_token + )->withAsync(); + } + + /** + * @param ScheduleRecord $record + */ + public function allowActionForRecord(mixed $record): bool + { + return !($record['is_used'] ?? true); + } + + public function getModal( + URLBuilder $url_builder, + array $selected_records, + bool $all_records_selected + ): ?Modal { + return $this->ui_factory->modal()->interruptive( + $this->lng->txt('confirm'), + $this->lng->txt('book_confirm_delete'), + $url_builder->buildURI()->__toString() + )->withAffectedItems( + array_map(fn(array $record) => $this->ui_factory->modal()->interruptiveItem()->standard( + (string) $record['booking_schedule_id'], + $record['title'] ?? '' + ), $selected_records) + )->withActionButtonLabel($this->lng->txt('delete')); + } + + public function onSubmit( + URLBuilder $url_builder, + array $selected_records, + bool $all_records_selected + ): ?Modal { + if (!$this->access->canManageSettings($this->ref_id)) { + $this->showErrorMessage($this->lng->txt('no_permission')); + return null; + } + + $selected_records = array_filter( + $selected_records, + static fn(array $record): bool => !($record['is_used'] ?? true) + ); + + foreach ($selected_records as $record) { + $schedule = new \ilBookingSchedule($record['booking_schedule_id']); + $schedule->delete(); + } + + $this->showSuccessMessage($this->lng->txt('book_schedule_deleted')); + return null; + } + + public function getSelectionErrorMessage(): ?string + { + return $this->lng->txt('no_valid_selection'); + } + + protected function resolveRecords(array $selected_ids): array + { + $schedules = $this->schedule_manager->getScheduleData(); + + if ($selected_ids === []) { + return $schedules; + } + + return array_filter(array_map(fn($id) => $schedules[$id] ?? null, $selected_ids)); + } +} diff --git a/components/ILIAS/BookingManager/src/Schedule/ScheduleTableEditAction.php b/components/ILIAS/BookingManager/src/Schedule/ScheduleTableEditAction.php new file mode 100644 index 000000000000..2ab298410419 --- /dev/null +++ b/components/ILIAS/BookingManager/src/Schedule/ScheduleTableEditAction.php @@ -0,0 +1,107 @@ + + */ +class ScheduleTableEditAction implements TableAction +{ + public const string ACTION_ID = 'edit'; + + public function __construct( + private readonly UIFactory $ui_factory, + private readonly ilLanguage $lng, + private readonly AccessManager $access, + private readonly ilCtrlInterface $ctrl, + private readonly HttpService $http_service, + private readonly int $ref_id + ) { + } + + public function getActionId(): string + { + return self::ACTION_ID; + } + + public function isAvailable(): bool + { + return $this->access->canManageSettings($this->ref_id); + } + + public function getTableAction( + URLBuilder $url_builder, + URLBuilderToken $row_id_token, + URLBuilderToken $action_token, + URLBuilderToken $action_type_token + ): Action { + return $this->ui_factory->table()->action()->single( + $this->lng->txt('edit'), + $url_builder + ->withParameter($action_token, self::ACTION_ID) + ->withParameter($action_type_token, 'edit'), // TODO: Check for constant. + $row_id_token + ); + } + + public function onExecute( + URLBuilder $url_builder, + URLBuilderToken $row_id_token, + URLBuilderToken $action_token, + URLBuilderToken $action_type_token + ): mixed { + $record_id = $this->http_service->resolveRowParameter($row_id_token->getName()); + + $this->ctrl->setParameterByClass( + \ilBookingScheduleGUI::class, + 'schedule_id', + $record_id + ); + $this->ctrl->redirectByClass( + \ilBookingScheduleGUI::class, + 'edit' + ); + } + + /** + * @param ScheduleRecord $record + */ + public function allowActionForRecord(mixed $record): bool + { + return true; + } + + public function getSelectionErrorMessage(): ?string + { + return null; + } +} diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 7d608730932d..6460db6456ba 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -2564,6 +2564,9 @@ book#:#book_confirm_booking_schedule_number_of_objects_info#:#Bitte wählen Sie book#:#book_confirm_cancel#:#Sind Sie sicher, dass Sie die folgenden Buchungen stornieren möchten? book#:#book_confirm_cancel_aggregation#:#Anzahl Stornierungen book#:#book_confirm_delete#:#Sind Sie sicher, dass Sie die folgenden Angebote löschen wollen? +book#:#book_confirm_delete_participant#:#Sind Sie sicher, dass Sie den folgenden Teilnehmer löschen möchten? Dies wird auch alle zugehörigen Buchungen löschen. +book#:#book_confirm_delete_participants#:#Sind Sie sicher, dass Sie die folgenden Teilnehmer löschen möchten? Dies wird auch alle zugehörigen Buchungen löschen. +book#:#book_confirm_remove_participant#:#Sind Sie sicher, dass Sie die folgende(n) Teilnehmer entfernen wollen? Dabei werden alle zugehörigen Buchungen gelöscht. book#:#book_copy#:#Buchungspool kopieren book#:#book_create_objects#:#Angebote erstellen book#:#book_deadline#:#Deadline @@ -2616,8 +2619,11 @@ book#:#book_objects_available#:#Verfügbare Angebote: %s book#:#book_open#:#Buchungspool öffnen book#:#book_overall_limit#:#Gesamtzahl der Buchungen pro Benutzer begrenzen book#:#book_overall_limit_warning#:#Sie haben die maximale Anzahl Buchungen erreicht. +book#:#book_participant#:#Teilnehmer book#:#book_participant_already_assigned#:#Mindestens eine Person war bereits zugewiesen. book#:#book_participant_assigned#:#Die Personen wurden hinzugefügt. +book#:#book_participant_deleted#:#Der ausgewählte Teilnehmer und alle zugehörigen Buchungen wurden erfolgreich gelöscht. +book#:#book_participant_removed#:#Der ausgewählte Teilnehmer wurde aus dem Buchungspool entfernt. book#:#book_period#:#Dauer book#:#book_pool_added#:#Ein Buchungspool wurde angelegt. book#:#book_pool_selection#:#Auswahl Buchungspool @@ -2643,6 +2649,8 @@ book#:#book_reminder_day#:#Versenden book#:#book_reminder_day_info#:#Sendet eine Liste eigener anstehender Buchungen und zusätzlich eine Gesamtliste an Personen mit dem Recht „Einstellungen bearbeiten“. Bitte beachten Sie: Um Benachrichtigungen zu erhalten, müssen die Personen diese über das Menü „Aktionen“ oben rechts im Buchungspool „Benachrichtigungen“ aktiviert haben. book#:#book_reminder_days#:#Tage vor der Buchung book#:#book_reminder_setting#:#Per Mail an eigene Buchung erinnern +book#:#book_remove_participant#:#Teilnehmer entfernen +book#:#book_remove_participants#:#Teilnehmer entfernen book#:#book_rerun_assignments#:#Zuweisungen erneut durchführen book#:#book_rerun_confirmation#:#Achtung. Die Zuweisung nach Präferenzen ist bereits erfolgt. In seltenen Fällen kann ein Abbruch des Prozesses zu keinen oder unvollständigen Buchungen führen und ein erneutes Starten des Prozesses notwendig machen. Bevor sie den automatischen Prozess erneut starten, müssen sie alle bestehenden Buchungen entfernen, um Mehrfachzuweisungen zu verhindern. book#:#book_reservation_available#:#%s verfügbar diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 1fcbe16b7213..e6d58773a08e 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -2565,6 +2565,9 @@ book#:#book_confirm_booking_schedule_number_of_objects_info#:#Please enter the n book#:#book_confirm_cancel#:#Are you sure you want to cancel the following booking(s)? book#:#book_confirm_cancel_aggregation#:#Number of Cancellations book#:#book_confirm_delete#:#Are you sure you want to delete the following items? +book#:#book_confirm_delete_participant#:#Are you sure you want to delete the following participant? This will also delete all related bookings. +book#:#book_confirm_delete_participants#:#Are you sure you want to delete the following participants? This will also delete all related bookings. +book#:#book_confirm_remove_participant#:#Are you sure you want to remove the following Participant(s)? This will delete all related Bookings. book#:#book_copy#:#Copy Booking Pool book#:#book_create_objects#:#Create Items book#:#book_deadline#:#Deadline @@ -2617,8 +2620,11 @@ book#:#book_objects_available#:#Items available %s book#:#book_open#:#Open Booking Pool book#:#book_overall_limit#:#Limit Total Number of Bookings per User book#:#book_overall_limit_warning#:#You have reached the maximum number of bookings allowed. +book#:#book_participant#:#Participant book#:#book_participant_already_assigned#:#One or more participants have already been added. book#:#book_participant_assigned#:#Participant(s) added. +book#:#book_participant_deleted#:#The selected participant and all related bookings have been deleted successfully. +book#:#book_participant_removed#:#The selected participant has been removed from the booking pool. book#:#book_period#:#Period book#:#book_pool_added#:#Booking pool successfully created. book#:#book_pool_selection#:#Booking Pool Selection @@ -2644,6 +2650,8 @@ book#:#book_reminder_day#:#Send Reminder book#:#book_reminder_day_info#:#Send list of own bookings to users and full list of all upcoming bookings to admins. Please note: in order to receive reminders, users need to have activated notifications via the ‘Actions’ menu at the top right-hand corner of the booking pool. book#:#book_reminder_days#:#day(s) before time-slot of booking book#:#book_reminder_setting#:#Reminder +book#:#book_remove_participant#:#Remove Participant +book#:#book_remove_participants#:#Remove Participant(s) book#:#book_rerun_assignments#:#Run Allocation Process book#:#book_rerun_confirmation#:#Attention. The process of allocating bookings according to preferences has already taken place. You may restart the process if any errors have occurred, e.g. no bookings have been saved. To prevent multiple allocations, please delete all existing bookings before restarting the process. book#:#book_reservation_available#:#%s available