PluginProbe
Booking Calendar / 11.7
Booking Calendar v11.7
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 10.11.4 All 201 releases
booking / includes / _capacity / create_booking.php

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

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