| @@ -1,4 +1,4 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly // FixIn: 9.8.0.4. |
| 4 | 4 | |
| @@ -28,22 +28,28 @@ | ||
| 28 | 28 | // Response AJAX parameters |
| 29 | 29 | $ajx_data_arr = array(); |
| 30 | 30 | $ajx_data_arr['status'] = 'ok'; |
| 31 | 31 | |
| 32 | - $admin_uri = ltrim( str_replace( get_site_url( null, '', 'admin' ), '', admin_url( 'admin.php?' ) ), '/' ); // 'wp-admin/admin.php?' | |
| 33 | - $server_http_referer_uri = ( ( isset( $_SERVER['HTTP_REFERER'] ) ) ? sanitize_text_field( $_SERVER['HTTP_REFERER'] ) : '' ); /* phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash */ /* FixIn: sanitize_unslash */ | |
| 34 | - // Local parameters | |
| 35 | - $local_params = array(); | |
| 36 | - $local_params['is_from_admin_panel'] = ( false !== strpos( $server_http_referer_uri, $admin_uri ) ); // true | false | |
| 37 | - $local_params['user_id'] = ( isset( $_REQUEST['wpbc_ajx_user_id'] ) ) ? intval( $_REQUEST['wpbc_ajx_user_id'] ) : wpbc_get_current_user_id(); // 1 | |
| 32 | + // Local parameters | |
| 33 | + $local_params = array(); | |
| 34 | + $local_params['user_id'] = ( isset( $_REQUEST['wpbc_ajx_user_id'] ) ) ? intval( $_REQUEST['wpbc_ajx_user_id'] ) : wpbc_get_current_user_id(); // 1 | |
| 38 | 35 | |
| 39 | - // Request parameters | |
| 40 | - $user_request = new WPBC_AJX__REQUEST( array( // Using this class here only for escaping variables | |
| 41 | - 'db_option_name' => 'booking__wpbc_booking_create__request_params', // Not necessary, because we not save request, only sanitize it | |
| 42 | - 'user_id' => $local_params['user_id'], // Not necessary, because we not save request, only sanitize it | |
| 43 | - 'request_rules_structure' => array( | |
| 44 | - 'resource_id' => array( 'validate' => 'd', 'default' => 1 ), // 'digit_or_csd'. | |
| 45 | - 'aggregate_resource_id_arr' => array( 'validate' => 'digit_or_csd', 'default' => '' ), | |
| 36 | + // Request parameters for the released Appointment and Resource Selector workflows. | |
| 37 | + $workflow_request_rules = array( | |
| 38 | + 'service_id' => array( 'validate' => 'd', 'default' => 0 ), | |
| 39 | + 'appointment_service_required' => array( 'validate' => 'd', 'default' => 0 ), | |
| 40 | + 'appointment_context_token' => array( 'validate' => 'strong', 'default' => '' ), | |
| 41 | + 'resource_selector_required' => array( 'validate' => 'd', 'default' => 0 ), | |
| 42 | + 'resource_selector_context_token' => array( 'validate' => 'strong', 'default' => '' ), | |
| 43 | + 'wpbc_admin_booking_nonce' => array( 'validate' => 'strong', 'default' => '' ), | |
| 44 | + ); | |
| 45 | + | |
| 46 | + $user_request = new WPBC_AJX__REQUEST( array( // Using this class here only for escaping variables | |
| 47 | + 'db_option_name' => 'booking__wpbc_booking_create__request_params', // Not necessary, because we not save request, only sanitize it | |
| 48 | + 'user_id' => $local_params['user_id'], // Not necessary, because we not save request, only sanitize it | |
| 49 | + 'request_rules_structure' => array_merge( array( | |
| 50 | + 'resource_id' => array( 'validate' => 'd', 'default' => 1 ), // 'digit_or_csd'. | |
| 51 | + 'aggregate_resource_id_arr' => array( 'validate' => 'digit_or_csd', 'default' => '' ), | |
| 46 | 52 | 'dates_ddmmyy_csv' => array( 'validate' => 'csv_dates', 'default' => '' ), // FixIn: 9.9.1.1. |
| 47 | 53 | 'formdata' => array( 'validate' => 'strong', 'default' => '' ), |
| 48 | 54 | 'booking_hash' => array( 'validate' => 'strong', 'default' => '' ), |
| 49 | 55 | 'custom_form' => array( 'validate' => 'strong', 'default' => '' ), |
| @@ -51,19 +57,21 @@ | ||
| 51 | 57 | 'captcha_user_input' => array( 'validate' => 'strong', 'default' => '' ), |
| 52 | 58 | 'is_emails_send' => array( 'validate' => 'd', 'default' => 1 ), |
| 53 | 59 | 'active_locale' => array( 'validate' => 'strong', 'default' => '' ), |
| 54 | 60 | 'form_status' => array( 'validate' => 'strong', 'default' => 'published' ), |
| 55 | - 'allow_past' => array( 'validate' => 'd', 'default' => 0 ), | |
| 61 | + 'allow_past' => array( 'validate' => 'd', 'default' => 0 ), | |
| 62 | + 'classic_booking_context_token' => array( 'validate' => 'strong', 'default' => '' ), | |
| 56 | 63 | 'wpbc_bfb_preview' => array( 'validate' => 'd', 'default' => 0 ), |
| 57 | 64 | 'wpbc_bfb_preview_token' => array( 'validate' => 'strong', 'default' => '' ), |
| 58 | 65 | 'wpbc_bfb_preview_form_id' => array( 'validate' => 'd', 'default' => 0 ), |
| 59 | 66 | 'wpbc_bfb_preview_nonce' => array( 'validate' => 'strong', 'default' => '' ), |
| 60 | 67 | 'wpbc_time_override_enabled' => array( 'validate' => 'd', 'default' => 0 ), |
| 61 | - 'wpbc_time_override_source' => array( 'validate' => 'strong', 'default' => '' ), | |
| 62 | - 'wpbc_time_override_start' => array( 'validate' => 'strong', 'default' => '' ), | |
| 63 | - 'wpbc_time_override_end' => array( 'validate' => 'strong', 'default' => '' ), | |
| 64 | - ) | |
| 65 | - )); | |
| 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 ) | |
| 73 | + )); | |
| 66 | 74 | |
| 67 | 75 | // Escape of request params in Ajax Post. We use prefix 'calendar_request_params', if Ajax sent - $_REQUEST['calendar_request_params']['resource_id'], ... |
| 68 | 76 | $request_prefix = 'calendar_request_params'; |
| 69 | 77 | |
| @@ -68,15 +76,16 @@ | ||
| 68 | 76 | $request_prefix = 'calendar_request_params'; |
| 69 | 77 | |
| 70 | 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"; |
| 71 | 79 | |
| 72 | - $request_params = $user_request->get_sanitized__in_request__value_or_default( $request_prefix ); // NOT Direct: $_REQUEST['calendar_request_params']['resource_id'] | |
| 73 | - $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 */ | |
| 74 | - $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'] ); | |
| 75 | 84 | |
| 76 | 85 | // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- CAPTCHA " > |
| 77 | 86 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 78 | - 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 ] ); | |
| 79 | 88 | // </editor-fold> |
| 80 | 89 | |
| 81 | 90 | // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING_RESOURCE ID " > |
| 82 | 91 | if ( $request_params['resource_id'] <= 0 ) { |
| @@ -83,24 +92,21 @@ | ||
| 83 | 92 | $ajx_data_arr['status'] = 'error'; |
| 84 | 93 | $ajx_data_arr['status_error'] = 'resource_id_incorrect'; |
| 85 | 94 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 86 | 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'] . ' ]'; |
| 87 | - $ajx_data_arr['ajx_after_action_message_status'] = 'error'; | |
| 88 | - wp_send_json( array( | |
| 89 | - 'ajx_data' => $ajx_data_arr, | |
| 90 | - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized | |
| 91 | - 'ajx_search_params' => $_REQUEST[ $request_prefix ], | |
| 92 | - 'ajx_cleaned_params' => $request_params, | |
| 93 | - 'resource_id' => $request_params['resource_id'], | |
| 94 | - ) ); | |
| 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 | + ) ); | |
| 95 | 101 | } |
| 96 | 102 | // </editor-fold> |
| 97 | 103 | |
| 98 | 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 */ |
| 99 | 105 | |
| 100 | - $request_save_params = array( | |
| 101 | - 'resource_id' => $request_params['resource_id'], | |
| 102 | - 'dates_ddmmyy_csv' => $request_params['dates_ddmmyy_csv'], | |
| 106 | + $request_save_params = array( | |
| 107 | + 'resource_id' => $request_params['resource_id'], | |
| 108 | + 'dates_ddmmyy_csv' => $request_params['dates_ddmmyy_csv'], | |
| 103 | 109 | 'form_data' => $request_params['formdata'], |
| 104 | 110 | 'aggregate_resource_id_arr' => $request_params['aggregate_resource_id_arr'], // Optional can be ''. |
| 105 | 111 | 'booking_hash' => $request_params['booking_hash'], |
| 106 | 112 | 'custom_form' => $request_params['custom_form'], |
| @@ -108,29 +114,37 @@ | ||
| 108 | 114 | 'is_show_payment_form' => 1, |
| 109 | 115 | 'user_id' => $local_params['user_id'], |
| 110 | 116 | 'request_uri' => $server_http_referer_uri, |
| 111 | 117 | 'form_status' => $request_params['form_status'], |
| 112 | - 'allow_past' => $request_params['allow_past'], | |
| 118 | + 'allow_past' => $request_params['allow_past'], | |
| 119 | + 'classic_booking_context_token' => $request_params['classic_booking_context_token'], | |
| 113 | 120 | 'wpbc_bfb_preview' => $request_params['wpbc_bfb_preview'], |
| 114 | 121 | 'wpbc_bfb_preview_token' => $request_params['wpbc_bfb_preview_token'], |
| 115 | 122 | 'wpbc_bfb_preview_form_id' => $request_params['wpbc_bfb_preview_form_id'], |
| 116 | 123 | 'wpbc_bfb_preview_nonce' => $request_params['wpbc_bfb_preview_nonce'], |
| 117 | 124 | 'wpbc_time_override_enabled' => $request_params['wpbc_time_override_enabled'], |
| 118 | - 'wpbc_time_override_source' => $request_params['wpbc_time_override_source'], | |
| 119 | - 'wpbc_time_override_start' => $request_params['wpbc_time_override_start'], | |
| 120 | - 'wpbc_time_override_end' => $request_params['wpbc_time_override_end'], | |
| 121 | - ); | |
| 125 | + 'wpbc_time_override_source' => $request_params['wpbc_time_override_source'], | |
| 126 | + 'wpbc_time_override_start' => $request_params['wpbc_time_override_start'], | |
| 127 | + 'wpbc_time_override_end' => $request_params['wpbc_time_override_end'], | |
| 128 | + 'wpbc_admin_cost_correction' => $request_params['wpbc_admin_cost_correction'], | |
| 129 | + ); | |
| 130 | + $request_save_params['service_id'] = $request_params['service_id']; | |
| 131 | + $request_save_params['appointment_service_required'] = $request_params['appointment_service_required']; | |
| 132 | + $request_save_params['appointment_context_token'] = $request_params['appointment_context_token']; | |
| 133 | + $request_save_params['resource_selector_required'] = $request_params['resource_selector_required']; | |
| 134 | + $request_save_params['resource_selector_context_token'] = $request_params['resource_selector_context_token']; | |
| 135 | + $request_save_params['wpbc_admin_booking_nonce'] = $request_params['wpbc_admin_booking_nonce']; | |
| 122 | 136 | $booking_save_arr = wpbc_booking_save( $request_save_params ); |
| 123 | 137 | |
| 124 | 138 | // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING " > |
| 125 | 139 | if ( 'ok' !== $booking_save_arr['ajx_data']['status'] ) { |
| 126 | 140 | |
| 127 | - wp_send_json( array( 'ajx_data' => $booking_save_arr['ajx_data'], | |
| 128 | - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized | |
| 129 | - 'ajx_search_params' => $_REQUEST[ $request_prefix ], | |
| 130 | - 'ajx_cleaned_params' => $request_params, | |
| 131 | - 'resource_id' => $request_params['resource_id'] | |
| 132 | - )); | |
| 141 | + wp_send_json( | |
| 142 | + array( | |
| 143 | + 'ajx_data' => $booking_save_arr['ajx_data'], | |
| 144 | + 'resource_id' => $request_params['resource_id'], | |
| 145 | + ) | |
| 146 | + ); | |
| 133 | 147 | } |
| 134 | 148 | // </editor-fold> |
| 135 | 149 | |
| 136 | 150 | $ajx_data_arr = $booking_save_arr['ajx_data']; |
| @@ -252,11 +266,11 @@ | ||
| 252 | 266 | // 1. Direct Clean Params |
| 253 | 267 | // ----------------------------------------------------------------------------------------------------------------- |
| 254 | 268 | $server_request_uri = ( ( isset( $_SERVER['REQUEST_URI'] ) ) ? sanitize_text_field( $_SERVER['REQUEST_URI'] ) : '' ); /* phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash */ /* FixIn: sanitize_unslash */ |
| 255 | 269 | $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 */ |
| 256 | - $validate_arr_rules = array( | |
| 257 | - 'resource_id' => array( 'validate' => 'd', 'default' => 1 ), // INT | |
| 258 | - 'dates_ddmmyy_csv' => array( 'validate' => 'csv_dates', 'default' => '' ), // FixIn: 9.9.1.1. | |
| 270 | + $validate_arr_rules = array( | |
| 271 | + 'resource_id' => array( 'validate' => 'd', 'default' => 1 ), // INT | |
| 272 | + 'dates_ddmmyy_csv' => array( 'validate' => 'csv_dates', 'default' => '' ), // FixIn: 9.9.1.1. | |
| 259 | 273 | 'form_data' => array( 'validate' => 'strong', 'default' => '' ), |
| 260 | 274 | 'booking_hash' => array( 'validate' => 'strong', 'default' => '' ), |
| 261 | 275 | 'custom_form' => array( 'validate' => 'strong', 'default' => '' ), |
| 262 | 276 | 'is_emails_send' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1 |
| @@ -261,9 +275,10 @@ | ||
| 261 | 275 | 'custom_form' => array( 'validate' => 'strong', 'default' => '' ), |
| 262 | 276 | 'is_emails_send' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1 |
| 263 | 277 | 'is_show_payment_form' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1 |
| 264 | 278 | 'user_id' => array( 'validate' => 'd', 'default' => wpbc_get_current_user_id() ), // INT |
| 265 | - 'allow_past' => array( 'validate' => 'd', 'default' => 0 ), | |
| 279 | + 'allow_past' => array( 'validate' => 'd', 'default' => 0 ), | |
| 280 | + 'classic_booking_context_token' => array( 'validate' => 'strong', 'default' => '' ), | |
| 266 | 281 | 'request_uri' => array( 'validate' => 'strong', 'default' => ( ( defined( 'DOING_AJAX' ) ) && ( DOING_AJAX ) ) ? $server_http_referer_uri : $server_request_uri ), // front-end: $server_request_uri | ajax: $server_http_referer_uri |
| 267 | 282 | // Really Optional: |
| 268 | 283 | 'aggregate_resource_id_arr' => array( 'validate' => 'digit_or_csd', 'default' => '' ), |
| 269 | 284 | //TODO: this parameter does not transfer during saving, so here will be always default value 'bookings_only' // FixIn: 10.0.0.7. |
| @@ -278,17 +293,74 @@ | ||
| 278 | 293 | 'wpbc_bfb_preview_token' => array( 'validate' => 'strong', 'default' => '' ), |
| 279 | 294 | 'wpbc_bfb_preview_form_id' => array( 'validate' => 'd', 'default' => 0 ), |
| 280 | 295 | 'wpbc_bfb_preview_nonce' => array( 'validate' => 'strong', 'default' => '' ), |
| 281 | 296 | 'wpbc_time_override_enabled' => array( 'validate' => 'd', 'default' => 0 ), |
| 282 | - 'wpbc_time_override_source' => array( 'validate' => 'strong', 'default' => '' ), | |
| 283 | - 'wpbc_time_override_start' => array( 'validate' => 'strong', 'default' => '' ), | |
| 284 | - 'wpbc_time_override_end' => array( 'validate' => 'strong', 'default' => '' ), | |
| 285 | - ); | |
| 286 | - $re_cleaned_params = wpbc_sanitize_params_in_arr( $request_params, $validate_arr_rules ); | |
| 287 | - | |
| 288 | - $admin_uri = ltrim( str_replace( get_site_url( null, '', 'admin' ), '', admin_url( 'admin.php?' ) ), '/' ); // wp-admin/admin.php? | |
| 289 | - | |
| 290 | - $re_cleaned_params['form_status'] = sanitize_key( $re_cleaned_params['form_status'] ); | |
| 297 | + 'wpbc_time_override_source' => array( 'validate' => 'strong', 'default' => '' ), | |
| 298 | + 'wpbc_time_override_start' => array( 'validate' => 'strong', 'default' => '' ), | |
| 299 | + 'wpbc_time_override_end' => array( 'validate' => 'strong', 'default' => '' ), | |
| 300 | + 'wpbc_admin_cost_correction' => array( 'validate' => 'strong', 'default' => '' ), | |
| 301 | + ); | |
| 302 | + $validate_arr_rules['service_id'] = array( 'validate' => 'd', 'default' => 0 ); | |
| 303 | + $validate_arr_rules['appointment_service_required'] = array( 'validate' => 'd', 'default' => 0 ); | |
| 304 | + $validate_arr_rules['appointment_context_token'] = array( 'validate' => 'strong', 'default' => '' ); | |
| 305 | + $validate_arr_rules['resource_selector_required'] = array( 'validate' => 'd', 'default' => 0 ); | |
| 306 | + $validate_arr_rules['resource_selector_context_token'] = array( 'validate' => 'strong', 'default' => '' ); | |
| 307 | + $validate_arr_rules['wpbc_admin_booking_nonce'] = array( 'validate' => 'strong', 'default' => '' ); | |
| 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; | |
| 311 | + if ( ! empty( $re_cleaned_params['appointment_service_required'] ) && empty( $re_cleaned_params['service_id'] ) ) { | |
| 312 | + $ajx_data_arr['status'] = 'error'; | |
| 313 | + $ajx_data_arr['status_error'] = 'appointment_service_required'; | |
| 314 | + $ajx_data_arr['ajx_after_action_message'] = __( 'Please select a Service.', 'booking' ); | |
| 315 | + $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 316 | + return array( 'ajx_data' => $ajx_data_arr ); | |
| 317 | + } | |
| 318 | + if ( ! empty( $re_cleaned_params['service_id'] ) ) { | |
| 319 | + if ( ! function_exists( 'wpbc_booking_appointment_validate_submission_context' ) ) { | |
| 320 | + $appointment_context_check = new WP_Error( 'appointment_context_unavailable', __( 'The Appointment selection cannot be verified. Please reload the page and try again.', 'booking' ) ); | |
| 321 | + } else { | |
| 322 | + $appointment_context_check = wpbc_booking_appointment_validate_submission_context( | |
| 323 | + $re_cleaned_params['appointment_context_token'], | |
| 324 | + $re_cleaned_params['service_id'], | |
| 325 | + $re_cleaned_params['resource_id'] | |
| 326 | + ); | |
| 327 | + } | |
| 328 | + if ( is_wp_error( $appointment_context_check ) ) { | |
| 329 | + $ajx_data_arr['status'] = 'error'; | |
| 330 | + $ajx_data_arr['status_error'] = $appointment_context_check->get_error_code(); | |
| 331 | + $ajx_data_arr['ajx_after_action_message'] = $appointment_context_check->get_error_message(); | |
| 332 | + $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 333 | + return array( 'ajx_data' => $ajx_data_arr ); | |
| 334 | + } | |
| 335 | + $has_verified_appointment_context = true; | |
| 336 | + | |
| 337 | + // A client value cannot enable past Appointment creation; trust only the site-authored signed context. | |
| 338 | + $re_cleaned_params['allow_past'] = wpbc_booking_appointment_is_past_booking_enabled( $appointment_context_check ) ? 1 : 0; | |
| 339 | + } | |
| 340 | + if ( ! empty( $re_cleaned_params['resource_selector_required'] ) || ! empty( $re_cleaned_params['resource_selector_context_token'] ) ) { | |
| 341 | + if ( ! function_exists( 'wpbc_booking_resource_selector_validate_submission_context' ) ) { | |
| 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' ) ); | |
| 343 | + } else { | |
| 344 | + $resource_selector_context_check = wpbc_booking_resource_selector_validate_submission_context( | |
| 345 | + $re_cleaned_params['resource_selector_context_token'], | |
| 346 | + $re_cleaned_params['resource_id'] | |
| 347 | + ); | |
| 348 | + } | |
| 349 | + if ( is_wp_error( $resource_selector_context_check ) ) { | |
| 350 | + $ajx_data_arr['status'] = 'error'; | |
| 351 | + $ajx_data_arr['status_error'] = $resource_selector_context_check->get_error_code(); | |
| 352 | + $ajx_data_arr['ajx_after_action_message'] = $resource_selector_context_check->get_error_message(); | |
| 353 | + $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 354 | + return array( 'ajx_data' => $ajx_data_arr ); | |
| 355 | + } | |
| 356 | + $has_verified_resource_selector_context = true; | |
| 357 | + | |
| 358 | + // Trust only the site-authored signed selector context for public past bookings. | |
| 359 | + $re_cleaned_params['allow_past'] = wpbc_booking_resource_selector_is_past_booking_enabled( $resource_selector_context_check ) ? 1 : 0; | |
| 360 | + } | |
| 361 | + | |
| 362 | + $re_cleaned_params['form_status'] = sanitize_key( $re_cleaned_params['form_status'] ); | |
| 291 | 363 | if ( 'preview' !== $re_cleaned_params['form_status'] ) { |
| 292 | 364 | $re_cleaned_params['form_status'] = 'published'; |
| 293 | 365 | } |
| 294 | 366 | // FixIn: 2026-02-05 - make preview/published available to form parsing/templates during this request. |
| @@ -305,14 +377,26 @@ | ||
| 305 | 377 | |
| 306 | 378 | // ----------------------------------------------------------------------------------------------------------------- |
| 307 | 379 | // Local parameters |
| 308 | 380 | // ----------------------------------------------------------------------------------------------------------------- |
| 309 | - $local_params = array(); | |
| 310 | - $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; | |
| 311 | 384 | $local_params['user_id'] = $re_cleaned_params['user_id']; // 1 |
| 312 | - $local_params['sync_gid'] = $re_cleaned_params['sync_gid']; // '' | |
| 313 | - $local_params['is_approve_booking'] = $re_cleaned_params['is_approve_booking']; // 0 | 1 | |
| 314 | - $local_params['is_use_booking_recurrent_time'] = ( 1 === $re_cleaned_params['is_use_booking_recurrent_time'] ); // false | true | |
| 385 | + $local_params['sync_gid'] = $re_cleaned_params['sync_gid']; // '' | |
| 386 | + $local_params['is_approve_booking'] = $re_cleaned_params['is_approve_booking']; // 0 | 1 | |
| 387 | + $local_params['is_use_booking_recurrent_time'] = ( 1 === $re_cleaned_params['is_use_booking_recurrent_time'] ); // false | true | |
| 388 | + $request_action = isset( $_REQUEST['action'] ) && is_scalar( $_REQUEST['action'] ) | |
| 389 | + ? sanitize_key( (string) wp_unslash( $_REQUEST['action'] ) ) | |
| 390 | + : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 391 | + $is_public_booking_create_request = wp_doing_ajax() | |
| 392 | + && 'wpbc_ajx_booking__create' === strtolower( $request_action ) | |
| 393 | + && ! $is_authorized_admin_booking_request; | |
| 394 | + | |
| 395 | + // Time overrides belong exclusively to the capability-protected Add Booking administration workflow. | |
| 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 ); | |
| 315 | 399 | |
| 316 | 400 | // ----------------------------------------------------------------------------------------------------------------- |
| 317 | 401 | // Parse Local parameters for later use |
| 318 | 402 | // ----------------------------------------------------------------------------------------------------------------- |
| @@ -340,10 +424,71 @@ | ||
| 340 | 424 | 'name' => 'endtime', |
| 341 | 425 | 'value' => $local_params['time_override_arr']['end'], |
| 342 | 426 | ); |
| 343 | 427 | } |
| 344 | - // Important! : [ 64800, 72000 ] | |
| 345 | - $local_params['time_as_seconds_arr'] = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $local_params['structured_booking_data_arr'] ); | |
| 428 | + // Important! : [ 64800, 72000 ] | |
| 429 | + $local_params['time_as_seconds_arr'] = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $local_params['structured_booking_data_arr'] ); | |
| 430 | + $local_params['appointment_service'] = array(); | |
| 431 | + if ( ! empty( $re_cleaned_params['service_id'] ) && function_exists( 'wpbc_appointment_services_repository' ) ) { | |
| 432 | + $range_time_value = isset( $local_params['structured_booking_data_arr']['rangetime'] ) ? $local_params['structured_booking_data_arr']['rangetime'] : ''; | |
| 433 | + $start_time_value = isset( $local_params['structured_booking_data_arr']['starttime'] ) ? $local_params['structured_booking_data_arr']['starttime'] : ''; | |
| 434 | + $range_time_value = is_array( $range_time_value ) ? implode( '', $range_time_value ) : $range_time_value; | |
| 435 | + $start_time_value = is_array( $start_time_value ) ? implode( '', $start_time_value ) : $start_time_value; | |
| 436 | + $has_appointment_time = ! empty( $local_params['time_override_arr'] ) | |
| 437 | + || '' !== trim( (string) $range_time_value ) | |
| 438 | + || '' !== trim( (string) $start_time_value ); | |
| 439 | + if ( ! $has_appointment_time ) { | |
| 440 | + $ajx_data_arr['status'] = 'error'; | |
| 441 | + $ajx_data_arr['status_error'] = 'appointment_service_time_required'; | |
| 442 | + $ajx_data_arr['ajx_after_action_message'] = __( 'A Service appointment requires a start time. Add a time field to the Booking Form and select a time.', 'booking' ); | |
| 443 | + $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 444 | + return array( 'ajx_data' => $ajx_data_arr ); | |
| 445 | + } | |
| 446 | + $appointment_service = wpbc_appointment_services_repository()->find_active_for_resource( $re_cleaned_params['service_id'], $re_cleaned_params['resource_id'] ); | |
| 447 | + if ( is_wp_error( $appointment_service ) ) { | |
| 448 | + $ajx_data_arr['status'] = 'error'; | |
| 449 | + $ajx_data_arr['status_error'] = 'appointment_service_unavailable'; | |
| 450 | + $ajx_data_arr['ajx_after_action_message'] = $appointment_service->get_error_message(); | |
| 451 | + $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 452 | + return array( 'ajx_data' => $ajx_data_arr ); | |
| 453 | + } | |
| 454 | + if ( count( $local_params['time_as_seconds_arr'] ) < 2 || ! function_exists( 'wpbc_appointment_services_resolve_end_seconds' ) ) { | |
| 455 | + $ajx_data_arr['status'] = 'error'; | |
| 456 | + $ajx_data_arr['status_error'] = 'appointment_service_duration_invalid'; | |
| 457 | + $ajx_data_arr['ajx_after_action_message'] = __( 'The selected Service duration is invalid. Please contact the website administrator.', 'booking' ); | |
| 458 | + $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 459 | + return array( 'ajx_data' => $ajx_data_arr ); | |
| 460 | + } | |
| 461 | + $maximum_duration_minutes = absint( apply_filters( 'wpbc_booking_appointment_maximum_duration_minutes', 24 * 60, array() ) ); | |
| 462 | + $service_end_second = wpbc_appointment_services_resolve_end_seconds( $appointment_service, $local_params['time_as_seconds_arr'][0], $maximum_duration_minutes ); | |
| 463 | + if ( is_wp_error( $service_end_second ) ) { | |
| 464 | + $ajx_data_arr['status'] = 'error'; | |
| 465 | + $ajx_data_arr['status_error'] = $service_end_second->get_error_code(); | |
| 466 | + $ajx_data_arr['ajx_after_action_message'] = $service_end_second->get_error_message(); | |
| 467 | + $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 468 | + return array( 'ajx_data' => $ajx_data_arr ); | |
| 469 | + } | |
| 470 | + $local_params['time_as_seconds_arr'][1] = $service_end_second; | |
| 471 | + $local_params['appointment_service'] = $appointment_service; | |
| 472 | + $service_start_time = wpbc_transform__seconds__in__24_hours_his( $local_params['time_as_seconds_arr'][0] ); | |
| 473 | + $service_end_time = wpbc_transform__seconds__in__24_hours_his( $local_params['time_as_seconds_arr'][1] ); | |
| 474 | + unset( $local_params['structured_booking_data_arr']['rangetime'], $local_params['structured_booking_data_arr']['durationtime'] ); | |
| 475 | + $local_params['structured_booking_data_arr']['starttime'] = $service_start_time; | |
| 476 | + $local_params['structured_booking_data_arr']['endtime'] = $service_end_time; | |
| 477 | + unset( $local_params['all_booking_data_arr']['rangetime'], $local_params['all_booking_data_arr']['durationtime'] ); | |
| 478 | + $local_params['all_booking_data_arr']['starttime'] = array( 'type' => 'text', 'original_name' => 'starttime' . $re_cleaned_params['resource_id'], 'name' => 'starttime', 'value' => $service_start_time ); | |
| 479 | + $local_params['all_booking_data_arr']['endtime'] = array( 'type' => 'text', 'original_name' => 'endtime' . $re_cleaned_params['resource_id'], 'name' => 'endtime', 'value' => $service_end_time ); | |
| 480 | + } | |
| 481 | + if ( function_exists( 'wpbc_appointment_services_sync_service_hint_booking_data' ) ) { | |
| 482 | + $service_hint_booking_data = wpbc_appointment_services_sync_service_hint_booking_data( | |
| 483 | + $local_params['structured_booking_data_arr'], | |
| 484 | + $local_params['all_booking_data_arr'], | |
| 485 | + $local_params['appointment_service'], | |
| 486 | + $re_cleaned_params['resource_id'] | |
| 487 | + ); | |
| 488 | + $local_params['structured_booking_data_arr'] = $service_hint_booking_data['structured_booking_data']; | |
| 489 | + $local_params['all_booking_data_arr'] = $service_hint_booking_data['all_booking_data']; | |
| 490 | + } | |
| 346 | 491 | // [ "18:00:00", "20:00:00" ] |
| 347 | 492 | $time_as_seconds_arr = $local_params['time_as_seconds_arr']; |
| 348 | 493 | $time_as_seconds_arr[0] = ( 0 != $time_as_seconds_arr[0] ) ? $time_as_seconds_arr[0] + 1 : $time_as_seconds_arr[0]; // set check in time with ended 1 second |
| 349 | 494 | $time_as_seconds_arr[1] = ( ( 24 * 60 * 60 ) != $time_as_seconds_arr[1] ) ? $time_as_seconds_arr[1] + 2 : $time_as_seconds_arr[1]; // set check out time with ended 2 seconds |
| @@ -355,19 +500,77 @@ | ||
| 355 | 500 | wpbc_transform__seconds__in__24_hours_his( $time_as_seconds_arr[0] ), |
| 356 | 501 | wpbc_transform__seconds__in__24_hours_his( $time_as_seconds_arr[1] ) |
| 357 | 502 | ); |
| 358 | 503 | // [ '2023-09-10', '2023-09-11' ] |
| 359 | - $local_params['dates_only_sql_arr'] = wpbc_convert_dates_str__dd_mm_yyyy__to__yyyy_mm_dd( $re_cleaned_params["dates_ddmmyy_csv"] ); | |
| 360 | - $local_params['dates_only_sql_arr'] = explode( ',', $local_params['dates_only_sql_arr'] ); | |
| 504 | + $local_params['dates_only_sql_arr'] = wpbc_convert_dates_str__dd_mm_yyyy__to__yyyy_mm_dd( $re_cleaned_params["dates_ddmmyy_csv"] ); | |
| 505 | + $local_params['dates_only_sql_arr'] = explode( ',', $local_params['dates_only_sql_arr'] ); | |
| 506 | + | |
| 507 | + $classic_context = array(); | |
| 508 | + $has_verified_classic_context = false; | |
| 509 | + if ( ! empty( $re_cleaned_params['classic_booking_context_token'] ) && function_exists( 'wpbc_classic_booking_context_validate_submission' ) ) { | |
| 510 | + $classic_context = wpbc_classic_booking_context_validate_submission( | |
| 511 | + $re_cleaned_params['classic_booking_context_token'], | |
| 512 | + $re_cleaned_params['resource_id'], | |
| 513 | + $local_params['dates_only_sql_arr'], | |
| 514 | + $re_cleaned_params['custom_form'], | |
| 515 | + $re_cleaned_params['aggregate_resource_id_arr'] | |
| 516 | + ); | |
| 517 | + if ( is_wp_error( $classic_context ) ) { | |
| 518 | + $ajx_data_arr['status'] = 'error'; | |
| 519 | + $ajx_data_arr['status_error'] = $classic_context->get_error_code(); | |
| 520 | + $ajx_data_arr['ajx_after_action_message'] = $classic_context->get_error_message(); | |
| 521 | + $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 522 | + return array( 'ajx_data' => $ajx_data_arr ); | |
| 523 | + } | |
| 524 | + | |
| 525 | + $has_verified_classic_context = true; | |
| 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'] ); | |
| 529 | + } | |
| 530 | + | |
| 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 | |
| 544 | + ); | |
| 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 | + } | |
| 552 | + } | |
| 553 | + | |
| 554 | + if ( | |
| 555 | + ( ! empty( $local_params['time_override_arr'] ) ) | |
| 556 | + && ( 'times_availability' === $local_params['time_override_arr']['source'] ) | |
| 557 | + && ( count( array_filter( $local_params['dates_only_sql_arr'] ) ) > 1 ) | |
| 558 | + ) { | |
| 559 | + $local_params['is_use_booking_recurrent_time'] = true; | |
| 560 | + } | |
| 361 | 561 | |
| 362 | 562 | $local_params['is_show_payment_form'] = $re_cleaned_params["is_show_payment_form"]; |
| 363 | 563 | |
| 364 | 564 | // FixIn: 9.9.0.35. |
| 365 | - if ( $local_params['is_show_payment_form'] ) { | |
| 366 | - $local_params['is_show_payment_form'] = ( false !== strpos( $re_cleaned_params['request_uri'], 'is_show_payment_form=Off' ) ) | |
| 367 | - ? 0 | |
| 368 | - : $local_params['is_show_payment_form']; // 1|0 | |
| 369 | - } | |
| 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 | + } | |
| 370 | 573 | |
| 371 | 574 | // Get EDIT booking data |
| 372 | 575 | $local_params['edit_resource_id'] = ''; |
| 373 | 576 | $local_params['skip_booking_id'] = ''; |
| @@ -373,27 +576,44 @@ | ||
| 373 | 576 | $local_params['skip_booking_id'] = ''; |
| 374 | 577 | $local_params['is_edit_booking'] = 0; |
| 375 | 578 | $local_params['is_duplicate_booking'] = 0; |
| 376 | 579 | $is_edit_booking = wpbc_get_data__if_edit_booking( $re_cleaned_params['booking_hash'], $re_cleaned_params['request_uri'] ); |
| 377 | - if ( false !== $is_edit_booking ) { | |
| 378 | - $local_params['edit_resource_id'] = $is_edit_booking['resource_id']; // can be parent booking resource, where we edit the booking | |
| 379 | - $local_params['skip_booking_id'] = $is_edit_booking['booking_id']; // booking ID | |
| 380 | - $local_params['is_edit_booking'] = $is_edit_booking['booking_id']; // booking ID | |
| 580 | + if ( false !== $is_edit_booking ) { | |
| 581 | + $local_params['edit_resource_id'] = $is_edit_booking['resource_id']; // can be parent booking resource, where we edit the booking | |
| 582 | + $local_params['skip_booking_id'] = $is_edit_booking['booking_id']; // booking ID | |
| 583 | + $local_params['is_edit_booking'] = $is_edit_booking['booking_id']; // booking ID | |
| 381 | 584 | if ( |
| 382 | 585 | ( ! empty( $local_params['structured_booking_data_arr']['wpbc_other_action'] ) ) |
| 383 | 586 | && ( 'duplicate_booking' === $local_params['structured_booking_data_arr']['wpbc_other_action'] ) |
| 384 | 587 | ){ |
| 385 | - $local_params['is_duplicate_booking'] = 1; | |
| 386 | - } | |
| 387 | - } | |
| 388 | - // It can be request resource ID or if we edit booking, it can be 'edit resource' - (e.g. child resource) | |
| 588 | + $local_params['is_duplicate_booking'] = 1; | |
| 589 | + } | |
| 590 | + } | |
| 591 | + | |
| 592 | + $is_frontend_ajax_edit = wp_doing_ajax() | |
| 593 | + && 'wpbc_ajx_booking__create' === strtolower( $request_action ) | |
| 594 | + && 0 !== $local_params['is_edit_booking']; | |
| 595 | + $is_authorized_admin_edit = $is_authorized_admin_booking_request; | |
| 596 | + | |
| 597 | + if ( | |
| 598 | + $is_frontend_ajax_edit | |
| 599 | + && ! $is_authorized_admin_edit | |
| 600 | + && ! wpbc_is_visitor_booking_action_allowed( $local_params['is_edit_booking'] ) | |
| 601 | + ) { | |
| 602 | + $ajx_data_arr['status'] = 'error'; | |
| 603 | + $ajx_data_arr['status_error'] = 'visitor_booking_dates_in_past'; | |
| 604 | + $ajx_data_arr['ajx_after_action_message'] = __( 'This booking can no longer be edited because its dates have already passed.', 'booking' ); | |
| 605 | + $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 606 | + return array( 'ajx_data' => $ajx_data_arr ); | |
| 607 | + } | |
| 608 | + // It can be request resource ID or if we edit booking, it can be 'edit resource' - (e.g. child resource) | |
| 389 | 609 | $local_params['initial_resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) ) ? $local_params['edit_resource_id'] : $re_cleaned_params['resource_id']; |
| 390 | 610 | |
| 391 | - // 2 | |
| 392 | - $local_params['how_many_items_to_book'] = wpbc_get__how_many_items_to_book__in_booking_form( $local_params['structured_booking_data_arr'], $local_params['initial_resource_id'] ); | |
| 393 | - | |
| 394 | - | |
| 395 | - $local_params['aggregate_resource_id_arr'] = explode( ',', $re_cleaned_params['aggregate_resource_id_arr'] ); | |
| 611 | + // 2 | |
| 612 | + $local_params['how_many_items_to_book'] = wpbc_get__how_many_items_to_book__in_booking_form( $local_params['structured_booking_data_arr'], $local_params['initial_resource_id'] ); | |
| 613 | + | |
| 614 | + | |
| 615 | + $local_params['aggregate_resource_id_arr'] = explode( ',', $re_cleaned_params['aggregate_resource_id_arr'] ); | |
| 396 | 616 | $local_params['aggregate_resource_id_arr'] = array_filter( $local_params['aggregate_resource_id_arr'] ); // All entries of array equal to FALSE (0, '', '0' ) will be removed. |
| 397 | 617 | $local_params['aggregate_resource_id_arr'] = array_unique( $local_params['aggregate_resource_id_arr'] ); // Erase duplicates |
| 398 | 618 | |
| 399 | 619 | // ----------------------------------------------------------------------------------------------------------------- |
| @@ -440,8 +660,9 @@ | ||
| 440 | 660 | 'how_many_items_to_book' => $local_params['how_many_items_to_book'], // 1 |
| 441 | 661 | 'request_uri' => $re_cleaned_params['request_uri'], // 'http://beta/resource-id2/' |
| 442 | 662 | 'allow_past' => ! empty( $re_cleaned_params['allow_past'] ), |
| 443 | 663 | 'is_use_booking_recurrent_time' => $local_params['is_use_booking_recurrent_time'], // true | false |
| 664 | + 'time_override_source' => ! empty( $local_params['time_override_arr']['source'] ) ? $local_params['time_override_arr']['source'] : '', | |
| 444 | 665 | 'as_single_resource' => false, // false |
| 445 | 666 | 'aggregate_resource_id_arr' => $local_params['aggregate_resource_id_arr'], // Optional can be '' |
| 446 | 667 | '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. |
| 447 | 668 | 'custom_form' => $re_cleaned_params['custom_form'] // FixIn: 10.0.0.10. |
| @@ -458,12 +679,29 @@ | ||
| 458 | 679 | |
| 459 | 680 | // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " > |
| 460 | 681 | $php_performance = wpbc_php_performance_END( 'wpbc__where_to_save_booking' , $php_performance ); |
| 461 | 682 | // </editor-fold> |
| 462 | - } | |
| 463 | - | |
| 464 | - | |
| 465 | - // Get parameters, from REQUEST | |
| 683 | + } | |
| 684 | + | |
| 685 | + if ( ! empty( $local_params['appointment_service'] ) && function_exists( 'wpbc_appointment_services_check_buffer_conflicts' ) ) { | |
| 686 | + $buffer_check = wpbc_appointment_services_check_buffer_conflicts( | |
| 687 | + $local_params['appointment_service'], | |
| 688 | + $where_to_save_booking['main__resource_id'], | |
| 689 | + array_keys( $where_to_save_booking['resources_in_dates'] ), | |
| 690 | + $local_params['time_as_seconds_arr'], | |
| 691 | + $local_params['skip_booking_id'] | |
| 692 | + ); | |
| 693 | + if ( is_wp_error( $buffer_check ) ) { | |
| 694 | + $ajx_data_arr['status'] = 'error'; | |
| 695 | + $ajx_data_arr['status_error'] = 'appointment_service_buffer_conflict'; | |
| 696 | + $ajx_data_arr['ajx_after_action_message'] = $buffer_check->get_error_message(); | |
| 697 | + $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 698 | + return array( 'ajx_data' => $ajx_data_arr ); | |
| 699 | + } | |
| 700 | + } | |
| 701 | + | |
| 702 | + | |
| 703 | + // Get parameters, from REQUEST | |
| 466 | 704 | $create_params = $local_params; |
| 467 | 705 | $create_params['resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) ) |
| 468 | 706 | ? $local_params['edit_resource_id'] // If we edit, then use original resource ??? |
| 469 | 707 | : $where_to_save_booking['main__resource_id']; // Here is important TIP, resource can be where is free, and not where we submit |
| @@ -488,9 +726,9 @@ | ||
| 488 | 726 | |
| 489 | 727 | // ----------------------------------------------------------------------------------------------------------------- |
| 490 | 728 | // == CREATE_THE 'NEW_BOOKING' == |
| 491 | 729 | // ----------------------------------------------------------------------------------------------------------------- |
| 492 | - $create_booking_params = array( | |
| 730 | + $create_booking_params = array( | |
| 493 | 731 | 'resource_id' => $create_params['resource_id'], |
| 494 | 732 | 'custom_form' => $create_params['custom_form'], |
| 495 | 733 | 'all_booking_data_arr' => $create_params['all_booking_data_arr'], |
| 496 | 734 | 'dates_only_sql_arr' => $create_params['dates_only_sql_arr'], |
| @@ -499,23 +737,26 @@ | ||
| 499 | 737 | 'is_edit_booking' => $create_params['is_edit_booking'], |
| 500 | 738 | 'is_duplicate_booking' => $create_params['is_duplicate_booking'], |
| 501 | 739 | 'is_approve_booking' => $create_params['is_approve_booking'], |
| 502 | 740 | 'how_many_items_to_book' => $create_params['how_many_items_to_book'], |
| 503 | - 'is_use_booking_recurrent_time' => $create_params['is_use_booking_recurrent_time'] // true | false | |
| 504 | - ); | |
| 741 | + 'is_use_booking_recurrent_time' => $create_params['is_use_booking_recurrent_time'] // true | false | |
| 742 | + ); | |
| 743 | + if ( ! empty( $create_params['appointment_service'] ) ) { $create_booking_params['appointment_service'] = $create_params['appointment_service']; } | |
| 505 | 744 | if ( ! empty( $create_params['sync_gid'] ) ) { $create_booking_params['sync_gid'] = $create_params['sync_gid']; } |
| 506 | 745 | |
| 507 | 746 | $booking_new_arr = wpbc_db__booking_save( $create_booking_params, $where_to_save_booking ); |
| 508 | 747 | |
| 509 | 748 | // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING CREATION " > |
| 510 | - if ( 'ok' !== $booking_new_arr['status'] ) { | |
| 749 | + if ( 'ok' !== $booking_new_arr['status'] ) { | |
| 511 | 750 | $ajx_data_arr['status'] = $booking_new_arr['status']; |
| 512 | 751 | $ajx_data_arr['status_error'] = 'booking_can_not_save'; |
| 513 | 752 | $ajx_data_arr['ajx_after_action_message'] = $booking_new_arr['message']; |
| 514 | 753 | $ajx_data_arr['ajx_after_action_message_status'] = 'error'; |
| 515 | 754 | return array( 'ajx_data' => $ajx_data_arr ); |
| 516 | - } | |
| 517 | - // </editor-fold> | |
| 755 | + } | |
| 756 | + // </editor-fold> | |
| 757 | + | |
| 758 | + do_action( 'wpbc_booking_after_save', $booking_new_arr['booking_id'], $create_booking_params, $where_to_save_booking ); | |
| 518 | 759 | |
| 519 | 760 | // FixIn: 9.9.0.36. |
| 520 | 761 | if ( |
| 521 | 762 | ( 0 !== $create_params['is_edit_booking'] ) // If edit booking |
| @@ -543,9 +784,10 @@ | ||
| 543 | 784 | ); |
| 544 | 785 | $str_dates__dd_mm_yyyy = wpbc_convert_dates_arr__yyyy_mm_dd__to__dd_mm_yyyy( $payment_params['booked_dates_times_arr']['dates_ymd_arr'] ); // ['2023-10-20','2023-10-25'] => ['20.10.2023','25.10.2023'] |
| 545 | 786 | $payment_params['str_dates__dd_mm_yyyy'] = implode( ',', $str_dates__dd_mm_yyyy ); // REQUIRED -- '14.11.2023, 15.11.2023, 16.11.2023, 17.11.2023' |
| 546 | 787 | $payment_params['booking_id'] = $booking_new_arr['booking_id']; // REQUIRED -- '2' |
| 547 | - $payment_params['resource_id'] = $create_params['resource_id']; // REQUIRED -- '2' can be child resource (changed in wpbc_where_to_save() ) | |
| 788 | + $payment_params['resource_id'] = $create_params['resource_id']; // REQUIRED -- '2' can be child resource (changed in wpbc_where_to_save() ) | |
| 789 | + $payment_params['service_id'] = ! empty( $create_params['appointment_service']['service_id'] ) ? absint( $create_params['appointment_service']['service_id'] ) : 0; | |
| 548 | 790 | $payment_params['initial_resource_id'] = $local_params['initial_resource_id']; // REQUIRED -- '2' initial calendar - parent resource |
| 549 | 791 | $payment_params['form_data'] = $booking_new_arr['form_data']; // we re-save it, because here can be sync_guid and custom form new data from wpbc_db__booking_save(..) // REQUIRED -- 'text^selected_short_timedates_hint4^06/11/2018 14:00...' |
| 550 | 792 | $payment_params['times_array'] = array( |
| 551 | 793 | explode( ':', $where_to_save_booking['time_to_book'][0] ), // ["10","00","00"] |
| @@ -555,9 +797,10 @@ | ||
| 555 | 797 | $payment_params['is_edit_booking'] = $create_params['is_edit_booking']; // => 0 0 | int - ID of the booking |
| 556 | 798 | $payment_params['custom_form'] = $create_params['custom_form']; // => '' '' | 'some_name' |
| 557 | 799 | $payment_params['is_duplicate_booking'] = $create_params['is_duplicate_booking']; // => 0 0 | 1 |
| 558 | 800 | $payment_params['is_from_admin_panel'] = $create_params['is_from_admin_panel']; // => false true | false |
| 559 | - $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']; | |
| 560 | 803 | if ( $payment_params['is_from_admin_panel'] ) { |
| 561 | 804 | // $payment_params['is_show_payment_form'] = 0; // FixIn: 9.9.0.21. |
| 562 | 805 | } |
| 563 | 806 | // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " > |
| @@ -931,9 +1174,13 @@ | ||
| 931 | 1174 | return array( 'status' => 'error', 'message' => 'Sent request with no dates.' ); |
| 932 | 1175 | } |
| 933 | 1176 | |
| 934 | 1177 | // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- CHECK_IN_DATE_OLDER_THAN_CHECK_OUT " > |
| 935 | - if ( count( $create_params['dates_only_sql_arr'] ) == 1 ) { // Is it single selected date ? | |
| 1178 | + $is_no_dates_booking = ( | |
| 1179 | + function_exists( 'wpbc_is_these_dates__for__no_dates' ) | |
| 1180 | + && wpbc_is_these_dates__for__no_dates( $create_params['dates_only_sql_arr'] ) | |
| 1181 | + ); | |
| 1182 | + if ( ( count( $create_params['dates_only_sql_arr'] ) == 1 ) && ( ! $is_no_dates_booking ) ) { // Is it single selected date ? | |
| 936 | 1183 | |
| 937 | 1184 | // Is 'check in' date/time older than 'check out' date/time when SINGLE day for booking? Then show error. |
| 938 | 1185 | |
| 939 | 1186 | /** |
| @@ -1019,9 +1266,9 @@ | ||
| 1019 | 1266 | $sql_field_arr[] = array( 'name' => 'form', 'type' => '%s', 'value' => $form_data ); |
| 1020 | 1267 | $sql_field_arr[] = array( 'name' => 'booking_type', 'type' => '%d', 'value' => $create_params['resource_id'] ); |
| 1021 | 1268 | $sql_field_arr[] = array( 'name' => 'modification_date', 'type' => '%s', 'value' => gmdate( 'Y-m-d H:i:s' ) ); |
| 1022 | 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] ); |
| 1023 | - $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() ); | |
| 1024 | 1271 | |
| 1025 | 1272 | |
| 1026 | 1273 | if ( |
| 1027 | 1274 | ( 0 == $create_params['is_edit_booking'] ) || // If not edit, then INSERT. |
| @@ -1042,12 +1289,15 @@ | ||
| 1042 | 1289 | $sql_prepare_arr['name'] = implode( ', ', $sql_prepare_arr['name'] ); |
| 1043 | 1290 | $sql_prepare_arr['type'] = implode( ', ', $sql_prepare_arr['type'] ); |
| 1044 | 1291 | /* phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare */ |
| 1045 | 1292 | $sql = $wpdb->prepare( "INSERT INTO {$wpdb->prefix}booking " . " ( {$sql_prepare_arr['name']} )" . " VALUES ( {$sql_prepare_arr['type']} )", $sql_prepare_arr['value'] ); |
| 1046 | - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter | |
| 1047 | - if ( false === $wpdb->query( $sql ) ) { | |
| 1048 | - return array( 'status' => 'error', 'message' => 'Error. INSERT New Data in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $sql ); | |
| 1049 | - } | |
| 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 | + } | |
| 1050 | 1300 | // Get ID of booking |
| 1051 | 1301 | $booking_id = (int) $wpdb->insert_id; |
| 1052 | 1302 | |
| 1053 | 1303 | } else { // Edit - UPDATE |
| @@ -1061,14 +1311,15 @@ | ||
| 1061 | 1311 | $sql_prepare_arr['set'] = implode( ', ', $sql_prepare_arr['set'] ); |
| 1062 | 1312 | |
| 1063 | 1313 | // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare |
| 1064 | 1314 | $sql = $wpdb->prepare( "UPDATE {$wpdb->prefix}booking SET {$sql_prepare_arr['set']} WHERE booking_id={$booking_id};", $sql_prepare_arr['value'] ); |
| 1065 | - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter | |
| 1066 | - if ( false === $wpdb->query( $sql ) ) { | |
| 1067 | - return array( 'status' => 'error', | |
| 1068 | - 'message' => 'Error. UPDATE Exist Data in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $sql, | |
| 1069 | - ); | |
| 1070 | - } | |
| 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 | + } | |
| 1071 | 1322 | |
| 1072 | 1323 | // Check if dates previously was approved. |
| 1073 | 1324 | $slct_sql = "SELECT approved FROM {$wpdb->prefix}bookingdates WHERE booking_id IN ({$booking_id}) LIMIT 0,1"; |
| 1074 | 1325 | // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter |
| @@ -1359,9 +1610,9 @@ | ||
| 1359 | 1610 | * |
| 1360 | 1611 | * // Now get start/end times as seconds: [ 64800, 72000 ] |
| 1361 | 1612 | * $time_as_seconds_arr = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $structured_booking_data_arr ); |
| 1362 | 1613 | */ |
| 1363 | - 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 ){ | |
| 1364 | 1615 | |
| 1365 | 1616 | $selected_time_fields = wpbc_get__selected_time_fields__in_booking_form__as_arr( $booking_form_data__arr ); |
| 1366 | 1617 | |
| 1367 | 1618 | // 2.2 Get selected SECONDS to book --------------------------------------------------------------------------- |
| @@ -1400,14 +1651,159 @@ | ||
| 1400 | 1651 | } |
| 1401 | 1652 | } |
| 1402 | 1653 | } |
| 1403 | 1654 | |
| 1404 | - return $time_as_seconds_arr; | |
| 1405 | - } | |
| 1406 | - | |
| 1407 | - | |
| 1408 | - /** | |
| 1409 | - * Get explicit admin-selected time override from Add Booking modal request. | |
| 1655 | + return $time_as_seconds_arr; | |
| 1656 | + } | |
| 1657 | + | |
| 1658 | + | |
| 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 | + /** | |
| 1717 | + * Remove administrator time-override values from an unauthorized booking request. | |
| 1718 | + * | |
| 1719 | + * The public booking endpoint intentionally accepts unauthenticated requests, so | |
| 1720 | + * sanitizing these values is not sufficient authorization. Clearing every related | |
| 1721 | + * value here prevents a public client from replacing the Booking Form's configured | |
| 1722 | + * time while preserving the capability-protected Add Booking workflow. | |
| 1723 | + * | |
| 1724 | + * @param array $request_params Sanitized booking request parameters. | |
| 1725 | + * @param bool $is_authorized_admin_booking_request Whether the current request is an authorized Add Booking administration request. | |
| 1726 | + * | |
| 1727 | + * @return array Booking request parameters with unauthorized override values removed. | |
| 1728 | + */ | |
| 1729 | + function wpbc_restrict_booking_time_override_to_authorized_admin( $request_params, $is_authorized_admin_booking_request ) { | |
| 1730 | + | |
| 1731 | + $request_params = is_array( $request_params ) ? $request_params : array(); | |
| 1732 | + if ( $is_authorized_admin_booking_request ) { | |
| 1733 | + return $request_params; | |
| 1734 | + } | |
| 1735 | + | |
| 1736 | + $request_params['wpbc_time_override_enabled'] = 0; | |
| 1737 | + $request_params['wpbc_time_override_source'] = ''; | |
| 1738 | + $request_params['wpbc_time_override_start'] = ''; | |
| 1739 | + $request_params['wpbc_time_override_end'] = ''; | |
| 1740 | + | |
| 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; | |
| 1801 | + } | |
| 1802 | + | |
| 1803 | + | |
| 1804 | + /** | |
| 1805 | + * Get explicit admin-selected time override from Add Booking modal request. | |
| 1410 | 1806 | * |
| 1411 | 1807 | * @param array $request_params Sanitized booking request params. |
| 1412 | 1808 | * |
| 1413 | 1809 | * @return array Empty array or array with start/end HH:MM values. |