PluginProbe
Timetics – Appointment Booking Calendar & Scheduling / 1.0.38
Timetics – Appointment Booking Calendar & Scheduling v1.0.38
1.0.62 1.0.63 1.0.61 1.0.60 1.0.59 1.0.58 1.0.57 1.0.56 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.2 1.0.20 1.0.21 1.0.22 All 64 releases
timetics / core / bookings / api-booking.php

api-booking.php in Timetics – Appointment Booking Calendar & Scheduling 1.0.38, at core/bookings/api-booking.php

1,295 lines 47.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Booking api
4 *
5 * @package Timetics
6 */
7 namespace Timetics\Core\Bookings;
8
9 use Error;
10 use Timetics\Base\Api;
11 use Timetics\Core\Appointments\Api_Appointment;
12 use Timetics\Core\Appointments\Appointment;
13 use Timetics\Core\Customers\Customer;
14 use Timetics\Core\Emails\Cancel_Event_Customer_Email;
15 use Timetics\Core\Emails\Cancel_Event_Email;
16 use Timetics\Core\Emails\New_Event_Customer_Email;
17 use Timetics\Core\Emails\New_Event_Email;
18 use Timetics\Core\Emails\Update_Event_Customer_Email;
19 use Timetics\Core\Emails\Update_Event_Email;
20 use Timetics\Core\Staffs\Staff;
21 use Timetics\Utils\Singleton;
22 use TimeticsPro\Core\SeatPlan\SeatPlan;
23 use WP_Error;
24 use WP_HTTP_Response;
25 use WP_Query;
26
27 class Api_Booking extends Api {
28 use Singleton;
29
30 /**
31 * Store api namespace
32 *
33 * @var string
34 */
35 protected $namespace = 'timetics/v1';
36
37 /**
38 * Store rest base
39 *
40 * @var string
41 */
42 protected $rest_base = 'bookings';
43
44 /**
45 * Booking Type
46 *
47 * @var string
48 */
49 protected $type = '';
50
51 /**
52 * Register rest routes
53 *
54 * @return void
55 */
56 public function register_routes() {
57 /**
58 * Register route
59 *
60 * @var void
61 */
62 register_rest_route(
63 $this->namespace, $this->rest_base, [
64 [
65 'methods' => \WP_REST_Server::READABLE,
66 'callback' => [$this, 'get_items'],
67 'permission_callback' => function () {
68 return current_user_can( 'manage_timetics' );
69 },
70 ],
71 [
72 'methods' => \WP_REST_Server::CREATABLE,
73 'callback' => [$this, 'create_item'],
74 'permission_callback' => function () {
75 return true;
76 },
77 ],
78 [
79 'methods' => \WP_REST_Server::DELETABLE,
80 'callback' => [$this, 'bulk_delete'],
81 'permission_callback' => function () {
82 return current_user_can( 'edit_booking' );
83 },
84 ],
85 ]
86 );
87
88 /**
89 * Register route
90 *
91 * @var void
92 */
93 register_rest_route(
94 $this->namespace, '/' . $this->rest_base . '/(?P<booking_id>[\d]+)', [
95 [
96 'methods' => \WP_REST_Server::READABLE,
97 'callback' => [$this, 'get_item'],
98 'permission_callback' => [$this, 'get_item_permission_callback'],
99 ],
100 [
101 'methods' => \WP_REST_Server::EDITABLE,
102 'callback' => [$this, 'update_item'],
103 'permission_callback' => [$this, 'update_item_permission_callback'],
104 ],
105 [
106 'methods' => \WP_REST_Server::DELETABLE,
107 'callback' => [$this, 'delete_item'],
108 'permission_callback' => function () {
109 return current_user_can( 'edit_booking' );
110 },
111 ],
112 ]
113 );
114
115 register_rest_route(
116 $this->namespace, '/' . $this->rest_base . '/(?P<booking_id>[\d]+)/payment', [
117 [
118 'methods' => \WP_REST_Server::EDITABLE,
119 'callback' => [$this, 'make_payment'],
120 'permission_callback' => function () {
121 return true;
122 },
123 ],
124 ]
125 );
126
127 register_rest_route(
128 $this->namespace, $this->rest_base . '/search', [
129 [
130 'methods' => \WP_REST_Server::READABLE,
131 'callback' => [$this, 'search_items'],
132 'permission_callback' => function () {
133 return current_user_can( 'edit_posts' );
134 },
135 ],
136 ]
137 );
138
139 register_rest_route(
140 $this->namespace, $this->rest_base . '/entries', [
141 [
142 'methods' => \WP_REST_Server::READABLE,
143 'callback' => [$this, 'get_entries'],
144 'permission_callback' => function () {
145 return true;
146 },
147 ],
148 ]
149 );
150
151 register_rest_route(
152 $this->namespace, $this->rest_base . '/payment_methods', [
153 [
154 'methods' => \WP_REST_Server::READABLE,
155 'callback' => [$this, 'get_payment_methods'],
156 'permission_callback' => function () {
157 return true;
158 },
159 ],
160 ]
161 );
162 }
163
164 /**
165 * Get all bookings
166 *
167 * @param WP_Rest_Request $request
168 *
169 * @return JSON
170 */
171 public function get_items( $request ) {
172 $per_page = ! empty( $request['per_page'] ) ? intval( $request['per_page'] ) : 20;
173 $paged = ! empty( $request['paged'] ) ? intval( $request['paged'] ) : 1;
174 $meeting_id = ! empty( $request['meeting_id'] ) ? intval( $request['meeting_id'] ) : 0;
175 $start_date = ! empty( $request['start_date'] ) ? $request['start_date'] : '';
176 $staff_id = ! current_user_can( 'edit_booking' ) ? get_current_user_id() : 0;
177
178 $args = [
179 'posts_per_page' => $per_page,
180 'paged' => $paged,
181 'meeting' => $meeting_id,
182 'staff' => $staff_id,
183 ];
184
185 $args = apply_filters( 'timetics/add/item/data', $args, $request );
186
187 if ( $start_date ) {
188 $args['start_date'] = $start_date;
189 }
190
191 $bookings = Booking::all( $args );
192 $items = [];
193
194 if ( ! current_user_can( 'manage_options' ) ) {
195 $user_id = get_current_user_id();
196 $appointment_ids = Appointment::get_appointment_ids_by_author( $user_id );
197 $bookings = Booking::get_booking_ids_by_appointment( $appointment_ids, $per_page );
198 }
199
200 foreach ( $bookings['items'] as $item ) {
201 $items[] = $this->prepare_item( $item->ID );
202 }
203
204 /**
205 * Added temporary for leagacy sass. It will remove in future.
206 */
207 $items = apply_filters( 'timetics/admin/booking/get_items', $items );
208
209 $data = [
210 'success' => 1,
211 'status_code' => 200,
212 'data' => [
213 'total' => $bookings['total'],
214 'items' => $items,
215 ],
216 ];
217
218 return rest_ensure_response( $data );
219 }
220
221 /**
222 * Get single booking
223 *
224 * @param WP_Rest_Request $request
225 *
226 * @return JSON
227 */
228 public function get_item( $request ) {
229 $booking_id = (int) $request['booking_id'];
230 $booking = new Booking( $booking_id );
231
232 if ( ! $booking->is_booking() ) {
233 return [
234 'success' => 0,
235 'status_code' => 404,
236 'message' => esc_html__( 'Invalid booking id.', 'timetics' ),
237 'data' => [],
238 ];
239 }
240
241 /**
242 * Added temporary for leagacy sass. It will remove in future.
243 */
244 do_action( 'timetics/admin/booking/get_item', $this->prepare_item( $booking ) );
245
246 $data = [
247 'success' => 1,
248 'status_code' => 200,
249 'data' => $this->prepare_item( $booking ),
250 ];
251
252 return rest_ensure_response( $data );
253 }
254
255 /**
256 * Create booking
257 *
258 * @param WP_Rest_Request $request
259 *
260 * @return JSON
261 */
262 public function create_item( $request ) {
263
264 $bookings_count = Booking::all();
265
266 $response = [
267 'success' => 0,
268 'status_code' => 502,
269 'message' => esc_html__( 'Something went wrong', 'timetics' ),
270 'data' => [],
271 ];
272
273 if ( apply_filters( 'timetics/staff/booking/count_check', false, $bookings_count ) == true ) {
274 return new WP_HTTP_Response( apply_filters( 'timetics/admin/booking/error_data', $response, 'count_check' ), 403 );
275 }
276
277 $data = json_decode( $request->get_body(), true );
278
279 if ( apply_filters( 'timetics/booking/appointment/type_check', false, $request ) == true ) {
280 return new WP_HTTP_Response( apply_filters( 'timetics/admin/booking/error_data', $response, 'type_check' ), 403 );
281 }
282
283 $recurring_booking = ! empty( $data['recurring_dates'] ) ? $data['recurring_dates'] : [];
284
285 if ( $recurring_booking && apply_filters( 'timetics/booking/appointment/recurring_check', false, $recurring_booking ) == true ) {
286 $response = [
287 'status_code' => 403,
288 'success' => 0,
289 'message' => esc_html__( 'Recurring booking limit exit', 'timetics' ),
290 ];
291
292 return new WP_HTTP_Response( $response, 403 );
293 } // End.
294
295 return $this->save_bookings( $request );
296 }
297
298 /**
299 * Update booking
300 *
301 * @param WP_Rest_Request $request
302 *
303 * @return JSON
304 */
305 public function update_item( $request ) {
306
307 $booking_id = (int) $request['booking_id'];
308 $booking = new Booking( $booking_id );
309
310 if ( ! $booking->is_booking() ) {
311 return [
312 'status_code' => 404,
313 'message' => esc_html__( 'Invalid booking id.', 'timetics' ),
314 'data' => [],
315 ];
316 }
317
318 if ( apply_filters( 'timetics/booking/appointment/custom_form_data', false, $request ) == true ) {
319 $response = [
320 'status_code' => 409,
321 'success' => 0,
322 'message' => esc_html__( 'Custom Field Booking Restricted ', 'timetics' ),
323 ];
324
325 return new WP_HTTP_Response( $response, 403 );
326 }
327
328 return $this->save_bookings( $request, $booking_id );
329 }
330
331 /**
332 * Delete booking
333 *
334 * @param WP_Rest_Request $request
335 *
336 * @return JSON
337 */
338 public function delete_item( $request ) {
339
340 $booking_id = (int) $request['booking_id'];
341
342 $delete = $this->delete( $booking_id );
343
344 if ( ! $delete ) {
345 $data = [
346 'success' => 1,
347 'status_code' => 409,
348 'message' => esc_html__( 'Something went wrong, Please try again.', 'timetics' ),
349 'data' => [],
350 ];
351
352 return new WP_HTTP_Response( $data, 409 );
353 }
354
355 $data = [
356 'success' => 1,
357 'status_code' => 200,
358 'message' => esc_html__( 'Successfully deleted booking', 'timetics' ),
359 'data' => [],
360 ];
361
362 return rest_ensure_response( $data );
363 }
364
365 /**
366 * Delete multiples
367 *
368 * @param WP_Rest_Request $request
369 *
370 * @return JSON
371 */
372 public function bulk_delete( $request ) {
373
374 $bookings = json_decode( $request->get_body(), true );
375
376 foreach ( $bookings as $booking ) {
377 $delete = $this->delete( $booking );
378
379 if ( ! $delete ) {
380 return [
381 'success' => 0,
382 'status_code' => 404,
383 'message' => esc_html__( 'Invalid booking id.', 'timetics' ),
384 'data' => [],
385 ];
386 }
387 }
388
389 /**
390 * Added temporary for leagacy sass. It will remove in future.
391 */
392 do_action( 'timetics/admin/booking/bulk_delete', $bookings );
393
394 return [
395 'success' => 1,
396 'status_code' => 200,
397 'message' => esc_html__( 'Successfully deleted booking', 'timetics' ),
398 ];
399 }
400
401 /**
402 * Get payment methods
403 *
404 * @return array
405 */
406 public function get_payment_methods() {
407
408 $payment_methods = timetics_get_payment_methods();
409
410 return [
411 'success' => 1,
412 'status_code' => 200,
413 'data' => $payment_methods,
414 ];
415 }
416
417 /**
418 * Search bookings
419 *
420 * @param WP_Rest_Request $request
421 *
422 * @return JSON
423 */
424 public function search_items( $request ) {
425
426 // Prepare search args.
427 $per_page = ! empty( $request['per_page'] ) ? intval( $request['per_page'] ) : 20;
428 $paged = ! empty( $request['paged'] ) ? intval( $request['paged'] ) : 1;
429 $search = ! empty( $request['search'] ) ? sanitize_text_field( $request['search'] ) : '';
430
431 // Get search.
432 $booking = new WP_Query(
433 array(
434 'post_type' => 'timetics-booking',
435 'posts_per_page' => $per_page,
436 'paged' => $paged,
437 'post_status' => 'any',
438
439 // @codingStandardsIgnoreStart
440 'meta_query' => array(
441 'relation' => 'OR',
442 array(
443 'key' => '_tt_booking_customer_fname',
444 'value' => $search,
445 'compare' => 'LIKE',
446 ),
447 array(
448 'key' => '_tt_booking_customer_lname',
449 'value' => $search,
450 'compare' => 'LIKE',
451 ),
452 array(
453 'key' => '_tt_booking_customer_email',
454 'value' => $search,
455 'compare' => 'LIKE',
456 ),
457 array(
458 'key' => '_tt_booking_customer_phone',
459 'value' => $search,
460 'compare' => 'LIKE',
461 ),
462 array(
463 'key' => '_tt_booking_staff_fname',
464 'value' => $search,
465 'compare' => 'LIKE',
466 ),
467 array(
468 'key' => '_tt_booking_staff_lname',
469 'value' => $search,
470 'compare' => 'LIKE',
471 ),
472 array(
473 'key' => '_tt_booking_staff_email',
474 'value' => $search,
475 'compare' => 'LIKE',
476 ),
477 array(
478 'key' => '_tt_booking_meeting_name',
479 'value' => $search,
480 'compare' => 'LIKE',
481 ),
482 array(
483 'key' => '_tt_booking_meeting_description',
484 'value' => $search,
485 'compare' => 'LIKE',
486 ),
487 array(
488 'key' => '_tt_booking_meeting_type',
489 'value' => $search,
490 'compare' => 'LIKE',
491 ),
492 ),
493 // @codingStandardsIgnoreEnd
494 )
495 );
496
497 // Prepare items for response.
498 $items = [];
499
500 foreach ( $booking->posts as $item ) {
501 $items[] = $this->prepare_item( $item->ID );
502 }
503
504 /**
505 * Added temporary for leagacy sass. It will remove in future.
506 */
507 $items = apply_filters( 'timetics/admin/booking/search_items', $items );
508
509 $data = [
510 'success' => 1,
511 'status' => 200,
512 'data' => [
513 'total' => $booking->found_posts,
514 'items' => $items,
515 ],
516 ];
517
518 return rest_ensure_response( $data );
519 }
520
521 /**
522 * Get all booking entries
523 *
524 * @param WP_Rest_Request $request
525 *
526 * @return JSON
527 */
528 public function get_entries( $request ) {
529 $staff_id = ! empty( $request['staff_id'] ) ? intval( $request['staff_id'] ) : 0;
530 $meeting_id = ! empty( $request['meeting_id'] ) ? intval( $request['meeting_id'] ) : 0;
531 $start_date = ! empty( $request['start_date'] ) ? sanitize_text_field( $request['start_date'] ) : 0;
532 $timezone = ! empty( $request['timezone'] ) ? sanitize_text_field( $request['timezone'] ) : 0;
533 $end_date = ! empty( $request['end_date'] ) ? sanitize_text_field( $request['end_date'] ) : 0;
534
535 $meeting = new Appointment( $meeting_id );
536
537 // Validate timezone.
538 if ( ! timetics_is_valid_timezone( $timezone ) ) {
539 return new WP_Error( 'timezone_error', __( 'Your booking timezone is invalid', 'timetics' ) );
540 }
541
542 // Validate meeting timezone.
543 if ( ! timetics_is_valid_timezone( $meeting->get_timezone() ) ) {
544 return new WP_Error( 'timezone_error', __( 'Your meeting timezone is invalid. Please update your meeting timezone with proper timezone.', 'timetics' ) );
545 }
546
547 $days = $meeting->prepare_schedule( $start_date, $end_date, $staff_id, $timezone );
548 $days = apply_filters( 'timetics_schedule_data_for_selected_date', $days, $staff_id, $meeting_id, $timezone );
549
550 $data = [
551 'today' => gmdate( 'Y-m-d' ),
552 'availability_timezone' => $meeting->get_timezone(),
553 'days' => $days,
554 ];
555
556 /**
557 * Added temporary for leagacy sass. It will remove in future.
558 */
559 $data = apply_filters( 'timetics/admin/booking/get_entries', $data );
560
561 return [
562 'success' => true,
563 'status_code' => 200,
564 'message' => esc_html__( 'Get all entries', 'timetics' ),
565 'data' => $data,
566 ];
567 }
568
569 /**
570 * Make payment transaction for the current booking
571 *
572 * @param WP_Rest_Request $request
573 *
574 * @return JSON
575 */
576 public function make_payment( $request ) {
577 $booking_id = intval( $request['booking_id'] );
578 $booking = new Booking( $booking_id );
579 $data = json_decode( $request->get_body(), true );
580 $status = ! empty( $data['status'] ) ? sanitize_text_field( $data['status'] ) : '';
581 $default_booking_status = timetics_get_option( 'default_booking_status', 'approved' );
582 $post_status = 'succeeded' === $status ? $default_booking_status : 'pending';
583 $payment_method = ! empty( $data['payment_method'] ) ? sanitize_text_field( $data['payment_method'] ) : '';
584 $payment_details = ! empty( $data['payment_details'] ) ? $data['payment_details'] : '';
585 $type = $booking->get_type();
586
587 if ( ! $booking->is_booking() ) {
588 return [
589 'status_code' => 404,
590 'message' => esc_html__( 'Invalid booking id.', 'timetics' ),
591 'data' => [],
592 ];
593 }
594
595 $update = $booking->update(
596 [
597 'post_status' => $post_status,
598 'payment_status' => $status,
599 'payment_details' => $payment_details,
600 'payment_method' => $payment_method,
601 ]
602 );
603
604 if ( is_wp_error( $update ) ) {
605 $data = [
606 'success' => 0,
607 'status_code' => 409,
608 /* translators: Action */
609 'message' => $update->get_error_message(),
610 ];
611
612 return new WP_HTTP_Response( $data, 409 );
613 }
614
615 if ( $default_booking_status === $post_status ) {
616 $booking->create_event();
617
618 if( 'timetics-event' == $type ){
619 return;
620 }
621
622 $is_email_to_customer = timetics_get_option( 'booking_created_customer');
623 $is_email_to_host = timetics_get_option( 'booking_created_host');
624
625 if ( $is_email_to_host ) {
626 $new_event_email = new New_Event_Email( $booking );
627 $new_event_email->send();
628 }
629
630 if ( $is_email_to_customer ) {
631 $new_event_customer_email = new New_Event_Customer_Email( $booking );
632 $new_event_customer_email->send();
633 }
634
635 do_action( 'timetics_booking_payment', $booking );
636
637 }
638
639 /**
640 * Added temporary for leagacy sass. It will remove in future.
641 */
642 do_action( 'timetics/admin/booking/make_payment', $post_status );
643
644 $data = [
645 'success' => 1,
646 'status_code' => 200,
647 /* translators: Action */
648 'message' => sprintf( esc_html__( 'Payment %s', 'timetics' ), $post_status ),
649 ];
650
651 return new WP_HTTP_Response( $data, 200 );
652 }
653
654 /**
655 * Save booking
656 *
657 * @param WP_Rest_Request $request
658 * @param integer $id Booking id
659 *
660 * @return JSON
661 */
662 public function save_bookings( $request, $id = 0 ) {
663 $data = json_decode( $request->get_body(), true );
664
665 if( isset( $data['type'] ) && 'timetics-event' == $data['type'] ) {
666 $this->type = $data['type'];
667 return apply_filters('timetics_booking_event', $data, $id );
668 }else {
669 return $this->booking_appointment($data, $id);
670 }
671 }
672
673 /**
674 * Booking Appointment
675 *
676 * @param array $data All the data of booking
677 * @param integer $id Booking id
678 *
679 * @return JSON
680 */
681 protected function booking_appointment ($data, $id) {
682 $first_name = ! empty( $data['first_name'] ) ? sanitize_text_field( $data['first_name'] ) : '';
683 $last_name = ! empty( $data['last_name'] ) ? sanitize_text_field( $data['last_name'] ) : '';
684 $email = ! empty( $data['email'] ) ? sanitize_text_field( $data['email'] ) : '';
685 $phone = ! empty( $data['phone'] ) ? sanitize_text_field( $data['phone'] ) : '';
686 $city = ! empty( $data['city'] ) ? sanitize_text_field( $data['city'] ) : '';
687 $state = ! empty( $data['state'] ) ? sanitize_text_field( $data['state'] ) : '';
688 $post_code = ! empty( $data['post_code'] ) ? sanitize_text_field( $data['post_code'] ) : '';
689 $country = ! empty( $data['country'] ) ? sanitize_text_field( $data['country'] ) : '';
690 $payment_method = ! empty( $data['payment_method'] ) ? sanitize_text_field( $data['payment_method'] ) : '';
691 $address_1 = ! empty( $data['address_1'] ) ? sanitize_text_field( $data['address_1'] ) : '';
692 $address_2 = ! empty( $data['address_2'] ) ? sanitize_text_field( $data['address_2'] ) : '';
693 $appointment = ! empty( $data['appointment'] ) ? intval( $data['appointment'] ) : 0;
694 $staff_id = ! empty( $data['staff'] ) ? intval( $data['staff'] ) : 0;
695 $start_date = ! empty( $data['start_date'] ) ? sanitize_text_field( $data['start_date'] ) : '';
696 $date = ! empty( $data['date'] ) ? sanitize_text_field( $data['date'] ) : '';
697 $end_date = ! empty( $data['end_date'] ) ? sanitize_text_field( $data['end_date'] ) : $start_date;
698 $start_time = ! empty( $data['start_time'] ) ? sanitize_text_field( $data['start_time'] ) : '';
699 $end_time = ! empty( $data['end_time'] ) ? sanitize_text_field( $data['end_time'] ) : '';
700 $order_total = ! empty( $data['order_total'] ) ? intval( $data['order_total'] ) : 0;
701 $status = ! empty( $data['status'] ) ? sanitize_text_field( $data['status'] ) : timetics_get_option( 'default_booking_status', 'approved' );
702 $location = ! empty( $data['location'] ) ? sanitize_text_field( $data['location'] ) : '';
703 $location_type = ! empty( $data['location_type'] ) ? sanitize_text_field( $data['location_type'] ) : '';
704 $description = ! empty( $data['description'] ) ? sanitize_text_field( $data['description'] ) : '';
705 $timezone = ! empty( $data['timezone'] ) ? sanitize_text_field( $data['timezone'] ) : '';
706 $recurring_dates = ! empty( $data['recurring_dates'] ) ? $data['recurring_dates'] : [];
707 $seats = ! empty( $data['seats'] ) ? $data['seats'] : [];
708 $cancel_reason = ! empty( $data['cancel_reason'] ) ? $data['cancel_reason'] : [];
709 $booking_time = ! empty( $data['booking_createAt'] ) ? $data['booking_createAt'] : '';
710 $action = $id ? 'updated' : 'created';
711 $appointment_token = ! empty( $data['appointment_token'] ) ? sanitize_text_field( $data['appointment_token'] ) : '';
712
713 $validate = $this->validate(
714 $data, [
715 'first_name',
716 'email',
717 'payment_method',
718 'appointment',
719 'start_date',
720 'start_time',
721 'end_time',
722 ]
723 );
724
725 if ( is_wp_error( $validate ) ) {
726 $data = [
727 'status_code' => 403,
728 'success' => 0,
729 'message' => $validate->get_error_messages(),
730 ];
731 return new WP_HTTP_Response( $data, 403 );
732 }
733
734 $customer = new Customer();
735 $meeting = new Appointment( $appointment );
736 $staff = new Staff( $staff_id );
737 $booking = new Booking( $id );
738 $booking_entry = new Booking_Entry();
739
740 // Validate booking
741
742 $validation = $this->validate_booking( $appointment, $data );
743 if(is_wp_error($validation)){
744 return $validation;
745 }
746
747
748
749 if ( 'created' === $action && ! $this->is_available_slot( $meeting, [
750 'staff_id' => $staff->get_id(),
751 'start_date' => $start_date,
752 'start_time' => $start_time,
753 'timezone' => $timezone,
754 ] ) ) {
755 return new WP_Error( 'time_slot_error', sprintf( __( '%s time slot is not available', 'timetics' ), $start_time ) );
756 }
757
758 if ( $meeting->is_recurring() ) {
759 $valid_recurrence = apply_filters( 'timetics_validate_recurring_booking', $recurring_dates, $start_time, $staff->get_id(), $meeting->get_id() );
760
761 if ( ! $valid_recurrence ) {
762 $recurring_error = [
763 'status_code' => 403,
764 'success' => 0,
765 'message' => __( 'Couldn\'t possible to book. Plese try another time.', 'timetics' ),
766 ];
767
768 return new WP_HTTP_Response( $recurring_error, 403 );
769 }
770 }
771
772 $customer->make(
773 [
774 'first_name' => $first_name,
775 'last_name' => $last_name,
776 'email' => $email,
777 'phone' => $phone,
778 ]
779 );
780
781 // Update booking schedule.
782 if ( $id ) {
783
784 $entries = $booking_entry->find(
785 [
786 'staff_id' => $booking->get_staff_id(),
787 'meeting_id' => $booking->get_appointment(),
788 'date' => $booking->get_start_date(),
789 'start' => $booking->get_start_time(),
790 ]
791
792 );
793
794 if ( $entries ) {
795 $entry = $booking_entry->first();
796
797 if ( 'one-to-one' == strtolower( $meeting->get_type() ) ) {
798 $entry->delete();
799 } else {
800 $booked = intval( $entry->get_booked() ) - 1;
801 $booked_data = apply_filters( 'timetics_booking_update_schedule', $entry, ['booked' => $booked], $data, $booking );
802 $entry->update( $booked_data );
803 }
804 }
805 }
806
807 if ( $id && $booking->get_status() == 'cancel' && $status == 'cancel' ) {
808 return new WP_Error( 'booking_cancel_error', __( 'This booking alreay canceled', 'timetics' ) );
809 }
810
811 $booking_props = [
812 'customer' => $customer->get_id(),
813 'appointment' => $meeting->get_id(),
814 'appointment_name' => $meeting->get_name(),
815 'staff' => $staff->get_id(),
816 'customer_fname' => $customer->get_first_name(),
817 'customer_lname' => $customer->get_last_name(),
818 'customer_email' => $customer->get_email(),
819 'customer_phone' => $customer->get_phone(),
820 'staff_fname' => $staff->get_first_name(),
821 'staff_lname' => $staff->get_last_name(),
822 'staff_email' => $staff->get_email(),
823 'meeting_name' => $meeting->get_name(),
824 'meeting_description' => $meeting->get_description(),
825 'meeting_type' => $meeting->get_type(),
826 'booking_time' => $booking_time,
827 'description' => $description,
828 'start_date' => $start_date,
829 'date' => $date,
830 'end_date' => $end_date,
831 'start_time' => $start_time,
832 'end_time' => $end_time,
833 'order_total' => $this->calculate_order_total( $data ),
834 'post_status' => $status,
835 'location' => $location,
836 'location_type' => $location_type,
837 'timezone' => $timezone,
838 'cancel_reason' => $cancel_reason,
839 ];
840
841 if( 'created' == $action ){
842 $booking_props['security_token'] = $booking->generate_security_token();
843 }
844
845 $booking->set_props( $booking_props );
846
847
848 $booking = apply_filters( 'timetics/bookings/booking/set', $booking );
849
850 $booking->save();
851
852 // Fire when booking is completed.
853 do_action( 'timetics_after_booking_create', $booking->get_id(), $customer->get_id(), $meeting->get_id(), $data );
854
855 // Create or update calendar event.
856 if ( $id ) {
857 if ( 'cancel' === $status ) {
858 $booking->delete_event();
859 $is_email_to_customer = timetics_get_option( 'booking_canceled_customer');
860 $is_email_to_host = timetics_get_option( 'booking_canceled_host');
861
862 if ( $is_email_to_host ) {
863 $cancel_event_email = new Cancel_Event_Email( $booking );
864 $cancel_event_email->send();
865 }
866
867 if ( $is_email_to_customer ) {
868 $customer_cancel_event_email = new Cancel_Event_Customer_Email( $booking );
869 $customer_cancel_event_email->send();
870 }
871
872 /**
873 * Added temporary for leagacy sass. It will remove in future.
874 */
875 do_action( 'timetics/admin/booking/after_delete_item', $booking );
876 } else {
877 $booking->update_event();
878 $is_email_to_reschedule_customer = timetics_get_option( 'booking_rescheduled_customer');
879 $is_email_to_reschedule_host = timetics_get_option( 'booking_rescheduled_host');
880
881 if ( $is_email_to_reschedule_host ) {
882 $update_event_email = new Update_Event_Email( $booking );
883 $update_event_email->send();
884 }
885
886 if ( $is_email_to_reschedule_customer ) {
887 $update_event_customer_email = new Update_Event_Customer_Email( $booking );
888 $update_event_customer_email->send();
889 }
890
891
892 }
893 }
894
895 // Convert booking time to staff/meeting time.
896 $date_time = timetics_convert_timezone( $start_date . ' ' . $start_time, $timezone, $meeting->get_timezone() );
897 $end_time = timetics_convert_timezone( $start_date . ' ' . $end_time, $timezone, $meeting->get_timezone() );
898
899 // Create booking schedule.
900 $entries = $booking_entry->find(
901 [
902 'staff_id' => $staff->get_id(),
903 'meeting_id' => $meeting->get_id(),
904 'date' => $date_time->format( 'Y-m-d' ),
905 'start' => $date_time->format( 'h:i a' ),
906 ]
907 );
908
909 if ( $entries ) {
910 $entry = $booking_entry->first();
911
912 if ( 'cancel' === $status ) {
913 $booked = intval( $entry->get_booked() ) - 1;
914 } else {
915 $booked = intval( $entry->get_booked() ) + 1;
916 }
917
918 $booked_data = apply_filters( 'timetics_booking_update_schedule', $entry, ['booked' => $booked], $data, $booking );
919
920 if ( 'cancel' === $status && 'one-to-one' == strtolower( $meeting->get_type() ) ) {
921 $entry->delete();
922 } else {
923 $entry->update( $booked_data );
924 }
925
926 } else {
927 $book_entry_data = [
928 'meeting_id' => $meeting->get_id(),
929 'staff_id' => $staff->get_id(),
930 'customer_id' => $customer->get_id(),
931 'booking_id' => $booking->get_id(),
932 'booked' => 1,
933 'date' => $date_time->format( 'Y-m-d' ),
934 'start' => $date_time->format( 'h:i a' ),
935 'end' => $end_time->format( 'h:i a' ),
936 ];
937
938 $book_entry_data = apply_filters( 'timetics_booking_schedule', $book_entry_data, $data );
939 $booking_entry->create( $book_entry_data );
940 }
941
942 // Fire after booking schedule create.
943 do_action( 'timetics_after_booking_schedule', $booking->get_id(), $customer->get_id(), $meeting->get_id(), $data );
944
945 $data = [
946 'success' => 1,
947 'status_code' => 200,
948 /* translators: Action */
949 'message' => sprintf( esc_html__( 'Successfully %s booking', 'timetics' ), $action ),
950 'data' => $this->prepare_item( $booking ),
951 ];
952
953 return new WP_HTTP_Response( $data, 200 );
954 }
955
956 /**
957 * Prepare item for response
958 *
959 * @param integer $booking_id
960 *
961 * @return array
962 */
963 public function prepare_item( $booking_id ) {
964 $booking = new Booking( $booking_id );
965 $appointment = new Appointment( $booking->get_appointment() );
966 $staff = new Staff( $booking->get_staff_id() );
967 $customer = new Customer( $booking->get_customer_id() );
968 $meeting_timezone = $appointment->get_timezone();
969 $booking_timezone = $booking->get_timezone();
970
971 $start_date_time = timetics_convert_timezone( $booking->get_start_date() . ' ' . $booking->get_start_time(), $booking_timezone, $meeting_timezone );
972 $end_date_time = timetics_convert_timezone( $booking->get_end_date() . ' ' . $booking->get_end_time(), $booking_timezone, $meeting_timezone );
973 $date = timetics_datetime( 'Y-m-d', $booking->get_date(), $meeting_timezone );
974
975 $event = $booking->get_event();
976 $join_link = 'google-meet' === $booking->get_location_type() && ! empty( $event['hangoutLink'] ) ? $event['hangoutLink'] : '';
977
978 $booking_title = $appointment->is_appointment() ? $appointment->get_name() : $booking->get_appointment_name();
979
980 $response = [
981 'id' => $booking->get_id(),
982 'random_id' => $booking->get_random_id(),
983 'status' => $booking->get_status(),
984 'order_total' => $booking->get_total(),
985 'start_date' => $start_date_time->format( 'Y-m-d' ),
986 'end_date' => $end_date_time->format( 'Y-m-d' ),
987 'date' => $date,
988 'start_time' => $start_date_time->format( 'h:i a' ),
989 'end_time' => $end_date_time->format( 'h:i a' ),
990 'booking_time' => $booking->get_booking_time(),
991 'location' => $booking->get_location(),
992 'location_type' => $booking->get_location_type(),
993 'description' => $booking->get_description(),
994 'cancel_reason' => $booking->get_cancel_reason(),
995 'security_token'=> $booking->get_security_token(),
996 'customer' => [
997 'id' => $customer->get_id(),
998 'full_name' => $customer->get_display_name(),
999 'first_name' => $customer->get_first_name(),
1000 'last_name' => $customer->get_last_name(),
1001 'email' => $customer->get_email(),
1002 'phone' => $customer->get_phone(),
1003 ],
1004 'appointment' => [
1005 'id' => $appointment->get_id(),
1006 'name' => $booking_title,
1007 'duration' => $appointment->get_duration(),
1008 'type' => $appointment->get_type(),
1009 'price' => $appointment->get_price(),
1010 'locations' => $appointment->get_locations(),
1011 'timezone' => $appointment->get_timezone(),
1012 'permalink' => $appointment->get_appointment_permalink(),
1013 ],
1014 'staff' => [
1015 'id' => $staff->get_id(),
1016 'full_name' => $staff->get_display_name(),
1017 'first_name' => $staff->get_first_name(),
1018 'last_name' => $staff->get_last_name(),
1019 'email_name' => $staff->get_email(),
1020 'phone' => $staff->get_phone(),
1021 'image' => $staff->get_image(),
1022 ],
1023 ];
1024
1025 if ( $join_link ) {
1026 $response['meeting_link'] = $join_link;
1027 }
1028
1029 return apply_filters( 'timetics_booking_json_data', $response, $booking );
1030 }
1031
1032 /**
1033 * Delete booking
1034 *
1035 * @param integer $booking_id
1036 *
1037 * @return bool
1038 */
1039 private function delete( $booking_id ) {
1040 $booking = new Booking( $booking_id );
1041 $meeting = new Appointment( $booking->get_appointment() );
1042
1043 if ( ! $booking->is_booking() ) {
1044 return false;
1045 }
1046
1047 $current_user_id = get_current_user_id();
1048
1049 if (
1050 $meeting->is_appointment()
1051 && ! user_can( $current_user_id, 'manage_options' )
1052 && $meeting->get_author() != $current_user_id
1053 ) {
1054 $data = [
1055 'success' => 0,
1056 'message' => __( 'You are not allowed to delete this booking.', 'timetics' ),
1057 ];
1058
1059 return new WP_HTTP_Response( $data, 403 );
1060 }
1061
1062 $booking_entry = new Booking_Entry();
1063
1064 $date_time = timetics_convert_timezone( $booking->get_start_date() . ' ' . $booking->get_start_time(), $booking->get_timezone(), $meeting->get_timezone() );
1065
1066 $entries = $booking_entry->find(
1067 [
1068 'staff_id' => $booking->get_staff_id(),
1069 'meeting_id' => $booking->get_appointment(),
1070 'date' => $date_time->format( 'Y-m-d' ),
1071 'start' => $date_time->format( 'h:i a' ),
1072 ]
1073 );
1074
1075 if ( $entries ) {
1076 $entry = $booking_entry->first();
1077
1078 if ( 'one-to-one' == strtolower( $meeting->get_type() ) ) {
1079 $entry->delete();
1080 } else {
1081 $booked = intval( $entry->get_booked() ) - 1;
1082 $booked_seat = ! empty( $booking->get_seat() ) ? $booking->get_seat() : [];
1083 $existing_seat = ! empty( $entry->get_seats() ) ? $entry->get_seats() : [];
1084
1085 $entry->update( [
1086 'booked' => $booked,
1087 'seats' => array_values( array_diff( $existing_seat, $booked_seat ) ),
1088 ] );
1089 }
1090 }
1091
1092 $recurrences = $booking->get_recurrence();
1093 $booking->delete_event();
1094 $booking->delete();
1095
1096 $is_email_to_customer = timetics_get_option( 'booking_canceled_customer');
1097 $is_email_to_host = timetics_get_option( 'booking_canceled_host');
1098
1099 if ( $is_email_to_host ) {
1100 $cancel_event_email = new Cancel_Event_Email( $booking );
1101 $cancel_event_email->send();
1102 }
1103
1104 if ( $is_email_to_customer ) {
1105
1106 $customer_cancel_event_email = new Cancel_Event_Customer_Email( $booking );
1107 $customer_cancel_event_email->send();
1108 }
1109
1110
1111
1112 do_action( 'timetics_after_booking_delete', $recurrences );
1113
1114 return true;
1115 }
1116
1117 public function is_available_slot( $meeting, $booking_data = [] ) {
1118 $start_date = $booking_data['start_date'];
1119 $start_time = $booking_data['start_time'];
1120 $booking_timezone = $booking_data['timezone'];
1121 $booking_entry = new Booking_Entry();
1122 $meeting_id = $meeting->get_id();
1123 $staff_id = $booking_data['staff_id'];
1124
1125 $time = is_string( $start_time ) ? strtotime( $start_time ) : $start_time;
1126 $time = gmdate( 'H:i', $time );
1127 $booking_entries = new Booking_Entry();
1128 $meeting = new Appointment( $meeting_id );
1129
1130 $entries = $booking_entries->find( [
1131 'meeting_id' => $meeting_id,
1132 'staff_id' => $staff_id,
1133 'date' => $start_date,
1134 ] );
1135
1136 $booked = false;
1137
1138 foreach ( $entries as $entry ) {
1139 $booking = new Booking( $entry->get_booking_id() );
1140 $booking_time = timetics_convert_timezone( $booking->get_start_date() . ' ' . $entry->get_start(), $booking->get_timezone(), $booking_timezone )->format( 'H:i' );
1141
1142 if ( $booking_time == $time ) {
1143 $booked = $entry;
1144 break;
1145 }
1146 }
1147
1148 if ( $booked && $booked->get_booked() >= $meeting->get_capacity() ) {
1149 return false;
1150 }
1151
1152 return true;
1153 }
1154
1155 /**
1156 * Validates a booking.
1157 *
1158 * @param int $appointment_id The ID of the appointment.
1159 * @param array $data The data for the booking.
1160 * @throws None
1161 * @return mixed Returns an error response if the validation fails, otherwise returns nothing.
1162 */
1163 public function validate_booking($appointment_id, $data) {
1164 $meeting = new Appointment($appointment_id);
1165 $all_seats = (array) $meeting->get_seats();
1166 $meeting_price = $meeting->get_price();
1167 $meeting_locations = (array) $meeting->get_locations();
1168 $total_price = 0;
1169
1170 $staff_id = ! empty( $data['staff'] ) ? intval( $data['staff'] ) : 0;
1171 $order_total = ! empty( $data['order_total'] ) ? floatval( $data['order_total'] ) : 0;
1172 $location_type = ! empty( $data['location_type'] ) ? sanitize_text_field( $data['location_type'] ) : '';
1173 $start_date = ! empty( $data['start_date'] ) ? sanitize_text_field( $data['start_date'] ) : '';
1174 $timezone = ! empty( $data['timezone'] ) ? sanitize_text_field( $data['timezone'] ) : '';
1175 $start_time = ! empty( $data['start_time'] ) ? sanitize_text_field( $data['start_time'] ) : '';
1176 $status = ! empty( $data['status'] ) ? sanitize_text_field( $data['status'] ) : '';
1177 $seats = ! empty( $data['seats'] ) ? $data['seats'] : [];
1178 $timeslots = $meeting->get_avilable_timeslots( $start_date, $staff_id, $timezone );
1179 $meeting_has_buffer_time = $meeting->get_buffer_time_after_in_seconds() > 0 || $meeting->get_buffer_time_before_in_seconds() > 0;
1180
1181 if ( ! $meeting->is_appointment() ) {
1182 return $this->create_error_response( __( 'Invalid meeting.', 'timetics' ), 422 );
1183 }
1184
1185 if ( 'cancel' !== $status ) {
1186 if ( ! $meeting_has_buffer_time && ! in_array( date('g:ia', strtotime( $start_time ) ), $timeslots ) ) {
1187 return $this->create_error_response( __( 'Invalid timeslot.', 'timetics' ), 422 );
1188 }
1189
1190 // Check if the staff is matched
1191 if ( ! in_array( $staff_id, $meeting->get_staff_ids() ) ) {
1192 return $this->create_error_response(__('Team member not matched', 'timetics'), 403);
1193
1194 }
1195 // Check if the location type is matched
1196 if ( ! in_array( $location_type, array_column( $meeting_locations, 'location_type' ) ) ) {
1197 return $this->create_error_response(__('Location type not matched', 'timetics'), 403);
1198 }
1199 }
1200 }
1201
1202 /**
1203 * Creates an error response with the given message and status code.
1204 *
1205 * @param string $message The error message.
1206 * @param int $status_code The HTTP status code.
1207 * @return WP_HTTP_Response The error response.
1208 */
1209 public function create_error_response($message, $status_code) {
1210 return new WP_Error( 'timezone_error', $message, ['status' => $status_code] );
1211 }
1212
1213 /**
1214 * Calculate order total
1215 *
1216 * @param array $data Request data
1217 *
1218 * @return integer
1219 */
1220 private function calculate_order_total($data) {
1221 $seats = ! empty( $data['seats'] ) ? $data['seats'] : [];
1222 $meeting_id = ! empty( $data['appointment'] ) ? $data['appointment'] : 0;
1223 $total_price = 0;
1224
1225 if ( class_exists( SeatPlan::class ) && $seats ) {
1226 foreach( $seats as $seat ) {
1227 $seat_object = SeatPlan::find( $seat );
1228 $total_price += $seat_object->price;
1229 }
1230
1231 return $total_price;
1232 }
1233
1234 $meeting = new Appointment( $meeting_id );
1235
1236 $prices = $meeting->get_price();
1237
1238 if ( $prices && is_array( $prices ) ) {
1239 return $prices[0]['ticket_price'];
1240 }
1241
1242 return 0;
1243 }
1244
1245 /**
1246 * Update item permission callback
1247 * @param WP_Rest_Request $request
1248 * @return bool
1249 */
1250 public function update_item_permission_callback($request){
1251 $nonce = $request->get_header('X-WP-Nonce');
1252
1253 $booking_id = (int) $request->get_param('booking_id');
1254 $appointment_token = $request->get_param('appointment_token');
1255
1256 // If no token provided, this might be an old link. Verify with nonce for that case temporarily
1257 if ( !empty($booking_id) && empty($appointment_token) && wp_verify_nonce($nonce, 'wp_rest')) {
1258 return wp_verify_nonce($nonce, 'wp_rest');
1259 }
1260
1261 if (empty($booking_id) || empty($appointment_token)) {
1262 return false;
1263 }
1264
1265 $booking = new Booking($booking_id);
1266
1267 if (!$booking->is_booking()) {
1268 return false;
1269 }
1270
1271 // Get the stored security token from booking meta
1272 $stored_token = $booking->get_security_token();
1273
1274 // Check if the provided token matches the stored token and have nonce verified
1275 if ($appointment_token === $stored_token && !empty($stored_token) && wp_verify_nonce($nonce, 'wp_rest') ) {
1276 return true;
1277 }
1278
1279 return false;
1280 }
1281
1282 /**
1283 * Get item permission callback
1284 * @param WP_Rest_Request $request
1285 * @return bool
1286 */
1287 public function get_item_permission_callback($request){
1288 $nonce = $request->get_header('X-WP-Nonce');
1289 if (wp_verify_nonce($nonce, 'wp_rest')) {
1290 return true;
1291 }
1292 return false;
1293 }
1294 }
1295