PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.16
VikBooking Hotel Booking Engine & PMS v1.8.16
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 / views / ratesoverv / tmpl / default.php

default.php in VikBooking Hotel Booking Engine & PMS 1.8.16, at admin/views/ratesoverv/tmpl/default.php

4,110 lines 161.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 $all_rooms = $this->all_rooms;
14 $categories = $this->categories;
15 $roomrows = $this->roomrows;
16 $seasoncal_nights = $this->seasons_cal_nights;
17 $seasons_cal = $this->seasons_cal;
18 $tsstart = $this->tsstart;
19
20 // JS lang defs
21 JText::script('VBO_BOOKNOW');
22 JText::script('VBPVIEWORDERSTHREE');
23 JText::script('VBEDITORDERTHREE');
24 JText::script('VBDAYS');
25 JText::script('VBDAY');
26 JText::script('VBMAILADULTS');
27 JText::script('VBMAILADULT');
28 JText::script('VBMAILCHILDREN');
29 JText::script('VBMAILCHILD');
30 JText::script('VBO_MISSING_SUBUNIT');
31 JText::script('VBRATESOVWRATESCALCULATORCALC');
32 JText::script('VBRATESOVWRATESCALCULATORCALCING');
33 JText::script('VBOVCMRATESRES');
34 JText::script('VBO_IS_DERIVED_RATE');
35 JText::script('VBO_PLEASE_FILL_FIELDS');
36 JText::script('VBO_RATES_AND_RESTR');
37 JText::script('VBSAVE');
38 JText::script('VBANNULLA');
39 JText::script('VBAPPLY');
40 JText::script('VBNEWRESTRICTIONDFROMRANGE');
41 JText::script('VBNEWRESTRICTIONDTORANGE');
42
43 $document = JFactory::getDocument();
44
45 $vbo_app = VikBooking::getVboApplication();
46 $vbo_app->loadSelect2();
47 $vbo_app->loadDatePicker();
48 $vbo_app->loadDatesRangePicker();
49 $vbo_app->loadContextMenuAssets();
50
51 $is_mobile = VikBooking::detectUserAgent(false, false);
52
53 $pdebug = VikRequest::getint('e4j_debug', '', 'request');
54
55 $vbo_df = VikBooking::getDateFormat();
56 $datesep = VikBooking::getDateSeparator();
57 $df = $vbo_df == "%d/%m/%Y" ? 'd/m/Y' : ($vbo_df == "%m/%d/%Y" ? 'm/d/Y' : 'Y/m/d');
58 $juidf = $vbo_df == "%d/%m/%Y" ? 'dd/mm/yy' : ($vbo_df == "%m/%d/%Y" ? 'mm/dd/yy' : 'yy/mm/dd');
59
60 $currencysymb = VikBooking::getCurrencySymb();
61 $currencypos = VikBooking::getCurrencyPosition();
62 list($currency_digits, $currency_decimals, $currency_thousands) = explode(':', VikBooking::getNumberFormatData());
63
64 // immediately define the maximum visible days
65 $MAX_DAYS = $this->max_days;
66
67 // check-in/out times
68 $pcheckinh = 0;
69 $pcheckinm = 0;
70 $pcheckouth = 0;
71 $pcheckoutm = 0;
72 $timeopst = VikBooking::getTimeOpenStore();
73 if (is_array($timeopst)) {
74 $opent = VikBooking::getHoursMinutes($timeopst[0]);
75 $closet = VikBooking::getHoursMinutes($timeopst[1]);
76 $pcheckinh = $opent[0];
77 $pcheckinm = $opent[1];
78 $pcheckouth = $closet[0];
79 $pcheckoutm = $closet[1];
80 }
81
82 /**
83 * Check if season records should be preloaded. Beware of the hundreds of MBs of server's memory
84 * that could be used for pre-loading and pre-caching records in favour of CPU.
85 *
86 * @since 1.16.10 (J) - 1.6.10 (WP)
87 * @since 1.18.0 (J) - 1.8.0 (WP) seasons caching is enabled by default for better performances.
88 */
89 $from_info = getdate($tsstart);
90 $season_from_ts = mktime($pcheckinh, $pcheckinm, 0, $from_info['mon'], $from_info['mday'], $from_info['year']);
91 $season_to_ts = mktime($pcheckouth, $pcheckoutm, 0, $from_info['mon'], $from_info['mday'] + $MAX_DAYS, $from_info['year']);
92 $cached_seasons = VikBooking::getDateSeasonRecords($season_from_ts, $season_to_ts, $this->req_room_ids);
93 // update configuration record
94 VBOFactory::getConfig()->set('pre_cache_seasons', 1);
95
96 /**
97 * In order to improve the performances, we parse all listing rates at once, as long as season records were cached.
98 * This is only performed over the first (main) room rate plan, because multiple rate plans are not compatible.
99 *
100 * @since 1.18.0 (J) - 1.8.0 (WP)
101 */
102 $main_room_rates = array_map(function($rrate) {
103 // convert a possible multi-rate-plan listing into a single-rate-plan listing
104 return [$rrate[0]];
105 }, array_filter($this->roomrates));
106 $current_rates_pool = [];
107 $iter_date = $from_info;
108 $cell_count = 0;
109 while ($cell_count < $MAX_DAYS) {
110 $day_key = date('Y-m-d', $iter_date[0]);
111 $today_tsin = mktime($pcheckinh, $pcheckinm, 0, $iter_date['mon'], $iter_date['mday'], $iter_date['year']);
112 $today_tsout = mktime($pcheckouth, $pcheckoutm, 0, $iter_date['mon'], ($iter_date['mday'] + 1), $iter_date['year']);
113
114 $listing_tars = VikBooking::applySeasonalPrices($main_room_rates, $today_tsin, $today_tsout, $cached_seasons, VikBooking::getWdaySeasonRecords());
115
116 // scan the tariff results
117 foreach ($listing_tars as $listing_id => $tars) {
118 // initialize listing rates, if needed
119 if (!isset($current_rates_pool[$listing_id])) {
120 $current_rates_pool[$listing_id] = [];
121 }
122
123 // set rate for this listing and day (associative by rate plan id - just one)
124 $current_rates_pool[$listing_id][$day_key] = array_combine(array_column($tars, 'idprice'), array_values($tars));
125 }
126
127 // increase cells counter
128 $cell_count++;
129
130 // go to next day
131 $iter_date = getdate(mktime(0, 0, 0, $iter_date['mon'], ($iter_date['mday'] + 1), $iter_date['year']));
132 }
133
134 // free memory up
135 unset($main_room_rates);
136
137 // build environment data
138 $mb_support = function_exists('mb_substr');
139 $short_wdays = [
140 JText::translate('VBSUNDAY'),
141 JText::translate('VBMONDAY'),
142 JText::translate('VBTUESDAY'),
143 JText::translate('VBWEDNESDAY'),
144 JText::translate('VBTHURSDAY'),
145 JText::translate('VBFRIDAY'),
146 JText::translate('VBSATURDAY'),
147 ];
148 $short_mons = [
149 JText::translate('VBMONTHONE'),
150 JText::translate('VBMONTHTWO'),
151 JText::translate('VBMONTHTHREE'),
152 JText::translate('VBMONTHFOUR'),
153 JText::translate('VBMONTHFIVE'),
154 JText::translate('VBMONTHSIX'),
155 JText::translate('VBMONTHSEVEN'),
156 JText::translate('VBMONTHEIGHT'),
157 JText::translate('VBMONTHNINE'),
158 JText::translate('VBMONTHTEN'),
159 JText::translate('VBMONTHELEVEN'),
160 JText::translate('VBMONTHTWELVE'),
161 ];
162 foreach ($short_wdays as $k => $v) {
163 if ($mb_support) {
164 $short_wdays[$k] = mb_substr($v, 0, 3, 'UTF-8');
165 } else {
166 $short_wdays[$k] = substr($v, 0, 3);
167 }
168 }
169 foreach ($short_mons as $k => $v) {
170 if ($mb_support) {
171 $short_mons[$k] = mb_substr($v, 0, 3, 'UTF-8');
172 } else {
173 $short_mons[$k] = substr($v, 0, 3);
174 }
175 }
176 $json_short_wdays = json_encode($short_wdays);
177 $json_short_mons = json_encode($short_mons);
178 $json_room_ota_rels = json_encode($this->room_ota_relations);
179 $json_mini_thumbnails = json_encode(VBORoomHelper::getInstance()->loadMiniThumbnails($all_rooms));
180
181 // add to DOM the necessary script declaration
182 $document->addScriptDeclaration(
183 <<<JS
184 var vboMapWdays = $json_short_wdays;
185 var vboMapMons = $json_short_mons;
186 var vboRoomOtaRels = $json_room_ota_rels;
187 var vboListingsMiniThumbnails = $json_mini_thumbnails;
188 var vbo_currency_symbol = "$currencysymb";
189 var vbo_currency_position = "$currencypos";
190 var vbo_currency_digits = "$currency_digits";
191 var vbo_currency_decimals = "$currency_decimals";
192 var vbo_currency_thousands = "$currency_thousands";
193 JS
194 );
195
196 $price_types_show = true;
197 $los_show = true;
198 $cookie = JFactory::getApplication()->input->cookie;
199 $cookie_tab = $cookie->get('vboRovwRab', 'cal', 'string');
200 $collapse_status = $cookie->get('vboRovwColl', 0, 'int');
201
202 // access room helper to detect LOS rates
203 $room_helper = VBORoomHelper::getInstance();
204
205 // derived rate plans data
206 $have_derived_rates = [];
207 $derived_rates_info = [];
208 foreach (VikBooking::getAvailabilityInstance()->loadRatePlans() as $rate_plan) {
209 if ($rate_plan['derived_id'] && $rate_plan['derived_data']) {
210 $derived_rates_info[$rate_plan['id']] = $rate_plan;
211 if (!in_array($rate_plan['derived_id'], $have_derived_rates)) {
212 // push parent rate to the list
213 $have_derived_rates[] = $rate_plan['derived_id'];
214 }
215 }
216 }
217
218 ?>
219
220 <div class="vbo-ratesoverview-top-outer">
221 <div class="vbo-ratesoverview-top-outer-inner vbo-toggle-small">
222 <?php echo $vbo_app->printYesNoButtons('vbo_collapse', JText::translate('VBYES'), JText::translate('VBNO'), (!$collapse_status ? 1 : 0), 1, 0, "vboToggleCollapse();"); ?>
223 <span onclick="jQuery(this).parent().find('input').trigger('click');"><?php echo JText::translate('VBO_EXPAND'); ?></span>
224 </div>
225 </div>
226
227 <div class="vbo-ratesoverview-top-container<?php echo $collapse_status ? ' collapsed' : ''; ?>">
228 <div class="vbo-ratesoverview-roomsel-block">
229 <form method="get" action="index.php?option=com_vikbooking" name="vboratesovwform">
230 <input type="hidden" name="option" value="com_vikbooking" />
231 <input type="hidden" name="task" value="ratesoverv" />
232 <div class="vbo-ratesoverview-roomsel-entry vbo-ratesoverview-roomsel-entry-chrooms">
233 <label for="roomsel"><?php VikBookingIcons::e('bed'); ?> <?php echo JText::translate('VBPVIEWORDERSTHREE'); ?></label>
234 <select name="cid[]" <?php echo count($all_rooms) > 1 ? 'multiple="multiple"' : 'onchange="document.vboratesovwform.submit();"' ?> id="roomsel" style="min-width: 160px; max-width: 250px;">
235 <?php
236 foreach ($all_rooms as $room) {
237 ?>
238 <option value="<?php echo $room['id']; ?>"<?php echo in_array($room['id'], $this->req_room_ids) ? ' selected="selected"' : ''; ?>><?php echo $room['name']; ?></option>
239 <?php
240 }
241
242 /**
243 * Use the optgroup to separate rooms from categories in the
244 * drop down menu, only if all_rooms > 1 e categories > 1.
245 *
246 * @since 1.13
247 */
248 if (count($all_rooms) > 1 && count($categories) > 1) {
249 ?>
250 <optgroup label="<?php echo addslashes(JText::translate('VBOCATEGORYFILTER')); ?>">
251 <?php
252 foreach ($categories as $cat) {
253 // we use negative values for the IDs of the categories
254 $cat_id = ($cat['id'] - ($cat['id'] * 2));
255 ?>
256 <option value="<?php echo $cat_id; ?>"><?php echo (in_array($cat['id'], $this->reqcats) ? '- ' : '') . $cat['name']; ?></option>
257 <?php
258 }
259 ?>
260 </optgroup>
261 <?php
262 }
263 //
264 ?>
265 </select>
266 <button type="button" class="btn vbo-config-btn" title="<?php echo htmlspecialchars(JText::translate('VBODRIVERLOAD')); ?>" onclick="document.vboratesovwform.submit();"><i class="vboicn-loop2"></i></button>
267 <?php
268 if (count($all_rooms) > 1 && count($all_rooms) < 50 && count($this->req_room_ids) < count($all_rooms)) {
269 // display select all rooms button
270 ?>
271 <div class="vbo-roverview-selall-rooms">
272 <button type="button" class="btn vbo-config-btn" onclick="vboSelectAllRooms(this);"><?php VikBookingIcons::e('layer-group'); ?> <?php echo JText::translate('VBOSELECTALL'); ?></button>
273 </div>
274 <?php
275 }
276 ?>
277 </div>
278 <div class="vbo-ratesoverview-roomsel-entry vbo-ratesoverview-roomsel-entry-calc">
279 <label for="checkindate" onclick="vboCheckCollapse();"><?php VikBookingIcons::e('calculator'); ?> <?php echo JText::translate('VBRATESOVWRATESCALCULATOR'); ?></label>
280 <div class="vbo-ratesoverview-roomsel-entry-calc-inner"<?php echo $collapse_status ? ' style="display: none;"' : ''; ?>>
281 <span class="vbo-ratesoverview-entryinline vbo-ratesoverview-rcalc-srooms">
282 <select name="roomselcalc" id="roomselcalc" style="max-width: 250px;">
283 <?php
284 foreach ($all_rooms as $room) {
285 ?>
286 <option value="<?php echo $room['id']; ?>"<?php echo $room['id'] == ($roomrows[$this->firstroom]['id'] ?? '') ? ' selected="selected"' : ''; ?>><?php echo $room['name']; ?></option>
287 <?php
288 }
289 ?>
290 </select>
291 </span>
292 <span class="vbo-ratesoverview-entryinline">
293 <div class="vbo-field-calendar">
294 <div class="input-append">
295 <input type="text" autocomplete="off" value="" class="vbo-roverv-rcalc-cal" size="10" id="checkindate" name="checkindate" placeholder="<?php echo JText::translate('VBPICKUPAT'); ?>" />
296 <button type="button" class="btn btn-secondary" id="checkindate-trig"><?php VikBookingIcons::e('calendar', 'icn-nomargin'); ?></button>
297 </div>
298 </div>
299 </span>
300 <span class="vbo-ratesoverview-entryinline">
301 <div class="vbo-field-calendar">
302 <div class="input-append">
303 <input type="text" autocomplete="off" value="" class="vbo-roverv-rcalc-cal" size="10" id="checkoutdate" name="checkoutdate" placeholder="<?php echo JText::translate('VBRELEASEAT'); ?>" />
304 <button type="button" class="btn btn-secondary" id="checkoutdate-trig"><?php VikBookingIcons::e('calendar', 'icn-nomargin'); ?></button>
305 </div>
306 </div>
307 </span>
308 <span class="vbo-ratesoverview-entryinline"><span><?php echo JText::translate('VBRATESOVWRATESCALCNUMNIGHTS'); ?></span> <input type="number" id="vbo-numnights" value="1" min="1" max="365" step="1" /></span>
309 <span class="vbo-ratesoverview-entryinline"><span><?php echo JText::translate('VBRATESOVWRATESCALCNUMADULTS'); ?></span> <input type="number" id="vbo-numadults" value="<?php echo ($roomrows[$this->firstroom]['fromadult'] ?? ''); ?>" step="1"/></span>
310 <span class="vbo-ratesoverview-entryinline"><span><?php echo JText::translate('VBRATESOVWRATESCALCNUMCHILDREN'); ?></span> <input type="number" id="vbo-numchildren" value="<?php echo ($roomrows[$this->firstroom]['fromchild'] ?? ''); ?>" step="1"/></span>
311 <span class="vbo-ratesoverview-entryinline"><button type="button" class="btn vbo-config-btn" id="vbo-ratesoverview-calculate"><?php echo JText::translate('VBRATESOVWRATESCALCULATORCALC'); ?></button></span>
312 </div>
313
314 <div class="vbo-ratesoverview-calculation-response"></div>
315
316 </div>
317 <div class="vbo-ratesoverview-roomsel-entry vbo-ratesoverview-roomsel-entry-forecast"<?php echo (!empty($cookie_tab) && $cookie_tab != 'cal' ? ' style="display: none;"' : ''); ?>>
318 <label onclick="vboCheckCollapse();"><?php VikBookingIcons::e('cloud-sun-rain'); ?> <?php echo JText::translate('VBOFORECAST'); ?></label>
319 <div class="vbo-roverv-forecast-inner"<?php echo $collapse_status ? ' style="display: none;"' : ''; ?>>
320 <?php echo JLayoutHelper::render('reports.occupancy', array('vbo_page' => 'ratesoverv', 'room_ids' => $this->req_room_ids)); ?>
321 </div>
322 </div>
323 <div class="vbo-ratesoverview-roomsel-entry vbo-ratesoverview-roomsel-entry-los"<?php echo (!empty($cookie_tab) && $cookie_tab == 'cal' ? ' style="display: none;"' : ''); ?>>
324 <label><?php echo JText::translate('VBRATESOVWNUMNIGHTSACT'); ?></label>
325 <?php
326 foreach ($seasoncal_nights as $numnights) {
327 ?>
328 <span class="vbo-ratesoverview-numnight" id="numnights<?php echo $numnights; ?>"><?php echo $numnights; ?></span>
329 <input type="hidden" name="nights_cal[]" id="inpnumnights<?php echo $numnights; ?>" value="<?php echo $numnights; ?>" />
330 <?php
331 }
332 ?>
333 <input type="number" id="vbo-addnumnight" value="<?php echo isset($numnights) ? ($numnights + 1) : 1; ?>" min="1"/>
334 <span id="vbo-addnumnight-act"><?php VikBookingIcons::e('plus-square'); ?></span>
335 <button type="button" class="btn vbo-config-btn vbo-apply-los-btn" onclick="document.vboratesovwform.submit();"><?php echo JText::translate('VBRATESOVWAPPLYLOS'); ?></button>
336 </div>
337 </form>
338 </div>
339 <div class="vbo-ratesoverview-right-block">
340 <div class="vbo-ratesoverview-right-inner"></div>
341 </div>
342 </div>
343
344 <div class="vbo-ratesoverview-bottom-container">
345 <?php
346 foreach ($roomrows as $rid => $roomrow) {
347 if (count($this->req_room_ids) < 2) {
348 ?>
349 <div class="vbo-ratesoverview-bottom-head">
350 <div class="vbo-ratesoverview-roomdetails">
351 <h3 class="vbo-ratesoverview-roomname"><?php echo $roomrow['name']; ?></h3>
352 </div>
353 <div class="vbo-ratesoverview-tabscont">
354 <div class="vbo-ratesoverview-tab-cal <?php echo (!empty($cookie_tab) && $cookie_tab == 'cal' ? 'vbo-ratesoverview-tab-active' : 'vbo-ratesoverview-tab-unactive'); ?>"><i class="vboicn-calendar"></i> <?php echo JText::translate('VBRATESOVWTABCALENDAR'); ?></div>
355 <div class="vbo-ratesoverview-tab-los <?php echo (!empty($cookie_tab) && $cookie_tab == 'cal' ? 'vbo-ratesoverview-tab-unactive' : 'vbo-ratesoverview-tab-active'); ?>"><i class="vboicn-clock"></i> <?php echo JText::translate('VBRATESOVWTABLOS'); ?></div>
356 </div>
357 </div>
358 <?php
359 }
360 ?>
361
362 <div class="vbo-ratesoverview-caltab-cont" style="display: <?php echo count($this->req_room_ids) > 1 || (!empty($cookie_tab) && $cookie_tab == 'cal') ? 'block' : 'none'; ?>;">
363 <?php
364 // display the "Actions" button only once
365 if (!isset($vbo_actions_btn_shown)) {
366 // turn flag on
367 $vbo_actions_btn_shown = 1;
368 ?>
369 <div class="vbo-roverv-actions-wrapper">
370 <div class="vbo-roverv-actions-content">
371 <button type="button" class="btn vbo-context-menu-btn vbo-context-menu-btn-raw vbo-context-menu-roverv-actions">
372 <span class="vbo-context-menu-lbl"><?php echo JText::translate('VBCRONACTIONS'); ?></span>
373 <span class="vbo-context-menu-ico"><?php VikBookingIcons::e('sort-down'); ?></span>
374 </button>
375 </div>
376 </div>
377 <?php
378 }
379 if (count($this->req_room_ids) > 1) {
380 // display room name here when multiple rooms displayed
381 ?>
382 <div class="vbo-ratesoverview-roomdetails">
383 <h3 class="vbo-ratesoverview-roomname" data-idroom="<?php echo $roomrow['id']; ?>"><?php echo $roomrow['name']; ?></h3>
384 </div>
385 <?php
386 } else {
387 // room name node is needed for JS
388 ?>
389 <div class="vbo-ratesoverview-roomdetails" style="display: none;">
390 <h3 class="vbo-ratesoverview-roomname" data-idroom="<?php echo $roomrow['id']; ?>"><?php echo $roomrow['name']; ?></h3>
391 </div>
392 <?php
393 }
394 ?>
395 <div class="vbo-ratesoverview-caltab-wrapper">
396 <div class="vbo-table-responsive">
397 <table class="vboverviewtable vbratesoverviewtable vbo-table" data-idroom="<?php echo $rid; ?>">
398 <tbody>
399 <tr class="vbo-roverviewrowone">
400 <td class="bluedays skip-bluedays-click">
401 <form name="vbratesoverview" method="post" action="index.php?option=com_vikbooking&amp;task=ratesoverv">
402 <div class="vbo-roverview-datecmd-top">
403 <div class="vbo-roverview-datecmd-date">
404 <span>
405 <?php VikBookingIcons::e('calendar'); ?>
406 <input type="text" autocomplete="off" value="<?php echo date($df, $tsstart); ?>" class="vbodatepicker" name="startdate" />
407 </span>
408 </div>
409 </div>
410 </form>
411 </td>
412 <?php
413 $nowts = getdate($tsstart);
414 $days_labels = array(
415 JText::translate('VBSUN'),
416 JText::translate('VBMON'),
417 JText::translate('VBTUE'),
418 JText::translate('VBWED'),
419 JText::translate('VBTHU'),
420 JText::translate('VBFRI'),
421 JText::translate('VBSAT')
422 );
423 $long_days_labels = array(
424 JText::translate('VBSUNDAY'),
425 JText::translate('VBMONDAY'),
426 JText::translate('VBTUESDAY'),
427 JText::translate('VBWEDNESDAY'),
428 JText::translate('VBTHURSDAY'),
429 JText::translate('VBFRIDAY'),
430 JText::translate('VBSATURDAY')
431 );
432 $months_labels = array(
433 JText::translate('VBMONTHONE'),
434 JText::translate('VBMONTHTWO'),
435 JText::translate('VBMONTHTHREE'),
436 JText::translate('VBMONTHFOUR'),
437 JText::translate('VBMONTHFIVE'),
438 JText::translate('VBMONTHSIX'),
439 JText::translate('VBMONTHSEVEN'),
440 JText::translate('VBMONTHEIGHT'),
441 JText::translate('VBMONTHNINE'),
442 JText::translate('VBMONTHTEN'),
443 JText::translate('VBMONTHELEVEN'),
444 JText::translate('VBMONTHTWELVE')
445 );
446 $long_months_labels = $months_labels;
447 foreach ($months_labels as $i => $v) {
448 $months_labels[$i] = function_exists('mb_substr') ? mb_substr($v, 0, 3, 'UTF-8') : substr($v, 0, 3);
449 }
450 $cell_count = 0;
451 $weekend_arr = array(0, 6);
452 while ($cell_count < $MAX_DAYS) {
453 $style = '';
454 $curdayymd = date('Y-m-d', $nowts[0]);
455 $read_day = $days_labels[$nowts['wday']] . ' ' . $nowts['mday'] . ' ' . $months_labels[$nowts['mon']-1] . ' ' . $nowts['year'];
456
457 $cell_classes = array(
458 'bluedays',
459 'cell-' . $nowts['mday'] . '-' . $nowts['mon'],
460 );
461
462 /**
463 * Critical dates defined at room-day level.
464 *
465 * @since 1.13.5
466 */
467 $rdaynote_keyid = $curdayymd . '_' . $rid . '_0';
468 $rdaynote_icn = '';
469 if (isset($this->rdaynotes[$rdaynote_keyid])) {
470 // note exists for this combination of date, room ID and subunit
471 array_push($cell_classes, 'vbo-roverw-roomdaynote-full');
472 $rdaynote_icn = '<i class="' . VikBookingIcons::i('sticky-note') . '"></i>';
473 }
474 //
475
476 if (in_array((int)$nowts['wday'], $weekend_arr)) {
477 array_push($cell_classes, 'vbo-roverw-tablewday-wend');
478 }
479 if (isset($this->festivities[$curdayymd])) {
480 array_push($cell_classes, 'vbo-roverv-festcell');
481 }
482 ?>
483 <td data-ymd="<?php echo $curdayymd; ?>" data-readymd="<?php echo $read_day; ?>" data-rid="<?php echo $rid; ?>" class="<?php echo implode(' ', $cell_classes); ?>"<?php echo $style; ?>>
484 <?php
485 if (!empty($rdaynote_icn)) {
486 ?>
487 <span class="vbo-roverw-roomdaynote-icn"><?php echo $rdaynote_icn; ?></span>
488 <?php
489 }
490 ?>
491 <span class="vbo-roverw-tablewday"><?php echo $days_labels[$nowts['wday']]; ?></span>
492 <span class="vbo-roverw-tablemday"><?php echo $nowts['mday']; ?></span>
493 <span class="vbo-roverw-tablemonth"><?php echo $months_labels[$nowts['mon']-1]; ?></span>
494 </td>
495 <?php
496 $next = $nowts['mday'] + 1;
497 $dayts = mktime(0, 0, 0, $nowts['mon'], $next, $nowts['year']);
498 $nowts = getdate($dayts);
499 $cell_count++;
500 }
501 ?>
502 <td class="vbo-roverv-gonext-cell" data-rid="<?php echo $rid; ?>" rowspan="<?php echo count($this->roomrates[$rid]) + 3; ?>">
503 <div class="vbo-roverv-gonext-cell-inner">
504 <button type="button" class="btn vbo-config-btn vbo-config-btn-rounded vbo-config-btn-large" onclick="vboDisplayNextDays(this);"><?php VikBookingIcons::e('angle-double-right'); ?></button>
505 </div>
506 </td>
507 </tr>
508 <?php
509 $room_obp_overrides = [];
510 $closed_roomrateplans = VikBooking::getRoomRplansClosingDates($roomrow['id']);
511 foreach ($this->roomrates[$rid] as $roomrate) {
512 $nowts = getdate($tsstart);
513 $cell_count = 0;
514 $rplan_minlos = !empty($roomrate['minlos']) && $roomrate['minlos'] > 0 ? $roomrate['minlos'] : 0;
515 $rplan_haslos = $room_helper->hasLosRecords($roomrate['idroom'], $roomrate['idprice'], true);
516
517 // check for derived rate plans
518 $derived_str = '';
519 $derived_icn = '';
520 if (isset($derived_rates_info[$roomrate['idprice']])) {
521 // build derived info string
522 $derived_data = $derived_rates_info[$roomrate['idprice']]['derived_data'] ?? [];
523 $derived_str .= ($derived_rates_info[$roomrate['idprice']]['parent_rate_name'] ?? '') . ' ';
524 $derived_str .= ($derived_data['type'] ?? 'percent') == 'absolute' ? $currencysymb . ' ' : '';
525 $derived_str .= ($derived_data['mode'] ?? 'discount') == 'discount' ? '-' : '+';
526 $derived_str .= $derived_data['value'] ?? 0;
527 $derived_str .= ($derived_data['type'] ?? 'percent') == 'percent' ? ' %' : '';
528 // set derived icon
529 $derived_icn = '<span class="badge badge-warning"><i class="' . VikBookingIcons::i('link') . '"></i></span> ';
530 }
531 ?>
532 <tr class="vbo-roverviewtablerow" id="vbo-roverw-<?php echo $roomrate['id'].'-'.$roomrate['idroom']; ?>" data-idprice="<?php echo $roomrate['idprice']; ?>">
533 <td class="vbo-roverv-rplan<?php echo $rplan_minlos || $rplan_haslos ? ' vbo-roverv-rplan-restricted' : ''; ?>" data-defrate="<?php echo $roomrate['cost']; ?>" data-roomname="<?php echo htmlspecialchars($roomrow['name']); ?>" data-room-plan="<?php echo $roomrate['idroom'] . '-' . $roomrate['idprice'] ?>" data-has-derived-rates="<?php echo in_array($roomrate['idprice'], $have_derived_rates) ? '1' : '0'; ?>">
534 <span class="vbo-rplan-name<?php echo $derived_str ? ' vbo-rplan-is-derived' : ''; ?>" title="<?php echo $this->escape($derived_str); ?>"><?php echo $derived_icn . $roomrate['name']; ?></span>
535 <?php
536 if ($rplan_minlos || $rplan_haslos) {
537 ?>
538 <div class="vbo-roverv-rplan-restrictions<?php echo $rplan_haslos ? ' vbo-roverv-rplan-haslosrates' : ''; ?>">
539 <?php
540 if ($rplan_haslos) {
541 ?>
542 <span class="badge badge-info vbo-roverv-rplan-restrictions-los">LOS</span>
543 <?php
544 }
545 if ($rplan_minlos) {
546 ?>
547 <span class="vbo-roverv-rplan-restrictions-lbl"><?php echo JText::translate('VBOMINIMUMSTAY'); ?></span>
548 <span class="vbo-roverv-rplan-restrictions-val"><?php echo $rplan_minlos; ?></span>
549 <?php
550 }
551 ?>
552 </div>
553 <?php
554 }
555 ?>
556 </td>
557 <?php
558 while ($cell_count < $MAX_DAYS) {
559 $style = '';
560 $dclass = "vbo-roverw-rplan-on";
561 if (count($closed_roomrateplans) > 0 && array_key_exists($roomrate['idprice'], $closed_roomrateplans) && in_array(date('Y-m-d', $nowts[0]), $closed_roomrateplans[$roomrate['idprice']])) {
562 $dclass = "vbo-roverw-rplan-off";
563 }
564 $id_block = "cell-".$nowts['mday'].'-'.$nowts['mon']."-".$nowts['year']."-".$roomrate['idprice']."-".$roomrate['idroom'];
565 $dclass .= ' day-block';
566
567 $day_key = date('Y-m-d', $nowts[0]);
568
569 if ($current_rates_pool[$rid][$day_key][$roomrate['idprice']] ?? []) {
570 // rates were loaded already
571 $tars = [$current_rates_pool[$rid][$day_key][$roomrate['idprice']]];
572 } else {
573 // load rates
574 $today_tsin = mktime($pcheckinh, $pcheckinm, 0, $nowts['mon'], $nowts['mday'], $nowts['year']);
575 $today_tsout = mktime($pcheckouth, $pcheckoutm, 0, $nowts['mon'], ($nowts['mday'] + 1), $nowts['year']);
576
577 $tars = VikBooking::applySeasonsRoom([$roomrate], $today_tsin, $today_tsout, [], $cached_seasons, VikBooking::getWdaySeasonRecords());
578 }
579
580 // store the OBP overrides for this day and rate plan
581 if (!empty($tars[0]['occupancy_ovr'])) {
582 if (!isset($room_obp_overrides[$nowts[0]])) {
583 $room_obp_overrides[$nowts[0]] = [];
584 }
585 $room_obp_overrides[$nowts[0]][] = [
586 'idprice' => $roomrate['idprice'],
587 'obp' => $tars[0]['occupancy_ovr'],
588 ];
589 }
590
591 ?>
592 <td align="center" class="<?php echo $dclass.' cell-'.$nowts['mday'].'-'.$nowts['mon']; ?>" id="<?php echo $id_block; ?>" data-vboprice="<?php echo $tars[0]['cost']; ?>" data-vbodate="<?php echo date('Y-m-d', $nowts[0]); ?>" data-vbodateread="<?php echo $days_labels[$nowts['wday']].', '.$months_labels[$nowts['mon']-1].' '.$nowts['mday']; ?>" data-vbospids="<?php echo (array_key_exists('spids', $tars[0]) && count($tars[0]['spids']) > 0 ? implode('-', $tars[0]['spids']) : ''); ?>"<?php echo $style; ?>>
593 <?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($tars[0]['cost']), $currencysymb, ['<span class="vbo-rplan-currency">%s</span>', '<span class="vbo-rplan-price">%s</span>']); ?>
594 </td>
595 <?php
596
597 $next = $nowts['mday'] + 1;
598 $dayts = mktime(0, 0, 0, $nowts['mon'], $next, $nowts['year']);
599 $nowts = getdate($dayts);
600
601 $cell_count++;
602 }
603 ?>
604 </tr>
605 <?php
606 }
607
608 // Occupancy Based Pricing rules
609 $adultsdiff = array_reverse(VikBooking::loadRoomAdultsDiff($roomrow['id']), true);
610
611 // display the room OBP rules (must be defined at room-level or no overrides will be displayed)
612 foreach ($adultsdiff as $adults => $diffusageprice) {
613 $base_obp_rules = $diffusageprice;
614 $nowts = getdate($tsstart);
615 $cell_count = 0;
616 ?>
617 <tr class="vbo-roverviewtablerow-occupancy">
618 <td>
619 <span class="vbo-rplan-name vbo-rplan-name-occ">
620 <span><?php echo JText::translate('VBPVIEWOPTIONALSTHREE'); ?></span>
621 <span class="vbo-occ-label"><?php VikBookingIcons::e('male'); ?> x <?php echo $adults ?></span>
622 </span>
623 </td>
624 <?php
625 while ($cell_count < $MAX_DAYS) {
626 $style = '';
627 $dclass = "vbo-roverw-rplan-occ";
628
629 // check for OBP overrides
630 $compare_obp_ovr = [];
631 if (isset($room_obp_overrides[$nowts[0]]) && count($room_obp_overrides[$nowts[0]]) === count($this->roomrates[$rid])) {
632 // the OBP overrides affect all room rate plans, make sure the overrides are identical though
633 $compare_obp_ovr = $room_obp_overrides[$nowts[0]][0]['obp'];
634 foreach ($room_obp_overrides[$nowts[0]] as $room_obp_override) {
635 if ($compare_obp_ovr != $room_obp_override['obp']) {
636 // this OBP override does not match the first one
637 $compare_obp_ovr = [];
638 break;
639 }
640 }
641 }
642 if ($compare_obp_ovr && isset($compare_obp_ovr[$adults])) {
643 // display the OBP override value for this day
644 $diffusageprice = $compare_obp_ovr[$adults];
645 } else {
646 // display the OBP rules at room-level
647 $diffusageprice = $base_obp_rules;
648 }
649
650 if (($diffusageprice['value'] - floor($diffusageprice['value'])) <= 0) {
651 $diffusageprice['value'] = intval($diffusageprice['value']);
652 }
653
654 if ($diffusageprice['chdisc'] == 1) {
655 // charge
656 if ($diffusageprice['valpcent'] == 1) {
657 // fixed value
658 $occ_diff = '+ ' . VikBooking::formatCurrencyNumber($diffusageprice['value'], $currencysymb);
659 } else {
660 // percentage value
661 $occ_diff = '+ ' . $diffusageprice['value'] . '%';
662 }
663 } else {
664 // discount
665 if ($diffusageprice['valpcent'] == 1) {
666 // fixed value
667 $occ_diff = '- ' . VikBooking::formatCurrencyNumber($diffusageprice['value'], $currencysymb);
668 } else {
669 // percentage value
670 $occ_diff = '- ' . $diffusageprice['value'] . '%';
671 }
672 }
673
674 ?>
675 <td align="center" class="<?php echo $dclass; ?>"<?php echo $style; ?>>
676 <span class="vbo-roverv-priceoccdiff"><?php echo $occ_diff; ?></span>
677 </td>
678 <?php
679
680 $next = $nowts['mday'] + 1;
681 $dayts = mktime(0, 0, 0, $nowts['mon'], $next, $nowts['year']);
682 $nowts = getdate($dayts);
683
684 $cell_count++;
685 }
686 ?>
687 </tr>
688 <?php
689 }
690 ?>
691 <tr class="vbo-roverviewtableavrow">
692 <td><span class="vbo-roverview-roomunits"><?php echo $roomrow['units']; ?></span><span class="vbo-roverview-uleftlbl"><?php echo JText::translate('VBPCHOOSEBUSYCAVAIL'); ?></span></td>
693 <?php
694 $nowts = getdate($tsstart);
695 $cell_count = 0;
696 $last_booking_info = [];
697 $last_booking_tag = [];
698 $room_bookings_pool = [];
699 while ($cell_count < $MAX_DAYS) {
700 $style = '';
701 $is_checkin = false;
702 $dclass = "vbo-roverw-daynotbusy";
703 $id_block = "cell-".$nowts['mday'].'-'.$nowts['mon']."-".$nowts['year']."-".$nowts['wday']."-".$rid."-avail";
704
705 $prev_day_key = date('Y-m-d', strtotime('-1 day', $nowts[0]));
706 $cur_day_key = date('Y-m-d', $nowts[0]);
707 $totfound = 0;
708 $last_bid_data = [];
709 $bids_pool = [];
710
711 $day_booking_snake = '';
712 $day_booking_data = [];
713
714 foreach (($this->booked_dates[$roomrow['id']] ?? []) as $b) {
715 $tmpone = getdate($b['checkin']);
716 $ritts = mktime(0, 0, 0, $tmpone['mon'], $tmpone['mday'], $tmpone['year']);
717 $tmptwo = getdate($b['checkout']);
718 $conts = mktime(0, 0, 0, $tmptwo['mon'], $tmptwo['mday'], $tmptwo['year']);
719 if ($nowts[0] >= $ritts && $nowts[0] < $conts) {
720 // booking found
721 $dclass = "vbo-roverw-daybusy";
722 $last_bid_data = $b;
723 $totfound++;
724
725 // push bid to pool
726 $bid_str = '-' . $b['idorder'] . '-';
727 if (!in_array($bid_str, $bids_pool)) {
728 $bids_pool[] = $bid_str;
729 }
730
731 // push day booking details
732 if (!isset($room_bookings_pool[$roomrow['id']])) {
733 $room_bookings_pool[$roomrow['id']] = [];
734 }
735 if (!isset($room_bookings_pool[$roomrow['id']][$cur_day_key])) {
736 $room_bookings_pool[$roomrow['id']][$cur_day_key] = [];
737 }
738 if ($roomrow['units'] == 1 && !$b['closure'] && !($b['sharedcal'] ?? 0)) {
739 // push room day booking
740 $room_bookings_pool[$roomrow['id']][$cur_day_key][] = $b;
741 }
742
743 if ($nowts[0] == $ritts) {
744 // turn flag on
745 $is_checkin = true;
746 }
747 }
748 }
749
750 // count units left
751 $units_remaining = $roomrow['units'] - $totfound;
752
753 if ($units_remaining > 0 && $units_remaining < $roomrow['units'] && $roomrow['units'] > 1) {
754 // room with hotel-like inventory
755 $dclass .= " vbo-roverw-daybusypartially";
756 } elseif ($units_remaining <= 0 && $roomrow['units'] <= 1 && $last_bid_data) {
757 // single-unit listing
758
759 // gather the booking color tag data
760 $btag_style = '';
761 $binfo = ($last_booking_info['data'] ?? null) && ($last_booking_info['id'] ?? null) == $last_bid_data['idorder'] ? $last_booking_info['data'] : $last_bid_data;
762 if ($binfo) {
763 $bcolortag = ($last_booking_tag['data'] ?? null) && ($last_booking_tag['id'] ?? null) == $last_bid_data['idorder'] ? $last_booking_tag['data'] : VikBooking::applyBookingColorTag($binfo);
764 if ($bcolortag) {
765 // set values
766 $bcolortag['name'] = JText::translate($bcolortag['name']);
767 $btag_style = "background-color: ".$bcolortag['color']."; color: ".(array_key_exists('fontcolor', $bcolortag) ? $bcolortag['fontcolor'] : '#ffffff').";";
768 $dclass .= ' vbo-roverw-hascolortag';
769 // cache data to avoid duplicate queries
770 $last_booking_info = [
771 'id' => $last_bid_data['idorder'],
772 'data' => $binfo,
773 ];
774 $last_booking_tag = [
775 'id' => $last_bid_data['idorder'],
776 'data' => $bcolortag,
777 ];
778 }
779 }
780 if (!empty($btag_style)) {
781 $style = !empty($style) ? ' style="display: none; '.$btag_style.'"' : ' style="'.$btag_style.'"';
782 $style .= ' title="'.addslashes($bcolortag['name']).'"';
783 }
784
785 /**
786 * Build room-day-booking snake.
787 *
788 * @since 1.17.2 (J) - 1.7.2 (WP)
789 */
790 if ($room_bookings_pool[$roomrow['id']][$cur_day_key][0] ?? []) {
791 $day_booking_data = $room_bookings_pool[$roomrow['id']][$cur_day_key][0];
792 }
793 if ($room_bookings_pool[$roomrow['id']][$prev_day_key][0] ?? []) {
794 if (!$room_bookings_pool[$roomrow['id']][$prev_day_key][0]['closure'] && date('Y-m-d', $room_bookings_pool[$roomrow['id']][$prev_day_key][0]['checkout']) == $cur_day_key) {
795 // prepend checkout snake
796 $day_booking_snake .= '<div class="vbo-tableaux-booking vbo-tableaux-booking-singleunit vbo-tableaux-booking-checkout"><span>&nbsp;</span></div>';
797 }
798 }
799 if ($day_booking_data && !$day_booking_data['closure']) {
800 // build tableaux-style snake container for guest
801 $customer_descr = '';
802 if ($is_checkin) {
803 // customer details
804 if (!empty($day_booking_data['first_name']) || !empty($day_booking_data['last_name'])) {
805 // check if we need to display a profile picture or a channel logo
806 $booking_avatar_src = null;
807 $booking_avatar_alt = null;
808 if (!empty($day_booking_data['pic'])) {
809 // customer profile picture
810 $booking_avatar_src = strpos($day_booking_data['pic'], 'http') === 0 ? $day_booking_data['pic'] : VBO_SITE_URI . 'resources/uploads/' . $day_booking_data['pic'];
811 $booking_avatar_alt = basename($booking_avatar_src);
812 } elseif (!empty($day_booking_data['idorderota']) && !empty($day_booking_data['channel'])) {
813 // channel logo
814 $logo_helper = VikBooking::getVcmChannelsLogo($day_booking_data['channel'], $get_istance = true);
815 if ($logo_helper !== false) {
816 $booking_avatar_src = $logo_helper->getSmallLogoURL();
817 $booking_avatar_alt = $logo_helper->provenience;
818 }
819 }
820
821 if (!empty($booking_avatar_src)) {
822 // make sure the alt attribute is not too long in case of broken images
823 $booking_avatar_alt = !empty($booking_avatar_alt) && strlen($booking_avatar_alt) > 15 ? '...' . substr($booking_avatar_alt, -12) : $booking_avatar_alt;
824 // append booking avatar image
825 $customer_descr .= '<span class="vbo-tableaux-booking-avatar"><img src="' . $booking_avatar_src . '" class="vbo-tableaux-booking-avatar-img" ' . (!empty($booking_avatar_alt) ? 'alt="' . htmlspecialchars($booking_avatar_alt) . '" ' : '') . '/></span>';
826 }
827
828 // customer name
829 $customer_fullname = trim($day_booking_data['first_name'] . ' ' . $day_booking_data['last_name']);
830 if (strlen($customer_fullname) > 26) {
831 if (function_exists('mb_substr')) {
832 $customer_fullname = trim(mb_substr($customer_fullname, 0, 26, 'UTF-8')) . '..';
833 } else {
834 $customer_fullname = trim(substr($customer_fullname, 0, 26)) . '..';
835 }
836 }
837 $customer_descr .= '<span class="vbo-tableaux-guest-name">' . $customer_fullname . '</span>';
838 } else {
839 // parse the customer data string
840 $custdata_parts = explode("\n", $day_booking_data['custdata']);
841 $enoughinfo = false;
842 if (count($custdata_parts) > 2 && strpos($custdata_parts[0], ':') !== false && strpos($custdata_parts[1], ':') !== false) {
843 // get the first two fields
844 $custvalues = array();
845 foreach ($custdata_parts as $custdet) {
846 if (strlen($custdet) < 1) {
847 continue;
848 }
849 $custdet_parts = explode(':', $custdet);
850 if (count($custdet_parts) >= 2) {
851 unset($custdet_parts[0]);
852 array_push($custvalues, trim(implode(':', $custdet_parts)));
853 }
854 if (count($custvalues) > 1) {
855 break;
856 }
857 }
858 if (count($custvalues) > 1) {
859 $enoughinfo = true;
860 $customer_nominative = trim(implode(' ', $custvalues));
861 if (strlen($customer_nominative) > 26) {
862 if (function_exists('mb_substr')) {
863 $customer_nominative = trim(mb_substr($customer_nominative, 0, 26, 'UTF-8')) . '..';
864 } else {
865 $customer_nominative = trim(substr($customer_nominative, 0, 26)) . '..';
866 }
867 }
868 if (!empty($day_booking_data['idorderota']) && !empty($day_booking_data['channel'])) {
869 // add support for the channel logo for the imported OTA reservations with no customer record
870 $logo_helper = VikBooking::getVcmChannelsLogo($day_booking_data['channel'], $get_istance = true);
871 if ($logo_helper !== false) {
872 $booking_avatar_src = $logo_helper->getSmallLogoURL();
873 $booking_avatar_alt = $logo_helper->provenience;
874 // make sure the alt attribute is not too long in case of broken images
875 $booking_avatar_alt = !empty($booking_avatar_alt) && strlen($booking_avatar_alt) > 15 ? '...' . substr($booking_avatar_alt, -12) : $booking_avatar_alt;
876 // append booking avatar image
877 $customer_descr .= '<span class="vbo-tableaux-booking-avatar"><img src="' . $booking_avatar_src . '" class="vbo-tableaux-booking-avatar-img" ' . (!empty($booking_avatar_alt) ? 'alt="' . htmlspecialchars($booking_avatar_alt) . '" ' : '') . '/></span>';
878 }
879 }
880 // set customer nominative built
881 $customer_descr .= '<span class="vbo-tableaux-guest-name">' . $customer_nominative . '</span>';
882 }
883 }
884 if (!$enoughinfo) {
885 $customer_descr .= '<span class="vbo-tableaux-guest-name">#' . $day_booking_data['idorder'] . '</span>';
886 }
887 }
888 }
889 // set value
890 $day_booking_snake .= '<div class="vbo-tableaux-booking vbo-tableaux-booking-singleunit ' . ($is_checkin ? 'vbo-tableaux-booking-checkin' : 'vbo-tableaux-booking-stay') . '"' . ($is_checkin ? ' data-nights="' . $day_booking_data['days'] . '"' : '') . '><span>' . ($is_checkin ? $customer_descr : '&nbsp;') . '</span></div>';
891 }
892 }
893
894 if ($roomrow['units'] <= 1 && !$totfound) {
895 // no booked records
896 if ($room_bookings_pool[$roomrow['id']][$prev_day_key][0] ?? []) {
897 if (!$room_bookings_pool[$roomrow['id']][$prev_day_key][0]['closure'] && date('Y-m-d', $room_bookings_pool[$roomrow['id']][$prev_day_key][0]['checkout']) == $cur_day_key) {
898 // prepend checkout snake
899 $day_booking_snake .= '<div class="vbo-tableaux-booking vbo-tableaux-booking-singleunit vbo-tableaux-booking-checkout"><span>&nbsp;</span></div>';
900 }
901 }
902 }
903
904 ?>
905 <td align="center" class="vbo-grid-avcell <?php echo $dclass.' cell-'.$nowts['mday'].'-'.$nowts['mon']; ?>" id="<?php echo $id_block; ?>" data-bids="<?php echo implode(',', $bids_pool); ?>" data-vbodateread="<?php echo $days_labels[$nowts['wday']].', '.$months_labels[$nowts['mon']-1].' '.$nowts['mday']; ?>" data-curunits="<?php echo $units_remaining; ?>"<?php echo $style; ?>>
906 <?php
907 if (($day_booking_snake ?? '')) {
908 echo $day_booking_snake;
909 } else {
910 ?>
911 <span class="vbo-roverw-curunits"><?php echo $units_remaining; ?></span>
912 <?php
913 }
914 ?>
915 </td>
916 <?php
917
918 $next = $nowts['mday'] + 1;
919 $dayts = mktime(0, 0, 0, ($nowts['mon'] < 10 ? "0".$nowts['mon'] : $nowts['mon']), ($next < 10 ? "0".$next : $next), $nowts['year']);
920 $nowts = getdate($dayts);
921
922 $cell_count++;
923 }
924 ?>
925 </tr>
926 <?php
927 // VBO 1.11 - MinLOS restrictions row
928 ?>
929 <tr class="vbo-roverviewtablerow-restrs">
930 <td><span class="vbo-roverview-restrs"><?php echo JText::translate('VBOMINIMUMSTAY'); ?></span></td>
931 <?php
932 $nowts = getdate($tsstart);
933 $cell_count = 0;
934 $glob_minlos = VikBooking::getDefaultNightsCalendar();
935 $glob_minlos = $glob_minlos < 1 ? 1 : $glob_minlos;
936 while ($cell_count < $MAX_DAYS) {
937 $style = '';
938 $dclass = "vbo-roverw-rplan-restr";
939
940 $id_block = "cell-".$nowts['mday'].'-'.$nowts['mon']."-".$nowts['year']."-".$rid."-restr";
941
942 $today_tsin = mktime(0, 0, 0, $nowts['mon'], $nowts['mday'], $nowts['year']);
943 $today_tsout = mktime(0, 0, 0, $nowts['mon'], ($nowts['mday'] + 1), $nowts['year']);
944
945 $restr = VikBooking::parseSeasonRestrictions($today_tsin, $today_tsout, 1, (isset($this->all_restrictions[$rid]) ? $this->all_restrictions[$rid] : array()));
946 $minlos = count($restr) ? $restr['minlos'] : $glob_minlos;
947
948 ?>
949 <td align="center" class="<?php echo $dclass; ?>" id="<?php echo $id_block; ?>"<?php echo $style; ?>>
950 <span class="vbo-roverw-curminlos<?php echo $minlos > 1 ? ' vbo-roverw-curminlos-active' : ''; ?>"><?php echo $minlos; ?></span>
951 </td>
952 <?php
953
954 $next = $nowts['mday'] + 1;
955 $dayts = mktime(0, 0, 0, ($nowts['mon'] < 10 ? "0".$nowts['mon'] : $nowts['mon']), ($next < 10 ? "0".$next : $next), $nowts['year']);
956 $nowts = getdate($dayts);
957
958 $cell_count++;
959 }
960 ?>
961 </tr>
962 <?php
963 //
964 ?>
965 </tbody>
966 </table>
967 </div>
968 <div class="vbo-ratesoverview-period-container" style="<?php echo $collapse_status ? 'display: none;' : ''; ?>">
969 <div class="vbo-ratesoverview-period-inner">
970 <div class="vbo-ratesoverview-period-lbl">
971 <span><?php echo JText::translate('VBOROVWSELPERIOD'); ?></span>
972 </div>
973 <div class="vbo-ratesoverview-period-boxes">
974 <div class="vbo-ratesoverview-period-boxes-inner">
975 <div class="vbo-ratesoverview-period-box-left">
976 <div class="vbo-ratesoverview-period-box-lbl">
977 <span><?php echo JText::translate('VBOROVWSELPERIODFROM'); ?></span>
978 </div>
979 <div class="vbo-ratesoverview-period-box-val">
980 <div class="vbo-ratesoverview-period-from">
981 <span class="vbo-ratesoverview-period-wday"></span>
982 <span class="vbo-ratesoverview-period-mday"></span>
983 <span class="vbo-ratesoverview-period-month"></span>
984 </div>
985 <span class="vbo-ratesoverview-period-from-icon"><?php VikBookingIcons::e('calendar'); ?></span>
986 </div>
987 </div>
988 <div class="vbo-ratesoverview-period-box-right">
989 <div class="vbo-ratesoverview-period-box-lbl">
990 <span><?php echo JText::translate('VBOROVWSELPERIODTO'); ?></span>
991 </div>
992 <div class="vbo-ratesoverview-period-box-val">
993 <div class="vbo-ratesoverview-period-to">
994 <span class="vbo-ratesoverview-period-wday"></span>
995 <span class="vbo-ratesoverview-period-mday"></span>
996 <span class="vbo-ratesoverview-period-month"></span>
997 </div>
998 <span class="vbo-ratesoverview-period-to-icon"><?php VikBookingIcons::e('calendar'); ?></span>
999 </div>
1000 </div>
1001 </div>
1002 <div class="vbo-ratesoverview-period-box-cals" style="display: none;">
1003 <div class="vbo-ratesoverview-period-box-cals-inner">
1004 <div class="vbo-ratesoverview-period-cal-left">
1005 <h4><?php echo JText::translate('VBOROVWSELPERIODFROM') . ' / ' . JText::translate('VBOROVWSELPERIODTO'); ?></h4>
1006 <div class="vbo-period-from" data-idroom="<?php echo $rid; ?>" data-roomname="<?php echo htmlspecialchars($roomrow['name']); ?>"></div>
1007 <div class="vbo-period-to" data-idroom="<?php echo $rid; ?>" data-roomname="<?php echo htmlspecialchars($roomrow['name']); ?>"></div>
1008 <input type="hidden" class="vbo-period-from-val" value="" />
1009 <input type="hidden" class="vbo-period-to-val" value="" />
1010 </div>
1011 <div class="vbo-ratesoverview-period-cal-cmd">
1012 <h4><?php echo JText::translate('VBOROVWSELRPLAN'); ?></h4>
1013 <div class="vbo-ratesoverview-period-cal-cmd-inner">
1014 <select class="vbo-selperiod-rplanid" onchange="vboUpdateRplan(this);">
1015 <?php
1016 foreach ($this->roomrates[$rid] as $krr => $roomrate) {
1017 ?>
1018 <option value="<?php echo $roomrate['idprice']; ?>" data-defrate="<?php echo $roomrate['cost']; ?>"<?php echo $krr < 1 ? ' selected="selected"' : ''; ?>><?php echo $roomrate['name']; ?></option>
1019 <?php
1020 }
1021 ?>
1022 </select>
1023 </div>
1024 </div>
1025 </div>
1026 </div>
1027 </div>
1028 </div>
1029 <div class="vbo-ratesoverview-orphans-wrapper" id="vbo-ratesoverview-orphans-wrapper-<?php echo $rid; ?>" style="display: none;">
1030 <div class="vbo-ratesoverview-orphans-lbl">
1031 <span><?php echo $vbo_app->createPopover(array('title' => JText::translate('VBORPHANSFOUND'), 'content' => JText::translate('VBORPHANSFOUNDSHELP'), 'icon_class' => VikBookingIcons::i('exclamation-triangle'))); ?> <?php echo JText::translate('VBORPHANSFOUND'); ?></span>
1032 </div>
1033 <div class="vbo-ratesoverview-orphans-list" id="vbo-ratesoverview-orphans-list-<?php echo $rid; ?>"></div>
1034 </div>
1035 </div>
1036 </div>
1037 </div>
1038
1039 <?php
1040 // start los pricing overview IF statement for just 1 room
1041 if (count($this->req_room_ids) < 2) :
1042 ?>
1043 <div class="vbo-ratesoverview-lostab-cont"<?php echo (!empty($cookie_tab) && $cookie_tab == 'cal' ? ' style="display: none;"' : ''); ?>>
1044 <?php
1045 if (count($seasons_cal) > 0) {
1046 //Special Prices Timeline
1047 if (isset($seasons_cal['seasons']) && count($seasons_cal['seasons'])) {
1048 ?>
1049 <div class="vbo-timeline-container">
1050 <ul id="vbo-timeline">
1051 <?php
1052 foreach ($seasons_cal['seasons'] as $ks => $timeseason) {
1053 $s_val_diff = '';
1054 if ($timeseason['val_pcent'] == 2) {
1055 //percentage
1056 $s_val_diff = (($timeseason['diffcost'] - abs($timeseason['diffcost'])) > 0.00 ? VikBooking::numberFormat($timeseason['diffcost']) : intval($timeseason['diffcost']))." %";
1057 } else {
1058 //absolute
1059 $s_val_diff = VikBooking::formatCurrencyNumber(VikBooking::numberFormat($timeseason['diffcost']), $currencysymb, ['spacing' => '']);
1060 }
1061 $s_explanation = array();
1062 if (empty($timeseason['year'])) {
1063 $s_explanation[] = JText::translate('VBSEASONANYYEARS');
1064 }
1065 if (!empty($timeseason['losoverride'])) {
1066 $s_explanation[] = JText::translate('VBSEASONBASEDLOS');
1067 }
1068 ?>
1069 <li data-fromts="<?php echo $timeseason['from_ts']; ?>" data-tots="<?php echo $timeseason['to_ts']; ?>">
1070 <input type="radio" name="timeline" class="vbo-timeline-radio" id="vbo-timeline-dot<?php echo $ks; ?>" <?php echo $ks === 0 ? 'checked="checked"' : ''; ?>/>
1071 <div class="vbo-timeline-relative">
1072 <label class="vbo-timeline-label" for="vbo-timeline-dot<?php echo $ks; ?>"><?php echo $timeseason['spname']; ?></label>
1073 <span class="vbo-timeline-date"><?php echo VikBooking::formatSeasonDates($timeseason['from_ts'], $timeseason['to_ts']); ?></span>
1074 <span class="vbo-timeline-circle" onclick="Javascript: jQuery('#vbo-timeline-dot<?php echo $ks; ?>').trigger('click');"></span>
1075 </div>
1076 <div class="vbo-timeline-content">
1077 <p>
1078 <span class="vbo-seasons-calendar-slabel vbo-seasons-calendar-season-<?php echo $timeseason['type'] == 2 ? 'discount' : 'charge'; ?>"><?php echo $timeseason['type'] == 2 ? '-' : '+'; ?> <?php echo $s_val_diff; ?> <?php echo JText::translate('VBSEASONPERNIGHT'); ?></span>
1079 <br/>
1080 <?php
1081 if (count($s_explanation) > 0) {
1082 echo implode(' - ', $s_explanation);
1083 }
1084 ?>
1085 </p>
1086 </div>
1087 </li>
1088 <?php
1089 }
1090 ?>
1091 </ul>
1092 </div>
1093
1094 <script>
1095 jQuery(function() {
1096 jQuery('.vbo-timeline-container').css('min-height', (jQuery('.vbo-timeline-container').outerHeight() + 20));
1097 });
1098 </script>
1099 <?php
1100 }
1101 //
1102 //Begin Seasons Calendar
1103 ?>
1104 <div class="table-responsive">
1105 <table class="table vbo-seasons-calendar-table">
1106 <tr class="vbo-seasons-calendar-nightsrow">
1107 <td>&nbsp;</td>
1108 <?php
1109 foreach ($seasons_cal['offseason'] as $numnights => $ntars) {
1110 ?>
1111 <td><span><?php echo JText::sprintf(($numnights > 1 ? 'VBOSEASONCALNUMNIGHTS' : 'VBOSEASONCALNUMNIGHT'), $numnights); ?></span></td>
1112 <?php
1113 }
1114 ?>
1115 </tr>
1116 <tr class="vbo-seasons-calendar-offseasonrow">
1117 <td>
1118 <span class="vbo-seasons-calendar-offseasonname"><?php echo JText::translate('VBOSEASONSCALOFFSEASONPRICES'); ?></span>
1119 </td>
1120 <?php
1121 foreach ($seasons_cal['offseason'] as $numnights => $tars) {
1122 ?>
1123 <td>
1124 <div class="vbo-seasons-calendar-offseasoncosts">
1125 <?php
1126 foreach ($tars as $tar) {
1127 ?>
1128 <div class="vbo-seasons-calendar-offseasoncost">
1129 <?php
1130 if ($price_types_show) {
1131 ?>
1132 <span class="vbo-seasons-calendar-pricename"><?php echo $tar['name']; ?></span>
1133 <?php
1134 }
1135 ?>
1136 <span class="vbo-seasons-calendar-pricecost">
1137 <?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($tar['cost']), $currencysymb, ['<span class="vbo_currency">%s</span>', '<span class="vbo_price">%s</span>', 'spacing' => '']); ?>
1138 </span>
1139 </div>
1140 <?php
1141 if (!$price_types_show) {
1142 break;
1143 }
1144 }
1145 ?>
1146 </div>
1147 </td>
1148 <?php
1149 }
1150 ?>
1151 </tr>
1152 <?php
1153 if (!isset($seasons_cal['seasons'])) {
1154 $seasons_cal['seasons'] = array();
1155 }
1156 foreach ($seasons_cal['seasons'] as $s_id => $s) {
1157 $restr_diff_nights = array();
1158 if ($los_show && array_key_exists($s_id, $seasons_cal['restrictions'])) {
1159 $restr_diff_nights = VikBooking::compareSeasonRestrictionsNights($seasons_cal['restrictions'][$s_id]);
1160 }
1161 $s_val_diff = '';
1162 if ($s['val_pcent'] == 2) {
1163 //percentage
1164 $s_val_diff = (($s['diffcost'] - abs($s['diffcost'])) > 0.00 ? VikBooking::numberFormat($s['diffcost']) : intval($s['diffcost']))." %";
1165 } else {
1166 //absolute
1167 $s_val_diff = VikBooking::formatCurrencyNumber(VikBooking::numberFormat($s['diffcost']), $currencysymb, ['spacing' => '']);
1168 }
1169 ?>
1170 <tr class="vbo-seasons-calendar-seasonrow">
1171 <td>
1172 <div class="vbo-seasons-calendar-seasondates">
1173 <span class="vbo-seasons-calendar-seasonfrom"><?php echo date(str_replace("/", $datesep, $df), $s['from_ts']); ?></span>
1174 <span class="vbo-seasons-calendar-seasondates-separe">-</span>
1175 <span class="vbo-seasons-calendar-seasonto"><?php echo date(str_replace("/", $datesep, $df), $s['to_ts']); ?></span>
1176 </div>
1177 <div class="vbo-seasons-calendar-seasonchargedisc">
1178 <span class="vbo-seasons-calendar-slabel vbo-seasons-calendar-season-<?php echo $s['type'] == 2 ? 'discount' : 'charge'; ?>"><span class="vbo-seasons-calendar-operator"><?php echo $s['type'] == 2 ? '-' : '+'; ?></span><?php echo $s_val_diff; ?></span>
1179 </div>
1180 <span class="vbo-seasons-calendar-seasonname"><a href="index.php?option=com_vikbooking&amp;task=editseason&amp;cid[]=<?php echo $s['id']; ?>" target="_blank"><?php echo $s['spname']; ?></a></span>
1181 <?php
1182 if ($los_show && array_key_exists($s_id, $seasons_cal['restrictions']) && count($restr_diff_nights) == 0) {
1183 //Season Restrictions
1184 $season_restrictions = array();
1185 foreach ($seasons_cal['restrictions'][$s_id] as $restr) {
1186 $season_restrictions = $restr;
1187 break;
1188 }
1189 ?>
1190 <div class="vbo-seasons-calendar-restrictions">
1191 <?php
1192 if ($season_restrictions['minlos'] > 1) {
1193 ?>
1194 <span class="vbo-seasons-calendar-restriction-minlos"><?php echo JText::translate('VBORESTRMINLOS'); ?><span class="vbo-seasons-calendar-restriction-minlos-badge"><?php echo $season_restrictions['minlos']; ?></span></span>
1195 <?php
1196 }
1197 if (array_key_exists('maxlos', $season_restrictions) && $season_restrictions['maxlos'] > 1) {
1198 ?>
1199 <span class="vbo-seasons-calendar-restriction-maxlos"><?php echo JText::translate('VBORESTRMAXLOS'); ?><span class="vbo-seasons-calendar-restriction-maxlos-badge"><?php echo $season_restrictions['maxlos']; ?></span></span>
1200 <?php
1201 }
1202 if (array_key_exists('wdays', $season_restrictions) && count($season_restrictions['wdays']) > 0) {
1203 ?>
1204 <div class="vbo-seasons-calendar-restriction-wdays">
1205 <label><?php echo JText::translate((count($season_restrictions['wdays']) > 1 ? 'VBORESTRARRIVWDAYS' : 'VBORESTRARRIVWDAY')); ?></label>
1206 <?php
1207 foreach ($season_restrictions['wdays'] as $wday) {
1208 ?>
1209 <span class="vbo-seasons-calendar-restriction-wday"><?php echo VikBooking::sayWeekDay($wday); ?></span>
1210 <?php
1211 }
1212 ?>
1213 </div>
1214 <?php
1215 } elseif ((array_key_exists('cta', $season_restrictions) && count($season_restrictions['cta']) > 0) || (array_key_exists('ctd', $season_restrictions) && count($season_restrictions['ctd']) > 0)) {
1216 if (array_key_exists('cta', $season_restrictions) && count($season_restrictions['cta']) > 0) {
1217 ?>
1218 <div class="vbo-seasons-calendar-restriction-wdays vbo-seasons-calendar-restriction-cta">
1219 <label><?php echo JText::translate('VBORESTRWDAYSCTA'); ?></label>
1220 <?php
1221 foreach ($season_restrictions['cta'] as $wday) {
1222 ?>
1223 <span class="vbo-seasons-calendar-restriction-wday"><?php echo VikBooking::sayWeekDay(str_replace('-', '', $wday)); ?></span>
1224 <?php
1225 }
1226 ?>
1227 </div>
1228 <?php
1229 }
1230 if (array_key_exists('ctd', $season_restrictions) && count($season_restrictions['ctd']) > 0) {
1231 ?>
1232 <div class="vbo-seasons-calendar-restriction-wdays vbo-seasons-calendar-restriction-ctd">
1233 <label><?php echo JText::translate('VBORESTRWDAYSCTD'); ?></label>
1234 <?php
1235 foreach ($season_restrictions['ctd'] as $wday) {
1236 ?>
1237 <span class="vbo-seasons-calendar-restriction-wday"><?php echo VikBooking::sayWeekDay(str_replace('-', '', $wday)); ?></span>
1238 <?php
1239 }
1240 ?>
1241 </div>
1242 <?php
1243 }
1244 }
1245 ?>
1246 </div>
1247 <?php
1248 }
1249 ?>
1250 </td>
1251 <?php
1252 if (array_key_exists($s_id, $seasons_cal['season_prices']) && count($seasons_cal['season_prices'][$s_id]) > 0) {
1253 foreach ($seasons_cal['season_prices'][$s_id] as $numnights => $tars) {
1254 $show_day_cost = true;
1255 if ($los_show && array_key_exists($s_id, $seasons_cal['restrictions']) && array_key_exists($numnights, $seasons_cal['restrictions'][$s_id])) {
1256 if ($seasons_cal['restrictions'][$s_id][$numnights]['allowed'] === false) {
1257 $show_day_cost = false;
1258 }
1259 }
1260 ?>
1261 <td>
1262 <?php
1263 if ($show_day_cost) {
1264 ?>
1265 <div class="vbo-seasons-calendar-seasoncosts">
1266 <?php
1267 foreach ($tars as $tar) {
1268 //print the types of price that are not being modified by this special price with opacity
1269 $not_affected = (!array_key_exists('origdailycost', $tar));
1270 //
1271 ?>
1272 <div class="vbo-seasons-calendar-seasoncost<?php echo ($not_affected ? ' vbo-seasons-calendar-seasoncost-notaffected' : ''); ?>">
1273 <?php
1274 if ($price_types_show) {
1275 ?>
1276 <span class="vbo-seasons-calendar-pricename"><?php echo $tar['name']; ?></span>
1277 <?php
1278 }
1279 ?>
1280 <span class="vbo-seasons-calendar-pricecost">
1281 <?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($tar['cost']), $currencysymb, ['<span class="vbo_currency">%s</span>', '<span class="vbo_price">%s</span>', 'spacing' => '']); ?>
1282 </span>
1283 </div>
1284 <?php
1285 if (!$price_types_show) {
1286 break;
1287 }
1288 }
1289 ?>
1290 </div>
1291 <?php
1292 } else {
1293 ?>
1294 <div class="vbo-seasons-calendar-seasoncosts-disabled"></div>
1295 <?php
1296 }
1297 ?>
1298 </td>
1299 <?php
1300 }
1301 }
1302 ?>
1303 </tr>
1304 <?php
1305 }
1306 ?>
1307 </table>
1308 </div>
1309 <?php
1310 //End Seasons Calendar
1311 } else {
1312 ?>
1313 <p class="vbo-warning"><?php echo JText::translate('VBOWARNNORATESROOM'); ?></p>
1314 <?php
1315 }
1316 ?>
1317 </div>
1318 <?php
1319 // end los pricing overview IF statement for just 1 room
1320 endif;
1321 }
1322 ?>
1323 </div>
1324 <?php
1325
1326 // overlay modal to change rates, restrictions etc..
1327 $vcm_enabled = VikBooking::vcmAutoUpdate();
1328 ?>
1329 <div class="vbo-ratesoverview-newratesrestr-helper" style="display: none;">
1330
1331 <div class="vbo-ratesoverview-newratesrestr-wrap">
1332 <div class="vbo-roverw-infoblock">
1333 <span id="rovervw-roomname"></span>
1334 <div class="vbo-roverw-inforates">
1335 <span id="rovervw-rplan"></span>
1336 <span id="rovervw-fromdate"></span> - <span id="rovervw-todate"></span>
1337 </div>
1338 </div>
1339 <div class="vbo-roverw-alldays">
1340 <div class="vbo-roverw-alldays-inner"></div>
1341 </div>
1342 <div class="vbo-roverw-setnewrate">
1343 <div class="vbo-roverw-setnewrate-title">
1344 <h4><?php VikBookingIcons::e('tag'); ?> <?php echo JText::translate('VBO_RATES_AND_RESTR'); ?></h4>
1345 <div class="vbo-roverw-setnewrate-skip-derived vbo-toggle-small vbo-toggle-mini" style="display: none;">
1346 <label for="roverw-skip-derived-on" class="vbo-roverw-setnewrate-skip-derived-lbl"><?php echo JText::translate('VBO_SKIP_DERIVED_RPLANS'); ?></label>
1347 <?php echo $vbo_app->printYesNoButtons('roverw-skip-derived', JText::translate('VBYES'), JText::translate('VBNO'), 0, 1, 0); ?>
1348 </div>
1349 </div>
1350 <div class="vbo-roverw-flexnew">
1351 <div class="vbo-roverw-newrwrap" data-rate-type="fixed">
1352 <h4>
1353 <button type="button" class="vbo-btn-transparent vbo-context-menu-rate-type">
1354 <span class="vbo-transparent-wrap">
1355 <?php VikBookingIcons::e('edit'); ?>
1356 <span class="vbo-roverw-action-rates-title"><?php echo JText::translate('VBRATESOVWSETNEWRATE'); ?></span>
1357 <?php VikBookingIcons::e('chevron-down'); ?>
1358 </span>
1359 </button>
1360 </h4>
1361 <div class="vbo-roverw-newrcont" data-rate-type="fixed">
1362 <label for="roverw-newrate" class="vbo-roverw-setnewrate-currency"><?php echo $currencysymb; ?></label>
1363 <input type="number" step="any" min="0" id="roverw-newrate" value="" size="7" />
1364 </div>
1365 <div class="vbo-roverw-newrcont" data-rate-type="addsub" style="display: none;">
1366 <div class="vbo-roverw-setnewrate-addsub-elem">
1367 <select data-rate-type-rule="rmodsop">
1368 <option value="1">+</option>
1369 <option value="0">-</option>
1370 </select>
1371 </div>
1372 <div class="vbo-roverw-setnewrate-addsub-elem">
1373 <input type="number" value="0" step="any" min="0" data-rate-type-rule="rmodsamount" />
1374 </div>
1375 <div class="vbo-roverw-setnewrate-addsub-elem">
1376 <select data-rate-type-rule="rmodsval">
1377 <option value="0"><?php echo $currencysymb; ?></option>
1378 <option value="1">%</option>
1379 </select>
1380 </div>
1381 </div>
1382 </div>
1383 <div class="vbo-roverw-newrestr-wrap" style="display: none;">
1384 <div class="vbo-roverw-newrestrcont">
1385 <h4><?php VikBookingIcons::e('ban'); ?> <?php echo JText::translate('VBOMINIMUMSTAYSET'); ?></h4>
1386 <div class="vbo-roverw-newrestrcont-inner">
1387 <label for="roverw-newrestr" class="vbo-roverw-setnewrestr-lbl"><?php echo JText::translate('VBDAYS'); ?></label>
1388 <input type="number" step="1" min="0" id="roverw-newrestr" value="" size="7" />
1389 </div>
1390 </div>
1391 </div>
1392 </div>
1393 <div class="vbo-roverw-setnewrate-inner">
1394 <div class="vbo-roverw-setnewrate-vcm">
1395 <div class="vbo-roverw-setnewrate-vcm-head">
1396 <span class="<?php echo $vcm_enabled < 0 ? 'vbo-vcm-notinstalled' : 'vbo-vcm-installed'; ?>">
1397 <?php echo $vbo_app->createPopover(array('title' => JText::translate('VBOUPDRATESONCHANNELS'), 'content' => ($vcm_enabled < 0 ? JText::translate('VBCONFIGVCMAUTOUPDMISS') : JText::translate('VBOUPDRATESONCHANNELSHELP')), 'icon_class' => VikBookingIcons::i('rocket'))); ?>
1398 <?php echo JText::translate('VBOUPDRATESONCHANNELS'); ?>
1399 </span>
1400 </div>
1401 <div class="vbo-roverw-setnewrate-vcm-body vbo-toggle-small">
1402 <?php
1403 echo $vbo_app->printYesNoButtons('roverw-newrate-vcm', JText::translate('VBYES'), JText::translate('VBNO'), ($vcm_enabled > 0 ? 1 : 0), 1, 0, 'vboCheckVcmRestrictions();', ['blue']);
1404
1405 if ($vcm_enabled < 0) {
1406 // disable the toggle button when VCM is not available
1407 ?>
1408 <script type="text/javascript">
1409 jQuery(function() {
1410 jQuery('input[name="roverw-newrate-vcm"]').prop('disabled', true);
1411 });
1412 </script>
1413 <?php
1414 }
1415 ?>
1416 </div>
1417 <div class="vbo-roverw-setnewrate-vcm-otas"></div>
1418 </div>
1419 </div>
1420 </div>
1421 <div class="vbo-roverw-closeopenrp">
1422 <h4><i class="vboicn-switch"></i><?php echo JText::translate('VBRATESOVWCLOSEOPENRRP'); ?> <span id="rovervw-closeopen-rplan"></span> <span class="vbo-roverw-closeopenrp-arrow"><?php VikBookingIcons::e('chevron-down'); ?></span></h4>
1423 <div class="vbo-roverw-closeopenrp-btns">
1424 <button type="button" class="btn btn-danger" onclick="modRoomRatePlan('close');"><?php VikBookingIcons::e('lock'); ?> <?php echo JText::translate('VBRATESOVWCLOSERRP'); ?></button>
1425 <button type="button" class="btn btn-success" onclick="modRoomRatePlan('open');"><?php VikBookingIcons::e('lock-open'); ?> <?php echo JText::translate('VBRATESOVWOPENRRP'); ?></button>
1426 </div>
1427 </div>
1428 </div>
1429
1430 <div class="vbo-roverw-setnewrate-vcm-ota-pricing-alteration">
1431 <div class="vbo-roverw-setnewrate-vcm-ota-alteration-elem">
1432 <select data-alter-rule="rmodsop">
1433 <option value="1">+</option>
1434 <option value="0">-</option>
1435 </select>
1436 </div>
1437 <div class="vbo-roverw-setnewrate-vcm-ota-alteration-elem">
1438 <input type="number" value="" step="any" min="0" data-alter-rule="rmodsamount" />
1439 </div>
1440 <div class="vbo-roverw-setnewrate-vcm-ota-alteration-elem">
1441 <select data-alter-rule="rmodsval">
1442 <option value="1">%</option>
1443 <option value="0"><?php echo $currencysymb; ?></option>
1444 </select>
1445 </div>
1446 </div>
1447
1448 </div>
1449
1450 <form name="adminForm" id="adminForm" action="index.php" method="post">
1451 <input type="hidden" name="task" value="">
1452 <input type="hidden" name="option" value="com_vikbooking">
1453 </form>
1454
1455 <a id="vbo-base-booknow-link" style="display: none;" href="index.php?option=com_vikbooking&task=calendar&cid[]=&checkin=&checkout=&adults=&children=&idprice=&booknow=1"></a>
1456 <a class="vbo-basenavuri-details" href="index.php?option=com_vikbooking&task=editorder&goto=ratesoverv&cid[]=%d" style="display: none;"></a>
1457
1458 <script type="text/Javascript">
1459 function vboFormatCalDate(elem, idc) {
1460 var vb_period = elem.parent().find('.vbo-'+idc+'-val').val();
1461 if (!vb_period || !vb_period.length) {
1462 return false;
1463 }
1464 var vb_period_parts = vb_period.split("/");
1465 if ('%d/%m/%Y' == '<?php echo $vbo_df; ?>') {
1466 var period_date = new Date(vb_period_parts[2], (parseInt(vb_period_parts[1]) - 1), parseInt(vb_period_parts[0], 10), 0, 0, 0, 0);
1467 var data = [parseInt(vb_period_parts[0], 10), parseInt(vb_period_parts[1]), vb_period_parts[2]];
1468 } else if ('%m/%d/%Y' == '<?php echo $vbo_df; ?>') {
1469 var period_date = new Date(vb_period_parts[2], (parseInt(vb_period_parts[0]) - 1), parseInt(vb_period_parts[1], 10), 0, 0, 0, 0);
1470 var data = [parseInt(vb_period_parts[1], 10), parseInt(vb_period_parts[0]), vb_period_parts[2]];
1471 } else {
1472 var period_date = new Date(vb_period_parts[0], (parseInt(vb_period_parts[1]) - 1), parseInt(vb_period_parts[2], 10), 0, 0, 0, 0);
1473 var data = [parseInt(vb_period_parts[2], 10), parseInt(vb_period_parts[1]), vb_period_parts[0]];
1474 }
1475 var elcont = elem.closest('.vbo-ratesoverview-period-boxes').find('.vbo-ratesoverview-'+idc);
1476 elcont.find('.vbo-ratesoverview-period-wday').text(vboMapWdays[period_date.getDay()]);
1477 elcont.find('.vbo-ratesoverview-period-mday').text(period_date.getDate());
1478 elcont.find('.vbo-ratesoverview-period-month').text(vboMapMons[period_date.getMonth()]);
1479 elem.closest('.vbo-ratesoverview-period-boxes').find('.vbo-ratesoverview-'+idc+'-icon').hide();
1480 data.push(elem.closest('.vbo-ratesoverview-period-boxes').find('.vbo-selperiod-rplanid').val());
1481 data.push(elem.closest('.vbo-ratesoverview-period-boxes').find('.vbo-selperiod-rplanid option:selected').text());
1482 data.push(elem.closest('.vbo-ratesoverview-period-boxes').find('.vbo-selperiod-rplanid option:selected').attr('data-defrate'));
1483 data.push(elem.attr('data-idroom'));
1484 data.push(elem.attr('data-roomname'));
1485 var struct = getPeriodStructure(data);
1486 if (idc.indexOf('from') >= 0) {
1487 //period from date selected
1488 if (!vbolistener.pickFirst(struct)) {
1489 //first already picked: update it
1490 vbolistener.first = struct;
1491 }
1492 }
1493 if (idc.indexOf('to') >= 0) {
1494 //period to date selected
1495 if (!vbolistener.pickFirst(struct)) {
1496 //first already picked
1497 if ((vbolistener.first.isBeforeThan(struct) || vbolistener.first.isSameDay(struct)) && vbolistener.first.isSameRplan(struct) && vbolistener.first.isSameRoom(struct)) {
1498 //last > first: pick last
1499 if (vbolistener.pickLast(struct)) {
1500 showVboDialogPeriod();
1501 }
1502 }
1503 }
1504 }
1505 }
1506 function vbCalcNights() {
1507 var vbcheckin = document.getElementById('checkindate').value;
1508 var vbcheckout = document.getElementById('checkoutdate').value;
1509 if (!vbcheckin.length || !vbcheckout.length) {
1510 return;
1511 }
1512 var vbcheckinp = vbcheckin.split("-");
1513 var vbcheckoutp = vbcheckout.split("-");
1514 var vbinmonth = parseInt(vbcheckinp[1]);
1515 vbinmonth = vbinmonth - 1;
1516 var vbinday = parseInt(vbcheckinp[2], 10);
1517 var vbcheckind = new Date(vbcheckinp[0], vbinmonth, vbinday);
1518 var vboutmonth = parseInt(vbcheckoutp[1]);
1519 vboutmonth = vboutmonth - 1;
1520 var vboutday = parseInt(vbcheckoutp[2], 10);
1521 var vbcheckoutd = new Date(vbcheckoutp[0], vboutmonth, vboutday);
1522 var vbdivider = 1000 * 60 * 60 * 24;
1523 var vbints = vbcheckind.getTime();
1524 var vboutts = vbcheckoutd.getTime();
1525 if (vboutts > vbints) {
1526 var utc1 = Date.UTC(vbcheckind.getFullYear(), vbcheckind.getMonth(), vbcheckind.getDate());
1527 var utc2 = Date.UTC(vbcheckoutd.getFullYear(), vbcheckoutd.getMonth(), vbcheckoutd.getDate());
1528 var vbnights = Math.ceil((utc2 - utc1) / vbdivider);
1529 if (vbnights > 0) {
1530 jQuery('#vbo-numnights').val(vbnights);
1531 }
1532 }
1533 }
1534
1535 /**
1536 * Scrollable tables will show the arrows to navigate to the next N days.
1537 *
1538 * @since 1.15.0 (J) - 1.5.0 (WP)
1539 */
1540 function vboDisplayNextDays(btn) {
1541 var cell = jQuery(btn).closest('.vbo-roverv-gonext-cell');
1542 if (!cell || !cell.length) {
1543 return;
1544 }
1545 var rid = cell.attr('data-rid');
1546 if (!rid || !rid.length) {
1547 return;
1548 }
1549 var table = jQuery('table.vbratesoverviewtable[data-idroom="' + rid + '"]');
1550 if (!table || !table.length) {
1551 return;
1552 }
1553 // get the datepicker calendar instance for this room
1554 var calendar = table.find('input.vbodatepicker');
1555 try {
1556 // calculate the new date
1557 var cur_date = calendar.datepicker('getDate');
1558 var dobj = new Date(cur_date);
1559 dobj.setDate(dobj.getDate() + <?php echo $MAX_DAYS; ?>);
1560 // set the new date
1561 calendar.datepicker('setDate', dobj);
1562 // populate hidden form fields
1563 var parentform = calendar.closest('form');
1564 var roomsids = jQuery('#roomsel').val();
1565 if (roomsids) {
1566 if (!Array.isArray(roomsids)) {
1567 // if there is just one room type, the select is not multiple, so this is a string
1568 roomsids = [jQuery('#roomsel').val()];
1569 }
1570 jQuery.each(roomsids, function(k, v) {
1571 parentform.append('<input type="hidden" name="cid[]" value="' + v + '" />');
1572 });
1573 }
1574 // auto-submit form with new date
1575 parentform.submit();
1576 } catch(e) {
1577 console.error(e);
1578 alert('Calendar not found. Please change date manually.');
1579 }
1580 }
1581
1582 jQuery(function() {
1583
1584 /**
1585 * Calendar for start date of every room-rates table.
1586 */
1587 jQuery('.vbodatepicker').datepicker({
1588 showOn: 'focus',
1589 dateFormat: '<?php echo $juidf; ?>',
1590 minDate: '-1y',
1591 numberOfMonths: 1,
1592 changeMonth: true,
1593 changeYear: true,
1594 yearRange: '<?php echo (date('Y') - 1).':'.(date('Y') + 3); ?>',
1595 onSelect: function(selectedDate) {
1596 var parentform = jQuery(this).closest('form');
1597 var roomsids = jQuery('#roomsel').val();
1598 if (roomsids) {
1599 if (!Array.isArray(roomsids)) {
1600 // if there is just one room type, the select is not multiple, so this is a string
1601 roomsids = [jQuery('#roomsel').val()];
1602 }
1603 jQuery.each(roomsids, function(k, v) {
1604 parentform.append('<input type="hidden" name="cid[]" value="' + v + '" />');
1605 });
1606 }
1607 parentform.submit();
1608 }
1609 });
1610
1611 /**
1612 * Disable keyboard on regular datepicker input fields.
1613 */
1614 jQuery('.vbodatepicker').on('focus', function() {
1615 jQuery(this).blur();
1616 });
1617
1618 /**
1619 * Dates-Range-Picker calendar for Rates Calculator tool.
1620 */
1621 jQuery('#checkindate').vboDatesRangePicker({
1622 checkout: '#checkoutdate',
1623 showOn: 'focus',
1624 dateFormat: 'yy-mm-dd',
1625 minDate: '0d',
1626 numberOfMonths: <?php echo $is_mobile ? 1 : 2; ?>,
1627 onSelect: {
1628 checkin: (selectedDate) => {
1629 var nownights = parseInt(jQuery('#vbo-numnights').val());
1630 var nowcheckin = jQuery('#checkindate').vboDatesRangePicker('getCheckinDate');
1631 var nowcheckindate = new Date(nowcheckin.getTime());
1632 nowcheckindate.setDate(nowcheckindate.getDate() + nownights);
1633 jQuery('#checkindate').vboDatesRangePicker('checkout', 'minDate', nowcheckindate);
1634 vbCalcNights();
1635 },
1636 checkout: (selectedDate) => {
1637 if (!jQuery('#checkindate').vboDatesRangePicker('getCheckoutDate')) {
1638 return;
1639 }
1640 vbCalcNights();
1641 },
1642 },
1643 labels: {
1644 checkin: Joomla.JText._('VBPICKUPROOM'),
1645 checkout: Joomla.JText._('VBRETURNROOM'),
1646 },
1647 bottomCommands: {
1648 clear: Joomla.JText._('VBO_CLEAR_DATES'),
1649 close: Joomla.JText._('VBO_CLOSE'),
1650 onClear: () => {
1651 vbCalcNights();
1652 },
1653 },
1654 environment: {
1655 section: 'admin',
1656 autoHide: true,
1657 },
1658 });
1659 jQuery('#checkindate-trig, #checkoutdate-trig').click(function() {
1660 let dp = jQuery(this).prev('input');
1661 if (!dp.length) {
1662 return;
1663 }
1664 if (dp.hasClass('hasDatepicker')) {
1665 dp.focus();
1666 } else if (dp.attr('id') == 'checkoutdate') {
1667 jQuery('#checkindate').focus();
1668 }
1669 });
1670 jQuery('#vbo-numnights').on('change keyup', function() {
1671 if (!jQuery('#checkindate').vboDatesRangePicker('getCheckinDate')) {
1672 return;
1673 }
1674 var nownights = parseInt(jQuery(this).val());
1675 var nowcheckin = jQuery('#checkindate').vboDatesRangePicker('getCheckinDate');
1676 var nowcheckindate = new Date(nowcheckin.getTime());
1677 nowcheckindate.setDate(nowcheckindate.getDate() + nownights);
1678 // update checkout date
1679 jQuery('#checkindate').vboDatesRangePicker('setCheckoutDate', nowcheckindate);
1680 });
1681
1682 /**
1683 * Dates-Range-Picker calendar for selecting a period of room dates.
1684 */
1685 jQuery('.vbo-period-from').each(function(index, elem) {
1686 jQuery(elem).vboDatesRangePicker({
1687 checkout: jQuery(elem).closest('.vbo-ratesoverview-period-box-cals').find('.vbo-period-to'),
1688 dateFormat: '<?php echo $juidf; ?>',
1689 numberOfMonths: 1,
1690 minDate: '0d',
1691 altFields: {
1692 checkin: '.vbo-period-from-val',
1693 checkout: '.vbo-period-to-val',
1694 },
1695 onSelect: {
1696 checkin: function(selectedDate) {
1697 jQuery(this).parent().find('.vbo-period-from-val').val(selectedDate);
1698 vboFormatCalDate(jQuery(this), 'period-from');
1699 },
1700 checkout: function(selectedDate) {
1701 jQuery(this).parent().find('.vbo-period-to-val').val(selectedDate);
1702 vboFormatCalDate(jQuery(this), 'period-to');
1703 },
1704 },
1705 labels: {
1706 checkin: Joomla.JText._('VBNEWRESTRICTIONDFROMRANGE'),
1707 checkout: Joomla.JText._('VBNEWRESTRICTIONDTORANGE'),
1708 },
1709 environment: {
1710 section: 'admin',
1711 },
1712 });
1713 });
1714 jQuery('.vbo-ratesoverview-period-box-left, .vbo-ratesoverview-period-box-right').click(function() {
1715 let calbox = jQuery(this).closest('.vbo-ratesoverview-period-boxes').find('.vbo-ratesoverview-period-box-cals');
1716 if (!calbox.is(':visible')) {
1717 // unset current dates from DRP, if any
1718 calbox.find('.vbo-period-from').vboDatesRangePicker('setDates', []);
1719 }
1720 calbox.toggle();
1721 });
1722
1723 /**
1724 * Render select2.
1725 */
1726 jQuery("#roomsel").select2({
1727 allowClear: true,
1728 templateResult: (element) => {
1729 if (typeof vboListingsMiniThumbnails !== 'undefined' && vboListingsMiniThumbnails.hasOwnProperty((element.id || 0))) {
1730 return jQuery('<span class="vbo-sel2-element-img"><img src="' + vboListingsMiniThumbnails[element.id] + '" /> <span>' + element.text + '</span></span>');
1731 } else {
1732 return element.text;
1733 }
1734 },
1735 });
1736 jQuery("#roomselcalc").select2({
1737 templateResult: (element) => {
1738 if (typeof vboListingsMiniThumbnails !== 'undefined' && vboListingsMiniThumbnails.hasOwnProperty((element.id || 0))) {
1739 return jQuery('<span class="vbo-sel2-element-img"><img src="' + vboListingsMiniThumbnails[element.id] + '" /> <span>' + element.text + '</span></span>');
1740 } else {
1741 return element.text;
1742 }
1743 },
1744 });
1745 });
1746 <?php
1747 if ($df == "Y/m/d") {
1748 ?>
1749 Date.prototype.format = "yy/mm/dd";
1750 <?php
1751 } elseif ($df == "m/d/Y") {
1752 ?>
1753 Date.prototype.format = "mm/dd/yy";
1754 <?php
1755 } else {
1756 ?>
1757 Date.prototype.format = "dd/mm/yy";
1758 <?php
1759 }
1760 ?>
1761 Date.prototype.datesep = "<?php echo addslashes(VikBooking::getDateSeparator()); ?>";
1762 var currencysymb = '<?php echo $currencysymb; ?>';
1763 var debug_mode = '<?php echo $pdebug; ?>';
1764 var vcm_exists = <?php echo VikBooking::vcmAutoUpdate(); ?>;
1765 var roverw_messages = {
1766 "setNewRatesMissing": "<?php echo addslashes(JText::translate('VBRATESOVWERRNEWRATE')); ?>",
1767 "modRplansMissing": "<?php echo addslashes(JText::translate('VBRATESOVWERRMODRPLANS')); ?>",
1768 "openSpLink": "<?php echo addslashes(JText::translate('VBRATESOVWOPENSPL')); ?>",
1769 "vcmRatesChanged": "<?php echo addslashes(JText::translate('VBRATESOVWVCMRCHANGED')); ?>",
1770 "vcmRatesChangedOpen": "<?php echo addslashes(JText::translate('VBRATESOVWVCMRCHANGEDOPEN')); ?>",
1771 "notesForRoom": "<?php echo addslashes(JText::translate('VBONOTESFORROOM')); ?>"
1772 };
1773 var vboFests = <?php echo json_encode($this->festivities); ?>;
1774 var vboRdayNotes = <?php echo json_encode($this->rdaynotes); ?>;
1775 </script>
1776
1777 <script type="text/Javascript">
1778 /**
1779 * Open a booking in a new tab.
1780 */
1781 function vboRovervOpenBooking(bid) {
1782 var open_url = jQuery('.vbo-basenavuri-details').attr('href');
1783 open_url = open_url.replace('%d', bid);
1784 // navigate in a new tab
1785 window.open(open_url, '_blank');
1786 }
1787
1788 /* Labels for months and weekdays */
1789 var vbowdays = <?php echo json_encode($long_days_labels); ?>;
1790 var vbomonths = <?php echo json_encode($long_months_labels); ?>;
1791 /* Dates selection - Start */
1792 var vbolistener = null;
1793
1794 jQuery(function() {
1795
1796 // configure the currency object
1797 VBOCore.getCurrency({
1798 symbol: <?php echo json_encode($currencysymb) ?: '"$"'; ?>,
1799 position: <?php echo json_encode($currencypos); ?>,
1800 digits: <?php echo intval($currency_digits); ?>,
1801 decimals: <?php echo json_encode($currency_decimals) ?: '"."'; ?>,
1802 thousands: <?php echo json_encode($currency_thousands) ?: '","'; ?>,
1803 noDecimals: 1,
1804 });
1805
1806 // fests
1807 jQuery(document.body).on("click", "td.bluedays", function() {
1808 if (jQuery(this).hasClass('skip-bluedays-click')) {
1809 return;
1810 }
1811 var ymd = jQuery(this).attr('data-ymd');
1812 var daytitle = jQuery(this).attr('data-readymd');
1813 if (jQuery(this).hasClass('vbo-roverv-festcell')) {
1814 // cell has fests
1815 if (!vboFests.hasOwnProperty(ymd)) {
1816 return;
1817 }
1818 vboRenderFests(ymd, daytitle);
1819 } else {
1820 // let the admin create a new fest
1821
1822 // define the modal buttons
1823 let cancel_btn = jQuery('<button></button>')
1824 .attr('type', 'button')
1825 .addClass('btn')
1826 .text(Joomla.JText._('VBANNULLA'))
1827 .on('click', function() {
1828 VBOCore.emitEvent('vbo-dismiss-modal-roverv-fests');
1829 });
1830
1831 let save_btn = jQuery('<button></button>')
1832 .attr('type', 'button')
1833 .addClass('btn btn-success')
1834 .text(Joomla.JText._('VBSAVE'))
1835 .on('click', function() {
1836 vboAddFest();
1837 });
1838
1839 let modal_body = VBOCore.displayModal({
1840 suffix: 'ratesoverv-fests',
1841 extra_class: 'vbo-modal-rounded vbo-modal-tall',
1842 title: '<?php VikBookingIcons::e('star'); ?> ' + daytitle,
1843 footer_left: cancel_btn,
1844 footer_right: save_btn,
1845 dismiss_event: 'vbo-dismiss-modal-roverv-fests',
1846 onDismiss: () => {
1847 jQuery('.vbo-ratesoverview-fests-wrap').appendTo('.vbo-ratesoverview-fests-helper');
1848 },
1849 });
1850
1851 // update ymd key for the selected date, useful for adding new fests
1852 jQuery('.vbo-overlay-fests-addnew').attr('data-ymd', ymd);
1853
1854 // unset content and display modal for just adding a new fest
1855 jQuery('.vbo-overlay-fests-list').html('');
1856 jQuery('.vbo-ratesoverview-fests-wrap').appendTo(modal_body);
1857 }
1858 if (jQuery(this).hasClass('vbo-roverw-roomdaynote-full')) {
1859 // display the room-day notes
1860 vboRenderRdayNotes(ymd, jQuery(this).attr('data-rid'));
1861 }
1862 });
1863
1864 // calendar-day selection listener
1865 vbolistener = new CalendarListener();
1866
1867 jQuery('.day-block').click(function() {
1868 pickBlock(jQuery(this).attr('id'));
1869 });
1870
1871 jQuery('.day-block').hover(
1872 function() {
1873 if (vbolistener.isFirstPicked() && !vbolistener.isLastPicked()) {
1874 var struct = initBlockStructure(jQuery(this).attr('id'));
1875 var all_blocks = getAllBlocksBetween(vbolistener.first, struct, false);
1876 if (all_blocks !== false) {
1877 jQuery.each(all_blocks, function(k, v) {
1878 if (!v.hasClass('block-picked-middle')) {
1879 v.addClass('block-picked-middle');
1880 }
1881 });
1882 jQuery(this).addClass('block-picked-end');
1883 }
1884 }
1885 },
1886 function() {
1887 if (!vbolistener.isLastPicked()) {
1888 jQuery('.day-block').removeClass('block-picked-middle block-picked-end');
1889 }
1890 }
1891 );
1892
1893 jQuery("body").on("click", ".vbo-roverw-daymod-infospids", function() {
1894 if (jQuery(this).hasClass('vbo-roverw-daymod-infospids-on')) {
1895 // dismiss modal
1896 VBOCore.emitEvent('vbo-ratesoverview-spids-details-dismiss');
1897 } else {
1898 // show modal
1899 let spids_modal = VBOCore.displayModal({
1900 suffix: 'vbo-ratesoverview-spids',
1901 extra_class: 'vbo-modal-rounded vbo-modal-dialog vbo-modal-nofooter',
1902 title: jQuery(this).attr('data-dt'),
1903 dismiss_event: 'vbo-ratesoverview-spids-details-dismiss',
1904 });
1905
1906 let spids_info = jQuery(this).next('.vbo-roverw-daymod-infospids-outcont').find('.vbo-roverw-daymod-infospids-incont').clone();
1907 spids_info.appendTo(spids_modal);
1908 }
1909 });
1910
1911 jQuery('.vbo-roverw-closeopenrp h4').click(function() {
1912 jQuery('.vbo-roverw-closeopenrp-btns').fadeToggle(400, function() {
1913 if (jQuery(this).is(':visible')) {
1914 jQuery('.vbo-roverw-closeopenrp-arrow').html('<?php VikBookingIcons::e('chevron-up'); ?>');
1915 } else {
1916 jQuery('.vbo-roverw-closeopenrp-arrow').html('<?php VikBookingIcons::e('chevron-down'); ?>');
1917 }
1918 });
1919 });
1920
1921 jQuery(document.body).on('click', '.vbo-ratesoverview-vcmwarn-close', function() {
1922 vcm_exists = 0;
1923 jQuery('.vbo-ratesoverview-right-inner').hide().html('');
1924 });
1925
1926 // show booking details
1927 jQuery('.vbo-roverw-daybusy[data-bids]').click(function() {
1928 var date_bids = jQuery(this).attr('data-bids');
1929 if (!date_bids || !date_bids.length) {
1930 return;
1931 }
1932
1933 // get room name, date and day-bids
1934 var rid = jQuery(this).closest('table').attr('data-idroom');
1935 var room_name = jQuery('.vbo-ratesoverview-roomname[data-idroom="' + rid + '"]').text();
1936 var date_read = jQuery(this).attr('data-vbodateread');
1937 var def_bicon = '<?php VikBookingIcons::e('user', 'vbo-dashboard-guest-activity-avatar-icon'); ?>';
1938 var closure_i = '<?php VikBookingIcons::e('ban', 'vbo-dashboard-guest-activity-avatar-icon'); ?>';
1939
1940 // display modal with booking details
1941 var rday_bookings_modal_body = VBOCore.displayModal({
1942 suffix: 'overv-rdaybookings',
1943 extra_class: 'vbo-modal-rounded vbo-modal-tall',
1944 title: room_name + ' - ' + date_read,
1945 dismiss_event: 'vbo-dismiss-modal-roverv-rdaybookings',
1946 loading_event: 'vbo-loading-modal-roverv-rdaybookings',
1947 loading_body: '<?php VikBookingIcons::e('circle-notch', 'fa-spin fa-fw'); ?>',
1948 });
1949
1950 // show loading
1951 VBOCore.emitMultitaskEvent('vbo-loading-modal-roverv-rdaybookings');
1952
1953 // make the request to get the bookings information
1954 VBOCore.doAjax(
1955 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=getbookingsinfo'); ?>",
1956 {
1957 status: 'any',
1958 idorders: date_bids,
1959 tmpl: 'component'
1960 },
1961 (res) => {
1962 // stop loading
1963 VBOCore.emitMultitaskEvent('vbo-loading-modal-roverv-rdaybookings');
1964 try {
1965 var obj_res = typeof res === 'string' ? JSON.parse(res) : res;
1966 // build the HTML response
1967 var rday_bookings_html = '';
1968 rday_bookings_html += '<div class="vbo-dashboard-guests-latest">' + "\n";
1969 rday_bookings_html += ' <div class="vbo-dashboard-guest-messages-list">' + "\n";
1970 for (var b in obj_res) {
1971 if (!obj_res.hasOwnProperty(b)) {
1972 continue;
1973 }
1974 // nights and guests
1975 var nights_guests = [
1976 obj_res[b]['roomsnum'] + ' ' + Joomla.JText._((obj_res[b]['roomsnum'] > 1 ? 'VBPVIEWORDERSTHREE' : 'VBEDITORDERTHREE')),
1977 obj_res[b]['days'] + ' ' + Joomla.JText._((obj_res[b]['days'] > 1 ? 'VBDAYS' : 'VBDAY')),
1978 obj_res[b]['tot_adults'] + ' ' + Joomla.JText._((obj_res[b]['tot_adults'] > 1 ? 'VBMAILADULTS' : 'VBMAILADULT'))
1979 ];
1980 if (obj_res[b]['tot_children'] > 0) {
1981 nights_guests.push(obj_res[b]['tot_children'] + ' ' + Joomla.JText._((obj_res[b]['tot_children'] > 1 ? 'VBMAILCHILDREN' : 'VBMAILCHILD')));
1982 }
1983
1984 // build booking structure
1985 rday_bookings_html += ' <div class="vbo-dashboard-guest-activity vbo-w-guestmessages-message" data-idorder="' + obj_res[b]['id'] + '" onclick="vboRovervOpenBooking(\'' + obj_res[b]['id'] + '\');">' + "\n";
1986 rday_bookings_html += ' <div class="vbo-dashboard-guest-activity-avatar">' + "\n";
1987 if (obj_res[b]['avatar_src']) {
1988 rday_bookings_html += '<img class="vbo-dashboard-guest-activity-avatar-profile" src="' + obj_res[b]['avatar_src'] + '" alt="' + obj_res[b]['avatar_alt'] + '" />' + "\n";
1989 } else if (obj_res[b]['closure']) {
1990 rday_bookings_html += closure_i + "\n";
1991 } else {
1992 rday_bookings_html += def_bicon + "\n";
1993 }
1994 rday_bookings_html += ' </div>' + "\n";
1995 rday_bookings_html += ' <div class="vbo-dashboard-guest-activity-content">' + "\n";
1996 rday_bookings_html += ' <div class="vbo-dashboard-guest-activity-content-head">' + "\n";
1997 rday_bookings_html += ' <div class="vbo-dashboard-guest-activity-content-info-details">' + "\n";
1998 rday_bookings_html += ' <h4 class="vbo-w-guestmessages-message-gtitle">' + (!obj_res[b]['closure'] ? obj_res[b]['cinfo'] : obj_res[b]['closure_txt']) + '</h4>' + "\n";
1999 rday_bookings_html += ' <div class="vbo-dashboard-guest-activity-content-info-icon">' + "\n";
2000 rday_bookings_html += ' <span class="badge badge-info">' + obj_res[b]['id'] + '</span> ' + "\n";
2001 rday_bookings_html += ' <span class="badge badge-' + (obj_res[b]['status'] == 'confirmed' ? 'success' : 'warning') + '">' + obj_res[b]['status_lbl'] + '</span>' + "\n";
2002 rday_bookings_html += ' <span class="vbo-w-guestmessages-message-staydates">' + "\n";
2003 rday_bookings_html += ' <?php VikBookingIcons::e('calendar-alt'); ?>' + "\n";
2004 rday_bookings_html += ' <span class="vbo-w-guestmessages-message-staydates-in">' + obj_res[b]['checkin_short'] + '</span>' + "\n";
2005 rday_bookings_html += ' <span class="vbo-w-guestmessages-message-staydates-sep">-</span>' + "\n";
2006 rday_bookings_html += ' <span class="vbo-w-guestmessages-message-staydates-out">' + obj_res[b]['checkout_short'] + '</span>' + "\n";
2007 rday_bookings_html += ' </span>' + "\n";
2008 rday_bookings_html += ' </div>' + "\n";
2009 rday_bookings_html += ' </div>' + "\n";
2010 rday_bookings_html += ' <div class="vbo-dashboard-guest-activity-content-info-date">' + "\n";
2011 rday_bookings_html += ' <span>' + obj_res[b]['book_date'] + '</span>' + "\n";
2012 rday_bookings_html += ' <span>' + obj_res[b]['book_time'] + '</span>' + "\n";
2013 rday_bookings_html += ' </div>' + "\n";
2014 rday_bookings_html += ' </div>' + "\n";
2015 rday_bookings_html += ' <div class="vbo-dashboard-guest-activity-content-info-msg">' + "\n";
2016 rday_bookings_html += ' <div><?php VikBookingIcons::e('bed'); ?> ' + nights_guests.join(', ') + '</div>' + "\n";
2017 if (obj_res[b].hasOwnProperty('sub_units_data')) {
2018 rday_bookings_html += ' <div class="vbo-rdaybooking-subunits">' + "\n";
2019 for (let sub_rname in obj_res[b]['sub_units_data']) {
2020 if (!obj_res[b]['sub_units_data'].hasOwnProperty(sub_rname)) {
2021 continue;
2022 }
2023 rday_bookings_html += '<span class="label label-success">' + (obj_res[b]['roomsnum'] > 1 ? sub_rname + ' ' : '') + '#' + obj_res[b]['sub_units_data'][sub_rname] + '</span>' + "\n";
2024 }
2025 rday_bookings_html += ' </div>' + "\n";
2026 }
2027 rday_bookings_html += ' </div>' + "\n";
2028 rday_bookings_html += ' </div>' + "\n";
2029 rday_bookings_html += ' </div>' + "\n";
2030 }
2031 rday_bookings_html += ' </div>' + "\n";
2032 rday_bookings_html += '</div>' + "\n";
2033
2034 // append the response
2035 rday_bookings_modal_body.html(rday_bookings_html);
2036 } catch(err) {
2037 console.error('could not parse JSON response', err, res);
2038 alert('Could not parse JSON response');
2039 }
2040 },
2041 (err) => {
2042 // stop loading and display alert message
2043 VBOCore.emitMultitaskEvent('vbo-loading-modal-roverv-rdaybookings');
2044 console.error(err);
2045 alert(err.responseText);
2046 }
2047 );
2048 });
2049
2050 // register listener for the "input" event on the "set new rate" input field of type number
2051 document.querySelector('#roverw-newrate').addEventListener('input', VBOCore.debounceEvent((e) => {
2052 // dispatch the event to calculate the new OTA pricing value
2053 VBOCore.emitEvent('vbo-roverv-setnewrate-calc-ota-pricing', {rate: e.target.value});
2054 }, 200));
2055
2056 // register listener for when a new rate is set to update what will be the OTA pricing value
2057 document.addEventListener('vbo-roverv-setnewrate-calc-ota-pricing', VBOCore.debounceEvent((e) => {
2058 if (!e || !e.detail || !e.detail.rate) {
2059 // invalid event data
2060 return;
2061 }
2062
2063 // get the new PMS rate
2064 let rate_amount = parseFloat(e.detail.rate);
2065
2066 // access the currency object
2067 let currencyObj = VBOCore.getCurrency({
2068 symbol: vbo_currency_symbol,
2069 position: vbo_currency_position,
2070 digits: vbo_currency_digits,
2071 decimals: vbo_currency_decimals,
2072 thousands: vbo_currency_thousands,
2073 noDecimals: 1,
2074 });
2075
2076 // scan all OTA alteration rules, if any
2077 document.querySelectorAll('.vbo-roverw-setnewrate-ota-pricing-currentvalue[data-alteration]').forEach((elem) => {
2078 // channel alteration string
2079 let alter_string = elem.getAttribute('data-alteration');
2080 if (!alter_string) {
2081 alter_string = '+0%';
2082 }
2083
2084 // default alteration factors (no pricing alteration rules)
2085 let alter_op = alter_string.substr(0, 1);
2086 let alter_val = alter_string.substr(-1, 1);
2087 let alter_amount = parseFloat(alter_string.replace(alter_op, '').replace(alter_val, ''));
2088
2089 // calculate what the rate will be on the OTA
2090 let ota_rate_amount = rate_amount;
2091
2092 if (!isNaN(alter_amount) && Math.abs(alter_amount) > 0) {
2093 if (alter_op == '+') {
2094 // increase rate
2095 if (alter_val == '%') {
2096 // percent
2097 let amount_inc = currencyObj.multiply(alter_amount, 0.01);
2098 amount_inc = currencyObj.multiply(rate_amount, amount_inc);
2099 ota_rate_amount = currencyObj.sum(rate_amount, amount_inc);
2100 } else {
2101 // absolute
2102 ota_rate_amount = currencyObj.sum(rate_amount, alter_amount);
2103 }
2104 } else {
2105 // discount rate
2106 if (alter_val == '%') {
2107 // percent
2108 let amount_inc = currencyObj.multiply(alter_amount, 0.01);
2109 amount_inc = currencyObj.multiply(rate_amount, amount_inc);
2110 ota_rate_amount = currencyObj.diff(rate_amount, amount_inc);
2111 } else {
2112 // absolute
2113 ota_rate_amount = currencyObj.diff(rate_amount, alter_amount);
2114 }
2115 }
2116 }
2117
2118 // get the element containing the calculated ota pricing
2119 let will_alter_elem = elem
2120 .closest('.vbo-roverw-setnewrate-vcm-ota-pricing-startvalue')
2121 .querySelector('.vbo-roverw-setnewrate-ota-pricing-willvalue');
2122
2123 // define the currency options
2124 let ota_currency_options = {};
2125
2126 // check if the channel requires a specific currency
2127 let ota_currency_data = will_alter_elem.getAttribute('data-currency');
2128 if (ota_currency_data) {
2129 // decode currency data instructions
2130 try {
2131 ota_currency_data = JSON.parse(ota_currency_data);
2132 } catch (e) {
2133 ota_currency_data = {};
2134 }
2135
2136 // set custom currency options
2137 if (ota_currency_data['symbol']) {
2138 ota_currency_options['symbol'] = ota_currency_data['symbol'];
2139 }
2140 if (ota_currency_data['decimals']) {
2141 ota_currency_options['digits'] = ota_currency_data['decimals'];
2142 }
2143 if (ota_currency_data['decimals_sep']) {
2144 ota_currency_options['decimals'] = ota_currency_data['decimals_sep'];
2145 }
2146 if (ota_currency_data['thousands_sep']) {
2147 ota_currency_options['thousands'] = ota_currency_data['thousands_sep'];
2148 }
2149 }
2150
2151 // set calculated OTA rate value
2152 will_alter_elem.innerHTML = currencyObj.format(ota_rate_amount, ota_currency_options);
2153 });
2154 }, 200));
2155
2156 // build context menu button text element
2157 let btnOtaRatesEl = document.createElement('span');
2158 btnOtaRatesEl.classList.add('vbo-ctxmenu-entry-icn');
2159 btnOtaRatesEl.innerHTML = '<span>' + <?php echo json_encode(JText::translate('VBO_SHOW_OTA_RATES')); ?> + '</span>';
2160 let icnOtaRatesEl = document.createElement('i');
2161 icnOtaRatesEl.classList.add('vbo-save-pref-ota-rates');
2162 icnOtaRatesEl.classList.add(...(<?php echo json_encode(VikBookingIcons::i('thumbtack', 'vbo-deactivated-icon')); ?>.split(' ')));
2163 if (<?php echo $cookie->get('vboRovwOtr', 0, 'int') ? 'true' : 'false'; ?>) {
2164 icnOtaRatesEl.classList.add('vbo-activated-icon');
2165 }
2166 let icnOtaRatesWrapEl = document.createElement('span');
2167 icnOtaRatesWrapEl.classList.add('vbo-ctxmenu-entry-icn-secondary', 'vbo-tooltip', 'vbo-tooltip-top-left');
2168 icnOtaRatesWrapEl.setAttribute('data-tooltiptext', <?php echo json_encode(JText::translate('VBO_REMEMBER_PREF')); ?>);
2169 icnOtaRatesWrapEl.append(icnOtaRatesEl);
2170 btnOtaRatesEl.append(icnOtaRatesWrapEl);
2171
2172 // start context menu on the proper actions button element
2173 jQuery('.vbo-context-menu-roverv-actions').vboContextMenu({
2174 placement: 'bottom-right',
2175 buttons: [
2176 {
2177 activeState: <?php echo $cookie->get('vboRovwObp', 1, 'int') ? 'true' : 'false'; ?>,
2178 class: 'vbo-context-menu-entry-secondary',
2179 text: <?php echo json_encode(JText::translate('VBOROVERVOBP')); ?>,
2180 separator: true,
2181 icon: function() {
2182 return this.activeState ? '<?php echo VikBookingIcons::i('toggle-on', 'vbo-enabled-icon'); ?>' : '<?php echo VikBookingIcons::i('toggle-off'); ?>';
2183 },
2184 action: function(root, event) {
2185 // toggle state
2186 this.activeState = !this.activeState;
2187
2188 // toggle OBP rows
2189 vboToggleOBPRows(this.activeState);
2190 },
2191 },
2192 {
2193 activeState: <?php echo $cookie->get('vboRovwOtr', 0, 'int') ? 'true' : 'false'; ?>,
2194 pinnedState: <?php echo $cookie->get('vboRovwOtr', 0, 'int') ? 'true' : 'false'; ?>,
2195 class: 'vbo-context-menu-entry-secondary',
2196 text: btnOtaRatesEl,
2197 separator: false,
2198 icon: function() {
2199 return this.activeState ? '<?php echo VikBookingIcons::i('toggle-on', 'vbo-enabled-icon'); ?>' : '<?php echo VikBookingIcons::i('toggle-off'); ?>';
2200 },
2201 action: function(root, event) {
2202 if (event?.target && (event.target[0] || event.target).matches('.vbo-save-pref-ota-rates')) {
2203 // toggle pinned state
2204 this.pinnedState = !this.pinnedState;
2205
2206 // update cookie preference
2207 let nd = new Date();
2208 nd.setTime(nd.getTime() + (365*24*60*60*1000));
2209 document.cookie = "vboRovwOtr=" + (this.pinnedState ? 1 : 0) + "; expires=" + nd.toUTCString() + "; path=/; SameSite=Lax";
2210
2211 // update icon class
2212 icnOtaRatesEl.classList.toggle('vbo-deactivated-icon', !this.pinnedState);
2213 icnOtaRatesEl.classList.toggle('vbo-activated-icon', this.pinnedState);
2214
2215 // do not proceed
2216 return;
2217 }
2218
2219 // toggle state
2220 this.activeState = !this.activeState;
2221
2222 if (!this.activeState) {
2223 // remove all OTA-rate rows
2224 document.querySelectorAll('tr.vbo-roverviewtablerow-otarates').forEach((row) => {
2225 row.remove();
2226 });
2227 // abort
2228 return;
2229 }
2230
2231 // access trigger elements
2232 let ctx_elem = document.querySelector('.vbo-context-menu-roverv-actions');
2233 let lbl_elem = ctx_elem.querySelector('.vbo-context-menu-lbl');
2234 let orig_lbl = lbl_elem.innerText;
2235
2236 // start loading animation
2237 ctx_elem.loading = 1;
2238 lbl_elem.innerHTML = '<?php VikBookingIcons::e('circle-notch', 'fa-spin fa-fw'); ?>';
2239
2240 // load and display OTA-rate rows
2241 VBOCore.doAjax(
2242 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=pricing.loadOtaRateRecords'); ?>",
2243 {
2244 from_date: <?php echo json_encode(date('Y-m-d', $this->tsstart)); ?>,
2245 days: <?php echo $this->max_days; ?>,
2246 room_ids: <?php echo json_encode(array_keys($this->roomrows)); ?>,
2247 },
2248 (res) => {
2249 // stop loading animation
2250 lbl_elem.innerHTML = '';
2251 lbl_elem.innerText = orig_lbl;
2252 ctx_elem.loading = 0;
2253
2254 try {
2255 // check the response
2256 let obj_res = typeof res === 'string' ? JSON.parse(res) : res;
2257
2258 if (!obj_res?.records) {
2259 // abort and display message
2260 throw new Error(<?php echo json_encode(JText::translate('VBO_NO_RECORDS_FOUND')); ?>);
2261 }
2262
2263 // render response records
2264 obj_res.records.reverse().forEach((otaRecord) => {
2265 // find the current room table
2266 let tableEl = document.querySelector('table.vbratesoverviewtable[data-idroom="' + otaRecord?.id_room + '"]');
2267 if (!tableEl) {
2268 return;
2269 }
2270
2271 // find the current room rable rate plan row
2272 let rowElement = tableEl.querySelector('tr.vbo-roverviewtablerow[data-idprice="' + otaRecord?.id_price + '"]');
2273 if (!rowElement) {
2274 return;
2275 }
2276
2277 // build row element
2278 let otaRowEl = document.createElement('tr');
2279 otaRowEl.classList.add('vbo-roverviewtablerow-otarates');
2280 otaRowEl.setAttribute('data-channel-id', otaRecord?.id_channel);
2281
2282 // build first row cell for channel details
2283 let mainRowCell = document.createElement('td');
2284 let channelWrap = document.createElement('span');
2285 channelWrap.classList.add('vbo-roverv-ota-wrap');
2286 if (obj_res?.data[otaRecord?.id_channel]?.logo) {
2287 // append channel logo
2288 let channelLogo = document.createElement('img');
2289 channelLogo.classList.add('vbo-roverv-ota-logo');
2290 channelLogo.setAttribute('src', obj_res.data[otaRecord.id_channel].logo);
2291 channelWrap.append(channelLogo);
2292 }
2293 let channelName = otaRecord?.id_channel || '';
2294 if (obj_res?.data[otaRecord?.id_channel]?.name) {
2295 channelName = obj_res.data[otaRecord.id_channel].name;
2296 }
2297 // append channel name
2298 let channelNameEl = document.createElement('span');
2299 channelNameEl.textContent = channelName;
2300 channelWrap.append(channelNameEl);
2301 // append content to main row cell
2302 mainRowCell.append(channelWrap);
2303
2304 // append first cell to row
2305 otaRowEl.append(mainRowCell);
2306
2307 // build the OTA cell rates
2308 for (const [ymdDay, dayRate] of Object.entries((otaRecord?.rates || {}))) {
2309 // build current day identifier by removing leading zeros in month-days and months
2310 let noLeadingZerosDt = ymdDay.replace(/-(0)([1-9]{1})/g, "-$2");
2311 let dtParts = noLeadingZerosDt.split('-');
2312
2313 // build day rate cell
2314 let cellIdElements = [
2315 'cell',
2316 dtParts[2],
2317 dtParts[1],
2318 dtParts[0],
2319 otaRecord?.id_room,
2320 'otarate',
2321 ];
2322 let dayRateCellEl = document.createElement('td');
2323 dayRateCellEl.classList.add('vbo-roverw-rplan-otarate');
2324 dayRateCellEl.setAttribute('id', cellIdElements.join('-'));
2325 dayRateCellEl.setAttribute('align', 'center');
2326
2327 if (!dayRate) {
2328 // must be a null nightly rate
2329 dayRateCellEl.textContent = '---';
2330 } else {
2331 // format currency price
2332 dayRateCellEl.innerHTML = VBOCore.getCurrency().format(dayRate);
2333 }
2334
2335 // append day cell to row
2336 otaRowEl.append(dayRateCellEl);
2337 }
2338
2339 // we can insert the row underneath the room rate plan row
2340 rowElement.insertAdjacentElement('afterend', otaRowEl);
2341 });
2342 } catch(err) {
2343 // display error
2344 alert(err);
2345 }
2346 },
2347 (err) => {
2348 alert(err.responseText || 'Request failed');
2349 // stop loading animation
2350 lbl_elem.innerHTML = '';
2351 lbl_elem.innerText = orig_lbl;
2352 ctx_elem.loading = 0;
2353 }
2354 );
2355 },
2356 disabled: () => {
2357 let ctx_elem = document.querySelector('.vbo-context-menu-roverv-actions');
2358
2359 return ctx_elem?.loading == 1;
2360 }
2361 },
2362 ],
2363 });
2364
2365 // check default state for actions context menu
2366 if (<?php echo $cookie->get('vboRovwOtr', 0, 'int') ? 'true' : 'false'; ?>) {
2367 // display OTA rates upon page loading according to cookie preferences
2368 setTimeout(() => {
2369 // access "display OTA rates" button-option
2370 let ctxOtaRatesBtn = jQuery('.vbo-context-menu-roverv-actions').vboContextMenu('buttons')[1];
2371 // let the initial state be disabled
2372 ctxOtaRatesBtn.activeState = false;
2373 // invoke action callback to toggle the active state to true
2374 ctxOtaRatesBtn.action();
2375 }, 200);
2376 }
2377
2378 // start context menu on the button element to choose the new rates type
2379 const fixedRateLbl = <?php echo json_encode(JText::translate('VBRATESOVWSETNEWRATE')); ?>;
2380 const adjustRatesLbl = <?php echo json_encode(JText::translate('VBO_INCR_DECR_RATES')); ?>;
2381 jQuery('.vbo-context-menu-rate-type').vboContextMenu({
2382 placement: 'bottom-center',
2383 buttons: [
2384 {
2385 activeState: true,
2386 rateType: 'fixed',
2387 class: 'vbo-context-menu-entry-secondary',
2388 text: fixedRateLbl,
2389 separator: false,
2390 icon: function() {
2391 return this.activeState === true ? '<?php echo VikBookingIcons::i('check-square'); ?>' : '<?php echo VikBookingIcons::i('far fa-square'); ?>';
2392 },
2393 action: function(root, event) {
2394 // make the active state property enabled
2395 this.activeState = true;
2396
2397 // update data attribute for the preference chosen
2398 document.querySelector('.vbo-roverw-newrwrap').setAttribute('data-rate-type', 'fixed');
2399
2400 // update element action title
2401 document.querySelector('.vbo-roverw-action-rates-title').textContent = fixedRateLbl;
2402
2403 // toggle visible element for defining the new rate
2404 document.querySelector('.vbo-roverw-newrcont[data-rate-type="fixed"]').style.display = '';
2405 document.querySelector('.vbo-roverw-newrcont[data-rate-type="addsub"]').style.display = 'none';
2406
2407 // parse all context menu buttons to set the proper active state
2408 jQuery('.vbo-context-menu-rate-type').vboContextMenu('buttons').forEach((btn) => {
2409 if (btn.rateType != 'fixed') {
2410 btn.activeState = false;
2411 }
2412 });
2413 },
2414 },
2415 {
2416 activeState: false,
2417 rateType: 'addsub',
2418 class: 'vbo-context-menu-entry-secondary',
2419 text: adjustRatesLbl,
2420 separator: false,
2421 icon: function() {
2422 return this.activeState === true ? '<?php echo VikBookingIcons::i('check-square'); ?>' : '<?php echo VikBookingIcons::i('far fa-square'); ?>';
2423 },
2424 action: function(root, event) {
2425 // make the active state property enabled
2426 this.activeState = true;
2427
2428 // update data attribute for the preference chosen
2429 document.querySelector('.vbo-roverw-newrwrap').setAttribute('data-rate-type', 'addsub');
2430
2431 // update element action title
2432 document.querySelector('.vbo-roverw-action-rates-title').textContent = adjustRatesLbl;
2433
2434 // toggle visible element for defining the new rate
2435 document.querySelector('.vbo-roverw-newrcont[data-rate-type="addsub"]').style.display = '';
2436 document.querySelector('.vbo-roverw-newrcont[data-rate-type="fixed"]').style.display = 'none';
2437
2438 // parse all context menu buttons to set the proper active state
2439 jQuery('.vbo-context-menu-rate-type').vboContextMenu('buttons').forEach((btn) => {
2440 if (btn.rateType != 'addsub') {
2441 btn.activeState = false;
2442 }
2443 });
2444 },
2445 }
2446 ],
2447 });
2448
2449 });
2450
2451 /**
2452 * Room-day notes
2453 */
2454 function vboRenderRdayNotes(day, rid) {
2455 // compose room-day notes information (no sub-units)
2456 var notes_html = '';
2457 var keyid = day + '_' + rid + '_0';
2458 if (vboRdayNotes.hasOwnProperty(keyid) && vboRdayNotes[keyid]['info'] && vboRdayNotes[keyid]['info'].length) {
2459 for (var i = 0; i < vboRdayNotes[keyid]['info'].length; i++) {
2460 var note_data = vboRdayNotes[keyid]['info'][i];
2461 notes_html += '<div class="vbo-overlay-fest-details vbo-roverv-roomdaynotes-note-details">';
2462 notes_html += ' <div class="vbo-fest-info vbo-roverv-roomdaynotes-note-info">';
2463 notes_html += ' <div class="vbo-fest-name vbo-roverv-roomdaynotes-note-name">' + note_data['name'] + '</div>';
2464 notes_html += ' <div class="vbo-fest-desc vbo-roverv-roomdaynotes-note-desc">' + note_data['descr'].replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + '<br />' + '$2') + '</div>';
2465 notes_html += ' </div>';
2466 notes_html += '</div>';
2467 }
2468 }
2469 // append room-day notes to the list of festivities
2470 if (notes_html.length) {
2471 notes_html = '<h4 class="vbo-roverw-roomdaynotes-title">' + roverw_messages.notesForRoom + '</h4>' + notes_html;
2472 jQuery('.vbo-overlay-fests-list').append(notes_html);
2473 }
2474 }
2475
2476 /**
2477 * Fests
2478 */
2479 function vboRenderFests(day, daytitle) {
2480 // compose fests information
2481 let fests_html = '';
2482 if (vboFests[day] && vboFests[day]['festinfo'] && vboFests[day]['festinfo'].length) {
2483 for (var i = 0; i < vboFests[day]['festinfo'].length; i++) {
2484 let fest = vboFests[day]['festinfo'][i];
2485 fests_html += '<div class="vbo-overlay-fest-details">';
2486 fests_html += ' <div class="vbo-fest-info">';
2487 fests_html += ' <div class="vbo-fest-name">' + fest['trans_name'] + '</div>';
2488 fests_html += ' <div class="vbo-fest-desc">' + fest['descr'].replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + '<br />' + '$2') + '</div>';
2489 fests_html += ' </div>';
2490 fests_html += ' <div class="vbo-fest-cmds">';
2491 fests_html += ' <button type="button" class="btn btn-danger" onclick="vboRemoveFest(\'' + day + '\', \'' + i + '\', \'' + fest['type'] + '\', this);"><?php VikBookingIcons::e('trash-alt', 'icn-nomargin'); ?></button>';
2492 fests_html += ' </div>';
2493 fests_html += '</div>';
2494 }
2495 }
2496
2497 // update ymd key for the selected date, useful for adding new fests
2498 jQuery('.vbo-overlay-fests-addnew').attr('data-ymd', day);
2499
2500 // set content
2501 jQuery('.vbo-overlay-fests-list').html(fests_html);
2502
2503 if (daytitle) {
2504 // define the modal buttons
2505 let cancel_btn = jQuery('<button></button>')
2506 .attr('type', 'button')
2507 .addClass('btn')
2508 .text(Joomla.JText._('VBANNULLA'))
2509 .on('click', function() {
2510 VBOCore.emitEvent('vbo-dismiss-modal-roverv-fests');
2511 });
2512
2513 let save_btn = jQuery('<button></button>')
2514 .attr('type', 'button')
2515 .addClass('btn btn-success')
2516 .text(Joomla.JText._('VBSAVE'))
2517 .on('click', function() {
2518 vboAddFest();
2519 });
2520
2521 let modal_body = VBOCore.displayModal({
2522 suffix: 'ratesoverv-fests',
2523 extra_class: 'vbo-modal-rounded vbo-modal-tall',
2524 title: '<?php VikBookingIcons::e('star'); ?> ' + daytitle,
2525 footer_left: cancel_btn,
2526 footer_right: save_btn,
2527 dismiss_event: 'vbo-dismiss-modal-roverv-fests',
2528 onDismiss: () => {
2529 jQuery('.vbo-ratesoverview-fests-wrap').appendTo('.vbo-ratesoverview-fests-helper');
2530 },
2531 });
2532
2533 jQuery('.vbo-ratesoverview-fests-wrap').appendTo(modal_body);
2534 }
2535 }
2536
2537 function vboRemoveFest(day, index, fest_type, that) {
2538 if (!confirm('<?php echo addslashes(JText::translate('VBDELCONFIRM')); ?>')) {
2539 return false;
2540 }
2541 var elem = jQuery(that);
2542 // make the AJAX request to the controller to remove this fest from the DB
2543 VBOCore.doAjax(
2544 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=remove_fest'); ?>",
2545 {
2546 tmpl: "component",
2547 dt: day,
2548 ind: index,
2549 type: fest_type
2550 },
2551 (res) => {
2552 if (res.indexOf('e4j.ok') >= 0) {
2553 // delete fest also from the json-decode array of objects
2554 if (vboFests[day] && vboFests[day]['festinfo']) {
2555 // use splice to remove the desired index from array, or delete would not make the length of the array change
2556 vboFests[day]['festinfo'].splice(index, 1);
2557 // re-build indexes of delete buttons, fundamental for removing the right index at next click
2558 vboRenderFests(day);
2559 if (!vboFests[day]['festinfo'].length) {
2560 // delete also this date object from fests
2561 delete vboFests[day];
2562 // no more fests, remove the class for this date from all cells
2563 jQuery('td.bluedays[data-ymd="'+day+'"]').removeClass('vbo-roverv-festcell');
2564 }
2565 }
2566 elem.closest('.vbo-overlay-fest-details').remove();
2567 } else {
2568 console.log(res);
2569 alert('Invalid response');
2570 }
2571 },
2572 (err) => {
2573 alert('Request failed');
2574 }
2575 );
2576 }
2577
2578 function vboAddFest() {
2579 var ymd = jQuery('.vbo-overlay-fests-addnew').attr('data-ymd');
2580 var fest_name = jQuery('#vbo-newfest-name').val();
2581 var fest_descr = jQuery('#vbo-newfest-descr').val();
2582 if (!fest_name.length) {
2583 alert(Joomla.JText._('VBO_PLEASE_FILL_FIELDS'));
2584 return false;
2585 }
2586
2587 // make the AJAX request to the controller to add the festivity to the DB
2588 VBOCore.doAjax(
2589 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=add_fest'); ?>",
2590 {
2591 tmpl: "component",
2592 dt: ymd,
2593 type: "custom",
2594 name: fest_name,
2595 descr: fest_descr
2596 },
2597 (res) => {
2598 // parse the JSON response that contains the fest object for the passed date
2599 try {
2600 var stored_fest = typeof res === 'string' ? JSON.parse(res) : res;
2601 if (!vboFests.hasOwnProperty(stored_fest['dt'])) {
2602 // we need to add the proper class to all cells to show that there is a fest
2603 jQuery('td.bluedays[data-ymd="'+stored_fest['dt']+'"]').addClass('vbo-roverv-festcell');
2604 }
2605 vboFests[stored_fest['dt']] = stored_fest;
2606 hideVboDialogFests();
2607 // reset input fields
2608 jQuery('#vbo-newfest-name').val('');
2609 jQuery('#vbo-newfest-descr').val('');
2610 } catch (e) {
2611 console.log(res);
2612 alert('Invalid response');
2613 return false;
2614 }
2615 },
2616 (err) => {
2617 alert('Request failed');
2618 }
2619 );
2620 }
2621
2622 /**
2623 * Fests dialog
2624 */
2625 function hideVboDialogFests() {
2626 VBOCore.emitEvent('vbo-dismiss-modal-roverv-fests');
2627 }
2628
2629 function checkInvokeVcm() {
2630 // trigger restriction and related checks
2631 vboCheckVcmRestrictions();
2632
2633 if (!vbolistener || !vbolistener.first || !vbolistener.first.rplan) {
2634 return;
2635 }
2636 var rplanid = vbolistener.first.rplan;
2637 var idroom = vbolistener.first.idroom;
2638 var curval = document.querySelector('input[name="roverw-newrate-vcm"]').value;
2639 if (parseInt(curval) < 0) {
2640 return;
2641 }
2642 var buiscuits = document.cookie;
2643 if (!buiscuits.length) {
2644 return;
2645 }
2646 var vcmmatch = "vboVcmRov"+idroom+rplanid+"=";
2647 if (buiscuits.indexOf(vcmmatch) >= 0) {
2648 // last cookie does not terminate with ; so just use 0 to compare
2649 vcmmatch += "0";
2650 if (buiscuits.indexOf(vcmmatch) >= 0) {
2651 jQuery('input[name="roverw-newrate-vcm"]').prop('checked', false);
2652 } else {
2653 jQuery('input[name="roverw-newrate-vcm"]').prop('checked', true);
2654 }
2655
2656 // re-trigger elements update
2657 vboCheckVcmRestrictions();
2658 }
2659 }
2660
2661 /**
2662 * Restrictions can be updated only if VCM is available and toggled ON, because
2663 * the creation and transmission is made through the Connector Class of VCM.
2664 * On top of that, the OTA pricing alteration rule overrides will toggle a status class.
2665 *
2666 * @since 1.11
2667 */
2668 function vboCheckVcmRestrictions() {
2669 if (!jQuery('input[name="roverw-newrate-vcm"]').prop('disabled') && jQuery('input[name="roverw-newrate-vcm"]').prop('checked')) {
2670 jQuery('#roverw-newrestr').val('');
2671 jQuery('.vbo-roverw-newrestr-wrap').show();
2672 jQuery('.vbo-roverw-setnewrate-vcm-ota-relation').removeClass('vbo-roverw-setnewrate-vcm-ota-relation-disabled');
2673 } else {
2674 jQuery('.vbo-roverw-newrestr-wrap').hide();
2675 jQuery('.vbo-roverw-setnewrate-vcm-ota-relation').addClass('vbo-roverw-setnewrate-vcm-ota-relation-disabled');
2676 }
2677 }
2678
2679 function showVboDialog() {
2680 var format = new Date().format;
2681 format = format.replace(new RegExp("/", 'g'), new Date().datesep);
2682 jQuery("#rovervw-roomname").html('<?php VikBookingIcons::e('building'); ?> ' + vbolistener.first.roomName);
2683 jQuery("#rovervw-rplan").html(vbolistener.first.rplanName);
2684 jQuery("#rovervw-closeopen-rplan").html('"'+vbolistener.first.rplanName+'"');
2685 jQuery("#rovervw-fromdate").html(vbolistener.first.toDate(format));
2686 jQuery("#rovervw-todate").html(vbolistener.last.toDate(format));
2687 jQuery(".vbo-roverw-alldays-inner").html("");
2688 var all_blocks = getAllBlocksBetween(vbolistener.first, vbolistener.last, true);
2689 if (all_blocks !== false) {
2690 var newdayscont = '';
2691 var highestrate = vbolistener.first.defRate;
2692 var allblocksclosed = true;
2693 jQuery.each(all_blocks, function(k, v) {
2694 let date_read = jQuery(v).attr('data-vbodateread');
2695 if (!v.hasClass('vbo-roverw-rplan-off')) {
2696 allblocksclosed = false;
2697 }
2698 var spids = jQuery(v).attr("data-vbospids").split("-");
2699 var spids_det = '';
2700 if (jQuery(v).attr("data-vbospids").length > 0 && spids.length > 0) {
2701 spids_det += "<div class=\"vbo-roverw-daymod-infospids\" data-dt=\"" + date_read + "\"><span><i class=\"<?php echo VikBookingIcons::i('info-circle'); ?>\"></i></span></div>";
2702 spids_det += "<div class=\"vbo-roverw-daymod-infospids-outcont\">";
2703 spids_det += "<div class=\"vbo-roverw-daymod-infospids-incont\"><ul>";
2704 for (var x = 0; x < spids.length; x++) {
2705 spids_det += "<li><a target=\"_blank\" href=\"<?php echo VBOPlatformDetection::isWordPress() ? 'admin.php' : 'index.php'; ?>?option=com_vikbooking&task=editseason&cid[]="+spids[x]+"\">"+roverw_messages.openSpLink.replace("%d", spids[x])+"</a></li>";
2706 }
2707 spids_det += "</ul></div></div>";
2708 }
2709 var dayrate = parseFloat(v.find('.vbo-rplan-price').text());
2710 if (!isNaN(dayrate) && dayrate > highestrate) {
2711 highestrate = dayrate;
2712 }
2713
2714 let date_parts = date_read.split(',');
2715 let wday = date_parts[0].trim();
2716 let mon = date_parts[1].replace(/[^a-z]/ig, '');
2717 let mday = date_parts[1].replace(/[^0-9]/g, '');
2718
2719 newdayscont += "<div class=\"vbo-roverw-daymod\">";
2720 newdayscont += " <div class=\"vbo-roverw-daymod-inner\">";
2721 newdayscont += " <div class=\"vbo-roverw-daymod-innercell\">";
2722 newdayscont += " <span class=\"vbo-roverw-daydate\">";
2723 newdayscont += " <span class=\"vbo-roverw-daydate-wdaymday\">";
2724 newdayscont += " <span class=\"vbo-roverw-daydate-wday\">" + wday + "</span>";
2725 newdayscont += " <span class=\"vbo-roverw-daydate-mday\">" + mday + "</span>";
2726 newdayscont += " </span>";
2727 newdayscont += " <span class=\"vbo-roverw-daydate-month\">" + mon + "</span>";
2728 newdayscont += " </span>";
2729 newdayscont += " <span class=\"vbo-roverw-dayprice\">" + v.html() + "</span>" + spids_det;
2730 newdayscont += " </div>";
2731 newdayscont += " </div>";
2732 newdayscont += "</div>";
2733 });
2734 jQuery(".vbo-roverw-alldays-inner").html(newdayscont);
2735 // if all selected blocks have a closed rate plan, show it in red in the name of the rate plan because this will be triggered to VCM if "Apply" is clicked
2736 if (allblocksclosed) {
2737 jQuery("#rovervw-rplan").html('<span style="color: #f00"><i class="<?php echo VikBookingIcons::i('ban'); ?>"></i> '+vbolistener.first.rplanName+'</span>');
2738 }
2739 }
2740
2741 // choose whether to suggest to skip derived rate plans
2742 let parentPlanRow = jQuery('.vbo-roverv-rplan[data-room-plan="' + vbolistener.first.idroom + '-' + vbolistener.first.rplan + '"][data-has-derived-rates="1"]');
2743 if (parentPlanRow.length && parentPlanRow.closest('.vbratesoverviewtable').find('.vbo-roverv-rplan').length > 1) {
2744 // parent rate plan selected among multiple room rate plans
2745 jQuery('.vbo-roverw-setnewrate-skip-derived').show();
2746 } else {
2747 // either a derived rate plan selected, or single rate plan available
2748 jQuery('.vbo-roverw-setnewrate-skip-derived').hide();
2749 // turn off the flag to skip the derived rate plans from being updated
2750 jQuery('input[name="roverw-skip-derived"]').prop('checked', false);
2751 }
2752
2753 // populate OTA relations
2754 vboRatesOvervSetRoomOtaRelations(vbolistener.first.idroom, vbolistener.first.rplan);
2755
2756 // VCM check
2757 checkInvokeVcm();
2758
2759 // define the modal buttons
2760 let cancel_btn = jQuery('<button></button>')
2761 .attr('type', 'button')
2762 .addClass('btn')
2763 .text(Joomla.JText._('VBANNULLA'))
2764 .on('click', function() {
2765 VBOCore.emitEvent('vbo-dismiss-modal-roverv-newratesrestr');
2766 });
2767
2768 let save_btn = jQuery('<button></button>')
2769 .attr('type', 'button')
2770 .addClass('btn btn-success')
2771 .html('<?php VikBookingIcons::e('check-circle'); ?>' + Joomla.JText._('VBAPPLY'))
2772 .on('click', function() {
2773 setNewRates();
2774 });
2775
2776 // display modal
2777 let modal_body = VBOCore.displayModal({
2778 suffix: 'ratesoverv-newratesrestr',
2779 extra_class: 'vbo-modal-rounded vbo-modal-tall',
2780 title: Joomla.JText._('VBO_RATES_AND_RESTR'),
2781 lock_scroll: true,
2782 draggable: true,
2783 footer_left: cancel_btn,
2784 footer_right: save_btn,
2785 dismiss_event: 'vbo-dismiss-modal-roverv-newratesrestr',
2786 loading_event: 'vbo-loading-modal-roverv-newratesrestr',
2787 loading_body: '<?php VikBookingIcons::e('refresh', 'fa-spin fa-3x fa-fw'); ?>',
2788 onDismiss: () => {
2789 // cancel dates selection
2790 vbolistener.clear();
2791 jQuery('.day-block').removeClass('block-picked-start block-picked-middle block-picked-end');
2792
2793 // reset period selection
2794 jQuery('.vbo-ratesoverview-period-from').find('span').text('');
2795 jQuery('.vbo-ratesoverview-period-from-icon').show();
2796 jQuery('.vbo-ratesoverview-period-to').find('span').text('');
2797 jQuery('.vbo-ratesoverview-period-to-icon').show();
2798
2799 // reset room-ota relations
2800 jQuery('.vbo-roverw-setnewrate-vcm-otas').html('');
2801
2802 // move the element back to its original position
2803 jQuery('.vbo-ratesoverview-newratesrestr-wrap').appendTo('.vbo-ratesoverview-newratesrestr-helper');
2804 },
2805 });
2806
2807 jQuery('.vbo-ratesoverview-newratesrestr-wrap').appendTo(modal_body);
2808 }
2809
2810 function showVboDialogPeriod() {
2811 var format = new Date().format;
2812 format = format.replace(new RegExp("/", 'g'), new Date().datesep);
2813 jQuery('.vbo-ratesoverview-period-box-cals').hide();
2814 jQuery("#rovervw-roomname").html('<?php VikBookingIcons::e('building'); ?> ' + vbolistener.first.roomName);
2815 jQuery("#rovervw-rplan").html(vbolistener.first.rplanName);
2816 jQuery("#rovervw-closeopen-rplan").html('"'+vbolistener.first.rplanName+'"');
2817 jQuery("#rovervw-fromdate").html(vbolistener.first.toDate(format));
2818 jQuery("#rovervw-todate").html(vbolistener.last.toDate(format));
2819 jQuery(".vbo-roverw-alldays-inner").html("");
2820 // reset default new price
2821 jQuery("#roverw-newrate").val("");
2822 // check if all selected blocks are closed
2823 var all_blocks = getAllBlocksBetween(vbolistener.first, vbolistener.last, true);
2824 if (all_blocks !== false) {
2825 var allblocksclosed = true;
2826 jQuery.each(all_blocks, function(k, v) {
2827 if (!v.hasClass('vbo-roverw-rplan-off')) {
2828 allblocksclosed = false;
2829 return false;
2830 }
2831 });
2832 if (allblocksclosed) {
2833 jQuery("#rovervw-rplan").html('<span style="color: #f00"><i class="<?php echo VikBookingIcons::i('ban'); ?>"></i> '+vbolistener.first.rplanName+'</span>');
2834 }
2835 }
2836
2837 // choose whether to suggest to skip derived rate plans
2838 let parentPlanRow = jQuery('.vbo-roverv-rplan[data-room-plan="' + vbolistener.first.idroom + '-' + vbolistener.first.rplan + '"][data-has-derived-rates="1"]');
2839 if (parentPlanRow.length && parentPlanRow.closest('.vbratesoverviewtable').find('.vbo-roverv-rplan').length > 1) {
2840 // parent rate plan selected among multiple room rate plans
2841 jQuery('.vbo-roverw-setnewrate-skip-derived').show();
2842 } else {
2843 // either a derived rate plan selected, or single rate plan available
2844 jQuery('.vbo-roverw-setnewrate-skip-derived').hide();
2845 // turn off the flag to skip the derived rate plans from being updated
2846 jQuery('input[name="roverw-skip-derived"]').prop('checked', false);
2847 }
2848
2849 // populate OTA relations
2850 vboRatesOvervSetRoomOtaRelations(vbolistener.first.idroom, vbolistener.first.rplan);
2851
2852 // VCM check
2853 checkInvokeVcm();
2854
2855 // define the modal buttons
2856 let cancel_btn = jQuery('<button></button>')
2857 .attr('type', 'button')
2858 .addClass('btn')
2859 .text(Joomla.JText._('VBANNULLA'))
2860 .on('click', function() {
2861 VBOCore.emitEvent('vbo-dismiss-modal-roverv-newratesrestr');
2862 });
2863
2864 let save_btn = jQuery('<button></button>')
2865 .attr('type', 'button')
2866 .addClass('btn btn-success')
2867 .html('<?php VikBookingIcons::e('check-circle'); ?>' + Joomla.JText._('VBAPPLY'))
2868 .on('click', function() {
2869 setNewRates();
2870 });
2871
2872 // display modal
2873 let modal_body = VBOCore.displayModal({
2874 suffix: 'ratesoverv-newratesrestr',
2875 extra_class: 'vbo-modal-rounded vbo-modal-tall',
2876 title: Joomla.JText._('VBO_RATES_AND_RESTR'),
2877 lock_scroll: true,
2878 draggable: true,
2879 footer_left: cancel_btn,
2880 footer_right: save_btn,
2881 dismiss_event: 'vbo-dismiss-modal-roverv-newratesrestr',
2882 loading_event: 'vbo-loading-modal-roverv-newratesrestr',
2883 loading_body: '<?php VikBookingIcons::e('refresh', 'fa-spin fa-3x fa-fw'); ?>',
2884 onDismiss: () => {
2885 // cancel dates selection
2886 vbolistener.clear();
2887 jQuery('.day-block').removeClass('block-picked-start block-picked-middle block-picked-end');
2888
2889 // reset period selection
2890 jQuery('.vbo-ratesoverview-period-from').find('span').text('');
2891 jQuery('.vbo-ratesoverview-period-from-icon').show();
2892 jQuery('.vbo-ratesoverview-period-to').find('span').text('');
2893 jQuery('.vbo-ratesoverview-period-to-icon').show();
2894
2895 // reset room-ota relations
2896 jQuery('.vbo-roverw-setnewrate-vcm-otas').html('');
2897
2898 // move the element back to its original position
2899 jQuery('.vbo-ratesoverview-newratesrestr-wrap').appendTo('.vbo-ratesoverview-newratesrestr-helper');
2900 },
2901 });
2902
2903 jQuery('.vbo-ratesoverview-newratesrestr-wrap').appendTo(modal_body);
2904 }
2905
2906 function vboCheckVcmRatesChanges() {
2907 if (vcm_exists < 1) {
2908 return false;
2909 }
2910
2911 VBOCore.doAjax(
2912 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=checkvcmrateschanges'); ?>",
2913 {
2914 tmpl: "component",
2915 e4j_debug: debug_mode
2916 },
2917 (res) => {
2918 if (res.indexOf('e4j.error') >= 0 ) {
2919 console.log(res);
2920 alert(res.replace("e4j.error.", ""));
2921 jQuery('.vbo-ratesoverview-right-inner').hide();
2922 } else {
2923 //display the VCM link for updating the rates on the OTAs
2924 let obj_res = typeof res === 'string' ? JSON.parse(res) : res;
2925 let esitcont = "";
2926 if (obj_res.changesCount > 0 && obj_res.hasOwnProperty('changesData') && obj_res.changesData.hasOwnProperty('dfrom')) {
2927 esitcont += "<span class=\"vbo-ratesoverview-vcmwarn-close\"> <i class=\"vboicn-cancel-circle\"></i></span>";
2928 esitcont += "<span class=\"vbo-ratesoverview-vcmwarn-count\"><i class=\"vboicn-notification\"></i> <span>"+roverw_messages.vcmRatesChanged.replace("%d", obj_res.changesCount)+"</span></span>";
2929 esitcont += "<span class=\"vbo-ratesoverview-vcmwarn-open\"><a href=\"index.php?option=com_vikchannelmanager&amp;task=ratespush&amp;vbosess=1\" class=\"btn btn-primary\">"+roverw_messages.vcmRatesChangedOpen+"</a></span>";
2930 jQuery('.vbo-ratesoverview-right-inner').html(esitcont).fadeIn();
2931 } else {
2932 jQuery('.vbo-ratesoverview-right-inner').hide().html('');
2933 }
2934 }
2935 },
2936 (err) => {
2937 console.error(err);
2938 jQuery('.vbo-ratesoverview-right-inner').hide();
2939 }
2940 );
2941 }
2942
2943 /* Delay and launch the check VCM rates modification function, when the page loads */
2944 setTimeout(function() {
2945 vboCheckVcmRatesChanges();
2946 }, 1000);
2947 /* - */
2948
2949 /**
2950 * Displays the results of the Channel Manager update request for rates/restrictions.
2951 * Supports multiple rate plans due to derived/linkage rules.
2952 */
2953 function renderChannelManagerResult(vcm_response) {
2954 if (!Array.isArray(vcm_response)) {
2955 // make sure the result is a list of result objects
2956 vcm_response = [vcm_response];
2957 }
2958
2959 // compose modal body
2960 var htmlres = '<div class="vbo-vcm-rates-res-container">';
2961
2962 vcm_response.forEach((obj) => {
2963 htmlres += '<div class="vbo-vcm-rates-res-rplan-wrap">';
2964
2965 if (obj.hasOwnProperty('rplan_name')) {
2966 htmlres += '<div class="vbo-vcm-rates-res-rplan-data">';
2967 htmlres += '<strong>' + obj['rplan_name'] + '</strong>';
2968 if (obj.hasOwnProperty('is_derived') && obj['is_derived']) {
2969 htmlres += ' <span class="label label-info">' + Joomla.JText._('VBO_IS_DERIVED_RATE') + '</span>';
2970 }
2971 htmlres += '</div>';
2972 }
2973
2974 if (obj.hasOwnProperty('channels_success')) {
2975 htmlres += '<div class="vbo-vcm-rates-res-success">';
2976 for (var ch_id in obj['channels_success']) {
2977 htmlres += '<div class="vbo-vcm-rates-res-channel">';
2978 htmlres += ' <div class="vbo-vcm-rates-res-channel-esit">';
2979 htmlres += ' <i class="<?php echo VikBookingIcons::i('check'); ?>"></i>';
2980 htmlres += ' </div>';
2981 htmlres += ' <div class="vbo-vcm-rates-res-channel-logo">';
2982 if (obj['channels_updated'].hasOwnProperty(ch_id) && obj['channels_updated'][ch_id]['logo'].length) {
2983 htmlres += '<img src="'+obj['channels_updated'][ch_id]['logo']+'" />';
2984 } else {
2985 htmlres += '<span>'+obj['channels_success'][ch_id]+'</span>';
2986 }
2987 htmlres += ' </div>';
2988 htmlres += '</div>';
2989 }
2990
2991 if (obj.hasOwnProperty('channels_bkdown')) {
2992 htmlres += '<div class="vbo-vcm-rates-res-bkdown">';
2993 htmlres += ' <div><pre>'+obj['channels_bkdown']+'</pre></div>';
2994 htmlres += '</div>';
2995 }
2996 htmlres += '</div>';
2997 }
2998
2999 if (obj.hasOwnProperty('channels_warnings')) {
3000 htmlres += '<div class="vbo-vcm-rates-res-warning">';
3001 for (var ch_id in obj['channels_warnings']) {
3002 htmlres += '<div class="vbo-vcm-rates-res-channel">';
3003 htmlres += ' <div class="vbo-vcm-rates-res-channel-esit">';
3004 htmlres += ' <i class="<?php echo VikBookingIcons::i('exclamation-triangle'); ?>"></i>';
3005 htmlres += ' </div>';
3006 htmlres += ' <div class="vbo-vcm-rates-res-channel-logo">';
3007 if (obj['channels_updated'].hasOwnProperty(ch_id) && obj['channels_updated'][ch_id]['logo'].length) {
3008 htmlres += '<img src="'+obj['channels_updated'][ch_id]['logo']+'" />';
3009 } else if (obj['channels_updated'].hasOwnProperty(ch_id)) {
3010 htmlres += '<span>'+obj['channels_updated'][ch_id]['name']+'</span>';
3011 }
3012 htmlres += ' </div>';
3013 htmlres += ' <div class="vbo-vcm-rates-res-channel-det">';
3014 htmlres += ' <pre>'+obj['channels_warnings'][ch_id]+'</pre>';
3015 htmlres += ' </div>';
3016 htmlres += '</div>';
3017 }
3018 htmlres += '</div>';
3019 }
3020
3021 if (obj.hasOwnProperty('channels_errors')) {
3022 htmlres += '<div class="vbo-vcm-rates-res-error">';
3023 for (var ch_id in obj['channels_errors']) {
3024 htmlres += '<div class="vbo-vcm-rates-res-channel">';
3025 htmlres += ' <div class="vbo-vcm-rates-res-channel-esit">';
3026 htmlres += ' <i class="<?php echo VikBookingIcons::i('times'); ?>"></i>';
3027 htmlres += ' </div>';
3028 htmlres += ' <div class="vbo-vcm-rates-res-channel-logo">';
3029 if (obj['channels_updated'].hasOwnProperty(ch_id) && obj['channels_updated'][ch_id]['logo'].length) {
3030 htmlres += ' <img src="'+obj['channels_updated'][ch_id]['logo']+'" />';
3031 } else if (obj['channels_updated'].hasOwnProperty(ch_id)) {
3032 htmlres += ' <span>'+obj['channels_updated'][ch_id]['name']+'</span>';
3033 }
3034 htmlres += ' </div>';
3035 htmlres += ' <div class="vbo-vcm-rates-res-channel-det">';
3036 htmlres += ' <pre>'+obj['channels_errors'][ch_id]+'</pre>';
3037 htmlres += ' </div>';
3038 htmlres += '</div>';
3039 }
3040 htmlres += '</div>';
3041 }
3042
3043 htmlres += '</div>';
3044 });
3045
3046 // close container
3047 htmlres += '</div>';
3048
3049 // display results within a modal window
3050 VBOCore.displayModal({
3051 suffix: 'vbo-vcm-rates-res',
3052 extra_class: 'vbo-modal-rounded vbo-modal-tall',
3053 title: Joomla.JText._('VBOVCMRATESRES'),
3054 body: htmlres,
3055 draggable: true,
3056 });
3057 }
3058
3059 function vboRatesOvervSetRoomOtaRelations(room_id, rplan_id) {
3060 // the room-ota relations wrapper
3061 let wrapper = jQuery('.vbo-roverw-setnewrate-vcm-otas');
3062
3063 // always empty the wrapper
3064 wrapper.html('');
3065
3066 if (!room_id || !rplan_id || !vboRoomOtaRels.hasOwnProperty(room_id)) {
3067 // nothing to render
3068 return;
3069 }
3070
3071 // start counter
3072 let ota_ch_counter = 0;
3073
3074 // build and append room-OTA relations
3075 for (const ota_name in vboRoomOtaRels[room_id]['channels']) {
3076 // build ota readable name
3077 let ota_read_name = ota_name;
3078 ota_read_name = ota_read_name.replace(/api$/, '');
3079 ota_read_name = ota_read_name.replace(/^(google)(hotel|vr)$/i, '$1 $2');
3080
3081 // build room-ota relation block and elements
3082 let ota_block = jQuery('<div></div>');
3083 ota_block.addClass('vbo-roverw-setnewrate-vcm-ota-relation');
3084
3085 let ota_block_inner = jQuery('<div></div>');
3086 ota_block_inner
3087 .addClass('vbo-roverw-setnewrate-vcm-ota-relation-pricing')
3088 .attr('data-ota', (ota_name + '').toLowerCase());
3089
3090 let ota_block_channel = jQuery('<div></div>');
3091 ota_block_channel
3092 .addClass('vbo-roverw-setnewrate-vcm-ota-relation-channel')
3093 .attr('data-otaid', vboRoomOtaRels[room_id]['accounts'][ota_ch_counter]['idchannel'])
3094 .append('<img src="' + vboRoomOtaRels[room_id]['channels'][ota_name] + '" />')
3095 .append('<span>' + ota_read_name + '</span>');
3096
3097 let ota_pricing_value = jQuery('<span></span>');
3098 ota_pricing_value
3099 .addClass('vbo-roverw-setnewrate-vcm-ota-pricing-startvalue')
3100 .html('<?php VikBookingIcons::e('circle-notch', 'fa-spin fa-fw'); ?>')
3101 .on('click', function() {
3102 jQuery(this)
3103 .closest('.vbo-roverw-setnewrate-vcm-ota-relation-pricing')
3104 .find('.vbo-roverw-setnewrate-vcm-ota-channel-pricing')
3105 .toggle();
3106 });
3107
3108 let ota_block_pricing = jQuery('<div></div>');
3109 ota_block_pricing
3110 .addClass('vbo-roverw-setnewrate-vcm-ota-channel-pricing')
3111 .css('display', 'none')
3112 .append(jQuery('.vbo-roverw-setnewrate-vcm-ota-pricing-alteration').first().clone());
3113
3114 // register "input" event for select/input elements to control the channel alteration rule overrides
3115 ota_block_pricing.find('select, input').on('input', function() {
3116 let input_elem = jQuery(this);
3117
3118 // get the current channel alteration command
3119 let ota_alteration_command = input_elem
3120 .closest('.vbo-roverw-setnewrate-vcm-ota-relation-pricing')
3121 .find('.vbo-roverw-setnewrate-ota-pricing-currentvalue[data-alteration]')
3122 .attr('data-alteration');
3123
3124 // access alteration rule and input value
3125 let rmod_type = input_elem.attr('data-alter-rule');
3126 let rmod_value = input_elem.val();
3127
3128 if (!ota_alteration_command || !rmod_type || !(rmod_value + '').length) {
3129 return;
3130 }
3131
3132 // check what pricing factor was changed
3133 if (rmod_type == 'rmodsop') {
3134 // increase or decrease rate
3135 let command_old_val = ota_alteration_command.substr(0, 1);
3136 let command_new_val = parseInt(rmod_value) == 1 ? '+' : '-';
3137 ota_alteration_command = ota_alteration_command.replace(command_old_val, command_new_val);
3138 } else if (rmod_type == 'rmodsamount') {
3139 // amount
3140 let command_op = ota_alteration_command.substr(0, 1);
3141 let command_val = ota_alteration_command.substr(-1, 1);
3142 let command_old_val = ota_alteration_command.replace(command_op, '').replace(command_val, '');
3143 let command_new_val = parseFloat(rmod_value);
3144 ota_alteration_command = ota_alteration_command.replace(command_old_val, command_new_val);
3145 } else if (rmod_type == 'rmodsval') {
3146 // percent or absolute
3147 let command_old_val = ota_alteration_command.substr(-1, 1);
3148 let command_new_val = parseInt(rmod_value) == 1 ? '%' : '*';
3149 ota_alteration_command = ota_alteration_command.replace(command_old_val, command_new_val);
3150 }
3151
3152 // get current currency options
3153 let currencyObj = VBOCore.getCurrency();
3154 let orig_currency_options = currencyObj.getOptions();
3155
3156 // check if the channel requires a specific currency
3157 let ota_currency_data = input_elem
3158 .closest('.vbo-roverw-setnewrate-vcm-ota-relation-pricing')
3159 .find('.vbo-roverw-setnewrate-ota-pricing-willvalue')
3160 .attr('data-currency');
3161 if (ota_currency_data) {
3162 // decode currency data instructions
3163 try {
3164 ota_currency_data = JSON.parse(ota_currency_data);
3165 } catch (e) {
3166 ota_currency_data = {};
3167 }
3168 }
3169
3170 // define the current channel alteration string (readable)
3171 let ota_alteration_string = ota_alteration_command;
3172
3173 // finalize the current channel alteration string (readable)
3174 let ota_alteration_val = ota_alteration_string.substr(-1, 1);
3175 if (ota_alteration_val != '%') {
3176 ota_alteration_string = ota_alteration_string.replace(ota_alteration_val, '') + ((ota_currency_data && ota_currency_data?.symbol ? ota_currency_data.symbol : '') || vbo_currency_symbol);
3177 }
3178
3179 // update the alteration rule command attribute
3180 input_elem
3181 .closest('.vbo-roverw-setnewrate-vcm-ota-relation-pricing')
3182 .find('.vbo-roverw-setnewrate-ota-pricing-currentvalue[data-alteration]')
3183 .attr('data-alteration', ota_alteration_command);
3184
3185 // update the alteration rule string tag text
3186 input_elem
3187 .closest('.vbo-roverw-setnewrate-vcm-ota-relation-pricing')
3188 .find('.vbo-roverw-setnewrate-ota-pricing-currentvalue[data-alteration]')
3189 .html(ota_alteration_string);
3190
3191 // get the current rate to set
3192 let current_room_rate = jQuery('#roverw-newrate').val();
3193 if (current_room_rate) {
3194 // dispatch the event to trigger the re-calculation of the OTA rates
3195 VBOCore.emitEvent('vbo-roverv-setnewrate-calc-ota-pricing', {
3196 rate: current_room_rate,
3197 });
3198 }
3199 });
3200
3201 // append elements to wrapper
3202 ota_block_channel.append(ota_pricing_value);
3203 ota_block_inner.append(ota_block_channel);
3204 ota_block_inner.append(ota_block_pricing);
3205 ota_block.append(ota_block_inner);
3206 wrapper.append(ota_block);
3207
3208 // increase OTA channel counter
3209 ota_ch_counter++;
3210 }
3211
3212 // trigger an AJAX request to load the current alteration rules, if any
3213 VBOCore.doAjax(
3214 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=pricing.loadOtaAlterationRules'); ?>",
3215 {
3216 room_id: room_id,
3217 rate_id: rplan_id,
3218 },
3219 (res) => {
3220 var obj_res = typeof res === 'string' ? JSON.parse(res) : res;
3221 let alter_room_rates = obj_res['rmod'] == '1' || obj_res['rmod'] == 1;
3222
3223 // scan all room OTAs
3224 jQuery('.vbo-roverw-setnewrate-vcm-otas').find('.vbo-roverw-setnewrate-vcm-ota-relation').each(function(key, elem) {
3225 // get the current OTA identifier and whether pricing is altered
3226 let ota_wrap = jQuery(elem);
3227 let ota_id = ota_wrap.find('.vbo-roverw-setnewrate-vcm-ota-relation-channel').attr('data-otaid');
3228 let alter_ota_rates = alter_room_rates && obj_res['channels'] && (obj_res['channels'].includes(ota_id) || obj_res['channels'].includes(parseInt(ota_id)));
3229 if (!alter_ota_rates && alter_room_rates && obj_res.hasOwnProperty('rmod_channels') && obj_res['rmod_channels'].hasOwnProperty(ota_id)) {
3230 alter_ota_rates = true;
3231 }
3232
3233 // check if the current channel is using a different currency
3234 let ota_currency_data = {};
3235 if (obj_res.hasOwnProperty('cur_rplans') && obj_res['cur_rplans'].hasOwnProperty(ota_id)) {
3236 let ota_check_currency = obj_res['cur_rplans'][ota_id];
3237 if (obj_res.hasOwnProperty('currency_data_options') && obj_res['currency_data_options'].hasOwnProperty(ota_check_currency)) {
3238 // set custom currency data returned
3239 ota_currency_data = obj_res['currency_data_options'][ota_check_currency];
3240 }
3241 }
3242
3243 // build pricing alteration strings
3244 let alteration_command = '';
3245 let alteration_string = '';
3246
3247 // default alteration factors (no pricing alteration rules)
3248 let alter_op = '+';
3249 let alter_amount = '0';
3250 let alter_val = '%';
3251
3252 if (alter_ota_rates) {
3253 // check how rates are altered for this channel
3254 if (obj_res.hasOwnProperty('rmod_channels') && obj_res['rmod_channels'].hasOwnProperty(ota_id)) {
3255 // ota-level pricing alteration rule
3256 if (parseInt(obj_res['rmod_channels'][ota_id]['rmod']) == 1) {
3257 alter_op = parseInt(obj_res['rmod_channels'][ota_id]['rmodop']) == 1 ? '+' : '-';
3258 alter_amount = obj_res['rmod_channels'][ota_id]['rmodamount'];
3259 alter_val = parseInt(obj_res['rmod_channels'][ota_id]['rmodval']) == 1 ? '%' : '*';
3260 }
3261 } else {
3262 // room-level pricing alteration rule
3263 alter_op = parseInt(obj_res['rmodop']) == 1 ? '+' : '-';
3264 alter_amount = obj_res['rmodamount'] || '0';
3265 alter_val = parseInt(obj_res['rmodval']) == 1 ? '%' : '*';
3266 }
3267 }
3268
3269 // construct alteration strings
3270 alteration_command = alter_op + (alter_amount + '') + (alter_val + '');
3271 alteration_string = alter_op + (alter_amount + '') + (alter_val == '%' ? '%' : (ota_currency_data?.symbol || vbo_currency_symbol));
3272
3273 // stop room-ota loading and set alteration string
3274 let alteration_elem = jQuery('<span></span>');
3275 alteration_elem
3276 .addClass('vbo-roverw-setnewrate-ota-pricing-currentvalue')
3277 .attr('data-alteration', alteration_command)
3278 .html(alteration_string);
3279
3280 let will_alter_elem = jQuery('<span></span>').addClass('vbo-roverw-setnewrate-ota-pricing-willvalue');
3281
3282 if (ota_currency_data.symbol) {
3283 // set currency data object
3284 will_alter_elem.attr('data-currency', JSON.stringify(ota_currency_data));
3285 }
3286
3287 // set elements
3288 ota_wrap
3289 .find('.vbo-roverw-setnewrate-vcm-ota-pricing-startvalue')
3290 .html('')
3291 .append(will_alter_elem)
3292 .append(alteration_elem)
3293 .append('<?php VikBookingIcons::e('edit', 'edit-ota-pricing'); ?>');
3294
3295 // populate default values for input element overrides
3296 ota_wrap.find('select[data-alter-rule="rmodsop"]').val(alter_op == '+' ? 1 : 0);
3297 ota_wrap.find('input[data-alter-rule="rmodsamount"]').val(parseInt(alter_amount) > 0 ? alter_amount : '');
3298 ota_wrap.find('select[data-alter-rule="rmodsval"]').val(alter_val == '%' ? 1 : 0);
3299 });
3300
3301 // check the current rate value
3302 let current_room_rate = jQuery('#roverw-newrate').val();
3303 if (current_room_rate) {
3304 // dispatch the event to allow the actual calculation of the OTA rate
3305 VBOCore.emitEvent('vbo-roverv-setnewrate-calc-ota-pricing', {
3306 rate: current_room_rate,
3307 room_id: room_id,
3308 rate_id: rplan_id,
3309 });
3310 }
3311 },
3312 (err) => {
3313 alert(err.responseText || 'Request Failed');
3314 }
3315 );
3316 }
3317
3318 function setNewRates() {
3319 // access cells selected
3320 let all_blocks = getAllBlocksBetween(vbolistener.first, vbolistener.last, true);
3321
3322 // gather rates modification values
3323 let toval = parseFloat(jQuery("#roverw-newrate").val());
3324 let invoke_vcm = jQuery('input[name="roverw-newrate-vcm"]').is(':checked') ? 1 : 0;
3325 let setminlos = jQuery('#roverw-newrestr').val();
3326 let closerplan = 0;
3327 let rateType = document.querySelector('.vbo-roverw-newrwrap').getAttribute('data-rate-type') || 'fixed';
3328 let addsub_op = rateType === 'addsub' ? document.querySelector('.vbo-roverw-newrcont[data-rate-type="addsub"] [data-rate-type-rule="rmodsop"]')?.value : 0;
3329 let addsub_amount = rateType === 'addsub' ? document.querySelector('.vbo-roverw-newrcont[data-rate-type="addsub"] [data-rate-type-rule="rmodsamount"]')?.value : 0;
3330 let addsub_value = rateType === 'addsub' ? document.querySelector('.vbo-roverw-newrcont[data-rate-type="addsub"] [data-rate-type-rule="rmodsval"]')?.value : 0;
3331
3332 if (all_blocks === false || (rateType === 'fixed' && (isNaN(toval) || toval <= 0))) {
3333 if (setminlos && setminlos > 0) {
3334 alert(<?php echo json_encode(JText::translate('VBO_INCR_ZERO_RESTR_TIP')); ?>);
3335 } else {
3336 // incomplete form
3337 alert(roverw_messages.setNewRatesMissing);
3338 }
3339
3340 // always abort
3341 return false;
3342 }
3343
3344 if (rateType === 'addsub' && (isNaN(addsub_amount) || addsub_amount <= 0)) {
3345 if (!setminlos || setminlos <= 0) {
3346 alert(roverw_messages.setNewRatesMissing);
3347 return false;
3348 } else {
3349 // allow to change just the minimum stay by forcing the operation to increase rates by 0
3350 addsub_op = 1;
3351 addsub_amount = 0;
3352 addsub_value = 0;
3353 }
3354 }
3355
3356 // set cookie to remember the action to invoke VCM for this combination of room-rateplan
3357 let nd = new Date();
3358 nd.setTime(nd.getTime() + (365*24*60*60*1000));
3359 document.cookie = "vboVcmRov"+vbolistener.first.idroom+vbolistener.first.rplan+"="+invoke_vcm+"; expires=" + nd.toUTCString() + "; path=/; SameSite=Lax";
3360 // check whether all blocks have closed the rate plan
3361 let allblocksclosed = true;
3362 jQuery.each(all_blocks, function(k, v) {
3363 if (!v.hasClass('vbo-roverw-rplan-off')) {
3364 allblocksclosed = false;
3365 // break
3366 return false;
3367 }
3368 });
3369 closerplan = allblocksclosed ? 1 : closerplan;
3370
3371 // check the OTA pricing alteration rules, if any
3372 let ota_pricing = {};
3373 if (invoke_vcm) {
3374 // scan all OTA alteration rules, if any
3375 document.querySelectorAll('.vbo-roverw-setnewrate-ota-pricing-currentvalue[data-alteration]').forEach((elem) => {
3376 // channel alteration string
3377 let alter_string = elem.getAttribute('data-alteration');
3378 if (!alter_string) {
3379 alter_string = '';
3380 }
3381
3382 // access the parent node to get the OTA channel identifier
3383 let ota_id = elem
3384 .closest('.vbo-roverw-setnewrate-vcm-ota-relation-channel[data-otaid]')
3385 .getAttribute('data-otaid');
3386
3387 if (!ota_id || !alter_string || alter_string == '+0%' || alter_string == '+0*') {
3388 // avoid pushing an empty alteration command
3389 return;
3390 }
3391
3392 // push OTA pricing alteration command
3393 ota_pricing[ota_id] = alter_string;
3394 });
3395 }
3396
3397 if (!Object.keys(ota_pricing).length) {
3398 // unset the object for the request
3399 ota_pricing = null;
3400 }
3401
3402 // show loading
3403 VBOCore.emitEvent('vbo-loading-modal-roverv-newratesrestr');
3404
3405 VBOCore.doAjax(
3406 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=pricing.setnewrates'); ?>",
3407 {
3408 tmpl: "component",
3409 e4j_debug: debug_mode,
3410 id_room: vbolistener.first.idroom,
3411 id_price: vbolistener.first.rplan,
3412 rate_type: rateType,
3413 rate: toval,
3414 addsub_op: addsub_op,
3415 addsub_amount: addsub_amount,
3416 addsub_value: addsub_value,
3417 vcm: invoke_vcm,
3418 minlos: setminlos,
3419 fromdate: vbolistener.first.toDate("yy-mm-dd"),
3420 todate: vbolistener.last.toDate("yy-mm-dd"),
3421 rateclosed: closerplan,
3422 ota_pricing: ota_pricing,
3423 skip_derived: (jQuery('input[name="roverw-skip-derived"]').prop('checked') ? 1 : 0),
3424 },
3425 (res) => {
3426 if (typeof res === 'string' && res.indexOf('e4j.error') === 0) {
3427 alert(res.replace('e4j.error.', ''));
3428 // stop loading
3429 VBOCore.emitEvent('vbo-loading-modal-roverv-newratesrestr');
3430 // abort
3431 return;
3432 }
3433
3434 try {
3435 // display new rates in all_blocks IDs
3436 let restr_set = false;
3437 let obj_res = typeof res === 'string' ? JSON.parse(res) : res;
3438
3439 // memorize the cells whose new minimum stay was set, to avoid issues with multiple and derived rate plans
3440 let minStayCellsUpdated = [];
3441
3442 jQuery.each(obj_res, function(k, v) {
3443 if (k == 'vcm') {
3444 return true;
3445 }
3446
3447 let elem = jQuery("#cell-"+k+"-"+vbolistener.first.idroom);
3448 if (elem.length) {
3449 elem.find(".vbo-rplan-price").html(v.cost);
3450 let spids = '';
3451 if (v.hasOwnProperty('spids')) {
3452 jQuery.each(v.spids, function(spk, spv) {
3453 spids += spv+'-';
3454 });
3455 //right trim dash
3456 spids = spids.replace(/-+$/, '');
3457 }
3458 elem.attr('data-vbospids', spids);
3459 // check if restrictions were set
3460 if (v.hasOwnProperty('newminlos')) {
3461 // always convert v.newminlos to a string to avoid errors with indexOf
3462 let newminlos = v.newminlos+'';
3463 if (newminlos.indexOf('e4j.error') >= 0) {
3464 // an error occurred
3465 alert(newminlos.replace("e4j.error.", ""));
3466 } else {
3467 // get cell identifier part
3468 restr_set = true;
3469 let cell_parts = k.split('-');
3470 let cell_identifier = cell_parts[0]+'-'+cell_parts[1]+'-'+cell_parts[2]+'-'+vbolistener.first.idroom;
3471 let restr_elem = jQuery('#cell-' + cell_identifier + '-restr');
3472 if (restr_elem.length && !minStayCellsUpdated.includes(cell_identifier)) {
3473 let restr_cont = restr_elem.find('.vbo-roverw-curminlos');
3474 restr_cont.html(newminlos);
3475 if (parseInt(newminlos) > 1) {
3476 restr_cont.addClass('vbo-roverw-curminlos-active');
3477 } else {
3478 restr_cont.removeClass('vbo-roverw-curminlos-active');
3479 }
3480 // store flag for cell being updated with minimum stay
3481 minStayCellsUpdated.push(cell_identifier);
3482 }
3483 // attempt to remove an eventual orphan list
3484 let orphan_elem = jQuery('.vbo-ratesoverview-orphan-dt[data-dt="' + cell_identifier + '"]');
3485 if (orphan_elem.length) {
3486 if (orphan_elem.length < 2) {
3487 jQuery('#vbo-ratesoverview-orphans-wrapper-'+vbolistener.first.idroom).fadeOut(400, function() {
3488 orphan_elem.remove();
3489 });
3490 } else {
3491 orphan_elem.remove();
3492 }
3493 }
3494 }
3495 }
3496 //
3497 }
3498 });
3499
3500 // stop loading
3501 VBOCore.emitEvent('vbo-loading-modal-roverv-newratesrestr');
3502
3503 // dismiss modal
3504 VBOCore.emitEvent('vbo-dismiss-modal-roverv-newratesrestr');
3505
3506 if (obj_res.hasOwnProperty('vcm')) {
3507 // display CM results
3508 renderChannelManagerResult(obj_res['vcm']);
3509 } else {
3510 setTimeout(function() {
3511 vboCheckVcmRatesChanges();
3512 }, 500);
3513 }
3514
3515 if (restr_set) {
3516 // re-calculate orphans after setting the restriction
3517 setTimeout(function() {
3518 vboCheckOrphans();
3519 }, 200);
3520 }
3521 } catch(err) {
3522 throw new Error(err);
3523 }
3524 },
3525 (err) => {
3526 alert(err.responseText || 'Request Failed');
3527 // stop loading
3528 VBOCore.emitEvent('vbo-loading-modal-roverv-newratesrestr');
3529 }
3530 );
3531 }
3532
3533 function modRoomRatePlan(mode) {
3534 var all_blocks = getAllBlocksBetween(vbolistener.first, vbolistener.last, true);
3535 if (all_blocks !== false && mode.length > 0) {
3536 // start loading
3537 VBOCore.emitEvent('vbo-loading-modal-roverv-newratesrestr');
3538
3539 VBOCore.doAjax(
3540 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=modroomrateplans'); ?>",
3541 {
3542 tmpl: "component",
3543 e4j_debug: debug_mode,
3544 id_room: vbolistener.first.idroom,
3545 id_price: vbolistener.first.rplan,
3546 type: mode,
3547 fromdate: vbolistener.first.toDate("yy-mm-dd"),
3548 todate: vbolistener.last.toDate("yy-mm-dd")
3549 },
3550 (res) => {
3551 if (res.indexOf('e4j.error') >= 0 ) {
3552 console.log(res);
3553 alert(res.replace("e4j.error.", ""));
3554 // stop loading
3555 VBOCore.emitEvent('vbo-loading-modal-roverv-newratesrestr');
3556 } else {
3557 // apply new classes in all_blocks IDs
3558 let obj_res = typeof res === 'string' ? JSON.parse(res) : res;
3559 jQuery.each(obj_res, function(k, v) {
3560 var elem = jQuery("#cell-"+k+"-"+vbolistener.first.idroom);
3561 if (elem.length) {
3562 elem.removeClass(v.oldcls).addClass(v.newcls);
3563 }
3564 });
3565
3566 // stop loading
3567 VBOCore.emitEvent('vbo-loading-modal-roverv-newratesrestr');
3568
3569 // dismiss modal
3570 VBOCore.emitEvent('vbo-dismiss-modal-roverv-newratesrestr');
3571
3572 setTimeout(function() {
3573 vboCheckVcmRatesChanges();
3574 }, 500);
3575 }
3576 },
3577 (err) => {
3578 alert("Request Failed");
3579 // stop loading
3580 VBOCore.emitEvent('vbo-loading-modal-roverv-newratesrestr');
3581 }
3582 );
3583 } else {
3584 alert(roverw_messages.modRplansMissing);
3585 return false;
3586 }
3587 }
3588
3589 function vboUpdateRplan(that) {
3590 if (vbolistener === null || vbolistener.first === null) {
3591 return true;
3592 }
3593 vbolistener.first.rplan = jQuery(that).val();
3594 vbolistener.first.rplanName = jQuery(that).find('option:selected').text();
3595 vbolistener.first.defRate = jQuery(that).find('option:selected').attr('data-defrate');
3596 }
3597
3598 function pickBlock(id) {
3599 var struct = initBlockStructure(id);
3600
3601 if (!vbolistener.pickFirst(struct)) {
3602 // first already picked
3603 if ((vbolistener.first.isBeforeThan(struct) || vbolistener.first.isSameDay(struct)) && vbolistener.first.isSameRplan(struct) && vbolistener.first.isSameRoom(struct)) {
3604 // last > first : pick last
3605 if (vbolistener.pickLast(struct)) {
3606 var all_blocks = getAllBlocksBetween(vbolistener.first, vbolistener.last, false);
3607 if (all_blocks !== false) {
3608 jQuery.each(all_blocks, function(k, v){
3609 if ( !v.hasClass('block-picked-middle') ) {
3610 v.addClass('block-picked-middle');
3611 }
3612 });
3613 jQuery('#'+vbolistener.last.id).addClass('block-picked-end');
3614 showVboDialog();
3615 }
3616 }
3617 } else {
3618 // last < first : clear selection
3619 vbolistener.clear();
3620 jQuery('.day-block').removeClass('block-picked-start block-picked-middle block-picked-end');
3621 }
3622 } else {
3623 // first picked
3624 jQuery('#'+vbolistener.first.id).addClass('block-picked-start');
3625 }
3626 }
3627
3628 function getAllBlocksBetween(start, end, outers_included) {
3629 if (!start.isSameRplan(end) || !start.isSameRoom(end)) {
3630 return false;
3631 }
3632
3633 if (start.isAfterThan(end)) {
3634 return false;
3635 }
3636
3637 var queue = new Array();
3638
3639 if (outers_included) {
3640 queue.push(jQuery('#'+start.id));
3641 }
3642
3643 if (start.isSameDay(end)) {
3644 return queue;
3645 }
3646
3647 var node = jQuery('#'+start.id).next();
3648 var end_id = jQuery('#'+end.id).attr('id');
3649 while (node.length > 0 && node.attr('id') != end_id) {
3650 queue.push(node);
3651 node = node.next();
3652 }
3653
3654 if (outers_included) {
3655 queue.push(jQuery('#'+end.id));
3656 }
3657
3658 return queue;
3659 }
3660
3661 function getPeriodStructure(data) {
3662 return {
3663 "day": parseInt(data[0]),
3664 "month": parseInt(data[1]),
3665 "year": parseInt(data[2]),
3666 "rplan": data[3],
3667 "idroom": data[6],
3668 "roomName": data[7],
3669 "rplanName": data[4],
3670 "defRate": data[5],
3671 "id": "cell-"+parseInt(data[0])+"-"+parseInt(data[1])+"-"+parseInt(data[2])+"-"+data[3]+"-"+data[6],
3672 "isSameDay": function(block) {
3673 return (this.month == block.month && this.day == block.day && this.year == block.year);
3674 },
3675 "isBeforeThan": function(block) {
3676 return (
3677 (this.year < block.year) ||
3678 (this.year == block.year && this.month < block.month) ||
3679 (this.year == block.year && this.month == block.month && this.day < block.day)
3680 );
3681 },
3682 "isAfterThan": function(block) {
3683 return (
3684 (this.year > block.year) ||
3685 (this.year == block.year && this.month > block.month) ||
3686 (this.year == block.year && this.month == block.month && this.day > block.day)
3687 );
3688 },
3689 "isSameRplan": function(block) {
3690 return (this.rplan == block.rplan);
3691 },
3692 "isSameRoom": function(block) {
3693 return (this.idroom == block.idroom);
3694 },
3695 "toDate": function(format) {
3696 return format.replace(
3697 'dd', (this.day < 10 ? '0' : '' ) + this.day
3698 ).replace(
3699 'mm', (this.month < 10 ? '0' : '') + this.month
3700 ).replace(
3701 'yy', this.year
3702 );
3703 }
3704 };
3705 }
3706
3707 function initBlockStructure(id) {
3708 var s = id.split("-");
3709 if (s.length != 6) {
3710 return {};
3711 }
3712 var elem = jQuery("#"+id);
3713 return {
3714 "day": parseInt(s[1]),
3715 "month": parseInt(s[2]),
3716 "year": parseInt(s[3]),
3717 "rplan": s[4],
3718 "idroom": s[5],
3719 "roomName": elem.parent("tr").find("td").first().attr("data-roomname"),
3720 "rplanName": elem.parent("tr").find("td").first().find(".vbo-rplan-name").text(),
3721 "defRate": elem.parent("tr").find("td").first().attr("data-defrate"),
3722 "id": id,
3723 "isSameDay": function(block) {
3724 return (this.month == block.month && this.day == block.day && this.year == block.year);
3725 },
3726 "isBeforeThan": function(block) {
3727 return (
3728 (this.year < block.year) ||
3729 (this.year == block.year && this.month < block.month) ||
3730 (this.year == block.year && this.month == block.month && this.day < block.day)
3731 );
3732 },
3733 "isAfterThan": function(block) {
3734 return (
3735 (this.year > block.year) ||
3736 (this.year == block.year && this.month > block.month) ||
3737 (this.year == block.year && this.month == block.month && this.day > block.day)
3738 );
3739 },
3740 "isSameRplan": function(block) {
3741 return (this.rplan == block.rplan);
3742 },
3743 "isSameRoom": function(block) {
3744 return (this.idroom == block.idroom);
3745 },
3746 "toDate": function(format) {
3747 return format.replace(
3748 'dd', (this.day < 10 ? '0' : '') + this.day
3749 ).replace(
3750 'mm', (this.month < 10 ? '0' : '') + this.month
3751 ).replace(
3752 'yy', this.year
3753 );
3754 }
3755 };
3756 }
3757
3758 function CalendarListener() {
3759 this.first = null;
3760 this.last = null;
3761 }
3762
3763 CalendarListener.prototype.pickFirst = function(struct) {
3764 if (!this.isFirstPicked()) {
3765 this.first = struct;
3766 return true;
3767 }
3768 return false;
3769 }
3770
3771 CalendarListener.prototype.pickLast = function(struct) {
3772 if (!this.isLastPicked() && this.isFirstPicked()) {
3773 this.last = struct;
3774 return true;
3775 }
3776 return false;
3777 }
3778
3779 CalendarListener.prototype.clear = function() {
3780 this.first = null;
3781 this.last = null;
3782 }
3783
3784 CalendarListener.prototype.isFirstPicked = function() {
3785 return this.first != null;
3786 }
3787
3788 CalendarListener.prototype.isLastPicked = function() {
3789 return this.last != null;
3790 }
3791
3792 /* Dates selection - End */
3793
3794 function vboToggleOBPRows(showRows) {
3795 if (showRows) {
3796 jQuery('tr.vbo-roverviewtablerow-occupancy').show();
3797 } else {
3798 jQuery('tr.vbo-roverviewtablerow-occupancy').hide();
3799 }
3800
3801 let newcookieval = showRows ? 1 : 0;
3802 let nd = new Date();
3803 nd.setTime(nd.getTime() + (365*24*60*60*1000));
3804 document.cookie = "vboRovwObp=" + newcookieval + "; expires=" + nd.toUTCString() + "; path=/; SameSite=Lax";
3805 }
3806
3807 function vboToggleCollapse() {
3808 var expand = jQuery('input[name="vbo_collapse"]').is(':checked');
3809 var collapse = expand ? 0 : 1;
3810 if (expand) {
3811 jQuery('.vbo-ratesoverview-roomsel-entry-calc-inner, .vbo-roverv-forecast-inner, .vbo-ratesoverview-period-container').show();
3812 jQuery('.vbo-ratesoverview-top-container').removeClass('collapsed');
3813 } else {
3814 jQuery('.vbo-ratesoverview-roomsel-entry-calc-inner, .vbo-roverv-forecast-inner, .vbo-ratesoverview-period-container').hide();
3815 jQuery('.vbo-ratesoverview-top-container').addClass('collapsed');
3816 }
3817 var nd = new Date();
3818 nd.setTime(nd.getTime() + (365*24*60*60*1000));
3819 document.cookie = "vboRovwColl=" + collapse + "; expires=" + nd.toUTCString() + "; path=/; SameSite=Lax";
3820 }
3821
3822 function vboCheckCollapse() {
3823 if (jQuery('.vbo-ratesoverview-roomsel-entry-calc-inner').is(':visible') && jQuery('.vbo-roverv-forecast-inner').is(':visible')) {
3824 // all elements are visible, expanded
3825 return;
3826 }
3827 jQuery('input[name="vbo_collapse"]').trigger('click');
3828 }
3829
3830 function vboSelectAllRooms(btn) {
3831 var sel_elem = jQuery('#roomsel');
3832 sel_elem.find('option').prop('selected', true);
3833 sel_elem.trigger('change');
3834 // hide button
3835 jQuery(btn).parent().remove();
3836 // auto-submit form
3837 document.vboratesovwform.submit();
3838 }
3839
3840 var timeline_height_set = false;
3841
3842 jQuery(function() {
3843 // register to the event emitted when a new booking is created through an admin widget
3844 document.addEventListener('vbo_new_booking_created', (e) => {
3845 if (!e || !e.detail || !e.detail.hasOwnProperty('bid') || !e.detail['bid']) {
3846 // do nothing
3847 return;
3848 }
3849 // reload the page to display the new booking just created
3850 location.reload();
3851 });
3852
3853 // check for OBP rows initial status
3854 if (!(<?php echo $cookie->get('vboRovwObp', 1, 'int'); ?>) && jQuery('tr.vbo-roverviewtablerow-occupancy').length) {
3855 // hide occupancy rows when page loads for cookie preferences
3856 jQuery('tr.vbo-roverviewtablerow-occupancy').hide();
3857 }
3858 //
3859 jQuery(".vbo-ratesoverview-tab-los").click(function() {
3860 var nd = new Date();
3861 nd.setTime(nd.getTime() + (365*24*60*60*1000));
3862 document.cookie = "vboRovwRab=los; expires=" + nd.toUTCString() + "; path=/; SameSite=Lax";
3863 jQuery(this).removeClass("vbo-ratesoverview-tab-unactive").addClass("vbo-ratesoverview-tab-active");
3864 jQuery(".vbo-ratesoverview-tab-cal").removeClass("vbo-ratesoverview-tab-active").addClass("vbo-ratesoverview-tab-unactive");
3865 jQuery(".vbo-ratesoverview-roomsel-entry-los").show();
3866 jQuery(".vbo-ratesoverview-roomsel-entry-forecast").hide();
3867 jQuery(".vbo-ratesoverview-caltab-cont").hide();
3868 jQuery(".vbo-ratesoverview-lostab-cont").fadeIn();
3869 if (!timeline_height_set) {
3870 jQuery('.vbo-timeline-container').css('min-height', (jQuery('.vbo-timeline-container').outerHeight() + 20));
3871 timeline_height_set = true;
3872 }
3873 });
3874 jQuery(".vbo-ratesoverview-tab-cal").click(function() {
3875 var nd = new Date();
3876 nd.setTime(nd.getTime() + (365*24*60*60*1000));
3877 document.cookie = "vboRovwRab=cal; expires=" + nd.toUTCString() + "; path=/; SameSite=Lax";
3878 jQuery(this).removeClass("vbo-ratesoverview-tab-unactive").addClass("vbo-ratesoverview-tab-active");
3879 jQuery(".vbo-ratesoverview-tab-los").removeClass("vbo-ratesoverview-tab-active").addClass("vbo-ratesoverview-tab-unactive");
3880 jQuery(".vbo-ratesoverview-roomsel-entry-los").hide();
3881 jQuery(".vbo-ratesoverview-roomsel-entry-forecast").show();
3882 jQuery(".vbo-ratesoverview-lostab-cont").hide();
3883 jQuery(".vbo-ratesoverview-caltab-cont").fadeIn();
3884 });
3885 if (window.location.hash == '#tabcal') {
3886 jQuery(".vbo-ratesoverview-tab-cal").trigger("click");
3887 }
3888 jQuery("body").on("click", ".vbo-ratesoverview-numnight", function() {
3889 var inpnight = jQuery(this).attr('id');
3890 if (jQuery('.vbo-ratesoverview-numnight').length > 1) {
3891 jQuery('#inp'+inpnight).remove();
3892 jQuery(this).remove();
3893 }
3894 });
3895 jQuery("body").on("dblclick", ".vbo-calcrates-rateblock", function() {
3896 if (jQuery(this).parent('.vbo-ratesoverview-calculation-response-room').find('.vbo-calcrates-rateblock').length < 2) {
3897 // remove the whole container as there is just one rate plan
3898 jQuery(this).parent('.vbo-ratesoverview-calculation-response-room').remove();
3899 } else {
3900 // remove only this rate plan
3901 jQuery(this).remove();
3902 }
3903 });
3904 jQuery('#vbo-addnumnight-act').click(function() {
3905 var setnights = jQuery('#vbo-addnumnight').val();
3906 if (parseInt(setnights) > 0) {
3907 var los_exists = false;
3908 jQuery('.vbo-ratesoverview-numnight').each(function() {
3909 if (parseInt(jQuery(this).text()) == parseInt(setnights)) {
3910 los_exists = true;
3911 }
3912 });
3913 if (!los_exists) {
3914 jQuery('.vbo-ratesoverview-numnight').last().after("<span class=\"vbo-ratesoverview-numnight\" id=\"numnights"+setnights+"\">"+setnights+"</span><input type=\"hidden\" name=\"nights_cal[]\" id=\"inpnumnights"+setnights+"\" value=\""+setnights+"\" />");
3915 } else {
3916 jQuery('#vbo-addnumnight').val((parseInt(setnights) + 1));
3917 }
3918 }
3919 });
3920 jQuery('#vbo-ratesoverview-calculate').click(function() {
3921 jQuery(this).text(Joomla.JText._('VBRATESOVWRATESCALCULATORCALCING')).prop('disabled', true);
3922 var checkindate = jQuery("#checkindate").val();
3923 if (!(checkindate.length > 0)) {
3924 checkindate = '<?php echo date('Y-m-d') ?>';
3925 jQuery("#checkindate").val(checkindate);
3926 }
3927 var nights = jQuery("#vbo-numnights").val();
3928 var adults = jQuery("#vbo-numadults").val();
3929 var children = jQuery("#vbo-numchildren").val();
3930 var idroom = jQuery("#roomselcalc").val();
3931 // always remove warning messages
3932 jQuery(".vbo-ratesoverview-calculation-response").find('.vbo-warning').remove();
3933 if (jQuery("#vbo-ratesoverview-calculation-response-room"+idroom).length) {
3934 // remove previous containers for this room
3935 jQuery("#vbo-ratesoverview-calculation-response-room"+idroom).remove();
3936 }
3937 if (!jQuery(".vbo-ratesoverview-calculation-response-room").length) {
3938 // if no rooms responses, empty the whole container
3939 jQuery('.vbo-ratesoverview-calculation-response').html('');
3940 }
3941
3942 VBOCore.doAjax(
3943 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=calc_rates'); ?>",
3944 {
3945 tmpl: "component",
3946 id_room: idroom,
3947 checkin: checkindate,
3948 num_nights: nights,
3949 num_adults: adults,
3950 num_children: children
3951 },
3952 (res) => {
3953 res = typeof res === 'string' ? JSON.parse(res) : res;
3954 res = res[0];
3955 if (res.indexOf('e4j.error') >= 0 ) {
3956 jQuery(".vbo-ratesoverview-calculation-response").html("<p class='vbo-warning'>" + res.replace("e4j.error.", "") + "</p>").fadeIn();
3957 } else {
3958 var titlecont = '<span class="vbo-ratesoverview-calculation-response-room-name">'+jQuery("#roomselcalc option:selected").text() + '</span> ' + checkindate + ' - ' + jQuery("#checkoutdate").val() + ', ' + nights + ' <?php echo addslashes(JText::translate('VBRATESOVWRATESCALCNUMNIGHTS')); ?>, ' + adults + ' <?php echo addslashes(JText::translate('VBRATESOVWRATESCALCNUMADULTS')); ?>';
3959 var newcont = '<div class="vbo-ratesoverview-calculation-response-room" id="vbo-ratesoverview-calculation-response-room'+idroom+'"><h4>'+titlecont+'</h4>'+res+'</div>';
3960 // check whether the content should be appended
3961 if (jQuery(".vbo-ratesoverview-calculation-response").find('.vbo-ratesoverview-calculation-response-room').length) {
3962 newcont = jQuery(".vbo-ratesoverview-calculation-response").html() + newcont;
3963 }
3964 //
3965 jQuery(".vbo-ratesoverview-calculation-response").html(newcont).fadeIn();
3966 // loop over every room response and pricing to append the book-now button for the page calendar
3967 var base_booknow_link_orig = jQuery('#vbo-base-booknow-link').attr('href');
3968 jQuery('.vbo-calcrates-rateblock').each(function(k, v) {
3969 var elem = jQuery(v);
3970 var base_booknow_link = base_booknow_link_orig;
3971 // remove existing button
3972 elem.find('.vbo-room-booknow-rct').remove();
3973 //
3974 var b_idprice = elem.attr('data-idprice');
3975 base_booknow_link = base_booknow_link.replace('idprice=', 'idprice=' + b_idprice);
3976 var b_idroom = elem.attr('data-idroom');
3977 base_booknow_link = base_booknow_link.replace('cid[]=', 'cid[]=' + b_idroom);
3978 var b_checkin = elem.attr('data-checkin');
3979 base_booknow_link = base_booknow_link.replace('checkin=', 'checkin=' + b_checkin);
3980 var b_checkout = elem.attr('data-checkout');
3981 base_booknow_link = base_booknow_link.replace('checkout=', 'checkout=' + b_checkout);
3982 var b_adults = elem.attr('data-adults');
3983 base_booknow_link = base_booknow_link.replace('adults=', 'adults=' + b_adults);
3984 var b_children = elem.attr('data-children');
3985 base_booknow_link = base_booknow_link.replace('children=', 'children=' + b_children);
3986 var booknow = '<a href="' + base_booknow_link + '" class="btn btn-primary vbo-room-booknow-rct" target="_blank">' + Joomla.JText._('VBO_BOOKNOW') + '</a>';
3987 elem.append(booknow);
3988 });
3989 //
3990 }
3991 jQuery('#vbo-ratesoverview-calculate').text(Joomla.JText._('VBRATESOVWRATESCALCULATORCALC')).prop('disabled', false);
3992 },
3993 (err) => {
3994 console.error(err);
3995 jQuery(".vbo-ratesoverview-calculation-response").fadeOut();
3996 jQuery('#vbo-ratesoverview-calculate').text(Joomla.JText._('VBRATESOVWRATESCALCULATORCALC')).prop('disabled', false);
3997 alert("Error Performing Ajax Request");
3998 }
3999 );
4000 });
4001
4002 /* Orphans Calculation */
4003 vboCheckOrphans();
4004 //
4005 });
4006
4007 function vboCheckOrphans() {
4008 /* Orphans Calculation - Start */
4009 var calc_method = '<?php echo VikBooking::orphansCalculation(); ?>';
4010 jQuery('table.vbratesoverviewtable').each(function() {
4011 var current_room = jQuery(this).attr('data-idroom');
4012 var orphans_pool = new Array;
4013 var avcells = jQuery(this).find('tr.vbo-roverviewtableavrow').find('td');
4014 var restrcells = jQuery(this).find('tr.vbo-roverviewtablerow-restrs').find('td');
4015 avcells.each(function(k, v) {
4016 if (!jQuery(v).attr('data-curunits')) {
4017 // continue
4018 return true;
4019 }
4020 var todayav = parseInt(jQuery(v).attr('data-curunits'));
4021 if (!restrcells.hasOwnProperty(k) || isNaN(todayav) || todayav < 1) {
4022 // continue, no restriction cell found or no availability for this day
4023 return true;
4024 }
4025 var todayminlos = parseInt(jQuery(restrcells[k]).find('.vbo-roverw-curminlos').text());
4026 if (isNaN(todayminlos) || todayminlos < 2) {
4027 // continue, no min los > 1 set for this day
4028 return true;
4029 }
4030 // check if any night after today, until min los, is fully booked
4031 var cell_queue = new Array;
4032 var hasorphans = false;
4033 var forward_count = 0;
4034 cell_queue.push(v);
4035 for (var i = 1; i < todayminlos; i++) {
4036 if (!avcells.hasOwnProperty((k + i))) {
4037 // break loop, no info for this day after
4038 break;
4039 }
4040 var tomorrowav = parseInt(jQuery(avcells[(k + i)]).attr('data-curunits'));
4041 if (isNaN(tomorrowav) || tomorrowav != 0) {
4042 // continue, availability found for tomorrow, we need a non available next-day
4043 continue;
4044 }
4045 // orphan found for this day
4046 hasorphans = true;
4047 forward_count = i;
4048 break;
4049 }
4050 // backward calculation method only if "prevnext".
4051 var backward_count = 0;
4052 for (var i = 1; i <= todayminlos; i++) {
4053 if (!avcells.hasOwnProperty((k - i))) {
4054 // break loop, no info for this prev day
4055 break;
4056 }
4057 var yesterdayav = parseInt(jQuery(avcells[(k - i)]).attr('data-curunits'));
4058 if (isNaN(yesterdayav) || yesterdayav != 0) {
4059 // increase free nights going backward
4060 backward_count++;
4061 }
4062 }
4063 if (calc_method == 'prevnext' && hasorphans && backward_count > 0 && (backward_count >= todayminlos || (backward_count + forward_count) >= todayminlos)) {
4064 // this should not be an orphan date because of enough free days back, or enough free days in between
4065 hasorphans = false;
4066 }
4067
4068 if (hasorphans && cell_queue.length) {
4069 for (var i in cell_queue) {
4070 if (cell_queue.hasOwnProperty(i)) {
4071 // push the cell
4072 orphans_pool.push(cell_queue[i]);
4073 }
4074 }
4075 }
4076 });
4077 if (orphans_pool.length) {
4078 var orphans_list = '';
4079 for (var i in orphans_pool) {
4080 if (!orphans_pool.hasOwnProperty(i)) {
4081 continue;
4082 }
4083 var idparts = jQuery(orphans_pool[i]).attr('id').split('-');
4084 orphans_list += '<div class="vbo-ratesoverview-orphan-dt" data-dt="'+idparts[1]+'-'+idparts[2]+'-'+idparts[3]+'-'+current_room+'">'+vbowdays[idparts[4]]+', '+vbomonths[(parseInt(idparts[2]) - 1)]+' '+idparts[1]+' '+idparts[3]+'</div>';
4085 }
4086 jQuery('#vbo-ratesoverview-orphans-list-'+current_room).html(orphans_list);
4087 jQuery('#vbo-ratesoverview-orphans-wrapper-'+current_room).fadeIn();
4088 }
4089 });
4090 /* Orphans Calculation - End */
4091 }
4092 </script>
4093
4094 <div class="vbo-ratesoverview-fests-helper" style="display: none;">
4095 <div class="vbo-ratesoverview-fests-wrap">
4096 <div class="vbo-overlay-fests-list"></div>
4097 <div class="vbo-overlay-fests-addnew" data-ymd="">
4098 <h4><?php echo JText::translate('VBOADDCUSTOMFESTTODAY'); ?></h4>
4099 <div class="vbo-overlay-fests-addnew-elem">
4100 <label for="vbo-newfest-name"><?php echo JText::translate('VBPVIEWPLACESONE'); ?></label>
4101 <input type="text" id="vbo-newfest-name" value="" />
4102 </div>
4103 <div class="vbo-overlay-fests-addnew-elem">
4104 <label for="vbo-newfest-descr"><?php echo JText::translate('VBPLACEDESCR'); ?></label>
4105 <textarea id="vbo-newfest-descr"></textarea>
4106 </div>
4107 </div>
4108 </div>
4109 </div>
4110