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 +604 -99 11.011.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,14 +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' ),
61 + 'allow_past' => array( 'validate' => 'd', 'default' => 0 ),
62 + 'classic_booking_context_token' => array( 'validate' => 'strong', 'default' => '' ),
55 63 'wpbc_bfb_preview' => array( 'validate' => 'd', 'default' => 0 ),
56 64 'wpbc_bfb_preview_token' => array( 'validate' => 'strong', 'default' => '' ),
57 65 'wpbc_bfb_preview_form_id' => array( 'validate' => 'd', 'default' => 0 ),
58 66 'wpbc_bfb_preview_nonce' => array( 'validate' => 'strong', 'default' => '' ),
59 - )
60 - ));
67 + 'wpbc_time_override_enabled' => array( 'validate' => 'd', 'default' => 0 ),
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 + ));
61 74
62 75 // Escape of request params in Ajax Post. We use prefix 'calendar_request_params', if Ajax sent - $_REQUEST['calendar_request_params']['resource_id'], ...
63 76 $request_prefix = 'calendar_request_params';
64 77
@@ -63,15 +76,16 @@
63 76 $request_prefix = 'calendar_request_params';
64 77
65 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";
66 79
67 - $request_params = $user_request->get_sanitized__in_request__value_or_default( $request_prefix ); // NOT Direct: $_REQUEST['calendar_request_params']['resource_id']
68 - $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 */
69 - $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'] );
70 84
71 85 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- CAPTCHA " >
72 86 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
73 - 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 ] );
74 88 // </editor-fold>
75 89
76 90 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING_RESOURCE ID " >
77 91 if ( $request_params['resource_id'] <= 0 ) {
@@ -78,24 +92,21 @@
78 92 $ajx_data_arr['status'] = 'error';
79 93 $ajx_data_arr['status_error'] = 'resource_id_incorrect';
80 94 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
81 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'] . ' ]';
82 - $ajx_data_arr['ajx_after_action_message_status'] = 'error';
83 - wp_send_json( array(
84 - 'ajx_data' => $ajx_data_arr,
85 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
86 - 'ajx_search_params' => $_REQUEST[ $request_prefix ],
87 - 'ajx_cleaned_params' => $request_params,
88 - 'resource_id' => $request_params['resource_id'],
89 - ) );
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 + ) );
90 101 }
91 102 // </editor-fold>
92 103
93 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 */
94 105
95 - $request_save_params = array(
96 - 'resource_id' => $request_params['resource_id'],
97 - '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'],
98 109 'form_data' => $request_params['formdata'],
99 110 'aggregate_resource_id_arr' => $request_params['aggregate_resource_id_arr'], // Optional can be ''.
100 111 'booking_hash' => $request_params['booking_hash'],
101 112 'custom_form' => $request_params['custom_form'],
@@ -103,24 +114,37 @@
103 114 'is_show_payment_form' => 1,
104 115 'user_id' => $local_params['user_id'],
105 116 'request_uri' => $server_http_referer_uri,
106 117 'form_status' => $request_params['form_status'],
118 + 'allow_past' => $request_params['allow_past'],
119 + 'classic_booking_context_token' => $request_params['classic_booking_context_token'],
107 120 'wpbc_bfb_preview' => $request_params['wpbc_bfb_preview'],
108 121 'wpbc_bfb_preview_token' => $request_params['wpbc_bfb_preview_token'],
109 122 'wpbc_bfb_preview_form_id' => $request_params['wpbc_bfb_preview_form_id'],
110 123 'wpbc_bfb_preview_nonce' => $request_params['wpbc_bfb_preview_nonce'],
111 - );
124 + 'wpbc_time_override_enabled' => $request_params['wpbc_time_override_enabled'],
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'];
112 136 $booking_save_arr = wpbc_booking_save( $request_save_params );
113 137
114 138 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING " >
115 139 if ( 'ok' !== $booking_save_arr['ajx_data']['status'] ) {
116 140
117 - wp_send_json( array( 'ajx_data' => $booking_save_arr['ajx_data'],
118 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
119 - 'ajx_search_params' => $_REQUEST[ $request_prefix ],
120 - 'ajx_cleaned_params' => $request_params,
121 - 'resource_id' => $request_params['resource_id']
122 - ));
141 + wp_send_json(
142 + array(
143 + 'ajx_data' => $booking_save_arr['ajx_data'],
144 + 'resource_id' => $request_params['resource_id'],
145 + )
146 + );
123 147 }
124 148 // </editor-fold>
125 149
126 150 $ajx_data_arr = $booking_save_arr['ajx_data'];
@@ -242,11 +266,11 @@
242 266 // 1. Direct Clean Params
243 267 // -----------------------------------------------------------------------------------------------------------------
244 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 */
245 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 */
246 - $validate_arr_rules = array(
247 - 'resource_id' => array( 'validate' => 'd', 'default' => 1 ), // INT
248 - '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.
249 273 'form_data' => array( 'validate' => 'strong', 'default' => '' ),
250 274 'booking_hash' => array( 'validate' => 'strong', 'default' => '' ),
251 275 'custom_form' => array( 'validate' => 'strong', 'default' => '' ),
252 276 'is_emails_send' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1
@@ -251,8 +275,10 @@
251 275 'custom_form' => array( 'validate' => 'strong', 'default' => '' ),
252 276 'is_emails_send' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1
253 277 'is_show_payment_form' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1
254 278 'user_id' => array( 'validate' => 'd', 'default' => wpbc_get_current_user_id() ), // INT
279 + 'allow_past' => array( 'validate' => 'd', 'default' => 0 ),
280 + 'classic_booking_context_token' => array( 'validate' => 'strong', 'default' => '' ),
255 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
256 282 // Really Optional:
257 283 'aggregate_resource_id_arr' => array( 'validate' => 'digit_or_csd', 'default' => '' ),
258 284 //TODO: this parameter does not transfer during saving, so here will be always default value 'bookings_only' // FixIn: 10.0.0.7.
@@ -266,14 +292,75 @@
266 292 'wpbc_bfb_preview' => array( 'validate' => 'd', 'default' => 0 ),
267 293 'wpbc_bfb_preview_token' => array( 'validate' => 'strong', 'default' => '' ),
268 294 'wpbc_bfb_preview_form_id' => array( 'validate' => 'd', 'default' => 0 ),
269 295 'wpbc_bfb_preview_nonce' => array( 'validate' => 'strong', 'default' => '' ),
270 - );
271 - $re_cleaned_params = wpbc_sanitize_params_in_arr( $request_params, $validate_arr_rules );
272 -
273 - $admin_uri = ltrim( str_replace( get_site_url( null, '', 'admin' ), '', admin_url( 'admin.php?' ) ), '/' ); // wp-admin/admin.php?
274 -
275 - $re_cleaned_params['form_status'] = sanitize_key( $re_cleaned_params['form_status'] );
296 + 'wpbc_time_override_enabled' => array( 'validate' => 'd', 'default' => 0 ),
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'] );
276 363 if ( 'preview' !== $re_cleaned_params['form_status'] ) {
277 364 $re_cleaned_params['form_status'] = 'published';
278 365 }
279 366 // FixIn: 2026-02-05 - make preview/published available to form parsing/templates during this request.
@@ -290,14 +377,26 @@
290 377
291 378 // -----------------------------------------------------------------------------------------------------------------
292 379 // Local parameters
293 380 // -----------------------------------------------------------------------------------------------------------------
294 - $local_params = array();
295 - $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;
296 384 $local_params['user_id'] = $re_cleaned_params['user_id']; // 1
297 - $local_params['sync_gid'] = $re_cleaned_params['sync_gid']; // ''
298 - $local_params['is_approve_booking'] = $re_cleaned_params['is_approve_booking']; // 0 | 1
299 - $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 );
300 399
301 400 // -----------------------------------------------------------------------------------------------------------------
302 401 // Parse Local parameters for later use
303 402 // -----------------------------------------------------------------------------------------------------------------
@@ -305,10 +404,91 @@
305 404 * Get parsed booking form: = [ name = "John", secondname = "Smith", email = "john.smith@server.com", visitors = "2",... ]
306 405 */
307 406 $local_params['structured_booking_data_arr'] = wpbc_get_parsed_booking_data_arr( $re_cleaned_params["form_data"], $re_cleaned_params["resource_id"], array( 'get' => 'value' ) );
308 407 $local_params['all_booking_data_arr'] = wpbc_get_parsed_booking_data_arr( $re_cleaned_params["form_data"], $re_cleaned_params["resource_id"] );
309 - // Important! : [ 64800, 72000 ]
310 - $local_params['time_as_seconds_arr'] = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $local_params['structured_booking_data_arr'] );
408 + $local_params['time_override_arr'] = wpbc_get_booking_time_override__as_arr( $re_cleaned_params );
409 + if ( ! empty( $local_params['time_override_arr'] ) ) {
410 + unset( $local_params['structured_booking_data_arr']['rangetime'], $local_params['structured_booking_data_arr']['durationtime'] );
411 + $local_params['structured_booking_data_arr']['starttime'] = $local_params['time_override_arr']['start'];
412 + $local_params['structured_booking_data_arr']['endtime'] = $local_params['time_override_arr']['end'];
413 +
414 + unset( $local_params['all_booking_data_arr']['rangetime'], $local_params['all_booking_data_arr']['durationtime'] );
415 + $local_params['all_booking_data_arr']['starttime'] = array(
416 + 'type' => 'text',
417 + 'original_name' => 'starttime' . $re_cleaned_params['resource_id'],
418 + 'name' => 'starttime',
419 + 'value' => $local_params['time_override_arr']['start'],
420 + );
421 + $local_params['all_booking_data_arr']['endtime'] = array(
422 + 'type' => 'text',
423 + 'original_name' => 'endtime' . $re_cleaned_params['resource_id'],
424 + 'name' => 'endtime',
425 + 'value' => $local_params['time_override_arr']['end'],
426 + );
427 + }
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 + }
311 491 // [ "18:00:00", "20:00:00" ]
312 492 $time_as_seconds_arr = $local_params['time_as_seconds_arr'];
313 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
314 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
@@ -320,19 +500,77 @@
320 500 wpbc_transform__seconds__in__24_hours_his( $time_as_seconds_arr[0] ),
321 501 wpbc_transform__seconds__in__24_hours_his( $time_as_seconds_arr[1] )
322 502 );
323 503 // [ '2023-09-10', '2023-09-11' ]
324 - $local_params['dates_only_sql_arr'] = wpbc_convert_dates_str__dd_mm_yyyy__to__yyyy_mm_dd( $re_cleaned_params["dates_ddmmyy_csv"] );
325 - $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 + }
326 561
327 562 $local_params['is_show_payment_form'] = $re_cleaned_params["is_show_payment_form"];
328 563
329 564 // FixIn: 9.9.0.35.
330 - if ( $local_params['is_show_payment_form'] ) {
331 - $local_params['is_show_payment_form'] = ( false !== strpos( $re_cleaned_params['request_uri'], 'is_show_payment_form=Off' ) )
332 - ? 0
333 - : $local_params['is_show_payment_form']; // 1|0
334 - }
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 + }
335 573
336 574 // Get EDIT booking data
337 575 $local_params['edit_resource_id'] = '';
338 576 $local_params['skip_booking_id'] = '';
@@ -338,27 +576,44 @@
338 576 $local_params['skip_booking_id'] = '';
339 577 $local_params['is_edit_booking'] = 0;
340 578 $local_params['is_duplicate_booking'] = 0;
341 579 $is_edit_booking = wpbc_get_data__if_edit_booking( $re_cleaned_params['booking_hash'], $re_cleaned_params['request_uri'] );
342 - if ( false !== $is_edit_booking ) {
343 - $local_params['edit_resource_id'] = $is_edit_booking['resource_id']; // can be parent booking resource, where we edit the booking
344 - $local_params['skip_booking_id'] = $is_edit_booking['booking_id']; // booking ID
345 - $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
346 584 if (
347 585 ( ! empty( $local_params['structured_booking_data_arr']['wpbc_other_action'] ) )
348 586 && ( 'duplicate_booking' === $local_params['structured_booking_data_arr']['wpbc_other_action'] )
349 587 ){
350 - $local_params['is_duplicate_booking'] = 1;
351 - }
352 - }
353 - // 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)
354 609 $local_params['initial_resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) ) ? $local_params['edit_resource_id'] : $re_cleaned_params['resource_id'];
355 610
356 - // 2
357 - $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'] );
358 -
359 -
360 - $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'] );
361 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.
362 617 $local_params['aggregate_resource_id_arr'] = array_unique( $local_params['aggregate_resource_id_arr'] ); // Erase duplicates
363 618
364 619 // -----------------------------------------------------------------------------------------------------------------
@@ -403,9 +658,11 @@
403 658 'dates_only_sql_arr' => $local_params['dates_only_sql_arr'], // [ "2023-10-18", "2023-10-25", "2023-11-25" ]
404 659 'time_as_seconds_arr' => $local_params['time_as_seconds_arr'], // [ 36000, 39600 ]
405 660 'how_many_items_to_book' => $local_params['how_many_items_to_book'], // 1
406 661 'request_uri' => $re_cleaned_params['request_uri'], // 'http://beta/resource-id2/'
662 + 'allow_past' => ! empty( $re_cleaned_params['allow_past'] ),
407 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'] : '',
408 665 'as_single_resource' => false, // false
409 666 'aggregate_resource_id_arr' => $local_params['aggregate_resource_id_arr'], // Optional can be ''
410 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.
411 668 'custom_form' => $re_cleaned_params['custom_form'] // FixIn: 10.0.0.10.
@@ -422,12 +679,29 @@
422 679
423 680 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
424 681 $php_performance = wpbc_php_performance_END( 'wpbc__where_to_save_booking' , $php_performance );
425 682 // </editor-fold>
426 - }
427 -
428 -
429 - // 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
430 704 $create_params = $local_params;
431 705 $create_params['resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) )
432 706 ? $local_params['edit_resource_id'] // If we edit, then use original resource ???
433 707 : $where_to_save_booking['main__resource_id']; // Here is important TIP, resource can be where is free, and not where we submit
@@ -452,9 +726,9 @@
452 726
453 727 // -----------------------------------------------------------------------------------------------------------------
454 728 // == CREATE_THE 'NEW_BOOKING' ==
455 729 // -----------------------------------------------------------------------------------------------------------------
456 - $create_booking_params = array(
730 + $create_booking_params = array(
457 731 'resource_id' => $create_params['resource_id'],
458 732 'custom_form' => $create_params['custom_form'],
459 733 'all_booking_data_arr' => $create_params['all_booking_data_arr'],
460 734 'dates_only_sql_arr' => $create_params['dates_only_sql_arr'],
@@ -463,23 +737,26 @@
463 737 'is_edit_booking' => $create_params['is_edit_booking'],
464 738 'is_duplicate_booking' => $create_params['is_duplicate_booking'],
465 739 'is_approve_booking' => $create_params['is_approve_booking'],
466 740 'how_many_items_to_book' => $create_params['how_many_items_to_book'],
467 - 'is_use_booking_recurrent_time' => $create_params['is_use_booking_recurrent_time'] // true | false
468 - );
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']; }
469 744 if ( ! empty( $create_params['sync_gid'] ) ) { $create_booking_params['sync_gid'] = $create_params['sync_gid']; }
470 745
471 746 $booking_new_arr = wpbc_db__booking_save( $create_booking_params, $where_to_save_booking );
472 747
473 748 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING CREATION " >
474 - if ( 'ok' !== $booking_new_arr['status'] ) {
749 + if ( 'ok' !== $booking_new_arr['status'] ) {
475 750 $ajx_data_arr['status'] = $booking_new_arr['status'];
476 751 $ajx_data_arr['status_error'] = 'booking_can_not_save';
477 752 $ajx_data_arr['ajx_after_action_message'] = $booking_new_arr['message'];
478 753 $ajx_data_arr['ajx_after_action_message_status'] = 'error';
479 754 return array( 'ajx_data' => $ajx_data_arr );
480 - }
481 - // </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 );
482 759
483 760 // FixIn: 9.9.0.36.
484 761 if (
485 762 ( 0 !== $create_params['is_edit_booking'] ) // If edit booking
@@ -507,9 +784,10 @@
507 784 );
508 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']
509 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'
510 787 $payment_params['booking_id'] = $booking_new_arr['booking_id']; // REQUIRED -- '2'
511 - $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;
512 790 $payment_params['initial_resource_id'] = $local_params['initial_resource_id']; // REQUIRED -- '2' initial calendar - parent resource
513 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...'
514 792 $payment_params['times_array'] = array(
515 793 explode( ':', $where_to_save_booking['time_to_book'][0] ), // ["10","00","00"]
@@ -519,9 +797,10 @@
519 797 $payment_params['is_edit_booking'] = $create_params['is_edit_booking']; // => 0 0 | int - ID of the booking
520 798 $payment_params['custom_form'] = $create_params['custom_form']; // => '' '' | 'some_name'
521 799 $payment_params['is_duplicate_booking'] = $create_params['is_duplicate_booking']; // => 0 0 | 1
522 800 $payment_params['is_from_admin_panel'] = $create_params['is_from_admin_panel']; // => false true | false
523 - $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'];
524 803 if ( $payment_params['is_from_admin_panel'] ) {
525 804 // $payment_params['is_show_payment_form'] = 0; // FixIn: 9.9.0.21.
526 805 }
527 806 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
@@ -895,9 +1174,13 @@
895 1174 return array( 'status' => 'error', 'message' => 'Sent request with no dates.' );
896 1175 }
897 1176
898 1177 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- CHECK_IN_DATE_OLDER_THAN_CHECK_OUT " >
899 - 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 ?
900 1183
901 1184 // Is 'check in' date/time older than 'check out' date/time when SINGLE day for booking? Then show error.
902 1185
903 1186 /**
@@ -983,9 +1266,9 @@
983 1266 $sql_field_arr[] = array( 'name' => 'form', 'type' => '%s', 'value' => $form_data );
984 1267 $sql_field_arr[] = array( 'name' => 'booking_type', 'type' => '%d', 'value' => $create_params['resource_id'] );
985 1268 $sql_field_arr[] = array( 'name' => 'modification_date', 'type' => '%s', 'value' => gmdate( 'Y-m-d H:i:s' ) );
986 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] );
987 - $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() );
988 1271
989 1272
990 1273 if (
991 1274 ( 0 == $create_params['is_edit_booking'] ) || // If not edit, then INSERT.
@@ -1006,12 +1289,15 @@
1006 1289 $sql_prepare_arr['name'] = implode( ', ', $sql_prepare_arr['name'] );
1007 1290 $sql_prepare_arr['type'] = implode( ', ', $sql_prepare_arr['type'] );
1008 1291 /* phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare */
1009 1292 $sql = $wpdb->prepare( "INSERT INTO {$wpdb->prefix}booking " . " ( {$sql_prepare_arr['name']} )" . " VALUES ( {$sql_prepare_arr['type']} )", $sql_prepare_arr['value'] );
1010 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1011 - if ( false === $wpdb->query( $sql ) ) {
1012 - return array( 'status' => 'error', 'message' => 'Error. INSERT New Data in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $sql );
1013 - }
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 + }
1014 1300 // Get ID of booking
1015 1301 $booking_id = (int) $wpdb->insert_id;
1016 1302
1017 1303 } else { // Edit - UPDATE
@@ -1025,14 +1311,15 @@
1025 1311 $sql_prepare_arr['set'] = implode( ', ', $sql_prepare_arr['set'] );
1026 1312
1027 1313 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1028 1314 $sql = $wpdb->prepare( "UPDATE {$wpdb->prefix}booking SET {$sql_prepare_arr['set']} WHERE booking_id={$booking_id};", $sql_prepare_arr['value'] );
1029 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1030 - if ( false === $wpdb->query( $sql ) ) {
1031 - return array( 'status' => 'error',
1032 - 'message' => 'Error. UPDATE Exist Data in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $sql,
1033 - );
1034 - }
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 + }
1035 1322
1036 1323 // Check if dates previously was approved.
1037 1324 $slct_sql = "SELECT approved FROM {$wpdb->prefix}bookingdates WHERE booking_id IN ({$booking_id}) LIMIT 0,1";
1038 1325 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
@@ -1323,9 +1610,9 @@
1323 1610 *
1324 1611 * // Now get start/end times as seconds: [ 64800, 72000 ]
1325 1612 * $time_as_seconds_arr = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $structured_booking_data_arr );
1326 1613 */
1327 - 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 ){
1328 1615
1329 1616 $selected_time_fields = wpbc_get__selected_time_fields__in_booking_form__as_arr( $booking_form_data__arr );
1330 1617
1331 1618 // 2.2 Get selected SECONDS to book ---------------------------------------------------------------------------
@@ -1364,9 +1651,227 @@
1364 1651 }
1365 1652 }
1366 1653 }
1367 1654
1368 - return $time_as_seconds_arr;
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.
1806 + *
1807 + * @param array $request_params Sanitized booking request params.
1808 + *
1809 + * @return array Empty array or array with start/end HH:MM values.
1810 + */
1811 + function wpbc_get_booking_time_override__as_arr( $request_params ) {
1812 +
1813 + if ( empty( $request_params['wpbc_time_override_enabled'] ) ) {
1814 + return array();
1815 + }
1816 +
1817 + $start_time = wpbc_sanitize_booking_time_override__hm( isset( $request_params['wpbc_time_override_start'] ) ? $request_params['wpbc_time_override_start'] : '' );
1818 + $end_time = wpbc_sanitize_booking_time_override__hm( isset( $request_params['wpbc_time_override_end'] ) ? $request_params['wpbc_time_override_end'] : '' );
1819 +
1820 + if (
1821 + ( '' === $start_time )
1822 + || ( '' === $end_time )
1823 + || ( wpbc_booking_time_override__hm_to_seconds( $start_time ) >= wpbc_booking_time_override__hm_to_seconds( $end_time ) )
1824 + ) {
1825 + return array();
1826 + }
1827 +
1828 + return array(
1829 + 'start' => $start_time,
1830 + 'end' => $end_time,
1831 + 'source' => isset( $request_params['wpbc_time_override_source'] ) ? sanitize_key( $request_params['wpbc_time_override_source'] ) : '',
1832 + );
1833 + }
1834 +
1835 +
1836 + /**
1837 + * Sanitize HH:MM value for admin booking time override.
1838 + *
1839 + * @param string $time_value Time value.
1840 + *
1841 + * @return string
1842 + */
1843 + function wpbc_sanitize_booking_time_override__hm( $time_value ) {
1844 +
1845 + $time_value = trim( sanitize_text_field( (string) $time_value ) );
1846 +
1847 + if ( ! preg_match( '/^([0-9]{1,2}):([0-9]{2})$/', $time_value, $matches ) ) {
1848 + return '';
1849 + }
1850 +
1851 + $hour = absint( $matches[1] );
1852 + $minute = absint( $matches[2] );
1853 +
1854 + if ( $hour > 24 || $minute > 59 || ( 24 === $hour && 0 !== $minute ) ) {
1855 + return '';
1856 + }
1857 +
1858 + return sprintf( '%02d:%02d', $hour, $minute );
1859 + }
1860 +
1861 +
1862 + /**
1863 + * Convert HH:MM to seconds.
1864 + *
1865 + * @param string $time_value Time value.
1866 + *
1867 + * @return int
1868 + */
1869 + function wpbc_booking_time_override__hm_to_seconds( $time_value ) {
1870 +
1871 + $time_arr = explode( ':', (string) $time_value );
1872 +
1873 + return ( absint( $time_arr[0] ) * 60 * 60 ) + ( absint( $time_arr[1] ) * 60 );
1369 1874 }
1370 1875
1371 1876
1372 1877 /**