PluginProbe
Timetics – Appointment Booking Calendar & Scheduling / 1.0.58
Timetics – Appointment Booking Calendar & Scheduling v1.0.58
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 / booking.php

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

1,183 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 if ( $entries ) {
805 $entry = $booking_entry->first();
806
807 $event = $entry->get_google_event();
808
809 if ( ! $event ) {
810 $event = $calendar->create_event( $event_data );
811 $entry->update( [
812 'google_event' => $event,
813 ] );
814 }
815 }
816
817 if ( $event ) {
818 update_post_meta( $this->id, $this->meta_prefix . 'calendar_event', $event );
819 }
820 }
821
822 /**
823 * Update calendar event
824 *
825 * @return bool
826 */
827 public function update_event() {
828 if ( ! timetics_google_setup() ) {
829 return;
830 }
831
832 $data = [
833 'summary' => timetics_get_option( 'booking_rescheduled_customer_email_title' ),
834 'description' => timetics_get_option( 'booking_rescheduled_customer_email_body' ),
835 ];
836
837 $calendar = new Calendar();
838 $event_data = $this->prepare_event( $data );
839 $calendar_event = $this->get_event();
840
841 if ( ! is_array( $calendar_event ) ) {
842 return;
843 }
844
845 if ( ! empty( $calendar_event['id'] ) ) {
846 // Update calendar event.
847 $event = $calendar->update_event( $calendar_event['id'], $event_data );
848
849 // update calendar event data.
850 update_post_meta( $this->id, $this->meta_prefix . 'calendar_event', $event );
851 }
852 }
853
854 /**
855 * Delete calendar event
856 *
857 * @return
858 */
859 public function delete_event() {
860 if ( ! timetics_google_setup() ) {
861 return;
862 }
863
864 $calendar = new Calendar();
865 $calendar_event = $this->get_event();
866 $access_token = timetics_get_google_access_token( $this->get_staff_id() );
867
868 if ( ! is_array( $calendar_event ) ) {
869 return;
870 }
871
872 if ( ! $access_token ) {
873 return;
874 }
875
876 if ( ! empty( $calendar_event['id'] ) ) {
877 // Update calendar event.
878 $event = $calendar->delete_event( $calendar_event['id'], $access_token );
879
880 // update calendar event data.
881 update_post_meta( $this->id, $this->meta_prefix . 'calendar_event', $event );
882 }
883 }
884
885 /**
886 * Prepare event data
887 *
888 * @return array
889 */
890 private function prepare_event( $data = [] ) {
891
892 $meeting_id = $this->get_appointment();
893 $staff_id = $this->get_staff_id();
894 $customer_id = $this->get_customer_id();
895 $location_type = $this->get_location_type();
896 $access_token = timetics_get_google_access_token( $staff_id );
897
898 if ( ! $access_token ) {
899 return false;
900 }
901
902 $meeting = new Appointment( $meeting_id );
903 $staff = new Staff( $staff_id );
904 $customer = new Customer( $customer_id );
905
906 $time = gmdate( 'h:i a', strtotime( $this->get_start_time() ) );
907 $date = gmdate( 'd F Y', strtotime( $this->get_start_date() ) );
908 $location = $this->get_location();
909
910 $placeholders = [
911 '{%meeting_title%}' => $meeting->get_name(),
912 '{%meeting_date%}' => $date,
913 '{%meeting_time%}' => $time,
914 '{%meeting_location%}' => $location,
915 '{%meeting_duration%}' => $meeting->get_duration(),
916 '{%host_name%}' => $staff->get_display_name(),
917 '{%host_email%}' => $staff->get_email(),
918 '{%customer_name%}' => $customer->get_display_name(),
919 '{%customer_email%}' => $customer->get_email(),
920 ];
921
922 $summary = ! empty( $data['summary'] ) ? timetics_replace_placeholder( $data['summary'], $placeholders ) : $meeting->get_name();
923 $description = ! empty( $data['description'] ) ? timetics_replace_placeholder( $data['description'], $placeholders ) : $meeting->get_description();
924
925 $description = apply_filters( 'timetics_booking_event_data', $description, $summary, $this );
926
927 $event = [
928 'summary' => $summary,
929 'description' => $description,
930 'start' => [
931 'date' => $this->get_start_date(),
932 'time' => $this->get_start_time(),
933 ],
934 'end' => [
935 'date' => $this->get_end_date(),
936 'time' => $this->get_end_time(),
937 ],
938 'attendees' => [
939 ['email' => $staff->get_email()],
940 ['email' => $customer->get_email()],
941 ],
942 'timezone' => $this->get_timezone(),
943 'google_meet' => 'google-meet' === $location_type,
944 'access_token' => $access_token,
945 ];
946
947 return $event;
948 }
949
950 /**
951 * Get all data of an object
952 *
953 * @return array
954 */
955 public function to_array() {
956 $data = [];
957
958 foreach ( $this->data as $key => $value ) {
959
960 $data[$key] = $this->get_prop( $key );
961 }
962
963 return $data;
964 }
965
966 /**
967 * Send notification after booking
968 *
969 * @return void
970 */
971 private function send_notification( $action ) {
972
973 do_action( 'timetics_booking_notification', $this, $action );
974 }
975
976 /**
977 * Get booking ids by appointment id
978 *
979 * @param array Collection of appointment ids
980 *
981 * @return array Collection of booking ids
982 */
983 public static function get_booking_ids_by_appointment( $appointment_ids, $per_page = -1 ) {
984 if ( empty( $appointment_ids ) ) {
985 return [];
986 }
987
988 $query = new WP_Query([
989 'post_type' => 'timetics-booking',
990 'post_status' => 'any',
991 'posts_per_page' => $per_page,
992 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering bookings by appointment
993 'meta_query' => [
994 [
995 'key' => '_tt_booking_appointment',
996 'value' => $appointment_ids,
997 'compare' => 'IN',
998 ],
999 ],
1000 ]);
1001
1002 return [
1003 'total' => $query->found_posts,
1004 'items' => $query->posts,
1005 ];
1006 }
1007
1008 /**
1009 * Get booking ids visible to a given user.
1010 *
1011 * Union of:
1012 * - bookings where the user is the assigned staff (`_tt_booking_staff`)
1013 * - bookings whose appointment is authored by the user
1014 *
1015 * @param integer $user_id WP user id.
1016 *
1017 * @return int[] Deduped list of booking post ids.
1018 */
1019 public static function get_visible_ids_for_user( $user_id ) {
1020 $user_id = (int) $user_id;
1021
1022 if ( ! $user_id ) {
1023 return [];
1024 }
1025
1026 $staff_ids = get_posts( [
1027 'post_type' => 'timetics-booking',
1028 'post_status' => 'any',
1029 'posts_per_page' => -1,
1030 'fields' => 'ids',
1031 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- needed to scope bookings to current staff user
1032 'meta_query' => [
1033 [
1034 'key' => '_tt_booking_staff',
1035 'value' => $user_id,
1036 ],
1037 ],
1038 ] );
1039
1040 $appointment_ids = Appointment::get_appointment_ids_by_author( $user_id );
1041
1042 $appointment_booking_ids = [];
1043 if ( ! empty( $appointment_ids ) ) {
1044 $appointment_booking_ids = get_posts( [
1045 'post_type' => 'timetics-booking',
1046 'post_status' => 'any',
1047 'posts_per_page' => -1,
1048 'fields' => 'ids',
1049 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- needed to scope bookings to appointments owned by current user
1050 'meta_query' => [
1051 [
1052 'key' => '_tt_booking_appointment',
1053 'value' => $appointment_ids,
1054 'compare' => 'IN',
1055 ],
1056 ],
1057 ] );
1058 }
1059
1060 return array_values( array_unique( array_map( 'intval', array_merge( $staff_ids, $appointment_booking_ids ) ) ) );
1061 }
1062
1063 /**
1064 * Get Google Calendar Event ID for this booking
1065 *
1066 * @return string
1067 */
1068 public function get_google_event_id() {
1069 return $this->get_metadata( 'google_event_id' );
1070 }
1071
1072 /**
1073 * Set Google Calendar Event ID for this booking
1074 *
1075 * @param string $event_id
1076 * @return bool
1077 */
1078 public function set_google_event_id( $event_id ) {
1079 return $this->save_metadata( 'google_event_id', $event_id );
1080 }
1081
1082 public function set_sync_status( $status ) {
1083 return $this->save_metadata( 'google_calendar_sync_status', $status );
1084 }
1085
1086 public function get_sync_status() {
1087 return $this->get_metadata( 'google_calendar_sync_status' );
1088 }
1089 /**
1090 * Get all Google Event IDs for all bookings
1091 *
1092 * @return array
1093 */
1094 public function get_all_google_event_ids() {
1095 $meta_key = $this->meta_prefix . 'google_event_id';
1096
1097 $posts = get_posts(
1098 array(
1099 'post_type' => 'any',
1100 'posts_per_page' => -1,
1101 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering posts by meta key
1102 'meta_query' => array(
1103 array(
1104 'key' => $meta_key,
1105 'value' => '',
1106 'compare' => '!=',
1107 ),
1108 ),
1109 'fields' => 'ids',
1110 )
1111 );
1112
1113 if ( empty( $posts ) ) {
1114 return array();
1115 }
1116
1117 $event_ids = array();
1118 foreach ( $posts as $post_id ) {
1119 $event_id = get_post_meta( $post_id, $meta_key, true );
1120 if ( ! empty( $event_id ) ) {
1121 $event_ids[] = $event_id;
1122 }
1123 }
1124
1125 return $event_ids;
1126 }
1127
1128 /**
1129 * Get name.
1130 *
1131 * @since 1.0.0
1132 *
1133 * @return string
1134 */
1135 public function get_security_token() {
1136 return $this->get_prop( 'security_token' );
1137 }
1138
1139 /**
1140 * Generate and store a secure reschedule token for a booking
1141 *
1142 * @param int $booking_id
1143 * @return string The generated token
1144 */
1145 public function generate_security_token() {
1146 $token = bin2hex(random_bytes(4)); // 8 hex chars
1147 return $token;
1148 }
1149
1150 /**
1151 * Rotate the booking's security token so the previously-issued one cannot
1152 * be reused (e.g. after a payment is approved).
1153 *
1154 * @return void
1155 */
1156 public function rotate_security_token() {
1157 $meta_key = $this->meta_prefix . 'security_token';
1158 $new_token = $this->generate_security_token();
1159 update_post_meta( $this->id, $meta_key, $new_token );
1160 }
1161
1162 /**
1163 * Get the Stripe PaymentIntent id bound to this booking, if any.
1164 *
1165 * @return string
1166 */
1167 public function get_stripe_payment_intent_id() {
1168 return (string) get_post_meta( $this->id, '_tt_stripe_payment_intent_id', true );
1169 }
1170
1171 /**
1172 * Bind a Stripe PaymentIntent id to this booking. Used for replay
1173 * protection: a second make_payment call with a different intent will be
1174 * rejected.
1175 *
1176 * @param string $intent_id
1177 * @return void
1178 */
1179 public function set_stripe_payment_intent_id( $intent_id ) {
1180 update_post_meta( $this->id, '_tt_stripe_payment_intent_id', sanitize_text_field( (string) $intent_id ) );
1181 }
1182 }
1183