← All changes
|
core/module/google-calendar/Controller/calendar-sync-controller.php
+75
-15
1.0.13
→
1.0.28
View file →
| @@ -6,8 +6,10 @@ | ||
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | 8 | namespace Booktics\Module\Google_Calendar\Controller; |
| 9 | 9 | |
| 10 | +if ( ! defined( 'ABSPATH' ) ) exit; | |
| 11 | + | |
| 10 | 12 | use Booktics\Abstracts\Base_Rest_Controller; |
| 11 | 13 | use Booktics\Module\Google_Calendar\Service\Calendar; |
| 12 | 14 | use Booktics\Models\Appointment_Model; |
| 13 | 15 | use Booktics\Models\Service_Model; |
| @@ -65,8 +67,9 @@ | ||
| 65 | 67 | add_filter( 'booktics_booking_calendar_urls', array( $this, 'get_add_to_calendar_links' ), 10, 2 ); |
| 66 | 68 | |
| 67 | 69 | add_action( 'booktics_after_appointment_created', array( $this, 'handle_appointment_created' ), 10, 1 ); |
| 68 | 70 | add_action( 'booktics_after_appointment_updated', array( $this, 'handle_appointment_updated' ), 10, 1 ); |
| 71 | + add_action( 'booktics_after_appointment_cancelled', array( $this, 'handle_appointment_cancelled' ), 10, 1 ); | |
| 69 | 72 | add_action( 'before_delete_post', array( $this, 'capture_appointment_data_before_delete' ), 10, 1 ); |
| 70 | 73 | add_action( 'booktics_after_appointment_deleted', array( $this, 'handle_appointment_deleted' ), 10, 1 ); |
| 71 | 74 | } |
| 72 | 75 | |
| @@ -139,9 +142,11 @@ | ||
| 139 | 142 | if ( $event_id ) { |
| 140 | 143 | update_post_meta( $appointment->id, '_google_calendar_event_id', $event_id ); |
| 141 | 144 | } |
| 142 | 145 | } catch ( \Throwable $e ) { |
| 143 | - error_log( '[Booktics Google Calendar] Failed to create Google Calendar event: ' . $e->getMessage() ); | |
| 146 | + if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) { | |
| 147 | + error_log( '[Booktics Google Calendar] Failed to create Google Calendar event: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log | |
| 148 | + } | |
| 144 | 149 | } |
| 145 | 150 | } |
| 146 | 151 | |
| 147 | 152 | /** |
| @@ -157,13 +162,54 @@ | ||
| 157 | 162 | if ( $event_id ) { |
| 158 | 163 | $this->calendar->update_event( $event_id, $appointment, $team_member_id ); |
| 159 | 164 | } |
| 160 | 165 | } catch ( \Throwable $e ) { |
| 161 | - error_log( 'Failed to updated Google Calendar event: ' . $e->getMessage() ); | |
| 166 | + if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) { | |
| 167 | + error_log( 'Failed to updated Google Calendar event: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log | |
| 168 | + } | |
| 162 | 169 | } |
| 163 | 170 | } |
| 164 | 171 | |
| 165 | 172 | /** |
| 173 | + * On appointment cancelled, remove its Google Calendar event. | |
| 174 | + * | |
| 175 | + * A cancellation only flips the appointment status (no post deletion), so | |
| 176 | + * the delete hooks never fire. Without this the calendar event lingers in | |
| 177 | + * the team member's calendar for a booking that was auto-cancelled (e.g. an | |
| 178 | + * abandoned/unpaid online booking) or refunded, keeping the slot visually | |
| 179 | + * blocked. Deleting the event frees the calendar slot to match the freed | |
| 180 | + * appointment slot. | |
| 181 | + * | |
| 182 | + * @param \Booktics\Models\Appointment_Model $appointment appointment data | |
| 183 | + */ | |
| 184 | + public function handle_appointment_cancelled( $appointment ) { | |
| 185 | + try { | |
| 186 | + if ( ! isset( $appointment->id ) ) { | |
| 187 | + return; | |
| 188 | + } | |
| 189 | + | |
| 190 | + $event_id = get_post_meta( $appointment->id, '_google_calendar_event_id', true ); | |
| 191 | + $team_member_id = get_post_meta( $appointment->id, 'team_member_id', true ); | |
| 192 | + | |
| 193 | + if ( ! $team_member_id && isset( $appointment->team_member_id ) ) { | |
| 194 | + $team_member_id = $appointment->team_member_id; | |
| 195 | + } | |
| 196 | + | |
| 197 | + if ( ! $event_id || ! $team_member_id ) { | |
| 198 | + return; | |
| 199 | + } | |
| 200 | + | |
| 201 | + if ( $this->calendar->delete_event( $event_id, $team_member_id ) ) { | |
| 202 | + delete_post_meta( $appointment->id, '_google_calendar_event_id' ); | |
| 203 | + } | |
| 204 | + } catch ( \Throwable $e ) { | |
| 205 | + if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) { | |
| 206 | + error_log( '[Booktics Google Calendar] Failed to delete Google Calendar event on cancel: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log | |
| 207 | + } | |
| 208 | + } | |
| 209 | + } | |
| 210 | + | |
| 211 | + /** | |
| 166 | 212 | * Capture appointment Google Calendar data before WordPress deletes the post. |
| 167 | 213 | * |
| 168 | 214 | * @param int $post_id Post ID being deleted |
| 169 | 215 | */ |
| @@ -203,9 +249,11 @@ | ||
| 203 | 249 | unset( $this->deletion_data[ $appointment->id ] ); |
| 204 | 250 | } |
| 205 | 251 | } |
| 206 | 252 | } catch ( \Throwable $e ) { |
| 207 | - error_log( '[Booktics Google Calendar] Exception in handle_appointment_deleted: ' . $e->getMessage() ); | |
| 253 | + if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) { | |
| 254 | + error_log( '[Booktics Google Calendar] Exception in handle_appointment_deleted: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log | |
| 255 | + } | |
| 208 | 256 | } |
| 209 | 257 | } |
| 210 | 258 | |
| 211 | 259 | /** |
| @@ -277,13 +325,26 @@ | ||
| 277 | 325 | if ( ! $formatted_appointment['service']['enable_google_meet'] ) { |
| 278 | 326 | return $notification_flow_data; |
| 279 | 327 | } |
| 280 | 328 | |
| 281 | - $user_id = get_current_user_id(); | |
| 282 | - $tokens = get_user_meta( $user_id, 'booktics_google_auth_token', true ); | |
| 283 | - $access_token = $tokens['access_token'] ?? ''; | |
| 329 | + $user_id = $formatted_appointment['team']['id'] ?? null; | |
| 284 | 330 | |
| 331 | + if ( empty( $user_id ) ) { | |
| 332 | + return $notification_flow_data; | |
| 333 | + } | |
| 334 | + | |
| 335 | + // get_access_token_for_api() also refreshes the token when it has expired. | |
| 336 | + $access_token = $this->calendar->get_access_token_for_api( $user_id ); | |
| 337 | + | |
| 285 | 338 | if ( empty( $access_token ) ) { |
| 339 | + // The booking's own team member owns the Meet link, so a member who never | |
| 340 | + // connected Google - or whose token has died - silently drops the link | |
| 341 | + // from the email. Log it, otherwise the only symptom is an empty | |
| 342 | + // google_meet_url smart tag with nothing to explain it. | |
| 343 | + if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) { | |
| 344 | + error_log( '[Booktics Google Calendar] No usable Google token for team member ' . $user_id . '; skipping Meet link.' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log | |
| 345 | + } | |
| 346 | + | |
| 286 | 347 | return $notification_flow_data; |
| 287 | 348 | } |
| 288 | 349 | try { |
| 289 | 350 | // Get the WordPress timezone |
| @@ -289,11 +350,12 @@ | ||
| 289 | 350 | // Get the WordPress timezone |
| 290 | 351 | $timezone = wp_timezone(); |
| 291 | 352 | $timezone_string = $timezone->getName(); |
| 292 | 353 | |
| 293 | - // Convert timestamps to ISO 8601 format with timezone | |
| 294 | - $start_timestamp = strtotime( $formatted_appointment['start_time'] ); | |
| 295 | - $end_timestamp = strtotime( $formatted_appointment['end_time'] ); | |
| 354 | + // Appointment date/time are stored in UTC. Combine them so the Meet event lands on the actual appointment date instead of "today". | |
| 355 | + $appointment_date = $formatted_appointment['date'] ?? ''; | |
| 356 | + $start_timestamp = strtotime( $appointment_date . ' ' . $formatted_appointment['start_time'] . ' UTC' ); | |
| 357 | + $end_timestamp = strtotime( $appointment_date . ' ' . $formatted_appointment['end_time'] . ' UTC' ); | |
| 296 | 358 | |
| 297 | 359 | // Format dates in ISO 8601 format with timezone using wp_date |
| 298 | 360 | $start_time = wp_date( 'c', $start_timestamp, $timezone ); |
| 299 | 361 | $end_time = wp_date( 'c', $end_timestamp, $timezone ); |
| @@ -358,16 +420,14 @@ | ||
| 358 | 420 | private function get_google_calendar_api_filters( $args ) { |
| 359 | 421 | $api_filters = array(); |
| 360 | 422 | |
| 361 | 423 | // Get start and end dates from args in appropriate format |
| 362 | - if ( ! isset( $args['start_date'] ) ) { | |
| 363 | - $args['start_date'] = wp_date( DATE_RFC3339, strtotime( $args['start_date'] ) ); | |
| 364 | - $api_filters['timeMin'] = rawurlencode( $args['start_date'] ); | |
| 424 | + if ( isset( $args['start_date'] ) ) { | |
| 425 | + $api_filters['timeMin'] = rawurlencode( wp_date( DATE_RFC3339, strtotime( $args['start_date'] ) ) ); | |
| 365 | 426 | } |
| 366 | 427 | |
| 367 | - if ( ! isset( $args['end_date'] ) ) { | |
| 368 | - $args['end_date'] = wp_date( DATE_RFC3339, strtotime( $args['end_date'] ) ); | |
| 369 | - $api_filters['timeMax'] = rawurlencode( $args['end_date'] ); | |
| 428 | + if ( isset( $args['end_date'] ) ) { | |
| 429 | + $api_filters['timeMax'] = rawurlencode( wp_date( DATE_RFC3339, strtotime( $args['end_date'] ) ) ); | |
| 370 | 430 | } |
| 371 | 431 | |
| 372 | 432 | return $api_filters; |
| 373 | 433 | } |