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 +899 -278 10.1.3 → 11.9 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2
3 -if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly //FixIn: 9.8.0.4
3 +if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly // FixIn: 9.8.0.4.
4 4
5 5 // ---------------------------------------------------------------------------------------------------------------------
6 6 // == Ajax Response on creation of new booking
7 7 // ---------------------------------------------------------------------------------------------------------------------
@@ -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.
@@ -20,9 +20,9 @@
20 20
21 21 // Security ------------------------------------------------------------------------------------------------------ // in Ajax Post: 'nonce': _wpbc.get_secure_param( 'nonce' ),
22 22 $action_name = 'wpbc_calendar_load_ajx' . '_wpbcnonce';
23 23 $nonce_post_key = 'nonce';
24 - if ( wpbc_is_use_nonce_at_front_end() ) { //FixIn: 10.1.1.2
24 + if ( wpbc_is_use_nonce_at_front_end() ) { // FixIn: 10.1.1.2.
25 25 $result_check = check_ajax_referer( $action_name, $nonce_post_key );
26 26 }
27 27
28 28 // Response AJAX parameters
@@ -28,89 +28,123 @@
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?'
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
33 35
34 - // Local parameters
35 - $local_params = array();
36 - $local_params['is_from_admin_panel'] = ( false !== strpos( $_SERVER['HTTP_REFERER'], $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
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 + ));
38 74
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 -
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']
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'] );
67 84
68 - $request_params['request_uri'] = $_SERVER['HTTP_REFERER']; // Parameter needed for Error in booking saving and reloading calendar again with these actual parameters.
69 -
70 85 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- CAPTCHA " >
71 - wpbc_captcha__in_ajx__check( $request_params, $local_params['is_from_admin_panel'], $_REQUEST[ $request_prefix ] );
86 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
87 + wpbc_captcha__in_ajx__check( $request_params, $is_authorized_admin_booking_request, $_REQUEST[ $request_prefix ] );
72 88 // </editor-fold>
73 89
74 90 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING_RESOURCE ID " >
75 91 if ( $request_params['resource_id'] <= 0 ) {
76 - $ajx_data_arr['status'] = 'error';
77 - $ajx_data_arr['status_error'] = 'resource_id_incorrect';
92 + $ajx_data_arr['status'] = 'error';
93 + $ajx_data_arr['status_error'] = 'resource_id_incorrect';
94 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
78 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'] . ' ]';
79 - $ajx_data_arr['ajx_after_action_message_status'] = 'error';
80 - wp_send_json( array( 'ajx_data' => $ajx_data_arr,
81 - 'ajx_search_params' => $_REQUEST[ $request_prefix ],
82 - 'ajx_cleaned_params' => $request_params,
83 - 'resource_id' => $request_params['resource_id']
84 - ) );
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 + ) );
85 101 }
86 102 // </editor-fold>
87 103
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 */
88 105
89 - $request_save_params = array(
90 - 'resource_id' => $request_params['resource_id'],
91 - 'dates_ddmmyy_csv' => $request_params['dates_ddmmyy_csv'],
92 - 'form_data' => $request_params['formdata'],
93 - 'aggregate_resource_id_arr' => $request_params['aggregate_resource_id_arr'], // Optional can be ''
94 -
95 - 'booking_hash' => $request_params['booking_hash'],
96 - 'custom_form' => $request_params['custom_form'],
97 -
98 - 'is_emails_send' => $request_params['is_emails_send'],
99 - 'is_show_payment_form' => 1,
100 - 'user_id' => $local_params['user_id'],
101 - 'request_uri' => $_SERVER['HTTP_REFERER']
102 - );
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'];
103 136 $booking_save_arr = wpbc_booking_save( $request_save_params );
104 137
105 138 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING " >
106 139 if ( 'ok' !== $booking_save_arr['ajx_data']['status'] ) {
107 140
108 - wp_send_json( array( 'ajx_data' => $booking_save_arr['ajx_data'],
109 - 'ajx_search_params' => $_REQUEST[ $request_prefix ],
110 - 'ajx_cleaned_params' => $request_params,
111 - 'resource_id' => $request_params['resource_id']
112 - ));
141 + wp_send_json(
142 + array(
143 + 'ajx_data' => $booking_save_arr['ajx_data'],
144 + 'resource_id' => $request_params['resource_id'],
145 + )
146 + );
113 147 }
114 148 // </editor-fold>
115 149
116 150 $ajx_data_arr = $booking_save_arr['ajx_data'];
@@ -127,17 +161,17 @@
127 161 }
128 162
129 163 // $ajx_data_arr['ajx_after_action_message'] .= __( 'Booking was created with ID: ' . $booking_save_arr[ 'booking_id' ] , 'booking' );
130 164 // $ajx_data_arr['ajx_after_action_message'] .= '<hr>Total time: <strong>' . $booking_save_arr['php_performance']['total'] . ' s. </strong>';
131 - // $ajx_data_arr['ajx_after_action_message'] .= str_replace( array( ',', '{', '}' ), '<br>', json_encode( $booking_save_arr['php_performance'] ) );
165 + // $ajx_data_arr['ajx_after_action_message'] .= str_replace( array( ',', '{', '}' ), '<br>', wp_json_encode( $booking_save_arr['php_performance'] ) );
132 166 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
133 167
134 168
135 169
136 170 /* if admin edit ?
137 - var my_message = '<?php echo html_entity_decode( esc_js( __('Updated successfully' ,'booking') ),ENT_QUOTES) ; ?>';
171 + var my_message = '<?php echo esc_js( __('Updated successfully' ,'booking') ) ; ?>';
138 172 wpbc_admin_show_message( my_message, 'success', 3000 );
139 - 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'] ; ?>';
140 174 */
141 175
142 176
143 177 // -----------------------------------------------------------------------------------------------------------------
@@ -179,10 +213,94 @@
179 213 // ---------------------------------------------------------------------------------------------------------------------
180 214 // == Save Booking
181 215 // ---------------------------------------------------------------------------------------------------------------------
182 216
183 -/**
184 - * 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
185 303 *
186 304 * @param $request_params = [
187 305 * resource_id = 2 REQUIRED Default: 1
188 306 * dates_ddmmyy_csv = '27.10.2023, 28.10.2023, 29.10.2023' REQUIRED
@@ -222,9 +340,9 @@
222 340 *
223 341 */
224 342 function wpbc_booking_save( $request_params ){
225 343 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
226 - $php_performance = php_performance_START( 'total', array() );
344 + $php_performance = wpbc_php_performance_START( 'total', array() );
227 345 // </editor-fold>
228 346 $ajx_data_arr = array();
229 347 $ajx_data_arr['status'] = 'ok';
230 348
@@ -230,11 +348,13 @@
230 348
231 349 // -----------------------------------------------------------------------------------------------------------------
232 350 // 1. Direct Clean Params
233 351 // -----------------------------------------------------------------------------------------------------------------
234 - $validate_arr_rules = array(
235 - 'resource_id' => array( 'validate' => 'd', 'default' => 1 ), // INT
236 - 'dates_ddmmyy_csv' => array( 'validate' => 'csv_dates', 'default' => '' ), //FixIn: 9.9.1.1
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 */
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 */
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.
237 357 'form_data' => array( 'validate' => 'strong', 'default' => '' ),
238 358 'booking_hash' => array( 'validate' => 'strong', 'default' => '' ),
239 359 'custom_form' => array( 'validate' => 'strong', 'default' => '' ),
240 360 'is_emails_send' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1
@@ -239,32 +359,128 @@
239 359 'custom_form' => array( 'validate' => 'strong', 'default' => '' ),
240 360 'is_emails_send' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1
241 361 'is_show_payment_form' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1
242 362 'user_id' => array( 'validate' => 'd', 'default' => wpbc_get_current_user_id() ), // INT
243 - 'request_uri' => array( 'validate' => 'strong', 'default' => ( ( defined( 'DOING_AJAX' ) ) && ( DOING_AJAX ) ) ? $_SERVER['HTTP_REFERER'] : $_SERVER['REQUEST_URI'] ), // front-end: $_SERVER['REQUEST_URI'] | ajax: $_SERVER['HTTP_REFERER']
363 + 'allow_past' => array( 'validate' => 'd', 'default' => 0 ),
364 + 'classic_booking_context_token' => array( 'validate' => 'strong', 'default' => '' ),
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
244 366 // Really Optional:
245 367 'aggregate_resource_id_arr' => array( 'validate' => 'digit_or_csd', 'default' => '' ),
246 - //TODO: this parameter does not transfer during saving, so here will be always default value 'bookings_only' //FixIn: 10.0.0.7
368 + //TODO: this parameter does not transfer during saving, so here will be always default value 'bookings_only' // FixIn: 10.0.0.7.
247 369 'aggregate_type' => array( 'validate' => 'strong', 'default' => 'bookings_only' ), // Optional. 'all' | 'bookings_only' <- it is depends on shortcode parameter: options="{aggregate type=bookings_only}"
248 370 'is_approve_booking' => array( 'validate' => 'd', 'default' => 0 ), // 0 | 1
249 371 'save_booking_even_if_unavailable' => array( 'validate' => 'd', 'default' => 0 ), // 0 | 1
250 372 'sync_gid' => array( 'validate' => 'strong', 'default' => '' ),
251 - 'is_use_booking_recurrent_time' => array( 'validate' => 'd', 'default' => intval( ( 'On' === get_bk_option( 'booking_recurrent_time' ) ) ) )
252 - );
253 - $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' ) ) ) ),
254 374
255 - $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 + );
256 461
257 -
258 462 // -----------------------------------------------------------------------------------------------------------------
259 463 // Local parameters
260 464 // -----------------------------------------------------------------------------------------------------------------
261 - $local_params = array();
262 - $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;
263 468 $local_params['user_id'] = $re_cleaned_params['user_id']; // 1
264 - $local_params['sync_gid'] = $re_cleaned_params['sync_gid']; // ''
265 - $local_params['is_approve_booking'] = $re_cleaned_params['is_approve_booking']; // 0 | 1
266 - $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 );
267 483
268 484 // -----------------------------------------------------------------------------------------------------------------
269 485 // Parse Local parameters for later use
270 486 // -----------------------------------------------------------------------------------------------------------------
@@ -272,10 +488,91 @@
272 488 * Get parsed booking form: = [ name = "John", secondname = "Smith", email = "[email protected]", visitors = "2",... ]
273 489 */
274 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' ) );
275 491 $local_params['all_booking_data_arr'] = wpbc_get_parsed_booking_data_arr( $re_cleaned_params["form_data"], $re_cleaned_params["resource_id"] );
276 - // Important! : [ 64800, 72000 ]
277 - $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 + }
278 575 // [ "18:00:00", "20:00:00" ]
279 576 $time_as_seconds_arr = $local_params['time_as_seconds_arr'];
280 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
281 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
@@ -287,19 +584,77 @@
287 584 wpbc_transform__seconds__in__24_hours_his( $time_as_seconds_arr[0] ),
288 585 wpbc_transform__seconds__in__24_hours_his( $time_as_seconds_arr[1] )
289 586 );
290 587 // [ '2023-09-10', '2023-09-11' ]
291 - $local_params['dates_only_sql_arr'] = wpbc_convert_dates_str__dd_mm_yyyy__to__yyyy_mm_dd( $re_cleaned_params["dates_ddmmyy_csv"] );
292 - $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 + }
293 645
294 646 $local_params['is_show_payment_form'] = $re_cleaned_params["is_show_payment_form"];
295 647
296 - //FixIn: 9.9.0.35
297 - if ( $local_params['is_show_payment_form'] ) {
298 - $local_params['is_show_payment_form'] = ( false !== strpos( $re_cleaned_params['request_uri'], 'is_show_payment_form=Off' ) )
299 - ? 0
300 - : $local_params['is_show_payment_form']; // 1|0
301 - }
648 + // FixIn: 9.9.0.35.
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 + }
302 657
303 658 // Get EDIT booking data
304 659 $local_params['edit_resource_id'] = '';
305 660 $local_params['skip_booking_id'] = '';
@@ -305,27 +660,44 @@
305 660 $local_params['skip_booking_id'] = '';
306 661 $local_params['is_edit_booking'] = 0;
307 662 $local_params['is_duplicate_booking'] = 0;
308 663 $is_edit_booking = wpbc_get_data__if_edit_booking( $re_cleaned_params['booking_hash'], $re_cleaned_params['request_uri'] );
309 - if ( false !== $is_edit_booking ) {
310 - $local_params['edit_resource_id'] = $is_edit_booking['resource_id']; // can be parent booking resource, where we edit the booking
311 - $local_params['skip_booking_id'] = $is_edit_booking['booking_id']; // booking ID
312 - $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
313 668 if (
314 669 ( ! empty( $local_params['structured_booking_data_arr']['wpbc_other_action'] ) )
315 670 && ( 'duplicate_booking' === $local_params['structured_booking_data_arr']['wpbc_other_action'] )
316 671 ){
317 - $local_params['is_duplicate_booking'] = 1;
318 - }
319 - }
320 - // 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)
321 693 $local_params['initial_resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) ) ? $local_params['edit_resource_id'] : $re_cleaned_params['resource_id'];
322 694
323 - // 2
324 - $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'] );
325 -
326 -
327 - $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'] );
328 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.
329 701 $local_params['aggregate_resource_id_arr'] = array_unique( $local_params['aggregate_resource_id_arr'] ); // Erase duplicates
330 702
331 703 // -----------------------------------------------------------------------------------------------------------------
@@ -331,124 +703,111 @@
331 703 // -----------------------------------------------------------------------------------------------------------------
332 704 // Here GO
333 705 // -----------------------------------------------------------------------------------------------------------------
334 706
335 - // Force - resource saving parameters, instead of wpbc__where_to_save_booking()
336 - 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 );
337 808
338 - $local_params['how_many_items_to_book'] = 1;
339 -
340 - $dates_keys_arr = array_values( $local_params['dates_only_sql_arr'] ); // [ '2023-09-23', '2023-09-24' ]
341 -
342 - $resources_in_dates = array_fill_keys( $dates_keys_arr , array( $local_params['initial_resource_id'] ) ); // [ 2023-09-23 = [ 2 ], 2023-09-24 = [ 2 ] ]
343 -
344 - $where_to_save_booking = array();
345 - $where_to_save_booking['result'] = 'ok';
346 - $where_to_save_booking['resources_in_dates'] = $resources_in_dates; // [ 2023-09-23 = [ 2, 10, 11 ], 2023-09-24 = [ 2, 10, 11 ]
347 - $where_to_save_booking['time_to_book'] = $local_params['time_as_his_arr']; // [ "00:00:00", "24:00:00" ]
348 - $where_to_save_booking['main__resource_id'] = $local_params['initial_resource_id']; // here edit or request (parent/single) resource
349 -
350 - } else {
351 - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
352 - $php_performance = php_performance_START( 'wpbc__where_to_save_booking' , $php_performance );
353 - // </editor-fold>
354 -
355 - /**
356 - * Get slots [] where we can save booking = [ 'resources_in_dates' => [ 2023-10-18 = [ 2, 12, 10, 11 ]
357 - * 2023-10-19 = [ 2, 12, 10, 11 ]
358 - * 2023-10-20 = [ 2, 12, 10, 11 ]
359 - * ],
360 - * 'time_to_book' => [ "14:00:01" , "12:00:01" ],
361 - * 'result' => 'ok'
362 - * 'main__resource_id' => 2
363 - * ]
364 - * OR
365 - * [ 'result' => 'error', 'message' => 'Booking can not be saved ...' ]
366 - */
367 - $where_to_save_booking = wpbc__where_to_save_booking( array(
368 - 'resource_id' => $local_params['initial_resource_id'], // 2 //TODO: If edit booking. What to pass 'edit' or 'parent' resource ID?
369 - 'skip_booking_id' => $local_params['skip_booking_id'], // '', | 125 if edit booking
370 - 'dates_only_sql_arr' => $local_params['dates_only_sql_arr'], // [ "2023-10-18", "2023-10-25", "2023-11-25" ]
371 - 'time_as_seconds_arr' => $local_params['time_as_seconds_arr'], // [ 36000, 39600 ]
372 - 'how_many_items_to_book' => $local_params['how_many_items_to_book'], // 1
373 - 'request_uri' => $re_cleaned_params['request_uri'], // 'http://beta/resource-id2/'
374 - 'is_use_booking_recurrent_time' => $local_params['is_use_booking_recurrent_time'], // true | false
375 - 'as_single_resource' => false, // false
376 - 'aggregate_resource_id_arr' => $local_params['aggregate_resource_id_arr'], // Optional can be ''
377 - '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
378 - 'custom_form' => $re_cleaned_params['custom_form'] //FixIn: 10.0.0.10
379 - ));
380 - // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- NO SLOTS TO SAVE " >
381 - if ( 'error' == $where_to_save_booking['result'] ) {
382 - $ajx_data_arr['status'] = 'error';
383 - $ajx_data_arr['status_error'] = 'booking_can_not_save';
384 - $ajx_data_arr['ajx_after_action_message'] = $where_to_save_booking['message'];
385 - $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
386 - return array( 'ajx_data' => $ajx_data_arr );
387 - }
388 - // </editor-fold>
389 -
390 - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
391 - $php_performance = php_performance_END( 'wpbc__where_to_save_booking' , $php_performance );
392 - // </editor-fold>
393 - }
394 -
395 -
396 - // Get parameters, from REQUEST
397 - $create_params = $local_params;
398 - $create_params['resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) )
399 - ? $local_params['edit_resource_id'] // If we edit, then use original resource ???
400 - : $where_to_save_booking['main__resource_id']; // Here is important TIP, resource can be where is free, and not where we submit
401 - /**
402 - * 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']
403 - * but what, if $where_to_save_booking do not contain this $local_params['edit_resource_id'] as available resource.
404 - * or even we have $local_params['edit_resource_id'] = 2 and $where_to_save_booking contain resources like [ 1, 2, 3, 4 ]
405 - * we make booking for 3 slots
406 - * in this case, main resource will be 2
407 - * 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 )
408 - * "(205, '2023-10-04 00:00:00', 0, NULL)" <- main resource '2' e.g. $local_params['edit_resource_id'] = 2
409 - * "(205, '2023-10-04 00:00:00', 0, 2)" ? <- child resource '2' e.g. [ .., 2, .. ] in $where_to_save_booking WHICH IS WRONG
410 - */
411 - $create_params['is_emails_send'] = $re_cleaned_params['is_emails_send'];
412 - $create_params['custom_form'] = $re_cleaned_params['custom_form'];
413 -
414 - make_bk_action( 'check_multiuser_params_for_client_side', $create_params['resource_id'] ); // Activate working with specific user in WP MU
415 -
416 - // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
417 - $php_performance = php_performance_START( 'wpbc_db__booking_save' , $php_performance );
418 - // </editor-fold>
419 -
420 - // -----------------------------------------------------------------------------------------------------------------
421 - // == CREATE_THE 'NEW_BOOKING' ==
422 - // -----------------------------------------------------------------------------------------------------------------
423 - $create_booking_params = array(
424 - 'resource_id' => $create_params['resource_id'],
425 - 'custom_form' => $create_params['custom_form'],
426 - 'all_booking_data_arr' => $create_params['all_booking_data_arr'],
427 - 'dates_only_sql_arr' => $create_params['dates_only_sql_arr'],
428 - 'time_as_his_arr' => $create_params['time_as_his_arr'],
429 - 'is_from_admin_panel' => $create_params['is_from_admin_panel'],
430 - 'is_edit_booking' => $create_params['is_edit_booking'],
431 - 'is_duplicate_booking' => $create_params['is_duplicate_booking'],
432 - 'is_approve_booking' => $create_params['is_approve_booking'],
433 - 'how_many_items_to_book' => $create_params['how_many_items_to_book'],
434 - 'is_use_booking_recurrent_time' => $create_params['is_use_booking_recurrent_time'] // true | false
435 - );
436 - if ( ! empty( $create_params['sync_gid'] ) ) { $create_booking_params['sync_gid'] = $create_params['sync_gid']; }
437 -
438 - $booking_new_arr = wpbc_db__booking_save( $create_booking_params, $where_to_save_booking );
439 -
440 - // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING CREATION " >
441 - if ( 'ok' !== $booking_new_arr['status'] ) {
442 - $ajx_data_arr['status'] = $booking_new_arr['status'];
443 - $ajx_data_arr['status_error'] = 'booking_can_not_save';
444 - $ajx_data_arr['ajx_after_action_message'] = $booking_new_arr['message'];
445 - $ajx_data_arr['ajx_after_action_message_status'] = 'error';
446 - return array( 'ajx_data' => $ajx_data_arr );
447 - }
448 - // </editor-fold>
449 -
450 - //FixIn: 9.9.0.36
809 + // FixIn: 9.9.0.36.
451 810 if (
452 811 ( 0 !== $create_params['is_edit_booking'] ) // If edit booking
453 812 && ( 1 != $create_params['is_duplicate_booking'] ) // If not duplicate
454 813 ) {
@@ -458,9 +817,9 @@
458 817 $booking_note .= __( 'The booking has been edited', 'booking' ) . '. | Edit URL: ' . esc_url_raw( $re_cleaned_params['request_uri'] ) . '';
459 818 make_bk_action( 'wpdev_make_update_of_remark', $booking_new_arr['booking_id'], $booking_note, true );
460 819 }
461 820 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
462 - $php_performance = php_performance_END( 'wpbc_db__booking_save' , $php_performance );
821 + $php_performance = wpbc_php_performance_END( 'wpbc_db__booking_save' , $php_performance );
463 822 // </editor-fold>
464 823
465 824 // -----------------------------------------------------------------------------------------------------------------
466 825 // Get payment form(s) and Update COST of the booking
@@ -474,9 +833,10 @@
474 833 );
475 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']
476 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'
477 836 $payment_params['booking_id'] = $booking_new_arr['booking_id']; // REQUIRED -- '2'
478 - $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;
479 839 $payment_params['initial_resource_id'] = $local_params['initial_resource_id']; // REQUIRED -- '2' initial calendar - parent resource
480 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...'
481 841 $payment_params['times_array'] = array(
482 842 explode( ':', $where_to_save_booking['time_to_book'][0] ), // ["10","00","00"]
@@ -486,14 +846,15 @@
486 846 $payment_params['is_edit_booking'] = $create_params['is_edit_booking']; // => 0 0 | int - ID of the booking
487 847 $payment_params['custom_form'] = $create_params['custom_form']; // => '' '' | 'some_name'
488 848 $payment_params['is_duplicate_booking'] = $create_params['is_duplicate_booking']; // => 0 0 | 1
489 849 $payment_params['is_from_admin_panel'] = $create_params['is_from_admin_panel']; // => false true | false
490 - $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'];
491 852 if ( $payment_params['is_from_admin_panel'] ) {
492 - // $payment_params['is_show_payment_form'] = 0; //FixIn: 9.9.0.21
853 + // $payment_params['is_show_payment_form'] = 0; // FixIn: 9.9.0.21.
493 854 }
494 855 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
495 - $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 );
496 857 // </editor-fold>
497 858
498 859 // GET PAYMENT FORMS ===============================================================================================
499 860 if ( function_exists( 'wpbc_maybe_get_payment_form' ) ) {
@@ -530,11 +891,11 @@
530 891 }
531 892
532 893
533 894 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
534 - $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 );
535 896
536 - $php_performance = php_performance_START( 'emails_sending' , $php_performance );
897 + $php_performance = wpbc_php_performance_START( 'emails_sending' , $php_performance );
537 898 // </editor-fold>
538 899
539 900 // -----------------------------------------------------------------------------------------------------------------
540 901 // == Emails ===
@@ -548,9 +909,9 @@
548 909 ob_clean();
549 910
550 911 if (
551 912 ( 0 === $local_params['is_edit_booking'] )
552 - || ( 1 === $local_params['is_duplicate_booking'] ) //FixIn: 10.0.0.42
913 + || ( 1 === $local_params['is_duplicate_booking'] ) // FixIn: 10.0.0.42.
553 914 ){
554 915
555 916 // New booking to Admin
556 917 wpbc_send_email_new_admin( $payment_params['booking_id'], $payment_params['resource_id'], $email_content );
@@ -563,8 +924,10 @@
563 924 // New approved to Visitor / Admin
564 925 wpbc_send_email_approved( $payment_params['booking_id'], 1 );
565 926 }
566 927
928 + do_action( 'wpbc_booking_is_approved_during_creation' , $payment_params['booking_id'] , (int) $is_booking_approved ); // FixIn: 10.10.1.1.
929 +
567 930 // Payment request from admin panel, if needed
568 931 if(
569 932 ( $payment_params['is_from_admin_panel'] )
570 933 && ( 'On' == get_bk_option( 'booking_payment_request_auto_send_in_bap' ) )
@@ -573,10 +936,10 @@
573 936 $payment_reason = '';
574 937 $is_send = wpbc_send_email_payment_request( $payment_params['booking_id'], $payment_params['resource_id'], $email_content , $payment_reason );
575 938 }
576 939
577 - do_action( 'wpbc_booking_approved' , $payment_params['booking_id'] , (int) $is_booking_approved );
578 940
941 +
579 942 } else {
580 943
581 944 // Edited booking to Visitor / Admin
582 945 if ( function_exists( 'wpbc_send_email_modified' ) ) {
@@ -588,9 +951,9 @@
588 951 ob_end_clean();
589 952
590 953 if ( ! empty( $errors_on_email_sending_html ) ) {
591 954 // Show these messages as warning after creation of the booking
592 - $errors_on_email_sending_html = strip_tags( $errors_on_email_sending_html );
955 + $errors_on_email_sending_html = wp_strip_all_tags( $errors_on_email_sending_html );
593 956 $errors_on_email_sending_html = esc_attr( $errors_on_email_sending_html );
594 957 $errors_on_email_sending_html = str_replace( "\\n", '', $errors_on_email_sending_html );
595 958
596 959 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
@@ -598,9 +961,9 @@
598 961 }
599 962 }
600 963
601 964 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
602 - $php_performance = php_performance_END( 'emails_sending' , $php_performance );
965 + $php_performance = wpbc_php_performance_END( 'emails_sending' , $php_performance );
603 966 // </editor-fold>
604 967
605 968 // -----------------------------------------------------------------------------------------------------------------
606 969 // == Track booking - New | Edit ===
@@ -672,9 +1035,9 @@
672 1035 }
673 1036
674 1037
675 1038 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
676 - $php_performance = php_performance_START( 'confirmation' , $php_performance );
1039 + $php_performance = wpbc_php_performance_START( 'confirmation' , $php_performance );
677 1040 // </editor-fold>
678 1041
679 1042 // <editor-fold defaultstate="collapsed" desc=" == Confirmation data == " >
680 1043
@@ -705,15 +1068,15 @@
705 1068 if ( $payment_params['is_from_admin_panel'] ) {
706 1069
707 1070 $confirmation_params_arr['ty_is_redirect'] = 'message'; // Do not make redirect, if it's in admin panel!
708 1071
709 - // But if we edit / duplicate the booking, then do redirection to Booking Listing page //FixIn: 9.9.0.3
1072 + // But if we edit / duplicate the booking, then do redirection to Booking Listing page // FixIn: 9.9.0.3.
710 1073 if (
711 1074 ( 0 !== $local_params['is_edit_booking'] )
712 1075 // && ( empty( $local_params['is_duplicate_booking'] ) )
713 1076 ){
714 1077 $confirmation_params_arr['ty_is_redirect'] = 'page';
715 - $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'];
716 1079 }
717 1080 }
718 1081 $confirmation = wpbc_booking_confirmation( $confirmation_params_arr );
719 1082
@@ -719,9 +1082,9 @@
719 1082
720 1083 // </editor-fold>
721 1084
722 1085 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
723 - $php_performance = php_performance_END( 'confirmation' , $php_performance );
1086 + $php_performance = wpbc_php_performance_END( 'confirmation' , $php_performance );
724 1087 // </editor-fold>
725 1088
726 1089
727 1090 make_bk_action( 'finish_check_multiuser_params_for_client_side', $create_params['resource_id'] ); // Deactivate working with specific user in WP MU
@@ -727,9 +1090,9 @@
727 1090 make_bk_action( 'finish_check_multiuser_params_for_client_side', $create_params['resource_id'] ); // Deactivate working with specific user in WP MU
728 1091
729 1092
730 1093 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
731 - $php_performance = php_performance_END( 'total' , $php_performance );
1094 + $php_performance = wpbc_php_performance_END( 'total' , $php_performance );
732 1095 $php_performance['other_code'] = - 1 * array_reduce( $php_performance,
733 1096 function ( $sum, $item ) {
734 1097 $sum += $item;
735 1098 return $sum;
@@ -736,8 +1099,9 @@
736 1099 }
737 1100 , - 2 * $php_performance['total'] ); // PERFORMANCE OTHER - after TOTAL
738 1101 // </editor-fold>
739 1102
1103 + wpbc_clear_request_form_context();
740 1104
741 1105 return array( 'ajx_data' => $ajx_data_arr, // [ 'status' => "ok", 'wpbc_payment_output' => "<p>Dear John<br..." ]
742 1106 'booking_id' => $booking_new_arr['booking_id'], // 254
743 1107 'booking_arr' => $payment_params,
@@ -792,10 +1156,10 @@
792 1156 * 'message' => '' 'If error, then here can be description of error'
793 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,
794 1158 * ]
795 1159 */
796 -function wpbc_db__booking_save( $create_params, $where_to_save_booking ) {
797 -
1160 +function wpbc_db__booking_save( &$create_params, &$where_to_save_booking ) {
1161 + //FixIn: 10.11.5.4
798 1162 /**
799 1163 * Tip: $create_params['all_booking_data_arr'] - contain: [ 'field_name' => [ 'type' = "checkbox", 'original_name' = "fixed_fee2[]", 'name' = "fixed_fee", 'value' = "true" ] , ... ]
800 1164 * $create_params['structured_booking_data_arr'] - contain: [ 'field_name' => 'field_value' , ... ]
801 1165 */
@@ -847,10 +1211,10 @@
847 1211 ( 'On' === get_bk_option( 'booking_last_checkout_day_available' ) )
848 1212 && ( ! empty( $create_params['dates_only_sql_arr'] ) )
849 1213 && ( count( $create_params['dates_only_sql_arr'] ) > 1 )
850 1214 ) {
851 - unset( $create_params['dates_only_sql_arr'][ ( count( $create_params['dates_only_sql_arr'] ) - 1 ) ] ); // Remove LAST selected day in calendar //FixIn: 6.2.3.6
852 - // Delete last item //FixIn: 9.9.0.19
1215 + unset( $create_params['dates_only_sql_arr'][ ( count( $create_params['dates_only_sql_arr'] ) - 1 ) ] ); // Remove LAST selected day in calendar // FixIn: 6.2.3.6.
1216 + // Delete last item // FixIn: 9.9.0.19.
853 1217 $resources_in_dates_last_key = key( array_slice( $where_to_save_booking['resources_in_dates'], - 1, 1, true ) );
854 1218 unset( $where_to_save_booking['resources_in_dates'][ $resources_in_dates_last_key ] );
855 1219 }
856 1220
@@ -859,9 +1223,13 @@
859 1223 return array( 'status' => 'error', 'message' => 'Sent request with no dates.' );
860 1224 }
861 1225
862 1226 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- CHECK_IN_DATE_OLDER_THAN_CHECK_OUT " >
863 - 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 ?
864 1232
865 1233 // Is 'check in' date/time older than 'check out' date/time when SINGLE day for booking? Then show error.
866 1234
867 1235 /**
@@ -916,9 +1284,9 @@
916 1284 }
917 1285 add_filter( 'wpbc_get_booking_resources_arr_to_auto_approve', 'my_wpbc_get_booking_resources_arr_to_auto_approve' );
918 1286 */
919 1287 $booking_resources_to_approve = array();
920 - $booking_resources_to_approve = apply_filters( 'wpbc_get_booking_resources_arr_to_auto_approve', $booking_resources_to_approve ); //FixIn: 8.5.2.27
1288 + $booking_resources_to_approve = apply_filters( 'wpbc_get_booking_resources_arr_to_auto_approve', $booking_resources_to_approve ); // FixIn: 8.5.2.27.
921 1289 if ( in_array( $create_params['resource_id'], $booking_resources_to_approve ) ) {
922 1290 $is_approved_dates = 1;
923 1291 }
924 1292
@@ -924,12 +1292,21 @@
924 1292
925 1293 if (
926 1294 ( $create_params['is_from_admin_panel'] ) // true | false
927 1295 && ( get_bk_option( 'booking_auto_approve_bookings_if_added_in_admin_panel' ) == 'On' )
928 - ){ //FixIn: 8.1.3.27
1296 + ){ // FixIn: 8.1.3.27.
929 1297 $is_approved_dates = 1;
930 1298 }
931 1299
1300 + // If the booking auto-approved, then we need to mark it as "Read".
1301 + if ( $is_approved_dates ) {
1302 + $sql_field_arr[] = array(
1303 + 'name' => 'is_new',
1304 + 'type' => '%d',
1305 + 'value' => 0,
1306 + );
1307 + }
1308 +
932 1309 // <editor-fold defaultstate="collapsed" desc=" == Save Booking == " >
933 1310 // -----------------------------------------------------------------------------------------------------------------
934 1311 // Save Booking
935 1312 // -----------------------------------------------------------------------------------------------------------------
@@ -938,17 +1315,21 @@
938 1315 $sql_field_arr[] = array( 'name' => 'form', 'type' => '%s', 'value' => $form_data );
939 1316 $sql_field_arr[] = array( 'name' => 'booking_type', 'type' => '%d', 'value' => $create_params['resource_id'] );
940 1317 $sql_field_arr[] = array( 'name' => 'modification_date', 'type' => '%s', 'value' => gmdate( 'Y-m-d H:i:s' ) );
941 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] );
942 - $sql_field_arr[] = array( 'name' => 'hash', 'type' => 'MD5(%s)', 'value' => time() . '_' . rand( 1000, 1000000 ) );
1319 + $sql_field_arr[] = array( 'name' => 'hash', 'type' => '%s', 'value' => wpbc_hash__generate_booking_hash() );
943 1320
944 1321
945 1322 if (
946 - ( 0 == $create_params['is_edit_booking'] ) // If not edit, then INSERT
947 - || ( 1 == $create_params['is_duplicate_booking'] ) // If duplicate, then INSERT
948 - ){
1323 + ( 0 == $create_params['is_edit_booking'] ) || // If not edit, then INSERT.
1324 + ( 1 == $create_params['is_duplicate_booking'] ) // If duplicate, then INSERT.
1325 + ) {
949 1326 // Saved only for new booking creation.
950 - $sql_field_arr[] = array( 'name' => 'creation_date', 'type' => '%s', 'value' => gmdate( 'Y-m-d H:i:s' ) );
1327 + $sql_field_arr[] = array(
1328 + 'name' => 'creation_date',
1329 + 'type' => '%s',
1330 + 'value' => gmdate( 'Y-m-d H:i:s' ),
1331 + );
951 1332
952 1333 $sql_prepare_arr = array();
953 1334 $sql_prepare_arr['name'] = array_map( function ( $value ) { return $value['name']; }, $sql_field_arr );
954 1335 $sql_prepare_arr['type'] = array_map( function ( $value ) { return $value['type']; }, $sql_field_arr );
@@ -955,18 +1336,17 @@
955 1336 $sql_prepare_arr['value'] = array_map( function ( $value ) { return $value['value']; }, $sql_field_arr );
956 1337
957 1338 $sql_prepare_arr['name'] = implode( ', ', $sql_prepare_arr['name'] );
958 1339 $sql_prepare_arr['type'] = implode( ', ', $sql_prepare_arr['type'] );
959 -
960 - $sql = $wpdb->prepare( "INSERT INTO {$wpdb->prefix}booking "
961 - . " ( {$sql_prepare_arr['name']} )"
962 - . " VALUES ( {$sql_prepare_arr['type']} )"
963 - , $sql_prepare_arr['value']
964 - );
965 -
966 - if ( false === $wpdb->query( $sql ) ) {
967 - return array( 'status' => 'error','message' => 'Error. INSERT New Data in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $sql );
968 - }
1340 + /* phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare */
1341 + $sql = $wpdb->prepare( "INSERT INTO {$wpdb->prefix}booking " . " ( {$sql_prepare_arr['name']} )" . " VALUES ( {$sql_prepare_arr['type']} )", $sql_prepare_arr['value'] );
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 + }
969 1349 // Get ID of booking
970 1350 $booking_id = (int) $wpdb->insert_id;
971 1351
972 1352 } else { // Edit - UPDATE
@@ -978,27 +1358,30 @@
978 1358 $sql_prepare_arr['value'] = array_map( function ( $value ) { return $value['value']; }, $sql_field_arr );
979 1359
980 1360 $sql_prepare_arr['set'] = implode( ', ', $sql_prepare_arr['set'] );
981 1361
982 - $sql = $wpdb->prepare( "UPDATE {$wpdb->prefix}booking SET "
983 - . " {$sql_prepare_arr['set']} "
984 - . " WHERE booking_id={$booking_id};"
985 - , $sql_prepare_arr['value']
986 - );
987 - if ( false === $wpdb->query( $sql ) ){
988 - return array( 'status' => 'error','message' => 'Error. UPDATE Exist Data in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $sql );
989 - }
1362 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1363 + $sql = $wpdb->prepare( "UPDATE {$wpdb->prefix}booking SET {$sql_prepare_arr['set']} WHERE booking_id={$booking_id};", $sql_prepare_arr['value'] );
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 + }
990 1371
991 1372 // Check if dates previously was approved.
992 1373 $slct_sql = "SELECT approved FROM {$wpdb->prefix}bookingdates WHERE booking_id IN ({$booking_id}) LIMIT 0,1";
993 - $slct_sql_results = $wpdb->get_results( $slct_sql );
1374 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1375 + $slct_sql_results = $wpdb->get_results( $slct_sql );
994 1376 $is_approved_dates = ( count( $slct_sql_results ) > 0 ) ? $slct_sql_results[0]->approved : $is_approved_dates;
995 1377
996 1378
997 1379 $delete_sql = "DELETE FROM {$wpdb->prefix}bookingdates WHERE booking_id IN ({$booking_id})";
998 - if ( false === $wpdb->query( $delete_sql ) ){
999 - return array( 'status' => 'error','message' => 'Error. DELETE Old Dates in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $delete_sql );
1000 - }
1380 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1381 + if ( false === $wpdb->query( $delete_sql ) ) {
1382 + return array( 'status' => 'error', 'message' => 'Error. DELETE Old Dates in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $delete_sql );
1383 + }
1001 1384 }
1002 1385 // </editor-fold>
1003 1386
1004 1387
@@ -1084,11 +1467,11 @@
1084 1467 }
1085 1468 }
1086 1469
1087 1470 $dates_sql .= implode( ', ', $insert_dates_arr );
1088 -
1471 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1089 1472 if ( false === $wpdb->query( $dates_sql ) ) {
1090 - return array( 'status' => 'error','message' => 'Error. INSERT "D A T E S" in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $dates_sql );
1473 + return array( 'status' => 'error', 'message' => 'Error. INSERT "D A T E S" in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $dates_sql );
1091 1474 }
1092 1475
1093 1476 // -----------------------------------------------------------------------------------------------------------------
1094 1477 // End D A T E S
@@ -1130,9 +1513,24 @@
1130 1513
1131 1514 return $insert_dates_arr;
1132 1515 }
1133 1516
1517 +// == Help functions ==
1518 +function wpbc_set_request_form_context( $ctx ) {
1519 + $GLOBALS['wpbc_request_form_context'] = ( is_array( $ctx ) ) ? $ctx : array();
1520 +}
1134 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 +
1135 1533 // ---------------------------------------------------------------------------------------------------------------------
1136 1534 // Support
1137 1535 // ---------------------------------------------------------------------------------------------------------------------
1138 1536
@@ -1155,10 +1553,11 @@
1155 1553 $is_edit_booking = array();
1156 1554 $is_edit_booking['booking_id'] = intval( $my_booking_id_type[0] );
1157 1555 $is_edit_booking['resource_id'] = intval( $my_booking_id_type[1] );
1158 1556
1159 - //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
1160 - if ( strpos( $server_request_url, 'resource_no_update' ) === false ) { //FixIn: 9.4.2.3
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.
1558 + // FixIn: 10.10.1.2
1559 + //if ( strpos( $server_request_url, 'resource_no_update' ) === false ) { // FixIn: 9.4.2.3.
1161 1560
1162 1561 if ( ( function_exists( 'wpbc_is_this_child_resource' ) ) && ( wpbc_is_this_child_resource( $is_edit_booking['resource_id'] ) ) ) {
1163 1562 $bk_parent_br_id = wpbc_get_parent_resource( $is_edit_booking['resource_id'] );
1164 1563
@@ -1163,9 +1562,9 @@
1163 1562 $bk_parent_br_id = wpbc_get_parent_resource( $is_edit_booking['resource_id'] );
1164 1563
1165 1564 $is_edit_booking['resource_id'] = intval( $bk_parent_br_id );
1166 1565 }
1167 - }
1566 + //}
1168 1567 }
1169 1568 }
1170 1569 return $is_edit_booking;
1171 1570 }
@@ -1193,9 +1592,9 @@
1193 1592 function wpbc_get__how_many_items_to_book__in_booking_form( $booking_form_data__arr, $resource_id ){
1194 1593
1195 1594 $how_many_items_to_book = 1;
1196 1595
1197 - //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
1198 1597 if (
1199 1598 ( class_exists( 'wpdev_bk_biz_l' ) )
1200 1599 && ( 0 !== wpbc_get_child_resources_number( $resource_id ) ) // Here several child booking resources
1201 1600 ) {
@@ -1260,9 +1659,9 @@
1260 1659 *
1261 1660 * // Now get start/end times as seconds: [ 64800, 72000 ]
1262 1661 * $time_as_seconds_arr = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $structured_booking_data_arr );
1263 1662 */
1264 - 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 ){
1265 1664
1266 1665 $selected_time_fields = wpbc_get__selected_time_fields__in_booking_form__as_arr( $booking_form_data__arr );
1267 1666
1268 1667 // 2.2 Get selected SECONDS to book ---------------------------------------------------------------------------
@@ -1292,14 +1691,236 @@
1292 1691 foreach ( $selected_time_fields as $time_fields_obj ) { // { times_as_seconds: [ 21600 ], value_option_24h: '06:00', name: 'durationtime'}
1293 1692
1294 1693 if ( false !== strpos( $time_fields_obj['name'], 'durationtime' ) ) {
1295 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 + }
1296 1699 break;
1297 1700 }
1298 1701 }
1299 1702 }
1300 1703
1301 - 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 );
1302 1923 }
1303 1924
1304 1925
1305 1926 /**