| @@ -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 | /** |