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
booking / includes / _capacity / create_booking.php

create_booking.php in Booking Calendar 11.9, at includes/_capacity/create_booking.php

2,036 lines 111.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly // FixIn: 9.8.0.4.
4
5 // ---------------------------------------------------------------------------------------------------------------------
6 // == Ajax Response on creation of new booking
7 // ---------------------------------------------------------------------------------------------------------------------
8
9 /**
10 * Response to Ajax request, about loading calendar data
11 *
12 * @return void
13 */
14 function ajax_WPBC_AJX_BOOKING__CREATE() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound
15
16 /**
17 * Tip / translation /
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.
19 */
20
21 // Security ------------------------------------------------------------------------------------------------------ // in Ajax Post: 'nonce': _wpbc.get_secure_param( 'nonce' ),
22 $action_name = 'wpbc_calendar_load_ajx' . '_wpbcnonce';
23 $nonce_post_key = 'nonce';
24 if ( wpbc_is_use_nonce_at_front_end() ) { // FixIn: 10.1.1.2.
25 $result_check = check_ajax_referer( $action_name, $nonce_post_key );
26 }
27
28 // Response AJAX parameters
29 $ajx_data_arr = array();
30 $ajx_data_arr['status'] = 'ok';
31
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
35
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 ));
74
75 // Escape of request params in Ajax Post. We use prefix 'calendar_request_params', if Ajax sent - $_REQUEST['calendar_request_params']['resource_id'], ...
76 $request_prefix = 'calendar_request_params';
77
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";
79
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'] );
84
85 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- CAPTCHA " >
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 ] );
88 // </editor-fold>
89
90 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING_RESOURCE ID " >
91 if ( $request_params['resource_id'] <= 0 ) {
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
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'] . ' ]';
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 ) );
101 }
102 // </editor-fold>
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 */
105
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'];
136 $booking_save_arr = wpbc_booking_save( $request_save_params );
137
138 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING " >
139 if ( 'ok' !== $booking_save_arr['ajx_data']['status'] ) {
140
141 wp_send_json(
142 array(
143 'ajx_data' => $booking_save_arr['ajx_data'],
144 'resource_id' => $request_params['resource_id'],
145 )
146 );
147 }
148 // </editor-fold>
149
150 $ajx_data_arr = $booking_save_arr['ajx_data'];
151
152
153
154 // TODO: If we have the calendar with specific capacity, then maybe showing by dots (the booked child booking resources) the slots and not the time slot !
155
156 if ( empty( $ajx_data_arr['ajx_after_action_message_status'] ) ) {
157 $ajx_data_arr['ajx_after_action_message_status'] = 'success';
158 }
159 if ( empty( $ajx_data_arr['ajx_after_action_message'] ) ) {
160 $ajx_data_arr['ajx_after_action_message'] = '';
161 }
162
163 // $ajx_data_arr['ajx_after_action_message'] .= __( 'Booking was created with ID: ' . $booking_save_arr[ 'booking_id' ] , 'booking' );
164 // $ajx_data_arr['ajx_after_action_message'] .= '<hr>Total time: <strong>' . $booking_save_arr['php_performance']['total'] . ' s. </strong>';
165 // $ajx_data_arr['ajx_after_action_message'] .= str_replace( array( ',', '{', '}' ), '<br>', wp_json_encode( $booking_save_arr['php_performance'] ) );
166 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
167
168
169
170 /* if admin edit ?
171 var my_message = '<?php echo esc_js( __('Updated successfully' ,'booking') ) ; ?>';
172 wpbc_admin_show_message( my_message, 'success', 3000 );
173 location.href='<?php echo wpbc_get_bookings_url() ;?>&tab=vm_booking_listing&wh_booking_id=<?php echo $is_edit_booking['booking_id'] ; ?>';
174 */
175
176
177 // -----------------------------------------------------------------------------------------------------------------
178 // == Ajax ===
179 // -----------------------------------------------------------------------------------------------------------------
180 /**
181 * Send JSON. It will make "wp_json_encode" - so pass only array, and This function call wp_die( '', '', array( 'response' => null, ) ) .
182 * Pass JS OBJ: response_data in "jQuery.post " function on success.
183 *
184 * Other End Ajax actions:
185 * $error_obj = new WP_Error( 'WPBC_CREATE', __( 'test error.' ), 'some/data' ); wp_send_json_error( $error_obj );
186 * wp_send_json_error( array( 'message' => 'invalid-api-key' ) );
187 * wp_send_json_success( array( 'message' =>'Ok:)' ) );
188 */
189 wp_send_json( array(
190 'booking_id' => $booking_save_arr['booking_id'],
191 'resource_id' => $request_params['resource_id'],
192 'ajx_data' => $ajx_data_arr,
193 'ajx_confirmation' => $booking_save_arr['confirmation'],
194
195 // For debug purpose <- comment in live serv
196 'php_process_times' => $booking_save_arr['php_performance'],
197 'booking_arr' => $booking_save_arr ['booking_arr'],
198
199 // Not needed ?
200 // 'ajx_search_params' => $_REQUEST[ $request_prefix ],
201 // 'ajx_cleaned_params' => $request_params,
202
203 ) );
204 }
205
206 // Ajax Hooks
207 if ( is_admin() && ( defined( 'DOING_AJAX' ) ) && ( DOING_AJAX ) ) {
208 add_action( 'wp_ajax_nopriv_' . 'WPBC_AJX_BOOKING__CREATE', 'ajax_' . 'WPBC_AJX_BOOKING__CREATE' ); // Client (not logged in)
209 add_action( 'wp_ajax_' . 'WPBC_AJX_BOOKING__CREATE', 'ajax_' . 'WPBC_AJX_BOOKING__CREATE' ); // Logged In users (or admin panel)
210 }
211
212
213 // ---------------------------------------------------------------------------------------------------------------------
214 // == Save Booking
215 // ---------------------------------------------------------------------------------------------------------------------
216
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
303 *
304 * @param $request_params = [
305 * resource_id = 2 REQUIRED Default: 1
306 * dates_ddmmyy_csv = '27.10.2023, 28.10.2023, 29.10.2023' REQUIRED
307 * form_data = 'text^selected_short_dates_hint2^Fri, ...9, 2023~text^...' REQUIRED
308 * booking_hash = '' Optional Default: ''
309 * custom_form = '' Optional Default: ''
310 * is_emails_send = 1 Optional Default: 1
311 * is_show_payment_form => 1 Optional Default: 1
312 * user_id = 1 Optional Default: 0 or ID of logged-in user
313 * request_uri = 'http://beta/resource-id2/', // Optional Default: for front-end: $_SERVER['REQUEST_URI'] | ajax: $_SERVER['HTTP_REFERER']
314 *
315 * 'sync_gid' => 'ghjgjgjgh5f5f45f' // Really Optional: can be passed only during import .ics
316 * 'is_approve_booking' => 0 // Really Optional: 0 | 1
317 * 'save_booking_even_if_unavailable' => 0 // Really Optional: 0 | 1, if 1 then force save booking even if dates unavailable.
318 * ]
319 *
320 * @return [] ok: [
321 *
322 * ]
323 * error: [
324 * 'ajx_data': [ 'status':'error', 'status_error':'booking_can_not_save', 'ajx_after_action_message': 'Can not save booking', 'ajx_after_action_message_status': 'warning' ]
325 * ]
326 *
327 * Example:
328 *
329 * wpbc_booking_save( array(
330 * 'resource_id' => 2,
331 * 'dates_ddmmyy_csv' => '04.10.2023, 05.10.2023, 06.10.2023',
332 * 'form_data' => 'text^cost_hint2^150.00฿~selectbox-multiple^rangetime2[]^14:00 - 16:00~text^name2^John~text^secondname2^Smith~email^email2^[email protected]~selectbox-one^visitors2^2~selectbox-one^children2^0~textarea^details2^test',
333 * 'booking_hash' => '',
334 * 'custom_form' => '',
335 * 'is_emails_send' => 1,
336 * 'is_show_payment_form' => 1,
337 * 'user_id' => 1,
338 * 'request_uri' => 'http://beta/resource-id2/'
339 * ) );
340 *
341 */
342 function wpbc_booking_save( $request_params ){
343 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
344 $php_performance = wpbc_php_performance_START( 'total', array() );
345 // </editor-fold>
346 $ajx_data_arr = array();
347 $ajx_data_arr['status'] = 'ok';
348
349 // -----------------------------------------------------------------------------------------------------------------
350 // 1. Direct Clean Params
351 // -----------------------------------------------------------------------------------------------------------------
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.
357 'form_data' => array( 'validate' => 'strong', 'default' => '' ),
358 'booking_hash' => array( 'validate' => 'strong', 'default' => '' ),
359 'custom_form' => array( 'validate' => 'strong', 'default' => '' ),
360 'is_emails_send' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1
361 'is_show_payment_form' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1
362 'user_id' => array( 'validate' => 'd', 'default' => wpbc_get_current_user_id() ), // INT
363 'allow_past' => array( 'validate' => 'd', 'default' => 0 ),
364 'classic_booking_context_token' => array( 'validate' => 'strong', 'default' => '' ),
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
366 // Really Optional:
367 'aggregate_resource_id_arr' => array( 'validate' => 'digit_or_csd', 'default' => '' ),
368 //TODO: this parameter does not transfer during saving, so here will be always default value 'bookings_only' // FixIn: 10.0.0.7.
369 'aggregate_type' => array( 'validate' => 'strong', 'default' => 'bookings_only' ), // Optional. 'all' | 'bookings_only' <- it is depends on shortcode parameter: options="{aggregate type=bookings_only}"
370 'is_approve_booking' => array( 'validate' => 'd', 'default' => 0 ), // 0 | 1
371 'save_booking_even_if_unavailable' => array( 'validate' => 'd', 'default' => 0 ), // 0 | 1
372 'sync_gid' => array( 'validate' => 'strong', 'default' => '' ),
373 'is_use_booking_recurrent_time' => array( 'validate' => 'd', 'default' => intval( ( 'On' === get_bk_option( 'booking_recurrent_time' ) ) ) ),
374
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 );
461
462 // -----------------------------------------------------------------------------------------------------------------
463 // Local parameters
464 // -----------------------------------------------------------------------------------------------------------------
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;
468 $local_params['user_id'] = $re_cleaned_params['user_id']; // 1
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 );
483
484 // -----------------------------------------------------------------------------------------------------------------
485 // Parse Local parameters for later use
486 // -----------------------------------------------------------------------------------------------------------------
487 /**
488 * Get parsed booking form: = [ name = "John", secondname = "Smith", email = "[email protected]", visitors = "2",... ]
489 */
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' ) );
491 $local_params['all_booking_data_arr'] = wpbc_get_parsed_booking_data_arr( $re_cleaned_params["form_data"], $re_cleaned_params["resource_id"] );
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 }
575 // [ "18:00:00", "20:00:00" ]
576 $time_as_seconds_arr = $local_params['time_as_seconds_arr'];
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
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
579 if ( ( 0 != $time_as_seconds_arr[0] ) && ( ( 24 * 60 * 60 ) == $time_as_seconds_arr[1] ) ) {
580 //FixIn: 10.0.0.49 - in case if we have start time != 00:00 and end time as 24:00 then set end time as 23:59:52
581 $time_as_seconds_arr[1] += - 8;
582 }
583 $local_params['time_as_his_arr'] = array(
584 wpbc_transform__seconds__in__24_hours_his( $time_as_seconds_arr[0] ),
585 wpbc_transform__seconds__in__24_hours_his( $time_as_seconds_arr[1] )
586 );
587 // [ '2023-09-10', '2023-09-11' ]
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 }
645
646 $local_params['is_show_payment_form'] = $re_cleaned_params["is_show_payment_form"];
647
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 }
657
658 // Get EDIT booking data
659 $local_params['edit_resource_id'] = '';
660 $local_params['skip_booking_id'] = '';
661 $local_params['is_edit_booking'] = 0;
662 $local_params['is_duplicate_booking'] = 0;
663 $is_edit_booking = wpbc_get_data__if_edit_booking( $re_cleaned_params['booking_hash'], $re_cleaned_params['request_uri'] );
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
668 if (
669 ( ! empty( $local_params['structured_booking_data_arr']['wpbc_other_action'] ) )
670 && ( 'duplicate_booking' === $local_params['structured_booking_data_arr']['wpbc_other_action'] )
671 ){
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)
693 $local_params['initial_resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) ) ? $local_params['edit_resource_id'] : $re_cleaned_params['resource_id'];
694
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'] );
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.
701 $local_params['aggregate_resource_id_arr'] = array_unique( $local_params['aggregate_resource_id_arr'] ); // Erase duplicates
702
703 // -----------------------------------------------------------------------------------------------------------------
704 // Here GO
705 // -----------------------------------------------------------------------------------------------------------------
706
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 );
808
809 // FixIn: 9.9.0.36.
810 if (
811 ( 0 !== $create_params['is_edit_booking'] ) // If edit booking
812 && ( 1 != $create_params['is_duplicate_booking'] ) // If not duplicate
813 ) {
814 // Log the cost info.
815 $is_add_timezone_offset = true;
816 $booking_note = wpbc_date_localized( gmdate( 'Y-m-d H:i:s' ), '[Y-m-d H:i]', $is_add_timezone_offset ) . ' ';
817 $booking_note .= __( 'The booking has been edited', 'booking' ) . '. | Edit URL: ' . esc_url_raw( $re_cleaned_params['request_uri'] ) . '';
818 make_bk_action( 'wpdev_make_update_of_remark', $booking_new_arr['booking_id'], $booking_note, true );
819 }
820 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
821 $php_performance = wpbc_php_performance_END( 'wpbc_db__booking_save' , $php_performance );
822 // </editor-fold>
823
824 // -----------------------------------------------------------------------------------------------------------------
825 // Get payment form(s) and Update COST of the booking
826 // -----------------------------------------------------------------------------------------------------------------
827 $payment_params = array();
828
829 // Usually we have this: ( $str_dates__dd_mm_yyyy == $create_params['dates_only_sql_arr'] ) - but for ensure, use saved dates, e.g. $str_dates__dd_mm_yyyy
830 $payment_params['booked_dates_times_arr'] = array(
831 'dates_ymd_arr' => array_keys( $where_to_save_booking['resources_in_dates'] ), // [ 2023-10-20=>[], 2023-10-25=>[] ] -> [ "2023-10-20", "2023-10-25" ]
832 'times_his_arr' => $where_to_save_booking['time_to_book'] // ['16:00:01', '18:00:02']
833 );
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']
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'
836 $payment_params['booking_id'] = $booking_new_arr['booking_id']; // REQUIRED -- '2'
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;
839 $payment_params['initial_resource_id'] = $local_params['initial_resource_id']; // REQUIRED -- '2' initial calendar - parent resource
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...'
841 $payment_params['times_array'] = array(
842 explode( ':', $where_to_save_booking['time_to_book'][0] ), // ["10","00","00"]
843 explode( ':', $where_to_save_booking['time_to_book'][1] ) // ["12","00","00"]
844 );
845 // Additional options
846 $payment_params['is_edit_booking'] = $create_params['is_edit_booking']; // => 0 0 | int - ID of the booking
847 $payment_params['custom_form'] = $create_params['custom_form']; // => '' '' | 'some_name'
848 $payment_params['is_duplicate_booking'] = $create_params['is_duplicate_booking']; // => 0 0 | 1
849 $payment_params['is_from_admin_panel'] = $create_params['is_from_admin_panel']; // => false true | false
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'];
852 if ( $payment_params['is_from_admin_panel'] ) {
853 // $payment_params['is_show_payment_form'] = 0; // FixIn: 9.9.0.21.
854 }
855 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
856 $php_performance = wpbc_php_performance_START( 'wpbc_maybe_get_payment_form' , $php_performance );
857 // </editor-fold>
858
859 // GET PAYMENT FORMS ===============================================================================================
860 if ( function_exists( 'wpbc_maybe_get_payment_form' ) ) {
861
862 $response__payment_form__arr = wpbc_maybe_get_payment_form( $payment_params );
863
864 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- COSTS || PAYMENT_SYSTEMS " >
865 if (
866 ( ! empty( $response__payment_form__arr['status'] ) )
867 && ( 'ok' != $response__payment_form__arr['status'] )
868 ) {
869 $ajx_data_arr['status'] = $response__payment_form__arr['status'];
870 $ajx_data_arr['status_error'] = 'booking_can_not_save';
871 $ajx_data_arr['ajx_after_action_message'] = $response__payment_form__arr['message'];
872 $ajx_data_arr['ajx_after_action_message_status'] = 'error';
873 return array( 'ajx_data' => $ajx_data_arr );
874 }
875 // </editor-fold>
876
877 if ( ! empty( $response__payment_form__arr['costs_arr']['form_data'] ) ) {
878 $payment_params['form_data'] = $response__payment_form__arr['costs_arr']['form_data']; // we re-save it, because here can be [cost_correction] shortcode data
879 }
880 //TODO: Do we really need this in output $ajx_data_arr ???, because it stored in $confirmation
881 if ( ! empty( $response__payment_form__arr['gateways_output_arr'] ) ) {
882 $ajx_data_arr['gateways_output_arr'] = $response__payment_form__arr['gateways_output_arr'];
883 }
884 if ( function_exists( 'wpbc_if_zero_cost__approve_booking_dates' ) ) {
885 wpbc_if_zero_cost__approve_booking_dates( $payment_params['booking_id'] );
886 }
887
888 } else {
889 $response__payment_form__arr = $payment_params;
890 $response__payment_form__arr['status'] = 'ok';
891 }
892
893
894 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
895 $php_performance = wpbc_php_performance_END( 'wpbc_maybe_get_payment_form' , $php_performance );
896
897 $php_performance = wpbc_php_performance_START( 'emails_sending' , $php_performance );
898 // </editor-fold>
899
900 // -----------------------------------------------------------------------------------------------------------------
901 // == Emails ===
902 // -----------------------------------------------------------------------------------------------------------------
903 if ( 1 == $re_cleaned_params['is_emails_send'] ) {
904
905 $email_content = $payment_params['form_data'];
906
907 // If we output any text, then probably it's errors or warnings, we need to catch them
908 ob_start();
909 ob_clean();
910
911 if (
912 ( 0 === $local_params['is_edit_booking'] )
913 || ( 1 === $local_params['is_duplicate_booking'] ) // FixIn: 10.0.0.42.
914 ){
915
916 // New booking to Admin
917 wpbc_send_email_new_admin( $payment_params['booking_id'], $payment_params['resource_id'], $email_content );
918
919 // New pending to Visitor
920 wpbc_send_email_new_visitor( $payment_params['booking_id'], $payment_params['resource_id'], $email_content ) ;
921
922 $is_booking_approved = wpbc_is_booking_approved( $payment_params['booking_id'] );
923 if ( $is_booking_approved ) {
924 // New approved to Visitor / Admin
925 wpbc_send_email_approved( $payment_params['booking_id'], 1 );
926 }
927
928 do_action( 'wpbc_booking_is_approved_during_creation' , $payment_params['booking_id'] , (int) $is_booking_approved ); // FixIn: 10.10.1.1.
929
930 // Payment request from admin panel, if needed
931 if(
932 ( $payment_params['is_from_admin_panel'] )
933 && ( 'On' == get_bk_option( 'booking_payment_request_auto_send_in_bap' ) )
934 && ( function_exists( 'wpbc_send_email_payment_request' ) )
935 ){
936 $payment_reason = '';
937 $is_send = wpbc_send_email_payment_request( $payment_params['booking_id'], $payment_params['resource_id'], $email_content , $payment_reason );
938 }
939
940
941
942 } else {
943
944 // Edited booking to Visitor / Admin
945 if ( function_exists( 'wpbc_send_email_modified' ) ) {
946 wpbc_send_email_modified( $payment_params['booking_id'], $payment_params['resource_id'], $email_content );
947 }
948 }
949
950 $errors_on_email_sending_html = ob_get_contents();
951 ob_end_clean();
952
953 if ( ! empty( $errors_on_email_sending_html ) ) {
954 // Show these messages as warning after creation of the booking
955 $errors_on_email_sending_html = wp_strip_all_tags( $errors_on_email_sending_html );
956 $errors_on_email_sending_html = esc_attr( $errors_on_email_sending_html );
957 $errors_on_email_sending_html = str_replace( "\\n", '', $errors_on_email_sending_html );
958
959 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
960 $ajx_data_arr['ajx_after_action_message'] = $errors_on_email_sending_html;
961 }
962 }
963
964 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
965 $php_performance = wpbc_php_performance_END( 'emails_sending' , $php_performance );
966 // </editor-fold>
967
968 // -----------------------------------------------------------------------------------------------------------------
969 // == Track booking - New | Edit ===
970 // -----------------------------------------------------------------------------------------------------------------
971 /**
972 * Useful hook for Google Ads Conversion tracking. How to use this hook?
973 *
974 * Add code similar to this in your functions.php file in your theme, or in some other php file:
975 *
976 // Track adding new booking
977 //
978 // @param $params = array (
979 // 'str_dates__dd_mm_yyyy' => '08.10.2023,09.10.2023,10.10.2023,11.10.2023',
980 // 'booking_id' => 254,
981 // 'resource_id' => 11, // child or parent or single
982 // 'initial_resource_id' => 2, // parent or single
983 // 'form_data' => 'text^selected_short_dates_hint11^Sun...',
984 // 'times_array' => array ( array ( '14', '00', '01' ), array( '12', '00', '02' ) ),
985 // 'is_edit_booking' => 0,
986 // 'custom_form' => '',
987 // 'is_duplicate_booking' => 0,
988 // 'is_from_admin_panel' => false,
989 // 'is_show_payment_form' => 1
990 // )
991 function my_booking_tracking( $params ){
992 // Your code here
993 ?><!-- Google Code for Booking Conversion Page -->
994 <script type="text/javascript">
995 // Insert bellow your Google Conversion Code
996 </script><?php
997 }
998 add_action( 'wpbc_track_new_booking', 'my_booking_tracking' );
999 *
1000 *
1001 *
1002 * Useful hook booking edit tracking
1003 *
1004 * Add code similar to this in your functions.php file in your theme, or in some other php file:
1005 *
1006 // Track edit existing booking
1007 //
1008 // @param $params = array (
1009 // 'str_dates__dd_mm_yyyy' => '08.10.2023,09.10.2023,10.10.2023,11.10.2023',
1010 // 'booking_id' => 254,
1011 // 'resource_id' => 11, // child or parent or single
1012 // 'initial_resource_id' => 2, // parent or single
1013 // 'form_data' => 'text^selected_short_dates_hint11^Sun...',
1014 // 'times_array' => array ( array ( '14', '00', '01' ), array( '12', '00', '02' ) ),
1015 // 'is_edit_booking' => 1,
1016 // 'custom_form' => '',
1017 // 'is_duplicate_booking' => 0,
1018 // 'is_from_admin_panel' => false,
1019 // 'is_show_payment_form' => 1
1020 // )
1021 function my_edit_booking_tracking( $params ){
1022 // Your code here
1023 ?><!-- Google Code for Booking Conversion Page -->
1024 <script type="text/javascript">
1025 // Insert bellow your Google Conversion Code
1026 </script><?php
1027
1028 }
1029 add_action( 'wpbc_track_edit_booking', 'my_edit_booking_tracking' );
1030 */
1031 if ( 0 === $local_params['is_edit_booking'] ) {
1032 do_action( 'wpbc_track_new_booking', $payment_params );
1033 } else {
1034 do_action( 'wpbc_track_edit_booking', $payment_params );
1035 }
1036
1037
1038 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
1039 $php_performance = wpbc_php_performance_START( 'confirmation' , $php_performance );
1040 // </editor-fold>
1041
1042 // <editor-fold defaultstate="collapsed" desc=" == Confirmation data == " >
1043
1044
1045 $confirmation_params_arr = array(
1046 'is_from_admin_panel' => $payment_params['is_from_admin_panel'],
1047
1048 'booking_id' => $booking_new_arr['booking_id'], // 16102023
1049 'resource_id' => $payment_params['resource_id'], // 1
1050 'form_data' => $payment_params['form_data'], // 'text^selected_short_dates_hint11^Sun...',
1051 'dates_ymd_arr' => $payment_params['booked_dates_times_arr']['dates_ymd_arr'], // [ '2023-10-20', '2023-10-25' ]
1052 'times_his_arr' => $payment_params['booked_dates_times_arr']['times_his_arr'], // [ '16:00:01', '18:00:02' ]
1053
1054 'total_cost' => ( isset( $response__payment_form__arr['costs_arr'] ) && isset( $response__payment_form__arr['costs_arr']['total_cost'] ) )
1055 ? $response__payment_form__arr['costs_arr']['total_cost']
1056 : 0,
1057 'deposit_cost' => ( isset( $response__payment_form__arr['costs_arr'] ) && isset( $response__payment_form__arr['costs_arr']['deposit_cost'] ) )
1058 ? $response__payment_form__arr['costs_arr']['deposit_cost']
1059 : 0,
1060 'booking_summary' => ( ( ! empty( $response__payment_form__arr['gateways_output_arr'] ) ) && ( ! empty( $response__payment_form__arr['gateways_output_arr']['booking_summary'] ) ) )
1061 ? $response__payment_form__arr['gateways_output_arr']['booking_summary']
1062 : '',
1063 'gateway_rows' => ( ( ! empty( $response__payment_form__arr['gateways_output_arr'] ) ) && ( ! empty( $response__payment_form__arr['gateways_output_arr']['gateway_rows'] ) ) )
1064 ? $response__payment_form__arr['gateways_output_arr']['gateway_rows']
1065 : ''
1066 );
1067 // It will not show payment form in Booking > Add booking page and if defined, do not make redirect
1068 if ( $payment_params['is_from_admin_panel'] ) {
1069
1070 $confirmation_params_arr['ty_is_redirect'] = 'message'; // Do not make redirect, if it's in admin panel!
1071
1072 // But if we edit / duplicate the booking, then do redirection to Booking Listing page // FixIn: 9.9.0.3.
1073 if (
1074 ( 0 !== $local_params['is_edit_booking'] )
1075 // && ( empty( $local_params['is_duplicate_booking'] ) )
1076 ){
1077 $confirmation_params_arr['ty_is_redirect'] = 'page';
1078 $confirmation_params_arr['ty_url'] = wpbc_get_bookings_url() . '&tab=vm_booking_listing&wh_booking_id=' . $confirmation_params_arr['booking_id'];
1079 }
1080 }
1081 $confirmation = wpbc_booking_confirmation( $confirmation_params_arr );
1082
1083 // </editor-fold>
1084
1085 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
1086 $php_performance = wpbc_php_performance_END( 'confirmation' , $php_performance );
1087 // </editor-fold>
1088
1089
1090 make_bk_action( 'finish_check_multiuser_params_for_client_side', $create_params['resource_id'] ); // Deactivate working with specific user in WP MU
1091
1092
1093 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
1094 $php_performance = wpbc_php_performance_END( 'total' , $php_performance );
1095 $php_performance['other_code'] = - 1 * array_reduce( $php_performance,
1096 function ( $sum, $item ) {
1097 $sum += $item;
1098 return $sum;
1099 }
1100 , - 2 * $php_performance['total'] ); // PERFORMANCE OTHER - after TOTAL
1101 // </editor-fold>
1102
1103 wpbc_clear_request_form_context();
1104
1105 return array( 'ajx_data' => $ajx_data_arr, // [ 'status' => "ok", 'wpbc_payment_output' => "<p>Dear John<br..." ]
1106 'booking_id' => $booking_new_arr['booking_id'], // 254
1107 'booking_arr' => $payment_params,
1108 'php_performance' => $php_performance, // [ ... ]
1109 'confirmation' => $confirmation // [ ]
1110 );
1111 }
1112
1113
1114 // ---------------------------------------------------------------------------------------------------------------------
1115 // New booking creation sub functions
1116 // ---------------------------------------------------------------------------------------------------------------------
1117
1118 /**
1119 * Save booking in DB
1120 *
1121 * @param array $create_params = [
1122 * 'resource_id' => 10,
1123 * 'dates_only_sql_arr' => [ '2023-10-04', '2023-10-05', '2023-10-06'],
1124 * 'time_as_his_arr' => [ '14:00:01', '16:00:02' ],
1125 * 'is_from_admin_panel' => false,
1126 * 'is_edit_booking' => 0,
1127 * 'is_duplicate_booking' => 0,
1128 * 'is_approve_booking' => 0,
1129 * 'how_many_items_to_book' => 2,
1130 * 'custom_form' => '',
1131 * 'is_use_booking_recurrent_time' => false,
1132 * 'all_booking_data_arr' => [ 'rangetime' => [
1133 * 'type' => 'selectbox-multiple',
1134 * 'original_name' => 'rangetime2[]', //NOTE: here RESOURCE ID (2) can be from Parent resource, but resource_id can rely on child (10) resource
1135 * 'name' => 'rangetime',
1136 * 'value' => '14:00 - 16:00',
1137 * ],
1138 * 'name' => [ 'type' => 'text', 'original_name' => 'name2', ... ],
1139 * ...
1140 * ]
1141 * ]
1142 *
1143 * @param $where_to_save_booking = [
1144 * 'result' = 'ok',
1145 * 'main__resource_id' = 2
1146 * 'resources_in_dates' = [ 2023-10-18 = [ 2, 12, 10, 11 ]
1147 * 2023-10-19 = [ 2, 12, 10, 11 ]
1148 * 2023-10-20 = [ 2, 12, 10, 11 ]
1149 * ],
1150 * 'time_to_book' = [ "14:00:01" , "16:00:02" ]
1151 * ]
1152 *
1153 * @return [
1154 * 'status' => 'ok' 'ok' | 'error' | 'warning'
1155 * 'booking_id' => 100 int
1156 * 'message' => '' 'If error, then here can be description of error'
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,
1158 * ]
1159 */
1160 function wpbc_db__booking_save( &$create_params, &$where_to_save_booking ) {
1161 //FixIn: 10.11.5.4
1162 /**
1163 * Tip: $create_params['all_booking_data_arr'] - contain: [ 'field_name' => [ 'type' = "checkbox", 'original_name' = "fixed_fee2[]", 'name' = "fixed_fee", 'value' = "true" ] , ... ]
1164 * $create_params['structured_booking_data_arr'] - contain: [ 'field_name' => 'field_value' , ... ]
1165 */
1166
1167 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- 'Wrong resource ID " >
1168 if ( empty( $create_params['resource_id'] ) ) {
1169 return array( 'status' => 'error', 'message' => 'Wrong ID of booking resource: ' . $create_params['resource_id'] );
1170 }
1171 // </editor-fold>
1172
1173
1174 $defaults = array(
1175 'is_use_booking_recurrent_time' => ( 'On' === get_bk_option( 'booking_recurrent_time' ) )
1176 );
1177 $create_params = wp_parse_args( $create_params, $defaults );
1178
1179
1180 $sql_field_arr = array();
1181
1182
1183
1184 // Is it was used custom booking form ?
1185 if ( ! empty( $create_params['custom_form'] ) ) {
1186 $create_params['all_booking_data_arr']['wpbc_custom_booking_form'] = array(
1187 'type' => 'text',
1188 'original_name' => 'wpbc_custom_booking_form' . $create_params['resource_id'],
1189 'name' => 'wpbc_custom_booking_form',
1190 'value' => $create_params['custom_form']
1191 );
1192 }
1193
1194 if ( ! empty( $create_params['sync_gid'] ) ) {
1195 /**
1196 * Such fields are comming from '../wp-content/plugins/booking/core/sync/wpbc-gcal-class.php' in function run()
1197 */
1198
1199 // Escape any XSS injection from values in booking form
1200 list( $create_params['sync_gid'] ) = wpbc_escape_any_xss_in_arr( array( $create_params['sync_gid'] ) );
1201
1202 $sql_field_arr[] = array( 'name' => 'sync_gid', 'type' => '%s', 'value' => $create_params['sync_gid'] );
1203 }
1204
1205 // Escape any XSS injection from values in booking form
1206 $create_params['all_booking_data_arr'] = wpbc_escape_any_xss_in_arr( $create_params['all_booking_data_arr'] );
1207
1208
1209 //Set LAST check out day as AVAILABLE - remove it
1210 if (
1211 ( 'On' === get_bk_option( 'booking_last_checkout_day_available' ) )
1212 && ( ! empty( $create_params['dates_only_sql_arr'] ) )
1213 && ( count( $create_params['dates_only_sql_arr'] ) > 1 )
1214 ) {
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.
1217 $resources_in_dates_last_key = key( array_slice( $where_to_save_booking['resources_in_dates'], - 1, 1, true ) );
1218 unset( $where_to_save_booking['resources_in_dates'][ $resources_in_dates_last_key ] );
1219 }
1220
1221 // :: ERROR ::
1222 if ( empty( $create_params['dates_only_sql_arr'] ) ) { // No dates :?
1223 return array( 'status' => 'error', 'message' => 'Sent request with no dates.' );
1224 }
1225
1226 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- CHECK_IN_DATE_OLDER_THAN_CHECK_OUT " >
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 ?
1232
1233 // Is 'check in' date/time older than 'check out' date/time when SINGLE day for booking? Then show error.
1234
1235 /**
1236 * If we are having "change over" days activated and selected only 1 day in calendar,
1237 * then we can have error: "Warning! Number of check in != check out times.", because "check in" day older than "check out" date.
1238 */
1239
1240 $is_apply__check_in_out__10s = false;
1241 $datestamp_check_in = wpbc_convert__sql_date__to_seconds( $create_params['dates_only_sql_arr'][0] . ' ' . $create_params['time_as_his_arr'][0], $is_apply__check_in_out__10s );
1242 $datestamp_check_out = wpbc_convert__sql_date__to_seconds( $create_params['dates_only_sql_arr'][0] . ' ' . $create_params['time_as_his_arr'][1], $is_apply__check_in_out__10s );
1243
1244
1245 if ( $datestamp_check_in > $datestamp_check_out ) {
1246 $error_message = sprintf( 'Error! Your check in date %s older than check out date %s. <br>Try to select more dates or use different time.'
1247 , '<strong>' . wpbc_convert__seconds__to_sql_date( $datestamp_check_in, $is_apply__check_in_out__10s ) . '</strong>'
1248 , '<strong>' . wpbc_convert__seconds__to_sql_date( $datestamp_check_out, $is_apply__check_in_out__10s ) . '</strong>'
1249 );
1250 $error_message .= '<br>' . '<strong>Settings that can be reason of the issue:</strong> ';
1251 if( 'On' === get_bk_option( 'booking_last_checkout_day_available' )){
1252 $error_message .= '<br>' . 'You have enabled <strong>"Set check out date as available"</strong> option at Booking > Settings General page in "Calendar" section. ';
1253 }
1254 if ( 'On' === get_bk_option( 'booking_range_selection_time_is_active' ) ){
1255 $error_message .= '<br>' . 'You have enabled <strong>"Use changeover days"</strong> option at Booking > Settings General page in "Calendar" section with check-in/out times: ' . get_bk_option( 'booking_range_selection_start_time' ) . '/' . get_bk_option( 'booking_range_selection_end_time' );
1256 }
1257 if ( $create_params['is_use_booking_recurrent_time'] ) {
1258 $error_message .= '<br>' . 'You have enabled <strong>"Use time selections as recurrent time slots"</strong> option at Booking > Settings General page in "Calendar" section. ';
1259 }
1260 return array( 'status' => 'error', 'message' => $error_message );
1261 }
1262 }
1263 // </editor-fold>
1264
1265 // Compose form data for DB. Can be different resource: 'selectbox-multiple^rangetime10[]^14..' <-> 'selectbox-multiple^rangetime2[]^14..' -> 'rangetime10' - child res. Previously 'rangetime2' - parent
1266 $form_data = wpbc_encode_booking_data_to_string( $create_params['all_booking_data_arr'], $create_params['resource_id'] );
1267
1268
1269 // -----------------------------------------------------------------------------------------------------------------
1270 // APPROVED / PENDING
1271 // -----------------------------------------------------------------------------------------------------------------
1272 // Is approve booking
1273 $auto_approve_new_bookings_is_active = trim( get_bk_option( 'booking_auto_approve_new_bookings_is_active' ) );
1274 $is_approved_dates = ( $auto_approve_new_bookings_is_active == 'On' ) ? 1 : intval( $create_params['is_approve_booking'] );
1275
1276 // Auto approve only for specific booking resources
1277 /**
1278 * How to use "Auto approve bookings only for specific booking resources" ?
1279 * Add code similar to this in your functions.php file in your theme, or in some other php file:
1280 *
1281 function my_wpbc_get_booking_resources_arr_to_auto_approve( $resources_to_approve ) {
1282 $resources_to_approve = array( 9, 12, 33 ); // Array of booking resources ID, which you need to auto approve
1283 return $resources_to_approve;
1284 }
1285 add_filter( 'wpbc_get_booking_resources_arr_to_auto_approve', 'my_wpbc_get_booking_resources_arr_to_auto_approve' );
1286 */
1287 $booking_resources_to_approve = array();
1288 $booking_resources_to_approve = apply_filters( 'wpbc_get_booking_resources_arr_to_auto_approve', $booking_resources_to_approve ); // FixIn: 8.5.2.27.
1289 if ( in_array( $create_params['resource_id'], $booking_resources_to_approve ) ) {
1290 $is_approved_dates = 1;
1291 }
1292
1293 if (
1294 ( $create_params['is_from_admin_panel'] ) // true | false
1295 && ( get_bk_option( 'booking_auto_approve_bookings_if_added_in_admin_panel' ) == 'On' )
1296 ){ // FixIn: 8.1.3.27.
1297 $is_approved_dates = 1;
1298 }
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
1309 // <editor-fold defaultstate="collapsed" desc=" == Save Booking == " >
1310 // -----------------------------------------------------------------------------------------------------------------
1311 // Save Booking
1312 // -----------------------------------------------------------------------------------------------------------------
1313 global $wpdb;
1314
1315 $sql_field_arr[] = array( 'name' => 'form', 'type' => '%s', 'value' => $form_data );
1316 $sql_field_arr[] = array( 'name' => 'booking_type', 'type' => '%d', 'value' => $create_params['resource_id'] );
1317 $sql_field_arr[] = array( 'name' => 'modification_date', 'type' => '%s', 'value' => gmdate( 'Y-m-d H:i:s' ) );
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] );
1319 $sql_field_arr[] = array( 'name' => 'hash', 'type' => '%s', 'value' => wpbc_hash__generate_booking_hash() );
1320
1321
1322 if (
1323 ( 0 == $create_params['is_edit_booking'] ) || // If not edit, then INSERT.
1324 ( 1 == $create_params['is_duplicate_booking'] ) // If duplicate, then INSERT.
1325 ) {
1326 // Saved only for new booking creation.
1327 $sql_field_arr[] = array(
1328 'name' => 'creation_date',
1329 'type' => '%s',
1330 'value' => gmdate( 'Y-m-d H:i:s' ),
1331 );
1332
1333 $sql_prepare_arr = array();
1334 $sql_prepare_arr['name'] = array_map( function ( $value ) { return $value['name']; }, $sql_field_arr );
1335 $sql_prepare_arr['type'] = array_map( function ( $value ) { return $value['type']; }, $sql_field_arr );
1336 $sql_prepare_arr['value'] = array_map( function ( $value ) { return $value['value']; }, $sql_field_arr );
1337
1338 $sql_prepare_arr['name'] = implode( ', ', $sql_prepare_arr['name'] );
1339 $sql_prepare_arr['type'] = implode( ', ', $sql_prepare_arr['type'] );
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 }
1349 // Get ID of booking
1350 $booking_id = (int) $wpdb->insert_id;
1351
1352 } else { // Edit - UPDATE
1353
1354 $booking_id = (int) $create_params['is_edit_booking'];
1355
1356 $sql_prepare_arr = array();
1357 $sql_prepare_arr['set'] = array_map( function ( $value ) { return $value['name'] . '=' . $value['type']; }, $sql_field_arr );
1358 $sql_prepare_arr['value'] = array_map( function ( $value ) { return $value['value']; }, $sql_field_arr );
1359
1360 $sql_prepare_arr['set'] = implode( ', ', $sql_prepare_arr['set'] );
1361
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 }
1371
1372 // Check if dates previously was approved.
1373 $slct_sql = "SELECT approved FROM {$wpdb->prefix}bookingdates WHERE booking_id IN ({$booking_id}) LIMIT 0,1";
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 );
1376 $is_approved_dates = ( count( $slct_sql_results ) > 0 ) ? $slct_sql_results[0]->approved : $is_approved_dates;
1377
1378
1379 $delete_sql = "DELETE FROM {$wpdb->prefix}bookingdates WHERE booking_id IN ({$booking_id})";
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 }
1384 }
1385 // </editor-fold>
1386
1387
1388 // <editor-fold defaultstate="collapsed" desc=" == Compose D A T E S for DB == " >
1389 // -----------------------------------------------------------------------------------------------------------------
1390 // Compose D A T E S for DB
1391 // -----------------------------------------------------------------------------------------------------------------
1392
1393 if ( class_exists( 'wpdev_bk_biz_l' ) ) {
1394 $field_names_arr = array( 'booking_id', 'booking_date', 'approved', 'type_id' );
1395 } else {
1396 $field_names_arr = array( 'booking_id', 'booking_date', 'approved' );
1397 }
1398 $field_names = implode( ', ', $field_names_arr );
1399
1400 $dates_sql = "INSERT INTO {$wpdb->prefix}bookingdates ( {$field_names} ) VALUES ";
1401 $insert_dates_arr = array();
1402
1403 $how_many_items_to_book = $create_params['how_many_items_to_book'];
1404 // $i - INDEX of child booking resource to book (if $how_many_items_to_book=1, then index always = 0 ) in [ 'resources_in_dates' = [ '2023-10-18' = [ 9, 12, 10, 11 ], ... ]...] - e.g. here = 9
1405 for( $i = 0; $i < $how_many_items_to_book; $i++) {
1406
1407 $date_number = 0;
1408 foreach ( $where_to_save_booking['resources_in_dates'] as $only_date_sql => $resources_in_date_arr ) {
1409
1410 $date_resource_id = $resources_in_date_arr[ $i ];
1411
1412 // $create_params['resource_id'] <- Main resource (saved in wp_booking )
1413 // $only_date_sql <- '2023-09-12'
1414 // $date_resource_id <- Child resource (need to save in wp_bookingdates) OR if ( $create_params['resource_id'] == $date_resource_id ) then NULL
1415
1416 // ---------------------------------------------------------------------------------------------------------
1417 // Is full day booking:
1418 if (
1419 ( '00:00' === substr( $where_to_save_booking['time_to_book'][0], 0, 5 ) )
1420 && (
1421 ( '24:00' === substr( $where_to_save_booking['time_to_book'][1], 0, 5 ) )
1422 || ( '00:00' === substr( $where_to_save_booking['time_to_book'][1], 0, 5 ) )
1423 )
1424 ){
1425 // Full days *******************************************************************************************
1426 $full_date_sql = $only_date_sql . ' 00:00:00';
1427
1428 $insert_dates_arr[] = wpbc_prepare_date_row( $booking_id, $full_date_sql, $is_approved_dates, $date_resource_id, $create_params['resource_id'] );
1429
1430 } else { // Times
1431
1432 if (
1433 ( $create_params['is_use_booking_recurrent_time'] ) // Activated option to book times as 'time-slots' OR
1434 || ( 1 === count( $where_to_save_booking['resources_in_dates'] ) ) // Selected only 1 date, so use time as time-slot
1435 ) {
1436 // Time slots in each day **************************************************************************
1437
1438 // Start Time
1439 $full_date_sql = $only_date_sql . ' ' . $where_to_save_booking['time_to_book'][0];
1440 $insert_dates_arr[] = wpbc_prepare_date_row( $booking_id, $full_date_sql, $is_approved_dates, $date_resource_id, $create_params['resource_id'] );
1441
1442 // End Time
1443 $full_date_sql = $only_date_sql . ' ' . $where_to_save_booking['time_to_book'][1];
1444 $insert_dates_arr[] = wpbc_prepare_date_row( $booking_id, $full_date_sql, $is_approved_dates, $date_resource_id, $create_params['resource_id'] );
1445
1446 } else {
1447 // Check in/out ************************************************************************************
1448
1449 if ( 0 == $date_number ) { // Is check in ?
1450
1451 $full_date_sql = $only_date_sql . ' ' . $where_to_save_booking['time_to_book'][0];
1452
1453 } else if ( ( count( $where_to_save_booking['resources_in_dates'] ) - 1 ) == $date_number ) { // Is check out ?
1454
1455 $full_date_sql = $only_date_sql . ' ' . $where_to_save_booking['time_to_book'][1];
1456
1457 } else { // Middle date - Full Date
1458 $full_date_sql = $only_date_sql . ' 00:00:00';
1459 }
1460
1461 $insert_dates_arr[] = wpbc_prepare_date_row( $booking_id, $full_date_sql, $is_approved_dates, $date_resource_id, $create_params['resource_id'] );
1462
1463 }
1464 }
1465 // ---------------------------------------------------------------------------------------------------------
1466 $date_number++;
1467 }
1468 }
1469
1470 $dates_sql .= implode( ', ', $insert_dates_arr );
1471 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1472 if ( false === $wpdb->query( $dates_sql ) ) {
1473 return array( 'status' => 'error', 'message' => 'Error. INSERT "D A T E S" in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $dates_sql );
1474 }
1475
1476 // -----------------------------------------------------------------------------------------------------------------
1477 // End D A T E S
1478 // -----------------------------------------------------------------------------------------------------------------
1479 // </editor-fold>
1480
1481
1482 return array(
1483 'status' => 'ok'
1484 , 'booking_id' => $booking_id
1485 , 'form_data' => $form_data
1486 , 'message' => ''
1487 );
1488 }
1489
1490
1491 /**
1492 * Get SQL ROWs of VALUES for INSERT to DB
1493 *
1494 * @param int $booking_id 101 ID of the booking
1495 * @param string $full_date_sql '2023-09-12 10:00:01' SQL date
1496 * @param int $is_approved_dates 1 1 - approved, 0 - pending
1497 * @param int $date_resource_id ( >= biz_l ): 4 ID of child booking resource (if we save ONE booking in SEVERAL booking resources) field 'type_id' in wp_bookingdates
1498 * @param int $main_resource_id ( >= biz_l ): 1 ID of main (parent booking resource)
1499 *
1500 * @return string - SQL for dates VALUES to insert
1501 */
1502 function wpbc_prepare_date_row( $booking_id, $full_date_sql, $is_approved_dates, $date_resource_id, $main_resource_id ) {
1503
1504 global $wpdb;
1505
1506 if ( class_exists( 'wpdev_bk_biz_l' ) ) {
1507 $insert_dates_arr = ( $main_resource_id != $date_resource_id )
1508 ? $wpdb->prepare( "(%d, %s, %d, %d)", $booking_id, $full_date_sql, $is_approved_dates, $date_resource_id )
1509 : $wpdb->prepare( "(%d, %s, %d, NULL)", $booking_id, $full_date_sql, $is_approved_dates );
1510 } else {
1511 $insert_dates_arr = $wpdb->prepare( "(%d, %s, %d)", $booking_id, $full_date_sql, $is_approved_dates );
1512 }
1513
1514 return $insert_dates_arr;
1515 }
1516
1517 // == Help functions ==
1518 function wpbc_set_request_form_context( $ctx ) {
1519 $GLOBALS['wpbc_request_form_context'] = ( is_array( $ctx ) ) ? $ctx : array();
1520 }
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
1533 // ---------------------------------------------------------------------------------------------------------------------
1534 // Support
1535 // ---------------------------------------------------------------------------------------------------------------------
1536
1537 /**
1538 * Get booking_id and resource_id if we are editing the booking, by booking hash
1539 *
1540 * @param $booking_hash
1541 * @param $server_request_url
1542 *
1543 * @return array | false false if not edit Otherwise [ 'booking_id': 100, 'resource_id': 3 ]
1544 */
1545 function wpbc_get_data__if_edit_booking( $booking_hash, $server_request_url ) {
1546
1547 $is_edit_booking = false;
1548 if ( ! empty( $booking_hash ) ) {
1549
1550 $my_booking_id_type = wpbc_hash__get_booking_id__resource_id( $booking_hash );
1551 if ( $my_booking_id_type !== false ) {
1552
1553 $is_edit_booking = array();
1554 $is_edit_booking['booking_id'] = intval( $my_booking_id_type[0] );
1555 $is_edit_booking['resource_id'] = intval( $my_booking_id_type[1] );
1556
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.
1560
1561 if ( ( function_exists( 'wpbc_is_this_child_resource' ) ) && ( wpbc_is_this_child_resource( $is_edit_booking['resource_id'] ) ) ) {
1562 $bk_parent_br_id = wpbc_get_parent_resource( $is_edit_booking['resource_id'] );
1563
1564 $is_edit_booking['resource_id'] = intval( $bk_parent_br_id );
1565 }
1566 //}
1567 }
1568 }
1569 return $is_edit_booking;
1570 }
1571
1572
1573 /**
1574 * Get how many items to book. Usually it's from [selectbox visitors "1" ... ] field.
1575 *
1576 * @param booking_form_data__arr = [
1577 * selected_short_dates_hint = "September 27, 2023 - September 28, 2023"
1578 * days_number_hint = "2"
1579 * rangetime = "16:00 - 18:00"
1580 * starttime = "21:00"
1581 * durationtime = "00:30"
1582 * name = "John"
1583 * secondname = "Smith"
1584 * email = "[email protected]"
1585 * visitors = "1"
1586 * children = "0"
1587 * details = ""
1588 * ]
1589 *
1590 * @return int
1591 */
1592 function wpbc_get__how_many_items_to_book__in_booking_form( $booking_form_data__arr, $resource_id ){
1593
1594 $how_many_items_to_book = 1;
1595
1596 //TODO: Check about some URL parameter: '&resource_no_update' to book parent resource as single resource! // FixIn: 10.10.1.2
1597 if (
1598 ( class_exists( 'wpdev_bk_biz_l' ) )
1599 && ( 0 !== wpbc_get_child_resources_number( $resource_id ) ) // Here several child booking resources
1600 ) {
1601 $booking_capacity_field = wpbc_get__booking_capacity_field__name();
1602 if (
1603 ( ! empty( $booking_capacity_field ) )
1604 && ( isset( $booking_form_data__arr[ $booking_capacity_field ] ) )
1605 ){
1606 $how_many_items_to_book = intval( $booking_form_data__arr[ $booking_capacity_field ] ); // Get value of how many booking resources to book
1607 $how_many_items_to_book = ( $how_many_items_to_book > 0 ) ? $how_many_items_to_book : 1;
1608 }
1609 }
1610
1611 return $how_many_items_to_book;
1612 }
1613
1614
1615 /**
1616 * Get capacity field -- 'pure name'
1617 *
1618 * @return string - field name, or empty string - '' if not defined
1619 *
1620 * In DB, we are saved field name type and possible name of custom booking form in format: 'custom_form_name^field_type^capacity_field_name' -> 'minimal^select^adults'
1621 * or for standard form: 'select^visitors'
1622 * and here we get only field name: 'visitors'
1623 */
1624 function wpbc_get__booking_capacity_field__name() {
1625
1626 if ( class_exists( 'wpdev_bk_biz_l' ) ) {
1627
1628 if ( 'On' == get_bk_option( 'booking_quantity_control' ) ) {
1629
1630 $booking_capacity_field = get_bk_option( 'booking_capacity_field' );
1631
1632 if ( ! empty( $booking_capacity_field ) ) {
1633
1634 $booking_capacity_field = explode( '^', $booking_capacity_field );
1635
1636 if ( ! empty( $booking_capacity_field ) ) {
1637
1638 $booking_capacity_field_name = $booking_capacity_field[ count( $booking_capacity_field ) - 1 ];
1639
1640 return $booking_capacity_field_name;
1641 }
1642 }
1643 }
1644 }
1645 return '';
1646 }
1647
1648
1649 /**
1650 * Get time for booking from the booking form, as array of seconds: [ 0, 24 * 60 * 60 ] OR [ 10*60*60, 14*60*60 ]. If timefields not exist, then get time for full day booking.
1651 * @param $booking_form_data__arr
1652 *
1653 * @return array [ 0, 24 * 60 * 60 ] OR [ 10*60*60, 14*60*60 ] <- start and end time in seconds
1654 *
1655 * Example:
1656 *
1657 * // Firstly, we need to Get parsed booking form: [ name = "John", secondname = "Smith", email = "[email protected]", visitors = "2",... ]
1658 * $structured_booking_data_arr = wpbc_get_parsed_booking_data_arr( $params["form_data"], $params["resource_id"], array( 'get' => 'value' ) );
1659 *
1660 * // Now get start/end times as seconds: [ 64800, 72000 ]
1661 * $time_as_seconds_arr = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $structured_booking_data_arr );
1662 */
1663 function wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $booking_form_data__arr ){
1664
1665 $selected_time_fields = wpbc_get__selected_time_fields__in_booking_form__as_arr( $booking_form_data__arr );
1666
1667 // 2.2 Get selected SECONDS to book ---------------------------------------------------------------------------
1668 $time_as_seconds_arr = array( 0, 24 * 60 * 60 ); // Full day booking by default
1669
1670 foreach ( $selected_time_fields as $time_fields_obj ) { // { times_as_seconds: [ 21600, 23400 ], value_option_24h: '06:00 - 06:30', name: 'rangetime'}
1671
1672 if ( false !== strpos( $time_fields_obj['name'], 'rangetime' ) ) {
1673 $time_as_seconds_arr[ 0 ] = $time_fields_obj['times_as_seconds'][ 0 ];
1674 $time_as_seconds_arr[ 1 ] = $time_fields_obj['times_as_seconds'][ 1 ];
1675 //break; // If we have range-time then skip this loop
1676 }
1677 if ( false !== strpos( $time_fields_obj['name'], 'starttime' ) ) {
1678 $time_as_seconds_arr[ 0 ] = $time_fields_obj['times_as_seconds'][ 0 ];
1679 }
1680 if ( false !== strpos( $time_fields_obj['name'], 'endtime' ) ) {
1681 $time_as_seconds_arr[ 1 ] = $time_fields_obj['times_as_seconds'][ 0 ];
1682 }
1683 }
1684
1685 // For duration time we need to make a new loop, because we need to be sure that was defined START_TIME before this,
1686 // and end time was NOT defined, e.g. == (otherwise it's means that we already used END_TIME or RANGE_TIME)
1687 if (
1688 ( ( 0 ) !== $time_as_seconds_arr[ 0 ] )
1689 && ( (24 * 60 * 60 ) === $time_as_seconds_arr[ 1 ] )
1690 ){
1691 foreach ( $selected_time_fields as $time_fields_obj ) { // { times_as_seconds: [ 21600 ], value_option_24h: '06:00', name: 'durationtime'}
1692
1693 if ( false !== strpos( $time_fields_obj['name'], 'durationtime' ) ) {
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 }
1699 break;
1700 }
1701 }
1702 }
1703
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 );
1923 }
1924
1925
1926 /**
1927 * Get all time fields in the booking form as array of objects
1928 *
1929 * @param booking_form_data__arr = [
1930 * selected_short_dates_hint = "September 27, 2023 - September 28, 2023"
1931 * days_number_hint = "2"
1932 * rangetime = "16:00 - 18:00"
1933 * starttime = "21:00"
1934 * durationtime = "00:30"
1935 * name = "John"
1936 * secondname = "Smith"
1937 * email = "[email protected]"
1938 * visitors = "1"
1939 * children = "0"
1940 * details = ""
1941 * ]
1942 * @returns []
1943 *
1944 * Example:
1945 * [
1946 * [
1947 * name = "rangetime"
1948 * value_option_24h = "16:00 - 18:00"
1949 * times_as_seconds = [ 57600, 64800 ]
1950 * ]
1951 * [
1952 * name = "starttime"
1953 * value_option_24h = "21:00"
1954 * times_as_seconds = [ 75600 ]
1955 * ]
1956 * [
1957 * name = "durationtime"
1958 * value_option_24h = "00:30"
1959 * times_as_seconds = [ 1800 ]
1960 * ]
1961 * ]
1962 */
1963 function wpbc_get__selected_time_fields__in_booking_form__as_arr( $booking_form_data__arr ){
1964
1965 /**
1966 * Fields with [] like this select[name="rangetime1[]"]
1967 * it's when we have 'multiple' in shortcode: [select* rangetime multiple "06:00 - 06:30" ... ]
1968 */
1969 $time_fields_arr = array( 'rangetime', 'starttime', 'endtime', 'durationtime' );
1970
1971 $time_fields_obj_arr = array();
1972
1973 // Loop all Time Fields
1974 for ( $ctf = 0; $ctf < count( $time_fields_arr ); $ctf ++ ) {
1975
1976 $time_field = $time_fields_arr[ $ctf ];
1977 if ( isset( $booking_form_data__arr[ $time_field ] ) ) {
1978
1979 $value_option_seconds_arr = explode( '-', $booking_form_data__arr[ $time_field ] );
1980 $times_as_seconds_arr = array();
1981
1982 foreach ( $value_option_seconds_arr as $time_val ) {
1983 $time_val = trim( $time_val );
1984 if ( ! empty( $time_val ) ) {
1985 $start_end_times_arr = explode( ':', $time_val );
1986 $time_in_seconds = intval( $start_end_times_arr[0] ) * 60 * 60 + intval( $start_end_times_arr[1] ) * 60;
1987 $times_as_seconds_arr[] = $time_in_seconds;
1988 }
1989 }
1990
1991 if (! empty($times_as_seconds_arr)) {
1992
1993 $time_fields_obj_arr[] = array(
1994 'name' => $time_field,
1995 'value_option_24h' => $booking_form_data__arr[ $time_field ],
1996 'times_as_seconds' => $times_as_seconds_arr
1997 );
1998 }
1999 }
2000 }
2001
2002 return $time_fields_obj_arr;
2003 }
2004
2005
2006 //TODO: 2023-10-13 16:37
2007 // - create new function for confirmation section
2008 // - Update payment request
2009 // - create redirection to the "Thank you." page and show there conformation, and payment request"
2010 // - define in the settings new Stripe and PayPal button design as default !
2011 // - Be sure that 'booking_log_booking_actions' active by default
2012 // - test it in dark theme
2013 // - test closing booked dates if all time slot was booked - it's relative to 'different time slots in dif dates'
2014 // - add migrate support old dates selection variables
2015 // - check and remove other global Js vars
2016 // - Create wizard booking form style with steps ?
2017 // - Check Booking > Availability page relative new functionality of calendar_load !
2018 // - Update last func in wpbc-booking-new.php and remove it.
2019 // - Test capacity in dates and time slots
2020 // - Next 9.9 Customizer Wizard
2021 // - Next 9.9 Update search admin UI
2022 // - Next 9.9 - update functionality in Search functionality based on similar to where_to_save function.
2023 // - Next 9.9 refactor Dates functions.
2024 // Done. - Next 9.9 Update Booking > Settings General page to show tabs vertically in left column
2025 // Create the same navigation panel at the Pro Booking > Settings > Form page. At left column custom forms, at top tolbar selectio of Booking form and "Content of booking fields data" form.
2026 // Create the same navigation for Emails.
2027 /**
2028 * TODO: Performance improvement:
2029 * "other_code": 0.003080129623413086
2030 * "wpbc__where_to_save_booking": 0.060331106185913086
2031 * "wpbc_db__booking_save": 0.023384809494018555
2032 * "wpbc_maybe_get_payment_form": 1.3650128841400146 <-
2033 * "emails_sending": 1.5024309158325195 <-
2034 * "total": 2.9542438983917236
2035 */
2036