39 private const ENTITY_TYPE =
'user';
40 private const MAX_NUMBER = 5;
41 private const TIME_SLICE = 2600000;
44 private $mapperFactory;
49 private function __construct()
51 $this->mapperFactory = ServiceLocator::getInstance()->get(
'calendar.service.mappers.factory');
64 (
new self())->dataSync();
66 return "\\Bitrix\\Calendar\\Sync\\Managers\\DataSyncManager::dataSyncAgent();";
89 if (!Loader::includeModule(
'dav') || !Loader::includeModule(
'calendar'))
94 $connections = $this->getConnections($userId);
95 foreach ($connections as $connection)
97 $connection = $this->createConnectionObject($connection);
98 $result = $this->syncConnection($connection);
99 if ($result->isSuccess())
101 \CDavConnection::SetLastResult($connection->getId(), $result->getData()[
'lastResult']);
104 $connection->getAccountType() => [
105 'status' => $result->getData()[
'syncStatus'],
106 'type' => $connection->getAccountType(),
108 'id' => $connection->getId(),
137 RequestLogger::isEnabled()
140 $logger =
new RequestLogger($connection->getOwner()->getId(), $connection->getVendor()->getCode());
143 $client = $this->initClient($connection);
145 $calendarsList = $client->GetCalendarList($connection->getServer()->getBasePath(),
null);
147 if ($client->getError())
149 $error = $this->processError($client->getError());
151 'lastResult' => $error,
152 'syncStatus' =>
false,
158 if (!$calendarsList || !is_array($calendarsList))
161 'lastResult' =>
'[204] No Content',
162 'syncStatus' =>
true,
168 $calendarsList = $this->syncSections($connection, $calendarsList);
170 foreach ($calendarsList as $calendar)
172 [$events, $eventsMap] = $this->getEventsToSync(
179 if ($client->getError())
181 $error = $this->processError($client->getError());
183 'lastResult' => $error,
184 'syncStatus' =>
false,
190 foreach ($events as $event)
203 'lastResult' =>
'[200] OK',
204 'syncStatus' =>
true,
223 private function modifyEvent(
224 Connection $connection,
225 \CDavGroupdavClientCalendar $client,
231 if (!array_key_exists($event[
'href'], $eventsMap))
237 $existEvent = $eventsMap[$event[
'href']];
238 if (empty($event[
'calendar-data']))
243 [$event, $exDate] = $this->mergeExternalEventWithLocal($existEvent, $event, $client);
245 if (!empty($event[
'calendar-data']) && is_array($event[
'calendar-data']))
247 $eventId = $this->modifySingleEvent(
249 $event[
'calendar-data'],
251 'SECTION_ID' => $calendar[
'SECTION_ID'],
252 'VERSION' => $existEvent[
'VERSION'],
253 'EVENT_CONNECTION_ID' => $eventsMap[$event[
'href']][
'EVENT_CONNECTION_ID'],
258 if ($eventId && !empty($event[
'calendar-data-ex'] && is_array($event[
'calendar-data-ex'])))
260 $this->modifyRecurrenceEvent(
262 $event[
'calendar-data-ex'],
264 'PARENT_ID' => $eventId,
265 'SECTION_ID' => $calendar[
'SECTION_ID'],
266 'PERIOD_UNTIL' => $event[
'calendar-data'][
'PROPERTY_PERIOD_UNTIL'] ??
null,
270 else if ($exDate && $event[
'calendar-data'] && $event[
'calendar-data'][
'ID'])
272 $this->deleteDuplicateExDates(
274 $event[
'calendar-data'][
'DATE_FROM'],
275 $event[
'calendar-data'][
'ID'],
276 $connection->getOwner()->getId(),
293 private function deleteDuplicateExDates($exDate, $dateFrom, $eventId, $userId): void
296 $exDates = \CCalendarEvent::GetExDate($exDate);
297 $dtStartTimestamp = \CCalendar::Timestamp($dateFrom,
false);
298 $needToUpdate =
false;
299 foreach ($exDates as $date)
301 $dateTs = \CCalendar::Timestamp($date,
false);
302 if ($dateTs < $dtStartTimestamp)
304 $needToUpdate =
true;
311 $childEvents = EventConnectionTable::query()
315 'DATE_FROM' =>
'EVENT.DATE_FROM',
316 'EVENT_CONNECTION_ID' =>
'ID',
318 ->where(
'EVENT.RECURRENCE_ID', $eventId)
319 ->where(
'EVENT.DELETED',
'N')
320 ->where(
'EVENT.OWNER_ID', $userId)
324 while ($child = $childEvents->fetch())
326 $eventIdList = $this->getAllEventByParentId($child[
'EVENT_ID']);
328 EventTable::updateMulti($eventIdList, [
'DELETED' =>
'Y']);
330 EventConnectionTable::delete($child[
'EVENT_CONNECTION_ID']);
347 private function modifySingleEvent(Connection $connection, array $event, array $additionalParams): ?int
349 $eventObject = $this->prepareEventParams(
351 $additionalParams[
'SECTION_ID'],
352 $connection->getOwner()->getId()
355 if ($eventObject->getId())
357 $result = $this->mapperFactory->getEvent()->update($eventObject, [
358 'userId' => $connection->getOwner()->getId(),
359 'bAffectToDav' =>
false,
360 'bSilentAccessMeeting' =>
true,
361 'autoDetectSection' =>
false,
362 'originalFrom' => $connection->getVendor()->getCode(),
367 $result = $this->mapperFactory->getEvent()->create($eventObject, [
368 'userId' => $connection->getOwner()->getId(),
369 'bAffectToDav' =>
false,
370 'bSilentAccessMeeting' =>
true,
371 'autoDetectSection' =>
false,
372 'originalFrom' => $connection->getVendor()->getCode(),
376 if ($result && $result->getId())
380 if (!empty($event[
'ATTENDEE']) || !empty($event[
'ORGANIZER_ENTITY']))
382 $this->parseInvitedAttendees($event, $data);
384 if (!empty($event[
'ATTACH']))
386 $this->parseAttachments($event, $data);
388 if (!empty($event[
'URL']))
390 $data[
'URL'] = $event[
'URL'];
393 if (!empty($additionalParams[
'EVENT_CONNECTION_ID']))
395 EventConnectionTable::update($additionalParams[
'EVENT_CONNECTION_ID'], [
397 'ENTITY_TAG' => $event[
'MODIFICATION_LABEL'] ??
null,
398 'VERSION' => (
string)($additionalParams[
'VERSION'] ??
null),
399 'VENDOR_VERSION_ID' => (
string)($additionalParams[
'VERSION'] ??
null),
405 EventConnectionTable::add([
406 'EVENT_ID' => (
int)$result->getId(),
407 'CONNECTION_ID' => $connection->getId(),
408 'VENDOR_EVENT_ID' => $event[
'XML_ID'] ??
null,
410 'ENTITY_TAG' => $event[
'MODIFICATION_LABEL'] ??
null,
411 'VERSION' => (
string)($additionalParams[
'VERSION'] ??
null),
412 'VENDOR_VERSION_ID' => (
string)($additionalParams[
'VERSION'] ??
null),
417 return (
int)$result->getId();
435 private function modifyRecurrenceEvent(
436 Connection $connection,
437 array $importInstances,
438 array $additionalParams
441 [$importInstances, $importedInstancesDates] = $this->prepareInstanceEvents($importInstances);
442 $parentEvent = \CCalendarEvent::GetById($additionalParams[
'PARENT_ID'],
true,
true);
444 if ($parentEvent && \CCalendarEvent::CheckRecurcion($parentEvent))
446 $exDates = \CCalendarEvent::GetExDate($parentEvent[
'EXDATE']);
447 $localInstances = EventConnectionTable::query()
451 'DATE_FROM' =>
'EVENT.DATE_FROM',
452 'EVENT_CONNECTION_ID' =>
'ID',
453 'MEETING' =>
'EVENT.MEETING',
454 'IS_MEETING' =>
'EVENT.IS_MEETING',
455 'MEETING_HOST' =>
'EVENT.MEETING_HOST',
456 'ATTENDEES_CODES' =>
'EVENT.ATTENDEES_CODES',
457 'ACCESSIBILITY' =>
'EVENT.ACCESSIBILITY',
459 ->where(
'EVENT.RECURRENCE_ID', $additionalParams[
'PARENT_ID'])
460 ->where(
'EVENT.DELETED',
'N')
461 ->where(
'EVENT.OWNER_ID', $connection->getOwner()->getId())
462 ->whereNot(
'EVENT.MEETING_STATUS',
'N')
466 $importedInstancesCount = count($importInstances);
467 while ($localInstance = $localInstances->fetch())
470 $localInstanceDate = \CCalendar::Date(\CCalendar::Timestamp($localInstance[
'DATE_FROM']),
false);
471 for ($i = 0; $i < $importedInstancesCount; $i++)
473 if ($localInstanceDate === $importedInstancesDates[$i])
475 $this->mergeInstanceParams($importInstances[$i], $localInstance);
484 \CCalendarEvent::Delete([
485 'id' => $localInstance[
'EVENT_ID'],
486 'bMarkDeleted' =>
true,
487 'bAffectToDav' =>
false,
488 'originalFrom' => $connection->getVendor()->getCode(),
489 'userId' => $connection->getOwner()->getId(),
492 EventConnectionTable::delete($localInstance[
'EVENT_CONNECTION_ID']);
496 foreach ($importInstances as $instance)
499 $additionalParams[
'PERIOD_UNTIL']
500 && \CCalendar::Timestamp($instance[
'DATE_FROM']) > \CCalendar::Timestamp($additionalParams[
'PERIOD_UNTIL'])
505 $instance = $this->addParentDataToInstance($instance, $parentEvent);
507 if ($instance && $instance[
'RECURRENCE_ID'])
509 unset($instance[
'RRULE'], $instance[
'EXDATE']);
511 $instanceId = $this->modifySingleEvent(
515 'SECTION_ID' => $additionalParams[
'SECTION_ID'],
516 'VERSION' => $instance[
'VERSION'] ?? 1,
517 'EVENT_CONNECTION_ID' => $instance[
'EVENT_CONNECTION_ID'] ?? 0,
521 if (!empty($instance[
'RECURRENCE_ID_DATE']))
523 $exDates[] = \CCalendar::Date(\CCalendar::Timestamp($instance[
'RECURRENCE_ID_DATE']),
false);
525 $originalRecursionId = (int)($parentEvent[
'ORIGINAL_RECURSION_ID'] ?? $parentEvent[
'ID']);
527 (
new EventOriginalRecursion())->add($instanceId, $originalRecursionId);
531 $exDate = \CCalendarEvent::SetExDate($exDates);
532 $eventIdList = $this->getAllEventByParentId($parentEvent[
'ID']);
534 EventTable::updateMulti($eventIdList, [
'EXDATE' => $exDate]);
546 private function getAllEventByParentId(
int $parentId): array
548 $eventIdList = EventTable::query()
550 ->where(
'PARENT_ID', $parentId)
555 return array_map(
static function($event){
556 return (
int)$event[
'ID'];
568 private function getConnections($userId =
null): EO_DavConnection_Collection
570 $query = DavConnectionTable::query()
572 ->whereIn(
'ACCOUNT_TYPE', [Icloud\Helper::ACCOUNT_TYPE])
573 ->where(
'ENTITY_TYPE', self::ENTITY_TYPE)
574 ->where(
'IS_DELETED',
'N')
575 ->setLimit(self::MAX_NUMBER)
576 ->setOrder([
'SYNCHRONIZED' =>
'ASC'])
580 $query->where(
'ENTITY_ID', $userId);
583 return $query->exec()->fetchCollection();
591 private function initClient(Connection $connection): \CDavGroupdavClientCalendar
593 $client = new \CDavGroupdavClientCalendar(
594 $connection->getServer()->getScheme(),
595 $connection->getServer()->getHost(),
596 $connection->getServer()->getPort(),
597 $connection->getServer()->getUserName(),
598 $connection->getServer()->getPassword()
600 if (\CDav::UseProxy())
602 $proxy = \CDav::GetProxySettings();
604 $proxy[
'PROXY_SCHEME'],
605 $proxy[
'PROXY_HOST'],
606 $proxy[
'PROXY_PORT'],
607 $proxy[
'PROXY_USERNAME'],
608 $proxy[
'PROXY_PASSWORD']
627 private function syncCalendarSections(
628 Connection $connection,
635 foreach ($calendars as $calendar)
637 if (isset($calendar[
'TYPE']) && $calendar[
'TYPE'] ===
'VEVENT')
639 $calendarNames[$calendar[
'XML_ID']] = $calendar;
643 $sectionsLink = SectionConnectionTable::query()
645 'SECTION_CONNECTION_ID' =>
'ID',
646 'NAME' =>
'SECTION.NAME',
647 'EXTERNAL_TYPE' =>
'SECTION.EXTERNAL_TYPE',
652 ->where(
'SECTION.CAL_TYPE', self::ENTITY_TYPE)
653 ->where(
'SECTION.OWNER_ID', $connection->getOwner()->getId())
654 ->where(
'CONNECTION_ID', $connection->getId())
658 while ($link = $sectionsLink->fetch())
660 $xmlId = $link[
'VENDOR_SECTION_ID'];
666 if (!array_key_exists($xmlId, $calendarNames))
668 $section = $this->mapperFactory->getSection()->getById($link[
'SECTION_ID']);
671 (
new IncomingManager($connection))->deleteSection($section, $link[
'SECTION_CONNECTION_ID']);
676 if (($link[
'VERSION_ID'] ??
null) !== ($calendarNames[$xmlId][
'MODIFICATION_LABEL'] ??
null))
679 'ID' => (int)($link[
'SECTION_ID'] ??
null),
680 'NAME' => isset($link[
'EXTERNAL_TYPE']) && $link[
'EXTERNAL_TYPE'] ===
'local'
682 : $calendarNames[$xmlId][
'NAME']
684 'DESCRIPTION' => $calendarNames[$xmlId][
'DESCRIPTION'] ??
null,
685 'COLOR' => $calendarNames[$xmlId][
'COLOR'] ??
null,
688 \CCalendarSect::Edit([
689 'arFields' => $fields,
690 'bAffectToDav' =>
false,
691 'originalFrom' => $connection->getVendor()->getCode(),
694 SectionConnectionTable::update((
int)$link[
'SECTION_CONNECTION_ID'], [
695 'LAST_SYNC_DATE' =>
new DateTime(),
697 'VERSION_ID' => $calendarNames[$xmlId][
'MODIFICATION_LABEL'] ??
null,
703 'SECTION_ID' => $link[
'SECTION_ID'],
704 'SECTION_CONNECTION_ID' => $link[
'SECTION_CONNECTION_ID'],
705 'SYNC_TOKEN' => $link[
'VERSION_ID'] ??
null,
710 unset($calendarNames[$xmlId]);
714 foreach ($calendarNames as $curXmlId => $calendar)
717 'CAL_TYPE' => self::ENTITY_TYPE,
718 'OWNER_ID' => $connection->getOwner()->getId(),
719 'CREATED_BY' => $connection->getOwner()->getId(),
720 'NAME' => $calendar[
'NAME'],
721 'DESCRIPTION' => $calendar[
'DESCRIPTION'],
722 'COLOR' => $calendar[
'COLOR'],
723 'EXPORT' => [
'ALLOW' =>
false],
724 'EXTERNAL_TYPE' => $connection->getVendor()->getCode(),
727 $id = (int)\CCalendarSect::Edit([
728 'arFields' => $fields,
729 'bAffectToDav' =>
false,
730 'originalFrom' => $connection->getVendor()->getCode(),
735 $linkId = SectionConnectionTable::add([
737 'CONNECTION_ID' => $connection->getId(),
738 'VENDOR_SECTION_ID' => $curXmlId,
740 'LAST_SYNC_DATE' =>
new DateTime(),
742 'VERSION_ID' => $calendar[
'MODIFICATION_LABEL'],
746 'XML_ID' => $curXmlId,
748 'SECTION_CONNECTION_ID' => $linkId->getId(),
749 'SYNC_TOKEN' => $calendar[
'MODIFICATION_LABEL'],
769 private function syncCalendarEvents(
770 Connection $connection,
778 $eventsLink = EventConnectionTable::query()
780 'EVENT_CONNECTION_ID' =>
'ID',
785 'IS_MEETING' =>
'EVENT.IS_MEETING',
786 'MEETING' =>
'EVENT.MEETING',
787 'EXDATE' =>
'EVENT.EXDATE',
788 'EVENT_PARENT_ID' =>
'EVENT.PARENT_ID',
789 'EVENT_TYPE' =>
'EVENT.EVENT_TYPE',
790 'EVENT_NAME' =>
'EVENT.NAME',
791 'EVENT_OWNER_ID' =>
'EVENT.OWNER_ID',
792 'EVENT_DATE_FROM' =>
'EVENT.DATE_FROM',
793 'EVENT_DATE_TO' =>
'EVENT.DATE_TO',
794 'EVENT_TZ_FROM' =>
'EVENT.TZ_FROM',
795 'EVENT_TZ_TO' =>
'EVENT.TZ_TO',
796 'EVENT_VERSION' =>
'EVENT.VERSION',
797 'ATTENDEES_CODES' =>
'EVENT.ATTENDEES_CODES',
798 'ACCESSIBILITY' =>
'EVENT.ACCESSIBILITY',
800 ->where(
'EVENT.CAL_TYPE', self::ENTITY_TYPE)
801 ->where(
'EVENT.OWNER_ID', $connection->getOwner()->getId())
802 ->where(
'EVENT.SECTION_ID', $sectionId)
803 ->where(
'EVENT.DELETED',
'N')
804 ->where(Query::filter()
806 ->whereNot(
'EVENT.MEETING_STATUS',
'N')
807 ->whereNull(
'EVENT.MEETING_STATUS')
809 ->whereNotNull(
'ENTITY_TAG')
813 while ($event = $eventsLink->fetch())
815 $linksMap[$event[
'VENDOR_EVENT_ID']] = $event;
818 foreach ($events as $index => $event)
820 if (!empty($linksMap[$event[
'XML_ID']]))
822 $existEvent = $linksMap[$event[
'XML_ID']];
824 if (($existEvent[
'ENTITY_TAG'] ??
null) === ($event[
'SYNC_TOKEN'] ??
null))
828 if ($this->isBlockedChange($existEvent, $event))
833 if ((
int)$event[
'STATUS'] === 200)
835 if (($linksMap[$event[
'XML_ID']][
'ENTITY_TAG'] ??
null) !== ($event[
'SYNC_TOKEN'] ??
null))
837 $result[] = $this->prepareExistedEventParams($event[
'XML_ID'], $linksMap[$event[
'XML_ID']]);
841 unset($events[$index]);
844 else if ((
int)$event[
'STATUS'] === 404)
846 \CCalendar::DeleteEvent(
847 $linksMap[$event[
'XML_ID']][
'EVENT_ID'],
850 'markDeleted' =>
true,
851 'originalFrom' => $connection->getVendor()->getCode(),
852 'checkPermissions' =>
false,
856 EventConnectionTable::delete($linksMap[$event[
'XML_ID']][
'EVENT_CONNECTION_ID']);
859 else if (!empty($event[
'SYNC_TOKEN']) && (
int)$event[
'STATUS'] === 200)
861 $result[] = $this->prepareExistedEventParams($event[
'XML_ID']);
875 private function isBlockedChange(array $existEvent, array $importedEvent): bool
877 if ($existEvent[
'EVENT_TYPE'] === ResourceBooking::EVENT_LABEL)
882 if ($existEvent[
'EVENT_ID'] !== $existEvent[
'EVENT_PARENT_ID'])
898 private function prepareEventParams(array $event,
int $sectionId,
int $entityId): Event
901 'ID' => (int)($event[
'ID'] ??
null),
902 'NAME' => $this->prepareName($event[
'NAME']),
903 'CAL_TYPE' => self::ENTITY_TYPE,
904 'DESCRIPTION' => $event[
'DESCRIPTION'] ??
'',
905 'OWNER_ID' => $entityId,
906 'CREATED_BY' => $entityId,
907 'ATTENDEES_CODES' => [
'U' . $entityId],
909 'ACCESSIBILITY' => !empty($event[
'ID'])
910 ? $event[
'ACCESSIBILITY']
911 : ($event[
'PROPERTY_ACCESSIBILITY'] ??
'busy')
913 'IS_MEETING' => $event[
'IS_MEETING'] ?
true : null,
914 'IMPORTANCE' => $event[
'IMPORTANCE'] ??
'normal',
915 'REMIND' => is_array($event[
'REMIND'] ?? null) ? $event[
'REMIND'] : [],
916 'RRULE' => is_array($event[
'RRULE'] ?? null) ? $event[
'RRULE'] : [],
917 'VERSION' => (int)$event[
'VERSION'],
918 'PRIVATE_EVENT' => (bool)($event[
'PRIVATE_EVENT'] ?? null),
919 'DATE_FROM' => $event[
'DATE_FROM'],
920 'DATE_TO' => $event[
'DATE_TO'],
921 'TZ_FROM' => $event[
'TZ_FROM'],
922 'TZ_TO' => $event[
'TZ_TO'],
923 'SKIP_TIME' => $event[
'SKIP_TIME'] ?
'Y' :
'N',
926 'TIMESTAMP_X' => new DateTime(),
929 if (!empty($event[
'RECURRENCE_ID']))
931 $fields[
'RECURRENCE_ID'] = $event[
'RECURRENCE_ID'];
934 if (!empty($event[
'MEETING']))
936 $fields[
'MEETING'] = $event[
'MEETING'];
937 $fields[
'MEETING_HOST'] = $event[
'MEETING'][
'MEETING_CREATOR'] ??
null;
941 $fields[
'MEETING'] = [
942 'HOST_NAME' => \CCalendar::GetUserName($entityId),
945 'ALLOW_INVITE' =>
false,
946 'MEETING_CREATOR' => $entityId,
947 'HIDE_GUESTS' =>
true,
948 'LANGUAGE_ID' => \CCalendar::getUserLanguageId($entityId)
950 $fields[
'MEETING_HOST'] = $entityId;
951 $fields[
'MEETING_STATUS'] =
'H';
954 if (!empty($event[
'ATTENDEES_CODES']))
956 $fields[
'ATTENDEES_CODES'] = $event[
'ATTENDEES_CODES'];
959 if (!empty($event[
'RECURRENCE_ID_DATE']))
961 $fields[
'ORIGINAL_DATE_FROM'] = $event[
'RECURRENCE_ID_DATE'];
964 if (!empty($fields[
'ORIGINAL_DATE_FROM']) && !empty($fields[
'RECURRENCE_ID']))
966 $fields[
'RELATIONS'] = [
'COMMENT_XML_ID' => \CCalendarEvent::GetEventCommentXmlId($fields)];
969 if (empty($fields[
'TZ_TO']) && $fields[
'SKIP_TIME'] ===
'N')
971 $currentTimezone = (
new DateTime())->getTimeZone()->getName();
972 $fields[
'TZ_TO'] = $currentTimezone;
973 $fields[
'TZ_FROM'] = $currentTimezone;
976 if (!empty($event[
'SKIP_TIME']))
978 $fields[
'DATE_FROM'] = \CCalendar::Date(\CCalendar::Timestamp($fields[
'DATE_FROM'],
false));
979 $fields[
'DATE_TO'] = \CCalendar::Date(
980 \CCalendar::Timestamp($fields[
'DATE_TO']) - \CCalendar::GetDayLen(),
985 if (!empty($event[
'PROPERTY_REMIND_SETTINGS']))
987 if (is_array($event[
'PROPERTY_REMIND_SETTINGS']))
989 foreach ($event[
'PROPERTY_REMIND_SETTINGS'] as $remind)
991 $parsed = explode(
'_', $remind);
992 $this->prepareRemind($parsed, $fields);
997 $parsed = explode(
'_', $event[
'PROPERTY_REMIND_SETTINGS']);
998 $this->prepareRemind($parsed, $fields);
1002 if (!empty($event[
'PROPERTY_IMPORTANCE']))
1004 $fields[
'IMPORTANCE'] = $event[
'PROPERTY_IMPORTANCE'];
1007 if (!empty($event[
'PROPERTY_LOCATION']))
1009 $fields[
'LOCATION'] = Rooms\Util::unParseTextLocation($event[
'PROPERTY_LOCATION']);
1012 if (!empty($event[
'DETAIL_TEXT']))
1014 $this->prepareDescription($event, $fields);
1019 !empty($event[
'PROPERTY_PERIOD_TYPE'])
1020 && in_array($event[
'PROPERTY_PERIOD_TYPE'], [
'DAILY',
'WEEKLY',
'MONTHLY',
'YEARLY'])
1023 $this->prepareRecurrenceRule($event, $fields);
1026 return (
new EventBuilderFromArray($fields))->build();
1040 private function syncSections(Connection $connection, $calendarsList): array
1043 foreach ($calendarsList as $calendar)
1046 'XML_ID' => $calendar[
'href'],
1047 'NAME' => $calendar[
'displayname'] ??
null,
1048 'DESCRIPTION' => $calendar[
'calendar-description'] ??
'',
1049 'TYPE' => $calendar[
'supported-calendar-component-set'] ??
'',
1050 'COLOR' => $calendar[
'calendar-color'] ??
null,
1051 'MODIFICATION_LABEL' => $calendar[
'getctag'] ??
null,
1055 return $this->syncCalendarSections(
1072 private function getEventsToSync(
1073 Connection $connection,
1074 \CDavGroupdavClientCalendar $client,
1076 ?RequestLogger $logger =
null
1079 $calendarEvents = [];
1080 $syncToken = !$calendar[
'IS_NEW'] ? $calendar[
'SYNC_TOKEN'] :
null;
1082 $calendarItems = $client->GetCalendarItemsBySyncToken($calendar[
'XML_ID'], $syncToken, $logger);
1084 if (!$calendarItems || !is_array($calendarItems))
1086 return $calendarEvents;
1089 foreach ($calendarItems as $item)
1092 (
int)$item[
'status'] === 404
1093 || mb_strpos($item[
'getcontenttype'],
'text/calendar') !==
false
1096 $calendarEvents[] = [
1097 'XML_ID' => $client::getBasenameWithoutExtension($item[
'href']),
1098 'SYNC_TOKEN' => $item[
'getetag'] ??
null,
1099 'STATUS' => $item[
'status'],
1104 $calendarEvents = $this->syncCalendarEvents(
1106 $calendar[
'SECTION_ID'],
1110 $eventsToUpdate = [];
1112 foreach ($calendarEvents as $event)
1114 $link = $client->GetRequestEventPath($calendar[
'XML_ID'], $event[
'XML_ID']);
1115 $eventsToUpdate[] = $link;
1116 $eventsMap[$link] = $event;
1119 $calendarItems = [];
1120 if ($eventsToUpdate)
1122 $calendarItems = $client->GetCalendarItemsList(
1123 $calendar[
'XML_ID'],
1132 if (!$syncToken && $calendarItems)
1134 $calendarItems = $this->applyTimeLimitForEvents($calendarItems);
1137 return [$calendarItems, $eventsMap];
1145 private function applyTimeLimitForEvents($events): array
1147 $timestamp = time() - self::TIME_SLICE;
1148 foreach ($events as $key => $event)
1150 if (!empty($event[
'calendar-data'][
'PROPERTY_PERIOD_UNTIL']))
1152 if ((
int)\CCalendar::Timestamp($event[
'calendar-data'][
'PROPERTY_PERIOD_UNTIL']) - $timestamp < 0)
1154 unset($events[$key]);
1158 !empty($event[
'calendar-data'][
'DATE_TO'])
1159 && (
int)\CCalendar::Timestamp($event[
'calendar-data'][
'DATE_TO']) - $timestamp < 0
1162 unset($events[$key]);
1166 return array_values($events);
1175 private function parseInvitedAttendees(array $event, array &$data): void
1177 if (!empty($event[
'ATTENDEE']))
1180 foreach ($event[
'ATTENDEE'] as $attendee)
1184 if ($attendee->Parameter(
'CN'))
1186 $attendeeData[
'CN'] = $attendee->Parameter(
'CN');
1188 if ($attendee->Parameter(
'CUTYPE'))
1190 $attendeeData[
'CUTYPE'] = $attendee->Parameter(
'CUTYPE');
1192 if ($attendee->Parameter(
'PARTSTAT'))
1194 $attendeeData[
'PARTSTAT'] = $attendee->Parameter(
'PARTSTAT');
1196 if ($attendee->Parameter(
'ROLE'))
1198 $attendeeData[
'ROLE'] = $attendee->Parameter(
'ROLE');
1200 if ($attendee->Parameter(
'EMAIL'))
1202 $attendeeData[
'EMAIL'] = $attendee->Parameter(
'EMAIL');
1204 if ($attendee->Parameter(
'SCHEDULE-STATUS'))
1206 $attendeeData[
'SCHEDULE-STATUS'] = $attendee->Parameter(
'SCHEDULE-STATUS');
1208 if ($attendee->Value())
1210 $attendeeData[
'VALUE'] = $attendee->Value();
1213 $data[
'ATTENDEE'][] = $attendeeData;
1217 if ($organizer = $event[
'ORGANIZER_ENTITY'][0])
1219 if ($organizer->Parameter(
'EMAIL'))
1221 $data[
'ORGANIZER'][
'EMAIL'] = $organizer->Parameter(
'EMAIL');
1223 if ($organizer->Parameter(
'CN'))
1225 $data[
'ORGANIZER'][
'CN'] = $organizer->Parameter(
'CN');
1227 if ($organizer->Value())
1229 $data[
'ORGANIZER'][
'VALUE'] = $organizer->Value();
1240 private function parseAttachments(array $event, array &$data): void
1243 foreach ($event[
'ATTACH'] as $attachment)
1245 $attachmentData = [];
1246 if ($attachment->Parameter(
'FMTTYPE'))
1248 $attachmentData[
'FMTTYPE'] = $attachment->Parameter(
'FMTTYPE');
1250 if ($attachment->Parameter(
'SIZE'))
1252 $attachmentData[
'SIZE'] = $attachment->Parameter(
'SIZE');
1254 if ($attachment->Parameter(
'FILENAME'))
1256 $attachmentData[
'FILENAME'] = $attachment->Parameter(
'FILENAME');
1258 if ($attachment->Parameter(
'MANAGED-ID'))
1260 $attachmentData[
'MANAGED-ID'] = $attachment->Parameter(
'MANAGED-ID');
1262 if ($attachment->Value())
1264 $attachmentData[
'VALUE'] = $attachment->Value();
1267 $data[
'ATTACH'][] = $attachmentData;
1278 private function prepareRemind($parsed, array &$fields): void
1280 $cnt = count($parsed);
1281 if ($cnt === 2 && $parsed[1] ===
'date')
1283 $fields[
'REMIND'][] = [
1284 'type' => $parsed[1],
1285 'value' =>
new DateTime($parsed[0],
'Ymd\\THis\\Z'),
1288 else if ($cnt === 2 && $fields[
'SKIP_TIME'] ===
'Y')
1290 $fields[
'REMIND'][] = [
1291 'type' =>
'daybefore',
1293 'time' => 1440 - (int)$parsed[0] * 60,
1296 else if ($cnt === 2)
1298 $fields[
'REMIND'][] = [
1299 'count' => (int)$parsed[0],
1300 'type' => $parsed[1],
1303 else if ($cnt === 3 && $parsed[2] ===
'daybefore')
1305 $fields[
'REMIND'][] = [
1306 'type' => $parsed[2],
1308 'time' => (int)$parsed[0] * 60,
1311 else if ($cnt === 4 && $fields[
'SKIP_TIME'] ===
'Y')
1313 $fields[
'REMIND'][] = [
1314 'type' =>
'daybefore',
1315 'before' => $parsed[0] + 1,
1316 'time' => 1440 - (int)$parsed[2] * 60,
1319 else if ($cnt === 4)
1321 $fields[
'REMIND'][] = [
1322 'type' => $parsed[3],
1323 'count' => (int)$parsed[0] * 24 + $parsed[2],
1333 private function createConnectionObject(EO_DavConnection $connection): Connection
1335 return (
new BuilderConnectionFromDM($connection))->build();
1343 private function processError(array $error): string
1345 return '[' . $error[0] .
'] ' . $error[1];
1353 private function prepareName(?
string $name): string
1357 IncludeModuleLangFile($_SERVER[
"DOCUMENT_ROOT"].
"/bitrix/modules/calendar/classes/general/calendar_js.php");
1370 private function prepareRecurrenceRule(array $event, array &$fields): void
1372 $fields[
'RRULE'][
'FREQ'] = $event[
'PROPERTY_PERIOD_TYPE'];
1373 $fields[
'RRULE'][
'INTERVAL'] = $event[
'PROPERTY_PERIOD_COUNT'];
1375 if (empty($fields[
'DT_LENGTH']) && !empty($event[
'PROPERTY_EVENT_LENGTH']))
1377 $fields[
'DT_LENGTH'] = (int)$fields[
'PROPERTY_EVENT_LENGTH'];
1379 else if (isset($event[
'DT_TO_TS'], $event[
'DT_FROM_TS']))
1381 $fields[
'DT_LENGTH'] = $event[
'DT_TO_TS'] - $event[
'DT_FROM_TS'];
1385 $fields[
'DT_LENGTH'] =
null;
1388 if ($fields[
'RRULE'][
'FREQ'] ===
'WEEKLY' && !empty($event[
'PROPERTY_PERIOD_ADDITIONAL']))
1390 $fields[
'RRULE'][
'BYDAY'] = [];
1391 $days = explode(
',', $event[
'PROPERTY_PERIOD_ADDITIONAL']);
1392 foreach ($days as $day)
1394 $day = \CCalendar::WeekDayByInd($day,
false);
1397 $fields[
'RRULE'][
'BYDAY'][] = $day;
1400 $fields[
'RRULE'][
'BYDAY'] = implode(
',', $fields[
'RRULE'][
'BYDAY']);
1403 if (!empty($event[
'PROPERTY_RRULE_COUNT']))
1405 $fields[
'RRULE'][
'COUNT'] = $event[
'PROPERTY_RRULE_COUNT'];
1407 else if (!empty($event[
'PROPERTY_PERIOD_UNTIL']))
1409 $fields[
'RRULE'][
'UNTIL'] = $event[
'PROPERTY_PERIOD_UNTIL'];
1413 $fields[
'RRULE'][
'UNTIL'] = $event[
'DT_TO_TS'] ??
null;
1416 if (!empty($event[
'EXDATE']))
1418 $fields[
'EXDATE'] = $event[
'EXDATE'];
1428 private function prepareDescription(array $event, array &$fields): void
1430 if (isset($event[
'MEETING']) && !empty($event[
'MEETING'][
'LANGUAGE_ID']))
1432 $languageId = $event[
'MEETING'][
'LANGUAGE_ID'];
1436 $languageId = \CCalendar::getUserLanguageId((
int)$fields[
'OWNER_ID']);
1439 $fields[
'DESCRIPTION'] = (
new EventDescription())->prepareAfterImport($event[
'DETAIL_TEXT'], $languageId);
1448 private function mergeInstanceParams(array &$instance, array $localInstance): void
1450 $instance[
'ID'] = (int)$localInstance[
'EVENT_ID'];
1451 $instance[
'EVENT_CONNECTION_VERSION'] = (int)$localInstance[
'VERSION'];
1452 $instance[
'EVENT_CONNECTION_ID'] = (int)$localInstance[
'EVENT_CONNECTION_ID'];
1454 if (!empty($localInstance[
'MEETING']))
1456 $instance[
'MEETING'] = unserialize($localInstance[
'MEETING'], [
'allowed_classes' =>
false]);
1458 if (!empty($localInstance[
'ATTENDEES_CODES']))
1460 $instance[
'ATTENDEES_CODES'] = explode(
',', $localInstance[
'ATTENDEES_CODES']);
1462 if (!empty($localInstance[
'IS_MEETING']))
1464 $instance[
'IS_MEETING'] = (bool)$localInstance[
'IS_MEETING'];
1466 if (!empty($localInstance[
'MEETING_HOST']))
1468 $instance[
'MEETING_HOST'] = $localInstance[
'MEETING_HOST'];
1470 if (!empty($localInstance[
'ACCESSIBILITY']))
1472 $instance[
'ACCESSIBILITY'] = $localInstance[
'ACCESSIBILITY'];
1482 private function addParentDataToInstance(array $instance, array $parentEvent): array
1484 if (empty($instance[
'IS_MEETING']))
1486 $instance[
'IS_MEETING'] = $parentEvent[
'IS_MEETING'];
1488 if (empty($instance[
'MEETING_HOST']))
1490 $instance[
'MEETING_HOST'] = $parentEvent[
'MEETING_HOST'];
1492 if (empty($instance[
'MEETING']))
1494 $instance[
'MEETING'] = $parentEvent[
'MEETING'];
1496 if (empty($instance[
'ATTENDEES_CODES']))
1498 $instance[
'ATTENDEES_CODES'] = $parentEvent[
'ATTENDEES_CODES'];
1501 $instance[
'VERSION'] = !empty($instance[
'EVENT_CONNECTION_VERSION'])
1502 ? max($parentEvent[
'VERSION'], $instance[
'EVENT_CONNECTION_VERSION'])
1503 : $parentEvent[
'VERSION']
1506 $instance[
'RECURRENCE_ID'] = $parentEvent[
'ID'];
1518 private function prepareInstanceEvents(array $events): array
1522 $eventsCount = count($events);
1524 for ($i = $eventsCount - 1; $i >= 0; $i--)
1526 $eventDate = \CCalendar::Date(\CCalendar::Timestamp($events[$i][
'DATE_FROM']),
false);
1527 if (!in_array($eventDate, $eventDates,
true))
1529 $instances[] = $events[$i];
1530 $eventDates[] = $eventDate;
1534 return [$instances, $eventDates];
1543 private function prepareExistedEventParams(
string $xmlId, ?array $link =
null): array
1547 'ID' => (int)($link[
'EVENT_ID'] ??
null),
1548 'EVENT_NAME' => $link[
'EVENT_NAME'] ??
null,
1549 'EVENT_CONNECTION_ID' => (int)($link[
'EVENT_CONNECTION_ID'] ??
null),
1550 'EXDATE' => $link[
'EXDATE'] ??
null,
1551 'VERSION' => $link[
'EVENT_VERSION'] ?? $link[
'VERSION'] ?? 1,
1552 'MEETING' => ($link[
'MEETING'] ??
null)
1553 ? unserialize($link[
'MEETING'], [
'allowed_classes' =>
false])
1556 'IS_MEETING' => (bool)($link[
'IS_MEETING'] ??
null),
1557 'ATTENDEES_CODES' => ($link[
'ATTENDEES_CODES'] ??
null)
1558 ? explode(
',', $link[
'ATTENDEES_CODES'])
1561 'ACCESSIBILITY' => $link[
'ACCESSIBILITY'] ??
'busy',
1562 'DATE_FROM' => $link[
'EVENT_DATE_FROM'] ??
null,
1563 'DATE_TO' => $link[
'EVENT_DATE_TO'] ??
null,
1564 'TZ_FROM' => $link[
'EVENT_TZ_FROM'] ??
null,
1565 'TZ_TO' => $link[
'EVENT_TZ_TO'] ??
null,
1576 private function mergeExternalEventWithLocal(
1577 array $existedEvent,
1579 \CDavGroupdavClientCalendar $client
1582 $exDate = $existedEvent[
'EXDATE'];
1583 $event[
'calendar-data'] = array_merge($event[
'calendar-data'], [
1584 'ID' => $existedEvent[
'ID'],
1585 'XML_ID' => $client::getBasenameWithoutExtension($event[
'href']),
1586 'MODIFICATION_LABEL' => $event[
'getetag'],
1587 'MEETING' => $existedEvent[
'MEETING'],
1588 'IS_MEETING' => $existedEvent[
'IS_MEETING'],
1589 'ATTENDEES_CODES' => $existedEvent[
'ATTENDEES_CODES'],
1590 'ACCESSIBILITY' => $existedEvent[
'ACCESSIBILITY'],
1593 return [$event, $exDate];