PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.29
Booktics – Appointment Booking Calendar for Service Businesses v1.0.29
1.0.29 1.0.28 1.0.27 1.0.26 1.0.25 1.0.24 1.0.23 1.0.22 1.0.21 1.0.20 1.0.19 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.2 1.0.3 All 31 releases
← All changes | core/module/google-calendar/Controller/calendar-sync-controller.php +61 -7 1.0.21 → 1.0.29 View file →
@@ -67,8 +67,9 @@
67 67 add_filter( 'booktics_booking_calendar_urls', array( $this, 'get_add_to_calendar_links' ), 10, 2 );
68 68
69 69 add_action( 'booktics_after_appointment_created', array( $this, 'handle_appointment_created' ), 10, 1 );
70 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 );
71 72 add_action( 'before_delete_post', array( $this, 'capture_appointment_data_before_delete' ), 10, 1 );
72 73 add_action( 'booktics_after_appointment_deleted', array( $this, 'handle_appointment_deleted' ), 10, 1 );
73 74 }
74 75
@@ -161,9 +162,9 @@
161 162 if ( $event_id ) {
162 163 $this->calendar->update_event( $event_id, $appointment, $team_member_id );
163 164 }
164 165 } catch ( \Throwable $e ) {
165 - if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
166 + if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
166 167 error_log( 'Failed to updated Google Calendar event: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
167 168 }
168 169 }
169 170 }
@@ -168,8 +169,47 @@
168 169 }
169 170 }
170 171
171 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 + /**
172 212 * Capture appointment Google Calendar data before WordPress deletes the post.
173 213 *
174 214 * @param int $post_id Post ID being deleted
175 215 */
@@ -285,13 +325,26 @@
285 325 if ( ! $formatted_appointment['service']['enable_google_meet'] ) {
286 326 return $notification_flow_data;
287 327 }
288 328
289 - $user_id = get_current_user_id();
290 - $tokens = get_user_meta( $user_id, 'booktics_google_auth_token', true );
291 - $access_token = $tokens['access_token'] ?? '';
329 + $user_id = $formatted_appointment['team']['id'] ?? null;
292 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 +
293 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 +
294 347 return $notification_flow_data;
295 348 }
296 349 try {
297 350 // Get the WordPress timezone
@@ -297,11 +350,12 @@
297 350 // Get the WordPress timezone
298 351 $timezone = wp_timezone();
299 352 $timezone_string = $timezone->getName();
300 353
301 - // Convert timestamps to ISO 8601 format with timezone
302 - $start_timestamp = strtotime( $formatted_appointment['start_time'] );
303 - $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' );
304 358
305 359 // Format dates in ISO 8601 format with timezone using wp_date
306 360 $start_time = wp_date( 'c', $start_timestamp, $timezone );
307 361 $end_time = wp_date( 'c', $end_timestamp, $timezone );