PluginProbe
Booking Calendar / 11.8.2
Booking Calendar v11.8.2
11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 10.11 10.11.2 10.11.3 All 202 releases
booking / includes / _capacity / create_booking.php

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

1,987 lines 112.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 * Save Booking - ADD NEW or UPDATE exist booking
219 *
220 * @param $request_params = [
221 * resource_id = 2 REQUIRED Default: 1
222 * dates_ddmmyy_csv = '27.10.2023, 28.10.2023, 29.10.2023' REQUIRED
223 * form_data = 'text^selected_short_dates_hint2^Fri, ...9, 2023~text^...' REQUIRED
224 * booking_hash = '' Optional Default: ''
225 * custom_form = '' Optional Default: ''
226 * is_emails_send = 1 Optional Default: 1
227 * is_show_payment_form => 1 Optional Default: 1
228 * user_id = 1 Optional Default: 0 or ID of logged-in user
229 * request_uri = 'http://beta/resource-id2/', // Optional Default: for front-end: $_SERVER['REQUEST_URI'] | ajax: $_SERVER['HTTP_REFERER']
230 *
231 * 'sync_gid' => 'ghjgjgjgh5f5f45f' // Really Optional: can be passed only during import .ics
232 * 'is_approve_booking' => 0 // Really Optional: 0 | 1
233 * 'save_booking_even_if_unavailable' => 0 // Really Optional: 0 | 1, if 1 then force save booking even if dates unavailable.
234 * ]
235 *
236 * @return [] ok: [
237 *
238 * ]
239 * error: [
240 * 'ajx_data': [ 'status':'error', 'status_error':'booking_can_not_save', 'ajx_after_action_message': 'Can not save booking', 'ajx_after_action_message_status': 'warning' ]
241 * ]
242 *
243 * Example:
244 *
245 * wpbc_booking_save( array(
246 * 'resource_id' => 2,
247 * 'dates_ddmmyy_csv' => '04.10.2023, 05.10.2023, 06.10.2023',
248 * 'form_data' => 'text^cost_hint2^150.00฿~selectbox-multiple^rangetime2[]^14:00 - 16:00~text^name2^John~text^secondname2^Smith~email^email2^john.smith@server.com~selectbox-one^visitors2^2~selectbox-one^children2^0~textarea^details2^test',
249 * 'booking_hash' => '',
250 * 'custom_form' => '',
251 * 'is_emails_send' => 1,
252 * 'is_show_payment_form' => 1,
253 * 'user_id' => 1,
254 * 'request_uri' => 'http://beta/resource-id2/'
255 * ) );
256 *
257 */
258 function wpbc_booking_save( $request_params ){
259 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
260 $php_performance = wpbc_php_performance_START( 'total', array() );
261 // </editor-fold>
262 $ajx_data_arr = array();
263 $ajx_data_arr['status'] = 'ok';
264
265 // -----------------------------------------------------------------------------------------------------------------
266 // 1. Direct Clean Params
267 // -----------------------------------------------------------------------------------------------------------------
268 $server_request_uri = ( ( isset( $_SERVER['REQUEST_URI'] ) ) ? sanitize_text_field( $_SERVER['REQUEST_URI'] ) : '' ); /* phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash */ /* FixIn: sanitize_unslash */
269 $server_http_referer_uri = ( ( isset( $_SERVER['HTTP_REFERER'] ) ) ? sanitize_text_field( $_SERVER['HTTP_REFERER'] ) : '' ); /* phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash */ /* FixIn: sanitize_unslash */
270 $validate_arr_rules = array(
271 'resource_id' => array( 'validate' => 'd', 'default' => 1 ), // INT
272 'dates_ddmmyy_csv' => array( 'validate' => 'csv_dates', 'default' => '' ), // FixIn: 9.9.1.1.
273 'form_data' => array( 'validate' => 'strong', 'default' => '' ),
274 'booking_hash' => array( 'validate' => 'strong', 'default' => '' ),
275 'custom_form' => array( 'validate' => 'strong', 'default' => '' ),
276 'is_emails_send' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1
277 'is_show_payment_form' => array( 'validate' => 'd', 'default' => 1 ), // 0 | 1
278 'user_id' => array( 'validate' => 'd', 'default' => wpbc_get_current_user_id() ), // INT
279 'allow_past' => array( 'validate' => 'd', 'default' => 0 ),
280 'classic_booking_context_token' => array( 'validate' => 'strong', 'default' => '' ),
281 'request_uri' => array( 'validate' => 'strong', 'default' => ( ( defined( 'DOING_AJAX' ) ) && ( DOING_AJAX ) ) ? $server_http_referer_uri : $server_request_uri ), // front-end: $server_request_uri | ajax: $server_http_referer_uri
282 // Really Optional:
283 'aggregate_resource_id_arr' => array( 'validate' => 'digit_or_csd', 'default' => '' ),
284 //TODO: this parameter does not transfer during saving, so here will be always default value 'bookings_only' // FixIn: 10.0.0.7.
285 'aggregate_type' => array( 'validate' => 'strong', 'default' => 'bookings_only' ), // Optional. 'all' | 'bookings_only' <- it is depends on shortcode parameter: options="{aggregate type=bookings_only}"
286 'is_approve_booking' => array( 'validate' => 'd', 'default' => 0 ), // 0 | 1
287 'save_booking_even_if_unavailable' => array( 'validate' => 'd', 'default' => 0 ), // 0 | 1
288 'sync_gid' => array( 'validate' => 'strong', 'default' => '' ),
289 'is_use_booking_recurrent_time' => array( 'validate' => 'd', 'default' => intval( ( 'On' === get_bk_option( 'booking_recurrent_time' ) ) ) ),
290
291 'form_status' => array( 'validate' => 'strong', 'default' => 'published' ),
292 'wpbc_bfb_preview' => array( 'validate' => 'd', 'default' => 0 ),
293 'wpbc_bfb_preview_token' => array( 'validate' => 'strong', 'default' => '' ),
294 'wpbc_bfb_preview_form_id' => array( 'validate' => 'd', 'default' => 0 ),
295 'wpbc_bfb_preview_nonce' => array( 'validate' => 'strong', 'default' => '' ),
296 'wpbc_time_override_enabled' => array( 'validate' => 'd', 'default' => 0 ),
297 'wpbc_time_override_source' => array( 'validate' => 'strong', 'default' => '' ),
298 'wpbc_time_override_start' => array( 'validate' => 'strong', 'default' => '' ),
299 'wpbc_time_override_end' => array( 'validate' => 'strong', 'default' => '' ),
300 'wpbc_admin_cost_correction' => array( 'validate' => 'strong', 'default' => '' ),
301 );
302 $validate_arr_rules['service_id'] = array( 'validate' => 'd', 'default' => 0 );
303 $validate_arr_rules['appointment_service_required'] = array( 'validate' => 'd', 'default' => 0 );
304 $validate_arr_rules['appointment_context_token'] = array( 'validate' => 'strong', 'default' => '' );
305 $validate_arr_rules['resource_selector_required'] = array( 'validate' => 'd', 'default' => 0 );
306 $validate_arr_rules['resource_selector_context_token'] = array( 'validate' => 'strong', 'default' => '' );
307 $validate_arr_rules['wpbc_admin_booking_nonce'] = array( 'validate' => 'strong', 'default' => '' );
308 $re_cleaned_params = wpbc_sanitize_params_in_arr( $request_params, $validate_arr_rules );
309 $has_verified_appointment_context = false;
310 $has_verified_resource_selector_context = false;
311 if ( ! empty( $re_cleaned_params['appointment_service_required'] ) && empty( $re_cleaned_params['service_id'] ) ) {
312 $ajx_data_arr['status'] = 'error';
313 $ajx_data_arr['status_error'] = 'appointment_service_required';
314 $ajx_data_arr['ajx_after_action_message'] = __( 'Please select a Service.', 'booking' );
315 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
316 return array( 'ajx_data' => $ajx_data_arr );
317 }
318 if ( ! empty( $re_cleaned_params['service_id'] ) ) {
319 if ( ! function_exists( 'wpbc_booking_appointment_validate_submission_context' ) ) {
320 $appointment_context_check = new WP_Error( 'appointment_context_unavailable', __( 'The Appointment selection cannot be verified. Please reload the page and try again.', 'booking' ) );
321 } else {
322 $appointment_context_check = wpbc_booking_appointment_validate_submission_context(
323 $re_cleaned_params['appointment_context_token'],
324 $re_cleaned_params['service_id'],
325 $re_cleaned_params['resource_id']
326 );
327 }
328 if ( is_wp_error( $appointment_context_check ) ) {
329 $ajx_data_arr['status'] = 'error';
330 $ajx_data_arr['status_error'] = $appointment_context_check->get_error_code();
331 $ajx_data_arr['ajx_after_action_message'] = $appointment_context_check->get_error_message();
332 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
333 return array( 'ajx_data' => $ajx_data_arr );
334 }
335 $has_verified_appointment_context = true;
336
337 // A client value cannot enable past Appointment creation; trust only the site-authored signed context.
338 $re_cleaned_params['allow_past'] = wpbc_booking_appointment_is_past_booking_enabled( $appointment_context_check ) ? 1 : 0;
339 }
340 if ( ! empty( $re_cleaned_params['resource_selector_required'] ) || ! empty( $re_cleaned_params['resource_selector_context_token'] ) ) {
341 if ( ! function_exists( 'wpbc_booking_resource_selector_validate_submission_context' ) ) {
342 $resource_selector_context_check = new WP_Error( 'resource_selector_context_unavailable', __( 'The Booking Resource selection cannot be verified. Please reload the page and try again.', 'booking' ) );
343 } else {
344 $resource_selector_context_check = wpbc_booking_resource_selector_validate_submission_context(
345 $re_cleaned_params['resource_selector_context_token'],
346 $re_cleaned_params['resource_id']
347 );
348 }
349 if ( is_wp_error( $resource_selector_context_check ) ) {
350 $ajx_data_arr['status'] = 'error';
351 $ajx_data_arr['status_error'] = $resource_selector_context_check->get_error_code();
352 $ajx_data_arr['ajx_after_action_message'] = $resource_selector_context_check->get_error_message();
353 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
354 return array( 'ajx_data' => $ajx_data_arr );
355 }
356 $has_verified_resource_selector_context = true;
357
358 // Trust only the site-authored signed selector context for public past bookings.
359 $re_cleaned_params['allow_past'] = wpbc_booking_resource_selector_is_past_booking_enabled( $resource_selector_context_check ) ? 1 : 0;
360 }
361
362 $re_cleaned_params['form_status'] = sanitize_key( $re_cleaned_params['form_status'] );
363 if ( 'preview' !== $re_cleaned_params['form_status'] ) {
364 $re_cleaned_params['form_status'] = 'published';
365 }
366 // FixIn: 2026-02-05 - make preview/published available to form parsing/templates during this request.
367 wpbc_set_request_form_context(
368 array(
369 'form_status' => $re_cleaned_params['form_status'],
370 'user_id' => $re_cleaned_params['user_id'],
371 'wpbc_bfb_preview' => absint( $re_cleaned_params['wpbc_bfb_preview'] ),
372 'wpbc_bfb_preview_token' => sanitize_key( $re_cleaned_params['wpbc_bfb_preview_token'] ),
373 'wpbc_bfb_preview_form_id' => absint( $re_cleaned_params['wpbc_bfb_preview_form_id'] ),
374 'wpbc_bfb_preview_nonce' => (string) $re_cleaned_params['wpbc_bfb_preview_nonce'],
375 )
376 );
377
378 // -----------------------------------------------------------------------------------------------------------------
379 // Local parameters
380 // -----------------------------------------------------------------------------------------------------------------
381 $local_params = array();
382 $is_authorized_admin_booking_request = wpbc_is_authorized_admin_booking_request( $re_cleaned_params['wpbc_admin_booking_nonce'] );
383 $local_params['is_from_admin_panel'] = $is_authorized_admin_booking_request;
384 $local_params['user_id'] = $re_cleaned_params['user_id']; // 1
385 $local_params['sync_gid'] = $re_cleaned_params['sync_gid']; // ''
386 $local_params['is_approve_booking'] = $re_cleaned_params['is_approve_booking']; // 0 | 1
387 $local_params['is_use_booking_recurrent_time'] = ( 1 === $re_cleaned_params['is_use_booking_recurrent_time'] ); // false | true
388 $request_action = isset( $_REQUEST['action'] ) && is_scalar( $_REQUEST['action'] )
389 ? sanitize_key( (string) wp_unslash( $_REQUEST['action'] ) )
390 : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
391 $is_public_booking_create_request = wp_doing_ajax()
392 && 'wpbc_ajx_booking__create' === strtolower( $request_action )
393 && ! $is_authorized_admin_booking_request;
394
395 // Time overrides belong exclusively to the capability-protected Add Booking administration workflow.
396 $re_cleaned_params = wpbc_restrict_booking_time_override_to_authorized_admin( $re_cleaned_params, $is_authorized_admin_booking_request );
397 // Cost corrections belong exclusively to capability-protected administrator booking workflows.
398 $re_cleaned_params = wpbc_restrict_booking_cost_correction_to_authorized_admin( $re_cleaned_params, $is_authorized_admin_booking_request );
399
400 // -----------------------------------------------------------------------------------------------------------------
401 // Parse Local parameters for later use
402 // -----------------------------------------------------------------------------------------------------------------
403 /**
404 * Get parsed booking form: = [ name = "John", secondname = "Smith", email = "john.smith@server.com", visitors = "2",... ]
405 */
406 $local_params['structured_booking_data_arr'] = wpbc_get_parsed_booking_data_arr( $re_cleaned_params["form_data"], $re_cleaned_params["resource_id"], array( 'get' => 'value' ) );
407 $local_params['all_booking_data_arr'] = wpbc_get_parsed_booking_data_arr( $re_cleaned_params["form_data"], $re_cleaned_params["resource_id"] );
408 $local_params['time_override_arr'] = wpbc_get_booking_time_override__as_arr( $re_cleaned_params );
409 if ( ! empty( $local_params['time_override_arr'] ) ) {
410 unset( $local_params['structured_booking_data_arr']['rangetime'], $local_params['structured_booking_data_arr']['durationtime'] );
411 $local_params['structured_booking_data_arr']['starttime'] = $local_params['time_override_arr']['start'];
412 $local_params['structured_booking_data_arr']['endtime'] = $local_params['time_override_arr']['end'];
413
414 unset( $local_params['all_booking_data_arr']['rangetime'], $local_params['all_booking_data_arr']['durationtime'] );
415 $local_params['all_booking_data_arr']['starttime'] = array(
416 'type' => 'text',
417 'original_name' => 'starttime' . $re_cleaned_params['resource_id'],
418 'name' => 'starttime',
419 'value' => $local_params['time_override_arr']['start'],
420 );
421 $local_params['all_booking_data_arr']['endtime'] = array(
422 'type' => 'text',
423 'original_name' => 'endtime' . $re_cleaned_params['resource_id'],
424 'name' => 'endtime',
425 'value' => $local_params['time_override_arr']['end'],
426 );
427 }
428 // Important! : [ 64800, 72000 ]
429 $local_params['time_as_seconds_arr'] = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $local_params['structured_booking_data_arr'] );
430 $local_params['appointment_service'] = array();
431 if ( ! empty( $re_cleaned_params['service_id'] ) && function_exists( 'wpbc_appointment_services_repository' ) ) {
432 $range_time_value = isset( $local_params['structured_booking_data_arr']['rangetime'] ) ? $local_params['structured_booking_data_arr']['rangetime'] : '';
433 $start_time_value = isset( $local_params['structured_booking_data_arr']['starttime'] ) ? $local_params['structured_booking_data_arr']['starttime'] : '';
434 $range_time_value = is_array( $range_time_value ) ? implode( '', $range_time_value ) : $range_time_value;
435 $start_time_value = is_array( $start_time_value ) ? implode( '', $start_time_value ) : $start_time_value;
436 $has_appointment_time = ! empty( $local_params['time_override_arr'] )
437 || '' !== trim( (string) $range_time_value )
438 || '' !== trim( (string) $start_time_value );
439 if ( ! $has_appointment_time ) {
440 $ajx_data_arr['status'] = 'error';
441 $ajx_data_arr['status_error'] = 'appointment_service_time_required';
442 $ajx_data_arr['ajx_after_action_message'] = __( 'A Service appointment requires a start time. Add a time field to the Booking Form and select a time.', 'booking' );
443 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
444 return array( 'ajx_data' => $ajx_data_arr );
445 }
446 $appointment_service = wpbc_appointment_services_repository()->find_active_for_resource( $re_cleaned_params['service_id'], $re_cleaned_params['resource_id'] );
447 if ( is_wp_error( $appointment_service ) ) {
448 $ajx_data_arr['status'] = 'error';
449 $ajx_data_arr['status_error'] = 'appointment_service_unavailable';
450 $ajx_data_arr['ajx_after_action_message'] = $appointment_service->get_error_message();
451 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
452 return array( 'ajx_data' => $ajx_data_arr );
453 }
454 if ( count( $local_params['time_as_seconds_arr'] ) < 2 || ! function_exists( 'wpbc_appointment_services_resolve_end_seconds' ) ) {
455 $ajx_data_arr['status'] = 'error';
456 $ajx_data_arr['status_error'] = 'appointment_service_duration_invalid';
457 $ajx_data_arr['ajx_after_action_message'] = __( 'The selected Service duration is invalid. Please contact the website administrator.', 'booking' );
458 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
459 return array( 'ajx_data' => $ajx_data_arr );
460 }
461 $maximum_duration_minutes = absint( apply_filters( 'wpbc_booking_appointment_maximum_duration_minutes', 24 * 60, array() ) );
462 $service_end_second = wpbc_appointment_services_resolve_end_seconds( $appointment_service, $local_params['time_as_seconds_arr'][0], $maximum_duration_minutes );
463 if ( is_wp_error( $service_end_second ) ) {
464 $ajx_data_arr['status'] = 'error';
465 $ajx_data_arr['status_error'] = $service_end_second->get_error_code();
466 $ajx_data_arr['ajx_after_action_message'] = $service_end_second->get_error_message();
467 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
468 return array( 'ajx_data' => $ajx_data_arr );
469 }
470 $local_params['time_as_seconds_arr'][1] = $service_end_second;
471 $local_params['appointment_service'] = $appointment_service;
472 $service_start_time = wpbc_transform__seconds__in__24_hours_his( $local_params['time_as_seconds_arr'][0] );
473 $service_end_time = wpbc_transform__seconds__in__24_hours_his( $local_params['time_as_seconds_arr'][1] );
474 unset( $local_params['structured_booking_data_arr']['rangetime'], $local_params['structured_booking_data_arr']['durationtime'] );
475 $local_params['structured_booking_data_arr']['starttime'] = $service_start_time;
476 $local_params['structured_booking_data_arr']['endtime'] = $service_end_time;
477 unset( $local_params['all_booking_data_arr']['rangetime'], $local_params['all_booking_data_arr']['durationtime'] );
478 $local_params['all_booking_data_arr']['starttime'] = array( 'type' => 'text', 'original_name' => 'starttime' . $re_cleaned_params['resource_id'], 'name' => 'starttime', 'value' => $service_start_time );
479 $local_params['all_booking_data_arr']['endtime'] = array( 'type' => 'text', 'original_name' => 'endtime' . $re_cleaned_params['resource_id'], 'name' => 'endtime', 'value' => $service_end_time );
480 }
481 if ( function_exists( 'wpbc_appointment_services_sync_service_hint_booking_data' ) ) {
482 $service_hint_booking_data = wpbc_appointment_services_sync_service_hint_booking_data(
483 $local_params['structured_booking_data_arr'],
484 $local_params['all_booking_data_arr'],
485 $local_params['appointment_service'],
486 $re_cleaned_params['resource_id']
487 );
488 $local_params['structured_booking_data_arr'] = $service_hint_booking_data['structured_booking_data'];
489 $local_params['all_booking_data_arr'] = $service_hint_booking_data['all_booking_data'];
490 }
491 // [ "18:00:00", "20:00:00" ]
492 $time_as_seconds_arr = $local_params['time_as_seconds_arr'];
493 $time_as_seconds_arr[0] = ( 0 != $time_as_seconds_arr[0] ) ? $time_as_seconds_arr[0] + 1 : $time_as_seconds_arr[0]; // set check in time with ended 1 second
494 $time_as_seconds_arr[1] = ( ( 24 * 60 * 60 ) != $time_as_seconds_arr[1] ) ? $time_as_seconds_arr[1] + 2 : $time_as_seconds_arr[1]; // set check out time with ended 2 seconds
495 if ( ( 0 != $time_as_seconds_arr[0] ) && ( ( 24 * 60 * 60 ) == $time_as_seconds_arr[1] ) ) {
496 //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
497 $time_as_seconds_arr[1] += - 8;
498 }
499 $local_params['time_as_his_arr'] = array(
500 wpbc_transform__seconds__in__24_hours_his( $time_as_seconds_arr[0] ),
501 wpbc_transform__seconds__in__24_hours_his( $time_as_seconds_arr[1] )
502 );
503 // [ '2023-09-10', '2023-09-11' ]
504 $local_params['dates_only_sql_arr'] = wpbc_convert_dates_str__dd_mm_yyyy__to__yyyy_mm_dd( $re_cleaned_params["dates_ddmmyy_csv"] );
505 $local_params['dates_only_sql_arr'] = explode( ',', $local_params['dates_only_sql_arr'] );
506
507 $classic_context = array();
508 $has_verified_classic_context = false;
509 if ( ! empty( $re_cleaned_params['classic_booking_context_token'] ) && function_exists( 'wpbc_classic_booking_context_validate_submission' ) ) {
510 $classic_context = wpbc_classic_booking_context_validate_submission(
511 $re_cleaned_params['classic_booking_context_token'],
512 $re_cleaned_params['resource_id'],
513 $local_params['dates_only_sql_arr'],
514 $re_cleaned_params['custom_form'],
515 $re_cleaned_params['aggregate_resource_id_arr']
516 );
517 if ( is_wp_error( $classic_context ) ) {
518 $ajx_data_arr['status'] = 'error';
519 $ajx_data_arr['status_error'] = $classic_context->get_error_code();
520 $ajx_data_arr['ajx_after_action_message'] = $classic_context->get_error_message();
521 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
522 return array( 'ajx_data' => $ajx_data_arr );
523 }
524
525 $has_verified_classic_context = true;
526 $re_cleaned_params['allow_past'] = ! empty( $classic_context['allow_past'] ) ? 1 : 0;
527 // Pass only the signed canonical set into final availability and persistence decisions.
528 $re_cleaned_params['aggregate_resource_id_arr'] = implode( ',', $classic_context['aggregate_resource_ids'] );
529 }
530
531 if ( $is_public_booking_create_request && ! $has_verified_classic_context ) {
532 $ajx_data_arr['status'] = 'error';
533 $ajx_data_arr['status_error'] = 'classic_booking_context_required';
534 $ajx_data_arr['ajx_after_action_message'] = __( 'The booking form context has expired. Please reload the page and try again.', 'booking' );
535 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
536 return array( 'ajx_data' => $ajx_data_arr );
537 }
538
539 if ( $has_verified_classic_context ) {
540 $workflow_context_error = wpbc_booking_create_validate_required_workflow(
541 $classic_context,
542 $has_verified_appointment_context,
543 $has_verified_resource_selector_context
544 );
545 if ( is_wp_error( $workflow_context_error ) ) {
546 $ajx_data_arr['status'] = 'error';
547 $ajx_data_arr['status_error'] = $workflow_context_error->get_error_code();
548 $ajx_data_arr['ajx_after_action_message'] = $workflow_context_error->get_error_message();
549 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
550 return array( 'ajx_data' => $ajx_data_arr );
551 }
552 }
553
554 if (
555 ( ! empty( $local_params['time_override_arr'] ) )
556 && ( 'times_availability' === $local_params['time_override_arr']['source'] )
557 && ( count( array_filter( $local_params['dates_only_sql_arr'] ) ) > 1 )
558 ) {
559 $local_params['is_use_booking_recurrent_time'] = true;
560 }
561
562 $local_params['is_show_payment_form'] = $re_cleaned_params["is_show_payment_form"];
563
564 // FixIn: 9.9.0.35.
565 if ( $local_params['is_show_payment_form'] ) {
566 $local_params['is_show_payment_form'] = (
567 $is_authorized_admin_booking_request
568 && false !== strpos( $re_cleaned_params['request_uri'], 'is_show_payment_form=Off' )
569 )
570 ? 0
571 : $local_params['is_show_payment_form']; // 1|0
572 }
573
574 // Get EDIT booking data
575 $local_params['edit_resource_id'] = '';
576 $local_params['skip_booking_id'] = '';
577 $local_params['is_edit_booking'] = 0;
578 $local_params['is_duplicate_booking'] = 0;
579 $is_edit_booking = wpbc_get_data__if_edit_booking( $re_cleaned_params['booking_hash'], $re_cleaned_params['request_uri'] );
580 if ( false !== $is_edit_booking ) {
581 $local_params['edit_resource_id'] = $is_edit_booking['resource_id']; // can be parent booking resource, where we edit the booking
582 $local_params['skip_booking_id'] = $is_edit_booking['booking_id']; // booking ID
583 $local_params['is_edit_booking'] = $is_edit_booking['booking_id']; // booking ID
584 if (
585 ( ! empty( $local_params['structured_booking_data_arr']['wpbc_other_action'] ) )
586 && ( 'duplicate_booking' === $local_params['structured_booking_data_arr']['wpbc_other_action'] )
587 ){
588 $local_params['is_duplicate_booking'] = 1;
589 }
590 }
591
592 $is_frontend_ajax_edit = wp_doing_ajax()
593 && 'wpbc_ajx_booking__create' === strtolower( $request_action )
594 && 0 !== $local_params['is_edit_booking'];
595 $is_authorized_admin_edit = $is_authorized_admin_booking_request;
596
597 if (
598 $is_frontend_ajax_edit
599 && ! $is_authorized_admin_edit
600 && ! wpbc_is_visitor_booking_action_allowed( $local_params['is_edit_booking'] )
601 ) {
602 $ajx_data_arr['status'] = 'error';
603 $ajx_data_arr['status_error'] = 'visitor_booking_dates_in_past';
604 $ajx_data_arr['ajx_after_action_message'] = __( 'This booking can no longer be edited because its dates have already passed.', 'booking' );
605 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
606 return array( 'ajx_data' => $ajx_data_arr );
607 }
608 // It can be request resource ID or if we edit booking, it can be 'edit resource' - (e.g. child resource)
609 $local_params['initial_resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) ) ? $local_params['edit_resource_id'] : $re_cleaned_params['resource_id'];
610
611 // 2
612 $local_params['how_many_items_to_book'] = wpbc_get__how_many_items_to_book__in_booking_form( $local_params['structured_booking_data_arr'], $local_params['initial_resource_id'] );
613
614
615 $local_params['aggregate_resource_id_arr'] = explode( ',', $re_cleaned_params['aggregate_resource_id_arr'] );
616 $local_params['aggregate_resource_id_arr'] = array_filter( $local_params['aggregate_resource_id_arr'] ); // All entries of array equal to FALSE (0, '', '0' ) will be removed.
617 $local_params['aggregate_resource_id_arr'] = array_unique( $local_params['aggregate_resource_id_arr'] ); // Erase duplicates
618
619 // -----------------------------------------------------------------------------------------------------------------
620 // Here GO
621 // -----------------------------------------------------------------------------------------------------------------
622
623 // Force - resource saving parameters, instead of wpbc__where_to_save_booking()
624 if ( ! empty( $re_cleaned_params["save_booking_even_if_unavailable"] ) ) {
625
626 $local_params['how_many_items_to_book'] = 1;
627
628 $dates_keys_arr = array_values( $local_params['dates_only_sql_arr'] ); // [ '2023-09-23', '2023-09-24' ]
629
630 $resources_in_dates = array_fill_keys( $dates_keys_arr , array( $local_params['initial_resource_id'] ) ); // [ 2023-09-23 = [ 2 ], 2023-09-24 = [ 2 ] ]
631
632 $where_to_save_booking = array();
633 $where_to_save_booking['result'] = 'ok';
634 $where_to_save_booking['resources_in_dates'] = $resources_in_dates; // [ 2023-09-23 = [ 2, 10, 11 ], 2023-09-24 = [ 2, 10, 11 ]
635 $where_to_save_booking['time_to_book'] = $local_params['time_as_his_arr']; // [ "00:00:00", "24:00:00" ]
636 $where_to_save_booking['main__resource_id'] = $local_params['initial_resource_id']; // here edit or request (parent/single) resource
637
638 } else {
639 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
640 $php_performance = wpbc_php_performance_START( 'wpbc__where_to_save_booking' , $php_performance );
641 // </editor-fold>
642
643 /**
644 * Get slots [] where we can save booking = [ 'resources_in_dates' => [ 2023-10-18 = [ 2, 12, 10, 11 ]
645 * 2023-10-19 = [ 2, 12, 10, 11 ]
646 * 2023-10-20 = [ 2, 12, 10, 11 ]
647 * ],
648 * 'time_to_book' => [ "14:00:01" , "12:00:01" ],
649 * 'result' => 'ok'
650 * 'main__resource_id' => 2
651 * ]
652 * OR
653 * [ 'result' => 'error', 'message' => 'Booking can not be saved ...' ]
654 */
655 $where_to_save_booking = wpbc__where_to_save_booking( array(
656 'resource_id' => $local_params['initial_resource_id'], // 2 //TODO: If edit booking. What to pass 'edit' or 'parent' resource ID?
657 'skip_booking_id' => $local_params['skip_booking_id'], // '', | 125 if edit booking
658 'dates_only_sql_arr' => $local_params['dates_only_sql_arr'], // [ "2023-10-18", "2023-10-25", "2023-11-25" ]
659 'time_as_seconds_arr' => $local_params['time_as_seconds_arr'], // [ 36000, 39600 ]
660 'how_many_items_to_book' => $local_params['how_many_items_to_book'], // 1
661 'request_uri' => $re_cleaned_params['request_uri'], // 'http://beta/resource-id2/'
662 'allow_past' => ! empty( $re_cleaned_params['allow_past'] ),
663 'is_use_booking_recurrent_time' => $local_params['is_use_booking_recurrent_time'], // true | false
664 'time_override_source' => ! empty( $local_params['time_override_arr']['source'] ) ? $local_params['time_override_arr']['source'] : '',
665 'as_single_resource' => false, // false
666 'aggregate_resource_id_arr' => $local_params['aggregate_resource_id_arr'], // Optional can be ''
667 'aggregate_type' => $re_cleaned_params['aggregate_type'], //TODO: this parameter does not transfer during saving, so here will be always default value 'bookings_only' // FixIn: 10.0.0.7.
668 'custom_form' => $re_cleaned_params['custom_form'] // FixIn: 10.0.0.10.
669 ));
670 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- NO SLOTS TO SAVE " >
671 if ( 'error' == $where_to_save_booking['result'] ) {
672 $ajx_data_arr['status'] = 'error';
673 $ajx_data_arr['status_error'] = 'booking_can_not_save';
674 $ajx_data_arr['ajx_after_action_message'] = $where_to_save_booking['message'];
675 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
676 return array( 'ajx_data' => $ajx_data_arr );
677 }
678 // </editor-fold>
679
680 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
681 $php_performance = wpbc_php_performance_END( 'wpbc__where_to_save_booking' , $php_performance );
682 // </editor-fold>
683 }
684
685 if ( ! empty( $local_params['appointment_service'] ) && function_exists( 'wpbc_appointment_services_check_buffer_conflicts' ) ) {
686 $buffer_check = wpbc_appointment_services_check_buffer_conflicts(
687 $local_params['appointment_service'],
688 $where_to_save_booking['main__resource_id'],
689 array_keys( $where_to_save_booking['resources_in_dates'] ),
690 $local_params['time_as_seconds_arr'],
691 $local_params['skip_booking_id']
692 );
693 if ( is_wp_error( $buffer_check ) ) {
694 $ajx_data_arr['status'] = 'error';
695 $ajx_data_arr['status_error'] = 'appointment_service_buffer_conflict';
696 $ajx_data_arr['ajx_after_action_message'] = $buffer_check->get_error_message();
697 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
698 return array( 'ajx_data' => $ajx_data_arr );
699 }
700 }
701
702
703 // Get parameters, from REQUEST
704 $create_params = $local_params;
705 $create_params['resource_id'] = ( ! empty( $local_params['edit_resource_id'] ) )
706 ? $local_params['edit_resource_id'] // If we edit, then use original resource ???
707 : $where_to_save_booking['main__resource_id']; // Here is important TIP, resource can be where is free, and not where we submit
708 /**
709 * TODO: I think it's resolved! Just test about this situation, when we edit the booking - and it's means that we have $local_params['edit_resource_id']
710 * but what, if $where_to_save_booking do not contain this $local_params['edit_resource_id'] as available resource.
711 * or even we have $local_params['edit_resource_id'] = 2 and $where_to_save_booking contain resources like [ 1, 2, 3, 4 ]
712 * we make booking for 3 slots
713 * in this case, main resource will be 2
714 * but then when we loop resources in wpbc_db__booking_save() we will save child booking resources for dates like: 2, 3, 4 ( and it's wrong )
715 * "(205, '2023-10-04 00:00:00', 0, NULL)" <- main resource '2' e.g. $local_params['edit_resource_id'] = 2
716 * "(205, '2023-10-04 00:00:00', 0, 2)" ? <- child resource '2' e.g. [ .., 2, .. ] in $where_to_save_booking WHICH IS WRONG
717 */
718 $create_params['is_emails_send'] = $re_cleaned_params['is_emails_send'];
719 $create_params['custom_form'] = $re_cleaned_params['custom_form'];
720
721 make_bk_action( 'check_multiuser_params_for_client_side', $create_params['resource_id'] ); // Activate working with specific user in WP MU
722
723 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
724 $php_performance = wpbc_php_performance_START( 'wpbc_db__booking_save' , $php_performance );
725 // </editor-fold>
726
727 // -----------------------------------------------------------------------------------------------------------------
728 // == CREATE_THE 'NEW_BOOKING' ==
729 // -----------------------------------------------------------------------------------------------------------------
730 $create_booking_params = array(
731 'resource_id' => $create_params['resource_id'],
732 'custom_form' => $create_params['custom_form'],
733 'all_booking_data_arr' => $create_params['all_booking_data_arr'],
734 'dates_only_sql_arr' => $create_params['dates_only_sql_arr'],
735 'time_as_his_arr' => $create_params['time_as_his_arr'],
736 'is_from_admin_panel' => $create_params['is_from_admin_panel'],
737 'is_edit_booking' => $create_params['is_edit_booking'],
738 'is_duplicate_booking' => $create_params['is_duplicate_booking'],
739 'is_approve_booking' => $create_params['is_approve_booking'],
740 'how_many_items_to_book' => $create_params['how_many_items_to_book'],
741 'is_use_booking_recurrent_time' => $create_params['is_use_booking_recurrent_time'] // true | false
742 );
743 if ( ! empty( $create_params['appointment_service'] ) ) { $create_booking_params['appointment_service'] = $create_params['appointment_service']; }
744 if ( ! empty( $create_params['sync_gid'] ) ) { $create_booking_params['sync_gid'] = $create_params['sync_gid']; }
745
746 $booking_new_arr = wpbc_db__booking_save( $create_booking_params, $where_to_save_booking );
747
748 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- BOOKING CREATION " >
749 if ( 'ok' !== $booking_new_arr['status'] ) {
750 $ajx_data_arr['status'] = $booking_new_arr['status'];
751 $ajx_data_arr['status_error'] = 'booking_can_not_save';
752 $ajx_data_arr['ajx_after_action_message'] = $booking_new_arr['message'];
753 $ajx_data_arr['ajx_after_action_message_status'] = 'error';
754 return array( 'ajx_data' => $ajx_data_arr );
755 }
756 // </editor-fold>
757
758 do_action( 'wpbc_booking_after_save', $booking_new_arr['booking_id'], $create_booking_params, $where_to_save_booking );
759
760 // FixIn: 9.9.0.36.
761 if (
762 ( 0 !== $create_params['is_edit_booking'] ) // If edit booking
763 && ( 1 != $create_params['is_duplicate_booking'] ) // If not duplicate
764 ) {
765 // Log the cost info.
766 $is_add_timezone_offset = true;
767 $booking_note = wpbc_date_localized( gmdate( 'Y-m-d H:i:s' ), '[Y-m-d H:i]', $is_add_timezone_offset ) . ' ';
768 $booking_note .= __( 'The booking has been edited', 'booking' ) . '. | Edit URL: ' . esc_url_raw( $re_cleaned_params['request_uri'] ) . '';
769 make_bk_action( 'wpdev_make_update_of_remark', $booking_new_arr['booking_id'], $booking_note, true );
770 }
771 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
772 $php_performance = wpbc_php_performance_END( 'wpbc_db__booking_save' , $php_performance );
773 // </editor-fold>
774
775 // -----------------------------------------------------------------------------------------------------------------
776 // Get payment form(s) and Update COST of the booking
777 // -----------------------------------------------------------------------------------------------------------------
778 $payment_params = array();
779
780 // 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
781 $payment_params['booked_dates_times_arr'] = array(
782 'dates_ymd_arr' => array_keys( $where_to_save_booking['resources_in_dates'] ), // [ 2023-10-20=>[], 2023-10-25=>[] ] -> [ "2023-10-20", "2023-10-25" ]
783 'times_his_arr' => $where_to_save_booking['time_to_book'] // ['16:00:01', '18:00:02']
784 );
785 $str_dates__dd_mm_yyyy = wpbc_convert_dates_arr__yyyy_mm_dd__to__dd_mm_yyyy( $payment_params['booked_dates_times_arr']['dates_ymd_arr'] ); // ['2023-10-20','2023-10-25'] => ['20.10.2023','25.10.2023']
786 $payment_params['str_dates__dd_mm_yyyy'] = implode( ',', $str_dates__dd_mm_yyyy ); // REQUIRED -- '14.11.2023, 15.11.2023, 16.11.2023, 17.11.2023'
787 $payment_params['booking_id'] = $booking_new_arr['booking_id']; // REQUIRED -- '2'
788 $payment_params['resource_id'] = $create_params['resource_id']; // REQUIRED -- '2' can be child resource (changed in wpbc_where_to_save() )
789 $payment_params['service_id'] = ! empty( $create_params['appointment_service']['service_id'] ) ? absint( $create_params['appointment_service']['service_id'] ) : 0;
790 $payment_params['initial_resource_id'] = $local_params['initial_resource_id']; // REQUIRED -- '2' initial calendar - parent resource
791 $payment_params['form_data'] = $booking_new_arr['form_data']; // we re-save it, because here can be sync_guid and custom form new data from wpbc_db__booking_save(..) // REQUIRED -- 'text^selected_short_timedates_hint4^06/11/2018 14:00...'
792 $payment_params['times_array'] = array(
793 explode( ':', $where_to_save_booking['time_to_book'][0] ), // ["10","00","00"]
794 explode( ':', $where_to_save_booking['time_to_book'][1] ) // ["12","00","00"]
795 );
796 // Additional options
797 $payment_params['is_edit_booking'] = $create_params['is_edit_booking']; // => 0 0 | int - ID of the booking
798 $payment_params['custom_form'] = $create_params['custom_form']; // => '' '' | 'some_name'
799 $payment_params['is_duplicate_booking'] = $create_params['is_duplicate_booking']; // => 0 0 | 1
800 $payment_params['is_from_admin_panel'] = $create_params['is_from_admin_panel']; // => false true | false
801 $payment_params['is_show_payment_form'] = $create_params['is_show_payment_form']; // => 1 0 | 1
802 $payment_params['wpbc_admin_cost_correction'] = $re_cleaned_params['wpbc_admin_cost_correction'];
803 if ( $payment_params['is_from_admin_panel'] ) {
804 // $payment_params['is_show_payment_form'] = 0; // FixIn: 9.9.0.21.
805 }
806 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
807 $php_performance = wpbc_php_performance_START( 'wpbc_maybe_get_payment_form' , $php_performance );
808 // </editor-fold>
809
810 // GET PAYMENT FORMS ===============================================================================================
811 if ( function_exists( 'wpbc_maybe_get_payment_form' ) ) {
812
813 $response__payment_form__arr = wpbc_maybe_get_payment_form( $payment_params );
814
815 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- COSTS || PAYMENT_SYSTEMS " >
816 if (
817 ( ! empty( $response__payment_form__arr['status'] ) )
818 && ( 'ok' != $response__payment_form__arr['status'] )
819 ) {
820 $ajx_data_arr['status'] = $response__payment_form__arr['status'];
821 $ajx_data_arr['status_error'] = 'booking_can_not_save';
822 $ajx_data_arr['ajx_after_action_message'] = $response__payment_form__arr['message'];
823 $ajx_data_arr['ajx_after_action_message_status'] = 'error';
824 return array( 'ajx_data' => $ajx_data_arr );
825 }
826 // </editor-fold>
827
828 if ( ! empty( $response__payment_form__arr['costs_arr']['form_data'] ) ) {
829 $payment_params['form_data'] = $response__payment_form__arr['costs_arr']['form_data']; // we re-save it, because here can be [cost_correction] shortcode data
830 }
831 //TODO: Do we really need this in output $ajx_data_arr ???, because it stored in $confirmation
832 if ( ! empty( $response__payment_form__arr['gateways_output_arr'] ) ) {
833 $ajx_data_arr['gateways_output_arr'] = $response__payment_form__arr['gateways_output_arr'];
834 }
835 if ( function_exists( 'wpbc_if_zero_cost__approve_booking_dates' ) ) {
836 wpbc_if_zero_cost__approve_booking_dates( $payment_params['booking_id'] );
837 }
838
839 } else {
840 $response__payment_form__arr = $payment_params;
841 $response__payment_form__arr['status'] = 'ok';
842 }
843
844
845 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
846 $php_performance = wpbc_php_performance_END( 'wpbc_maybe_get_payment_form' , $php_performance );
847
848 $php_performance = wpbc_php_performance_START( 'emails_sending' , $php_performance );
849 // </editor-fold>
850
851 // -----------------------------------------------------------------------------------------------------------------
852 // == Emails ===
853 // -----------------------------------------------------------------------------------------------------------------
854 if ( 1 == $re_cleaned_params['is_emails_send'] ) {
855
856 $email_content = $payment_params['form_data'];
857
858 // If we output any text, then probably it's errors or warnings, we need to catch them
859 ob_start();
860 ob_clean();
861
862 if (
863 ( 0 === $local_params['is_edit_booking'] )
864 || ( 1 === $local_params['is_duplicate_booking'] ) // FixIn: 10.0.0.42.
865 ){
866
867 // New booking to Admin
868 wpbc_send_email_new_admin( $payment_params['booking_id'], $payment_params['resource_id'], $email_content );
869
870 // New pending to Visitor
871 wpbc_send_email_new_visitor( $payment_params['booking_id'], $payment_params['resource_id'], $email_content ) ;
872
873 $is_booking_approved = wpbc_is_booking_approved( $payment_params['booking_id'] );
874 if ( $is_booking_approved ) {
875 // New approved to Visitor / Admin
876 wpbc_send_email_approved( $payment_params['booking_id'], 1 );
877 }
878
879 do_action( 'wpbc_booking_is_approved_during_creation' , $payment_params['booking_id'] , (int) $is_booking_approved ); // FixIn: 10.10.1.1.
880
881 // Payment request from admin panel, if needed
882 if(
883 ( $payment_params['is_from_admin_panel'] )
884 && ( 'On' == get_bk_option( 'booking_payment_request_auto_send_in_bap' ) )
885 && ( function_exists( 'wpbc_send_email_payment_request' ) )
886 ){
887 $payment_reason = '';
888 $is_send = wpbc_send_email_payment_request( $payment_params['booking_id'], $payment_params['resource_id'], $email_content , $payment_reason );
889 }
890
891
892
893 } else {
894
895 // Edited booking to Visitor / Admin
896 if ( function_exists( 'wpbc_send_email_modified' ) ) {
897 wpbc_send_email_modified( $payment_params['booking_id'], $payment_params['resource_id'], $email_content );
898 }
899 }
900
901 $errors_on_email_sending_html = ob_get_contents();
902 ob_end_clean();
903
904 if ( ! empty( $errors_on_email_sending_html ) ) {
905 // Show these messages as warning after creation of the booking
906 $errors_on_email_sending_html = wp_strip_all_tags( $errors_on_email_sending_html );
907 $errors_on_email_sending_html = esc_attr( $errors_on_email_sending_html );
908 $errors_on_email_sending_html = str_replace( "\\n", '', $errors_on_email_sending_html );
909
910 $ajx_data_arr['ajx_after_action_message_status'] = 'warning';
911 $ajx_data_arr['ajx_after_action_message'] = $errors_on_email_sending_html;
912 }
913 }
914
915 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
916 $php_performance = wpbc_php_performance_END( 'emails_sending' , $php_performance );
917 // </editor-fold>
918
919 // -----------------------------------------------------------------------------------------------------------------
920 // == Track booking - New | Edit ===
921 // -----------------------------------------------------------------------------------------------------------------
922 /**
923 * Useful hook for Google Ads Conversion tracking. How to use this hook?
924 *
925 * Add code similar to this in your functions.php file in your theme, or in some other php file:
926 *
927 // Track adding new booking
928 //
929 // @param $params = array (
930 // 'str_dates__dd_mm_yyyy' => '08.10.2023,09.10.2023,10.10.2023,11.10.2023',
931 // 'booking_id' => 254,
932 // 'resource_id' => 11, // child or parent or single
933 // 'initial_resource_id' => 2, // parent or single
934 // 'form_data' => 'text^selected_short_dates_hint11^Sun...',
935 // 'times_array' => array ( array ( '14', '00', '01' ), array( '12', '00', '02' ) ),
936 // 'is_edit_booking' => 0,
937 // 'custom_form' => '',
938 // 'is_duplicate_booking' => 0,
939 // 'is_from_admin_panel' => false,
940 // 'is_show_payment_form' => 1
941 // )
942 function my_booking_tracking( $params ){
943 // Your code here
944 ?><!-- Google Code for Booking Conversion Page -->
945 <script type="text/javascript">
946 // Insert bellow your Google Conversion Code
947 </script><?php
948 }
949 add_action( 'wpbc_track_new_booking', 'my_booking_tracking' );
950 *
951 *
952 *
953 * Useful hook booking edit tracking
954 *
955 * Add code similar to this in your functions.php file in your theme, or in some other php file:
956 *
957 // Track edit existing booking
958 //
959 // @param $params = array (
960 // 'str_dates__dd_mm_yyyy' => '08.10.2023,09.10.2023,10.10.2023,11.10.2023',
961 // 'booking_id' => 254,
962 // 'resource_id' => 11, // child or parent or single
963 // 'initial_resource_id' => 2, // parent or single
964 // 'form_data' => 'text^selected_short_dates_hint11^Sun...',
965 // 'times_array' => array ( array ( '14', '00', '01' ), array( '12', '00', '02' ) ),
966 // 'is_edit_booking' => 1,
967 // 'custom_form' => '',
968 // 'is_duplicate_booking' => 0,
969 // 'is_from_admin_panel' => false,
970 // 'is_show_payment_form' => 1
971 // )
972 function my_edit_booking_tracking( $params ){
973 // Your code here
974 ?><!-- Google Code for Booking Conversion Page -->
975 <script type="text/javascript">
976 // Insert bellow your Google Conversion Code
977 </script><?php
978
979 }
980 add_action( 'wpbc_track_edit_booking', 'my_edit_booking_tracking' );
981 */
982 if ( 0 === $local_params['is_edit_booking'] ) {
983 do_action( 'wpbc_track_new_booking', $payment_params );
984 } else {
985 do_action( 'wpbc_track_edit_booking', $payment_params );
986 }
987
988
989 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
990 $php_performance = wpbc_php_performance_START( 'confirmation' , $php_performance );
991 // </editor-fold>
992
993 // <editor-fold defaultstate="collapsed" desc=" == Confirmation data == " >
994
995
996 $confirmation_params_arr = array(
997 'is_from_admin_panel' => $payment_params['is_from_admin_panel'],
998
999 'booking_id' => $booking_new_arr['booking_id'], // 16102023
1000 'resource_id' => $payment_params['resource_id'], // 1
1001 'form_data' => $payment_params['form_data'], // 'text^selected_short_dates_hint11^Sun...',
1002 'dates_ymd_arr' => $payment_params['booked_dates_times_arr']['dates_ymd_arr'], // [ '2023-10-20', '2023-10-25' ]
1003 'times_his_arr' => $payment_params['booked_dates_times_arr']['times_his_arr'], // [ '16:00:01', '18:00:02' ]
1004
1005 'total_cost' => ( isset( $response__payment_form__arr['costs_arr'] ) && isset( $response__payment_form__arr['costs_arr']['total_cost'] ) )
1006 ? $response__payment_form__arr['costs_arr']['total_cost']
1007 : 0,
1008 'deposit_cost' => ( isset( $response__payment_form__arr['costs_arr'] ) && isset( $response__payment_form__arr['costs_arr']['deposit_cost'] ) )
1009 ? $response__payment_form__arr['costs_arr']['deposit_cost']
1010 : 0,
1011 'booking_summary' => ( ( ! empty( $response__payment_form__arr['gateways_output_arr'] ) ) && ( ! empty( $response__payment_form__arr['gateways_output_arr']['booking_summary'] ) ) )
1012 ? $response__payment_form__arr['gateways_output_arr']['booking_summary']
1013 : '',
1014 'gateway_rows' => ( ( ! empty( $response__payment_form__arr['gateways_output_arr'] ) ) && ( ! empty( $response__payment_form__arr['gateways_output_arr']['gateway_rows'] ) ) )
1015 ? $response__payment_form__arr['gateways_output_arr']['gateway_rows']
1016 : ''
1017 );
1018 // It will not show payment form in Booking > Add booking page and if defined, do not make redirect
1019 if ( $payment_params['is_from_admin_panel'] ) {
1020
1021 $confirmation_params_arr['ty_is_redirect'] = 'message'; // Do not make redirect, if it's in admin panel!
1022
1023 // But if we edit / duplicate the booking, then do redirection to Booking Listing page // FixIn: 9.9.0.3.
1024 if (
1025 ( 0 !== $local_params['is_edit_booking'] )
1026 // && ( empty( $local_params['is_duplicate_booking'] ) )
1027 ){
1028 $confirmation_params_arr['ty_is_redirect'] = 'page';
1029 $confirmation_params_arr['ty_url'] = wpbc_get_bookings_url() . '&tab=vm_booking_listing&wh_booking_id=' . $confirmation_params_arr['booking_id'];
1030 }
1031 }
1032 $confirmation = wpbc_booking_confirmation( $confirmation_params_arr );
1033
1034 // </editor-fold>
1035
1036 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
1037 $php_performance = wpbc_php_performance_END( 'confirmation' , $php_performance );
1038 // </editor-fold>
1039
1040
1041 make_bk_action( 'finish_check_multiuser_params_for_client_side', $create_params['resource_id'] ); // Deactivate working with specific user in WP MU
1042
1043
1044 // <editor-fold defaultstate="collapsed" desc=" = PERFORMANCE = " >
1045 $php_performance = wpbc_php_performance_END( 'total' , $php_performance );
1046 $php_performance['other_code'] = - 1 * array_reduce( $php_performance,
1047 function ( $sum, $item ) {
1048 $sum += $item;
1049 return $sum;
1050 }
1051 , - 2 * $php_performance['total'] ); // PERFORMANCE OTHER - after TOTAL
1052 // </editor-fold>
1053
1054 wpbc_clear_request_form_context();
1055
1056 return array( 'ajx_data' => $ajx_data_arr, // [ 'status' => "ok", 'wpbc_payment_output' => "<p>Dear John<br..." ]
1057 'booking_id' => $booking_new_arr['booking_id'], // 254
1058 'booking_arr' => $payment_params,
1059 'php_performance' => $php_performance, // [ ... ]
1060 'confirmation' => $confirmation // [ ]
1061 );
1062 }
1063
1064
1065 // ---------------------------------------------------------------------------------------------------------------------
1066 // New booking creation sub functions
1067 // ---------------------------------------------------------------------------------------------------------------------
1068
1069 /**
1070 * Save booking in DB
1071 *
1072 * @param array $create_params = [
1073 * 'resource_id' => 10,
1074 * 'dates_only_sql_arr' => [ '2023-10-04', '2023-10-05', '2023-10-06'],
1075 * 'time_as_his_arr' => [ '14:00:01', '16:00:02' ],
1076 * 'is_from_admin_panel' => false,
1077 * 'is_edit_booking' => 0,
1078 * 'is_duplicate_booking' => 0,
1079 * 'is_approve_booking' => 0,
1080 * 'how_many_items_to_book' => 2,
1081 * 'custom_form' => '',
1082 * 'is_use_booking_recurrent_time' => false,
1083 * 'all_booking_data_arr' => [ 'rangetime' => [
1084 * 'type' => 'selectbox-multiple',
1085 * 'original_name' => 'rangetime2[]', //NOTE: here RESOURCE ID (2) can be from Parent resource, but resource_id can rely on child (10) resource
1086 * 'name' => 'rangetime',
1087 * 'value' => '14:00 - 16:00',
1088 * ],
1089 * 'name' => [ 'type' => 'text', 'original_name' => 'name2', ... ],
1090 * ...
1091 * ]
1092 * ]
1093 *
1094 * @param $where_to_save_booking = [
1095 * 'result' = 'ok',
1096 * 'main__resource_id' = 2
1097 * 'resources_in_dates' = [ 2023-10-18 = [ 2, 12, 10, 11 ]
1098 * 2023-10-19 = [ 2, 12, 10, 11 ]
1099 * 2023-10-20 = [ 2, 12, 10, 11 ]
1100 * ],
1101 * 'time_to_book' = [ "14:00:01" , "16:00:02" ]
1102 * ]
1103 *
1104 * @return [
1105 * 'status' => 'ok' 'ok' | 'error' | 'warning'
1106 * 'booking_id' => 100 int
1107 * 'message' => '' 'If error, then here can be description of error'
1108 * '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,
1109 * ]
1110 */
1111 function wpbc_db__booking_save( &$create_params, &$where_to_save_booking ) {
1112 //FixIn: 10.11.5.4
1113 /**
1114 * Tip: $create_params['all_booking_data_arr'] - contain: [ 'field_name' => [ 'type' = "checkbox", 'original_name' = "fixed_fee2[]", 'name' = "fixed_fee", 'value' = "true" ] , ... ]
1115 * $create_params['structured_booking_data_arr'] - contain: [ 'field_name' => 'field_value' , ... ]
1116 */
1117
1118 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- 'Wrong resource ID " >
1119 if ( empty( $create_params['resource_id'] ) ) {
1120 return array( 'status' => 'error', 'message' => 'Wrong ID of booking resource: ' . $create_params['resource_id'] );
1121 }
1122 // </editor-fold>
1123
1124
1125 $defaults = array(
1126 'is_use_booking_recurrent_time' => ( 'On' === get_bk_option( 'booking_recurrent_time' ) )
1127 );
1128 $create_params = wp_parse_args( $create_params, $defaults );
1129
1130
1131 $sql_field_arr = array();
1132
1133
1134
1135 // Is it was used custom booking form ?
1136 if ( ! empty( $create_params['custom_form'] ) ) {
1137 $create_params['all_booking_data_arr']['wpbc_custom_booking_form'] = array(
1138 'type' => 'text',
1139 'original_name' => 'wpbc_custom_booking_form' . $create_params['resource_id'],
1140 'name' => 'wpbc_custom_booking_form',
1141 'value' => $create_params['custom_form']
1142 );
1143 }
1144
1145 if ( ! empty( $create_params['sync_gid'] ) ) {
1146 /**
1147 * Such fields are comming from '../wp-content/plugins/booking/core/sync/wpbc-gcal-class.php' in function run()
1148 */
1149
1150 // Escape any XSS injection from values in booking form
1151 list( $create_params['sync_gid'] ) = wpbc_escape_any_xss_in_arr( array( $create_params['sync_gid'] ) );
1152
1153 $sql_field_arr[] = array( 'name' => 'sync_gid', 'type' => '%s', 'value' => $create_params['sync_gid'] );
1154 }
1155
1156 // Escape any XSS injection from values in booking form
1157 $create_params['all_booking_data_arr'] = wpbc_escape_any_xss_in_arr( $create_params['all_booking_data_arr'] );
1158
1159
1160 //Set LAST check out day as AVAILABLE - remove it
1161 if (
1162 ( 'On' === get_bk_option( 'booking_last_checkout_day_available' ) )
1163 && ( ! empty( $create_params['dates_only_sql_arr'] ) )
1164 && ( count( $create_params['dates_only_sql_arr'] ) > 1 )
1165 ) {
1166 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.
1167 // Delete last item // FixIn: 9.9.0.19.
1168 $resources_in_dates_last_key = key( array_slice( $where_to_save_booking['resources_in_dates'], - 1, 1, true ) );
1169 unset( $where_to_save_booking['resources_in_dates'][ $resources_in_dates_last_key ] );
1170 }
1171
1172 // :: ERROR ::
1173 if ( empty( $create_params['dates_only_sql_arr'] ) ) { // No dates :?
1174 return array( 'status' => 'error', 'message' => 'Sent request with no dates.' );
1175 }
1176
1177 // <editor-fold defaultstate="collapsed" desc=" :: ERROR :: <- CHECK_IN_DATE_OLDER_THAN_CHECK_OUT " >
1178 $is_no_dates_booking = (
1179 function_exists( 'wpbc_is_these_dates__for__no_dates' )
1180 && wpbc_is_these_dates__for__no_dates( $create_params['dates_only_sql_arr'] )
1181 );
1182 if ( ( count( $create_params['dates_only_sql_arr'] ) == 1 ) && ( ! $is_no_dates_booking ) ) { // Is it single selected date ?
1183
1184 // Is 'check in' date/time older than 'check out' date/time when SINGLE day for booking? Then show error.
1185
1186 /**
1187 * If we are having "change over" days activated and selected only 1 day in calendar,
1188 * then we can have error: "Warning! Number of check in != check out times.", because "check in" day older than "check out" date.
1189 */
1190
1191 $is_apply__check_in_out__10s = false;
1192 $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 );
1193 $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 );
1194
1195
1196 if ( $datestamp_check_in > $datestamp_check_out ) {
1197 $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.'
1198 , '<strong>' . wpbc_convert__seconds__to_sql_date( $datestamp_check_in, $is_apply__check_in_out__10s ) . '</strong>'
1199 , '<strong>' . wpbc_convert__seconds__to_sql_date( $datestamp_check_out, $is_apply__check_in_out__10s ) . '</strong>'
1200 );
1201 $error_message .= '<br>' . '<strong>Settings that can be reason of the issue:</strong> ';
1202 if( 'On' === get_bk_option( 'booking_last_checkout_day_available' )){
1203 $error_message .= '<br>' . 'You have enabled <strong>"Set check out date as available"</strong> option at Booking > Settings General page in "Calendar" section. ';
1204 }
1205 if ( 'On' === get_bk_option( 'booking_range_selection_time_is_active' ) ){
1206 $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' );
1207 }
1208 if ( $create_params['is_use_booking_recurrent_time'] ) {
1209 $error_message .= '<br>' . 'You have enabled <strong>"Use time selections as recurrent time slots"</strong> option at Booking > Settings General page in "Calendar" section. ';
1210 }
1211 return array( 'status' => 'error', 'message' => $error_message );
1212 }
1213 }
1214 // </editor-fold>
1215
1216 // Compose form data for DB. Can be different resource: 'selectbox-multiple^rangetime10[]^14..' <-> 'selectbox-multiple^rangetime2[]^14..' -> 'rangetime10' - child res. Previously 'rangetime2' - parent
1217 $form_data = wpbc_encode_booking_data_to_string( $create_params['all_booking_data_arr'], $create_params['resource_id'] );
1218
1219
1220 // -----------------------------------------------------------------------------------------------------------------
1221 // APPROVED / PENDING
1222 // -----------------------------------------------------------------------------------------------------------------
1223 // Is approve booking
1224 $auto_approve_new_bookings_is_active = trim( get_bk_option( 'booking_auto_approve_new_bookings_is_active' ) );
1225 $is_approved_dates = ( $auto_approve_new_bookings_is_active == 'On' ) ? 1 : intval( $create_params['is_approve_booking'] );
1226
1227 // Auto approve only for specific booking resources
1228 /**
1229 * How to use "Auto approve bookings only for specific booking resources" ?
1230 * Add code similar to this in your functions.php file in your theme, or in some other php file:
1231 *
1232 function my_wpbc_get_booking_resources_arr_to_auto_approve( $resources_to_approve ) {
1233 $resources_to_approve = array( 9, 12, 33 ); // Array of booking resources ID, which you need to auto approve
1234 return $resources_to_approve;
1235 }
1236 add_filter( 'wpbc_get_booking_resources_arr_to_auto_approve', 'my_wpbc_get_booking_resources_arr_to_auto_approve' );
1237 */
1238 $booking_resources_to_approve = array();
1239 $booking_resources_to_approve = apply_filters( 'wpbc_get_booking_resources_arr_to_auto_approve', $booking_resources_to_approve ); // FixIn: 8.5.2.27.
1240 if ( in_array( $create_params['resource_id'], $booking_resources_to_approve ) ) {
1241 $is_approved_dates = 1;
1242 }
1243
1244 if (
1245 ( $create_params['is_from_admin_panel'] ) // true | false
1246 && ( get_bk_option( 'booking_auto_approve_bookings_if_added_in_admin_panel' ) == 'On' )
1247 ){ // FixIn: 8.1.3.27.
1248 $is_approved_dates = 1;
1249 }
1250
1251 // If the booking auto-approved, then we need to mark it as "Read".
1252 if ( $is_approved_dates ) {
1253 $sql_field_arr[] = array(
1254 'name' => 'is_new',
1255 'type' => '%d',
1256 'value' => 0,
1257 );
1258 }
1259
1260 // <editor-fold defaultstate="collapsed" desc=" == Save Booking == " >
1261 // -----------------------------------------------------------------------------------------------------------------
1262 // Save Booking
1263 // -----------------------------------------------------------------------------------------------------------------
1264 global $wpdb;
1265
1266 $sql_field_arr[] = array( 'name' => 'form', 'type' => '%s', 'value' => $form_data );
1267 $sql_field_arr[] = array( 'name' => 'booking_type', 'type' => '%d', 'value' => $create_params['resource_id'] );
1268 $sql_field_arr[] = array( 'name' => 'modification_date', 'type' => '%s', 'value' => gmdate( 'Y-m-d H:i:s' ) );
1269 $sql_field_arr[] = array( 'name' => 'sort_date', 'type' => '%s', 'value' => $create_params['dates_only_sql_arr'][0] . ' ' . $create_params['time_as_his_arr'][0] );
1270 $sql_field_arr[] = array( 'name' => 'hash', 'type' => '%s', 'value' => wpbc_hash__generate_booking_hash() );
1271
1272
1273 if (
1274 ( 0 == $create_params['is_edit_booking'] ) || // If not edit, then INSERT.
1275 ( 1 == $create_params['is_duplicate_booking'] ) // If duplicate, then INSERT.
1276 ) {
1277 // Saved only for new booking creation.
1278 $sql_field_arr[] = array(
1279 'name' => 'creation_date',
1280 'type' => '%s',
1281 'value' => gmdate( 'Y-m-d H:i:s' ),
1282 );
1283
1284 $sql_prepare_arr = array();
1285 $sql_prepare_arr['name'] = array_map( function ( $value ) { return $value['name']; }, $sql_field_arr );
1286 $sql_prepare_arr['type'] = array_map( function ( $value ) { return $value['type']; }, $sql_field_arr );
1287 $sql_prepare_arr['value'] = array_map( function ( $value ) { return $value['value']; }, $sql_field_arr );
1288
1289 $sql_prepare_arr['name'] = implode( ', ', $sql_prepare_arr['name'] );
1290 $sql_prepare_arr['type'] = implode( ', ', $sql_prepare_arr['type'] );
1291 /* phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare */
1292 $sql = $wpdb->prepare( "INSERT INTO {$wpdb->prefix}booking " . " ( {$sql_prepare_arr['name']} )" . " VALUES ( {$sql_prepare_arr['type']} )", $sql_prepare_arr['value'] );
1293 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1294 if ( false === $wpdb->query( $sql ) ) {
1295 return array(
1296 'status' => 'error',
1297 'message' => __( 'The booking could not be saved because of a database error. Please try again or contact the website administrator.', 'booking' ),
1298 );
1299 }
1300 // Get ID of booking
1301 $booking_id = (int) $wpdb->insert_id;
1302
1303 } else { // Edit - UPDATE
1304
1305 $booking_id = (int) $create_params['is_edit_booking'];
1306
1307 $sql_prepare_arr = array();
1308 $sql_prepare_arr['set'] = array_map( function ( $value ) { return $value['name'] . '=' . $value['type']; }, $sql_field_arr );
1309 $sql_prepare_arr['value'] = array_map( function ( $value ) { return $value['value']; }, $sql_field_arr );
1310
1311 $sql_prepare_arr['set'] = implode( ', ', $sql_prepare_arr['set'] );
1312
1313 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1314 $sql = $wpdb->prepare( "UPDATE {$wpdb->prefix}booking SET {$sql_prepare_arr['set']} WHERE booking_id={$booking_id};", $sql_prepare_arr['value'] );
1315 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1316 if ( false === $wpdb->query( $sql ) ) {
1317 return array(
1318 'status' => 'error',
1319 'message' => __( 'The booking could not be updated because of a database error. Please try again or contact the website administrator.', 'booking' ),
1320 );
1321 }
1322
1323 // Check if dates previously was approved.
1324 $slct_sql = "SELECT approved FROM {$wpdb->prefix}bookingdates WHERE booking_id IN ({$booking_id}) LIMIT 0,1";
1325 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1326 $slct_sql_results = $wpdb->get_results( $slct_sql );
1327 $is_approved_dates = ( count( $slct_sql_results ) > 0 ) ? $slct_sql_results[0]->approved : $is_approved_dates;
1328
1329
1330 $delete_sql = "DELETE FROM {$wpdb->prefix}bookingdates WHERE booking_id IN ({$booking_id})";
1331 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1332 if ( false === $wpdb->query( $delete_sql ) ) {
1333 return array( 'status' => 'error', 'message' => 'Error. DELETE Old Dates in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $delete_sql );
1334 }
1335 }
1336 // </editor-fold>
1337
1338
1339 // <editor-fold defaultstate="collapsed" desc=" == Compose D A T E S for DB == " >
1340 // -----------------------------------------------------------------------------------------------------------------
1341 // Compose D A T E S for DB
1342 // -----------------------------------------------------------------------------------------------------------------
1343
1344 if ( class_exists( 'wpdev_bk_biz_l' ) ) {
1345 $field_names_arr = array( 'booking_id', 'booking_date', 'approved', 'type_id' );
1346 } else {
1347 $field_names_arr = array( 'booking_id', 'booking_date', 'approved' );
1348 }
1349 $field_names = implode( ', ', $field_names_arr );
1350
1351 $dates_sql = "INSERT INTO {$wpdb->prefix}bookingdates ( {$field_names} ) VALUES ";
1352 $insert_dates_arr = array();
1353
1354 $how_many_items_to_book = $create_params['how_many_items_to_book'];
1355 // $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
1356 for( $i = 0; $i < $how_many_items_to_book; $i++) {
1357
1358 $date_number = 0;
1359 foreach ( $where_to_save_booking['resources_in_dates'] as $only_date_sql => $resources_in_date_arr ) {
1360
1361 $date_resource_id = $resources_in_date_arr[ $i ];
1362
1363 // $create_params['resource_id'] <- Main resource (saved in wp_booking )
1364 // $only_date_sql <- '2023-09-12'
1365 // $date_resource_id <- Child resource (need to save in wp_bookingdates) OR if ( $create_params['resource_id'] == $date_resource_id ) then NULL
1366
1367 // ---------------------------------------------------------------------------------------------------------
1368 // Is full day booking:
1369 if (
1370 ( '00:00' === substr( $where_to_save_booking['time_to_book'][0], 0, 5 ) )
1371 && (
1372 ( '24:00' === substr( $where_to_save_booking['time_to_book'][1], 0, 5 ) )
1373 || ( '00:00' === substr( $where_to_save_booking['time_to_book'][1], 0, 5 ) )
1374 )
1375 ){
1376 // Full days *******************************************************************************************
1377 $full_date_sql = $only_date_sql . ' 00:00:00';
1378
1379 $insert_dates_arr[] = wpbc_prepare_date_row( $booking_id, $full_date_sql, $is_approved_dates, $date_resource_id, $create_params['resource_id'] );
1380
1381 } else { // Times
1382
1383 if (
1384 ( $create_params['is_use_booking_recurrent_time'] ) // Activated option to book times as 'time-slots' OR
1385 || ( 1 === count( $where_to_save_booking['resources_in_dates'] ) ) // Selected only 1 date, so use time as time-slot
1386 ) {
1387 // Time slots in each day **************************************************************************
1388
1389 // Start Time
1390 $full_date_sql = $only_date_sql . ' ' . $where_to_save_booking['time_to_book'][0];
1391 $insert_dates_arr[] = wpbc_prepare_date_row( $booking_id, $full_date_sql, $is_approved_dates, $date_resource_id, $create_params['resource_id'] );
1392
1393 // End Time
1394 $full_date_sql = $only_date_sql . ' ' . $where_to_save_booking['time_to_book'][1];
1395 $insert_dates_arr[] = wpbc_prepare_date_row( $booking_id, $full_date_sql, $is_approved_dates, $date_resource_id, $create_params['resource_id'] );
1396
1397 } else {
1398 // Check in/out ************************************************************************************
1399
1400 if ( 0 == $date_number ) { // Is check in ?
1401
1402 $full_date_sql = $only_date_sql . ' ' . $where_to_save_booking['time_to_book'][0];
1403
1404 } else if ( ( count( $where_to_save_booking['resources_in_dates'] ) - 1 ) == $date_number ) { // Is check out ?
1405
1406 $full_date_sql = $only_date_sql . ' ' . $where_to_save_booking['time_to_book'][1];
1407
1408 } else { // Middle date - Full Date
1409 $full_date_sql = $only_date_sql . ' 00:00:00';
1410 }
1411
1412 $insert_dates_arr[] = wpbc_prepare_date_row( $booking_id, $full_date_sql, $is_approved_dates, $date_resource_id, $create_params['resource_id'] );
1413
1414 }
1415 }
1416 // ---------------------------------------------------------------------------------------------------------
1417 $date_number++;
1418 }
1419 }
1420
1421 $dates_sql .= implode( ', ', $insert_dates_arr );
1422 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1423 if ( false === $wpdb->query( $dates_sql ) ) {
1424 return array( 'status' => 'error', 'message' => 'Error. INSERT "D A T E S" in DB.' . ' FILE:' . __FILE__ . ' LINE:' . __LINE__ . ' SQL:' . $dates_sql );
1425 }
1426
1427 // -----------------------------------------------------------------------------------------------------------------
1428 // End D A T E S
1429 // -----------------------------------------------------------------------------------------------------------------
1430 // </editor-fold>
1431
1432
1433 return array(
1434 'status' => 'ok'
1435 , 'booking_id' => $booking_id
1436 , 'form_data' => $form_data
1437 , 'message' => ''
1438 );
1439 }
1440
1441
1442 /**
1443 * Get SQL ROWs of VALUES for INSERT to DB
1444 *
1445 * @param int $booking_id 101 ID of the booking
1446 * @param string $full_date_sql '2023-09-12 10:00:01' SQL date
1447 * @param int $is_approved_dates 1 1 - approved, 0 - pending
1448 * @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
1449 * @param int $main_resource_id ( >= biz_l ): 1 ID of main (parent booking resource)
1450 *
1451 * @return string - SQL for dates VALUES to insert
1452 */
1453 function wpbc_prepare_date_row( $booking_id, $full_date_sql, $is_approved_dates, $date_resource_id, $main_resource_id ) {
1454
1455 global $wpdb;
1456
1457 if ( class_exists( 'wpdev_bk_biz_l' ) ) {
1458 $insert_dates_arr = ( $main_resource_id != $date_resource_id )
1459 ? $wpdb->prepare( "(%d, %s, %d, %d)", $booking_id, $full_date_sql, $is_approved_dates, $date_resource_id )
1460 : $wpdb->prepare( "(%d, %s, %d, NULL)", $booking_id, $full_date_sql, $is_approved_dates );
1461 } else {
1462 $insert_dates_arr = $wpdb->prepare( "(%d, %s, %d)", $booking_id, $full_date_sql, $is_approved_dates );
1463 }
1464
1465 return $insert_dates_arr;
1466 }
1467
1468 // == Help functions ==
1469 function wpbc_set_request_form_context( $ctx ) {
1470 $GLOBALS['wpbc_request_form_context'] = ( is_array( $ctx ) ) ? $ctx : array();
1471 }
1472
1473 function wpbc_get_request_form_context() {
1474 return ( isset( $GLOBALS['wpbc_request_form_context'] ) && is_array( $GLOBALS['wpbc_request_form_context'] ) )
1475 ? $GLOBALS['wpbc_request_form_context'] : array();
1476 }
1477
1478 function wpbc_clear_request_form_context() {
1479 if ( isset( $GLOBALS['wpbc_request_form_context'] ) ) {
1480 unset( $GLOBALS['wpbc_request_form_context'] );
1481 }
1482 }
1483
1484 // ---------------------------------------------------------------------------------------------------------------------
1485 // Support
1486 // ---------------------------------------------------------------------------------------------------------------------
1487
1488 /**
1489 * Get booking_id and resource_id if we are editing the booking, by booking hash
1490 *
1491 * @param $booking_hash
1492 * @param $server_request_url
1493 *
1494 * @return array | false false if not edit Otherwise [ 'booking_id': 100, 'resource_id': 3 ]
1495 */
1496 function wpbc_get_data__if_edit_booking( $booking_hash, $server_request_url ) {
1497
1498 $is_edit_booking = false;
1499 if ( ! empty( $booking_hash ) ) {
1500
1501 $my_booking_id_type = wpbc_hash__get_booking_id__resource_id( $booking_hash );
1502 if ( $my_booking_id_type !== false ) {
1503
1504 $is_edit_booking = array();
1505 $is_edit_booking['booking_id'] = intval( $my_booking_id_type[0] );
1506 $is_edit_booking['resource_id'] = intval( $my_booking_id_type[1] );
1507
1508 //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.
1509 // FixIn: 10.10.1.2
1510 //if ( strpos( $server_request_url, 'resource_no_update' ) === false ) { // FixIn: 9.4.2.3.
1511
1512 if ( ( function_exists( 'wpbc_is_this_child_resource' ) ) && ( wpbc_is_this_child_resource( $is_edit_booking['resource_id'] ) ) ) {
1513 $bk_parent_br_id = wpbc_get_parent_resource( $is_edit_booking['resource_id'] );
1514
1515 $is_edit_booking['resource_id'] = intval( $bk_parent_br_id );
1516 }
1517 //}
1518 }
1519 }
1520 return $is_edit_booking;
1521 }
1522
1523
1524 /**
1525 * Get how many items to book. Usually it's from [selectbox visitors "1" ... ] field.
1526 *
1527 * @param booking_form_data__arr = [
1528 * selected_short_dates_hint = "September 27, 2023 - September 28, 2023"
1529 * days_number_hint = "2"
1530 * rangetime = "16:00 - 18:00"
1531 * starttime = "21:00"
1532 * durationtime = "00:30"
1533 * name = "John"
1534 * secondname = "Smith"
1535 * email = "john.smith@server.com"
1536 * visitors = "1"
1537 * children = "0"
1538 * details = ""
1539 * ]
1540 *
1541 * @return int
1542 */
1543 function wpbc_get__how_many_items_to_book__in_booking_form( $booking_form_data__arr, $resource_id ){
1544
1545 $how_many_items_to_book = 1;
1546
1547 //TODO: Check about some URL parameter: '&resource_no_update' to book parent resource as single resource! // FixIn: 10.10.1.2
1548 if (
1549 ( class_exists( 'wpdev_bk_biz_l' ) )
1550 && ( 0 !== wpbc_get_child_resources_number( $resource_id ) ) // Here several child booking resources
1551 ) {
1552 $booking_capacity_field = wpbc_get__booking_capacity_field__name();
1553 if (
1554 ( ! empty( $booking_capacity_field ) )
1555 && ( isset( $booking_form_data__arr[ $booking_capacity_field ] ) )
1556 ){
1557 $how_many_items_to_book = intval( $booking_form_data__arr[ $booking_capacity_field ] ); // Get value of how many booking resources to book
1558 $how_many_items_to_book = ( $how_many_items_to_book > 0 ) ? $how_many_items_to_book : 1;
1559 }
1560 }
1561
1562 return $how_many_items_to_book;
1563 }
1564
1565
1566 /**
1567 * Get capacity field -- 'pure name'
1568 *
1569 * @return string - field name, or empty string - '' if not defined
1570 *
1571 * 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'
1572 * or for standard form: 'select^visitors'
1573 * and here we get only field name: 'visitors'
1574 */
1575 function wpbc_get__booking_capacity_field__name() {
1576
1577 if ( class_exists( 'wpdev_bk_biz_l' ) ) {
1578
1579 if ( 'On' == get_bk_option( 'booking_quantity_control' ) ) {
1580
1581 $booking_capacity_field = get_bk_option( 'booking_capacity_field' );
1582
1583 if ( ! empty( $booking_capacity_field ) ) {
1584
1585 $booking_capacity_field = explode( '^', $booking_capacity_field );
1586
1587 if ( ! empty( $booking_capacity_field ) ) {
1588
1589 $booking_capacity_field_name = $booking_capacity_field[ count( $booking_capacity_field ) - 1 ];
1590
1591 return $booking_capacity_field_name;
1592 }
1593 }
1594 }
1595 }
1596 return '';
1597 }
1598
1599
1600 /**
1601 * 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.
1602 * @param $booking_form_data__arr
1603 *
1604 * @return array [ 0, 24 * 60 * 60 ] OR [ 10*60*60, 14*60*60 ] <- start and end time in seconds
1605 *
1606 * Example:
1607 *
1608 * // Firstly, we need to Get parsed booking form: [ name = "John", secondname = "Smith", email = "john.smith@server.com", visitors = "2",... ]
1609 * $structured_booking_data_arr = wpbc_get_parsed_booking_data_arr( $params["form_data"], $params["resource_id"], array( 'get' => 'value' ) );
1610 *
1611 * // Now get start/end times as seconds: [ 64800, 72000 ]
1612 * $time_as_seconds_arr = wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $structured_booking_data_arr );
1613 */
1614 function wpbc_get_in_booking_form__time_to_book_as_seconds_arr( $booking_form_data__arr ){
1615
1616 $selected_time_fields = wpbc_get__selected_time_fields__in_booking_form__as_arr( $booking_form_data__arr );
1617
1618 // 2.2 Get selected SECONDS to book ---------------------------------------------------------------------------
1619 $time_as_seconds_arr = array( 0, 24 * 60 * 60 ); // Full day booking by default
1620
1621 foreach ( $selected_time_fields as $time_fields_obj ) { // { times_as_seconds: [ 21600, 23400 ], value_option_24h: '06:00 - 06:30', name: 'rangetime'}
1622
1623 if ( false !== strpos( $time_fields_obj['name'], 'rangetime' ) ) {
1624 $time_as_seconds_arr[ 0 ] = $time_fields_obj['times_as_seconds'][ 0 ];
1625 $time_as_seconds_arr[ 1 ] = $time_fields_obj['times_as_seconds'][ 1 ];
1626 //break; // If we have range-time then skip this loop
1627 }
1628 if ( false !== strpos( $time_fields_obj['name'], 'starttime' ) ) {
1629 $time_as_seconds_arr[ 0 ] = $time_fields_obj['times_as_seconds'][ 0 ];
1630 }
1631 if ( false !== strpos( $time_fields_obj['name'], 'endtime' ) ) {
1632 $time_as_seconds_arr[ 1 ] = $time_fields_obj['times_as_seconds'][ 0 ];
1633 }
1634 }
1635
1636 // For duration time we need to make a new loop, because we need to be sure that was defined START_TIME before this,
1637 // and end time was NOT defined, e.g. == (otherwise it's means that we already used END_TIME or RANGE_TIME)
1638 if (
1639 ( ( 0 ) !== $time_as_seconds_arr[ 0 ] )
1640 && ( (24 * 60 * 60 ) === $time_as_seconds_arr[ 1 ] )
1641 ){
1642 foreach ( $selected_time_fields as $time_fields_obj ) { // { times_as_seconds: [ 21600 ], value_option_24h: '06:00', name: 'durationtime'}
1643
1644 if ( false !== strpos( $time_fields_obj['name'], 'durationtime' ) ) {
1645 $time_as_seconds_arr[ 1 ] = $time_as_seconds_arr[ 0 ] + $time_fields_obj['times_as_seconds'][ 0 ];
1646 // FixIn: 10.14.7.1.
1647 while ( $time_as_seconds_arr[1] > ( 24 * 60 * 60 ) ) {
1648 $time_as_seconds_arr[1] = $time_as_seconds_arr[1] - ( 24 * 60 * 60 );
1649 }
1650 break;
1651 }
1652 }
1653 }
1654
1655 return $time_as_seconds_arr;
1656 }
1657
1658
1659 /**
1660 * Determine whether a booking-create request is an authorized administration workflow.
1661 *
1662 * The public booking action is intentionally available to signed-out visitors. A
1663 * Referer, request path, or caller-supplied Boolean therefore cannot establish an
1664 * administrator security context. The Add Booking UI supplies this user-bound nonce,
1665 * and the server independently rechecks login, capability, and MultiUser access.
1666 *
1667 * @param mixed $admin_booking_nonce Candidate Add Booking administration nonce.
1668 *
1669 * @return bool True only for an authorized Add Booking administration request.
1670 */
1671 function wpbc_is_authorized_admin_booking_request( $admin_booking_nonce ) {
1672
1673 if (
1674 ! is_scalar( $admin_booking_nonce )
1675 || '' === trim( (string) $admin_booking_nonce )
1676 || ! is_user_logged_in()
1677 || ! wp_verify_nonce( sanitize_text_field( (string) $admin_booking_nonce ), 'wpbc_admin_booking_create' )
1678 || ! class_exists( 'WPBC_Add_Booking_Component' )
1679 || ! WPBC_Add_Booking_Component::current_user_can_add_booking()
1680 || ! wpbc_is_mu_user_can_be_here( 'activated_user' )
1681 ) {
1682 return false;
1683 }
1684
1685 return true;
1686 }
1687
1688
1689 /**
1690 * Require the signed workflow proof declared by a verified Booking Form context.
1691 *
1692 * Appointment and Resource Selector JavaScript flags are presentation hints only.
1693 * The signed Booking Form context identifies the server-rendered workflow, so removing
1694 * a flag or domain token cannot downgrade that form to a different workflow.
1695 *
1696 * @param array $classic_context Verified Booking Form context.
1697 * @param bool $has_verified_appointment_context Whether Service and Provider proof passed validation.
1698 * @param bool $has_verified_resource_selector_context Whether Resource Selector proof passed validation.
1699 *
1700 * @return true|WP_Error True when the required proof is present, otherwise a safe validation error.
1701 */
1702 function wpbc_booking_create_validate_required_workflow( $classic_context, $has_verified_appointment_context, $has_verified_resource_selector_context ) {
1703
1704 $booking_workflow = isset( $classic_context['booking_workflow'] ) ? sanitize_key( $classic_context['booking_workflow'] ) : '';
1705 if ( 'appointment' === $booking_workflow && ! $has_verified_appointment_context ) {
1706 return new WP_Error( 'appointment_context_required', __( 'The Appointment selection has expired. Please start over and try again.', 'booking' ) );
1707 }
1708 if ( 'resource_selector' === $booking_workflow && ! $has_verified_resource_selector_context ) {
1709 return new WP_Error( 'resource_selector_context_required', __( 'The Booking Resource selection has expired. Please start over and try again.', 'booking' ) );
1710 }
1711
1712 return true;
1713 }
1714
1715
1716 /**
1717 * Remove administrator time-override values from an unauthorized booking request.
1718 *
1719 * The public booking endpoint intentionally accepts unauthenticated requests, so
1720 * sanitizing these values is not sufficient authorization. Clearing every related
1721 * value here prevents a public client from replacing the Booking Form's configured
1722 * time while preserving the capability-protected Add Booking workflow.
1723 *
1724 * @param array $request_params Sanitized booking request parameters.
1725 * @param bool $is_authorized_admin_booking_request Whether the current request is an authorized Add Booking administration request.
1726 *
1727 * @return array Booking request parameters with unauthorized override values removed.
1728 */
1729 function wpbc_restrict_booking_time_override_to_authorized_admin( $request_params, $is_authorized_admin_booking_request ) {
1730
1731 $request_params = is_array( $request_params ) ? $request_params : array();
1732 if ( $is_authorized_admin_booking_request ) {
1733 return $request_params;
1734 }
1735
1736 $request_params['wpbc_time_override_enabled'] = 0;
1737 $request_params['wpbc_time_override_source'] = '';
1738 $request_params['wpbc_time_override_start'] = '';
1739 $request_params['wpbc_time_override_end'] = '';
1740
1741 return $request_params;
1742 }
1743
1744
1745 /**
1746 * Authorize and normalize an administrator cost-correction request value.
1747 *
1748 * Booking creation is intentionally public, so a sanitized numeric value is
1749 * not sufficient authorization. Only capability-protected Add Booking and
1750 * Add Appointment workflows in Business Small or higher may retain this value.
1751 * Missing, malformed, out-of-range, public, and unsupported-edition values
1752 * are reduced to an empty sentinel, which preserves automatic calculation.
1753 *
1754 * @param array $request_params Sanitized booking request parameters.
1755 * @param bool $is_authorized_admin_booking_request Whether this is an authorized administrator booking request.
1756 *
1757 * @return array Booking request parameters with a normalized or empty cost correction.
1758 */
1759 function wpbc_restrict_booking_cost_correction_to_authorized_admin( $request_params, $is_authorized_admin_booking_request ) {
1760
1761 $request_params = is_array( $request_params ) ? $request_params : array();
1762 $raw_cost = isset( $request_params['wpbc_admin_cost_correction'] ) ? $request_params['wpbc_admin_cost_correction'] : '';
1763
1764 $request_params['wpbc_admin_cost_correction'] = '';
1765 if ( ! $is_authorized_admin_booking_request || ! class_exists( 'wpdev_bk_biz_s' ) ) {
1766 return $request_params;
1767 }
1768
1769 $request_params['wpbc_admin_cost_correction'] = wpbc_sanitize_booking_cost_correction( $raw_cost );
1770
1771 return $request_params;
1772 }
1773
1774
1775 /**
1776 * Sanitize one exact administrator-entered Booking total.
1777 *
1778 * @param mixed $raw_cost Raw request value.
1779 *
1780 * @return string Normalized decimal without trailing zeroes, or an empty string when invalid.
1781 */
1782 function wpbc_sanitize_booking_cost_correction( $raw_cost ) {
1783
1784 if ( ! is_scalar( $raw_cost ) ) {
1785 return '';
1786 }
1787
1788 $raw_cost = trim( sanitize_text_field( (string) $raw_cost ) );
1789 if ( '' === $raw_cost || ! preg_match( '/^[0-9]{1,10}(?:\.[0-9]{1,8})?$/', $raw_cost ) ) {
1790 return '';
1791 }
1792
1793 $normalized_cost = (float) $raw_cost;
1794 if ( ! is_finite( $normalized_cost ) || $normalized_cost < 0 || $normalized_cost > 1000000000 ) {
1795 return '';
1796 }
1797
1798 $normalized_cost = rtrim( rtrim( number_format( $normalized_cost, 8, '.', '' ), '0' ), '.' );
1799
1800 return '' === $normalized_cost ? '0' : $normalized_cost;
1801 }
1802
1803
1804 /**
1805 * Get explicit admin-selected time override from Add Booking modal request.
1806 *
1807 * @param array $request_params Sanitized booking request params.
1808 *
1809 * @return array Empty array or array with start/end HH:MM values.
1810 */
1811 function wpbc_get_booking_time_override__as_arr( $request_params ) {
1812
1813 if ( empty( $request_params['wpbc_time_override_enabled'] ) ) {
1814 return array();
1815 }
1816
1817 $start_time = wpbc_sanitize_booking_time_override__hm( isset( $request_params['wpbc_time_override_start'] ) ? $request_params['wpbc_time_override_start'] : '' );
1818 $end_time = wpbc_sanitize_booking_time_override__hm( isset( $request_params['wpbc_time_override_end'] ) ? $request_params['wpbc_time_override_end'] : '' );
1819
1820 if (
1821 ( '' === $start_time )
1822 || ( '' === $end_time )
1823 || ( wpbc_booking_time_override__hm_to_seconds( $start_time ) >= wpbc_booking_time_override__hm_to_seconds( $end_time ) )
1824 ) {
1825 return array();
1826 }
1827
1828 return array(
1829 'start' => $start_time,
1830 'end' => $end_time,
1831 'source' => isset( $request_params['wpbc_time_override_source'] ) ? sanitize_key( $request_params['wpbc_time_override_source'] ) : '',
1832 );
1833 }
1834
1835
1836 /**
1837 * Sanitize HH:MM value for admin booking time override.
1838 *
1839 * @param string $time_value Time value.
1840 *
1841 * @return string
1842 */
1843 function wpbc_sanitize_booking_time_override__hm( $time_value ) {
1844
1845 $time_value = trim( sanitize_text_field( (string) $time_value ) );
1846
1847 if ( ! preg_match( '/^([0-9]{1,2}):([0-9]{2})$/', $time_value, $matches ) ) {
1848 return '';
1849 }
1850
1851 $hour = absint( $matches[1] );
1852 $minute = absint( $matches[2] );
1853
1854 if ( $hour > 24 || $minute > 59 || ( 24 === $hour && 0 !== $minute ) ) {
1855 return '';
1856 }
1857
1858 return sprintf( '%02d:%02d', $hour, $minute );
1859 }
1860
1861
1862 /**
1863 * Convert HH:MM to seconds.
1864 *
1865 * @param string $time_value Time value.
1866 *
1867 * @return int
1868 */
1869 function wpbc_booking_time_override__hm_to_seconds( $time_value ) {
1870
1871 $time_arr = explode( ':', (string) $time_value );
1872
1873 return ( absint( $time_arr[0] ) * 60 * 60 ) + ( absint( $time_arr[1] ) * 60 );
1874 }
1875
1876
1877 /**
1878 * Get all time fields in the booking form as array of objects
1879 *
1880 * @param booking_form_data__arr = [
1881 * selected_short_dates_hint = "September 27, 2023 - September 28, 2023"
1882 * days_number_hint = "2"
1883 * rangetime = "16:00 - 18:00"
1884 * starttime = "21:00"
1885 * durationtime = "00:30"
1886 * name = "John"
1887 * secondname = "Smith"
1888 * email = "john.smith@server.com"
1889 * visitors = "1"
1890 * children = "0"
1891 * details = ""
1892 * ]
1893 * @returns []
1894 *
1895 * Example:
1896 * [
1897 * [
1898 * name = "rangetime"
1899 * value_option_24h = "16:00 - 18:00"
1900 * times_as_seconds = [ 57600, 64800 ]
1901 * ]
1902 * [
1903 * name = "starttime"
1904 * value_option_24h = "21:00"
1905 * times_as_seconds = [ 75600 ]
1906 * ]
1907 * [
1908 * name = "durationtime"
1909 * value_option_24h = "00:30"
1910 * times_as_seconds = [ 1800 ]
1911 * ]
1912 * ]
1913 */
1914 function wpbc_get__selected_time_fields__in_booking_form__as_arr( $booking_form_data__arr ){
1915
1916 /**
1917 * Fields with [] like this select[name="rangetime1[]"]
1918 * it's when we have 'multiple' in shortcode: [select* rangetime multiple "06:00 - 06:30" ... ]
1919 */
1920 $time_fields_arr = array( 'rangetime', 'starttime', 'endtime', 'durationtime' );
1921
1922 $time_fields_obj_arr = array();
1923
1924 // Loop all Time Fields
1925 for ( $ctf = 0; $ctf < count( $time_fields_arr ); $ctf ++ ) {
1926
1927 $time_field = $time_fields_arr[ $ctf ];
1928 if ( isset( $booking_form_data__arr[ $time_field ] ) ) {
1929
1930 $value_option_seconds_arr = explode( '-', $booking_form_data__arr[ $time_field ] );
1931 $times_as_seconds_arr = array();
1932
1933 foreach ( $value_option_seconds_arr as $time_val ) {
1934 $time_val = trim( $time_val );
1935 if ( ! empty( $time_val ) ) {
1936 $start_end_times_arr = explode( ':', $time_val );
1937 $time_in_seconds = intval( $start_end_times_arr[0] ) * 60 * 60 + intval( $start_end_times_arr[1] ) * 60;
1938 $times_as_seconds_arr[] = $time_in_seconds;
1939 }
1940 }
1941
1942 if (! empty($times_as_seconds_arr)) {
1943
1944 $time_fields_obj_arr[] = array(
1945 'name' => $time_field,
1946 'value_option_24h' => $booking_form_data__arr[ $time_field ],
1947 'times_as_seconds' => $times_as_seconds_arr
1948 );
1949 }
1950 }
1951 }
1952
1953 return $time_fields_obj_arr;
1954 }
1955
1956
1957 //TODO: 2023-10-13 16:37
1958 // - create new function for confirmation section
1959 // - Update payment request
1960 // - create redirection to the "Thank you." page and show there conformation, and payment request"
1961 // - define in the settings new Stripe and PayPal button design as default !
1962 // - Be sure that 'booking_log_booking_actions' active by default
1963 // - test it in dark theme
1964 // - test closing booked dates if all time slot was booked - it's relative to 'different time slots in dif dates'
1965 // - add migrate support old dates selection variables
1966 // - check and remove other global Js vars
1967 // - Create wizard booking form style with steps ?
1968 // - Check Booking > Availability page relative new functionality of calendar_load !
1969 // - Update last func in wpbc-booking-new.php and remove it.
1970 // - Test capacity in dates and time slots
1971 // - Next 9.9 Customizer Wizard
1972 // - Next 9.9 Update search admin UI
1973 // - Next 9.9 - update functionality in Search functionality based on similar to where_to_save function.
1974 // - Next 9.9 refactor Dates functions.
1975 // Done. - Next 9.9 Update Booking > Settings General page to show tabs vertically in left column
1976 // 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.
1977 // Create the same navigation for Emails.
1978 /**
1979 * TODO: Performance improvement:
1980 * "other_code": 0.003080129623413086
1981 * "wpbc__where_to_save_booking": 0.060331106185913086
1982 * "wpbc_db__booking_save": 0.023384809494018555
1983 * "wpbc_maybe_get_payment_form": 1.3650128841400146 <-
1984 * "emails_sending": 1.5024309158325195 <-
1985 * "total": 2.9542438983917236
1986 */
1987