Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
incomingmanager.php
1<?php
2
4
27use CCalendar;
28use Bitrix\Calendar\Core;
29use CCalendarEvent;
30use CCalendarSect;
31use Exception;
32
37{
38 private Connection $connection;
39
40 private FactoryInterface $factory;
41
42 private VendorSynchronization $syncManager;
44 private Core\Mappers\Factory $mapperFactory;
45
51 public function __construct(Connection $connection)
52 {
53 $this->connection = $connection;
54
55 $this->mapperFactory = ServiceLocator::getInstance()->get('calendar.service.mappers.factory');
56 }
57
63 public function importSections(): Result
64 {
65 // In this case we can guess, they were deleted on the vendor side.
66 $resultData = [
67 'links' => [
68 'updated' => [],
69 'imported' => [],
70 'skipped' => [],
71 ],
72 'vendorSections' => [],
73 ];
74 $result = new Result();
75
76 $getResult = $this->getFactory()->getIncomingSectionManager()->getSections();
77 if ($getResult->isSuccess())
78 {
79 $sections = $getResult->getData()['externalSyncSectionMap'];
80
81 $mapper = $this->mapperFactory->getSection();
83 foreach ($sections as $syncSection)
84 {
85 $link = null;
86 try {
87 if ($link = $this->getSectionLinkByVendorId($syncSection->getSectionConnection()->getVendorSectionId()))
88 {
89 $resultData['linkedSectionIds'][] = $link->getSection()->getId();
90 if (!$link->isActive())
91 {
92 $resultData['links']['skipped'][] = $link;
93 continue;
94 }
95 if (
96 !empty($syncSection->getAction() === 'updated')
97 && $syncSection->getSection()->getDateModified() > $link->getSection()->getDateModified()
98 )
99 {
100 $section = $this->mergeSections($link->getSection(), $syncSection->getSection());
101 $mapper->update($section, [
102 'originalFrom' => $this->connection->getVendor()->getCode(),
103 ]);
104 (new Core\Mappers\SectionConnection())->update($link);
105
106 $resultData['importedSectionIds'][] = $link->getSection()->getId();
107 $resultData['links']['updated'][] = $link;
108 }
109 }
110 else
111 {
112 $link = $this->importSectionSimple(
113 $syncSection->getSection(),
114 $syncSection->getSectionConnection()->getVendorSectionId(),
115 $syncSection->getSectionConnection()->getVersionId() ?? null,
116 $syncSection->getSectionConnection()->isPrimary() ?? null,
117 );
118 $resultData['links']['imported'][] = $link;
119 }
120 if (!empty($link))
121 {
122 $resultData['importedSectionIds'][] = $link->getSection()->getId();
123 }
124
125 }
126 catch (SystemException $e)
127 {
128 $resultData['sections'][$syncSection['id']] = Dictionary::SYNC_STATUS['failed'];
129 }
130 }
131
132 return $result->setData($resultData);
133 }
134 else
135 {
136 $result->addErrors($getResult->getErrors());
137 return $result;
138 }
139 }
140
141
151 private function getSectionLinkByVendorId(string $sectionId): ?SectionConnection
152 {
153 $mapper = $this->mapperFactory->getSectionConnection();
154 $map = $mapper->getMap([
155 '=CONNECTION_ID' => $this->connection->getId(),
156 '=VENDOR_SECTION_ID' => $sectionId,
157 ]);
158
159 return $map->fetch();
160 }
161
170 public function import(): Result
171 {
172 $resultData = [];
173 $result = new Result();
175 $sections = $this->getFactory()->getSectionManager()->getSections($this->connection);
176
182 foreach ($sections as $vendorSectionPack)
183 {
184 try {
185 $sectionLink = $this->importSection(
186 $vendorSectionPack['section'],
187 $vendorSectionPack['id'],
188 $vendorSectionPack['version'] ?? null,
189 $vendorSectionPack['is_primary'] ?? null,
190
191 );
192 $resultData['importedSectionIds'][] = $sectionLink->getSection()->getId();
193 if ($sectionLink->isActive())
194 {
195 $eventsResult = $this->importSectionEvents($sectionLink);
196 $resultData['events'][$vendorSectionPack['id']] = $eventsResult->getData();
197 $resultData['sections'][$vendorSectionPack['id']] = Dictionary::SYNC_STATUS['success'];
198 }
199 else
200 {
201 $resultData['sections'][$vendorSectionPack['id']] = Dictionary::SYNC_STATUS['inactive'];
202 $resultData['events'][$vendorSectionPack['id']] = null;
203 }
204 } catch (SystemException $e) {
205 $resultData['sections'][$vendorSectionPack['id']] = Dictionary::SYNC_STATUS['failed'];
206 }
207 }
208
209 return $result->setData($resultData);
210 }
211
215 private function getFactory(): FactoryInterface
216 {
217 if (empty($this->factory))
218 {
219 $this->factory = FactoryBuilder::create(
220 $this->connection->getVendor()->getCode(),
221 $this->connection,
222 new Context()
223 );
224 }
225
226 return $this->factory;
227 }
228
247 private function importSection(
248 Section $section,
249 string $vendorId,
250 string $vendorVersion = null,
251 bool $isPrimary = false
252 ): SectionConnection
253 {
254 $sectionFactory = new SectionConnectionFactory();
255 $link = $sectionFactory->getSectionConnection([
256 'filter' => [
257 'CONNECTION_ID' => $this->connection->getId(),
258 'VENDOR_SECTION_ID' => $vendorId,
259 ],
260 'connectionObject' => $this->connection,
261 ]);
262 if (!$link)
263 {
264 $fields = [
265 'NAME' => $section->getName(),
266 'ACTIVE' => 'Y',
267 'DESCRIPTION' => $section->getDescription() ?? '',
268 'COLOR' => $section->getColor() ?? '',
269 'CAL_TYPE' => $this->connection->getOwner()
270 ? $this->connection->getOwner()->getType()
271 : Core\Role\User::TYPE
272 ,
273 'OWNER_ID' => $this->connection->getOwner()
274 ? $this->connection->getOwner()->getId()
275 : null
276 ,
277 'CREATED_BY' => $this->connection->getOwner()->getId(),
278 'DATE_CREATE' => new DateTime(),
279 'TIMESTAMP_X' => new DateTime(),
280 'EXTERNAL_TYPE' => $this->connection->getVendor()->getCode(),
281 ];
282 if ($sectionId = CCalendar::SaveSection([
283 'arFields' => $fields,
284 'originalFrom' => $this->connection->getVendor()->getCode(),
285
286 ]))
287 {
288 $section = $this->mapperFactory->getSection()->resetCacheById($sectionId)->getById($sectionId);
289
290 $protoLink = (new SectionConnection())
291 ->setSection($section)
292 ->setConnection($this->connection)
293 ->setVendorSectionId($vendorId)
294 ->setVersionId($vendorVersion)
295 ->setPrimary($isPrimary)
296 ;
297 $link = $this->mapperFactory->getSectionConnection()->create($protoLink);
298 }
299 else
300 {
301 throw new SystemException("Can't create Bitrix Calendar Section", 500, __FILE__, __LINE__ );
302 }
303 }
304
305 if ($link)
306 {
307 $this->subscribeSection($link);
308 }
309
310 return $link;
311 }
312
326 private function importSectionSimple(
327 Section $section,
328 string $vendorId,
329 string $vendorVersion = null,
330 bool $isPrimary = null
331 ): SectionConnection
332 {
333
334 if ($section = $this->saveSection($section))
335 {
336 if ($link = $this->createSectionConnection($section, $vendorId, $vendorVersion, $isPrimary))
337 {
338 return $link;
339 }
340 else
341 {
342 throw new SystemException("Can't create Section Connection link.", 500, __FILE__, __LINE__ );
343 }
344 }
345 else
346 {
347 throw new SystemException("Can't create Bitrix Calendar Section.", 500, __FILE__, __LINE__ );
348 }
349 }
350
362 private function createSectionConnection(
363 Section $section,
364 string $vendorId,
365 string $vendorVersion = null,
366 bool $isPrimary = null
367 ): ?SectionConnection
368 {
369 $entity = (new SectionConnection())
370 ->setSection($section)
371 ->setConnection($this->connection)
372 ->setVendorSectionId($vendorId)
373 ->setVersionId($vendorVersion)
374 ->setPrimary($isPrimary ?? false)
375 ;
377 $result = (new Core\Mappers\SectionConnection())->create($entity);
378
379 return $result;
380 }
381
392 private function subscribeSection(SectionConnection $link): Result
393 {
394 $mainResult = new Result();
395 if ($this->getSyncManager()->canSubscribeSection())
396 {
397 $pushManager = new PushManager();
398 $subscription = $pushManager->getPush(
400 $link->getId()
401 );
402 if ($subscription && !$subscription->isExpired())
403 {
404 $result = $this->getSyncManager()->renewPush($subscription);
405 if ($result->isSuccess())
406 {
407 $mainResult = $pushManager->renewPush($subscription, $result->getData());
408 }
409 else
410 {
411 $mainResult->addError(new Error('Error of renew subscription.'));
412 $mainResult->addErrors($result->getErrors());
413 }
414 }
415 else
416 {
417 $subscribeResult = $this->getSyncManager()->subscribeSection($link);
418 if ($subscribeResult->isSuccess())
419 {
420 if ($subscription !== null)
421 {
422 $pushManager->renewPush($subscription, $subscribeResult->getData());
423 }
424 else
425 {
426 $pushManager->addPush(
427 'SECTION_CONNECTION',
428 $link->getId(),
429 $subscribeResult->getData()
430 );
431 }
432 }
433 else
434 {
435 $mainResult->addError(new Error('Error of add subscription.'));
436 $mainResult->addErrors($subscribeResult->getErrors());
437 }
438 }
439 }
440
441 return $mainResult;
442 }
443
452 public function importSectionEvents(SectionConnection $sectionLink): Result
453 {
454 $mainResult = new Result();
455 $resultData = [
456 'events' => [
457 'deleted' => [],
458 'imported' => [],
459 'updated' => [],
460 'stripped' => [],
461 'error' => [],
462 ],
463 ];
464
465 $pushResult = static function(array $result) use (&$resultData)
466 {
467 if (empty($result['entityType']))
468 {
469 return;
470 }
471 if ($result['entityType'] === 'link')
472 {
473 $resultData['events'][$result['action']] = $result['entity']->getEvent()->getId();
474 }
475 elseif ($result['entityType'] === 'eventId')
476 {
477 $resultData['events'][$result['action']] = $result['entity'];
478 }
479 };
480
481 $vendorManager = $this->getFactory()->getEventManager();
482 foreach ($vendorManager->fetchSectionEvents($sectionLink) as $eventPack)
483 {
484 $masterId = null;
485 foreach ($eventPack as $eventData)
486 {
488 if ($event = $eventData['event'])
489 {
490 $event->setSection($sectionLink->getSection());
491 }
492 if ($eventData['type'] === 'deleted')
493 {
494 $result = $this->deleteInstance($eventData['id']);
495 if ($result->isSuccess())
496 {
497 $resultData['events']['deleted'][] = $result->getData()['eventId'];
498 $resultData[$eventData['id']] = 'delete success';
499 }
500 else
501 {
502 $resultData['events']['error'][] = $result->getData()['eventId'];
503 $resultData[$eventData['id']] = 'delete fail';
504 }
505 }
506 elseif ($eventData['type'] === 'single')
507 {
508 $result = $this->importEvent(
509 $eventData['event'],
510 $eventData['id'],
511 $eventData['version'],
512 [
513 'data' => $eventData['data'] ?? null,
514 ]
515 );
516 if ($result->isSuccess())
517 {
518 $pushResult($result->getData());
519 $resultData[$eventData['id']] = 'create success';
520 }
521 else
522 {
523 $resultData[$eventData['id']] = 'create fail';
524 }
525 }
526 elseif ($eventData['type'] === 'master')
527 {
528 $result = $this->importEvent(
529 $eventData['event'],
530 $eventData['id'],
531 $eventData['version'],
532 [
533 'recursionEditMode' => 'all',
534 'data' => $eventData['data'] ?? null,
535 ]);
536 if ($result->isSuccess())
537 {
538 $masterId = $result->getData()['id'];
539 $resultData[$eventData['id']] = 'create success';
540 }
541 else
542 {
543 $resultData[$eventData['id']] = 'create fail';
544 }
545 }
546 elseif ($eventData['type'] === 'exception')
547 {
548 if ($masterId === null)
549 {
550 $resultData[$eventData['id']] = 'create fail: master event not found';
551 continue;
552 }
553 $eventData['event']->setRecurrenceId($masterId);
554 $result = $this->importEvent(
555 $eventData['event'],
556 $eventData['id'],
557 $eventData['version'],
558 [
559 'data' => $eventData['data'] ?? null,
560 ]
561 );
562 if ($result->isSuccess())
563 {
564 $masterId = $result->getData()['id'];
565 $resultData[$eventData['id']] = 'create success';
566 }
567 else
568 {
569 $resultData[$eventData['id']] = 'create fail';
570 }
571 }
572 }
573 }
574
575 return $mainResult->setData($resultData);
576 }
577
586 private function deleteInstance(string $vendorId): Result
587 {
588 $result = new Result();
589 $linkData = EventConnectionTable::query()
590 ->setSelect(['*', 'EVENT'])
591 ->addFilter('CONNECTION_ID', $this->connection->getId())
592 ->addFilter('=VENDOR_EVENT_ID', $vendorId)
593 ->exec()->fetchObject();
594
595 if ($linkData)
596 {
597 if (!CCalendarEvent::Delete([
598 'id' => $linkData->getEventId(),
599 'userId' => $this->connection->getOwner()->getId(),
600 'bMarkDeleted' => true,
601 'originalFrom' => $this->connection->getVendor()->getCode(),
602 ]))
603 {
604 $result->addError(new Error('Error of delete event'));
605 $result->setData(['eventId' => $linkData->getEventId()]);
606 }
607 }
608 else
609 {
610 $result->addError(new Error('Event not found'));
611 }
612
613 return $result;
614 }
615
628 private function importEvent(
629 Event $event,
630 string $vendorId,
631 string $vendorVersion = null,
632 array $params = []
633 ): Result
634 {
635 $prepareResult = function (string $action, string $entityType, $entity)
636 {
637 return [
638 'type' => $entityType,
639 'action' => $action,
640 'value' => $entity,
641 ];
642 };
643
644 $result = new Result();
645
646 $mapper = new Core\Mappers\Event();
647
648 $link = (new EventConnectionFactory())->getEventConnection([
649 'filter' => [
650 'CONNECTION_ID' => $this->connection->getId(),
651 '=VENDOR_EVENT_ID' => $vendorId,
652 ],
653 'connection' => $this->connection,
654 ]);
655 // TODO: explode to 2 methods
656 if ($link)
657 {
658 if ($vendorVersion && $link->getEntityTag() === $vendorVersion)
659 {
660 $resultData = $prepareResult('skipped', 'link', $link);
661 }
662 elseif (
663 empty($event->getDateModified())
664 || empty($link->getEvent()->getDateModified())
665 || ($link->getEvent()->getDateModified()->getTimestamp() >= $event->getDateModified()->getTimestamp())
666 )
667 {
668 $resultData = $prepareResult('skipped', 'link', $link);
669 }
670 else
671 {
672 try {
673 $event->setId($link->getEvent()->getId());
674 $event->setOwner(Core\Role\Helper::getRole(
675 $this->connection->getOwner()->getId(),
676 $this->connection->getOwner()->getType(),
677 ));
678 $this->mergeReminders($event, $link->getEvent());
679 $mapper->update($event, [
680 'originalFrom' => $this->connection->getVendor()->getCode(),
681 'userId' => $this->connection->getOwner()->getId(),
682 ]);
683 EventConnectionTable::update($link->getId(), [
684 'ENTITY_TAG' => $vendorVersion,
685 'DATA' => $params['data'] ?? null,
686 ]);
687 $resultData = $prepareResult('updated', 'link', $link);
688
689 }
690 catch (Exception $e) {
691 $resultData = $prepareResult('error', 'link', $link);
692 $result->addError(new Error($e->getMessage(), $e->getCode()));
693 }
694 }
695 }
696 else
697 {
698 try
699 {
700 $event->setOwner(Core\Role\Helper::getRole(
701 $this->connection->getOwner()->getId(),
702 $this->connection->getOwner()->getType(),
703 ));
704 $newEvent = $mapper->create($event, [
705 'originalFrom' => $this->connection->getVendor()->getCode(),
706 'userId' => $this->connection->getOwner()->getId(),
707 ]);
708 $linkResult = EventConnectionTable::add([
709 'EVENT_ID' => $newEvent->getId(),
710 'CONNECTION_ID' => $this->connection->getId(),
711 'VENDOR_EVENT_ID' => $vendorId,
712 'SYNC_STATUS' => Dictionary::SYNC_STATUS['success'],
713 'ENTITY_TAG' => $vendorVersion,
714 'VERSION' => 1,
715 'DATA' => $params['data'] ?? null,
716 ]);
717 if ($linkResult->isSuccess())
718 {
719 $resultData = $prepareResult('imported', 'eventId', $newEvent->getId());
720 }
721 else
722 {
723 $resultData = $prepareResult('error', 'eventId', $newEvent->getId());
724 $result->addError(reset($linkResult->getErrors()));
725 }
726 }
727 catch (Exception $e)
728 {
729 $resultData = $prepareResult('error', 'vendorId', $vendorId);
730 $result->addError(new Error($e->getMessage()));
731 }
732 }
733
734 return $result->setData($resultData ?? []);
735 }
736
742 private function getSyncManager(): VendorSynchronization
743 {
744 if (empty($this->syncManager))
745 {
746 $this->syncManager = new VendorSynchronization($this->getFactory());
747 }
748
749 return $this->syncManager;
750 }
751
752 private function mergeSections(Section $baseSection, Section $newSection): Section
753 {
754 $baseSection
755 ->setId($newSection->getId() ?: $baseSection->getId())
756 ->setName($newSection->getName() ?: $baseSection->getName())
757 ->setDescription($newSection->getDescription())
758 ->setIsActive($newSection->isActive())
759 ->setColor($newSection->getColor())
760 ;
761 return $baseSection;
762 }
763
773 private function saveSection(Section $section): ?Section
774 {
775 if ($this->connection->getOwner() === null)
776 {
777 throw new Core\Base\BaseException('The connection must have an owner');
778 }
779
780 $fields = [
781 'NAME' => $section->getName(),
782 'ACTIVE' => 'Y',
783 'DESCRIPTION' => $section->getDescription() ?? '',
784 'COLOR' => $section->getColor() ?? '',
785 'CAL_TYPE' => $this->connection->getOwner()->getType(),
786 'OWNER_ID' => $this->connection->getOwner()->getId(),
787 'CREATED_BY' => $this->connection->getOwner()->getId(),
788 'DATE_CREATE' => new DateTime(),
789 'TIMESTAMP_X' => new DateTime(),
790 'EXTERNAL_TYPE' => $this->connection->getVendor()->getCode(),
791 ];
792 $sectionId = CCalendar::SaveSection([
793 'arFields' => $fields,
794 'originalFrom' => $this->connection->getVendor()->getCode(),
795 ]);
796
797 if ($sectionId)
798 {
799 return $this->mapperFactory->getSection()
800 ->resetCacheById($sectionId)
801 ->getById($sectionId)
802 ;
803 }
804
805 return null;
806 }
807
814 private function mergeReminders(Event $targetEvent, Event $sourceEvent)
815 {
816 foreach ($sourceEvent->getRemindCollection()->getCollection() as $item)
817 {
818 $targetEvent->getRemindCollection()->add($item);
819 }
820 }
821
834 public function deleteSection(Section $section, int $linkId)
835 {
836 $this->deleteSectionConnection($section->getId(), $linkId);
837
838 if ($section->getExternalType() !== 'local')
839 {
840 CCalendarSect::Delete($section->getId(), false, [
841 'originalFrom' => $this->connection->getVendor()->getCode(),
842 ]);
843 }
844 else
845 {
846 $this->exportLocalSection($section);
847 }
848 }
849
857 private function deleteSectionConnection(int $sectionId, int $sectionConnectionId)
858 {
859 global $DB;
860
861 if ($this->connection->getId() && $sectionId)
862 {
863 $DB->Query("
864 DELETE FROM b_calendar_event_connection
865 WHERE CONNECTION_ID = " . $this->connection->getId() . "
866 AND EVENT_ID IN (SELECT EV.ID FROM b_calendar_event EV
867 WHERE EV.SECTION_ID = " . $sectionId . " );"
868 );
869 }
870
871 if ($sectionConnectionId)
872 {
873 SectionConnectionTable::delete($sectionConnectionId);
874 }
875 }
876
886 private function exportLocalSection(Section $section): void
887 {
888 $manager = new OutgoingManager($this->connection);
889 $sectionResult = $manager->exportSectionSimple($section);
890 if ($sectionResult->isSuccess() && $sectionResult->getData()['exported'])
891 {
893 $sectionLink = $sectionResult->getData()['exported'];
894 $manager->exportSectionEvents($sectionLink);
895 }
896 }
897}
deleteSection(Section $section, int $linkId)