PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.8
VikBooking Hotel Booking Engine & PMS v1.8.8
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 / admin / controllers / quote.php

quote.php in VikBooking Hotel Booking Engine & PMS 1.8.8, at admin/controllers/quote.php

710 lines 26.1 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) 2026 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 * VikBooking quote (quotation) controller (admin).
16 *
17 * @since 1.18.8 (J) - 1.8.8 (WP)
18 */
19 class VikBookingControllerQuote extends JControllerAdmin
20 {
21 /**
22 * AJAX endpoint to save a new quotation.
23 *
24 * @return void
25 */
26 public function save()
27 {
28 $app = JFactory::getApplication();
29
30 if (!JSession::checkToken()) {
31 // missing CSRF-proof token
32 VBOHttpDocument::getInstance($app)->close(403, JText::translate('JINVALID_TOKEN'));
33 }
34
35 // gather request values
36 $quote = $app->input->get('quote', [], 'array');
37 $sessionId = $app->input->getUInt('session_id', 0);
38
39 if (empty($quote['customer']['id']) && empty($quote['customer']['first_name'])) {
40 // missing customer data
41 VBOHttpDocument::getInstance($app)->close(400, 'Missing customer information.');
42 }
43
44 // filter out invalid booking solutions
45 $quote['solutions'] = array_values(array_filter((array) ($quote['solutions'] ?? []), function($solution) {
46 if (empty($solution['checkin']) || empty($solution['checkout']) || empty($solution['rooms']) || !is_array($solution['rooms'])) {
47 // missing stay dates or booking rooms
48 return false;
49 }
50
51 foreach ($solution['rooms'] as $roomSol) {
52 if (empty($roomSol['id'])) {
53 // missing room ID
54 return false;
55 }
56 if ((empty($roomSol['id_price']) || empty($roomSol['room_cost'])) && empty($roomSol['cust_cost'])) {
57 // missing room rate, either a rate plan or a custom rate
58 return false;
59 }
60 if (empty($roomSol['adults']) && empty($roomSol['children'])) {
61 // invalid guest party
62 return false;
63 }
64 }
65
66 return true;
67 }));
68
69 if (empty($quote['solutions'])) {
70 // missing booking solutions
71 VBOHttpDocument::getInstance($app)->close(400, 'No valid booking solutions.');
72 }
73
74 // access customer model
75 $customerModel = VBOMvcModel::getInstance('customer');
76
77 // build customer information
78 $customerInfo = [
79 'first_name' => $quote['customer']['first_name'],
80 'last_name' => ($quote['customer']['last_name'] ?? '') ?: '(Quote)',
81 'email' => $quote['customer']['email'] ?? null,
82 'phone' => $quote['customer']['phone'] ?? null,
83 'country' => $quote['customer']['country'] ?? null,
84 ];
85
86 if (!empty($quote['customer']['id'])) {
87 // make sure the given customer exists
88 $customer = $customerModel->getItem((int) $quote['customer']['id']);
89 if (!$customer) {
90 // abort on error
91 VBOHttpDocument::getInstance($app)->close(404, 'Customer ID not found.');
92 }
93 // assign customer ID and information
94 $customerId = $customer->id;
95 $customerInfo = (array) $customer;
96 } else {
97 // save new customer or update existing
98 $customerId = $customerModel->save($customerInfo);
99 }
100
101 // access quote model
102 $quoteModel = VBOMvcModel::getInstance('quote');
103
104 // save quote record
105 $quoteId = $quoteModel->save([
106 'name' => ($quote['name'] ?? '') ?: date('Y-m-d H:i:s'),
107 'subject' => $quote['message']['subject'] ?? null,
108 'message' => $quote['message']['content'] ?? null,
109 'notes' => $quote['message']['notes'] ?? null,
110 'valid_until' => $quote['validity'] ?? null,
111 'idcustomer' => $customerId ?: null,
112 'first_name' => $quote['customer']['first_name'],
113 'last_name' => $quote['customer']['last_name'] ?? null,
114 'email' => $quote['customer']['email'] ?? null,
115 'phone' => $quote['customer']['phone'] ?? null,
116 'country_3_code' => $quote['customer']['country'] ?? null,
117 'preferred' => !empty($quote['message']['preferred']) ? 1 : 0,
118 'sent' => !empty($quote['message']['send']) ? 1 : 0,
119 ]);
120
121 if (!$quoteId) {
122 // raise an error
123 VBOHttpDocument::getInstance($app)->close(500, sprintf('Could not save quote record: %s.', (string) ($quoteModel->getError() ?: '---')));
124 }
125
126 foreach ($quote['solutions'] as $index => $solution) {
127 // access a new instance of the reservation model
128 $resModel = VBOModelReservation::getInstance([
129 'status' => 'standby',
130 'type' => 'quote',
131 'checkin' => strtotime($solution['checkin']),
132 'checkout' => strtotime($solution['checkout']),
133 'num_rooms' => count($solution['rooms']),
134 'adults' => array_sum(array_column($solution['rooms'], 'adults')),
135 'children' => array_sum(array_column($solution['rooms'], 'children')),
136 'id_payment' => $quote['id_payment'] ?? null,
137 'lock_until' => $quote['validity'] ?? null,
138 'idquote' => $quoteId,
139 ], true);
140
141 // set booking customer
142 $resModel->setCustomer([
143 'id' => $customerId,
144 'first_name' => $customerInfo['first_name'],
145 'last_name' => $customerInfo['last_name'],
146 'email' => $customerInfo['email'],
147 'country' => $customerInfo['country'],
148 'phone' => $customerInfo['phone'],
149 ]);
150
151 // scan all booking solution rooms
152 $roomSolutions = [];
153 foreach ($solution['rooms'] as $roomSolution) {
154 // check if we have a custom cancellation policy ID to apply
155 $custCancPolicyId = null;
156 if (!empty($roomSolution['cust_cost']) && !empty($roomSolution['id_price'])) {
157 // apply the selected rate plan for the custom cancellation policy
158 $custCancPolicyId = $roomSolution['id_price'];
159 }
160
161 // push booking solution room
162 $roomSolutions[] = [
163 'id' => $roomSolution['id'],
164 'adults' => $roomSolution['adults'] ?? null,
165 'children' => $roomSolution['children'] ?? null,
166 'id_price' => $roomSolution['id_price'] ?? null,
167 'room_cost' => $roomSolution['room_cost'] ?? null,
168 'cust_cost' => $roomSolution['cust_cost'] ?? null,
169 'id_tax' => $roomSolution['id_tax'] ?? null,
170 'cust_cpolicy_id' => $custCancPolicyId,
171 'options' => $roomSolution['options'] ?? null,
172 'extras' => $roomSolution['extras'] ?? null,
173 ];
174 }
175
176 // set all booking rooms
177 $resModel->setRooms($roomSolutions);
178
179 // store the reservation
180 $resModel->create();
181
182 // get the new booking ID
183 $res_id = $resModel->getNewBookingID();
184
185 if (!$res_id) {
186 // delete quote
187 $quoteModel->delete($quoteId);
188
189 // abort on error
190 VBOHttpDocument::getInstance($app)->close(500, sprintf('Error saving booking solution #%d: %s', ($index + 1), (string) ($resModel->getError() ?: '---')));
191 }
192 }
193
194 // check if the quote should be sent via email
195 $sendResultErr = null;
196 if (!empty($quote['message']['send'])) {
197 // send the quotation to the guest via email
198 try {
199 $quoteModel->sendEmail($quoteId);
200 } catch (Exception $e) {
201 // catch the error message
202 $sendResultErr = $e->getMessage();
203 }
204 }
205
206 // check if the quote was originated from a chat session
207 if ($sessionId) {
208 try {
209 // link the newly generated quote to the initial chat session
210 (new VBOChatSessionModel)->setMetadata($sessionId, 'quote_id', $quoteId);
211 } catch (Exception $e) {
212 // do nothing on error
213 }
214 }
215
216 // send the response to output
217 VBOHttpDocument::getInstance($app)->json([
218 'success' => true,
219 'idquote' => $quoteId,
220 'sending_error' => $sendResultErr,
221 ]);
222 }
223
224 /**
225 * AJAX endpoint to update a quotation.
226 *
227 * @return void
228 */
229 public function update()
230 {
231 $app = JFactory::getApplication();
232
233 if (!JSession::checkToken()) {
234 // missing CSRF-proof token
235 VBOHttpDocument::getInstance($app)->close(403, JText::translate('JINVALID_TOKEN'));
236 }
237
238 // gather request values
239 $quote = $app->input->get('quote', [], 'array');
240
241 if (empty($quote['id'])) {
242 VBOHttpDocument::getInstance($app)->close(400, 'Missing quote ID to update.');
243 }
244
245 // access quote model
246 $quoteModel = VBOMvcModel::getInstance('quote');
247
248 // get requested quote record
249 $quoteRecord = $quoteModel->getItem($quote['id']);
250
251 if (!$quoteRecord) {
252 VBOHttpDocument::getInstance($app)->close(404, 'Quote record not found.');
253 }
254
255 // update record
256 $result = $quoteModel->save([
257 'id' => $quoteRecord->id,
258 'name' => $quote['name'] ?? null,
259 'subject' => $quote['message']['subject'] ?? null,
260 'message' => $quote['message']['content'] ?? null,
261 'notes' => $quote['message']['notes'] ?? null,
262 'valid_until' => $quote['validity'] ?? null,
263 'idcustomer' => $quote['customer']['id'] ?? null,
264 'first_name' => $quote['customer']['first_name'] ?? null,
265 'last_name' => $quote['customer']['last_name'] ?? null,
266 'email' => $quote['customer']['email'] ?? null,
267 'phone' => $quote['customer']['phone'] ?? null,
268 'country_3_code' => $quote['customer']['country'] ?? null,
269 'preferred' => !empty($quote['message']['preferred']) ? 1 : 0,
270 ]);
271
272 if (!$result) {
273 // abort
274 VBOHttpDocument::getInstance($app)->close(500, 'Could not update quote record.');
275 }
276
277 // check if we have a different validity date for the quote
278 if (!empty($quote['validity']) && JFactory::getDate($quote['validity'], $app->get('offset'))->toSql() != $quoteRecord->valid_until) {
279 // validity date has changed, booking solutions must be updated
280 $quoteData = $quoteModel->loadBookingRecords(0, 0, [
281 'filters' => [
282 'id' => $quoteRecord->id,
283 ],
284 ])[0] ?? null;
285
286 if (!$quoteData) {
287 // abort
288 VBOHttpDocument::getInstance($app)->close(500, 'Quote record missing booking solutions.');
289 }
290
291 // extract confirmed booking solutions
292 $confirmedBookings = array_filter($quoteData->solutions ?? [], function($solution) {
293 return ($solution->status ?? '') == 'confirmed';
294 });
295
296 // extract pending booking solutions
297 $pendingBookings = array_filter($quoteData->solutions ?? [], function($solution) {
298 return ($solution->status ?? '') == 'standby';
299 });
300
301 // make sure the quote still needs to be confirmed
302 if (!$confirmedBookings && $pendingBookings) {
303 // determine the timestamp to lock until
304 $lockUntilTs = strtotime($quote['validity']);
305
306 // check whether the CM may require to update previously locked records
307 if (method_exists('VCMRequestAvailability', 'updateReleaseDate')) {
308 // let the CM update the release date of the involved and unconfirmed booking IDs, if needed
309 VCMRequestAvailability::getInstance()->updateReleaseDate(array_column($pendingBookings, 'id'), $lockUntilTs);
310 }
311
312 // access temp-lock model
313 $tmpLockModel = VBOMvcModel::getInstance('tmplock');
314
315 // scan all pending reservations
316 foreach ($pendingBookings as $pendingBooking) {
317 // delete any previously locked record for this booking
318 $tmpLockModel->deleteFromBooking($pendingBooking->id);
319 // scan all booking rooms
320 foreach ($pendingBooking->rooms as $pendingRoomBooking) {
321 // lock records temporarily until new validity date
322 $tmpLockModel->save([
323 'idroom' => $pendingRoomBooking->id,
324 'checkin' => $pendingBooking->checkin,
325 'checkout' => $pendingBooking->checkout,
326 'until' => $lockUntilTs,
327 'realback' => $pendingBooking->checkout,
328 'idorder' => $pendingBooking->id,
329 ]);
330 }
331 if (VikBooking::vcmAutoUpdate() > 0) {
332 // silently trigger an availability update request for this pending booking
333 $vcm_obj = VikBooking::getVcmInvoker();
334 $vcm_obj->setOids([$pendingBooking->id])->setSyncType('new');
335 // ignore the update result
336 $sync_result = $vcm_obj->doSync();
337 }
338 }
339 }
340 }
341
342 // send the response to output
343 VBOHttpDocument::getInstance($app)->json([
344 'success' => true,
345 ]);
346 }
347
348 /**
349 * AJAX endpoint to delete a quotation.
350 *
351 * @return void
352 */
353 public function delete()
354 {
355 $app = JFactory::getApplication();
356
357 if (!JSession::checkToken()) {
358 // missing CSRF-proof token
359 VBOHttpDocument::getInstance($app)->close(403, JText::translate('JINVALID_TOKEN'));
360 }
361
362 // gather quotation ID
363 $quoteId = $app->input->getUInt('quote_id', 0);
364
365 if (empty($quoteId)) {
366 VBOHttpDocument::getInstance($app)->close(400, 'Missing quote ID to delete.');
367 }
368
369 // access quote model
370 $quoteModel = VBOMvcModel::getInstance('quote');
371
372 // get requested quote data
373 $quoteData = $quoteModel->loadBookingRecords(0, 0, [
374 'filters' => [
375 'id' => $quoteId,
376 ],
377 ])[0] ?? null;
378
379 if (!$quoteData) {
380 VBOHttpDocument::getInstance($app)->close(404, 'Quote record not found.');
381 }
382
383 // extract confirmed booking solutions
384 $confirmedBookings = array_filter($quoteData->solutions ?? [], function($solution) {
385 return ($solution->status ?? '') == 'confirmed';
386 });
387
388 // extract pending booking solutions
389 $pendingBookings = array_filter($quoteData->solutions ?? [], function($solution) {
390 return ($solution->status ?? '') == 'standby';
391 });
392
393 if ($confirmedBookings) {
394 // do not proceed
395 VBOHttpDocument::getInstance($app)->close(400, 'Cannot delete quote record because one of its booking solutions is confirmed. Delete reservations first.');
396 }
397
398 // access reservation model
399 $resModel = VBOModelReservation::getInstance();
400
401 // scan all pending bookings involved, if any
402 foreach ($pendingBookings as $pendingBooking) {
403 // set reservation status to cancelled
404 $resModel->delete([
405 'booking_id' => $pendingBooking->id,
406 'cancellation_reason' => sprintf('Quote #%d cancelled.', $quoteData->id),
407 ]);
408 }
409
410 // delete quote record
411 $result = $quoteModel->delete($quoteData->id);
412
413 if (!$result) {
414 // raise error
415 VBOHttpDocument::getInstance($app)->close(500, 'Deleting requested quote record failed.');
416 }
417
418 // send the response to output
419 VBOHttpDocument::getInstance($app)->json([
420 'success' => true,
421 ]);
422 }
423
424 /**
425 * AJAX endpoint to send a quotation via email.
426 *
427 * @return void
428 */
429 public function sendMail()
430 {
431 $app = JFactory::getApplication();
432
433 if (!JSession::checkToken()) {
434 // missing CSRF-proof token
435 VBOHttpDocument::getInstance($app)->close(403, JText::translate('JINVALID_TOKEN'));
436 }
437
438 // gather quote ID
439 $quoteId = $app->input->getUInt('quote_id', 0);
440
441 if (!$quoteId) {
442 // missing quotation ID
443 VBOHttpDocument::getInstance($app)->close(400, 'Missing quotation ID.');
444 }
445
446 // access quote model
447 $quoteModel = VBOMvcModel::getInstance('quote');
448
449 try {
450 // send the quotation via email
451 $quoteModel->sendEmail($quoteId);
452 } catch (Exception $e) {
453 // propagate the error
454 VBOHttpDocument::getInstance($app)->close($e->getCode() ?: 500, $e->getMessage());
455 }
456
457 // send the response to output
458 VBOHttpDocument::getInstance($app)->json([
459 'success' => true,
460 ]);
461 }
462
463 /**
464 * AJAX endpoint to send a quotation via messaging account (i.e. WhatsApp).
465 *
466 * @return void
467 */
468 public function sendMessaging()
469 {
470 $app = JFactory::getApplication();
471
472 if (!JSession::checkToken()) {
473 // missing CSRF-proof token
474 VBOHttpDocument::getInstance($app)->close(403, JText::translate('JINVALID_TOKEN'));
475 }
476
477 // gather quote ID and template configuration data
478 $quoteId = $app->input->getUInt('quote_id', 0);
479 $accountId = $app->input->getString('account_id', '');
480 $phoneId = $app->input->getString('phone_id', '');
481 $configId = $app->input->getUInt('config_id', 0);
482
483 if (!$quoteId) {
484 // missing quotation ID
485 VBOHttpDocument::getInstance($app)->close(400, 'Missing quotation ID.');
486 }
487
488 if (empty($accountId) || empty($phoneId)) {
489 // missing account data
490 VBOHttpDocument::getInstance($app)->close(400, 'Missing messaging account information.');
491 }
492
493 // access quote model
494 $quoteModel = VBOMvcModel::getInstance('quote');
495
496 // get current quote and related booking record(s)
497 $quoteData = $quoteModel->loadBookingRecords(0, 0, [
498 'filters' => [
499 'id' => $quoteId,
500 ],
501 ])[0] ?? null;
502
503 if (!$quoteData) {
504 // quotation not found
505 VBOHttpDocument::getInstance($app)->close(404, 'Quotation not found.');
506 }
507
508 // access booking registry through the first quote booking solution
509 try {
510 $booking = VBOBookingRegistry::getInstance(['id' => $quoteData->solutions[0]->id ?? null]);
511 } catch (Exception $e) {
512 // propagate error
513 VBOHttpDocument::getInstance($app)->close($e->getCode() ?: 404, 'Could not load quote booking solution.');
514 }
515
516 // get recipient (guest) phone number
517 $phoneNumber = ($quoteData->phone ?? null) ?: $booking->getPhoneNumber();
518 if (!$phoneNumber) {
519 // unable to proceed without a recipient
520 VBOHttpDocument::getInstance($app)->close(400, 'Missing guest recipient phone number.');
521 }
522
523 if (!class_exists('VCMMessagingAccountsModel')) {
524 // unsupported request
525 VBOHttpDocument::getInstance($app)->close(400, 'Missing Channel Manager with messaging accounts.');
526 }
527
528 // access messaging accounts model
529 $maModel = VCMMessagingAccountsModel::getInstance();
530
531 // fetch requested messaging account record
532 $accountRecord = $maModel->getItem([
533 'account_id' => $accountId,
534 'phone_id' => $phoneId,
535 ]);
536
537 if (!$accountRecord || empty($accountRecord->settings['configurations'][$configId])) {
538 VBOHttpDocument::getInstance($app)->close(404, 'Could not find the requested messaging account.');
539 }
540
541 // load channel details
542 $channelDetails = VikChannelManager::getChannel($accountRecord->idchannel);
543 if (!$channelDetails) {
544 VBOHttpDocument::getInstance($app)->close(404, 'Messaging channel not found.');
545 }
546
547 // register booking messaging account provider
548 $maModel->setBookingAccountProvider($booking, $accountRecord);
549
550 // access chat handler first
551 $chatHandler = VikBooking::getVcmChatInstance($booking->getID(), $channelDetails['name']);
552
553 if (!$chatHandler) {
554 VBOHttpDocument::getInstance($app)->close(500, 'Could not invoke proper chat handler.');
555 }
556
557 // build chat message object
558 $chatMessage = new VCMChatMessage(
559 // no message content
560 '',
561 // no attachments
562 [],
563 // data to bind
564 [
565 'recipient' => $phoneNumber,
566 'account' => $accountRecord,
567 'message_template' => $configId,
568 'quote_data' => $quoteData,
569 'mark_previous_replied' => true,
570 ]
571 );
572
573 // inject thread ID with recipient phone number
574 $chatMessage->set('idthread', $phoneNumber);
575
576 try {
577 // send message
578 if (!$chatHandler->send($chatMessage)) {
579 throw new Exception($chatHandler->getError() ?: 'Could not send the message template to recipient phone number.', 500);
580 }
581 } catch (Exception $e) {
582 // propagate error
583 VBOHttpDocument::getInstance($app)->close($e->getCode() ?: 500, $e->getMessage());
584 }
585
586 // flag record and "sent"
587 $quoteModel->save([
588 'id' => $quoteId,
589 'sent' => 1,
590 ]);
591
592 // send the response to output
593 VBOHttpDocument::getInstance($app)->json([
594 'success' => true,
595 'recipient' => $phoneNumber,
596 ]);
597 }
598
599 /**
600 * AJAX endpoint to toggle or update the preferred status of a quotation.
601 *
602 * @return void
603 */
604 public function togglePreferred()
605 {
606 $app = JFactory::getApplication();
607
608 if (!JSession::checkToken()) {
609 // missing CSRF-proof token
610 VBOHttpDocument::getInstance($app)->close(403, JText::translate('JINVALID_TOKEN'));
611 }
612
613 // gather quote ID and optional preferred status
614 $quoteId = $app->input->getUInt('quote_id', 0);
615 $status = $app->input->get('status', null);
616
617 if (!$quoteId) {
618 // missing quotation ID
619 VBOHttpDocument::getInstance($app)->close(400, 'Missing quotation ID.');
620 }
621
622 // access quote model
623 $quoteModel = VBOMvcModel::getInstance('quote');
624
625 // get current record
626 $quoteRecord = $quoteModel->getItem($quoteId);
627
628 if (!$quoteRecord) {
629 // quotation not found
630 VBOHttpDocument::getInstance($app)->close(404, 'Quotation not found.');
631 }
632
633 // determine new status
634 $preferredStatus = $quoteRecord->preferred ? 0 : 1;
635 if ($status !== null) {
636 $preferredStatus = (int) boolval($status);
637 }
638
639 // update record
640 $result = $quoteModel->save([
641 'id' => $quoteRecord->id,
642 'preferred' => $preferredStatus,
643 ]);
644
645 if (!$result) {
646 // abort
647 VBOHttpDocument::getInstance($app)->close(500, 'Could not update quote record.');
648 }
649
650 // send the response to output
651 VBOHttpDocument::getInstance($app)->json([
652 'success' => true,
653 ]);
654 }
655
656 /**
657 * AJAX endpoint to get a list of messaging template configurations for a given quote.
658 *
659 * @return void
660 */
661 public function getMessagingConfigurations()
662 {
663 $app = JFactory::getApplication();
664
665 if (!JSession::checkToken()) {
666 // missing CSRF-proof token
667 VBOHttpDocument::getInstance($app)->close(403, JText::translate('JINVALID_TOKEN'));
668 }
669
670 // gather quote ID
671 $quoteId = $app->input->getUInt('quote_id', 0);
672
673 if (!$quoteId) {
674 // missing quotation ID
675 VBOHttpDocument::getInstance($app)->close(400, 'Missing quotation ID.');
676 }
677
678 // access quote model
679 $quoteModel = VBOMvcModel::getInstance('quote');
680
681 // get current record
682 $quoteRecord = $quoteModel->getItem($quoteId);
683
684 if (!$quoteRecord) {
685 // quotation not found
686 VBOHttpDocument::getInstance($app)->close(404, 'Quotation not found.');
687 }
688
689 if (!class_exists('VCMMessagingAccountsModel')) {
690 // unsupported feature
691 VBOHttpDocument::getInstance($app)->close(400, 'Missing required Channel Manager and Messaging services.');
692 }
693
694 // load messaging account configurations data by injecting the current quote
695 $configurationsData = VCMMessagingAccountsModel::getInstance()->getConfigurationsData(
696 new VCMMessagingTemplateDecoratorQuote($quoteId)
697 );
698
699 if (!$configurationsData) {
700 // abort
701 VBOHttpDocument::getInstance($app)->close(400, 'Missing configuration for Channel Manager messaging services. No messaging templates found.');
702 }
703
704 // send the response to output
705 VBOHttpDocument::getInstance($app)->json([
706 'data' => $configurationsData,
707 ]);
708 }
709 }
710