| @@ -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 |
| @@ -78,15 +76,16 @@ | ||
| 78 | 76 | $request_prefix = 'calendar_request_params'; |
| 79 | 77 | |
| 80 | 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"; |
| 81 | 79 | |
| 82 | - $request_params = $user_request->get_sanitized__in_request__value_or_default( $request_prefix ); // NOT Direct: $_REQUEST['calendar_request_params']['resource_id'] | |
| 83 | - $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 */ | |
| 84 | - $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'] ); | |
| 85 | 84 | |
| 86 | 85 | // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- CAPTCHA " > |
| 87 | 86 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 88 | - 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 ] ); | |
| 89 | 88 | // </editor-fold> |
| 90 | 89 | |
| 91 | 90 | // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING_RESOURCE ID " > |
| 92 | 91 | if ( $request_params['resource_id'] <= 0 ) { |
| @@ -93,16 +92,13 @@ | ||
| 93 | 92 | $ajx_data_arr['status'] = 'error'; |
| 94 | 93 | $ajx_data_arr['status_error'] = 'resource_id_incorrect'; |
| 95 | 94 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 96 | 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'] . ' ]'; |
| 97 | - $ajx_data_arr['ajx_after_action_message_status'] = 'error'; | |
| 98 | - wp_send_json( array( | |
| 99 | - 'ajx_data' => $ajx_data_arr, | |
| 100 | - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized | |
| 101 | - 'ajx_search_params' => $_REQUEST[ $request_prefix ], | |
| 102 | - 'ajx_cleaned_params' => $request_params, | |
| 103 | - 'resource_id' => $request_params['resource_id'], | |
| 104 | - ) ); | |
| 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 | + ) ); | |
| 105 | 101 | } |
| 106 | 102 | // </editor-fold> |
| 107 | 103 | |
| 108 | 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 */ |
| @@ -135,19 +131,20 @@ | ||
| 135 | 131 | $request_save_params['appointment_service_required'] = $request_params['appointment_service_required']; |
| 136 | 132 | $request_save_params['appointment_context_token'] = $request_params['appointment_context_token']; |
| 137 | 133 | $request_save_params['resource_selector_required'] = $request_params['resource_selector_required']; |
| 138 | 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']; | |
| 139 | 136 | $booking_save_arr = wpbc_booking_save( $request_save_params ); |
| 140 | 137 | |
| 141 | 138 | // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING " > |
| 142 | 139 | if ( 'ok' !== $booking_save_arr['ajx_data']['status'] ) { |
| 143 | 140 | |
| 144 | - wp_send_json( array( 'ajx_data' => $booking_save_arr['ajx_data'], | |
| 145 | - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized | |
| 146 | - 'ajx_search_params' => $_REQUEST[ $request_prefix ], | |
| 147 | - 'ajx_cleaned_params' => $request_params, | |
| 148 | - 'resource_id' => $request_params['resource_id'] | |
| 149 | - )); | |
| 141 | + wp_send_json( | |
| 142 | + array( | |
| 143 | + 'ajx_data' => $booking_save_arr['ajx_data'], | |
| 144 | + 'resource_id' => $request_params['resource_id'], | |
| 145 | + ) | |
| 146 | + ); | |
| 150 | 147 | } |
| 151 | 148 | // </editor-fold> |
| 152 | 149 | |
| 153 | 150 | $ajx_data_arr = $booking_save_arr['ajx_data']; |
| @@ -216,10 +213,94 @@ | ||
| 216 | 213 | // --------------------------------------------------------------------------------------------------------------------- |
| 217 | 214 | // == Save Booking |
| 218 | 215 | // --------------------------------------------------------------------------------------------------------------------- |
| 219 | 216 | |
| 220 | -/** | |
| 221 | - * Save Booking - ADD NEW or UPDATE exist booking | |
| 217 | +/** | |
| 218 | + * Resolve and validate the final booking destination against current storage. | |
| 219 | + * | |
| 220 | + * This function contains the availability, Appointment working-time, and | |
| 221 | + * Appointment buffer checks that must be repeated if the database connection | |
| 222 | + * loses its advisory lock before persistence. The caller clears the relevant | |
| 223 | + * request-local cache before every invocation. | |
| 224 | + * | |
| 225 | + * @param array $local_params Parsed booking parameters, passed by reference because force-save mode fixes capacity at one. | |
| 226 | + * @param array $cleaned_params Sanitized booking request parameters. | |
| 227 | + * @param array $php_performance Performance measurements, passed by reference. | |
| 228 | + * | |
| 229 | + * @return array|WP_Error Validated storage destination, or a visitor-safe validation error. | |
| 230 | + */ | |
| 231 | +function wpbc_booking_validate_save_availability( &$local_params, $cleaned_params, &$php_performance ) { | |
| 232 | + | |
| 233 | + // Privileged imports and other established integrations may intentionally force a save. | |
| 234 | + if ( ! empty( $cleaned_params['save_booking_even_if_unavailable'] ) ) { | |
| 235 | + $local_params['how_many_items_to_book'] = 1; | |
| 236 | + $dates_keys_arr = array_values( $local_params['dates_only_sql_arr'] ); | |
| 237 | + $resources_in_dates = array_fill_keys( $dates_keys_arr, array( $local_params['initial_resource_id'] ) ); | |
| 238 | + $where_to_save_booking = array( | |
| 239 | + 'result' => 'ok', | |
| 240 | + 'resources_in_dates' => $resources_in_dates, | |
| 241 | + 'time_to_book' => $local_params['time_as_his_arr'], | |
| 242 | + 'main__resource_id' => $local_params['initial_resource_id'], | |
| 243 | + ); | |
| 244 | + } else { | |
| 245 | + $php_performance = wpbc_php_performance_START( 'wpbc__where_to_save_booking', $php_performance ); | |
| 246 | + | |
| 247 | + $where_to_save_booking = wpbc__where_to_save_booking( | |
| 248 | + array( | |
| 249 | + 'resource_id' => $local_params['initial_resource_id'], | |
| 250 | + 'skip_booking_id' => $local_params['skip_booking_id'], | |
| 251 | + 'dates_only_sql_arr' => $local_params['dates_only_sql_arr'], | |
| 252 | + 'time_as_seconds_arr' => $local_params['time_as_seconds_arr'], | |
| 253 | + 'how_many_items_to_book' => $local_params['how_many_items_to_book'], | |
| 254 | + 'request_uri' => $cleaned_params['request_uri'], | |
| 255 | + 'allow_past' => ! empty( $cleaned_params['allow_past'] ), | |
| 256 | + 'is_use_booking_recurrent_time' => $local_params['is_use_booking_recurrent_time'], | |
| 257 | + 'time_override_source' => ! empty( $local_params['time_override_arr']['source'] ) ? $local_params['time_override_arr']['source'] : '', | |
| 258 | + 'as_single_resource' => false, | |
| 259 | + 'aggregate_resource_id_arr' => $local_params['aggregate_resource_id_arr'], | |
| 260 | + 'aggregate_type' => $cleaned_params['aggregate_type'], | |
| 261 | + 'custom_form' => $cleaned_params['custom_form'], | |
| 262 | + ) | |
| 263 | + ); | |
| 264 | + | |
| 265 | + if ( 'error' === $where_to_save_booking['result'] ) { | |
| 266 | + return new WP_Error( 'booking_can_not_save', $where_to_save_booking['message'] ); | |
| 267 | + } | |
| 268 | + | |
| 269 | + $php_performance = wpbc_php_performance_END( 'wpbc__where_to_save_booking', $php_performance ); | |
| 270 | + } | |
| 271 | + | |
| 272 | + if ( ! empty( $local_params['appointment_service'] ) && function_exists( 'wpbc_appointment_services_check_working_time' ) ) { | |
| 273 | + $working_time_check = wpbc_appointment_services_check_working_time( | |
| 274 | + $local_params['appointment_service'], | |
| 275 | + $where_to_save_booking['main__resource_id'], | |
| 276 | + array_keys( $where_to_save_booking['resources_in_dates'] ), | |
| 277 | + $local_params['time_as_seconds_arr'] | |
| 278 | + ); | |
| 279 | + if ( is_wp_error( $working_time_check ) ) { | |
| 280 | + return $working_time_check; | |
| 281 | + } | |
| 282 | + } | |
| 283 | + | |
| 284 | + if ( ! empty( $local_params['appointment_service'] ) && function_exists( 'wpbc_appointment_services_check_buffer_conflicts' ) ) { | |
| 285 | + $buffer_check = wpbc_appointment_services_check_buffer_conflicts( | |
| 286 | + $local_params['appointment_service'], | |
| 287 | + $where_to_save_booking['main__resource_id'], | |
| 288 | + array_keys( $where_to_save_booking['resources_in_dates'] ), | |
| 289 | + $local_params['time_as_seconds_arr'], | |
| 290 | + $local_params['skip_booking_id'] | |
| 291 | + ); | |
| 292 | + if ( is_wp_error( $buffer_check ) ) { | |
| 293 | + return $buffer_check; | |
| 294 | + } | |
| 295 | + } | |
| 296 | + | |
| 297 | + return $where_to_save_booking; | |
| 298 | +} | |
| 299 | + | |
| 300 | + | |
| 301 | +/** | |
| 302 | + * Save Booking - ADD NEW or UPDATE exist booking | |
| 222 | 303 | * |
| 223 | 304 | * @param $request_params = [ |
| 224 | 305 | * resource_id = 2 REQUIRED Default: 1 |
| 225 | 306 | * dates_ddmmyy_csv = '27.10.2023, 28.10.2023, 29.10.2023' REQUIRED |
| @@ -306,9 +387,12 @@ | ||
| 306 | 387 | $validate_arr_rules['appointment_service_required'] = array( 'validate' => 'd', 'default' => 0 ); |
| 307 | 388 | $validate_arr_rules['appointment_context_token'] = array( 'validate' => 'strong', 'default' => '' ); |
| 308 | 389 | $validate_arr_rules['resource_selector_required'] = array( 'validate' => 'd', 'default' => 0 ); |
| 309 | 390 | $validate_arr_rules['resource_selector_context_token'] = array( 'validate' => 'strong', 'default' => '' ); |
| 391 | + $validate_arr_rules['wpbc_admin_booking_nonce'] = array( 'validate' => 'strong', 'default' => '' ); | |
| 310 | 392 | $re_cleaned_params = wpbc_sanitize_params_in_arr( $request_params, $validate_arr_rules ); |
| 393 | + $has_verified_appointment_context = false; | |
| 394 | + $has_verified_resource_selector_context = false; | |
| 311 | 395 | if ( ! empty( $re_cleaned_params['appointment_service_required'] ) && empty( $re_cleaned_params['service_id'] ) ) { |
| 312 | 396 | $ajx_data_arr['status'] = 'error'; |
| 313 | 397 | $ajx_data_arr['status_error'] = 'appointment_service_required'; |
| 314 | 398 | $ajx_data_arr['ajx_after_action_message'] = __( 'Please select a Service.', 'booking' ); |
| @@ -331,13 +415,14 @@ | ||
| 331 | 415 | $ajx_data_arr['ajx_after_action_message'] = $appointment_context_check->get_error_message(); |
| 332 | 416 | $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; |
| 333 | 417 | return array( 'ajx_data' => $ajx_data_arr ); |
| 334 | 418 | } |
| 419 | + $has_verified_appointment_context = true; | |
| 335 | 420 | |
| 336 | 421 | // A client value cannot enable past Appointment creation; trust only the site-authored signed context. |
| 337 | 422 | $re_cleaned_params['allow_past'] = wpbc_booking_appointment_is_past_booking_enabled( $appointment_context_check ) ? 1 : 0; |
| 338 | 423 | } |
| 339 | - if ( ! empty( $re_cleaned_params['resource_selector_required'] ) ) { | |
| 424 | + if ( ! empty( $re_cleaned_params['resource_selector_required'] ) || ! empty( $re_cleaned_params['resource_selector_context_token'] ) ) { | |
| 340 | 425 | if ( ! function_exists( 'wpbc_booking_resource_selector_validate_submission_context' ) ) { |
| 341 | 426 | $resource_selector_context_check = new WP_Error( 'resource_selector_context_unavailable', __( 'The Booking Resource selection cannot be verified. Please reload the page and try again.', 'booking' ) ); |
| 342 | 427 | } else { |
| 343 | 428 | $resource_selector_context_check = wpbc_booking_resource_selector_validate_submission_context( |
| @@ -351,16 +436,15 @@ | ||
| 351 | 436 | $ajx_data_arr['ajx_after_action_message'] = $resource_selector_context_check->get_error_message(); |
| 352 | 437 | $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; |
| 353 | 438 | return array( 'ajx_data' => $ajx_data_arr ); |
| 354 | 439 | } |
| 440 | + $has_verified_resource_selector_context = true; | |
| 355 | 441 | |
| 356 | 442 | // Trust only the site-authored signed selector context for public past bookings. |
| 357 | 443 | $re_cleaned_params['allow_past'] = wpbc_booking_resource_selector_is_past_booking_enabled( $resource_selector_context_check ) ? 1 : 0; |
| 358 | 444 | } |
| 359 | 445 | |
| 360 | - $admin_uri = ltrim( str_replace( get_site_url( null, '', 'admin' ), '', admin_url( 'admin.php?' ) ), '/' ); // wp-admin/admin.php? | |
| 361 | - | |
| 362 | - $re_cleaned_params['form_status'] = sanitize_key( $re_cleaned_params['form_status'] ); | |
| 446 | + $re_cleaned_params['form_status'] = sanitize_key( $re_cleaned_params['form_status'] ); | |
| 363 | 447 | if ( 'preview' !== $re_cleaned_params['form_status'] ) { |
| 364 | 448 | $re_cleaned_params['form_status'] = 'published'; |
| 365 | 449 | } |
| 366 | 450 | // FixIn: 2026-02-05 - make preview/published available to form parsing/templates during this request. |
| @@ -377,10 +461,11 @@ | ||
| 377 | 461 | |
| 378 | 462 | // ----------------------------------------------------------------------------------------------------------------- |
| 379 | 463 | // Local parameters |
| 380 | 464 | // ----------------------------------------------------------------------------------------------------------------- |
| 381 | - $local_params = array(); | |
| 382 | - $local_params['is_from_admin_panel'] = ( false !== strpos( $re_cleaned_params['request_uri'], $admin_uri ) ); // true | false | |
| 465 | + $local_params = array(); | |
| 466 | + $is_authorized_admin_booking_request = wpbc_is_authorized_admin_booking_request( $re_cleaned_params['wpbc_admin_booking_nonce'] ); | |
| 467 | + $local_params['is_from_admin_panel'] = $is_authorized_admin_booking_request; | |
| 383 | 468 | $local_params['user_id'] = $re_cleaned_params['user_id']; // 1 |
| 384 | 469 | $local_params['sync_gid'] = $re_cleaned_params['sync_gid']; // '' |
| 385 | 470 | $local_params['is_approve_booking'] = $re_cleaned_params['is_approve_booking']; // 0 | 1 |
| 386 | 471 | $local_params['is_use_booking_recurrent_time'] = ( 1 === $re_cleaned_params['is_use_booking_recurrent_time'] ); // false | true |
| @@ -386,13 +471,8 @@ | ||
| 386 | 471 | $local_params['is_use_booking_recurrent_time'] = ( 1 === $re_cleaned_params['is_use_booking_recurrent_time'] ); // false | true |
| 387 | 472 | $request_action = isset( $_REQUEST['action'] ) && is_scalar( $_REQUEST['action'] ) |
| 388 | 473 | ? sanitize_key( (string) wp_unslash( $_REQUEST['action'] ) ) |
| 389 | 474 | : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 390 | - $is_authorized_admin_booking_request = $local_params['is_from_admin_panel'] | |
| 391 | - && is_user_logged_in() | |
| 392 | - && class_exists( 'WPBC_Add_Booking_Component' ) | |
| 393 | - && WPBC_Add_Booking_Component::current_user_can_add_booking() | |
| 394 | - && wpbc_is_mu_user_can_be_here( 'activated_user' ); | |
| 395 | 475 | $is_public_booking_create_request = wp_doing_ajax() |
| 396 | 476 | && 'wpbc_ajx_booking__create' === strtolower( $request_action ) |
| 397 | 477 | && ! $is_authorized_admin_booking_request; |
| 398 | 478 | |
| @@ -507,8 +587,9 @@ | ||
| 507 | 587 | // [ '2023-09-10', '2023-09-11' ] |
| 508 | 588 | $local_params['dates_only_sql_arr'] = wpbc_convert_dates_str__dd_mm_yyyy__to__yyyy_mm_dd( $re_cleaned_params["dates_ddmmyy_csv"] ); |
| 509 | 589 | $local_params['dates_only_sql_arr'] = explode( ',', $local_params['dates_only_sql_arr'] ); |
| 510 | 590 | |
| 591 | + $classic_context = array(); | |
| 511 | 592 | $has_verified_classic_context = false; |
| 512 | 593 | if ( ! empty( $re_cleaned_params['classic_booking_context_token'] ) && function_exists( 'wpbc_classic_booking_context_validate_submission' ) ) { |
| 513 | 594 | $classic_context = wpbc_classic_booking_context_validate_submission( |
| 514 | 595 | $re_cleaned_params['classic_booking_context_token'], |
| @@ -524,22 +605,38 @@ | ||
| 524 | 605 | $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; |
| 525 | 606 | return array( 'ajx_data' => $ajx_data_arr ); |
| 526 | 607 | } |
| 527 | 608 | |
| 528 | - $has_verified_classic_context = true; | |
| 609 | + $has_verified_classic_context = true; | |
| 529 | 610 | $re_cleaned_params['allow_past'] = ! empty( $classic_context['allow_past'] ) ? 1 : 0; |
| 611 | + // Pass only the signed canonical set into final availability and persistence decisions. | |
| 612 | + $re_cleaned_params['aggregate_resource_id_arr'] = implode( ',', $classic_context['aggregate_resource_ids'] ); | |
| 530 | 613 | } |
| 531 | 614 | |
| 532 | - $has_verified_workflow_context = ( | |
| 533 | - ( ! empty( $re_cleaned_params['service_id'] ) && ! empty( $re_cleaned_params['appointment_context_token'] ) ) | |
| 534 | - || ( ! empty( $re_cleaned_params['resource_selector_required'] ) && ! empty( $re_cleaned_params['resource_selector_context_token'] ) ) | |
| 615 | + if ( $is_public_booking_create_request && ! $has_verified_classic_context ) { | |
| 616 | + $ajx_data_arr['status'] = 'error'; | |
| 617 | + $ajx_data_arr['status_error'] = 'classic_booking_context_required'; | |
| 618 | + $ajx_data_arr['ajx_after_action_message'] = wpbc_classic_booking_context_get_visitor_message( 'message_booking_form_context_required', $re_cleaned_params['resource_id'] ); | |
| 619 | + $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 620 | + return array( 'ajx_data' => $ajx_data_arr ); | |
| 621 | + } | |
| 622 | + | |
| 623 | + if ( $has_verified_classic_context ) { | |
| 624 | + $workflow_context_error = wpbc_booking_create_validate_required_workflow( | |
| 625 | + $classic_context, | |
| 626 | + $has_verified_appointment_context, | |
| 627 | + $has_verified_resource_selector_context | |
| 535 | 628 | ); |
| 536 | - if ( $is_public_booking_create_request && ! $has_verified_classic_context && ! $has_verified_workflow_context ) { | |
| 537 | - $re_cleaned_params['allow_past'] = 0; | |
| 538 | - $re_cleaned_params['request_uri'] = remove_query_arg( 'allow_past', $re_cleaned_params['request_uri'] ); | |
| 629 | + if ( is_wp_error( $workflow_context_error ) ) { | |
| 630 | + $ajx_data_arr['status'] = 'error'; | |
| 631 | + $ajx_data_arr['status_error'] = $workflow_context_error->get_error_code(); | |
| 632 | + $ajx_data_arr['ajx_after_action_message'] = $workflow_context_error->get_error_message(); | |
| 633 | + $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 634 | + return array( 'ajx_data' => $ajx_data_arr ); | |
| 635 | + } | |
| 539 | 636 | } |
| 540 | - | |
| 541 | - if ( | |
| 637 | + | |
| 638 | + if ( | |
| 542 | 639 | ( ! empty( $local_params['time_override_arr'] ) ) |
| 543 | 640 | && ( 'times_availability' === $local_params['time_override_arr']['source'] ) |
| 544 | 641 | && ( count( array_filter( $local_params['dates_only_sql_arr'] ) ) > 1 ) |
| 545 | 642 | ) { |
| @@ -548,13 +645,16 @@ | ||
| 548 | 645 | |
| 549 | 646 | $local_params['is_show_payment_form'] = $re_cleaned_params["is_show_payment_form"]; |
| 550 | 647 | |
| 551 | 648 | // FixIn: 9.9.0.35. |
| 552 | - if ( $local_params['is_show_payment_form'] ) { | |
| 553 | - $local_params['is_show_payment_form'] = ( false !== strpos( $re_cleaned_params['request_uri'], 'is_show_payment_form=Off' ) ) | |
| 554 | - ? 0 | |
| 555 | - : $local_params['is_show_payment_form']; // 1|0 | |
| 556 | - } | |
| 649 | + if ( $local_params['is_show_payment_form'] ) { | |
| 650 | + $local_params['is_show_payment_form'] = ( | |
| 651 | + $is_authorized_admin_booking_request | |
| 652 | + && false !== strpos( $re_cleaned_params['request_uri'], 'is_show_payment_form=Off' ) | |
| 653 | + ) | |
| 654 | + ? 0 | |
| 655 | + : $local_params['is_show_payment_form']; // 1|0 | |
| 656 | + } | |
| 557 | 657 | |
| 558 | 658 | // Get EDIT booking data |
| 559 | 659 | $local_params['edit_resource_id'] = ''; |
| 560 | 660 | $local_params['skip_booking_id'] = ''; |
| @@ -603,143 +703,108 @@ | ||
| 603 | 703 | // ----------------------------------------------------------------------------------------------------------------- |
| 604 | 704 | // Here GO |
| 605 | 705 | // ----------------------------------------------------------------------------------------------------------------- |
| 606 | 706 | |
| 607 | - // Force - resource saving parameters, instead of wpbc__where_to_save_booking() | |
| 608 | - if ( ! empty( $re_cleaned_params["save_booking_even_if_unavailable"] ) ) { | |
| 609 | - | |
| 610 | - $local_params['how_many_items_to_book'] = 1; | |
| 611 | - | |
| 612 | - $dates_keys_arr = array_values( $local_params['dates_only_sql_arr'] ); // [ '2023-09-23', '2023-09-24' ] | |
| 613 | - | |
| 614 | - $resources_in_dates = array_fill_keys( $dates_keys_arr , array( $local_params['initial_resource_id'] ) ); // [ 2023-09-23 = [ 2 ], 2023-09-24 = [ 2 ] ] | |
| 615 | - | |
| 616 | - $where_to_save_booking = array(); | |
| 617 | - $where_to_save_booking['result'] = 'ok'; | |
| 618 | - $where_to_save_booking['resources_in_dates'] = $resources_in_dates; // [ 2023-09-23 = [ 2, 10, 11 ], 2023-09-24 = [ 2, 10, 11 ] | |
| 619 | - $where_to_save_booking['time_to_book'] = $local_params['time_as_his_arr']; // [ "00:00:00", "24:00:00" ] | |
| 620 | - $where_to_save_booking['main__resource_id'] = $local_params['initial_resource_id']; // here edit or request (parent/single) resource | |
| 621 | - | |
| 622 | - } else { | |
| 623 | - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " > | |
| 624 | - $php_performance = wpbc_php_performance_START( 'wpbc__where_to_save_booking' , $php_performance ); | |
| 625 | - // </editor-fold> | |
| 626 | - | |
| 627 | - /** | |
| 628 | - * Get slots [] where we can save booking = [ 'resources_in_dates' => [ 2023-10-18 = [ 2, 12, 10, 11 ] | |
| 629 | - * 2023-10-19 = [ 2, 12, 10, 11 ] | |
| 630 | - * 2023-10-20 = [ 2, 12, 10, 11 ] | |
| 631 | - * ], | |
| 632 | - * 'time_to_book' => [ "14:00:01" , "12:00:01" ], | |
| 633 | - * 'result' => 'ok' | |
| 634 | - * 'main__resource_id' => 2 | |
| 635 | - * ] | |
| 636 | - * OR | |
| 637 | - * [ 'result' => 'error', 'message' => 'Booking can not be saved ...' ] | |
| 638 | - */ | |
| 639 | - $where_to_save_booking = wpbc__where_to_save_booking( array( | |
| 640 | - 'resource_id' => $local_params['initial_resource_id'], // 2 //TODO: If edit booking. What to pass 'edit' or 'parent' resource ID? | |
| 641 | - 'skip_booking_id' => $local_params['skip_booking_id'], // '', | 125 if edit booking | |
| 642 | - 'dates_only_sql_arr' => $local_params['dates_only_sql_arr'], // [ "2023-10-18", "2023-10-25", "2023-11-25" ] | |
| 643 | - 'time_as_seconds_arr' => $local_params['time_as_seconds_arr'], // [ 36000, 39600 ] | |
| 644 | - 'how_many_items_to_book' => $local_params['how_many_items_to_book'], // 1 | |
| 645 | - 'request_uri' => $re_cleaned_params['request_uri'], // 'http://beta/resource-id2/' | |
| 646 | - 'allow_past' => ! empty( $re_cleaned_params['allow_past'] ), | |
| 647 | - 'is_use_booking_recurrent_time' => $local_params['is_use_booking_recurrent_time'], // true | false | |
| 648 | - 'time_override_source' => ! empty( $local_params['time_override_arr']['source'] ) ? $local_params['time_override_arr']['source'] : '', | |
| 649 | - 'as_single_resource' => false, // false | |
| 650 | - 'aggregate_resource_id_arr' => $local_params['aggregate_resource_id_arr'], // Optional can be '' | |
| 651 | - '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. | |
| 652 | - 'custom_form' => $re_cleaned_params['custom_form'] // FixIn: 10.0.0.10. | |
| 653 | - )); | |
| 654 | - // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- NO SLOTS TO SAVE " > | |
| 655 | - if ( 'error' == $where_to_save_booking['result'] ) { | |
| 656 | - $ajx_data_arr['status'] = 'error'; | |
| 657 | - $ajx_data_arr['status_error'] = 'booking_can_not_save'; | |
| 658 | - $ajx_data_arr['ajx_after_action_message'] = $where_to_save_booking['message']; | |
| 659 | - $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 660 | - return array( 'ajx_data' => $ajx_data_arr ); | |
| 661 | - } | |
| 662 | - // </editor-fold> | |
| 663 | - | |
| 664 | - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " > | |
| 665 | - $php_performance = wpbc_php_performance_END( 'wpbc__where_to_save_booking' , $php_performance ); | |
| 666 | - // </editor-fold> | |
| 707 | + $availability_guard = wpbc_booking_availability_guard_acquire(); | |
| 708 | + if ( is_wp_error( $availability_guard ) ) { | |
| 709 | + $ajx_data_arr['status'] = 'error'; | |
| 710 | + $ajx_data_arr['status_error'] = $availability_guard->get_error_code(); | |
| 711 | + $ajx_data_arr['ajx_after_action_message'] = $availability_guard->get_error_message(); | |
| 712 | + $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 713 | + | |
| 714 | + return array( 'ajx_data' => $ajx_data_arr ); | |
| 667 | 715 | } |
| 668 | 716 | |
| 669 | - if ( ! empty( $local_params['appointment_service'] ) && function_exists( 'wpbc_appointment_services_check_buffer_conflicts' ) ) { | |
| 670 | - $buffer_check = wpbc_appointment_services_check_buffer_conflicts( | |
| 671 | - $local_params['appointment_service'], | |
| 672 | - $where_to_save_booking['main__resource_id'], | |
| 673 | - array_keys( $where_to_save_booking['resources_in_dates'] ), | |
| 674 | - $local_params['time_as_seconds_arr'], | |
| 675 | - $local_params['skip_booking_id'] | |
| 676 | - ); | |
| 677 | - if ( is_wp_error( $buffer_check ) ) { | |
| 678 | - $ajx_data_arr['status'] = 'error'; | |
| 679 | - $ajx_data_arr['status_error'] = 'appointment_service_buffer_conflict'; | |
| 680 | - $ajx_data_arr['ajx_after_action_message'] = $buffer_check->get_error_message(); | |
| 681 | - $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 682 | - return array( 'ajx_data' => $ajx_data_arr ); | |
| 683 | - } | |
| 684 | - } | |
| 717 | + $guard_revalidation_attempts = 0; | |
| 718 | + try { | |
| 719 | + while ( true ) { | |
| 720 | + wpbc_cache__clear( 'wpbc__sql__get_booking_dates' ); | |
| 721 | + $where_to_save_booking = wpbc_booking_validate_save_availability( $local_params, $re_cleaned_params, $php_performance ); | |
| 685 | 722 | |
| 723 | + if ( is_wp_error( $where_to_save_booking ) ) { | |
| 724 | + $ajx_data_arr['status'] = 'error'; | |
| 725 | + $ajx_data_arr['status_error'] = $where_to_save_booking->get_error_code(); | |
| 726 | + $ajx_data_arr['ajx_after_action_message'] = $where_to_save_booking->get_error_message(); | |
| 727 | + $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 686 | 728 | |
| 687 | - // Get parameters, from REQUEST | |
| 688 | - $create_params = $local_params; | |
| 689 | - $create_params['resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) ) | |
| 690 | - ? $local_params['edit_resource_id'] // If we edit, then use original resource ??? | |
| 691 | - : $where_to_save_booking['main__resource_id']; // Here is important TIP, resource can be where is free, and not where we submit | |
| 692 | - /** | |
| 693 | - * TODO: I think it's resolved! Just test about this situation, when we edit the booking - and it's means that we have $local_params['edit_resource_id'] | |
| 694 | - * but what, if $where_to_save_booking do not contain this $local_params['edit_resource_id'] as available resource. | |
| 695 | - * or even we have $local_params['edit_resource_id'] = 2 and $where_to_save_booking contain resources like [ 1, 2, 3, 4 ] | |
| 696 | - * we make booking for 3 slots | |
| 697 | - * in this case, main resource will be 2 | |
| 698 | - * but then when we loop resources in wpbc_db__booking_save() we will save child booking resources for dates like: 2, 3, 4 ( and it's wrong ) | |
| 699 | - * "(205, '2023-10-04 00:00:00', 0, NULL)" <- main resource '2' e.g. $local_params['edit_resource_id'] = 2 | |
| 700 | - * "(205, '2023-10-04 00:00:00', 0, 2)" ? <- child resource '2' e.g. [ .., 2, .. ] in $where_to_save_booking WHICH IS WRONG | |
| 701 | - */ | |
| 702 | - $create_params['is_emails_send'] = $re_cleaned_params['is_emails_send']; | |
| 703 | - $create_params['custom_form'] = $re_cleaned_params['custom_form']; | |
| 704 | - | |
| 705 | - make_bk_action( 'check_multiuser_params_for_client_side', $create_params['resource_id'] ); // Activate working with specific user in WP MU | |
| 706 | - | |
| 707 | - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " > | |
| 708 | - $php_performance = wpbc_php_performance_START( 'wpbc_db__booking_save' , $php_performance ); | |
| 709 | - // </editor-fold> | |
| 710 | - | |
| 711 | - // ----------------------------------------------------------------------------------------------------------------- | |
| 712 | - // == CREATE_THE 'NEW_BOOKING' == | |
| 713 | - // ----------------------------------------------------------------------------------------------------------------- | |
| 714 | - $create_booking_params = array( | |
| 715 | - 'resource_id' => $create_params['resource_id'], | |
| 716 | - 'custom_form' => $create_params['custom_form'], | |
| 717 | - 'all_booking_data_arr' => $create_params['all_booking_data_arr'], | |
| 718 | - 'dates_only_sql_arr' => $create_params['dates_only_sql_arr'], | |
| 719 | - 'time_as_his_arr' => $create_params['time_as_his_arr'], | |
| 720 | - 'is_from_admin_panel' => $create_params['is_from_admin_panel'], | |
| 721 | - 'is_edit_booking' => $create_params['is_edit_booking'], | |
| 722 | - 'is_duplicate_booking' => $create_params['is_duplicate_booking'], | |
| 723 | - 'is_approve_booking' => $create_params['is_approve_booking'], | |
| 724 | - 'how_many_items_to_book' => $create_params['how_many_items_to_book'], | |
| 725 | - 'is_use_booking_recurrent_time' => $create_params['is_use_booking_recurrent_time'] // true | false | |
| 726 | - ); | |
| 727 | - if ( ! empty( $create_params['appointment_service'] ) ) { $create_booking_params['appointment_service'] = $create_params['appointment_service']; } | |
| 728 | - if ( ! empty( $create_params['sync_gid'] ) ) { $create_booking_params['sync_gid'] = $create_params['sync_gid']; } | |
| 729 | - | |
| 730 | - $booking_new_arr = wpbc_db__booking_save( $create_booking_params, $where_to_save_booking ); | |
| 731 | - | |
| 732 | - // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING CREATION " > | |
| 733 | - if ( 'ok' !== $booking_new_arr['status'] ) { | |
| 734 | - $ajx_data_arr['status'] = $booking_new_arr['status']; | |
| 735 | - $ajx_data_arr['status_error'] = 'booking_can_not_save'; | |
| 736 | - $ajx_data_arr['ajx_after_action_message'] = $booking_new_arr['message']; | |
| 737 | - $ajx_data_arr['ajx_after_action_message_status'] = 'error'; | |
| 738 | - return array( 'ajx_data' => $ajx_data_arr ); | |
| 729 | + return array( 'ajx_data' => $ajx_data_arr ); | |
| 730 | + } | |
| 731 | + | |
| 732 | + // Get parameters, from REQUEST. | |
| 733 | + $create_params = $local_params; | |
| 734 | + $create_params['resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) ) | |
| 735 | + ? $local_params['edit_resource_id'] | |
| 736 | + : $where_to_save_booking['main__resource_id']; | |
| 737 | + $create_params['is_emails_send'] = $re_cleaned_params['is_emails_send']; | |
| 738 | + $create_params['custom_form'] = $re_cleaned_params['custom_form']; | |
| 739 | + | |
| 740 | + make_bk_action( 'check_multiuser_params_for_client_side', $create_params['resource_id'] ); | |
| 741 | + | |
| 742 | + $create_booking_params = array( | |
| 743 | + 'resource_id' => $create_params['resource_id'], | |
| 744 | + 'custom_form' => $create_params['custom_form'], | |
| 745 | + 'all_booking_data_arr' => $create_params['all_booking_data_arr'], | |
| 746 | + 'dates_only_sql_arr' => $create_params['dates_only_sql_arr'], | |
| 747 | + 'time_as_his_arr' => $create_params['time_as_his_arr'], | |
| 748 | + 'is_from_admin_panel' => $create_params['is_from_admin_panel'], | |
| 749 | + 'is_edit_booking' => $create_params['is_edit_booking'], | |
| 750 | + 'is_duplicate_booking' => $create_params['is_duplicate_booking'], | |
| 751 | + 'is_approve_booking' => $create_params['is_approve_booking'], | |
| 752 | + 'how_many_items_to_book' => $create_params['how_many_items_to_book'], | |
| 753 | + 'is_use_booking_recurrent_time' => $create_params['is_use_booking_recurrent_time'], | |
| 754 | + ); | |
| 755 | + if ( ! empty( $create_params['appointment_service'] ) ) { | |
| 756 | + $create_booking_params['appointment_service'] = $create_params['appointment_service']; | |
| 757 | + } | |
| 758 | + if ( ! empty( $create_params['sync_gid'] ) ) { | |
| 759 | + $create_booking_params['sync_gid'] = $create_params['sync_gid']; | |
| 760 | + } | |
| 761 | + | |
| 762 | + if ( ! wpbc_booking_availability_guard_is_owned( $availability_guard ) ) { | |
| 763 | + wpbc_booking_availability_guard_release( $availability_guard ); | |
| 764 | + if ( 1 <= $guard_revalidation_attempts ) { | |
| 765 | + $availability_guard = wpbc_booking_availability_guard_get_busy_error(); | |
| 766 | + } else { | |
| 767 | + ++$guard_revalidation_attempts; | |
| 768 | + $availability_guard = wpbc_booking_availability_guard_acquire(); | |
| 769 | + } | |
| 770 | + | |
| 771 | + if ( is_wp_error( $availability_guard ) ) { | |
| 772 | + $ajx_data_arr['status'] = 'error'; | |
| 773 | + $ajx_data_arr['status_error'] = $availability_guard->get_error_code(); | |
| 774 | + $ajx_data_arr['ajx_after_action_message'] = $availability_guard->get_error_message(); | |
| 775 | + $ajx_data_arr['ajx_after_action_message_status'] = 'warning'; | |
| 776 | + | |
| 777 | + return array( 'ajx_data' => $ajx_data_arr ); | |
| 778 | + } | |
| 779 | + | |
| 780 | + continue; | |
| 781 | + } | |
| 782 | + | |
| 783 | + $php_performance = wpbc_php_performance_START( 'wpbc_db__booking_save', $php_performance ); | |
| 784 | + $booking_new_arr = wpbc_db__booking_save( $create_booking_params, $where_to_save_booking ); | |
| 785 | + if ( 'ok' !== $booking_new_arr['status'] ) { | |
| 786 | + $ajx_data_arr['status'] = $booking_new_arr['status']; | |
| 787 | + $ajx_data_arr['status_error'] = 'booking_can_not_save'; | |
| 788 | + $ajx_data_arr['ajx_after_action_message'] = $booking_new_arr['message']; | |
| 789 | + $ajx_data_arr['ajx_after_action_message_status'] = 'error'; | |
| 790 | + | |
| 791 | + return array( 'ajx_data' => $ajx_data_arr ); | |
| 792 | + } | |
| 793 | + | |
| 794 | + // Appointment buffers must become visible before the serialized availability section ends. | |
| 795 | + if ( function_exists( 'wpbc_appointment_services_after_booking_save' ) ) { | |
| 796 | + wpbc_appointment_services_after_booking_save( $booking_new_arr['booking_id'], $create_booking_params, $where_to_save_booking ); | |
| 797 | + } | |
| 798 | + | |
| 799 | + break; | |
| 800 | + } | |
| 801 | + } finally { | |
| 802 | + wpbc_cache__clear( 'wpbc__sql__get_booking_dates' ); | |
| 803 | + wpbc_booking_availability_guard_release( $availability_guard ); | |
| 739 | 804 | } |
| 740 | - // </editor-fold> | |
| 741 | 805 | |
| 806 | + // Released compatibility hook: arbitrary callbacks must not extend the database lock duration. | |
| 742 | 807 | do_action( 'wpbc_booking_after_save', $booking_new_arr['booking_id'], $create_booking_params, $where_to_save_booking ); |
| 743 | 808 | |
| 744 | 809 | // FixIn: 9.9.0.36. |
| 745 | 810 | if ( |
| @@ -1250,9 +1315,9 @@ | ||
| 1250 | 1315 | $sql_field_arr[] = array( 'name' => 'form', 'type' => '%s', 'value' => $form_data ); |
| 1251 | 1316 | $sql_field_arr[] = array( 'name' => 'booking_type', 'type' => '%d', 'value' => $create_params['resource_id'] ); |
| 1252 | 1317 | $sql_field_arr[] = array( 'name' => 'modification_date', 'type' => '%s', 'value' => gmdate( 'Y-m-d H:i:s' ) ); |
| 1253 | 1318 | $sql_field_arr[] = array( 'name' => 'sort_date', 'type' => '%s', 'value' => $create_params['dates_only_sql_arr'][0] . ' ' . $create_params['time_as_his_arr'][0] ); |
| 1254 | - $sql_field_arr[] = array( 'name' => 'hash', 'type' => 'MD5(%s)', 'value' => time() . '_' . wp_rand( 1000, 1000000 ) ); | |
| 1319 | + $sql_field_arr[] = array( 'name' => 'hash', 'type' => '%s', 'value' => wpbc_hash__generate_booking_hash() ); | |
| 1255 | 1320 | |
| 1256 | 1321 | |
| 1257 | 1322 | if ( |
| 1258 | 1323 | ( 0 == $create_params['is_edit_booking'] ) || // If not edit, then INSERT. |
| @@ -1273,12 +1338,15 @@ | ||
| 1273 | 1338 | $sql_prepare_arr['name'] = implode( ', ', $sql_prepare_arr['name'] ); |
| 1274 | 1339 | $sql_prepare_arr['type'] = implode( ', ', $sql_prepare_arr['type'] ); |
| 1275 | 1340 | /* phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare */ |
| 1276 | 1341 | $sql = $wpdb->prepare( "INSERT INTO {$wpdb->prefix}booking " . " ( {$sql_prepare_arr['name']} )" . " VALUES ( {$sql_prepare_arr['type']} )", $sql_prepare_arr['value'] ); |
| 1277 | - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter | |
| 1278 | - if ( false === $wpdb->query( $sql ) ) { | |
| 1279 | - return array( 'status' => 'error', 'message' => 'Error. INSERT New Data in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $sql ); | |
| 1280 | - } | |
| 1342 | + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter | |
| 1343 | + if ( false === $wpdb->query( $sql ) ) { | |
| 1344 | + return array( | |
| 1345 | + 'status' => 'error', | |
| 1346 | + 'message' => __( 'The booking could not be saved because of a database error. Please try again or contact the website administrator.', 'booking' ), | |
| 1347 | + ); | |
| 1348 | + } | |
| 1281 | 1349 | // Get ID of booking |
| 1282 | 1350 | $booking_id = (int) $wpdb->insert_id; |
| 1283 | 1351 | |
| 1284 | 1352 | } else { // Edit - UPDATE |
| @@ -1292,14 +1360,15 @@ | ||
| 1292 | 1360 | $sql_prepare_arr['set'] = implode( ', ', $sql_prepare_arr['set'] ); |
| 1293 | 1361 | |
| 1294 | 1362 | // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare |
| 1295 | 1363 | $sql = $wpdb->prepare( "UPDATE {$wpdb->prefix}booking SET {$sql_prepare_arr['set']} WHERE booking_id={$booking_id};", $sql_prepare_arr['value'] ); |
| 1296 | - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter | |
| 1297 | - if ( false === $wpdb->query( $sql ) ) { | |
| 1298 | - return array( 'status' => 'error', | |
| 1299 | - 'message' => 'Error. UPDATE Exist Data in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $sql, | |
| 1300 | - ); | |
| 1301 | - } | |
| 1364 | + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter | |
| 1365 | + if ( false === $wpdb->query( $sql ) ) { | |
| 1366 | + return array( | |
| 1367 | + 'status' => 'error', | |
| 1368 | + 'message' => __( 'The booking could not be updated because of a database error. Please try again or contact the website administrator.', 'booking' ), | |
| 1369 | + ); | |
| 1370 | + } | |
| 1302 | 1371 | |
| 1303 | 1372 | // Check if dates previously was approved. |
| 1304 | 1373 | $slct_sql = "SELECT approved FROM {$wpdb->prefix}bookingdates WHERE booking_id IN ({$booking_id}) LIMIT 0,1"; |
| 1305 | 1374 | // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter |
| @@ -1590,9 +1659,9 @@ | ||
| 1590 | 1659 | * |
| 1591 | 1660 | * // Now get start/end times as seconds: [ 64800, 72000 ] |
| 1592 | 1661 | * $time_as_seconds_arr = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $structured_booking_data_arr ); |
| 1593 | 1662 | */ |
| 1594 | - function wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $booking_form_data__arr ){ | |
| 1663 | + function wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $booking_form_data__arr ){ | |
| 1595 | 1664 | |
| 1596 | 1665 | $selected_time_fields = wpbc_get__selected_time_fields__in_booking_form__as_arr( $booking_form_data__arr ); |
| 1597 | 1666 | |
| 1598 | 1667 | // 2.2 Get selected SECONDS to book --------------------------------------------------------------------------- |
| @@ -1631,12 +1700,69 @@ | ||
| 1631 | 1700 | } |
| 1632 | 1701 | } |
| 1633 | 1702 | } |
| 1634 | 1703 | |
| 1635 | - return $time_as_seconds_arr; | |
| 1636 | - } | |
| 1637 | - | |
| 1638 | - | |
| 1704 | + return $time_as_seconds_arr; | |
| 1705 | + } | |
| 1706 | + | |
| 1707 | + | |
| 1708 | + /** | |
| 1709 | + * Determine whether a booking-create request is an authorized administration workflow. | |
| 1710 | + * | |
| 1711 | + * The public booking action is intentionally available to signed-out visitors. A | |
| 1712 | + * Referer, request path, or caller-supplied Boolean therefore cannot establish an | |
| 1713 | + * administrator security context. The Add Booking UI supplies this user-bound nonce, | |
| 1714 | + * and the server independently rechecks login, capability, and MultiUser access. | |
| 1715 | + * | |
| 1716 | + * @param mixed $admin_booking_nonce Candidate Add Booking administration nonce. | |
| 1717 | + * | |
| 1718 | + * @return bool True only for an authorized Add Booking administration request. | |
| 1719 | + */ | |
| 1720 | + function wpbc_is_authorized_admin_booking_request( $admin_booking_nonce ) { | |
| 1721 | + | |
| 1722 | + if ( | |
| 1723 | + ! is_scalar( $admin_booking_nonce ) | |
| 1724 | + || '' === trim( (string) $admin_booking_nonce ) | |
| 1725 | + || ! is_user_logged_in() | |
| 1726 | + || ! wp_verify_nonce( sanitize_text_field( (string) $admin_booking_nonce ), 'wpbc_admin_booking_create' ) | |
| 1727 | + || ! class_exists( 'WPBC_Add_Booking_Component' ) | |
| 1728 | + || ! WPBC_Add_Booking_Component::current_user_can_add_booking() | |
| 1729 | + || ! wpbc_is_mu_user_can_be_here( 'activated_user' ) | |
| 1730 | + ) { | |
| 1731 | + return false; | |
| 1732 | + } | |
| 1733 | + | |
| 1734 | + return true; | |
| 1735 | + } | |
| 1736 | + | |
| 1737 | + | |
| 1738 | + /** | |
| 1739 | + * Require the signed workflow proof declared by a verified Booking Form context. | |
| 1740 | + * | |
| 1741 | + * Appointment and Resource Selector JavaScript flags are presentation hints only. | |
| 1742 | + * The signed Booking Form context identifies the server-rendered workflow, so removing | |
| 1743 | + * a flag or domain token cannot downgrade that form to a different workflow. | |
| 1744 | + * | |
| 1745 | + * @param array $classic_context Verified Booking Form context. | |
| 1746 | + * @param bool $has_verified_appointment_context Whether Service and Provider proof passed validation. | |
| 1747 | + * @param bool $has_verified_resource_selector_context Whether Resource Selector proof passed validation. | |
| 1748 | + * | |
| 1749 | + * @return true|WP_Error True when the required proof is present, otherwise a safe validation error. | |
| 1750 | + */ | |
| 1751 | + function wpbc_booking_create_validate_required_workflow( $classic_context, $has_verified_appointment_context, $has_verified_resource_selector_context ) { | |
| 1752 | + | |
| 1753 | + $booking_workflow = isset( $classic_context['booking_workflow'] ) ? sanitize_key( $classic_context['booking_workflow'] ) : ''; | |
| 1754 | + if ( 'appointment' === $booking_workflow && ! $has_verified_appointment_context ) { | |
| 1755 | + return new WP_Error( 'appointment_context_required', __( 'The Appointment selection has expired. Please start over and try again.', 'booking' ) ); | |
| 1756 | + } | |
| 1757 | + if ( 'resource_selector' === $booking_workflow && ! $has_verified_resource_selector_context ) { | |
| 1758 | + return new WP_Error( 'resource_selector_context_required', __( 'The Booking Resource selection has expired. Please start over and try again.', 'booking' ) ); | |
| 1759 | + } | |
| 1760 | + | |
| 1761 | + return true; | |
| 1762 | + } | |
| 1763 | + | |
| 1764 | + | |
| 1639 | 1765 | /** |
| 1640 | 1766 | * Remove administrator time-override values from an unauthorized booking request. |
| 1641 | 1767 | * |
| 1642 | 1768 | * The public booking endpoint intentionally accepts unauthenticated requests, so |