ILIAS  trunk Revision v12.0_alpha-1613-gae4c99ebb18
BookableItemTableBookAction.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
27use ilDateTime;
28use ilLanguage;
33use ILIAS\Refinery\Factory as Refinery;
36use ILIAS\UI\Factory as UIFactory;
37use ILIAS\UI\Renderer as UIRenderer;
45use ilObjUser;
46use DateTimeZone;
47use DateTime;
48
50{
52
53 public const string ACTION_ID = 'book';
54 public const string ACTION_LABEL = 'book_book';
55
56 public function __construct(
57 private readonly UIFactory $ui_factory,
58 private readonly UIRenderer $ui_renderer,
59 private readonly ilGlobalTemplateInterface $tpl,
60 private readonly ilLanguage $lng,
61 private readonly HttpService $http,
62 private readonly Refinery $refinery,
63 private readonly ilCtrlInterface $ctrl,
64 private readonly AccessManager $access,
65 private readonly ilObjBookingPool $pool,
66 private readonly BookingProcessManager $process_manager,
67 private readonly ilObjUser $user,
68 private readonly int $ref_id,
69 private readonly int $booking_context_obj_id,
70 private readonly array $bookable_items,
71 ) {
72 }
73
74 public function getActionId(): string
75 {
76 return self::ACTION_ID;
77 }
78
79 public function getActionLabel(): string
80 {
81 return self::ACTION_LABEL;
82 }
83
84 public function isAvailable(): bool
85 {
86 $schedule_type = $this->pool->getScheduleType();
87
88 if ($schedule_type === ilObjBookingPool::TYPE_NO_SCHEDULE) {
89 return !$this->isUserBookingPoolLimitReached();
90 }
91
93 return false;
94 }
95
96 return $this->access->canManageOwnReservations($this->ref_id) || $this->access->canManageObjects($this->ref_id);
97 }
98
99 public function allowActionForRecord(mixed $record): bool
100 {
101 $schedule_type = $this->pool->getScheduleType();
102 if ($schedule_type === ilObjBookingPool::TYPE_NO_SCHEDULE) {
103 return !$this->isUserBookingPoolLimitReached() && !($record['has_user_active_booking'] ?? false);
104 }
105
106 return $record['is_available'] ?? false;
107 }
108
109 public function getTableAction(
110 URLBuilder $url_builder,
111 URLBuilderToken $row_id_token,
112 URLBuilderToken $action_token,
113 URLBuilderToken $action_type_token
114 ): Action {
115 return $this->ui_factory->table()->action()->standard(
116 $this->lng->txt($this->getActionLabel()),
117 $url_builder
118 ->withParameter($action_token, $this->getActionId())
119 ->withParameter($action_type_token, self::SHOW_MODAL_ACTION),
120 $row_id_token
121 )->withAsync();
122 }
123
124 protected function showModal(
125 URLBuilder $url_builder,
126 URLBuilderToken $row_id_token,
127 URLBuilderToken $action_token,
128 URLBuilderToken $action_type_token
129 ): void {
130 $selected_records = $this->http->resolveRowParameters($row_id_token->getName());
131 $all_records_selected = $selected_records === HttpService::ALL_OBJECTS;
132 if ($all_records_selected) {
133 $selected_records = array_keys($this->bookable_items);
134 }
135
136 $selected_records = $this->resolveBookingEntriesPayload($this->lng, $selected_records);
137 $selected_records['entries'] = array_values(array_filter(
138 $selected_records['entries'],
139 fn(array $entry): bool => $this->allowActionForRecord($entry)
140 ));
141
142 if ($this->pool->getScheduleType() === ilObjBookingPool::TYPE_NO_SCHEDULE) {
143 $remaining = $this->getRemainingBookingCapacity();
144 if ($remaining !== null && count($selected_records['entries']) > $remaining) {
145 $this->http->sendAsync(
146 $this->ui_renderer->renderAsync(
147 $this->buildBookModalInformative(
148 $this->lng->txt('book_overall_limit_would_be_exceeded')
149 )
150 )
151 );
152 return;
153 }
154 }
155
156 $this->http->sendAsync(
157 $this->ui_renderer->renderAsync(
158 $this->getModal(
159 $url_builder
161 $row_id_token,
162 $all_records_selected
164 : array_column($selected_records['entries'], 'row_id')
165 )
166 ->withParameter($action_token, $this->getActionId())
167 ->withParameter($action_type_token, self::SUBMIT_MODAL_ACTION),
168 $selected_records,
169 $all_records_selected
170 )
171 )
172 );
173 }
174
175 public function getModal(
176 URLBuilder $url_builder,
177 array $selected_records,
178 bool $all_records_selected
179 ): ?Modal {
180 $entries = $selected_records['entries'];
181 if ($entries === []) {
182 return $this->buildBookModalInformative($this->lng->txt('no_valid_selection'));
183 }
184
185 $grouped = [];
186 foreach ($entries as $entry) {
187 $grouped[$entry['booking_object_id']][] = $entry;
188 }
189 ksort($grouped);
190
191 $skipped_descriptions = $selected_records['skipped_descriptions'];
192 $content = null;
193 if ($skipped_descriptions !== []) {
194 $content = [
195 $this->ui_factory->messageBox()->confirmation(
196 $this->lng->txt('book_modal_warning_skipped_selections')
197 . $this->ui_renderer->render($this->ui_factory->listing()->unordered($skipped_descriptions))
198 )
199 ];
200 }
201
202 $form_components = [];
203 $field_factory = $this->ui_factory->input()->field();
204 foreach ($grouped as $object_id => $entries) {
205 $section_input_components = [];
206
207 foreach ($entries as $entry) {
208 if (!$entry['has_schedule']) {
209 continue;
210 }
211
212 $max_quantity = $entry['max_quantity'];
213
214 $section_input_components[$entry['row_id']] = $field_factory->numeric(
215 $this->formatBookModalSlotLabel($entry['slot_from'], $entry['slot_to']),
216 sprintf($this->lng->txt('book_objects_available'), $max_quantity)
217 )
218 ->withValue(1)
219 ->withAdditionalTransformation(
220 $this->refinery->logical()->parallel(
221 [
222 $this->refinery->int()->isGreaterThanOrEqual(1),
223 $this->refinery->int()->isLessThanOrEqual($max_quantity)
224 ]
225 )
226 )
227 ->withAdditionalOnLoadCode(
228 static fn(string $id): string
229 => "
230 var element = document.getElementById('{$id}').querySelector('div input');
231 element.min = 1;
232 element.max = {$max_quantity};
233 "
234 );
235 }
236
237 if ($this->pool->usesMessages()) {
238 $section_input_components['message'] = $field_factory->textarea(
239 $this->lng->txt('book_message'),
240 $this->lng->txt('book_message_info')
241 );
242 }
243
244 $form_components[$object_id] = $field_factory->section(
245 $section_input_components,
246 $entries[0]['title'],
247 $this->lng->txt('book_modal_enter_quantity_intro')
248 );
249 }
250
251 return $this->ui_factory->modal()->roundtrip(
252 $this->lng->txt('book_modal_booking_confirmation'),
253 $content,
254 $form_components,
255 $url_builder->buildURI()->__toString()
256 );
257 }
258
259 public function onSubmit(
260 URLBuilder $url_builder,
261 array $selected_records,
262 bool $all_records_selected
263 ): ?Modal {
264 if ($this->pool->getScheduleType() === ilObjBookingPool::TYPE_NO_SCHEDULE) {
265 $remaining = $this->getRemainingBookingCapacity();
266 if ($remaining !== null && count($selected_records) > $remaining) {
267 return $this->buildBookModalInformative(
268 $this->lng->txt('book_overall_limit_warning')
269 );
270 }
271 }
272
274 $modal = $this
275 ->getModal(
276 $url_builder,
277 [
278 'entries' => $selected_records,
279 'skipped_descriptions' => []
280 ],
281 $all_records_selected
282 );
283 $modal = $modal->withRequest($this->http->getRequest());
284
286 $data = $modal->getData();
287 if ($data === null) {
288 return $modal->withOnLoad($modal->getShowSignal());
289 }
290
291 $booked_total = 0;
292 $unavailable = [];
293
294 foreach ($data as $object_id => $section) {
295 $message = $section['message'] ?? '';
296 unset($section['message']);
297
298 $bookable_item = new ilBookingObject($object_id);
299 if ($section === [] && !$bookable_item->getScheduleId()) {
300 $section = [$object_id => 1];
301 }
302
303 foreach ($section as $row_id => $amount) {
304 $from_to = $bookable_item->getScheduleId() ? explode('_', $row_id) : [];
305
306 $booked = $this->process_manager->bookAvailableObjects(
307 $bookable_item->getId(),
308 $this->user->getId(),
309 $this->user->getId(),
310 $this->booking_context_obj_id,
311 (int) ($from_to[1] ?? 0),
312 (int) ($from_to[2] ?? 0),
313 0, // TODO
314 $amount,
315 null, // TODO
316 $message
317 );
318
319 if ($booked !== []) {
320 $booked_total += count($booked);
321 continue;
322 }
323
324 $unavailable[] = $row_id;
325 }
326 }
327
328 if ($unavailable !== []) {
329 $this->tpl->setOnScreenMessage(
331 $this->lng->txt('book_some_reservations_unavailable'),
332 true
333 );
334 }
335
336 if ($booked_total === 0 && $unavailable === []) {
337 $this->tpl->setOnScreenMessage(
339 $this->lng->txt('book_reservation_failed'),
340 true
341 );
342 }
343
344 if ($booked_total > 0) {
345 $this->tpl->setOnScreenMessage(
347 $this->lng->txt('book_reservation_confirmed'),
348 true
349 );
350 }
351
352 $this->ctrl->redirectByClass(ilBookingObjectGUI::class, 'render');
353 return null;
354 }
355
360 protected function resolveRecords(?array $selected_ids = null): array
361 {
362 return $this->resolveBookingEntriesPayload(
363 $this->lng,
364 $selected_ids ?? array_keys($this->bookable_items)
365 )['entries'];
366 }
367
368 private function buildBookModalInformative(string $message): Modal
369 {
370 return $this->ui_factory->modal()->roundtrip(
371 $this->lng->txt('book_modal_booking_confirmation'),
372 [$this->ui_factory->messageBox()->failure($message)]
373 )->withAdditionalOnLoadCode(static fn(string $id): string => "il.repository.ui.initModal('$id');");
374 }
375
376 private function resolveBookingEntriesPayload(ilLanguage $lng, string|array $row_ids): array
377 {
378 if (!is_array($row_ids)) {
379 $row_ids = $row_ids === HttpService::ALL_OBJECTS ? array_keys($this->bookable_items) : [];
380 }
381
382 $entries = [];
383 $skipped_descriptions = [];
384
385 foreach ($row_ids as $row_id) {
386 $row_key = (string) $row_id;
387 $record = $this->bookable_items[$row_key] ?? null;
388 if ($record === null) {
389 $skipped_descriptions[] = sprintf($lng->txt('book_modal_skipped_unknown_item'), $row_key);
390 continue;
391 }
392
393 $availability = (int) ($record['available'] ?? 0);
394 $has_schedule = ($record['schedule_id'] ?? 0) > 0;
395 $slot_from = (int) ($record['slot_from'] ?? 0);
396 $slot_to = (int) ($record['slot_to'] ?? 0);
397
398 if ($availability <= 0) {
399 $skipped_descriptions[] = $has_schedule
400 ? sprintf(
401 '%s — %s',
402 $record['title'],
403 $this->formatBookModalSlotLabel($slot_from, $slot_to)
404 )
405 : $record['title'];
406 continue;
407 }
408
409 if (!$has_schedule && ($record['has_user_active_booking'] ?? false)) {
410 $skipped_descriptions[] = $record['title'];
411 continue;
412 }
413
414 $entries[] = [
415 'row_id' => $row_key,
416 'booking_object_id' => (int) $record['booking_object_id'],
417 'title' => (string) $record['title'],
418 'has_schedule' => $has_schedule,
419 'has_user_booking' => (bool) ($record['has_user_booking'] ?? false),
420 'slot_from' => $slot_from,
421 'slot_to' => $slot_to,
422 'max_quantity' => $availability,
423 'is_available' => true,
424 ];
425 }
426
427 return [
428 'entries' => $entries,
429 'skipped_descriptions' => $skipped_descriptions,
430 ];
431 }
432
433 private function formatBookModalSlotLabel(int $slot_from, int $slot_to): string
434 {
435 $this->lng->loadLanguageModule('dateplaner');
436
438 new ilDateTime($slot_from, IL_CAL_UNIX),
439 new ilDateTime($slot_to, IL_CAL_UNIX)
440 );
441 }
442
443 private function getRemainingBookingCapacity(): ?int
444 {
445 $limit = $this->pool->getOverallLimit();
446 if ($limit === null || $limit === 0) {
447 return null;
448 }
449
451 $this->user->getId(),
452 $this->pool->getId()
453 );
454
455 return max(0, $limit - $current);
456 }
457
458 private function isUserBookingPoolLimitReached(): bool
459 {
460 return $this->getRemainingBookingCapacity() === 0;
461 }
462}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
__construct(private readonly UIFactory $ui_factory, private readonly UIRenderer $ui_renderer, private readonly ilGlobalTemplateInterface $tpl, private readonly ilLanguage $lng, private readonly HttpService $http, private readonly Refinery $refinery, private readonly ilCtrlInterface $ctrl, private readonly AccessManager $access, private readonly ilObjBookingPool $pool, private readonly BookingProcessManager $process_manager, private readonly ilObjUser $user, private readonly int $ref_id, private readonly int $booking_context_obj_id, private readonly array $bookable_items,)
getModal(URLBuilder $url_builder, array $selected_records, bool $all_records_selected)
showModal(URLBuilder $url_builder, URLBuilderToken $row_id_token, URLBuilderToken $action_token, URLBuilderToken $action_type_token)
getTableAction(URLBuilder $url_builder, URLBuilderToken $row_id_token, URLBuilderToken $action_token, URLBuilderToken $action_type_token)
Builds a Color from either hex- or rgb values.
Definition: Factory.php:31
Builds data types.
Definition: Factory.php:36
buildURI()
Get a URI representation of the full URL including query string and fragment/hash.
Definition: URLBuilder.php:214
withParameter(URLBuilderToken $token, string|array $value)
Change an acquired parameter's value if the supplied token is valid.
Definition: URLBuilder.php:166
const IL_CAL_UNIX
@ilCtrl_Calls ilBookingObjectGUI: ilPropertyFormGUI, ilBookingProcessWithScheduleGUI,...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static isBookingPoolLimitReachedByUser(int $a_user_id, int $a_pool_id)
Class for date presentation.
static formatPeriod(ilDateTime $start, ilDateTime $end, bool $a_skip_starting_day=false, ?ilObjUser $user=null)
Format a period of two dates Shows: 14.
@classDescription Date and time handling
language handling
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
User class.
return['delivery_method'=> 'php',]
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
withAdditionalOnLoadCode(Closure $binder)
Add some onload-code to the component instead of replacing the existing one.
This describes commonalities between the different modals.
Definition: Modal.php:35
An entity that renders components to a string output.
Definition: Renderer.php:31
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$ref_id
Definition: ltiauth.php:66
trait TableActionModalTrait
@template RecordType
onSubmit(URLBuilder $url_builder, array $selected_records, bool $all_records_selected)
static http()
Fetches the global http state from ILIAS.
global $lng
Definition: privfeed.php:26
if(!file_exists('../ilias.ini.php'))