PluginProbe
Bookit — Booking & Appointment Calendar / 2.6.0.5
Bookit — Booking & Appointment Calendar v2.6.0.5
2.6.0.5 2.6.0.4 2.6.0.3 2.6.0.2 2.6.0.1 2.6.0 trunk 1.2 1.2.2 1.2.3 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 All 62 releases
bookit / includes / classes / AppointmentController.php

AppointmentController.php in Bookit — Booking & Appointment Calendar 2.6.0.5, at includes/classes/AppointmentController.php

346 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Bookit\Classes;
4
5 use Bookit\Classes\Admin\SettingsController;
6 use Bookit\Classes\Base\Plugin;
7 use Bookit\Classes\Base\User;
8 use Bookit\Classes\Database\Appointments;
9 use Bookit\Classes\Database\Customers;
10 use Bookit\Classes\Database\Staff_Services;
11 use Bookit\Classes\Vendor\Payments;
12 use Bookit\Helpers\CleanHelper;
13 use DateTimeZone;
14 use DateTime;
15 use Exception;
16
17
18 class AppointmentController {
19
20 protected static $googleCalendarAddon = 'google-calendar';
21
22 /**
23 * Check for Pro Addon active.
24 *
25 * @since 2.5.0
26 *
27 * @var string
28 */
29 protected static $proAddon = 'pro';
30
31 private static function getCleanRules() {
32
33 return array(
34 'clear_price' => array( 'type' => 'floatval' ),
35 'user_id' => array( 'type' => 'intval' ),
36 'staff_id' => array( 'type' => 'intval' ),
37 'service_id' => array( 'type' => 'intval' ),
38 'start_timestamp' => array( 'type' => 'intval' ),
39 'end_timestamp' => array( 'type' => 'intval' ),
40 'now_timestamp' => array( 'type' => 'intval' ),
41 'today_timestamp' => array( 'type' => 'intval' ),
42 'email' => array(
43 'type' => 'strval',
44 'function' => array(
45 'custom' => false,
46 'name' => 'sanitize_email',
47 ),
48 ),
49 'payment_method' => array( 'type' => 'strval' ),
50 'full_name' => array( 'type' => 'strval' ),
51 'phone' => array(
52 'function' => array(
53 'custom' => true,
54 'name' => 'custom_sanitize_phone',
55 ),
56 ),
57 );
58 }
59
60 /**
61 * Convert UTC offset to timezone name.
62 *
63 * @since 2.5.2
64 *
65 * @param string $timezone_string The timezone string from wp_timezone_string().
66 *
67 * @return string The timezone name or an empty string if no match is found.
68 */
69 private static function convert_offset_to_timezone_name( $timezone_string ) {
70 // Check if it's a UTC offset (format: +HH:MM or -HH:MM).
71 if ( preg_match( '/^([+-])(\d{2}):(\d{2})$/', $timezone_string, $matches ) ) {
72 $sign = ( $matches[1] === '-' ) ? -1 : 1;
73 $hours = (int) $matches[2];
74 $minutes = (int) $matches[3];
75 $offset_seconds = $sign * ( $hours * 3600 + $minutes * 60 );
76
77 // Try to find a timezone name based on the offset.
78 $timezone_name = timezone_name_from_abbr( '', $offset_seconds, 0 );
79
80 if ( $timezone_name !== false ) {
81 return $timezone_name;
82 }
83
84 // If timezone_name_from_abbr() fails, search through timezone identifiers.
85 $timezone_identifiers = DateTimeZone::listIdentifiers();
86 $now = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
87
88 foreach ( $timezone_identifiers as $identifier ) {
89 try {
90 $tz = new DateTimeZone( $identifier );
91 $offset = $tz->getOffset( $now );
92
93 if ( $offset === $offset_seconds ) {
94 return $identifier;
95 }
96 } catch ( Exception $e ) {
97 continue;
98 }
99 }
100
101 // If no match found, return an empty string.
102 return '';
103 }
104
105 // Already a timezone name, return as is.
106 return $timezone_string;
107 }
108
109 /**
110 * Validation
111 *
112 * @since 2.6.0 Booking identity is derived from the authenticated session in Registered mode.
113 *
114 * @param $data
115 */
116 public static function validate( $data ) {
117 $errors = array();
118 $settings = SettingsController::get_settings();
119
120 if ( ! $data['staff_id'] ) {
121 $errors['staff_id'] = __( 'No staff', 'bookit' );
122 }
123 if ( ! $data['service_id'] ) {
124 $errors['service_id'] = __( 'No service', 'bookit' );
125 }
126 if ( ! $data['start_time'] ) {
127 $errors['start_time'] = __( 'No start time', 'bookit' );
128 }
129 if ( ! $data['end_time'] ) {
130 $errors['end_time'] = __( 'No end time', 'bookit' );
131 }
132 if ( ! $data['date_timestamp'] ) {
133 $errors['date_timestamp'] = __( 'No date', 'bookit' );
134 }
135
136 $appointment = Appointments::checkAppointment( $data );
137
138 if ( $appointment > 0 ) {
139 $errors['appointment'] = __( 'Selected Service Time is not available!', 'bookit' );
140 }
141
142 if ( $data['phone'] || false === $data['phone'] ) {
143 if ( ! preg_match( '/^((\+)?[0-9]{8,14})$/', $data['phone'] ) ) {
144 $errors['phone'] = __( 'Please enter a valid phone number', 'bookit' );
145 }
146 }
147
148 if ( 'guest' == $settings['booking_type'] ) {
149 if ( ! $data['email'] && ! $data['phone'] ) {
150 $errors['phone'] = __( 'Please enter email or phone', 'bookit' );
151 }
152 }
153
154 if ( ! $data['email'] ) {
155 $errors['email'] = __( 'Please enter email', 'bookit' );
156 }
157
158 if ( $data['email'] && ! is_email( $data['email'] ) ) {
159 $errors['email'] = __( 'Please enter your email in format youremail@example.com', 'bookit' );
160 }
161
162 if ( $data['full_name'] ) {
163 if ( strlen( $data['full_name'] ) < 3 || strlen( $data['full_name'] ) > 25 ) {
164 $errors['full_name'] = __( 'Full name must be between 3 and 25 characters long', 'bookit' );
165 }
166 } else {
167 $errors['full_name'] = __( 'Please enter full name', 'bookit' );
168 }
169
170 $price = Staff_Services::get_service_price_by_staff( $data['service_id'], $data['staff_id'] );
171 if ( (float) $data['clear_price'] !== (float) $price ) {
172 $errors['clear_price'] = __( 'Price is incorrect', 'bookit' );
173 }
174
175 if ( (float) $data['clear_price'] > 0 && ! array_key_exists( $data['payment_method'], $settings['payments'] ) ) {
176 $errors['payment_method'] = __( 'Please choose correct payment method', 'bookit' );
177 }
178
179 if ( count( $errors ) > 0 ) {
180 wp_send_json_error( array( 'errors' => $errors ) );
181 }
182 }
183
184 /**
185 * Book Appointment
186 *
187 * @since 2.6.0 Require authentication and link the booking to the current user in Registered mode.
188 * @since 2.6.0.2 Derive the booking identity from the current session.
189 */
190 public static function save() {
191 $send_no_cache_headers = apply_filters( 'rest_send_nocache_headers', is_user_logged_in() );
192 $nonce = '';
193 if ( ! $send_no_cache_headers ) {
194 $nonce = wp_create_nonce( 'bookit_nonce' );
195 $_SERVER['HTTP_X_WP_NONCE'] = $nonce;
196 }
197
198 check_ajax_referer( 'bookit_book_appointment', 'nonce' );
199
200 $data = CleanHelper::cleanData( $_POST, self::getCleanRules() );
201
202 // Identify the booking from the current session.
203 $data['user_id'] = get_current_user_id();
204
205 $settings = SettingsController::get_settings();
206 if ( 'registered' == $settings['booking_type'] ) {
207 // Registered booking is tied to the authenticated visitor.
208 if ( ! is_user_logged_in() ) {
209 wp_send_json_error( array(
210 'login_required' => true,
211 'message' => __( 'Please log in to complete your booking.', 'bookit' ),
212 ) );
213 }
214
215 $data['email'] = wp_get_current_user()->user_email;
216 }
217
218 self::validate( $data );
219
220 if ( empty( $data ) ) {
221 wp_send_json_error( array( 'message' => __( 'Error occurred!', 'bookit' ) ) );
222 die();
223 }
224
225 $customer = CustomerController::get_customer( $data );
226
227 $notes = array();
228 if ( ! empty( $data['comment'] ) ) {
229 $notes['comment'] = $data['comment'];
230 }
231 if ( $customer->email != $data['email'] ) {
232 $notes['email'] = $data['email'];
233 }
234 if ( $customer->phone != $data['phone'] && ! empty( $data['phone'] ) ) {
235 $notes['phone'] = $data['phone'];
236 }
237 if ( $customer->full_name != $data['full_name'] && ! empty( $data['full_name'] ) ) {
238 $notes['full_name'] = $data['full_name'];
239 }
240 $data['customer_id'] = $customer->id;
241 $data['notes'] = serialize( $notes );
242 $data['status'] = Appointments::$pending;
243
244 $id = Appointments::create_appointment( $data );
245
246 do_action( 'bookit_appointment_created', $id );
247
248 $appointment = (array) Appointments::get_full_appointment_by_id( $id );
249 $appointment['token'] = $data['token'];
250
251 /** if google calendar addon is installed */
252 if ( Plugin::isAddonInstalledAndEnabled( self::$proAddon )
253 && has_action( 'bookit_google_calendar_create_appointment' ) ) {
254 $appointment['customer_email'] = $notes['email'] ?? $appointment['customer_email'];
255 $appointment['customer_phone'] = $notes['phone'] ?? $appointment['customer_phone'];
256 do_action( 'bookit_google_calendar_create_appointment', $appointment );
257 }
258 /** if google calendar addon is installed | end */
259
260 // Add timezone to the appointment.
261 $appointment['timezone_name'] = self::convert_offset_to_timezone_name(
262 wp_timezone_string()
263 );
264
265 $redirect_url = '';
266 if ( ! is_null( $appointment['payment_method'] ) ) {
267 $payments = new Payments( $appointment );
268 $redirect_url = $payments->redirect_url();
269 }
270 wp_send_json_success(
271 array(
272 'appointment' => $appointment,
273 'customer' => $customer,
274 'nonce' => $nonce,
275 'redirect_url' => $redirect_url,
276 'message' => __( 'Appointment Saved!', 'bookit' ),
277 )
278 );
279 }
280
281 /**
282 * Get Month Appointments
283 */
284 public static function get_month_appointments() {
285 check_ajax_referer( 'bookit_month_appointments', 'nonce' );
286
287 $data = CleanHelper::cleanData( $_POST, self::getCleanRules() );
288 if ( ! empty( $data ) ) {
289 $appointments = Appointments::month_appointments( $data );
290
291 wp_send_json_success( $appointments );
292 }
293
294 wp_send_json_error( array( 'message' => __( 'Error occurred!', 'bookit' ) ) );
295 }
296
297 /**
298 * Get Day Appointments
299 */
300 public static function get_day_appointments() {
301 check_ajax_referer( 'bookit_day_appointments', 'nonce' );
302
303 $data = CleanHelper::cleanData( $_POST, self::getCleanRules() );
304
305 if ( empty( $data ) ) {
306 wp_send_json_error( array( 'message' => __( 'Error occurred!', 'bookit' ) ) );
307 die();
308 }
309
310 $appointments = Appointments::day_appointments( $data );
311
312 /** if google calendar addon is installed */
313 if ( Plugin::isAddonInstalledAndEnabled( self::$proAddon )
314 && has_filter( 'bookit_google_calendar_get_events_by_date' ) ) {
315 $gcBusyTimeSlots = apply_filters( 'bookit_google_calendar_get_events_by_date', $data );
316
317 /** remove double values by id key */
318 foreach ( $gcBusyTimeSlots as $key => $value ) {
319 if ( array_search( $value->id, array_column( $appointments, 'id' ) ) !== false
320 || array_search( (string) $value->start_time, array_column( $appointments, 'start_time' ) ) ) {
321 unset( $gcBusyTimeSlots[ $key ] );
322 }
323 }
324 $appointments = array_merge( $gcBusyTimeSlots, $appointments );
325 }
326 /** if google calendar addon is installed | end */
327 wp_send_json_success( $appointments );
328 }
329
330 /**
331 * Check is Appointment time free
332 */
333 public static function is_free_appointment() {
334 check_ajax_referer( 'bookit_is_free_appointment', 'nonce' );
335
336 $data = CleanHelper::cleanData( $_POST, self::getCleanRules() );
337 if ( empty( $data['staff_id'] ) || empty( $data['service_id'] ) || empty( $data['date_timestamp'] )
338 || empty( $data['start_time'] ) || empty( $data['end_time'] ) ) {
339 wp_send_json_error( array( 'message' => __( 'Error occurred!', 'bookit' ) ) );
340 }
341
342 $appointment = Appointments::checkAppointment( $data );
343 wp_send_json_success( array( 'is_free' => false ) );
344 }
345 }
346