403Webshell
Server IP : 80.87.202.40  /  Your IP : 216.73.216.169
Web Server : Apache
System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64
User : bitrix ( 600)
PHP Version : 8.2.27
Disable Function : NONE
MySQL : OFF |  cURL : ON |  WGET : ON |  Perl : ON |  Python : OFF |  Sudo : ON |  Pkexec : ON
Directory :  /home/bitrix/ext_www/ilovecveti.ru/bitrix/modules/calendar/lib/sync/managers/

Upload File :
current_dir [ Writeable] document_root [ Writeable]

 

Command :


[ Back ]     

Current File : /home/bitrix/ext_www/ilovecveti.ru/bitrix/modules/calendar/lib/sync/managers/datasyncmanager.php
<?php

namespace Bitrix\Calendar\Sync\Managers;

use Bitrix\Calendar\Core\Builders\EventBuilderFromArray;
use Bitrix\Calendar\Core\Event\Event;
use Bitrix\Calendar\Core\Managers\EventOriginalRecursion;
use Bitrix\Calendar\Core\Mappers\Factory;
use Bitrix\Calendar\Integration\Pull\PushCommand;
use Bitrix\Calendar\Internals\Counter\CounterService;
use Bitrix\Calendar\Internals\Counter\Event\EventDictionary;
use Bitrix\Calendar\Internals\EventConnectionTable;
use Bitrix\Calendar\Internals\EventTable;
use Bitrix\Calendar\Sync\Builders\BuilderConnectionFromDM;
use Bitrix\Calendar\Sync\Connection\Connection;
use Bitrix\Calendar\Sync\Dictionary;
use Bitrix\Calendar\Sync\Icloud;
use Bitrix\Calendar\Rooms;
use Bitrix\Calendar\Internals\SectionConnectionTable;
use Bitrix\Calendar\Sync\Util\EventDescription;
use Bitrix\Calendar\Sync\Util\RequestLogger;
use Bitrix\Calendar\Sync\Util\Result;
use Bitrix\Calendar\UserField\ResourceBooking;
use Bitrix\Calendar\Util;
use Bitrix\Dav\Internals\DavConnectionTable;
use Bitrix\Dav\Internals\EO_DavConnection;
use Bitrix\Dav\Internals\EO_DavConnection_Collection;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\ArgumentNullException;
use Bitrix\Main\DI\ServiceLocator;
use Bitrix\Main\Loader;
use Bitrix\Main\LoaderException;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ObjectException;
use Bitrix\Main\ObjectPropertyException;
use Bitrix\Main\ORM\Query\Query;
use Bitrix\Main\SystemException;
use Bitrix\Main\Type\DateTime;

class DataSyncManager
{
	private const ENTITY_TYPE = 'user';
	private const MAX_NUMBER = 5;
	private const TIME_SLICE = 2600000;

	/** @var Factory $mapperHelper */
	private $mapperFactory;

	/**
	 * @throws \Bitrix\Main\ObjectNotFoundException|\Psr\Container\NotFoundExceptionInterface
	 */
	private function __construct()
	{
		$this->mapperFactory = ServiceLocator::getInstance()->get('calendar.service.mappers.factory');
	}

	/**
	 * @return string
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws \Bitrix\Main\LoaderException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 * @throws \Bitrix\Main\SystemException
	 * @throws \CDavArgumentNullException
	 */
	public static function dataSyncAgent(): string
	{
		(new self())->dataSync();

		return "\\Bitrix\\Calendar\\Sync\\Managers\\DataSyncManager::dataSyncAgent();";
	}

	/**
	 * @return DataSyncManager
	 */
	public static function createInstance(): DataSyncManager
	{
		return new self();
	}

	/**
	 * @param $userId
	 *
	 * @return bool
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws \Bitrix\Main\LoaderException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 * @throws \Bitrix\Main\SystemException
	 * @throws \CDavArgumentNullException
	 */
	public function dataSync($userId = null): bool
	{
		if (!Loader::includeModule('dav') || !Loader::includeModule('calendar'))
		{
			return true;
		}

		$userIds = [];
		$connections = $this->getConnections($userId);
		foreach ($connections as $connection)
		{
			$connection = $this->createConnectionObject($connection);
			$ownerId = $connection->getOwner()?->getId();
			if ($ownerId)
			{
				$userIds[] = $ownerId;
			}
			$result = $this->syncConnection($connection);
			if ($result->isSuccess())
			{
				\CDavConnection::SetLastResult($connection->getId(), $result->getData()['lastResult']);
				Util::addPullEvent(
					PushCommand::RefreshSyncStatus,
					$connection->getOwner()->getId(),
					[
						'syncInfo' => [
							$connection->getAccountType() => [
								'status' => $result->getData()['syncStatus'],
								'type' => $connection->getAccountType(),
								'connected' => true,
								'id' => $connection->getId(),
								'syncOffset' => 0
							],
						],
						'requestUid' => Util::getRequestUid(),
					]
				);
			}
		}

		CounterService::addEvent(EventDictionary::SYNC_CHANGED, ['user_ids' => $userIds]);

		return true;
	}

	/**
	 *
	 * @param Connection $connection
	 *
	 * @return Result
	 * @throws LoaderException
	 * @throws SystemException
	 * @throws ArgumentException
	 * @throws ArgumentNullException
	 * @throws ObjectPropertyException
	 */
	private function syncConnection(Connection $connection): Result
	{
		$result = new Result();
		$logger = null;

		if (
			RequestLogger::isEnabled()
		)
		{
			$logger = new RequestLogger($connection->getOwner()->getId(), $connection->getVendor()->getCode());
		}

		$client = $this->initClient($connection);

		$calendarsList = $client->GetCalendarList($connection->getServer()->getBasePath(), null);

		if ($client->getError())
		{
			$error = $this->processError($client->getError());
			$result->setData([
				'lastResult' => $error,
				'syncStatus' => false,
			]);

			return $result;
		}

		if (!$calendarsList || !is_array($calendarsList))
		{
			$result->setData([
				'lastResult' => '[204] No Content',
				'syncStatus' => true,
			]);

			return $result;
		}

		$calendarsList = $this->syncSections($connection, $calendarsList);

		foreach ($calendarsList as $calendar)
		{
			[$events, $eventsMap] = $this->getEventsToSync(
				$connection,
				$client,
				$calendar,
				$logger
			);

			if ($client->getError())
			{
				$error = $this->processError($client->getError());
				$result->setData([
					'lastResult' => $error,
					'syncStatus' => false,
				]);

				return $result;
			}

			foreach ($events as $event)
			{
				$this->modifyEvent(
					$connection,
					$client,
					$event,
					$eventsMap,
					$calendar
				);
			}
		}

		$result->setData([
			'lastResult' => '[200] OK',
			'syncStatus' => true,
		]);

		return $result;
	}

	/**
	 * @param Connection $connection
	 * @param \CDavGroupdavClientCalendar $client
	 * @param array $event
	 * @param array $eventsMap
	 * @param array $calendar
	 *
	 * @return void
	 * @throws SystemException
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 * @throws LoaderException
	 */
	private function modifyEvent(
		Connection $connection,
		\CDavGroupdavClientCalendar $client,
		array $event,
		array $eventsMap,
		array $calendar
	): void
	{
		if (!array_key_exists($event['href'], $eventsMap))
		{
			return;
		}

		$eventId = null;
		$existEvent = $eventsMap[$event['href']];
		if (empty($event['calendar-data']))
		{
			return;
		}

		[$event, $exDate] = $this->mergeExternalEventWithLocal($existEvent, $event, $client);

		if (!empty($event['calendar-data']) && is_array($event['calendar-data']))
		{
			$eventId = $this->modifySingleEvent(
				$connection,
				$event['calendar-data'],
				[
					'SECTION_ID' => $calendar['SECTION_ID'],
					'VERSION' => $existEvent['VERSION'],
					'EVENT_CONNECTION_ID' => $eventsMap[$event['href']]['EVENT_CONNECTION_ID'],
				]
			);
		}

		if ($eventId && !empty($event['calendar-data-ex']) && is_array($event['calendar-data-ex']))
		{
			$this->modifyRecurrenceEvent(
				$connection,
				$event['calendar-data-ex'],
				[
					'PARENT_ID' => $eventId,
					'SECTION_ID' => $calendar['SECTION_ID'],
					'PERIOD_UNTIL' => $event['calendar-data']['PROPERTY_PERIOD_UNTIL'] ?? null,
				]
			);
		}
		else if ($exDate && $event['calendar-data'] && $event['calendar-data']['ID'])
		{
			$this->deleteDuplicateExDates(
				$exDate,
				$event['calendar-data']['DATE_FROM'],
				$event['calendar-data']['ID'],
				$connection->getOwner()->getId(),
			);
		}
	}

	/**
	 * @param $exDate
	 * @param $dateFrom
	 * @param $eventId
	 * @param $userId
	 *
	 * @return void
	 * @throws SystemException
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 * @throws \Exception
	 */
	private function deleteDuplicateExDates($exDate, $dateFrom, $eventId, $userId): void
	{
		global $DB;
		$exDates = \CCalendarEvent::GetExDate($exDate);
		$dtStartTimestamp = \CCalendar::Timestamp($dateFrom, false);
		$needToUpdate = false;
		foreach ($exDates as $date)
		{
			$dateTs = \CCalendar::Timestamp($date, false);
			if ($dateTs < $dtStartTimestamp)
			{
				$needToUpdate = true;
				break;
			}
		}

		if ($needToUpdate)
		{
			$childEvents = EventConnectionTable::query()
				->setSelect([
		            'EVENT_ID',
		            'VERSION',
		            'DATE_FROM' => 'EVENT.DATE_FROM',
		            'EVENT_CONNECTION_ID' => 'ID',
				])
				->where('EVENT.RECURRENCE_ID', $eventId)
				->where('EVENT.DELETED', 'N')
				->where('EVENT.OWNER_ID', $userId)
				->exec()
			;

			while ($child = $childEvents->fetch())
			{
				$eventIdList = $this->getAllEventByParentId($child['EVENT_ID']);

				EventTable::updateMulti($eventIdList, ['DELETED' => 'Y']);

				EventConnectionTable::delete($child['EVENT_CONNECTION_ID']);
			}
		}
	}

	/**
	 * @param Connection $connection
	 * @param array $event
	 * @param array $additionalParams
	 *
	 * @return int|null
	 * @throws SystemException
	 * @throws \Bitrix\Calendar\Core\Base\BaseException
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 * @throws \Exception
	 */
	private function modifySingleEvent(Connection $connection, array $event, array $additionalParams): ?int
	{
		$eventObject = $this->prepareEventParams(
			$event,
			$additionalParams['SECTION_ID'],
			$connection->getOwner()->getId()
		);

		if ($eventObject->getId())
		{
			$result = $this->mapperFactory->getEvent()->update($eventObject, [
				'userId' => $connection->getOwner()->getId(),
				'bAffectToDav' => false, // Used to prevent synchro with calDav again
				'bSilentAccessMeeting' => true,
				'autoDetectSection' => false,
				'originalFrom' => $connection->getVendor()->getCode(),
			]);
		}
		else
		{
			$result = $this->mapperFactory->getEvent()->create($eventObject, [
				'userId' => $connection->getOwner()->getId(),
				'bAffectToDav' => false, // Used to prevent synchro with calDav again
				'bSilentAccessMeeting' => true,
				'autoDetectSection' => false,
				'originalFrom' => $connection->getVendor()->getCode(),
			]);
		}

		if ($result && $result->getId())
		{
			$data = [];
			// Prepare Data with outer params
			if (!empty($event['ATTENDEE']) || !empty($event['ORGANIZER_ENTITY']))
			{
				$this->parseInvitedAttendees($event, $data);
			}
			if (!empty($event['ATTACH']))
			{
				$this->parseAttachments($event, $data);
			}
			if (!empty($event['URL']))
			{
				$data['URL'] = $event['URL'];
			}

			if (!empty($additionalParams['EVENT_CONNECTION_ID']))
			{
				EventConnectionTable::update($additionalParams['EVENT_CONNECTION_ID'], [
					'SYNC_STATUS' => Dictionary::SYNC_STATUS['success'],
					'ENTITY_TAG' => $event['MODIFICATION_LABEL'] ?? null,
					'VERSION' => (string)($additionalParams['VERSION'] ?? null),
					'VENDOR_VERSION_ID' => (string)($additionalParams['VERSION'] ?? null),
					'DATA' => $data,
				]);
			}
			else
			{
				EventConnectionTable::add([
					'EVENT_ID' => (int)$result->getId(),
					'CONNECTION_ID' => $connection->getId(),
					'VENDOR_EVENT_ID' => $event['XML_ID'] ?? null,
					'SYNC_STATUS' => Dictionary::SYNC_STATUS['success'],
					'ENTITY_TAG' => $event['MODIFICATION_LABEL'] ?? null,
					'VERSION' => (string)($additionalParams['VERSION'] ?? null),
					'VENDOR_VERSION_ID' => (string)($additionalParams['VERSION'] ?? null),
					'DATA' => $data,
				]);
			}

			return (int)$result->getId();
		}

		return null;
	}

	/**
	 * @param Connection $connection
	 * @param array $importInstances
	 * @param array $additionalParams
	 *
	 * @return void
	 * @throws SystemException
	 * @throws \Bitrix\Calendar\Core\Base\BaseException
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 * @throws \Exception
	 */
	private function modifyRecurrenceEvent(
		Connection $connection,
		array $importInstances,
		array $additionalParams
	): void
	{
		[$importInstances, $importedInstancesDates] = $this->prepareInstanceEvents($importInstances);
		$parentEvent = \CCalendarEvent::GetById($additionalParams['PARENT_ID'], true, true);

		if ($parentEvent && \CCalendarEvent::CheckRecurcion($parentEvent))
		{
			$exDates = \CCalendarEvent::GetExDate($parentEvent['EXDATE']);
			$localInstances = EventConnectionTable::query()
				->setSelect([
		            'EVENT_ID',
		            'VERSION',
		            'DATE_FROM' => 'EVENT.DATE_FROM',
		            'EVENT_CONNECTION_ID' => 'ID',
		            'MEETING' => 'EVENT.MEETING',
					'IS_MEETING' => 'EVENT.IS_MEETING',
					'MEETING_HOST' => 'EVENT.MEETING_HOST',
 		            'ATTENDEES_CODES' => 'EVENT.ATTENDEES_CODES',
		            'ACCESSIBILITY' => 'EVENT.ACCESSIBILITY',
	            ])
				->where('EVENT.RECURRENCE_ID', $additionalParams['PARENT_ID'])
				->where('EVENT.DELETED', 'N')
				->where('EVENT.OWNER_ID', $connection->getOwner()->getId())
				->whereNot('EVENT.MEETING_STATUS', 'N')
				->exec()
			;

			$importedInstancesCount = count($importInstances);
			while ($localInstance = $localInstances->fetch())
			{
				$isActive = false;
				$localInstanceDate = \CCalendar::Date(\CCalendar::Timestamp($localInstance['DATE_FROM']), false);
				for ($i = 0; $i < $importedInstancesCount; $i++)
				{
					if ($localInstanceDate === $importedInstancesDates[$i])
					{
						$this->mergeInstanceParams($importInstances[$i], $localInstance);
						$isActive = true;

						break;
					}
				}

				if (!$isActive)
				{
					\CCalendarEvent::Delete([
						'id' => $localInstance['EVENT_ID'],
						'bMarkDeleted' => true,
						'bAffectToDav' => false,
						'originalFrom' => $connection->getVendor()->getCode(),
						'userId' => $connection->getOwner()->getId(),
					]);

					EventConnectionTable::delete($localInstance['EVENT_CONNECTION_ID']);
				}
			}

			foreach ($importInstances as $instance)
			{
				if (
					$additionalParams['PERIOD_UNTIL']
					&& \CCalendar::Timestamp($instance['DATE_FROM']) > \CCalendar::Timestamp($additionalParams['PERIOD_UNTIL'])
				)
				{
					continue;
				}
				$instance = $this->addParentDataToInstance($instance, $parentEvent);

				if ($instance && $instance['RECURRENCE_ID'])
				{
					unset($instance['RRULE'], $instance['EXDATE']);

					$instanceId = $this->modifySingleEvent(
						$connection,
						$instance,
						[
							'SECTION_ID' => $additionalParams['SECTION_ID'],
							'VERSION' => $instance['VERSION'] ?? 1,
							'EVENT_CONNECTION_ID' => $instance['EVENT_CONNECTION_ID'] ?? 0,
						]
					);

					if (!empty($instance['RECURRENCE_ID_DATE']))
					{
						$exDates[] = \CCalendar::Date(\CCalendar::Timestamp($instance['RECURRENCE_ID_DATE']), false);

						$originalRecursionId = (int)($parentEvent['ORIGINAL_RECURSION_ID'] ?? $parentEvent['ID']);

						(new EventOriginalRecursion())->add($instanceId, $originalRecursionId);
					}
				}
			}
			$exDate = \CCalendarEvent::SetExDate($exDates);
			$eventIdList = $this->getAllEventByParentId($parentEvent['ID']);

			EventTable::updateMulti($eventIdList, ['EXDATE' => $exDate]);
		}
	}

	/**
	 * @param int $parentId
	 *
	 * @return array
	 * @throws ArgumentException
	 * @throws ObjectPropertyException
	 * @throws SystemException
	 */
	private function getAllEventByParentId(int $parentId): array
	{
		$eventIdList = EventTable::query()
			->setSelect(['ID'])
			->where('PARENT_ID', $parentId)
			->exec()
			->fetchAll()
		;

		return array_map(static function($event){
			return (int)$event['ID'];
		}, $eventIdList);
	}

	/**
	 * @param $userId
	 *
	 * @return EO_DavConnection_Collection
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 * @throws \Bitrix\Main\SystemException
	 */
	private function getConnections($userId = null): EO_DavConnection_Collection
	{
		$query = DavConnectionTable::query()
			->setSelect(['*'])
			->whereIn('ACCOUNT_TYPE', [Icloud\Helper::ACCOUNT_TYPE])
			->where('ENTITY_TYPE', self::ENTITY_TYPE)
			->where('IS_DELETED', 'N')
			->setLimit(self::MAX_NUMBER)
			->setOrder(['SYNCHRONIZED' => 'ASC'])
		;
		if ($userId)
		{
			$query->where('ENTITY_ID', $userId);
		}

		return $query->exec()->fetchCollection();
	}

	/**
	 * @param Connection $connection
	 *
	 * @return \CDavGroupdavClientCalendar
	 */
	private function initClient(Connection $connection): \CDavGroupdavClientCalendar
	{
		$client = new \CDavGroupdavClientCalendar(
			$connection->getServer()->getScheme(),
			$connection->getServer()->getHost(),
			$connection->getServer()->getPort(),
			$connection->getServer()->getUserName(),
			$connection->getServer()->getPassword()
		);
		if (\CDav::UseProxy())
		{
			$proxy = \CDav::GetProxySettings();
			$client->SetProxy(
				$proxy['PROXY_SCHEME'],
				$proxy['PROXY_HOST'],
				$proxy['PROXY_PORT'],
				$proxy['PROXY_USERNAME'],
				$proxy['PROXY_PASSWORD']
			);
		}

		return $client;
	}

	/**
	 * @param Connection $connection
	 * @param array $calendars
	 *
	 * @return array
	 * @throws SystemException
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws ObjectException
	 * @throws \Bitrix\Main\ObjectNotFoundException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 * @throws \Exception
	 */
	private function syncCalendarSections(
		Connection $connection,
		array $calendars
	): array
	{
		$calendarNames = [];
		$result = [];

		foreach ($calendars as $calendar)
		{
			if (isset($calendar['TYPE']) && $calendar['TYPE'] === 'VEVENT')
			{
				$calendarNames[$calendar['XML_ID']] = $calendar;
			}
		}

		$sectionsLink = SectionConnectionTable::query()
			->setSelect([
	            'SECTION_CONNECTION_ID' => 'ID',
	            'NAME' => 'SECTION.NAME',
	            'EXTERNAL_TYPE' => 'SECTION.EXTERNAL_TYPE',
	            'VENDOR_SECTION_ID',
	            'VERSION_ID',
	            'SECTION_ID',
			])
			->where('SECTION.CAL_TYPE', self::ENTITY_TYPE)
			->where('SECTION.OWNER_ID', $connection->getOwner()->getId())
			->where('CONNECTION_ID', $connection->getId())
			->exec()
		;

		while ($link = $sectionsLink->fetch())
		{
			$xmlId = $link['VENDOR_SECTION_ID'];
			if (empty($xmlId))
			{
				continue;
			}

			if (!array_key_exists($xmlId, $calendarNames))
			{
				$section = $this->mapperFactory->getSection()->getById($link['SECTION_ID']);
				if ($section)
				{
					(new IncomingManager($connection))->deleteSection($section, $link['SECTION_CONNECTION_ID']);
				}
			}
			else
			{
				if (($link['VERSION_ID'] ?? null) !== ($calendarNames[$xmlId]['MODIFICATION_LABEL'] ?? null))
				{
					$fields =  [
						'ID' => (int)($link['SECTION_ID'] ?? null),
						'NAME' => isset($link['EXTERNAL_TYPE']) && $link['EXTERNAL_TYPE'] === 'local'
							? $link['NAME']
							: $calendarNames[$xmlId]['NAME']
						,
						'DESCRIPTION' => $calendarNames[$xmlId]['DESCRIPTION'] ?? null,
						'COLOR' => $calendarNames[$xmlId]['COLOR'] ?? null,
					];

					\CCalendarSect::Edit([
						'arFields' => $fields,
						'bAffectToDav' => false,
						'originalFrom' => $connection->getVendor()->getCode(),
					]);

					SectionConnectionTable::update((int)$link['SECTION_CONNECTION_ID'], [
							'LAST_SYNC_DATE' => new DateTime(),
							'LAST_SYNC_STATUS' => Dictionary::SYNC_STATUS['success'],
							'VERSION_ID' => $calendarNames[$xmlId]['MODIFICATION_LABEL'] ?? null,
						]
					);

					$result[] = [
						'XML_ID' => $xmlId,
						'SECTION_ID' => $link['SECTION_ID'],
						'SECTION_CONNECTION_ID' => $link['SECTION_CONNECTION_ID'],
						'SYNC_TOKEN' => $link['VERSION_ID'] ?? null,
						'IS_NEW' => false,
					];
				}

				unset($calendarNames[$xmlId]);
			}
		}

		foreach ($calendarNames as $curXmlId => $calendar)
		{
			$fields = [
				'CAL_TYPE' => self::ENTITY_TYPE,
				'OWNER_ID' => $connection->getOwner()->getId(),
				'CREATED_BY' => $connection->getOwner()->getId(),
				'NAME' => $calendar['NAME'],
				'DESCRIPTION' => $calendar['DESCRIPTION'],
				'COLOR' => $calendar['COLOR'],
				'EXPORT' => ['ALLOW' => false],
				'EXTERNAL_TYPE' => $connection->getVendor()->getCode(),
			];

			$id = (int)\CCalendarSect::Edit([
				'arFields' => $fields,
				'bAffectToDav' => false,
				'originalFrom' => $connection->getVendor()->getCode(),
			]);

			if ($id)
			{
				$linkId = SectionConnectionTable::add([
                    'SECTION_ID' => $id,
                    'CONNECTION_ID' => $connection->getId(),
                    'VENDOR_SECTION_ID' => $curXmlId,
                    'ACTIVE' => 'Y',
                    'LAST_SYNC_DATE' => new DateTime(),
                    'LAST_SYNC_STATUS' => Dictionary::SYNC_STATUS['success'],
                    'VERSION_ID' => $calendar['MODIFICATION_LABEL'],
                ]);

				$result[] = [
					'XML_ID' => $curXmlId,
					'SECTION_ID' => $id,
					'SECTION_CONNECTION_ID' => $linkId->getId(),
					'SYNC_TOKEN' => $calendar['MODIFICATION_LABEL'],
					'IS_NEW' => true,
				];
			}
		}

		return $result;
	}

	/**
	 * @param Connection $connection
	 * @param int $sectionId
	 * @param array $events
	 *
	 * @return array
	 * @throws SystemException
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 * @throws \Exception
	 */
	private function syncCalendarEvents(
		Connection $connection,
		int $sectionId,
		array $events
	): array
	{
		$linksMap = [];
		$result = [];

		$eventsLink = EventConnectionTable::query()
			->setSelect([
	            'EVENT_CONNECTION_ID' => 'ID',
	            'VENDOR_EVENT_ID',
	            'VERSION',
	            'ENTITY_TAG',
	            'EVENT_ID',
				'IS_MEETING' => 'EVENT.IS_MEETING',
	            'MEETING' => 'EVENT.MEETING',
	            'EXDATE' => 'EVENT.EXDATE',
	            'EVENT_PARENT_ID' => 'EVENT.PARENT_ID',
	            'EVENT_TYPE' => 'EVENT.EVENT_TYPE',
	            'EVENT_NAME' => 'EVENT.NAME',
	            'EVENT_OWNER_ID' => 'EVENT.OWNER_ID',
				'EVENT_DATE_FROM' => 'EVENT.DATE_FROM',
				'EVENT_DATE_TO' => 'EVENT.DATE_TO',
				'EVENT_TZ_FROM' => 'EVENT.TZ_FROM',
				'EVENT_TZ_TO' => 'EVENT.TZ_TO',
				'EVENT_VERSION' => 'EVENT.VERSION',
	            'ATTENDEES_CODES' => 'EVENT.ATTENDEES_CODES',
	            'ACCESSIBILITY' => 'EVENT.ACCESSIBILITY',
			])
			->where('EVENT.CAL_TYPE', self::ENTITY_TYPE)
			->where('EVENT.OWNER_ID', $connection->getOwner()->getId())
			->where('EVENT.SECTION_ID', $sectionId)
			->where('EVENT.DELETED', 'N')
			->where(Query::filter()
				->logic('or')
				->whereNot('EVENT.MEETING_STATUS', 'N')
				->whereNull('EVENT.MEETING_STATUS')
			)
			->whereNotNull('ENTITY_TAG')
			->exec()
		;

		while ($event = $eventsLink->fetch())
		{
			$linksMap[$event['VENDOR_EVENT_ID']] = $event;
		}

		foreach ($events as $index => $event)
		{
			if (!empty($linksMap[$event['XML_ID']]))
			{
				$existEvent = $linksMap[$event['XML_ID']];

				if (($existEvent['ENTITY_TAG'] ?? null) === ($event['SYNC_TOKEN'] ?? null))
				{
					continue;
				}
				if ($this->isBlockedChange($existEvent, $event))
				{
					continue;
				}

				if ((int)$event['STATUS'] === 200)
				{
					if (($linksMap[$event['XML_ID']]['ENTITY_TAG'] ?? null) !== ($event['SYNC_TOKEN'] ?? null))
					{
						$result[] = $this->prepareExistedEventParams($event['XML_ID'], $linksMap[$event['XML_ID']]);
					}
					else
					{
						unset($events[$index]);
					}
				}
				else if ((int)$event['STATUS'] === 404)
				{
					\CCalendar::DeleteEvent(
						$linksMap[$event['XML_ID']]['EVENT_ID'],
						false,
						[
							'markDeleted' => true,
							'originalFrom' => $connection->getVendor()->getCode(),
							'checkPermissions' => false,
						]
					);

					EventConnectionTable::delete($linksMap[$event['XML_ID']]['EVENT_CONNECTION_ID']);
				}
			}
			else if (!empty($event['SYNC_TOKEN']) && (int)$event['STATUS'] === 200)
			{
				$result[] = $this->prepareExistedEventParams($event['XML_ID']);
			}
		}

		return $result;
	}

	/**
	 * @param array $existEvent
	 * @param array $importedEvent
	 *
	 * @return bool
	 *
	 */
	private function isBlockedChange(array $existEvent, array $importedEvent): bool
	{
		if ($existEvent['EVENT_TYPE'] === ResourceBooking::EVENT_LABEL)
		{
			return true;
		}

		if ($existEvent['EVENT_ID'] !== $existEvent['EVENT_PARENT_ID'])
		{
			return true;
		}

		return false;
	}

	/**
	 * @param array $event
	 * @param int $sectionId
	 * @param int $entityId
	 *
	 * @return Event
	 * @throws \Bitrix\Main\SystemException
	 */
	private function prepareEventParams(array $event, int $sectionId, int $entityId): Event
	{
		$fields = [
			'ID' => (int)($event['ID'] ?? null),
			'NAME' => $this->prepareName($event['NAME']),
			'CAL_TYPE' => self::ENTITY_TYPE,
			'DESCRIPTION' => $event['DESCRIPTION'] ?? '',
			'OWNER_ID' => $entityId,
			'CREATED_BY' => $entityId,
			'ATTENDEES_CODES' => ['U' . $entityId],
			'SECTIONS' => [$sectionId],
			'ACCESSIBILITY' => !empty($event['ID'])
				? $event['ACCESSIBILITY']
				: ($event['PROPERTY_ACCESSIBILITY'] ?? 'busy')
			,
			'IS_MEETING' => $event['IS_MEETING'] ? true : null,
			'IMPORTANCE' => $event['IMPORTANCE'] ?? 'normal',
			'REMIND' => is_array($event['REMIND'] ?? null) ? $event['REMIND'] : [],
			'RRULE' => is_array($event['RRULE'] ?? null) ? $event['RRULE'] : [],
			'VERSION' => (int)$event['VERSION'],
			'PRIVATE_EVENT' => (bool)($event['PRIVATE_EVENT'] ?? null),
			'DATE_FROM' => $event['DATE_FROM'],
			'DATE_TO' => $event['DATE_TO'],
			'TZ_FROM' => $event['TZ_FROM'],
			'TZ_TO' => $event['TZ_TO'],
			'SKIP_TIME' => $event['SKIP_TIME'] ? 'Y' : 'N',
			'ACTIVE' => 'Y',
			'DELETED' => 'N',
			'TIMESTAMP_X' => new DateTime(),
		];

		if (!empty($event['RECURRENCE_ID']))
		{
			$fields['RECURRENCE_ID'] = $event['RECURRENCE_ID'];
		}

		if (!empty($event['MEETING']))
		{
			$fields['MEETING'] = $event['MEETING'];
			$fields['MEETING_HOST'] = $event['MEETING']['MEETING_CREATOR'] ?? null;
		}
		else
		{
			$fields['MEETING'] = [
				'HOST_NAME' => \CCalendar::GetUserName($entityId),
				'NOTIFY' => true,
				'REINVITE' => false,
				'ALLOW_INVITE' => false,
				'MEETING_CREATOR' => $entityId,
				'HIDE_GUESTS' => true,
				'LANGUAGE_ID' => \CCalendar::getUserLanguageId($entityId)
			];
			$fields['MEETING_HOST'] = $entityId;
			$fields['MEETING_STATUS'] = 'H';
		}

		if (!empty($event['ATTENDEES_CODES']))
		{
			$fields['ATTENDEES_CODES'] = $event['ATTENDEES_CODES'];
		}

		if (!empty($event['RECURRENCE_ID_DATE']))
		{
			$fields['ORIGINAL_DATE_FROM'] = $event['RECURRENCE_ID_DATE'];
		}

		if (!empty($fields['ORIGINAL_DATE_FROM']) && !empty($fields['RECURRENCE_ID']))
		{
			$fields['RELATIONS'] = ['COMMENT_XML_ID' => \CCalendarEvent::GetEventCommentXmlId($fields)];
		}

		if (empty($fields['TZ_TO']) && $fields['SKIP_TIME'] === 'N')
		{
			$currentTimezone = (new DateTime())->getTimeZone()->getName();
			$fields['TZ_TO'] = $currentTimezone;
			$fields['TZ_FROM'] = $currentTimezone;
		}

		if (!empty($event['SKIP_TIME']))
		{
			$fields['DATE_FROM'] = \CCalendar::Date(\CCalendar::Timestamp($fields['DATE_FROM'], false));
			$fields['DATE_TO'] = \CCalendar::Date(
				\CCalendar::Timestamp($fields['DATE_TO']) - \CCalendar::GetDayLen(),
				false
			);
		}

		if (!empty($event['PROPERTY_REMIND_SETTINGS']))
		{
			if (is_array($event['PROPERTY_REMIND_SETTINGS']))
			{
				foreach ($event['PROPERTY_REMIND_SETTINGS'] as $remind)
				{
					$parsed = explode('_', $remind);
					$this->prepareRemind($parsed, $fields);
				}
			}
			else
			{
				$parsed = explode('_', $event['PROPERTY_REMIND_SETTINGS']);
				$this->prepareRemind($parsed, $fields);
			}
		}

		if (!empty($event['PROPERTY_IMPORTANCE']))
		{
			$fields['IMPORTANCE'] = $event['PROPERTY_IMPORTANCE'];
		}

		if (!empty($event['PROPERTY_LOCATION']))
		{
			$fields['LOCATION'] = Rooms\Util::unParseTextLocation($event['PROPERTY_LOCATION']);
		}

		if (!empty($event['DETAIL_TEXT']))
		{
			$this->prepareDescription($event, $fields);
		}

		//RRULE SEGMENT
		if (
			!empty($event['PROPERTY_PERIOD_TYPE'])
			&& in_array($event['PROPERTY_PERIOD_TYPE'], ['DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY'])
		)
		{
			$this->prepareRecurrenceRule($event, $fields);
		}

		return (new EventBuilderFromArray($fields))->build();
	}

	/**
	 * @param Connection $connection
	 *
	 * @param $calendarsList
	 * @return array
	 * @throws ArgumentException
	 * @throws ObjectException
	 * @throws ObjectPropertyException
	 * @throws SystemException
	 * @throws \Bitrix\Main\ObjectNotFoundException
	 */
	private function syncSections(Connection $connection, $calendarsList): array
	{
		$result = [];
		foreach ($calendarsList as $calendar)
		{
			$result[] = [
				'XML_ID' => $calendar['href'],
				'NAME' => $calendar['displayname'] ?? null,
				'DESCRIPTION' => $calendar['calendar-description'] ?? '',
				'TYPE' => $calendar['supported-calendar-component-set'] ?? '',
				'COLOR' => $calendar['calendar-color'] ?? null,
				'MODIFICATION_LABEL' => $calendar['getctag'] ?? null,
			];
		}

		return $this->syncCalendarSections(
			$connection,
			$result
		);
	}

	/**
	 * @param Connection $connection
	 * @param \CDavGroupdavClientCalendar $client
	 * @param array $calendar
	 * @param RequestLogger|null $logger
	 *
	 * @return array
	 * @throws SystemException
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 */
	private function getEventsToSync(
		Connection $connection,
		\CDavGroupdavClientCalendar $client,
		array $calendar,
		?RequestLogger $logger = null
	): array
	{
		$calendarEvents = [];
		$syncToken = !$calendar['IS_NEW'] ? $calendar['SYNC_TOKEN'] : null;

		$calendarItems = $client->GetCalendarItemsBySyncToken($calendar['XML_ID'], $syncToken, $logger);

		if (!$calendarItems || !is_array($calendarItems))
		{
			return $calendarEvents;
		}

		foreach ($calendarItems as $item)
		{
			if (
				(int)$item['status'] === 404
				|| mb_strpos($item['getcontenttype'], 'text/calendar') !== false
			)
			{
				$calendarEvents[] = [
					'XML_ID' => $client::getBasenameWithoutExtension($item['href']),
					'SYNC_TOKEN' => $item['getetag'] ?? null,
					'STATUS' => $item['status'],
				];
			}
		}

		$calendarEvents = $this->syncCalendarEvents(
			$connection,
			$calendar['SECTION_ID'],
			$calendarEvents
		);

		$eventsToUpdate = [];
		$eventsMap = [];
		foreach ($calendarEvents as $event)
		{
			$link = $client->GetRequestEventPath($calendar['XML_ID'], $event['XML_ID']);
			$eventsToUpdate[] = $link;
			$eventsMap[$link] = $event;
		}

		$calendarItems = [];
		if ($eventsToUpdate)
		{
			$calendarItems = $client->GetCalendarItemsList(
				$calendar['XML_ID'],
				$eventsToUpdate,
				true,
				1,
				[],
				$logger
			);
		}

		if (!$syncToken && $calendarItems)
		{
			$calendarItems = $this->applyTimeLimitForEvents($calendarItems);
		}

		return [$calendarItems, $eventsMap];
	}

	/**
	 * @param $events
	 *
	 * @return array
	 */
	private function applyTimeLimitForEvents($events): array
	{
		$timestamp = time() - self::TIME_SLICE;
		foreach ($events as $key => $event)
		{
			if (!empty($event['calendar-data']['PROPERTY_PERIOD_UNTIL']))
			{
				if ((int)\CCalendar::Timestamp($event['calendar-data']['PROPERTY_PERIOD_UNTIL']) - $timestamp < 0)
				{
					unset($events[$key]);
				}
			}
			else if (
				!empty($event['calendar-data']['DATE_TO'])
				&& (int)\CCalendar::Timestamp($event['calendar-data']['DATE_TO']) - $timestamp < 0
			)
			{
				unset($events[$key]);
			}
		}

		return array_values($events);
	}

	/**
	 * @param array $event
	 * @param array $data
	 *
	 * @return void
	 */
	private function parseInvitedAttendees(array $event, array &$data): void
	{
		if (!empty($event['ATTENDEE']))
		{
			/** @var \CDavICalendarProperty $attendee */
			foreach ($event['ATTENDEE'] as $attendee)
			{
				$attendeeData = [];

				if ($attendee->Parameter('CN'))
				{
					$attendeeData['CN'] = $attendee->Parameter('CN');
				}
				if ($attendee->Parameter('CUTYPE'))
				{
					$attendeeData['CUTYPE'] = $attendee->Parameter('CUTYPE');
				}
				if ($attendee->Parameter('PARTSTAT'))
				{
					$attendeeData['PARTSTAT'] = $attendee->Parameter('PARTSTAT');
				}
				if ($attendee->Parameter('ROLE'))
				{
					$attendeeData['ROLE'] = $attendee->Parameter('ROLE');
				}
				if ($attendee->Parameter('EMAIL'))
				{
					$attendeeData['EMAIL'] = $attendee->Parameter('EMAIL');
				}
				if ($attendee->Parameter('SCHEDULE-STATUS'))
				{
					$attendeeData['SCHEDULE-STATUS'] = $attendee->Parameter('SCHEDULE-STATUS');
				}
				if ($attendee->Value())
				{
					$attendeeData['VALUE'] = $attendee->Value();
				}

				$data['ATTENDEE'][] = $attendeeData;
			}
		}
		/** @var \CDavICalendarProperty $organizer */
		if ($organizer = $event['ORGANIZER_ENTITY'][0])
		{
			if ($organizer->Parameter('EMAIL'))
			{
				$data['ORGANIZER']['EMAIL'] = $organizer->Parameter('EMAIL');
			}
			if ($organizer->Parameter('CN'))
			{
				$data['ORGANIZER']['CN'] = $organizer->Parameter('CN');
			}
			if ($organizer->Value())
			{
				$data['ORGANIZER']['VALUE'] = $organizer->Value();
			}
		}
	}

	/**
	 * @param array $event
	 * @param array $data
	 *
	 * @return void
	 */
	private function parseAttachments(array $event, array &$data): void
	{
		/** @var \CDavICalendarProperty $attachment */
		foreach ($event['ATTACH'] as $attachment)
		{
			$attachmentData = [];
			if ($attachment->Parameter('FMTTYPE'))
			{
				$attachmentData['FMTTYPE'] = $attachment->Parameter('FMTTYPE');
			}
			if ($attachment->Parameter('SIZE'))
			{
				$attachmentData['SIZE'] = $attachment->Parameter('SIZE');
			}
			if ($attachment->Parameter('FILENAME'))
			{
				$attachmentData['FILENAME'] = $attachment->Parameter('FILENAME');
			}
			if ($attachment->Parameter('MANAGED-ID'))
			{
				$attachmentData['MANAGED-ID'] = $attachment->Parameter('MANAGED-ID');
			}
			if ($attachment->Value())
			{
				$attachmentData['VALUE'] = $attachment->Value();
			}

			$data['ATTACH'][] = $attachmentData;
		}
	}

	/**
	 * @param $parsed
	 * @param array $fields
	 *
	 * @return void
	 * @throws ObjectException
	 */
	private function prepareRemind($parsed, array &$fields): void
	{
		$cnt = count($parsed);
		if ($cnt === 2 && $parsed[1] === 'date')
		{
			$fields['REMIND'][] = [
				'type' => $parsed[1],
				'value' => new DateTime($parsed[0], 'Ymd\\THis\\Z'),
			];
		}
		else if ($cnt === 2 && $fields['SKIP_TIME'] === 'Y')
		{
			$fields['REMIND'][] = [
				'type' => 'daybefore',
				'before' => 1,
				'time' => 1440 - (int)$parsed[0] * 60,
			];
		}
		else if ($cnt === 2)
		{
			$fields['REMIND'][] = [
				'count' => (int)$parsed[0],
				'type' => $parsed[1],
			];
		}
		else if ($cnt === 3 && $parsed[2] === 'daybefore')
		{
			$fields['REMIND'][] = [
				'type' => $parsed[2],
				'before' => 0,
				'time' => (int)$parsed[0] * 60,
			];
		}
		else if ($cnt === 4 && $fields['SKIP_TIME'] === 'Y')
		{
			$fields['REMIND'][] = [
				'type' => 'daybefore',
				'before' => $parsed[0] + 1,
				'time' => 1440 - (int)$parsed[2] * 60,
			];
		}
		else if ($cnt === 4)
		{
			$fields['REMIND'][] = [
				'type' => $parsed[3],
				'count' => (int)$parsed[0] * 24 + $parsed[2],
			];
		}
	}

	/**
	 * @param EO_DavConnection $connection
	 *
	 * @return Connection
	 */
	private function createConnectionObject(EO_DavConnection $connection): Connection
	{
		return (new BuilderConnectionFromDM($connection))->build();
	}

	/**
	 * @param array $error
	 *
	 * @return string
	 */
	private function processError(array $error): string
	{
		return '[' . $error[0] . '] ' . $error[1];
	}

	/**
	 * @param string|null $name
	 *
	 * @return string
	 */
	private function prepareName(?string $name): string
	{
		if (!$name)
		{
			IncludeModuleLangFile($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/calendar/classes/general/calendar_js.php");
			$name = Loc::getMessage('EC_DEFAULT_ENTRY_NAME');
		}

		return $name;
	}

	/**
	 * @param array $event
	 * @param array $fields
	 *
	 * @return void
	 */
	private function prepareRecurrenceRule(array $event, array &$fields): void
	{
		$fields['RRULE']['FREQ'] = $event['PROPERTY_PERIOD_TYPE'];
		$fields['RRULE']['INTERVAL'] = $event['PROPERTY_PERIOD_COUNT'];

		if (empty($fields['DT_LENGTH']) && !empty($event['PROPERTY_EVENT_LENGTH']))
		{
			$fields['DT_LENGTH'] = (int)$fields['PROPERTY_EVENT_LENGTH'];
		}
		else if (isset($event['DT_TO_TS'], $event['DT_FROM_TS']))
		{
			$fields['DT_LENGTH'] = $event['DT_TO_TS'] - $event['DT_FROM_TS'];
		}
		else
		{
			$fields['DT_LENGTH'] = null;
		}

		if ($fields['RRULE']['FREQ'] === 'WEEKLY' && !empty($event['PROPERTY_PERIOD_ADDITIONAL']))
		{
			$fields['RRULE']['BYDAY'] = [];
			$days = explode(',', $event['PROPERTY_PERIOD_ADDITIONAL']);
			foreach ($days as $day)
			{
				$day = \CCalendar::WeekDayByInd($day, false);
				if ($day !== false)
				{
					$fields['RRULE']['BYDAY'][] = $day;
				}
			}
			$fields['RRULE']['BYDAY'] = implode(',', $fields['RRULE']['BYDAY']);
		}

		if (!empty($event['PROPERTY_RRULE_COUNT']))
		{
			$fields['RRULE']['COUNT'] = $event['PROPERTY_RRULE_COUNT'];
		}
		else if (!empty($event['PROPERTY_PERIOD_UNTIL']))
		{
			$fields['RRULE']['UNTIL'] = $event['PROPERTY_PERIOD_UNTIL'];
		}
		else
		{
			$fields['RRULE']['UNTIL'] = $event['DT_TO_TS'] ?? null;
		}

		if (!empty($event['EXDATE']))
		{
			$fields['EXDATE'] = $event['EXDATE'];
		}
	}

	/**
	* @param array $fields
	* @param array $event
	*
	* @return void
	 */
	private function prepareDescription(array $event, array &$fields): void
	{
		if (isset($event['MEETING']) && !empty($event['MEETING']['LANGUAGE_ID']))
		{
			$languageId = $event['MEETING']['LANGUAGE_ID'];
		}
		else
		{
			$languageId = \CCalendar::getUserLanguageId((int)$fields['OWNER_ID']);
		}

		$fields['DESCRIPTION'] = (new EventDescription())->prepareAfterImport($event['DETAIL_TEXT'], $languageId);
	}

	/**
	 * @param array $instance
	 * @param array $localInstance
	 *
	 * @return void
	 */
	private function mergeInstanceParams(array &$instance, array $localInstance): void
	{
		$instance['ID'] = (int)$localInstance['EVENT_ID'];
		$instance['EVENT_CONNECTION_VERSION'] = (int)$localInstance['VERSION'];
		$instance['EVENT_CONNECTION_ID'] = (int)$localInstance['EVENT_CONNECTION_ID'];

		if (!empty($localInstance['MEETING']))
		{
			$instance['MEETING'] = unserialize($localInstance['MEETING'], ['allowed_classes' => false]);
		}
		if (!empty($localInstance['ATTENDEES_CODES']))
		{
			$instance['ATTENDEES_CODES'] = explode(',', $localInstance['ATTENDEES_CODES']);
		}
		if (!empty($localInstance['IS_MEETING']))
		{
			$instance['IS_MEETING'] = (bool)$localInstance['IS_MEETING'];
		}
		if (!empty($localInstance['MEETING_HOST']))
		{
			$instance['MEETING_HOST'] = $localInstance['MEETING_HOST'];
		}
		if (!empty($localInstance['ACCESSIBILITY']))
		{
			$instance['ACCESSIBILITY'] = $localInstance['ACCESSIBILITY'];
		}
	}

	/**
	 * @param array $instance
	 * @param array $parentEvent
	 *
	 * @return array
	 */
	private function addParentDataToInstance(array $instance, array $parentEvent): array
	{
		if (empty($instance['IS_MEETING']))
		{
			$instance['IS_MEETING'] = $parentEvent['IS_MEETING'];
		}
		if (empty($instance['MEETING_HOST']))
		{
			$instance['MEETING_HOST'] = $parentEvent['MEETING_HOST'];
		}
		if (empty($instance['MEETING']))
		{
			$instance['MEETING'] = $parentEvent['MEETING'];
		}
		if (empty($instance['ATTENDEES_CODES']))
		{
			$instance['ATTENDEES_CODES'] = $parentEvent['ATTENDEES_CODES'];
		}

		$instance['VERSION'] = !empty($instance['EVENT_CONNECTION_VERSION'])
			? max($parentEvent['VERSION'], $instance['EVENT_CONNECTION_VERSION'])
			: $parentEvent['VERSION']
		;

		$instance['RECURRENCE_ID'] = $parentEvent['ID'];

		return $instance;
	}

	/**
	 * @param array $events
	 *
	 * @return array[]
	 *
	 * Removes data of same-date instances
	 */
	private function prepareInstanceEvents(array $events): array
	{
		$instances = [];
		$eventDates = [];
		$eventsCount = count($events);

		for ($i = $eventsCount - 1; $i >= 0; $i--)
		{
			$eventDate = \CCalendar::Date(\CCalendar::Timestamp($events[$i]['DATE_FROM']), false);
			if (!in_array($eventDate, $eventDates, true))
			{
				$instances[] = $events[$i];
				$eventDates[] = $eventDate;
			}
		}

		return [$instances, $eventDates];
	}

	/**
	 * @param string $xmlId
	 * @param array|null $link
	 *
	 * @return array
	 */
	private function prepareExistedEventParams(string $xmlId, ?array $link = null): array
	{
		return [
			'XML_ID' => $xmlId,
			'ID' => (int)($link['EVENT_ID'] ?? null),
			'EVENT_NAME' => $link['EVENT_NAME'] ?? null,
			'EVENT_CONNECTION_ID' => (int)($link['EVENT_CONNECTION_ID'] ?? null),
			'EXDATE' => $link['EXDATE'] ?? null,
			'VERSION' => $link['EVENT_VERSION'] ?? $link['VERSION'] ?? 1,
			'MEETING' => ($link['MEETING'] ?? null)
				? unserialize($link['MEETING'], ['allowed_classes' => false])
				: null
			,
			'IS_MEETING' => (bool)($link['IS_MEETING'] ?? null),
			'ATTENDEES_CODES' => ($link['ATTENDEES_CODES'] ?? null)
				? explode(',', $link['ATTENDEES_CODES'])
				: null
			,
			'ACCESSIBILITY' => $link['ACCESSIBILITY'] ?? 'busy',
			'DATE_FROM' => $link['EVENT_DATE_FROM'] ?? null,
			'DATE_TO' => $link['EVENT_DATE_TO'] ?? null,
			'TZ_FROM' => $link['EVENT_TZ_FROM'] ?? null,
			'TZ_TO' => $link['EVENT_TZ_TO'] ?? null,
		];
	}

	/**
	 * @param array $existedEvent
	 * @param array $event
	 * @param \CDavGroupdavClientCalendar $client
	 *
	 * @return array
	 */
	private function mergeExternalEventWithLocal(
		array $existedEvent,
		array $event,
		\CDavGroupdavClientCalendar $client
	): array
	{
		$exDate = $existedEvent['EXDATE'];
		$event['calendar-data'] = array_merge($event['calendar-data'], [
			'ID' => $existedEvent['ID'],
			'XML_ID' => $client::getBasenameWithoutExtension($event['href']),
			'MODIFICATION_LABEL' => $event['getetag'],
			'MEETING' => $existedEvent['MEETING'],
			'IS_MEETING' => $existedEvent['IS_MEETING'],
			'ATTENDEES_CODES' => $existedEvent['ATTENDEES_CODES'],
			'ACCESSIBILITY' => $existedEvent['ACCESSIBILITY'],
		]);

		return [$event, $exDate];
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit