PluginProbe
Booking Calendar / 11.6
Booking Calendar v11.6
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.6, at includes/booking-appointment/ajax/booking-appointment__validate-time.php

206 lines 9.2 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 *
98 * @return array<string,mixed> Public, non-sensitive validation result.
99 */
100 function wpbc_booking_appointment_validate_one_start_time( $service, $dates, $start_time, $maximum_duration, $existing_intervals ) {
101 $start_seconds = wpbc_booking_appointment_get_preflight_start_seconds( $start_time );
102 if ( is_wp_error( $start_seconds ) ) {
103 return array( 'valid' => false, 'message' => $start_seconds->get_error_message(), 'code' => $start_seconds->get_error_code() );
104 }
105
106 $end_seconds = wpbc_appointment_services_resolve_end_seconds( $service, $start_seconds, $maximum_duration );
107 if ( is_wp_error( $end_seconds ) ) {
108 return array( 'valid' => false, 'message' => $end_seconds->get_error_message(), 'code' => $end_seconds->get_error_code() );
109 }
110
111 $buffer_check = wpbc_appointment_services_check_buffer_conflicts_in_intervals( $service, $dates, array( $start_seconds, $end_seconds ), $existing_intervals );
112 if ( is_wp_error( $buffer_check ) ) {
113 return array(
114 'valid' => false,
115 'message' => $buffer_check->get_error_message(),
116 'code' => $buffer_check->get_error_code(),
117 'start_time' => sanitize_text_field( $start_time ),
118 'end_time' => wpbc_transform__seconds__in__24_hours_his( $end_seconds ),
119 );
120 }
121
122 return array(
123 'valid' => true,
124 'message' => '',
125 'code' => '',
126 'start_time' => sanitize_text_field( $start_time ),
127 'end_time' => wpbc_transform__seconds__in__24_hours_his( $end_seconds ),
128 );
129 }
130
131 /**
132 * Validate a selected Appointment time with the same Service rules as save.
133 *
134 * Expected scheduling conflicts return HTTP 200 with `valid: false`; invalid
135 * or tampered request context remains a controlled HTTP error.
136 *
137 * @return void Terminates with a JSON response.
138 */
139 function wpbc_booking_appointment_ajax_validate_time() {
140 if ( false === check_ajax_referer( 'wpbc_booking_appointment_ajax', 'nonce', false ) ) {
141 wp_send_json_error( array( 'message' => __( 'Security check failed. Reload the page and try again.', 'booking' ) ), 403 );
142 }
143
144 $service_id = isset( $_POST['service_id'] ) && ! is_array( $_POST['service_id'] ) ? absint( wp_unslash( $_POST['service_id'] ) ) : 0;
145 $provider_id = isset( $_POST['provider_id'] ) && ! is_array( $_POST['provider_id'] ) ? absint( wp_unslash( $_POST['provider_id'] ) ) : 0;
146 $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
147 $context_check = wpbc_booking_appointment_validate_submission_context( $context_token, $service_id, $provider_id );
148 if ( is_wp_error( $context_check ) ) {
149 wp_send_json_error( array( 'message' => $context_check->get_error_message(), 'code' => $context_check->get_error_code() ), 400 );
150 }
151
152 $dates = wpbc_booking_appointment_get_preflight_dates();
153 if ( is_wp_error( $dates ) ) {
154 $error = $dates;
155 wp_send_json_error( array( 'message' => $error->get_error_message(), 'code' => $error->get_error_code() ), 400 );
156 }
157
158 $service = wpbc_appointment_services_repository()->find_active_for_resource( $service_id, $provider_id );
159 if ( is_wp_error( $service ) ) {
160 wp_send_json_error( array( 'message' => $service->get_error_message(), 'code' => $service->get_error_code() ), 400 );
161 }
162
163 $maximum_duration = absint( apply_filters( 'wpbc_booking_appointment_maximum_duration_minutes', 24 * 60, $context_check ) );
164 $existing_intervals = wpbc_appointment_services_get_existing_buffer_intervals( $provider_id, $dates );
165
166 if ( isset( $_POST['start_times'] ) ) {
167 $start_times = wpbc_booking_appointment_get_preflight_start_times();
168 if ( is_wp_error( $start_times ) ) {
169 wp_send_json_error( array( 'message' => $start_times->get_error_message(), 'code' => $start_times->get_error_code() ), 400 );
170 }
171
172 $slots = array();
173 foreach ( $start_times as $start_time ) {
174 $slots[ $start_time ] = wpbc_booking_appointment_validate_one_start_time( $service, $dates, $start_time, $maximum_duration, $existing_intervals );
175 }
176 wp_send_json_success(
177 array(
178 'valid' => true,
179 'slots' => $slots,
180 'duration' => absint( $service['duration_minutes'] ),
181 'buffer_before' => absint( $service['buffer_before_minutes'] ),
182 'buffer_after' => absint( $service['buffer_after_minutes'] ),
183 )
184 );
185 }
186
187 $start_time = isset( $_POST['start_time'] ) && ! is_array( $_POST['start_time'] ) ? sanitize_text_field( wp_unslash( $_POST['start_time'] ) ) : '';
188 $result = wpbc_booking_appointment_validate_one_start_time( $service, $dates, $start_time, $maximum_duration, $existing_intervals );
189 if ( empty( $result['valid'] ) ) {
190 wp_send_json_success( $result );
191 }
192
193 wp_send_json_success(
194 array_merge(
195 $result,
196 array(
197 'duration' => absint( $service['duration_minutes'] ),
198 'buffer_before' => absint( $service['buffer_before_minutes'] ),
199 'buffer_after' => absint( $service['buffer_after_minutes'] ),
200 )
201 )
202 );
203 }
204 add_action( 'wp_ajax_nopriv_WPBC_AJX_BOOKING_APPOINTMENT_VALIDATE_TIME', 'wpbc_booking_appointment_ajax_validate_time' );
205 add_action( 'wp_ajax_WPBC_AJX_BOOKING_APPOINTMENT_VALIDATE_TIME', 'wpbc_booking_appointment_ajax_validate_time' );
206