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 +490 -64 11.0 → 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;
@@ -117,12 +119,16 @@
117 119
118 120 // In case we edit booking in past or have specific parameter in URL.
119 121 var wpbc_edit_booking_hash = _wpbc.get_other_param( 'this_page_booking_hash' );
120 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' );
121 126 var wpbc_is_add_booking_admin_page = ( location.href.indexOf( 'page=wpbc' ) != -1 ) && ( location.href.indexOf( 'tab=add-booking' ) != -1 );
122 - if ( ( wpbc_is_add_booking_admin_page || wpbc_is_edit_booking_context )
127 + if ( ( wpbc_is_add_booking_admin_page || wpbc_is_edit_booking_context || wpbc_is_allow_past_context )
123 128 && (
124 129 wpbc_is_edit_booking_context
130 + || wpbc_is_allow_past_context
125 131 || ( location.href.indexOf('booking_hash') != -1 ) // Comment this line for ability to add booking in past days at Booking > Add booking page.
126 132 || ( location.href.indexOf('allow_past') != -1 ) // FixIn: 10.7.1.2.
127 133 )
128 134 ){
@@ -127,9 +133,10 @@
127 133 )
128 134 ){
129 135 // local__min_date = null;
130 136 // FixIn: 10.14.1.4.
131 - 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 );
132 139 local__max_date = null;
133 140 }
134 141
135 142 var local__start_weekday = _wpbc.calendar__get_param_value( resource_id, 'booking_start_day_weeek' );
@@ -139,10 +146,14 @@
139 146 // -----------------------------------------------------------------------------------------------------------------
140 147 // Show calendar
141 148 // -----------------------------------------------------------------------------------------------------------------
142 149 jQuery('#calendar_booking'+ resource_id).datepick(
143 - {
144 - 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 ){
145 156 return wpbc__calendar__apply_css_to_days( js_date, {'resource_id': resource_id}, this );
146 157 },
147 158 onSelect: function ( string_dates, js_dates_arr ){ /**
148 159 * string_dates = '23.08.2023 - 26.08.2023'
@@ -260,13 +271,96 @@
260 271 }
261 272 }
262 273
263 274 return false;
264 - }
265 -
266 -
267 - /**
268 - * 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
269 363 *
270 364 * @param date - JavaScript Date Obj: Mon Dec 11 2023 00:00:00
271 365 * GMT+0200 (Eastern European Standard Time)
272 366 * @param calendar_params_arr - Calendar Settings Object: {
@@ -311,16 +405,18 @@
311 405 }
312 406 }
313 407
314 408
315 - var is_day_selectable = false;
409 + var is_day_selectable = false;
410 + var date_constraint_result;
316 411
317 412 // If something not defined, then this date closed --------------------------------------------------------------- // FixIn: 10.12.4.6.
318 - if ( (false === date_bookings_obj) || ('undefined' === typeof (date_bookings_obj[resource_id])) ) {
319 -
320 - css_classes__for_date.push( 'date_user_unavailable' );
321 -
322 - 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( ' ' ) ];
323 419 }
324 420
325 421
326 422 // -----------------------------------------------------------------------------------------------------------------
@@ -476,9 +572,11 @@
476 572 css_classes__for_date.push( 'check_out_time_date_approved', 'check_in_time_date_approved' );
477 573 }
478 574 }
479 575
480 - 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( ' ' ) ];
481 579 }
482 580
483 581
484 582 /**
@@ -531,8 +629,9 @@
531 629 // U n h o v e r i n g in UNSELECTABLE_CALENDAR ------------------------------------------------------------
532 630 var is_unselectable_calendar = ( jQuery( '#calendar_booking_unselectable' + resource_id ).length > 0); // FixIn: 8.0.1.2.
533 631 var is_booking_form_exist = ( jQuery( '#booking_form_div' + resource_id ).length > 0 );
534 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 );
535 634
536 635 if ( ( is_unselectable_calendar ) && ( ! is_booking_form_exist ) ){
537 636
538 637 /**
@@ -554,12 +653,13 @@
554 653 if (
555 654 ( location.href.indexOf( 'page=wpbc' ) == -1 )
556 655 || ( ( location.href.indexOf( 'page=wpbc' ) > 0 ) && ( location.href.indexOf( 'tab=add-booking' ) > 0 ) )
557 656 || ( is_add_booking_modal_calendar )
657 + || ( is_admin_calendar_preview )
558 658 || ( location.href.indexOf( 'page=wpbc-setup' ) > 0 )
559 659 || ( location.href.indexOf( 'page=wpbc-availability' ) > 0 )
560 660 || ( ( location.href.indexOf( 'page=wpbc-settings' ) > 0 ) &&
561 - ( location.href.indexOf( '&tab=form' ) > 0 )
661 + ( location.href.indexOf( 'tab=builder_booking_form' ) > 0 )
562 662 )
563 663 ){
564 664 // The same as dates selection, but for days hovering
565 665
@@ -564,8 +664,10 @@
564 664 // The same as dates selection, but for days hovering
565 665
566 666 if ( 'function' == typeof( wpbc__calendar__do_days_highlight__bs ) ){
567 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 );
568 670 }
569 671 }
570 672
571 673 }
@@ -571,8 +673,273 @@
571 673 }
572 674
573 675
574 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 + /**
575 942 * Select calendar date cells
576 943 *
577 944 * @param date - JavaScript Date Obj: Mon Dec 11 2023 00:00:00
578 945 * GMT+0200 (Eastern European Standard Time)
@@ -597,10 +964,16 @@
597 964
598 965 jQuery( '#date_booking' + resource_id ).val( date ); // Add selected dates to hidden textarea
599 966
600 967
601 - 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 + }
602 973
974 + wpbc_range_selection__sync_guidance( resource_id );
975 +
603 976 wpbc_disable_time_fields_in_booking_form( resource_id );
604 977
605 978 // Hook -- trigger day selection -----------------------------------------------------------------------------------
606 979 var mouse_clicked_dates = date; // Can be: "05.10.2023 - 07.10.2023" | "10.10.2023 - 10.10.2023" |
@@ -667,8 +1040,17 @@
667 1040 var is_check_in;
668 1041
669 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 );
670 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 );
671 1053
672 1054 // 4. Loop all time Fields options // FixIn: 10.3.0.2.
673 1055 for ( let field_key = 0; field_key < time_fields_obj_arr.length; field_key++ ){
674 1056
@@ -681,11 +1063,12 @@
681 1063
682 1064 // Get Date: '2023-08-18'.
683 1065 sql_date = selected_dates_arr[i];
684 1066
685 - 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 );
686 1068 // Exception for 'End Time' field, when selected several dates. // FixIn: 10.14.1.5.
687 - 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' )) &&
688 1071 (-1 !== time_fields_obj.name.indexOf( 'endtime' )) &&
689 1072 (selected_dates_arr.length > 1)
690 1073 ) {
691 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 );
@@ -1207,8 +1590,10 @@
1207 1590 if ( 'undefined' === typeof (resource_id) ){
1208 1591 resource_id = '1';
1209 1592 }
1210 1593
1594 + wpbc_range_selection__hide_guidance( resource_id );
1595 +
1211 1596 var inst = wpbc_calendar__get_inst( resource_id )
1212 1597
1213 1598 if ( null !== inst ){
1214 1599
@@ -1276,44 +1661,70 @@
1276 1661 }
1277 1662 return false;
1278 1663 }
1279 1664
1280 - /**
1281 - * Is this date selectable in calendar (mainly it's means AVAILABLE date)
1282 - *
1283 - * @param {int|string} resource_id 1
1284 - * @param {string} sql_class_day '2023-08-11'
1285 - * @returns {boolean} true | false
1286 - */
1287 - 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 + }
1288 1726
1289 - // Get Data --------------------------------------------------------------------------------------------------------
1290 - var date_bookings_obj = _wpbc.bookings_in_calendar__get_for_date( resource_id, sql_class_day );
1291 -
1292 - var is_day_selectable = ( parseInt( date_bookings_obj[ 'day_availability' ] ) > 0 );
1293 -
1294 - if ( typeof (date_bookings_obj[ 'summary' ]) === 'undefined' ){
1295 - return is_day_selectable;
1296 - }
1297 -
1298 - if ( 'available' != date_bookings_obj[ 'summary']['status_for_day' ] ){
1299 -
1300 - 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.
1301 - var booking_statuses_arr = wpbc_get_booking_statuses__as_arr( date_bookings_obj );
1302 -
1303 - if (
1304 - ( wpbc_booking_statuses__has( booking_statuses_arr, 'pending' ) )
1305 - || ( wpbc_booking_statuses__has( booking_statuses_arr, 'pending_pending' ) )
1306 - || ( wpbc_booking_statuses__has( booking_statuses_arr, 'pending_approved' ) )
1307 - || ( wpbc_booking_statuses__has( booking_statuses_arr, 'approved_pending' ) )
1308 - ){
1309 - is_day_selectable = (is_day_selectable) ? true : is_set_pending_days_selectable;
1310 - }
1311 - }
1312 -
1313 - return is_day_selectable;
1314 - }
1315 -
1316 1727 /**
1317 1728 * Is date to check IN array of selected dates
1318 1729 *
1319 1730 * @param {date}js_date_to_check - JS Date - simple JavaScript Date object
@@ -1971,9 +2382,9 @@
1971 2382 // Scroll to specific month, if we set dates in some future months
1972 2383 wpbc_calendar__scroll_to( resource_id, inst.dates[ 0 ].getFullYear(), inst.dates[ 0 ].getMonth()+1 );
1973 2384 }
1974 2385
1975 - return inst.dates.length;
2386 + return wpbc_get__selected_dates_sql__as_arr( resource_id ).length;
1976 2387 }
1977 2388
1978 2389 return 0;
1979 2390 }
@@ -2096,9 +2507,12 @@
2096 2507 } );
2097 2508 }
2098 2509 }
2099 2510
2100 - if ( url_params.has( 'wpbc_auto_fill' ) ){
2511 + if (
2512 + url_params.has( 'wpbc_auto_fill' )
2513 + && ! jQuery( 'body' ).hasClass( 'wp-admin' )
2514 + ){
2101 2515
2102 2516 var wpbc_auto_fill_value = url_params.get( 'wpbc_auto_fill' );
2103 2517
2104 2518 // Convert back. Some systems do not like symbol '~' in URL, so we need to replace to some other symbols
@@ -2111,9 +2525,13 @@
2111 2525
2112 2526 /**
2113 2527 * Autofill / select booking form fields by values from the GET request parameter: ?wpbc_auto_fill=
2114 2528 *
2115 - * @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}
2116 2534 */
2117 2535 function wpbc_auto_fill_booking_fields( auto_fill_str ){ // FixIn: 10.0.0.48.
2118 2536
2119 2537 if ( '' == auto_fill_str ){
@@ -2121,14 +2539,22 @@
2121 2539 }
2122 2540
2123 2541 // console.log( 'WPBC_AUTO_FILL_BOOKING_FIELDS( AUTO_FILL_STR )', auto_fill_str);
2124 2542
2125 - var fields_arr = wpbc_auto_fill_booking_fields__parse( auto_fill_str );
2126 -
2127 - for ( let i = 0; i < fields_arr.length; i++ ){
2128 - jQuery( '[name="' + fields_arr[ i ][ 'name' ] + '"]' ).val( fields_arr[ i ][ 'value' ] );
2129 - }
2130 -}
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 +}
2131 2557
2132 2558 /**
2133 2559 * Parse data from get parameter: ?wpbc_auto_fill=visitors231^2~max_capacity231^2
2134 2560 *