PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.28
Booktics – Appointment Booking Calendar for Service Businesses v1.0.28
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 +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 }