Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
outgoingmanager.php
1<?php
2
4
6use Bitrix\Calendar\Core;
8use Bitrix\Calendar\Internals\EO_Event;
9use Bitrix\Calendar\Internals\EO_Event_Collection;
10use Bitrix\Calendar\Internals\EO_EventConnection;
30use Bitrix\Main\Entity\ReferenceField;
36use Exception;
37use Generator;
38
40{
41 const TIME_SLICE = 2600000;
42
43 private Connection $connection;
44
45 private FactoryInterface $factory;
46
47 private VendorSynchronization $syncManager;
51 private Core\Mappers\Factory $mapperFactory;
52
56 public function __construct(Connection $connection)
57 {
58 $this->connection = $connection;
59 $context = new Context([
60 'connection' => $connection,
61 ]);
62 $this->factory = FactoryBuilder::create($connection->getVendor()->getCode(), $connection, $context);
63 $this->syncManager = new VendorSynchronization($this->factory);
64 $this->mapperFactory = ServiceLocator::getInstance()->get('calendar.service.mappers.factory');
65 }
66
76 public function exportSections(array $params = []): Result
77 {
78 $mainResult = new Result();
79 $resultData = [
80 'links' => [
81 'exported' => [],
82 'updated' => [],
83 'skipped' => [],
84 'error' => [],
85 ],
86 'exportErr' => [],
87 ];
88
89 $excludeIds = $params['excludeIds'] ?? [];
91 foreach ($this->fetchSections($excludeIds) as $section)
92 {
93 $sectionResult = $this->exportSectionSimple($section);
94 if ($sectionResult->isSuccess())
95 {
96 foreach ($sectionResult->getData() as $key => $link)
97 {
98 $resultData['links'][$key][] = $link;
99 }
100 $resultData['exported'][] = $section->getId();
101 }
102 else
103 {
104 $mainResult->addErrors($sectionResult->getErrors());
105 $resultData['exportErr'][] = $section->getId();
106 }
107 }
108
109 return $mainResult->setData($resultData);
110 }
111
123 public function exportSectionEvents(SectionConnection $sectionLink, array $excludeEventIds = []): Result
124 {
125 $result = new Result();
126 $resultData = [
127 'events' => [
128 'deleted' => [],
129 'exported' => [],
130 'updated' => [],
131 'stripped' => [],
132 'error' => [],
133 ]
134 ];
135
136 $pushResult = static function(array $result) use (&$resultData)
137 {
138 if (empty($result['entityType']) || empty($result['entity']))
139 {
140 return;
141 }
142 if ($result['entityType'] === 'link')
143 {
144 $resultData['events'][$result['action']] = $result['entity']->getEvent()->getId();
145 }
146 elseif ($result['entityType'] === 'eventId')
147 {
148 $resultData['events'][$result['action']] = $result['entity'];
149 }
150 };
151
152 foreach ($this->fetchSectionEvents($sectionLink->getSection(), $excludeEventIds) as $eventPack)
153 {
154 $exportResult = $this->exportEvent($eventPack, $sectionLink);
155 if ($exportResult->isSuccess())
156 {
157 $pushResult($exportResult->getData());
158 }
159 else
160 {
161 $id = null;
162
163 if ($eventPack['event'])
164 {
165 $id = $eventPack['event']->getId();
166 }
167 else if ($eventPack['master'])
168 {
169 $id = $eventPack['master']->getId();
170 }
171 $resultData['events']['error'] = $id;
172 }
173 }
174
175 return $result->setData($resultData);
176 }
177
187 public function export(): Result
188 {
189 $mainResult = new Result();
190 $resultData = [];
191
192 foreach ($this->fetchSections() as $section)
193 {
194 $sectionResult = $this->exportSection($section);
195 if (!$sectionResult->isSuccess())
196 {
197 continue;
198 }
199
201 $sectionLink = $sectionResult->getData()['sectionConnection'] ?? null;
202
203 if ($sectionLink)
204 {
205 foreach ($this->fetchSectionEvents($section) as $event)
206 {
207 $this->exportEvent($event, $sectionLink);
208 }
209 }
210 else
211 {
212 throw new ObjectPropertyException('Context object das not have sectionLink information');
213 }
214 }
215
216 return $mainResult->setData($resultData);
217 }
218
230 public function exportSection(Core\Section\Section $section): Result
231 {
232 $sectionContext = new SectionContext();
233 if ($link = $this->getSectionLink($section))
234 {
235 if ($link->isActive())
236 {
237 $result = $this->syncManager->updateSection($section, $sectionContext);
238 if ($result->isSuccess())
239 {
240 $result->setData(array_merge($result->getData(), [
241 'sectionConnection' => $link,
242 ]));
243 }
244 else if (
245 !$result->isSuccess()
246 && $result->getData()['error'] === 404
247 && $section->getExternalType() === 'local'
248 )
249 {
250 $this->deleteSectionConnection($link);
251 $sectionContext->setSectionConnection(null);
252 $result = $this->syncManager->createSection($section, $sectionContext);
253 }
254 }
255 else
256 {
257 $result = new Result();
258 }
259 }
260 else
261 {
262 $result = $this->syncManager->createSection($section, $sectionContext);
263 }
264
265 return $result;
266 }
267
280 public function exportSectionSimple(Core\Section\Section $section): Result
281 {
282 $resultData = [];
283 $mainResult = new Result();
284 $sectionContext = new SectionContext();
285
286 if ($link = $this->getSectionLink($section))
287 {
288 $sectionContext->setSectionConnection($link);
289 if ($link->isActive())
290 {
291 $result = $this->syncManager->updateSection($section, $sectionContext);
292 if ($result->isSuccess())
293 {
294 $resultData['updated'] = $link;
295 }
296 else
297 {
298 $resultData['error'] = $link;
299 }
300 }
301 else
302 {
303 $resultData['skipped'] = $link;
304 }
305 }
306 elseif ($section->getExternalType() !== Core\Section\Section::LOCAL_EXTERNAL_TYPE)
307 {
308 $resultData['skipped'] = $section;
309 }
310 else
311 {
312 $result = $this->syncManager->createSection($section, $sectionContext);
313 if (!empty($result->getData()['sectionConnection']))
314 {
315 $resultData['exported'] = $result->getData()['sectionConnection'];
316 }
317 else
318 {
319 $result->addError(new Error('Error of export section'));
320 $resultData['section'] = $section;
321 }
322 }
323
324 return $mainResult->setData($resultData);
325 }
326
336 private function fetchSections(array $exclude = []): Generator
337 {
338 $sectionList = SectionTable::getList([
339 'filter' => [
340 '=CAL_TYPE' => $this->connection->getOwner()->getType(),
341 'OWNER_ID' => $this->connection->getOwner()->getId(),
342 'EXTERNAL_TYPE' => ['local', $this->connection->getVendor()->getCode()],
343 '!ID' => $exclude
344 ],
345 'select' => ['*'],
346 ]);
347
348 $mapper = new Core\Mappers\Section();
349 while ($sectionDM = $sectionList->fetchObject())
350 {
351 $section = $mapper->getByEntityObject($sectionDM);
352 yield $section;
353 }
354 }
355
365 private function getSectionLink(\Bitrix\Calendar\Core\Section\Section $section): ?SectionConnection
366 {
367 $mapper = new Core\Mappers\SectionConnection();
368 $map = $mapper->getMap([
369 '=SECTION_ID' => $section->getId(),
370 '=CONNECTION_ID' => $this->connection->getId(),
371 ]);
372 switch ($map->count())
373 {
374 case 0:
375 return null;
376 case 1:
377 return $map->fetch();
378 default:
379 throw new Core\Base\BaseException('More than 1 SectionConnections found.');
380 }
381 }
382
386 private function getFactory(): FactoryInterface
387 {
388 return $this->factory;
389 }
390
401 private function fetchSectionEvents(Core\Section\Section $section, array $excludeEventIds = []): Generator
402 {
403 $timestamp = time() - self::TIME_SLICE;
404 $eventList = EventTable::getList([
405 'select' => [
406 '*',
407 'LINK.*',
408 'LINK.CONNECTION',
409 ],
410 'filter' => [
411 '=SECTION_ID' => $section->getId(),
412 '=DELETED' => 'N',
413 '!ID' => $excludeEventIds,
414 '>DATE_TO_TS_UTC' => $timestamp,
415 '!=MEETING_STATUS' => 'N',
416 ],
417 'runtime' => [
418 new ReferenceField(
419 'LINK',
420 EventConnectionTable::class,
421 [
422 '=this.ID' => 'ref.EVENT_ID',
423 'ref.CONNECTION_ID' => ['?', $this->connection->getId()],
424 ],
425 ['join_type' => 'LEFT']
426 ),
427 ],
428 ])->fetchCollection();
429
430 $eventList = $this->prepareEvents($eventList);
431
432 foreach ($eventList as $eventPack)
433 {
434 yield $eventPack;
435 }
436 }
437
438 private function prepareEvents(EO_Event_Collection $events): array
439 {
440 $result = [];
441
442 foreach ($events as $event)
443 {
444 if ($event->getRrule())
445 {
446 $recId = $event->getParentId();
447 $result[$recId]['type'] = 'recurrence';
448 $result[$recId]['master'] = $event;
449 }
450 else if ($event->getRecurrenceId())
451 {
452 $result[$event->getRecurrenceId()]['instances'][] = $event;
453 }
454 else
455 {
456 $result[$event->getId()] = [
457 'type' => 'single',
458 'event' => $event,
459 ];
460 }
461 }
462
463 return $result;
464 }
465
472 private function getEventLink(EO_Event $eventEntity): ?EventConnection
473 {
475 if ($link = $eventEntity->get('LINK'))
476 {
477 $link->setEvent($eventEntity);
478
479 return $this->mapperFactory->getEventConnection()->getByEntityObject($link);
480 }
481
482 return null;
483 }
484
496 private function exportEvent(array $eventPack, SectionConnection $sectionLink): Result
497 {
498 if (!empty($eventPack['master']))
499 {
500 return $this->exportRecurrenceEvent($sectionLink, $eventPack);
501 }
502
503 if (!empty($eventPack['event']))
504 {
505 return $this->exportSimpleEvent($sectionLink, $eventPack['event']);
506 }
507
508 return (new Result())->addError(new Error('Unsupported eventpack format'));
509 }
510
524 {
525 $mainResult = new Result();
526 if ($this->syncManager->canSubscribeSection())
527 {
528 $pushManager = new Sync\Managers\PushManager();
529 $subscription = $pushManager->getPush(
531 $link->getId()
532 );
533 if ($subscription && !$subscription->isExpired())
534 {
535 $result = $this->syncManager->renewPush($subscription);
536 if ($result->isSuccess())
537 {
538 $mainResult = $pushManager->renewPush($subscription, $result->getData());
539 }
540 else
541 {
542 $mainResult->addError(new Error('Error of renew subscription.'));
543 $mainResult->addErrors($result->getErrors());
544 }
545 }
546 else
547 {
548 $subscribeResult = $this->syncManager->subscribeSection($link);
549 if ($subscribeResult->isSuccess())
550 {
551 if ($subscription)
552 {
553 $pushManager->renewPush($subscription, $subscribeResult->getData());
554 }
555 else
556 {
557 try
558 {
559 $pushManager->addPush(
560 'SECTION_CONNECTION',
561 $link->getId(),
562 $subscribeResult->getData()
563 );
564 }
565 catch(SqlQueryException $e)
566 {
567 if (
568 $e->getCode() === 400
569 && substr($e->getDatabaseMessage(), 0, 6) === '(1062)')
570 {
571 // there's a race situation
572 }
573 else
574 {
575 throw $e;
576 }
577 }
578
579 }
580 }
581 else
582 {
583 $mainResult->addError(new Error('Error of add subscription.'));
584 $mainResult->addErrors($subscribeResult->getErrors());
585 }
586 }
587 }
588
589 return $mainResult;
590 }
591
598 public function subscribeConnection(): Result
599 {
600 $mainResult = new Result();
601 if ($this->syncManager->canSubscribeConnection())
602 {
603 $pushManager = new Sync\Managers\PushManager();
604 $subscription = $pushManager->getPush(
606 $this->connection->getId()
607 );
608
609 if ($subscription && !$subscription->isExpired())
610 {
611 $result = $this->syncManager->renewPush($subscription);
612 if ($result->isSuccess())
613 {
614 $mainResult = $pushManager->renewPush($subscription, $result->getData());
615 }
616 else
617 {
618 $mainResult->addError(new Error('Error of renew subscription.'));
619 $mainResult->addErrors($result->getErrors());
620 }
621 }
622 else
623 {
624 $subscribeResult = $this->syncManager->subscribeConnection();
625 if ($subscribeResult->isSuccess())
626 {
627 if ($subscription !== null)
628 {
629 $pushManager->renewPush($subscription, $subscribeResult->getData());
630 }
631 else
632 {
633 $pushManager->addPush(
634 'CONNECTION',
635 $this->connection->getId(),
636 $subscribeResult->getData()
637 );
638 }
639 }
640 else
641 {
642 $mainResult->addError(new Error('Error of add subscription.'));
643 $mainResult->addErrors($subscribeResult->getErrors());
644 }
645 }
646 }
647
648 return $mainResult;
649 }
650
657 private function deleteSectionConnection(SectionConnection $link): void
658 {
659 global $DB;
660
661 if ($link->getConnection()->getId() && $link->getSection()->getId())
662 {
663 $DB->Query("
664 DELETE FROM b_calendar_event_connection
665 WHERE CONNECTION_ID = " . (int)$link->getConnection()->getId() ."
666 AND EVENT_ID IN (SELECT EV.ID FROM b_calendar_event EV
667 WHERE EV.SECTION_ID = " . (int)$link->getSection()->getId() . ");"
668 );
669 }
670
671 if ($link->getId())
672 {
673 SectionConnectionTable::delete($link->getId());
674 }
675 }
676
688 private function exportSimpleEvent(SectionConnection $sectionLink, EO_Event $eventData): Result
689 {
690 $result = new Result();
691 $context = new Context([]);
692 $context->add('sync', 'sectionLink', $sectionLink);
693
694 $event = $this->mapperFactory->getEvent()->getByEntityObject($eventData);
695
696 $eventLink = $this->getEventLink($eventData);
697 $eventContext = (new EventContext())
698 ->merge($context)
699 ->setSectionConnection($sectionLink)
700 ->setEventConnection($eventLink)
701 ;
702
703 if ($eventLink && !$eventLink->getVendorEventId())
704 {
705 $this->mapperFactory->getEventConnection()->delete($eventLink);
706 $eventLink = null;
707 }
708
709 if ($event && $eventLink)
710 {
711 $resultUpdate = $this->syncManager->updateEvent($event, $eventContext);
712 $resultData = [
713 'entityType' => 'link',
714 'entity' => $eventLink,
715 'action' => 'updated',
716 ];
717 if (!$resultUpdate->isSuccess())
718 {
719 $result->addErrors($resultUpdate->getErrors());
720 }
721 }
722 else
723 {
724 $resultAdd = $this->syncManager->createEvent($event, $eventContext);
725 $resultData = [
726 'entityType' => 'link',
727 'action' => 'exported',
728 ];
729 if ($resultAdd->isSuccess())
730 {
731 if (!empty($resultAdd->getData()['eventConnectionId']))
732 {
733 $resultData['entity'] = $this->mapperFactory->getEventConnection()
734 ->getById($resultAdd->getData()['eventConnectionId'])
735 ;
736 }
737 else
738 {
739 $resultData['entity'] = $this->mapperFactory->getEventConnection()->getMap([
740 '=EVENT_ID' => $event->getId(),
741 '=CONNECTION_ID' => $sectionLink->getConnection()->getId(),
742 ])->fetch();
743 }
744 }
745 else
746 {
747 $resultData = [
748 'entityType' => 'errorLink',
749 'action' => 'exported',
750 ];
751 $result->addErrors($resultAdd->getErrors());
752 }
753 }
754
755 return $result->setData($resultData);
756 }
757
765 private function exportRecurrenceEvent(SectionConnection $sectionLink, array $eventData): Result
766 {
767 $context = new EventContext();
768 $context->setSectionConnection($sectionLink);
769
770 $recurrenceEvent = $this->buildRecurrenceEvent($eventData);
771
772 if ($recurrenceEvent->getEventConnection())
773 {
774 $context->setEventConnection($recurrenceEvent->getEventConnection());
775 $result = $this->syncManager->updateRecurrence($recurrenceEvent, $context);
776 }
777 else
778 {
779 $result = $this->syncManager->createRecurrence($recurrenceEvent, $context);
780 }
781
782 $resultData = [
783 'entityType' => 'link',
784 'action' => 'updated',
785 ];
786
787 return $result->setData($resultData);
788 }
789
796 private function buildRecurrenceEvent(array $eventData): SyncEvent
797 {
798 $masterEvent = $this->mapperFactory->getEvent()->getByEntityObject($eventData['master']);
799 $masterLink = $this->getEventLink($eventData['master']);
800 $masterSyncEvent = (new SyncEvent())
801 ->setEvent($masterEvent)
802 ->setEventConnection($masterLink)
803 ;
804 if ($masterSyncEvent->getEventConnection() && !$masterSyncEvent->getEventConnection()->getVendorEventId())
805 {
806 $this->mapperFactory->getEventConnection()->delete($masterSyncEvent->getEventConnection());
807 $masterSyncEvent->setEventConnection(null);
808 }
809
810 $instancesCollection = new InstanceMap();
811 $instances = $eventData['instances'] ?? [];
812 foreach ($instances as $instance)
813 {
814 $instanceEvent = $this->mapperFactory->getEvent()->getByEntityObject($instance);
815 $instanceLink = $this->getEventLink($instance);
816 $instanceSyncEvent = (new SyncEvent())
817 ->setEvent($instanceEvent)
818 ->setEventConnection($instanceLink)
819 ;
820
821 if ($instanceSyncEvent->getEventConnection() && !$instanceSyncEvent->getEventConnection()->getVendorEventId())
822 {
823 $this->mapperFactory->getEventConnection()->delete($instanceSyncEvent->getEventConnection());
824 $instanceSyncEvent->setEventConnection(null);
825 }
826 $instancesCollection->add($instanceSyncEvent);
827 }
828
829 $masterSyncEvent->setInstanceMap($instancesCollection);
830
831 return $masterSyncEvent;
832 }
833}
exportSection(Core\Section\Section $section)
exportSectionEvents(SectionConnection $sectionLink, array $excludeEventIds=[])
exportSectionSimple(Core\Section\Section $section)