PluginProbe
Booking Calendar / 11.8.4
Booking Calendar v11.8.4
11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 10.11 All 204 releases
← All changes | includes/_capacity/create_booking.php +142 -65 11.711.8.4 View file →
@@ -28,14 +28,11 @@
28 28 // Response AJAX parameters
29 29 $ajx_data_arr = array();
30 30 $ajx_data_arr['status'] = 'ok';
31 31
32 - $admin_uri = ltrim( str_replace( get_site_url( null, '', 'admin' ), '', admin_url( 'admin.php?' ) ), '/' ); // 'wp-admin/admin.php?'
33 - $server_http_referer_uri = ( ( isset( $_SERVER['HTTP_REFERER'] ) ) ? sanitize_text_field( $_SERVER['HTTP_REFERER'] ) : '' ); /* phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash */ /* FixIn: sanitize_unslash */
34 - // Local parameters
35 - $local_params = array();
36 - $local_params['is_from_admin_panel'] = ( false !== strpos( $server_http_referer_uri, $admin_uri ) ); // true | false
37 - $local_params['user_id'] = ( isset( $_REQUEST['wpbc_ajx_user_id'] ) ) ? intval( $_REQUEST['wpbc_ajx_user_id'] ) : wpbc_get_current_user_id(); // 1
32 + // Local parameters
33 + $local_params = array();
34 + $local_params['user_id'] = ( isset( $_REQUEST['wpbc_ajx_user_id'] ) ) ? intval( $_REQUEST['wpbc_ajx_user_id'] ) : wpbc_get_current_user_id(); // 1
38 35
39 36 // Request parameters for the released Appointment and Resource Selector workflows.
40 37 $workflow_request_rules = array(
41 38 'service_id' => array( 'validate' => 'd', 'default' => 0 ),
@@ -42,8 +39,9 @@
42 39 'appointment_service_required' => array( 'validate' => 'd', 'default' => 0 ),
43 40 'appointment_context_token' => array( 'validate' => 'strong', 'default' => '' ),
44 41 'resource_selector_required' => array( 'validate' => 'd', 'default' => 0 ),
45 42 'resource_selector_context_token' => array( 'validate' => 'strong', 'default' => '' ),
43 + 'wpbc_admin_booking_nonce' => array( 'validate' => 'strong', 'default' => '' ),
46 44 );
47 45
48 46 $user_request = new WPBC_AJX__REQUEST( array( // Using this class here only for escaping variables
49 47 'db_option_name' => 'booking__wpbc_booking_create__request_params', // Not necessary, because we not save request, only sanitize it
@@ -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'];
@@ -306,9 +303,12 @@
306 303 $validate_arr_rules['appointment_service_required'] = array( 'validate' => 'd', 'default' => 0 );
307 304 $validate_arr_rules['appointment_context_token'] = array( 'validate' => 'strong', 'default' => '' );
308 305 $validate_arr_rules['resource_selector_required'] = array( 'validate' => 'd', 'default' => 0 );
309 306 $validate_arr_rules['resource_selector_context_token'] = array( 'validate' => 'strong', 'default' => '' );
307 + $validate_arr_rules['wpbc_admin_booking_nonce'] = array( 'validate' => 'strong', 'default' => '' );
310 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 311 if ( ! empty( $re_cleaned_params['appointment_service_required'] ) && empty( $re_cleaned_params['service_id'] ) ) {
312 312 $ajx_data_arr['status'] = 'error';
313 313 $ajx_data_arr['status_error'] = 'appointment_service_required';
314 314 $ajx_data_arr['ajx_after_action_message'] = __( 'Please select a Service.', 'booking' );
@@ -331,13 +331,14 @@
331 331 $ajx_data_arr['ajx_after_action_message'] = $appointment_context_check->get_error_message();
332 332 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
333 333 return array( 'ajx_data' => $ajx_data_arr );
334 334 }
335 + $has_verified_appointment_context = true;
335 336
336 337 // A client value cannot enable past Appointment creation; trust only the site-authored signed context.
337 338 $re_cleaned_params['allow_past'] = wpbc_booking_appointment_is_past_booking_enabled( $appointment_context_check ) ? 1 : 0;
338 339 }
339 - if ( ! empty( $re_cleaned_params['resource_selector_required'] ) ) {
340 + if ( ! empty( $re_cleaned_params['resource_selector_required'] ) || ! empty( $re_cleaned_params['resource_selector_context_token'] ) ) {
340 341 if ( ! function_exists( 'wpbc_booking_resource_selector_validate_submission_context' ) ) {
341 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' ) );
342 343 } else {
343 344 $resource_selector_context_check = wpbc_booking_resource_selector_validate_submission_context(
@@ -351,16 +352,15 @@
351 352 $ajx_data_arr['ajx_after_action_message'] = $resource_selector_context_check->get_error_message();
352 353 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
353 354 return array( 'ajx_data' => $ajx_data_arr );
354 355 }
356 + $has_verified_resource_selector_context = true;
355 357
356 358 // Trust only the site-authored signed selector context for public past bookings.
357 359 $re_cleaned_params['allow_past'] = wpbc_booking_resource_selector_is_past_booking_enabled( $resource_selector_context_check ) ? 1 : 0;
358 360 }
359 361
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'] );
362 + $re_cleaned_params['form_status'] = sanitize_key( $re_cleaned_params['form_status'] );
363 363 if ( 'preview' !== $re_cleaned_params['form_status'] ) {
364 364 $re_cleaned_params['form_status'] = 'published';
365 365 }
366 366 // FixIn: 2026-02-05 - make preview/published available to form parsing/templates during this request.
@@ -377,10 +377,11 @@
377 377
378 378 // -----------------------------------------------------------------------------------------------------------------
379 379 // Local parameters
380 380 // -----------------------------------------------------------------------------------------------------------------
381 - $local_params = array();
382 - $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;
383 384 $local_params['user_id'] = $re_cleaned_params['user_id']; // 1
384 385 $local_params['sync_gid'] = $re_cleaned_params['sync_gid']; // ''
385 386 $local_params['is_approve_booking'] = $re_cleaned_params['is_approve_booking']; // 0 | 1
386 387 $local_params['is_use_booking_recurrent_time'] = ( 1 === $re_cleaned_params['is_use_booking_recurrent_time'] ); // false | true
@@ -386,13 +387,8 @@
386 387 $local_params['is_use_booking_recurrent_time'] = ( 1 === $re_cleaned_params['is_use_booking_recurrent_time'] ); // false | true
387 388 $request_action = isset( $_REQUEST['action'] ) && is_scalar( $_REQUEST['action'] )
388 389 ? sanitize_key( (string) wp_unslash( $_REQUEST['action'] ) )
389 390 : ''; // 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 391 $is_public_booking_create_request = wp_doing_ajax()
396 392 && 'wpbc_ajx_booking__create' === strtolower( $request_action )
397 393 && ! $is_authorized_admin_booking_request;
398 394
@@ -507,8 +503,9 @@
507 503 // [ '2023-09-10', '2023-09-11' ]
508 504 $local_params['dates_only_sql_arr'] = wpbc_convert_dates_str__dd_mm_yyyy__to__yyyy_mm_dd( $re_cleaned_params["dates_ddmmyy_csv"] );
509 505 $local_params['dates_only_sql_arr'] = explode( ',', $local_params['dates_only_sql_arr'] );
510 506
507 + $classic_context = array();
511 508 $has_verified_classic_context = false;
512 509 if ( ! empty( $re_cleaned_params['classic_booking_context_token'] ) && function_exists( 'wpbc_classic_booking_context_validate_submission' ) ) {
513 510 $classic_context = wpbc_classic_booking_context_validate_submission(
514 511 $re_cleaned_params['classic_booking_context_token'],
@@ -524,22 +521,38 @@
524 521 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
525 522 return array( 'ajx_data' => $ajx_data_arr );
526 523 }
527 524
528 - $has_verified_classic_context = true;
525 + $has_verified_classic_context = true;
529 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'] );
530 529 }
531 530
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'] ) )
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
535 544 );
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'] );
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 + }
539 552 }
540 -
541 - if (
553 +
554 + if (
542 555 ( ! empty( $local_params['time_override_arr'] ) )
543 556 && ( 'times_availability' === $local_params['time_override_arr']['source'] )
544 557 && ( count( array_filter( $local_params['dates_only_sql_arr'] ) ) > 1 )
545 558 ) {
@@ -548,13 +561,16 @@
548 561
549 562 $local_params['is_show_payment_form'] = $re_cleaned_params["is_show_payment_form"];
550 563
551 564 // 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 - }
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 + }
557 573
558 574 // Get EDIT booking data
559 575 $local_params['edit_resource_id'] = '';
560 576 $local_params['skip_booking_id'] = '';
@@ -1250,9 +1266,9 @@
1250 1266 $sql_field_arr[] = array( 'name' => 'form', 'type' => '%s', 'value' => $form_data );
1251 1267 $sql_field_arr[] = array( 'name' => 'booking_type', 'type' => '%d', 'value' => $create_params['resource_id'] );
1252 1268 $sql_field_arr[] = array( 'name' => 'modification_date', 'type' => '%s', 'value' => gmdate( 'Y-m-d H:i:s' ) );
1253 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] );
1254 - $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() );
1255 1271
1256 1272
1257 1273 if (
1258 1274 ( 0 == $create_params['is_edit_booking'] ) || // If not edit, then INSERT.
@@ -1273,12 +1289,15 @@
1273 1289 $sql_prepare_arr['name'] = implode( ', ', $sql_prepare_arr['name'] );
1274 1290 $sql_prepare_arr['type'] = implode( ', ', $sql_prepare_arr['type'] );
1275 1291 /* phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare */
1276 1292 $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 - }
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 + }
1281 1300 // Get ID of booking
1282 1301 $booking_id = (int) $wpdb->insert_id;
1283 1302
1284 1303 } else { // Edit - UPDATE
@@ -1292,14 +1311,15 @@
1292 1311 $sql_prepare_arr['set'] = implode( ', ', $sql_prepare_arr['set'] );
1293 1312
1294 1313 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1295 1314 $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 - }
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 + }
1302 1322
1303 1323 // Check if dates previously was approved.
1304 1324 $slct_sql = "SELECT approved FROM {$wpdb->prefix}bookingdates WHERE booking_id IN ({$booking_id}) LIMIT 0,1";
1305 1325 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
@@ -1590,9 +1610,9 @@
1590 1610 *
1591 1611 * // Now get start/end times as seconds: [ 64800, 72000 ]
1592 1612 * $time_as_seconds_arr = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $structured_booking_data_arr );
1593 1613 */
1594 - 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 ){
1595 1615
1596 1616 $selected_time_fields = wpbc_get__selected_time_fields__in_booking_form__as_arr( $booking_form_data__arr );
1597 1617
1598 1618 // 2.2 Get selected SECONDS to book ---------------------------------------------------------------------------
@@ -1631,12 +1651,69 @@
1631 1651 }
1632 1652 }
1633 1653 }
1634 1654
1635 - return $time_as_seconds_arr;
1636 - }
1637 -
1638 -
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 +
1639 1716 /**
1640 1717 * Remove administrator time-override values from an unauthorized booking request.
1641 1718 *
1642 1719 * The public booking endpoint intentionally accepts unauthenticated requests, so