PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.16
VikBooking Hotel Booking Engine & PMS v1.8.16
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
vikbooking / site / layouts / taskmanager / taskdetails.php

taskdetails.php in VikBooking Hotel Booking Engine & PMS 1.8.16, at site/layouts/taskmanager/taskdetails.php

555 lines 23.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2025 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 /**
15 * Obtain vars from arguments received in the layout file.
16 *
17 * @var string $tool The operator tool identifier calling this layout file.
18 * @var array $operator The operator record accessing the tool.
19 * @var object $permissions The operator-tool permissions registry.
20 * @var string $tool_uri The base URI for rendering this tool.
21 * @var array $data The data for rendering the tasks of a given month within a calendar.
22 */
23 extract($displayData);
24
25 if (!$operator || empty($operator['id'])) {
26 throw new Exception('Missing operator details', 400);
27 }
28
29 // get task details
30 $taskId = $data['filters']['task_id'] ?? 0;
31
32 if (empty($taskId)) {
33 throw new Exception('Missing task details', 400);
34 }
35
36 // get the current operator permissions
37 $accept_tasks = (bool) ($permissions ? $permissions->get('accept_tasks', 0) : 0);
38
39 // get a registry of the requested task record
40 $taskRegistry = VBOTaskTaskregistry::getRecordInstance((int) $taskId);
41
42 if (!$taskRegistry->getID()) {
43 // task record not found
44 throw new Exception('Cannot find task details', 404);
45 }
46
47 // get the task assignee IDs
48 $assingeeIds = $taskRegistry->getAssigneeIds();
49
50 if ((!$assingeeIds && !$accept_tasks) || ($assingeeIds && !in_array($operator['id'], $assingeeIds))) {
51 // task record not accessible by the current operator
52 throw new Exception('Cannot access task details', 403);
53 }
54
55 // get the current task area object wrapper
56 $taskArea = VBOTaskArea::getRecordInstance($taskRegistry->getAreaID());
57
58 if ($taskArea->isPrivate()) {
59 // the area of this task seems to be private
60 throw new Exception('Cannot access this area', 403);
61 }
62
63 // inject current area in task registry wrapper
64 $taskRegistry->setArea($taskArea);
65
66 // access data for backward navigation
67 $calendar_back_type = $data['filters']['calendar_back_type'] ?? 'month';
68 $calendar_back_type = in_array($calendar_back_type, ['month', 'day']) ? $calendar_back_type : 'month';
69
70 // access the task manager object
71 $taskManager = VBOFactory::getTaskManager();
72
73 // load task assignees
74 $assignees = $taskRegistry->getAssigneeDetails();
75
76 // load task tags
77 $tags = $taskRegistry->getTags() ? $taskRegistry->getTagRecords() : [];
78
79 // get task status object
80 $status = $taskManager->statusTypeExists($taskRegistry->getStatus()) ? $taskManager->getStatusTypeInstance($taskRegistry->getStatus()) : null;
81
82 // load all the available status types
83 $statusTypes = [];
84
85 foreach ($taskManager->getStatusGroupElements($taskArea->getStatuses()) as $groupId => $group) {
86 // push group button
87 $statusTypes[] = [
88 'id' => null,
89 'text' => $group['text'],
90 ];
91
92 // iterate over the statuses of this group
93 foreach ($group['elements'] as $statusType) {
94 // push status button
95 $statusTypes[] = [
96 'id' => $statusType['id'],
97 'text' => $statusType['text'],
98 'color' => $statusType['color'],
99 ];
100 }
101 }
102
103 $roomInfo = null;
104
105 ?>
106
107 <div class="vbo-tm-calendar-wrap">
108
109 <div class="vbo-tm-task-details" data-task-id="<?php echo (int) $taskRegistry->getID(); ?>">
110
111 <div class="vbo-tm-task-head">
112 <div class="vbo-tm-calendar-head">
113 <div class="vbo-tm-calendar-info">
114 <a href="javascript:void(0)" class="vbo-tm-calendar-day-back" data-month="<?php echo $data['filters']['calendar_month'] ?? ''; ?>" data-day="<?php echo $data['filters']['calendar_day'] ?? ''; ?>">
115 <?php echo VikBookingIcons::e('chevron-left'); ?>
116 </a>
117 <h4 class="vbo-tm-task-title"><?php echo $taskRegistry->getTitle(); ?></h4>
118 </div>
119 </div>
120
121 <div class="vbo-tm-task-status-snapshot">
122
123 <?php if ($status): ?>
124 <?php echo JHtml::fetch('vbohtml.taskmanager.status', $status); ?>
125 <?php endif; ?>
126
127 <?php if ($dueDate = $taskRegistry->getDueDate()): ?>
128 <div class="vbo-tm-task-due date">
129 <?php VikBookingIcons::e('calendar-day'); ?>
130 <span><?php echo JHtml::fetch('date', $dueDate, 'd F Y'); ?></span>
131 </div>
132 <div class="vbo-tm-task-due time">
133 <?php VikBookingIcons::e('stopwatch'); ?>
134 <?php if ($assignees): ?>
135 <input type="time" class="task-time-picker" value="<?php echo JHtml::fetch('date', $dueDate, 'H:i'); ?>" />
136 <?php else: ?>
137 <span><?php echo JHtml::fetch('date', $dueDate, 'H:i'); ?></span>
138 <?php endif; ?>
139 </div>
140 <?php endif; ?>
141
142 </div>
143 </div>
144
145 <div class="vbo-tm-task-body">
146
147 <div class="vbo-tm-task-info">
148
149 <div class="vbo-tm-task-summary">
150
151 <div class="vbo-tm-task-summary-block vbo-tm-task-assignees">
152 <div class="vbo-tm-task-summary-lbl">
153 <span><?php echo JText::translate('VBO_ASSIGNEES'); ?></span>
154 </div>
155 <div class="vbo-tm-task-summary-cont">
156 <?php
157 foreach ($assignees as $operator) {
158 ?>
159 <span class="vbo-tm-list-area-task-assignee vbo-tm-task-assignee">
160 <span class="vbo-tm-list-area-task-assignee-avatar vbo-tm-task-assignee-avatar" title="<?php echo JHtml::fetch('esc_attr', $operator['name']); ?>">
161 <?php
162 if (!empty($operator['img_uri'])) {
163 ?>
164 <img src="<?php echo $operator['img_uri']; ?>" alt="<?php echo JHtml::fetch('esc_attr', $operator['initials']); ?>" decoding="async" loading="lazy" />
165 <?php
166 } else {
167 ?>
168 <span><?php echo $operator['initials']; ?></span>
169 <?php
170 }
171 ?>
172 </span>
173 </span>
174 <?php
175 }
176
177 if (!$assignees) {
178 ?>
179 <span class="vbo-tm-task-no-assignees"><?php echo JText::translate('VBO_TASK_NO_ASSIGNEES'); ?></span>
180 <?php
181 }
182 ?>
183 </div>
184 </div>
185
186 <?php
187 if ($taskRegistry->getListingId()) {
188 $roomInfo = VikBooking::getRoomInfo($taskRegistry->getListingId(), $columns = ['name', 'params'], $no_cache = true);
189 $roomInfo['params'] = json_decode($roomInfo['params']);
190 $geo = $roomInfo['params']->geo ?? null;
191 ?>
192 <div class="vbo-tm-task-summary-block vbo-tm-task-listing-info">
193 <div class="vbo-tm-task-summary-lbl">
194 <span><?php echo JText::translate('VBO_LISTING'); ?></span>
195 </div>
196 <div class="vbo-tm-task-summary-cont">
197 <?php if (!empty($geo->enabled) && !empty($geo->latitude) && !empty($geo->longitude)): ?>
198 <a href="javascript:void(0)" class="listing-name open-map"><?php VikBookingIcons::e('home'); ?><?php echo $roomInfo['name']; ?></a>
199 <?php else: ?>
200 <span class="listing-name"><?php VikBookingIcons::e('home'); ?> <?php echo $roomInfo['name']; ?></span>
201 <?php endif; ?>
202 </div>
203 </div>
204 <?php
205 }
206
207 $order = null;
208
209 if ($taskRegistry->getBookingId() && $bookingElement = $taskRegistry->buildBookingElement()) {
210 if (!empty($bookingElement['img'])) {
211 $img = '<img src="' . $bookingElement['img'] . '" class="vbo-booking-badge-avatar" decoding="async" loading="lazy" />';
212 } else {
213 $img = '<span class="vbo-booking-badge-avatar"><i class="' . VikBookingIcons::i($bookingElement['icon_class'] ?? 'hotel') . '"></i></span>';
214 }
215
216 // fetch booking details
217 $order = VikBooking::getBookingInfoFromID($taskRegistry->getBookingId());
218 $order['rooms'] = VikBooking::loadOrdersRoomsData($taskRegistry->getBookingId());
219
220 $bookingText = $bookingElement['text'] . ' #' . $bookingElement['id'];
221 ?>
222 <div class="vbo-tm-task-summary-block vbo-tm-task-booking-info">
223 <div class="vbo-tm-task-summary-lbl">
224 <span><?php echo JText::translate('VBORDNOL'); ?></span>
225 </div>
226 <div class="vbo-tm-task-summary-cont">
227 <?php echo $img; ?>
228 <a href="javascript:void(0)" class="guest-name open-reservation"><?php echo $bookingText; ?></span></a>
229 </div>
230 </div>
231 <?php
232 }
233 ?>
234
235 </div>
236
237 <?php if ($tags): ?>
238 <div class="vbo-tm-task-tags">
239 <?php
240 foreach ($tags as $tag) {
241 $tag = (array) $tag;
242 ?>
243 <span class="vbo-tm-task-tag vbo-tm-color <?php echo $tag['color']; ?>"><?php echo $tag['name']; ?></span>
244 <?php
245 }
246 ?>
247 </div>
248 <?php endif; ?>
249
250 <?php if ($status && ($statusDisplay = $status->display($taskRegistry))): ?>
251 <div class="vbo-tm-task-status-notes"><?php echo $statusDisplay; ?></div>
252 <?php endif; ?>
253
254 <?php if ($notes = $taskRegistry->getNotes()): ?>
255 <div class="vbo-tm-task-notes vbo-hideable-pane"><?php echo $notes; ?></div>
256 <?php endif; ?>
257
258 <div class="vbo-tm-task-listing-map vbo-hideable-pane" style="display: none;">
259
260 </div>
261
262 <?php if ($order): ?>
263 <div class="vbo-tm-task-reservation-details vbo-hideable-pane" style="display: none;">
264
265 <div class="task-res-boxes">
266 <div class="task-res-box checkin">
267 <div class="box-icon"><?php VikBookingIcons::e('plane-arrival'); ?></div>
268 <div class="box-label"><?php echo JText::translate('VBPICKUP'); ?></div>
269 <div class="box-value"><?php echo JHtml::fetch('date', $order['checkin'], 'd F Y H:i', date_default_timezone_get()); ?></div>
270 </div>
271
272 <div class="task-res-box checkout">
273 <div class="box-icon"><?php VikBookingIcons::e('plane-departure'); ?></div>
274 <div class="box-label"><?php echo JText::translate('VBRETURN'); ?></div>
275 <div class="box-value"><?php echo JHtml::fetch('date', $order['checkout'], 'd F Y H:i', date_default_timezone_get()); ?></div>
276 </div>
277 </div>
278
279 <?php if ($order['rooms']):
280 // take the details of the room matching the selected index
281 $orderRoomInfo = array_filter($order['rooms'], fn($room) => $room['roomindex'] == $taskRegistry->get('room_index'));
282 $orderRoomInfo = $orderRoomInfo ? reset($orderRoomInfo) : $order['rooms'][0];
283
284 // obtain room index name
285 $roomIndex = $roomInfo['params']->features->{$taskRegistry->get('room_index')} ?? null;
286 ?>
287 <div class="task-res-boxes">
288 <div class="task-res-box adults">
289 <div class="box-icon"><?php VikBookingIcons::e('male'); ?><?php VikBookingIcons::e('female'); ?></div>
290 <div class="box-label"><?php echo JText::translate('VBFORMADULTS'); ?></div>
291 <div class="box-value"><?php echo $orderRoomInfo['adults']; ?></div>
292 </div>
293
294 <?php if (!empty($orderRoomInfo['children'])): ?>
295 <div class="task-res-box children">
296 <div class="box-icon"><?php VikBookingIcons::e('child'); ?></div>
297 <div class="box-label"><?php echo JText::translate('VBFORMCHILDREN'); ?></div>
298 <div class="box-value"><?php echo $orderRoomInfo['children']; ?></div>
299 </div>
300 <?php endif; ?>
301
302 <?php if (!empty($orderRoomInfo['pets'])): ?>
303 <div class="task-res-box pets">
304 <div class="box-icon"><?php VikBookingIcons::e('dog'); ?></div>
305 <div class="box-label"><?php echo JText::translate('VBO_PETS'); ?></div>
306 <div class="box-value"><?php echo $orderRoomInfo['pets']; ?></div>
307 </div>
308 <?php endif; ?>
309
310 <?php
311 // display room number here only in case both the children and pets are empty
312 if ($roomIndex && empty($orderRoomInfo['children']) && empty($orderRoomInfo['pets'])): ?>
313 <div class="task-res-box roomnr">
314 <div class="box-icon"><?php VikBookingIcons::e('hashtag'); ?></div>
315 <div class="box-label"><?php echo JText::translate(key($roomIndex)); ?></div>
316 <div class="box-value"><?php echo reset($roomIndex); ?></div>
317 </div>
318 <?php endif; ?>
319 </div>
320
321 <?php
322 // display room number on a new box only in case the children or the pets are not empty
323 if ($roomIndex && (!empty($orderRoomInfo['children']) || !empty($orderRoomInfo['pets']))): ?>
324 <div class="task-res-boxes">
325 <div class="task-res-box roomnr">
326 <div class="box-icon"><?php VikBookingIcons::e('hashtag'); ?></div>
327 <div class="box-label"><?php echo JText::translate(key($roomIndex)); ?></div>
328 <div class="box-value"><?php echo reset($roomIndex); ?></div>
329 </div>
330 </div>
331 <?php endif; ?>
332 <?php endif; ?>
333 </div>
334 <?php endif; ?>
335
336 </div>
337
338 <div class="vbo-tm-task-chat">
339 <?php
340 // get the chat mediator
341 $chat = VBOFactory::getChatMediator();
342 // build chat for the given context
343 $chat_context = $chat->createContext('task', $taskRegistry->getID());
344 // display the chat for the current task context
345 echo $chat->render($chat_context, [
346 'assets' => false,
347 ]);
348 ?>
349 </div>
350
351 </div>
352
353 </div>
354
355 </div>
356
357 <script type="text/javascript">
358 VBOCore.DOMLoaded(() => {
359 // register click event on calendar backward navigation
360 document.querySelectorAll('.vbo-tm-calendar-day-back').forEach((backBtn) => {
361 backBtn.addEventListener('click', () => {
362 let navMonth = backBtn.getAttribute('data-month');
363 let navDay = backBtn.getAttribute('data-day');
364 let navType = navDay ? 'day' : 'month';
365 VBOCore.emitEvent('vbo-tm-apply-filters', {
366 filters: {
367 calendar_type: navType,
368 calendar_month: navMonth || '<?php echo date('Y-m-01'); ?>',
369 calendar_day: navDay,
370 },
371 });
372 });
373 });
374
375 // register change event on due time picker
376 document.querySelectorAll('.task-time-picker').forEach((timePicker) => {
377 let currentTime = timePicker.value;
378
379 timePicker.addEventListener('blur', () => {
380 if (timePicker.value == currentTime) {
381 // nothing has change, do not proceed with the update
382 return;
383 }
384
385 timePicker.disabled = true;
386
387 // make the request
388 VBOCore.doAjax(
389 '<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=taskmanager.updateTask'); ?>',
390 {
391 data: {
392 id: <?php echo $taskId ?>,
393 dueon: '<?php echo JHtml::fetch('date', $dueDate, 'Y-m-d'); ?> ' + timePicker.value,
394 }
395 },
396 (resp) => {
397 timePicker.disabled = false;
398 currentTime = timePicker.value;
399 },
400 (error) => {
401 // display error message
402 alert(error.responseText);
403
404 timePicker.disabled = false;
405 }
406 );
407 });
408 });
409
410 let checklistQueue = [], isUpdatingChecklist = false;
411
412 const updateChecklist = (checklistTask) => {
413 // update checklist task
414 jQuery(checklistTask.node).attr('data-checked', checklistTask.status ? 'true' : 'false');
415
416 if (isUpdatingChecklist) {
417 checklistQueue.push(checklistTask);
418 return;
419 }
420
421 isUpdatingChecklist = true;
422
423 // make the request
424 VBOCore.doAjax(
425 '<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=taskmanager.updateChecklist'); ?>',
426 {
427 id: <?php echo $taskId ?>,
428 index: checklistTask.index + 1,
429 status: checklistTask.status ? 1 : 0,
430 },
431 (resp) => {
432 isUpdatingChecklist = false;
433
434 if (checklistQueue.length) {
435 // move to the next one
436 updateChecklist(checklistQueue.shift());
437 }
438 },
439 (error) => {
440 // display error message
441 alert(error.responseText);
442
443 // restore previous value
444 jQuery(checklistTask.node).attr('data-checked', checklistTask.status ? 'false' : 'true');
445
446 isUpdatingChecklist = false;
447
448 if (checklistQueue.length) {
449 // move to the next one
450 updateChecklist(checklistQueue.shift());
451 }
452 }
453 );
454 }
455
456 jQuery('.vbo-tm-task-notes ul[data-checked]').on('click', function(event) {
457 if (event.originalEvent.offsetX > 16) {
458 // simulate click only on the pseudo-element
459 return false;
460 }
461
462 // fetch the nth-index of the clicked checkbox
463 const index = jQuery('.vbo-tm-task-notes ul[data-checked]').index(this);
464 const status = jQuery(this).attr('data-checked') === 'true' ? false : true;
465
466 updateChecklist({
467 index: index,
468 status: status,
469 node: this,
470 });
471 });
472
473 <?php if (!empty($roomInfo)): ?>
474 jQuery('.listing-name.open-map').on('click', () => {
475 const mapTarget = jQuery('.vbo-tm-task-listing-map');
476 const isVisible = mapTarget.is(':visible');
477
478 jQuery('.vbo-hideable-pane').hide();
479
480 if (isVisible) {
481 // re-display task notes
482 jQuery('.vbo-tm-task-notes').show();
483 } else {
484 // display map
485 mapTarget.show();
486 }
487
488 initializeListingMap(mapTarget[0]);
489 });
490
491 let mapInitialized = false;
492
493 const initializeListingMap = async (target) => {
494 if (mapInitialized) {
495 return;
496 }
497
498 mapInitialized = true;
499
500 // init libraries
501 const { Map, InfoWindow } = await google.maps.importLibrary('maps');
502 const { AdvancedMarkerElement } = await google.maps.importLibrary('marker');
503
504 const position = {
505 lat: <?php echo floatval($roomInfo['params']->geo->latitude ?? null); ?>,
506 lng: <?php echo floatval($roomInfo['params']->geo->longitude ?? null); ?>,
507 };
508
509 const map = new Map(target, {
510 zoom: 14,
511 center: position,
512 mapId: 'listing_location',
513 });
514
515 const popup = new InfoWindow({
516 content: <?php echo json_encode($roomInfo['params']->geo->address ?? $roomInfo['name']); ?>,
517 ariaLabel: <?php echo json_encode($roomInfo['name']); ?>,
518 });
519
520 const marker = new AdvancedMarkerElement({
521 position: position,
522 map: map,
523 title: <?php echo json_encode($roomInfo['name']); ?>,
524 });
525
526 marker.addListener('gmp-click', () => {
527 popup.open({
528 anchor: marker,
529 map: map,
530 });
531 });
532 }
533 <?php endif; ?>
534
535 jQuery('.guest-name.open-reservation').on('click', () => {
536 const resTarget = jQuery('.vbo-tm-task-reservation-details');
537 const isVisible = resTarget.is(':visible');
538
539 jQuery('.vbo-hideable-pane').hide();
540
541 if (isVisible) {
542 // re-display task notes
543 jQuery('.vbo-tm-task-notes').show();
544 } else {
545 // display reservation
546 resTarget.show();
547 }
548 });
549
550 VBOCore.emitEvent('vbo-tm-contents-loaded', {
551 element: document.querySelector('.vbo-tm-task-details'),
552 statuses: <?php echo json_encode($taskArea->getStatuses()); ?>,
553 });
554 });
555 </script>