36 public const EXTERNAL_LINK =
'https://www.bitrix24.com/controller/google_calendar_push.php?target_host=';
41 private $syncTransport;
42 private $nextSyncToken =
'',
45 $calendarList = array(),
46 $defaultReminderData = array(),
47 $calendarColors =
false,
49 $eventMapping = array(
50 'DAV_XML_ID' =>
'iCalUID',
53 'CAL_DAV_LABEL' =>
'etag'
58 private $connectionId;
62 private $nextPageToken =
'';
74 $userId = \CCalendar::GetUserId();
76 $this->userId = $userId;
77 $this->connectionId = $connectionId;
89 $this->syncTransport->stopChannel($channelId, $resourceId);
92 if (is_string($error))
109 $channel = $this->syncTransport->openCalendarListChannel($this->makeChannelParams($name, self::CONNECTION_CHANNEL_TYPE));
110 if (!$this->syncTransport->getErrors())
112 $channel[
'expiration'] = Type\DateTime::createFromTimestamp($channel[
'expiration']/1000);
117 if (is_string($error))
125 private function makeChannelParams($inputSecretWord, $type)
127 if (defined(
'BX24_HOST_NAME') && BX24_HOST_NAME)
129 $externalUrl = self::EXTERNAL_LINK . BX24_HOST_NAME;
134 if (defined(
'SITE_SERVER_NAME') && SITE_SERVER_NAME)
136 $host = SITE_SERVER_NAME;
140 $host = Option::get(
'main',
'server_name', $request->getHttpHost());
143 $externalUrl =
'https://' . $host .
'/bitrix/tools/calendar/push.php';
147 'id' => $type.
'_'.$this->userId.
'_'.md5($inputSecretWord. time()),
148 'type' =>
'web_hook',
149 'address' => $externalUrl,
162 $channel = $this->syncTransport->openEventsWatchChannel(
164 $this->makeChannelParams($calendarId, self::SECTION_CHANNEL_TYPE)
167 if (!$this->syncTransport->getErrors())
169 $channel[
'expiration'] = Type\DateTime::createFromTimestamp($channel[
'expiration']/1000);
201 return $this->syncTransport->getErrors();
204 private function setColors()
206 if ($this->calendarColors ===
false || $this->eventColors ===
false)
208 $cacheTime = 86400 * 7;
213 $cache = \Bitrix\Main\Data\Cache::createInstance();
214 $cacheId =
"google_colors";
215 $cachePath =
'googlecolors';
217 if ($cache->initCache($cacheTime, $cacheId, $cachePath))
219 $res = $cache->getVars();
220 $colorData = $res[
"colorData"];
224 if (!$cacheTime || empty($colorData))
226 $colorData = $this->syncTransport->getColors();
227 if ($cacheTime && isset($cache, $cacheId, $cachePath))
229 $cache->startDataCache($cacheTime, $cacheId, $cachePath);
230 $cache->endDataCache(array(
231 "colorData" => $colorData
236 $this->calendarColors = array();
237 $this->eventColors = array();
238 if (is_array($colorData) && !empty($colorData[
'calendar']) && !empty($colorData[
'event']))
240 foreach ($colorData[
'calendar'] as $key => $color)
242 $this->calendarColors[$key] = $color;
245 foreach ($colorData[
'event'] as $key => $color)
247 $this->eventColors[$key] = $color;
258 private function getCalendarColor($colorId, $background =
true)
260 $calendarColors = is_array($this->calendarColors) ? $this->calendarColors : array();
261 return $calendarColors[$colorId][($background ?
'background' :
'foreground')];
269 private function getEventColor($colorId, $background =
true)
271 $eventColors = is_array($this->eventColors) ? $this->eventColors : array();
272 return $eventColors[$colorId][($background ?
'background' :
'foreground')];
282 $connectionError = $this->syncTransport->getErrorByCode(
'CONNECTION');
283 if ($connectionError)
285 return $connectionError[
'message'];
302 if (empty($this->calendarList))
304 $response = $this->syncTransport->getCalendarList($this->getCalendarListParams($syncToken));
307 if ($response && !empty($response[
'items']))
309 foreach($response[
'items'] as $calendarItem)
311 $calendarItem[
'backgroundColor'] = $this->getCalendarColor($calendarItem[
'colorId']);
312 $calendarItem[
'textColor'] = $this->getCalendarColor($calendarItem[
'colorId'],
true);
313 $this->calendarList[] = $calendarItem;
316 $this->nextSyncToken = $response[
'nextSyncToken'];
320 return $this->calendarList;
328 return $this->nextSyncToken;
340 $this->nextSyncToken = $calendarData[
'SYNC_TOKEN'] ??
'';
341 $this->nextPageToken = $calendarData[
'PAGE_TOKEN'] ??
'';
343 if (!empty($response = $this->runSyncEvents($calendarData[
'GAPI_CALENDAR_ID'])))
345 return $this->processResponseReceivingEvents($response);
358 $calendar = $this->getCalendarById(
'primary');
359 return !empty($calendar) ? $calendar[
'id'] :
'';
368 private function getCalendarById($calendarId)
372 foreach ($this->calendarList as $calendar)
374 if (($calendar[
'id'] == $calendarId) || (isset($calendar[
'primary']) && $calendarId ==
'primary'))
391 return $this->syncTransport->deleteEvent($eventId, urlencode($calendarId));
401 public function saveEvent($eventData, $calendarId, $parameters = []): ?array
403 $params[
'editInstance'] = $parameters[
'editInstance'] ??
false;
404 $params[
'originalDavXmlId'] = $parameters[
'originalDavXmlId'] ??
null;
405 $params[
'editParentEvents'] = $parameters[
'editParentEvents'] ??
false;
406 $params[
'editNextEvents'] = $parameters[
'editNextEvents'] ??
false;
407 $params[
'calendarId'] = $calendarId;
408 $params[
'instanceTz'] = $parameters[
'instanceTz'] ??
null;
409 $params[
'originalDateFrom'] = $eventData[
'ORIGINAL_DATE_FROM'] ??
null;
410 $params[
'gEventId'] = $eventData[
'G_EVENT_ID'] ?: str_replace(
'@google.com',
'', $eventData[
'DAV_XML_ID']);
411 $params[
'syncCaldav'] = $parameters[
'syncCaldav'] ??
false;
413 $newEvent = $this->prepareToSaveEvent($eventData, $params);
415 $externalEvent = $this->sendToSaveEvent($newEvent, $params);
420 'DAV_XML_ID' => $externalEvent[
'iCalUID'],
421 'CAL_DAV_LABEL' => $externalEvent[
'etag'],
422 'ORIGINAL_DATE_FROM' => $externalEvent[
'originalStartTime'] ? $eventData[
'ORIGINAL_DATE_FROM'] :
null,
423 'G_EVENT_ID' => $externalEvent[
'id'],
437 public function saveBatchEvents(array $events,
string $gApiCalendarId, array $params): array
439 $responseFields = [];
442 foreach ($events as $event)
444 $localEvent[
'gEventId'] = $event[
'gEventId'];
445 $partBody = $this->prepareToSaveEvent($event);
446 $localEvent[
'partBody'] = Web\Json::encode($partBody, JSON_UNESCAPED_SLASHES);
447 $prepareEvents[$event[
'ID']] = $localEvent;
450 $externalEvents = $this->syncTransport->sendBatchEvents($prepareEvents, $gApiCalendarId, $params);
454 foreach ($externalEvents as $key => $externalEvent)
456 $responseFields[$key][
'DAV_XML_ID'] = $externalEvent[
'iCalUID'];
457 $responseFields[$key][
'CAL_DAV_LABEL'] = $externalEvent[
'etag'];
458 $responseFields[$key][
'G_EVENT_ID'] = $externalEvent[
'id'];
459 $responseFields[$key][
'ORIGINAL_DATE_FROM'] = $externalEvent[
'originalStartTime'] ? $events[$key][
'ORIGINAL_DATE_FROM'] :
null;
463 return $responseFields;
468 if (Loader::includeModule(
'dav') && !empty($this->connectionId))
470 CDavConnection::Update($this->connectionId, [
471 "LAST_RESULT" => $lastResult,
472 "SYNCHRONIZED" => ConvertTimeStamp(time(),
"FULL"),
478 AddMessage2Log(
"Bad interaction with Google calendar for connectionId: " . $this->connectionId .
" " .$lastResult,
"calendar");
492 private function prepareEvent($event): array
495 'TZ_FROM' => $this->defaultTimezone,
496 'TZ_TO' => $this->defaultTimezone
499 foreach ($this->eventMapping as $internalKey => $externalKey)
501 $returnData[$internalKey] = (isset($event[$externalKey]) ? $event[$externalKey] :
'');
504 $returnData[
'iCalUID'] = $event[
'iCalUID'];
505 $returnData[
'DAV_XML_ID'] = $event[
'iCalUID'];
506 $returnData[
'G_EVENT_ID'] = $event[
'id'];
508 if (!empty($event[
'description']))
510 $description = str_replace(
"<br>",
"\r\n", $event[
'description']);
511 $returnData[
"DESCRIPTION"] = trim(\CTextParser::clearAllTags($description));
514 if (empty($event[
'summary']))
516 $returnData[
'NAME'] = GetMessage(
'EC_T_NEW_EVENT');
519 if (!empty($event[
'transparency']) && $event[
'transparency'] ==
'transparent')
521 $returnData[
'ACCESSIBILITY'] =
'free';
525 $returnData[
'ACCESSIBILITY'] =
'busy';
528 if (!empty($event[
'visibility']) && $event[
'visibility'] ===
'private')
530 $returnData[
'PRIVATE_EVENT'] =
true;
534 $returnData[
'PRIVATE_EVENT'] =
false;
537 $returnData[
'OWNER_ID'] = $this->userId;
538 $returnData[
'CREATED_BY'] = $this->userId;
539 $returnData[
'CAL_TYPE'] =
'user';
541 if (!empty($event[
'start'][
'dateTime']) && !empty($event[
'end'][
'dateTime']))
543 $returnData[
'TZ_FROM'] =
Util::isTimezoneValid($event[
'start'][
'timeZone']) ? $event[
'start'][
'timeZone'] : $this->defaultTimezone;
544 $returnData[
'TZ_TO'] =
Util::isTimezoneValid($event[
'end'][
'timeZone']) ? $event[
'end'][
'timeZone'] : $this->defaultTimezone;
546 $eventStartDateTime =
new Type\DateTime($event[
'start'][
'dateTime'], self::DATE_TIME_FORMAT,
new \DateTimeZone($this->defaultTimezone));
547 $returnData[
'DATE_FROM'] = \CCalendar::Date(\CCalendar::Timestamp($eventStartDateTime->setTimeZone(
new \DateTimeZone($returnData[
'TZ_FROM']))->format(
Type\Date::convertFormatToPhp(FORMAT_DATETIME))));
549 $eventStartDateTime =
new Type\DateTime($event[
'end'][
'dateTime'], self::DATE_TIME_FORMAT,
new \DateTimeZone($this->defaultTimezone));
550 $returnData[
'DATE_TO'] = \CCalendar::Date(\CCalendar::Timestamp($eventStartDateTime->setTimeZone(
new \DateTimeZone($returnData[
'TZ_TO']))->format(
Type\Date::convertFormatToPhp(FORMAT_DATETIME))));
553 if (!empty($event[
'start'][
'date']))
555 $returnData[
'DATE_FROM'] = \CCalendar::Date(strtotime($event[
'start'][
'date']),
false);
558 if (!empty($event[
'end'][
'date']))
560 if ($event[
'end'][
'date'] === $event[
'start'][
'date'])
562 $dateStr = strtotime($event[
'end'][
'date']);
569 $returnData[
'DATE_TO'] = \CCalendar::Date($dateStr,
false);
570 $returnData[
'DT_SKIP_TIME'] =
'Y';
574 $returnData[
'DT_SKIP_TIME'] =
'N';
577 $returnData[
'DATE_CREATE'] = \CCalendar::Date(strtotime($event[
'created']));
579 if (!empty($event[
'colorId']))
581 $returnData[
'COLOR'] = $this->getEventColor($event[
'colorId']);
582 $returnData[
'TEXT_COLOR'] = $this->getEventColor($event[
'colorId'],
false);
585 $returnData[
'DATE_CREATE'] = \CCalendar::Date(time());
586 $returnData[
'status'] = $event[
'status'];
587 $returnData[
'hasMoved'] =
"N";
588 $returnData[
'isRecurring'] =
"N";
592 if ($event[
'recurrence'])
594 foreach ($event[
'recurrence'] as $recurrence)
596 if (preg_match(
'/(RRULE)/', $recurrence))
598 $rRuleData = preg_replace(
'/(RRULE\:)/',
'', $recurrence);
599 $rRuleList = explode(
';', $rRuleData);
601 foreach ($rRuleList as $rRuleElement)
603 [$rRuleProp, $rRuleVal] = explode(
'=', $rRuleElement);
607 if (in_array($rRuleVal, [
'HOURLY',
'DAILY',
'WEEKLY',
'MONTHLY',
'YEARLY']))
609 $rRuleSet[
'FREQ'] = $rRuleVal;
613 $rRuleSet[
'COUNT'] = $rRuleVal;
616 $rRuleSet[
'INTERVAL'] = $rRuleVal;
619 $rRuleByDay = array();
620 foreach(explode(
',', $rRuleVal) as $day)
623 if (preg_match(
'/((\-|\+)?\d+)?(MO|TU|WE|TH|FR|SA|SU)/', $day, $matches))
625 $rRuleByDay[$matches[1] ===
''
633 if (!empty($rRuleByDay))
635 $rRuleSet[
'BYDAY'] = $rRuleByDay;
641 $rRuleValDateTime =
new Type\DateTime($rRuleVal,
'Ymd\THis\Z',
new \DateTimeZone(
'UTC'));
642 $rRuleValDateTime->setTimeZone(
new \DateTimeZone($returnData[
'TZ_TO']));
643 $untilDateTime = explode(
"T", $rRuleVal);
645 if ($untilDateTime[1] ===
"000000Z")
647 $rRuleValDateTime = $rRuleValDateTime->add(
"-1 day");
650 $rRuleSet[
'UNTIL'] = \CCalendar::Date(\CCalendar::Timestamp($rRuleValDateTime->format(
Type\Date::convertFormatToPhp(FORMAT_DATETIME))) - 60,
false,
false);
654 $rRuleSet[
'UNTIL'] = \CCalendar::Date(strtotime($rRuleVal),
false,
false);
660 $returnData[
"RRULE"] = \CCalendarEvent::CheckRRULE($rRuleSet);
662 elseif (preg_match(
'/(\d{4}-?\d{2}-?\d{2})(Z)?/', $recurrence, $date))
664 if (!empty($date[1]))
666 $exDates[] = \CCalendar::Date(strtotime($date[1]),
false);
672 if (!empty($event[
'recurringEventId']))
674 $returnData[
'isRecurring'] =
"Y";
675 if ($event[
'status'] ===
'cancelled')
677 $exDates[] = date(Date::convertFormatToPhp(FORMAT_DATE), strtotime(
678 !empty($event[
'originalStartTime'][
'dateTime'])
679 ? $event[
'originalStartTime'][
'dateTime']
680 : $event[
'originalStartTime'][
'date']
683 elseif ($event[
'status'] ===
'confirmed' && !empty($event[
'originalStartTime']))
685 $returnData[
'hasMoved'] =
"Y";
686 $exDates[] = date(Date::convertFormatToPhp(FORMAT_DATE), strtotime(!empty($event[
'originalStartTime'][
'dateTime']) ? $event[
'originalStartTime'][
'dateTime'] : $event[
'originalStartTime'][
'date']));
688 if (!empty($event[
'originalStartTime'][
'dateTime']))
690 $originalTimeZone =
Util::isTimezoneValid($event[
'originalStartTime'][
'timeZone']) ? $event[
'originalStartTime'][
'timeZone'] : $returnData[
'TZ_FROM'];
691 $eventOriginalDateFrom =
new Type\DateTime($event[
'originalStartTime'][
'dateTime'], self::DATE_TIME_FORMAT,
new \DateTimeZone($this->defaultTimezone));
692 $returnData[
'ORIGINAL_DATE_FROM'] = \CCalendar::Date(\CCalendar::Timestamp($eventOriginalDateFrom->setTimeZone(
new \DateTimeZone($originalTimeZone))->format(
Type\Date::convertFormatToPhp(FORMAT_DATETIME))));
695 if (!empty($event[
'originalStartTime'][
'date']))
697 $returnData[
'ORIGINAL_DATE_FROM'] = \CCalendar::Date(strtotime($event[
'originalStartTime'][
'date']),
false);
701 $returnData[
'recurringEventId'] = $event[
'recurringEventId'];
703 if (!empty($exDates))
705 $returnData[
'EXDATE'] = implode(
';', $exDates);
708 $returnData[
'REMIND'] = [];
709 if (!empty($event[
'reminders'][
'overrides']))
711 foreach ($event[
'reminders'][
'overrides'] as $remindData)
713 $remindTimeout = $remindData[
'minutes'];
714 $returnData[
'REMIND'][] = [
716 'count' => $remindTimeout
720 if (!empty($event[
'reminders'][
'useDefault']) && !empty($this->defaultReminderData) && $event[
'reminders'][
'useDefault'] === 1)
722 foreach ($this->defaultReminderData as $remindData)
724 $remindTimeout = $remindData[
'minutes'];
725 $returnData[
'REMIND'][] = [
727 'count' => $remindTimeout
731 if (!empty($event[
'location']))
733 $returnData[
'LOCATION'] = Rooms\Util::unParseTextLocation($event[
'location']);
736 if (!empty($event[
'sequence']))
753 private function prepareToSaveEvent($eventData, $params =
null): array
756 $newEvent[
'summary'] = $eventData[
'NAME'];
758 if (!empty($eventData[
'ATTENDEES_CODES']) && is_string($eventData[
'ATTENDEES_CODES']))
760 $eventData[
'ATTENDEES_CODES'] = explode(
",", $eventData[
'ATTENDEES_CODES']);
763 if (is_string($eventData[
'MEETING']))
765 $eventData[
'MEETING'] = unserialize($eventData[
'MEETING'], [
'allowed_classes' =>
false]);
767 if (empty($eventData[
'MEETING'][
'LANGUAGE_ID']))
769 $eventData[
'MEETING'][
'LANGUAGE_ID'] = \CCalendar::getUserLanguageId((
int)$eventData[
'OWNER_ID']);
772 if (isset($eventData[
'ATTENDEES_CODES']) && is_countable($eventData[
'ATTENDEES_CODES']) && count($eventData[
'ATTENDEES_CODES']) > 1)
775 $newEvent[
'description'] =
Loc::getMessage(
'ATTENDEES_EVENT',
null, $eventData[
'MEETING'][
'LANGUAGE_ID']).
': '
776 . stripcslashes(implode(
', ', $users))
778 . $eventData[
"DESCRIPTION"];
780 elseif (!empty($eventData[
'DESCRIPTION']) && is_string($eventData[
'DESCRIPTION']))
782 $newEvent[
'description'] = $eventData[
'DESCRIPTION'];
785 if (!empty($eventData[
'ACCESSIBILITY']) && $eventData[
'ACCESSIBILITY'] ===
'busy')
787 $newEvent[
'transparency'] =
'opaque';
791 $newEvent[
'transparency'] =
'transparent';
794 if (!empty($eventData[
'LOCATION'][
'NEW']) && is_string($eventData[
'LOCATION'][
'NEW']))
796 $newEvent[
'location'] = \CCalendar::GetTextLocation($eventData[
'LOCATION'][
'NEW']);
798 elseif (!empty($eventData[
'LOCATION']) && is_string($eventData[
'LOCATION']))
800 $newEvent[
'location'] = \CCalendar::GetTextLocation($eventData[
'LOCATION']);
803 if (!empty($eventData[
'REMIND']))
805 $newEvent[
'reminders'] = $this->prepareRemind($eventData);
808 if ($eventData[
'DT_SKIP_TIME'] ===
"Y")
816 if (!empty($eventData[
'G_EVENT_ID']))
818 $newEvent[
'start'][
'dateTime'] =
null;
819 $newEvent[
'end'][
'dateTime'] =
null;
820 $newEvent[
'start'][
'timeZone'] =
null;
821 $newEvent[
'end'][
'timeZone'] =
null;
826 $newEvent[
'start'][
'dateTime'] =
Util::getDateObject($eventData[
'DATE_FROM'],
false, $eventData[
'TZ_FROM'])
827 ->format(self::DATE_TIME_FORMAT);
830 $newEvent[
'end'][
'dateTime'] =
Util::getDateObject($eventData[
'DATE_TO'],
false, $eventData[
'TZ_TO'])
831 ->format(self::DATE_TIME_FORMAT);
834 if (!empty($eventData[
'G_EVENT_ID']))
836 $newEvent[
'start'][
'date'] =
null;
837 $newEvent[
'end'][
'date'] =
null;
842 !empty($eventData[
'RRULE'])
843 && is_array($eventData[
'RRULE'])
844 && isset($eventData[
'RRULE'][
'FREQ'])
845 && $eventData[
'RRULE'][
'FREQ'] !==
'NONE'
848 $newEvent[
'recurrence'] = $this->prepareRRule($eventData, $params[
'editNextEvents']);
851 if (isset($eventData[
'ORIGINAL_DATE_FROM']))
853 $newEvent[
'originalStartTime'] =
Util::getDateObject($eventData[
'ORIGINAL_DATE_FROM'],
false, $eventData[
'TZ_FROM'])
854 ->format(self::DATE_TIME_FORMAT);
857 if (isset($eventData[
'G_EVENT_ID']) && isset($eventData[
'RECURRENCE_ID']))
859 $newEvent[
'recurringEventId'] = $eventData[
'G_EVENT_ID'];
862 if ($params[
'syncCaldav'] || isset($eventData[
'DAV_XML_ID']))
864 $newEvent[
'iCalUID'] = $eventData[
'DAV_XML_ID'];
867 if (isset($eventData[
'G_EVENT_ID']))
869 $newEvent[
'id'] = $eventData[
'G_EVENT_ID'];
872 if (!empty($eventData[
'PRIVATE_EVENT']))
874 $newEvent[
'visibility'] =
"private";
878 $newEvent[
'visibility'] =
"public";
881 if (isset($eventData[
'VERSION']))
895 private function sendToSaveEvent($newEvent, $params)
897 if ($params[
'editInstance'] ===
true)
899 $eventOriginalStart =
Util::getDateObject($params[
'originalDateFrom'],
false, $params[
'instanceTz']);
900 $originalStart = $eventOriginalStart->format(self::DATE_TIME_FORMAT);
901 $externalId = $params[
'gEventId'] ?: $params[
'originalDavXmlId'];
902 $instance = $this->syncTransport->getInstanceRecurringEvent($params[
'calendarId'], $externalId, $originalStart);
904 $newEvent[
'originalStartTime'] = $originalStart;
905 $newEvent[
'recurringEventId'] = $params[
'originalDavXmlId'];
907 if (is_array($instance[
'items']))
909 return $this->syncTransport->updateEvent($newEvent, urlencode($params[
'calendarId']), $instance[
'items'][0][
'id']);
912 elseif ($params[
'editParentEvents'] ===
true)
914 return $this->syncTransport->updateEvent($newEvent, urldecode($params[
'calendarId']), $params[
'gEventId']);
916 elseif ($params[
'syncCaldav'])
918 return $this->syncTransport->importEvent($newEvent, urlencode($params[
'calendarId']));
920 elseif (($params[
'gEventId']))
922 return $this->syncTransport->updateEvent($newEvent, urlencode($params[
'calendarId']), $params[
'gEventId']);
926 return $this->syncTransport->insertEvent($newEvent, urlencode($params[
'calendarId']));
938 $externalData = $this->syncTransport->insertCalendar($this->prepareCalendar($calendar));
941 ? [
'GAPI_CALENDAR_ID' => $externalData[
'id']]
950 private function prepareCalendar($calendar): array
952 $returnData[
'summary'] = Emoji::decode($calendar[
'NAME']);
953 if (isset($calendar[
'EXTERNAL_TYPE']) && $calendar[
'EXTERNAL_TYPE'] === \CCalendarSect::EXTERNAL_TYPE_LOCAL)
955 IncludeModuleLangFile($_SERVER[
'DOCUMENT_ROOT'] . BX_ROOT .
'/modules/calendar/classes/general/calendar.php');
956 $returnData[
'summary'] =
Loc::getMessage(
'EC_CALENDAR_BITRIX24_NAME') .
" " . $returnData[
'summary'];
968 if (empty($channelId))
974 preg_match(
'/(' . self::CONNECTION_CHANNEL_TYPE .
'|' . self::SECTION_CHANNEL_TYPE .
')_(\d+)_.+/', $channelId, $matches);
976 return !empty($matches) && (int)$matches[2] > 0
984 return !empty($this->nextPageToken);
990 private function hasExpiredSyncTokenError(): bool
992 return !empty($this->getExpiredSyncTokenError());
998 private function getExpiredSyncTokenError(): array
1000 return array_filter($this->syncTransport->getErrors(), function ($error) {
1001 return preg_match(
"/^\[(410)\][a-z0-9 _]*/i", $error[
'message']);
1009 private function processResponseReceivingEvents(array $response): array
1011 $this->setSyncSettings($response);
1013 return $this->getEventsList($response[
'items']);
1019 private function getRequestParamsWithSyncToken(): array
1022 'pageToken' => $this->nextPageToken,
1023 'syncToken' => $this->nextSyncToken,
1024 'showDeleted' =>
'true',
1031 private function getRequestParamsForFirstSync(): array
1034 'pageToken' => $this->nextPageToken,
1035 'showDeleted' =>
'true',
1037 'timeMin' => (
new Type\DateTime())->add(self::SYNC_EVENTS_DATE_INTERVAL)->format(self::DATE_TIME_FORMAT),
1045 private function runSyncEvents($gApiCalendarId)
1047 $response = !empty($this->nextSyncToken)
1048 ? $this->syncTransport->getEvents($gApiCalendarId, $this->getRequestParamsWithSyncToken())
1049 : $this->syncTransport->getEvents($gApiCalendarId, $this->getRequestParamsForFirstSync());
1051 if (!$response && $this->hasExpiredSyncTokenError())
1053 return $this->syncTransport->getEvents($gApiCalendarId, $this->getRequestParamsForFirstSync());
1063 private function getEventsList(iterable $events =
null): array
1069 foreach ($events as $event)
1071 $preparedEvent = $this->prepareEvent($event);
1072 $eventsList[$preparedEvent[
'G_EVENT_ID']] = $preparedEvent;
1081 private function setSyncSettings(array $response =
null): void
1083 $this->nextPageToken = $response[
'nextPageToken'] ??
'';
1084 $this->nextSyncToken = $response[
'nextSyncToken'] ??
'';
1085 $this->defaultReminderData = $response[
'defaultReminders'] ?? $this->defaultReminderData;
1086 $this->defaultTimezone =
Util::isTimezoneValid($response[
'timeZone']) ? $response[
'timeZone'] : $this->defaultTimezone;
1094 private function prepareRemind($eventData): array
1097 $reminders[
'useDefault'] =
false;
1098 $reminders[
'overrides'] = [];
1100 if (!is_iterable($eventData[
'REMIND']))
1105 foreach ($eventData[
'REMIND'] as $remindRule)
1107 $minutes = $remindRule[
'count'];
1108 if ($remindRule[
'type'] ===
'hour')
1110 $minutes = 60 * $remindRule[
'count'];
1112 elseif ($remindRule[
'type'] ===
'day')
1114 $minutes = 24 * 60 * $remindRule[
'count'];
1116 elseif ($remindRule[
'type'] ===
'daybefore')
1119 $eventData[
'DATE_FROM'],
1120 $eventData[
'DT_SKIP_TIME'] ===
'Y',
1121 $eventData[
'TZ_FROM']
1124 $remind = clone $dateFrom;
1125 if (method_exists($remind,
'setTime'))
1127 $remind->setTime(0, 0, 0);
1129 $remind->add(
"-{$remindRule['before']} days")->add(
"{$remindRule['time']} minutes");
1131 if ($dateFrom->getTimestamp() < $remind->getTimestamp())
1136 $minutes = $this->calculateMinutes($dateFrom, $remind);
1138 elseif ($remindRule[
'type'] ===
'date')
1141 $eventData[
'DATE_FROM'],
1142 $eventData[
'DT_SKIP_TIME'] ===
'Y',
1143 $eventData[
'TZ_FROM']
1146 $remindRule[
'value'],
1147 $eventData[
'DT_SKIP_TIME'] ===
'Y',
1148 $eventData[
'TZ_FROM']
1151 if ($dateFrom->getTimestamp() < $remind->getTimestamp())
1156 $minutes = $this->calculateMinutes($dateFrom, $remind);
1159 $reminders[
'overrides'][] = [
1160 'minutes' => $minutes,
1161 'method' =>
'popup',
1173 private function calculateMinutes(
Type\Date $dateFrom,
Type\Date $remind): int
1175 $diff = $dateFrom->getDiff($remind);
1176 $days = $diff->format(
'%d');
1177 $hours = $diff->format(
'%h');
1178 $minutes = $diff->format(
'%i');
1180 return ((
int)$days * 24 * 60) + ((int)$hours * 60) + (int)$minutes;
1189 private function prepareRRule($event, $editNextEvents): array
1192 $parsedRule = \CCalendarEvent::ParseRRULE($event[
'RRULE']);
1194 $rRule .=
'FREQ=' .$parsedRule[
'FREQ'];
1195 $rRule .= !empty($parsedRule[
'INTERVAL']) ?
';INTERVAL=' . $parsedRule[
'INTERVAL'] :
'';
1196 if (!empty($parsedRule[
'BYDAY']))
1198 if (is_string($parsedRule[
'BYDAY']))
1200 $rRule .=
';BYDAY=' . $parsedRule[
'BYDAY'];
1202 elseif (is_array($parsedRule[
'BYDAY']))
1204 $rRule .=
';BYDAY=' . implode(
",", $parsedRule[
'BYDAY']);
1212 if (!empty($parsedRule[
'COUNT']))
1214 $rRule .=
';COUNT=' . $parsedRule[
'COUNT'];
1216 elseif (!empty($parsedRule[
'UNTIL']))
1219 if ($event[
'DT_SKIP_TIME'] ===
"N" && $tsTo->getTimestamp() < (
new Type\Date(self::END_OF_DATE,
"d.m.Y"))->
getTimestamp())
1221 $tsTo->add(
'+1 day');
1223 $rRule .=
';UNTIL='.$tsTo->format(
'Ymd\THis\Z');
1228 if (!empty($event[
'EXDATE']) && $editNextEvents !==
true)
1230 $exDates = explode(
';', $event[
'EXDATE']);
1231 foreach ($exDates as $exDate)
1233 if ($event[
'DT_SKIP_TIME'] ===
"Y")
1235 $rule[] =
'EXDATE;VALUE=DATE:' . date(
'Ymd', strtotime($exDate));
1239 $rule[] =
'EXDATE;TZID=UTC:'
1240 . date(
"Ymd", strtotime($exDate))
1242 ->setTimeZone(
new \DateTimeZone(
'UTC'))->format(
'\\THis\\Z');
1256 $this->syncTransport->deleteCalendar($gApiCalendarId);
1262 private function getCalendarListParams(
string $syncToken =
null): array
1264 if ($syncToken ===
null)
1270 'showDeleted' =>
'true',
1271 'showHidden' =>
'true',
1272 'syncToken' => $syncToken,
1283 $this->syncTransport->updateCalendar($gApiCalendarId, $this->prepareCalendar($calendarData));
1294 return $this->syncTransport->updateCalendarList($gApiCalendarId, $this->prepareCalendarList($section));
1302 private function prepareCalendarList(array $calendar): array
1306 if (isset($calendar[
'COLOR']))
1308 $parameters[
'backgroundColor'] = $calendar[
'COLOR'];
1309 $parameters[
'foregroundColor'] =
'#ffffff';
1312 $parameters[
'selected'] =
'true';
1322 return $this->nextPageToken;