PluginProbe
Booking Calendar / 11.9
Booking Calendar v11.9
11.9 11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 All 205 releases
booking / includes / __js / client / cal / wpbc_cal.js

wpbc_cal.js in Booking Calendar 11.9, at includes/__js/client/cal/wpbc_cal.js

2,735 lines 104.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * =====================================================================================================================
3 * includes/__js/cal/wpbc_cal.js
4 * =====================================================================================================================
5 */
6
7 /**
8 * Order or child booking resources saved here: _wpbc.booking__get_param_value( resource_id,
9 * 'resources_id_arr__in_dates' ) [2,10,12,11]
10 */
11
12 /**
13 * How to check booked times on specific date: ?
14 *
15 _wpbc.bookings_in_calendar__get_for_date(2,'2023-08-21');
16
17 console.log(
18 _wpbc.bookings_in_calendar__get_for_date(2,'2023-08-21')[2].booked_time_slots.merged_seconds,
19 _wpbc.bookings_in_calendar__get_for_date(2,'2023-08-21')[10].booked_time_slots.merged_seconds,
20 _wpbc.bookings_in_calendar__get_for_date(2,'2023-08-21')[11].booked_time_slots.merged_seconds,
21 _wpbc.bookings_in_calendar__get_for_date(2,'2023-08-21')[12].booked_time_slots.merged_seconds
22 );
23 * OR
24 console.log(
25 _wpbc.bookings_in_calendar__get_for_date(2,'2023-08-21')[2].booked_time_slots.merged_readable,
26 _wpbc.bookings_in_calendar__get_for_date(2,'2023-08-21')[10].booked_time_slots.merged_readable,
27 _wpbc.bookings_in_calendar__get_for_date(2,'2023-08-21')[11].booked_time_slots.merged_readable,
28 _wpbc.bookings_in_calendar__get_for_date(2,'2023-08-21')[12].booked_time_slots.merged_readable
29 );
30 *
31 */
32
33 /**
34 * Days selection:
35 * wpbc_calendar__unselect_all_dates( resource_id );
36 *
37 * var resource_id = 1;
38 * Example 1: var num_selected_days = wpbc_auto_select_dates_in_calendar( resource_id, '2024-05-15',
39 * '2024-05-25' ); Example 2: var num_selected_days = wpbc_auto_select_dates_in_calendar( resource_id,
40 * ['2024-05-09','2024-05-19','2024-05-25'] );
41 *
42 */
43
44
45 /**
46 * C A L E N D A R ---------------------------------------------------------------------------------------------------
47 */
48
49
50 /**
51 * Show WPBC Calendar
52 *
53 * @param resource_id - resource ID
54 * @returns {boolean}
55 */
56 function wpbc_calendar_show( resource_id ){
57
58 // If no calendar HTML tag, then exit
59 if ( 0 === jQuery( '#calendar_booking' + resource_id ).length ){ return false; }
60
61 // If the calendar with the same Booking resource is activated already, then exit. But in Elementor the class can be stale, so verify instance.
62 if ( jQuery( '#calendar_booking' + resource_id ).hasClass( 'hasDatepick' ) ) {
63
64 var existing_inst = null;
65
66 try {
67 existing_inst = jQuery.datepick._getInst( jQuery( '#calendar_booking' + resource_id ).get( 0 ) );
68 } catch ( e ) {
69 existing_inst = null;
70 }
71
72 if ( existing_inst ) {
73 return false;
74 }
75
76 // Stale marker: remove and continue with init.
77 jQuery( '#calendar_booking' + resource_id ).removeClass( 'hasDatepick' );
78 }
79
80 wpbc_range_selection__prepare_guidance( resource_id );
81
82
83
84 // -----------------------------------------------------------------------------------------------------------------
85 // Days selection
86 // -----------------------------------------------------------------------------------------------------------------
87 var local__is_range_select = false;
88 var local__multi_days_select_num = 365; // multiple | fixed
89 if ( 'dynamic' === _wpbc.calendar__get_param_value( resource_id, 'days_select_mode' ) ){
90 local__is_range_select = true;
91 local__multi_days_select_num = 0;
92 }
93 if ( 'single' === _wpbc.calendar__get_param_value( resource_id, 'days_select_mode' ) ){
94 local__multi_days_select_num = 0;
95 }
96
97 // -----------------------------------------------------------------------------------------------------------------
98 // Min - Max days to scroll/show
99 // -----------------------------------------------------------------------------------------------------------------
100 var local__min_date = 0;
101 local__min_date = new Date( _wpbc.get_other_param( 'today_arr' )[ 0 ], (parseInt( _wpbc.get_other_param( 'today_arr' )[ 1 ] ) - 1), _wpbc.get_other_param( 'today_arr' )[ 2 ], 0, 0, 0 ); // FixIn: 9.9.0.17.
102 //console.log( local__min_date );
103 var local__max_date = _wpbc.calendar__get_param_value( resource_id, 'booking_max_monthes_in_calendar' );
104 //local__max_date = new Date(2024, 5, 28); It is here issue of not selectable dates, but some dates showing in calendar as available, but we can not select it.
105
106 //// Define last day in calendar (as a last day of month (and not date, which is related to actual 'Today' date).
107 //// E.g. if today is 2023-09-25, and we set 'Number of months to scroll' as 5 months, then last day will be 2024-02-29 and not the 2024-02-25.
108 // var cal_last_day_in_month = jQuery.datepick._determineDate( null, local__max_date, new Date() );
109 // cal_last_day_in_month = new Date( cal_last_day_in_month.getFullYear(), cal_last_day_in_month.getMonth() + 1, 0 );
110 // local__max_date = cal_last_day_in_month; // FixIn: 10.0.0.26.
111
112 // Get start / end dates from the Booking Calendar shortcode. Example: [booking calendar_dates_start='2026-01-01' calendar_dates_end='2026-12-31' resource_id=1] // FixIn: 10.13.1.4.
113 if ( false !== wpbc_calendar__get_dates_start( resource_id ) ) {
114 local__min_date = wpbc_calendar__get_dates_start( resource_id ); // E.g. - local__min_date = new Date( 2025, 0, 1 );
115 }
116 if ( false !== wpbc_calendar__get_dates_end( resource_id ) ) {
117 local__max_date = wpbc_calendar__get_dates_end( resource_id ); // E.g. - local__max_date = new Date( 2025, 11, 31 );
118 }
119
120 // In case we edit booking in past or have specific parameter in URL.
121 var wpbc_edit_booking_hash = _wpbc.get_other_param( 'this_page_booking_hash' );
122 var wpbc_is_edit_booking_context = ( 'undefined' !== typeof wpbc_edit_booking_hash ) && ( '' !== wpbc_edit_booking_hash );
123 var wpbc_allow_past_context = _wpbc.get_other_param( 'this_page_allow_past' );
124 var wpbc_is_allow_past_context = ( '1' === String( wpbc_allow_past_context ) ) || ( 1 === wpbc_allow_past_context ) || ( true === wpbc_allow_past_context );
125 var wpbc_allow_past_date_arr = _wpbc.get_other_param( 'this_page_allow_past_arr' );
126 var wpbc_is_add_booking_admin_page = ( location.href.indexOf( 'page=wpbc' ) != -1 ) && ( location.href.indexOf( 'tab=add-booking' ) != -1 );
127 if ( ( wpbc_is_add_booking_admin_page || wpbc_is_edit_booking_context || wpbc_is_allow_past_context )
128 && (
129 wpbc_is_edit_booking_context
130 || wpbc_is_allow_past_context
131 || ( location.href.indexOf('booking_hash') != -1 ) // Comment this line for ability to add booking in past days at Booking > Add booking page.
132 || ( location.href.indexOf('allow_past') != -1 ) // FixIn: 10.7.1.2.
133 )
134 ){
135 // local__min_date = null;
136 // FixIn: 10.14.1.4.
137 var wpbc_min_date_arr = ( wpbc_is_allow_past_context && wpbc_allow_past_date_arr && ( 5 <= wpbc_allow_past_date_arr.length ) ) ? wpbc_allow_past_date_arr : _wpbc.get_other_param( 'time_local_arr' );
138 local__min_date = new Date( wpbc_min_date_arr[0], ( parseInt( wpbc_min_date_arr[1] ) - 1), wpbc_min_date_arr[2], wpbc_min_date_arr[3], wpbc_min_date_arr[4], 0 );
139 local__max_date = null;
140 }
141
142 var local__start_weekday = _wpbc.calendar__get_param_value( resource_id, 'booking_start_day_weeek' );
143 var local__number_of_months = parseInt( _wpbc.calendar__get_param_value( resource_id, 'calendar_number_of_months' ) );
144
145 jQuery( '#calendar_booking' + resource_id ).text( '' ); // Remove all HTML in calendar tag
146 // -----------------------------------------------------------------------------------------------------------------
147 // Show calendar
148 // -----------------------------------------------------------------------------------------------------------------
149 jQuery('#calendar_booking'+ resource_id).datepick(
150 {
151 beforeSelectDay: function ( js_date, inst ) {
152 return 'function' === typeof wpbc__calendar__before_select_days__bs
153 ? wpbc__calendar__before_select_days__bs( js_date, resource_id, inst ) : true;
154 },
155 beforeShowDay: function ( js_date ){
156 return wpbc__calendar__apply_css_to_days( js_date, {'resource_id': resource_id}, this );
157 },
158 onSelect: function ( string_dates, js_dates_arr ){ /**
159 * string_dates = '23.08.2023 - 26.08.2023'
160 * | '23.08.2023 - 23.08.2023' |
161 * '19.09.2023, 24.08.2023, 30.09.2023'
162 * js_dates_arr = range: [ Date (Aug 23 2023),
163 * Date (Aug 25 2023)] | multiple: [
164 * Date(Oct 24 2023), Date(Oct 20 2023), Date(Oct
165 * 16 2023) ]
166 */
167 return wpbc__calendar__on_select_days( string_dates, {'resource_id': resource_id}, this );
168 },
169 onHover: function ( string_date, js_date ){
170 return wpbc__calendar__on_hover_days( string_date, js_date, {'resource_id': resource_id}, this );
171 },
172 onChangeMonthYear: function ( year, real_month, js_date__1st_day_in_month ){ },
173 showOn : 'both',
174 numberOfMonths: local__number_of_months,
175 stepMonths : 1,
176 // prevText : '&laquo;',
177 // nextText : '&raquo;',
178 prevText : '&lsaquo;',
179 nextText : '&rsaquo;',
180 dateFormat : 'dd.mm.yy',
181 changeMonth : false,
182 changeYear : false,
183 minDate : local__min_date,
184 maxDate : local__max_date, // '1Y',
185 // minDate: new Date(2020, 2, 1), maxDate: new Date(2020, 9, 31), // Ability to set any start and end date in calendar
186 showStatus : false,
187 multiSeparator : ', ',
188 closeAtTop : false,
189 firstDay : local__start_weekday,
190 gotoCurrent : false,
191 hideIfNoPrevNext: true,
192 multiSelect : local__multi_days_select_num,
193 rangeSelect : local__is_range_select,
194 // showWeeks: true,
195 useThemeRoller: false
196 }
197 );
198
199
200
201 // -----------------------------------------------------------------------------------------------------------------
202 // Clear today date highlighting
203 // -----------------------------------------------------------------------------------------------------------------
204 setTimeout( function (){ wpbc_calendars__clear_days_highlighting( resource_id ); }, 500 ); // FixIn: 7.1.2.8.
205
206 // -----------------------------------------------------------------------------------------------------------------
207 // Scroll calendar to specific month
208 // -----------------------------------------------------------------------------------------------------------------
209 var start_bk_month = _wpbc.calendar__get_param_value( resource_id, 'calendar_scroll_to' );
210 if ( false !== start_bk_month ){
211 wpbc_calendar__scroll_to( resource_id, start_bk_month[ 0 ], start_bk_month[ 1 ] );
212 }
213 }
214
215
216 /**
217 * Get booking statuses as array from the structured summary field, with fallback to the legacy string field.
218 *
219 * @param date_bookings_obj
220 * @returns {[]}
221 */
222 function wpbc_get_booking_statuses__as_arr( date_bookings_obj ){
223
224 if (
225 ( ! date_bookings_obj )
226 || ( 'undefined' === typeof (date_bookings_obj[ 'summary' ]) )
227 ){
228 return [];
229 }
230
231 if ( Array.isArray( date_bookings_obj[ 'summary' ][ 'status_for_bookings_arr' ] ) ){
232 return date_bookings_obj[ 'summary' ][ 'status_for_bookings_arr' ].filter( function ( booking_status ){
233 return '' !== booking_status;
234 } );
235 }
236
237 if ( ! date_bookings_obj[ 'summary' ][ 'status_for_bookings' ] ){
238 return [];
239 }
240
241 return date_bookings_obj[ 'summary' ][ 'status_for_bookings' ].toString().trim().split( /\s+/ ).filter( function ( booking_status ){
242 return '' !== booking_status;
243 } );
244 }
245
246
247 /**
248 * Check exact booking status in statuses array.
249 *
250 * @param {[]} booking_statuses_arr
251 * @param {string} booking_status
252 * @returns {boolean}
253 */
254 function wpbc_booking_statuses__has( booking_statuses_arr, booking_status ){
255 return booking_statuses_arr.indexOf( booking_status ) > -1;
256 }
257
258
259 /**
260 * Check booking status part, e.g. "pending" in "approved_pending".
261 *
262 * @param {[]} booking_statuses_arr
263 * @param {string} booking_status_part
264 * @returns {boolean}
265 */
266 function wpbc_booking_statuses__has_part( booking_statuses_arr, booking_status_part ){
267
268 for ( var i = 0; i < booking_statuses_arr.length; i++ ){
269 if ( booking_statuses_arr[ i ].split( '_' ).indexOf( booking_status_part ) > -1 ){
270 return true;
271 }
272 }
273
274 return false;
275 }
276
277
278 /**
279 * Apply optional, one-way date constraints to a native availability result.
280 *
281 * Listeners receive only a new constraint object. No booking, availability,
282 * season, or calendar parameter objects are exposed. Core availability stays
283 * authoritative because the returned state can only change from selectable to
284 * blocked; a listener cannot reopen a natively unavailable date.
285 *
286 * Event: wpbc_calendar__apply_date_constraints
287 *
288 * Constraint object:
289 * {
290 * resource_id: resource_id,
291 * sql_date: sql_date,
292 * is_blocked: false,
293 * css_classes: []
294 * }
295 *
296 * Listeners may set is_blocked to the boolean true and append individual,
297 * valid CSS class names to css_classes. Core-owned availability classes are
298 * ignored when supplied by listeners.
299 *
300 * @param {number|string} resource_id Booking resource ID.
301 * @param {string} sql_date Calendar date in YYYY-MM-DD format.
302 * @param {boolean} is_natively_selectable Whether core marked the date selectable.
303 * @param {string[]} native_css_classes Core-generated CSS classes.
304 * @returns {{is_selectable: boolean, css_classes: string[]}} Constrained result.
305 */
306 function wpbc_calendar__evaluate_date_constraints( resource_id, sql_date, is_natively_selectable, native_css_classes ) {
307
308 var date_constraint = {
309 'resource_id': resource_id,
310 'sql_date': sql_date,
311 'is_blocked': false,
312 'css_classes': []
313 };
314 var result_css_classes = Array.isArray( native_css_classes ) ? native_css_classes.slice( 0 ) : [];
315 var constraint_class_index;
316 var constraint_css_class;
317
318 jQuery( 'body' ).trigger( 'wpbc_calendar__apply_date_constraints', [ date_constraint ] );
319
320 if ( true === date_constraint.is_blocked ) {
321 for ( constraint_class_index = result_css_classes.length - 1; constraint_class_index >= 0; constraint_class_index-- ) {
322 if ( 'date_available' === result_css_classes[ constraint_class_index ] ) {
323 result_css_classes.splice( constraint_class_index, 1 );
324 }
325 }
326
327 if ( -1 === result_css_classes.indexOf( 'date_user_unavailable' ) ) {
328 result_css_classes.push( 'date_user_unavailable' );
329 }
330 }
331
332 if ( Array.isArray( date_constraint.css_classes ) ) {
333 for ( constraint_class_index = 0; constraint_class_index < date_constraint.css_classes.length; constraint_class_index++ ) {
334 if ( 'string' !== typeof date_constraint.css_classes[ constraint_class_index ] ) {
335 continue;
336 }
337
338 constraint_css_class = date_constraint.css_classes[ constraint_class_index ].trim();
339 if (
340 '' === constraint_css_class
341 || 'date_available' === constraint_css_class
342 || 'date_user_unavailable' === constraint_css_class
343 || ! /^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$/.test( constraint_css_class )
344 ) {
345 continue;
346 }
347
348 if ( -1 === result_css_classes.indexOf( constraint_css_class ) ) {
349 result_css_classes.push( constraint_css_class );
350 }
351 }
352 }
353
354 return {
355 'is_selectable': Boolean( is_natively_selectable ) && true !== date_constraint.is_blocked,
356 'css_classes': result_css_classes
357 };
358 }
359
360
361 /**
362 * Apply CSS to calendar date cells
363 *
364 * @param date - JavaScript Date Obj: Mon Dec 11 2023 00:00:00
365 * GMT+0200 (Eastern European Standard Time)
366 * @param calendar_params_arr - Calendar Settings Object: {
367 * "resource_id": 4
368 * }
369 * @param datepick_this - this of datepick Obj
370 * @returns {(*|string)[]|(boolean|string)[]} - [ {true -available | false - unavailable}, 'CSS classes for
371 * calendar day cell' ]
372 */
373 function wpbc__calendar__apply_css_to_days( date, calendar_params_arr, datepick_this ){
374
375 var today_date = new Date( _wpbc.get_other_param( 'today_arr' )[ 0 ], (parseInt( _wpbc.get_other_param( 'today_arr' )[ 1 ] ) - 1), _wpbc.get_other_param( 'today_arr' )[ 2 ], 0, 0, 0 ); // Today JS_Date_Obj.
376 var class_day = wpbc__get__td_class_date( date ); // '1-9-2023'
377 var sql_class_day = wpbc__get__sql_class_date( date ); // '2023-01-09'
378 var resource_id = ( 'undefined' !== typeof(calendar_params_arr[ 'resource_id' ]) ) ? calendar_params_arr[ 'resource_id' ] : '1'; // '1'
379
380 // Get Selected dates in calendar
381 var selected_dates_sql = wpbc_get__selected_dates_sql__as_arr( resource_id );
382
383 // Get Data --------------------------------------------------------------------------------------------------------
384 var date_bookings_obj = _wpbc.bookings_in_calendar__get_for_date( resource_id, sql_class_day );
385
386
387 // Array with CSS classes for date ---------------------------------------------------------------------------------
388 var css_classes__for_date = [];
389 css_classes__for_date.push( 'sql_date_' + sql_class_day ); // 'sql_date_2023-07-21'
390 css_classes__for_date.push( 'cal4date-' + class_day ); // 'cal4date-7-21-2023'
391 css_classes__for_date.push( 'wpbc_weekday_' + date.getDay() ); // 'wpbc_weekday_4'
392
393 // Define Selected Check In/Out dates in TD -----------------------------------------------------------------------
394 if (
395 ( selected_dates_sql.length )
396 //&& ( selected_dates_sql[ 0 ] !== selected_dates_sql[ (selected_dates_sql.length - 1) ] )
397 ){
398 if ( sql_class_day === selected_dates_sql[ 0 ] ){
399 css_classes__for_date.push( 'selected_check_in' );
400 css_classes__for_date.push( 'selected_check_in_out' );
401 }
402 if ( ( selected_dates_sql.length > 1 ) && ( sql_class_day === selected_dates_sql[ (selected_dates_sql.length - 1) ] ) ) {
403 css_classes__for_date.push( 'selected_check_out' );
404 css_classes__for_date.push( 'selected_check_in_out' );
405 }
406 }
407
408
409 var is_day_selectable = false;
410 var date_constraint_result;
411
412 // If something not defined, then this date closed --------------------------------------------------------------- // FixIn: 10.12.4.6.
413 if ( (false === date_bookings_obj) || ('undefined' === typeof (date_bookings_obj[resource_id])) ) {
414
415 css_classes__for_date.push( 'date_user_unavailable' );
416 date_constraint_result = wpbc_calendar__evaluate_date_constraints( resource_id, sql_class_day, is_day_selectable, css_classes__for_date );
417
418 return [ date_constraint_result.is_selectable, date_constraint_result.css_classes.join( ' ' ) ];
419 }
420
421
422 // -----------------------------------------------------------------------------------------------------------------
423 // date_bookings_obj - Defined. Dates can be selectable.
424 // -----------------------------------------------------------------------------------------------------------------
425 var booking_statuses_arr = wpbc_get_booking_statuses__as_arr( date_bookings_obj );
426
427 // -----------------------------------------------------------------------------------------------------------------
428 // Add season names to the day CSS classes -- it is required for correct work of conditional fields --------------
429 var season_names_arr = _wpbc.seasons__get_for_date( resource_id, sql_class_day );
430
431 for ( var season_key in season_names_arr ){
432
433 css_classes__for_date.push( season_names_arr[ season_key ] ); // 'wpdevbk_season_september_2023'
434 }
435 // -----------------------------------------------------------------------------------------------------------------
436
437
438 // Cost Rate -------------------------------------------------------------------------------------------------------
439 css_classes__for_date.push( 'rate_' + date_bookings_obj[ resource_id ][ 'date_cost_rate' ].toString().replace( /[\.\s]/g, '_' ) ); // 'rate_99_00' -> 99.00
440
441
442 if ( parseInt( date_bookings_obj[ 'day_availability' ] ) > 0 ){
443 is_day_selectable = true;
444 css_classes__for_date.push( 'date_available' );
445 css_classes__for_date.push( 'reserved_days_count' + parseInt( date_bookings_obj[ 'max_capacity' ] - date_bookings_obj[ 'day_availability' ] ) );
446 } else {
447 is_day_selectable = false;
448 css_classes__for_date.push( 'date_user_unavailable' );
449 }
450
451
452 switch ( date_bookings_obj[ 'summary']['status_for_day' ] ){
453
454 case 'available':
455 break;
456
457 case 'time_slots_booking':
458 css_classes__for_date.push( 'timespartly', 'times_clock' );
459 break;
460
461 case 'full_day_booking':
462 css_classes__for_date.push( 'full_day_booking' );
463 break;
464
465 case 'season_filter':
466 css_classes__for_date.push( 'date_user_unavailable', 'season_unavailable' );
467 date_bookings_obj[ 'summary']['status_for_bookings' ] = ''; // Reset booking status color for possible old bookings on this date
468 date_bookings_obj[ 'summary']['status_for_bookings_arr' ] = [];
469 booking_statuses_arr = [];
470 break;
471
472 case 'resource_availability':
473 css_classes__for_date.push( 'date_user_unavailable', 'resource_unavailable' );
474 date_bookings_obj[ 'summary']['status_for_bookings' ] = ''; // Reset booking status color for possible old bookings on this date
475 date_bookings_obj[ 'summary']['status_for_bookings_arr' ] = [];
476 booking_statuses_arr = [];
477 break;
478
479 case 'weekday_unavailable':
480 css_classes__for_date.push( 'date_user_unavailable', 'weekday_unavailable' );
481 date_bookings_obj[ 'summary']['status_for_bookings' ] = ''; // Reset booking status color for possible old bookings on this date
482 date_bookings_obj[ 'summary']['status_for_bookings_arr' ] = [];
483 booking_statuses_arr = [];
484 break;
485
486 case 'from_today_unavailable':
487 css_classes__for_date.push( 'date_user_unavailable', 'from_today_unavailable' );
488 date_bookings_obj[ 'summary']['status_for_bookings' ] = ''; // Reset booking status color for possible old bookings on this date
489 date_bookings_obj[ 'summary']['status_for_bookings_arr' ] = [];
490 booking_statuses_arr = [];
491 break;
492
493 case 'limit_available_from_today':
494 css_classes__for_date.push( 'date_user_unavailable', 'limit_available_from_today' );
495 date_bookings_obj[ 'summary']['status_for_bookings' ] = ''; // Reset booking status color for possible old bookings on this date
496 date_bookings_obj[ 'summary']['status_for_bookings_arr' ] = [];
497 booking_statuses_arr = [];
498 break;
499
500 case 'change_over':
501 /*
502 *
503 // check_out_time_date2approve check_in_time_date2approve
504 // check_out_time_date2approve check_in_time_date_approved
505 // check_in_time_date2approve check_out_time_date_approved
506 // check_out_time_date_approved check_in_time_date_approved
507 */
508
509 css_classes__for_date.push( 'timespartly', 'check_in_time', 'check_out_time' );
510 // FixIn: 10.0.0.2.
511 if ( wpbc_booking_statuses__has( booking_statuses_arr, 'approved_pending' ) ){
512 css_classes__for_date.push( 'check_out_time_date_approved', 'check_in_time_date2approve' );
513 }
514 if ( wpbc_booking_statuses__has( booking_statuses_arr, 'pending_approved' ) ){
515 css_classes__for_date.push( 'check_out_time_date2approve', 'check_in_time_date_approved' );
516 }
517 break;
518
519 case 'check_in':
520 css_classes__for_date.push( 'timespartly', 'check_in_time' );
521
522 // FixIn: 9.9.0.33.
523 if ( wpbc_booking_statuses__has_part( booking_statuses_arr, 'pending' ) ){
524 css_classes__for_date.push( 'check_in_time_date2approve' );
525 } else if ( wpbc_booking_statuses__has_part( booking_statuses_arr, 'approved' ) ){
526 css_classes__for_date.push( 'check_in_time_date_approved' );
527 }
528 break;
529
530 case 'check_out':
531 css_classes__for_date.push( 'timespartly', 'check_out_time' );
532
533 // FixIn: 9.9.0.33.
534 if ( wpbc_booking_statuses__has_part( booking_statuses_arr, 'pending' ) ){
535 css_classes__for_date.push( 'check_out_time_date2approve' );
536 } else if ( wpbc_booking_statuses__has_part( booking_statuses_arr, 'approved' ) ){
537 css_classes__for_date.push( 'check_out_time_date_approved' );
538 }
539 break;
540
541 default:
542 // mixed statuses: 'change_over check_out' .... variations.... check more in function wpbc_get_availability_per_days_arr()
543 date_bookings_obj[ 'summary']['status_for_day' ] = 'available';
544 }
545
546
547
548 if ( 'available' != date_bookings_obj[ 'summary']['status_for_day' ] ){
549
550 var is_set_pending_days_selectable = _wpbc.calendar__get_param_value( resource_id, 'pending_days_selectable' ); // set pending days selectable // FixIn: 8.6.1.18.
551
552 if ( wpbc_booking_statuses__has( booking_statuses_arr, 'pending' ) ){
553 css_classes__for_date.push( 'date2approve' );
554 is_day_selectable = (is_day_selectable) ? true : is_set_pending_days_selectable;
555 }
556 if ( wpbc_booking_statuses__has( booking_statuses_arr, 'approved' ) ){
557 css_classes__for_date.push( 'date_approved' );
558 }
559 if ( wpbc_booking_statuses__has( booking_statuses_arr, 'pending_pending' ) ){
560 css_classes__for_date.push( 'check_out_time_date2approve', 'check_in_time_date2approve' );
561 is_day_selectable = (is_day_selectable) ? true : is_set_pending_days_selectable;
562 }
563 if ( wpbc_booking_statuses__has( booking_statuses_arr, 'pending_approved' ) ){
564 css_classes__for_date.push( 'check_out_time_date2approve', 'check_in_time_date_approved' );
565 is_day_selectable = (is_day_selectable) ? true : is_set_pending_days_selectable;
566 }
567 if ( wpbc_booking_statuses__has( booking_statuses_arr, 'approved_pending' ) ){
568 css_classes__for_date.push( 'check_out_time_date_approved', 'check_in_time_date2approve' );
569 is_day_selectable = (is_day_selectable) ? true : is_set_pending_days_selectable;
570 }
571 if ( wpbc_booking_statuses__has( booking_statuses_arr, 'approved_approved' ) ){
572 css_classes__for_date.push( 'check_out_time_date_approved', 'check_in_time_date_approved' );
573 }
574 }
575
576 date_constraint_result = wpbc_calendar__evaluate_date_constraints( resource_id, sql_class_day, is_day_selectable, css_classes__for_date );
577
578 return [ date_constraint_result.is_selectable, date_constraint_result.css_classes.join( ' ' ) ];
579 }
580
581
582 /**
583 * Mouseover calendar date cells
584 *
585 * @param string_date
586 * @param date - JavaScript Date Obj: Mon Dec 11 2023 00:00:00
587 * GMT+0200 (Eastern European Standard Time)
588 * @param calendar_params_arr - Calendar Settings Object: {
589 * "resource_id": 4
590 * }
591 * @param datepick_this - this of datepick Obj
592 * @returns {boolean}
593 */
594 function wpbc__calendar__on_hover_days( string_date, date, calendar_params_arr, datepick_this ) {
595
596 if ( null === date ) {
597 wpbc_calendars__clear_days_highlighting( ('undefined' !== typeof (calendar_params_arr[ 'resource_id' ])) ? calendar_params_arr[ 'resource_id' ] : '1' ); // FixIn: 10.5.2.4.
598 return false;
599 }
600
601 var class_day = wpbc__get__td_class_date( date ); // '1-9-2023'
602 var sql_class_day = wpbc__get__sql_class_date( date ); // '2023-01-09'
603 var resource_id = ( 'undefined' !== typeof(calendar_params_arr[ 'resource_id' ]) ) ? calendar_params_arr[ 'resource_id' ] : '1'; // '1'
604
605 // Get Data --------------------------------------------------------------------------------------------------------
606 var date_booking_obj = _wpbc.bookings_in_calendar__get_for_date( resource_id, sql_class_day ); // {...}
607
608 if ( ! date_booking_obj ){ return false; }
609
610
611 // T o o l t i p s -------------------------------------------------------------------------------------------------
612 var tooltip_text = '';
613 if ( date_booking_obj[ 'summary']['tooltip_availability' ].length > 0 ){
614 tooltip_text += date_booking_obj[ 'summary']['tooltip_availability' ];
615 }
616 if ( date_booking_obj[ 'summary']['tooltip_day_cost' ].length > 0 ){
617 tooltip_text += date_booking_obj[ 'summary']['tooltip_day_cost' ];
618 }
619 if ( date_booking_obj[ 'summary']['tooltip_times' ].length > 0 ){
620 tooltip_text += date_booking_obj[ 'summary']['tooltip_times' ];
621 }
622 if ( date_booking_obj[ 'summary']['tooltip_booking_details' ].length > 0 ){
623 tooltip_text += date_booking_obj[ 'summary']['tooltip_booking_details' ];
624 }
625 wpbc_set_tooltip___for__calendar_date( tooltip_text, resource_id, class_day );
626
627
628
629 // U n h o v e r i n g in UNSELECTABLE_CALENDAR ------------------------------------------------------------
630 var is_unselectable_calendar = ( jQuery( '#calendar_booking_unselectable' + resource_id ).length > 0); // FixIn: 8.0.1.2.
631 var is_booking_form_exist = ( jQuery( '#booking_form_div' + resource_id ).length > 0 );
632 var is_add_booking_modal_calendar = ( jQuery( '#calendar_booking' + resource_id ).closest( '#wpbc_modal__add_booking__section' ).length > 0 );
633 var is_admin_calendar_preview = ( jQuery( '#calendar_booking' + resource_id ).closest( '[data-wpbc-admin-calendar-preview="1"]' ).length > 0 );
634
635 if ( ( is_unselectable_calendar ) && ( ! is_booking_form_exist ) ){
636
637 /**
638 * Un Hover all dates in calendar (without the booking form), if only Availability Calendar here and we do
639 * not insert Booking form by mistake.
640 */
641
642 wpbc_calendars__clear_days_highlighting( resource_id ); // Clear days highlighting
643
644 var css_of_calendar = '.wpbc_only_calendar #calendar_booking' + resource_id;
645 jQuery( css_of_calendar + ' .datepick-days-cell, '
646 + css_of_calendar + ' .datepick-days-cell a' ).css( 'cursor', 'default' ); // Set cursor to Default
647 return false;
648 }
649
650
651
652 // D a y s H o v e r i n g ------------------------------------------------------------------------------------
653 if (
654 ( location.href.indexOf( 'page=wpbc' ) == -1 )
655 || ( ( location.href.indexOf( 'page=wpbc' ) > 0 ) && ( location.href.indexOf( 'tab=add-booking' ) > 0 ) )
656 || ( is_add_booking_modal_calendar )
657 || ( is_admin_calendar_preview )
658 || ( location.href.indexOf( 'page=wpbc-setup' ) > 0 )
659 || ( location.href.indexOf( 'page=wpbc-availability' ) > 0 )
660 || ( ( location.href.indexOf( 'page=wpbc-settings' ) > 0 ) &&
661 ( location.href.indexOf( 'tab=builder_booking_form' ) > 0 )
662 )
663 ){
664 // The same as dates selection, but for days hovering
665
666 if ( 'function' == typeof( wpbc__calendar__do_days_highlight__bs ) ){
667 wpbc__calendar__do_days_highlight__bs( sql_class_day, date, resource_id );
668 } else if ( 'dynamic' === _wpbc.calendar__get_param_value( resource_id, 'days_select_mode' ) ) {
669 wpbc__calendar__do_days_highlight__unrestricted_range( sql_class_day, date, resource_id );
670 }
671 }
672
673 }
674
675
676 /**
677 * Highlight an unrestricted proposed range after the first click when Business Small is not loaded.
678 *
679 * @param {string} sql_class_day Hovered date in SQL format.
680 * @param {Date} date Hovered date.
681 * @param {number|string} resource_id Booking resource ID.
682 * @returns {boolean} True when the complete proposed range is available and highlighted.
683 */
684 function wpbc__calendar__do_days_highlight__unrestricted_range( sql_class_day, date, resource_id ){
685
686 var inst = wpbc_calendar__get_inst( resource_id );
687 var range_start;
688 var range_end;
689 var working_date;
690 var td_overs = [];
691
692 wpbc_calendars__clear_days_highlighting( resource_id );
693
694 if (
695 null === inst
696 || 'dynamic' !== _wpbc.calendar__get_param_value( resource_id, 'days_select_mode' )
697 ) {
698 return false;
699 }
700
701 // Before a range starts, or after it is complete, preview the hovered date as the possible first day.
702 if ( 1 !== inst.dates.length || ! inst.dates[ 0 ] ) {
703 if ( ! wpbc_is_this_day_selectable( resource_id, sql_class_day ) ) {
704 return false;
705 }
706
707 jQuery( '#calendar_booking' + resource_id + ' .cal4date-' + wpbc__get__td_class_date( date ) )
708 .addClass( 'datepick-days-cell-over' );
709 return true;
710 }
711
712 range_start = new Date( inst.dates[ 0 ].getFullYear(), inst.dates[ 0 ].getMonth(), inst.dates[ 0 ].getDate(), 0, 0, 0 );
713 range_end = new Date( date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0 );
714
715 // Datepick only permits a check-out date on/after the first click. Do not preview a backward range.
716 if ( range_end.getTime() < range_start.getTime() ) {
717 return false;
718 }
719
720 working_date = new Date( range_start.getTime() );
721 while ( working_date.getTime() <= range_end.getTime() ) {
722 sql_class_day = wpbc__get__sql_class_date( working_date );
723 if ( ! wpbc_is_this_day_selectable( resource_id, sql_class_day ) ) {
724 return false;
725 }
726
727 td_overs.push( '#calendar_booking' + resource_id + ' .cal4date-' + wpbc__get__td_class_date( working_date ) );
728 working_date.setDate( working_date.getDate() + 1 );
729 }
730
731 if ( td_overs.length ) {
732 jQuery( td_overs.join( ',' ) ).addClass( 'datepick-days-cell-over' );
733 }
734
735 return true;
736 }
737
738
739 /**
740 * Complete an unrestricted two-click range when the Business Small range engine is not loaded.
741 * The hidden booking field must contain every date because Free time/hint handlers consume a comma-separated list.
742 *
743 * @param {string} selected_dates Datepick's endpoint string.
744 * @param {number|string} resource_id Booking resource ID.
745 * @returns {boolean} True after a complete valid range, false while incomplete or invalid.
746 */
747 function wpbc__calendar__do_days_select__unrestricted_range( selected_dates, resource_id ){
748
749 var inst = wpbc_calendar__get_inst( resource_id );
750 var range_start;
751 var range_end;
752 var current_date;
753 var selected_range = [];
754
755 if ( null === inst ) {
756 return false;
757 }
758
759 // Datepick writes "start - start" after click one. Keep its internal endpoint, but do not expose a submittable range yet.
760 if ( true === inst.stayOpen || inst.dates.length < 2 || ! inst.dates[ 0 ] || ! inst.dates[ 1 ] ) {
761 jQuery( '#date_booking' + resource_id ).val( '' );
762 return false;
763 }
764
765 range_start = new Date( inst.dates[ 0 ].getFullYear(), inst.dates[ 0 ].getMonth(), inst.dates[ 0 ].getDate(), 0, 0, 0 );
766 range_end = new Date( inst.dates[ 1 ].getFullYear(), inst.dates[ 1 ].getMonth(), inst.dates[ 1 ].getDate(), 0, 0, 0 );
767
768 // Defensive normalization. Datepick normally prevents an end date before the first endpoint.
769 if ( range_end.getTime() < range_start.getTime() ) {
770 current_date = range_start;
771 range_start = range_end;
772 range_end = current_date;
773 }
774
775 current_date = new Date( range_start.getTime() );
776 while ( current_date.getTime() <= range_end.getTime() ) {
777 if ( ! wpbc_is_this_day_selectable( resource_id, wpbc__get__sql_class_date( current_date ) ) ) {
778 wpbc_calendar__unselect_all_dates( resource_id );
779 if ( 'function' === typeof ( wpbc_front_end__show_message__warning ) ) {
780 wpbc_front_end__show_message__warning(
781 '#booking_form_div' + resource_id + ' .bk_calendar_frame',
782 _wpbc.get_message( 'message_range_selection_has_unavailable_dates' ),
783 'text'
784 );
785 }
786 return false;
787 }
788
789 selected_range.push( jQuery.datepick._formatDate( inst, current_date ) );
790 current_date.setDate( current_date.getDate() + 1 );
791 }
792
793 jQuery( '#date_booking' + resource_id ).val( selected_range.join( ', ' ) );
794 return true;
795 }
796
797
798 /**
799 * Prepare a collapsed range-selection guidance note for one calendar.
800 * Keeping one reusable node avoids repeated insertion while inactive guidance occupies no layout space.
801 *
802 * @param {number|string} resource_id Booking resource ID.
803 * @returns {jQuery} The prepared note, or an empty collection when guidance is unavailable.
804 */
805 function wpbc_range_selection__prepare_guidance( resource_id ){
806
807 var is_enabled = _wpbc.calendar__get_param_value( resource_id, 'range_selection_guidance_is_enabled' );
808 var $booking_form = jQuery( '#booking_form_div' + resource_id );
809 var $calendar_frame;
810 var $note = jQuery( '#wpbc_range_selection_guidance' + resource_id );
811
812 if (
813 false === is_enabled
814 || 0 === is_enabled
815 || '0' === is_enabled
816 || 'off' === String( is_enabled ).toLowerCase()
817 || 'false' === String( is_enabled ).toLowerCase()
818 || 'dynamic' !== _wpbc.calendar__get_param_value( resource_id, 'days_select_mode' )
819 || 0 === $booking_form.length
820 ) {
821 $note.remove();
822 return jQuery();
823 }
824
825 if ( 0 < $note.length ) {
826 $note.stop( true, true ).hide().attr( 'aria-hidden', 'true' );
827 return $note;
828 }
829
830 $calendar_frame = $booking_form.find( '.block_hints.datepick' ).first();
831 if ( 0 === $calendar_frame.length ) {
832 $calendar_frame = $booking_form.find( '.wpbc_cal_powered_by' ).first();
833 if ( 0 === $calendar_frame.length ) {
834 $calendar_frame = $booking_form.find( '.bk_calendar_frame' ).first();
835 if ( 0 === $calendar_frame.length ) {
836 return jQuery();
837 }
838 }
839 }
840
841 $note = jQuery( '<div>', {
842 'id': 'wpbc_range_selection_guidance' + resource_id,
843 'class': 'wpbc_range_selection_guidance wpbc_front_end__message wpbc_fe_message_info',
844 'role': 'status',
845 'aria-live': 'polite',
846 'aria-hidden': 'true',
847 'style': 'display: none;'
848 } );
849 $note.append( jQuery( '<i>', {
850 'class': 'menu_icon icon-1x wpbc_icn_info_outline',
851 'aria-hidden': 'true'
852 } ) );
853 $note.append( jQuery( '<span>' ).text( _wpbc.get_message( 'message_range_selection_click_last_date' ) ) );
854 $calendar_frame.after( $note );
855
856 return $note;
857 }
858
859
860 /**
861 * Get the range-guidance animation duration for the current motion preference.
862 *
863 * @returns {number} Animation duration in milliseconds, or zero when reduced motion is preferred.
864 */
865 function wpbc_range_selection__get_guidance_animation_duration(){
866
867 if (
868 'function' === typeof window.matchMedia
869 && window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches
870 ) {
871 return 0;
872 }
873
874 return 160;
875 }
876
877
878 /**
879 * Collapse the range-selection guidance so it does not reserve empty layout space.
880 *
881 * @param {number|string} resource_id Booking resource ID.
882 */
883 function wpbc_range_selection__hide_guidance( resource_id ){
884
885 jQuery( '#wpbc_range_selection_guidance' + resource_id )
886 .attr( 'aria-hidden', 'true' )
887 .stop( true, true )
888 .slideUp( wpbc_range_selection__get_guidance_animation_duration() );
889 }
890
891
892 /**
893 * Show the prepared informational note after the first range click.
894 * The calendar parameter can be set to false by PHP or JavaScript to suppress this UI per resource.
895 *
896 * @param {number|string} resource_id Booking resource ID.
897 * @returns {boolean} True when the note was shown.
898 */
899 function wpbc_range_selection__show_guidance( resource_id ){
900
901 var $note = wpbc_range_selection__prepare_guidance( resource_id );
902
903 if ( 0 === $note.length ) {
904 return false;
905 }
906
907 $note
908 .stop( true, true )
909 .slideDown( wpbc_range_selection__get_guidance_animation_duration() )
910 .removeAttr( 'aria-hidden' );
911 $note.find( 'span' ).text( _wpbc.get_message( 'message_range_selection_click_last_date' ) );
912
913 return true;
914 }
915
916
917 /**
918 * Synchronize the guidance with Datepick's two-click range state.
919 *
920 * @param {number|string} resource_id Booking resource ID.
921 */
922 function wpbc_range_selection__sync_guidance( resource_id ){
923
924 var inst = wpbc_calendar__get_inst( resource_id );
925
926 if (
927 null !== inst
928 && 'dynamic' === _wpbc.calendar__get_param_value( resource_id, 'days_select_mode' )
929 && true === inst.stayOpen
930 && 1 === inst.dates.length
931 && inst.dates[ 0 ]
932 ) {
933 wpbc_range_selection__show_guidance( resource_id );
934 return;
935 }
936
937 wpbc_range_selection__hide_guidance( resource_id );
938 }
939
940
941 /**
942 * Select calendar date cells
943 *
944 * @param date - JavaScript Date Obj: Mon Dec 11 2023 00:00:00
945 * GMT+0200 (Eastern European Standard Time)
946 * @param calendar_params_arr - Calendar Settings Object: {
947 * "resource_id": 4
948 * }
949 * @param datepick_this - this of datepick Obj
950 *
951 */
952 function wpbc__calendar__on_select_days( date, calendar_params_arr, datepick_this ){
953
954 var resource_id = ( 'undefined' !== typeof(calendar_params_arr[ 'resource_id' ]) ) ? calendar_params_arr[ 'resource_id' ] : '1'; // '1'
955
956 // Set unselectable, if only Availability Calendar here (and we do not insert Booking form by mistake).
957 var is_unselectable_calendar = ( jQuery( '#calendar_booking_unselectable' + resource_id ).length > 0); // FixIn: 8.0.1.2.
958 var is_booking_form_exist = ( jQuery( '#booking_form_div' + resource_id ).length > 0 );
959 if ( ( is_unselectable_calendar ) && ( ! is_booking_form_exist ) ){
960 wpbc_calendar__unselect_all_dates( resource_id ); // Unselect Dates
961 jQuery('.wpbc_only_calendar .popover_calendar_hover').remove(); // Hide all opened popovers
962 return false;
963 }
964
965 jQuery( '#date_booking' + resource_id ).val( date ); // Add selected dates to hidden textarea
966
967
968 if ( 'function' === typeof (wpbc__calendar__do_days_select__bs) ) {
969 wpbc__calendar__do_days_select__bs( date, resource_id );
970 } else if ( 'dynamic' === _wpbc.calendar__get_param_value( resource_id, 'days_select_mode' ) ) {
971 wpbc__calendar__do_days_select__unrestricted_range( date, resource_id );
972 }
973
974 wpbc_range_selection__sync_guidance( resource_id );
975
976 wpbc_disable_time_fields_in_booking_form( resource_id );
977
978 // Hook -- trigger day selection -----------------------------------------------------------------------------------
979 var mouse_clicked_dates = date; // Can be: "05.10.2023 - 07.10.2023" | "10.10.2023 - 10.10.2023" |
980 var all_selected_dates_arr = wpbc_get__selected_dates_sql__as_arr( resource_id ); // Can be: [ "2023-10-05", "2023-10-06", "2023-10-07", … ]
981 jQuery( ".booking_form_div" ).trigger( "date_selected", [ resource_id, mouse_clicked_dates, all_selected_dates_arr ] );
982 }
983
984 // Mark middle selected dates with 0.5 opacity // FixIn: 10.3.0.9.
985 jQuery( document ).ready( function (){
986 jQuery( ".booking_form_div" ).on( 'date_selected', function ( event, resource_id, date ){
987 if (
988 ( 'fixed' === _wpbc.calendar__get_param_value( resource_id, 'days_select_mode' ))
989 || ('dynamic' === _wpbc.calendar__get_param_value( resource_id, 'days_select_mode' ))
990 ){
991 var closed_timer = setTimeout( function (){
992 var middle_days_opacity = _wpbc.get_other_param( 'calendars__days_selection__middle_days_opacity' );
993 jQuery( '#calendar_booking' + resource_id + ' .datepick-current-day' ).not( ".selected_check_in_out" ).css( 'opacity', middle_days_opacity );
994 }, 10 );
995 }
996 } );
997 } );
998
999
1000 /**
1001 * -- T i m e F i e l d s start --------------------------------------------------------------------------
1002 */
1003
1004 /**
1005 * Disable time slots in booking form depend on selected dates and booked dates/times
1006 *
1007 * @param resource_id
1008 */
1009 function wpbc_disable_time_fields_in_booking_form( resource_id ){
1010
1011 /**
1012 * 1. Get all time fields in the booking form as array of objects
1013 * [
1014 * { jquery_option: jQuery_Object {}
1015 * name: 'rangetime2[]'
1016 * times_as_seconds: [ 21600, 23400 ]
1017 * value_option_24h: '06:00 - 06:30'
1018 * }
1019 * ...
1020 * { jquery_option: jQuery_Object {}
1021 * name: 'starttime2[]'
1022 * times_as_seconds: [ 21600 ]
1023 * value_option_24h: '06:00'
1024 * }
1025 * ]
1026 */
1027 var time_fields_obj_arr = wpbc_get__time_fields__in_booking_form__as_arr( resource_id );
1028
1029 // 2. Get all selected dates in SQL format like this [ "2023-08-23", "2023-08-24", "2023-08-25", ... ]
1030 var selected_dates_arr = wpbc_get__selected_dates_sql__as_arr( resource_id );
1031
1032 // 3. Get child booking resources or single booking resource that exist in dates
1033 var child_resources_arr = wpbc_clone_obj( _wpbc.booking__get_param_value( resource_id, 'resources_id_arr__in_dates' ) );
1034
1035 var sql_date;
1036 var child_resource_id;
1037 var merged_seconds;
1038 var time_fields_obj;
1039 var is_intersect;
1040 var is_check_in;
1041
1042 var today_time__real = new Date( _wpbc.get_other_param( 'time_local_arr' )[0], ( parseInt( _wpbc.get_other_param( 'time_local_arr' )[1] ) - 1), _wpbc.get_other_param( 'time_local_arr' )[2], _wpbc.get_other_param( 'time_local_arr' )[3], _wpbc.get_other_param( 'time_local_arr' )[4], 0 );
1043 var today_time__shift = new Date( _wpbc.get_other_param( 'today_arr' )[0], ( parseInt( _wpbc.get_other_param( 'today_arr' )[1] ) - 1), _wpbc.get_other_param( 'today_arr' )[2], _wpbc.get_other_param( 'today_arr' )[3], _wpbc.get_other_param( 'today_arr' )[4], 0 );
1044 var allow_past_context = _wpbc.get_other_param( 'this_page_allow_past' );
1045 var edit_booking_hash_context = _wpbc.get_other_param( 'this_page_booking_hash' );
1046 var is_allow_past_context =
1047 ( '1' === String( allow_past_context ) )
1048 || ( 1 === allow_past_context )
1049 || ( true === allow_past_context )
1050 || ( '' !== String( edit_booking_hash_context || '' ) )
1051 || ( location.href.indexOf( 'booking_hash' ) > -1 )
1052 || ( location.href.indexOf( 'allow_past' ) > -1 );
1053
1054 // 4. Loop all time Fields options // FixIn: 10.3.0.2.
1055 for ( let field_key = 0; field_key < time_fields_obj_arr.length; field_key++ ){
1056
1057 time_fields_obj_arr[ field_key ].disabled = 0; // By default, this time field is not disabled.
1058
1059 time_fields_obj = time_fields_obj_arr[ field_key ]; // { times_as_seconds: [ 21600, 23400 ], value_option_24h: '06:00 - 06:30', name: 'rangetime2[]', jquery_option: jQuery_Object {}}
1060
1061 // Loop all selected dates.
1062 for ( var i = 0; i < selected_dates_arr.length; i++ ) {
1063
1064 // Get Date: '2023-08-18'.
1065 sql_date = selected_dates_arr[i];
1066
1067 var is_time_in_past = is_allow_past_context ? false : wpbc_check_is_time_in_past( today_time__shift, sql_date, time_fields_obj );
1068 // Exception for 'End Time' field, when selected several dates. // FixIn: 10.14.1.5.
1069 if ( ( ! is_allow_past_context ) &&
1070 ('On' !== _wpbc.calendar__get_param_value( resource_id, 'booking_recurrent_time' )) &&
1071 (-1 !== time_fields_obj.name.indexOf( 'endtime' )) &&
1072 (selected_dates_arr.length > 1)
1073 ) {
1074 is_time_in_past = wpbc_check_is_time_in_past( today_time__shift, selected_dates_arr[(selected_dates_arr.length - 1)], time_fields_obj );
1075 }
1076 if ( is_time_in_past ) {
1077 // This time for selected date already in the past.
1078 time_fields_obj_arr[field_key].disabled = 1;
1079 break; // exist from Dates LOOP.
1080 }
1081 // FixIn: 9.9.0.31.
1082 if (
1083 ( 'Off' === _wpbc.calendar__get_param_value( resource_id, 'booking_recurrent_time' ) )
1084 && ( selected_dates_arr.length>1 )
1085 ){
1086 //TODO: skip some fields checking if it's start / end time for mulple dates selection mode.
1087 //TODO: we need to fix situation for entimes, when user select several dates, and in start time booked 00:00 - 15:00 , but systsme block untill 15:00 the end time as well, which is wrong, because it 2 or 3 dates selection and end date can be fullu available
1088
1089 if ( (0 == i) && (time_fields_obj[ 'name' ].indexOf( 'endtime' ) >= 0) ){
1090 break;
1091 }
1092 if ( ( (selected_dates_arr.length-1) == i ) && (time_fields_obj[ 'name' ].indexOf( 'starttime' ) >= 0) ){
1093 break;
1094 }
1095 }
1096
1097
1098
1099 var how_many_resources_intersected = 0;
1100 // Loop all resources ID
1101 // for ( var res_key in child_resources_arr ){ // FixIn: 10.3.0.2.
1102 if ( null === child_resources_arr ) {
1103 child_resources_arr = [];
1104 }
1105 for ( let res_key = 0; res_key < child_resources_arr.length; res_key++ ){
1106
1107 child_resource_id = child_resources_arr[ res_key ];
1108
1109 // _wpbc.bookings_in_calendar__get_for_date(2,'2023-08-21')[12].booked_time_slots.merged_seconds = [ "07:00:11 - 07:30:02", "10:00:11 - 00:00:00" ]
1110 // _wpbc.bookings_in_calendar__get_for_date(2,'2023-08-21')[2].booked_time_slots.merged_seconds = [ [ 25211, 27002 ], [ 36011, 86400 ] ]
1111
1112 if ( false !== _wpbc.bookings_in_calendar__get_for_date( resource_id, sql_date ) ){
1113 merged_seconds = _wpbc.bookings_in_calendar__get_for_date( resource_id, sql_date )[ child_resource_id ].booked_time_slots.merged_seconds; // [ [ 25211, 27002 ], [ 36011, 86400 ] ]
1114 } else {
1115 merged_seconds = [];
1116 }
1117 if ( time_fields_obj.times_as_seconds.length > 1 ){
1118 is_intersect = wpbc_is_intersect__range_time_interval( [
1119 [
1120 ( parseInt( time_fields_obj.times_as_seconds[0] ) + 20 ),
1121 ( parseInt( time_fields_obj.times_as_seconds[1] ) - 20 )
1122 ]
1123 ]
1124 , merged_seconds );
1125 } else {
1126 is_check_in = (-1 !== time_fields_obj.name.indexOf( 'start' ));
1127 is_intersect = wpbc_is_intersect__one_time_interval(
1128 ( ( is_check_in )
1129 ? parseInt( time_fields_obj.times_as_seconds ) + 20
1130 : parseInt( time_fields_obj.times_as_seconds ) - 20
1131 )
1132 , merged_seconds );
1133 }
1134 if (is_intersect){
1135 how_many_resources_intersected++; // Increase
1136 }
1137
1138 }
1139
1140 if ( child_resources_arr.length == how_many_resources_intersected ) {
1141 // All resources intersected, then it's means that this time-slot or time must be Disabled, and we can exist from selected_dates_arr LOOP
1142
1143 time_fields_obj_arr[ field_key ].disabled = 1;
1144 break; // exist from Dates LOOP
1145 }
1146 }
1147 }
1148
1149
1150 // 5. Now we can disable time slot in HTML by using ( field.disabled == 1 ) property
1151 wpbc__html__time_field_options__set_disabled( time_fields_obj_arr );
1152
1153 jQuery( ".booking_form_div" ).trigger( 'wpbc_hook_timeslots_disabled', [resource_id, selected_dates_arr] ); // Trigger hook on disabling timeslots. Usage: jQuery( ".booking_form_div" ).on( 'wpbc_hook_timeslots_disabled', function ( event, bk_type, all_dates ){ ... } ); // FixIn: 8.7.11.9.
1154 }
1155
1156
1157 /**
1158 * Check if specific time(-slot) already in the past for selected date
1159 *
1160 * @param js_current_time_to_check - JS Date
1161 * @param sql_date - '2025-01-26'
1162 * @param time_fields_obj - Object
1163 * @returns {boolean}
1164 */
1165 function wpbc_check_is_time_in_past( js_current_time_to_check, sql_date, time_fields_obj ) {
1166
1167 // FixIn: 10.9.6.4
1168 var sql_date_arr = sql_date.split( '-' );
1169 var sql_date__midnight = new Date( parseInt( sql_date_arr[0] ), ( parseInt( sql_date_arr[1] ) - 1 ), parseInt( sql_date_arr[2] ), 0, 0, 0 );
1170 var sql_date__midnight_miliseconds = sql_date__midnight.getTime();
1171
1172 var is_intersect = false;
1173
1174 if ( time_fields_obj.times_as_seconds.length > 1 ) {
1175
1176 if ( js_current_time_to_check.getTime() > (sql_date__midnight_miliseconds + (parseInt( time_fields_obj.times_as_seconds[0] ) + 20) * 1000) ) {
1177 is_intersect = true;
1178 }
1179 if ( js_current_time_to_check.getTime() > (sql_date__midnight_miliseconds + (parseInt( time_fields_obj.times_as_seconds[1] ) - 20) * 1000) ) {
1180 is_intersect = true;
1181 }
1182
1183 } else {
1184 var is_check_in = (-1 !== time_fields_obj.name.indexOf( 'start' ));
1185
1186 var times_as_seconds_check = (is_check_in) ? parseInt( time_fields_obj.times_as_seconds ) + 20 : parseInt( time_fields_obj.times_as_seconds ) - 20;
1187
1188 times_as_seconds_check = sql_date__midnight_miliseconds + times_as_seconds_check * 1000;
1189
1190 if ( js_current_time_to_check.getTime() > times_as_seconds_check ) {
1191 is_intersect = true;
1192 }
1193 }
1194
1195 return is_intersect;
1196 }
1197
1198 /**
1199 * Is number inside /intersect of array of intervals ?
1200 *
1201 * @param time_A - 25800
1202 * @param time_interval_B - [ [ 25211, 27002 ], [ 36011, 86400 ] ]
1203 * @returns {boolean}
1204 */
1205 function wpbc_is_intersect__one_time_interval( time_A, time_interval_B ){
1206
1207 for ( var j = 0; j < time_interval_B.length; j++ ){
1208
1209 if ( (parseInt( time_A ) > parseInt( time_interval_B[ j ][ 0 ] )) && (parseInt( time_A ) < parseInt( time_interval_B[ j ][ 1 ] )) ){
1210 return true
1211 }
1212
1213 // if ( ( parseInt( time_A ) == parseInt( time_interval_B[ j ][ 0 ] ) ) || ( parseInt( time_A ) == parseInt( time_interval_B[ j ][ 1 ] ) ) ) {
1214 // // Time A just at the border of interval
1215 // }
1216 }
1217
1218 return false;
1219 }
1220
1221 /**
1222 * Is these array of intervals intersected ?
1223 *
1224 * @param time_interval_A - [ [ 21600, 23400 ] ]
1225 * @param time_interval_B - [ [ 25211, 27002 ], [ 36011, 86400 ] ]
1226 * @returns {boolean}
1227 */
1228 function wpbc_is_intersect__range_time_interval( time_interval_A, time_interval_B ){
1229
1230 var is_intersect;
1231
1232 for ( var i = 0; i < time_interval_A.length; i++ ){
1233
1234 for ( var j = 0; j < time_interval_B.length; j++ ){
1235
1236 is_intersect = wpbc_intervals__is_intersected( time_interval_A[ i ], time_interval_B[ j ] );
1237
1238 if ( is_intersect ){
1239 return true;
1240 }
1241 }
1242 }
1243
1244 return false;
1245 }
1246
1247 /**
1248 * Get all time fields in the booking form as array of objects
1249 *
1250 * @param resource_id
1251 * @returns []
1252 *
1253 * Example:
1254 * [
1255 * {
1256 * value_option_24h: '06:00 - 06:30'
1257 * times_as_seconds: [ 21600, 23400 ]
1258 * jquery_option: jQuery_Object {}
1259 * name: 'rangetime2[]'
1260 * }
1261 * ...
1262 * {
1263 * value_option_24h: '06:00'
1264 * times_as_seconds: [ 21600 ]
1265 * jquery_option: jQuery_Object {}
1266 * name: 'starttime2[]'
1267 * }
1268 * ]
1269 */
1270 function wpbc_get__time_fields__in_booking_form__as_arr( resource_id ){
1271 /**
1272 * Fields with [] like this select[name="rangetime1[]"]
1273 * it's when we have 'multiple' in shortcode: [select* rangetime multiple "06:00 - 06:30" ... ]
1274 */
1275 var time_fields_arr=[
1276 'select[name="rangetime' + resource_id + '"]',
1277 'select[name="rangetime' + resource_id + '[]"]',
1278 'select[name="starttime' + resource_id + '"]',
1279 'select[name="starttime' + resource_id + '[]"]',
1280 'select[name="endtime' + resource_id + '"]',
1281 'select[name="endtime' + resource_id + '[]"]'
1282 ];
1283
1284 var time_fields_obj_arr = [];
1285
1286 // Loop all Time Fields
1287 for ( var ctf= 0; ctf < time_fields_arr.length; ctf++ ){
1288
1289 var time_field = time_fields_arr[ ctf ];
1290 var time_option = jQuery( time_field + ' option' );
1291
1292 // Loop all options in time field
1293 for ( var j = 0; j < time_option.length; j++ ){
1294
1295 var jquery_option = jQuery( time_field + ' option:eq(' + j + ')' );
1296 var value_option_seconds_arr = jquery_option.val().split( '-' );
1297 var times_as_seconds = [];
1298
1299 // Get time as seconds
1300 if ( value_option_seconds_arr.length ){ // FixIn: 9.8.10.1.
1301 for ( let i = 0; i < value_option_seconds_arr.length; i++ ){ // FixIn: 10.0.0.56.
1302 // value_option_seconds_arr[i] = '14:00 ' | ' 16:00' (if from 'rangetime') and '16:00' if (start/end time)
1303
1304 var start_end_times_arr = value_option_seconds_arr[ i ].trim().split( ':' );
1305
1306 var time_in_seconds = parseInt( start_end_times_arr[ 0 ] ) * 60 * 60 + parseInt( start_end_times_arr[ 1 ] ) * 60;
1307
1308 times_as_seconds.push( time_in_seconds );
1309 }
1310 }
1311
1312 time_fields_obj_arr.push( {
1313 'name' : jQuery( time_field ).attr( 'name' ),
1314 'value_option_24h': jquery_option.val(),
1315 'jquery_option' : jquery_option,
1316 'times_as_seconds': times_as_seconds
1317 } );
1318 }
1319 }
1320
1321 return time_fields_obj_arr;
1322 }
1323
1324 /**
1325 * Disable HTML options and add booked CSS class
1326 *
1327 * @param time_fields_obj_arr - this value is from the func:
1328 * wpbc_get__time_fields__in_booking_form__as_arr( resource_id )
1329 * [
1330 * { jquery_option: jQuery_Object {}
1331 * name: 'rangetime2[]'
1332 * times_as_seconds: [ 21600, 23400 ]
1333 * value_option_24h: '06:00 - 06:30'
1334 * disabled = 1
1335 * }
1336 * ...
1337 * { jquery_option: jQuery_Object {}
1338 * name: 'starttime2[]'
1339 * times_as_seconds: [ 21600 ]
1340 * value_option_24h: '06:00'
1341 * disabled = 0
1342 * }
1343 * ]
1344 *
1345 */
1346 function wpbc__html__time_field_options__set_disabled( time_fields_obj_arr ){
1347
1348 var jquery_option;
1349
1350 for ( var i = 0; i < time_fields_obj_arr.length; i++ ){
1351
1352 var jquery_option = time_fields_obj_arr[ i ].jquery_option;
1353
1354 if ( 1 == time_fields_obj_arr[ i ].disabled ){
1355 jquery_option.prop( 'disabled', true ); // Make disable some options
1356 jquery_option.addClass( 'booked' ); // Add "booked" CSS class
1357
1358 // if this booked element selected --> then deselect it
1359 if ( jquery_option.prop( 'selected' ) ){
1360 jquery_option.prop( 'selected', false );
1361
1362 jquery_option.parent().find( 'option:not([disabled]):first' ).prop( 'selected', true ).trigger( "change" );
1363 }
1364
1365 } else {
1366 jquery_option.prop( 'disabled', false ); // Make active all times
1367 jquery_option.removeClass( 'booked' ); // Remove class "booked"
1368 }
1369 }
1370
1371 }
1372
1373 /**
1374 * Check if this time_range | Time_Slot is Full Day booked
1375 *
1376 * @param timeslot_arr_in_seconds - [ 36011, 86400 ]
1377 * @returns {boolean}
1378 */
1379 function wpbc_is_this_timeslot__full_day_booked( timeslot_arr_in_seconds ){
1380
1381 if (
1382 ( timeslot_arr_in_seconds.length > 1 )
1383 && ( parseInt( timeslot_arr_in_seconds[ 0 ] ) < 30 )
1384 && ( parseInt( timeslot_arr_in_seconds[ 1 ] ) > ( (24 * 60 * 60) - 30) )
1385 ){
1386 return true;
1387 }
1388
1389 return false;
1390 }
1391
1392
1393 // -----------------------------------------------------------------------------------------------------------------
1394 /* == S e l e c t e d D a t e s / T i m e - F i e l d s ==
1395 // ----------------------------------------------------------------------------------------------------------------- */
1396
1397 /**
1398 * Get all selected dates in SQL format like this [ "2023-08-23", "2023-08-24" , ... ]
1399 *
1400 * @param resource_id
1401 * @returns {[]} [ "2023-08-23", "2023-08-24", "2023-08-25", "2023-08-26", "2023-08-27", "2023-08-28",
1402 * "2023-08-29" ]
1403 */
1404 function wpbc_get__selected_dates_sql__as_arr( resource_id ){
1405
1406 var selected_dates_arr = [];
1407 selected_dates_arr = jQuery( '#date_booking' + resource_id ).val().split(',');
1408
1409 if ( selected_dates_arr.length ){ // FixIn: 9.8.10.1.
1410 for ( let i = 0; i < selected_dates_arr.length; i++ ){ // FixIn: 10.0.0.56.
1411 selected_dates_arr[ i ] = selected_dates_arr[ i ].trim();
1412 selected_dates_arr[ i ] = selected_dates_arr[ i ].split( '.' );
1413 if ( selected_dates_arr[ i ].length > 1 ){
1414 selected_dates_arr[ i ] = selected_dates_arr[ i ][ 2 ] + '-' + selected_dates_arr[ i ][ 1 ] + '-' + selected_dates_arr[ i ][ 0 ];
1415 }
1416 }
1417 }
1418
1419 // Remove empty elements from an array
1420 selected_dates_arr = selected_dates_arr.filter( function ( n ){ return parseInt(n); } );
1421
1422 selected_dates_arr.sort();
1423
1424 return selected_dates_arr;
1425 }
1426
1427
1428 /**
1429 * Get all time fields in the booking form as array of objects
1430 *
1431 * @param resource_id
1432 * @param is_only_selected_time
1433 * @returns []
1434 *
1435 * Example:
1436 * [
1437 * {
1438 * value_option_24h: '06:00 - 06:30'
1439 * times_as_seconds: [ 21600, 23400 ]
1440 * jquery_option: jQuery_Object {}
1441 * name: 'rangetime2[]'
1442 * }
1443 * ...
1444 * {
1445 * value_option_24h: '06:00'
1446 * times_as_seconds: [ 21600 ]
1447 * jquery_option: jQuery_Object {}
1448 * name: 'starttime2[]'
1449 * }
1450 * ]
1451 */
1452 function wpbc_get__selected_time_fields__in_booking_form__as_arr( resource_id, is_only_selected_time = true ){
1453 /**
1454 * Fields with [] like this select[name="rangetime1[]"]
1455 * it's when we have 'multiple' in shortcode: [select* rangetime multiple "06:00 - 06:30" ... ]
1456 */
1457 var time_fields_arr=[
1458 'select[name="rangetime' + resource_id + '"]',
1459 'select[name="rangetime' + resource_id + '[]"]',
1460 'select[name="starttime' + resource_id + '"]',
1461 'select[name="starttime' + resource_id + '[]"]',
1462 'select[name="endtime' + resource_id + '"]',
1463 'select[name="endtime' + resource_id + '[]"]',
1464 'select[name="durationtime' + resource_id + '"]',
1465 'select[name="durationtime' + resource_id + '[]"]'
1466 ];
1467
1468 var time_fields_obj_arr = [];
1469
1470 // Loop all Time Fields
1471 for ( var ctf= 0; ctf < time_fields_arr.length; ctf++ ){
1472
1473 var time_field = time_fields_arr[ ctf ];
1474
1475 var time_option;
1476 if ( is_only_selected_time ){
1477 time_option = jQuery( '#booking_form' + resource_id + ' ' + time_field + ' option:selected' ); // Exclude conditional fields, because of using '#booking_form3 ...'
1478 } else {
1479 time_option = jQuery( '#booking_form' + resource_id + ' ' + time_field + ' option' ); // All time fields
1480 }
1481
1482
1483 // Loop all options in time field
1484 for ( var j = 0; j < time_option.length; j++ ){
1485
1486 var jquery_option = jQuery( time_option[ j ] ); // Get only selected options //jQuery( time_field + ' option:eq(' + j + ')' );
1487 var value_option_seconds_arr = jquery_option.val().split( '-' );
1488 var times_as_seconds = [];
1489
1490 // Get time as seconds
1491 if ( value_option_seconds_arr.length ){ // FixIn: 9.8.10.1.
1492 for ( let i = 0; i < value_option_seconds_arr.length; i++ ){ // FixIn: 10.0.0.56.
1493 // value_option_seconds_arr[i] = '14:00 ' | ' 16:00' (if from 'rangetime') and '16:00' if (start/end time)
1494
1495 var start_end_times_arr = value_option_seconds_arr[ i ].trim().split( ':' );
1496
1497 var time_in_seconds = parseInt( start_end_times_arr[ 0 ] ) * 60 * 60 + parseInt( start_end_times_arr[ 1 ] ) * 60;
1498
1499 times_as_seconds.push( time_in_seconds );
1500 }
1501 }
1502
1503 time_fields_obj_arr.push( {
1504 'name' : jQuery( '#booking_form' + resource_id + ' ' + time_field ).attr( 'name' ),
1505 'value_option_24h': jquery_option.val(),
1506 'jquery_option' : jquery_option,
1507 'times_as_seconds': times_as_seconds
1508 } );
1509 }
1510 }
1511
1512 // Text: [starttime] - [endtime] -----------------------------------------------------------------------------
1513
1514 var text_time_fields_arr=[
1515 'input[name="starttime' + resource_id + '"]',
1516 'input[name="endtime' + resource_id + '"]',
1517 ];
1518 for ( var tf= 0; tf < text_time_fields_arr.length; tf++ ){
1519
1520 var text_jquery = jQuery( '#booking_form' + resource_id + ' ' + text_time_fields_arr[ tf ] ); // Exclude conditional fields, because of using '#booking_form3 ...'
1521 if ( text_jquery.length > 0 ){
1522
1523 var time__h_m__arr = text_jquery.val().trim().split( ':' ); // '14:00'
1524 if ( 0 == time__h_m__arr.length ){
1525 continue; // Not entered time value in a field
1526 }
1527 if ( 1 == time__h_m__arr.length ){
1528 if ( '' === time__h_m__arr[ 0 ] ){
1529 continue; // Not entered time value in a field
1530 }
1531 time__h_m__arr[ 1 ] = 0;
1532 }
1533 var text_time_in_seconds = parseInt( time__h_m__arr[ 0 ] ) * 60 * 60 + parseInt( time__h_m__arr[ 1 ] ) * 60;
1534
1535 var text_times_as_seconds = [];
1536 text_times_as_seconds.push( text_time_in_seconds );
1537
1538 time_fields_obj_arr.push( {
1539 'name' : text_jquery.attr( 'name' ),
1540 'value_option_24h': text_jquery.val(),
1541 'jquery_option' : text_jquery,
1542 'times_as_seconds': text_times_as_seconds
1543 } );
1544 }
1545 }
1546
1547 return time_fields_obj_arr;
1548 }
1549
1550
1551
1552 // ---------------------------------------------------------------------------------------------------------------------
1553 /* == S U P P O R T for C A L E N D A R ==
1554 // --------------------------------------------------------------------------------------------------------------------- */
1555
1556 /**
1557 * Get Calendar datepick Instance.
1558 *
1559 * @param {int|string} resource_id
1560 * @returns {*|null}
1561 */
1562 function wpbc_calendar__get_inst(resource_id) {
1563
1564 if ( 'undefined' === typeof (resource_id) ) {
1565 resource_id = '1';
1566 }
1567
1568 if ( jQuery( '#calendar_booking' + resource_id ).length > 0 ) {
1569
1570 try {
1571 var inst = jQuery.datepick._getInst( jQuery( '#calendar_booking' + resource_id ).get( 0 ) );
1572 return inst ? inst : null;
1573 } catch ( e ) {
1574 return null;
1575 }
1576 }
1577
1578 return null;
1579 }
1580
1581
1582 /**
1583 * Unselect all dates in calendar and visually update this calendar
1584 *
1585 * @param resource_id ID of booking resource
1586 * @returns {boolean} true on success | false, if no such calendar
1587 */
1588 function wpbc_calendar__unselect_all_dates( resource_id ){
1589
1590 if ( 'undefined' === typeof (resource_id) ){
1591 resource_id = '1';
1592 }
1593
1594 wpbc_range_selection__hide_guidance( resource_id );
1595
1596 var inst = wpbc_calendar__get_inst( resource_id )
1597
1598 if ( null !== inst ){
1599
1600 // Unselect all dates and set properties of Datepick
1601 jQuery( '#date_booking' + resource_id ).val( '' ); //FixIn: 5.4.3
1602 inst.stayOpen = false;
1603 inst.dates = [];
1604 jQuery.datepick._updateDatepick( inst );
1605
1606 return true
1607 }
1608
1609 return false;
1610
1611 }
1612
1613 /**
1614 * Clear days highlighting in All or specific Calendars
1615 *
1616 * @param resource_id - can be skiped to clear highlighting in all calendars
1617 */
1618 function wpbc_calendars__clear_days_highlighting( resource_id ){
1619
1620 if ( 'undefined' !== typeof ( resource_id ) ){
1621
1622 jQuery( '#calendar_booking' + resource_id + ' .datepick-days-cell-over' ).removeClass( 'datepick-days-cell-over' ); // Clear in specific calendar
1623
1624 } else {
1625 jQuery( '.datepick-days-cell-over' ).removeClass( 'datepick-days-cell-over' ); // Clear in all calendars
1626 }
1627 }
1628
1629 /**
1630 * Scroll to specific month in calendar
1631 *
1632 * @param resource_id ID of resource
1633 * @param year - real year - 2023
1634 * @param month - real month - 12
1635 * @returns {boolean}
1636 */
1637 function wpbc_calendar__scroll_to( resource_id, year, month ){
1638
1639 if ( 'undefined' === typeof (resource_id) ){ resource_id = '1'; }
1640 var inst = wpbc_calendar__get_inst( resource_id )
1641 if ( null !== inst ){
1642
1643 year = parseInt( year );
1644 month = parseInt( month ) - 1; // In JS date, month -1
1645
1646 inst.cursorDate = new Date();
1647 // In some cases, the setFullYear can set only Year, and not the Month and day // FixIn: 6.2.3.5.
1648 inst.cursorDate.setFullYear( year, month, 1 );
1649 inst.cursorDate.setMonth( month );
1650 inst.cursorDate.setDate( 1 );
1651
1652 inst.drawMonth = inst.cursorDate.getMonth();
1653 inst.drawYear = inst.cursorDate.getFullYear();
1654
1655 jQuery.datepick._notifyChange( inst );
1656 jQuery.datepick._adjustInstDate( inst );
1657 jQuery.datepick._showDate( inst );
1658 jQuery.datepick._updateDatepick( inst );
1659
1660 return true;
1661 }
1662 return false;
1663 }
1664
1665 /**
1666 * Determine native date selectability without optional date constraints.
1667 *
1668 * This helper exposes the canonical calendar result for algorithms, such as
1669 * gap discovery, that must not consume their own derived constraints. It does
1670 * not trigger wpbc_calendar__apply_date_constraints and must remain free of
1671 * optional-module state.
1672 *
1673 * @param {number|string} resource_id Booking resource ID.
1674 * @param {string} sql_date Calendar date in YYYY-MM-DD format.
1675 * @returns {boolean} True when core calendar data marks the date selectable.
1676 */
1677 function wpbc_is_this_day_selectable__raw( resource_id, sql_date ){
1678
1679 // Get Data --------------------------------------------------------------------------------------------------------
1680 var date_bookings_obj = _wpbc.bookings_in_calendar__get_for_date( resource_id, sql_date );
1681 var is_day_selectable = false;
1682
1683 if ( date_bookings_obj && 'undefined' !== typeof ( date_bookings_obj[ 'day_availability' ] ) ) {
1684 is_day_selectable = ( parseInt( date_bookings_obj[ 'day_availability' ] ) > 0 );
1685
1686 if ( 'undefined' !== typeof ( date_bookings_obj[ 'summary' ] ) && 'available' != date_bookings_obj[ 'summary']['status_for_day' ] ){
1687
1688 var is_set_pending_days_selectable = _wpbc.calendar__get_param_value( resource_id, 'pending_days_selectable' ); // set pending days selectable // FixIn: 8.6.1.18.
1689 var booking_statuses_arr = wpbc_get_booking_statuses__as_arr( date_bookings_obj );
1690
1691 if (
1692 ( wpbc_booking_statuses__has( booking_statuses_arr, 'pending' ) )
1693 || ( wpbc_booking_statuses__has( booking_statuses_arr, 'pending_pending' ) )
1694 || ( wpbc_booking_statuses__has( booking_statuses_arr, 'pending_approved' ) )
1695 || ( wpbc_booking_statuses__has( booking_statuses_arr, 'approved_pending' ) )
1696 ){
1697 is_day_selectable = (is_day_selectable) ? true : is_set_pending_days_selectable;
1698 }
1699 }
1700 }
1701
1702 return Boolean( is_day_selectable );
1703 }
1704
1705 /**
1706 * Determine final date selectability after native and optional constraints.
1707 *
1708 * Native unavailability returns immediately. Optional modules may therefore
1709 * only block a natively selectable date and can never reopen a core-unavailable
1710 * date.
1711 *
1712 * @param {number|string} resource_id Booking resource ID.
1713 * @param {string} sql_date Calendar date in YYYY-MM-DD format.
1714 * @returns {boolean} True when the date remains selectable after constraints.
1715 */
1716 function wpbc_is_this_day_selectable( resource_id, sql_date ){
1717
1718 var is_natively_selectable = wpbc_is_this_day_selectable__raw( resource_id, sql_date );
1719
1720 if ( ! is_natively_selectable ) {
1721 return false;
1722 }
1723
1724 return wpbc_calendar__evaluate_date_constraints( resource_id, sql_date, is_natively_selectable, [] ).is_selectable;
1725 }
1726
1727 /**
1728 * Is date to check IN array of selected dates
1729 *
1730 * @param {date}js_date_to_check - JS Date - simple JavaScript Date object
1731 * @param {[]} js_dates_arr - [ JSDate, ... ] - array of JS dates
1732 * @returns {boolean}
1733 */
1734 function wpbc_is_this_day_among_selected_days( js_date_to_check, js_dates_arr ){
1735
1736 for ( var date_index = 0; date_index < js_dates_arr.length ; date_index++ ){ // FixIn: 8.4.5.16.
1737 if ( ( js_dates_arr[ date_index ].getFullYear() === js_date_to_check.getFullYear() ) &&
1738 ( js_dates_arr[ date_index ].getMonth() === js_date_to_check.getMonth() ) &&
1739 ( js_dates_arr[ date_index ].getDate() === js_date_to_check.getDate() ) ) {
1740 return true;
1741 }
1742 }
1743
1744 return false;
1745 }
1746
1747 /**
1748 * Get SQL Class Date '2023-08-01' from JS Date
1749 *
1750 * @param date JS Date
1751 * @returns {string} '2023-08-12'
1752 */
1753 function wpbc__get__sql_class_date( date ){
1754
1755 var sql_class_day = date.getFullYear() + '-';
1756 sql_class_day += ( ( date.getMonth() + 1 ) < 10 ) ? '0' : '';
1757 sql_class_day += ( date.getMonth() + 1 ) + '-'
1758 sql_class_day += ( date.getDate() < 10 ) ? '0' : '';
1759 sql_class_day += date.getDate();
1760
1761 return sql_class_day;
1762 }
1763
1764 /**
1765 * Get JS Date from the SQL date format '2024-05-14'
1766 * @param sql_class_date
1767 * @returns {Date}
1768 */
1769 function wpbc__get__js_date( sql_class_date ){
1770
1771 var sql_class_date_arr = sql_class_date.split( '-' );
1772
1773 var date_js = new Date();
1774
1775 date_js.setFullYear( parseInt( sql_class_date_arr[ 0 ] ), (parseInt( sql_class_date_arr[ 1 ] ) - 1), parseInt( sql_class_date_arr[ 2 ] ) ); // year, month, date
1776
1777 // Without this time adjust Dates selection in Datepicker can not work!!!
1778 date_js.setHours(0);
1779 date_js.setMinutes(0);
1780 date_js.setSeconds(0);
1781 date_js.setMilliseconds(0);
1782
1783 return date_js;
1784 }
1785
1786 /**
1787 * Get TD Class Date '1-31-2023' from JS Date
1788 *
1789 * @param date JS Date
1790 * @returns {string} '1-31-2023'
1791 */
1792 function wpbc__get__td_class_date( date ){
1793
1794 var td_class_day = (date.getMonth() + 1) + '-' + date.getDate() + '-' + date.getFullYear(); // '1-9-2023'
1795
1796 return td_class_day;
1797 }
1798
1799 /**
1800 * Get date params from string date
1801 *
1802 * @param date string date like '31.5.2023'
1803 * @param separator default '.' can be skipped.
1804 * @returns { {date: number, month: number, year: number} }
1805 */
1806 function wpbc__get__date_params__from_string_date( date , separator){
1807
1808 separator = ( 'undefined' !== typeof (separator) ) ? separator : '.';
1809
1810 var date_arr = date.split( separator );
1811 var date_obj = {
1812 'year' : parseInt( date_arr[ 2 ] ),
1813 'month': (parseInt( date_arr[ 1 ] ) - 1),
1814 'date' : parseInt( date_arr[ 0 ] )
1815 };
1816 return date_obj; // for = new Date( date_obj.year , date_obj.month , date_obj.date );
1817 }
1818
1819 /**
1820 * Add Spin Loader to calendar
1821 * @param resource_id
1822 */
1823 function wpbc_calendar__loading__start( resource_id ){
1824 if ( ! jQuery( '#calendar_booking' + resource_id ).next().hasClass( 'wpbc_spins_loader_wrapper' ) ){
1825 jQuery( '#calendar_booking' + resource_id ).after( '<div class="wpbc_spins_loader_wrapper"><div class="wpbc_spin_loader_one_new"></div></div>' );
1826 }
1827 if ( ! jQuery( '#calendar_booking' + resource_id ).hasClass( 'wpbc_calendar_blur_small' ) ){
1828 jQuery( '#calendar_booking' + resource_id ).addClass( 'wpbc_calendar_blur_small' );
1829 }
1830 wpbc_calendar__blur__start( resource_id );
1831 }
1832
1833 /**
1834 * Remove Spin Loader to calendar
1835 * @param resource_id
1836 */
1837 function wpbc_calendar__loading__stop( resource_id ){
1838 jQuery( '#calendar_booking' + resource_id + ' + .wpbc_spins_loader_wrapper' ).remove();
1839 jQuery( '#calendar_booking' + resource_id ).removeClass( 'wpbc_calendar_blur_small' );
1840 wpbc_calendar__blur__stop( resource_id );
1841 }
1842
1843 /**
1844 * Add Blur to calendar
1845 * @param resource_id
1846 */
1847 function wpbc_calendar__blur__start( resource_id ){
1848 if ( ! jQuery( '#calendar_booking' + resource_id ).hasClass( 'wpbc_calendar_blur' ) ){
1849 jQuery( '#calendar_booking' + resource_id ).addClass( 'wpbc_calendar_blur' );
1850 }
1851 }
1852
1853 /**
1854 * Remove Blur in calendar
1855 * @param resource_id
1856 */
1857 function wpbc_calendar__blur__stop( resource_id ){
1858 jQuery( '#calendar_booking' + resource_id ).removeClass( 'wpbc_calendar_blur' );
1859 }
1860
1861
1862 // .................................................................................................................
1863 /* == Calendar Update - View ==
1864 // ................................................................................................................. */
1865
1866 /**
1867 * Update look of calendar (safe).
1868 *
1869 * In Elementor preview the DOM can be re-rendered, so the calendar element may exist
1870 * while the Datepick instance is missing. In that case try to (re)initialize.
1871 *
1872 * @param {int|string} resource_id
1873 * @return {boolean} true if updated, false if not possible
1874 */
1875 function wpbc_calendar__update_look(resource_id) {
1876
1877 var inst = wpbc_calendar__get_inst( resource_id );
1878
1879 // If instance missing, try to re-init calendar once.
1880 if ( null === inst ) {
1881
1882 var jq_cal = jQuery( '#calendar_booking' + resource_id );
1883
1884 if ( jq_cal.length && ('function' === typeof wpbc_calendar_show) ) {
1885
1886 // Elementor sometimes leaves stale class without real instance.
1887 if ( jq_cal.hasClass( 'hasDatepick' ) ) {
1888 jq_cal.removeClass( 'hasDatepick' );
1889 }
1890
1891 // Try to init datepick markup now.
1892 wpbc_calendar_show( resource_id );
1893
1894 // Try again.
1895 inst = wpbc_calendar__get_inst( resource_id );
1896 }
1897 }
1898
1899 // Still no instance -> do not crash the whole ajax flow.
1900 if ( null === inst ) {
1901 return false;
1902 }
1903
1904 jQuery.datepick._updateDatepick( inst );
1905 return true;
1906 }
1907
1908
1909
1910 /**
1911 * Update dynamically Number of Months in calendar
1912 *
1913 * @param resource_id int
1914 * @param months_number int
1915 */
1916 function wpbc_calendar__update_months_number( resource_id, months_number ){
1917 var inst = wpbc_calendar__get_inst( resource_id );
1918 if ( null !== inst ){
1919 inst.settings[ 'numberOfMonths' ] = months_number;
1920 //_wpbc.calendar__set_param_value( resource_id, 'calendar_number_of_months', months_number );
1921 wpbc_calendar__update_look( resource_id );
1922 }
1923 }
1924
1925
1926 /**
1927 * Show calendar in different Skin
1928 *
1929 * @param selected_skin_url
1930 */
1931 function wpbc__calendar__change_skin( selected_skin_url ){
1932
1933 //console.log( 'SKIN SELECTION ::', selected_skin_url );
1934
1935 // Remove CSS skin
1936 var stylesheet = document.getElementById( 'wpbc-calendar-skin-css' );
1937 stylesheet.parentNode.removeChild( stylesheet );
1938
1939
1940 // Add new CSS skin
1941 var headID = document.getElementsByTagName( "head" )[ 0 ];
1942 var cssNode = document.createElement( 'link' );
1943 cssNode.type = 'text/css';
1944 cssNode.setAttribute( "id", "wpbc-calendar-skin-css" );
1945 cssNode.rel = 'stylesheet';
1946 cssNode.media = 'screen';
1947 cssNode.href = selected_skin_url; //"http://beta/wp-content/plugins/booking/css/skins/green-01.css";
1948 headID.appendChild( cssNode );
1949 }
1950
1951
1952 function wpbc__css__change_skin( selected_skin_url, stylesheet_id = 'wpbc-time_picker-skin-css' ){
1953
1954 // Remove CSS skin
1955 var stylesheet = document.getElementById( stylesheet_id );
1956 stylesheet.parentNode.removeChild( stylesheet );
1957
1958
1959 // Add new CSS skin
1960 var headID = document.getElementsByTagName( "head" )[ 0 ];
1961 var cssNode = document.createElement( 'link' );
1962 cssNode.type = 'text/css';
1963 cssNode.setAttribute( "id", stylesheet_id );
1964 cssNode.rel = 'stylesheet';
1965 cssNode.media = 'screen';
1966 cssNode.href = selected_skin_url; //"http://beta/wp-content/plugins/booking/css/skins/green-01.css";
1967 headID.appendChild( cssNode );
1968 }
1969
1970
1971 // ---------------------------------------------------------------------------------------------------------------------
1972 /* == S U P P O R T M A T H ==
1973 // --------------------------------------------------------------------------------------------------------------------- */
1974
1975 /**
1976 * Merge several intersected intervals or return not intersected:
1977 * [[1,3],[2,6],[8,10],[15,18]] -> [[1,6],[8,10],[15,18]]
1978 *
1979 * @param [] intervals [ [1,3],[2,4],[6,8],[9,10],[3,7] ]
1980 * @returns [] [ [1,8],[9,10] ]
1981 *
1982 * Exmample: wpbc_intervals__merge_inersected( [ [1,3],[2,4],[6,8],[9,10],[3,7] ] );
1983 */
1984 function wpbc_intervals__merge_inersected( intervals ){
1985
1986 if ( ! intervals || intervals.length === 0 ){
1987 return [];
1988 }
1989
1990 var merged = [];
1991 intervals.sort( function ( a, b ){
1992 return a[ 0 ] - b[ 0 ];
1993 } );
1994
1995 var mergedInterval = intervals[ 0 ];
1996
1997 for ( var i = 1; i < intervals.length; i++ ){
1998 var interval = intervals[ i ];
1999
2000 if ( interval[ 0 ] <= mergedInterval[ 1 ] ){
2001 mergedInterval[ 1 ] = Math.max( mergedInterval[ 1 ], interval[ 1 ] );
2002 } else {
2003 merged.push( mergedInterval );
2004 mergedInterval = interval;
2005 }
2006 }
2007
2008 merged.push( mergedInterval );
2009 return merged;
2010 }
2011
2012
2013 /**
2014 * Is 2 intervals intersected: [36011, 86392] <=> [1, 43192] => true ( intersected )
2015 *
2016 * Good explanation here
2017 * https://stackoverflow.com/questions/3269434/whats-the-most-efficient-way-to-test-if-two-ranges-overlap
2018 *
2019 * @param interval_A - [ 36011, 86392 ]
2020 * @param interval_B - [ 1, 43192 ]
2021 *
2022 * @return bool
2023 */
2024 function wpbc_intervals__is_intersected( interval_A, interval_B ) {
2025
2026 if (
2027 ( 0 == interval_A.length )
2028 || ( 0 == interval_B.length )
2029 ){
2030 return false;
2031 }
2032
2033 interval_A[ 0 ] = parseInt( interval_A[ 0 ] );
2034 interval_A[ 1 ] = parseInt( interval_A[ 1 ] );
2035 interval_B[ 0 ] = parseInt( interval_B[ 0 ] );
2036 interval_B[ 1 ] = parseInt( interval_B[ 1 ] );
2037
2038 var is_intersected = Math.max( interval_A[ 0 ], interval_B[ 0 ] ) - Math.min( interval_A[ 1 ], interval_B[ 1 ] );
2039
2040 // if ( 0 == is_intersected ) {
2041 // // Such ranges going one after other, e.g.: [ 12, 15 ] and [ 15, 21 ]
2042 // }
2043
2044 if ( is_intersected < 0 ) {
2045 return true; // INTERSECTED
2046 }
2047
2048 return false; // Not intersected
2049 }
2050
2051
2052 /**
2053 * Get the closets ABS value of element in array to the current myValue
2054 *
2055 * @param myValue - int element to search closet 4
2056 * @param myArray - array of elements where to search [5,8,1,7]
2057 * @returns int 5
2058 */
2059 function wpbc_get_abs_closest_value_in_arr( myValue, myArray ){
2060
2061 if ( myArray.length == 0 ){ // If the array is empty -> return the myValue
2062 return myValue;
2063 }
2064
2065 var obj = myArray[ 0 ];
2066 var diff = Math.abs( myValue - obj ); // Get distance between 1st element
2067 var closetValue = myArray[ 0 ]; // Save 1st element
2068
2069 for ( var i = 1; i < myArray.length; i++ ){
2070 obj = myArray[ i ];
2071
2072 if ( Math.abs( myValue - obj ) < diff ){ // we found closer value -> save it
2073 diff = Math.abs( myValue - obj );
2074 closetValue = obj;
2075 }
2076 }
2077
2078 return closetValue;
2079 }
2080
2081
2082 // ---------------------------------------------------------------------------------------------------------------------
2083 /* == T O O L T I P S ==
2084 // --------------------------------------------------------------------------------------------------------------------- */
2085
2086 /**
2087 * Define tooltip to show, when mouse over Date in Calendar
2088 *
2089 * @param tooltip_text - Text to show 'Booked time: 12:00 - 13:00<br>Cost: $20.00'
2090 * @param resource_id - ID of booking resource '1'
2091 * @param td_class - SQL class '1-9-2023'
2092 * @returns {boolean} - defined to show or not
2093 */
2094 function wpbc_set_tooltip___for__calendar_date( tooltip_text, resource_id, td_class ){
2095
2096 //TODO: make escaping of text for quot symbols, and JS/HTML...
2097
2098 jQuery( '#calendar_booking' + resource_id + ' td.cal4date-' + td_class ).attr( 'data-content', tooltip_text );
2099
2100 var td_el = jQuery( '#calendar_booking' + resource_id + ' td.cal4date-' + td_class ).get( 0 ); // FixIn: 9.0.1.1.
2101
2102 if (
2103 ( 'undefined' !== typeof(td_el) )
2104 && ( undefined == td_el._tippy )
2105 && ( '' !== tooltip_text )
2106 ){
2107
2108 wpbc_tippy( td_el , {
2109 content( reference ){
2110
2111 var popover_content = reference.getAttribute( 'data-content' );
2112
2113 return '<div class="popover popover_tippy">'
2114 + '<div class="popover-content">'
2115 + popover_content
2116 + '</div>'
2117 + '</div>';
2118 },
2119 allowHTML : true,
2120 trigger : 'mouseenter focus',
2121 interactive : false,
2122 hideOnClick : true,
2123 interactiveBorder: 10,
2124 maxWidth : 550,
2125 theme : 'wpbc-tippy-times',
2126 placement : 'top',
2127 delay : [400, 0], // FixIn: 9.4.2.2.
2128 //delay : [0, 9999999999], // Debuge tooltip
2129 ignoreAttributes : true,
2130 touch : true, //['hold', 500], // 500ms delay // FixIn: 9.2.1.5.
2131 appendTo: () => document.body,
2132 });
2133
2134 return true;
2135 }
2136
2137 return false;
2138 }
2139
2140
2141 // ---------------------------------------------------------------------------------------------------------------------
2142 /* == Dates Functions ==
2143 // --------------------------------------------------------------------------------------------------------------------- */
2144
2145 /**
2146 * Get number of dates between 2 JS Dates
2147 *
2148 * @param date1 JS Date
2149 * @param date2 JS Date
2150 * @returns {number}
2151 */
2152 function wpbc_dates__days_between(date1, date2) {
2153
2154 // The number of milliseconds in one day
2155 var ONE_DAY = 1000 * 60 * 60 * 24;
2156
2157 // Convert both dates to milliseconds
2158 var date1_ms = date1.getTime();
2159 var date2_ms = date2.getTime();
2160
2161 // Calculate the difference in milliseconds
2162 var difference_ms = date1_ms - date2_ms;
2163
2164 // Convert back to days and return
2165 return Math.round(difference_ms/ONE_DAY);
2166 }
2167
2168
2169 /**
2170 * Check if this array of dates is consecutive array of dates or not.
2171 * e.g. ['2024-05-09','2024-05-19','2024-05-30'] -> false
2172 * e.g. ['2024-05-09','2024-05-10','2024-05-11'] -> true
2173 * @param sql_dates_arr array e.g.: ['2024-05-09','2024-05-19','2024-05-30']
2174 * @returns {boolean}
2175 */
2176 function wpbc_dates__is_consecutive_dates_arr_range( sql_dates_arr ){ // FixIn: 10.0.0.50.
2177
2178 if ( sql_dates_arr.length > 1 ){
2179 var previos_date = wpbc__get__js_date( sql_dates_arr[ 0 ] );
2180 var current_date;
2181
2182 for ( var i = 1; i < sql_dates_arr.length; i++ ){
2183 current_date = wpbc__get__js_date( sql_dates_arr[i] );
2184
2185 if ( wpbc_dates__days_between( current_date, previos_date ) != 1 ){
2186 return false;
2187 }
2188
2189 previos_date = current_date;
2190 }
2191 }
2192
2193 return true;
2194 }
2195
2196
2197 // ---------------------------------------------------------------------------------------------------------------------
2198 /* == Auto Dates Selection ==
2199 // --------------------------------------------------------------------------------------------------------------------- */
2200
2201 /**
2202 * == How to use ? ==
2203 *
2204 * For Dates selection, we need to use this logic! We need select the dates only after booking data loaded!
2205 *
2206 * Check example bellow.
2207 *
2208 * // Fire on all booking dates loaded
2209 * jQuery( 'body' ).on( 'wpbc_calendar_ajx__loaded_data', function ( event, loaded_resource_id ){
2210 *
2211 * if ( loaded_resource_id == select_dates_in_calendar_id ){
2212 * wpbc_auto_select_dates_in_calendar( select_dates_in_calendar_id, '2024-05-15', '2024-05-25' );
2213 * }
2214 * } );
2215 *
2216 */
2217
2218
2219 /**
2220 * Try to Auto select dates in specific calendar by simulated clicks in datepicker
2221 *
2222 * @param resource_id 1
2223 * @param check_in_ymd '2024-05-09' OR ['2024-05-09','2024-05-19','2024-05-20']
2224 * @param check_out_ymd '2024-05-15' Optional
2225 *
2226 * @returns {number} number of selected dates
2227 *
2228 * Example 1: var num_selected_days = wpbc_auto_select_dates_in_calendar( 1, '2024-05-15',
2229 * '2024-05-25' ); Example 2: var num_selected_days = wpbc_auto_select_dates_in_calendar( 1,
2230 * ['2024-05-09','2024-05-19','2024-05-20'] );
2231 */
2232 function wpbc_auto_select_dates_in_calendar( resource_id, check_in_ymd, check_out_ymd = '' ){ // FixIn: 10.0.0.47.
2233
2234 console.log( 'WPBC_AUTO_SELECT_DATES_IN_CALENDAR( RESOURCE_ID, CHECK_IN_YMD, CHECK_OUT_YMD )', resource_id, check_in_ymd, check_out_ymd );
2235
2236 if (
2237 ( '2100-01-01' == check_in_ymd )
2238 || ( '2100-01-01' == check_out_ymd )
2239 || ( ( '' == check_in_ymd ) && ( '' == check_out_ymd ) )
2240 ){
2241 return 0;
2242 }
2243
2244 // -----------------------------------------------------------------------------------------------------------------
2245 // If check_in_ymd = [ '2024-05-09','2024-05-19','2024-05-30' ] ARRAY of DATES // FixIn: 10.0.0.50.
2246 // -----------------------------------------------------------------------------------------------------------------
2247 var dates_to_select_arr = [];
2248 if ( Array.isArray( check_in_ymd ) ){
2249 dates_to_select_arr = wpbc_clone_obj( check_in_ymd );
2250
2251 // -------------------------------------------------------------------------------------------------------------
2252 // Exceptions to set MULTIPLE DAYS mode
2253 // -------------------------------------------------------------------------------------------------------------
2254 // if dates as NOT CONSECUTIVE: ['2024-05-09','2024-05-19','2024-05-30'], -> set MULTIPLE DAYS mode
2255 if (
2256 ( dates_to_select_arr.length > 0 )
2257 && ( '' == check_out_ymd )
2258 && ( ! wpbc_dates__is_consecutive_dates_arr_range( dates_to_select_arr ) )
2259 ){
2260 wpbc_cal_days_select__multiple( resource_id );
2261 }
2262 // if multiple days to select, but enabled SINGLE day mode, -> set MULTIPLE DAYS mode
2263 if (
2264 ( dates_to_select_arr.length > 1 )
2265 && ( '' == check_out_ymd )
2266 && ( 'single' === _wpbc.calendar__get_param_value( resource_id, 'days_select_mode' ) )
2267 ){
2268 wpbc_cal_days_select__multiple( resource_id );
2269 }
2270 // -------------------------------------------------------------------------------------------------------------
2271 check_in_ymd = dates_to_select_arr[ 0 ];
2272 if ( '' == check_out_ymd ){
2273 check_out_ymd = dates_to_select_arr[ (dates_to_select_arr.length-1) ];
2274 }
2275 }
2276 // -----------------------------------------------------------------------------------------------------------------
2277
2278
2279 if ( '' == check_in_ymd ){
2280 check_in_ymd = check_out_ymd;
2281 }
2282 if ( '' == check_out_ymd ){
2283 check_out_ymd = check_in_ymd;
2284 }
2285
2286 if ( 'undefined' === typeof (resource_id) ){
2287 resource_id = '1';
2288 }
2289
2290
2291 var inst = wpbc_calendar__get_inst( resource_id );
2292
2293 if ( null !== inst ){
2294
2295 // Unselect all dates and set properties of Datepick
2296 jQuery( '#date_booking' + resource_id ).val( '' ); //FixIn: 5.4.3
2297 inst.stayOpen = false;
2298 inst.dates = [];
2299 var check_in_js = wpbc__get__js_date( check_in_ymd );
2300 var td_cell = wpbc_get_clicked_td( inst.id, check_in_js );
2301
2302 // Is ome type of error, then select multiple days selection mode.
2303 if ( '' === _wpbc.calendar__get_param_value( resource_id, 'days_select_mode' ) ) {
2304 _wpbc.calendar__set_param_value( resource_id, 'days_select_mode', 'multiple' );
2305 }
2306
2307
2308 // ---------------------------------------------------------------------------------------------------------
2309 // == DYNAMIC ==
2310 if ( 'dynamic' === _wpbc.calendar__get_param_value( resource_id, 'days_select_mode' ) ){
2311 // 1-st click
2312 inst.stayOpen = false;
2313 jQuery.datepick._selectDay( td_cell, '#' + inst.id, check_in_js.getTime() );
2314 if ( 0 === inst.dates.length ){
2315 return 0; // First click was unsuccessful, so we must not make other click
2316 }
2317
2318 // 2-nd click
2319 var check_out_js = wpbc__get__js_date( check_out_ymd );
2320 var td_cell_out = wpbc_get_clicked_td( inst.id, check_out_js );
2321 inst.stayOpen = true;
2322 jQuery.datepick._selectDay( td_cell_out, '#' + inst.id, check_out_js.getTime() );
2323 }
2324
2325 // ---------------------------------------------------------------------------------------------------------
2326 // == FIXED ==
2327 if ( 'fixed' === _wpbc.calendar__get_param_value( resource_id, 'days_select_mode' )) {
2328 jQuery.datepick._selectDay( td_cell, '#' + inst.id, check_in_js.getTime() );
2329 }
2330
2331 // ---------------------------------------------------------------------------------------------------------
2332 // == SINGLE ==
2333 if ( 'single' === _wpbc.calendar__get_param_value( resource_id, 'days_select_mode' ) ){
2334 //jQuery.datepick._restrictMinMax( inst, jQuery.datepick._determineDate( inst, check_in_js, null ) ); // Do we need to run this ? Please note, check_in_js must have time, min, sec defined to 0!
2335 jQuery.datepick._selectDay( td_cell, '#' + inst.id, check_in_js.getTime() );
2336 }
2337
2338 // ---------------------------------------------------------------------------------------------------------
2339 // == MULTIPLE ==
2340 if ( 'multiple' === _wpbc.calendar__get_param_value( resource_id, 'days_select_mode' ) ){
2341
2342 var dates_arr;
2343
2344 if ( dates_to_select_arr.length > 0 ){
2345 // Situation, when we have dates array: ['2024-05-09','2024-05-19','2024-05-30']. and not the Check In / Check out dates as parameter in this function
2346 dates_arr = wpbc_get_selection_dates_js_str_arr__from_arr( dates_to_select_arr );
2347 } else {
2348 dates_arr = wpbc_get_selection_dates_js_str_arr__from_check_in_out( check_in_ymd, check_out_ymd, inst );
2349 }
2350
2351 if ( 0 === dates_arr.dates_js.length ){
2352 return 0;
2353 }
2354
2355 // For Calendar Days selection
2356 for ( var j = 0; j < dates_arr.dates_js.length; j++ ){ // Loop array of dates
2357
2358 var str_date = wpbc__get__sql_class_date( dates_arr.dates_js[ j ] );
2359
2360 // Date unavailable !
2361 if ( 0 == _wpbc.bookings_in_calendar__get_for_date( resource_id, str_date ).day_availability ){
2362 return 0;
2363 }
2364
2365 if ( dates_arr.dates_js[ j ] != -1 ) {
2366 inst.dates.push( dates_arr.dates_js[ j ] );
2367 }
2368 }
2369
2370 var check_out_date = dates_arr.dates_js[ (dates_arr.dates_js.length - 1) ];
2371
2372 inst.dates.push( check_out_date ); // Need add one additional SAME date for correct works of dates selection !!!!!
2373
2374 var checkout_timestamp = check_out_date.getTime();
2375 var td_cell = wpbc_get_clicked_td( inst.id, check_out_date );
2376
2377 jQuery.datepick._selectDay( td_cell, '#' + inst.id, checkout_timestamp );
2378 }
2379
2380
2381 if ( 0 !== inst.dates.length ){
2382 // Scroll to specific month, if we set dates in some future months
2383 wpbc_calendar__scroll_to( resource_id, inst.dates[ 0 ].getFullYear(), inst.dates[ 0 ].getMonth()+1 );
2384 }
2385
2386 return wpbc_get__selected_dates_sql__as_arr( resource_id ).length;
2387 }
2388
2389 return 0;
2390 }
2391
2392 /**
2393 * Get HTML td element (where was click in calendar day cell)
2394 *
2395 * @param calendar_html_id 'calendar_booking1'
2396 * @param date_js JS Date
2397 * @returns {*|jQuery} Dom HTML td element
2398 */
2399 function wpbc_get_clicked_td( calendar_html_id, date_js ){
2400
2401 var td_cell = jQuery( '#' + calendar_html_id + ' .sql_date_' + wpbc__get__sql_class_date( date_js ) ).get( 0 );
2402
2403 return td_cell;
2404 }
2405
2406 /**
2407 * Get arrays of JS and SQL dates as dates array
2408 *
2409 * @param check_in_ymd '2024-05-15'
2410 * @param check_out_ymd '2024-05-25'
2411 * @param inst Datepick Inst. Use wpbc_calendar__get_inst( resource_id );
2412 * @returns {{dates_js: *[], dates_str: *[]}}
2413 */
2414 function wpbc_get_selection_dates_js_str_arr__from_check_in_out( check_in_ymd, check_out_ymd , inst ){
2415
2416 var original_array = [];
2417 var date;
2418 var bk_distinct_dates = [];
2419
2420 var check_in_date = check_in_ymd.split( '-' );
2421 var check_out_date = check_out_ymd.split( '-' );
2422
2423 date = new Date();
2424 date.setFullYear( check_in_date[ 0 ], (check_in_date[ 1 ] - 1), check_in_date[ 2 ] ); // year, month, date
2425 var original_check_in_date = date;
2426 original_array.push( jQuery.datepick._restrictMinMax( inst, jQuery.datepick._determineDate( inst, date, null ) ) ); //add date
2427 if ( ! wpbc_in_array( bk_distinct_dates, (check_in_date[ 2 ] + '.' + check_in_date[ 1 ] + '.' + check_in_date[ 0 ]) ) ){
2428 bk_distinct_dates.push( parseInt(check_in_date[ 2 ]) + '.' + parseInt(check_in_date[ 1 ]) + '.' + check_in_date[ 0 ] );
2429 }
2430
2431 var date_out = new Date();
2432 date_out.setFullYear( check_out_date[ 0 ], (check_out_date[ 1 ] - 1), check_out_date[ 2 ] ); // year, month, date
2433 var original_check_out_date = date_out;
2434
2435 var mewDate = new Date( original_check_in_date.getFullYear(), original_check_in_date.getMonth(), original_check_in_date.getDate() );
2436 mewDate.setDate( original_check_in_date.getDate() + 1 );
2437
2438 while (
2439 (original_check_out_date > date) &&
2440 (original_check_in_date != original_check_out_date) ){
2441 date = new Date( mewDate.getFullYear(), mewDate.getMonth(), mewDate.getDate() );
2442
2443 original_array.push( jQuery.datepick._restrictMinMax( inst, jQuery.datepick._determineDate( inst, date, null ) ) ); //add date
2444 if ( !wpbc_in_array( bk_distinct_dates, (date.getDate() + '.' + parseInt( date.getMonth() + 1 ) + '.' + date.getFullYear()) ) ){
2445 bk_distinct_dates.push( (parseInt(date.getDate()) + '.' + parseInt( date.getMonth() + 1 ) + '.' + date.getFullYear()) );
2446 }
2447
2448 mewDate = new Date( date.getFullYear(), date.getMonth(), date.getDate() );
2449 mewDate.setDate( mewDate.getDate() + 1 );
2450 }
2451 original_array.pop();
2452 bk_distinct_dates.pop();
2453
2454 return {'dates_js': original_array, 'dates_str': bk_distinct_dates};
2455 }
2456
2457 /**
2458 * Get arrays of JS and SQL dates as dates array
2459 *
2460 * @param dates_to_select_arr = ['2024-05-09','2024-05-19','2024-05-30']
2461 *
2462 * @returns {{dates_js: *[], dates_str: *[]}}
2463 */
2464 function wpbc_get_selection_dates_js_str_arr__from_arr( dates_to_select_arr ){ // FixIn: 10.0.0.50.
2465
2466 var original_array = [];
2467 var bk_distinct_dates = [];
2468 var one_date_str;
2469
2470 for ( var d = 0; d < dates_to_select_arr.length; d++ ){
2471
2472 original_array.push( wpbc__get__js_date( dates_to_select_arr[ d ] ) );
2473
2474 one_date_str = dates_to_select_arr[ d ].split('-')
2475 if ( ! wpbc_in_array( bk_distinct_dates, (one_date_str[ 2 ] + '.' + one_date_str[ 1 ] + '.' + one_date_str[ 0 ]) ) ){
2476 bk_distinct_dates.push( parseInt(one_date_str[ 2 ]) + '.' + parseInt(one_date_str[ 1 ]) + '.' + one_date_str[ 0 ] );
2477 }
2478 }
2479
2480 return {'dates_js': original_array, 'dates_str': original_array};
2481 }
2482
2483 // =====================================================================================================================
2484 /* == Auto Fill Fields / Auto Select Dates ==
2485 // ===================================================================================================================== */
2486
2487 jQuery( document ).ready( function (){
2488
2489 var url_params = new URLSearchParams( window.location.search );
2490
2491 // Disable days selection in calendar, after redirection from the "Search results page, after search availability" // FixIn: 8.8.2.3.
2492 if ( 'On' != _wpbc.get_other_param( 'is_enabled_booking_search_results_days_select' ) ) {
2493 if (
2494 ( url_params.has( 'wpbc_select_check_in' ) ) &&
2495 ( url_params.has( 'wpbc_select_check_out' ) ) &&
2496 ( url_params.has( 'wpbc_select_calendar_id' ) )
2497 ){
2498
2499 var select_dates_in_calendar_id = parseInt( url_params.get( 'wpbc_select_calendar_id' ) );
2500
2501 // Fire on all booking dates loaded
2502 jQuery( 'body' ).on( 'wpbc_calendar_ajx__loaded_data', function ( event, loaded_resource_id ){
2503
2504 if ( loaded_resource_id == select_dates_in_calendar_id ){
2505 wpbc_auto_select_dates_in_calendar( select_dates_in_calendar_id, url_params.get( 'wpbc_select_check_in' ), url_params.get( 'wpbc_select_check_out' ) );
2506 }
2507 } );
2508 }
2509 }
2510
2511 if (
2512 url_params.has( 'wpbc_auto_fill' )
2513 && ! jQuery( 'body' ).hasClass( 'wp-admin' )
2514 ){
2515
2516 var wpbc_auto_fill_value = url_params.get( 'wpbc_auto_fill' );
2517
2518 // Convert back. Some systems do not like symbol '~' in URL, so we need to replace to some other symbols
2519 wpbc_auto_fill_value = wpbc_auto_fill_value.replaceAll( '_^_', '~' );
2520
2521 wpbc_auto_fill_booking_fields( wpbc_auto_fill_value );
2522 }
2523
2524 } );
2525
2526 /**
2527 * Autofill / select booking form fields by values from the GET request parameter: ?wpbc_auto_fill=
2528 *
2529 * URL-provided field names are resolved through the exact-name DOM API so
2530 * they can never be interpreted as CSS selector syntax.
2531 *
2532 * @param {string} auto_fill_str Serialized field-name and value pairs.
2533 * @return {void}
2534 */
2535 function wpbc_auto_fill_booking_fields( auto_fill_str ){ // FixIn: 10.0.0.48.
2536
2537 if ( '' == auto_fill_str ){
2538 return;
2539 }
2540
2541 // console.log( 'WPBC_AUTO_FILL_BOOKING_FIELDS( AUTO_FILL_STR )', auto_fill_str);
2542
2543 var fields_arr = wpbc_auto_fill_booking_fields__parse( auto_fill_str );
2544
2545 for ( let i = 0; i < fields_arr.length; i++ ){
2546 var field_name = String( fields_arr[ i ][ 'name' ] || '' );
2547 if ( '' === field_name ) {
2548 continue;
2549 }
2550
2551 var field_elements = document.getElementsByName( field_name );
2552 if ( 0 < field_elements.length ) {
2553 jQuery( field_elements ).val( fields_arr[ i ][ 'value' ] );
2554 }
2555 }
2556 }
2557
2558 /**
2559 * Parse data from get parameter: ?wpbc_auto_fill=visitors231^2~max_capacity231^2
2560 *
2561 * @param data_str = 'visitors231^2~max_capacity231^2';
2562 * @returns {*}
2563 */
2564 function wpbc_auto_fill_booking_fields__parse( data_str ){
2565
2566 var filter_options_arr = [];
2567
2568 var data_arr = data_str.split( '~' );
2569
2570 for ( var j = 0; j < data_arr.length; j++ ){
2571
2572 var my_form_field = data_arr[ j ].split( '^' );
2573
2574 var filter_name = ('undefined' !== typeof (my_form_field[ 0 ])) ? my_form_field[ 0 ] : '';
2575 var filter_value = ('undefined' !== typeof (my_form_field[ 1 ])) ? my_form_field[ 1 ] : '';
2576
2577 filter_options_arr.push(
2578 {
2579 'name' : filter_name,
2580 'value' : filter_value
2581 }
2582 );
2583 }
2584 return filter_options_arr;
2585 }
2586
2587 /**
2588 * Parse data from get parameter: ?search_get__custom_params=...
2589 *
2590 * @param data_str = 'text^search_field__display_check_in^23.05.2024~text^search_field__display_check_out^26.05.2024~selectbox-one^search_quantity^2~selectbox-one^location^Spain~selectbox-one^max_capacity^2~selectbox-one^amenity^parking~checkbox^search_field__extend_search_days^5~submit^^Search~hidden^search_get__check_in_ymd^2024-05-23~hidden^search_get__check_out_ymd^2024-05-26~hidden^search_get__time^~hidden^search_get__quantity^2~hidden^search_get__extend^5~hidden^search_get__users_id^~hidden^search_get__custom_params^~';
2591 * @returns {*}
2592 */
2593 function wpbc_auto_fill_search_fields__parse( data_str ){
2594
2595 var filter_options_arr = [];
2596
2597 var data_arr = data_str.split( '~' );
2598
2599 for ( var j = 0; j < data_arr.length; j++ ){
2600
2601 var my_form_field = data_arr[ j ].split( '^' );
2602
2603 var filter_type = ('undefined' !== typeof (my_form_field[ 0 ])) ? my_form_field[ 0 ] : '';
2604 var filter_name = ('undefined' !== typeof (my_form_field[ 1 ])) ? my_form_field[ 1 ] : '';
2605 var filter_value = ('undefined' !== typeof (my_form_field[ 2 ])) ? my_form_field[ 2 ] : '';
2606
2607 filter_options_arr.push(
2608 {
2609 'type' : filter_type,
2610 'name' : filter_name,
2611 'value' : filter_value
2612 }
2613 );
2614 }
2615 return filter_options_arr;
2616 }
2617
2618
2619 // ---------------------------------------------------------------------------------------------------------------------
2620 /* == Auto Update number of months in calendars ON screen size changed ==
2621 // --------------------------------------------------------------------------------------------------------------------- */
2622
2623 /**
2624 * Auto Update Number of Months in Calendar, e.g.: if ( WINDOW_WIDTH <= 782px ) >>> MONTHS_NUMBER = 1
2625 * ELSE: number of months defined in shortcode.
2626 * @param resource_id int
2627 *
2628 */
2629 function wpbc_calendar__auto_update_months_number__on_resize( resource_id ){
2630
2631 if ( true === _wpbc.get_other_param( 'is_allow_several_months_on_mobile' ) ) {
2632 return false;
2633 }
2634
2635 var local__number_of_months = parseInt( _wpbc.calendar__get_param_value( resource_id, 'calendar_number_of_months' ) );
2636
2637 if ( local__number_of_months > 1 ){
2638
2639 if ( jQuery( window ).width() <= 782 ){
2640 wpbc_calendar__update_months_number( resource_id, 1 );
2641 } else {
2642 wpbc_calendar__update_months_number( resource_id, local__number_of_months );
2643 }
2644
2645 }
2646 }
2647
2648 /**
2649 * Auto Update Number of Months in ALL Calendars
2650 *
2651 */
2652 function wpbc_calendars__auto_update_months_number(){
2653
2654 var all_calendars_arr = _wpbc.calendars_all__get();
2655
2656 // This LOOP "for in" is GOOD, because we check here keys 'calendar_' === calendar_id.slice( 0, 9 )
2657 for ( var calendar_id in all_calendars_arr ){
2658 if ( 'calendar_' === calendar_id.slice( 0, 9 ) ){
2659 var resource_id = parseInt( calendar_id.slice( 9 ) ); // 'calendar_3' -> 3
2660 if ( resource_id > 0 ){
2661 wpbc_calendar__auto_update_months_number__on_resize( resource_id );
2662 }
2663 }
2664 }
2665 }
2666
2667 /**
2668 * If browser window changed, then update number of months.
2669 */
2670 jQuery( window ).on( 'resize', function (){
2671 wpbc_calendars__auto_update_months_number();
2672 } );
2673
2674 /**
2675 * Auto update calendar number of months on initial page load
2676 */
2677 jQuery( document ).ready( function (){
2678 var closed_timer = setTimeout( function (){
2679 wpbc_calendars__auto_update_months_number();
2680 }, 100 );
2681 });
2682
2683 // ---------------------------------------------------------------------------------------------------------------------
2684 /* == Check: calendar_dates_start: "2026-01-01", calendar_dates_end: "2026-12-31" == // FixIn: 10.13.1.4.
2685 // --------------------------------------------------------------------------------------------------------------------- */
2686 /**
2687 * Get Start JS Date of starting dates in calendar, from the _wpbc object.
2688 *
2689 * @param integer resource_id - resource ID, e.g.: 1.
2690 */
2691 function wpbc_calendar__get_dates_start( resource_id ) {
2692 return wpbc_calendar__get_date_parameter( resource_id, 'calendar_dates_start' );
2693 }
2694
2695 /**
2696 * Get End JS Date of ending dates in calendar, from the _wpbc object.
2697 *
2698 * @param integer resource_id - resource ID, e.g.: 1.
2699 */
2700 function wpbc_calendar__get_dates_end(resource_id) {
2701 return wpbc_calendar__get_date_parameter( resource_id, 'calendar_dates_end' );
2702 }
2703
2704 /**
2705 * Get validates date parameter.
2706 *
2707 * @param resource_id - 1
2708 * @param parameter_str - 'calendar_dates_start' | 'calendar_dates_end' | ...
2709 */
2710 function wpbc_calendar__get_date_parameter(resource_id, parameter_str) {
2711
2712 var date_expected_ymd = _wpbc.calendar__get_param_value( resource_id, parameter_str );
2713
2714 if ( ! date_expected_ymd ) {
2715 return false; // '' | 0 | null | undefined -> false.
2716 }
2717
2718 if ( -1 !== date_expected_ymd.indexOf( '-' ) ) {
2719
2720 var date_expected_ymd_arr = date_expected_ymd.split( '-' ); // '2025-07-26' -> ['2025', '07', '26']
2721
2722 if ( date_expected_ymd_arr.length > 0 ) {
2723 var year = (date_expected_ymd_arr.length > 0) ? parseInt( date_expected_ymd_arr[0] ) : new Date().getFullYear(); // Year.
2724 var month = (date_expected_ymd_arr.length > 1) ? (parseInt( date_expected_ymd_arr[1] ) - 1) : 0; // (month - 1) or 0 - Jan.
2725 var day = (date_expected_ymd_arr.length > 2) ? parseInt( date_expected_ymd_arr[2] ) : 1; // date or Otherwise 1st of month
2726
2727 var date_js = new Date( year, month, day, 0, 0, 0, 0 );
2728
2729 return date_js;
2730 }
2731 }
2732
2733 return false; // Fallback, if we not parsed this parameter 'calendar_dates_start' = '2025-07-26', for example because of 'calendar_dates_start' = 'sfsdf'.
2734 }
2735