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
← All changes | includes/__js/client/cal/wpbc_cal.js +598 -110 10.15.7 → 11.9 View file →
@@ -1,4 +1,4 @@
1 1 /**
2 2 * =====================================================================================================================
3 3 * includes/__js/cal/wpbc_cal.js
4 4 * =====================================================================================================================
@@ -76,10 +76,12 @@
76 76 // Stale marker: remove and continue with init.
77 77 jQuery( '#calendar_booking' + resource_id ).removeClass( 'hasDatepick' );
78 78 }
79 79
80 + wpbc_range_selection__prepare_guidance( resource_id );
80 81
81 82
83 +
82 84 // -----------------------------------------------------------------------------------------------------------------
83 85 // Days selection
84 86 // -----------------------------------------------------------------------------------------------------------------
85 87 var local__is_range_select = false;
@@ -115,17 +117,26 @@
115 117 local__max_date = wpbc_calendar__get_dates_end( resource_id ); // E.g. - local__max_date = new Date( 2025, 11, 31 );
116 118 }
117 119
118 120 // In case we edit booking in past or have specific parameter in URL.
119 - if ( ( location.href.indexOf('page=wpbc-new') != -1 )
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 )
120 128 && (
121 - ( location.href.indexOf('booking_hash') != -1 ) // Comment this line for ability to add booking in past days at Booking > Add booking page.
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.
122 132 || ( location.href.indexOf('allow_past') != -1 ) // FixIn: 10.7.1.2.
123 133 )
124 134 ){
125 135 // local__min_date = null;
126 136 // FixIn: 10.14.1.4.
127 - local__min_date = 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 );
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 );
128 139 local__max_date = null;
129 140 }
130 141
131 142 var local__start_weekday = _wpbc.calendar__get_param_value( resource_id, 'booking_start_day_weeek' );
@@ -135,10 +146,14 @@
135 146 // -----------------------------------------------------------------------------------------------------------------
136 147 // Show calendar
137 148 // -----------------------------------------------------------------------------------------------------------------
138 149 jQuery('#calendar_booking'+ resource_id).datepick(
139 - {
140 - beforeShowDay: function ( js_date ){
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 ){
141 156 return wpbc__calendar__apply_css_to_days( js_date, {'resource_id': resource_id}, this );
142 157 },
143 158 onSelect: function ( string_dates, js_dates_arr ){ /**
144 159 * string_dates = '23.08.2023 - 26.08.2023'
@@ -181,14 +196,14 @@
181 196 }
182 197 );
183 198
184 199
185 -
200 +
186 201 // -----------------------------------------------------------------------------------------------------------------
187 202 // Clear today date highlighting
188 203 // -----------------------------------------------------------------------------------------------------------------
189 204 setTimeout( function (){ wpbc_calendars__clear_days_highlighting( resource_id ); }, 500 ); // FixIn: 7.1.2.8.
190 -
205 +
191 206 // -----------------------------------------------------------------------------------------------------------------
192 207 // Scroll calendar to specific month
193 208 // -----------------------------------------------------------------------------------------------------------------
194 209 var start_bk_month = _wpbc.calendar__get_param_value( resource_id, 'calendar_scroll_to' );
@@ -194,14 +209,159 @@
194 209 var start_bk_month = _wpbc.calendar__get_param_value( resource_id, 'calendar_scroll_to' );
195 210 if ( false !== start_bk_month ){
196 211 wpbc_calendar__scroll_to( resource_id, start_bk_month[ 0 ], start_bk_month[ 1 ] );
197 212 }
198 -}
213 + }
199 214
200 215
201 216 /**
202 - * Apply CSS to calendar date cells
217 + * Get booking statuses as array from the structured summary field, with fallback to the legacy string field.
203 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 + *
204 364 * @param date - JavaScript Date Obj: Mon Dec 11 2023 00:00:00
205 365 * GMT+0200 (Eastern European Standard Time)
206 366 * @param calendar_params_arr - Calendar Settings Object: {
207 367 * "resource_id": 4
@@ -245,16 +405,18 @@
245 405 }
246 406 }
247 407
248 408
249 - var is_day_selectable = false;
409 + var is_day_selectable = false;
410 + var date_constraint_result;
250 411
251 412 // If something not defined, then this date closed --------------------------------------------------------------- // FixIn: 10.12.4.6.
252 - if ( (false === date_bookings_obj) || ('undefined' === typeof (date_bookings_obj[resource_id])) ) {
253 -
254 - css_classes__for_date.push( 'date_user_unavailable' );
255 -
256 - return [ is_day_selectable, css_classes__for_date.join(' ') ];
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( ' ' ) ];
257 419 }
258 420
259 421
260 422 // -----------------------------------------------------------------------------------------------------------------
@@ -259,8 +421,9 @@
259 421
260 422 // -----------------------------------------------------------------------------------------------------------------
261 423 // date_bookings_obj - Defined. Dates can be selectable.
262 424 // -----------------------------------------------------------------------------------------------------------------
425 + var booking_statuses_arr = wpbc_get_booking_statuses__as_arr( date_bookings_obj );
263 426
264 427 // -----------------------------------------------------------------------------------------------------------------
265 428 // Add season names to the day CSS classes -- it is required for correct work of conditional fields --------------
266 429 var season_names_arr = _wpbc.seasons__get_for_date( resource_id, sql_class_day );
@@ -301,28 +464,38 @@
301 464
302 465 case 'season_filter':
303 466 css_classes__for_date.push( 'date_user_unavailable', 'season_unavailable' );
304 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 = [];
305 470 break;
306 471
307 472 case 'resource_availability':
308 473 css_classes__for_date.push( 'date_user_unavailable', 'resource_unavailable' );
309 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 = [];
310 477 break;
311 478
312 479 case 'weekday_unavailable':
313 480 css_classes__for_date.push( 'date_user_unavailable', 'weekday_unavailable' );
314 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 = [];
315 484 break;
316 485
317 486 case 'from_today_unavailable':
318 487 css_classes__for_date.push( 'date_user_unavailable', 'from_today_unavailable' );
319 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 = [];
320 491 break;
321 492
322 493 case 'limit_available_from_today':
323 494 css_classes__for_date.push( 'date_user_unavailable', 'limit_available_from_today' );
324 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 = [];
325 498 break;
326 499
327 500 case 'change_over':
328 501 /*
@@ -334,12 +507,12 @@
334 507 */
335 508
336 509 css_classes__for_date.push( 'timespartly', 'check_in_time', 'check_out_time' );
337 510 // FixIn: 10.0.0.2.
338 - if ( date_bookings_obj[ 'summary' ][ 'status_for_bookings' ].indexOf( 'approved_pending' ) > -1 ){
511 + if ( wpbc_booking_statuses__has( booking_statuses_arr, 'approved_pending' ) ){
339 512 css_classes__for_date.push( 'check_out_time_date_approved', 'check_in_time_date2approve' );
340 513 }
341 - if ( date_bookings_obj[ 'summary' ][ 'status_for_bookings' ].indexOf( 'pending_approved' ) > -1 ){
514 + if ( wpbc_booking_statuses__has( booking_statuses_arr, 'pending_approved' ) ){
342 515 css_classes__for_date.push( 'check_out_time_date2approve', 'check_in_time_date_approved' );
343 516 }
344 517 break;
345 518
@@ -346,11 +519,11 @@
346 519 case 'check_in':
347 520 css_classes__for_date.push( 'timespartly', 'check_in_time' );
348 521
349 522 // FixIn: 9.9.0.33.
350 - if ( date_bookings_obj[ 'summary' ][ 'status_for_bookings' ].indexOf( 'pending' ) > -1 ){
523 + if ( wpbc_booking_statuses__has_part( booking_statuses_arr, 'pending' ) ){
351 524 css_classes__for_date.push( 'check_in_time_date2approve' );
352 - } else if ( date_bookings_obj[ 'summary' ][ 'status_for_bookings' ].indexOf( 'approved' ) > -1 ){
525 + } else if ( wpbc_booking_statuses__has_part( booking_statuses_arr, 'approved' ) ){
353 526 css_classes__for_date.push( 'check_in_time_date_approved' );
354 527 }
355 528 break;
356 529
@@ -357,11 +530,11 @@
357 530 case 'check_out':
358 531 css_classes__for_date.push( 'timespartly', 'check_out_time' );
359 532
360 533 // FixIn: 9.9.0.33.
361 - if ( date_bookings_obj[ 'summary' ][ 'status_for_bookings' ].indexOf( 'pending' ) > -1 ){
534 + if ( wpbc_booking_statuses__has_part( booking_statuses_arr, 'pending' ) ){
362 535 css_classes__for_date.push( 'check_out_time_date2approve' );
363 - } else if ( date_bookings_obj[ 'summary' ][ 'status_for_bookings' ].indexOf( 'approved' ) > -1 ){
536 + } else if ( wpbc_booking_statuses__has_part( booking_statuses_arr, 'approved' ) ){
364 537 css_classes__for_date.push( 'check_out_time_date_approved' );
365 538 }
366 539 break;
367 540
@@ -375,49 +548,35 @@
375 548 if ( 'available' != date_bookings_obj[ 'summary']['status_for_day' ] ){
376 549
377 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.
378 551
379 - switch ( date_bookings_obj[ 'summary']['status_for_bookings' ] ){
380 -
381 - case '':
382 - // Usually it's means that day is available or unavailable without the bookings
383 - break;
384 -
385 - case 'pending':
386 - css_classes__for_date.push( 'date2approve' );
387 - is_day_selectable = (is_day_selectable) ? true : is_set_pending_days_selectable;
388 - break;
389 -
390 - case 'approved':
391 - css_classes__for_date.push( 'date_approved' );
392 - break;
393 -
394 - // Situations for "change-over" days: ----------------------------------------------------------------------
395 - case 'pending_pending':
396 - css_classes__for_date.push( 'check_out_time_date2approve', 'check_in_time_date2approve' );
397 - is_day_selectable = (is_day_selectable) ? true : is_set_pending_days_selectable;
398 - break;
399 -
400 - case 'pending_approved':
401 - css_classes__for_date.push( 'check_out_time_date2approve', 'check_in_time_date_approved' );
402 - is_day_selectable = (is_day_selectable) ? true : is_set_pending_days_selectable;
403 - break;
404 -
405 - case 'approved_pending':
406 - css_classes__for_date.push( 'check_out_time_date_approved', 'check_in_time_date2approve' );
407 - is_day_selectable = (is_day_selectable) ? true : is_set_pending_days_selectable;
408 - break;
409 -
410 - case 'approved_approved':
411 - css_classes__for_date.push( 'check_out_time_date_approved', 'check_in_time_date_approved' );
412 - break;
413 -
414 - default:
415 -
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;
416 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 + }
417 574 }
418 575
419 - return [ is_day_selectable, css_classes__for_date.join( ' ' ) ];
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( ' ' ) ];
420 579 }
421 580
422 581
423 582 /**
@@ -469,8 +628,10 @@
469 628
470 629 // U n h o v e r i n g in UNSELECTABLE_CALENDAR ------------------------------------------------------------
471 630 var is_unselectable_calendar = ( jQuery( '#calendar_booking_unselectable' + resource_id ).length > 0); // FixIn: 8.0.1.2.
472 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 );
473 634
474 635 if ( ( is_unselectable_calendar ) && ( ! is_booking_form_exist ) ){
475 636
476 637 /**
@@ -490,13 +651,15 @@
490 651
491 652 // D a y s H o v e r i n g ------------------------------------------------------------------------------------
492 653 if (
493 654 ( location.href.indexOf( 'page=wpbc' ) == -1 )
494 - || ( location.href.indexOf( 'page=wpbc-new' ) > 0 )
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 )
495 658 || ( location.href.indexOf( 'page=wpbc-setup' ) > 0 )
496 659 || ( location.href.indexOf( 'page=wpbc-availability' ) > 0 )
497 660 || ( ( location.href.indexOf( 'page=wpbc-settings' ) > 0 ) &&
498 - ( location.href.indexOf( '&tab=form' ) > 0 )
661 + ( location.href.indexOf( 'tab=builder_booking_form' ) > 0 )
499 662 )
500 663 ){
501 664 // The same as dates selection, but for days hovering
502 665
@@ -501,8 +664,10 @@
501 664 // The same as dates selection, but for days hovering
502 665
503 666 if ( 'function' == typeof( wpbc__calendar__do_days_highlight__bs ) ){
504 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 );
505 670 }
506 671 }
507 672
508 673 }
@@ -508,8 +673,273 @@
508 673 }
509 674
510 675
511 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 + /**
512 942 * Select calendar date cells
513 943 *
514 944 * @param date - JavaScript Date Obj: Mon Dec 11 2023 00:00:00
515 945 * GMT+0200 (Eastern European Standard Time)
@@ -534,10 +964,16 @@
534 964
535 965 jQuery( '#date_booking' + resource_id ).val( date ); // Add selected dates to hidden textarea
536 966
537 967
538 - if ( 'function' === typeof (wpbc__calendar__do_days_select__bs) ){ wpbc__calendar__do_days_select__bs( date, resource_id ); }
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 + }
539 973
974 + wpbc_range_selection__sync_guidance( resource_id );
975 +
540 976 wpbc_disable_time_fields_in_booking_form( resource_id );
541 977
542 978 // Hook -- trigger day selection -----------------------------------------------------------------------------------
543 979 var mouse_clicked_dates = date; // Can be: "05.10.2023 - 07.10.2023" | "10.10.2023 - 10.10.2023" |
@@ -604,8 +1040,17 @@
604 1040 var is_check_in;
605 1041
606 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 );
607 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 );
608 1053
609 1054 // 4. Loop all time Fields options // FixIn: 10.3.0.2.
610 1055 for ( let field_key = 0; field_key < time_fields_obj_arr.length; field_key++ ){
611 1056
@@ -618,11 +1063,12 @@
618 1063
619 1064 // Get Date: '2023-08-18'.
620 1065 sql_date = selected_dates_arr[i];
621 1066
622 - var is_time_in_past = wpbc_check_is_time_in_past( today_time__shift, sql_date, time_fields_obj );
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 );
623 1068 // Exception for 'End Time' field, when selected several dates. // FixIn: 10.14.1.5.
624 - if ( ('On' !== _wpbc.calendar__get_param_value( resource_id, 'booking_recurrent_time' )) &&
1069 + if ( ( ! is_allow_past_context ) &&
1070 + ('On' !== _wpbc.calendar__get_param_value( resource_id, 'booking_recurrent_time' )) &&
625 1071 (-1 !== time_fields_obj.name.indexOf( 'endtime' )) &&
626 1072 (selected_dates_arr.length > 1)
627 1073 ) {
628 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 );
@@ -1144,8 +1590,10 @@
1144 1590 if ( 'undefined' === typeof (resource_id) ){
1145 1591 resource_id = '1';
1146 1592 }
1147 1593
1594 + wpbc_range_selection__hide_guidance( resource_id );
1595 +
1148 1596 var inst = wpbc_calendar__get_inst( resource_id )
1149 1597
1150 1598 if ( null !== inst ){
1151 1599
@@ -1213,45 +1661,70 @@
1213 1661 }
1214 1662 return false;
1215 1663 }
1216 1664
1217 - /**
1218 - * Is this date selectable in calendar (mainly it's means AVAILABLE date)
1219 - *
1220 - * @param {int|string} resource_id 1
1221 - * @param {string} sql_class_day '2023-08-11'
1222 - * @returns {boolean} true | false
1223 - */
1224 - function wpbc_is_this_day_selectable( resource_id, sql_class_day ){
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 + }
1225 1726
1226 - // Get Data --------------------------------------------------------------------------------------------------------
1227 - var date_bookings_obj = _wpbc.bookings_in_calendar__get_for_date( resource_id, sql_class_day );
1228 -
1229 - var is_day_selectable = ( parseInt( date_bookings_obj[ 'day_availability' ] ) > 0 );
1230 -
1231 - if ( typeof (date_bookings_obj[ 'summary' ]) === 'undefined' ){
1232 - return is_day_selectable;
1233 - }
1234 -
1235 - if ( 'available' != date_bookings_obj[ 'summary']['status_for_day' ] ){
1236 -
1237 - 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.
1238 -
1239 - switch ( date_bookings_obj[ 'summary']['status_for_bookings' ] ){
1240 - case 'pending':
1241 - // Situations for "change-over" days:
1242 - case 'pending_pending':
1243 - case 'pending_approved':
1244 - case 'approved_pending':
1245 - is_day_selectable = (is_day_selectable) ? true : is_set_pending_days_selectable;
1246 - break;
1247 - default:
1248 - }
1249 - }
1250 -
1251 - return is_day_selectable;
1252 - }
1253 -
1254 1727 /**
1255 1728 * Is date to check IN array of selected dates
1256 1729 *
1257 1730 * @param {date}js_date_to_check - JS Date - simple JavaScript Date object
@@ -1909,9 +2382,9 @@
1909 2382 // Scroll to specific month, if we set dates in some future months
1910 2383 wpbc_calendar__scroll_to( resource_id, inst.dates[ 0 ].getFullYear(), inst.dates[ 0 ].getMonth()+1 );
1911 2384 }
1912 2385
1913 - return inst.dates.length;
2386 + return wpbc_get__selected_dates_sql__as_arr( resource_id ).length;
1914 2387 }
1915 2388
1916 2389 return 0;
1917 2390 }
@@ -2034,9 +2507,12 @@
2034 2507 } );
2035 2508 }
2036 2509 }
2037 2510
2038 - if ( url_params.has( 'wpbc_auto_fill' ) ){
2511 + if (
2512 + url_params.has( 'wpbc_auto_fill' )
2513 + && ! jQuery( 'body' ).hasClass( 'wp-admin' )
2514 + ){
2039 2515
2040 2516 var wpbc_auto_fill_value = url_params.get( 'wpbc_auto_fill' );
2041 2517
2042 2518 // Convert back. Some systems do not like symbol '~' in URL, so we need to replace to some other symbols
@@ -2049,9 +2525,13 @@
2049 2525
2050 2526 /**
2051 2527 * Autofill / select booking form fields by values from the GET request parameter: ?wpbc_auto_fill=
2052 2528 *
2053 - * @param auto_fill_str
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}
2054 2534 */
2055 2535 function wpbc_auto_fill_booking_fields( auto_fill_str ){ // FixIn: 10.0.0.48.
2056 2536
2057 2537 if ( '' == auto_fill_str ){
@@ -2059,15 +2539,23 @@
2059 2539 }
2060 2540
2061 2541 // console.log( 'WPBC_AUTO_FILL_BOOKING_FIELDS( AUTO_FILL_STR )', auto_fill_str);
2062 2542
2063 - var fields_arr = wpbc_auto_fill_booking_fields__parse( auto_fill_str );
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 +}
2064 2557
2065 - for ( let i = 0; i < fields_arr.length; i++ ){
2066 - jQuery( '[name="' + fields_arr[ i ][ 'name' ] + '"]' ).val( fields_arr[ i ][ 'value' ] );
2067 - }
2068 -}
2069 -
2070 2558 /**
2071 2559 * Parse data from get parameter: ?wpbc_auto_fill=visitors231^2~max_capacity231^2
2072 2560 *
2073 2561 * @param data_str = 'visitors231^2~max_capacity231^2';
@@ -2242,5 +2730,5 @@
2242 2730 }
2243 2731 }
2244 2732
2245 2733 return false; // Fallback, if we not parsed this parameter 'calendar_dates_start' = '2025-07-26', for example because of 'calendar_dates_start' = 'sfsdf'.
2246 -}
2734 +}