PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.9
VikBooking Hotel Booking Engine & PMS v1.8.9
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.9, at site/views/quote/tmpl/default.php

565 lines 31.8 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);
221 } elseif (!empty($bookingRoom->idtar) && !empty($tariffData['idprice'])) {
222 // use a system rate plan
223 $ratePlanData = VikBooking::getPriceInfo($tariffData['idprice']);
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', $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 $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 foreach ($roomOptsData as $roomOptData) {
403 ?>
404 <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>
405 <?php
406 }
407
408 // scan all extra services, if any
409 foreach ((array) ($bookingRoom->extracosts ?? []) as $extraService) {
410 ?>
411 <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>
412 <?php
413 }
414 ?>
415 </ul>
416 <?php
417 }
418 ?>
419 </div>
420 </div>
421 <?php
422 }
423 ?>
424 </div>
425 <div class="vbo-quote-sol-footer" style="<?php echo $isHid ? 'display: none;' : ''; ?>">
426 <div class="vbo-quote-sol-total">
427 <div class="vbo-quote-sol-total-label"><?php echo JText::translate('VBTOTAL'); ?></div>
428 <div class="vbo-quote-sol-total-price">
429 <?php
430 if ($rateComponents['applied'] && $rateComponents['original'] > $rateComponents['applied']) {
431 // calculate the total cost without the custom discounted rate
432 $originalTotal = $quoteSolution->total - $rateComponents['applied'] + $rateComponents['original'];
433 // display striked-through amount
434 ?>
435 <span class="vbo-quote-sol-total-price-disc"><?php
436 echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($originalTotal), $currencysymb, ['<span class="vbo_currency">%s</span>', '<span class="vbo_price">%s</span>']);
437 ?></span>
438 <?php
439 }
440
441 // display quote solution total amount
442 echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($quoteSolution->total), $currencysymb, ['<span class="vbo_currency">%s</span>', '<span class="vbo_price">%s</span>']);
443 ?>
444 </div>
445 <div class="vbo-quote-sol-total-subtext"><?php
446 echo implode(', ', [
447 sprintf('%d %s', $totalRooms, JText::translate($totalRooms === 1 ? 'VBSEARCHRESROOM' : 'VBSEARCHRESROOMS')),
448 sprintf('%d %s', $quoteSolution->nights, JText::translate($quoteSolution->nights == 1 ? 'VBSEARCHRESNIGHT' : 'VBSEARCHRESNIGHTS')),
449 ]);
450 ?></div>
451 </div>
452 <div class="vbo-quote-sol-footer-actions">
453 <?php
454 if (!$isConfirmed && $quoteSolution->status == 'standby') {
455 // display button to accept the quote booking solution
456 ?>
457 <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>
458 <div class="vbo-quote-sol-footer-comment">
459 <span><?php echo JText::translate('VBO_PAY_TO_CONFIRM'); ?></span>
460 </div>
461 <?php
462 } elseif ($quoteSolution->status == 'confirmed') {
463 // display label to tell the quote booking solution was confirmed
464 ?>
465 <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>
466 <?php
467 }
468 ?>
469 </div>
470 </div>
471 </div>
472 <?php
473 }
474 ?>
475 </div>
476 </div>
477
478 </div>
479
480 <script type="text/javascript">
481 VBOCore.DOMLoaded(() => {
482
483 /**
484 * Register listener to toggle the visibility of the quote solution details.
485 */
486 document.querySelectorAll('.vbo-quote-solution').forEach((solutionEl) => {
487 const headEl = solutionEl.querySelector('.vbo-quote-sol-head');
488 const toggleEl = solutionEl.querySelector('.vbo-quote-sol-toggle');
489 headEl.addEventListener('click', (e) => {
490 let isVisible = toggleEl.getAttribute('data-visible') == '1';
491 let solutionBodyEl = solutionEl.querySelector('.vbo-quote-sol-body');
492 let solutionFooterEl = solutionEl.querySelector('.vbo-quote-sol-footer');
493 let icnEl = toggleEl.querySelector('i');
494 icnEl.setAttribute('class', '');
495 if (isVisible) {
496 // hide elements
497 solutionBodyEl.style.display = 'none';
498 solutionFooterEl.style.display = 'none';
499 icnEl.classList.add(...('<?php echo VikBookingIcons::i('chevron-down'); ?>'.split(' ')));
500 } else {
501 // show elements
502 solutionBodyEl.style.display = '';
503 solutionFooterEl.style.display = '';
504 icnEl.classList.add(...('<?php echo VikBookingIcons::i('chevron-up'); ?>'.split(' ')));
505 }
506 // toggle data attribute
507 toggleEl.setAttribute('data-visible', isVisible ? 0 : 1);
508 });
509
510 });
511
512 /**
513 * Define room galleries.
514 */
515 document.querySelectorAll('.vbo-dots-slider-selector').forEach((sliderEl) => {
516 let galleryEl = sliderEl.querySelector('.vbo-quote-sol-roomlink');
517 let galleryData = (galleryEl.getAttribute('data-gallery') || '').split('|').filter(i => i);
518 if (!galleryData.length) {
519 return;
520 }
521 let thumbsBaseUri = '<?php echo VBO_SITE_URI . 'resources/uploads/thumb_'; ?>';
522 let images = [];
523 for (let i = 0; i < galleryData.length; i++) {
524 if (!galleryData[i].length) {
525 continue;
526 }
527 images.push(thumbsBaseUri + galleryData[i]);
528 }
529 // move original main photo and make it hidden
530 let mainPhoto = galleryEl.querySelector('img');
531 if (mainPhoto) {
532 mainPhoto.style.display = 'none';
533 galleryEl.closest('.vbo-quote-sol-room').append(mainPhoto);
534 }
535 // render slider
536 let slideHref = galleryEl.getAttribute('href');
537 let slideClass = galleryEl.getAttribute('class');
538 jQuery(sliderEl).html('').vikDotsSlider({
539 images: images,
540 navButPrevContent: '<?php VikBookingIcons::e('chevron-left'); ?>',
541 navButNextContent: '<?php VikBookingIcons::e('chevron-right'); ?>',
542 onDisplaySlide: function() {
543 let content = jQuery(this).children().clone(true, true);
544 <?php
545 if (VBOPlatformDetection::isWordPress()) {
546 /**
547 * @wponly We do not re-construct the A tag.
548 * We just append the slide image.
549 */
550 ?>
551 jQuery(this).html('').append(content);
552 <?php
553 } else {
554 ?>
555 let link = jQuery('<a target="_blank"></a>').attr('href', slideHref).attr('class', slideClass).append(content);
556 jQuery(this).html('').append(link);
557 <?php
558 }
559 ?>
560 }
561 });
562 });
563
564 });
565 </script>