Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
eventmanager.php
1<?php
2
4
9use Bitrix\Calendar\Core;
21use Bitrix\Calendar\Sync\Internals\HasContextTrait;
36use DateTimeZone;
37use Generator;
38
40{
41 use HasContextTrait;
42
43 private Helper $helper;
44
48 private ?EventConverter $eventConverter;
49
50 private Core\Mappers\EventConnection $eventConnectionMapper;
51
55 public function __construct(ContextInterface $context)
56 {
57 $this->context = $context;
58 $this->helper = $context->getHelper();
59 parent::__construct($context->getConnection());
60 }
61
75 public function create(Core\Event\Event $event, EventContext $context): Result
76 {
77 $result = new Result();
78 $internalDto = $this->getEventConverter()->eventToDto($event);
79
80 try
81 {
82 $dto = $this->getService()->createEvent($internalDto, $context->getSectionConnection()->getVendorSectionId());
83 if ($dto)
84 {
85 if ($event->getExcludedDateCollection() && $event->getExcludedDateCollection()->count())
86 {
87 $context->add('sync', 'masterEventId', $dto->id);
89 foreach ($event->getExcludedDateCollection() as $item)
90 {
91 $context->add('sync', 'excludeDate', $item);
92 $this->deleteInstance($event, $context);
93 }
94 }
95
96 if (!empty($dto->id))
97 {
98 $result->setData($this->prepareResultData($dto));
99 }
100 else
101 {
102 $result->addError(new Main\Error('Error of create a series master event'));
103 }
104 }
105 else
106 {
107 $result->addError(new Main\Error('Error of create event'));
108 }
109 }
110 catch (ApiException $exception)
111 {
112 if ((int)$exception->getCode() !== 400)
113 {
114 throw $exception;
115 }
116
117 $result->addError(new Main\Error($exception->getMessage(), $exception->getCode()));
118 }
119 catch (AuthException $exception)
120 {
121 $result->addError(new Main\Error($exception->getMessage(), $exception->getCode()));
122 }
123
124 return $result;
125 }
126
139 public function update(Core\Event\Event $event, EventContext $context): Result
140 {
141 $result = new Result();
142 $internalDto = $this->getEventConverter()->eventToDto($event);
143 $this->enrichVendorData($internalDto, $context->getEventConnection());
144
145 try
146 {
147 $dto = $this->getService()->updateEvent($context->getEventConnection()->getVendorEventId(), $internalDto);
148 if ($dto)
149 {
150 $result->setData($this->prepareResultData($dto));
151 }
152 }
153 catch (ApiException $exception)
154 {
155 if ((int)$exception->getCode() !== 400)
156 {
157 throw $exception;
158 }
159
160 $result->addError(new Main\Error($exception->getMessage(), $exception->getCode()));
161 }
162 catch (AuthException $exception)
163 {
164 $result->addError(new Main\Error($exception->getMessage(), $exception->getCode()));
165 }
166
167 return $result;
168 }
169
185 public function delete(Core\Event\Event $event, EventContext $context): Result
186 {
187 $this->getService()->deleteEvent($context->getEventConnection()->getVendorEventId());
188
189 return new Result();
190 }
191
197 private function prepareResultData(EventDto $dto): array
198 {
199 return [
200 'dto' => $dto,
201 'event' => [
202 'id' => $dto->id,
203 'version' => $dto->changeKey,
204 'etag' => $dto->etag,
205 'recurrence' => $dto->seriesMasterId ?? null,
206 'data' => $this->prepareCustomData($dto),
207 ],
208 ];
209 }
210
222 public function updateInstance(Event $event, EventContext $context): Result
223 {
224 $eventLink = $context->sync['instanceLink'];
225 if ($eventLink)
226 {
227 $eventContext = (new EventContext())
228 ->setSectionConnection($context->getSectionConnection())
229 ->setEventConnection($eventLink)
230 ;
231 return $this->update($event, $eventContext);
232 }
233
234 return (new Result())->addError(new Main\Error('Not found link for instance'));
235 }
236
249 public function createInstance(Core\Event\Event $event, EventContext $context): Result
250 {
251 if (
252 $event->getOriginalDateFrom()
253 && $event->getOriginalDateFrom()->format('Ymd') !== $event->getStart()->format('Ymd')
254 )
255 {
256 return $this->moveInstance($event, $context);
257 }
258
259 $result = new Result();
260 $masterLink = $context->getEventConnection();
261
262 try
263 {
264 if ($masterLink && $instance = $this->getInstanceForDay($masterLink->getVendorEventId(), $event->getStart()->getDate()))
265 {
266 $dto = $this->getService()->updateEvent(
267 $instance->id,
268 $this->getEventConverter()->eventToDto($event),
269 );
270 if ($dto && !empty($dto->id))
271 {
272 $result->setData($this->prepareResultData($dto));
273 }
274 else
275 {
276 $result->addError(new Main\Error("Error of create instance.", 404));
277 }
278 }
279 else
280 {
281 $result->addError(new Main\Error("Instances for event not found", 404));
282 }
283 }
284 catch (ApiException $exception)
285 {
286 if (!in_array((int)$exception->getCode(), [400, 404], true))
287 {
288 throw $exception;
289 }
290
291 $result->addError(new Main\Error($exception->getMessage(), $exception->getCode()));
292 }
293 catch (AuthException $exception)
294 {
295 $result->addError(new Main\Error($exception->getMessage(), $exception->getCode()));
296 }
297
298 return $result;
299 }
300
318 public function deleteInstance(Event $event, EventContext $context): Result
319 {
320 $result = new Result();
321 $masterEventId = $context->getEventConnection()
322 ? $context->getEventConnection()->getVendorEventId()
323 : ($context->sync['masterEventId'] ?? null)
324 ;
325 if ($masterEventId)
326 {
327 $excludeDate = new Main\Type\DateTime(
328 $context->sync['excludeDate']->getDate()->format('Ymd 000000'),
329 'Ymd His',
330 $event->getStartTimeZone()
331 ? $event->getStartTimeZone()->getTimeZone()
332 : new \DateTimeZone('UTC')
333 );
334 try
335 {
336 if ($instance = $this->getInstanceForDay($masterEventId, $excludeDate))
337 {
338 $this->getService()->deleteEvent(
339 $instance->id,
340 );
341 }
342 else
343 {
344 $result->addError(new Main\Error("Instances for event not found", 404));
345 }
346 }
347 catch (ApiException $e)
348 {
349 if ((int)$e->getCode() !== 400 && (int)$e->getCode() !== 404)
350 {
351 throw $e;
352 }
353
354 $result->addError(new Main\Error($e->getMessage(), $e->getCode()));
355 }
356 catch (AuthException $exception)
357 {
358 $result->addError(new Main\Error($exception->getMessage(), $exception->getCode()));
359 }
360 }
361
362 return $result;
363 }
364
381 private function moveInstance(Event $event, EventContext $context): Result
382 {
383 $result = new Result();
384 $instance = null;
385 $masterLink = $context->getEventConnection();
386
387 if ($masterLink && $event->getOriginalDateFrom())
388 {
389 $instance = $this->getInstanceForDay(
390 $masterLink->getVendorEventId(),
391 $event->getOriginalDateFrom()->getDate()
392 );
393 }
394
395 if ($instance)
396 {
397 try
398 {
399 $dto = $this->getService()->updateEvent(
400 $instance->id,
401 $this->getEventConverter()->eventToDto($event),
402 );
403 if ($dto && !empty($dto->id))
404 {
405 $result->setData($this->prepareResultData($dto));
406 }
407 else
408 {
409 $result->addError(new Main\Error('Error of move instance', 400));
410 }
411 }
412 catch (NotFoundException $e)
413 {
414 $result->addError(new Main\Error('Instance not found'));
415 }
416 }
417 else
418 {
419 $result->addError(new Main\Error('Instance not found'));
420 }
421
422 return $result;
423 }
424
442 public function fetchSectionEvents(SectionConnection $sectionLink): Generator
443 {
444 foreach ($this->getService()->getCalendarDelta($sectionLink) as $deltaData)
445 {
446 $data = [];
447 if (!empty($deltaData[Helper::EVENT_TYPES['deleted']]))
448 {
450 $dto = $deltaData[Helper::EVENT_TYPES['deleted']];
451 $data[] = [
452 'type' => 'deleted',
453 'id' => $dto->id,
454 'version' => $dto->changeKey,
455 'etag' => $dto->etag,
456 ];
457
458
459// $this->processEventInstance($deltaData[Helper::EVENT_TYPES['deleted']], $sectionLink);
460 }
461 elseif (!empty($deltaData[Helper::EVENT_TYPES['single']]))
462 {
464 $dto = $deltaData[Helper::EVENT_TYPES['single']];
465 $data[] = [
466 'type' => 'single',
467 'event' => $this->context->getConverter()
468 ->convertEvent($dto, $sectionLink->getSection()),
469 'id' => $dto->id,
470 'version' => $dto->changeKey,
471 'etag' => $dto->etag,
472 'data' => $this->prepareCustomData($dto),
473 ];
474 }
475 elseif (!empty($deltaData[Helper::EVENT_TYPES['series']]))
476 {
477 $data = $this->prepareSeries($deltaData, $sectionLink);
478
479 }
480
481 if ($data)
482 {
483 yield $data;
484 }
485 }
486 }
487
508 public function saveRecurrence(
509 SyncEvent $recurrenceEvent,
510 SectionConnection $sectionConnection,
511 Context $context
512 ): Result
513 {
514 $result = new Result();
515
516 if ($recurrenceEvent->getEventConnection())
517 {
518 try
519 {
520 $masterResult = $this->updateRecurrenceInstance($recurrenceEvent, $context);
521 }
522 catch(Calendar\Sync\Exceptions\NotFoundException $e)
523 {
524 $this->getEventConnectionMapper()->delete($recurrenceEvent->getEventConnection());
525 $recurrenceEvent->setEventConnection(null);
526 return $this->saveRecurrence($recurrenceEvent, $sectionConnection, $context);
527 }
528 }
529 else
530 {
531 $masterResult = $this->createRecurrenceInstance($recurrenceEvent, $sectionConnection, $context);
532 }
533
534 if (!$masterResult->isSuccess())
535 {
536 $result->addErrors($masterResult->getErrors());
537 return $result;
538 }
539
540 if ($recurrenceEvent->getEventConnection())
541 {
542 $recurrenceEvent->getEventConnection()
543 ->setLastSyncStatus(Calendar\Sync\Dictionary::SYNC_STATUS['success']);
544 }
545 $recurrenceEvent
546 ->setAction(Calendar\Sync\Dictionary::SYNC_STATUS['success']);
547
548 $excludes = $this->getExcludedDatesCollection($recurrenceEvent);
549 if ($recurrenceEvent->getInstanceMap())
550 {
552 foreach ($recurrenceEvent->getInstanceMap()->getCollection() as $instance)
553 {
554 if ($instance->getEvent()->getOriginalDateFrom() === null)
555 {
556 $result->addError(
557 new Main\Error('Instance is invalid - there is not original date from. ['.$instance->getEvent()->getId().']', 400));
558 continue;
559 }
560
561 if ($instance->getEventConnection())
562 {
563 try
564 {
565 $instanceResult = $this->updateRecurrenceInstance($instance, $context, $recurrenceEvent->getEventConnection());
566 }
567 catch(Calendar\Sync\Exceptions\NotFoundException $e)
568 {
569 $this->getEventConnectionMapper()->delete($instance->getEventConnection());
570 $instance->setEventConnection(null);
571 $instanceResult = $this->createRecurrenceInstance(
572 $instance,
573 $sectionConnection,
574 $context,
575 $recurrenceEvent->getEventConnection()
576 );
577 }
578 }
579 else
580 {
581 $instanceResult = $this->createRecurrenceInstance(
582 $instance,
583 $sectionConnection,
584 $context,
585 $recurrenceEvent->getEventConnection()
586 );
587 }
588 $excludes->removeDateFromCollection($instance->getEvent()->getOriginalDateFrom());
589 if (
590 $instance->getEvent()->getStart()->format('Ymd')
591 !== $instance->getEvent()->getOriginalDateFrom()->format('Ymd')
592 )
593 {
594 $excludes->removeDateFromCollection($instance->getEvent()->getStart());
595 }
596 if (!$instanceResult->isSuccess())
597 {
598 $result->addErrors($instanceResult->getErrors());
599 }
600 if ($instance->getEventConnection())
601 {
602 $instance->getEventConnection()->setLastSyncStatus(Calendar\Sync\Dictionary::SYNC_STATUS['success']);
603 }
604 }
605 }
606
607 if ($excludes->count() > 0)
608 {
609 $context = (new EventContext())->setEventConnection($recurrenceEvent->getEventConnection());
611 foreach ($excludes as $excludedDate)
612 {
613 $context->add('sync', 'excludeDate', $excludedDate);
614 $this->deleteInstance($recurrenceEvent->getEvent(), $context);
615 }
616 }
617
618 return $result;
619 }
620
624 private function getEventConnectionMapper(): Core\Mappers\EventConnection
625 {
626 if (empty($this->eventConnectionMapper))
627 {
628 $this->eventConnectionMapper = new Core\Mappers\EventConnection();
629 }
630
631 return $this->eventConnectionMapper;
632 }
633
654 public function createRecurrence(
655 SyncEvent $recurrenceEvent,
656 SectionConnection $sectionConnection,
657 Context $context
658 ): Result
659 {
660 return $this->saveRecurrence($recurrenceEvent, $sectionConnection, $context);
661 }
662
675 public function updateRecurrence(
676 SyncEvent $recurrenceEvent,
677 SectionConnection $sectionConnection,
678 Context $context
679 ): Result
680 {
681 return $this->saveRecurrence($recurrenceEvent, $sectionConnection, $context);
682 }
683
692 private function prepareSeries($deltaData, SectionConnection $sectionLink): array
693 {
694 $result = [];
696 $masterDto = $deltaData[Helper::EVENT_TYPES['series']];
698 $exceptionList = $deltaData[Helper::EVENT_TYPES['exception']] ?? [];
699
700 $masterEvent = $this->context->getConverter()->convertEvent($masterDto, $sectionLink->getSection());
701
702 if (!empty($exceptionList)) {
703 $exceptionsDates = array_map(function (EventDto $exception) {
704 return (new Main\Type\DateTime(
705 $exception->start->dateTime,
706 $this->helper::TIME_FORMAT_LONG,
707 new DateTimeZone($exception->start->timeZone),
708 ))->format('d.m.Y');
709 }, $exceptionList
710 );
711
712 $excludeCollection = new Core\Event\Properties\ExcludedDatesCollection($exceptionsDates);
713 $masterEvent->setExcludedDateCollection($excludeCollection);
714 }
715
716 $result[] = [
717 'type' => 'master',
718 'id' => $masterDto->id,
719 'event' => $masterEvent,
720 'version' => $masterDto->changeKey,
721 'etag' => $masterDto->etag,
722 'data' => $this->prepareCustomData($masterDto),
723 ];
724
725 foreach ($exceptionList as $exception) {
726 $event = $this->context->getConverter()->convertEvent($exception, $sectionLink->getSection());
727 $result[] = [
728 'type' => 'exception',
729 'event' => $event,
730 'id' => $exception->id,
731 'version' => $exception->changeKey,
732 'etag' => $exception->etag,
733 'recurrence' => $exception->seriesMasterId,
734 'data' => $this->prepareCustomData($exception),
735 ];
736 }
737
738 return $result;
739 }
740
757 private function getInstanceForDay(string $eventId, Main\Type\Date $dayStart): ? EventDto
758 {
759 $dateFrom = clone $dayStart;
760 if ($dateFrom instanceof Main\Type\DateTime)
761 {
762 $dateFrom->setTime(0,0);
763 }
764 $dateTo = clone $dateFrom;
765 $dateTo->add('1 day');
766
767 $instances = $this->getService()->getEventInstances([
768 'filter' => [
769 'event_id' => $eventId,
770 'from' => $dateFrom->format('c'), //($this->helper::TIME_FORMAT_LONG),
771 'to' => $dateTo->format('c'), //($this->helper::TIME_FORMAT_LONG),
772 ],
773 ]);
774
775 return $instances ? $instances[0] : null;
776 }
777
781 private function getEventConverter(): EventConverter
782 {
783 if (empty($this->eventConverter))
784 {
785 $this->eventConverter = new EventConverter();
786 }
787
788 return $this->eventConverter;
789 }
790
799 private function getService(): VendorSyncService
800 {
801 return $this->context->getVendorSyncService();
802 }
803
810 private function prepareCustomData(EventDto $dto): array
811 {
812 $result = [];
813 if (!empty($dto->location))
814 {
815 $result['location'] = $dto->location->toArray(true);
816 }
817 if (!empty($dto->locations))
818 {
819 foreach ($dto->locations as $location)
820 {
821 $result['locations'][] = $location->toArray(true);
822 }
823 }
824
825 if (!empty($dto->attendees))
826 {
827 $result['attendees'] = [];
828 foreach ($dto->attendees as $attendee)
829 {
830 $result['attendees'][] = $attendee->toArray(true);
831 }
832 }
833
834 return $result;
835 }
836
843 private function enrichVendorData(EventDto $dto, EventConnection $link)
844 {
845 // TODO: add info about attendees, locations and any others
846 }
847
861 private function createRecurrenceInstance(
862 SyncEvent $syncEvent,
863 SectionConnection $sectionConnection,
864 Context $context,
865 EventConnection $masterLink = null
866 ): Result
867 {
868 try
869 {
870 $eventContext = new EventContext();
871 $eventContext->merge($context);
872 if ($masterLink)
873 {
874 $eventContext->setEventConnection($masterLink);
875 $event = (new Core\Builders\EventCloner($syncEvent->getEvent()))->build();
876 $result = $this->createInstance($event, $eventContext);
877 }
878 else
879 {
880 $eventContext->setSectionConnection($sectionConnection);
881 $event = $this->prepareMasterEvent($syncEvent);
882 $result = $this->create($event, $eventContext);
883 }
884 if ($result->isSuccess())
885 {
886 if (!$syncEvent->getEvent()->isDeleted())
887 {
888 if (!empty($result->getData()['event']['id']))
889 {
890 $link = (new EventConnection())
891 ->setEvent($event)
892 ->setConnection($sectionConnection->getConnection())
893 ->setVersion($event->getVersion())
894 ->setVendorEventId($result->getData()['event']['id'])
895 ->setEntityTag($result->getData()['event']['etag'])
896 ->setVendorVersionId($result->getData()['event']['version'])
897 ->setRecurrenceId($result->getData()['event']['recurrence'] ?? null)
898 ->setLastSyncStatus(Calendar\Sync\Dictionary::SYNC_STATUS['success'])
899 ;
900 $syncEvent
901 ->setEventConnection($link)
902 ->setAction(Calendar\Sync\Dictionary::SYNC_STATUS['success']);
903 }
904 else
905 {
906 $errMessage = 'Unknown error of creating recurrence '
907 . ($masterLink ? 'master' : 'instance');
908 $result->addError(new Main\Error($errMessage, 400, ['data' => $result->getData()]));
909 }
910
911 }
912 else
913 {
914 $syncEvent->setAction(Calendar\Sync\Dictionary::SYNC_EVENT_ACTION['delete']);
915 }
916 }
917
918 return $result;
919 }
920 catch (Core\Base\BaseException $e)
921 {
922 return (new Result())->addError(new Main\Error($e->getMessage()));
923 }
924 }
925
939 private function updateRecurrenceInstance(
940 SyncEvent $syncEvent,
941 Context $context,
942 EventConnection $masterLink = null
943 ): Result
944 {
945 $eventContext = new EventContext();
946 $eventContext->merge($context);
947 if ($masterLink)
948 {
949 $eventContext
950 ->setEventConnection($masterLink)
951 ->add('sync', 'instanceLink', $syncEvent->getEventConnection())
952 ;
953 $result = $this->updateInstance($syncEvent->getEvent(), $eventContext);
954 }
955 else
956 {
957 $eventContext->setEventConnection($syncEvent->getEventConnection());
958 $result = $this->update($syncEvent->getEvent(), $eventContext);
959 }
960 if ($result->isSuccess())
961 {
962 $syncEvent->getEventConnection()
963 ->setEntityTag($result->getData()['event']['etag'])
964 ->setVendorVersionId($result->getData()['event']['version'])
965 ;
966 }
967
968 return $result;
969 }
970
976 private function getExcludedDatesCollection(SyncEvent $recurrenceEvent): Core\Event\Properties\ExcludedDatesCollection
977 {
978 $excludes = new Core\Event\Properties\ExcludedDatesCollection();
979
981 foreach ($recurrenceEvent->getEvent()->getExcludedDateCollection() as $item)
982 {
983 $excludes->add($item);
984 }
985 return $excludes;
986 }
987
993 private function prepareMasterEvent(SyncEvent $syncEvent): Event
994 {
995 $event = (new Core\Builders\EventCloner($syncEvent->getEvent()))->build();
996 (new Calendar\Sync\Util\ExcludeDatesHandler())->prepareEventExcludeDates($event, $syncEvent->getInstanceMap());
997
998 return $event;
999 }
1000}
updateRecurrence(SyncEvent $recurrenceEvent, SectionConnection $sectionConnection, Context $context)
createRecurrence(SyncEvent $recurrenceEvent, SectionConnection $sectionConnection, Context $context)
createInstance(Core\Event\Event $event, EventContext $context)
update(Core\Event\Event $event, EventContext $context)
updateInstance(Event $event, EventContext $context)
deleteInstance(Event $event, EventContext $context)
add(string $type, string $property, $value)
Definition context.php:53
create(Event $event, EventContext $context)
addError(Main\Error $error)
Definition error.php:22