PluginProbe
Booking Calendar / 11.8
Booking Calendar v11.8
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, at includes/_capacity/create_booking.php

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