PluginProbe
Timetics – Appointment Booking Calendar & Scheduling / 1.0.60
Timetics – Appointment Booking Calendar & Scheduling v1.0.60
1.0.64 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 All 65 releases
timetics / core / bookings / booking.php

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

1,185 lines 28.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Booking Class
4 *
5 * @package Timetics
6 */
7
8 namespace Timetics\Core\Bookings;
9
10 use Timetics\Core\Appointments\Appointment;
11 use Timetics\Core\Customers\Customer;
12 use Timetics\Core\Integrations\Google\Service\Calendar;
13 use Timetics\Core\Staffs\Staff;
14 use TimeticsEvent\Core\Event_Bookings\Event_Booking;
15 use TimeticsEvent\Core\Events\Event;
16 use WP_Query;
17
18 /**
19 * Class Booking
20 */
21 class Booking {
22 /**
23 * Store booking post type
24 *
25 * @var string
26 */
27 protected $pos_type = 'timetics-booking';
28
29 /**
30 * Store booking meta prefix
31 *
32 * @var string
33 */
34 protected $meta_prefix = '_tt_booking_';
35
36 /**
37 * Store booking id
38 *
39 * @var string
40 */
41 protected $id;
42
43 /**
44 * Store appointment object
45 *
46 * @var Object
47 */
48 protected $appointment;
49
50 /**
51 * Store appointment object
52 *
53 * @var Object
54 */
55 protected $event;
56
57 /**
58 * Store booking data
59 *
60 * @var array
61 */
62 protected $data = [
63
64 'post_status' => '',
65 'customer' => '',
66 'appointment' => '',
67 'appointment_name' => '',
68 'staff' => '',
69 'speaker' => '',
70 'event' => '',
71 'location' => '',
72 'description' => '',
73 'location_type' => '',
74 'payment_method' => '',
75 'payment_status' => '',
76 'payment_details' => '',
77 'order_total' => '',
78 'start_date' => '',
79 'end_date' => '',
80 'start_time' => '',
81 'end_time' => '',
82 'calendar_event' => '',
83 'random_id' => '',
84 'seats' => '',
85 'guests' => '',
86 'zoom' => '',
87 'custom_form_data' => '',
88 'timezone' => '',
89 'recurrence' => [],
90 'parent' => 0,
91 'cancel_reason' => '',
92 'custom_location_url' => '',
93 'security_token' => ''
94 ];
95
96 /**
97 * Booking Constructor
98 *
99 * @return void
100 */
101 public function __construct( $booking = 0 ) {
102 if ( $booking instanceof self ) {
103 $this->set_id( $booking->get_id() );
104 } elseif ( ! empty( $booking->ID ) ) {
105 $this->set_id( $booking->ID );
106 } elseif ( is_numeric( $booking ) && $booking > 0 ) {
107 $this->set_id( $booking );
108 }
109 }
110
111 /**
112 * Get id
113 *
114 * @return integer
115 */
116 public function get_id() {
117 return $this->id;
118 }
119
120 /**
121 * Get customer id
122 *
123 * @return integer
124 */
125 public function get_customer_id() {
126 return $this->get_prop( 'customer' );
127 }
128
129 /**
130 * Get attendee id
131 *
132 * @return integer
133 */
134 public function get_attendees() {
135 $attendees = $this->get_prop( 'attendees' );
136 return $attendees ? $attendees : [];
137 }
138
139 /**
140 * Get custom url
141 *
142 * @return string
143 */
144 public function get_custom_location_url() {
145 $custom_url = $this->get_prop( 'custom_location_url' );
146 return $custom_url ? $custom_url : '';
147 }
148
149 /**
150 * Get staff for the current booking
151 *
152 * @return integer
153 */
154 public function get_staff_id() {
155 return $this->get_prop( 'staff' );
156 }
157
158 /**
159 * Get speaker id
160 *
161 * @return integer
162 */
163 public function get_speaker_id() {
164 return $this->get_prop( 'speaker' );
165 }
166
167 /**
168 * Get booking type
169 *
170 * @return string
171 */
172 public function get_type() {
173 return $this->get_prop( 'type' );
174 }
175
176 /**
177 * Get event id
178 *
179 * @return integer
180 */
181 public function get_event_id() {
182 return $this->get_prop( 'event' );
183 }
184
185 /**
186 * Get customer
187 *
188 * @return User Object
189 */
190 public function get_customer() {
191 return get_userdata( $this->get_customer_id() );
192 }
193
194 /**
195 * Get seats of a booking
196 *
197 * @return array
198 */
199 public function get_seat() {
200 return $this->get_prop( 'seats' );
201 }
202
203 /**
204 * Get guests
205 *
206 * @return array
207 */
208 public function get_guests() {
209 return $this->get_prop( 'guests' );
210 }
211
212 /**
213 * Get custom fields
214 *
215 * @return array
216 */
217 public function get_custom_fields() {
218 return $this->get_prop( 'custom_form_data' );
219 }
220
221 /**
222 * Get billing first name
223 *
224 * @return string
225 */
226 public function get_billing_first_name() {
227 return $this->get_prop( 'first_name' );
228 }
229
230 /**
231 * Get location
232 *
233 * @return string
234 */
235 public function get_location() {
236 return $this->get_prop( 'location' );
237 }
238
239 /**
240 * Get location type
241 *
242 * @return string
243 */
244 public function get_location_type() {
245 return $this->get_prop( 'location_type' );
246 }
247
248 /**
249 * Get booking time
250 *
251 * @return string
252 */
253 public function get_booking_time() {
254 return $this->get_prop( 'booking_time' );
255 }
256
257 /**
258 * Get last name
259 *
260 * @return string
261 */
262 public function get_billing_last_name() {
263 return $this->get_prop( 'last_name' );
264 }
265
266 /**
267 * Get email
268 *
269 * @return string
270 */
271 public function get_billing_email() {
272 return $this->get_prop( 'email' );
273 }
274
275 /**
276 * Get billing phone
277 *
278 * @return string
279 */
280 public function get_billing_phone() {
281 return $this->get_prop( 'phone' );
282 }
283
284 /**
285 * Get timezone
286 *
287 * @return string
288 */
289 public function get_timezone() {
290 return $this->get_prop( 'timezone' );
291 }
292
293 /**
294 * Get description
295 *
296 * @return string
297 */
298 public function get_description() {
299 return $this->get_prop( 'description' );
300 }
301
302 /**
303 * Get billing address
304 *
305 * @param string $adress_type Booking billing address
306 *
307 * @return string
308 */
309 public function get_billing_address( $adress_type = 'address_1' ) {
310 return $this->get_prop( $adress_type );
311 }
312
313 /**
314 * Get billing city
315 *
316 * @return string
317 */
318 public function get_billing_city() {
319 return $this->get_prop( 'city' );
320 }
321
322 /**
323 * Get billing state
324 *
325 * @return string
326 */
327 public function get_billing_state() {
328 return $this->get_prop( 'state' );
329 }
330
331 /**
332 * Get billing post code
333 *
334 * @return string
335 */
336 public function get_billing_post_code() {
337 return $this->get_prop( 'post_code' );
338 }
339
340 /**
341 * Get billing
342 *
343 * @return string
344 */
345 public function get_billing_country() {
346 return $this->get_prop( 'country' );
347 }
348
349 /**
350 * Get payment method
351 *
352 * @return string
353 */
354 public function get_payment_method() {
355 return $this->get_prop( 'payment_method' );
356 }
357
358 /**
359 * Get payment status
360 *
361 * @return string
362 */
363 public function get_payment_status() {
364 return $this->get_prop( 'payment_status' );
365 }
366
367 /**
368 * Get appointment
369 *
370 * @return Appointment
371 */
372 public function get_appointment() {
373 return $this->get_prop( 'appointment' );
374 }
375
376 /**
377 * Get appointment name
378 *
379 * @return string
380 */
381 public function get_appointment_name() {
382 return $this->get_prop( 'appointment_name' );
383 }
384
385 /**
386 * Get calendar event
387 *
388 * @return array
389 */
390 public function get_event() {
391 return $this->get_prop( 'calendar_event' );
392 }
393
394 /**
395 * Get post status
396 *
397 * @return string
398 */
399 public function get_status() {
400 return get_post( $this->id )->post_status;
401 }
402
403 /**
404 * Get total
405 *
406 * @return integer
407 */
408 public function get_total() {
409 return $this->get_prop( 'order_total' );
410 }
411
412 /**
413 * Get start date
414 *
415 * @return string
416 */
417 public function get_start_date() {
418 return $this->get_prop( 'start_date' );
419 }
420 /**
421 * Get start date
422 *
423 * @return string
424 */
425 public function get_date() {
426 return $this->get_prop( 'date' );
427 }
428
429 /**
430 * Get end date
431 *
432 * @return string
433 */
434 public function get_end_date() {
435 return $this->get_prop( 'end_date' );
436 }
437
438 /**
439 * Get start time
440 *
441 * @return string
442 */
443 public function get_start_time() {
444 return $this->get_prop( 'start_time' );
445 }
446
447 /**
448 * Get end time
449 *
450 * @return string
451 */
452 public function get_end_time() {
453 return $this->get_prop( 'end_time' );
454 }
455
456 /**
457 * Get random id for the current booking
458 *
459 * @return string
460 */
461 public function get_random_id() {
462 return $this->get_prop( 'random_id' );
463 }
464
465 /**
466 * Get payment details
467 *
468 * @return array
469 */
470 public function get_payment_details() {
471 return $this->get_prop( 'payment_details' );
472 }
473
474 /**
475 * Get zoom meeting details
476 *
477 * @return array
478 */
479 public function get_zoom() {
480 return $this->get_prop( 'zoom' );
481 }
482
483 /**
484 * Get recurrences
485 *
486 * @return array
487 */
488 public function get_recurrence() {
489 return $this->get_prop( 'recurrence' );
490 }
491
492 /**
493 * Get parent
494 *
495 * @return integer
496 */
497 public function get_parent() {
498 return $this->get_prop( 'parent' );
499 }
500
501 /**
502 * Get booking cancel reason
503 *
504 * @return integer
505 */
506 public function get_cancel_reason() {
507 return $this->get_prop( 'cancel_reason' );
508 }
509
510 /**
511 * Get booking data
512 *
513 * @param string $prop
514 *
515 * @return mixed
516 */
517 private function get_prop( $prop = '' ) {
518 return $this->get_metadata( $prop );
519 }
520
521 /**
522 * Get metadata
523 *
524 * @param string $prop
525 *
526 * @return mixed
527 */
528 private function get_metadata( $prop = '' ) {
529 $meta_key = $this->meta_prefix . $prop;
530
531 return get_post_meta( $this->id, $meta_key, true );
532 }
533
534 /**
535 * Generate random id
536 *
537 * @return string
538 */
539 private function generate_randon_id() {
540 return chr( wp_rand( ord( 'A' ), ord( 'Z' ) ) ) . wp_rand( 575639, 575639 + 400 );
541 }
542
543 // Setter.
544
545 /**
546 * Set booking id
547 *
548 * @param integer $id
549 *
550 * @return void
551 */
552 public function set_id( $id ) {
553 $this->id = $id;
554 }
555
556 /**
557 * Set data
558 *
559 * @param array $args Booking Args
560 *
561 * @return void
562 */
563 public function set_props( $args = [] ) {
564 $this->data = wp_parse_args( $args, $this->data );
565 $this->appointment = new Appointment( $this->data['appointment'] );
566 if ( isset( $this->data['type'] ) && 'timetics-event' == $this->data['type'] ) {
567 $this->event = new Event( $this->data['event'] );
568 }
569
570 }
571
572 /**
573 * Save metada for booking
574 *
575 * @return void
576 */
577 public function save_metadata( $args = [] ) {
578 foreach ( $args as $key => $value ) {
579
580 // If a key not exists on data it will skip to save the data.
581 if ( ! array_key_exists( $key, $this->data ) ) {
582 continue;
583 }
584
585 // Prepare meta key.
586 $meta_key = $this->meta_prefix . $key;
587
588 if ( ! $value ) {
589 $value = $this->get_prop( $key );
590 }
591
592 // Update appointment meta data.
593 update_post_meta( $this->id, $meta_key, $value );
594 }
595 }
596
597 /**
598 * Save appointment
599 *
600 * @return void
601 */
602 public function save( $type = '' ) {
603 $args = [
604 'post_title' => $this->appointment->get_name(),
605 'post_type' => $this->pos_type,
606 'post_status' => $this->data['post_status'],
607 'post_author' => get_current_user_id(),
608 ];
609
610 if ( 'timetics-event' == $type ) {
611 $args = [
612 'post_title' => $this->event->get_name(),
613 'post_type' => $this->pos_type,
614 'post_status' => $this->data['post_status'],
615 'post_author' => get_current_user_id(),
616 ];
617 }
618
619 $updated = false;
620
621 if ( ! empty( $this->id ) ) {
622 $this->send_notification( 'update' );
623 $args['ID'] = $this->id;
624 $updated = true;
625 }
626
627 // Insert or Update appointment.
628 $booking_id = wp_insert_post( $args );
629 $this->data['random_id'] = $this->generate_randon_id();
630 $this->data = apply_filters( 'timetics/event/booking/all_data', $this->data);
631
632 if ( ! is_wp_error( $booking_id ) ) {
633 $this->set_id( $booking_id );
634 $this->save_metadata( $this->data );
635 }
636
637 if ( ! $updated ) {
638 $this->send_notification( 'create' );
639 }
640 }
641
642 /**
643 * Update booking
644 *
645 * @param array $args
646 *
647 * @return bool
648 */
649 public function update( $args = [] ) {
650 $post = get_post( $this->id )->to_array();
651 $args = wp_parse_args( $args, $post );
652
653 $updated = wp_update_post( $args );
654
655 if ( ! is_wp_error( $updated ) ) {
656 $this->save_metadata( $args );
657 }
658
659 return $updated;
660 }
661
662 /**
663 * Delete booking
664 *
665 * @return bool | WP_Error
666 */
667 public function delete() {
668 // Set action for sending notification.
669 $this->send_notification( 'delete' );
670
671 return wp_delete_post( $this->id, true );
672 }
673
674 /**
675 * Check is a booking or not
676 *
677 * @return bool
678 */
679 public function is_booking() {
680 $post = get_post( $this->id );
681
682 if ( $post && $this->pos_type === $post->post_type ) {
683 return true;
684 }
685
686 return false;
687 }
688
689 /**
690 * Get all bookings
691 *
692 * @param array $args Bookings args
693 *
694 * @return array
695 */
696 public static function all( $args = [] ) {
697
698 $status = [
699 'approved',
700 'pending',
701 'cancel',
702 'completed',
703 ];
704 $defaults = [
705 'post_type' => 'timetics-booking',
706 'posts_per_page' => 20,
707 'post_status' => 'any',
708 'paged' => 1,
709 ];
710
711 $args = wp_parse_args( $args, $defaults );
712
713 $args = apply_filters( 'timetics/admin/bookings/all', $args, $defaults );
714
715 $meta_query = ! empty( $args['meta_query'] ) && is_array( $args['meta_query'] ) ? $args['meta_query'] : [];
716
717 if ( ! empty( $args['start_date'] ) ) {
718 $meta_query[] = [
719 'key' => '_tt_booking_start_date',
720 'value' => $args['start_date'],
721 'compare' => '>=',
722 'type' => 'DATE',
723 ];
724 }
725
726 if ( ! empty( $args['meeting'] ) ) {
727 $meta_query[] = [
728 'key' => '_tt_booking_appointment',
729 'value' => $args['meeting'],
730 'compare' => '=',
731 ];
732 }
733
734 if ( ! empty( $args['staff'] ) ) {
735 $meta_query[] = [
736 'key' => '_tt_booking_staff',
737 'value' => $args['staff'],
738 'compare' => '=',
739 ];
740 }
741
742 if ( ! empty( $meta_query ) ) {
743 if ( count( $meta_query ) > 1 && empty( $meta_query['relation'] ) ) {
744 $meta_query['relation'] = 'AND';
745 }
746 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering bookings
747 $args['meta_query'] = $meta_query;
748 }
749
750 $post = new WP_Query( $args );
751
752 return [
753 'total' => $post->found_posts,
754 'items' => $post->posts,
755 ];
756 }
757
758 /**
759 * Create event after creating a booking
760 *
761 * @return bool
762 */
763 public function create_event() {
764
765 if ( ! timetics_google_setup() ) {
766 return;
767 }
768
769 if ('timetics-event' == $this->get_type()) {
770 Event_Booking::instance()->create_online_event($this->get_id());
771 }else {
772 $this->create_appointment_event();
773 }
774
775 }
776
777 public function create_appointment_event() {
778 $data = [
779 'summary' => timetics_get_option( 'booking_created_customer_email_title' ),
780 'description' => timetics_get_option( 'booking_created_customer_email_body' ),
781 ];
782
783 $calendar = new Calendar();
784 $event_data = $this->prepare_event( $data );
785
786 if ( ! $event_data ) {
787 return;
788 }
789
790 $booking_entry = new Booking_Entry();
791 $meeting = new Appointment( $this->get_appointment() );
792
793 $date_time = timetics_convert_timezone( $this->get_start_date() .' '. $this->get_start_time(), $this->get_timezone(), $meeting->get_timezone() );
794
795 $entries = $booking_entry->find(
796 [
797 'staff_id' => $this->get_staff_id(),
798 'meeting_id' => $this->get_appointment(),
799 'date' => $date_time->format('Y-m-d'),
800 'start' => $date_time->format('h:i a'),
801 ]
802 );
803
804 $event = false;
805
806 if ( $entries ) {
807 $entry = $booking_entry->first();
808
809 $event = $entry->get_google_event();
810
811 if ( ! $event ) {
812 $event = $calendar->create_event( $event_data );
813 $entry->update( [
814 'google_event' => $event,
815 ] );
816 }
817 }
818
819 if ( $event ) {
820 update_post_meta( $this->id, $this->meta_prefix . 'calendar_event', $event );
821 }
822 }
823
824 /**
825 * Update calendar event
826 *
827 * @return bool
828 */
829 public function update_event() {
830 if ( ! timetics_google_setup() ) {
831 return;
832 }
833
834 $data = [
835 'summary' => timetics_get_option( 'booking_rescheduled_customer_email_title' ),
836 'description' => timetics_get_option( 'booking_rescheduled_customer_email_body' ),
837 ];
838
839 $calendar = new Calendar();
840 $event_data = $this->prepare_event( $data );
841 $calendar_event = $this->get_event();
842
843 if ( ! is_array( $calendar_event ) ) {
844 return;
845 }
846
847 if ( ! empty( $calendar_event['id'] ) ) {
848 // Update calendar event.
849 $event = $calendar->update_event( $calendar_event['id'], $event_data );
850
851 // update calendar event data.
852 update_post_meta( $this->id, $this->meta_prefix . 'calendar_event', $event );
853 }
854 }
855
856 /**
857 * Delete calendar event
858 *
859 * @return
860 */
861 public function delete_event() {
862 if ( ! timetics_google_setup() ) {
863 return;
864 }
865
866 $calendar = new Calendar();
867 $calendar_event = $this->get_event();
868 $access_token = timetics_get_google_access_token( $this->get_staff_id() );
869
870 if ( ! is_array( $calendar_event ) ) {
871 return;
872 }
873
874 if ( ! $access_token ) {
875 return;
876 }
877
878 if ( ! empty( $calendar_event['id'] ) ) {
879 // Update calendar event.
880 $event = $calendar->delete_event( $calendar_event['id'], $access_token );
881
882 // update calendar event data.
883 update_post_meta( $this->id, $this->meta_prefix . 'calendar_event', $event );
884 }
885 }
886
887 /**
888 * Prepare event data
889 *
890 * @return array
891 */
892 private function prepare_event( $data = [] ) {
893
894 $meeting_id = $this->get_appointment();
895 $staff_id = $this->get_staff_id();
896 $customer_id = $this->get_customer_id();
897 $location_type = $this->get_location_type();
898 $access_token = timetics_get_google_access_token( $staff_id );
899
900 if ( ! $access_token ) {
901 return false;
902 }
903
904 $meeting = new Appointment( $meeting_id );
905 $staff = new Staff( $staff_id );
906 $customer = new Customer( $customer_id );
907
908 $time = gmdate( 'h:i a', strtotime( $this->get_start_time() ) );
909 $date = gmdate( 'd F Y', strtotime( $this->get_start_date() ) );
910 $location = $this->get_location();
911
912 $placeholders = [
913 '{%meeting_title%}' => $meeting->get_name(),
914 '{%meeting_date%}' => $date,
915 '{%meeting_time%}' => $time,
916 '{%meeting_location%}' => $location,
917 '{%meeting_duration%}' => $meeting->get_duration(),
918 '{%host_name%}' => $staff->get_display_name(),
919 '{%host_email%}' => $staff->get_email(),
920 '{%customer_name%}' => $customer->get_display_name(),
921 '{%customer_email%}' => $customer->get_email(),
922 ];
923
924 $summary = ! empty( $data['summary'] ) ? timetics_replace_placeholder( $data['summary'], $placeholders ) : $meeting->get_name();
925 $description = ! empty( $data['description'] ) ? timetics_replace_placeholder( $data['description'], $placeholders ) : $meeting->get_description();
926
927 $description = apply_filters( 'timetics_booking_event_data', $description, $summary, $this );
928
929 $event = [
930 'summary' => $summary,
931 'description' => $description,
932 'start' => [
933 'date' => $this->get_start_date(),
934 'time' => $this->get_start_time(),
935 ],
936 'end' => [
937 'date' => $this->get_end_date(),
938 'time' => $this->get_end_time(),
939 ],
940 'attendees' => [
941 ['email' => $staff->get_email()],
942 ['email' => $customer->get_email()],
943 ],
944 'timezone' => $this->get_timezone(),
945 'google_meet' => 'google-meet' === $location_type,
946 'access_token' => $access_token,
947 ];
948
949 return $event;
950 }
951
952 /**
953 * Get all data of an object
954 *
955 * @return array
956 */
957 public function to_array() {
958 $data = [];
959
960 foreach ( $this->data as $key => $value ) {
961
962 $data[$key] = $this->get_prop( $key );
963 }
964
965 return $data;
966 }
967
968 /**
969 * Send notification after booking
970 *
971 * @return void
972 */
973 private function send_notification( $action ) {
974
975 do_action( 'timetics_booking_notification', $this, $action );
976 }
977
978 /**
979 * Get booking ids by appointment id
980 *
981 * @param array Collection of appointment ids
982 *
983 * @return array Collection of booking ids
984 */
985 public static function get_booking_ids_by_appointment( $appointment_ids, $per_page = -1 ) {
986 if ( empty( $appointment_ids ) ) {
987 return [];
988 }
989
990 $query = new WP_Query([
991 'post_type' => 'timetics-booking',
992 'post_status' => 'any',
993 'posts_per_page' => $per_page,
994 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering bookings by appointment
995 'meta_query' => [
996 [
997 'key' => '_tt_booking_appointment',
998 'value' => $appointment_ids,
999 'compare' => 'IN',
1000 ],
1001 ],
1002 ]);
1003
1004 return [
1005 'total' => $query->found_posts,
1006 'items' => $query->posts,
1007 ];
1008 }
1009
1010 /**
1011 * Get booking ids visible to a given user.
1012 *
1013 * Union of:
1014 * - bookings where the user is the assigned staff (`_tt_booking_staff`)
1015 * - bookings whose appointment is authored by the user
1016 *
1017 * @param integer $user_id WP user id.
1018 *
1019 * @return int[] Deduped list of booking post ids.
1020 */
1021 public static function get_visible_ids_for_user( $user_id ) {
1022 $user_id = (int) $user_id;
1023
1024 if ( ! $user_id ) {
1025 return [];
1026 }
1027
1028 $staff_ids = get_posts( [
1029 'post_type' => 'timetics-booking',
1030 'post_status' => 'any',
1031 'posts_per_page' => -1,
1032 'fields' => 'ids',
1033 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- needed to scope bookings to current staff user
1034 'meta_query' => [
1035 [
1036 'key' => '_tt_booking_staff',
1037 'value' => $user_id,
1038 ],
1039 ],
1040 ] );
1041
1042 $appointment_ids = Appointment::get_appointment_ids_by_author( $user_id );
1043
1044 $appointment_booking_ids = [];
1045 if ( ! empty( $appointment_ids ) ) {
1046 $appointment_booking_ids = get_posts( [
1047 'post_type' => 'timetics-booking',
1048 'post_status' => 'any',
1049 'posts_per_page' => -1,
1050 'fields' => 'ids',
1051 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- needed to scope bookings to appointments owned by current user
1052 'meta_query' => [
1053 [
1054 'key' => '_tt_booking_appointment',
1055 'value' => $appointment_ids,
1056 'compare' => 'IN',
1057 ],
1058 ],
1059 ] );
1060 }
1061
1062 return array_values( array_unique( array_map( 'intval', array_merge( $staff_ids, $appointment_booking_ids ) ) ) );
1063 }
1064
1065 /**
1066 * Get Google Calendar Event ID for this booking
1067 *
1068 * @return string
1069 */
1070 public function get_google_event_id() {
1071 return $this->get_metadata( 'google_event_id' );
1072 }
1073
1074 /**
1075 * Set Google Calendar Event ID for this booking
1076 *
1077 * @param string $event_id
1078 * @return bool
1079 */
1080 public function set_google_event_id( $event_id ) {
1081 return $this->save_metadata( 'google_event_id', $event_id );
1082 }
1083
1084 public function set_sync_status( $status ) {
1085 return $this->save_metadata( 'google_calendar_sync_status', $status );
1086 }
1087
1088 public function get_sync_status() {
1089 return $this->get_metadata( 'google_calendar_sync_status' );
1090 }
1091 /**
1092 * Get all Google Event IDs for all bookings
1093 *
1094 * @return array
1095 */
1096 public function get_all_google_event_ids() {
1097 $meta_key = $this->meta_prefix . 'google_event_id';
1098
1099 $posts = get_posts(
1100 array(
1101 'post_type' => 'any',
1102 'posts_per_page' => -1,
1103 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering posts by meta key
1104 'meta_query' => array(
1105 array(
1106 'key' => $meta_key,
1107 'value' => '',
1108 'compare' => '!=',
1109 ),
1110 ),
1111 'fields' => 'ids',
1112 )
1113 );
1114
1115 if ( empty( $posts ) ) {
1116 return array();
1117 }
1118
1119 $event_ids = array();
1120 foreach ( $posts as $post_id ) {
1121 $event_id = get_post_meta( $post_id, $meta_key, true );
1122 if ( ! empty( $event_id ) ) {
1123 $event_ids[] = $event_id;
1124 }
1125 }
1126
1127 return $event_ids;
1128 }
1129
1130 /**
1131 * Get name.
1132 *
1133 * @since 1.0.0
1134 *
1135 * @return string
1136 */
1137 public function get_security_token() {
1138 return $this->get_prop( 'security_token' );
1139 }
1140
1141 /**
1142 * Generate and store a secure reschedule token for a booking
1143 *
1144 * @param int $booking_id
1145 * @return string The generated token
1146 */
1147 public function generate_security_token() {
1148 $token = bin2hex(random_bytes(4)); // 8 hex chars
1149 return $token;
1150 }
1151
1152 /**
1153 * Rotate the booking's security token so the previously-issued one cannot
1154 * be reused (e.g. after a payment is approved).
1155 *
1156 * @return void
1157 */
1158 public function rotate_security_token() {
1159 $meta_key = $this->meta_prefix . 'security_token';
1160 $new_token = $this->generate_security_token();
1161 update_post_meta( $this->id, $meta_key, $new_token );
1162 }
1163
1164 /**
1165 * Get the Stripe PaymentIntent id bound to this booking, if any.
1166 *
1167 * @return string
1168 */
1169 public function get_stripe_payment_intent_id() {
1170 return (string) get_post_meta( $this->id, '_tt_stripe_payment_intent_id', true );
1171 }
1172
1173 /**
1174 * Bind a Stripe PaymentIntent id to this booking. Used for replay
1175 * protection: a second make_payment call with a different intent will be
1176 * rejected.
1177 *
1178 * @param string $intent_id
1179 * @return void
1180 */
1181 public function set_stripe_payment_intent_id( $intent_id ) {
1182 update_post_meta( $this->id, '_tt_stripe_payment_intent_id', sanitize_text_field( (string) $intent_id ) );
1183 }
1184 }
1185