Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
vendordataexchangemanager.php
1<?php
2
4
5use Bitrix\Calendar\Core;
12use Bitrix\Calendar\Sync\Util\HandleStatusTrait;
32use CCalendar;
33use CCalendarSect;
34use Exception;
35
37{
39
40 private static array $outgoingManagersCache = [];
41
49 protected bool $isFullSync = false;
50
51 private Sync\Factories\SyncEventFactory $syncEventFactory;
52 private Core\Mappers\EventConnection $eventConnectionMapper;
53 private Core\Mappers\Event $eventMapper;
54 private Core\Mappers\Section $sectionMapper;
55 private Core\Mappers\SectionConnection $sectionConnectionMapper;
58 protected array $importedLocalEventUidList = [];
59
63 public function __construct(Sync\Factories\FactoryBase $factory, Sync\Entities\SyncSectionMap $syncSectionMap)
64 {
65 $this->factory = $factory;
66 $this->syncSectionMap = $syncSectionMap;
67 $this->isFullSync = !$this->factory->getConnection()->getToken();
68
70 $mapperHelper = ServiceLocator::getInstance()->get('calendar.service.mappers.factory');
71 $this->sectionConnectionMapper = $mapperHelper->getSectionConnection();
72 $this->sectionMapper = $mapperHelper->getSection();
73 $this->eventConnectionMapper = $mapperHelper->getEventConnection();
74 $this->eventMapper = $mapperHelper->getEvent();
75 $this->syncEventFactory = new Sync\Factories\SyncEventFactory();
76
77
78 $handlerMergeClass = Core\Handlers\HandlersMap::getHandler('syncEventMergeHandler');
79 $this->handlerMerge = new $handlerMergeClass();
80 }
81
90 public function exchange(): self
91 {
92 $pushManager = new PushManager();
93 $push = $pushManager->getPush(PushManager::TYPE_CONNECTION, $this->factory->getConnection()->getId());
94 // TODO: what could to do, if push is blocked ?
95 $pushManager->setBlockPush($push);
96 try
97 {
98 $this->exchangeSections();
99
100 $this->sendResult(MasterPushHandler::MASTER_STAGE[1]);
101
102 $this->blockSectionPush($this->syncSectionMap);
103 try
104 {
105 $this->exchangeEvents();
106 }
107 catch(BaseException $e)
108 {}
109 finally
110 {
111 $this->unblockSectionPush($this->syncSectionMap);
112 }
113
114 $this->sendResult(MasterPushHandler::MASTER_STAGE[2]);
115 $this->sendResult(MasterPushHandler::MASTER_STAGE[3]);
116
117 $this
118 ->updateConnection($this->factory->getConnection())
119 ->clearCache() // Clear legacy cache after whole sync
120 ;
121 }
122 catch(BaseException $e)
123 {
124 }
125 finally
126 {
127 $pushManager->setUnblockPush($push);
128 }
129
130 return $this;
131 }
132
133 private function sendResult(string $stage): void
134 {
135 $this->sendStatus([
136 'vendorName' => $this->factory->getConnection()->getVendor()->getCode(),
137 'accountName' => $this->factory->getConnection()->getName(),
138 'stage' => $stage,
139 ]);
140 }
141
142 public function clearCache(): void
143 {
144 CCalendar::ClearCache();
145 }
146
158 public function importEvents(): self
159 {
160 $importEventManager = (new ImportEventManager($this->factory, $this->syncSectionMap));
161
162 $syncEventMap = $importEventManager->import()->getEvents();
163
164 $this->prepareLocalSyncEventMapWithVendorEventId($syncEventMap);
165 $this->handleEventsToLocalStorage($syncEventMap);
166 $this->handleSectionsToLocalStorage($this->syncSectionMap);
167
168 return $this;
169 }
170
180 public function importSections(): self
181 {
182 //sections
183 $sectionImporter = (new ImportSectionManager($this->factory))->import();
184
185 $this->handleImportedSections($sectionImporter->getSyncSectionMap());
186
187 return $this;
188 }
189
195 public function updateConnection(Connection $connection): self
196 {
197 $connection->setLastSyncTime(new Core\Base\Date());
198 (new Core\Mappers\Connection())->update($connection);
199
200 $accountType = $connection->getAccountType() === Sync\Google\Helper::GOOGLE_ACCOUNT_TYPE_API
201 ? 'google'
202 : $connection->getAccountType()
203 ;
204
205 Util::addPullEvent('refresh_sync_status', $connection->getOwner()->getId(), [
206 'syncInfo' => [
207 $accountType => [
208 'status' => $this->getSyncStatus($connection->getStatus()),
209 'type' => $accountType,
210 'connected' => true,
211 'id' => $connection->getId(),
212 'syncOffset' => 0,
213 ],
214 ],
215 'requestUid' => Util::getRequestUid(),
216 ]);
217
218 return $this;
219 }
220
221 private function getSyncStatus(?string $status): bool
222 {
223 return $status && preg_match("/^\[(2\d\d|0)\][a-z0-9 _]*/i", $status);
224 }
225
234 public function handleImportedSections(Sync\Entities\SyncSectionMap $externalSyncSectionMap): void
235 {
236 $this->mergeSyncedSyncSectionsWithSavedSections($externalSyncSectionMap);
237 $this->handleSectionsToLocalStorage($externalSyncSectionMap);
238 $this->removeDeletedExternalSections($externalSyncSectionMap);
239 }
240
251 public function handleSyncEvent(
252 Sync\Entities\SyncEvent $syncEvent,
253 ?string $key = null,
254 ?Sync\Entities\SyncEvent $masterSyncEvent = null
255 ): void
256 {
257 if ($syncEvent->getEventConnection() === null)
258 {
259 return;
260 }
261
262 if ($masterSyncEvent !== null)
263 {
264 $syncEvent->getEvent()->setRecurrenceId($masterSyncEvent->getId());
265 }
266
267 $this->mergeExternalEventWithLocalParams($syncEvent);
268
269 if ($syncEvent->getAction() === Sync\Dictionary::SYNC_EVENT_ACTION['delete'])
270 {
271 //if we drag out an old event that has not been saved in our system
272 if ($syncEvent->getEvent()->getId() === null)
273 {
274 if ($syncEvent->isInstance())
275 {
276 if ($masterSyncEvent === null)
277 {
278 $masterSyncEvent = $this->getMasterSyncEvent($syncEvent);
279 }
280 if (!$masterSyncEvent)
281 {
282 return;
283 }
284
285 if ($masterSyncEvent->getAction() === Sync\Dictionary::SYNC_EVENT_ACTION['delete'])
286 {
287 return;
288 }
289
290 if ($masterSyncEvent->getId() === null)
291 {
292 $this->handleSyncEvent($masterSyncEvent);
293
294 return;
295 }
296
297 $this->updateMasterExdate($this->addExdateToMasterEvent($masterSyncEvent, $syncEvent));
298 }
299
300 return;
301 }
302
303 $this->deleteEvent($syncEvent);
304
305 return;
306 }
307
308 $this->saveEvent($syncEvent);
309
310 if (
311 $masterSyncEvent
312 && $masterSyncEvent->getId()
313 && $syncEvent->isInstance()
314 )
315 {
316 $this->updateMasterExdate($this->addExdateToMasterEvent($masterSyncEvent, $syncEvent));
317 }
318 }
319
325 public function filterUnchangedSections(
326 Sync\Entities\SyncSectionMap $externalSyncSectionMap,
327 Sync\Entities\SyncSection $syncSection
328 ): bool
329 {
331 if (
332 ($externalSyncSection = $externalSyncSectionMap->getItem($syncSection->getSectionConnection()
333 ->getVendorSectionId()))
334 && $syncSection->getSectionConnection()->getVersionId() !== $externalSyncSection->getSectionConnection()
335 ->getVersionId()
336 )
337 {
338 $syncSection->getSectionConnection()->setVersionId($externalSyncSection->getSectionConnection()->getVersionId());
339
340 return true;
341 }
342
343 return false;
344 }
345
354 public function handleDeleteInstance(Sync\Entities\SyncEvent $syncEvent): void
355 {
356 $masterSyncEvent = $this->getMasterSyncEvent($syncEvent);
357 if (!$masterSyncEvent)
358 {
359 return;
360 }
361
362 $this->prepareExcludedDatesMasterEvent(
363 $masterSyncEvent,
364 $syncEvent->getEvent()->getOriginalDateFrom()
365 );
366
367 if ($masterSyncEvent->getId() === null)
368 {
369 return;
370 }
371
372 $this->updateMasterExdate($masterSyncEvent->getEvent());
373 $masterSyncEvent->setEvent($masterSyncEvent->getEvent());
374
375 $this->syncEventMap->updateItem($masterSyncEvent, $masterSyncEvent->getVendorEventId());
376 }
377
382 public function getMeetingDescriptionForNewEvent(Core\Role\Role $owner): Core\Event\Properties\MeetingDescription
383 {
384 return (new Core\Event\Properties\MeetingDescription())
385 ->setHostName($owner->getFullName())
386 ->setMeetingCreator($owner->getId())
387 ->setReInvite(false)
388 ->setLanguageId(($owner->getRoleEntity() instanceof Core\Role\User)
389 ? $owner->getRoleEntity()->getLanguageId()
390 : LANGUAGE_ID
391 )
392 ->setIsNotify(true)
393 ->setAllowInvite(true)
394 ;
395 }
396
405 public function deleteEvent(Sync\Entities\SyncEvent $syncEvent): Event
406 {
407 $event = $syncEvent->getEvent();
408
409 // todo handle meeting status
410 if ($event->isInstance())
411 {
412 $this->handleDeleteInstance($syncEvent);
413 }
414
415 if ($event->isRecurrence())
416 {
417 $this->handleDeleteRecurrenceEvent($syncEvent);
418 }
419
420 if ($event->isSingle())
421 {
422 $this->handleDeleteSingleEvent($syncEvent);
423 }
424
425 $this->eventMapper->delete($event, [
426 'softDelete' => true,
427 'originalFrom' => $syncEvent->getEventConnection()->getConnection()->getVendor()->getCode(),
428 ]);
429
430 $this->eventConnectionMapper->delete($syncEvent->getEventConnection());
431
432 return $event;
433 }
434
441 public function saveEvent(Sync\Entities\SyncEvent $syncEvent): void
442 {
443 $event = $syncEvent->getEvent();
444 $event = $event->isNew()
445 ? $this->eventMapper->create($event, [
446 'originalFrom' => $this->factory->getServiceName(),
447 ])
448 : $this->eventMapper->update($event, [
449 'originalFrom' => $this->factory->getServiceName(),
450 ])
451 ;
452
453 if ($event)
454 {
455 $syncEvent->setEvent($event);
456 $eventConnection = $syncEvent->getEventConnection();
457 if ($eventConnection)
458 {
459 $eventConnection
460 ->setEvent($event)
461 ->setVersion($event->getVersion())
462 ;
463
464 $eventConnection->getId()
465 ? $this->eventConnectionMapper->update($eventConnection)
466 : $this->eventConnectionMapper->create($eventConnection)
467 ;
468 }
469 }
470 }
471
482 public function handleImportedEvents(Sync\Entities\SyncEventMap $externalEventMap): void
483 {
484 $this->prepareLocalSyncEventMapWithVendorEventId($externalEventMap);
485 $this->handleEventsToLocalStorage($externalEventMap);
486 }
487
492 public function savePermissions(Sync\Entities\SyncSection $syncSection): void
493 {
494 CCalendarSect::SavePermissions(
495 $syncSection->getSection()->getId(),
496 CCalendarSect::GetDefaultAccess(
497 $syncSection->getSection()->getType(),
498 $syncSection->getSection()->getOwner()->getId()
499 )
500 );
501 }
502
510 private function exchangeSections(): void
511 {
512 //sections
513 $sectionImporter = (new ImportSectionManager($this->factory))->import();
514
515 $sectionExporter = (new OutgoingSectionManager(
516 $this->factory,
517 $this->prepareSyncSectionBeforeExport(
518 $this->getFilteredSectionMapForExport($sectionImporter->getSyncSectionMap())
519 )
520 ))->export();
521
522 $this->handleImportedSections($sectionImporter->getSyncSectionMap());
523 $this->handleExportedSections($sectionExporter->getSyncSectionMap());
524
525 $this->filterBrokenSyncSections();
526 }
527
534 private function handleExportedSections(Sync\Entities\SyncSectionMap $syncSectionMap): void
535 {
536 $this->handleSectionsToLocalStorage($syncSectionMap);
537 }
538
549 private function exchangeEvents(): void
550 {
551 $eventImporter = (new ImportEventManager($this->factory, $this->syncSectionMap))->import();
552 $this->handleImportedEvents($eventImporter->getEvents());
553
554 $savedSyncEventMap = $this->getLocalEventsForExport();
555
556 if ($savedSyncEventMap)
557 {
558 (new ExportEventManager($this->factory, $this->syncSectionMap))->export($savedSyncEventMap);
559 $this->updateExportedEvents($savedSyncEventMap);
560 }
561
562 $this->handleSectionsToLocalStorage($this->syncSectionMap);
563 }
564
571 private function handleExportedInstances(Sync\Entities\SyncEvent $syncEvent): void
572 {
574 foreach ($syncEvent->getInstanceMap() as $instance)
575 {
576 if ($instance->getAction() !== Sync\Dictionary::SYNC_EVENT_ACTION['success'])
577 {
578 $this->handleExportedFailedSyncEvent($syncEvent);
579 continue;
580 }
581
582 if ($instanceEventConnection = $instance->getEventConnection())
583 {
584 $instanceEventConnection
585 ->setEvent($instance->getEvent())
586 ->setConnection($syncEvent->getEventConnection()->getConnection())
587 ;
588
589 if ($instanceEventConnection->getId())
590 {
591 $this->eventConnectionMapper->update($instanceEventConnection);
592 }
593 else
594 {
595 $this->eventConnectionMapper->create($instanceEventConnection);
596 }
597 }
598 }
599 }
600
611 public function validateSyncEventChange(
612 Sync\Entities\SyncEvent $syncEvent,
613 Sync\Entities\SyncEvent $existsSyncEvent = null
614 ): bool
615 {
616 if (!$existsSyncEvent)
617 {
618 return true;
619 }
620
621 if (
622 $syncEvent->getEventConnection() !== null
623 && (
624 ($syncEvent->getEventConnection()->getVendorVersionId() === $existsSyncEvent->getEventConnection()->getVendorVersionId())
625 || ($syncEvent->getEventConnection()->getEntityTag() === $existsSyncEvent->getEventConnection()->getEntityTag())
626 )
627 )
628 {
629 if (
630 (
631 $syncEvent->getEventConnection()->getConnection()->getVendor()->getCode()
632 !== Sync\Google\Helper::GOOGLE_ACCOUNT_TYPE_API
633 )
634 || !$this->hasDifferentEventFields($syncEvent, $existsSyncEvent))
635 {
636 return false;
637 }
638 }
639
640 // Changing an event for an invitee
641 // TODO: move it check to core.
642 if ($existsSyncEvent->getEventId() !== $existsSyncEvent->getParentId())
643 {
644 // temporary this functionality is turned off
645 // if (!$existsSyncEvent->getEvent()->isDeleted() && $this->hasDifferentEventFields($syncEvent, $existsSyncEvent))
646 // {
647 // $this->rollbackEvent(
648 // $existsSyncEvent,
649 // $syncEvent,
650 // 'CALENDAR_IMPORT_BLOCK_FROM_ATTENDEE'
651 // );
652 // }
653
654 return false;
655 }
656
657 // Prevent changing events with booking
658 if ($existsSyncEvent->getEvent()->getSpecialLabel() === ResourceBooking::EVENT_LABEL)
659 {
660 // temporary this functionality is turned off
661 // if ($this->hasDifferentEventFields($syncEvent, $existsSyncEvent))
662 // {
663 //
664 // $this->rollbackEvent(
665 // $existsSyncEvent,
666 // $syncEvent,
667 // 'CALENDAR_IMPORT_BLOCK_RESOURCE_BOOKING'
668 // );
669 // }
670
671 return false;
672 }
673
674 // temporary this functionality is turned off
675
676 // if ($syncEvent->getAction() !== Sync\Dictionary::SYNC_EVENT_ACTION['delete']
677 // && !$this->checkAttendeesAccessibility($existsSyncEvent->getEvent(), $syncEvent->getEvent()))
678 // {
679 // if ($this->hasDifferentEventFields($syncEvent, $existsSyncEvent))
680 // {
681 // $this->rollbackEvent(
682 // $existsSyncEvent,
683 // $syncEvent,
684 // 'CALENDAR_IMPORT_BLOCK_ATTENDEE_ACCESSIBILITY'
685 // );
686 // }
687 //
688 // return false;
689 // }
690
691 if ($syncEvent->isSingle() && $syncEvent->getEntityTag() !== $existsSyncEvent->getEntityTag())
692 {
693 return true;
694 }
695
696 if ($syncEvent->isRecurrence())
697 {
698 if ($syncEvent->getEntityTag() !== $existsSyncEvent->getEntityTag())
699 {
700 return true;
701 }
702
703 if ($syncEvent->hasInstances())
704 {
706 foreach ($syncEvent->getInstanceMap() as $key => $instance)
707 {
708 $existsInstanceMap = $existsSyncEvent->getInstanceMap();
709
710 if (!$existsInstanceMap)
711 {
712 return true;
713 }
714
716 $existsInstance = $existsInstanceMap->getItem($key);
717
718 if (!$existsInstance)
719 {
720 return true;
721 }
722
723 if ($existsInstance->getEntityTag() !== $instance->getEntityTag())
724 {
725 return true;
726 }
727 }
728 }
729 }
730
731 if ($syncEvent->isInstance())
732 {
733 return true;
734 }
735
736 return false;
737 }
738
739 private function hasDifferentEventFields(
740 Sync\Entities\SyncEvent $syncEvent,
741 Sync\Entities\SyncEvent $existSyncEvent
742 ): bool
743 {
744 if (!$syncEvent->getEvent() && !$existSyncEvent->getEvent())
745 {
746 return false;
747 }
748
749 if ($syncEvent->getAction() === Sync\Dictionary::SYNC_EVENT_ACTION['delete']
750 && !$existSyncEvent->getEvent()->isDeleted()
751 )
752 {
753 return true;
754 }
755
756 $comparator = new EventCompareManager($syncEvent->getEvent(), $existSyncEvent->getEvent());
757
758 $diff = $comparator->getDiff();
759 $significantFields = [
763 EventCompareManager::COMPARE_FIELDS['recurringRule'] => true,
764 EventCompareManager::COMPARE_FIELDS['description'] => true,
765 'excludedDates' => true,
766 ];
767 $significantDiff = array_intersect_key($diff, $significantFields);
768
769 return !empty($significantDiff);
770 }
771
777 public function addExdateToMasterEvent(Sync\Entities\SyncEvent $masterSyncEvent, Sync\Entities\SyncEvent $instance): Event
778 {
779 $masterEvent = $masterSyncEvent->getEvent();
780 $exdateCollection = $masterEvent->getExcludedDateCollection();
781 if ($exdateCollection === null)
782 {
783 $exdateCollection = new Core\Event\Properties\ExcludedDatesCollection();
784 $masterEvent->setExcludedDateCollection($exdateCollection);
785 }
786
787 if ($instance->getEvent()->getOriginalDateFrom() instanceof Core\Base\Date)
788 {
789 $exdateCollection->add(
790 (clone $instance->getEvent()->getOriginalDateFrom())
791 ->setDateTimeFormat(Core\Event\Properties\ExcludedDatesCollection::EXCLUDED_DATE_FORMAT)
792 );
793 }
794
795 return $masterEvent;
796 }
797
808 private function rollbackEvent(
809 Sync\Entities\SyncEvent $existsExternalSyncEvent,
810 Sync\Entities\SyncEvent $syncEvent,
811 string $messageCode
812 ): void
813 {
814 $muteNotice = $this->isNoticesMuted();
815 if ($existsExternalSyncEvent->getEvent()->isDeleted())
816 {
817 $muteNotice = true;
818 $syncStatus = Sync\Dictionary::SYNC_EVENT_ACTION['delete'];
819 }
820 else
821 {
822 $syncStatus = ($existsExternalSyncEvent->getEvent()->isRecurrence() || $syncEvent->getEvent()->isRecurrence())
823 ? Sync\Dictionary::SYNC_EVENT_ACTION['recreate']
824 : Sync\Dictionary::SYNC_EVENT_ACTION['update']
825 ;
826 }
827 $existsExternalSyncEvent->getEventConnection()
828 ->setLastSyncStatus($syncStatus)
829 ->setVersion($existsExternalSyncEvent->getEvent()->getVersion() - 1);
830 $this->eventConnectionMapper->update($existsExternalSyncEvent->getEventConnection());
831
832 if (!$muteNotice)
833 {
834 $this->noticeUser($existsExternalSyncEvent, $messageCode);
835 }
836 }
837
841 private function isNoticesMuted(): bool
842 {
843 return FlagRegistry::getInstance()->isFlag(Sync\Dictionary::FIRST_SYNC_FLAG_NAME);
844 }
845
849 private function createPusher(): Sync\Handlers\MasterPushHandler
850 {
851 return new Sync\Handlers\MasterPushHandler(
852 $this->factory->getConnection()->getOwner(),
853 'google',
854 $this->factory->getConnection()->getName()
855 );
856 }
857
862 private function mergeSyncedSyncSectionsWithSavedSections(Sync\Entities\SyncSectionMap $externalSyncSectionMap): void
863 {
865 foreach ($externalSyncSectionMap as $key => $externalSyncSection)
866 {
867 if ($externalSyncSection->getSectionConnection() === null)
868 {
869 $externalSyncSectionMap->remove($key);
870
871 continue;
872 }
873
875 if ($savedSyncSection = $this->syncSectionMap->has(
876 $externalSyncSection->getSectionConnection()->getVendorSectionId()
877 ))
878 {
879 $savedSyncSection = $this->syncSectionMap->getItem(
880 $externalSyncSection->getSectionConnection()->getVendorSectionId()
881 );
882 $externalSyncSection
883 ->getSectionConnection()
884 ->setId($savedSyncSection->getSectionConnection()->getId())
885 ->setSyncToken($savedSyncSection->getSectionConnection()->getSyncToken())
886 ->setPageToken($savedSyncSection->getSectionConnection()->getPageToken())
887 ;
888
889 $savedSection = $savedSyncSection->getSection();
890 $externalSyncSection
891 ->getSection()
892 ->setId($savedSection->getId())
893 ->setXmlId($savedSection->getXmlId())
894 ->setCreator($savedSection->getCreator())
895 ->setExternalType($savedSection->getExternalType())
896 ->setIsActive($savedSection->isActive())
897 ->setType($savedSection->getType())
898 ;
899 if (empty($externalSyncSection->getSection()->getColor()))
900 {
901 $externalSyncSection->getSection()->setColor(
902 $savedSection->getColor()
903 ?: Core\Property\ColorHelper::getOurColorRandom()
904 );
905 }
906 if ($savedSection->isLocal())
907 {
908 $externalSyncSection->getSection()->setName($savedSection->getName());
909 }
910 }
911 }
912 }
913
918 private function handleSectionsToLocalStorage(Sync\Entities\SyncSectionMap $syncSectionMap): void
919 {
921 foreach ($syncSectionMap as $key => $syncSection)
922 {
923 if (!$this->validateSyncSectionBeforeSave($syncSection))
924 {
925 $this->updateFailedSyncSection($syncSection);
926
927 continue;
928 }
929
930 if ($syncSection->getAction() === Sync\Dictionary::SYNC_STATUS['delete'])
931 {
932 if ($syncSection->getSection()->isLocal())
933 {
934 $this->createLocalDeletedSection($syncSection);
935 }
936 else
937 {
938 $this->deleteSyncSectionFromLocalStorage($syncSection);
939 continue;
940 }
941 }
942
943 if ($syncSection->getSection()->isNew())
944 {
946 // TODO: change later to saveManager
947 $this->sectionMapper->create($syncSection->getSection());
948 $this->savePermissions($syncSection);
949
950 $this->sectionConnectionMapper->create(
951 $syncSection->getSectionConnection()->setSection($syncSection->getSection())
952 );
953 }
954 else
955 {
956 $this->sectionMapper->update($syncSection->getSection());
957 $sectionConnection = $syncSection->getSectionConnection();
958 $sectionConnection->setSection($syncSection->getSection());
959
960 $sectionConnection->isNew()
961 ? $this->sectionConnectionMapper->create($sectionConnection)
962 : $this->sectionConnectionMapper->update($sectionConnection)
963 ;
964 }
965
966 $syncSection->setAction(Sync\Dictionary::SYNC_SECTION_ACTION['success']);
967
968 $this->syncSectionMap->add($syncSection, $key);
969 }
970 }
971
976 private function removeDeletedExternalSections(Sync\Entities\SyncSectionMap $externalSectionMap): void
977 {
978 if (!$this->isFullSync)
979 {
980 return;
981 }
982
983 $deleteCandidates = (new Sync\Entities\SyncSectionMap(
984 array_diff_key($this->syncSectionMap->getCollection(), $externalSectionMap->getCollection()),
985 ))->getNonLocalSections();
986
988 foreach ($deleteCandidates as $syncSection)
989 {
990 if (
991 ($syncSection->getSection() === null)
992 || ($syncSection->getSectionConnection() === null)
993 )
994 {
995 continue;
996 }
997 $this->deleteSyncSectionFromLocalStorage($syncSection);
998 }
999 }
1000
1001 private function createLocalDeletedSection(Sync\Entities\SyncSection $syncSection)
1002 {
1003
1004 }
1005
1010 private function deleteSyncSectionFromLocalStorage(Sync\Entities\SyncSection $syncSection): void
1011 {
1012 if ($syncSection->getSectionConnection()->getId() !== null)
1013 {
1014 $this->sectionConnectionMapper->delete($syncSection->getSectionConnection());
1015 $this->syncSectionMap->remove($syncSection->getSectionConnection()->getVendorSectionId());
1016 }
1017
1018 if ($syncSection->getSection()->getId() !== null)
1019 {
1020 $this->sectionMapper->delete($syncSection->getSection(), ['softDelete' => false]);
1021 $this->syncSectionMap->remove($syncSection->getSection()->getId());
1022 }
1023 }
1024
1029 private function getFilteredSectionMapForExport(
1030 Sync\Entities\SyncSectionMap $externalSectionMap
1031 ): Sync\Entities\SyncSectionMap
1032 {
1033 return (new Sync\Entities\SyncSectionMap(
1034 array_diff_key(
1035 $this->isFullSync
1036 ? $this->syncSectionMap->getCollection()
1037 : $this->syncSectionMap
1038 ->getItemsByKeys(array_keys($externalSectionMap->getCollection()))
1039 ->getCollection(),
1040 $externalSectionMap->getCollection()
1041 ),
1042 ))->getLocalSections();
1043 }
1044
1050 Sync\Entities\SyncSectionMap $syncSectionMap
1051 ): Sync\Entities\SyncSectionMap
1052 {
1053 $syncSectionList = $this->isFullSync
1054 ? $this->syncSectionMap->getCollection()
1055 : $this->syncSectionMap
1056 ->getItemsByKeys(array_keys($syncSectionMap->getCollection()))
1057 ->getCollection();
1058
1060 array_diff_key($syncSectionList, $syncSectionMap->getCollection()),
1061 );
1062 }
1063
1070 private function getLocalEventsForExport(): ?Sync\Entities\SyncEventMap
1071 {
1072 $sectionIdCollection = [];
1074 foreach ($this->syncSectionMap as $syncSection)
1075 {
1076 $sectionIdCollection[] = $syncSection->getSection()->getId();
1077 }
1078
1079 // foreach ($this->importedLocalEventUidList as $eventUid)
1080 // {
1081 // $candidatesForExport->remove($eventUid);
1082 // }
1083
1084 return $this->syncEventFactory->getSyncEventMapBySyncSectionIdCollectionForExport(
1085 $sectionIdCollection,
1086 $this->factory->getConnection()->getOwner()->getId(),
1087 $this->factory->getConnection()->getId()
1089 }
1090
1099 private function handleEventsToLocalStorage(Sync\Entities\SyncEventMap $externalSyncEventMap): void
1100 {
1102 foreach ($externalSyncEventMap as $key => $syncEvent)
1103 {
1105 $existsExternalSyncEvent = $this->syncEventMap->getItem($key);
1106
1107 if (!$this->validateSyncEventChange($syncEvent, $existsExternalSyncEvent))
1108 {
1109 continue;
1110 }
1111
1112 $masterSyncEvent = $this->getMasterSyncEvent($syncEvent);
1113 if (
1114 $masterSyncEvent
1115 && ($syncEvent->isInstance() || $syncEvent->getVendorRecurrenceId())
1116 && $masterSyncEvent->getId() !== $masterSyncEvent->getParentId()
1117 )
1118 {
1119 continue;
1120 }
1121
1122 $this->handleSyncEvent($syncEvent, $syncEvent->getVendorEventId(), $masterSyncEvent);
1123
1124 if (
1125 $existsExternalSyncEvent
1126 && (
1127 $syncEvent->isRecurrence()
1128 || ($syncEvent->getAction() === Sync\Dictionary::SYNC_EVENT_ACTION['delete'])
1129 )
1130 )
1131 {
1132 $this->removeDeprecatedInstances($existsExternalSyncEvent, $syncEvent);
1133 }
1134
1135 if ($syncEvent->hasInstances())
1136 {
1137 $collection = $syncEvent->getInstanceMap()->getCollection();
1138 array_walk($collection, [$this, 'handleSyncEvent'], $syncEvent);
1139 }
1140
1141 $this->syncEventMap->updateItem($syncEvent, $syncEvent->getVendorEventId());
1142 }
1143 }
1144
1153 private function getMasterSyncEvent(Sync\Entities\SyncEvent $syncEvent): ?Sync\Entities\SyncEvent
1154 {
1155 $recurrenceId = [];
1156 $eventConnection = $syncEvent->getEventConnection();
1157
1158 if ($eventConnection === null)
1159 {
1160 throw new BaseException('you should set EventConnection in SyncEvent');
1161 }
1162
1163 if ($masterSyncEvent = $this->syncEventMap->getItem($eventConnection->getRecurrenceId()))
1164 {
1165 return $masterSyncEvent;
1166 }
1167
1168 if ($eventConnection->getRecurrenceId())
1169 {
1170 $recurrenceId[] = $eventConnection->getRecurrenceId();
1171 }
1172
1173 return $this->syncEventFactory->getSyncEventCollectionByVendorIdList(
1174 $recurrenceId,
1175 $this->factory->getConnection()->getId()
1176 )->fetch();
1177 }
1178
1184 private function prepareExcludedDatesMasterEvent(Sync\Entities\SyncEvent $masterSyncEvent, Core\Base\Date $excludedDate): void
1185 {
1186 $masterEvent = $masterSyncEvent->getEvent();
1187
1188 $date = clone $excludedDate;
1189 $date->format(Core\Event\Properties\ExcludedDatesCollection::EXCLUDED_DATE_FORMAT);
1190
1191 if ($masterEvent->getExcludedDateCollection())
1192 {
1193 $masterEvent->getExcludedDateCollection()->add($date);
1194 }
1195 else
1196 {
1197 $masterEvent->setExcludedDateCollection(new Core\Event\Properties\ExcludedDatesCollection([$date]));
1198 }
1199 }
1200
1209 private function prepareNewEvent(Event $event, Sync\Entities\SyncSection $syncSection): void
1210 {
1211 $section = $syncSection->getSection();
1212 $owner = $section->getOwner();
1213 if ($owner === null)
1214 {
1215 throw new BaseException('section has not owner');
1216 }
1217
1218 $event
1219 ->setSection($syncSection->getSection())
1220 ->setOwner($owner)
1221 ->setCreator($owner)
1222 ->setEventHost($owner)
1223 ->setMeetingDescription($this->getMeetingDescriptionForNewEvent($owner))
1224 ->setIsActive(true)
1225 ->setIsDeleted(false)
1226 ->setCalendarType($owner->getType())
1227 ;
1228 }
1229
1241 public function handleMerge(
1242 Sync\Entities\SyncEventMap $localEventCollection,
1243 string $vendorId,
1244 SyncEventMergeHandler $handlerMerge,
1245 Sync\Entities\SyncEvent $syncEvent,
1246 ?Sync\Connection\EventConnection $eventConnection,
1247 Core\Event\Event $event,
1248 Sync\Entities\SyncSection $syncSection
1249 ): Core\Event\Event
1250 {
1251 $mergedSyncEvent = null;
1252 if ($localEventCollection->has($vendorId))
1253 {
1254 $mergedSyncEvent = $handlerMerge(
1255 $localEventCollection->getItem($vendorId),
1256 $syncEvent,
1257 $localEventCollection->getItem($vendorId)->getEvent()->getId()
1258 );
1259 $eventConnection->setId($localEventCollection->getItem($vendorId)->getEventConnection()->getId());
1260 }
1261 elseif (
1262 $syncEvent->getEventConnection()
1263 && $localEventCollection->has($syncEvent->getEventConnection()
1264 ->getRecurrenceId())
1265 )
1266 {
1268 $masterSyncEvent = $localEventCollection->getItem($syncEvent->getEventConnection()->getRecurrenceId());
1269 // merge with master event
1270 $mergedSyncEvent = $handlerMerge(
1271 $masterSyncEvent,
1272 $syncEvent
1273 );
1274 $mergedSyncEvent->getEvent()->setRecurrenceId($masterSyncEvent->getEvent()->getId())
1275 ;
1276 }
1277 if ($mergedSyncEvent !== null)
1278 {
1279 $event = $mergedSyncEvent->getEvent();
1280 }
1281 else
1282 {
1283 $this->prepareNewEvent($event, $syncSection);
1284 }
1285 return $event;
1286 }
1287
1293 private function mergeExternalEventWithLocalParams(Sync\Entities\SyncEvent $syncEvent): void
1294 {
1296 if ($existsSyncEvent = $this->syncEventMap->getItem($syncEvent->getEventConnection()->getVendorEventId()))
1297 {
1298 $this->mergeSyncEvent(
1299 $existsSyncEvent,
1300 $syncEvent,
1301 $existsSyncEvent->getEventId(),
1302 $existsSyncEvent->getEventConnection()->getId()
1303 );
1304
1305 if (
1306 $syncEvent->isRecurrence()
1307 && $syncEvent->hasInstances()
1308 )
1309 {
1310 foreach ($syncEvent->getInstanceMap() as $instanceSyncEvent)
1311 {
1312 if (
1313 $existsSyncEvent->hasInstances()
1314 && $instanceSyncEvent->getEvent()->getOriginalDateFrom()
1315 && $existsInstanceSyncEvent = $existsSyncEvent->getInstanceMap()->getItem(
1316 Sync\Entities\InstanceMap::getKeyByDate(
1317 $instanceSyncEvent->getEvent()->getOriginalDateFrom()
1318 )
1319 )
1320 )
1321 {
1322 $this->mergeSyncEvent(
1323 $existsInstanceSyncEvent,
1324 $instanceSyncEvent,
1325 $existsInstanceSyncEvent->getEventId(),
1326 $existsInstanceSyncEvent->getEventConnection()->getId()
1327 );
1328 }
1329 else
1330 {
1331 $this->prepareNewSyncEvent($syncEvent);
1332 }
1333 }
1334 }
1335
1336 return;
1337 }
1338
1340 if ($existsMasterSyncEvent = $this->syncEventMap->getItem($syncEvent->getEventConnection()->getRecurrenceId()))
1341 {
1342 if (
1343 $existsMasterSyncEvent->hasInstances()
1344 && $syncEvent->getEvent()->getOriginalDateFrom()
1345 && $existsInstanceSyncEvent = $existsMasterSyncEvent->getInstanceMap()->getItem(
1346 Sync\Entities\InstanceMap::getKeyByDate(
1347 $syncEvent->getEvent()->getOriginalDateFrom()
1348 )
1349 )
1350 )
1351 {
1352 $this->mergeSyncEvent(
1353 $existsInstanceSyncEvent,
1354 $syncEvent,
1355 $existsInstanceSyncEvent->getEventId(),
1356 $existsInstanceSyncEvent->getEventConnection()->getId()
1357 );
1358
1359 return;
1360 }
1361
1362 $this->mergeSyncEvent($existsMasterSyncEvent, $syncEvent);
1363
1364 return;
1365 }
1366
1367 if ($syncEvent->getAction() !== Sync\Dictionary::SYNC_EVENT_ACTION['delete'])
1368 {
1369 $this->prepareNewSyncEvent($syncEvent);
1370 }
1371 }
1372
1381 private function mergeSyncEvent(
1382 Sync\Entities\SyncEvent $existsSyncEvent,
1383 Sync\Entities\SyncEvent $externalSyncEvent,
1384 int $id = null,
1385 int $eventConnectionId = null
1386 ): void
1387 {
1388 $handler = new SyncEventMergeHandler();
1389 $handler($existsSyncEvent, $externalSyncEvent, $id, $eventConnectionId);
1390 }
1391
1396 private function prepareLocalSyncEventMapWithVendorEventId(Sync\Entities\SyncEventMap $externalSyncEventMap): void
1397 {
1398 $vendorEventIdList = [];
1400 foreach ($externalSyncEventMap->getCollection() as $item)
1401 {
1402 $vendorEventIdList[] = $item->getVendorEventId();
1403 if ($item->isInstance())
1404 {
1405 $vendorEventIdList[] = $item->getVendorRecurrenceId();
1406 }
1407 $this->importedLocalEventUidList[] = $item->getUid();
1408 }
1409
1410 $this->syncEventMap = $this->syncEventFactory->getSyncEventCollectionByVendorIdList(
1411 array_unique($vendorEventIdList),
1412 $this->factory->getConnection()->getId()
1413 );
1414 }
1415
1420 private function prepareNewSyncEvent(Sync\Entities\SyncEvent $syncEvent): void
1421 {
1422
1423 }
1424
1429 private function updateExportedEvents(Sync\Entities\SyncEventMap $localEventMap): void
1430 {
1432 foreach ($localEventMap as $syncEvent)
1433 {
1434 if ($syncEvent->getAction() !== Sync\Dictionary::SYNC_EVENT_ACTION['success'])
1435 {
1436 $this->handleExportedFailedSyncEvent($syncEvent);
1437 continue;
1438 }
1439
1440 if ($eventConnection = $syncEvent->getEventConnection())
1441 {
1442 if ($eventConnection->getId())
1443 {
1444 $this->eventConnectionMapper->update($eventConnection);
1445 }
1446 else
1447 {
1448 $this->eventConnectionMapper->create($eventConnection);
1449 }
1450
1451 if ($syncEvent->hasInstances())
1452 {
1453 $this->handleExportedInstances($syncEvent);
1454 }
1455 }
1456 }
1457 }
1458
1463 private function updateMasterExdate(Event $masterEvent): void
1464 {
1465 $handler = new UpdateMasterExdateHandler();
1466 $handler($masterEvent);
1467 }
1468
1473 private function validateSyncSectionBeforeSave(Sync\Entities\SyncSection $syncSection): bool
1474 {
1475 return $syncSection->getSection() && $syncSection->getSectionConnection();
1476 }
1477
1482 private function updateFailedSyncSection(Sync\Entities\SyncSection $syncSection): void
1483 {
1484
1485 }
1486
1490 private function filterBrokenSyncSections(): void
1491 {
1496 foreach ($this->syncSectionMap as $key => $item)
1497 {
1498 if ($item->getSectionConnection() === null)
1499 {
1500 $this->syncSectionMap->remove($key);
1501 }
1502 }
1503 }
1504
1508 private function handleDeleteRecurrenceEvent(SyncEvent $syncEvent)
1509 {
1510
1511 }
1512
1516 private function handleDeleteSingleEvent(SyncEvent $syncEvent)
1517 {
1518
1519 }
1520
1528 private function handleExportedFailedSyncEvent(Sync\Entities\SyncEvent $syncEvent): void
1529 {
1530 if (
1531 ($syncEvent->getEventConnection() === null)
1532 || ($syncEvent->getEventConnection()->getId() === null)
1533 )
1534 {
1535 return;
1536 }
1537
1538 $eventConnection = $syncEvent->getEventConnection();
1539
1540 switch ($syncEvent->getAction())
1541 {
1542 case Sync\Dictionary::SYNC_EVENT_ACTION['delete']:
1543 $eventConnection->setLastSyncStatus(Sync\Dictionary::SYNC_STATUS['delete']);
1544 break;
1545 default:
1546 $eventConnection->setLastSyncStatus(Sync\Dictionary::SYNC_STATUS['update']);
1547 break;
1548 }
1549
1550 $this->eventConnectionMapper->update($eventConnection);
1551 }
1552
1563 private function blockSectionPush(Sync\Entities\SyncSectionMap $syncSectionMap): void
1564 {
1565 $pushManager = new PushManager();
1567 foreach ($syncSectionMap as $syncSection)
1568 {
1569 $pushManager->setBlockPush(
1570 $pushManager->getPush(PushManager::TYPE_SECTION, $syncSection->getSection()->getId())
1571 );
1572
1573 if (
1574 ($syncSection->getSectionConnection() !== null)
1575 && ($syncSection->getSectionConnection()->getId() !== null)
1576 )
1577 {
1578 $pushManager->setBlockPush(
1579 $pushManager->getPush(
1581 $syncSection->getSectionConnection()->getId()
1582 )
1583 );
1584 }
1585 }
1586 }
1587
1600 private function unblockSectionPush(Sync\Entities\SyncSectionMap $syncSectionMap): void
1601 {
1602 $pushManager = new PushManager();
1604 foreach ($syncSectionMap as $syncSection)
1605 {
1606 $pushManager->setUnblockPush(
1607 $pushManager->getPush(PushManager::TYPE_SECTION, $syncSection->getSection()->getId())
1608 );
1609
1610 if ($syncSection->getSectionConnection() !== null
1611 && $syncSection->getSectionConnection()->getId() !== null
1612 )
1613 {
1614 $pushManager->setUnblockPush(
1615 $pushManager->getPush(
1617 $syncSection->getSectionConnection()->getId()
1618 )
1619 );
1620 }
1621 }
1622 }
1623
1630 private function removeDeprecatedInstances(
1631 Sync\Entities\SyncEvent $existsExternalSyncEvent,
1632 Sync\Entities\SyncEvent $syncEvent
1633 ): void
1634 {
1635 if ($existsExternalSyncEvent->hasInstances())
1636 {
1638 foreach ($existsExternalSyncEvent->getInstanceMap() as $key => $oldInstance)
1639 {
1640 if (!$syncEvent->hasInstances() || empty($syncEvent->getInstanceMap()->getItem($key)))
1641 {
1642 $this->eventConnectionMapper->delete($oldInstance->getEventConnection(), ['softDelete' => false]);
1643 $this->eventMapper->delete($oldInstance->getEvent(), [
1644 'softDelete' => false,
1645 'originalFrom' => $syncEvent->getEventConnection()?->getConnection()->getVendor()->getCode(),
1646 'recursionMode' => 'this',
1647 ]);
1648 }
1649 }
1650 }
1651 }
1652
1661 private function prepareSyncSectionBeforeExport(Sync\Entities\SyncSectionMap $syncSectionMap): Sync\Entities\SyncSectionMap
1662 {
1663 $syncSectionKeyIdList = [];
1665 foreach ($syncSectionMap as $key => $syncSection)
1666 {
1667 if ($syncSection->getSectionConnection() === null)
1668 {
1669 continue;
1670 }
1671
1672 if (in_array($syncSection->getSection()->getId(), $syncSectionKeyIdList, true))
1673 {
1674 $syncSectionMap->remove($key);
1675 }
1676 else
1677 {
1678 $syncSectionKeyIdList[$syncSection->getSectionConnection()->getVendorSectionId()] = $syncSection->getSection()->getId();
1679 }
1680
1681 $this->sectionConnectionMapper->delete($syncSection->getSectionConnection(), ['softDelete' => false]);
1682 $this->syncSectionMap->remove($key);
1683 $syncSection->setSectionConnection(null);
1684 }
1685
1686 // todo optimize this process after update mappers
1687 if ($syncSectionKeyIdList)
1688 {
1689 $syncEventMap = $this->syncEventFactory->getSyncEventMapBySyncSectionIdCollectionForExport(
1690 $syncSectionKeyIdList,
1691 $this->factory->getConnection()->getOwner()->getId(),
1692 $this->factory->getConnection()->getId()
1693 );
1694 if ($syncEventMap->count())
1695 {
1697 foreach ($syncEventMap as $syncEvent)
1698 {
1699 if ($syncEvent->getEventConnection() === null)
1700 {
1701 continue;
1702 }
1703
1704 $this->eventConnectionMapper->delete($syncEvent->getEventConnection(), ['softDelete' => false]);
1705 }
1706 }
1707 }
1708
1709 return $syncSectionMap;
1710 }
1711
1720 private function noticeUser(Sync\Entities\SyncEvent $syncEvent, string $messageCode = '')
1721 {
1722 if (Loader::includeModule('im') && Loader::includeModule('pull'))
1723 {
1724 $path = CCalendar::GetPath(
1725 $syncEvent->getEvent()->getOwner()->getType(),
1726 $syncEvent->getEvent()->getOwner()->getId(),
1727 true);
1728 $uri = (new Uri($path))
1729 ->deleteParams(["action", "sessid", "bx_event_calendar_request", "EVENT_ID", "EVENT_DATE"])
1730 ->addParams([
1731 'EVENT_ID' => $syncEvent->getEvent()->getId()])
1732 ;
1733
1735 $syncEvent->getEventConnection()->getConnection()->getOwner()->getId(),
1736 $messageCode,
1737 [
1738 '#EVENT_URL#' => $uri->getUri(),
1739 '#EVENT_TITLE#' => $syncEvent->getEvent()->getName(),
1740 'EVENT_ID' => $syncEvent->getEvent()->getId(),
1741 ]
1742 );
1743 }
1744 }
1745
1756 public function renewSubscription(Connection $connection)
1757 {
1758 $mapperFactory = ServiceLocator::getInstance()->get('calendar.service.mappers.factory');
1759 $links = $mapperFactory->getSectionConnection()->getMap([
1760 '=CONNECTION_ID' => $connection->getId(),
1761 '=ACTIVE' => 'Y'
1762 ]);
1763
1764 $manager = $this->getOutgoingManager($connection);
1765 foreach ($links as $link)
1766 {
1767 $manager->subscribeSection($link);
1768 }
1769
1770 $manager->subscribeConnection();
1771 }
1772
1780 private function getOutgoingManager(Connection $connection)
1781 {
1782 if (empty(static::$outgoingManagersCache[$connection->getId()]))
1783 {
1784 static::$outgoingManagersCache[$connection->getId()] = new OutgoingManager($connection);
1785 }
1786
1787 return static::$outgoingManagersCache[$connection->getId()];
1788 }
1789}
static sendBlockChangeNotification(int $userId, $messageCode, $vars)
getSyncSectionMapBySyncSectionMap(Sync\Entities\SyncSectionMap $syncSectionMap)
handleSyncEvent(Sync\Entities\SyncEvent $syncEvent, ?string $key=null, ?Sync\Entities\SyncEvent $masterSyncEvent=null)
addExdateToMasterEvent(Sync\Entities\SyncEvent $masterSyncEvent, Sync\Entities\SyncEvent $instance)
handleImportedSections(Sync\Entities\SyncSectionMap $externalSyncSectionMap)
handleImportedEvents(Sync\Entities\SyncEventMap $externalEventMap)
static addPullEvent(string $command, int $userId, array $params=[])
Definition util.php:373
static getRequestUid()
Definition util.php:520