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 +397 -206 11.6.1 → 11.9 View file →
@@ -28,14 +28,11 @@
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 36 // Request parameters for the released Appointment and Resource Selector workflows.
40 37 $workflow_request_rules = array(
41 38 'service_id' => array( 'validate' => 'd', 'default' => 0 ),
@@ -42,8 +39,9 @@
42 39 'appointment_service_required' => array( 'validate' => 'd', 'default' => 0 ),
43 40 'appointment_context_token' => array( 'validate' => 'strong', 'default' => '' ),
44 41 'resource_selector_required' => array( 'validate' => 'd', 'default' => 0 ),
45 42 'resource_selector_context_token' => array( 'validate' => 'strong', 'default' => '' ),
43 + 'wpbc_admin_booking_nonce' => array( 'validate' => 'strong', 'default' => '' ),
46 44 );
47 45
48 46 $user_request = new WPBC_AJX__REQUEST( array( // Using this class here only for escaping variables
49 47 'db_option_name' => 'booking__wpbc_booking_create__request_params', // Not necessary, because we not save request, only sanitize it
@@ -66,12 +64,13 @@
66 64 'wpbc_bfb_preview_token' => array( 'validate' => 'strong', 'default' => '' ),
67 65 'wpbc_bfb_preview_form_id' => array( 'validate' => 'd', 'default' => 0 ),
68 66 'wpbc_bfb_preview_nonce' => array( 'validate' => 'strong', 'default' => '' ),
69 67 'wpbc_time_override_enabled' => array( 'validate' => 'd', 'default' => 0 ),
70 - 'wpbc_time_override_source' => array( 'validate' => 'strong', 'default' => '' ),
71 - 'wpbc_time_override_start' => array( 'validate' => 'strong', 'default' => '' ),
72 - 'wpbc_time_override_end' => array( 'validate' => 'strong', 'default' => '' ),
73 - ), $workflow_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 )
74 73 ));
75 74
76 75 // Escape of request params in Ajax Post. We use prefix 'calendar_request_params', if Ajax sent - $_REQUEST['calendar_request_params']['resource_id'], ...
77 76 $request_prefix = 'calendar_request_params';
@@ -77,15 +76,16 @@
77 76 $request_prefix = 'calendar_request_params';
78 77
79 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";
80 79
81 - $request_params = $user_request->get_sanitized__in_request__value_or_default( $request_prefix ); // NOT Direct: $_REQUEST['calendar_request_params']['resource_id']
82 - $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 */
83 - $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'] );
84 84
85 85 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- CAPTCHA " >
86 86 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
87 - 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 ] );
88 88 // </editor-fold>
89 89
90 90 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING_RESOURCE ID " >
91 91 if ( $request_params['resource_id'] <= 0 ) {
@@ -92,16 +92,13 @@
92 92 $ajx_data_arr['status'] = 'error';
93 93 $ajx_data_arr['status_error'] = 'resource_id_incorrect';
94 94 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
95 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'] . ' ]';
96 - $ajx_data_arr['ajx_after_action_message_status'] = 'error';
97 - wp_send_json( array(
98 - 'ajx_data' => $ajx_data_arr,
99 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
100 - 'ajx_search_params' => $_REQUEST[ $request_prefix ],
101 - 'ajx_cleaned_params' => $request_params,
102 - 'resource_id' => $request_params['resource_id'],
103 - ) );
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 + ) );
104 101 }
105 102 // </editor-fold>
106 103
107 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 */
@@ -124,11 +121,12 @@
124 121 'wpbc_bfb_preview_token' => $request_params['wpbc_bfb_preview_token'],
125 122 'wpbc_bfb_preview_form_id' => $request_params['wpbc_bfb_preview_form_id'],
126 123 'wpbc_bfb_preview_nonce' => $request_params['wpbc_bfb_preview_nonce'],
127 124 'wpbc_time_override_enabled' => $request_params['wpbc_time_override_enabled'],
128 - 'wpbc_time_override_source' => $request_params['wpbc_time_override_source'],
129 - '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'],
130 127 'wpbc_time_override_end' => $request_params['wpbc_time_override_end'],
128 + 'wpbc_admin_cost_correction' => $request_params['wpbc_admin_cost_correction'],
131 129 );
132 130 $request_save_params['service_id'] = $request_params['service_id'];
133 131 $request_save_params['appointment_service_required'] = $request_params['appointment_service_required'];
134 132 $request_save_params['appointment_context_token'] = $request_params['appointment_context_token'];
@@ -133,19 +131,20 @@
133 131 $request_save_params['appointment_service_required'] = $request_params['appointment_service_required'];
134 132 $request_save_params['appointment_context_token'] = $request_params['appointment_context_token'];
135 133 $request_save_params['resource_selector_required'] = $request_params['resource_selector_required'];
136 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'];
137 136 $booking_save_arr = wpbc_booking_save( $request_save_params );
138 137
139 138 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING " >
140 139 if ( 'ok' !== $booking_save_arr['ajx_data']['status'] ) {
141 140
142 - wp_send_json( array( 'ajx_data' => $booking_save_arr['ajx_data'],
143 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
144 - 'ajx_search_params' => $_REQUEST[ $request_prefix ],
145 - 'ajx_cleaned_params' => $request_params,
146 - 'resource_id' => $request_params['resource_id']
147 - ));
141 + wp_send_json(
142 + array(
143 + 'ajx_data' => $booking_save_arr['ajx_data'],
144 + 'resource_id' => $request_params['resource_id'],
145 + )
146 + );
148 147 }
149 148 // </editor-fold>
150 149
151 150 $ajx_data_arr = $booking_save_arr['ajx_data'];
@@ -214,10 +213,94 @@
214 213 // ---------------------------------------------------------------------------------------------------------------------
215 214 // == Save Booking
216 215 // ---------------------------------------------------------------------------------------------------------------------
217 216
218 -/**
219 - * 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
220 303 *
221 304 * @param $request_params = [
222 305 * resource_id = 2 REQUIRED Default: 1
223 306 * dates_ddmmyy_csv = '27.10.2023, 28.10.2023, 29.10.2023' REQUIRED
@@ -294,18 +377,22 @@
294 377 'wpbc_bfb_preview_token' => array( 'validate' => 'strong', 'default' => '' ),
295 378 'wpbc_bfb_preview_form_id' => array( 'validate' => 'd', 'default' => 0 ),
296 379 'wpbc_bfb_preview_nonce' => array( 'validate' => 'strong', 'default' => '' ),
297 380 'wpbc_time_override_enabled' => array( 'validate' => 'd', 'default' => 0 ),
298 - 'wpbc_time_override_source' => array( 'validate' => 'strong', 'default' => '' ),
299 - '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' => '' ),
300 383 'wpbc_time_override_end' => array( 'validate' => 'strong', 'default' => '' ),
301 - );
384 + 'wpbc_admin_cost_correction' => array( 'validate' => 'strong', 'default' => '' ),
385 + );
302 386 $validate_arr_rules['service_id'] = array( 'validate' => 'd', 'default' => 0 );
303 387 $validate_arr_rules['appointment_service_required'] = array( 'validate' => 'd', 'default' => 0 );
304 388 $validate_arr_rules['appointment_context_token'] = array( 'validate' => 'strong', 'default' => '' );
305 389 $validate_arr_rules['resource_selector_required'] = array( 'validate' => 'd', 'default' => 0 );
306 390 $validate_arr_rules['resource_selector_context_token'] = array( 'validate' => 'strong', 'default' => '' );
391 + $validate_arr_rules['wpbc_admin_booking_nonce'] = array( 'validate' => 'strong', 'default' => '' );
307 392 $re_cleaned_params = wpbc_sanitize_params_in_arr( $request_params, $validate_arr_rules );
393 + $has_verified_appointment_context = false;
394 + $has_verified_resource_selector_context = false;
308 395 if ( ! empty( $re_cleaned_params['appointment_service_required'] ) && empty( $re_cleaned_params['service_id'] ) ) {
309 396 $ajx_data_arr['status'] = 'error';
310 397 $ajx_data_arr['status_error'] = 'appointment_service_required';
311 398 $ajx_data_arr['ajx_after_action_message'] = __( 'Please select a Service.', 'booking' );
@@ -328,13 +415,14 @@
328 415 $ajx_data_arr['ajx_after_action_message'] = $appointment_context_check->get_error_message();
329 416 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
330 417 return array( 'ajx_data' => $ajx_data_arr );
331 418 }
419 + $has_verified_appointment_context = true;
332 420
333 421 // A client value cannot enable past Appointment creation; trust only the site-authored signed context.
334 422 $re_cleaned_params['allow_past'] = wpbc_booking_appointment_is_past_booking_enabled( $appointment_context_check ) ? 1 : 0;
335 423 }
336 - if ( ! empty( $re_cleaned_params['resource_selector_required'] ) ) {
424 + if ( ! empty( $re_cleaned_params['resource_selector_required'] ) || ! empty( $re_cleaned_params['resource_selector_context_token'] ) ) {
337 425 if ( ! function_exists( 'wpbc_booking_resource_selector_validate_submission_context' ) ) {
338 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' ) );
339 427 } else {
340 428 $resource_selector_context_check = wpbc_booking_resource_selector_validate_submission_context(
@@ -348,16 +436,15 @@
348 436 $ajx_data_arr['ajx_after_action_message'] = $resource_selector_context_check->get_error_message();
349 437 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
350 438 return array( 'ajx_data' => $ajx_data_arr );
351 439 }
440 + $has_verified_resource_selector_context = true;
352 441
353 442 // Trust only the site-authored signed selector context for public past bookings.
354 443 $re_cleaned_params['allow_past'] = wpbc_booking_resource_selector_is_past_booking_enabled( $resource_selector_context_check ) ? 1 : 0;
355 444 }
356 445
357 - $admin_uri = ltrim( str_replace( get_site_url( null, '', 'admin' ), '', admin_url( 'admin.php?' ) ), '/' ); // wp-admin/admin.php?
358 -
359 - $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'] );
360 447 if ( 'preview' !== $re_cleaned_params['form_status'] ) {
361 448 $re_cleaned_params['form_status'] = 'published';
362 449 }
363 450 // FixIn: 2026-02-05 - make preview/published available to form parsing/templates during this request.
@@ -374,10 +461,11 @@
374 461
375 462 // -----------------------------------------------------------------------------------------------------------------
376 463 // Local parameters
377 464 // -----------------------------------------------------------------------------------------------------------------
378 - $local_params = array();
379 - $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;
380 468 $local_params['user_id'] = $re_cleaned_params['user_id']; // 1
381 469 $local_params['sync_gid'] = $re_cleaned_params['sync_gid']; // ''
382 470 $local_params['is_approve_booking'] = $re_cleaned_params['is_approve_booking']; // 0 | 1
383 471 $local_params['is_use_booking_recurrent_time'] = ( 1 === $re_cleaned_params['is_use_booking_recurrent_time'] ); // false | true
@@ -383,13 +471,8 @@
383 471 $local_params['is_use_booking_recurrent_time'] = ( 1 === $re_cleaned_params['is_use_booking_recurrent_time'] ); // false | true
384 472 $request_action = isset( $_REQUEST['action'] ) && is_scalar( $_REQUEST['action'] )
385 473 ? sanitize_key( (string) wp_unslash( $_REQUEST['action'] ) )
386 474 : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
387 - $is_authorized_admin_booking_request = $local_params['is_from_admin_panel']
388 - && is_user_logged_in()
389 - && class_exists( 'WPBC_Add_Booking_Component' )
390 - && WPBC_Add_Booking_Component::current_user_can_add_booking()
391 - && wpbc_is_mu_user_can_be_here( 'activated_user' );
392 475 $is_public_booking_create_request = wp_doing_ajax()
393 476 && 'wpbc_ajx_booking__create' === strtolower( $request_action )
394 477 && ! $is_authorized_admin_booking_request;
395 478
@@ -394,8 +477,10 @@
394 477 && ! $is_authorized_admin_booking_request;
395 478
396 479 // Time overrides belong exclusively to the capability-protected Add Booking administration workflow.
397 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 );
398 483
399 484 // -----------------------------------------------------------------------------------------------------------------
400 485 // Parse Local parameters for later use
401 486 // -----------------------------------------------------------------------------------------------------------------
@@ -502,8 +587,9 @@
502 587 // [ '2023-09-10', '2023-09-11' ]
503 588 $local_params['dates_only_sql_arr'] = wpbc_convert_dates_str__dd_mm_yyyy__to__yyyy_mm_dd( $re_cleaned_params["dates_ddmmyy_csv"] );
504 589 $local_params['dates_only_sql_arr'] = explode( ',', $local_params['dates_only_sql_arr'] );
505 590
591 + $classic_context = array();
506 592 $has_verified_classic_context = false;
507 593 if ( ! empty( $re_cleaned_params['classic_booking_context_token'] ) && function_exists( 'wpbc_classic_booking_context_validate_submission' ) ) {
508 594 $classic_context = wpbc_classic_booking_context_validate_submission(
509 595 $re_cleaned_params['classic_booking_context_token'],
@@ -519,22 +605,38 @@
519 605 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
520 606 return array( 'ajx_data' => $ajx_data_arr );
521 607 }
522 608
523 - $has_verified_classic_context = true;
609 + $has_verified_classic_context = true;
524 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'] );
525 613 }
526 614
527 - $has_verified_workflow_context = (
528 - ( ! empty( $re_cleaned_params['service_id'] ) && ! empty( $re_cleaned_params['appointment_context_token'] ) )
529 - || ( ! empty( $re_cleaned_params['resource_selector_required'] ) && ! empty( $re_cleaned_params['resource_selector_context_token'] ) )
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
530 628 );
531 - if ( $is_public_booking_create_request && ! $has_verified_classic_context && ! $has_verified_workflow_context ) {
532 - $re_cleaned_params['allow_past'] = 0;
533 - $re_cleaned_params['request_uri'] = remove_query_arg( 'allow_past', $re_cleaned_params['request_uri'] );
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 + }
534 636 }
535 -
536 - if (
637 +
638 + if (
537 639 ( ! empty( $local_params['time_override_arr'] ) )
538 640 && ( 'times_availability' === $local_params['time_override_arr']['source'] )
539 641 && ( count( array_filter( $local_params['dates_only_sql_arr'] ) ) > 1 )
540 642 ) {
@@ -543,13 +645,16 @@
543 645
544 646 $local_params['is_show_payment_form'] = $re_cleaned_params["is_show_payment_form"];
545 647
546 648 // FixIn: 9.9.0.35.
547 - if ( $local_params['is_show_payment_form'] ) {
548 - $local_params['is_show_payment_form'] = ( false !== strpos( $re_cleaned_params['request_uri'], 'is_show_payment_form=Off' ) )
549 - ? 0
550 - : $local_params['is_show_payment_form']; // 1|0
551 - }
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 + }
552 657
553 658 // Get EDIT booking data
554 659 $local_params['edit_resource_id'] = '';
555 660 $local_params['skip_booking_id'] = '';
@@ -598,143 +703,108 @@
598 703 // -----------------------------------------------------------------------------------------------------------------
599 704 // Here GO
600 705 // -----------------------------------------------------------------------------------------------------------------
601 706
602 - // Force - resource saving parameters, instead of wpbc__where_to_save_booking()
603 - if ( ! empty( $re_cleaned_params["save_booking_even_if_unavailable"] ) ) {
604 -
605 - $local_params['how_many_items_to_book'] = 1;
606 -
607 - $dates_keys_arr = array_values( $local_params['dates_only_sql_arr'] ); // [ '2023-09-23', '2023-09-24' ]
608 -
609 - $resources_in_dates = array_fill_keys( $dates_keys_arr , array( $local_params['initial_resource_id'] ) ); // [ 2023-09-23 = [ 2 ], 2023-09-24 = [ 2 ] ]
610 -
611 - $where_to_save_booking = array();
612 - $where_to_save_booking['result'] = 'ok';
613 - $where_to_save_booking['resources_in_dates'] = $resources_in_dates; // [ 2023-09-23 = [ 2, 10, 11 ], 2023-09-24 = [ 2, 10, 11 ]
614 - $where_to_save_booking['time_to_book'] = $local_params['time_as_his_arr']; // [ "00:00:00", "24:00:00" ]
615 - $where_to_save_booking['main__resource_id'] = $local_params['initial_resource_id']; // here edit or request (parent/single) resource
616 -
617 - } else {
618 - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
619 - $php_performance = wpbc_php_performance_START( 'wpbc__where_to_save_booking' , $php_performance );
620 - // </editor-fold>
621 -
622 - /**
623 - * Get slots [] where we can save booking = [ 'resources_in_dates' => [ 2023-10-18 = [ 2, 12, 10, 11 ]
624 - * 2023-10-19 = [ 2, 12, 10, 11 ]
625 - * 2023-10-20 = [ 2, 12, 10, 11 ]
626 - * ],
627 - * 'time_to_book' => [ "14:00:01" , "12:00:01" ],
628 - * 'result' => 'ok'
629 - * 'main__resource_id' => 2
630 - * ]
631 - * OR
632 - * [ 'result' => 'error', 'message' => 'Booking can not be saved ...' ]
633 - */
634 - $where_to_save_booking = wpbc__where_to_save_booking( array(
635 - 'resource_id' => $local_params['initial_resource_id'], // 2 //TODO: If edit booking. What to pass 'edit' or 'parent' resource ID?
636 - 'skip_booking_id' => $local_params['skip_booking_id'], // '', | 125 if edit booking
637 - 'dates_only_sql_arr' => $local_params['dates_only_sql_arr'], // [ "2023-10-18", "2023-10-25", "2023-11-25" ]
638 - 'time_as_seconds_arr' => $local_params['time_as_seconds_arr'], // [ 36000, 39600 ]
639 - 'how_many_items_to_book' => $local_params['how_many_items_to_book'], // 1
640 - 'request_uri' => $re_cleaned_params['request_uri'], // 'http://beta/resource-id2/'
641 - 'allow_past' => ! empty( $re_cleaned_params['allow_past'] ),
642 - 'is_use_booking_recurrent_time' => $local_params['is_use_booking_recurrent_time'], // true | false
643 - 'time_override_source' => ! empty( $local_params['time_override_arr']['source'] ) ? $local_params['time_override_arr']['source'] : '',
644 - 'as_single_resource' => false, // false
645 - 'aggregate_resource_id_arr' => $local_params['aggregate_resource_id_arr'], // Optional can be ''
646 - '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.
647 - 'custom_form' => $re_cleaned_params['custom_form'] // FixIn: 10.0.0.10.
648 - ));
649 - // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- NO SLOTS TO SAVE " >
650 - if ( 'error' == $where_to_save_booking['result'] ) {
651 - $ajx_data_arr['status'] = 'error';
652 - $ajx_data_arr['status_error'] = 'booking_can_not_save';
653 - $ajx_data_arr['ajx_after_action_message'] = $where_to_save_booking['message'];
654 - $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
655 - return array( 'ajx_data' => $ajx_data_arr );
656 - }
657 - // </editor-fold>
658 -
659 - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
660 - $php_performance = wpbc_php_performance_END( 'wpbc__where_to_save_booking' , $php_performance );
661 - // </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 );
662 715 }
663 716
664 - if ( ! empty( $local_params['appointment_service'] ) && function_exists( 'wpbc_appointment_services_check_buffer_conflicts' ) ) {
665 - $buffer_check = wpbc_appointment_services_check_buffer_conflicts(
666 - $local_params['appointment_service'],
667 - $where_to_save_booking['main__resource_id'],
668 - array_keys( $where_to_save_booking['resources_in_dates'] ),
669 - $local_params['time_as_seconds_arr'],
670 - $local_params['skip_booking_id']
671 - );
672 - if ( is_wp_error( $buffer_check ) ) {
673 - $ajx_data_arr['status'] = 'error';
674 - $ajx_data_arr['status_error'] = 'appointment_service_buffer_conflict';
675 - $ajx_data_arr['ajx_after_action_message'] = $buffer_check->get_error_message();
676 - $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
677 - return array( 'ajx_data' => $ajx_data_arr );
678 - }
679 - }
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 );
680 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';
681 728
682 - // Get parameters, from REQUEST
683 - $create_params = $local_params;
684 - $create_params['resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) )
685 - ? $local_params['edit_resource_id'] // If we edit, then use original resource ???
686 - : $where_to_save_booking['main__resource_id']; // Here is important TIP, resource can be where is free, and not where we submit
687 - /**
688 - * 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']
689 - * but what, if $where_to_save_booking do not contain this $local_params['edit_resource_id'] as available resource.
690 - * or even we have $local_params['edit_resource_id'] = 2 and $where_to_save_booking contain resources like [ 1, 2, 3, 4 ]
691 - * we make booking for 3 slots
692 - * in this case, main resource will be 2
693 - * 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 )
694 - * "(205, '2023-10-04 00:00:00', 0, NULL)" <- main resource '2' e.g. $local_params['edit_resource_id'] = 2
695 - * "(205, '2023-10-04 00:00:00', 0, 2)" ? <- child resource '2' e.g. [ .., 2, .. ] in $where_to_save_booking WHICH IS WRONG
696 - */
697 - $create_params['is_emails_send'] = $re_cleaned_params['is_emails_send'];
698 - $create_params['custom_form'] = $re_cleaned_params['custom_form'];
699 -
700 - make_bk_action( 'check_multiuser_params_for_client_side', $create_params['resource_id'] ); // Activate working with specific user in WP MU
701 -
702 - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
703 - $php_performance = wpbc_php_performance_START( 'wpbc_db__booking_save' , $php_performance );
704 - // </editor-fold>
705 -
706 - // -----------------------------------------------------------------------------------------------------------------
707 - // == CREATE_THE 'NEW_BOOKING' ==
708 - // -----------------------------------------------------------------------------------------------------------------
709 - $create_booking_params = array(
710 - 'resource_id' => $create_params['resource_id'],
711 - 'custom_form' => $create_params['custom_form'],
712 - 'all_booking_data_arr' => $create_params['all_booking_data_arr'],
713 - 'dates_only_sql_arr' => $create_params['dates_only_sql_arr'],
714 - 'time_as_his_arr' => $create_params['time_as_his_arr'],
715 - 'is_from_admin_panel' => $create_params['is_from_admin_panel'],
716 - 'is_edit_booking' => $create_params['is_edit_booking'],
717 - 'is_duplicate_booking' => $create_params['is_duplicate_booking'],
718 - 'is_approve_booking' => $create_params['is_approve_booking'],
719 - 'how_many_items_to_book' => $create_params['how_many_items_to_book'],
720 - 'is_use_booking_recurrent_time' => $create_params['is_use_booking_recurrent_time'] // true | false
721 - );
722 - if ( ! empty( $create_params['appointment_service'] ) ) { $create_booking_params['appointment_service'] = $create_params['appointment_service']; }
723 - if ( ! empty( $create_params['sync_gid'] ) ) { $create_booking_params['sync_gid'] = $create_params['sync_gid']; }
724 -
725 - $booking_new_arr = wpbc_db__booking_save( $create_booking_params, $where_to_save_booking );
726 -
727 - // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING CREATION " >
728 - if ( 'ok' !== $booking_new_arr['status'] ) {
729 - $ajx_data_arr['status'] = $booking_new_arr['status'];
730 - $ajx_data_arr['status_error'] = 'booking_can_not_save';
731 - $ajx_data_arr['ajx_after_action_message'] = $booking_new_arr['message'];
732 - $ajx_data_arr['ajx_after_action_message_status'] = 'error';
733 - 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 );
734 804 }
735 - // </editor-fold>
736 805
806 + // Released compatibility hook: arbitrary callbacks must not extend the database lock duration.
737 807 do_action( 'wpbc_booking_after_save', $booking_new_arr['booking_id'], $create_booking_params, $where_to_save_booking );
738 808
739 809 // FixIn: 9.9.0.36.
740 810 if (
@@ -776,9 +846,10 @@
776 846 $payment_params['is_edit_booking'] = $create_params['is_edit_booking']; // => 0 0 | int - ID of the booking
777 847 $payment_params['custom_form'] = $create_params['custom_form']; // => '' '' | 'some_name'
778 848 $payment_params['is_duplicate_booking'] = $create_params['is_duplicate_booking']; // => 0 0 | 1
779 849 $payment_params['is_from_admin_panel'] = $create_params['is_from_admin_panel']; // => false true | false
780 - $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'];
781 852 if ( $payment_params['is_from_admin_panel'] ) {
782 853 // $payment_params['is_show_payment_form'] = 0; // FixIn: 9.9.0.21.
783 854 }
784 855 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
@@ -1244,9 +1315,9 @@
1244 1315 $sql_field_arr[] = array( 'name' => 'form', 'type' => '%s', 'value' => $form_data );
1245 1316 $sql_field_arr[] = array( 'name' => 'booking_type', 'type' => '%d', 'value' => $create_params['resource_id'] );
1246 1317 $sql_field_arr[] = array( 'name' => 'modification_date', 'type' => '%s', 'value' => gmdate( 'Y-m-d H:i:s' ) );
1247 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] );
1248 - $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() );
1249 1320
1250 1321
1251 1322 if (
1252 1323 ( 0 == $create_params['is_edit_booking'] ) || // If not edit, then INSERT.
@@ -1267,12 +1338,15 @@
1267 1338 $sql_prepare_arr['name'] = implode( ', ', $sql_prepare_arr['name'] );
1268 1339 $sql_prepare_arr['type'] = implode( ', ', $sql_prepare_arr['type'] );
1269 1340 /* phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare */
1270 1341 $sql = $wpdb->prepare( "INSERT INTO {$wpdb->prefix}booking " . " ( {$sql_prepare_arr['name']} )" . " VALUES ( {$sql_prepare_arr['type']} )", $sql_prepare_arr['value'] );
1271 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1272 - if ( false === $wpdb->query( $sql ) ) {
1273 - return array( 'status' => 'error', 'message' => 'Error. INSERT New Data in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $sql );
1274 - }
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 + }
1275 1349 // Get ID of booking
1276 1350 $booking_id = (int) $wpdb->insert_id;
1277 1351
1278 1352 } else { // Edit - UPDATE
@@ -1286,14 +1360,15 @@
1286 1360 $sql_prepare_arr['set'] = implode( ', ', $sql_prepare_arr['set'] );
1287 1361
1288 1362 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1289 1363 $sql = $wpdb->prepare( "UPDATE {$wpdb->prefix}booking SET {$sql_prepare_arr['set']} WHERE booking_id={$booking_id};", $sql_prepare_arr['value'] );
1290 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1291 - if ( false === $wpdb->query( $sql ) ) {
1292 - return array( 'status' => 'error',
1293 - 'message' => 'Error. UPDATE Exist Data in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $sql,
1294 - );
1295 - }
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 + }
1296 1371
1297 1372 // Check if dates previously was approved.
1298 1373 $slct_sql = "SELECT approved FROM {$wpdb->prefix}bookingdates WHERE booking_id IN ({$booking_id}) LIMIT 0,1";
1299 1374 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
@@ -1584,9 +1659,9 @@
1584 1659 *
1585 1660 * // Now get start/end times as seconds: [ 64800, 72000 ]
1586 1661 * $time_as_seconds_arr = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $structured_booking_data_arr );
1587 1662 */
1588 - 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 ){
1589 1664
1590 1665 $selected_time_fields = wpbc_get__selected_time_fields__in_booking_form__as_arr( $booking_form_data__arr );
1591 1666
1592 1667 // 2.2 Get selected SECONDS to book ---------------------------------------------------------------------------
@@ -1625,13 +1700,70 @@
1625 1700 }
1626 1701 }
1627 1702 }
1628 1703
1629 - return $time_as_seconds_arr;
1630 - }
1631 -
1632 -
1704 + return $time_as_seconds_arr;
1705 + }
1706 +
1707 +
1633 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 + /**
1634 1766 * Remove administrator time-override values from an unauthorized booking request.
1635 1767 *
1636 1768 * The public booking endpoint intentionally accepts unauthenticated requests, so
1637 1769 * sanitizing these values is not sufficient authorization. Clearing every related
@@ -1655,8 +1787,67 @@
1655 1787 $request_params['wpbc_time_override_start'] = '';
1656 1788 $request_params['wpbc_time_override_end'] = '';
1657 1789
1658 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;
1659 1850 }
1660 1851
1661 1852
1662 1853 /**