PluginProbe
Timetics – Appointment Booking Calendar & Scheduling / 1.0.38
Timetics – Appointment Booking Calendar & Scheduling v1.0.38
1.0.62 1.0.63 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 All 64 releases
← All changes | core/integrations/google/service/calendar.php +29 -91 1.0.62 → 1.0.38 View file →
@@ -5,10 +5,8 @@
5 5 * @package Timetics
6 6 */
7 7 namespace Timetics\Core\Integrations\Google\Service;
8 8
9 -defined( 'ABSPATH' ) || exit;
10 -
11 9 /**
12 10 * Class Calendar
13 11 */
14 12 class Calendar {
@@ -23,24 +21,8 @@
23 21 * @param array $api_filters Additional API filters for google calendar API
24 22 *
25 23 * @return array List of calendar events.
26 24 */
27 - /**
28 - * Format an instant as RFC3339 in UTC ("...Z").
29 - *
30 - * Google rejects bounds whose "+hh:mm" offset reaches it unescaped, so
31 - * every timeMin/timeMax the plugin sends goes through here.
32 - *
33 - * @param int|\DateTimeInterface $when Timestamp or date object.
34 - *
35 - * @return string
36 - */
37 - public static function to_rfc3339_utc( $when ) {
38 - $timestamp = $when instanceof \DateTimeInterface ? $when->getTimestamp() : (int) $when;
39 -
40 - return gmdate( 'Y-m-d\TH:i:s\Z', $timestamp );
41 - }
42 -
43 25 public function get_events( $user_id, $api_filters = array() ) {
44 26 $access_token = timetics_get_google_access_token($user_id);
45 27
46 28 if ( ! $access_token ) {
@@ -46,22 +28,15 @@
46 28 if ( ! $access_token ) {
47 29 return ['error' => 'Access token not found or expired.'];
48 30 }
49 31
50 - // Define the time range for the last 3 months.
51 - //
52 - // Always formatted as UTC with a trailing "Z" rather than an offset:
53 - // an offset like "+06:00" carries a plus sign that survives into the
54 - // query string, where Google reads it as a space. The bounds then fail
55 - // to parse and the API answers with no items at all — which this class
56 - // cannot distinguish from "no events", so every event silently
57 - // disappeared. "Z" sidesteps the escaping problem entirely.
58 - $three_months_ago = self::to_rfc3339_utc( strtotime( '-3 months' ) );
59 - $three_months_ahead = self::to_rfc3339_utc( strtotime( '+3 months' ) );
32 + // Define the time range for the last 3 months
33 + $three_months_ago = date('c', strtotime('-3 months'));
34 + $three_months_ahead = date('c', strtotime('+3 months')); // 3 months ahead date-time in RFC3339 format
60 35
61 36 $filters = array(
62 - 'timeMin' => $three_months_ago,
63 - 'timeMax' => $three_months_ahead,
37 + 'timeMin' => rawurlencode($three_months_ago),
38 + 'timeMax' => rawurlencode($three_months_ahead),
64 39 'orderBy' => 'startTime',
65 40 'singleEvents' => 'true',
66 41 );
67 42
@@ -98,60 +73,28 @@
98 73 if ( empty( $event['start'] ) ) {
99 74 continue;
100 75 }
101 76
102 - // Google sends `dateTime` for timed events and a date-only `date`
103 - // for all-day ones. All-day bounds have no time and no timezone, so
104 - // they must not be run through setTimezone() — that shifts the
105 - // wall-clock and used to collapse them to 00:00:00-00:00:00, which
106 - // blocked nothing at all. Note Google's all-day end date is
107 - // EXCLUSIVE: a single day off is 08-10 to 08-11.
108 - $all_day = empty( $event['start']['dateTime'] );
77 + $start = ! empty($event['start']['dateTime']) ? $event['start']['dateTime'] : $event['start']['date'];
78 + $end = ! empty($event['end']['dateTime']) ? $event['end']['dateTime'] : $event['end']['date'];
109 79
110 - $start = $all_day ? $event['start']['date'] : $event['start']['dateTime'];
111 - $end = $all_day ? $event['end']['date'] : $event['end']['dateTime'];
112 -
113 - if ( $all_day ) {
114 - $filtered_events[] = [
115 - 'id' => $event['id'] ?? '',
116 - 'all_day' => true,
117 - 'start_date' => $start,
118 - 'start_time' => '00:00:00',
119 - 'end_date' => $end,
120 - 'end_time' => '00:00:00',
121 - 'summary' => $event['summary'] ?? '',
122 - 'description' => $event['description'] ?? '',
123 - ];
124 -
125 - continue;
126 - }
127 -
128 - $timezone = $event['start']['timeZone'] ?? timetics_wp_timezone_string();
80 + $timezone = $event['start']['timeZone'] ?? wp_timezone_string();
129 81 $timezone = new \DateTimeZone( $timezone );
130 82
131 83 $start_dt = new \DateTime( $start );
132 - $end_dt = new \DateTime( $end );
84 + $start_dt->setTimezone( $timezone );
133 85
134 - // Absolute instants, captured before the display conversion below,
135 - // so overlap maths never has to reason about wall-clock strings.
136 - $start_timestamp = $start_dt->getTimestamp();
137 - $end_timestamp = $end_dt->getTimestamp();
138 -
139 - $start_dt->setTimezone( $timezone );
86 + $end_dt = new \DateTime( $end );
140 87 $end_dt->setTimezone( $timezone );
141 88
142 89 $filtered_events[] = [
143 - 'id' => $event['id'] ?? '',
144 - 'all_day' => false,
145 - 'start_date' => $start_dt->format( 'Y-m-d' ),
146 - 'start_time' => $start_dt->format( 'H:i:s' ),
147 - 'end_date' => $end_dt->format( 'Y-m-d' ),
148 - 'end_time' => $end_dt->format( 'H:i:s' ),
149 - 'start_timestamp' => $start_timestamp,
150 - 'end_timestamp' => $end_timestamp,
151 - 'timezone' => $timezone->getName(),
152 - 'summary' => $event['summary'] ?? '',
153 - 'description' => $event['description'] ?? '',
90 + 'id' => $event['id'] ?? '',
91 + 'start_date' => $start_dt->format( 'Y-m-d' ),
92 + 'start_time' => $start_dt->format( 'H:i:s' ),
93 + 'end_date' => $end_dt->format( 'Y-m-d' ),
94 + 'end_time' => $end_dt->format( 'H:i:s' ),
95 + 'summary' => $event['summary'] ?? '',
96 + 'description'=> $event['description'] ?? '',
154 97 ];
155 98 }
156 99
157 100 return $filtered_events;
@@ -366,24 +309,26 @@
366 309 $start_date = isset( $data['start']['date'] ) ? $data['start']['date'] : gmdate( 'Y-m-d' );
367 310 $start_time = isset( $data['start']['time'] ) ? $data['start']['time'] : gmdate( 'H:i:s' );
368 311 $end_date = isset( $data['end']['date'] ) ? $data['end']['date'] : gmdate( 'Y-m-d' );
369 312 $end_time = isset( $data['end']['time'] ) ? $data['end']['time'] : gmdate( 'H:i:s' );
370 - $timezone = isset( $data['timezone'] ) ? $data['timezone'] : timetics_wp_timezone_string();
313 + $timezone = isset( $data['timezone'] ) ? $data['timezone'] : 'Asia/Dhaka';
314 + $timezone_offset = $this->get_timezone_offset( $timezone );
371 315
372 - // Create DateTime objects with proper timezone to avoid double conversion.
373 - $start_datetime = new \DateTime( $start_date . ' ' . $start_time, new \DateTimeZone( $timezone ) );
374 - $end_datetime = new \DateTime( $end_date . ' ' . $end_time, new \DateTimeZone( $timezone ) );
316 + $start_date_time = $start_date . 'T' . $this->convertTo24HourFormat( $start_time ) . $timezone_offset;
317 + $end_date_time = $end_date . 'T' . $this->convertTo24HourFormat( $end_time ) . $timezone_offset;
375 318
376 - return [
319 + $date_time = [
377 320 'start' => [
378 - 'dateTime' => $start_datetime->format( \DateTime::RFC3339 ),
321 + 'dateTime' => $start_date_time,
379 322 'timeZone' => $timezone,
380 323 ],
381 324 'end' => [
382 - 'dateTime' => $end_datetime->format( \DateTime::RFC3339 ),
325 + 'dateTime' => $end_date_time,
383 326 'timeZone' => $timezone,
384 327 ],
385 328 ];
329 +
330 + return $date_time;
386 331 }
387 332
388 333 /**
389 334 * Convet 12 hours format to 24 hours format
@@ -392,11 +337,9 @@
392 337 *
393 338 * @return string
394 339 */
395 340 public function convertTo24HourFormat( $time ) {
396 - // Use gmdate() instead of wp_date() to avoid timezone conversion
397 - // since we're building an RFC3339 datetime string with explicit timezone offset.
398 - return gmdate( 'H:i:s', strtotime( $time ) );
341 + return date('H:i:s', strtotime( $time ) );
399 342 }
400 343
401 344 /**
402 345 * Prepare event create requested data
@@ -419,16 +362,11 @@
419 362 $args['start'] = [$date['start']];
420 363 $args['end'] = [$date['end']];
421 364
422 365 if ( $args['google_meet'] ) {
423 - // requestId must be unique per request; Google ignores the
424 - // conference create request (no Meet link generated) if it
425 - // matches a previously used id.
426 - $request_id = function_exists( 'wp_generate_uuid4' ) ? wp_generate_uuid4() : uniqid( 'tt-meet-', true );
427 -
428 366 $args['conferenceData'] = [
429 367 'createRequest' => [
430 - 'requestId' => $request_id,
368 + 'requestId' => 'sample123',
431 369 'conferenceSolutionKey' => ['type' => 'hangoutsMeet'],
432 370 ],
433 371 ];
434 372 }
@@ -438,9 +376,9 @@
438 376 'headers' => [
439 377 'Authorization' => 'Bearer ' . $access_token,
440 378 'Content-Type' => 'application/json; charset=utf-8',
441 379 ],
442 - 'body' => wp_json_encode( $args ),
380 + 'body' => json_encode( $args ),
443 381 ];
444 382
445 383 return $data;
446 384 }