PluginProbe
Timetics – Appointment Booking Calendar & Scheduling / 1.0.20
Timetics – Appointment Booking Calendar & Scheduling v1.0.20
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.20, at core/bookings/api-booking.php

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