PluginProbe
Timetics – Appointment Booking Calendar & Scheduling / 1.0.24
Timetics – Appointment Booking Calendar & Scheduling v1.0.24
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 1.0.23 1.0.24 All 62 releases
timetics / core / bookings / api-booking.php

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

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