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/_capacity/create_booking.php +184 -133 11.8 → 11.9 View file →
@@ -213,10 +213,94 @@
213 213 // ---------------------------------------------------------------------------------------------------------------------
214 214 // == Save Booking
215 215 // ---------------------------------------------------------------------------------------------------------------------
216 216
217 -/**
218 - * Save Booking - ADD NEW or UPDATE exist booking
217 +/**
218 + * Resolve and validate the final booking destination against current storage.
219 + *
220 + * This function contains the availability, Appointment working-time, and
221 + * Appointment buffer checks that must be repeated if the database connection
222 + * loses its advisory lock before persistence. The caller clears the relevant
223 + * request-local cache before every invocation.
224 + *
225 + * @param array $local_params Parsed booking parameters, passed by reference because force-save mode fixes capacity at one.
226 + * @param array $cleaned_params Sanitized booking request parameters.
227 + * @param array $php_performance Performance measurements, passed by reference.
228 + *
229 + * @return array|WP_Error Validated storage destination, or a visitor-safe validation error.
230 + */
231 +function wpbc_booking_validate_save_availability( &$local_params, $cleaned_params, &$php_performance ) {
232 +
233 + // Privileged imports and other established integrations may intentionally force a save.
234 + if ( ! empty( $cleaned_params['save_booking_even_if_unavailable'] ) ) {
235 + $local_params['how_many_items_to_book'] = 1;
236 + $dates_keys_arr = array_values( $local_params['dates_only_sql_arr'] );
237 + $resources_in_dates = array_fill_keys( $dates_keys_arr, array( $local_params['initial_resource_id'] ) );
238 + $where_to_save_booking = array(
239 + 'result' => 'ok',
240 + 'resources_in_dates' => $resources_in_dates,
241 + 'time_to_book' => $local_params['time_as_his_arr'],
242 + 'main__resource_id' => $local_params['initial_resource_id'],
243 + );
244 + } else {
245 + $php_performance = wpbc_php_performance_START( 'wpbc__where_to_save_booking', $php_performance );
246 +
247 + $where_to_save_booking = wpbc__where_to_save_booking(
248 + array(
249 + 'resource_id' => $local_params['initial_resource_id'],
250 + 'skip_booking_id' => $local_params['skip_booking_id'],
251 + 'dates_only_sql_arr' => $local_params['dates_only_sql_arr'],
252 + 'time_as_seconds_arr' => $local_params['time_as_seconds_arr'],
253 + 'how_many_items_to_book' => $local_params['how_many_items_to_book'],
254 + 'request_uri' => $cleaned_params['request_uri'],
255 + 'allow_past' => ! empty( $cleaned_params['allow_past'] ),
256 + 'is_use_booking_recurrent_time' => $local_params['is_use_booking_recurrent_time'],
257 + 'time_override_source' => ! empty( $local_params['time_override_arr']['source'] ) ? $local_params['time_override_arr']['source'] : '',
258 + 'as_single_resource' => false,
259 + 'aggregate_resource_id_arr' => $local_params['aggregate_resource_id_arr'],
260 + 'aggregate_type' => $cleaned_params['aggregate_type'],
261 + 'custom_form' => $cleaned_params['custom_form'],
262 + )
263 + );
264 +
265 + if ( 'error' === $where_to_save_booking['result'] ) {
266 + return new WP_Error( 'booking_can_not_save', $where_to_save_booking['message'] );
267 + }
268 +
269 + $php_performance = wpbc_php_performance_END( 'wpbc__where_to_save_booking', $php_performance );
270 + }
271 +
272 + if ( ! empty( $local_params['appointment_service'] ) && function_exists( 'wpbc_appointment_services_check_working_time' ) ) {
273 + $working_time_check = wpbc_appointment_services_check_working_time(
274 + $local_params['appointment_service'],
275 + $where_to_save_booking['main__resource_id'],
276 + array_keys( $where_to_save_booking['resources_in_dates'] ),
277 + $local_params['time_as_seconds_arr']
278 + );
279 + if ( is_wp_error( $working_time_check ) ) {
280 + return $working_time_check;
281 + }
282 + }
283 +
284 + if ( ! empty( $local_params['appointment_service'] ) && function_exists( 'wpbc_appointment_services_check_buffer_conflicts' ) ) {
285 + $buffer_check = wpbc_appointment_services_check_buffer_conflicts(
286 + $local_params['appointment_service'],
287 + $where_to_save_booking['main__resource_id'],
288 + array_keys( $where_to_save_booking['resources_in_dates'] ),
289 + $local_params['time_as_seconds_arr'],
290 + $local_params['skip_booking_id']
291 + );
292 + if ( is_wp_error( $buffer_check ) ) {
293 + return $buffer_check;
294 + }
295 + }
296 +
297 + return $where_to_save_booking;
298 +}
299 +
300 +
301 +/**
302 + * Save Booking - ADD NEW or UPDATE exist booking
219 303 *
220 304 * @param $request_params = [
221 305 * resource_id = 2 REQUIRED Default: 1
222 306 * dates_ddmmyy_csv = '27.10.2023, 28.10.2023, 29.10.2023' REQUIRED
@@ -521,16 +605,18 @@
521 605 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
522 606 return array( 'ajx_data' => $ajx_data_arr );
523 607 }
524 608
525 - $has_verified_classic_context = true;
609 + $has_verified_classic_context = true;
526 610 $re_cleaned_params['allow_past'] = ! empty( $classic_context['allow_past'] ) ? 1 : 0;
611 + // Pass only the signed canonical set into final availability and persistence decisions.
612 + $re_cleaned_params['aggregate_resource_id_arr'] = implode( ',', $classic_context['aggregate_resource_ids'] );
527 613 }
528 614
529 615 if ( $is_public_booking_create_request && ! $has_verified_classic_context ) {
530 616 $ajx_data_arr['status'] = 'error';
531 617 $ajx_data_arr['status_error'] = 'classic_booking_context_required';
532 - $ajx_data_arr['ajx_after_action_message'] = __( 'The booking form context has expired. Please reload the page and try again.', 'booking' );
618 + $ajx_data_arr['ajx_after_action_message'] = wpbc_classic_booking_context_get_visitor_message( 'message_booking_form_context_required', $re_cleaned_params['resource_id'] );
533 619 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
534 620 return array( 'ajx_data' => $ajx_data_arr );
535 621 }
536 622
@@ -617,143 +703,108 @@
617 703 // -----------------------------------------------------------------------------------------------------------------
618 704 // Here GO
619 705 // -----------------------------------------------------------------------------------------------------------------
620 706
621 - // Force - resource saving parameters, instead of wpbc__where_to_save_booking()
622 - if ( ! empty( $re_cleaned_params["save_booking_even_if_unavailable"] ) ) {
623 -
624 - $local_params['how_many_items_to_book'] = 1;
625 -
626 - $dates_keys_arr = array_values( $local_params['dates_only_sql_arr'] ); // [ '2023-09-23', '2023-09-24' ]
627 -
628 - $resources_in_dates = array_fill_keys( $dates_keys_arr , array( $local_params['initial_resource_id'] ) ); // [ 2023-09-23 = [ 2 ], 2023-09-24 = [ 2 ] ]
629 -
630 - $where_to_save_booking = array();
631 - $where_to_save_booking['result'] = 'ok';
632 - $where_to_save_booking['resources_in_dates'] = $resources_in_dates; // [ 2023-09-23 = [ 2, 10, 11 ], 2023-09-24 = [ 2, 10, 11 ]
633 - $where_to_save_booking['time_to_book'] = $local_params['time_as_his_arr']; // [ "00:00:00", "24:00:00" ]
634 - $where_to_save_booking['main__resource_id'] = $local_params['initial_resource_id']; // here edit or request (parent/single) resource
635 -
636 - } else {
637 - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
638 - $php_performance = wpbc_php_performance_START( 'wpbc__where_to_save_booking' , $php_performance );
639 - // </editor-fold>
640 -
641 - /**
642 - * Get slots [] where we can save booking = [ 'resources_in_dates' => [ 2023-10-18 = [ 2, 12, 10, 11 ]
643 - * 2023-10-19 = [ 2, 12, 10, 11 ]
644 - * 2023-10-20 = [ 2, 12, 10, 11 ]
645 - * ],
646 - * 'time_to_book' => [ "14:00:01" , "12:00:01" ],
647 - * 'result' => 'ok'
648 - * 'main__resource_id' => 2
649 - * ]
650 - * OR
651 - * [ 'result' => 'error', 'message' => 'Booking can not be saved ...' ]
652 - */
653 - $where_to_save_booking = wpbc__where_to_save_booking( array(
654 - 'resource_id' => $local_params['initial_resource_id'], // 2 //TODO: If edit booking. What to pass 'edit' or 'parent' resource ID?
655 - 'skip_booking_id' => $local_params['skip_booking_id'], // '', | 125 if edit booking
656 - 'dates_only_sql_arr' => $local_params['dates_only_sql_arr'], // [ "2023-10-18", "2023-10-25", "2023-11-25" ]
657 - 'time_as_seconds_arr' => $local_params['time_as_seconds_arr'], // [ 36000, 39600 ]
658 - 'how_many_items_to_book' => $local_params['how_many_items_to_book'], // 1
659 - 'request_uri' => $re_cleaned_params['request_uri'], // 'http://beta/resource-id2/'
660 - 'allow_past' => ! empty( $re_cleaned_params['allow_past'] ),
661 - 'is_use_booking_recurrent_time' => $local_params['is_use_booking_recurrent_time'], // true | false
662 - 'time_override_source' => ! empty( $local_params['time_override_arr']['source'] ) ? $local_params['time_override_arr']['source'] : '',
663 - 'as_single_resource' => false, // false
664 - 'aggregate_resource_id_arr' => $local_params['aggregate_resource_id_arr'], // Optional can be ''
665 - 'aggregate_type' => $re_cleaned_params['aggregate_type'], //TODO: this parameter does not transfer during saving, so here will be always default value 'bookings_only' // FixIn: 10.0.0.7.
666 - 'custom_form' => $re_cleaned_params['custom_form'] // FixIn: 10.0.0.10.
667 - ));
668 - // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- NO SLOTS TO SAVE " >
669 - if ( 'error' == $where_to_save_booking['result'] ) {
670 - $ajx_data_arr['status'] = 'error';
671 - $ajx_data_arr['status_error'] = 'booking_can_not_save';
672 - $ajx_data_arr['ajx_after_action_message'] = $where_to_save_booking['message'];
673 - $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
674 - return array( 'ajx_data' => $ajx_data_arr );
675 - }
676 - // </editor-fold>
677 -
678 - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
679 - $php_performance = wpbc_php_performance_END( 'wpbc__where_to_save_booking' , $php_performance );
680 - // </editor-fold>
707 + $availability_guard = wpbc_booking_availability_guard_acquire();
708 + if ( is_wp_error( $availability_guard ) ) {
709 + $ajx_data_arr['status'] = 'error';
710 + $ajx_data_arr['status_error'] = $availability_guard->get_error_code();
711 + $ajx_data_arr['ajx_after_action_message'] = $availability_guard->get_error_message();
712 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
713 +
714 + return array( 'ajx_data' => $ajx_data_arr );
681 715 }
682 716
683 - if ( ! empty( $local_params['appointment_service'] ) && function_exists( 'wpbc_appointment_services_check_buffer_conflicts' ) ) {
684 - $buffer_check = wpbc_appointment_services_check_buffer_conflicts(
685 - $local_params['appointment_service'],
686 - $where_to_save_booking['main__resource_id'],
687 - array_keys( $where_to_save_booking['resources_in_dates'] ),
688 - $local_params['time_as_seconds_arr'],
689 - $local_params['skip_booking_id']
690 - );
691 - if ( is_wp_error( $buffer_check ) ) {
692 - $ajx_data_arr['status'] = 'error';
693 - $ajx_data_arr['status_error'] = 'appointment_service_buffer_conflict';
694 - $ajx_data_arr['ajx_after_action_message'] = $buffer_check->get_error_message();
695 - $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
696 - return array( 'ajx_data' => $ajx_data_arr );
697 - }
698 - }
717 + $guard_revalidation_attempts = 0;
718 + try {
719 + while ( true ) {
720 + wpbc_cache__clear( 'wpbc__sql__get_booking_dates' );
721 + $where_to_save_booking = wpbc_booking_validate_save_availability( $local_params, $re_cleaned_params, $php_performance );
699 722
723 + if ( is_wp_error( $where_to_save_booking ) ) {
724 + $ajx_data_arr['status'] = 'error';
725 + $ajx_data_arr['status_error'] = $where_to_save_booking->get_error_code();
726 + $ajx_data_arr['ajx_after_action_message'] = $where_to_save_booking->get_error_message();
727 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
700 728
701 - // Get parameters, from REQUEST
702 - $create_params = $local_params;
703 - $create_params['resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) )
704 - ? $local_params['edit_resource_id'] // If we edit, then use original resource ???
705 - : $where_to_save_booking['main__resource_id']; // Here is important TIP, resource can be where is free, and not where we submit
706 - /**
707 - * TODO: I think it's resolved! Just test about this situation, when we edit the booking - and it's means that we have $local_params['edit_resource_id']
708 - * but what, if $where_to_save_booking do not contain this $local_params['edit_resource_id'] as available resource.
709 - * or even we have $local_params['edit_resource_id'] = 2 and $where_to_save_booking contain resources like [ 1, 2, 3, 4 ]
710 - * we make booking for 3 slots
711 - * in this case, main resource will be 2
712 - * but then when we loop resources in wpbc_db__booking_save() we will save child booking resources for dates like: 2, 3, 4 ( and it's wrong )
713 - * "(205, '2023-10-04 00:00:00', 0, NULL)" <- main resource '2' e.g. $local_params['edit_resource_id'] = 2
714 - * "(205, '2023-10-04 00:00:00', 0, 2)" ? <- child resource '2' e.g. [ .., 2, .. ] in $where_to_save_booking WHICH IS WRONG
715 - */
716 - $create_params['is_emails_send'] = $re_cleaned_params['is_emails_send'];
717 - $create_params['custom_form'] = $re_cleaned_params['custom_form'];
718 -
719 - make_bk_action( 'check_multiuser_params_for_client_side', $create_params['resource_id'] ); // Activate working with specific user in WP MU
720 -
721 - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
722 - $php_performance = wpbc_php_performance_START( 'wpbc_db__booking_save' , $php_performance );
723 - // </editor-fold>
724 -
725 - // -----------------------------------------------------------------------------------------------------------------
726 - // == CREATE_THE 'NEW_BOOKING' ==
727 - // -----------------------------------------------------------------------------------------------------------------
728 - $create_booking_params = array(
729 - 'resource_id' => $create_params['resource_id'],
730 - 'custom_form' => $create_params['custom_form'],
731 - 'all_booking_data_arr' => $create_params['all_booking_data_arr'],
732 - 'dates_only_sql_arr' => $create_params['dates_only_sql_arr'],
733 - 'time_as_his_arr' => $create_params['time_as_his_arr'],
734 - 'is_from_admin_panel' => $create_params['is_from_admin_panel'],
735 - 'is_edit_booking' => $create_params['is_edit_booking'],
736 - 'is_duplicate_booking' => $create_params['is_duplicate_booking'],
737 - 'is_approve_booking' => $create_params['is_approve_booking'],
738 - 'how_many_items_to_book' => $create_params['how_many_items_to_book'],
739 - 'is_use_booking_recurrent_time' => $create_params['is_use_booking_recurrent_time'] // true | false
740 - );
741 - if ( ! empty( $create_params['appointment_service'] ) ) { $create_booking_params['appointment_service'] = $create_params['appointment_service']; }
742 - if ( ! empty( $create_params['sync_gid'] ) ) { $create_booking_params['sync_gid'] = $create_params['sync_gid']; }
743 -
744 - $booking_new_arr = wpbc_db__booking_save( $create_booking_params, $where_to_save_booking );
745 -
746 - // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING CREATION " >
747 - if ( 'ok' !== $booking_new_arr['status'] ) {
748 - $ajx_data_arr['status'] = $booking_new_arr['status'];
749 - $ajx_data_arr['status_error'] = 'booking_can_not_save';
750 - $ajx_data_arr['ajx_after_action_message'] = $booking_new_arr['message'];
751 - $ajx_data_arr['ajx_after_action_message_status'] = 'error';
752 - return array( 'ajx_data' => $ajx_data_arr );
729 + return array( 'ajx_data' => $ajx_data_arr );
730 + }
731 +
732 + // Get parameters, from REQUEST.
733 + $create_params = $local_params;
734 + $create_params['resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) )
735 + ? $local_params['edit_resource_id']
736 + : $where_to_save_booking['main__resource_id'];
737 + $create_params['is_emails_send'] = $re_cleaned_params['is_emails_send'];
738 + $create_params['custom_form'] = $re_cleaned_params['custom_form'];
739 +
740 + make_bk_action( 'check_multiuser_params_for_client_side', $create_params['resource_id'] );
741 +
742 + $create_booking_params = array(
743 + 'resource_id' => $create_params['resource_id'],
744 + 'custom_form' => $create_params['custom_form'],
745 + 'all_booking_data_arr' => $create_params['all_booking_data_arr'],
746 + 'dates_only_sql_arr' => $create_params['dates_only_sql_arr'],
747 + 'time_as_his_arr' => $create_params['time_as_his_arr'],
748 + 'is_from_admin_panel' => $create_params['is_from_admin_panel'],
749 + 'is_edit_booking' => $create_params['is_edit_booking'],
750 + 'is_duplicate_booking' => $create_params['is_duplicate_booking'],
751 + 'is_approve_booking' => $create_params['is_approve_booking'],
752 + 'how_many_items_to_book' => $create_params['how_many_items_to_book'],
753 + 'is_use_booking_recurrent_time' => $create_params['is_use_booking_recurrent_time'],
754 + );
755 + if ( ! empty( $create_params['appointment_service'] ) ) {
756 + $create_booking_params['appointment_service'] = $create_params['appointment_service'];
757 + }
758 + if ( ! empty( $create_params['sync_gid'] ) ) {
759 + $create_booking_params['sync_gid'] = $create_params['sync_gid'];
760 + }
761 +
762 + if ( ! wpbc_booking_availability_guard_is_owned( $availability_guard ) ) {
763 + wpbc_booking_availability_guard_release( $availability_guard );
764 + if ( 1 <= $guard_revalidation_attempts ) {
765 + $availability_guard = wpbc_booking_availability_guard_get_busy_error();
766 + } else {
767 + ++$guard_revalidation_attempts;
768 + $availability_guard = wpbc_booking_availability_guard_acquire();
769 + }
770 +
771 + if ( is_wp_error( $availability_guard ) ) {
772 + $ajx_data_arr['status'] = 'error';
773 + $ajx_data_arr['status_error'] = $availability_guard->get_error_code();
774 + $ajx_data_arr['ajx_after_action_message'] = $availability_guard->get_error_message();
775 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
776 +
777 + return array( 'ajx_data' => $ajx_data_arr );
778 + }
779 +
780 + continue;
781 + }
782 +
783 + $php_performance = wpbc_php_performance_START( 'wpbc_db__booking_save', $php_performance );
784 + $booking_new_arr = wpbc_db__booking_save( $create_booking_params, $where_to_save_booking );
785 + if ( 'ok' !== $booking_new_arr['status'] ) {
786 + $ajx_data_arr['status'] = $booking_new_arr['status'];
787 + $ajx_data_arr['status_error'] = 'booking_can_not_save';
788 + $ajx_data_arr['ajx_after_action_message'] = $booking_new_arr['message'];
789 + $ajx_data_arr['ajx_after_action_message_status'] = 'error';
790 +
791 + return array( 'ajx_data' => $ajx_data_arr );
792 + }
793 +
794 + // Appointment buffers must become visible before the serialized availability section ends.
795 + if ( function_exists( 'wpbc_appointment_services_after_booking_save' ) ) {
796 + wpbc_appointment_services_after_booking_save( $booking_new_arr['booking_id'], $create_booking_params, $where_to_save_booking );
797 + }
798 +
799 + break;
800 + }
801 + } finally {
802 + wpbc_cache__clear( 'wpbc__sql__get_booking_dates' );
803 + wpbc_booking_availability_guard_release( $availability_guard );
753 804 }
754 - // </editor-fold>
755 805
806 + // Released compatibility hook: arbitrary callbacks must not extend the database lock duration.
756 807 do_action( 'wpbc_booking_after_save', $booking_new_arr['booking_id'], $create_booking_params, $where_to_save_booking );
757 808
758 809 // FixIn: 9.9.0.36.
759 810 if (