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 +264 -82 11.4.1 → 11.9 View file →
@@ -1,4 +1,4 @@
1 1 /**
2 2 * =====================================================================================================================
3 3 * includes/__js/cal/wpbc_cal.js
4 4 * =====================================================================================================================
@@ -76,9 +76,9 @@
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__hide_guidance( resource_id );
80 + wpbc_range_selection__prepare_guidance( resource_id );
81 81
82 82
83 83
84 84 // -----------------------------------------------------------------------------------------------------------------
@@ -146,10 +146,14 @@
146 146 // -----------------------------------------------------------------------------------------------------------------
147 147 // Show calendar
148 148 // -----------------------------------------------------------------------------------------------------------------
149 149 jQuery('#calendar_booking'+ resource_id).datepick(
150 - {
151 - 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 ){
152 156 return wpbc__calendar__apply_css_to_days( js_date, {'resource_id': resource_id}, this );
153 157 },
154 158 onSelect: function ( string_dates, js_dates_arr ){ /**
155 159 * string_dates = '23.08.2023 - 26.08.2023'
@@ -267,13 +271,96 @@
267 271 }
268 272 }
269 273
270 274 return false;
271 - }
272 -
273 -
274 - /**
275 - * Apply CSS to calendar date cells
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
276 363 *
277 364 * @param date - JavaScript Date Obj: Mon Dec 11 2023 00:00:00
278 365 * GMT+0200 (Eastern European Standard Time)
279 366 * @param calendar_params_arr - Calendar Settings Object: {
@@ -318,16 +405,18 @@
318 405 }
319 406 }
320 407
321 408
322 - var is_day_selectable = false;
409 + var is_day_selectable = false;
410 + var date_constraint_result;
323 411
324 412 // If something not defined, then this date closed --------------------------------------------------------------- // FixIn: 10.12.4.6.
325 - if ( (false === date_bookings_obj) || ('undefined' === typeof (date_bookings_obj[resource_id])) ) {
326 -
327 - css_classes__for_date.push( 'date_user_unavailable' );
328 -
329 - 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( ' ' ) ];
330 419 }
331 420
332 421
333 422 // -----------------------------------------------------------------------------------------------------------------
@@ -483,9 +572,11 @@
483 572 css_classes__for_date.push( 'check_out_time_date_approved', 'check_in_time_date_approved' );
484 573 }
485 574 }
486 575
487 - 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( ' ' ) ];
488 579 }
489 580
490 581
491 582 /**
@@ -704,33 +795,21 @@
704 795 }
705 796
706 797
707 798 /**
708 - * Remove the range-selection guidance belonging to one calendar.
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.
709 801 *
710 802 * @param {number|string} resource_id Booking resource ID.
803 + * @returns {jQuery} The prepared note, or an empty collection when guidance is unavailable.
711 804 */
712 - function wpbc_range_selection__hide_guidance( resource_id ){
713 - jQuery( '#wpbc_range_selection_guidance' + resource_id ).remove();
714 - }
805 + function wpbc_range_selection__prepare_guidance( resource_id ){
715 806
716 -
717 - /**
718 - * Show a persistent informational note after the first range click.
719 - * The calendar parameter can be set to false by PHP or JavaScript to suppress this UI per resource.
720 - *
721 - * @param {number|string} resource_id Booking resource ID.
722 - * @returns {boolean} True when the note was inserted.
723 - */
724 - function wpbc_range_selection__show_guidance( resource_id ){
725 -
726 807 var is_enabled = _wpbc.calendar__get_param_value( resource_id, 'range_selection_guidance_is_enabled' );
727 808 var $booking_form = jQuery( '#booking_form_div' + resource_id );
728 809 var $calendar_frame;
729 - var $note;
810 + var $note = jQuery( '#wpbc_range_selection_guidance' + resource_id );
730 811
731 - wpbc_range_selection__hide_guidance( resource_id );
732 -
733 812 if (
734 813 false === is_enabled
735 814 || 0 === is_enabled
736 815 || '0' === is_enabled
@@ -738,11 +817,17 @@
738 817 || 'false' === String( is_enabled ).toLowerCase()
739 818 || 'dynamic' !== _wpbc.calendar__get_param_value( resource_id, 'days_select_mode' )
740 819 || 0 === $booking_form.length
741 820 ) {
742 - return false;
821 + $note.remove();
822 + return jQuery();
743 823 }
744 824
825 + if ( 0 < $note.length ) {
826 + $note.stop( true, true ).hide().attr( 'aria-hidden', 'true' );
827 + return $note;
828 + }
829 +
745 830 $calendar_frame = $booking_form.find( '.block_hints.datepick' ).first();
746 831 if ( 0 === $calendar_frame.length ) {
747 832 $calendar_frame = $booking_form.find( '.wpbc_cal_powered_by' ).first();
748 833 if ( 0 === $calendar_frame.length ) {
@@ -747,9 +832,9 @@
747 832 $calendar_frame = $booking_form.find( '.wpbc_cal_powered_by' ).first();
748 833 if ( 0 === $calendar_frame.length ) {
749 834 $calendar_frame = $booking_form.find( '.bk_calendar_frame' ).first();
750 835 if ( 0 === $calendar_frame.length ) {
751 - return false;
836 + return jQuery();
752 837 }
753 838 }
754 839 }
755 840
@@ -756,9 +841,11 @@
756 841 $note = jQuery( '<div>', {
757 842 'id': 'wpbc_range_selection_guidance' + resource_id,
758 843 'class': 'wpbc_range_selection_guidance wpbc_front_end__message wpbc_fe_message_info',
759 844 'role': 'status',
760 - 'aria-live': 'polite'
845 + 'aria-live': 'polite',
846 + 'aria-hidden': 'true',
847 + 'style': 'display: none;'
761 848 } );
762 849 $note.append( jQuery( '<i>', {
763 850 'class': 'menu_icon icon-1x wpbc_icn_info_outline',
764 851 'aria-hidden': 'true'
@@ -765,8 +852,65 @@
765 852 } ) );
766 853 $note.append( jQuery( '<span>' ).text( _wpbc.get_message( 'message_range_selection_click_last_date' ) ) );
767 854 $calendar_frame.after( $note );
768 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 +
769 913 return true;
770 914 }
771 915
772 916
@@ -1517,47 +1661,70 @@
1517 1661 }
1518 1662 return false;
1519 1663 }
1520 1664
1521 - /**
1522 - * Is this date selectable in calendar (mainly it's means AVAILABLE date)
1523 - *
1524 - * @param {int|string} resource_id 1
1525 - * @param {string} sql_class_day '2023-08-11'
1526 - * @returns {boolean} true | false
1527 - */
1528 - 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 + }
1529 1726
1530 - // Get Data --------------------------------------------------------------------------------------------------------
1531 - var date_bookings_obj = _wpbc.bookings_in_calendar__get_for_date( resource_id, sql_class_day );
1532 - if ( ! date_bookings_obj || 'undefined' === typeof ( date_bookings_obj[ 'day_availability' ] ) ) {
1533 - return false;
1534 - }
1535 -
1536 - var is_day_selectable = ( parseInt( date_bookings_obj[ 'day_availability' ] ) > 0 );
1537 -
1538 - if ( typeof (date_bookings_obj[ 'summary' ]) === 'undefined' ){
1539 - return is_day_selectable;
1540 - }
1541 -
1542 - if ( 'available' != date_bookings_obj[ 'summary']['status_for_day' ] ){
1543 -
1544 - 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.
1545 - var booking_statuses_arr = wpbc_get_booking_statuses__as_arr( date_bookings_obj );
1546 -
1547 - if (
1548 - ( wpbc_booking_statuses__has( booking_statuses_arr, 'pending' ) )
1549 - || ( wpbc_booking_statuses__has( booking_statuses_arr, 'pending_pending' ) )
1550 - || ( wpbc_booking_statuses__has( booking_statuses_arr, 'pending_approved' ) )
1551 - || ( wpbc_booking_statuses__has( booking_statuses_arr, 'approved_pending' ) )
1552 - ){
1553 - is_day_selectable = (is_day_selectable) ? true : is_set_pending_days_selectable;
1554 - }
1555 - }
1556 -
1557 - return is_day_selectable;
1558 - }
1559 -
1560 1727 /**
1561 1728 * Is date to check IN array of selected dates
1562 1729 *
1563 1730 * @param {date}js_date_to_check - JS Date - simple JavaScript Date object
@@ -2215,9 +2382,9 @@
2215 2382 // Scroll to specific month, if we set dates in some future months
2216 2383 wpbc_calendar__scroll_to( resource_id, inst.dates[ 0 ].getFullYear(), inst.dates[ 0 ].getMonth()+1 );
2217 2384 }
2218 2385
2219 - return inst.dates.length;
2386 + return wpbc_get__selected_dates_sql__as_arr( resource_id ).length;
2220 2387 }
2221 2388
2222 2389 return 0;
2223 2390 }
@@ -2340,9 +2507,12 @@
2340 2507 } );
2341 2508 }
2342 2509 }
2343 2510
2344 - if ( url_params.has( 'wpbc_auto_fill' ) ){
2511 + if (
2512 + url_params.has( 'wpbc_auto_fill' )
2513 + && ! jQuery( 'body' ).hasClass( 'wp-admin' )
2514 + ){
2345 2515
2346 2516 var wpbc_auto_fill_value = url_params.get( 'wpbc_auto_fill' );
2347 2517
2348 2518 // Convert back. Some systems do not like symbol '~' in URL, so we need to replace to some other symbols
@@ -2355,9 +2525,13 @@
2355 2525
2356 2526 /**
2357 2527 * Autofill / select booking form fields by values from the GET request parameter: ?wpbc_auto_fill=
2358 2528 *
2359 - * @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}
2360 2534 */
2361 2535 function wpbc_auto_fill_booking_fields( auto_fill_str ){ // FixIn: 10.0.0.48.
2362 2536
2363 2537 if ( '' == auto_fill_str ){
@@ -2365,14 +2539,22 @@
2365 2539 }
2366 2540
2367 2541 // console.log( 'WPBC_AUTO_FILL_BOOKING_FIELDS( AUTO_FILL_STR )', auto_fill_str);
2368 2542
2369 - var fields_arr = wpbc_auto_fill_booking_fields__parse( auto_fill_str );
2370 -
2371 - for ( let i = 0; i < fields_arr.length; i++ ){
2372 - jQuery( '[name="' + fields_arr[ i ][ 'name' ] + '"]' ).val( fields_arr[ i ][ 'value' ] );
2373 - }
2374 -}
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 +}
2375 2557
2376 2558 /**
2377 2559 * Parse data from get parameter: ?wpbc_auto_fill=visitors231^2~max_capacity231^2
2378 2560 *