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 +495 -245 11.5 → 11.9 View file →
@@ -28,26 +28,21 @@
28 28 // Response AJAX parameters
29 29 $ajx_data_arr = array();
30 30 $ajx_data_arr['status'] = 'ok';
31 31
32 - $admin_uri = ltrim( str_replace( get_site_url( null, '', 'admin' ), '', admin_url( 'admin.php?' ) ), '/' ); // 'wp-admin/admin.php?'
33 - $server_http_referer_uri = ( ( isset( $_SERVER['HTTP_REFERER'] ) ) ? sanitize_text_field( $_SERVER['HTTP_REFERER'] ) : '' ); /* phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash */ /* FixIn: sanitize_unslash */
34 - // Local parameters
35 - $local_params = array();
36 - $local_params['is_from_admin_panel'] = ( false !== strpos( $server_http_referer_uri, $admin_uri ) ); // true | false
37 - $local_params['user_id'] = ( isset( $_REQUEST['wpbc_ajx_user_id'] ) ) ? intval( $_REQUEST['wpbc_ajx_user_id'] ) : wpbc_get_current_user_id(); // 1
32 + // Local parameters
33 + $local_params = array();
34 + $local_params['user_id'] = ( isset( $_REQUEST['wpbc_ajx_user_id'] ) ) ? intval( $_REQUEST['wpbc_ajx_user_id'] ) : wpbc_get_current_user_id(); // 1
38 35
39 - // Request parameters.
40 - $experimental_request_rules = array();
41 - if ( function_exists( 'wpbc_is_11_5_features_enabled' ) && wpbc_is_11_5_features_enabled() ) {
42 - $experimental_request_rules = array(
43 - 'service_id' => array( 'validate' => 'd', 'default' => 0 ),
44 - 'appointment_service_required' => array( 'validate' => 'd', 'default' => 0 ),
45 - 'appointment_context_token' => array( 'validate' => 'strong', 'default' => '' ),
46 - 'resource_selector_required' => array( 'validate' => 'd', 'default' => 0 ),
47 - 'resource_selector_context_token' => array( 'validate' => 'strong', 'default' => '' ),
48 - );
49 - }
36 + // Request parameters for the released Appointment and Resource Selector workflows.
37 + $workflow_request_rules = array(
38 + 'service_id' => array( 'validate' => 'd', 'default' => 0 ),
39 + 'appointment_service_required' => array( 'validate' => 'd', 'default' => 0 ),
40 + 'appointment_context_token' => array( 'validate' => 'strong', 'default' => '' ),
41 + 'resource_selector_required' => array( 'validate' => 'd', 'default' => 0 ),
42 + 'resource_selector_context_token' => array( 'validate' => 'strong', 'default' => '' ),
43 + 'wpbc_admin_booking_nonce' => array( 'validate' => 'strong', 'default' => '' ),
44 + );
50 45
51 46 $user_request = new WPBC_AJX__REQUEST( array( // Using this class here only for escaping variables
52 47 'db_option_name' => 'booking__wpbc_booking_create__request_params', // Not necessary, because we not save request, only sanitize it
53 48 'user_id' => $local_params['user_id'], // Not necessary, because we not save request, only sanitize it
@@ -62,18 +57,20 @@
62 57 'captcha_user_input' => array( 'validate' => 'strong', 'default' => '' ),
63 58 'is_emails_send' => array( 'validate' => 'd', 'default' => 1 ),
64 59 'active_locale' => array( 'validate' => 'strong', 'default' => '' ),
65 60 'form_status' => array( 'validate' => 'strong', 'default' => 'published' ),
66 - 'allow_past' => array( 'validate' => 'd', 'default' => 0 ),
61 + 'allow_past' => array( 'validate' => 'd', 'default' => 0 ),
62 + 'classic_booking_context_token' => array( 'validate' => 'strong', 'default' => '' ),
67 63 'wpbc_bfb_preview' => array( 'validate' => 'd', 'default' => 0 ),
68 64 'wpbc_bfb_preview_token' => array( 'validate' => 'strong', 'default' => '' ),
69 65 'wpbc_bfb_preview_form_id' => array( 'validate' => 'd', 'default' => 0 ),
70 66 'wpbc_bfb_preview_nonce' => array( 'validate' => 'strong', 'default' => '' ),
71 67 'wpbc_time_override_enabled' => array( 'validate' => 'd', 'default' => 0 ),
72 - 'wpbc_time_override_source' => array( 'validate' => 'strong', 'default' => '' ),
73 - 'wpbc_time_override_start' => array( 'validate' => 'strong', 'default' => '' ),
74 - 'wpbc_time_override_end' => array( 'validate' => 'strong', 'default' => '' ),
75 - ), $experimental_request_rules )
68 + 'wpbc_time_override_source' => array( 'validate' => 'strong', 'default' => '' ),
69 + 'wpbc_time_override_start' => array( 'validate' => 'strong', 'default' => '' ),
70 + 'wpbc_time_override_end' => array( 'validate' => 'strong', 'default' => '' ),
71 + 'wpbc_admin_cost_correction' => array( 'validate' => 'strong', 'default' => '' ),
72 + ), $workflow_request_rules )
76 73 ));
77 74
78 75 // Escape of request params in Ajax Post. We use prefix 'calendar_request_params', if Ajax sent - $_REQUEST['calendar_request_params']['resource_id'], ...
79 76 $request_prefix = 'calendar_request_params';
@@ -79,15 +76,16 @@
79 76 $request_prefix = 'calendar_request_params';
80 77
81 78 //$_REQUEST['calendar_request_params']['dates_ddmmyy_csv'] .= "'%2b(select+'box'+from(select+sleep(2)+from+dual+where+1=1*)a)%2b'-02-21+00:00:00";
82 79
83 - $request_params = $user_request->get_sanitized__in_request__value_or_default( $request_prefix ); // NOT Direct: $_REQUEST['calendar_request_params']['resource_id']
84 - $server_http_referer_uri = ( ( isset( $_SERVER['HTTP_REFERER'] ) ) ? sanitize_text_field( $_SERVER['HTTP_REFERER'] ) : '' ); /* phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash */ /* FixIn: sanitize_unslash */
85 - $request_params['request_uri'] = $server_http_referer_uri; // Parameter needed for Error in booking saving and reloading calendar again with these actual parameters.
80 + $request_params = $user_request->get_sanitized__in_request__value_or_default( $request_prefix ); // NOT Direct: $_REQUEST['calendar_request_params']['resource_id']
81 + $server_http_referer_uri = ( ( isset( $_SERVER['HTTP_REFERER'] ) ) ? sanitize_text_field( $_SERVER['HTTP_REFERER'] ) : '' ); /* phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash */ /* FixIn: sanitize_unslash */
82 + $request_params['request_uri'] = $server_http_referer_uri; // Parameter needed for Error in booking saving and reloading calendar again with these actual parameters.
83 + $is_authorized_admin_booking_request = wpbc_is_authorized_admin_booking_request( $request_params['wpbc_admin_booking_nonce'] );
86 84
87 85 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- CAPTCHA " >
88 86 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
89 - wpbc_captcha__in_ajx__check( $request_params, $local_params['is_from_admin_panel'], $_REQUEST[ $request_prefix ] );
87 + wpbc_captcha__in_ajx__check( $request_params, $is_authorized_admin_booking_request, $_REQUEST[ $request_prefix ] );
90 88 // </editor-fold>
91 89
92 90 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING_RESOURCE ID " >
93 91 if ( $request_params['resource_id'] <= 0 ) {
@@ -94,16 +92,13 @@
94 92 $ajx_data_arr['status'] = 'error';
95 93 $ajx_data_arr['status_error'] = 'resource_id_incorrect';
96 94 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
97 95 $ajx_data_arr['ajx_after_action_message'] = 'Wrong ID of booking resource: ' . ' [ request ID: ' . $_REQUEST['calendar_request_params']['resource_id'] . ' | parsed ID: ' . $request_params['resource_id'] . ' ]';
98 - $ajx_data_arr['ajx_after_action_message_status'] = 'error';
99 - wp_send_json( array(
100 - 'ajx_data' => $ajx_data_arr,
101 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
102 - 'ajx_search_params' => $_REQUEST[ $request_prefix ],
103 - 'ajx_cleaned_params' => $request_params,
104 - 'resource_id' => $request_params['resource_id'],
105 - ) );
96 + $ajx_data_arr['ajx_after_action_message_status'] = 'error';
97 + wp_send_json( array(
98 + 'ajx_data' => $ajx_data_arr,
99 + 'resource_id' => $request_params['resource_id'],
100 + ) );
106 101 }
107 102 // </editor-fold>
108 103
109 104 $server_http_referer_uri = ( ( isset( $_SERVER['HTTP_REFERER'] ) ) ? sanitize_text_field( $_SERVER['HTTP_REFERER'] ) : '' ); /* phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash */ /* FixIn: sanitize_unslash */
@@ -119,36 +114,37 @@
119 114 'is_show_payment_form' => 1,
120 115 'user_id' => $local_params['user_id'],
121 116 'request_uri' => $server_http_referer_uri,
122 117 'form_status' => $request_params['form_status'],
123 - 'allow_past' => $request_params['allow_past'],
118 + 'allow_past' => $request_params['allow_past'],
119 + 'classic_booking_context_token' => $request_params['classic_booking_context_token'],
124 120 'wpbc_bfb_preview' => $request_params['wpbc_bfb_preview'],
125 121 'wpbc_bfb_preview_token' => $request_params['wpbc_bfb_preview_token'],
126 122 'wpbc_bfb_preview_form_id' => $request_params['wpbc_bfb_preview_form_id'],
127 123 'wpbc_bfb_preview_nonce' => $request_params['wpbc_bfb_preview_nonce'],
128 124 'wpbc_time_override_enabled' => $request_params['wpbc_time_override_enabled'],
129 - 'wpbc_time_override_source' => $request_params['wpbc_time_override_source'],
130 - 'wpbc_time_override_start' => $request_params['wpbc_time_override_start'],
125 + 'wpbc_time_override_source' => $request_params['wpbc_time_override_source'],
126 + 'wpbc_time_override_start' => $request_params['wpbc_time_override_start'],
131 127 'wpbc_time_override_end' => $request_params['wpbc_time_override_end'],
128 + 'wpbc_admin_cost_correction' => $request_params['wpbc_admin_cost_correction'],
132 129 );
133 - if ( wpbc_is_11_5_features_enabled() ) {
134 - $request_save_params['service_id'] = $request_params['service_id'];
135 - $request_save_params['appointment_service_required'] = $request_params['appointment_service_required'];
136 - $request_save_params['appointment_context_token'] = $request_params['appointment_context_token'];
137 - $request_save_params['resource_selector_required'] = $request_params['resource_selector_required'];
138 - $request_save_params['resource_selector_context_token'] = $request_params['resource_selector_context_token'];
139 - }
130 + $request_save_params['service_id'] = $request_params['service_id'];
131 + $request_save_params['appointment_service_required'] = $request_params['appointment_service_required'];
132 + $request_save_params['appointment_context_token'] = $request_params['appointment_context_token'];
133 + $request_save_params['resource_selector_required'] = $request_params['resource_selector_required'];
134 + $request_save_params['resource_selector_context_token'] = $request_params['resource_selector_context_token'];
135 + $request_save_params['wpbc_admin_booking_nonce'] = $request_params['wpbc_admin_booking_nonce'];
140 136 $booking_save_arr = wpbc_booking_save( $request_save_params );
141 137
142 138 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING " >
143 139 if ( 'ok' !== $booking_save_arr['ajx_data']['status'] ) {
144 140
145 - wp_send_json( array( 'ajx_data' => $booking_save_arr['ajx_data'],
146 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
147 - 'ajx_search_params' => $_REQUEST[ $request_prefix ],
148 - 'ajx_cleaned_params' => $request_params,
149 - 'resource_id' => $request_params['resource_id']
150 - ));
141 + wp_send_json(
142 + array(
143 + 'ajx_data' => $booking_save_arr['ajx_data'],
144 + 'resource_id' => $request_params['resource_id'],
145 + )
146 + );
151 147 }
152 148 // </editor-fold>
153 149
154 150 $ajx_data_arr = $booking_save_arr['ajx_data'];
@@ -217,10 +213,94 @@
217 213 // ---------------------------------------------------------------------------------------------------------------------
218 214 // == Save Booking
219 215 // ---------------------------------------------------------------------------------------------------------------------
220 216
221 -/**
222 - * 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
223 303 *
224 304 * @param $request_params = [
225 305 * resource_id = 2 REQUIRED Default: 1
226 306 * dates_ddmmyy_csv = '27.10.2023, 28.10.2023, 29.10.2023' REQUIRED
@@ -279,9 +359,10 @@
279 359 'custom_form' => array( 'validate' => 'strong', 'default' => '' ),
280 360 'is_emails_send' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1
281 361 'is_show_payment_form' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1
282 362 'user_id' => array( 'validate' => 'd', 'default' => wpbc_get_current_user_id() ), // INT
283 - 'allow_past' => array( 'validate' => 'd', 'default' => 0 ),
363 + 'allow_past' => array( 'validate' => 'd', 'default' => 0 ),
364 + 'classic_booking_context_token' => array( 'validate' => 'strong', 'default' => '' ),
284 365 'request_uri' => array( 'validate' => 'strong', 'default' => ( ( defined( 'DOING_AJAX' ) ) && ( DOING_AJAX ) ) ? $server_http_referer_uri : $server_request_uri ), // front-end: $server_request_uri | ajax: $server_http_referer_uri
285 366 // Really Optional:
286 367 'aggregate_resource_id_arr' => array( 'validate' => 'digit_or_csd', 'default' => '' ),
287 368 //TODO: this parameter does not transfer during saving, so here will be always default value 'bookings_only' // FixIn: 10.0.0.7.
@@ -296,21 +377,23 @@
296 377 'wpbc_bfb_preview_token' => array( 'validate' => 'strong', 'default' => '' ),
297 378 'wpbc_bfb_preview_form_id' => array( 'validate' => 'd', 'default' => 0 ),
298 379 'wpbc_bfb_preview_nonce' => array( 'validate' => 'strong', 'default' => '' ),
299 380 'wpbc_time_override_enabled' => array( 'validate' => 'd', 'default' => 0 ),
300 - 'wpbc_time_override_source' => array( 'validate' => 'strong', 'default' => '' ),
301 - 'wpbc_time_override_start' => array( 'validate' => 'strong', 'default' => '' ),
381 + 'wpbc_time_override_source' => array( 'validate' => 'strong', 'default' => '' ),
382 + 'wpbc_time_override_start' => array( 'validate' => 'strong', 'default' => '' ),
302 383 'wpbc_time_override_end' => array( 'validate' => 'strong', 'default' => '' ),
303 - );
304 - if ( wpbc_is_11_5_features_enabled() ) {
305 - $validate_arr_rules['service_id'] = array( 'validate' => 'd', 'default' => 0 );
306 - $validate_arr_rules['appointment_service_required'] = array( 'validate' => 'd', 'default' => 0 );
307 - $validate_arr_rules['appointment_context_token'] = array( 'validate' => 'strong', 'default' => '' );
308 - $validate_arr_rules['resource_selector_required'] = array( 'validate' => 'd', 'default' => 0 );
309 - $validate_arr_rules['resource_selector_context_token'] = array( 'validate' => 'strong', 'default' => '' );
310 - }
384 + 'wpbc_admin_cost_correction' => array( 'validate' => 'strong', 'default' => '' ),
385 + );
386 + $validate_arr_rules['service_id'] = array( 'validate' => 'd', 'default' => 0 );
387 + $validate_arr_rules['appointment_service_required'] = array( 'validate' => 'd', 'default' => 0 );
388 + $validate_arr_rules['appointment_context_token'] = array( 'validate' => 'strong', 'default' => '' );
389 + $validate_arr_rules['resource_selector_required'] = array( 'validate' => 'd', 'default' => 0 );
390 + $validate_arr_rules['resource_selector_context_token'] = array( 'validate' => 'strong', 'default' => '' );
391 + $validate_arr_rules['wpbc_admin_booking_nonce'] = array( 'validate' => 'strong', 'default' => '' );
311 392 $re_cleaned_params = wpbc_sanitize_params_in_arr( $request_params, $validate_arr_rules );
312 - if ( wpbc_is_11_5_features_enabled() && ! empty( $re_cleaned_params['appointment_service_required'] ) && empty( $re_cleaned_params['service_id'] ) ) {
393 + $has_verified_appointment_context = false;
394 + $has_verified_resource_selector_context = false;
395 + if ( ! empty( $re_cleaned_params['appointment_service_required'] ) && empty( $re_cleaned_params['service_id'] ) ) {
313 396 $ajx_data_arr['status'] = 'error';
314 397 $ajx_data_arr['status_error'] = 'appointment_service_required';
315 398 $ajx_data_arr['ajx_after_action_message'] = __( 'Please select a Service.', 'booking' );
316 399 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
@@ -315,9 +398,9 @@
315 398 $ajx_data_arr['ajx_after_action_message'] = __( 'Please select a Service.', 'booking' );
316 399 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
317 400 return array( 'ajx_data' => $ajx_data_arr );
318 401 }
319 - if ( wpbc_is_11_5_features_enabled() && ! empty( $re_cleaned_params['service_id'] ) ) {
402 + if ( ! empty( $re_cleaned_params['service_id'] ) ) {
320 403 if ( ! function_exists( 'wpbc_booking_appointment_validate_submission_context' ) ) {
321 404 $appointment_context_check = new WP_Error( 'appointment_context_unavailable', __( 'The Appointment selection cannot be verified. Please reload the page and try again.', 'booking' ) );
322 405 } else {
323 406 $appointment_context_check = wpbc_booking_appointment_validate_submission_context(
@@ -332,13 +415,14 @@
332 415 $ajx_data_arr['ajx_after_action_message'] = $appointment_context_check->get_error_message();
333 416 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
334 417 return array( 'ajx_data' => $ajx_data_arr );
335 418 }
419 + $has_verified_appointment_context = true;
336 420
337 421 // A client value cannot enable past Appointment creation; trust only the site-authored signed context.
338 422 $re_cleaned_params['allow_past'] = wpbc_booking_appointment_is_past_booking_enabled( $appointment_context_check ) ? 1 : 0;
339 423 }
340 - if ( wpbc_is_11_5_features_enabled() && ! empty( $re_cleaned_params['resource_selector_required'] ) ) {
424 + if ( ! empty( $re_cleaned_params['resource_selector_required'] ) || ! empty( $re_cleaned_params['resource_selector_context_token'] ) ) {
341 425 if ( ! function_exists( 'wpbc_booking_resource_selector_validate_submission_context' ) ) {
342 426 $resource_selector_context_check = new WP_Error( 'resource_selector_context_unavailable', __( 'The Booking Resource selection cannot be verified. Please reload the page and try again.', 'booking' ) );
343 427 } else {
344 428 $resource_selector_context_check = wpbc_booking_resource_selector_validate_submission_context(
@@ -352,16 +436,15 @@
352 436 $ajx_data_arr['ajx_after_action_message'] = $resource_selector_context_check->get_error_message();
353 437 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
354 438 return array( 'ajx_data' => $ajx_data_arr );
355 439 }
440 + $has_verified_resource_selector_context = true;
356 441
357 442 // Trust only the site-authored signed selector context for public past bookings.
358 443 $re_cleaned_params['allow_past'] = wpbc_booking_resource_selector_is_past_booking_enabled( $resource_selector_context_check ) ? 1 : 0;
359 444 }
360 445
361 - $admin_uri = ltrim( str_replace( get_site_url( null, '', 'admin' ), '', admin_url( 'admin.php?' ) ), '/' ); // wp-admin/admin.php?
362 -
363 - $re_cleaned_params['form_status'] = sanitize_key( $re_cleaned_params['form_status'] );
446 + $re_cleaned_params['form_status'] = sanitize_key( $re_cleaned_params['form_status'] );
364 447 if ( 'preview' !== $re_cleaned_params['form_status'] ) {
365 448 $re_cleaned_params['form_status'] = 'published';
366 449 }
367 450 // FixIn: 2026-02-05 - make preview/published available to form parsing/templates during this request.
@@ -378,14 +461,26 @@
378 461
379 462 // -----------------------------------------------------------------------------------------------------------------
380 463 // Local parameters
381 464 // -----------------------------------------------------------------------------------------------------------------
382 - $local_params = array();
383 - $local_params['is_from_admin_panel'] = ( false !== strpos( $re_cleaned_params['request_uri'], $admin_uri ) ); // true | false
465 + $local_params = array();
466 + $is_authorized_admin_booking_request = wpbc_is_authorized_admin_booking_request( $re_cleaned_params['wpbc_admin_booking_nonce'] );
467 + $local_params['is_from_admin_panel'] = $is_authorized_admin_booking_request;
384 468 $local_params['user_id'] = $re_cleaned_params['user_id']; // 1
385 - $local_params['sync_gid'] = $re_cleaned_params['sync_gid']; // ''
386 - $local_params['is_approve_booking'] = $re_cleaned_params['is_approve_booking']; // 0 | 1
387 - $local_params['is_use_booking_recurrent_time'] = ( 1 === $re_cleaned_params['is_use_booking_recurrent_time'] ); // false | true
469 + $local_params['sync_gid'] = $re_cleaned_params['sync_gid']; // ''
470 + $local_params['is_approve_booking'] = $re_cleaned_params['is_approve_booking']; // 0 | 1
471 + $local_params['is_use_booking_recurrent_time'] = ( 1 === $re_cleaned_params['is_use_booking_recurrent_time'] ); // false | true
472 + $request_action = isset( $_REQUEST['action'] ) && is_scalar( $_REQUEST['action'] )
473 + ? sanitize_key( (string) wp_unslash( $_REQUEST['action'] ) )
474 + : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
475 + $is_public_booking_create_request = wp_doing_ajax()
476 + && 'wpbc_ajx_booking__create' === strtolower( $request_action )
477 + && ! $is_authorized_admin_booking_request;
478 +
479 + // Time overrides belong exclusively to the capability-protected Add Booking administration workflow.
480 + $re_cleaned_params = wpbc_restrict_booking_time_override_to_authorized_admin( $re_cleaned_params, $is_authorized_admin_booking_request );
481 + // Cost corrections belong exclusively to capability-protected administrator booking workflows.
482 + $re_cleaned_params = wpbc_restrict_booking_cost_correction_to_authorized_admin( $re_cleaned_params, $is_authorized_admin_booking_request );
388 483
389 484 // -----------------------------------------------------------------------------------------------------------------
390 485 // Parse Local parameters for later use
391 486 // -----------------------------------------------------------------------------------------------------------------
@@ -416,9 +511,9 @@
416 511 }
417 512 // Important! : [ 64800, 72000 ]
418 513 $local_params['time_as_seconds_arr'] = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $local_params['structured_booking_data_arr'] );
419 514 $local_params['appointment_service'] = array();
420 - if ( wpbc_is_11_5_features_enabled() && ! empty( $re_cleaned_params['service_id'] ) && function_exists( 'wpbc_appointment_services_repository' ) ) {
515 + if ( ! empty( $re_cleaned_params['service_id'] ) && function_exists( 'wpbc_appointment_services_repository' ) ) {
421 516 $range_time_value = isset( $local_params['structured_booking_data_arr']['rangetime'] ) ? $local_params['structured_booking_data_arr']['rangetime'] : '';
422 517 $start_time_value = isset( $local_params['structured_booking_data_arr']['starttime'] ) ? $local_params['structured_booking_data_arr']['starttime'] : '';
423 518 $range_time_value = is_array( $range_time_value ) ? implode( '', $range_time_value ) : $range_time_value;
424 519 $start_time_value = is_array( $start_time_value ) ? implode( '', $start_time_value ) : $start_time_value;
@@ -489,12 +584,59 @@
489 584 wpbc_transform__seconds__in__24_hours_his( $time_as_seconds_arr[0] ),
490 585 wpbc_transform__seconds__in__24_hours_his( $time_as_seconds_arr[1] )
491 586 );
492 587 // [ '2023-09-10', '2023-09-11' ]
493 - $local_params['dates_only_sql_arr'] = wpbc_convert_dates_str__dd_mm_yyyy__to__yyyy_mm_dd( $re_cleaned_params["dates_ddmmyy_csv"] );
494 - $local_params['dates_only_sql_arr'] = explode( ',', $local_params['dates_only_sql_arr'] );
495 -
496 - if (
588 + $local_params['dates_only_sql_arr'] = wpbc_convert_dates_str__dd_mm_yyyy__to__yyyy_mm_dd( $re_cleaned_params["dates_ddmmyy_csv"] );
589 + $local_params['dates_only_sql_arr'] = explode( ',', $local_params['dates_only_sql_arr'] );
590 +
591 + $classic_context = array();
592 + $has_verified_classic_context = false;
593 + if ( ! empty( $re_cleaned_params['classic_booking_context_token'] ) && function_exists( 'wpbc_classic_booking_context_validate_submission' ) ) {
594 + $classic_context = wpbc_classic_booking_context_validate_submission(
595 + $re_cleaned_params['classic_booking_context_token'],
596 + $re_cleaned_params['resource_id'],
597 + $local_params['dates_only_sql_arr'],
598 + $re_cleaned_params['custom_form'],
599 + $re_cleaned_params['aggregate_resource_id_arr']
600 + );
601 + if ( is_wp_error( $classic_context ) ) {
602 + $ajx_data_arr['status'] = 'error';
603 + $ajx_data_arr['status_error'] = $classic_context->get_error_code();
604 + $ajx_data_arr['ajx_after_action_message'] = $classic_context->get_error_message();
605 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
606 + return array( 'ajx_data' => $ajx_data_arr );
607 + }
608 +
609 + $has_verified_classic_context = true;
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'] );
613 + }
614 +
615 + if ( $is_public_booking_create_request && ! $has_verified_classic_context ) {
616 + $ajx_data_arr['status'] = 'error';
617 + $ajx_data_arr['status_error'] = 'classic_booking_context_required';
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'] );
619 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
620 + return array( 'ajx_data' => $ajx_data_arr );
621 + }
622 +
623 + if ( $has_verified_classic_context ) {
624 + $workflow_context_error = wpbc_booking_create_validate_required_workflow(
625 + $classic_context,
626 + $has_verified_appointment_context,
627 + $has_verified_resource_selector_context
628 + );
629 + if ( is_wp_error( $workflow_context_error ) ) {
630 + $ajx_data_arr['status'] = 'error';
631 + $ajx_data_arr['status_error'] = $workflow_context_error->get_error_code();
632 + $ajx_data_arr['ajx_after_action_message'] = $workflow_context_error->get_error_message();
633 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
634 + return array( 'ajx_data' => $ajx_data_arr );
635 + }
636 + }
637 +
638 + if (
497 639 ( ! empty( $local_params['time_override_arr'] ) )
498 640 && ( 'times_availability' === $local_params['time_override_arr']['source'] )
499 641 && ( count( array_filter( $local_params['dates_only_sql_arr'] ) ) > 1 )
500 642 ) {
@@ -503,13 +645,16 @@
503 645
504 646 $local_params['is_show_payment_form'] = $re_cleaned_params["is_show_payment_form"];
505 647
506 648 // FixIn: 9.9.0.35.
507 - if ( $local_params['is_show_payment_form'] ) {
508 - $local_params['is_show_payment_form'] = ( false !== strpos( $re_cleaned_params['request_uri'], 'is_show_payment_form=Off' ) )
509 - ? 0
510 - : $local_params['is_show_payment_form']; // 1|0
511 - }
649 + if ( $local_params['is_show_payment_form'] ) {
650 + $local_params['is_show_payment_form'] = (
651 + $is_authorized_admin_booking_request
652 + && false !== strpos( $re_cleaned_params['request_uri'], 'is_show_payment_form=Off' )
653 + )
654 + ? 0
655 + : $local_params['is_show_payment_form']; // 1|0
656 + }
512 657
513 658 // Get EDIT booking data
514 659 $local_params['edit_resource_id'] = '';
515 660 $local_params['skip_booking_id'] = '';
@@ -527,20 +672,12 @@
527 672 $local_params['is_duplicate_booking'] = 1;
528 673 }
529 674 }
530 675
531 - $request_action = isset( $_REQUEST['action'] ) && is_scalar( $_REQUEST['action'] )
532 - ? sanitize_key( (string) wp_unslash( $_REQUEST['action'] ) )
533 - : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
534 - $is_frontend_ajax_edit = defined( 'DOING_AJAX' )
535 - && DOING_AJAX
676 + $is_frontend_ajax_edit = wp_doing_ajax()
536 677 && 'wpbc_ajx_booking__create' === strtolower( $request_action )
537 678 && 0 !== $local_params['is_edit_booking'];
538 - $is_authorized_admin_edit = $local_params['is_from_admin_panel']
539 - && is_user_logged_in()
540 - && class_exists( 'WPBC_Add_Booking_Component' )
541 - && WPBC_Add_Booking_Component::current_user_can_add_booking()
542 - && wpbc_is_mu_user_can_be_here( 'activated_user' );
679 + $is_authorized_admin_edit = $is_authorized_admin_booking_request;
543 680
544 681 if (
545 682 $is_frontend_ajax_edit
546 683 && ! $is_authorized_admin_edit
@@ -566,143 +703,108 @@
566 703 // -----------------------------------------------------------------------------------------------------------------
567 704 // Here GO
568 705 // -----------------------------------------------------------------------------------------------------------------
569 706
570 - // Force - resource saving parameters, instead of wpbc__where_to_save_booking()
571 - if ( ! empty( $re_cleaned_params["save_booking_even_if_unavailable"] ) ) {
572 -
573 - $local_params['how_many_items_to_book'] = 1;
574 -
575 - $dates_keys_arr = array_values( $local_params['dates_only_sql_arr'] ); // [ '2023-09-23', '2023-09-24' ]
576 -
577 - $resources_in_dates = array_fill_keys( $dates_keys_arr , array( $local_params['initial_resource_id'] ) ); // [ 2023-09-23 = [ 2 ], 2023-09-24 = [ 2 ] ]
578 -
579 - $where_to_save_booking = array();
580 - $where_to_save_booking['result'] = 'ok';
581 - $where_to_save_booking['resources_in_dates'] = $resources_in_dates; // [ 2023-09-23 = [ 2, 10, 11 ], 2023-09-24 = [ 2, 10, 11 ]
582 - $where_to_save_booking['time_to_book'] = $local_params['time_as_his_arr']; // [ "00:00:00", "24:00:00" ]
583 - $where_to_save_booking['main__resource_id'] = $local_params['initial_resource_id']; // here edit or request (parent/single) resource
584 -
585 - } else {
586 - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
587 - $php_performance = wpbc_php_performance_START( 'wpbc__where_to_save_booking' , $php_performance );
588 - // </editor-fold>
589 -
590 - /**
591 - * Get slots [] where we can save booking = [ 'resources_in_dates' => [ 2023-10-18 = [ 2, 12, 10, 11 ]
592 - * 2023-10-19 = [ 2, 12, 10, 11 ]
593 - * 2023-10-20 = [ 2, 12, 10, 11 ]
594 - * ],
595 - * 'time_to_book' => [ "14:00:01" , "12:00:01" ],
596 - * 'result' => 'ok'
597 - * 'main__resource_id' => 2
598 - * ]
599 - * OR
600 - * [ 'result' => 'error', 'message' => 'Booking can not be saved ...' ]
601 - */
602 - $where_to_save_booking = wpbc__where_to_save_booking( array(
603 - 'resource_id' => $local_params['initial_resource_id'], // 2 //TODO: If edit booking. What to pass 'edit' or 'parent' resource ID?
604 - 'skip_booking_id' => $local_params['skip_booking_id'], // '', | 125 if edit booking
605 - 'dates_only_sql_arr' => $local_params['dates_only_sql_arr'], // [ "2023-10-18", "2023-10-25", "2023-11-25" ]
606 - 'time_as_seconds_arr' => $local_params['time_as_seconds_arr'], // [ 36000, 39600 ]
607 - 'how_many_items_to_book' => $local_params['how_many_items_to_book'], // 1
608 - 'request_uri' => $re_cleaned_params['request_uri'], // 'http://beta/resource-id2/'
609 - 'allow_past' => ! empty( $re_cleaned_params['allow_past'] ),
610 - 'is_use_booking_recurrent_time' => $local_params['is_use_booking_recurrent_time'], // true | false
611 - 'time_override_source' => ! empty( $local_params['time_override_arr']['source'] ) ? $local_params['time_override_arr']['source'] : '',
612 - 'as_single_resource' => false, // false
613 - 'aggregate_resource_id_arr' => $local_params['aggregate_resource_id_arr'], // Optional can be ''
614 - '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.
615 - 'custom_form' => $re_cleaned_params['custom_form'] // FixIn: 10.0.0.10.
616 - ));
617 - // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- NO SLOTS TO SAVE " >
618 - if ( 'error' == $where_to_save_booking['result'] ) {
619 - $ajx_data_arr['status'] = 'error';
620 - $ajx_data_arr['status_error'] = 'booking_can_not_save';
621 - $ajx_data_arr['ajx_after_action_message'] = $where_to_save_booking['message'];
622 - $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
623 - return array( 'ajx_data' => $ajx_data_arr );
624 - }
625 - // </editor-fold>
626 -
627 - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
628 - $php_performance = wpbc_php_performance_END( 'wpbc__where_to_save_booking' , $php_performance );
629 - // </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 );
630 715 }
631 716
632 - if ( wpbc_is_11_5_features_enabled() && ! empty( $local_params['appointment_service'] ) && function_exists( 'wpbc_appointment_services_check_buffer_conflicts' ) ) {
633 - $buffer_check = wpbc_appointment_services_check_buffer_conflicts(
634 - $local_params['appointment_service'],
635 - $where_to_save_booking['main__resource_id'],
636 - array_keys( $where_to_save_booking['resources_in_dates'] ),
637 - $local_params['time_as_seconds_arr'],
638 - $local_params['skip_booking_id']
639 - );
640 - if ( is_wp_error( $buffer_check ) ) {
641 - $ajx_data_arr['status'] = 'error';
642 - $ajx_data_arr['status_error'] = 'appointment_service_buffer_conflict';
643 - $ajx_data_arr['ajx_after_action_message'] = $buffer_check->get_error_message();
644 - $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
645 - return array( 'ajx_data' => $ajx_data_arr );
646 - }
647 - }
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 );
648 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';
649 728
650 - // Get parameters, from REQUEST
651 - $create_params = $local_params;
652 - $create_params['resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) )
653 - ? $local_params['edit_resource_id'] // If we edit, then use original resource ???
654 - : $where_to_save_booking['main__resource_id']; // Here is important TIP, resource can be where is free, and not where we submit
655 - /**
656 - * 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']
657 - * but what, if $where_to_save_booking do not contain this $local_params['edit_resource_id'] as available resource.
658 - * or even we have $local_params['edit_resource_id'] = 2 and $where_to_save_booking contain resources like [ 1, 2, 3, 4 ]
659 - * we make booking for 3 slots
660 - * in this case, main resource will be 2
661 - * 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 )
662 - * "(205, '2023-10-04 00:00:00', 0, NULL)" <- main resource '2' e.g. $local_params['edit_resource_id'] = 2
663 - * "(205, '2023-10-04 00:00:00', 0, 2)" ? <- child resource '2' e.g. [ .., 2, .. ] in $where_to_save_booking WHICH IS WRONG
664 - */
665 - $create_params['is_emails_send'] = $re_cleaned_params['is_emails_send'];
666 - $create_params['custom_form'] = $re_cleaned_params['custom_form'];
667 -
668 - make_bk_action( 'check_multiuser_params_for_client_side', $create_params['resource_id'] ); // Activate working with specific user in WP MU
669 -
670 - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
671 - $php_performance = wpbc_php_performance_START( 'wpbc_db__booking_save' , $php_performance );
672 - // </editor-fold>
673 -
674 - // -----------------------------------------------------------------------------------------------------------------
675 - // == CREATE_THE 'NEW_BOOKING' ==
676 - // -----------------------------------------------------------------------------------------------------------------
677 - $create_booking_params = array(
678 - 'resource_id' => $create_params['resource_id'],
679 - 'custom_form' => $create_params['custom_form'],
680 - 'all_booking_data_arr' => $create_params['all_booking_data_arr'],
681 - 'dates_only_sql_arr' => $create_params['dates_only_sql_arr'],
682 - 'time_as_his_arr' => $create_params['time_as_his_arr'],
683 - 'is_from_admin_panel' => $create_params['is_from_admin_panel'],
684 - 'is_edit_booking' => $create_params['is_edit_booking'],
685 - 'is_duplicate_booking' => $create_params['is_duplicate_booking'],
686 - 'is_approve_booking' => $create_params['is_approve_booking'],
687 - 'how_many_items_to_book' => $create_params['how_many_items_to_book'],
688 - 'is_use_booking_recurrent_time' => $create_params['is_use_booking_recurrent_time'] // true | false
689 - );
690 - if ( ! empty( $create_params['appointment_service'] ) ) { $create_booking_params['appointment_service'] = $create_params['appointment_service']; }
691 - if ( ! empty( $create_params['sync_gid'] ) ) { $create_booking_params['sync_gid'] = $create_params['sync_gid']; }
692 -
693 - $booking_new_arr = wpbc_db__booking_save( $create_booking_params, $where_to_save_booking );
694 -
695 - // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING CREATION " >
696 - if ( 'ok' !== $booking_new_arr['status'] ) {
697 - $ajx_data_arr['status'] = $booking_new_arr['status'];
698 - $ajx_data_arr['status_error'] = 'booking_can_not_save';
699 - $ajx_data_arr['ajx_after_action_message'] = $booking_new_arr['message'];
700 - $ajx_data_arr['ajx_after_action_message_status'] = 'error';
701 - 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 );
702 804 }
703 - // </editor-fold>
704 805
806 + // Released compatibility hook: arbitrary callbacks must not extend the database lock duration.
705 807 do_action( 'wpbc_booking_after_save', $booking_new_arr['booking_id'], $create_booking_params, $where_to_save_booking );
706 808
707 809 // FixIn: 9.9.0.36.
708 810 if (
@@ -732,11 +834,9 @@
732 834 $str_dates__dd_mm_yyyy = wpbc_convert_dates_arr__yyyy_mm_dd__to__dd_mm_yyyy( $payment_params['booked_dates_times_arr']['dates_ymd_arr'] ); // ['2023-10-20','2023-10-25'] => ['20.10.2023','25.10.2023']
733 835 $payment_params['str_dates__dd_mm_yyyy'] = implode( ',', $str_dates__dd_mm_yyyy ); // REQUIRED -- '14.11.2023, 15.11.2023, 16.11.2023, 17.11.2023'
734 836 $payment_params['booking_id'] = $booking_new_arr['booking_id']; // REQUIRED -- '2'
735 837 $payment_params['resource_id'] = $create_params['resource_id']; // REQUIRED -- '2' can be child resource (changed in wpbc_where_to_save() )
736 - if ( wpbc_is_11_5_features_enabled() ) {
737 - $payment_params['service_id'] = ! empty( $create_params['appointment_service']['service_id'] ) ? absint( $create_params['appointment_service']['service_id'] ) : 0;
738 - }
838 + $payment_params['service_id'] = ! empty( $create_params['appointment_service']['service_id'] ) ? absint( $create_params['appointment_service']['service_id'] ) : 0;
739 839 $payment_params['initial_resource_id'] = $local_params['initial_resource_id']; // REQUIRED -- '2' initial calendar - parent resource
740 840 $payment_params['form_data'] = $booking_new_arr['form_data']; // we re-save it, because here can be sync_guid and custom form new data from wpbc_db__booking_save(..) // REQUIRED -- 'text^selected_short_timedates_hint4^06/11/2018 14:00...'
741 841 $payment_params['times_array'] = array(
742 842 explode( ':', $where_to_save_booking['time_to_book'][0] ), // ["10","00","00"]
@@ -746,9 +846,10 @@
746 846 $payment_params['is_edit_booking'] = $create_params['is_edit_booking']; // => 0 0 | int - ID of the booking
747 847 $payment_params['custom_form'] = $create_params['custom_form']; // => '' '' | 'some_name'
748 848 $payment_params['is_duplicate_booking'] = $create_params['is_duplicate_booking']; // => 0 0 | 1
749 849 $payment_params['is_from_admin_panel'] = $create_params['is_from_admin_panel']; // => false true | false
750 - $payment_params['is_show_payment_form'] = $create_params['is_show_payment_form']; // => 1 0 | 1
850 + $payment_params['is_show_payment_form'] = $create_params['is_show_payment_form']; // => 1 0 | 1
851 + $payment_params['wpbc_admin_cost_correction'] = $re_cleaned_params['wpbc_admin_cost_correction'];
751 852 if ( $payment_params['is_from_admin_panel'] ) {
752 853 // $payment_params['is_show_payment_form'] = 0; // FixIn: 9.9.0.21.
753 854 }
754 855 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
@@ -1214,9 +1315,9 @@
1214 1315 $sql_field_arr[] = array( 'name' => 'form', 'type' => '%s', 'value' => $form_data );
1215 1316 $sql_field_arr[] = array( 'name' => 'booking_type', 'type' => '%d', 'value' => $create_params['resource_id'] );
1216 1317 $sql_field_arr[] = array( 'name' => 'modification_date', 'type' => '%s', 'value' => gmdate( 'Y-m-d H:i:s' ) );
1217 1318 $sql_field_arr[] = array( 'name' => 'sort_date', 'type' => '%s', 'value' => $create_params['dates_only_sql_arr'][0] . ' ' . $create_params['time_as_his_arr'][0] );
1218 - $sql_field_arr[] = array( 'name' => 'hash', 'type' => 'MD5(%s)', 'value' => time() . '_' . wp_rand( 1000, 1000000 ) );
1319 + $sql_field_arr[] = array( 'name' => 'hash', 'type' => '%s', 'value' => wpbc_hash__generate_booking_hash() );
1219 1320
1220 1321
1221 1322 if (
1222 1323 ( 0 == $create_params['is_edit_booking'] ) || // If not edit, then INSERT.
@@ -1237,12 +1338,15 @@
1237 1338 $sql_prepare_arr['name'] = implode( ', ', $sql_prepare_arr['name'] );
1238 1339 $sql_prepare_arr['type'] = implode( ', ', $sql_prepare_arr['type'] );
1239 1340 /* phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare */
1240 1341 $sql = $wpdb->prepare( "INSERT INTO {$wpdb->prefix}booking " . " ( {$sql_prepare_arr['name']} )" . " VALUES ( {$sql_prepare_arr['type']} )", $sql_prepare_arr['value'] );
1241 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1242 - if ( false === $wpdb->query( $sql ) ) {
1243 - return array( 'status' => 'error', 'message' => 'Error. INSERT New Data in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $sql );
1244 - }
1342 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1343 + if ( false === $wpdb->query( $sql ) ) {
1344 + return array(
1345 + 'status' => 'error',
1346 + 'message' => __( 'The booking could not be saved because of a database error. Please try again or contact the website administrator.', 'booking' ),
1347 + );
1348 + }
1245 1349 // Get ID of booking
1246 1350 $booking_id = (int) $wpdb->insert_id;
1247 1351
1248 1352 } else { // Edit - UPDATE
@@ -1256,14 +1360,15 @@
1256 1360 $sql_prepare_arr['set'] = implode( ', ', $sql_prepare_arr['set'] );
1257 1361
1258 1362 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1259 1363 $sql = $wpdb->prepare( "UPDATE {$wpdb->prefix}booking SET {$sql_prepare_arr['set']} WHERE booking_id={$booking_id};", $sql_prepare_arr['value'] );
1260 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1261 - if ( false === $wpdb->query( $sql ) ) {
1262 - return array( 'status' => 'error',
1263 - 'message' => 'Error. UPDATE Exist Data in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $sql,
1264 - );
1265 - }
1364 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1365 + if ( false === $wpdb->query( $sql ) ) {
1366 + return array(
1367 + 'status' => 'error',
1368 + 'message' => __( 'The booking could not be updated because of a database error. Please try again or contact the website administrator.', 'booking' ),
1369 + );
1370 + }
1266 1371
1267 1372 // Check if dates previously was approved.
1268 1373 $slct_sql = "SELECT approved FROM {$wpdb->prefix}bookingdates WHERE booking_id IN ({$booking_id}) LIMIT 0,1";
1269 1374 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
@@ -1554,9 +1659,9 @@
1554 1659 *
1555 1660 * // Now get start/end times as seconds: [ 64800, 72000 ]
1556 1661 * $time_as_seconds_arr = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $structured_booking_data_arr );
1557 1662 */
1558 - function wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $booking_form_data__arr ){
1663 + function wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $booking_form_data__arr ){
1559 1664
1560 1665 $selected_time_fields = wpbc_get__selected_time_fields__in_booking_form__as_arr( $booking_form_data__arr );
1561 1666
1562 1667 // 2.2 Get selected SECONDS to book ---------------------------------------------------------------------------
@@ -1595,14 +1700,159 @@
1595 1700 }
1596 1701 }
1597 1702 }
1598 1703
1599 - return $time_as_seconds_arr;
1600 - }
1601 -
1602 -
1603 - /**
1604 - * Get explicit admin-selected time override from Add Booking modal request.
1704 + return $time_as_seconds_arr;
1705 + }
1706 +
1707 +
1708 + /**
1709 + * Determine whether a booking-create request is an authorized administration workflow.
1710 + *
1711 + * The public booking action is intentionally available to signed-out visitors. A
1712 + * Referer, request path, or caller-supplied Boolean therefore cannot establish an
1713 + * administrator security context. The Add Booking UI supplies this user-bound nonce,
1714 + * and the server independently rechecks login, capability, and MultiUser access.
1715 + *
1716 + * @param mixed $admin_booking_nonce Candidate Add Booking administration nonce.
1717 + *
1718 + * @return bool True only for an authorized Add Booking administration request.
1719 + */
1720 + function wpbc_is_authorized_admin_booking_request( $admin_booking_nonce ) {
1721 +
1722 + if (
1723 + ! is_scalar( $admin_booking_nonce )
1724 + || '' === trim( (string) $admin_booking_nonce )
1725 + || ! is_user_logged_in()
1726 + || ! wp_verify_nonce( sanitize_text_field( (string) $admin_booking_nonce ), 'wpbc_admin_booking_create' )
1727 + || ! class_exists( 'WPBC_Add_Booking_Component' )
1728 + || ! WPBC_Add_Booking_Component::current_user_can_add_booking()
1729 + || ! wpbc_is_mu_user_can_be_here( 'activated_user' )
1730 + ) {
1731 + return false;
1732 + }
1733 +
1734 + return true;
1735 + }
1736 +
1737 +
1738 + /**
1739 + * Require the signed workflow proof declared by a verified Booking Form context.
1740 + *
1741 + * Appointment and Resource Selector JavaScript flags are presentation hints only.
1742 + * The signed Booking Form context identifies the server-rendered workflow, so removing
1743 + * a flag or domain token cannot downgrade that form to a different workflow.
1744 + *
1745 + * @param array $classic_context Verified Booking Form context.
1746 + * @param bool $has_verified_appointment_context Whether Service and Provider proof passed validation.
1747 + * @param bool $has_verified_resource_selector_context Whether Resource Selector proof passed validation.
1748 + *
1749 + * @return true|WP_Error True when the required proof is present, otherwise a safe validation error.
1750 + */
1751 + function wpbc_booking_create_validate_required_workflow( $classic_context, $has_verified_appointment_context, $has_verified_resource_selector_context ) {
1752 +
1753 + $booking_workflow = isset( $classic_context['booking_workflow'] ) ? sanitize_key( $classic_context['booking_workflow'] ) : '';
1754 + if ( 'appointment' === $booking_workflow && ! $has_verified_appointment_context ) {
1755 + return new WP_Error( 'appointment_context_required', __( 'The Appointment selection has expired. Please start over and try again.', 'booking' ) );
1756 + }
1757 + if ( 'resource_selector' === $booking_workflow && ! $has_verified_resource_selector_context ) {
1758 + return new WP_Error( 'resource_selector_context_required', __( 'The Booking Resource selection has expired. Please start over and try again.', 'booking' ) );
1759 + }
1760 +
1761 + return true;
1762 + }
1763 +
1764 +
1765 + /**
1766 + * Remove administrator time-override values from an unauthorized booking request.
1767 + *
1768 + * The public booking endpoint intentionally accepts unauthenticated requests, so
1769 + * sanitizing these values is not sufficient authorization. Clearing every related
1770 + * value here prevents a public client from replacing the Booking Form's configured
1771 + * time while preserving the capability-protected Add Booking workflow.
1772 + *
1773 + * @param array $request_params Sanitized booking request parameters.
1774 + * @param bool $is_authorized_admin_booking_request Whether the current request is an authorized Add Booking administration request.
1775 + *
1776 + * @return array Booking request parameters with unauthorized override values removed.
1777 + */
1778 + function wpbc_restrict_booking_time_override_to_authorized_admin( $request_params, $is_authorized_admin_booking_request ) {
1779 +
1780 + $request_params = is_array( $request_params ) ? $request_params : array();
1781 + if ( $is_authorized_admin_booking_request ) {
1782 + return $request_params;
1783 + }
1784 +
1785 + $request_params['wpbc_time_override_enabled'] = 0;
1786 + $request_params['wpbc_time_override_source'] = '';
1787 + $request_params['wpbc_time_override_start'] = '';
1788 + $request_params['wpbc_time_override_end'] = '';
1789 +
1790 + return $request_params;
1791 + }
1792 +
1793 +
1794 + /**
1795 + * Authorize and normalize an administrator cost-correction request value.
1796 + *
1797 + * Booking creation is intentionally public, so a sanitized numeric value is
1798 + * not sufficient authorization. Only capability-protected Add Booking and
1799 + * Add Appointment workflows in Business Small or higher may retain this value.
1800 + * Missing, malformed, out-of-range, public, and unsupported-edition values
1801 + * are reduced to an empty sentinel, which preserves automatic calculation.
1802 + *
1803 + * @param array $request_params Sanitized booking request parameters.
1804 + * @param bool $is_authorized_admin_booking_request Whether this is an authorized administrator booking request.
1805 + *
1806 + * @return array Booking request parameters with a normalized or empty cost correction.
1807 + */
1808 + function wpbc_restrict_booking_cost_correction_to_authorized_admin( $request_params, $is_authorized_admin_booking_request ) {
1809 +
1810 + $request_params = is_array( $request_params ) ? $request_params : array();
1811 + $raw_cost = isset( $request_params['wpbc_admin_cost_correction'] ) ? $request_params['wpbc_admin_cost_correction'] : '';
1812 +
1813 + $request_params['wpbc_admin_cost_correction'] = '';
1814 + if ( ! $is_authorized_admin_booking_request || ! class_exists( 'wpdev_bk_biz_s' ) ) {
1815 + return $request_params;
1816 + }
1817 +
1818 + $request_params['wpbc_admin_cost_correction'] = wpbc_sanitize_booking_cost_correction( $raw_cost );
1819 +
1820 + return $request_params;
1821 + }
1822 +
1823 +
1824 + /**
1825 + * Sanitize one exact administrator-entered Booking total.
1826 + *
1827 + * @param mixed $raw_cost Raw request value.
1828 + *
1829 + * @return string Normalized decimal without trailing zeroes, or an empty string when invalid.
1830 + */
1831 + function wpbc_sanitize_booking_cost_correction( $raw_cost ) {
1832 +
1833 + if ( ! is_scalar( $raw_cost ) ) {
1834 + return '';
1835 + }
1836 +
1837 + $raw_cost = trim( sanitize_text_field( (string) $raw_cost ) );
1838 + if ( '' === $raw_cost || ! preg_match( '/^[0-9]{1,10}(?:\.[0-9]{1,8})?$/', $raw_cost ) ) {
1839 + return '';
1840 + }
1841 +
1842 + $normalized_cost = (float) $raw_cost;
1843 + if ( ! is_finite( $normalized_cost ) || $normalized_cost < 0 || $normalized_cost > 1000000000 ) {
1844 + return '';
1845 + }
1846 +
1847 + $normalized_cost = rtrim( rtrim( number_format( $normalized_cost, 8, '.', '' ), '0' ), '.' );
1848 +
1849 + return '' === $normalized_cost ? '0' : $normalized_cost;
1850 + }
1851 +
1852 +
1853 + /**
1854 + * Get explicit admin-selected time override from Add Booking modal request.
1605 1855 *
1606 1856 * @param array $request_params Sanitized booking request params.
1607 1857 *
1608 1858 * @return array Empty array or array with start/end HH:MM values.