PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.15
VikBooking Hotel Booking Engine & PMS v1.8.15
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 1.7.3 All 36 releases
vikbooking / site / controllers / operatortool.php

operatortool.php in VikBooking Hotel Booking Engine & PMS 1.8.15, at site/controllers/operatortool.php

524 lines 20.4 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 com_vikbooking
5 * @author Alessio Gaggii - E4J srl
6 * @copyright Copyright (C) 2025 E4J srl. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 * @link https://vikwp.com
9 */
10
11 // No direct access to this file
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 /**
15 * VikBooking operator-tool controller.
16 *
17 * @since 1.17.6 (J) - 1.7.6 (WP)
18 */
19 class VikBookingControllerOperatortool extends JControllerAdmin
20 {
21 /**
22 * AJAX endpoint for the tool "guest_messaging" to load threads assigned
23 * to manageable listing IDs by the currently logged operator account.
24 */
25 public function loadGuestThreads()
26 {
27 $app = JFactory::getApplication();
28
29 if (!JSession::checkToken()) {
30 // missing CSRF-proof token
31 VBOHttpDocument::getInstance($app)->close(403, JText::translate('JINVALID_TOKEN'));
32 }
33
34 // the name of the tool to access
35 $tool = 'guest_messaging';
36
37 try {
38 // obtain data from the validation of the current operator and tool permissions
39 list($operator, $permissions, $tool_uri) = VikBooking::getOperatorInstance()->authOperatorToolData($tool);
40 } catch (Exception $e) {
41 // abort
42 VBOHttpDocument::getInstance($app)->close($e->getCode(), $e->getMessage());
43 }
44
45 // get listings assigned to the current operator (no listings equals to all listings)
46 $listings = array_filter(
47 array_map('intval', (array) $permissions->get('rooms', []))
48 );
49
50 // attempt to require the chat handler from VCM
51 try {
52 VikBooking::getVcmChatInstance($oid = 0, $channel = null);
53 } catch (Throwable $e) {
54 // propagate the error code
55 VBOHttpDocument::getInstance()->close($e->getCode(), 'Channel Manager not available.');
56 }
57
58 // make sure VCM is available
59 if (!class_exists('VCMChatHandler')) {
60 // raise an error
61 VBOHttpDocument::getInstance()->close(500, 'Channel Manager not available.');
62 }
63
64 // current year Y and timestamp
65 $current_y = date('Y');
66 $today_ymd = date('Y-m-d');
67 $current_ts = time();
68 $nowdf = VikBooking::getDateFormat(true);
69 if ($nowdf == "%d/%m/%Y") {
70 $df = 'd/m/Y';
71 } elseif ($nowdf == "%m/%d/%Y") {
72 $df = 'm/d/Y';
73 } else {
74 $df = 'Y/m/d';
75 }
76
77 // load latest threads for the operator manageable listings
78 $threads = VCMChatHandler::getLatestThreads([
79 'sender' => 'guest',
80 'join_sender' => true,
81 'rooms' => $listings,
82 'start' => $app->input->getUInt('start', 0),
83 'limit' => $app->input->getUInt('limit', 20),
84 ]);
85
86 // map the threads by fetching the channel logo, if available
87 $threads = array_map(function($thread) {
88 if (empty($thread->channel) || !strcasecmp($thread->channel, 'vikbooking')) {
89 return $thread;
90 }
91 $channel_logo = VikChannelManager::getLogosInstance($thread->channel)->getSmallLogoURL();
92 if (!empty($channel_logo)) {
93 $thread->channel_logo = $channel_logo;
94 }
95 return $thread;
96 }, $threads);
97
98 // map the threads by appending the message date/time and status information
99 $threads = array_map(function($thread) use ($df, $current_y, $today_ymd) {
100 $str_checkin = '';
101 $str_checkout = '';
102 if (!empty($thread->b_checkin)) {
103 $stay_info_in = getdate($thread->b_checkin);
104 $stay_info_out = getdate($thread->b_checkout);
105 $str_checkin = date('d', $thread->b_checkin);
106 $str_checkin .= $stay_info_in['mon'] != $stay_info_out['mon'] ? ' ' . VikBooking::sayMonth($stay_info_in['mon'], $short = true) : '';
107 $str_checkout = date('d', $thread->b_checkout) . ' ' . VikBooking::sayMonth($stay_info_out['mon'], $short = true);
108 if ($stay_info_in['year'] != $stay_info_out['year'] || $stay_info_in['year'] != $current_y || $stay_info_out['year'] != $current_y) {
109 $str_checkout .= ' ' . $stay_info_in['year'];
110 }
111 }
112
113 $thread->message_info = [
114 'current_ts' => $current_ts,
115 'str_checkin' => $str_checkin,
116 'str_checkout' => $str_checkout,
117 'time' => JHtml::fetch('date', $thread->last_updated, 'H:i'),
118 'date' => JHtml::fetch('date', $thread->last_updated, str_replace('/', VikBooking::getDateSeparator(), $df)),
119 'is_today' => (JHtml::fetch('date', $thread->last_updated, 'Y-m-d') == $today_ymd),
120 ];
121
122 return $thread;
123 }, $threads);
124
125 // send response to output
126 VBOHttpDocument::getInstance($app)->json($threads);
127 }
128
129 /**
130 * AJAX endpoint for the tool "guest_messaging" to render the chat for a booking made
131 * for at least one manageable listing ID by the currently logged operator account.
132 */
133 public function renderGuestBookingChat()
134 {
135 $app = JFactory::getApplication();
136
137 if (!JSession::checkToken()) {
138 // missing CSRF-proof token
139 VBOHttpDocument::getInstance($app)->close(403, JText::translate('JINVALID_TOKEN'));
140 }
141
142 // the name of the tool to access
143 $tool = 'guest_messaging';
144
145 try {
146 // obtain data from the validation of the current operator and tool permissions
147 list($operator, $permissions, $tool_uri) = VikBooking::getOperatorInstance()->authOperatorToolData($tool);
148 } catch (Exception $e) {
149 // abort
150 VBOHttpDocument::getInstance($app)->close($e->getCode(), $e->getMessage());
151 }
152
153 // get listings assigned to the current operator (no listings equals to all listings)
154 $listings = array_filter(
155 array_map('intval', (array) $permissions->get('rooms', []))
156 );
157
158 // the booking ID for which the chat should be rendered
159 $bid = $app->input->getUInt('bid', 0);
160
161 if (!$bid) {
162 VBOHttpDocument::getInstance($app)->close(400, 'Missing booking ID.');
163 }
164
165 $booking = VikBooking::getBookingInfoFromID($bid);
166 if (!$booking) {
167 VBOHttpDocument::getInstance($app)->close(404, 'Booking record not found.');
168 }
169
170 $booking_rooms = VikBooking::loadOrdersRoomsData($booking['id']);
171 $booking_room_ids = array_column($booking_rooms, 'idroom');
172 if (!$booking_rooms || !$booking_room_ids) {
173 VBOHttpDocument::getInstance($app)->close(404, 'No booking rooms found.');
174 }
175
176 // ensure at least one listing of this booking is manageable by the operator permissions
177 if ($listings && !array_intersect($listings, $booking_room_ids)) {
178 VBOHttpDocument::getInstance($app)->close(403, 'Cannot access booking conversation.');
179 }
180
181 // initialize chat instance by getting the proper channel name
182 if (empty($booking['channel'])) {
183 // front-end reservation chat handler
184 $chat_channel = 'vikbooking';
185 } else {
186 $channelparts = explode('_', $booking['channel']);
187 // check if this is a meta search channel
188 $is_meta_search = false;
189 if (preg_match("/(customer).*[0-9]$/", $channelparts[0]) || !strcasecmp($channelparts[0], 'googlehotel') || !strcasecmp($channelparts[0], 'googlevr') || !strcasecmp($channelparts[0], 'trivago')) {
190 $is_meta_search = empty($booking['idorderota']);
191 }
192 if ($is_meta_search) {
193 // customer of type sales channel should use front-end reservation chat handler
194 $chat_channel = 'vikbooking';
195 } else {
196 // let the getInstance method validate the channel chat handler
197 $chat_channel = $booking['channel'];
198 }
199 }
200
201 $messaging = VikBooking::getVcmChatInstance($booking['id'], $chat_channel);
202
203 if (is_null($messaging)) {
204 VBOHttpDocument::getInstance($app)->close(500, 'Could not render chat.');
205 }
206
207 // send response to output
208 $chat = $messaging->renderChat([
209 'hideThreads' => 1,
210 ], $load_assets = false);
211
212 VBOHttpDocument::getInstance($app)->json(['html' => $chat]);
213 }
214
215 /**
216 * AJAX endpoint for the tool "guest_messaging" to load the listing details for a booking
217 * made for at least one manageable listing ID by the currently logged operator account.
218 */
219 public function loadGuestBookingListings()
220 {
221 $app = JFactory::getApplication();
222
223 if (!JSession::checkToken()) {
224 // missing CSRF-proof token
225 VBOHttpDocument::getInstance($app)->close(403, JText::translate('JINVALID_TOKEN'));
226 }
227
228 // the name of the tool to access
229 $tool = 'guest_messaging';
230
231 try {
232 // obtain data from the validation of the current operator and tool permissions
233 list($operator, $permissions, $tool_uri) = VikBooking::getOperatorInstance()->authOperatorToolData($tool);
234 } catch (Exception $e) {
235 // abort
236 VBOHttpDocument::getInstance($app)->close($e->getCode(), $e->getMessage());
237 }
238
239 // get listings assigned to the current operator (no listings equals to all listings)
240 $listings = array_filter(
241 array_map('intval', (array) $permissions->get('rooms', []))
242 );
243
244 // the booking ID for which the listing details should be fetched
245 $bid = $app->input->getUInt('bid', 0);
246
247 $booking_rooms = VikBooking::loadOrdersRoomsData($bid);
248 $booking_room_ids = array_column($booking_rooms, 'idroom');
249 if (!$booking_rooms || !$booking_room_ids) {
250 VBOHttpDocument::getInstance($app)->close(404, 'No booking rooms found.');
251 }
252
253 // ensure at least one listing of this booking is manageable by the operator permissions
254 if ($listings && !array_intersect($listings, $booking_room_ids)) {
255 VBOHttpDocument::getInstance($app)->close(403, 'Cannot access booking or listing details.');
256 }
257
258 // send response to output
259 VBOHttpDocument::getInstance($app)->json(['listings' => array_column($booking_rooms, 'room_name')]);
260 }
261
262 /**
263 * AJAX endpoint for the tool "guest_messaging" to toggle the no-reply-needed thread status for a
264 * booking made for at least one manageable listing ID by the currently logged operator account.
265 */
266 public function toggleGuestThreadNoReplyNeeded()
267 {
268 $app = JFactory::getApplication();
269 $dbo = JFactory::getDbo();
270
271 if (!JSession::checkToken()) {
272 // missing CSRF-proof token
273 VBOHttpDocument::getInstance($app)->close(403, JText::translate('JINVALID_TOKEN'));
274 }
275
276 // the name of the tool to access
277 $tool = 'guest_messaging';
278
279 try {
280 // obtain data from the validation of the current operator and tool permissions
281 list($operator, $permissions, $tool_uri) = VikBooking::getOperatorInstance()->authOperatorToolData($tool);
282 } catch (Exception $e) {
283 // abort
284 VBOHttpDocument::getInstance($app)->close($e->getCode(), $e->getMessage());
285 }
286
287 // get listings assigned to the current operator (no listings equals to all listings)
288 $listings = array_filter(
289 array_map('intval', (array) $permissions->get('rooms', []))
290 );
291
292 // the booking ID for which the listing details should be fetched
293 $bid = $app->input->getUInt('bid', 0);
294
295 $booking_rooms = VikBooking::loadOrdersRoomsData($bid);
296 $booking_room_ids = array_column($booking_rooms, 'idroom');
297 if (!$booking_rooms || !$booking_room_ids) {
298 VBOHttpDocument::getInstance($app)->close(404, 'No booking rooms found.');
299 }
300
301 // ensure at least one listing of this booking is manageable by the operator permissions
302 if ($listings && !array_intersect($listings, $booking_room_ids)) {
303 VBOHttpDocument::getInstance($app)->close(403, 'Cannot access booking or listing details.');
304 }
305
306 // gather thread ID and toggle status
307 $id_thread = $app->input->getUInt('id_thread', 0);
308 $status = $app->input->getUInt('status', 0);
309
310 if (empty($id_thread)) {
311 VBOHttpDocument::getInstance($app)->close(400, 'Missing booking thread.');
312 }
313
314 // update the information on the database
315 $dbo->setQuery(
316 $dbo->getQuery(true)
317 ->update($dbo->qn('#__vikchannelmanager_threads'))
318 ->set($dbo->qn('no_reply_needed') . ' = ' . (!$status ? 1 : 0))
319 ->where($dbo->qn('id') . ' = ' . $id_thread)
320 ->where($dbo->qn('idorder') . ' = ' . $bid)
321 );
322 $dbo->execute();
323
324 // send response to output
325 VBOHttpDocument::getInstance($app)->json(['status' => (!$status ? 1 : 0)]);
326 }
327
328 /**
329 * AJAX endpoint for the tool "guest_messaging" to check for new thread messages.
330 */
331 public function checkNewGuestThreads()
332 {
333 $app = JFactory::getApplication();
334
335 if (!JSession::checkToken()) {
336 // missing CSRF-proof token
337 VBOHttpDocument::getInstance($app)->close(403, JText::translate('JINVALID_TOKEN'));
338 }
339
340 // the name of the tool to access
341 $tool = 'guest_messaging';
342
343 try {
344 // obtain data from the validation of the current operator and tool permissions
345 list($operator, $permissions, $tool_uri) = VikBooking::getOperatorInstance()->authOperatorToolData($tool);
346 } catch (Exception $e) {
347 // abort
348 VBOHttpDocument::getInstance($app)->close($e->getCode(), $e->getMessage());
349 }
350
351 // get listings assigned to the current operator (no listings equals to all listings)
352 $listings = array_filter(
353 array_map('intval', (array) $permissions->get('rooms', []))
354 );
355
356 // attempt to require the chat handler from VCM
357 try {
358 VikBooking::getVcmChatInstance($oid = 0, $channel = null);
359 } catch (Throwable $e) {
360 // propagate the error code
361 VBOHttpDocument::getInstance()->close($e->getCode(), 'Channel Manager not available.');
362 }
363
364 // make sure VCM is available
365 if (!class_exists('VCMChatHandler')) {
366 // raise an error
367 VBOHttpDocument::getInstance()->close(500, 'Channel Manager not available.');
368 }
369
370 // load the latest thread message for the operator manageable listings
371 $threads = VCMChatHandler::getLatestThreads([
372 'sender' => 'guest',
373 'join_sender' => true,
374 'rooms' => $listings,
375 'start' => $app->input->getUInt('start', 0),
376 'limit' => $app->input->getUInt('limit', 1),
377 ]);
378
379 if (!$threads) {
380 // no thread messages at all
381 VBOHttpDocument::getInstance()->json(['newThreads' => []]);
382 }
383
384 // get the latest date requested
385 $last_date = $app->input->getString('last_date');
386
387 if (!$last_date) {
388 // all thread messages are considered as new
389 VBOHttpDocument::getInstance()->json(['newThreads' => $threads]);
390 }
391
392 // filter threads based on the given last date
393 $threads = array_filter($threads, function($thread) use ($last_date) {
394 return isset($thread->last_updated) && strtotime($thread->last_updated) > strtotime($last_date);
395 });
396
397 // reset keys
398 $threads = array_values($threads);
399
400 // send response to output
401 VBOHttpDocument::getInstance()->json(['newThreads' => $threads]);
402 }
403
404 /**
405 * Base endpoint to generate an iCal calendar file for the task manager events (tasks)
406 * of an operator. The authentication is performed through GET via the iCal URL.
407 *
408 * @since 1.18.0 (J) - 1.8.0 (WP)
409 */
410 public function tm_ical()
411 {
412 $app = JFactory::getApplication();
413
414 // gather request values for authentication (base64(id:md5(auth_code)))
415 $operator_signature = base64_decode(urldecode((string) $app->input->getBase64('opsid', '')));
416
417 // validate signature syntax
418 if (!preg_match('/^([0-9]+):([0-9a-f]{32})$/i', $operator_signature, $matches)) {
419 VBOHttpDocument::getInstance($app)->close(400, 'Bad URL.');
420 }
421
422 $operatorId = (int) $matches[1];
423 $operatorHashedCode = $matches[2];
424
425 // get the operator record by ID
426 $record = VikBooking::getOperatorInstance()->getOne($operatorId);
427
428 // attempt to perform a manual authentication
429 if (!$record || md5((string) $record['code']) != $operatorHashedCode || !VikBooking::getOperatorInstance()->authOperator($record['code'])) {
430 VBOHttpDocument::getInstance($app)->close(401, 'Unauthorized');
431 }
432
433 // the name of the tool to access
434 $tool = 'task_manager';
435
436 try {
437 // obtain data from the validation of the current operator and tool permissions
438 list($operator, $permissions, $tool_uri) = VikBooking::getOperatorInstance()->authOperatorToolData($tool);
439 } catch (Exception $e) {
440 // abort
441 VBOHttpDocument::getInstance($app)->close($e->getCode(), $e->getMessage());
442 }
443
444 // check from the permissions whether tasks can be accepted by the operator,
445 // hence null assigness should be included - use a different filter otherwise
446 $tasksFilterName = ((bool) $permissions->get('accept_tasks', 0)) ? 'operator' : 'assignee';
447
448 // get operator (future) tasks list for one year worth of dates
449 $tasks = VBOTaskModelTask::getInstance()->filterItems([
450 $tasksFilterName => $operator['id'],
451 'dates' => sprintf('%s:%s', date('Y-m-d'), date('Y-m-d', strtotime('+1 year'))),
452 ]);
453
454 // build and downlaod the calendar content
455 VBOTaskOperatorIcal::getInstance()
456 ->setCalendarSubscriber($app->input->getString('sub'))
457 ->setOperator($operator)
458 ->setPermissions($permissions)
459 ->setTool($tool)
460 ->setToolUri($tool_uri)
461 ->setEvents($tasks)
462 ->download($app);
463
464 // close the application
465 $app->close();
466 }
467
468 /**
469 * AJAX endpoint to render a layout file from an operator tool.
470 *
471 * @return void
472 */
473 public function renderLayout()
474 {
475 $app = JFactory::getApplication();
476
477 if (!JSession::checkToken()) {
478 // missing CSRF-proof token
479 VBOHttpDocument::getInstance($app)->close(403, JText::translate('JINVALID_TOKEN'));
480 }
481
482 $tool = $app->input->getString('tool', '');
483 $type = $app->input->getString('type', '');
484 $data = (array) $app->input->get('data', [], 'array');
485
486 try {
487 // obtain data from the validation of the current operator and tool permissions
488 list($operator, $permissions, $tool_uri) = VikBooking::getOperatorInstance()->authOperatorToolData($tool);
489 } catch (Exception $e) {
490 // abort
491 VBOHttpDocument::getInstance($app)->close($e->getCode(), $e->getMessage());
492 }
493
494 if (empty($type)) {
495 // invalid layout requested
496 VBOHttpDocument::getInstance($app)->close(404, sprintf('Could not find the layout [%s] to render.', $type));
497 }
498
499 // fetch the requested layout
500 $layout_data = [
501 'tool' => $tool,
502 'operator' => $operator,
503 'permissions' => $permissions,
504 'tool_uri' => $tool_uri,
505 'data' => $data,
506 ];
507
508 try {
509 $layout_html = JLayoutHelper::render($type, $layout_data, null, [
510 'component' => 'com_vikbooking',
511 'client' => 'site',
512 ]);
513 } catch (Exception $e) {
514 // raise the error caught
515 VBOHttpDocument::getInstance($app)->close($e->getCode() ?: 500, $e->getMessage());
516 }
517
518 // send the response to output
519 VBOHttpDocument::getInstance($app)->json([
520 'html' => $layout_html,
521 ]);
522 }
523 }
524