• Модуль: calendar
  • Путь к файлу: ~/bitrix/modules/calendar/classes/general/calendar.php
  • Класс: CCalendar
  • Вызов: CCalendar::SaveEventEx
static function SaveEventEx($params = [])
{
	$arFields = $params['arFields'];
	if (self::$type && !isset($arFields['CAL_TYPE']))
	{
		$arFields['CAL_TYPE'] = self::$type;
	}
	elseif (isset($arFields['SECTION_CAL_TYPE']) && !isset($arFields['CAL_TYPE']))
	{
		$arFields['CAL_TYPE'] = $arFields['SECTION_CAL_TYPE'];
	}
	if (self::$bOwner && !isset($arFields['OWNER_ID']))
	{
		$arFields['OWNER_ID'] = self::$ownerId;
	}
	elseif (isset($arFields['SECTION_OWNER_ID']) && !isset($arFields['OWNER_ID']))
	{
		$arFields['OWNER_ID'] = $arFields['SECTION_OWNER_ID'];
	}

	if (!isset($arFields['SKIP_TIME']) && isset($arFields['DT_SKIP_TIME']))
	{
		$arFields['SKIP_TIME'] = $arFields['DT_SKIP_TIME'] === 'Y';
	}

	//flags for synchronize the instance of a recurring event
	//modeSync - edit mode instance for avoid unnecessary request (patch)
	//editParentEvents - editing the parent event of the following
	$params['modeSync'] = true;
	$params['editInstance'] = $params['editInstance'] ?? false;
	$params['editNextEvents'] = $params['editNextEvents'] ?? false;
	$params['editParentEvents'] = $params['editParentEvents'] ?? false;
	$params['editEntryUntil'] = $params['editEntryUntil'] ?? false;
	$params['originalDavXmlId'] = $params['originalDavXmlId'] ?? null;
	$params['originalFrom'] = $params['originalFrom'] ?? null;
	$params['instanceTz'] = $params['instanceTz'] ?? null;
	$params['syncCaldav'] = $params['syncCaldav'] ?? false;
	$params['sendInvitesToDeclined'] = $params['sendInvitesToDeclined'] ?? false;
	$params['autoDetectSection'] = $params['autoDetectSection'] ?? false;
	$userId = $params['userId'] ?? self::getCurUserId();
	$accessController = new EventAccessController($userId);

	$result = [];
	$sectionId =
		(!empty($arFields['SECTIONS']) && is_array($arFields['SECTIONS']))
			? $arFields['SECTIONS'][0]
			: (int)($arFields['SECTIONS'] ?? null);
	$bPersonal = self::IsPersonal($arFields['CAL_TYPE'] ?? null, $arFields['OWNER_ID'] ?? null, $userId);
	$checkPermission = !isset($params['checkPermission']) || $params['checkPermission'] !== false;
	$silentErrorModePrev = self::$silentErrorMode;
	self::SetSilentErrorMode();

	if (
		isset($arFields['DT_FROM'], $arFields['DT_TO'])
		&& !isset($arFields['DATE_FROM'])
		&& !isset($arFields['DATE_TO'])
	)
	{
		$arFields['DATE_FROM'] = $arFields['DT_FROM'];
		$arFields['DATE_TO'] = $arFields['DT_TO'];
		unset($arFields['DT_FROM'], $arFields['DT_TO']);
	}

	// Fetch current event
	$curEvent = false;
	$bNew = !isset($arFields['ID']) || !$arFields['ID'];
	if (!$bNew)
	{
		$curEvent = CCalendarEvent::GetList(
			[
				'arFilter' => [
					"ID" => (int)$arFields['ID'],
					"DELETED" => 'N',
				],
				'parseRecursion' => false,
				'fetchAttendees' => true,
				'fetchMeetings' => false,
				'userId' => $userId,
				'checkPermissions' => $checkPermission,
			]
		);
		if ($curEvent)
		{
			$curEvent = $curEvent[0];
		}

		$canChangeDateRecurrenceEvent = isset($params['recursionEditMode'])
			&& in_array($params['recursionEditMode'], ['all', ''], true)
			&& (($arFields['DATE_FROM'] ?? null) !== ($curEvent['DATE_FROM'] ?? null))
			&& ($arFields['RRULE']['FREQ'] ?? null) !== 'NONE'
		;

		if ($canChangeDateRecurrenceEvent)
		{
			$arFields['DATE_FROM'] = self::GetOriginalDate(
				$arFields['DATE_FROM'],
				$curEvent['DATE_FROM'],
				$arFields['TZ_FROM'] ?? null
			);
			$arFields['DATE_TO'] = self::GetOriginalDate(
				$arFields['DATE_TO'],
				$curEvent['DATE_TO'],
				$arFields['TZ_TO'] ?? null
			);
		}

		$bPersonal = $bPersonal && self::IsPersonal($curEvent['CAL_TYPE'], $curEvent['OWNER_ID'], $userId);

		$arFields['CAL_TYPE'] = $curEvent['CAL_TYPE'];
		$arFields['OWNER_ID'] = $curEvent['OWNER_ID'];
		$arFields['CREATED_BY'] = $curEvent['CREATED_BY'];
		$arFields['ACTIVE'] = $curEvent['ACTIVE'] ?? null;

		$eventModel = CCalendarEvent::getEventModelForPermissionCheck((int)($curEvent['ID'] ?? 0), $curEvent, $userId);

		$accessCheckResult = $accessController->check(ActionDictionary::ACTION_EVENT_EDIT, $eventModel);
		$bChangeMeeting = !$checkPermission || $accessCheckResult;

		if (!$bChangeMeeting)
		{
			return Loc::getMessage('EC_ACCESS_DENIED');
		}

		if (!isset($arFields['NAME']))
		{
			$arFields['NAME'] = $curEvent['NAME'];
		}
		if (!isset($arFields['DESCRIPTION']))
		{
			$arFields['DESCRIPTION'] = $curEvent['DESCRIPTION'];
		}
		if (!isset($arFields['COLOR']) && $curEvent['COLOR'])
		{
			$arFields['COLOR'] = $curEvent['COLOR'];
		}
		if (!isset($arFields['TEXT_COLOR']) && !empty($curEvent['TEXT_COLOR']))
		{
			$arFields['TEXT_COLOR'] = $curEvent['TEXT_COLOR'];
		}
		if (!isset($arFields['SECTIONS']))
		{
			$arFields['SECTIONS'] = [$curEvent['SECT_ID']];
			$sectionId = !empty($arFields['SECTIONS']) ? $arFields['SECTIONS'][0] : 0;
		}
		if (!isset($arFields['IS_MEETING']))
		{
			$arFields['IS_MEETING'] = $curEvent['IS_MEETING'];
		}
		if (!isset($arFields['MEETING_HOST']))
		{
			$arFields['MEETING_HOST'] = $curEvent['MEETING_HOST'];
		}
		if (!isset($arFields['MEETING_STATUS']))
		{
			$arFields['MEETING_STATUS'] = $curEvent['MEETING_STATUS'];
		}
		if (!isset($arFields['ACTIVE']) && isset($curEvent['ACTIVE']))
		{
			$arFields['ACTIVE'] = $curEvent['ACTIVE'];
		}
		if (!isset($arFields['PRIVATE_EVENT']))
		{
			$arFields['PRIVATE_EVENT'] = $curEvent['PRIVATE_EVENT'];
		}
		if (!isset($arFields['ACCESSIBILITY']))
		{
			$arFields['ACCESSIBILITY'] = $curEvent['ACCESSIBILITY'];
		}
		if (!isset($arFields['IMPORTANCE']))
		{
			$arFields['IMPORTANCE'] = $curEvent['IMPORTANCE'];
		}
		if (!isset($arFields['SKIP_TIME']))
		{
			$arFields['SKIP_TIME'] = $curEvent['DT_SKIP_TIME'] === 'Y';
		}
		if (!isset($arFields['DATE_FROM']) && isset($curEvent['DATE_FROM']))
		{
			$arFields['DATE_FROM'] = $curEvent['DATE_FROM'];
		}
		if (!isset($arFields['DATE_TO']) && isset($curEvent['DATE_TO']))
		{
			$arFields['DATE_TO'] = $curEvent['DATE_TO'];
		}
		if (!isset($arFields['TZ_FROM']))
		{
			$arFields['TZ_FROM'] = $curEvent['TZ_FROM'];
		}
		if (!isset($arFields['TZ_TO']))
		{
			$arFields['TZ_TO'] = $curEvent['TZ_TO'];
		}
		if (!isset($arFields['RELATIONS']))
		{
			$arFields['RELATIONS'] = $curEvent['RELATIONS'];
		}
		if (!isset($arFields['MEETING']))
		{
			$arFields['MEETING'] = $curEvent['MEETING'];
		}
		if (!isset($arFields['SYNC_STATUS']) && $curEvent['SYNC_STATUS'])
		{
			$arFields['SYNC_STATUS'] = $curEvent['SYNC_STATUS'];
		}
		$arFields['MEETING']['LANGUAGE_ID'] = self::getUserLanguageId((int)$userId);

		if (
			!isset($arFields['ATTENDEES']) && !isset($arFields['ATTENDEES_CODES'])
			&& $arFields['IS_MEETING']
			&& !empty($curEvent['ATTENDEE_LIST'])
			&& is_array($curEvent['ATTENDEE_LIST'])
		)
		{
			$arFields['ATTENDEES'] = [];
			foreach ($curEvent['ATTENDEE_LIST'] as $attendee)
			{
				$arFields['ATTENDEES'][] = $attendee['id'];
			}
		}
		if (!isset($arFields['ATTENDEES_CODES']) && $arFields['IS_MEETING'])
		{
			$arFields['ATTENDEES_CODES'] = $curEvent['ATTENDEES_CODES'];
		}

		if (!isset($arFields['LOCATION']) && $curEvent['LOCATION'] !== "")
		{
			$arFields['LOCATION'] = [
				'OLD' => $curEvent['LOCATION'],
				'NEW' => $curEvent['LOCATION'],
			];

			//if location wasn't change when updating event
			$parsedLoc = BitrixCalendarRoomsUtil::parseLocation($curEvent['LOCATION']);
			if ($parsedLoc['room_event_id'])
			{
				$arFields['LOCATION']['NEW'] = 'calendar_' . $parsedLoc['room_id'];
			}
		}

		if (!$bChangeMeeting)
		{
			$arFields['IS_MEETING'] = $curEvent['IS_MEETING'];
		}

		if ($arFields['IS_MEETING'] && !$bPersonal && $arFields['CAL_TYPE'] === 'user')
		{
			$arFields['SECTIONS'] = [$curEvent['SECT_ID']];
		}

		// If it's attendee but modifying called from CalDav methods
		if (
			(!empty($params['bSilentAccessMeeting'])
				|| (isset($params['fromWebservice']) && $params['fromWebservice'] === true)
			)
			&& !empty($curEvent['IS_MEETING'])
			&& ($curEvent['PARENT_ID'] !== $curEvent['ID'])
		)
		{
			// TODO: It called when changes caused in google/webservise side but can't be
			// TODO: implemented because user is only attendee, not the owner of the event
			//Todo: we have to update such events back to revert changes from google
			return true; // CalDav will return 204
		}

		if (!isset($arFields["RRULE"]) && $curEvent["RRULE"] !== '' && ($params['fromWebservice'] ?? null) !== true)
		{
			$arFields["RRULE"] = CCalendarEvent::ParseRRULE($curEvent["RRULE"]);
		}

		if (
			(($params['fromWebservice'] ?? null) === true)
			&& $arFields["RRULE"] === -1
			&& CCalendarEvent::CheckRecurcion($curEvent)
		)
		{
			$arFields["RRULE"] = CCalendarEvent::ParseRRULE($curEvent['RRULE']);
		}

		if (!isset($arFields['EXDATE']) && !empty($arFields["RRULE"]))
		{
			$arFields['EXDATE'] = $curEvent['EXDATE'];
		}

		else if (
			isset($arFields['EXDATE'], $curEvent['EXDATE'])
			&& $arFields['EXDATE']
			&& $curEvent['EXDATE']
			&& !empty($arFields["RRULE"])
		)
		{
			$arFields['EXDATE'] = self::mergeExcludedDates($curEvent['EXDATE'], $arFields['EXDATE']);
		}

		if ($curEvent)
		{
			$params['currentEvent'] = $curEvent;
		}
	}
	elseif ($checkPermission && $sectionId > 0 && !$bPersonal)
	{
		$section = CCalendarSect::GetList(['arFilter' => ['ID' => $sectionId],
			'checkPermissions' => false,
			'getPermissions' => false
		])[0] ?? null;

		if ($section)
		{
			$arFields['CAL_TYPE'] = $section['CAL_TYPE'];
		}
		else
		{
			return self::ThrowError(Loc::getMessage('EC_ACCESS_DENIED'));
		}

		$newEventModel =
			EventModel::createNew()
				->setOwnerId((int)$arFields['OWNER_ID'])
				->setSectionId((int)$sectionId)
				->setSectionType($arFields['CAL_TYPE'])
		;

		if (!$accessController->check(ActionDictionary::ACTION_EVENT_ADD, $newEventModel))
		{
			return self::ThrowError(Loc::getMessage('EC_ACCESS_DENIED'));
		}
	}

	if ($params['autoDetectSection'] && $sectionId <= 0)
	{
		$sectionId = false;
		if ($arFields['CAL_TYPE'] === 'user')
		{
			$sectionId = self::GetMeetingSection($arFields['OWNER_ID'], true);
			if ($sectionId)
			{
				$res = CCalendarSect::GetList(
					[
						'arFilter' => [
							'CAL_TYPE' => $arFields['CAL_TYPE'],
							'OWNER_ID' => $arFields['OWNER_ID'],
							'ID' => $sectionId,
						],
					]
				);


				if (!$res || !$res[0] || CCalendarSect::CheckGoogleVirtualSection($res[0]['GAPI_CALENDAR_ID']))
				{
					$sectionId = false;
				}
			}
			else
			{
				$sectionId = false;
			}

			if ($sectionId)
			{
				$arFields['SECTIONS'] = [$sectionId];
			}
		}

		if (!$sectionId)
		{
			if (empty($arFields['CAL_TYPE']) || empty($arFields['OWNER_ID']))
			{
				return false;
			}

			$sectRes = CCalendarSect::GetSectionForOwner(
				$arFields['CAL_TYPE'],
				$arFields['OWNER_ID'],
				$params['autoCreateSection']
			);
			if ($sectRes['sectionId'] > 0)
			{
				$sectionId = $sectRes['sectionId'];
				$arFields['SECTIONS'] = [$sectionId];
				if ($sectRes['autoCreated'])
				{
					$params['bAffectToDav'] = false;
				}
			}
			else
			{
				return false;
			}
		}
	}

	if (isset($arFields["RRULE"]))
	{
		$arFields["RRULE"] = CCalendarEvent::CheckRRULE($arFields["RRULE"]);
	}

	if ($bNew && !$params['editInstance'] && !($arFields['DAV_XML_ID'] ?? null))
	{
		$arFields['DAV_XML_ID'] = UidGenerator::createInstance()
			->setDate(
				new Date(
					Util::getDateObject(
						$arFields['DATE_FROM'],
						$arFields['SKIP_TIME'] ?? null,
						$arFields['TZ_FROM'] ?? null,
					)
				)
			)
			->setPortalName(Util::getServerName())
			->setUserId((int)$arFields['OWNER_ID'])
			->getUidWithDate();
	}
	elseif ($params['editInstance'])
	{
		$arFields['DAV_XML_ID'] = $params['currentEvent']['DAV_XML_ID'];
	}

// Set version
	if (!isset($arFields['VERSION']) || ($arFields['VERSION'] <= ($curEvent['VERSION'] ?? null)))
	{
		$arFields['VERSION'] = ($curEvent['VERSION'] ?? null)
			? $curEvent['VERSION'] + 1
			: 1
		;
	}

	if ($params['autoDetectSection'] && $sectionId <= 0 && $arFields['OWNER_ID'] > 0)
	{
		$res = CCalendarSect::GetList(
			[
				'arFilter' => [
					'CAL_TYPE' => $arFields['CAL_TYPE'],
					'OWNER_ID' => $arFields['OWNER_ID'],
				],
				'checkPermissions' => false,
			]
		);
		if ($res && is_array($res) && isset($res[0]))
		{
			$sectionId = $res[0]['ID'];
		}
		else
		{
			$defCalendar = CCalendarSect::CreateDefault(array(
				'type' => $arFields['CAL_TYPE'],
				'ownerId' => $arFields['OWNER_ID'],
			));
			$sectionId = $defCalendar['ID'];
			self::SetCurUserMeetingSection($defCalendar['ID']);

			$params['bAffectToDav'] = false;
		}
		if ($sectionId > 0)
		{
			$arFields['SECTIONS'] = [$sectionId];
		}
		else
		{
			return false;
		}
	}

	$bExchange = self::IsExchangeEnabled() && $arFields['CAL_TYPE'] === 'user';
	$bCalDav = self::IsCalDAVEnabled() && $arFields['CAL_TYPE'] === 'user';

	if (
		(($params['editNextEvents'] ?? null) === false && ($params['recursionEditMode'] ?? null) === 'next')
		|| (in_array($params['recursionEditMode'] ?? null, ['this', 'skip'])
			&& ($params['editInstance'] ?? null) === false)
	)
	{
		$params['modeSync'] = false;

		if (($params['editParentEvents'] ?? null) === true)
		{
			$params['modeSync'] = true;
		}
	}

	if (
		(
			($params['bAffectToDav'] ?? null) !== false
			&& ($bExchange || $bCalDav)
			&& $sectionId > 0
			&& !(isset($params['dontSyncParent']) && $params['dontSyncParent'])
			&& ($params['overSaving'] ?? false) !== true
		)
		|| $params['syncCaldav']
	)
	{
		$davParams = [
			'bCalDav' => $bCalDav,
			'bExchange' => $bExchange,
			'sectionId' => $sectionId,
			'modeSync' => $params['modeSync'],
			'editInstance' => $params['editInstance'],
			'originalDavXmlId' => $params['originalDavXmlId'],
			'instanceTz' => $params['instanceTz'],
			'editParentEvents' => $params['editParentEvents'],
			'editNextEvents' => $params['editNextEvents'],
			'syncCaldav' => $params['syncCaldav'],
			'parentDateFrom' => ($params['parentDateFrom'] ?? null),
			'parentDateTo' => ($params['parentDateTo'] ?? null),
		];

		$res = CCalendarSync::DoSaveToDav( $arFields, $davParams, $curEvent);

		if ($res !== true && self::$silentErrorMode === true)
		{
			self::ThrowError($res);
		}
	}

	$params['arFields'] = $arFields;
	$params['userId'] = $userId;
	$params['path'] = self::GetPath($arFields['CAL_TYPE'], $arFields['OWNER_ID'], 1);

	$isSharingEvent =
		isset($curEvent['EVENT_TYPE'])
		&& in_array($curEvent['EVENT_TYPE'], SharingEventManager::getSharingEventTypes(), true)
	;

	if (!empty($arFields['ID']) && $isSharingEvent)
	{
		SharingEventManager::onSharingEventEdit($arFields);
	}

	if (
		$curEvent
		&& in_array(($params['recursionEditMode'] ?? null), ['this', 'next'], true)
		&& CCalendarEvent::CheckRecurcion($curEvent)
	)
	{
		// Edit only current instance of the set of recurrent events
		if ($params['recursionEditMode'] === 'this')
		{
			// 1. Edit current reccurent event: exclude current date
			$excludeDates = CCalendarEvent::GetExDate($curEvent['EXDATE']);
			$excludeDate = self::Date(
				self::Timestamp($params['currentEventDateFrom'] ?? $arFields['DATE_FROM']),
				false
			);
			$excludeDates[] = $excludeDate;

			$saveEventData = [
				'recursionEditMode' => 'skip',
				'silentErrorMode' => $params['silentErrorMode'],
				'sendInvitesToDeclined' => $params['sendInvitesToDeclined'],
				'sendInvitations' => false,
				'sendEditNotification' => false,
				'userId' => $userId,
				'requestUid' => $params['requestUid'] ?? null,
			];

			$arFieldsCurrent = [
				'ID' => $curEvent["ID"],
				'EXDATE' => CCalendarEvent::SetExDate($excludeDates),
			];

			if (
				!empty($params['arFields']['SECTIONS'][0])
				&& (int)$curEvent['SECTION_ID'] !== (int)$params['arFields']['SECTIONS'][0]
			)
			{
				$arFieldsCurrent['SECTIONS'] = $params['arFields']['SECTIONS'];
				$arFieldsCurrent['CAL_TYPE'] = $params['arFields']['CAL_TYPE'];
				$arFieldsCurrent['OWNER_ID'] = $userId;
			}

			$saveEventData['arFields'] = $arFieldsCurrent;

			// Save current event
			$id = self::SaveEvent($saveEventData);

			// 2. Copy event with new changes, but without recursion
			$newParams = $params;
			$newParams['sendEditNotification'] = false;

			if (!$newParams['arFields']['MEETING']['REINVITE'])
			{
				$newParams['saveAttendeesStatus'] = true;
			}

			$newParams['arFields']['RECURRENCE_ID'] = $curEvent['RECURRENCE_ID'] ?: $newParams['arFields']['ID'];

			unset(
				$newParams['arFields']['ID'],
				$newParams['arFields']['DAV_XML_ID'],
				$newParams['arFields']['G_EVENT_ID'],
				$newParams['arFields']['SYNC_STATUS'],
				$newParams['arFields']['CAL_DAV_LABEL'],
				$newParams['arFields']['RRULE'],
				$newParams['arFields']['EXDATE'],
				$newParams['recursionEditMode'],
			);

			$newParams['arFields']['REMIND'] = $params['currentEvent']['REMIND'];

			$fromTs = self::Timestamp($newParams['currentEventDateFrom']);
			$currentFromTs = self::Timestamp($newParams['arFields']['DATE_FROM']);
			$length = self::Timestamp($newParams['arFields']['DATE_TO']) - self::Timestamp($newParams['arFields']['DATE_FROM']);

			if (!isset($newParams['arFields']['DATE_FROM'], $newParams['arFields']['DATE_TO']))
			{
				$length = $curEvent['DT_LENGTH'];
				$currentFromTs = self::Timestamp($curEvent['DATE_FROM']);
			}

			$instanceDate = !isset($newParams['arFields']['DATE_FROM'])
				||self::Date(self::Timestamp($curEvent['DATE_FROM']), false) === self::Date($currentFromTs, false);

			if ($newParams['arFields']['SKIP_TIME'])
			{
				if ($instanceDate)
				{
					$newParams['arFields']['DATE_FROM'] = self::Date($fromTs, false);
					$newParams['arFields']['DATE_TO'] = self::Date($fromTs + $length - self::GetDayLen(), false);
				}
				else
				{
					$newParams['arFields']['DATE_FROM'] = self::Date($currentFromTs, false);
					$newParams['arFields']['DATE_TO'] = self::Date($currentFromTs + $length - self::GetDayLen(), false);
				}
			}
			elseif ($instanceDate)
			{
				$newFromTs = self::DateWithNewTime($currentFromTs, $fromTs);
				$newParams['arFields']['DATE_FROM'] = self::Date($newFromTs);
				$newParams['arFields']['DATE_TO'] = self::Date($newFromTs + $length);
			}

			$eventMod = $curEvent;
			if (!isset($eventMod['~DATE_FROM']))
			{
				$eventMod['~DATE_FROM'] = $eventMod['DATE_FROM'];
			}

			$eventMod['DATE_FROM'] = $newParams['currentEventDateFrom'];
			$commentXmlId = CCalendarEvent::GetEventCommentXmlId($eventMod);
			$newParams['arFields']['RELATIONS'] = array('COMMENT_XML_ID' => $commentXmlId);
			$newParams['editInstance'] = true;
			//original instance date start
			$newParams['arFields']['ORIGINAL_DATE_FROM'] = self::GetOriginalDate(
				$params['currentEvent']['DATE_FROM'],
				$eventMod['DATE_FROM'] ?? $newParams['currentEventDateFrom'],
				$newParams['arFields']['TZ_FROM']
			);
			$newParams['originalDavXmlId'] = $params['currentEvent']['G_EVENT_ID'];
			$newParams['instanceTz'] = $params['currentEvent']['TZ_FROM'];
			$newParams['parentDateFrom'] = $params['currentEvent']['DATE_FROM'];
			$newParams['parentDateTo'] = $params['currentEvent']['DATE_TO'];
			$newParams['requestUid'] = $params['requestUid'] ?? null;
			$newParams['sendInvitesToDeclined'] = $params['sendInvitesToDeclined'] ?? null;

			$result['recEventId'] = self::SaveEvent($newParams);
		}
		// Edit all next instances of the set of recurrent events
		elseif(($params['recursionEditMode']) === 'next')
		{
			$currentDateTimestamp = self::Timestamp($params['currentEventDateFrom'] ?? null);

			// Copy event with new changes
			$newParams = $params;
			$recId = $curEvent['RECURRENCE_ID'] ?: $newParams['arFields']['ID'];

			$newParams['arFields']['RELATIONS'] ??= [];
			$newParams['arFields']['RELATIONS'] = [
				'ORIGINAL_RECURSION_ID' => $curEvent['RELATIONS']['ORIGINAL_RECURSION_ID'] ?? $recId,
			];

			if (empty($newParams['arFields']['MEETING']['REINVITE']))
			{
				$newParams['saveAttendeesStatus'] = true;
			}

			$currentFromTs = self::Timestamp($newParams['arFields']['DATE_FROM'] ?? null);
			$length = self::Timestamp($newParams['arFields']['DATE_TO']) - self::Timestamp($newParams['arFields']['DATE_FROM']);

			if (!isset($newParams['arFields']['DATE_FROM']) || !isset($newParams['arFields']['DATE_TO']))
			{
				$length = $curEvent['DT_LENGTH'];
				$currentFromTs = self::Timestamp($curEvent['DATE_FROM']);
			}

			$instanceDate = !isset($newParams['arFields']['DATE_FROM'])
				||self::Date(self::Timestamp($curEvent['DATE_FROM']), false) === self::Date($currentFromTs, false);

			if ($newParams['arFields']['SKIP_TIME'])
			{
				if ($instanceDate)
				{
					$newParams['arFields']['DATE_FROM'] = self::Date($currentDateTimestamp, false);
					$newParams['arFields']['DATE_TO'] = self::Date($currentDateTimestamp + $length, false);
				}
				else
				{
					$newParams['arFields']['DATE_FROM'] = self::Date($currentFromTs, false);
					$newParams['arFields']['DATE_TO'] = self::Date($currentFromTs + $length, false);
				}
			}
			elseif ($instanceDate)
			{
				$newFromTs = self::DateWithNewTime($currentFromTs, $currentDateTimestamp);
				$newParams['arFields']['DATE_FROM'] = self::Date($newFromTs);
				$newParams['arFields']['DATE_TO'] = self::Date($newFromTs + $length);
			}

			if (isset($curEvent['EXDATE']) && $curEvent['EXDATE'] !== '')
			{
				$newParams['arFields']['EXDATE'] = $curEvent['EXDATE'];
			}

			if (isset($newParams['arFields']['RRULE']['COUNT']) && $newParams['arFields']['RRULE']['COUNT'] > 0)
			{
				$countParams = [
					'rrule' => $newParams['arFields']['RRULE'],
					'dateFrom' => $curEvent['DATE_FROM'],
					'dateTo' => $newParams['arFields']['DATE_FROM'],
					'timeZone' => $curEvent['TZ_FROM'],
				];
				
				$newParams['arFields']['RRULE']['COUNT'] = self::CountNumberFollowEvents($countParams);
				unset($newParams['arFields']['RRULE']['UNTIL'], $newParams['arFields']['RRULE']['~UNTIL']);
			}

			if (
				isset($newParams['arFields']['RRULE']['FREQ'])
				&& $newParams['arFields']['RRULE']['FREQ'] === 'WEEKLY'
				&& isset($curEvent['RRULE']['FREQ'])
				&& $curEvent['RRULE']['FREQ'] === 'WEEKLY'
				&& $newParams['arFields']['RRULE']['BYDAY'] === $curEvent['RRULE']['BYDAY']
			)
			{
				$currentDate = new TypeDate($params['currentEventDateFrom']);
				$currentFromDate = new TypeDate($newParams['arFields']['DATE_FROM']);
				$currentDateWeekday = self::WeekDayByInd($currentDate->format('N'));
				$currentFromDateWeekday = self::WeekDayByInd($currentFromDate->format('N'));

				if (isset($newParams['arFields']['RRULE']['BYDAY'][$currentDateWeekday]))
				{
					unset($newParams['arFields']['RRULE']['BYDAY'][$currentDateWeekday]);
				}

				$newParams['arFields']['RRULE']['BYDAY'][$currentFromDateWeekday] = $currentFromDateWeekday;
			}

			// Check if it's first instance of the series, so we shouldn't create another event
			if (self::Date(self::Timestamp($curEvent['DATE_FROM']), false) === self::Date($currentDateTimestamp, false))
			{
				$newParams['recursionEditMode'] = 'skip';
			}
			else
			{
				// 1. Edit current recurrent event: set finish date with date of current instance
				$arFieldsCurrent = [
					"ID" => $curEvent["ID"],
					"RRULE" => CCalendarEvent::ParseRRULE($curEvent['RRULE']),
				];
				$arFieldsCurrent['RRULE']['UNTIL'] = self::Date($currentDateTimestamp - self::GetDayLen(), false);
				unset($arFieldsCurrent['RRULE']['~UNTIL'], $arFieldsCurrent['RRULE']['COUNT']);

				if (
					!empty($params['arFields']['SECTIONS'][0])
					&& (int)$curEvent['SECTION_ID'] !== (int)$params['arFields']['SECTIONS'][0]
				)
				{
					$arFieldsCurrent['SECTIONS'] = $params['arFields']['SECTIONS'];
				}

				// Save current event
				$id = self::SaveEvent([
					'arFields' => $arFieldsCurrent,
					'silentErrorMode' => $params['silentErrorMode'] ?? null,
					'recursionEditMode' => 'skip',
					'sendInvitations' => false,
					'sendEditNotification' => false,
					'sendInvitesToDeclined' => $params['sendInvitesToDeclined'] ?? null,
					'userId' => $userId,
					'editNextEvents' => true,
					'editParentEvents' => true,
					'checkPermission' => $checkPermission,
					'requestUid' => $params['requestUid'] ?? null,
					'checkLocationOccupancyFields' => $newParams['arFields'],
					'checkLocationOccupancy' => $params['checkLocationOccupancy'] ?? false,
				]);

				unset($newParams['arFields']['ID'],
					$newParams['arFields']['DAV_XML_ID'],
					$newParams['arFields']['G_EVENT_ID'],
					$newParams['recursionEditMode']
				);
			}

			if (empty($newParams['arFields']['DAV_XML_ID']))
			{
				$newParams['arFields']['DAV_XML_ID'] = UidGenerator::createInstance()
					->setPortalName(Util::getServerName())
					->setDate(new Date(
						Util::getDateObject(
							$newParams['arFields']['ORIGINAL_DATE_FROM'] ?? null,
							$newParams['arFields']['SKIP_TIME'] ?? null,
							$newParams['arFields']['TZ_FROM'] ?? null
						)
					))
					->setUserId((int)($newParams['arFields']['OWNER_ID'] ?? null))
					->getUidWithDate()
				;
			}

			$newParams['sendInvitesToDeclined'] = $params['sendInvitesToDeclined'];
			$newParams['editNextEvents'] = true;
			$result = self::SaveEvent($newParams);
			if (!is_array($result))
			{
				$result = [
					'id' => $result,
					'recEventId' => $result,
				];
			}

			if ($recId)
			{
				$recRelatedEvents = CCalendarEvent::GetEventsByRecId($recId, false);

				foreach($recRelatedEvents as $ev)
				{
					if ($ev['ID'] === $result['id'])
					{
						continue;
					}

					$evFromTs = self::Timestamp($ev['DATE_FROM']);

					if ($evFromTs > $currentDateTimestamp)
					{
						$newParams['arFields']['ID'] = $ev['ID'];
						$newParams['arFields']['RRULE'] = CCalendarEvent::ParseRRULE($ev['RRULE']);

						if ($newParams['arFields']['SKIP_TIME'])
						{
							$newParams['arFields']['DATE_FROM'] = self::Date($evFromTs, false);
							$newParams['arFields']['DATE_TO'] = self::Date(self::Timestamp($ev['DATE_TO']), false);
						}
						else
						{
							$newFromTs = self::DateWithNewTime($currentFromTs, $evFromTs);
							$newParams['arFields']['DATE_FROM'] = self::Date($newFromTs);
							$newParams['arFields']['DATE_TO'] = self::Date($newFromTs + $length);
						}


						$newParams['arFields']['RECURRENCE_ID'] = $result['id'];
						$newParams['originalDavXmlId'] = $result['originalDavXmlId'];
						$newParams['arFields']['ORIGINAL_DATE_FROM'] = self::GetOriginalDate(
							$result['originalDateFrom'] ?? null,
							$ev['ORIGINAL_DATE_FROM'] ?? $newParams['currentEventDateFrom'],
							$result['instanceTz']
						);
						$newParams['instanceTz'] = $result['instanceTz'];
						$newParams['editInstance'] = true;

						unset($newParams['arFields']['EXDATE']);

						if (isset($newParams['arFields']['RELATIONS']['ORIGINAL_RECURSION_ID']))
						{
							unset($newParams['arFields']['RELATIONS']);
						}

						self::SaveEvent($newParams);
					}
				}
			}
		}
	}
	else
	{
		if (($params['recursionEditMode'] ?? null) !== 'all')
		{
			$params['recursionEditMode'] = 'skip';
		}
		else
		{
			$params['recursionEditMode'] = '';
		}

		$id = CCalendarEvent::Edit($params);

		if ($id)
		{
			$UFs = $params['UF'] ?? null;
			if(!empty($UFs) && is_array($UFs))
			{
				CCalendarEvent::UpdateUserFields($id, $UFs);

				if (!empty($arFields['IS_MEETING']) && !empty($UFs['UF_WEBDAV_CAL_EVENT']))
				{
					$UF = $GLOBALS['USER_FIELD_MANAGER']->GetUserFields("CALENDAR_EVENT", $id, LANGUAGE_ID);
					self::UpdateUFRights(
						$UFs['UF_WEBDAV_CAL_EVENT'],
						$arFields['ATTENDEES_CODES'] ?? null,
						$UF['UF_WEBDAV_CAL_EVENT'] ?? null
					);
				}
			}
		}

		if ($params['editNextEvents'] === true && $params['editParentEvents'] === false)
		{
			$result['originalDate'] = $params['arFields']['DATE_FROM'];
			$result['originalDavXmlId'] = $params['arFields']['DAV_XML_ID'];
			$result['instanceTz'] = $params['arFields']['TZ_FROM'];
			$result['recEventId'] = $id;
		}

		// Here we should select all events connected with edited via RECURRENCE_ID:
		// It could be original source event (without RECURRENCE_ID) or sibling events
		if (
			$curEvent
			&& !$params['recursionEditMode']
			&& !($params['arFields']['RECURRENCE_ID'] ?? null)
			&& CCalendarEvent::CheckRecurcion($curEvent)
		)
		{
			$events = [];
			$recId = $curEvent['RECURRENCE_ID'] ?: $curEvent['ID'];
			if ($curEvent['RECURRENCE_ID'] && $curEvent['RECURRENCE_ID'] !== $curEvent['ID'])
			{
				$masterEvent = CCalendarEvent::GetById($curEvent['RECURRENCE_ID']);
				if ($masterEvent)
				{
					$events[] = $masterEvent;
				}
			}

			if ($recId)
			{
				$instances = CCalendarEvent::GetList([
					'arFilter' => [
						'RECURRENCE_ID' => $recId,
					],
					'parseRecursion' => false,
					'setDefaultLimit' => false,
				]);

				if ($instances)
				{
					$events = array_merge($events, $instances);
				}
			}

			foreach($events as $ev)
			{
				if ($ev['ID'] !== $curEvent['ID'])
				{
					$newParams = $params;

					$newParams['arFields']['ID'] = $ev['ID'];
					$newParams['arFields']['RECURRENCE_ID'] = $ev['RECURRENCE_ID'];
					$newParams['arFields']['DAV_XML_ID'] = $ev['DAV_XML_ID'];
					$newParams['arFields']['G_EVENT_ID'] = $ev['G_EVENT_ID'];
					$newParams['arFields']['ORIGINAL_DATE_FROM'] = self::GetOriginalDate($arFields['DATE_FROM'], $ev['ORIGINAL_DATE_FROM'], $arFields['TZ_FROM']);
					$newParams['arFields']['CAL_DAV_LABEL'] = $ev['CAL_DAV_LABEL'];
					$newParams['arFields']['RRULE'] = CCalendarEvent::ParseRRULE($ev['RRULE']);
					$newParams['recursionEditMode'] = 'skip';
					$newParams['currentEvent'] = $ev;

					$eventFromTs = self::Timestamp($ev['DATE_FROM']);
					$currentFromTs = self::Timestamp($newParams['arFields']['DATE_FROM']);
					$length = self::Timestamp($newParams['arFields']['DATE_TO']) - self::Timestamp($newParams['arFields']['DATE_FROM']);

					if ($newParams['arFields']['SKIP_TIME'])
					{
						$newParams['arFields']['DATE_FROM'] = $ev['DATE_FROM'];
						$newParams['arFields']['DATE_TO'] = self::Date($eventFromTs + $length, false);
					}
					else
					{
						$newFromTs = self::DateWithNewTime($currentFromTs, $eventFromTs);
						$newParams['arFields']['DATE_FROM'] = self::Date($newFromTs);
						$newParams['arFields']['DATE_TO'] = self::Date($newFromTs + $length);
					}

					if (isset($ev['EXDATE']) && $ev['EXDATE'])
					{
						$newParams['arFields']['EXDATE'] = $ev['EXDATE'];
					}

					if (isset($newParams['arFields']['RELATIONS']['ORIGINAL_RECURSION_ID']))
					{
						unset($newParams['arFields']['RELATIONS']);
					}

					self::SaveEvent($newParams);
				}
			}
		}

		if ($id)
		{
			self::syncChange($id, $arFields, $params, $curEvent ?: null);
		}

		$arFields['ID'] = $id;
		if (($params['overSaving'] ?? false) !== true)
		{
			foreach(GetModuleEvents("calendar", "OnAfterCalendarEventEdit", true) as $arEvent)
			{
				ExecuteModuleEventEx($arEvent, array($arFields, $bNew, $userId));
			}
		}
	}

	self::SetSilentErrorMode($silentErrorModePrev);

	$result['id'] = $id ?? null;

	return $result;
}