| @@ -7,11 +7,12 @@ | ||
| 7 | 7 | * @package Timetics |
| 8 | 8 | */ |
| 9 | 9 | namespace Timetics\Core\Appointments; |
| 10 | 10 | |
| 11 | +defined( 'ABSPATH' ) || exit; | |
| 12 | + | |
| 11 | 13 | use DateTime; |
| 12 | 14 | use Timetics\Base\PostModel; |
| 13 | -use Timetics\Core\Bookings\Booking; | |
| 14 | 15 | use Timetics\Core\Bookings\Booking_Entry; |
| 15 | 16 | use Timetics\Core\Staffs\Staff; |
| 16 | 17 | use WP_Query; |
| 17 | 18 | |
| @@ -84,8 +85,10 @@ | ||
| 84 | 85 | 'pabbly_hook_overwrite' => '', |
| 85 | 86 | 'zapier_hook_overwrite' => '', |
| 86 | 87 | 'pabbly_webook' => '', |
| 87 | 88 | 'zapier_webook' => '', |
| 89 | + 'flowmattic_hook_overwrite' => '', | |
| 90 | + 'flowmattic_webhook' => '', | |
| 88 | 91 | 'min_notice_time' => '', |
| 89 | 92 | 'notifications' => [ |
| 90 | 93 | 'booking_created_email_form' => '', |
| 91 | 94 | 'booking_created_email_title' => '', |
| @@ -425,8 +428,14 @@ | ||
| 425 | 428 | } |
| 426 | 429 | public function get_zapier_hook_overwrite() { |
| 427 | 430 | return $this->get_prop( 'zapier_hook_overwrite' ); |
| 428 | 431 | } |
| 432 | + public function get_flowmattic_webhook() { | |
| 433 | + return $this->get_prop( 'flowmattic_webhook' ); | |
| 434 | + } | |
| 435 | + public function get_flowmattic_hook_overwrite() { | |
| 436 | + return $this->get_prop( 'flowmattic_hook_overwrite' ); | |
| 437 | + } | |
| 429 | 438 | public function get_min_notice_time() { |
| 430 | 439 | return $this->get_prop( 'min_notice_time' ); |
| 431 | 440 | } |
| 432 | 441 | public function get_guest_enabled() { |
| @@ -554,9 +563,9 @@ | ||
| 554 | 563 | */ |
| 555 | 564 | public function get_author() { |
| 556 | 565 | $post = get_post( $this->id ); |
| 557 | 566 | |
| 558 | - return $post->post_author; | |
| 567 | + return $post ? (int) $post->post_author : 0; | |
| 559 | 568 | } |
| 560 | 569 | |
| 561 | 570 | /** |
| 562 | 571 | * Get seat plan settings |
| @@ -666,13 +675,15 @@ | ||
| 666 | 675 | $args = [ |
| 667 | 676 | 'post_title' => $this->data['name'], |
| 668 | 677 | 'post_type' => $this->post_type, |
| 669 | 678 | 'post_status' => 'publish', |
| 670 | - 'post_author' => get_current_user_id(), | |
| 671 | 679 | ]; |
| 672 | 680 | |
| 673 | 681 | if ( ! empty( $this->id ) ) { |
| 674 | 682 | $args['ID'] = $this->id; |
| 683 | + } else { | |
| 684 | + // Only on creation — setting this on update reassigns post_author to whoever edits it. | |
| 685 | + $args['post_author'] = get_current_user_id(); | |
| 675 | 686 | } |
| 676 | 687 | |
| 677 | 688 | // Insert or Update appointment. |
| 678 | 689 | $appoint_id = wp_insert_post( $args ); |
| @@ -764,9 +775,10 @@ | ||
| 764 | 775 | ]; |
| 765 | 776 | |
| 766 | 777 | $args = wp_parse_args( $args, $defaults ); |
| 767 | 778 | |
| 768 | - if ( ! empty( $args['staff'] ) ) { | |
| 779 | + // isset()/'' check, not empty() — empty(0) skipped this filter, leaking all staff data to guests. | |
| 780 | + if ( isset( $args['staff'] ) && '' !== $args['staff'] ) { | |
| 769 | 781 | $args['meta_query'][] = [ |
| 770 | 782 | 'key' => '_tt_apointment_staff', |
| 771 | 783 | 'value' => $args['staff'], |
| 772 | 784 | 'compare' => 'LIKE', |
| @@ -834,8 +846,10 @@ | ||
| 834 | 846 | 'fleunt_crm_webhook' => $this->get_fleunt_crm_webhook(), |
| 835 | 847 | 'pabbly_hook_overwrite' => $this->get_pabbly_hook_overwrite(), |
| 836 | 848 | 'pabbly_webook' => $this->get_pabbly_webook(), |
| 837 | 849 | 'zapier_webook' => $this->get_zapier_webook(), |
| 850 | + 'flowmattic_webhook' => $this->get_flowmattic_webhook(), | |
| 851 | + 'flowmattic_hook_overwrite' => $this->get_flowmattic_hook_overwrite(), | |
| 838 | 852 | 'min_notice_time' => $this->get_min_notice_time(), |
| 839 | 853 | 'zapier_hook_overwrite' => $this->get_zapier_hook_overwrite(), |
| 840 | 854 | 'guest_enabled' => $this->get_guest_enabled(), |
| 841 | 855 | 'guest_limit' => $this->get_guest_limit(), |
| @@ -915,9 +929,11 @@ | ||
| 915 | 929 | |
| 916 | 930 | $booked = $booked_entry->get_booked(); |
| 917 | 931 | } |
| 918 | 932 | |
| 919 | - $datetime = $this->convert_timezone( $time, $time_zone ); | |
| 933 | + // $time is only a clock reading, so pass the slot's own date or | |
| 934 | + // today's DST offset gets applied to a date in the other season. | |
| 935 | + $datetime = $this->convert_timezone( $date . ' ' . gmdate( 'g:ia', $time ), $time_zone ); | |
| 920 | 936 | |
| 921 | 937 | $slot = [ |
| 922 | 938 | 'status' => $status, |
| 923 | 939 | 'start_time' => $datetime->format( 'g:ia' ), |
| @@ -954,16 +970,21 @@ | ||
| 954 | 970 | 'date' => $this->convert_timezone( $date, $this->get_timezone())->format('Y-m-d'), |
| 955 | 971 | ] ); |
| 956 | 972 | |
| 957 | 973 | foreach ( $entries as $entry ) { |
| 958 | - $booking = new Booking( $entry->get_booking_id() ); | |
| 959 | 974 | $start_time = $entry->get_start(); |
| 960 | 975 | $end_time = $entry->get_end(); |
| 961 | 976 | |
| 962 | 977 | // Check if current time slot falls within the booking duration |
| 963 | - if ( $this->is_time_in_range( $time, $start_time, $end_time ) ) { | |
| 964 | - return $entry; | |
| 978 | + if ( ! $this->is_time_in_range( $time, $start_time, $end_time ) ) { | |
| 979 | + continue; | |
| 965 | 980 | } |
| 981 | + | |
| 982 | + if ( $this->is_orphan_entry( $entry ) ) { | |
| 983 | + continue; | |
| 984 | + } | |
| 985 | + | |
| 986 | + return $entry; | |
| 966 | 987 | } |
| 967 | 988 | |
| 968 | 989 | return false; |
| 969 | 990 | } |
| @@ -968,8 +989,30 @@ | ||
| 968 | 989 | return false; |
| 969 | 990 | } |
| 970 | 991 | |
| 971 | 992 | /** |
| 993 | + * Whether an entry outlived its booking and should stop blocking the slot. | |
| 994 | + * | |
| 995 | + * Only single-booking entries qualify: a shared entry names just its first | |
| 996 | + * booker in `booking_id`, and entries with none belong to events/imports. | |
| 997 | + * | |
| 998 | + * @param Booking_Entry $entry | |
| 999 | + * | |
| 1000 | + * @return bool | |
| 1001 | + */ | |
| 1002 | + private function is_orphan_entry( $entry ) { | |
| 1003 | + $booking_id = $entry->get_booking_id(); | |
| 1004 | + | |
| 1005 | + if ( ! $booking_id || intval( $entry->get_booked() ) > 1 ) { | |
| 1006 | + return false; | |
| 1007 | + } | |
| 1008 | + | |
| 1009 | + $booking = get_post( $booking_id ); | |
| 1010 | + | |
| 1011 | + return ! $booking || 'timetics-booking' !== $booking->post_type; | |
| 1012 | + } | |
| 1013 | + | |
| 1014 | + /** | |
| 972 | 1015 | * Check if a time falls within a given time range |
| 973 | 1016 | * |
| 974 | 1017 | * @param string $current_time Current time in h:i a format |
| 975 | 1018 | * @param string $start_time Start time in h:i a format |
| @@ -1050,9 +1093,10 @@ | ||
| 1050 | 1093 | $start = strtotime( $day['start'] ); |
| 1051 | 1094 | $end = strtotime( $day['end'] ); |
| 1052 | 1095 | |
| 1053 | 1096 | for ( $time = $start; $time <= $end; $time += $interval ) { |
| 1054 | - $datetime = $this->convert_timezone( $time, $time_zone ); | |
| 1097 | + // Slot's own date, see get_schedule_by_date(). | |
| 1098 | + $datetime = $this->convert_timezone( $date . ' ' . gmdate( 'g:ia', $time ), $time_zone ); | |
| 1055 | 1099 | $slot = $datetime->format( 'g:ia' ); |
| 1056 | 1100 | $slots[] = $slot; |
| 1057 | 1101 | } |
| 1058 | 1102 | } |