PluginProbe
Booking Calendar / 11.9
Booking Calendar v11.9
11.9 11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 All 205 releases
← All changes | includes/_capacity/create_booking.php +849 -246 10.10 → 11.9 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
@@ -10,9 +10,9 @@
10 10 * Response to Ajax request, about loading calendar data
11 11 *
12 12 * @return void
13 13 */
14 -function ajax_WPBC_AJX_BOOKING__CREATE() {
14 +function ajax_WPBC_AJX_BOOKING__CREATE() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound
15 15
16 16 /**
17 17 * Tip / translation /
18 18 * Please note, translation was loaded on hook add_action( 'plugins_loaded', 'wpbc_load_translation', 1000 ); and use $_REQUEST['wpbc_ajx_locale'], so do not worry about it.
@@ -28,49 +28,64 @@
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'
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' => '' ),
52 + 'dates_ddmmyy_csv' => array( 'validate' => 'csv_dates', 'default' => '' ), // FixIn: 9.9.1.1.
53 + 'formdata' => array( 'validate' => 'strong', 'default' => '' ),
54 + 'booking_hash' => array( 'validate' => 'strong', 'default' => '' ),
55 + 'custom_form' => array( 'validate' => 'strong', 'default' => '' ),
56 + 'captcha_chalange' => array( 'validate' => 'strong', 'default' => '' ),
57 + 'captcha_user_input' => array( 'validate' => 'strong', 'default' => '' ),
58 + 'is_emails_send' => array( 'validate' => 'd', 'default' => 1 ),
59 + 'active_locale' => array( 'validate' => 'strong', 'default' => '' ),
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' => '' ),
63 + 'wpbc_bfb_preview' => array( 'validate' => 'd', 'default' => 0 ),
64 + 'wpbc_bfb_preview_token' => array( 'validate' => 'strong', 'default' => '' ),
65 + 'wpbc_bfb_preview_form_id' => array( 'validate' => 'd', 'default' => 0 ),
66 + 'wpbc_bfb_preview_nonce' => array( 'validate' => 'strong', 'default' => '' ),
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 + ));
45 74
46 - 'aggregate_resource_id_arr' => array( 'validate' => 'digit_or_csd', 'default' => '' ),
47 -
48 - 'dates_ddmmyy_csv' => array( 'validate' => 'csv_dates', 'default' => '' ), // FixIn: 9.9.1.1.
49 - 'formdata' => array( 'validate' => 'strong', 'default' => '' ),
50 - 'booking_hash' => array( 'validate' => 'strong', 'default' => '' ),
51 - 'custom_form' => array( 'validate' => 'strong', 'default' => '' ),
52 -
53 - 'captcha_chalange' => array( 'validate' => 'strong', 'default' => '' ),
54 - 'captcha_user_input' => array( 'validate' => 'strong', 'default' => '' ),
55 -
56 - 'is_emails_send' => array( 'validate' => 'd', 'default' => 1 ),
57 - 'active_locale' => array( 'validate' => 'strong', 'default' => '' )
58 - )
59 - ));
60 -
61 75 // Escape of request params in Ajax Post. We use prefix 'calendar_request_params', if Ajax sent - $_REQUEST['calendar_request_params']['resource_id'], ...
62 76 $request_prefix = 'calendar_request_params';
63 77
64 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";
65 79
66 - $request_params = $user_request->get_sanitized__in_request__value_or_default( $request_prefix ); // NOT Direct: $_REQUEST['calendar_request_params']['resource_id']
67 - $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 */
68 - $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'] );
69 84
70 85 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- CAPTCHA " >
71 86 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
72 - 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 ] );
73 88 // </editor-fold>
74 89
75 90 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING_RESOURCE ID " >
76 91 if ( $request_params['resource_id'] <= 0 ) {
@@ -77,45 +92,59 @@
77 92 $ajx_data_arr['status'] = 'error';
78 93 $ajx_data_arr['status_error'] = 'resource_id_incorrect';
79 94 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
80 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'] . ' ]';
81 - $ajx_data_arr['ajx_after_action_message_status'] = 'error';
82 - wp_send_json( array(
83 - 'ajx_data' => $ajx_data_arr,
84 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
85 - 'ajx_search_params' => $_REQUEST[ $request_prefix ],
86 - 'ajx_cleaned_params' => $request_params,
87 - 'resource_id' => $request_params['resource_id'],
88 - ) );
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 + ) );
89 101 }
90 102 // </editor-fold>
91 103
92 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 */
93 - $request_save_params = array(
94 - 'resource_id' => $request_params['resource_id'],
95 - 'dates_ddmmyy_csv' => $request_params['dates_ddmmyy_csv'],
96 - 'form_data' => $request_params['formdata'],
97 - 'aggregate_resource_id_arr' => $request_params['aggregate_resource_id_arr'], // Optional can be ''
98 105
99 - 'booking_hash' => $request_params['booking_hash'],
100 - 'custom_form' => $request_params['custom_form'],
101 -
102 - 'is_emails_send' => $request_params['is_emails_send'],
103 - 'is_show_payment_form' => 1,
104 - 'user_id' => $local_params['user_id'],
105 - 'request_uri' => $server_http_referer_uri
106 - );
106 + $request_save_params = array(
107 + 'resource_id' => $request_params['resource_id'],
108 + 'dates_ddmmyy_csv' => $request_params['dates_ddmmyy_csv'],
109 + 'form_data' => $request_params['formdata'],
110 + 'aggregate_resource_id_arr' => $request_params['aggregate_resource_id_arr'], // Optional can be ''.
111 + 'booking_hash' => $request_params['booking_hash'],
112 + 'custom_form' => $request_params['custom_form'],
113 + 'is_emails_send' => $request_params['is_emails_send'],
114 + 'is_show_payment_form' => 1,
115 + 'user_id' => $local_params['user_id'],
116 + 'request_uri' => $server_http_referer_uri,
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'],
120 + 'wpbc_bfb_preview' => $request_params['wpbc_bfb_preview'],
121 + 'wpbc_bfb_preview_token' => $request_params['wpbc_bfb_preview_token'],
122 + 'wpbc_bfb_preview_form_id' => $request_params['wpbc_bfb_preview_form_id'],
123 + 'wpbc_bfb_preview_nonce' => $request_params['wpbc_bfb_preview_nonce'],
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'];
107 136 $booking_save_arr = wpbc_booking_save( $request_save_params );
108 137
109 138 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING " >
110 139 if ( 'ok' !== $booking_save_arr['ajx_data']['status'] ) {
111 140
112 - wp_send_json( array( 'ajx_data' => $booking_save_arr['ajx_data'],
113 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
114 - 'ajx_search_params' => $_REQUEST[ $request_prefix ],
115 - 'ajx_cleaned_params' => $request_params,
116 - 'resource_id' => $request_params['resource_id']
117 - ));
141 + wp_send_json(
142 + array(
143 + 'ajx_data' => $booking_save_arr['ajx_data'],
144 + 'resource_id' => $request_params['resource_id'],
145 + )
146 + );
118 147 }
119 148 // </editor-fold>
120 149
121 150 $ajx_data_arr = $booking_save_arr['ajx_data'];
@@ -140,9 +169,9 @@
140 169
141 170 /* if admin edit ?
142 171 var my_message = '<?php echo esc_js( __('Updated successfully' ,'booking') ) ; ?>';
143 172 wpbc_admin_show_message( my_message, 'success', 3000 );
144 - location.href='<?php echo wpbc_get_bookings_url() ;?>&view_mode=vm_listing&tab=actions&wh_booking_id=<?php echo $is_edit_booking['booking_id'] ; ?>';
173 + location.href='<?php echo wpbc_get_bookings_url() ;?>&tab=vm_booking_listing&wh_booking_id=<?php echo $is_edit_booking['booking_id'] ; ?>';
145 174 */
146 175
147 176
148 177 // -----------------------------------------------------------------------------------------------------------------
@@ -184,10 +213,94 @@
184 213 // ---------------------------------------------------------------------------------------------------------------------
185 214 // == Save Booking
186 215 // ---------------------------------------------------------------------------------------------------------------------
187 216
188 -/**
189 - * Save Booking - ADD NEW or UPDATE exist booking
217 +/**
218 + * Resolve and validate the final booking destination against current storage.
219 + *
220 + * This function contains the availability, Appointment working-time, and
221 + * Appointment buffer checks that must be repeated if the database connection
222 + * loses its advisory lock before persistence. The caller clears the relevant
223 + * request-local cache before every invocation.
224 + *
225 + * @param array $local_params Parsed booking parameters, passed by reference because force-save mode fixes capacity at one.
226 + * @param array $cleaned_params Sanitized booking request parameters.
227 + * @param array $php_performance Performance measurements, passed by reference.
228 + *
229 + * @return array|WP_Error Validated storage destination, or a visitor-safe validation error.
230 + */
231 +function wpbc_booking_validate_save_availability( &$local_params, $cleaned_params, &$php_performance ) {
232 +
233 + // Privileged imports and other established integrations may intentionally force a save.
234 + if ( ! empty( $cleaned_params['save_booking_even_if_unavailable'] ) ) {
235 + $local_params['how_many_items_to_book'] = 1;
236 + $dates_keys_arr = array_values( $local_params['dates_only_sql_arr'] );
237 + $resources_in_dates = array_fill_keys( $dates_keys_arr, array( $local_params['initial_resource_id'] ) );
238 + $where_to_save_booking = array(
239 + 'result' => 'ok',
240 + 'resources_in_dates' => $resources_in_dates,
241 + 'time_to_book' => $local_params['time_as_his_arr'],
242 + 'main__resource_id' => $local_params['initial_resource_id'],
243 + );
244 + } else {
245 + $php_performance = wpbc_php_performance_START( 'wpbc__where_to_save_booking', $php_performance );
246 +
247 + $where_to_save_booking = wpbc__where_to_save_booking(
248 + array(
249 + 'resource_id' => $local_params['initial_resource_id'],
250 + 'skip_booking_id' => $local_params['skip_booking_id'],
251 + 'dates_only_sql_arr' => $local_params['dates_only_sql_arr'],
252 + 'time_as_seconds_arr' => $local_params['time_as_seconds_arr'],
253 + 'how_many_items_to_book' => $local_params['how_many_items_to_book'],
254 + 'request_uri' => $cleaned_params['request_uri'],
255 + 'allow_past' => ! empty( $cleaned_params['allow_past'] ),
256 + 'is_use_booking_recurrent_time' => $local_params['is_use_booking_recurrent_time'],
257 + 'time_override_source' => ! empty( $local_params['time_override_arr']['source'] ) ? $local_params['time_override_arr']['source'] : '',
258 + 'as_single_resource' => false,
259 + 'aggregate_resource_id_arr' => $local_params['aggregate_resource_id_arr'],
260 + 'aggregate_type' => $cleaned_params['aggregate_type'],
261 + 'custom_form' => $cleaned_params['custom_form'],
262 + )
263 + );
264 +
265 + if ( 'error' === $where_to_save_booking['result'] ) {
266 + return new WP_Error( 'booking_can_not_save', $where_to_save_booking['message'] );
267 + }
268 +
269 + $php_performance = wpbc_php_performance_END( 'wpbc__where_to_save_booking', $php_performance );
270 + }
271 +
272 + if ( ! empty( $local_params['appointment_service'] ) && function_exists( 'wpbc_appointment_services_check_working_time' ) ) {
273 + $working_time_check = wpbc_appointment_services_check_working_time(
274 + $local_params['appointment_service'],
275 + $where_to_save_booking['main__resource_id'],
276 + array_keys( $where_to_save_booking['resources_in_dates'] ),
277 + $local_params['time_as_seconds_arr']
278 + );
279 + if ( is_wp_error( $working_time_check ) ) {
280 + return $working_time_check;
281 + }
282 + }
283 +
284 + if ( ! empty( $local_params['appointment_service'] ) && function_exists( 'wpbc_appointment_services_check_buffer_conflicts' ) ) {
285 + $buffer_check = wpbc_appointment_services_check_buffer_conflicts(
286 + $local_params['appointment_service'],
287 + $where_to_save_booking['main__resource_id'],
288 + array_keys( $where_to_save_booking['resources_in_dates'] ),
289 + $local_params['time_as_seconds_arr'],
290 + $local_params['skip_booking_id']
291 + );
292 + if ( is_wp_error( $buffer_check ) ) {
293 + return $buffer_check;
294 + }
295 + }
296 +
297 + return $where_to_save_booking;
298 +}
299 +
300 +
301 +/**
302 + * Save Booking - ADD NEW or UPDATE exist booking
190 303 *
191 304 * @param $request_params = [
192 305 * resource_id = 2 REQUIRED Default: 1
193 306 * dates_ddmmyy_csv = '27.10.2023, 28.10.2023, 29.10.2023' REQUIRED
@@ -227,9 +340,9 @@
227 340 *
228 341 */
229 342 function wpbc_booking_save( $request_params ){
230 343 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
231 - $php_performance = php_performance_START( 'total', array() );
344 + $php_performance = wpbc_php_performance_START( 'total', array() );
232 345 // </editor-fold>
233 346 $ajx_data_arr = array();
234 347 $ajx_data_arr['status'] = 'ok';
235 348
@@ -237,11 +350,11 @@
237 350 // 1. Direct Clean Params
238 351 // -----------------------------------------------------------------------------------------------------------------
239 352 $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 */
240 353 $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 */
241 - $validate_arr_rules = array(
242 - 'resource_id' => array( 'validate' => 'd', 'default' => 1 ), // INT
243 - 'dates_ddmmyy_csv' => array( 'validate' => 'csv_dates', 'default' => '' ), // FixIn: 9.9.1.1.
354 + $validate_arr_rules = array(
355 + 'resource_id' => array( 'validate' => 'd', 'default' => 1 ), // INT
356 + 'dates_ddmmyy_csv' => array( 'validate' => 'csv_dates', 'default' => '' ), // FixIn: 9.9.1.1.
244 357 'form_data' => array( 'validate' => 'strong', 'default' => '' ),
245 358 'booking_hash' => array( 'validate' => 'strong', 'default' => '' ),
246 359 'custom_form' => array( 'validate' => 'strong', 'default' => '' ),
247 360 'is_emails_send' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1
@@ -246,8 +359,10 @@
246 359 'custom_form' => array( 'validate' => 'strong', 'default' => '' ),
247 360 'is_emails_send' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1
248 361 'is_show_payment_form' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1
249 362 'user_id' => array( 'validate' => 'd', 'default' => wpbc_get_current_user_id() ), // INT
363 + 'allow_past' => array( 'validate' => 'd', 'default' => 0 ),
364 + 'classic_booking_context_token' => array( 'validate' => 'strong', 'default' => '' ),
250 365 '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
251 366 // Really Optional:
252 367 'aggregate_resource_id_arr' => array( 'validate' => 'digit_or_csd', 'default' => '' ),
253 368 //TODO: this parameter does not transfer during saving, so here will be always default value 'bookings_only' // FixIn: 10.0.0.7.
@@ -254,24 +369,118 @@
254 369 'aggregate_type' => array( 'validate' => 'strong', 'default' => 'bookings_only' ), // Optional. 'all' | 'bookings_only' <- it is depends on shortcode parameter: options="{aggregate type=bookings_only}"
255 370 'is_approve_booking' => array( 'validate' => 'd', 'default' => 0 ), // 0 | 1
256 371 'save_booking_even_if_unavailable' => array( 'validate' => 'd', 'default' => 0 ), // 0 | 1
257 372 'sync_gid' => array( 'validate' => 'strong', 'default' => '' ),
258 - 'is_use_booking_recurrent_time' => array( 'validate' => 'd', 'default' => intval( ( 'On' === get_bk_option( 'booking_recurrent_time' ) ) ) )
259 - );
260 - $re_cleaned_params = wpbc_sanitize_params_in_arr( $request_params, $validate_arr_rules );
373 + 'is_use_booking_recurrent_time' => array( 'validate' => 'd', 'default' => intval( ( 'On' === get_bk_option( 'booking_recurrent_time' ) ) ) ),
261 374
262 - $admin_uri = ltrim( str_replace( get_site_url( null, '', 'admin' ), '', admin_url( 'admin.php?' ) ), '/' ); // wp-admin/admin.php?
375 + 'form_status' => array( 'validate' => 'strong', 'default' => 'published' ),
376 + 'wpbc_bfb_preview' => array( 'validate' => 'd', 'default' => 0 ),
377 + 'wpbc_bfb_preview_token' => array( 'validate' => 'strong', 'default' => '' ),
378 + 'wpbc_bfb_preview_form_id' => array( 'validate' => 'd', 'default' => 0 ),
379 + 'wpbc_bfb_preview_nonce' => array( 'validate' => 'strong', 'default' => '' ),
380 + 'wpbc_time_override_enabled' => array( 'validate' => 'd', 'default' => 0 ),
381 + 'wpbc_time_override_source' => array( 'validate' => 'strong', 'default' => '' ),
382 + 'wpbc_time_override_start' => array( 'validate' => 'strong', 'default' => '' ),
383 + 'wpbc_time_override_end' => array( 'validate' => 'strong', 'default' => '' ),
384 + 'wpbc_admin_cost_correction' => array( 'validate' => 'strong', 'default' => '' ),
385 + );
386 + $validate_arr_rules['service_id'] = array( 'validate' => 'd', 'default' => 0 );
387 + $validate_arr_rules['appointment_service_required'] = array( 'validate' => 'd', 'default' => 0 );
388 + $validate_arr_rules['appointment_context_token'] = array( 'validate' => 'strong', 'default' => '' );
389 + $validate_arr_rules['resource_selector_required'] = array( 'validate' => 'd', 'default' => 0 );
390 + $validate_arr_rules['resource_selector_context_token'] = array( 'validate' => 'strong', 'default' => '' );
391 + $validate_arr_rules['wpbc_admin_booking_nonce'] = array( 'validate' => 'strong', 'default' => '' );
392 + $re_cleaned_params = wpbc_sanitize_params_in_arr( $request_params, $validate_arr_rules );
393 + $has_verified_appointment_context = false;
394 + $has_verified_resource_selector_context = false;
395 + if ( ! empty( $re_cleaned_params['appointment_service_required'] ) && empty( $re_cleaned_params['service_id'] ) ) {
396 + $ajx_data_arr['status'] = 'error';
397 + $ajx_data_arr['status_error'] = 'appointment_service_required';
398 + $ajx_data_arr['ajx_after_action_message'] = __( 'Please select a Service.', 'booking' );
399 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
400 + return array( 'ajx_data' => $ajx_data_arr );
401 + }
402 + if ( ! empty( $re_cleaned_params['service_id'] ) ) {
403 + if ( ! function_exists( 'wpbc_booking_appointment_validate_submission_context' ) ) {
404 + $appointment_context_check = new WP_Error( 'appointment_context_unavailable', __( 'The Appointment selection cannot be verified. Please reload the page and try again.', 'booking' ) );
405 + } else {
406 + $appointment_context_check = wpbc_booking_appointment_validate_submission_context(
407 + $re_cleaned_params['appointment_context_token'],
408 + $re_cleaned_params['service_id'],
409 + $re_cleaned_params['resource_id']
410 + );
411 + }
412 + if ( is_wp_error( $appointment_context_check ) ) {
413 + $ajx_data_arr['status'] = 'error';
414 + $ajx_data_arr['status_error'] = $appointment_context_check->get_error_code();
415 + $ajx_data_arr['ajx_after_action_message'] = $appointment_context_check->get_error_message();
416 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
417 + return array( 'ajx_data' => $ajx_data_arr );
418 + }
419 + $has_verified_appointment_context = true;
420 +
421 + // A client value cannot enable past Appointment creation; trust only the site-authored signed context.
422 + $re_cleaned_params['allow_past'] = wpbc_booking_appointment_is_past_booking_enabled( $appointment_context_check ) ? 1 : 0;
423 + }
424 + if ( ! empty( $re_cleaned_params['resource_selector_required'] ) || ! empty( $re_cleaned_params['resource_selector_context_token'] ) ) {
425 + if ( ! function_exists( 'wpbc_booking_resource_selector_validate_submission_context' ) ) {
426 + $resource_selector_context_check = new WP_Error( 'resource_selector_context_unavailable', __( 'The Booking Resource selection cannot be verified. Please reload the page and try again.', 'booking' ) );
427 + } else {
428 + $resource_selector_context_check = wpbc_booking_resource_selector_validate_submission_context(
429 + $re_cleaned_params['resource_selector_context_token'],
430 + $re_cleaned_params['resource_id']
431 + );
432 + }
433 + if ( is_wp_error( $resource_selector_context_check ) ) {
434 + $ajx_data_arr['status'] = 'error';
435 + $ajx_data_arr['status_error'] = $resource_selector_context_check->get_error_code();
436 + $ajx_data_arr['ajx_after_action_message'] = $resource_selector_context_check->get_error_message();
437 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
438 + return array( 'ajx_data' => $ajx_data_arr );
439 + }
440 + $has_verified_resource_selector_context = true;
441 +
442 + // Trust only the site-authored signed selector context for public past bookings.
443 + $re_cleaned_params['allow_past'] = wpbc_booking_resource_selector_is_past_booking_enabled( $resource_selector_context_check ) ? 1 : 0;
444 + }
445 +
446 + $re_cleaned_params['form_status'] = sanitize_key( $re_cleaned_params['form_status'] );
447 + if ( 'preview' !== $re_cleaned_params['form_status'] ) {
448 + $re_cleaned_params['form_status'] = 'published';
449 + }
450 + // FixIn: 2026-02-05 - make preview/published available to form parsing/templates during this request.
451 + wpbc_set_request_form_context(
452 + array(
453 + 'form_status' => $re_cleaned_params['form_status'],
454 + 'user_id' => $re_cleaned_params['user_id'],
455 + 'wpbc_bfb_preview' => absint( $re_cleaned_params['wpbc_bfb_preview'] ),
456 + 'wpbc_bfb_preview_token' => sanitize_key( $re_cleaned_params['wpbc_bfb_preview_token'] ),
457 + 'wpbc_bfb_preview_form_id' => absint( $re_cleaned_params['wpbc_bfb_preview_form_id'] ),
458 + 'wpbc_bfb_preview_nonce' => (string) $re_cleaned_params['wpbc_bfb_preview_nonce'],
459 + )
460 + );
263 461
264 -
265 462 // -----------------------------------------------------------------------------------------------------------------
266 463 // Local parameters
267 464 // -----------------------------------------------------------------------------------------------------------------
268 - $local_params = array();
269 - $local_params['is_from_admin_panel'] = ( false !== strpos( $re_cleaned_params['request_uri'], $admin_uri ) ); // true | false
465 + $local_params = array();
466 + $is_authorized_admin_booking_request = wpbc_is_authorized_admin_booking_request( $re_cleaned_params['wpbc_admin_booking_nonce'] );
467 + $local_params['is_from_admin_panel'] = $is_authorized_admin_booking_request;
270 468 $local_params['user_id'] = $re_cleaned_params['user_id']; // 1
271 - $local_params['sync_gid'] = $re_cleaned_params['sync_gid']; // ''
272 - $local_params['is_approve_booking'] = $re_cleaned_params['is_approve_booking']; // 0 | 1
273 - $local_params['is_use_booking_recurrent_time'] = ( 1 === $re_cleaned_params['is_use_booking_recurrent_time'] ); // false | true
469 + $local_params['sync_gid'] = $re_cleaned_params['sync_gid']; // ''
470 + $local_params['is_approve_booking'] = $re_cleaned_params['is_approve_booking']; // 0 | 1
471 + $local_params['is_use_booking_recurrent_time'] = ( 1 === $re_cleaned_params['is_use_booking_recurrent_time'] ); // false | true
472 + $request_action = isset( $_REQUEST['action'] ) && is_scalar( $_REQUEST['action'] )
473 + ? sanitize_key( (string) wp_unslash( $_REQUEST['action'] ) )
474 + : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
475 + $is_public_booking_create_request = wp_doing_ajax()
476 + && 'wpbc_ajx_booking__create' === strtolower( $request_action )
477 + && ! $is_authorized_admin_booking_request;
478 +
479 + // Time overrides belong exclusively to the capability-protected Add Booking administration workflow.
480 + $re_cleaned_params = wpbc_restrict_booking_time_override_to_authorized_admin( $re_cleaned_params, $is_authorized_admin_booking_request );
481 + // Cost corrections belong exclusively to capability-protected administrator booking workflows.
482 + $re_cleaned_params = wpbc_restrict_booking_cost_correction_to_authorized_admin( $re_cleaned_params, $is_authorized_admin_booking_request );
274 483
275 484 // -----------------------------------------------------------------------------------------------------------------
276 485 // Parse Local parameters for later use
277 486 // -----------------------------------------------------------------------------------------------------------------
@@ -279,10 +488,91 @@
279 488 * Get parsed booking form: = [ name = "John", secondname = "Smith", email = "[email protected]", visitors = "2",... ]
280 489 */
281 490 $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' ) );
282 491 $local_params['all_booking_data_arr'] = wpbc_get_parsed_booking_data_arr( $re_cleaned_params["form_data"], $re_cleaned_params["resource_id"] );
283 - // Important! : [ 64800, 72000 ]
284 - $local_params['time_as_seconds_arr'] = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $local_params['structured_booking_data_arr'] );
492 + $local_params['time_override_arr'] = wpbc_get_booking_time_override__as_arr( $re_cleaned_params );
493 + if ( ! empty( $local_params['time_override_arr'] ) ) {
494 + unset( $local_params['structured_booking_data_arr']['rangetime'], $local_params['structured_booking_data_arr']['durationtime'] );
495 + $local_params['structured_booking_data_arr']['starttime'] = $local_params['time_override_arr']['start'];
496 + $local_params['structured_booking_data_arr']['endtime'] = $local_params['time_override_arr']['end'];
497 +
498 + unset( $local_params['all_booking_data_arr']['rangetime'], $local_params['all_booking_data_arr']['durationtime'] );
499 + $local_params['all_booking_data_arr']['starttime'] = array(
500 + 'type' => 'text',
501 + 'original_name' => 'starttime' . $re_cleaned_params['resource_id'],
502 + 'name' => 'starttime',
503 + 'value' => $local_params['time_override_arr']['start'],
504 + );
505 + $local_params['all_booking_data_arr']['endtime'] = array(
506 + 'type' => 'text',
507 + 'original_name' => 'endtime' . $re_cleaned_params['resource_id'],
508 + 'name' => 'endtime',
509 + 'value' => $local_params['time_override_arr']['end'],
510 + );
511 + }
512 + // Important! : [ 64800, 72000 ]
513 + $local_params['time_as_seconds_arr'] = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $local_params['structured_booking_data_arr'] );
514 + $local_params['appointment_service'] = array();
515 + if ( ! empty( $re_cleaned_params['service_id'] ) && function_exists( 'wpbc_appointment_services_repository' ) ) {
516 + $range_time_value = isset( $local_params['structured_booking_data_arr']['rangetime'] ) ? $local_params['structured_booking_data_arr']['rangetime'] : '';
517 + $start_time_value = isset( $local_params['structured_booking_data_arr']['starttime'] ) ? $local_params['structured_booking_data_arr']['starttime'] : '';
518 + $range_time_value = is_array( $range_time_value ) ? implode( '', $range_time_value ) : $range_time_value;
519 + $start_time_value = is_array( $start_time_value ) ? implode( '', $start_time_value ) : $start_time_value;
520 + $has_appointment_time = ! empty( $local_params['time_override_arr'] )
521 + || '' !== trim( (string) $range_time_value )
522 + || '' !== trim( (string) $start_time_value );
523 + if ( ! $has_appointment_time ) {
524 + $ajx_data_arr['status'] = 'error';
525 + $ajx_data_arr['status_error'] = 'appointment_service_time_required';
526 + $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' );
527 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
528 + return array( 'ajx_data' => $ajx_data_arr );
529 + }
530 + $appointment_service = wpbc_appointment_services_repository()->find_active_for_resource( $re_cleaned_params['service_id'], $re_cleaned_params['resource_id'] );
531 + if ( is_wp_error( $appointment_service ) ) {
532 + $ajx_data_arr['status'] = 'error';
533 + $ajx_data_arr['status_error'] = 'appointment_service_unavailable';
534 + $ajx_data_arr['ajx_after_action_message'] = $appointment_service->get_error_message();
535 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
536 + return array( 'ajx_data' => $ajx_data_arr );
537 + }
538 + if ( count( $local_params['time_as_seconds_arr'] ) < 2 || ! function_exists( 'wpbc_appointment_services_resolve_end_seconds' ) ) {
539 + $ajx_data_arr['status'] = 'error';
540 + $ajx_data_arr['status_error'] = 'appointment_service_duration_invalid';
541 + $ajx_data_arr['ajx_after_action_message'] = __( 'The selected Service duration is invalid. Please contact the website administrator.', 'booking' );
542 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
543 + return array( 'ajx_data' => $ajx_data_arr );
544 + }
545 + $maximum_duration_minutes = absint( apply_filters( 'wpbc_booking_appointment_maximum_duration_minutes', 24 * 60, array() ) );
546 + $service_end_second = wpbc_appointment_services_resolve_end_seconds( $appointment_service, $local_params['time_as_seconds_arr'][0], $maximum_duration_minutes );
547 + if ( is_wp_error( $service_end_second ) ) {
548 + $ajx_data_arr['status'] = 'error';
549 + $ajx_data_arr['status_error'] = $service_end_second->get_error_code();
550 + $ajx_data_arr['ajx_after_action_message'] = $service_end_second->get_error_message();
551 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
552 + return array( 'ajx_data' => $ajx_data_arr );
553 + }
554 + $local_params['time_as_seconds_arr'][1] = $service_end_second;
555 + $local_params['appointment_service'] = $appointment_service;
556 + $service_start_time = wpbc_transform__seconds__in__24_hours_his( $local_params['time_as_seconds_arr'][0] );
557 + $service_end_time = wpbc_transform__seconds__in__24_hours_his( $local_params['time_as_seconds_arr'][1] );
558 + unset( $local_params['structured_booking_data_arr']['rangetime'], $local_params['structured_booking_data_arr']['durationtime'] );
559 + $local_params['structured_booking_data_arr']['starttime'] = $service_start_time;
560 + $local_params['structured_booking_data_arr']['endtime'] = $service_end_time;
561 + unset( $local_params['all_booking_data_arr']['rangetime'], $local_params['all_booking_data_arr']['durationtime'] );
562 + $local_params['all_booking_data_arr']['starttime'] = array( 'type' => 'text', 'original_name' => 'starttime' . $re_cleaned_params['resource_id'], 'name' => 'starttime', 'value' => $service_start_time );
563 + $local_params['all_booking_data_arr']['endtime'] = array( 'type' => 'text', 'original_name' => 'endtime' . $re_cleaned_params['resource_id'], 'name' => 'endtime', 'value' => $service_end_time );
564 + }
565 + if ( function_exists( 'wpbc_appointment_services_sync_service_hint_booking_data' ) ) {
566 + $service_hint_booking_data = wpbc_appointment_services_sync_service_hint_booking_data(
567 + $local_params['structured_booking_data_arr'],
568 + $local_params['all_booking_data_arr'],
569 + $local_params['appointment_service'],
570 + $re_cleaned_params['resource_id']
571 + );
572 + $local_params['structured_booking_data_arr'] = $service_hint_booking_data['structured_booking_data'];
573 + $local_params['all_booking_data_arr'] = $service_hint_booking_data['all_booking_data'];
574 + }
285 575 // [ "18:00:00", "20:00:00" ]
286 576 $time_as_seconds_arr = $local_params['time_as_seconds_arr'];
287 577 $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
288 578 $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
@@ -294,19 +584,77 @@
294 584 wpbc_transform__seconds__in__24_hours_his( $time_as_seconds_arr[0] ),
295 585 wpbc_transform__seconds__in__24_hours_his( $time_as_seconds_arr[1] )
296 586 );
297 587 // [ '2023-09-10', '2023-09-11' ]
298 - $local_params['dates_only_sql_arr'] = wpbc_convert_dates_str__dd_mm_yyyy__to__yyyy_mm_dd( $re_cleaned_params["dates_ddmmyy_csv"] );
299 - $local_params['dates_only_sql_arr'] = explode( ',', $local_params['dates_only_sql_arr'] );
588 + $local_params['dates_only_sql_arr'] = wpbc_convert_dates_str__dd_mm_yyyy__to__yyyy_mm_dd( $re_cleaned_params["dates_ddmmyy_csv"] );
589 + $local_params['dates_only_sql_arr'] = explode( ',', $local_params['dates_only_sql_arr'] );
590 +
591 + $classic_context = array();
592 + $has_verified_classic_context = false;
593 + if ( ! empty( $re_cleaned_params['classic_booking_context_token'] ) && function_exists( 'wpbc_classic_booking_context_validate_submission' ) ) {
594 + $classic_context = wpbc_classic_booking_context_validate_submission(
595 + $re_cleaned_params['classic_booking_context_token'],
596 + $re_cleaned_params['resource_id'],
597 + $local_params['dates_only_sql_arr'],
598 + $re_cleaned_params['custom_form'],
599 + $re_cleaned_params['aggregate_resource_id_arr']
600 + );
601 + if ( is_wp_error( $classic_context ) ) {
602 + $ajx_data_arr['status'] = 'error';
603 + $ajx_data_arr['status_error'] = $classic_context->get_error_code();
604 + $ajx_data_arr['ajx_after_action_message'] = $classic_context->get_error_message();
605 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
606 + return array( 'ajx_data' => $ajx_data_arr );
607 + }
608 +
609 + $has_verified_classic_context = true;
610 + $re_cleaned_params['allow_past'] = ! empty( $classic_context['allow_past'] ) ? 1 : 0;
611 + // Pass only the signed canonical set into final availability and persistence decisions.
612 + $re_cleaned_params['aggregate_resource_id_arr'] = implode( ',', $classic_context['aggregate_resource_ids'] );
613 + }
614 +
615 + if ( $is_public_booking_create_request && ! $has_verified_classic_context ) {
616 + $ajx_data_arr['status'] = 'error';
617 + $ajx_data_arr['status_error'] = 'classic_booking_context_required';
618 + $ajx_data_arr['ajx_after_action_message'] = wpbc_classic_booking_context_get_visitor_message( 'message_booking_form_context_required', $re_cleaned_params['resource_id'] );
619 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
620 + return array( 'ajx_data' => $ajx_data_arr );
621 + }
622 +
623 + if ( $has_verified_classic_context ) {
624 + $workflow_context_error = wpbc_booking_create_validate_required_workflow(
625 + $classic_context,
626 + $has_verified_appointment_context,
627 + $has_verified_resource_selector_context
628 + );
629 + if ( is_wp_error( $workflow_context_error ) ) {
630 + $ajx_data_arr['status'] = 'error';
631 + $ajx_data_arr['status_error'] = $workflow_context_error->get_error_code();
632 + $ajx_data_arr['ajx_after_action_message'] = $workflow_context_error->get_error_message();
633 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
634 + return array( 'ajx_data' => $ajx_data_arr );
635 + }
636 + }
637 +
638 + if (
639 + ( ! empty( $local_params['time_override_arr'] ) )
640 + && ( 'times_availability' === $local_params['time_override_arr']['source'] )
641 + && ( count( array_filter( $local_params['dates_only_sql_arr'] ) ) > 1 )
642 + ) {
643 + $local_params['is_use_booking_recurrent_time'] = true;
644 + }
300 645
301 646 $local_params['is_show_payment_form'] = $re_cleaned_params["is_show_payment_form"];
302 647
303 648 // FixIn: 9.9.0.35.
304 - if ( $local_params['is_show_payment_form'] ) {
305 - $local_params['is_show_payment_form'] = ( false !== strpos( $re_cleaned_params['request_uri'], 'is_show_payment_form=Off' ) )
306 - ? 0
307 - : $local_params['is_show_payment_form']; // 1|0
308 - }
649 + if ( $local_params['is_show_payment_form'] ) {
650 + $local_params['is_show_payment_form'] = (
651 + $is_authorized_admin_booking_request
652 + && false !== strpos( $re_cleaned_params['request_uri'], 'is_show_payment_form=Off' )
653 + )
654 + ? 0
655 + : $local_params['is_show_payment_form']; // 1|0
656 + }
309 657
310 658 // Get EDIT booking data
311 659 $local_params['edit_resource_id'] = '';
312 660 $local_params['skip_booking_id'] = '';
@@ -312,27 +660,44 @@
312 660 $local_params['skip_booking_id'] = '';
313 661 $local_params['is_edit_booking'] = 0;
314 662 $local_params['is_duplicate_booking'] = 0;
315 663 $is_edit_booking = wpbc_get_data__if_edit_booking( $re_cleaned_params['booking_hash'], $re_cleaned_params['request_uri'] );
316 - if ( false !== $is_edit_booking ) {
317 - $local_params['edit_resource_id'] = $is_edit_booking['resource_id']; // can be parent booking resource, where we edit the booking
318 - $local_params['skip_booking_id'] = $is_edit_booking['booking_id']; // booking ID
319 - $local_params['is_edit_booking'] = $is_edit_booking['booking_id']; // booking ID
664 + if ( false !== $is_edit_booking ) {
665 + $local_params['edit_resource_id'] = $is_edit_booking['resource_id']; // can be parent booking resource, where we edit the booking
666 + $local_params['skip_booking_id'] = $is_edit_booking['booking_id']; // booking ID
667 + $local_params['is_edit_booking'] = $is_edit_booking['booking_id']; // booking ID
320 668 if (
321 669 ( ! empty( $local_params['structured_booking_data_arr']['wpbc_other_action'] ) )
322 670 && ( 'duplicate_booking' === $local_params['structured_booking_data_arr']['wpbc_other_action'] )
323 671 ){
324 - $local_params['is_duplicate_booking'] = 1;
325 - }
326 - }
327 - // It can be request resource ID or if we edit booking, it can be 'edit resource' - (e.g. child resource)
672 + $local_params['is_duplicate_booking'] = 1;
673 + }
674 + }
675 +
676 + $is_frontend_ajax_edit = wp_doing_ajax()
677 + && 'wpbc_ajx_booking__create' === strtolower( $request_action )
678 + && 0 !== $local_params['is_edit_booking'];
679 + $is_authorized_admin_edit = $is_authorized_admin_booking_request;
680 +
681 + if (
682 + $is_frontend_ajax_edit
683 + && ! $is_authorized_admin_edit
684 + && ! wpbc_is_visitor_booking_action_allowed( $local_params['is_edit_booking'] )
685 + ) {
686 + $ajx_data_arr['status'] = 'error';
687 + $ajx_data_arr['status_error'] = 'visitor_booking_dates_in_past';
688 + $ajx_data_arr['ajx_after_action_message'] = __( 'This booking can no longer be edited because its dates have already passed.', 'booking' );
689 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
690 + return array( 'ajx_data' => $ajx_data_arr );
691 + }
692 + // It can be request resource ID or if we edit booking, it can be 'edit resource' - (e.g. child resource)
328 693 $local_params['initial_resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) ) ? $local_params['edit_resource_id'] : $re_cleaned_params['resource_id'];
329 694
330 - // 2
331 - $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'] );
332 -
333 -
334 - $local_params['aggregate_resource_id_arr'] = explode( ',', $re_cleaned_params['aggregate_resource_id_arr'] );
695 + // 2
696 + $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'] );
697 +
698 +
699 + $local_params['aggregate_resource_id_arr'] = explode( ',', $re_cleaned_params['aggregate_resource_id_arr'] );
335 700 $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.
336 701 $local_params['aggregate_resource_id_arr'] = array_unique( $local_params['aggregate_resource_id_arr'] ); // Erase duplicates
337 702
338 703 // -----------------------------------------------------------------------------------------------------------------
@@ -338,123 +703,110 @@
338 703 // -----------------------------------------------------------------------------------------------------------------
339 704 // Here GO
340 705 // -----------------------------------------------------------------------------------------------------------------
341 706
342 - // Force - resource saving parameters, instead of wpbc__where_to_save_booking()
343 - if ( ! empty( $re_cleaned_params["save_booking_even_if_unavailable"] ) ) {
707 + $availability_guard = wpbc_booking_availability_guard_acquire();
708 + if ( is_wp_error( $availability_guard ) ) {
709 + $ajx_data_arr['status'] = 'error';
710 + $ajx_data_arr['status_error'] = $availability_guard->get_error_code();
711 + $ajx_data_arr['ajx_after_action_message'] = $availability_guard->get_error_message();
712 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
713 +
714 + return array( 'ajx_data' => $ajx_data_arr );
715 + }
716 +
717 + $guard_revalidation_attempts = 0;
718 + try {
719 + while ( true ) {
720 + wpbc_cache__clear( 'wpbc__sql__get_booking_dates' );
721 + $where_to_save_booking = wpbc_booking_validate_save_availability( $local_params, $re_cleaned_params, $php_performance );
722 +
723 + if ( is_wp_error( $where_to_save_booking ) ) {
724 + $ajx_data_arr['status'] = 'error';
725 + $ajx_data_arr['status_error'] = $where_to_save_booking->get_error_code();
726 + $ajx_data_arr['ajx_after_action_message'] = $where_to_save_booking->get_error_message();
727 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
728 +
729 + return array( 'ajx_data' => $ajx_data_arr );
730 + }
731 +
732 + // Get parameters, from REQUEST.
733 + $create_params = $local_params;
734 + $create_params['resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) )
735 + ? $local_params['edit_resource_id']
736 + : $where_to_save_booking['main__resource_id'];
737 + $create_params['is_emails_send'] = $re_cleaned_params['is_emails_send'];
738 + $create_params['custom_form'] = $re_cleaned_params['custom_form'];
739 +
740 + make_bk_action( 'check_multiuser_params_for_client_side', $create_params['resource_id'] );
741 +
742 + $create_booking_params = array(
743 + 'resource_id' => $create_params['resource_id'],
744 + 'custom_form' => $create_params['custom_form'],
745 + 'all_booking_data_arr' => $create_params['all_booking_data_arr'],
746 + 'dates_only_sql_arr' => $create_params['dates_only_sql_arr'],
747 + 'time_as_his_arr' => $create_params['time_as_his_arr'],
748 + 'is_from_admin_panel' => $create_params['is_from_admin_panel'],
749 + 'is_edit_booking' => $create_params['is_edit_booking'],
750 + 'is_duplicate_booking' => $create_params['is_duplicate_booking'],
751 + 'is_approve_booking' => $create_params['is_approve_booking'],
752 + 'how_many_items_to_book' => $create_params['how_many_items_to_book'],
753 + 'is_use_booking_recurrent_time' => $create_params['is_use_booking_recurrent_time'],
754 + );
755 + if ( ! empty( $create_params['appointment_service'] ) ) {
756 + $create_booking_params['appointment_service'] = $create_params['appointment_service'];
757 + }
758 + if ( ! empty( $create_params['sync_gid'] ) ) {
759 + $create_booking_params['sync_gid'] = $create_params['sync_gid'];
760 + }
761 +
762 + if ( ! wpbc_booking_availability_guard_is_owned( $availability_guard ) ) {
763 + wpbc_booking_availability_guard_release( $availability_guard );
764 + if ( 1 <= $guard_revalidation_attempts ) {
765 + $availability_guard = wpbc_booking_availability_guard_get_busy_error();
766 + } else {
767 + ++$guard_revalidation_attempts;
768 + $availability_guard = wpbc_booking_availability_guard_acquire();
769 + }
770 +
771 + if ( is_wp_error( $availability_guard ) ) {
772 + $ajx_data_arr['status'] = 'error';
773 + $ajx_data_arr['status_error'] = $availability_guard->get_error_code();
774 + $ajx_data_arr['ajx_after_action_message'] = $availability_guard->get_error_message();
775 + $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
776 +
777 + return array( 'ajx_data' => $ajx_data_arr );
778 + }
779 +
780 + continue;
781 + }
782 +
783 + $php_performance = wpbc_php_performance_START( 'wpbc_db__booking_save', $php_performance );
784 + $booking_new_arr = wpbc_db__booking_save( $create_booking_params, $where_to_save_booking );
785 + if ( 'ok' !== $booking_new_arr['status'] ) {
786 + $ajx_data_arr['status'] = $booking_new_arr['status'];
787 + $ajx_data_arr['status_error'] = 'booking_can_not_save';
788 + $ajx_data_arr['ajx_after_action_message'] = $booking_new_arr['message'];
789 + $ajx_data_arr['ajx_after_action_message_status'] = 'error';
790 +
791 + return array( 'ajx_data' => $ajx_data_arr );
792 + }
793 +
794 + // Appointment buffers must become visible before the serialized availability section ends.
795 + if ( function_exists( 'wpbc_appointment_services_after_booking_save' ) ) {
796 + wpbc_appointment_services_after_booking_save( $booking_new_arr['booking_id'], $create_booking_params, $where_to_save_booking );
797 + }
798 +
799 + break;
800 + }
801 + } finally {
802 + wpbc_cache__clear( 'wpbc__sql__get_booking_dates' );
803 + wpbc_booking_availability_guard_release( $availability_guard );
804 + }
805 +
806 + // Released compatibility hook: arbitrary callbacks must not extend the database lock duration.
807 + do_action( 'wpbc_booking_after_save', $booking_new_arr['booking_id'], $create_booking_params, $where_to_save_booking );
344 808
345 - $local_params['how_many_items_to_book'] = 1;
346 -
347 - $dates_keys_arr = array_values( $local_params['dates_only_sql_arr'] ); // [ '2023-09-23', '2023-09-24' ]
348 -
349 - $resources_in_dates = array_fill_keys( $dates_keys_arr , array( $local_params['initial_resource_id'] ) ); // [ 2023-09-23 = [ 2 ], 2023-09-24 = [ 2 ] ]
350 -
351 - $where_to_save_booking = array();
352 - $where_to_save_booking['result'] = 'ok';
353 - $where_to_save_booking['resources_in_dates'] = $resources_in_dates; // [ 2023-09-23 = [ 2, 10, 11 ], 2023-09-24 = [ 2, 10, 11 ]
354 - $where_to_save_booking['time_to_book'] = $local_params['time_as_his_arr']; // [ "00:00:00", "24:00:00" ]
355 - $where_to_save_booking['main__resource_id'] = $local_params['initial_resource_id']; // here edit or request (parent/single) resource
356 -
357 - } else {
358 - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
359 - $php_performance = php_performance_START( 'wpbc__where_to_save_booking' , $php_performance );
360 - // </editor-fold>
361 -
362 - /**
363 - * Get slots [] where we can save booking = [ 'resources_in_dates' => [ 2023-10-18 = [ 2, 12, 10, 11 ]
364 - * 2023-10-19 = [ 2, 12, 10, 11 ]
365 - * 2023-10-20 = [ 2, 12, 10, 11 ]
366 - * ],
367 - * 'time_to_book' => [ "14:00:01" , "12:00:01" ],
368 - * 'result' => 'ok'
369 - * 'main__resource_id' => 2
370 - * ]
371 - * OR
372 - * [ 'result' => 'error', 'message' => 'Booking can not be saved ...' ]
373 - */
374 - $where_to_save_booking = wpbc__where_to_save_booking( array(
375 - 'resource_id' => $local_params['initial_resource_id'], // 2 //TODO: If edit booking. What to pass 'edit' or 'parent' resource ID?
376 - 'skip_booking_id' => $local_params['skip_booking_id'], // '', | 125 if edit booking
377 - 'dates_only_sql_arr' => $local_params['dates_only_sql_arr'], // [ "2023-10-18", "2023-10-25", "2023-11-25" ]
378 - 'time_as_seconds_arr' => $local_params['time_as_seconds_arr'], // [ 36000, 39600 ]
379 - 'how_many_items_to_book' => $local_params['how_many_items_to_book'], // 1
380 - 'request_uri' => $re_cleaned_params['request_uri'], // 'http://beta/resource-id2/'
381 - 'is_use_booking_recurrent_time' => $local_params['is_use_booking_recurrent_time'], // true | false
382 - 'as_single_resource' => false, // false
383 - 'aggregate_resource_id_arr' => $local_params['aggregate_resource_id_arr'], // Optional can be ''
384 - '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.
385 - 'custom_form' => $re_cleaned_params['custom_form'] // FixIn: 10.0.0.10.
386 - ));
387 - // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- NO SLOTS TO SAVE " >
388 - if ( 'error' == $where_to_save_booking['result'] ) {
389 - $ajx_data_arr['status'] = 'error';
390 - $ajx_data_arr['status_error'] = 'booking_can_not_save';
391 - $ajx_data_arr['ajx_after_action_message'] = $where_to_save_booking['message'];
392 - $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
393 - return array( 'ajx_data' => $ajx_data_arr );
394 - }
395 - // </editor-fold>
396 -
397 - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
398 - $php_performance = php_performance_END( 'wpbc__where_to_save_booking' , $php_performance );
399 - // </editor-fold>
400 - }
401 -
402 -
403 - // Get parameters, from REQUEST
404 - $create_params = $local_params;
405 - $create_params['resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) )
406 - ? $local_params['edit_resource_id'] // If we edit, then use original resource ???
407 - : $where_to_save_booking['main__resource_id']; // Here is important TIP, resource can be where is free, and not where we submit
408 - /**
409 - * TODO: I think it's resolved! Just test about this situation, when we edit the booking - and it's means that we have $local_params['edit_resource_id']
410 - * but what, if $where_to_save_booking do not contain this $local_params['edit_resource_id'] as available resource.
411 - * or even we have $local_params['edit_resource_id'] = 2 and $where_to_save_booking contain resources like [ 1, 2, 3, 4 ]
412 - * we make booking for 3 slots
413 - * in this case, main resource will be 2
414 - * but then when we loop resources in wpbc_db__booking_save() we will save child booking resources for dates like: 2, 3, 4 ( and it's wrong )
415 - * "(205, '2023-10-04 00:00:00', 0, NULL)" <- main resource '2' e.g. $local_params['edit_resource_id'] = 2
416 - * "(205, '2023-10-04 00:00:00', 0, 2)" ? <- child resource '2' e.g. [ .., 2, .. ] in $where_to_save_booking WHICH IS WRONG
417 - */
418 - $create_params['is_emails_send'] = $re_cleaned_params['is_emails_send'];
419 - $create_params['custom_form'] = $re_cleaned_params['custom_form'];
420 -
421 - make_bk_action( 'check_multiuser_params_for_client_side', $create_params['resource_id'] ); // Activate working with specific user in WP MU
422 -
423 - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
424 - $php_performance = php_performance_START( 'wpbc_db__booking_save' , $php_performance );
425 - // </editor-fold>
426 -
427 - // -----------------------------------------------------------------------------------------------------------------
428 - // == CREATE_THE 'NEW_BOOKING' ==
429 - // -----------------------------------------------------------------------------------------------------------------
430 - $create_booking_params = array(
431 - 'resource_id' => $create_params['resource_id'],
432 - 'custom_form' => $create_params['custom_form'],
433 - 'all_booking_data_arr' => $create_params['all_booking_data_arr'],
434 - 'dates_only_sql_arr' => $create_params['dates_only_sql_arr'],
435 - 'time_as_his_arr' => $create_params['time_as_his_arr'],
436 - 'is_from_admin_panel' => $create_params['is_from_admin_panel'],
437 - 'is_edit_booking' => $create_params['is_edit_booking'],
438 - 'is_duplicate_booking' => $create_params['is_duplicate_booking'],
439 - 'is_approve_booking' => $create_params['is_approve_booking'],
440 - 'how_many_items_to_book' => $create_params['how_many_items_to_book'],
441 - 'is_use_booking_recurrent_time' => $create_params['is_use_booking_recurrent_time'] // true | false
442 - );
443 - if ( ! empty( $create_params['sync_gid'] ) ) { $create_booking_params['sync_gid'] = $create_params['sync_gid']; }
444 -
445 - $booking_new_arr = wpbc_db__booking_save( $create_booking_params, $where_to_save_booking );
446 -
447 - // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING CREATION " >
448 - if ( 'ok' !== $booking_new_arr['status'] ) {
449 - $ajx_data_arr['status'] = $booking_new_arr['status'];
450 - $ajx_data_arr['status_error'] = 'booking_can_not_save';
451 - $ajx_data_arr['ajx_after_action_message'] = $booking_new_arr['message'];
452 - $ajx_data_arr['ajx_after_action_message_status'] = 'error';
453 - return array( 'ajx_data' => $ajx_data_arr );
454 - }
455 - // </editor-fold>
456 -
457 809 // FixIn: 9.9.0.36.
458 810 if (
459 811 ( 0 !== $create_params['is_edit_booking'] ) // If edit booking
460 812 && ( 1 != $create_params['is_duplicate_booking'] ) // If not duplicate
@@ -465,9 +817,9 @@
465 817 $booking_note .= __( 'The booking has been edited', 'booking' ) . '. | Edit URL: ' . esc_url_raw( $re_cleaned_params['request_uri'] ) . '';
466 818 make_bk_action( 'wpdev_make_update_of_remark', $booking_new_arr['booking_id'], $booking_note, true );
467 819 }
468 820 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
469 - $php_performance = php_performance_END( 'wpbc_db__booking_save' , $php_performance );
821 + $php_performance = wpbc_php_performance_END( 'wpbc_db__booking_save' , $php_performance );
470 822 // </editor-fold>
471 823
472 824 // -----------------------------------------------------------------------------------------------------------------
473 825 // Get payment form(s) and Update COST of the booking
@@ -481,9 +833,10 @@
481 833 );
482 834 $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']
483 835 $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'
484 836 $payment_params['booking_id'] = $booking_new_arr['booking_id']; // REQUIRED -- '2'
485 - $payment_params['resource_id'] = $create_params['resource_id']; // REQUIRED -- '2' can be child resource (changed in wpbc_where_to_save() )
837 + $payment_params['resource_id'] = $create_params['resource_id']; // REQUIRED -- '2' can be child resource (changed in wpbc_where_to_save() )
838 + $payment_params['service_id'] = ! empty( $create_params['appointment_service']['service_id'] ) ? absint( $create_params['appointment_service']['service_id'] ) : 0;
486 839 $payment_params['initial_resource_id'] = $local_params['initial_resource_id']; // REQUIRED -- '2' initial calendar - parent resource
487 840 $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...'
488 841 $payment_params['times_array'] = array(
489 842 explode( ':', $where_to_save_booking['time_to_book'][0] ), // ["10","00","00"]
@@ -493,14 +846,15 @@
493 846 $payment_params['is_edit_booking'] = $create_params['is_edit_booking']; // => 0 0 | int - ID of the booking
494 847 $payment_params['custom_form'] = $create_params['custom_form']; // => '' '' | 'some_name'
495 848 $payment_params['is_duplicate_booking'] = $create_params['is_duplicate_booking']; // => 0 0 | 1
496 849 $payment_params['is_from_admin_panel'] = $create_params['is_from_admin_panel']; // => false true | false
497 - $payment_params['is_show_payment_form'] = $create_params['is_show_payment_form']; // => 1 0 | 1
850 + $payment_params['is_show_payment_form'] = $create_params['is_show_payment_form']; // => 1 0 | 1
851 + $payment_params['wpbc_admin_cost_correction'] = $re_cleaned_params['wpbc_admin_cost_correction'];
498 852 if ( $payment_params['is_from_admin_panel'] ) {
499 853 // $payment_params['is_show_payment_form'] = 0; // FixIn: 9.9.0.21.
500 854 }
501 855 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
502 - $php_performance = php_performance_START( 'wpbc_maybe_get_payment_form' , $php_performance );
856 + $php_performance = wpbc_php_performance_START( 'wpbc_maybe_get_payment_form' , $php_performance );
503 857 // </editor-fold>
504 858
505 859 // GET PAYMENT FORMS ===============================================================================================
506 860 if ( function_exists( 'wpbc_maybe_get_payment_form' ) ) {
@@ -537,11 +891,11 @@
537 891 }
538 892
539 893
540 894 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
541 - $php_performance = php_performance_END( 'wpbc_maybe_get_payment_form' , $php_performance );
895 + $php_performance = wpbc_php_performance_END( 'wpbc_maybe_get_payment_form' , $php_performance );
542 896
543 - $php_performance = php_performance_START( 'emails_sending' , $php_performance );
897 + $php_performance = wpbc_php_performance_START( 'emails_sending' , $php_performance );
544 898 // </editor-fold>
545 899
546 900 // -----------------------------------------------------------------------------------------------------------------
547 901 // == Emails ===
@@ -570,8 +924,10 @@
570 924 // New approved to Visitor / Admin
571 925 wpbc_send_email_approved( $payment_params['booking_id'], 1 );
572 926 }
573 927
928 + do_action( 'wpbc_booking_is_approved_during_creation' , $payment_params['booking_id'] , (int) $is_booking_approved ); // FixIn: 10.10.1.1.
929 +
574 930 // Payment request from admin panel, if needed
575 931 if(
576 932 ( $payment_params['is_from_admin_panel'] )
577 933 && ( 'On' == get_bk_option( 'booking_payment_request_auto_send_in_bap' ) )
@@ -580,10 +936,10 @@
580 936 $payment_reason = '';
581 937 $is_send = wpbc_send_email_payment_request( $payment_params['booking_id'], $payment_params['resource_id'], $email_content , $payment_reason );
582 938 }
583 939
584 - do_action( 'wpbc_booking_approved' , $payment_params['booking_id'] , (int) $is_booking_approved );
585 940
941 +
586 942 } else {
587 943
588 944 // Edited booking to Visitor / Admin
589 945 if ( function_exists( 'wpbc_send_email_modified' ) ) {
@@ -605,9 +961,9 @@
605 961 }
606 962 }
607 963
608 964 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
609 - $php_performance = php_performance_END( 'emails_sending' , $php_performance );
965 + $php_performance = wpbc_php_performance_END( 'emails_sending' , $php_performance );
610 966 // </editor-fold>
611 967
612 968 // -----------------------------------------------------------------------------------------------------------------
613 969 // == Track booking - New | Edit ===
@@ -679,9 +1035,9 @@
679 1035 }
680 1036
681 1037
682 1038 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
683 - $php_performance = php_performance_START( 'confirmation' , $php_performance );
1039 + $php_performance = wpbc_php_performance_START( 'confirmation' , $php_performance );
684 1040 // </editor-fold>
685 1041
686 1042 // <editor-fold defaultstate="collapsed" desc=" == Confirmation data == " >
687 1043
@@ -718,9 +1074,9 @@
718 1074 ( 0 !== $local_params['is_edit_booking'] )
719 1075 // && ( empty( $local_params['is_duplicate_booking'] ) )
720 1076 ){
721 1077 $confirmation_params_arr['ty_is_redirect'] = 'page';
722 - $confirmation_params_arr['ty_url'] = wpbc_get_bookings_url() . '&view_mode=vm_listing&tab=actions&wh_booking_id=' . $confirmation_params_arr['booking_id'];
1078 + $confirmation_params_arr['ty_url'] = wpbc_get_bookings_url() . '&tab=vm_booking_listing&wh_booking_id=' . $confirmation_params_arr['booking_id'];
723 1079 }
724 1080 }
725 1081 $confirmation = wpbc_booking_confirmation( $confirmation_params_arr );
726 1082
@@ -726,9 +1082,9 @@
726 1082
727 1083 // </editor-fold>
728 1084
729 1085 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
730 - $php_performance = php_performance_END( 'confirmation' , $php_performance );
1086 + $php_performance = wpbc_php_performance_END( 'confirmation' , $php_performance );
731 1087 // </editor-fold>
732 1088
733 1089
734 1090 make_bk_action( 'finish_check_multiuser_params_for_client_side', $create_params['resource_id'] ); // Deactivate working with specific user in WP MU
@@ -734,9 +1090,9 @@
734 1090 make_bk_action( 'finish_check_multiuser_params_for_client_side', $create_params['resource_id'] ); // Deactivate working with specific user in WP MU
735 1091
736 1092
737 1093 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
738 - $php_performance = php_performance_END( 'total' , $php_performance );
1094 + $php_performance = wpbc_php_performance_END( 'total' , $php_performance );
739 1095 $php_performance['other_code'] = - 1 * array_reduce( $php_performance,
740 1096 function ( $sum, $item ) {
741 1097 $sum += $item;
742 1098 return $sum;
@@ -743,8 +1099,9 @@
743 1099 }
744 1100 , - 2 * $php_performance['total'] ); // PERFORMANCE OTHER - after TOTAL
745 1101 // </editor-fold>
746 1102
1103 + wpbc_clear_request_form_context();
747 1104
748 1105 return array( 'ajx_data' => $ajx_data_arr, // [ 'status' => "ok", 'wpbc_payment_output' => "<p>Dear John<br..." ]
749 1106 'booking_id' => $booking_new_arr['booking_id'], // 254
750 1107 'booking_arr' => $payment_params,
@@ -799,10 +1156,10 @@
799 1156 * 'message' => '' 'If error, then here can be description of error'
800 1157 * 'form_data' => If 'ok' form data can be different here, 'custom_form' parameter, so it can add 'wpbc_custom_booking_form' field for identification, what custom booking form was used,
801 1158 * ]
802 1159 */
803 -function wpbc_db__booking_save( $create_params, $where_to_save_booking ) {
804 -
1160 +function wpbc_db__booking_save( &$create_params, &$where_to_save_booking ) {
1161 + //FixIn: 10.11.5.4
805 1162 /**
806 1163 * Tip: $create_params['all_booking_data_arr'] - contain: [ 'field_name' => [ 'type' = "checkbox", 'original_name' = "fixed_fee2[]", 'name' = "fixed_fee", 'value' = "true" ] , ... ]
807 1164 * $create_params['structured_booking_data_arr'] - contain: [ 'field_name' => 'field_value' , ... ]
808 1165 */
@@ -866,9 +1223,13 @@
866 1223 return array( 'status' => 'error', 'message' => 'Sent request with no dates.' );
867 1224 }
868 1225
869 1226 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- CHECK_IN_DATE_OLDER_THAN_CHECK_OUT " >
870 - if ( count( $create_params['dates_only_sql_arr'] ) == 1 ) { // Is it single selected date ?
1227 + $is_no_dates_booking = (
1228 + function_exists( 'wpbc_is_these_dates__for__no_dates' )
1229 + && wpbc_is_these_dates__for__no_dates( $create_params['dates_only_sql_arr'] )
1230 + );
1231 + if ( ( count( $create_params['dates_only_sql_arr'] ) == 1 ) && ( ! $is_no_dates_booking ) ) { // Is it single selected date ?
871 1232
872 1233 // Is 'check in' date/time older than 'check out' date/time when SINGLE day for booking? Then show error.
873 1234
874 1235 /**
@@ -954,9 +1315,9 @@
954 1315 $sql_field_arr[] = array( 'name' => 'form', 'type' => '%s', 'value' => $form_data );
955 1316 $sql_field_arr[] = array( 'name' => 'booking_type', 'type' => '%d', 'value' => $create_params['resource_id'] );
956 1317 $sql_field_arr[] = array( 'name' => 'modification_date', 'type' => '%s', 'value' => gmdate( 'Y-m-d H:i:s' ) );
957 1318 $sql_field_arr[] = array( 'name' => 'sort_date', 'type' => '%s', 'value' => $create_params['dates_only_sql_arr'][0] . ' ' . $create_params['time_as_his_arr'][0] );
958 - $sql_field_arr[] = array( 'name' => 'hash', 'type' => 'MD5(%s)', 'value' => time() . '_' . wp_rand( 1000, 1000000 ) );
1319 + $sql_field_arr[] = array( 'name' => 'hash', 'type' => '%s', 'value' => wpbc_hash__generate_booking_hash() );
959 1320
960 1321
961 1322 if (
962 1323 ( 0 == $create_params['is_edit_booking'] ) || // If not edit, then INSERT.
@@ -977,12 +1338,15 @@
977 1338 $sql_prepare_arr['name'] = implode( ', ', $sql_prepare_arr['name'] );
978 1339 $sql_prepare_arr['type'] = implode( ', ', $sql_prepare_arr['type'] );
979 1340 /* phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare */
980 1341 $sql = $wpdb->prepare( "INSERT INTO {$wpdb->prefix}booking " . " ( {$sql_prepare_arr['name']} )" . " VALUES ( {$sql_prepare_arr['type']} )", $sql_prepare_arr['value'] );
981 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
982 - if ( false === $wpdb->query( $sql ) ) {
983 - return array( 'status' => 'error', 'message' => 'Error. INSERT New Data in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $sql );
984 - }
1342 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1343 + if ( false === $wpdb->query( $sql ) ) {
1344 + return array(
1345 + 'status' => 'error',
1346 + 'message' => __( 'The booking could not be saved because of a database error. Please try again or contact the website administrator.', 'booking' ),
1347 + );
1348 + }
985 1349 // Get ID of booking
986 1350 $booking_id = (int) $wpdb->insert_id;
987 1351
988 1352 } else { // Edit - UPDATE
@@ -996,24 +1360,25 @@
996 1360 $sql_prepare_arr['set'] = implode( ', ', $sql_prepare_arr['set'] );
997 1361
998 1362 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
999 1363 $sql = $wpdb->prepare( "UPDATE {$wpdb->prefix}booking SET {$sql_prepare_arr['set']} WHERE booking_id={$booking_id};", $sql_prepare_arr['value'] );
1000 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
1001 - if ( false === $wpdb->query( $sql ) ) {
1002 - return array( 'status' => 'error',
1003 - 'message' => 'Error. UPDATE Exist Data in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $sql,
1004 - );
1005 - }
1364 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1365 + if ( false === $wpdb->query( $sql ) ) {
1366 + return array(
1367 + 'status' => 'error',
1368 + 'message' => __( 'The booking could not be updated because of a database error. Please try again or contact the website administrator.', 'booking' ),
1369 + );
1370 + }
1006 1371
1007 1372 // Check if dates previously was approved.
1008 1373 $slct_sql = "SELECT approved FROM {$wpdb->prefix}bookingdates WHERE booking_id IN ({$booking_id}) LIMIT 0,1";
1009 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
1374 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1010 1375 $slct_sql_results = $wpdb->get_results( $slct_sql );
1011 1376 $is_approved_dates = ( count( $slct_sql_results ) > 0 ) ? $slct_sql_results[0]->approved : $is_approved_dates;
1012 1377
1013 1378
1014 1379 $delete_sql = "DELETE FROM {$wpdb->prefix}bookingdates WHERE booking_id IN ({$booking_id})";
1015 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
1380 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1016 1381 if ( false === $wpdb->query( $delete_sql ) ) {
1017 1382 return array( 'status' => 'error', 'message' => 'Error. DELETE Old Dates in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $delete_sql );
1018 1383 }
1019 1384 }
@@ -1102,9 +1467,9 @@
1102 1467 }
1103 1468 }
1104 1469
1105 1470 $dates_sql .= implode( ', ', $insert_dates_arr );
1106 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
1471 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1107 1472 if ( false === $wpdb->query( $dates_sql ) ) {
1108 1473 return array( 'status' => 'error', 'message' => 'Error. INSERT "D A T E S" in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $dates_sql );
1109 1474 }
1110 1475
@@ -1148,9 +1513,24 @@
1148 1513
1149 1514 return $insert_dates_arr;
1150 1515 }
1151 1516
1517 +// == Help functions ==
1518 +function wpbc_set_request_form_context( $ctx ) {
1519 + $GLOBALS['wpbc_request_form_context'] = ( is_array( $ctx ) ) ? $ctx : array();
1520 +}
1152 1521
1522 +function wpbc_get_request_form_context() {
1523 + return ( isset( $GLOBALS['wpbc_request_form_context'] ) && is_array( $GLOBALS['wpbc_request_form_context'] ) )
1524 + ? $GLOBALS['wpbc_request_form_context'] : array();
1525 +}
1526 +
1527 +function wpbc_clear_request_form_context() {
1528 + if ( isset( $GLOBALS['wpbc_request_form_context'] ) ) {
1529 + unset( $GLOBALS['wpbc_request_form_context'] );
1530 + }
1531 +}
1532 +
1153 1533 // ---------------------------------------------------------------------------------------------------------------------
1154 1534 // Support
1155 1535 // ---------------------------------------------------------------------------------------------------------------------
1156 1536
@@ -1174,9 +1554,10 @@
1174 1554 $is_edit_booking['booking_id'] = intval( $my_booking_id_type[0] );
1175 1555 $is_edit_booking['resource_id'] = intval( $my_booking_id_type[1] );
1176 1556
1177 1557 //TODO: test it. Check situation when we have editing "child booking resource", so need to re-update calendar and form to have it for parent resource. // FixIn: 6.1.1.9.
1178 - if ( strpos( $server_request_url, 'resource_no_update' ) === false ) { // FixIn: 9.4.2.3.
1558 + // FixIn: 10.10.1.2
1559 + //if ( strpos( $server_request_url, 'resource_no_update' ) === false ) { // FixIn: 9.4.2.3.
1179 1560
1180 1561 if ( ( function_exists( 'wpbc_is_this_child_resource' ) ) && ( wpbc_is_this_child_resource( $is_edit_booking['resource_id'] ) ) ) {
1181 1562 $bk_parent_br_id = wpbc_get_parent_resource( $is_edit_booking['resource_id'] );
1182 1563
@@ -1181,9 +1562,9 @@
1181 1562 $bk_parent_br_id = wpbc_get_parent_resource( $is_edit_booking['resource_id'] );
1182 1563
1183 1564 $is_edit_booking['resource_id'] = intval( $bk_parent_br_id );
1184 1565 }
1185 - }
1566 + //}
1186 1567 }
1187 1568 }
1188 1569 return $is_edit_booking;
1189 1570 }
@@ -1211,9 +1592,9 @@
1211 1592 function wpbc_get__how_many_items_to_book__in_booking_form( $booking_form_data__arr, $resource_id ){
1212 1593
1213 1594 $how_many_items_to_book = 1;
1214 1595
1215 - //TODO: Check about some URL parameter: '&resource_no_update' to book parent resource as single resource!
1596 + //TODO: Check about some URL parameter: '&resource_no_update' to book parent resource as single resource! // FixIn: 10.10.1.2
1216 1597 if (
1217 1598 ( class_exists( 'wpdev_bk_biz_l' ) )
1218 1599 && ( 0 !== wpbc_get_child_resources_number( $resource_id ) ) // Here several child booking resources
1219 1600 ) {
@@ -1278,9 +1659,9 @@
1278 1659 *
1279 1660 * // Now get start/end times as seconds: [ 64800, 72000 ]
1280 1661 * $time_as_seconds_arr = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $structured_booking_data_arr );
1281 1662 */
1282 - function wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $booking_form_data__arr ){
1663 + function wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $booking_form_data__arr ){
1283 1664
1284 1665 $selected_time_fields = wpbc_get__selected_time_fields__in_booking_form__as_arr( $booking_form_data__arr );
1285 1666
1286 1667 // 2.2 Get selected SECONDS to book ---------------------------------------------------------------------------
@@ -1310,14 +1691,236 @@
1310 1691 foreach ( $selected_time_fields as $time_fields_obj ) { // { times_as_seconds: [ 21600 ], value_option_24h: '06:00', name: 'durationtime'}
1311 1692
1312 1693 if ( false !== strpos( $time_fields_obj['name'], 'durationtime' ) ) {
1313 1694 $time_as_seconds_arr[ 1 ] = $time_as_seconds_arr[ 0 ] + $time_fields_obj['times_as_seconds'][ 0 ];
1695 + // FixIn: 10.14.7.1.
1696 + while ( $time_as_seconds_arr[1] > ( 24 * 60 * 60 ) ) {
1697 + $time_as_seconds_arr[1] = $time_as_seconds_arr[1] - ( 24 * 60 * 60 );
1698 + }
1314 1699 break;
1315 1700 }
1316 1701 }
1317 1702 }
1318 1703
1319 - return $time_as_seconds_arr;
1704 + return $time_as_seconds_arr;
1705 + }
1706 +
1707 +
1708 + /**
1709 + * Determine whether a booking-create request is an authorized administration workflow.
1710 + *
1711 + * The public booking action is intentionally available to signed-out visitors. A
1712 + * Referer, request path, or caller-supplied Boolean therefore cannot establish an
1713 + * administrator security context. The Add Booking UI supplies this user-bound nonce,
1714 + * and the server independently rechecks login, capability, and MultiUser access.
1715 + *
1716 + * @param mixed $admin_booking_nonce Candidate Add Booking administration nonce.
1717 + *
1718 + * @return bool True only for an authorized Add Booking administration request.
1719 + */
1720 + function wpbc_is_authorized_admin_booking_request( $admin_booking_nonce ) {
1721 +
1722 + if (
1723 + ! is_scalar( $admin_booking_nonce )
1724 + || '' === trim( (string) $admin_booking_nonce )
1725 + || ! is_user_logged_in()
1726 + || ! wp_verify_nonce( sanitize_text_field( (string) $admin_booking_nonce ), 'wpbc_admin_booking_create' )
1727 + || ! class_exists( 'WPBC_Add_Booking_Component' )
1728 + || ! WPBC_Add_Booking_Component::current_user_can_add_booking()
1729 + || ! wpbc_is_mu_user_can_be_here( 'activated_user' )
1730 + ) {
1731 + return false;
1732 + }
1733 +
1734 + return true;
1735 + }
1736 +
1737 +
1738 + /**
1739 + * Require the signed workflow proof declared by a verified Booking Form context.
1740 + *
1741 + * Appointment and Resource Selector JavaScript flags are presentation hints only.
1742 + * The signed Booking Form context identifies the server-rendered workflow, so removing
1743 + * a flag or domain token cannot downgrade that form to a different workflow.
1744 + *
1745 + * @param array $classic_context Verified Booking Form context.
1746 + * @param bool $has_verified_appointment_context Whether Service and Provider proof passed validation.
1747 + * @param bool $has_verified_resource_selector_context Whether Resource Selector proof passed validation.
1748 + *
1749 + * @return true|WP_Error True when the required proof is present, otherwise a safe validation error.
1750 + */
1751 + function wpbc_booking_create_validate_required_workflow( $classic_context, $has_verified_appointment_context, $has_verified_resource_selector_context ) {
1752 +
1753 + $booking_workflow = isset( $classic_context['booking_workflow'] ) ? sanitize_key( $classic_context['booking_workflow'] ) : '';
1754 + if ( 'appointment' === $booking_workflow && ! $has_verified_appointment_context ) {
1755 + return new WP_Error( 'appointment_context_required', __( 'The Appointment selection has expired. Please start over and try again.', 'booking' ) );
1756 + }
1757 + if ( 'resource_selector' === $booking_workflow && ! $has_verified_resource_selector_context ) {
1758 + return new WP_Error( 'resource_selector_context_required', __( 'The Booking Resource selection has expired. Please start over and try again.', 'booking' ) );
1759 + }
1760 +
1761 + return true;
1762 + }
1763 +
1764 +
1765 + /**
1766 + * Remove administrator time-override values from an unauthorized booking request.
1767 + *
1768 + * The public booking endpoint intentionally accepts unauthenticated requests, so
1769 + * sanitizing these values is not sufficient authorization. Clearing every related
1770 + * value here prevents a public client from replacing the Booking Form's configured
1771 + * time while preserving the capability-protected Add Booking workflow.
1772 + *
1773 + * @param array $request_params Sanitized booking request parameters.
1774 + * @param bool $is_authorized_admin_booking_request Whether the current request is an authorized Add Booking administration request.
1775 + *
1776 + * @return array Booking request parameters with unauthorized override values removed.
1777 + */
1778 + function wpbc_restrict_booking_time_override_to_authorized_admin( $request_params, $is_authorized_admin_booking_request ) {
1779 +
1780 + $request_params = is_array( $request_params ) ? $request_params : array();
1781 + if ( $is_authorized_admin_booking_request ) {
1782 + return $request_params;
1783 + }
1784 +
1785 + $request_params['wpbc_time_override_enabled'] = 0;
1786 + $request_params['wpbc_time_override_source'] = '';
1787 + $request_params['wpbc_time_override_start'] = '';
1788 + $request_params['wpbc_time_override_end'] = '';
1789 +
1790 + return $request_params;
1791 + }
1792 +
1793 +
1794 + /**
1795 + * Authorize and normalize an administrator cost-correction request value.
1796 + *
1797 + * Booking creation is intentionally public, so a sanitized numeric value is
1798 + * not sufficient authorization. Only capability-protected Add Booking and
1799 + * Add Appointment workflows in Business Small or higher may retain this value.
1800 + * Missing, malformed, out-of-range, public, and unsupported-edition values
1801 + * are reduced to an empty sentinel, which preserves automatic calculation.
1802 + *
1803 + * @param array $request_params Sanitized booking request parameters.
1804 + * @param bool $is_authorized_admin_booking_request Whether this is an authorized administrator booking request.
1805 + *
1806 + * @return array Booking request parameters with a normalized or empty cost correction.
1807 + */
1808 + function wpbc_restrict_booking_cost_correction_to_authorized_admin( $request_params, $is_authorized_admin_booking_request ) {
1809 +
1810 + $request_params = is_array( $request_params ) ? $request_params : array();
1811 + $raw_cost = isset( $request_params['wpbc_admin_cost_correction'] ) ? $request_params['wpbc_admin_cost_correction'] : '';
1812 +
1813 + $request_params['wpbc_admin_cost_correction'] = '';
1814 + if ( ! $is_authorized_admin_booking_request || ! class_exists( 'wpdev_bk_biz_s' ) ) {
1815 + return $request_params;
1816 + }
1817 +
1818 + $request_params['wpbc_admin_cost_correction'] = wpbc_sanitize_booking_cost_correction( $raw_cost );
1819 +
1820 + return $request_params;
1821 + }
1822 +
1823 +
1824 + /**
1825 + * Sanitize one exact administrator-entered Booking total.
1826 + *
1827 + * @param mixed $raw_cost Raw request value.
1828 + *
1829 + * @return string Normalized decimal without trailing zeroes, or an empty string when invalid.
1830 + */
1831 + function wpbc_sanitize_booking_cost_correction( $raw_cost ) {
1832 +
1833 + if ( ! is_scalar( $raw_cost ) ) {
1834 + return '';
1835 + }
1836 +
1837 + $raw_cost = trim( sanitize_text_field( (string) $raw_cost ) );
1838 + if ( '' === $raw_cost || ! preg_match( '/^[0-9]{1,10}(?:\.[0-9]{1,8})?$/', $raw_cost ) ) {
1839 + return '';
1840 + }
1841 +
1842 + $normalized_cost = (float) $raw_cost;
1843 + if ( ! is_finite( $normalized_cost ) || $normalized_cost < 0 || $normalized_cost > 1000000000 ) {
1844 + return '';
1845 + }
1846 +
1847 + $normalized_cost = rtrim( rtrim( number_format( $normalized_cost, 8, '.', '' ), '0' ), '.' );
1848 +
1849 + return '' === $normalized_cost ? '0' : $normalized_cost;
1850 + }
1851 +
1852 +
1853 + /**
1854 + * Get explicit admin-selected time override from Add Booking modal request.
1855 + *
1856 + * @param array $request_params Sanitized booking request params.
1857 + *
1858 + * @return array Empty array or array with start/end HH:MM values.
1859 + */
1860 + function wpbc_get_booking_time_override__as_arr( $request_params ) {
1861 +
1862 + if ( empty( $request_params['wpbc_time_override_enabled'] ) ) {
1863 + return array();
1864 + }
1865 +
1866 + $start_time = wpbc_sanitize_booking_time_override__hm( isset( $request_params['wpbc_time_override_start'] ) ? $request_params['wpbc_time_override_start'] : '' );
1867 + $end_time = wpbc_sanitize_booking_time_override__hm( isset( $request_params['wpbc_time_override_end'] ) ? $request_params['wpbc_time_override_end'] : '' );
1868 +
1869 + if (
1870 + ( '' === $start_time )
1871 + || ( '' === $end_time )
1872 + || ( wpbc_booking_time_override__hm_to_seconds( $start_time ) >= wpbc_booking_time_override__hm_to_seconds( $end_time ) )
1873 + ) {
1874 + return array();
1875 + }
1876 +
1877 + return array(
1878 + 'start' => $start_time,
1879 + 'end' => $end_time,
1880 + 'source' => isset( $request_params['wpbc_time_override_source'] ) ? sanitize_key( $request_params['wpbc_time_override_source'] ) : '',
1881 + );
1882 + }
1883 +
1884 +
1885 + /**
1886 + * Sanitize HH:MM value for admin booking time override.
1887 + *
1888 + * @param string $time_value Time value.
1889 + *
1890 + * @return string
1891 + */
1892 + function wpbc_sanitize_booking_time_override__hm( $time_value ) {
1893 +
1894 + $time_value = trim( sanitize_text_field( (string) $time_value ) );
1895 +
1896 + if ( ! preg_match( '/^([0-9]{1,2}):([0-9]{2})$/', $time_value, $matches ) ) {
1897 + return '';
1898 + }
1899 +
1900 + $hour = absint( $matches[1] );
1901 + $minute = absint( $matches[2] );
1902 +
1903 + if ( $hour > 24 || $minute > 59 || ( 24 === $hour && 0 !== $minute ) ) {
1904 + return '';
1905 + }
1906 +
1907 + return sprintf( '%02d:%02d', $hour, $minute );
1908 + }
1909 +
1910 +
1911 + /**
1912 + * Convert HH:MM to seconds.
1913 + *
1914 + * @param string $time_value Time value.
1915 + *
1916 + * @return int
1917 + */
1918 + function wpbc_booking_time_override__hm_to_seconds( $time_value ) {
1919 +
1920 + $time_arr = explode( ':', (string) $time_value );
1921 +
1922 + return ( absint( $time_arr[0] ) * 60 * 60 ) + ( absint( $time_arr[1] ) * 60 );
1320 1923 }
1321 1924
1322 1925
1323 1926 /**