PluginProbe
Booking Calendar / 11.8.3
Booking Calendar v11.8.3
11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 10.11 10.11.2 All 203 releases
← All changes | includes/_capacity/create_booking.php +512 -116 11.111.8.3 View file →
@@ -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.