Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
datasyncmanager.php
1<?php
2
4
22use Bitrix\Dav\Internals\DavConnectionTable;
23use Bitrix\Dav\Internals\EO_DavConnection;
24use Bitrix\Dav\Internals\EO_DavConnection_Collection;
36
38{
39 private const ENTITY_TYPE = 'user';
40 private const MAX_NUMBER = 5;
41 private const TIME_SLICE = 2600000;
42
44 private $mapperFactory;
45
49 private function __construct()
50 {
51 $this->mapperFactory = ServiceLocator::getInstance()->get('calendar.service.mappers.factory');
52 }
53
62 public static function dataSyncAgent(): string
63 {
64 (new self())->dataSync();
65
66 return "\\Bitrix\\Calendar\\Sync\\Managers\\DataSyncManager::dataSyncAgent();";
67 }
68
72 public static function createInstance(): DataSyncManager
73 {
74 return new self();
75 }
76
87 public function dataSync($userId = null): bool
88 {
89 if (!Loader::includeModule('dav') || !Loader::includeModule('calendar'))
90 {
91 return true;
92 }
93
94 $connections = $this->getConnections($userId);
95 foreach ($connections as $connection)
96 {
97 $connection = $this->createConnectionObject($connection);
98 $result = $this->syncConnection($connection);
99 if ($result->isSuccess())
100 {
101 \CDavConnection::SetLastResult($connection->getId(), $result->getData()['lastResult']);
102 Util::addPullEvent('refresh_sync_status', $connection->getOwner()->getId(), [
103 'syncInfo' => [
104 $connection->getAccountType() => [
105 'status' => $result->getData()['syncStatus'],
106 'type' => $connection->getAccountType(),
107 'connected' => true,
108 'id' => $connection->getId(),
109 'syncOffset' => 0
110 ],
111 ],
112 'requestUid' => Util::getRequestUid(),
113 ]);
114 }
115 }
116
117 return true;
118 }
119
131 private function syncConnection(Connection $connection): Result
132 {
133 $result = new Result();
134 $logger = null;
135
136 if (
137 RequestLogger::isEnabled()
138 )
139 {
140 $logger = new RequestLogger($connection->getOwner()->getId(), $connection->getVendor()->getCode());
141 }
142
143 $client = $this->initClient($connection);
144
145 $calendarsList = $client->GetCalendarList($connection->getServer()->getBasePath(), null);
146
147 if ($client->getError())
148 {
149 $error = $this->processError($client->getError());
150 $result->setData([
151 'lastResult' => $error,
152 'syncStatus' => false,
153 ]);
154
155 return $result;
156 }
157
158 if (!$calendarsList || !is_array($calendarsList))
159 {
160 $result->setData([
161 'lastResult' => '[204] No Content',
162 'syncStatus' => true,
163 ]);
164
165 return $result;
166 }
167
168 $calendarsList = $this->syncSections($connection, $calendarsList);
169
170 foreach ($calendarsList as $calendar)
171 {
172 [$events, $eventsMap] = $this->getEventsToSync(
173 $connection,
174 $client,
175 $calendar,
176 $logger
177 );
178
179 if ($client->getError())
180 {
181 $error = $this->processError($client->getError());
182 $result->setData([
183 'lastResult' => $error,
184 'syncStatus' => false,
185 ]);
186
187 return $result;
188 }
189
190 foreach ($events as $event)
191 {
192 $this->modifyEvent(
193 $connection,
194 $client,
195 $event,
196 $eventsMap,
197 $calendar
198 );
199 }
200 }
201
202 $result->setData([
203 'lastResult' => '[200] OK',
204 'syncStatus' => true,
205 ]);
206
207 return $result;
208 }
209
223 private function modifyEvent(
224 Connection $connection,
225 \CDavGroupdavClientCalendar $client,
226 array $event,
227 array $eventsMap,
228 array $calendar
229 ): void
230 {
231 if (!array_key_exists($event['href'], $eventsMap))
232 {
233 return;
234 }
235
236 $eventId = null;
237 $existEvent = $eventsMap[$event['href']];
238 if (empty($event['calendar-data']))
239 {
240 return;
241 }
242
243 [$event, $exDate] = $this->mergeExternalEventWithLocal($existEvent, $event, $client);
244
245 if (!empty($event['calendar-data']) && is_array($event['calendar-data']))
246 {
247 $eventId = $this->modifySingleEvent(
248 $connection,
249 $event['calendar-data'],
250 [
251 'SECTION_ID' => $calendar['SECTION_ID'],
252 'VERSION' => $existEvent['VERSION'],
253 'EVENT_CONNECTION_ID' => $eventsMap[$event['href']]['EVENT_CONNECTION_ID'],
254 ]
255 );
256 }
257
258 if ($eventId && !empty($event['calendar-data-ex'] && is_array($event['calendar-data-ex'])))
259 {
260 $this->modifyRecurrenceEvent(
261 $connection,
262 $event['calendar-data-ex'],
263 [
264 'PARENT_ID' => $eventId,
265 'SECTION_ID' => $calendar['SECTION_ID'],
266 'PERIOD_UNTIL' => $event['calendar-data']['PROPERTY_PERIOD_UNTIL'] ?? null,
267 ]
268 );
269 }
270 else if ($exDate && $event['calendar-data'] && $event['calendar-data']['ID'])
271 {
272 $this->deleteDuplicateExDates(
273 $exDate,
274 $event['calendar-data']['DATE_FROM'],
275 $event['calendar-data']['ID'],
276 $connection->getOwner()->getId(),
277 );
278 }
279 }
280
293 private function deleteDuplicateExDates($exDate, $dateFrom, $eventId, $userId): void
294 {
295 global $DB;
296 $exDates = \CCalendarEvent::GetExDate($exDate);
297 $dtStartTimestamp = \CCalendar::Timestamp($dateFrom, false);
298 $needToUpdate = false;
299 foreach ($exDates as $date)
300 {
301 $dateTs = \CCalendar::Timestamp($date, false);
302 if ($dateTs < $dtStartTimestamp)
303 {
304 $needToUpdate = true;
305 break;
306 }
307 }
308
309 if ($needToUpdate)
310 {
311 $childEvents = EventConnectionTable::query()
312 ->setSelect([
313 'EVENT_ID',
314 'VERSION',
315 'DATE_FROM' => 'EVENT.DATE_FROM',
316 'EVENT_CONNECTION_ID' => 'ID',
317 ])
318 ->where('EVENT.RECURRENCE_ID', $eventId)
319 ->where('EVENT.DELETED', 'N')
320 ->where('EVENT.OWNER_ID', $userId)
321 ->exec()
322 ;
323
324 while ($child = $childEvents->fetch())
325 {
326 $eventIdList = $this->getAllEventByParentId($child['EVENT_ID']);
327
328 EventTable::updateMulti($eventIdList, ['DELETED' => 'Y']);
329
330 EventConnectionTable::delete($child['EVENT_CONNECTION_ID']);
331 }
332 }
333 }
334
347 private function modifySingleEvent(Connection $connection, array $event, array $additionalParams): ?int
348 {
349 $eventObject = $this->prepareEventParams(
350 $event,
351 $additionalParams['SECTION_ID'],
352 $connection->getOwner()->getId()
353 );
354
355 if ($eventObject->getId())
356 {
357 $result = $this->mapperFactory->getEvent()->update($eventObject, [
358 'userId' => $connection->getOwner()->getId(),
359 'bAffectToDav' => false, // Used to prevent synchro with calDav again
360 'bSilentAccessMeeting' => true,
361 'autoDetectSection' => false,
362 'originalFrom' => $connection->getVendor()->getCode(),
363 ]);
364 }
365 else
366 {
367 $result = $this->mapperFactory->getEvent()->create($eventObject, [
368 'userId' => $connection->getOwner()->getId(),
369 'bAffectToDav' => false, // Used to prevent synchro with calDav again
370 'bSilentAccessMeeting' => true,
371 'autoDetectSection' => false,
372 'originalFrom' => $connection->getVendor()->getCode(),
373 ]);
374 }
375
376 if ($result && $result->getId())
377 {
378 $data = [];
379 // Prepare Data with outer params
380 if (!empty($event['ATTENDEE']) || !empty($event['ORGANIZER_ENTITY']))
381 {
382 $this->parseInvitedAttendees($event, $data);
383 }
384 if (!empty($event['ATTACH']))
385 {
386 $this->parseAttachments($event, $data);
387 }
388 if (!empty($event['URL']))
389 {
390 $data['URL'] = $event['URL'];
391 }
392
393 if (!empty($additionalParams['EVENT_CONNECTION_ID']))
394 {
395 EventConnectionTable::update($additionalParams['EVENT_CONNECTION_ID'], [
396 'SYNC_STATUS' => Dictionary::SYNC_STATUS['success'],
397 'ENTITY_TAG' => $event['MODIFICATION_LABEL'] ?? null,
398 'VERSION' => (string)($additionalParams['VERSION'] ?? null),
399 'VENDOR_VERSION_ID' => (string)($additionalParams['VERSION'] ?? null),
400 'DATA' => $data,
401 ]);
402 }
403 else
404 {
405 EventConnectionTable::add([
406 'EVENT_ID' => (int)$result->getId(),
407 'CONNECTION_ID' => $connection->getId(),
408 'VENDOR_EVENT_ID' => $event['XML_ID'] ?? null,
409 'SYNC_STATUS' => Dictionary::SYNC_STATUS['success'],
410 'ENTITY_TAG' => $event['MODIFICATION_LABEL'] ?? null,
411 'VERSION' => (string)($additionalParams['VERSION'] ?? null),
412 'VENDOR_VERSION_ID' => (string)($additionalParams['VERSION'] ?? null),
413 'DATA' => $data,
414 ]);
415 }
416
417 return (int)$result->getId();
418 }
419
420 return null;
421 }
422
435 private function modifyRecurrenceEvent(
436 Connection $connection,
437 array $importInstances,
438 array $additionalParams
439 ): void
440 {
441 [$importInstances, $importedInstancesDates] = $this->prepareInstanceEvents($importInstances);
442 $parentEvent = \CCalendarEvent::GetById($additionalParams['PARENT_ID'], true, true);
443
444 if ($parentEvent && \CCalendarEvent::CheckRecurcion($parentEvent))
445 {
446 $exDates = \CCalendarEvent::GetExDate($parentEvent['EXDATE']);
447 $localInstances = EventConnectionTable::query()
448 ->setSelect([
449 'EVENT_ID',
450 'VERSION',
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',
458 ])
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')
463 ->exec()
464 ;
465
466 $importedInstancesCount = count($importInstances);
467 while ($localInstance = $localInstances->fetch())
468 {
469 $isActive = false;
470 $localInstanceDate = \CCalendar::Date(\CCalendar::Timestamp($localInstance['DATE_FROM']), false);
471 for ($i = 0; $i < $importedInstancesCount; $i++)
472 {
473 if ($localInstanceDate === $importedInstancesDates[$i])
474 {
475 $this->mergeInstanceParams($importInstances[$i], $localInstance);
476 $isActive = true;
477
478 break;
479 }
480 }
481
482 if (!$isActive)
483 {
484 \CCalendarEvent::Delete([
485 'id' => $localInstance['EVENT_ID'],
486 'bMarkDeleted' => true,
487 'bAffectToDav' => false,
488 'originalFrom' => $connection->getVendor()->getCode(),
489 'userId' => $connection->getOwner()->getId(),
490 ]);
491
492 EventConnectionTable::delete($localInstance['EVENT_CONNECTION_ID']);
493 }
494 }
495
496 foreach ($importInstances as $instance)
497 {
498 if (
499 $additionalParams['PERIOD_UNTIL']
500 && \CCalendar::Timestamp($instance['DATE_FROM']) > \CCalendar::Timestamp($additionalParams['PERIOD_UNTIL'])
501 )
502 {
503 continue;
504 }
505 $instance = $this->addParentDataToInstance($instance, $parentEvent);
506
507 if ($instance && $instance['RECURRENCE_ID'])
508 {
509 unset($instance['RRULE'], $instance['EXDATE']);
510
511 $instanceId = $this->modifySingleEvent(
512 $connection,
513 $instance,
514 [
515 'SECTION_ID' => $additionalParams['SECTION_ID'],
516 'VERSION' => $instance['VERSION'] ?? 1,
517 'EVENT_CONNECTION_ID' => $instance['EVENT_CONNECTION_ID'] ?? 0,
518 ]
519 );
520
521 if (!empty($instance['RECURRENCE_ID_DATE']))
522 {
523 $exDates[] = \CCalendar::Date(\CCalendar::Timestamp($instance['RECURRENCE_ID_DATE']), false);
524
525 $originalRecursionId = (int)($parentEvent['ORIGINAL_RECURSION_ID'] ?? $parentEvent['ID']);
526
527 (new EventOriginalRecursion())->add($instanceId, $originalRecursionId);
528 }
529 }
530 }
531 $exDate = \CCalendarEvent::SetExDate($exDates);
532 $eventIdList = $this->getAllEventByParentId($parentEvent['ID']);
533
534 EventTable::updateMulti($eventIdList, ['EXDATE' => $exDate]);
535 }
536 }
537
546 private function getAllEventByParentId(int $parentId): array
547 {
548 $eventIdList = EventTable::query()
549 ->setSelect(['ID'])
550 ->where('PARENT_ID', $parentId)
551 ->exec()
552 ->fetchAll()
553 ;
554
555 return array_map(static function($event){
556 return (int)$event['ID'];
557 }, $eventIdList);
558 }
559
568 private function getConnections($userId = null): EO_DavConnection_Collection
569 {
570 $query = DavConnectionTable::query()
571 ->setSelect(['*'])
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'])
577 ;
578 if ($userId)
579 {
580 $query->where('ENTITY_ID', $userId);
581 }
582
583 return $query->exec()->fetchCollection();
584 }
585
591 private function initClient(Connection $connection): \CDavGroupdavClientCalendar
592 {
593 $client = new \CDavGroupdavClientCalendar(
594 $connection->getServer()->getScheme(),
595 $connection->getServer()->getHost(),
596 $connection->getServer()->getPort(),
597 $connection->getServer()->getUserName(),
598 $connection->getServer()->getPassword()
599 );
600 if (\CDav::UseProxy())
601 {
602 $proxy = \CDav::GetProxySettings();
603 $client->SetProxy(
604 $proxy['PROXY_SCHEME'],
605 $proxy['PROXY_HOST'],
606 $proxy['PROXY_PORT'],
607 $proxy['PROXY_USERNAME'],
608 $proxy['PROXY_PASSWORD']
609 );
610 }
611
612 return $client;
613 }
614
627 private function syncCalendarSections(
628 Connection $connection,
629 array $calendars
630 ): array
631 {
632 $calendarNames = [];
633 $result = [];
634
635 foreach ($calendars as $calendar)
636 {
637 if (isset($calendar['TYPE']) && $calendar['TYPE'] === 'VEVENT')
638 {
639 $calendarNames[$calendar['XML_ID']] = $calendar;
640 }
641 }
642
643 $sectionsLink = SectionConnectionTable::query()
644 ->setSelect([
645 'SECTION_CONNECTION_ID' => 'ID',
646 'NAME' => 'SECTION.NAME',
647 'EXTERNAL_TYPE' => 'SECTION.EXTERNAL_TYPE',
648 'VENDOR_SECTION_ID',
649 'VERSION_ID',
650 'SECTION_ID',
651 ])
652 ->where('SECTION.CAL_TYPE', self::ENTITY_TYPE)
653 ->where('SECTION.OWNER_ID', $connection->getOwner()->getId())
654 ->where('CONNECTION_ID', $connection->getId())
655 ->exec()
656 ;
657
658 while ($link = $sectionsLink->fetch())
659 {
660 $xmlId = $link['VENDOR_SECTION_ID'];
661 if (empty($xmlId))
662 {
663 continue;
664 }
665
666 if (!array_key_exists($xmlId, $calendarNames))
667 {
668 $section = $this->mapperFactory->getSection()->getById($link['SECTION_ID']);
669 if ($section)
670 {
671 (new IncomingManager($connection))->deleteSection($section, $link['SECTION_CONNECTION_ID']);
672 }
673 }
674 else
675 {
676 if (($link['VERSION_ID'] ?? null) !== ($calendarNames[$xmlId]['MODIFICATION_LABEL'] ?? null))
677 {
678 $fields = [
679 'ID' => (int)($link['SECTION_ID'] ?? null),
680 'NAME' => isset($link['EXTERNAL_TYPE']) && $link['EXTERNAL_TYPE'] === 'local'
681 ? $link['NAME']
682 : $calendarNames[$xmlId]['NAME']
683 ,
684 'DESCRIPTION' => $calendarNames[$xmlId]['DESCRIPTION'] ?? null,
685 'COLOR' => $calendarNames[$xmlId]['COLOR'] ?? null,
686 ];
687
688 \CCalendarSect::Edit([
689 'arFields' => $fields,
690 'bAffectToDav' => false,
691 'originalFrom' => $connection->getVendor()->getCode(),
692 ]);
693
694 SectionConnectionTable::update((int)$link['SECTION_CONNECTION_ID'], [
695 'LAST_SYNC_DATE' => new DateTime(),
696 'LAST_SYNC_STATUS' => Dictionary::SYNC_STATUS['success'],
697 'VERSION_ID' => $calendarNames[$xmlId]['MODIFICATION_LABEL'] ?? null,
698 ]
699 );
700
701 $result[] = [
702 'XML_ID' => $xmlId,
703 'SECTION_ID' => $link['SECTION_ID'],
704 'SECTION_CONNECTION_ID' => $link['SECTION_CONNECTION_ID'],
705 'SYNC_TOKEN' => $link['VERSION_ID'] ?? null,
706 'IS_NEW' => false,
707 ];
708 }
709
710 unset($calendarNames[$xmlId]);
711 }
712 }
713
714 foreach ($calendarNames as $curXmlId => $calendar)
715 {
716 $fields = [
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(),
725 ];
726
727 $id = (int)\CCalendarSect::Edit([
728 'arFields' => $fields,
729 'bAffectToDav' => false,
730 'originalFrom' => $connection->getVendor()->getCode(),
731 ]);
732
733 if ($id)
734 {
735 $linkId = SectionConnectionTable::add([
736 'SECTION_ID' => $id,
737 'CONNECTION_ID' => $connection->getId(),
738 'VENDOR_SECTION_ID' => $curXmlId,
739 'ACTIVE' => 'Y',
740 'LAST_SYNC_DATE' => new DateTime(),
741 'LAST_SYNC_STATUS' => Dictionary::SYNC_STATUS['success'],
742 'VERSION_ID' => $calendar['MODIFICATION_LABEL'],
743 ]);
744
745 $result[] = [
746 'XML_ID' => $curXmlId,
747 'SECTION_ID' => $id,
748 'SECTION_CONNECTION_ID' => $linkId->getId(),
749 'SYNC_TOKEN' => $calendar['MODIFICATION_LABEL'],
750 'IS_NEW' => true,
751 ];
752 }
753 }
754
755 return $result;
756 }
757
769 private function syncCalendarEvents(
770 Connection $connection,
771 int $sectionId,
772 array $events
773 ): array
774 {
775 $linksMap = [];
776 $result = [];
777
778 $eventsLink = EventConnectionTable::query()
779 ->setSelect([
780 'EVENT_CONNECTION_ID' => 'ID',
781 'VENDOR_EVENT_ID',
782 'VERSION',
783 'ENTITY_TAG',
784 'EVENT_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',
799 ])
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()
805 ->logic('or')
806 ->whereNot('EVENT.MEETING_STATUS', 'N')
807 ->whereNull('EVENT.MEETING_STATUS')
808 )
809 ->whereNotNull('ENTITY_TAG')
810 ->exec()
811 ;
812
813 while ($event = $eventsLink->fetch())
814 {
815 $linksMap[$event['VENDOR_EVENT_ID']] = $event;
816 }
817
818 foreach ($events as $index => $event)
819 {
820 if (!empty($linksMap[$event['XML_ID']]))
821 {
822 $existEvent = $linksMap[$event['XML_ID']];
823
824 if (($existEvent['ENTITY_TAG'] ?? null) === ($event['SYNC_TOKEN'] ?? null))
825 {
826 continue;
827 }
828 if ($this->isBlockedChange($existEvent, $event))
829 {
830 continue;
831 }
832
833 if ((int)$event['STATUS'] === 200)
834 {
835 if (($linksMap[$event['XML_ID']]['ENTITY_TAG'] ?? null) !== ($event['SYNC_TOKEN'] ?? null))
836 {
837 $result[] = $this->prepareExistedEventParams($event['XML_ID'], $linksMap[$event['XML_ID']]);
838 }
839 else
840 {
841 unset($events[$index]);
842 }
843 }
844 else if ((int)$event['STATUS'] === 404)
845 {
846 \CCalendar::DeleteEvent(
847 $linksMap[$event['XML_ID']]['EVENT_ID'],
848 false,
849 [
850 'markDeleted' => true,
851 'originalFrom' => $connection->getVendor()->getCode(),
852 'checkPermissions' => false,
853 ]
854 );
855
856 EventConnectionTable::delete($linksMap[$event['XML_ID']]['EVENT_CONNECTION_ID']);
857 }
858 }
859 else if (!empty($event['SYNC_TOKEN']) && (int)$event['STATUS'] === 200)
860 {
861 $result[] = $this->prepareExistedEventParams($event['XML_ID']);
862 }
863 }
864
865 return $result;
866 }
867
875 private function isBlockedChange(array $existEvent, array $importedEvent): bool
876 {
877 if ($existEvent['EVENT_TYPE'] === ResourceBooking::EVENT_LABEL)
878 {
879 return true;
880 }
881
882 if ($existEvent['EVENT_ID'] !== $existEvent['EVENT_PARENT_ID'])
883 {
884 return true;
885 }
886
887 return false;
888 }
889
898 private function prepareEventParams(array $event, int $sectionId, int $entityId): Event
899 {
900 $fields = [
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],
908 'SECTIONS' => [$sectionId],
909 'ACCESSIBILITY' => !empty($event['ID'])
910 ? $event['ACCESSIBILITY']
911 : ($event['PROPERTY_ACCESSIBILITY'] ?? 'busy')
912 ,
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',
924 'ACTIVE' => 'Y',
925 'DELETED' => 'N',
926 'TIMESTAMP_X' => new DateTime(),
927 ];
928
929 if (!empty($event['RECURRENCE_ID']))
930 {
931 $fields['RECURRENCE_ID'] = $event['RECURRENCE_ID'];
932 }
933
934 if (!empty($event['MEETING']))
935 {
936 $fields['MEETING'] = $event['MEETING'];
937 $fields['MEETING_HOST'] = $event['MEETING']['MEETING_CREATOR'] ?? null;
938 }
939 else
940 {
941 $fields['MEETING'] = [
942 'HOST_NAME' => \CCalendar::GetUserName($entityId),
943 'NOTIFY' => true,
944 'REINVITE' => false,
945 'ALLOW_INVITE' => false,
946 'MEETING_CREATOR' => $entityId,
947 'HIDE_GUESTS' => true,
948 'LANGUAGE_ID' => \CCalendar::getUserLanguageId($entityId)
949 ];
950 $fields['MEETING_HOST'] = $entityId;
951 $fields['MEETING_STATUS'] = 'H';
952 }
953
954 if (!empty($event['ATTENDEES_CODES']))
955 {
956 $fields['ATTENDEES_CODES'] = $event['ATTENDEES_CODES'];
957 }
958
959 if (!empty($event['RECURRENCE_ID_DATE']))
960 {
961 $fields['ORIGINAL_DATE_FROM'] = $event['RECURRENCE_ID_DATE'];
962 }
963
964 if (!empty($fields['ORIGINAL_DATE_FROM']) && !empty($fields['RECURRENCE_ID']))
965 {
966 $fields['RELATIONS'] = ['COMMENT_XML_ID' => \CCalendarEvent::GetEventCommentXmlId($fields)];
967 }
968
969 if (empty($fields['TZ_TO']) && $fields['SKIP_TIME'] === 'N')
970 {
971 $currentTimezone = (new DateTime())->getTimeZone()->getName();
972 $fields['TZ_TO'] = $currentTimezone;
973 $fields['TZ_FROM'] = $currentTimezone;
974 }
975
976 if (!empty($event['SKIP_TIME']))
977 {
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(),
981 false
982 );
983 }
984
985 if (!empty($event['PROPERTY_REMIND_SETTINGS']))
986 {
987 if (is_array($event['PROPERTY_REMIND_SETTINGS']))
988 {
989 foreach ($event['PROPERTY_REMIND_SETTINGS'] as $remind)
990 {
991 $parsed = explode('_', $remind);
992 $this->prepareRemind($parsed, $fields);
993 }
994 }
995 else
996 {
997 $parsed = explode('_', $event['PROPERTY_REMIND_SETTINGS']);
998 $this->prepareRemind($parsed, $fields);
999 }
1000 }
1001
1002 if (!empty($event['PROPERTY_IMPORTANCE']))
1003 {
1004 $fields['IMPORTANCE'] = $event['PROPERTY_IMPORTANCE'];
1005 }
1006
1007 if (!empty($event['PROPERTY_LOCATION']))
1008 {
1009 $fields['LOCATION'] = Rooms\Util::unParseTextLocation($event['PROPERTY_LOCATION']);
1010 }
1011
1012 if (!empty($event['DETAIL_TEXT']))
1013 {
1014 $this->prepareDescription($event, $fields);
1015 }
1016
1017 //RRULE SEGMENT
1018 if (
1019 !empty($event['PROPERTY_PERIOD_TYPE'])
1020 && in_array($event['PROPERTY_PERIOD_TYPE'], ['DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY'])
1021 )
1022 {
1023 $this->prepareRecurrenceRule($event, $fields);
1024 }
1025
1026 return (new EventBuilderFromArray($fields))->build();
1027 }
1028
1040 private function syncSections(Connection $connection, $calendarsList): array
1041 {
1042 $result = [];
1043 foreach ($calendarsList as $calendar)
1044 {
1045 $result[] = [
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,
1052 ];
1053 }
1054
1055 return $this->syncCalendarSections(
1056 $connection,
1057 $result
1058 );
1059 }
1060
1072 private function getEventsToSync(
1073 Connection $connection,
1074 \CDavGroupdavClientCalendar $client,
1075 array $calendar,
1076 ?RequestLogger $logger = null
1077 ): array
1078 {
1079 $calendarEvents = [];
1080 $syncToken = !$calendar['IS_NEW'] ? $calendar['SYNC_TOKEN'] : null;
1081
1082 $calendarItems = $client->GetCalendarItemsBySyncToken($calendar['XML_ID'], $syncToken, $logger);
1083
1084 if (!$calendarItems || !is_array($calendarItems))
1085 {
1086 return $calendarEvents;
1087 }
1088
1089 foreach ($calendarItems as $item)
1090 {
1091 if (
1092 (int)$item['status'] === 404
1093 || mb_strpos($item['getcontenttype'], 'text/calendar') !== false
1094 )
1095 {
1096 $calendarEvents[] = [
1097 'XML_ID' => $client::getBasenameWithoutExtension($item['href']),
1098 'SYNC_TOKEN' => $item['getetag'] ?? null,
1099 'STATUS' => $item['status'],
1100 ];
1101 }
1102 }
1103
1104 $calendarEvents = $this->syncCalendarEvents(
1105 $connection,
1106 $calendar['SECTION_ID'],
1107 $calendarEvents
1108 );
1109
1110 $eventsToUpdate = [];
1111 $eventsMap = [];
1112 foreach ($calendarEvents as $event)
1113 {
1114 $link = $client->GetRequestEventPath($calendar['XML_ID'], $event['XML_ID']);
1115 $eventsToUpdate[] = $link;
1116 $eventsMap[$link] = $event;
1117 }
1118
1119 $calendarItems = [];
1120 if ($eventsToUpdate)
1121 {
1122 $calendarItems = $client->GetCalendarItemsList(
1123 $calendar['XML_ID'],
1124 $eventsToUpdate,
1125 true,
1126 1,
1127 [],
1128 $logger
1129 );
1130 }
1131
1132 if (!$syncToken && $calendarItems)
1133 {
1134 $calendarItems = $this->applyTimeLimitForEvents($calendarItems);
1135 }
1136
1137 return [$calendarItems, $eventsMap];
1138 }
1139
1145 private function applyTimeLimitForEvents($events): array
1146 {
1147 $timestamp = time() - self::TIME_SLICE;
1148 foreach ($events as $key => $event)
1149 {
1150 if (!empty($event['calendar-data']['PROPERTY_PERIOD_UNTIL']))
1151 {
1152 if ((int)\CCalendar::Timestamp($event['calendar-data']['PROPERTY_PERIOD_UNTIL']) - $timestamp < 0)
1153 {
1154 unset($events[$key]);
1155 }
1156 }
1157 else if (
1158 !empty($event['calendar-data']['DATE_TO'])
1159 && (int)\CCalendar::Timestamp($event['calendar-data']['DATE_TO']) - $timestamp < 0
1160 )
1161 {
1162 unset($events[$key]);
1163 }
1164 }
1165
1166 return array_values($events);
1167 }
1168
1175 private function parseInvitedAttendees(array $event, array &$data): void
1176 {
1177 if (!empty($event['ATTENDEE']))
1178 {
1180 foreach ($event['ATTENDEE'] as $attendee)
1181 {
1182 $attendeeData = [];
1183
1184 if ($attendee->Parameter('CN'))
1185 {
1186 $attendeeData['CN'] = $attendee->Parameter('CN');
1187 }
1188 if ($attendee->Parameter('CUTYPE'))
1189 {
1190 $attendeeData['CUTYPE'] = $attendee->Parameter('CUTYPE');
1191 }
1192 if ($attendee->Parameter('PARTSTAT'))
1193 {
1194 $attendeeData['PARTSTAT'] = $attendee->Parameter('PARTSTAT');
1195 }
1196 if ($attendee->Parameter('ROLE'))
1197 {
1198 $attendeeData['ROLE'] = $attendee->Parameter('ROLE');
1199 }
1200 if ($attendee->Parameter('EMAIL'))
1201 {
1202 $attendeeData['EMAIL'] = $attendee->Parameter('EMAIL');
1203 }
1204 if ($attendee->Parameter('SCHEDULE-STATUS'))
1205 {
1206 $attendeeData['SCHEDULE-STATUS'] = $attendee->Parameter('SCHEDULE-STATUS');
1207 }
1208 if ($attendee->Value())
1209 {
1210 $attendeeData['VALUE'] = $attendee->Value();
1211 }
1212
1213 $data['ATTENDEE'][] = $attendeeData;
1214 }
1215 }
1217 if ($organizer = $event['ORGANIZER_ENTITY'][0])
1218 {
1219 if ($organizer->Parameter('EMAIL'))
1220 {
1221 $data['ORGANIZER']['EMAIL'] = $organizer->Parameter('EMAIL');
1222 }
1223 if ($organizer->Parameter('CN'))
1224 {
1225 $data['ORGANIZER']['CN'] = $organizer->Parameter('CN');
1226 }
1227 if ($organizer->Value())
1228 {
1229 $data['ORGANIZER']['VALUE'] = $organizer->Value();
1230 }
1231 }
1232 }
1233
1240 private function parseAttachments(array $event, array &$data): void
1241 {
1243 foreach ($event['ATTACH'] as $attachment)
1244 {
1245 $attachmentData = [];
1246 if ($attachment->Parameter('FMTTYPE'))
1247 {
1248 $attachmentData['FMTTYPE'] = $attachment->Parameter('FMTTYPE');
1249 }
1250 if ($attachment->Parameter('SIZE'))
1251 {
1252 $attachmentData['SIZE'] = $attachment->Parameter('SIZE');
1253 }
1254 if ($attachment->Parameter('FILENAME'))
1255 {
1256 $attachmentData['FILENAME'] = $attachment->Parameter('FILENAME');
1257 }
1258 if ($attachment->Parameter('MANAGED-ID'))
1259 {
1260 $attachmentData['MANAGED-ID'] = $attachment->Parameter('MANAGED-ID');
1261 }
1262 if ($attachment->Value())
1263 {
1264 $attachmentData['VALUE'] = $attachment->Value();
1265 }
1266
1267 $data['ATTACH'][] = $attachmentData;
1268 }
1269 }
1270
1278 private function prepareRemind($parsed, array &$fields): void
1279 {
1280 $cnt = count($parsed);
1281 if ($cnt === 2 && $parsed[1] === 'date')
1282 {
1283 $fields['REMIND'][] = [
1284 'type' => $parsed[1],
1285 'value' => new DateTime($parsed[0], 'Ymd\\THis\\Z'),
1286 ];
1287 }
1288 else if ($cnt === 2 && $fields['SKIP_TIME'] === 'Y')
1289 {
1290 $fields['REMIND'][] = [
1291 'type' => 'daybefore',
1292 'before' => 1,
1293 'time' => 1440 - (int)$parsed[0] * 60,
1294 ];
1295 }
1296 else if ($cnt === 2)
1297 {
1298 $fields['REMIND'][] = [
1299 'count' => (int)$parsed[0],
1300 'type' => $parsed[1],
1301 ];
1302 }
1303 else if ($cnt === 3 && $parsed[2] === 'daybefore')
1304 {
1305 $fields['REMIND'][] = [
1306 'type' => $parsed[2],
1307 'before' => 0,
1308 'time' => (int)$parsed[0] * 60,
1309 ];
1310 }
1311 else if ($cnt === 4 && $fields['SKIP_TIME'] === 'Y')
1312 {
1313 $fields['REMIND'][] = [
1314 'type' => 'daybefore',
1315 'before' => $parsed[0] + 1,
1316 'time' => 1440 - (int)$parsed[2] * 60,
1317 ];
1318 }
1319 else if ($cnt === 4)
1320 {
1321 $fields['REMIND'][] = [
1322 'type' => $parsed[3],
1323 'count' => (int)$parsed[0] * 24 + $parsed[2],
1324 ];
1325 }
1326 }
1327
1333 private function createConnectionObject(EO_DavConnection $connection): Connection
1334 {
1335 return (new BuilderConnectionFromDM($connection))->build();
1336 }
1337
1343 private function processError(array $error): string
1344 {
1345 return '[' . $error[0] . '] ' . $error[1];
1346 }
1347
1353 private function prepareName(?string $name): string
1354 {
1355 if (!$name)
1356 {
1357 IncludeModuleLangFile($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/calendar/classes/general/calendar_js.php");
1358 $name = Loc::getMessage('EC_DEFAULT_ENTRY_NAME');
1359 }
1360
1361 return $name;
1362 }
1363
1370 private function prepareRecurrenceRule(array $event, array &$fields): void
1371 {
1372 $fields['RRULE']['FREQ'] = $event['PROPERTY_PERIOD_TYPE'];
1373 $fields['RRULE']['INTERVAL'] = $event['PROPERTY_PERIOD_COUNT'];
1374
1375 if (empty($fields['DT_LENGTH']) && !empty($event['PROPERTY_EVENT_LENGTH']))
1376 {
1377 $fields['DT_LENGTH'] = (int)$fields['PROPERTY_EVENT_LENGTH'];
1378 }
1379 else if (isset($event['DT_TO_TS'], $event['DT_FROM_TS']))
1380 {
1381 $fields['DT_LENGTH'] = $event['DT_TO_TS'] - $event['DT_FROM_TS'];
1382 }
1383 else
1384 {
1385 $fields['DT_LENGTH'] = null;
1386 }
1387
1388 if ($fields['RRULE']['FREQ'] === 'WEEKLY' && !empty($event['PROPERTY_PERIOD_ADDITIONAL']))
1389 {
1390 $fields['RRULE']['BYDAY'] = [];
1391 $days = explode(',', $event['PROPERTY_PERIOD_ADDITIONAL']);
1392 foreach ($days as $day)
1393 {
1394 $day = \CCalendar::WeekDayByInd($day, false);
1395 if ($day !== false)
1396 {
1397 $fields['RRULE']['BYDAY'][] = $day;
1398 }
1399 }
1400 $fields['RRULE']['BYDAY'] = implode(',', $fields['RRULE']['BYDAY']);
1401 }
1402
1403 if (!empty($event['PROPERTY_RRULE_COUNT']))
1404 {
1405 $fields['RRULE']['COUNT'] = $event['PROPERTY_RRULE_COUNT'];
1406 }
1407 else if (!empty($event['PROPERTY_PERIOD_UNTIL']))
1408 {
1409 $fields['RRULE']['UNTIL'] = $event['PROPERTY_PERIOD_UNTIL'];
1410 }
1411 else
1412 {
1413 $fields['RRULE']['UNTIL'] = $event['DT_TO_TS'] ?? null;
1414 }
1415
1416 if (!empty($event['EXDATE']))
1417 {
1418 $fields['EXDATE'] = $event['EXDATE'];
1419 }
1420 }
1421
1428 private function prepareDescription(array $event, array &$fields): void
1429 {
1430 if (isset($event['MEETING']) && !empty($event['MEETING']['LANGUAGE_ID']))
1431 {
1432 $languageId = $event['MEETING']['LANGUAGE_ID'];
1433 }
1434 else
1435 {
1436 $languageId = \CCalendar::getUserLanguageId((int)$fields['OWNER_ID']);
1437 }
1438
1439 $fields['DESCRIPTION'] = (new EventDescription())->prepareAfterImport($event['DETAIL_TEXT'], $languageId);
1440 }
1441
1448 private function mergeInstanceParams(array &$instance, array $localInstance): void
1449 {
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'];
1453
1454 if (!empty($localInstance['MEETING']))
1455 {
1456 $instance['MEETING'] = unserialize($localInstance['MEETING'], ['allowed_classes' => false]);
1457 }
1458 if (!empty($localInstance['ATTENDEES_CODES']))
1459 {
1460 $instance['ATTENDEES_CODES'] = explode(',', $localInstance['ATTENDEES_CODES']);
1461 }
1462 if (!empty($localInstance['IS_MEETING']))
1463 {
1464 $instance['IS_MEETING'] = (bool)$localInstance['IS_MEETING'];
1465 }
1466 if (!empty($localInstance['MEETING_HOST']))
1467 {
1468 $instance['MEETING_HOST'] = $localInstance['MEETING_HOST'];
1469 }
1470 if (!empty($localInstance['ACCESSIBILITY']))
1471 {
1472 $instance['ACCESSIBILITY'] = $localInstance['ACCESSIBILITY'];
1473 }
1474 }
1475
1482 private function addParentDataToInstance(array $instance, array $parentEvent): array
1483 {
1484 if (empty($instance['IS_MEETING']))
1485 {
1486 $instance['IS_MEETING'] = $parentEvent['IS_MEETING'];
1487 }
1488 if (empty($instance['MEETING_HOST']))
1489 {
1490 $instance['MEETING_HOST'] = $parentEvent['MEETING_HOST'];
1491 }
1492 if (empty($instance['MEETING']))
1493 {
1494 $instance['MEETING'] = $parentEvent['MEETING'];
1495 }
1496 if (empty($instance['ATTENDEES_CODES']))
1497 {
1498 $instance['ATTENDEES_CODES'] = $parentEvent['ATTENDEES_CODES'];
1499 }
1500
1501 $instance['VERSION'] = !empty($instance['EVENT_CONNECTION_VERSION'])
1502 ? max($parentEvent['VERSION'], $instance['EVENT_CONNECTION_VERSION'])
1503 : $parentEvent['VERSION']
1504 ;
1505
1506 $instance['RECURRENCE_ID'] = $parentEvent['ID'];
1507
1508 return $instance;
1509 }
1510
1518 private function prepareInstanceEvents(array $events): array
1519 {
1520 $instances = [];
1521 $eventDates = [];
1522 $eventsCount = count($events);
1523
1524 for ($i = $eventsCount - 1; $i >= 0; $i--)
1525 {
1526 $eventDate = \CCalendar::Date(\CCalendar::Timestamp($events[$i]['DATE_FROM']), false);
1527 if (!in_array($eventDate, $eventDates, true))
1528 {
1529 $instances[] = $events[$i];
1530 $eventDates[] = $eventDate;
1531 }
1532 }
1533
1534 return [$instances, $eventDates];
1535 }
1536
1543 private function prepareExistedEventParams(string $xmlId, ?array $link = null): array
1544 {
1545 return [
1546 'XML_ID' => $xmlId,
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])
1554 : null
1555 ,
1556 'IS_MEETING' => (bool)($link['IS_MEETING'] ?? null),
1557 'ATTENDEES_CODES' => ($link['ATTENDEES_CODES'] ?? null)
1558 ? explode(',', $link['ATTENDEES_CODES'])
1559 : null
1560 ,
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,
1566 ];
1567 }
1568
1576 private function mergeExternalEventWithLocal(
1577 array $existedEvent,
1578 array $event,
1579 \CDavGroupdavClientCalendar $client
1580 ): array
1581 {
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'],
1591 ]);
1592
1593 return [$event, $exDate];
1594 }
1595}
static addPullEvent(string $command, int $userId, array $params=[])
Definition util.php:373
static getRequestUid()
Definition util.php:520
static getMessage($code, $replace=null, $language=null)
Definition loc.php:29