PluginProbe
Booking Calendar / 11.9
Booking Calendar v11.9
11.9 11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 All 205 releases
booking / includes / booking-appointment / ajax / booking-appointment__validate-time.php

booking-appointment__validate-time.php in Booking Calendar 11.9, at includes/booking-appointment/ajax/booking-appointment__validate-time.php

218 lines 9.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Public read-only Appointment time preflight endpoint.
4 *
5 * @package Booking Calendar
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 /**
13 * Read and validate selected SQL dates from an Appointment preflight request.
14 *
15 * @return array<int,string>|WP_Error Unique YYYY-MM-DD dates or an error.
16 */
17 function wpbc_booking_appointment_get_preflight_dates() {
18 $raw_dates = isset( $_POST['dates'] ) && is_array( $_POST['dates'] ) ? wp_unslash( $_POST['dates'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
19 if ( empty( $raw_dates ) || count( $raw_dates ) > 31 ) {
20 return new WP_Error( 'appointment_dates_invalid', __( 'Select a valid appointment date and try again.', 'booking' ) );
21 }
22
23 $dates = array();
24 foreach ( $raw_dates as $raw_date ) {
25 if ( is_array( $raw_date ) ) {
26 return new WP_Error( 'appointment_dates_invalid', __( 'Select a valid appointment date and try again.', 'booking' ) );
27 }
28 $date = sanitize_text_field( $raw_date );
29 $date_parts = array_map( 'absint', explode( '-', $date ) );
30 if ( $date !== wpbc_sanitize_date( $date ) || 3 !== count( $date_parts ) || ! checkdate( $date_parts[1], $date_parts[2], $date_parts[0] ) ) {
31 return new WP_Error( 'appointment_dates_invalid', __( 'Select a valid appointment date and try again.', 'booking' ) );
32 }
33 $dates[] = $date;
34 }
35
36 $dates = array_values( array_unique( $dates ) );
37 sort( $dates );
38 $first_date = strtotime( reset( $dates ) . ' 00:00:00 UTC' );
39 $last_date = strtotime( end( $dates ) . ' 00:00:00 UTC' );
40 if ( false === $first_date || false === $last_date || ( $last_date - $first_date ) > YEAR_IN_SECONDS ) {
41 return new WP_Error( 'appointment_dates_invalid', __( 'Select appointment dates within one year and try again.', 'booking' ) );
42 }
43
44 return $dates;
45 }
46
47 /**
48 * Convert one strict browser start-time value to seconds in the day.
49 *
50 * @return int|WP_Error Start time in seconds, including zero for midnight.
51 */
52 function wpbc_booking_appointment_get_preflight_start_seconds( $start_time = null ) {
53 if ( null === $start_time ) {
54 $start_time = isset( $_POST['start_time'] ) && ! is_array( $_POST['start_time'] ) ? sanitize_text_field( wp_unslash( $_POST['start_time'] ) ) : '';
55 } else {
56 $start_time = is_scalar( $start_time ) ? sanitize_text_field( (string) $start_time ) : '';
57 }
58 if ( ! preg_match( '/^(?:[01]?\d|2[0-3]):[0-5]\d(?::[0-5]\d)?$/', $start_time ) ) {
59 return new WP_Error( 'appointment_start_time_invalid', __( 'Select a valid start time and try again.', 'booking' ) );
60 }
61
62 $parts = array_map( 'absint', explode( ':', $start_time ) );
63 return ( $parts[0] * HOUR_IN_SECONDS ) + ( $parts[1] * MINUTE_IN_SECONDS ) + ( isset( $parts[2] ) ? $parts[2] : 0 );
64 }
65
66 /**
67 * Read a bounded list of Start Time options for one bulk availability pass.
68 *
69 * @return string[]|WP_Error Unique strict browser time values or an error.
70 */
71 function wpbc_booking_appointment_get_preflight_start_times() {
72 $raw_times = isset( $_POST['start_times'] ) && is_array( $_POST['start_times'] ) ? wp_unslash( $_POST['start_times'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
73 if ( empty( $raw_times ) || count( $raw_times ) > 1440 ) {
74 return new WP_Error( 'appointment_start_times_invalid', __( 'Available start times could not be checked. Reload the page and try again.', 'booking' ) );
75 }
76
77 $start_times = array();
78 foreach ( $raw_times as $raw_time ) {
79 $start_seconds = wpbc_booking_appointment_get_preflight_start_seconds( $raw_time );
80 if ( is_wp_error( $start_seconds ) ) {
81 return $start_seconds;
82 }
83 $start_times[] = sanitize_text_field( (string) $raw_time );
84 }
85
86 return array_values( array_unique( $start_times ) );
87 }
88
89 /**
90 * Evaluate one start time against Service duration and preloaded intervals.
91 *
92 * @param array $service Effective Service values.
93 * @param string[] $dates Selected SQL dates.
94 * @param string $start_time Strict browser time value.
95 * @param int $maximum_duration Maximum allowed duration in minutes.
96 * @param array<int,array<string,int>> $existing_intervals Existing Provider intervals.
97 * @param int $provider_id Provider resource ID.
98 *
99 * @return array<string,mixed> Public, non-sensitive validation result.
100 */
101 function wpbc_booking_appointment_validate_one_start_time( $service, $dates, $start_time, $maximum_duration, $existing_intervals, $provider_id = 0 ) {
102 $start_seconds = wpbc_booking_appointment_get_preflight_start_seconds( $start_time );
103 if ( is_wp_error( $start_seconds ) ) {
104 return array( 'valid' => false, 'message' => $start_seconds->get_error_message(), 'code' => $start_seconds->get_error_code() );
105 }
106
107 $end_seconds = wpbc_appointment_services_resolve_end_seconds( $service, $start_seconds, $maximum_duration );
108 if ( is_wp_error( $end_seconds ) ) {
109 return array( 'valid' => false, 'message' => $end_seconds->get_error_message(), 'code' => $end_seconds->get_error_code() );
110 }
111
112 $working_time_check = wpbc_appointment_services_check_working_time( $service, $provider_id, $dates, array( $start_seconds, $end_seconds ) );
113 if ( is_wp_error( $working_time_check ) ) {
114 return array(
115 'valid' => false,
116 'message' => $working_time_check->get_error_message(),
117 'code' => $working_time_check->get_error_code(),
118 'start_time' => sanitize_text_field( $start_time ),
119 'end_time' => wpbc_transform__seconds__in__24_hours_his( $end_seconds ),
120 );
121 }
122
123 $buffer_check = wpbc_appointment_services_check_buffer_conflicts_in_intervals( $service, $dates, array( $start_seconds, $end_seconds ), $existing_intervals );
124 if ( is_wp_error( $buffer_check ) ) {
125 return array(
126 'valid' => false,
127 'message' => $buffer_check->get_error_message(),
128 'code' => $buffer_check->get_error_code(),
129 'start_time' => sanitize_text_field( $start_time ),
130 'end_time' => wpbc_transform__seconds__in__24_hours_his( $end_seconds ),
131 );
132 }
133
134 return array(
135 'valid' => true,
136 'message' => '',
137 'code' => '',
138 'start_time' => sanitize_text_field( $start_time ),
139 'end_time' => wpbc_transform__seconds__in__24_hours_his( $end_seconds ),
140 );
141 }
142
143 /**
144 * Validate a selected Appointment time with the same Service rules as save.
145 *
146 * Expected scheduling conflicts return HTTP 200 with `valid: false`; invalid
147 * or tampered request context remains a controlled HTTP error.
148 *
149 * @return void Terminates with a JSON response.
150 */
151 function wpbc_booking_appointment_ajax_validate_time() {
152 if ( false === check_ajax_referer( 'wpbc_booking_appointment_ajax', 'nonce', false ) ) {
153 wp_send_json_error( array( 'message' => __( 'Security check failed. Reload the page and try again.', 'booking' ) ), 403 );
154 }
155
156 $service_id = isset( $_POST['service_id'] ) && ! is_array( $_POST['service_id'] ) ? absint( wp_unslash( $_POST['service_id'] ) ) : 0;
157 $provider_id = isset( $_POST['provider_id'] ) && ! is_array( $_POST['provider_id'] ) ? absint( wp_unslash( $_POST['provider_id'] ) ) : 0;
158 $context_token = isset( $_POST['context_token'] ) && ! is_array( $_POST['context_token'] ) ? sanitize_text_field( wp_unslash( $_POST['context_token'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
159 $context_check = wpbc_booking_appointment_validate_submission_context( $context_token, $service_id, $provider_id );
160 if ( is_wp_error( $context_check ) ) {
161 wp_send_json_error( array( 'message' => $context_check->get_error_message(), 'code' => $context_check->get_error_code() ), 400 );
162 }
163
164 $dates = wpbc_booking_appointment_get_preflight_dates();
165 if ( is_wp_error( $dates ) ) {
166 $error = $dates;
167 wp_send_json_error( array( 'message' => $error->get_error_message(), 'code' => $error->get_error_code() ), 400 );
168 }
169
170 $service = wpbc_appointment_services_repository()->find_active_for_resource( $service_id, $provider_id );
171 if ( is_wp_error( $service ) ) {
172 wp_send_json_error( array( 'message' => $service->get_error_message(), 'code' => $service->get_error_code() ), 400 );
173 }
174
175 $maximum_duration = absint( apply_filters( 'wpbc_booking_appointment_maximum_duration_minutes', 24 * 60, $context_check ) );
176 $existing_intervals = wpbc_appointment_services_get_existing_buffer_intervals( $provider_id, $dates );
177
178 if ( isset( $_POST['start_times'] ) ) {
179 $start_times = wpbc_booking_appointment_get_preflight_start_times();
180 if ( is_wp_error( $start_times ) ) {
181 wp_send_json_error( array( 'message' => $start_times->get_error_message(), 'code' => $start_times->get_error_code() ), 400 );
182 }
183
184 $slots = array();
185 foreach ( $start_times as $start_time ) {
186 $slots[ $start_time ] = wpbc_booking_appointment_validate_one_start_time( $service, $dates, $start_time, $maximum_duration, $existing_intervals, $provider_id );
187 }
188 wp_send_json_success(
189 array(
190 'valid' => true,
191 'slots' => $slots,
192 'duration' => absint( $service['duration_minutes'] ),
193 'buffer_before' => absint( $service['buffer_before_minutes'] ),
194 'buffer_after' => absint( $service['buffer_after_minutes'] ),
195 )
196 );
197 }
198
199 $start_time = isset( $_POST['start_time'] ) && ! is_array( $_POST['start_time'] ) ? sanitize_text_field( wp_unslash( $_POST['start_time'] ) ) : '';
200 $result = wpbc_booking_appointment_validate_one_start_time( $service, $dates, $start_time, $maximum_duration, $existing_intervals, $provider_id );
201 if ( empty( $result['valid'] ) ) {
202 wp_send_json_success( $result );
203 }
204
205 wp_send_json_success(
206 array_merge(
207 $result,
208 array(
209 'duration' => absint( $service['duration_minutes'] ),
210 'buffer_before' => absint( $service['buffer_before_minutes'] ),
211 'buffer_after' => absint( $service['buffer_after_minutes'] ),
212 )
213 )
214 );
215 }
216 add_action( 'wp_ajax_nopriv_WPBC_AJX_BOOKING_APPOINTMENT_VALIDATE_TIME', 'wpbc_booking_appointment_ajax_validate_time' );
217 add_action( 'wp_ajax_WPBC_AJX_BOOKING_APPOINTMENT_VALIDATE_TIME', 'wpbc_booking_appointment_ajax_validate_time' );
218