PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.13
VikBooking Hotel Booking Engine & PMS v1.8.13
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 / views / quote / tmpl / default.php

default.php in VikBooking Hotel Booking Engine & PMS 1.8.13, at site/views/quote/tmpl/default.php

570 lines 32.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 com_vikbooking
5 * @author E4J srl
6 * @copyright Copyright (C) 2026 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 defined('ABSPATH') or die('No script kiddies please!');
12
13 /**
14 * Include the VBOCore JS class.
15 */
16 VikBooking::getVboApplication()->loadCoreJS();
17
18 // slider libraries
19 $document = JFactory::getDocument();
20 if (VikBooking::loadJquery()) {
21 //JHtml::fetch('jquery.framework', true, true);
22 JHtml::fetch('script', VBO_SITE_URI.'resources/jquery-1.12.4.min.js');
23 }
24 $document->addStyleSheet(VBO_SITE_URI . 'resources/vik-dots-slider.css');
25 JHtml::fetch('script', VBO_SITE_URI . 'resources/vik-dots-slider.js');
26
27 // quote valid until date object
28 $validUntilDt = !empty($this->quoteData->valid_until) ? JFactory::getDate($this->quoteData->valid_until) : null;
29
30 // whether the quote is expired
31 $isExpired = $validUntilDt && $validUntilDt < JFactory::getDate('now') ? true : false;
32
33 // currency symbol
34 $currencysymb = VikBooking::getCurrencySymb();
35
36 if (!$this->quoteData) {
37 ?>
38 <p class="err"><?php echo JText::translate('VBO_QUOTE_ERR_NOTFOUND'); ?></p>
39 <?php
40 // abort
41 return;
42 }
43
44 if ($isExpired) {
45 ?>
46 <p class="err"><?php echo JText::translate('VBO_QUOTE_ERR_EXPIRED'); ?></p>
47 <?php
48 // abort
49 return;
50 }
51
52 // count confirmed, pending, cancelled and overall quote booking solutions
53 $confirmedBookings = array_values(array_filter($this->quoteData->solutions, function($quoteSolution) {
54 return ($quoteSolution->status ?? '') == 'confirmed';
55 }));
56 $pendingBookings = array_values(array_filter($this->quoteData->solutions, function($quoteSolution) {
57 return ($quoteSolution->status ?? '') == 'standby';
58 }));
59 $cancelledBookings = array_values(array_filter($this->quoteData->solutions, function($quoteSolution) {
60 return ($quoteSolution->status ?? '') == 'cancelled';
61 }));
62 $overallBookings = count($this->quoteData->solutions);
63
64 if (!$overallBookings || count($cancelledBookings) == $overallBookings) {
65 // quote has been cancelled completely, raise an error
66 ?>
67 <p class="err"><?php echo JText::translate('VBO_QUOTE_ERR_CANCELLED'); ?></p>
68 <?php
69 // abort
70 return;
71 }
72
73 // tell if the quote has been confirmed
74 $isConfirmed = $confirmedBookings ? true : false;
75
76 // always filter out cancelled booking solutions
77 $this->quoteData->solutions = array_values(array_filter($this->quoteData->solutions, function($quoteSolution) {
78 return ($quoteSolution->status ?? '') != 'cancelled';
79 }));
80
81 // route room-detail URLs only once
82 $roomRoutedUris = array_combine(array_keys($this->roomsData), array_values(array_map(function($roomId) {
83 return VikBooking::externalroute('index.php?option=com_vikbooking&view=roomdetails&roomid=' . $roomId);
84 }, array_keys($this->roomsData))));
85
86 // rate plan meals map
87 $rate_plan_meals_map = [
88 'breakfast' => JText::translate('VBO_MEAL_BREAKFAST'),
89 'lunch' => JText::translate('VBO_MEAL_LUNCH'),
90 'dinner' => JText::translate('VBO_MEAL_DINNER'),
91 ];
92
93 ?>
94
95 <div class="vbo-quote-container">
96
97 <div class="vbo-quote-head">
98 <div class="vbo-quote-head-info">
99 <div class="vbo-quote-head-preinfo">
100 <div class="vbo-quote-head-id"><?php echo JText::translate('VBO_QUOTE'); ?> #<span><?php echo $this->quoteData->uuid; ?></span></div>
101 </div>
102 <h3 class="vbo-quote-head-header"><?php echo trim(sprintf('%s %s', (string) $this->quoteData->first_name, (string) $this->quoteData->last_name)); ?></h3>
103 <p class="vbo-quote-head-description"><?php echo nl2br((string) $this->quoteData->notes); ?></p>
104 </div>
105 <?php
106 if (!$isConfirmed && !empty($this->quoteData->valid_until)) {
107 // calculate remaining time in days/hours/minutes
108 $remainingPeriodDt = $validUntilDt->diff(JFactory::getDate('now'));
109 $daysLeft = (int) ($remainingPeriodDt->days ?? 0);
110 $hoursLeft = (int) ($remainingPeriodDt->h ?? 0);
111 $minutesLeft = (int) ($remainingPeriodDt->i ?? 0);
112 // set remaining components
113 $remainingPeriodParts = [];
114 if ($daysLeft > 0) {
115 // push days left
116 $remainingPeriodParts[] = sprintf('%d %s', $daysLeft, JText::translate($daysLeft === 1 ? 'VBO_DAY' : 'VBO_DAYS'));
117 }
118 if ($hoursLeft > 0 && $daysLeft < 5) {
119 // push hours left
120 $remainingPeriodParts[] = sprintf('%d %s', $hoursLeft, JText::translate($hoursLeft === 1 ? 'VBHOUR' : 'VBHOURS'));
121 }
122 if (!$daysLeft && $hoursLeft < 6 && $minutesLeft > 0) {
123 // push minutes left
124 $remainingPeriodParts[] = sprintf('%d %s', $minutesLeft, JText::translate($minutesLeft === 1 ? 'VBMINUTE' : 'VBMINUTES'));
125 }
126 ?>
127 <div class="vbo-quote-head-validity">
128 <div class="vbo-quote-head-validity-label">
129 <?php VikBookingIcons::e('clock'); ?>
130 <span><?php echo JText::translate('VBO_QUOTE_IS_VALID_UNT'); ?> <strong><?php echo trim(str_replace('(00:00)', '', JHtml::fetch('date', $this->quoteData->valid_until, 'd M Y (H:i)'))); ?></strong></span>
131 <span class="vbo-quote-head-validity-date">— <?php echo strtolower(JText::sprintf('VBO_PERIOD_LEFT', implode(', ', $remainingPeriodParts))); ?></span>
132 </div>
133 </div>
134 <?php
135 }
136 ?>
137 </div>
138
139 <div class="vbo-quote-body">
140 <div class="vbo-quote-solutions-list">
141
142 <?php
143 // scan all quote booking solutions
144 foreach ($this->quoteData->solutions as $solIndex => $quoteSolution) {
145 // tell if the stay dates are on the same year
146 $staySameYear = date('Y', $quoteSolution->checkin) == date('Y', $quoteSolution->checkout);
147 // count total adults, children and rooms
148 $totalAdults = array_sum(array_column($quoteSolution->rooms, 'adults'));
149 $totalChildren = array_sum(array_column($quoteSolution->rooms, 'children'));
150 $totalRooms = count($quoteSolution->rooms);
151 // tell if we should "hide" this quote solution by default
152 $isHid = $isConfirmed && $quoteSolution->status != 'confirmed';
153 // rate components collector
154 $rateComponents = [
155 'applied' => 0,
156 'original' => 0,
157 ];
158 ?>
159 <div class="vbo-quote-solution">
160
161 <div class="vbo-quote-sol-head">
162 <div class="vbo-quote-sol-head-left">
163 <div class="vbo-quote-sol-head-info">
164 <span class="vbo-quote-sol-head-title"><?php echo JText::translate('VBO_SOLUTION'); ?> <span class="vbo-quote-sol-head-numb">#<?php echo ++$solIndex; ?></span></span>
165 <div class="vbo-quote-sol-head-details">
166 <span class="vbo-quote-sol-dates">
167 <?php VikBookingIcons::e('calendar'); ?>
168 <span><?php echo JFactory::getDate(date('Y-m-d H:i:s', $quoteSolution->checkin))->format($staySameYear ? 'd M' : 'd M Y'); ?></span>
169 <?php VikBookingIcons::e('arrow-right'); ?>
170 <span><?php echo JFactory::getDate(date('Y-m-d H:i:s', $quoteSolution->checkout))->format('d M Y'); ?></span>
171 </span>
172 <span class="vbo-quote-sol-party vbo-quote-sol-nights"><?php VikBookingIcons::e('moon'); ?> <?php echo sprintf('%d %s', $quoteSolution->nights, JText::translate($quoteSolution->nights == 1 ? 'VBDAY' : 'VBDAYS')); ?></span>
173 <span class="vbo-quote-sol-party vbo-quote-sol-adults"><?php VikBookingIcons::e('user-friends'); ?> <?php echo sprintf('%d %s', $totalAdults, JText::translate($totalAdults == 1 ? 'VBMAILADULT' : 'VBMAILADULTS')); ?></span>
174 <?php
175 if ($totalChildren) {
176 ?>
177 <span class="vbo-quote-sol-party vbo-quote-sol-children"><?php VikBookingIcons::e('baby'); ?> <?php echo sprintf('%d %s', $totalChildren, JText::translate($totalChildren == 1 ? 'VBMAILCHILD' : 'VBMAILCHILDREN')); ?></span>
178 <?php
179 }
180 ?>
181 </div>
182 </div>
183 </div>
184
185 <div class="vbo-quote-sol-head-right">
186 <div class="vbo-quote-sol-head-price"><?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($quoteSolution->total), $currencysymb, ['<span class="vbo_currency">%s</span>', '<span class="vbo_price">%s</span>']); ?></div>
187 <span class="vbo-quote-sol-toggle" data-visible="<?php echo $isHid ? 0 : 1; ?>"><?php VikBookingIcons::e('chevron-up'); ?></span>
188 </div>
189 </div>
190
191 <div class="vbo-quote-sol-body" style="<?php echo $isHid ? 'display: none;' : ''; ?>">
192 <?php
193 // scan all quote booking solution rooms
194 foreach ($quoteSolution->rooms as $solRoomIndex => $bookingRoom) {
195 // attempt to calculate the average nightly rate for the current booking room
196 $roomNightlyRate = 0;
197 if (!empty($bookingRoom->cust_cost)) {
198 $roomNightlyRate = $bookingRoom->cust_cost / $quoteSolution->nights;
199 } elseif (!empty($bookingRoom->room_cost)) {
200 $roomNightlyRate = $bookingRoom->room_cost / $quoteSolution->nights;
201 }
202
203 // access tariff data, if any
204 $tariffData = !empty($bookingRoom->idtar) ? VBORoomHelper::getInstance()->getTariffData($bookingRoom->idtar) : null;
205
206 // get the rooom booking eligible options, if any
207 $eligibleOptions = VBORoomHelper::getInstance()->getEligibleOptions($bookingRoom->id, [
208 'checkin' => date('Y-m-d', $quoteSolution->checkin),
209 'checkout' => date('Y-m-d', $quoteSolution->checkout),
210 'adults' => $bookingRoom->adults,
211 'children' => $bookingRoom->children,
212 'rate_id' => !empty($bookingRoom->idtar) ? ($tariffData['idprice'] ?? null) : null,
213 '_rooms' => $this->roomsData,
214 ]);
215
216 // handle room rate plan details
217 $ratePlanData = null;
218 if (!empty($bookingRoom->cust_cost) && !empty($bookingRoom->cust_cpolicy_id)) {
219 // we have a custom rate with a cancellation policy selected
220 $ratePlanData = VikBooking::getPriceInfo($bookingRoom->cust_cpolicy_id, $this->vbo_tn);
221 } elseif (!empty($bookingRoom->idtar) && !empty($tariffData['idprice'])) {
222 // use a system rate plan
223 $ratePlanData = VikBooking::getPriceInfo($tariffData['idprice'], $this->vbo_tn);
224 }
225
226 // handle original room rate in case of custom rate
227 $origRoomRateData = null;
228 if (!empty($bookingRoom->cust_cost)) {
229 // read all room rates
230 $av_helper = VikBooking::getAvailabilityInstance(true);
231 $av_helper->ignoreRestrictions(true);
232 $av_helper->ignoreAvailability(true);
233 $av_helper->setStayDates(date('Y-m-d', $quoteSolution->checkin), date('Y-m-d', $quoteSolution->checkout));
234 $av_helper->setRoomParty($bookingRoom->adults, $bookingRoom->children);
235 $roomRates = $av_helper->getRates([
236 'hash' => md5('vbo.e4j.vbo'),
237 'req_type' => 'hotel_availability',
238 'nights' => $av_helper->countNightsOfStay($quoteSolution->checkin, $quoteSolution->checkout),
239 'num_rooms' => 1,
240 'only_rates' => 1,
241 'forced_room_ids' => [$bookingRoom->id],
242 ]);
243 // check if rates were obtained for this stay
244 if ($roomRates[$bookingRoom->id] ?? []) {
245 // obtain room-rate data
246 if ($ratePlanData) {
247 // attempt to extract the connected rate plan
248 $origRoomRateData = array_values(array_filter($roomRates[$bookingRoom->id], function($attRoomRate) use ($ratePlanData) {
249 return ($attRoomRate['idprice'] ?? 0) == $ratePlanData['id'];
250 }))[0] ?? null;
251 }
252 if (!$origRoomRateData) {
253 // fallback onto the last room rate obtained
254 $origRoomRateData = end($roomRates[$bookingRoom->id]) ?: null;
255 }
256 // increase rate components
257 $rateComponents['applied'] += $bookingRoom->cust_cost;
258 $rateComponents['original'] += $origRoomRateData['cost'];
259 }
260 }
261 ?>
262 <div class="vbo-quote-sol-room">
263 <?php
264 if (!empty($this->roomsData[$bookingRoom->id]['img'])) {
265 // build image gallery, if available
266 $gallery_data = [];
267 if (!empty($this->roomsData[$bookingRoom->id]['moreimgs'])) {
268 $moreimages = explode(';;', $this->roomsData[$bookingRoom->id]['moreimgs']);
269 foreach (array_filter($moreimages) as $mimg) {
270 // push thumb URL
271 $gallery_data[] = $mimg;
272 }
273 }
274 ?>
275 <div class="vbo-dots-slider-selector">
276 <a
277 href="<?php echo $roomRoutedUris[$bookingRoom->id] ?? '#'; ?>"
278 target="_blank"
279 class="vbo-quote-sol-roomlink"
280 data-gallery="<?php echo $gallery_data ? JHtml::fetch('esc_attr', implode('|', $gallery_data)) : ''; ?>"
281 >
282 <img class="vbo-quote-sol-img" src="<?php echo VBO_SITE_URI; ?>resources/uploads/<?php echo $this->roomsData[$bookingRoom->id]['img']; ?>" alt="<?php echo JHtml::fetch('esc_attr', ($this->roomsData[$bookingRoom->id]['name'] ?? $bookingRoom->name)); ?>" />
283 </a>
284 </div>
285 <?php
286 }
287 ?>
288 <div class="vbo-quote-sol-room-desc">
289 <div class="vbo-quote-sol-room-info">
290 <span class="vbo-quote-sol-room-title"><?php VikBookingIcons::e('bed'); ?> <a href="<?php echo $roomRoutedUris[$bookingRoom->id] ?? '#'; ?>" target="_blank"><?php echo ($this->roomsData[$bookingRoom->id]['name'] ?? $bookingRoom->name); ?></a></span>
291 <?php
292 if ($roomNightlyRate) {
293 ?>
294 <div class="vbo-quote-sol-room-price-block">
295 <?php
296 if (!empty($origRoomRateData['cost'])) {
297 // display the original room rate like if it was a discounted price
298 $origNightlyRate = $origRoomRateData['cost'] / $quoteSolution->nights;
299 if ($origNightlyRate > $roomNightlyRate) {
300 ?>
301 <span class="vbo-quote-sol-room-price-disc"><?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($origNightlyRate), $currencysymb, ['<span class="vbo_currency">%s</span>', '<span class="vbo_price">%s</span>']); ?> /<?php echo strtolower(JText::translate('VBSEARCHRESNIGHT')); ?></span>
302 <?php
303 }
304 }
305 ?>
306 <span class="vbo-quote-sol-room-price-wrap">
307 <span class="vbo-quote-sol-room-price"><?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($roomNightlyRate), $currencysymb, ['<span class="vbo_currency">%s</span>', '<span class="vbo_price">%s</span>']); ?></span>
308 <span class="vbo-quote-sol-room-price-lbl">/<?php echo strtolower(JText::translate('VBSEARCHRESNIGHT')); ?></span>
309 </span>
310 </div>
311 <?php
312 }
313 ?>
314 </div>
315 <div class="vbo-quote-sol-room-details">
316 <span class="vbo-quote-sol-room-guests">
317 <span><?php VikBookingIcons::e('user-friends'); ?> <?php echo sprintf('%d %s', $bookingRoom->adults, JText::translate($bookingRoom->adults == 1 ? 'VBMAILADULT' : 'VBMAILADULTS')); ?></span>
318 <?php
319 if (($bookingRoom->children ?? 0) > 0) {
320 ?>
321 <span><?php VikBookingIcons::e('baby'); ?> <?php echo sprintf('%d %s', $bookingRoom->children, JText::translate($bookingRoom->children == 1 ? 'VBMAILCHILD' : 'VBMAILCHILDREN')); ?></span>
322 <?php
323 }
324 ?>
325 </span>
326 <span class="vbo-quote-sol-room-policy"><?php echo $ratePlanData['name'] ?? JText::translate('VBOROOMCUSTRATEPLAN'); ?></span>
327 </div>
328 <?php
329 if (!empty($this->roomsData[$bookingRoom->id]['smalldesc'])) {
330 // display the room/listing description
331 // prepare CMS contents depending on platform
332 $this->roomsData[$bookingRoom->id] = VBORoomHelper::getInstance()->prepareCMSContents($this->roomsData[$bookingRoom->id], ['smalldesc']);
333 ?>
334 <div class="vbo-quote-sol-room-description">
335 <?php echo $this->roomsData[$bookingRoom->id]['smalldesc']; ?>
336 </div>
337 <?php
338 }
339 if ($ratePlanData) {
340 // gather meal plan and cancellation policy details
341 $cancellationPolicy = null;
342 $rate_plan_meals = [];
343 if (!empty($ratePlanData['meal_plans'])) {
344 $rate_plan_meals = is_array($ratePlanData['meal_plans']) ? $ratePlanData['meal_plans'] : (array) json_decode($ratePlanData['meal_plans'], true);
345 if (count($rate_plan_meals) === 1 && !strcasecmp(($rate_plan_meals[0] ?? ''), 'breakfast')) {
346 // when only breakfast is included, do not display the meal plans label
347 $rate_plan_meals = [];
348 $ratePlanData['breakfast_included'] = 1;
349 }
350 }
351 ?>
352 <div class="vbo-quote-sol-room-policy-details">
353 <?php
354 if ($rate_plan_meals) {
355 $rate_plan_meals_incl = array_map(function($rate_plan_meal) use ($rate_plan_meals_map) {
356 return $rate_plan_meals_map[$rate_plan_meal] ?? $rate_plan_meal;
357 }, $rate_plan_meals);
358 ?>
359 <p class="vbo-quote-sol-room-policy-meals"><?php echo rtrim(JText::translate('VBO_MEAL_PLANS_INCL'), ':') . ': ' . implode(', ', $rate_plan_meals_incl); ?></p>
360 <?php
361 } elseif (!empty($ratePlanData['breakfast_included'])) {
362 ?>
363 <p class="vbo-quote-sol-room-policy-meals"><?php echo JText::translate('VBBREAKFASTINCLUDED'); ?></p>
364 <?php
365 }
366 if (!empty($ratePlanData['free_cancellation'])) {
367 // refundable rate
368 if ($ratePlanData['canc_deadline'] > 0) {
369 ?>
370 <p class="vbo-quote-sol-room-policy-refund"><?php echo JText::sprintf('VBFREECANCELLATIONWITHIN', (int) $ratePlanData['canc_deadline']); ?></p>
371 <?php
372 } else {
373 ?>
374 <p class="vbo-quote-sol-room-policy-refund"><?php echo JText::translate('VBFREECANCELLATION'); ?></p>
375 <?php
376 }
377 // check if we have a custom cancellation policy to display
378 if (!empty($ratePlanData['canc_policy'])) {
379 $cancellationPolicy = strpos($ratePlanData['canc_policy'], '<') !== false ? $ratePlanData['canc_policy'] : nl2br($ratePlanData['canc_policy']);
380 }
381 } elseif ($ratePlanData && empty($ratePlanData['free_cancellation'])) {
382 // non-refundable rate
383 ?>
384 <p class="vbo-quote-sol-room-policy-refund" data-non-refundable="1"><?php echo JText::translate('VBONONREFUNDRATE'); ?></p>
385 <?php
386 }
387 if ($cancellationPolicy) {
388 ?>
389 <p class="vbo-quote-sol-room-policy-info"><?php echo $cancellationPolicy; ?></p>
390 <?php
391 }
392 ?>
393 </div>
394 <?php
395 }
396 if (!empty($bookingRoom->optionals) || !empty($bookingRoom->extracosts)) {
397 ?>
398 <ul class="vbo-quote-sol-room-extras">
399 <?php
400 // obtain the list of computed room booking options
401 $roomOptsData = !empty($bookingRoom->optionals) ? VBORoomHelper::getInstance()->computeBookingOptions((array) $bookingRoom, $eligibleOptions) : [];
402
403 // translate booking option records, if any
404 $this->vbo_tn->translateContents($roomOptsData, '#__vikbooking_optionals');
405
406 // scan computed room booking options, if any
407 foreach ($roomOptsData as $roomOptData) {
408 ?>
409 <li><?php VikBookingIcons::e('toolbox'); ?> <?php echo $roomOptData['name']; ?> <span class="vbo-quote-sol-room-features-price"><?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($roomOptData['cost']), $currencysymb, ['<span class="vbo_currency">%s</span>', '<span class="vbo_price">%s</span>']); ?></span></li>
410 <?php
411 }
412
413 // scan all extra services, if any
414 foreach ((array) ($bookingRoom->extracosts ?? []) as $extraService) {
415 ?>
416 <li><?php VikBookingIcons::e('toolbox'); ?> <?php echo $extraService['name']; ?> <span class="vbo-quote-sol-room-features-price"><?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($extraService['cost']), $currencysymb, ['<span class="vbo_currency">%s</span>', '<span class="vbo_price">%s</span>']); ?></span></li>
417 <?php
418 }
419 ?>
420 </ul>
421 <?php
422 }
423 ?>
424 </div>
425 </div>
426 <?php
427 }
428 ?>
429 </div>
430 <div class="vbo-quote-sol-footer" style="<?php echo $isHid ? 'display: none;' : ''; ?>">
431 <div class="vbo-quote-sol-total">
432 <div class="vbo-quote-sol-total-label"><?php echo JText::translate('VBTOTAL'); ?></div>
433 <div class="vbo-quote-sol-total-price">
434 <?php
435 if ($rateComponents['applied'] && $rateComponents['original'] > $rateComponents['applied']) {
436 // calculate the total cost without the custom discounted rate
437 $originalTotal = $quoteSolution->total - $rateComponents['applied'] + $rateComponents['original'];
438 // display striked-through amount
439 ?>
440 <span class="vbo-quote-sol-total-price-disc"><?php
441 echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($originalTotal), $currencysymb, ['<span class="vbo_currency">%s</span>', '<span class="vbo_price">%s</span>']);
442 ?></span>
443 <?php
444 }
445
446 // display quote solution total amount
447 echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($quoteSolution->total), $currencysymb, ['<span class="vbo_currency">%s</span>', '<span class="vbo_price">%s</span>']);
448 ?>
449 </div>
450 <div class="vbo-quote-sol-total-subtext"><?php
451 echo implode(', ', [
452 sprintf('%d %s', $totalRooms, JText::translate($totalRooms === 1 ? 'VBSEARCHRESROOM' : 'VBSEARCHRESROOMS')),
453 sprintf('%d %s', $quoteSolution->nights, JText::translate($quoteSolution->nights == 1 ? 'VBSEARCHRESNIGHT' : 'VBSEARCHRESNIGHTS')),
454 ]);
455 ?></div>
456 </div>
457 <div class="vbo-quote-sol-footer-actions">
458 <?php
459 if (!$isConfirmed && $quoteSolution->status == 'standby') {
460 // display button to accept the quote booking solution
461 ?>
462 <a class="btn vbo-accept" href="<?php echo VikBooking::externalroute('index.php?option=com_vikbooking&view=booking&sid=' . ($quoteSolution->sid ?? '') . '&ts=' . ($quoteSolution->ts ?? '')); ?>"><?php VikBookingIcons::e('check'); ?> <?php echo JText::translate('VBO_ACCEPT'); ?></a>
463 <div class="vbo-quote-sol-footer-comment">
464 <span><?php echo JText::translate('VBO_PAY_TO_CONFIRM'); ?></span>
465 </div>
466 <?php
467 } elseif ($quoteSolution->status == 'confirmed') {
468 // display label to tell the quote booking solution was confirmed
469 ?>
470 <a class="btn vbo-success" href="<?php echo VikBooking::externalroute('index.php?option=com_vikbooking&view=booking&sid=' . ($quoteSolution->sid ?? '') . '&ts=' . ($quoteSolution->ts ?? '')); ?>"><?php VikBookingIcons::e('check-double'); ?> <?php echo JText::translate('VBCONFIRMED'); ?></a>
471 <?php
472 }
473 ?>
474 </div>
475 </div>
476 </div>
477 <?php
478 }
479 ?>
480 </div>
481 </div>
482
483 </div>
484
485 <script type="text/javascript">
486 VBOCore.DOMLoaded(() => {
487
488 /**
489 * Register listener to toggle the visibility of the quote solution details.
490 */
491 document.querySelectorAll('.vbo-quote-solution').forEach((solutionEl) => {
492 const headEl = solutionEl.querySelector('.vbo-quote-sol-head');
493 const toggleEl = solutionEl.querySelector('.vbo-quote-sol-toggle');
494 headEl.addEventListener('click', (e) => {
495 let isVisible = toggleEl.getAttribute('data-visible') == '1';
496 let solutionBodyEl = solutionEl.querySelector('.vbo-quote-sol-body');
497 let solutionFooterEl = solutionEl.querySelector('.vbo-quote-sol-footer');
498 let icnEl = toggleEl.querySelector('i');
499 icnEl.setAttribute('class', '');
500 if (isVisible) {
501 // hide elements
502 solutionBodyEl.style.display = 'none';
503 solutionFooterEl.style.display = 'none';
504 icnEl.classList.add(...('<?php echo VikBookingIcons::i('chevron-down'); ?>'.split(' ')));
505 } else {
506 // show elements
507 solutionBodyEl.style.display = '';
508 solutionFooterEl.style.display = '';
509 icnEl.classList.add(...('<?php echo VikBookingIcons::i('chevron-up'); ?>'.split(' ')));
510 }
511 // toggle data attribute
512 toggleEl.setAttribute('data-visible', isVisible ? 0 : 1);
513 });
514
515 });
516
517 /**
518 * Define room galleries.
519 */
520 document.querySelectorAll('.vbo-dots-slider-selector').forEach((sliderEl) => {
521 let galleryEl = sliderEl.querySelector('.vbo-quote-sol-roomlink');
522 let galleryData = (galleryEl.getAttribute('data-gallery') || '').split('|').filter(i => i);
523 if (!galleryData.length) {
524 return;
525 }
526 let thumbsBaseUri = '<?php echo VBO_SITE_URI . 'resources/uploads/thumb_'; ?>';
527 let images = [];
528 for (let i = 0; i < galleryData.length; i++) {
529 if (!galleryData[i].length) {
530 continue;
531 }
532 images.push(thumbsBaseUri + galleryData[i]);
533 }
534 // move original main photo and make it hidden
535 let mainPhoto = galleryEl.querySelector('img');
536 if (mainPhoto) {
537 mainPhoto.style.display = 'none';
538 galleryEl.closest('.vbo-quote-sol-room').append(mainPhoto);
539 }
540 // render slider
541 let slideHref = galleryEl.getAttribute('href');
542 let slideClass = galleryEl.getAttribute('class');
543 jQuery(sliderEl).html('').vikDotsSlider({
544 images: images,
545 navButPrevContent: '<?php VikBookingIcons::e('chevron-left'); ?>',
546 navButNextContent: '<?php VikBookingIcons::e('chevron-right'); ?>',
547 onDisplaySlide: function() {
548 let content = jQuery(this).children().clone(true, true);
549 <?php
550 if (VBOPlatformDetection::isWordPress()) {
551 /**
552 * @wponly We do not re-construct the A tag.
553 * We just append the slide image.
554 */
555 ?>
556 jQuery(this).html('').append(content);
557 <?php
558 } else {
559 ?>
560 let link = jQuery('<a target="_blank"></a>').attr('href', slideHref).attr('class', slideClass).append(content);
561 jQuery(this).html('').append(link);
562 <?php
563 }
564 ?>
565 }
566 });
567 });
568
569 });
570 </script>