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 / views / search / tmpl / default_interactive_map.php

default_interactive_map.php in VikBooking Hotel Booking Engine & PMS 1.8.15, at site/views/search/tmpl/default_interactive_map.php

798 lines 33.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 Alessio Gaggii - e4j - Extensionsforjoomla.com
6 * @copyright Copyright (C) 2018 e4j - Extensionsforjoomla.com. 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 $geo = VikBooking::getGeocodingInstance();
14
15 $currencysymb = VikBooking::getCurrencySymb();
16 $vat_included = VikBooking::ivaInclusa();
17 $tax_summary = (!$vat_included && VikBooking::showTaxOnSummaryOnly());
18
19 $geo_info_complete = true;
20 $global_geo_params = null;
21 $rooms_geo_params = [];
22 $rooms_features = [];
23 $rooms_galleries = [];
24 $rooms_units_booked = [];
25 $rooms_units_pos = new stdClass;
26 $markers_parties = new stdClass;
27
28 // lang vars for JS
29 JText::script('VBSEARCHROOMNUM');
30 JText::script('VBSEARCHRESADULT');
31 JText::script('VBSEARCHRESADULTS');
32 JText::script('VBSEARCHRESCHILD');
33 JText::script('VBSEARCHRESCHILDREN');
34 JText::script('VBSELECTR');
35 JText::script('VBO_CANCEL_SELECTION');
36
37 // we build a fake booking array to count the units booked for the various rooms
38 $booking_data = [
39 'id' => 0,
40 'checkin' => $this->checkin,
41 'checkout' => $this->checkout,
42 ];
43
44 foreach ($this->res as $party_num => $party_rooms) {
45 foreach ($party_rooms as $party_room) {
46 foreach ($party_room as $room_rplan) {
47 if (empty($room_rplan['idroom']) || empty($room_rplan['params'])) {
48 // unable to proceed
49 $geo_info_complete = false;
50 break 3;
51 }
52 if (isset($rooms_geo_params[$room_rplan['idroom']])) {
53 // room already parsed
54 continue;
55 }
56 $rparams = json_decode($room_rplan['params']);
57 $geo_params = $geo->getRoomGeoParams($rparams);
58 if (is_object($geo_params) && isset($geo_params->enabled) && $geo_params->enabled) {
59 if (!isset($global_geo_params)) {
60 // we use the global geo params of the first room returned
61 $global_geo_params = $geo_params;
62 }
63 // push room geo params
64 $rooms_geo_params[$room_rplan['idroom']] = $geo_params;
65 // check if distinctive features are available
66 $rooms_features[$room_rplan['idroom']] = [];
67 $room_features = VikBooking::getRoomParam('features', $room_rplan['params']);
68 $room_features = !is_array($room_features) ? [] : $room_features;
69 foreach ($room_features as $rindex => $rfeatures) {
70 if (!is_array($rfeatures) || !$rfeatures) {
71 continue;
72 }
73 foreach ($rfeatures as $featname => $featval) {
74 if (empty($featval)) {
75 continue;
76 }
77 // use the first distinctive feature
78 $tn_featname = JText::translate($featname);
79 if ($tn_featname == $featname) {
80 // no translation was applied
81 if (VBOPlatformDetection::isWordPress()) {
82 // try to apply a translation through Gettext even if we have to pass a variable
83 $tn_featname = __($featname);
84 } else {
85 // convert the string to a hypothetical INI constant
86 $ini_constant = str_replace(' ', '_', strtoupper($featname));
87 $tn_featname = JText::translate($ini_constant);
88 $tn_featname = $tn_featname == $ini_constant ? $featname : $tn_featname;
89 }
90 }
91 $rooms_features[$room_rplan['idroom']][$rindex] = $tn_featname . ' ' . $featval;
92 break;
93 }
94 }
95 // build rooms gallery
96 $rooms_galleries[$room_rplan['idroom']] = [];
97 if (!empty($room_rplan['moreimgs'])) {
98 $moreimages = explode(';;', $room_rplan['moreimgs']);
99 foreach ($moreimages as $mimg) {
100 if (empty($mimg)) {
101 continue;
102 }
103 // push thumb URL
104 array_push($rooms_galleries[$room_rplan['idroom']], VBO_SITE_URI . 'resources/uploads/thumb_' . $mimg);
105 }
106 } elseif (!empty($room_rplan['img'])) {
107 // push main image URL
108 array_push($rooms_galleries[$room_rplan['idroom']], VBO_SITE_URI . 'resources/uploads/' . $room_rplan['img']);
109 }
110 // count room units booked
111 $rooms_units_booked[$room_rplan['idroom']] = VikBooking::getRoomUnitNumsUnavailable($booking_data, $room_rplan['idroom']);
112 } else {
113 // unable to proceed
114 $geo_info_complete = false;
115 break 3;
116 }
117 // we parse just the first rate plan
118 break;
119 }
120 }
121 }
122
123 // compose markers positions
124 foreach ($rooms_geo_params as $idroom => $geo_params) {
125 if (isset($geo_params->units_pos) && is_object($geo_params->units_pos) && count(get_object_vars($geo_params->units_pos))) {
126 $rooms_units_pos->{$idroom} = $geo_params->units_pos;
127 }
128 }
129
130 if ($geo->isSupported() && $geo_info_complete === true && count($rooms_geo_params) && count(get_object_vars($rooms_units_pos))) {
131 // load assets
132 $geo->loadAssets([
133 'callback' => 'vbo_gm_ready',
134 'libraries' => 'marker',
135 'loading' => 'async',
136 ]);
137
138 // check if ground overlay is implemented by the first room that returns the global geo params
139 $current_goverlay = null;
140 if ((int)$global_geo_params->goverlay > 0 && !empty($global_geo_params->overlay_img) && !empty($global_geo_params->overlay_south)) {
141 // ground overlay is available
142 $current_goverlay = new stdClass;
143 $current_goverlay->url = $global_geo_params->overlay_img;
144 $current_goverlay->south = (float)$global_geo_params->overlay_south;
145 $current_goverlay->west = (float)$global_geo_params->overlay_west;
146 $current_goverlay->north = (float)$global_geo_params->overlay_north;
147 $current_goverlay->east = (float)$global_geo_params->overlay_east;
148 }
149
150 // compose markers parties
151 foreach ($this->res as $party_num => $party_rooms) {
152 $markers_parties->{$party_num} = [];
153 $rooms_in_party = [];
154 foreach ($party_rooms as $party_room) {
155 foreach ($party_room as $room_rplan) {
156 if (empty($room_rplan['idroom']) || !isset($rooms_geo_params[$room_rplan['idroom']])) {
157 continue;
158 }
159 if (!in_array($room_rplan['idroom'], $rooms_in_party)) {
160 $raw_roomcost = $tax_summary ? $room_rplan['cost'] : VikBooking::sayCostPlusIva($room_rplan['cost'], $room_rplan['idprice']);
161 // build room info object
162 $room_info = new stdClass;
163 $room_info->id = $room_rplan['idroom'];
164 $room_info->name = $room_rplan['name'];
165 $room_info->cost = VikBooking::numberFormat($raw_roomcost);
166 $room_info->units = isset($room_rplan['unitsavail']) ? (int)$room_rplan['unitsavail'] : (int)$room_rplan['units'];
167 $room_info->tot_markers = 1;
168 if ($rooms_geo_params[$room_rplan['idroom']]->markers_multi > 0 && $room_rplan['units'] > 1) {
169 $room_info->tot_markers = (int)$room_rplan['units'];
170 }
171 // check if a promo was applied
172 if (isset($room_rplan['promotion']) && isset($room_rplan['promotion']['discount'])) {
173 if ($room_rplan['promotion']['discount']['pcent']) {
174 $prev_amount = $raw_roomcost * 100 / (100 - $room_rplan['promotion']['discount']['amount']);
175 } else {
176 $prev_amount = $raw_roomcost + $room_rplan['promotion']['discount']['amount'];
177 }
178 if ($prev_amount > 0) {
179 $room_info->prev_cost = VikBooking::numberFormat($prev_amount);
180 }
181 }
182 // push room info
183 array_push($markers_parties->{$party_num}, $room_info);
184 $rooms_in_party[] = $room_rplan['idroom'];
185 }
186 // we parse just the first rate plan
187 break;
188 }
189 }
190 }
191 ?>
192 <div class="vbo-searchresults-geo-wrapper">
193 <div class="vbo-geo-wrapper">
194 <?php
195 if ($this->roomsnum > 1) {
196 ?>
197 <div class="vbo-geomap-uicontrol">
198 <div class="vbo-geomap-uicontrol-inner">
199 <span class="vbo-geomap-uicontrol-partynum"><?php echo JText::translate('VBSEARCHROOMNUM'); ?> 1</span>
200 <span class="vbo-geomap-uicontrol-partyguests">
201 <?php VikBookingIcons::e('users', 'vbo-pref-color-text'); ?>
202 <?php echo $this->arrpeople[1]['adults']; ?>
203 <?php echo ($this->arrpeople[1]['adults'] == 1 ? JText::translate('VBSEARCHRESADULT') : JText::translate('VBSEARCHRESADULTS')); ?>
204 <?php echo ($this->showchildren && $this->arrpeople[1]['children'] > 0 ? ", ".$this->arrpeople[1]['children']." ".($this->arrpeople[1]['children'] == 1 ? JText::translate('VBSEARCHRESCHILD') : JText::translate('VBSEARCHRESCHILDREN')) : ""); ?>
205 </span>
206 </div>
207 </div>
208 <?php
209 }
210 ?>
211 <div class="vbo-results-geo-map-container">
212 <?php
213 if ($this->roomsnum > 1) {
214 ?>
215 <div class="vbo-geomap-minicart" style="display: none;">
216 <div class="vbo-geomap-minicart-inner">
217 <div class="vbo-geomap-minicart-head"><?php VikBookingIcons::e('shopping-cart'); ?></div>
218 <div class="vbo-geomap-minicart-body"></div>
219 </div>
220 </div>
221 <?php
222 }
223 ?>
224 <div id="vbo-results-geo-map" data-height="<?php echo $geo->getRoomGeoParams($global_geo_params, 'height', 300); ?>"></div>
225 </div>
226 </div>
227 </div>
228
229 <script type="text/javascript">
230 /**
231 * Define global scope vars
232 */
233 var vbo_geomap = null,
234 vbo_geo_currency = '<?php echo $currencysymb; ?>';
235 vbo_geo_currency_pos = '<?php echo VikBooking::getCurrencyPosition(); ?>';
236 vbo_geo_showchildren = <?php echo (int)$this->showchildren; ?>,
237 vbo_geo_rooms_galleries = <?php echo json_encode($rooms_galleries); ?>,
238 vbo_geo_guestparties = <?php echo json_encode($this->arrpeople); ?>,
239 vbo_geomarker_parties = <?php echo json_encode($markers_parties); ?>,
240 vbo_rooms_units_booked = <?php echo json_encode($rooms_units_booked); ?>,
241 vbo_rooms_units_selected = {},
242 vbo_geomarker_units = {},
243 vbo_geomarker_units_pos = <?php echo json_encode($rooms_units_pos); ?>,
244 vbo_info_markers = {},
245 vbo_info_markers_helper = <?php echo json_encode($rooms_features); ?>,
246 vbo_ground_overlay = null,
247 vbo_dbground_overlay = <?php echo is_object($current_goverlay) ? json_encode($current_goverlay) : 'null'; ?>,
248 vbo_geomap_booking_step = 1
249 vbo_geomap_total_steps = <?php echo $this->roomsnum; ?>;
250
251 // turn off the use of the modal/dialog window in case of multi rooms booking
252 var vbo_multirooms_dialog = false;
253
254 /**
255 * Returns the room information.
256 */
257 function vboGetGeoRoomPartyInformation(idroom) {
258 if (!vbo_geomarker_parties.hasOwnProperty(vbo_geomap_booking_step)) {
259 return null;
260 }
261 for (var room in vbo_geomarker_parties[vbo_geomap_booking_step]) {
262 if (!vbo_geomarker_parties[vbo_geomap_booking_step].hasOwnProperty(room)) {
263 continue;
264 }
265 if (vbo_geomarker_parties[vbo_geomap_booking_step][room]['id'] == idroom) {
266 return vbo_geomarker_parties[vbo_geomap_booking_step][room];
267 }
268 }
269 return null;
270 }
271
272 /**
273 * Generates the HTML content for the units marker infowindow.
274 */
275 function vboGeoInfoMarkerContent(idroom, index, marker_title, rcost, rprevcost) {
276 var rdomselector = '#vbcontainer' + vbo_geomap_booking_step + '_' + idroom;
277 var infowin_cont = '';
278 infowin_cont += '<div class="vbo-geomarker-infowin-wrap">';
279 // check if gallery is available
280 if (vbo_geo_rooms_galleries.hasOwnProperty(idroom) && vbo_geo_rooms_galleries[idroom].length) {
281 infowin_cont += ' <div class="vbo-geomarker-infowin-room-gallery" data-idroom="' + idroom + '"></div>';
282 }
283 infowin_cont += ' <div class="vbo-geomarker-room-title">';
284 if (jQuery(rdomselector).find('.vbmodalframe').length) {
285 infowin_cont += ' <span class="vbo-geomarker-rstay-info" onclick="jQuery(\'' + rdomselector + '\').find(\'.vbmodalframe\').first().trigger(\'click\');"><?php VikBookingIcons::e('info-circle'); ?></span>';
286 }
287 infowin_cont += ' <span class="vbo-geomarker-room-title-cont">' + marker_title + '</span>';
288 infowin_cont += ' </div>';
289 infowin_cont += ' <div class="vbo-geomarker-priceinfo">';
290 if (rprevcost != null) {
291 infowin_cont += ' <span class="vbo-geomarker-priceinfo-cost-beforedisc">' + (vbo_geo_currency_pos === 'after' ? '<span class="vbo_price">' + rprevcost + '</span> <span class="vbo_currency">' + vbo_geo_currency + '</span>' : '<span class="vbo_currency">' + vbo_geo_currency + '</span> <span class="vbo_price">' + rprevcost + '</span>') + '</span>';
292 }
293 infowin_cont += ' <span class="vbo-geomarker-priceinfo-cost">' + (vbo_geo_currency_pos === 'after' ? '<span class="vbo_price">' + rcost + '</span> <span class="vbo_currency">' + vbo_geo_currency + '</span>' : '<span class="vbo_currency">' + vbo_geo_currency + '</span> <span class="vbo_price">' + rcost + '</span>') + '</span>';
294 infowin_cont += ' </div>';
295 infowin_cont += ' <div class="vbo-geomarker-bookroom">';
296 infowin_cont += ' <button type="button" class="btn vbo-pref-color-btn" onclick="vboInteractiveGeoMapSelectRoom(' + idroom + ', ' + index + ');">' + Joomla.JText._('VBSELECTR') + '</button>';
297 infowin_cont += ' </div>';
298 infowin_cont += '</div>';
299
300 return infowin_cont;
301 }
302
303 /**
304 * Selects one room and index for booking the current party.
305 */
306 function vboInteractiveGeoMapSelectRoom(idroom, index) {
307 var current_step = vbo_geomap_booking_step;
308 var room_info = vboGetGeoRoomPartyInformation(idroom);
309 var tot_markers = room_info !== null ? room_info['tot_markers'] : 1;
310 // register room index hidden field
311 if (jQuery('#vbo-geo-roomindex-' + current_step).length) {
312 jQuery('#vbo-geo-roomindex-' + current_step).remove();
313 }
314 // if room does not support sub-units or has markers for sub-units, index value is 0 so that we can skip it later on in the booking process
315 var room_index_val = tot_markers === 1 ? 0 : index;
316 jQuery('#vbselectroomform').append('<input type="hidden" id="vbo-geo-roomindex-' + current_step + '" name="roomindex[]" value="' + room_index_val + '" />');
317 // trigger the main function to select the room
318 var res = vbSelectRoom(current_step, idroom);
319 if (res === false) {
320 jQuery('#vbo-geo-roomindex-' + current_step).remove();
321 return;
322 }
323 // check if the next step should be displayed
324 if (vbo_geomap_total_steps > 1 && vbo_geomap_total_steps > current_step) {
325 // register the room index selected
326 vbo_rooms_units_selected[current_step] = {
327 idroom: idroom,
328 index: index
329 };
330 // populate mini-cart
331 if (jQuery('.vbo-geomap-minicart').length) {
332 var rcost = room_info !== null ? room_info['cost'] : '';
333 var rname = room_info !== null ? room_info['name'] : '';
334 var room_title = rname + ' #' + (index + '');
335 if (tot_markers === 1) {
336 room_title = rname;
337 } else if (vbo_info_markers_helper.hasOwnProperty(idroom) && vbo_info_markers_helper[idroom].hasOwnProperty(index)) {
338 room_title = rname + ' - ' + vbo_info_markers_helper[idroom][index];
339 }
340 // get party info
341 var vbo_uicontrol_guests = '';
342 vbo_uicontrol_guests += '<?php VikBookingIcons::e('users', 'vbo-pref-color-text'); ?> ';
343 vbo_uicontrol_guests += vbo_geo_guestparties[current_step]['adults'] + ' ';
344 vbo_uicontrol_guests += (vbo_geo_guestparties[current_step]['adults'] == 1 ? Joomla.JText._('VBSEARCHRESADULT') : Joomla.JText._('VBSEARCHRESADULTS')) + ' ';
345 vbo_uicontrol_guests += (vbo_geo_showchildren > 0 && vbo_geo_guestparties[current_step]['children'] > 1 ? (', ' + vbo_geo_guestparties[current_step]['children'] + ' ' + (vbo_geo_guestparties[current_step]['children'] == 1 ? Joomla.JText._('VBSEARCHRESCHILD') : Joomla.JText._('VBSEARCHRESCHILDREN'))) : '');
346 var minicart_row = '';
347 minicart_row += '<div class="vbo-geomap-minicart-row" data-minicartrow="' + current_step + '">';
348 minicart_row += ' <div class="vbo-geomap-minicart-party">';
349 minicart_row += ' <span class="vbo-geomap-minicart-party-num">' + Joomla.JText._('VBSEARCHROOMNUM') + ' ' + current_step + '</span>';
350 minicart_row += ' <span class="vbo-geomap-minicart-party-guests">' + vbo_uicontrol_guests + '</span>';
351 minicart_row += ' </div>';
352 minicart_row += ' <div class="vbo-geomap-minicart-room">';
353 minicart_row += ' <span class="vbo-geomap-minicart-room-name">' + room_title + '</span>';
354 minicart_row += ' <span class="vbo-geomap-minicart-room-price">' + (vbo_geo_currency_pos === 'after' ? '<span class="vbo_price">' + rcost + '</span> <span class="vbo_currency">' + vbo_geo_currency + '</span>' : '<span class="vbo_currency">' + vbo_geo_currency + '</span> <span class="vbo_price">' + rcost + '</span>') + '</span>';
355 minicart_row += ' <span class="vbo-geomap-minicart-room-trash" onclick="vboInteractiveGeoMapDeselectRoom(' + current_step + ', ' + idroom + ');"><?php VikBookingIcons::e('trash'); ?></span>';
356 minicart_row += ' </div>';
357 minicart_row += '</div>';
358 jQuery('.vbo-geomap-minicart-body').append(minicart_row).removeClass('vbo-geomap-minicart-body-hid').addClass('vbo-geomap-minicart-body-enter');
359 jQuery('.vbo-geomap-minicart').show();
360 // register closing event for the minicart body
361 setTimeout(function() {
362 jQuery('.vbo-geomap-minicart-body').removeClass('vbo-geomap-minicart-body-enter').addClass('vbo-geomap-minicart-body-hid');
363 }, 1500);
364 }
365 // increase step for next selection
366 vbo_geomap_booking_step++;
367 // reload map for the next booking party
368 vboInitInteractiveGeoMap();
369 return;
370 }
371 if (res === true) {
372 // form is being submitted
373 return;
374 }
375 if (vbo_geomap_booking_step == vbo_geomap_total_steps) {
376 // we can submit the form
377 document.getElementById('vbselectroomform').submit();
378 return true;
379 }
380 }
381
382 /**
383 * Deselects the room from the given party step, as well as for any step after.
384 */
385 function vboInteractiveGeoMapDeselectRoom(step, idroom) {
386 if (confirm(Joomla.JText._('VBO_CANCEL_SELECTION'))) {
387 if (step > vbo_geomap_total_steps) {
388 console.error('invalid step given', step, vbo_geomap_total_steps);
389 return false;
390 }
391 // loop through all room parties starting from the last one until reaching the selected one
392 for (var now_step = vbo_geomap_total_steps; now_step >= step; now_step--) {
393 // remove cart row
394 if (jQuery('.vbo-geomap-minicart-row[data-minicartrow="' + now_step + '"]').length) {
395 jQuery('.vbo-geomap-minicart-row[data-minicartrow="' + now_step + '"]').remove();
396 }
397 // hide cart if no more rows
398 if (!jQuery('.vbo-geomap-minicart-row').length) {
399 jQuery('.vbo-geomap-minicart').hide();
400 }
401 // remove hidden field for room index
402 if (jQuery('#vbo-geo-roomindex-' + now_step).length) {
403 jQuery('#vbo-geo-roomindex-' + now_step).remove();
404 }
405 // delete the room index previously selected
406 if (vbo_rooms_units_selected.hasOwnProperty(now_step)) {
407 delete vbo_rooms_units_selected[now_step];
408 }
409 // deselect room from classic template
410 // remove selected class
411 if (jQuery('#vbcontainer' + now_step + '_' + idroom).length) {
412 jQuery('#vbcontainer' + now_step + '_' + idroom).removeClass('room_selected');
413 }
414 // change selector label
415 if (jQuery('#vbselector' + now_step + '_' + idroom).length) {
416 jQuery('#vbselector' + now_step + '_' + idroom).text(Joomla.JText._('VBSELECTR'));
417 }
418 // empty hidden input field
419 if (jQuery('#roomopt' + now_step).length) {
420 jQuery('#roomopt' + now_step).val('');
421 }
422 // update global r_counter object from classic template
423 if (typeof r_counter !== 'undefined' && r_counter.hasOwnProperty(idroom)) {
424 // decrease units used for this room
425 if (r_counter[idroom].hasOwnProperty('used') && r_counter[idroom]['used'] > 0) {
426 r_counter[idroom]['used']--;
427 }
428 }
429 }
430 // update global booking step
431 vbo_geomap_booking_step = step;
432 // reload map for the current booking party
433 vboInitInteractiveGeoMap();
434 }
435 }
436
437 /**
438 * Given the markers of the rooms for the current booking party, calculates and sets the map bounds
439 */
440 function vboInteractiveGeoMapCenterBounds() {
441 if (vbo_geomap === null || !vbo_geomarker_parties.hasOwnProperty(vbo_geomap_booking_step)) {
442 console.error('map is null or booking step not found');
443 return false;
444 }
445 // set map center and zoom automatically
446 var latlngbounds = new google.maps.LatLngBounds();
447
448 // get the position of all markers for the current room party
449 var party_rooms = vbo_geomarker_parties[vbo_geomap_booking_step];
450 var tot_coords = 0;
451 for (var room in party_rooms) {
452 if (!party_rooms.hasOwnProperty(room)) {
453 continue;
454 }
455 var idroom = party_rooms[room]['id'];
456 if (!vbo_geomarker_units_pos.hasOwnProperty(idroom)) {
457 continue;
458 }
459 for (var m in vbo_geomarker_units_pos[idroom]) {
460 if (!vbo_geomarker_units_pos[idroom].hasOwnProperty(m)) {
461 continue;
462 }
463 latlngbounds.extend({
464 lat: parseFloat(vbo_geomarker_units_pos[idroom][m]['lat']),
465 lng: parseFloat(vbo_geomarker_units_pos[idroom][m]['lng'])
466 });
467 tot_coords++;
468 }
469 }
470
471 // apply calculated center and bounds
472 if (tot_coords > 0) {
473 vbo_geomap.setCenter(latlngbounds.getCenter());
474 vbo_geomap.fitBounds(latlngbounds);
475 }
476 }
477
478 /**
479 * Given the the current booking party, adds the markers to the map
480 */
481 function vboInteractiveGeoMapSetPartyMarkers() {
482 if (vbo_geomap === null || !vbo_geomarker_parties.hasOwnProperty(vbo_geomap_booking_step)) {
483 console.error('map is null or booking step not found');
484 return false;
485 }
486
487 // update UI control for this room party
488 if (jQuery('.vbo-geomap-uicontrol').length) {
489 jQuery('.vbo-geomap-uicontrol-partynum').text(Joomla.JText._('VBSEARCHROOMNUM') + ' ' + vbo_geomap_booking_step);
490 var vbo_uicontrol_guests = '';
491 vbo_uicontrol_guests += '<?php VikBookingIcons::e('users', 'vbo-pref-color-text'); ?> ';
492 vbo_uicontrol_guests += vbo_geo_guestparties[vbo_geomap_booking_step]['adults'] + ' ';
493 vbo_uicontrol_guests += (vbo_geo_guestparties[vbo_geomap_booking_step]['adults'] == 1 ? Joomla.JText._('VBSEARCHRESADULT') : Joomla.JText._('VBSEARCHRESADULTS')) + ' ';
494 vbo_uicontrol_guests += (vbo_geo_showchildren > 0 && vbo_geo_guestparties[vbo_geomap_booking_step]['children'] > 1 ? (', ' + vbo_geo_guestparties[vbo_geomap_booking_step]['children'] + ' ' + (vbo_geo_guestparties[vbo_geomap_booking_step]['children'] == 1 ? Joomla.JText._('VBSEARCHRESCHILD') : Joomla.JText._('VBSEARCHRESCHILDREN'))) : '');
495 jQuery('.vbo-geomap-uicontrol-partyguests').html(vbo_uicontrol_guests);
496 }
497
498 // unset any previously defined marker, if any
499 for (var i in vbo_geomarker_units) {
500 if (vbo_geomarker_units.hasOwnProperty(i)) {
501 vbo_geomarker_units[i].setMap(null);
502 }
503 }
504 vbo_geomarker_units = {};
505
506 // iterate through rooms of current party
507 var party_rooms = vbo_geomarker_parties[vbo_geomap_booking_step];
508 for (var room in party_rooms) {
509 if (!party_rooms.hasOwnProperty(room)) {
510 continue;
511 }
512 var idroom = party_rooms[room]['id'];
513 var rname = party_rooms[room]['name'];
514 var runits = party_rooms[room]['units'];
515 var rcost = party_rooms[room]['cost'];
516 var rprevcost = party_rooms[room].hasOwnProperty('prev_cost') ? party_rooms[room]['prev_cost'] : null;
517 var tot_markers = party_rooms[room]['tot_markers'];
518 if (!vbo_geomarker_units_pos.hasOwnProperty(idroom)) {
519 continue;
520 }
521 // iterate through all markers for this room
522 for (var m = 1; m <= tot_markers; m++) {
523 if (!vbo_geomarker_units_pos[idroom].hasOwnProperty(m)) {
524 continue;
525 }
526 // make sure this room index is available on these dates
527 if (vbo_rooms_units_booked.hasOwnProperty(idroom)) {
528 var room_index_booked = false;
529 for (var res_id in vbo_rooms_units_booked[idroom]) {
530 if (!vbo_rooms_units_booked[idroom].hasOwnProperty(res_id)) {
531 continue;
532 }
533 if (vbo_rooms_units_booked[idroom][res_id] == m) {
534 room_index_booked = true;
535 break;
536 }
537 }
538 if (room_index_booked === true) {
539 // this marker (room unit) index is booked
540 continue;
541 }
542 }
543 // make sure this room is still selectable
544 var selected_units = 0;
545 for (var step in vbo_rooms_units_selected) {
546 if (!vbo_rooms_units_selected.hasOwnProperty(step)) {
547 continue;
548 }
549 if (vbo_rooms_units_selected[step]['idroom'] == idroom) {
550 // increase units selected for this room
551 selected_units++;
552 if (tot_markers > 1 && vbo_rooms_units_selected[step]['index'] == m) {
553 // this exact index was selected, and the room has got multiple markers, so skip it
554 selected_units = runits;
555 break;
556 }
557 }
558 }
559 if (selected_units >= runits) {
560 // this room has no more units available or this multi-marker was selected already, so skip the marker
561 continue;
562 }
563
564 // prepare marker object for the current room unit
565 var marker_title = rname + ' #' + (m + '');
566 if (tot_markers === 1) {
567 marker_title = rname;
568 } else if (vbo_info_markers_helper.hasOwnProperty(idroom) && vbo_info_markers_helper[idroom].hasOwnProperty(m)) {
569 marker_title = rname + ' - ' + vbo_info_markers_helper[idroom][m];
570 }
571
572 // build map marker options (no "content" by default, hence the classic Google Maps red marker)
573 var marker_options = {
574 map: vbo_geomap,
575 position: {
576 lat: parseFloat(vbo_geomarker_units_pos[idroom][m]['lat']),
577 lng: parseFloat(vbo_geomarker_units_pos[idroom][m]['lng'])
578 },
579 title: marker_title,
580 };
581
582 if (rcost) {
583 // define the marker "content" with a span node showing the listing cost
584 let marker_cost_el = document.createElement('span');
585 marker_cost_el.classList.add('vbo-map-listing-cost');
586 marker_cost_el.setAttribute('data-room-id', idroom);
587 if (vbo_geo_currency_pos === 'after') {
588 marker_cost_el.innerHTML = '<span class="vbo_price">' + rcost + '</span> <span class="vbo_currency">' + vbo_geo_currency + '</span>';
589 } else {
590 marker_cost_el.innerHTML = '<span class="vbo_currency">' + vbo_geo_currency + '</span> <span class="vbo_price">' + rcost + '</span>';
591 }
592 // set marker content
593 marker_options['content'] = marker_cost_el;
594 }
595
596 // check if we know a custom icon for this marker
597 if (vbo_geomarker_units_pos[idroom][m].hasOwnProperty('icon')) {
598 // custom icon defined, identify the type to build a node
599 if (vbo_geomarker_units_pos[idroom][m]['icon']?.url) {
600 // we've got an image
601 let marker_img = document.createElement('img');
602 marker_img.src = vbo_geomarker_units_pos[idroom][m]['icon'].url;
603 if (vbo_geomarker_units_pos[idroom][m]['icon']?.scaledSize?.width) {
604 marker_img.style.width = vbo_geomarker_units_pos[idroom][m]['icon'].scaledSize.width + 'px';
605 }
606 if (vbo_geomarker_units_pos[idroom][m]['icon']?.scaledSize?.height) {
607 marker_img.style.height = vbo_geomarker_units_pos[idroom][m]['icon'].scaledSize.height + 'px';
608 }
609 // center the image
610 marker_img.style.transform = 'translate(0, 50%)';
611 // set marker content
612 marker_options['content'] = marker_img;
613 } else if (vbo_geomarker_units_pos[idroom][m]['icon']?.path) {
614 // we've got an SVG path symbol
615 let svgns = 'http://www.w3.org/2000/svg';
616 let svg = document.createElementNS(svgns, 'svg');
617 svg.setAttribute('xmlns', svgns);
618 svg.setAttribute('viewBox', '0 0 90 90');
619 svg.setAttribute('width', 90);
620 svg.setAttribute('height', 90);
621 let path = document.createElementNS(svgns, 'path');
622 path.setAttribute('d', vbo_geomarker_units_pos[idroom][m]['icon'].path);
623 path.setAttribute('fill', vbo_geomarker_units_pos[idroom][m]['icon']?.fillColor || '#eeeeee');
624 path.setAttribute('fill-opacity', vbo_geomarker_units_pos[idroom][m]['icon']?.fillOpacity || 0.9);
625 path.setAttribute('stroke-width', vbo_geomarker_units_pos[idroom][m]['icon']?.strokeWeight || 0);
626 if (vbo_geomarker_units_pos[idroom][m]['icon']?.scale && vbo_geomarker_units_pos[idroom][m]['icon']?.anchor?.x && vbo_geomarker_units_pos[idroom][m]['icon']?.anchor?.y) {
627 path.setAttribute(
628 'transform',
629 'translate(' + vbo_geomarker_units_pos[idroom][m]['icon'].anchor.x + ', ' + vbo_geomarker_units_pos[idroom][m]['icon'].anchor.y + ') scale(' + vbo_geomarker_units_pos[idroom][m]['icon'].scale + ')'
630 );
631 }
632 svg.appendChild(path);
633 // set marker content
634 marker_options['content'] = svg;
635 }
636 }
637
638 // set custom properties
639 let marker_room_data = {};
640 marker_room_data['room_id'] = idroom;
641 marker_room_data['room_name'] = rname;
642 marker_room_data['room_units'] = runits;
643 marker_room_data['room_cost'] = rcost;
644 marker_room_data['room_prev_cost'] = rprevcost;
645 marker_room_data['room_index'] = m;
646 marker_room_data['room_gallery'] = (vbo_geo_rooms_galleries.hasOwnProperty(idroom) && vbo_geo_rooms_galleries[idroom].length ? 1 : 0);
647
648 // create marker infowindow
649 var vbo_info_marker_cont = vboGeoInfoMarkerContent(idroom, m, marker_title, rcost, rprevcost);
650 var vbo_info_marker = new google.maps.InfoWindow({
651 content: vbo_info_marker_cont,
652 });
653
654 // add unit marker to map
655 var vbo_geomarker_runit = new google.maps.marker.AdvancedMarkerElement(marker_options);
656
657 // add listener to marker's click event
658 vbo_geomarker_runit.addListener('click', function() {
659 // close any other open infowindow first
660 for (var infom in vbo_info_markers) {
661 if (!vbo_info_markers.hasOwnProperty(infom)) {
662 continue;
663 }
664 vbo_info_markers[infom].close();
665 }
666 // display its infowindow only
667 if (marker_room_data['room_id'] && marker_room_data['room_index']) {
668 var identifier = marker_room_data['room_id'] + '_' + marker_room_data['room_index'];
669 if (vbo_info_markers.hasOwnProperty(identifier)) {
670 vbo_info_markers[identifier].open(vbo_geomap, this);
671 if (marker_room_data['room_gallery'] > 0) {
672 var gallery_params = {
673 images: vbo_geo_rooms_galleries[marker_room_data['room_id']],
674 navButPrevContent: '<?php VikBookingIcons::e('chevron-left'); ?>',
675 navButNextContent: '<?php VikBookingIcons::e('chevron-right'); ?>',
676 containerHeight: '195px',
677 };
678 var gallery_selector = '.vbo-geomarker-infowin-room-gallery[data-idroom="' + marker_room_data['room_id'] + '"]';
679 if (jQuery(gallery_selector).length) {
680 // render gallery immediately
681 vboGeoMapRenderInfowinGallery(gallery_selector, gallery_params);
682 } else {
683 // infowindows may take some seconds to be displayed, so we delay the rendering
684 window.setTimeout(vboGeoMapRenderInfowinGallery.bind(null, gallery_selector, gallery_params), 500);
685 }
686 }
687 } else {
688 console.error('info marker identifier not found', this, identifier);
689 }
690 } else {
691 console.error('info marker properties not found', this);
692 }
693 });
694 // register marker to pool
695 var identifier = idroom + '_' + m;
696 vbo_geomarker_units[identifier] = vbo_geomarker_runit;
697 // register info window
698 vbo_info_markers[identifier] = vbo_info_marker;
699 }
700 }
701 }
702
703 /**
704 * Renders the vikDotsSlider gallery for an infowindow.
705 * We wrap the use of this plugin inside a function so that we can delay the execution.
706 */
707 function vboGeoMapRenderInfowinGallery(selector, params) {
708 jQuery(selector).vikDotsSlider(params);
709 }
710
711 /**
712 * Initializes the interactive geo map through Google Maps for the current booking step.
713 */
714 function vboInitInteractiveGeoMap() {
715 // default map options
716 var def_map_options = {
717 center: new google.maps.LatLng(<?php echo $global_geo_params->latitude; ?>, <?php echo $global_geo_params->longitude; ?>),
718 zoom: <?php echo (int)$global_geo_params->zoom; ?>,
719 mapId: 'vbo_map',
720 mapTypeId: '<?php echo $global_geo_params->mtype; ?>',
721 mapTypeControl: false
722 };
723 // initialize Map
724 vbo_geomap = new google.maps.Map(document.getElementById('vbo-results-geo-map'), def_map_options);
725 // add listeners to map
726 vbo_geomap.addListener('click', function(e) {
727 // close all infowindows
728 for (var infom in vbo_info_markers) {
729 if (!vbo_info_markers.hasOwnProperty(infom)) {
730 continue;
731 }
732 vbo_info_markers[infom].close();
733 }
734 });
735 // add markers and set UI control for the current rooms party
736 vboInteractiveGeoMapSetPartyMarkers();
737 // center bounds
738 vboInteractiveGeoMapCenterBounds();
739 // populate ground overlay image, if set
740 if (vbo_dbground_overlay !== null) {
741 // compose LatLngBounds object
742 var overlay_bounds = new google.maps.LatLngBounds(
743 new google.maps.LatLng(parseFloat(vbo_dbground_overlay.south), parseFloat(vbo_dbground_overlay.west)),
744 new google.maps.LatLng(parseFloat(vbo_dbground_overlay.north), parseFloat(vbo_dbground_overlay.east))
745 );
746 // update ground overlay object
747 vbo_ground_overlay = new google.maps.GroundOverlay(vbo_dbground_overlay.url, overlay_bounds);
748 // set the overlay to the map
749 vbo_ground_overlay.setMap(vbo_geomap);
750 }
751 }
752
753 jQuery(function() {
754
755 // register hovering events for minicart body
756 if (jQuery('.vbo-geomap-minicart').length) {
757 jQuery('.vbo-geomap-minicart').hover(function() {
758 jQuery('.vbo-geomap-minicart-body').removeClass('vbo-geomap-minicart-body-hid').addClass('vbo-geomap-minicart-body-enter');
759 }, function() {
760 jQuery('.vbo-geomap-minicart-body').removeClass('vbo-geomap-minicart-body-enter').addClass('vbo-geomap-minicart-body-hid');
761 });
762 }
763
764 // preload image gallery
765 jQuery(vbo_geo_rooms_galleries).vikDotsSlider('preloadImages');
766
767 // register class in search results container to tell the geomap is enabled
768 document.querySelectorAll('.vbo-results-content').forEach((el) => {
769 el.classList.add('vbo-results-with-geomap');
770 });
771
772 /**
773 * Register promise for Google Maps async loading completion.
774 */
775 VBOCore.onInstanceReady(() => {
776 // constantly check when Google Maps async loading is completed
777 return VBOGMapsUtils.isReady;
778 }).then(() => {
779 // init interactive geo map with markers
780 vboInitInteractiveGeoMap();
781 });
782 });
783
784 </script>
785 <?php
786 } else {
787 // register class in search results container to tell the geomap is disabled
788 JFactory::getDocument()->addScriptDeclaration(
789 <<<JAVASCRIPT
790 VBOCore.DOMLoaded(() => {
791 document.querySelectorAll('.vbo-results-content').forEach((el) => {
792 el.classList.add('vbo-results-without-geomap');
793 });
794 });
795 JAVASCRIPT
796 );
797 }
798