PluginProbe
Booking Calendar / 11.8.3
Booking Calendar v11.8.3
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 10.11 10.11.2 All 203 releases
← All changes | includes/_capacity/create_booking.php +217 -75 11.611.8.3 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'];
@@ -294,18 +293,22 @@
294 293 'wpbc_bfb_preview_token' => array( 'validate' => 'strong', 'default' => '' ),
295 294 'wpbc_bfb_preview_form_id' => array( 'validate' => 'd', 'default' => 0 ),
296 295 'wpbc_bfb_preview_nonce' => array( 'validate' => 'strong', 'default' => '' ),
297 296 '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' => '' ),
297 + 'wpbc_time_override_source' => array( 'validate' => 'strong', 'default' => '' ),
298 + 'wpbc_time_override_start' => array( 'validate' => 'strong', 'default' => '' ),
300 299 'wpbc_time_override_end' => array( 'validate' => 'strong', 'default' => '' ),
301 - );
300 + 'wpbc_admin_cost_correction' => array( 'validate' => 'strong', 'default' => '' ),
301 + );
302 302 $validate_arr_rules['service_id'] = array( 'validate' => 'd', 'default' => 0 );
303 303 $validate_arr_rules['appointment_service_required'] = array( 'validate' => 'd', 'default' => 0 );
304 304 $validate_arr_rules['appointment_context_token'] = array( 'validate' => 'strong', 'default' => '' );
305 305 $validate_arr_rules['resource_selector_required'] = array( 'validate' => 'd', 'default' => 0 );
306 306 $validate_arr_rules['resource_selector_context_token'] = array( 'validate' => 'strong', 'default' => '' );
307 + $validate_arr_rules['wpbc_admin_booking_nonce'] = array( 'validate' => 'strong', 'default' => '' );
307 308 $re_cleaned_params = wpbc_sanitize_params_in_arr( $request_params, $validate_arr_rules );
309 + $has_verified_appointment_context = false;
310 + $has_verified_resource_selector_context = false;
308 311 if ( ! empty( $re_cleaned_params['appointment_service_required'] ) && empty( $re_cleaned_params['service_id'] ) ) {
309 312 $ajx_data_arr['status'] = 'error';
310 313 $ajx_data_arr['status_error'] = 'appointment_service_required';
311 314 $ajx_data_arr['ajx_after_action_message'] = __( 'Please select a Service.', 'booking' );
@@ -328,13 +331,14 @@
328 331 $ajx_data_arr['ajx_after_action_message'] = $appointment_context_check->get_error_message();
329 332 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
330 333 return array( 'ajx_data' => $ajx_data_arr );
331 334 }
335 + $has_verified_appointment_context = true;
332 336
333 337 // A client value cannot enable past Appointment creation; trust only the site-authored signed context.
334 338 $re_cleaned_params['allow_past'] = wpbc_booking_appointment_is_past_booking_enabled( $appointment_context_check ) ? 1 : 0;
335 339 }
336 - if ( ! empty( $re_cleaned_params['resource_selector_required'] ) ) {
340 + if ( ! empty( $re_cleaned_params['resource_selector_required'] ) || ! empty( $re_cleaned_params['resource_selector_context_token'] ) ) {
337 341 if ( ! function_exists( 'wpbc_booking_resource_selector_validate_submission_context' ) ) {
338 342 $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 343 } else {
340 344 $resource_selector_context_check = wpbc_booking_resource_selector_validate_submission_context(
@@ -348,16 +352,15 @@
348 352 $ajx_data_arr['ajx_after_action_message'] = $resource_selector_context_check->get_error_message();
349 353 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
350 354 return array( 'ajx_data' => $ajx_data_arr );
351 355 }
356 + $has_verified_resource_selector_context = true;
352 357
353 358 // Trust only the site-authored signed selector context for public past bookings.
354 359 $re_cleaned_params['allow_past'] = wpbc_booking_resource_selector_is_past_booking_enabled( $resource_selector_context_check ) ? 1 : 0;
355 360 }
356 361
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'] );
362 + $re_cleaned_params['form_status'] = sanitize_key( $re_cleaned_params['form_status'] );
360 363 if ( 'preview' !== $re_cleaned_params['form_status'] ) {
361 364 $re_cleaned_params['form_status'] = 'published';
362 365 }
363 366 // FixIn: 2026-02-05 - make preview/published available to form parsing/templates during this request.
@@ -374,10 +377,11 @@
374 377
375 378 // -----------------------------------------------------------------------------------------------------------------
376 379 // Local parameters
377 380 // -----------------------------------------------------------------------------------------------------------------
378 - $local_params = array();
379 - $local_params['is_from_admin_panel'] = ( false !== strpos( $re_cleaned_params['request_uri'], $admin_uri ) ); // true | false
381 + $local_params = array();
382 + $is_authorized_admin_booking_request = wpbc_is_authorized_admin_booking_request( $re_cleaned_params['wpbc_admin_booking_nonce'] );
383 + $local_params['is_from_admin_panel'] = $is_authorized_admin_booking_request;
380 384 $local_params['user_id'] = $re_cleaned_params['user_id']; // 1
381 385 $local_params['sync_gid'] = $re_cleaned_params['sync_gid']; // ''
382 386 $local_params['is_approve_booking'] = $re_cleaned_params['is_approve_booking']; // 0 | 1
383 387 $local_params['is_use_booking_recurrent_time'] = ( 1 === $re_cleaned_params['is_use_booking_recurrent_time'] ); // false | true
@@ -383,13 +387,8 @@
383 387 $local_params['is_use_booking_recurrent_time'] = ( 1 === $re_cleaned_params['is_use_booking_recurrent_time'] ); // false | true
384 388 $request_action = isset( $_REQUEST['action'] ) && is_scalar( $_REQUEST['action'] )
385 389 ? sanitize_key( (string) wp_unslash( $_REQUEST['action'] ) )
386 390 : ''; // 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 391 $is_public_booking_create_request = wp_doing_ajax()
393 392 && 'wpbc_ajx_booking__create' === strtolower( $request_action )
394 393 && ! $is_authorized_admin_booking_request;
395 394
@@ -394,8 +393,10 @@
394 393 && ! $is_authorized_admin_booking_request;
395 394
396 395 // Time overrides belong exclusively to the capability-protected Add Booking administration workflow.
397 396 $re_cleaned_params = wpbc_restrict_booking_time_override_to_authorized_admin( $re_cleaned_params, $is_authorized_admin_booking_request );
397 + // Cost corrections belong exclusively to capability-protected administrator booking workflows.
398 + $re_cleaned_params = wpbc_restrict_booking_cost_correction_to_authorized_admin( $re_cleaned_params, $is_authorized_admin_booking_request );
398 399
399 400 // -----------------------------------------------------------------------------------------------------------------
400 401 // Parse Local parameters for later use
401 402 // -----------------------------------------------------------------------------------------------------------------
@@ -502,8 +503,9 @@
502 503 // [ '2023-09-10', '2023-09-11' ]
503 504 $local_params['dates_only_sql_arr'] = wpbc_convert_dates_str__dd_mm_yyyy__to__yyyy_mm_dd( $re_cleaned_params["dates_ddmmyy_csv"] );
504 505 $local_params['dates_only_sql_arr'] = explode( ',', $local_params['dates_only_sql_arr'] );
505 506
507 + $classic_context = array();
506 508 $has_verified_classic_context = false;
507 509 if ( ! empty( $re_cleaned_params['classic_booking_context_token'] ) && function_exists( 'wpbc_classic_booking_context_validate_submission' ) ) {
508 510 $classic_context = wpbc_classic_booking_context_validate_submission(
509 511 $re_cleaned_params['classic_booking_context_token'],
@@ -519,22 +521,38 @@
519 521 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
520 522 return array( 'ajx_data' => $ajx_data_arr );
521 523 }
522 524
523 - $has_verified_classic_context = true;
525 + $has_verified_classic_context = true;
524 526 $re_cleaned_params['allow_past'] = ! empty( $classic_context['allow_past'] ) ? 1 : 0;
527 + // Pass only the signed canonical set into final availability and persistence decisions.
528 + $re_cleaned_params['aggregate_resource_id_arr'] = implode( ',', $classic_context['aggregate_resource_ids'] );
525 529 }
526 530
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'] ) )
531 + if ( $is_public_booking_create_request && ! $has_verified_classic_context ) {
532 + $ajx_data_arr['status'] = 'error';
533 + $ajx_data_arr['status_error'] = 'classic_booking_context_required';
534 + $ajx_data_arr['ajx_after_action_message'] = __( 'The booking form context has expired. Please reload the page and try again.', 'booking' );
535 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
536 + return array( 'ajx_data' => $ajx_data_arr );
537 + }
538 +
539 + if ( $has_verified_classic_context ) {
540 + $workflow_context_error = wpbc_booking_create_validate_required_workflow(
541 + $classic_context,
542 + $has_verified_appointment_context,
543 + $has_verified_resource_selector_context
530 544 );
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'] );
545 + if ( is_wp_error( $workflow_context_error ) ) {
546 + $ajx_data_arr['status'] = 'error';
547 + $ajx_data_arr['status_error'] = $workflow_context_error->get_error_code();
548 + $ajx_data_arr['ajx_after_action_message'] = $workflow_context_error->get_error_message();
549 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
550 + return array( 'ajx_data' => $ajx_data_arr );
551 + }
534 552 }
535 -
536 - if (
553 +
554 + if (
537 555 ( ! empty( $local_params['time_override_arr'] ) )
538 556 && ( 'times_availability' === $local_params['time_override_arr']['source'] )
539 557 && ( count( array_filter( $local_params['dates_only_sql_arr'] ) ) > 1 )
540 558 ) {
@@ -543,13 +561,16 @@
543 561
544 562 $local_params['is_show_payment_form'] = $re_cleaned_params["is_show_payment_form"];
545 563
546 564 // 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 - }
565 + if ( $local_params['is_show_payment_form'] ) {
566 + $local_params['is_show_payment_form'] = (
567 + $is_authorized_admin_booking_request
568 + && false !== strpos( $re_cleaned_params['request_uri'], 'is_show_payment_form=Off' )
569 + )
570 + ? 0
571 + : $local_params['is_show_payment_form']; // 1|0
572 + }
552 573
553 574 // Get EDIT booking data
554 575 $local_params['edit_resource_id'] = '';
555 576 $local_params['skip_booking_id'] = '';
@@ -776,9 +797,10 @@
776 797 $payment_params['is_edit_booking'] = $create_params['is_edit_booking']; // => 0 0 | int - ID of the booking
777 798 $payment_params['custom_form'] = $create_params['custom_form']; // => '' '' | 'some_name'
778 799 $payment_params['is_duplicate_booking'] = $create_params['is_duplicate_booking']; // => 0 0 | 1
779 800 $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
801 + $payment_params['is_show_payment_form'] = $create_params['is_show_payment_form']; // => 1 0 | 1
802 + $payment_params['wpbc_admin_cost_correction'] = $re_cleaned_params['wpbc_admin_cost_correction'];
781 803 if ( $payment_params['is_from_admin_panel'] ) {
782 804 // $payment_params['is_show_payment_form'] = 0; // FixIn: 9.9.0.21.
783 805 }
784 806 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
@@ -1244,9 +1266,9 @@
1244 1266 $sql_field_arr[] = array( 'name' => 'form', 'type' => '%s', 'value' => $form_data );
1245 1267 $sql_field_arr[] = array( 'name' => 'booking_type', 'type' => '%d', 'value' => $create_params['resource_id'] );
1246 1268 $sql_field_arr[] = array( 'name' => 'modification_date', 'type' => '%s', 'value' => gmdate( 'Y-m-d H:i:s' ) );
1247 1269 $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 ) );
1270 + $sql_field_arr[] = array( 'name' => 'hash', 'type' => '%s', 'value' => wpbc_hash__generate_booking_hash() );
1249 1271
1250 1272
1251 1273 if (
1252 1274 ( 0 == $create_params['is_edit_booking'] ) || // If not edit, then INSERT.
@@ -1267,12 +1289,15 @@
1267 1289 $sql_prepare_arr['name'] = implode( ', ', $sql_prepare_arr['name'] );
1268 1290 $sql_prepare_arr['type'] = implode( ', ', $sql_prepare_arr['type'] );
1269 1291 /* phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare */
1270 1292 $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 - }
1293 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1294 + if ( false === $wpdb->query( $sql ) ) {
1295 + return array(
1296 + 'status' => 'error',
1297 + 'message' => __( 'The booking could not be saved because of a database error. Please try again or contact the website administrator.', 'booking' ),
1298 + );
1299 + }
1275 1300 // Get ID of booking
1276 1301 $booking_id = (int) $wpdb->insert_id;
1277 1302
1278 1303 } else { // Edit - UPDATE
@@ -1286,14 +1311,15 @@
1286 1311 $sql_prepare_arr['set'] = implode( ', ', $sql_prepare_arr['set'] );
1287 1312
1288 1313 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1289 1314 $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 - }
1315 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1316 + if ( false === $wpdb->query( $sql ) ) {
1317 + return array(
1318 + 'status' => 'error',
1319 + 'message' => __( 'The booking could not be updated because of a database error. Please try again or contact the website administrator.', 'booking' ),
1320 + );
1321 + }
1296 1322
1297 1323 // Check if dates previously was approved.
1298 1324 $slct_sql = "SELECT approved FROM {$wpdb->prefix}bookingdates WHERE booking_id IN ({$booking_id}) LIMIT 0,1";
1299 1325 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
@@ -1584,9 +1610,9 @@
1584 1610 *
1585 1611 * // Now get start/end times as seconds: [ 64800, 72000 ]
1586 1612 * $time_as_seconds_arr = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $structured_booking_data_arr );
1587 1613 */
1588 - function wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $booking_form_data__arr ){
1614 + function wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $booking_form_data__arr ){
1589 1615
1590 1616 $selected_time_fields = wpbc_get__selected_time_fields__in_booking_form__as_arr( $booking_form_data__arr );
1591 1617
1592 1618 // 2.2 Get selected SECONDS to book ---------------------------------------------------------------------------
@@ -1625,13 +1651,70 @@
1625 1651 }
1626 1652 }
1627 1653 }
1628 1654
1629 - return $time_as_seconds_arr;
1630 - }
1631 -
1632 -
1655 + return $time_as_seconds_arr;
1656 + }
1657 +
1658 +
1633 1659 /**
1660 + * Determine whether a booking-create request is an authorized administration workflow.
1661 + *
1662 + * The public booking action is intentionally available to signed-out visitors. A
1663 + * Referer, request path, or caller-supplied Boolean therefore cannot establish an
1664 + * administrator security context. The Add Booking UI supplies this user-bound nonce,
1665 + * and the server independently rechecks login, capability, and MultiUser access.
1666 + *
1667 + * @param mixed $admin_booking_nonce Candidate Add Booking administration nonce.
1668 + *
1669 + * @return bool True only for an authorized Add Booking administration request.
1670 + */
1671 + function wpbc_is_authorized_admin_booking_request( $admin_booking_nonce ) {
1672 +
1673 + if (
1674 + ! is_scalar( $admin_booking_nonce )
1675 + || '' === trim( (string) $admin_booking_nonce )
1676 + || ! is_user_logged_in()
1677 + || ! wp_verify_nonce( sanitize_text_field( (string) $admin_booking_nonce ), 'wpbc_admin_booking_create' )
1678 + || ! class_exists( 'WPBC_Add_Booking_Component' )
1679 + || ! WPBC_Add_Booking_Component::current_user_can_add_booking()
1680 + || ! wpbc_is_mu_user_can_be_here( 'activated_user' )
1681 + ) {
1682 + return false;
1683 + }
1684 +
1685 + return true;
1686 + }
1687 +
1688 +
1689 + /**
1690 + * Require the signed workflow proof declared by a verified Booking Form context.
1691 + *
1692 + * Appointment and Resource Selector JavaScript flags are presentation hints only.
1693 + * The signed Booking Form context identifies the server-rendered workflow, so removing
1694 + * a flag or domain token cannot downgrade that form to a different workflow.
1695 + *
1696 + * @param array $classic_context Verified Booking Form context.
1697 + * @param bool $has_verified_appointment_context Whether Service and Provider proof passed validation.
1698 + * @param bool $has_verified_resource_selector_context Whether Resource Selector proof passed validation.
1699 + *
1700 + * @return true|WP_Error True when the required proof is present, otherwise a safe validation error.
1701 + */
1702 + function wpbc_booking_create_validate_required_workflow( $classic_context, $has_verified_appointment_context, $has_verified_resource_selector_context ) {
1703 +
1704 + $booking_workflow = isset( $classic_context['booking_workflow'] ) ? sanitize_key( $classic_context['booking_workflow'] ) : '';
1705 + if ( 'appointment' === $booking_workflow && ! $has_verified_appointment_context ) {
1706 + return new WP_Error( 'appointment_context_required', __( 'The Appointment selection has expired. Please start over and try again.', 'booking' ) );
1707 + }
1708 + if ( 'resource_selector' === $booking_workflow && ! $has_verified_resource_selector_context ) {
1709 + return new WP_Error( 'resource_selector_context_required', __( 'The Booking Resource selection has expired. Please start over and try again.', 'booking' ) );
1710 + }
1711 +
1712 + return true;
1713 + }
1714 +
1715 +
1716 + /**
1634 1717 * Remove administrator time-override values from an unauthorized booking request.
1635 1718 *
1636 1719 * The public booking endpoint intentionally accepts unauthenticated requests, so
1637 1720 * sanitizing these values is not sufficient authorization. Clearing every related
@@ -1655,8 +1738,67 @@
1655 1738 $request_params['wpbc_time_override_start'] = '';
1656 1739 $request_params['wpbc_time_override_end'] = '';
1657 1740
1658 1741 return $request_params;
1742 + }
1743 +
1744 +
1745 + /**
1746 + * Authorize and normalize an administrator cost-correction request value.
1747 + *
1748 + * Booking creation is intentionally public, so a sanitized numeric value is
1749 + * not sufficient authorization. Only capability-protected Add Booking and
1750 + * Add Appointment workflows in Business Small or higher may retain this value.
1751 + * Missing, malformed, out-of-range, public, and unsupported-edition values
1752 + * are reduced to an empty sentinel, which preserves automatic calculation.
1753 + *
1754 + * @param array $request_params Sanitized booking request parameters.
1755 + * @param bool $is_authorized_admin_booking_request Whether this is an authorized administrator booking request.
1756 + *
1757 + * @return array Booking request parameters with a normalized or empty cost correction.
1758 + */
1759 + function wpbc_restrict_booking_cost_correction_to_authorized_admin( $request_params, $is_authorized_admin_booking_request ) {
1760 +
1761 + $request_params = is_array( $request_params ) ? $request_params : array();
1762 + $raw_cost = isset( $request_params['wpbc_admin_cost_correction'] ) ? $request_params['wpbc_admin_cost_correction'] : '';
1763 +
1764 + $request_params['wpbc_admin_cost_correction'] = '';
1765 + if ( ! $is_authorized_admin_booking_request || ! class_exists( 'wpdev_bk_biz_s' ) ) {
1766 + return $request_params;
1767 + }
1768 +
1769 + $request_params['wpbc_admin_cost_correction'] = wpbc_sanitize_booking_cost_correction( $raw_cost );
1770 +
1771 + return $request_params;
1772 + }
1773 +
1774 +
1775 + /**
1776 + * Sanitize one exact administrator-entered Booking total.
1777 + *
1778 + * @param mixed $raw_cost Raw request value.
1779 + *
1780 + * @return string Normalized decimal without trailing zeroes, or an empty string when invalid.
1781 + */
1782 + function wpbc_sanitize_booking_cost_correction( $raw_cost ) {
1783 +
1784 + if ( ! is_scalar( $raw_cost ) ) {
1785 + return '';
1786 + }
1787 +
1788 + $raw_cost = trim( sanitize_text_field( (string) $raw_cost ) );
1789 + if ( '' === $raw_cost || ! preg_match( '/^[0-9]{1,10}(?:\.[0-9]{1,8})?$/', $raw_cost ) ) {
1790 + return '';
1791 + }
1792 +
1793 + $normalized_cost = (float) $raw_cost;
1794 + if ( ! is_finite( $normalized_cost ) || $normalized_cost < 0 || $normalized_cost > 1000000000 ) {
1795 + return '';
1796 + }
1797 +
1798 + $normalized_cost = rtrim( rtrim( number_format( $normalized_cost, 8, '.', '' ), '0' ), '.' );
1799 +
1800 + return '' === $normalized_cost ? '0' : $normalized_cost;
1659 1801 }
1660 1802
1661 1803
1662 1804 /**