ILIAS  trunk Revision v13.0_alpha-52-g21c366bbdd4
BookableItemTableBookAction.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
29use ilDateTime;
30use ilLanguage;
35use ILIAS\Refinery\Factory as Refinery;
38use ILIAS\UI\Factory as UIFactory;
39use ILIAS\UI\Renderer as UIRenderer;
47use ilObjUser;
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 $booking_ids = [];
294
295 foreach ($data as $object_id => $section) {
296 $message = $section['message'] ?? '';
297 unset($section['message']);
298
299 $bookable_item = new ilBookingObject($object_id);
300 if ($section === [] && !$bookable_item->getScheduleId()) {
301 $section = [$object_id => 1];
302 }
303
304 foreach ($section as $row_id => $amount) {
305 $from_to = $bookable_item->getScheduleId() ? explode('_', $row_id) : [];
306
307 $booked = $this->process_manager->bookAvailableObjects(
308 $bookable_item->getId(),
309 $this->user->getId(),
310 $this->user->getId(),
311 $this->booking_context_obj_id,
312 (int) ($from_to[1] ?? 0),
313 (int) ($from_to[2] ?? 0),
314 0, // TODO
315 $amount,
316 null, // TODO
318 );
319
320 if ($booked !== []) {
321 $booked_total += count($booked);
322 foreach ($booked as $booking_id) {
323 $booking_ids[] = $booking_id;
324 }
325 continue;
326 }
327
328 $unavailable[] = $row_id;
329 }
330 }
331
332 if ($unavailable !== []) {
333 $this->tpl->setOnScreenMessage(
335 $this->lng->txt('book_some_reservations_unavailable'),
336 true
337 );
338 }
339
340 if ($booked_total === 0 && $unavailable === []) {
341 $this->tpl->setOnScreenMessage(
343 $this->lng->txt('book_reservation_failed'),
344 true
345 );
346 }
347
348 if ($booked_total > 0) {
349 $this->tpl->setOnScreenMessage(
351 $this->lng->txt('book_reservation_confirmed'),
352 true
353 );
354
355 $booking_process_gui_class = $this->pool->getScheduleType() === ilObjBookingPool::TYPE_FIX_SCHEDULE
356 ? ilBookingProcessWithScheduleGUI::class
357 : ilBookingProcessWithoutScheduleGUI::class;
358
359 $this->ctrl->setParameterByClass($booking_process_gui_class, 'rsv_ids', implode(';', array_unique($booking_ids)));
360 $this->ctrl->setParameterByClass($booking_process_gui_class, 'returnCmd', 'render');
361 $this->ctrl->redirectByClass($booking_process_gui_class, 'displayPostInfo');
362 return null;
363 }
364
365 $this->ctrl->redirectByClass(ilBookingObjectGUI::class, 'render');
366 return null;
367 }
368
373 protected function resolveRecords(?array $selected_ids = null): array
374 {
375 return $this->resolveBookingEntriesPayload(
376 $this->lng,
377 $selected_ids ?? array_keys($this->bookable_items)
378 )['entries'];
379 }
380
381 private function buildBookModalInformative(string $message): Modal
382 {
383 return $this->ui_factory->modal()->roundtrip(
384 $this->lng->txt('book_modal_booking_confirmation'),
385 [$this->ui_factory->messageBox()->failure($message)]
386 )->withAdditionalOnLoadCode(static fn(string $id): string => "il.repository.ui.initModal('$id');");
387 }
388
389 private function resolveBookingEntriesPayload(ilLanguage $lng, string|array $row_ids): array
390 {
391 if (!is_array($row_ids)) {
392 $row_ids = $row_ids === HttpService::ALL_OBJECTS ? array_keys($this->bookable_items) : [];
393 }
394
395 $entries = [];
396 $skipped_descriptions = [];
397
398 foreach ($row_ids as $row_id) {
399 $row_key = (string) $row_id;
400 $record = $this->bookable_items[$row_key] ?? null;
401 if ($record === null) {
402 $skipped_descriptions[] = sprintf($lng->txt('book_modal_skipped_unknown_item'), $row_key);
403 continue;
404 }
405
406 $availability = (int) ($record['available'] ?? 0);
407 $has_schedule = ($record['schedule_id'] ?? 0) > 0;
408 $slot_from = (int) ($record['slot_from'] ?? 0);
409 $slot_to = (int) ($record['slot_to'] ?? 0);
410
411 if ($availability <= 0) {
412 $skipped_descriptions[] = $has_schedule
413 ? sprintf(
414 '%s — %s',
415 $record['title'],
416 $this->formatBookModalSlotLabel($slot_from, $slot_to)
417 )
418 : $record['title'];
419 continue;
420 }
421
422 if (!$has_schedule && ($record['has_user_active_booking'] ?? false)) {
423 $skipped_descriptions[] = $record['title'];
424 continue;
425 }
426
427 $entries[] = [
428 'row_id' => $row_key,
429 'booking_object_id' => (int) $record['booking_object_id'],
430 'title' => (string) $record['title'],
431 'has_schedule' => $has_schedule,
432 'has_user_booking' => (bool) ($record['has_user_booking'] ?? false),
433 'slot_from' => $slot_from,
434 'slot_to' => $slot_to,
435 'max_quantity' => $availability,
436 'is_available' => true,
437 ];
438 }
439
440 return [
441 'entries' => $entries,
442 'skipped_descriptions' => $skipped_descriptions,
443 ];
444 }
445
446 private function formatBookModalSlotLabel(int $slot_from, int $slot_to): string
447 {
448 $this->lng->loadLanguageModule('dateplaner');
449
451 new ilDateTime($slot_from, IL_CAL_UNIX),
452 new ilDateTime($slot_to, IL_CAL_UNIX)
453 );
454 }
455
456 private function getRemainingBookingCapacity(): ?int
457 {
458 $limit = $this->pool->getOverallLimit();
459 if ($limit === null || $limit === 0) {
460 return null;
461 }
462
464 $this->user->getId(),
465 $this->pool->getId()
466 );
467
468 return max(0, $limit - $current);
469 }
470
471 private function isUserBookingPoolLimitReached(): bool
472 {
473 return $this->getRemainingBookingCapacity() === 0;
474 }
475}
$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...
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['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...
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...
trait TableActionModalTrait
@template RecordType
onSubmit(URLBuilder $url_builder, array $selected_records, bool $all_records_selected)
static http()
Fetches the global http state from ILIAS.
readonly ilGlobalTemplateInterface $tpl
allowActionForRecord(mixed $record)
global $lng
Definition: privfeed.php:26
if(!file_exists('../ilias.ini.php'))
$data
Definition: storeScorm.php:45