33 private ?
int $ownerId;
45 $this->
event = $event;
46 $this->hostId = $hostId;
47 $this->ownerId = $ownerId;
57 $this->
event = $event;
68 public function createEvent(
bool $sendInvitations =
true,
string $externalUserName =
''):
Result
72 if (!$this->doesEventHasCorrectTime())
79 if (!$this->doesEventSatisfyRule())
86 $members = $this->link->getMembers();
87 $users = array_merge([$this->link->getOwnerId()], array_map(
static function ($member){
88 return $member->getId();
91 if (!$this->checkUserAccessibility($users))
98 $eventId = (
new Mappers\Event())->create($this->event, [
99 'sendInvitations' => $sendInvitations
102 $this->
event->setId($eventId);
112 'eventId' => $eventId,
113 'ownerId' => $this->ownerId,
114 'hostId' => $this->hostId,
115 'parentLinkHash' => $this->link->getHash(),
120 'externalUserName' => $externalUserName,
126 'eventLink' => $eventLink,
127 'event' => $this->event,
141 (
new Mappers\Event())->
delete($this->event);
142 $this->notifyEventDeleted();
154 ->setCanceledTimestamp(time())
180 return self::isUserNameCorrect($userName);
183 private static function isUserNameCorrect(
string $userName): bool
185 return $userName !==
'';
190 return check_email($userContact);
196 && PhoneNumber\Parser::getInstance()->parse($userContact)->isValid()
207 $ownerId = (int)($data[
'ownerId'] ??
null);
208 $sectionId = self::getSectionId($userId);
210 $attendeesCodes = [
'U' . $userId,
'U' . $ownerId];
211 $members = $link->getMembers();
212 foreach ($members as $member)
214 $attendeesCodes[] =
'U' . $member->getId();
218 'HOST_NAME' => \CCalendar::GetUserName($userId),
221 'ALLOW_INVITE' =>
true,
222 'MEETING_CREATOR' => $userId,
223 'HIDE_GUESTS' =>
false,
227 'OWNER_ID' => $userId,
228 'NAME' => (string)($data[
'eventName'] ??
''),
229 'DATE_FROM' => (string)($data[
'dateFrom'] ??
''),
230 'DATE_TO' => (string)($data[
'dateTo'] ??
''),
231 'TZ_FROM' => (string)($data[
'timezone'] ??
''),
232 'TZ_TO' => (string)($data[
'timezone'] ??
''),
234 'SECTIONS' => [$sectionId],
235 'EVENT_TYPE' => $data[
'eventType'],
236 'ACCESSIBILITY' =>
'busy',
237 'IMPORTANCE' =>
'normal',
238 'ATTENDEES_CODES' => $attendeesCodes,
239 'MEETING_HOST' => $userId,
240 'IS_MEETING' =>
true,
241 'MEETING' => $meeting,
242 'DESCRIPTION' => (string)($data[
'description'] ??
''),
251 'ownerId' => (int)($request[
'ownerId'] ?? 0),
252 'dateFrom' => (string)($request[
'dateFrom'] ??
''),
253 'dateTo' => (string)($request[
'dateTo'] ??
''),
254 'timezone' => (string)($request[
'timezone'] ??
''),
255 'description' => (string)($request[
'description'] ??
''),
256 'eventType' => Dictionary::EVENT_TYPE[
'shared'],
262 $user = CUser::GetByID($userId)->Fetch();
263 $userName = ($user[
'NAME'] ??
'') .
' ' . ($user[
'LAST_NAME'] ??
'');
270 if (!empty($userName))
272 $result =
Loc::getMessage(
'CALENDAR_SHARING_EVENT_MANAGER_EVENT_NAME', [
273 '#GUEST_NAME#' => trim($userName),
278 $result =
Loc::getMessage(
'CALENDAR_SHARING_EVENT_MANAGER_EVENT_NAME_WITHOUT_GUEST');
291 'ownerId' =>(int)($request[
'ownerId'] ?? 0),
292 'dateFrom' => (string)($request[
'dateFrom'] ??
''),
293 'dateTo' => (string)($request[
'dateTo'] ??
''),
294 'timezone' => (string)($request[
'timezone'] ??
''),
295 'description' => (string)($request[
'description'] ??
''),
296 'eventType' => Dictionary::EVENT_TYPE[
'shared_crm'],
320 $eventId = $fields[
'ID'];
324 self::updateEventSharingLink($eventLink, $fields);
340 $eventLink->setCanceledTimestamp(time());
355 public static function onSharingEventMeetingStatusChange(
357 string $currentMeetingStatus,
358 array $userEventBeforeChange,
359 bool $isAutoAccept =
false
363 $eventLink = (
new Sharing\Link\Factory())->getEventLinkByEventId((
int)$userEventBeforeChange[
'PARENT_ID']);
370 $ownerId = $eventLink->getOwnerId();
372 if ($ownerId !== $userId && !$isAutoAccept)
374 self::onSharingEventGuestStatusChange($currentMeetingStatus, $userEventBeforeChange, $eventLink, $userId);
376 else if ($userEventBeforeChange[
'EVENT_TYPE'] === Dictionary::EVENT_TYPE[
'shared'])
378 self::onSharingCommonEventMeetingStatusChange($eventLink);
380 else if ($userEventBeforeChange[
'EVENT_TYPE'] === Dictionary::EVENT_TYPE[
'shared_crm'])
382 self::onSharingCrmEventStatusChange($currentMeetingStatus, $userEventBeforeChange, $userId, $ownerId);
386 private static function onSharingEventGuestStatusChange(
387 string $currentMeetingStatus,
389 Sharing\Link\EventLink $eventLink,
393 \CCalendarNotify::Send([
394 'mode' => $currentMeetingStatus ===
"Y" ?
'accept' :
'decline',
395 'name' => $event[
'NAME'],
396 'from' => $event[
"DATE_FROM"],
397 'to' => $event[
"DATE_TO"],
398 'location' => \CCalendar::GetTextLocation($userEvent[
"LOCATION"] ?? null),
400 'eventId' => $event[
'PARENT_ID'],
401 'userId' => $eventLink->getOwnerId(),
406 private static function onSharingCommonEventMeetingStatusChange(Sharing\Link\EventLink $eventLink): void
409 $event = (
new Mappers\Event())->getById($eventLink->getEventId());
411 $host = CUser::GetByID($eventLink->getHostId())->Fetch();
412 $email = $host[
'PERSONAL_MAILBOX'] ??
null;
413 $phone = $host[
'PERSONAL_PHONE'] ??
null;
414 $userContact = !empty($email) ? $email : $phone;
416 $notificationService =
null;
417 if ($userContact && self::isEmailCorrect($userContact))
419 $notificationService = (
new Sharing\Notification\Mail())
420 ->setEventLink($eventLink)
425 $notificationService?->notifyAboutMeetingStatus($userContact);
428 private static function onSharingCrmEventStatusChange(
429 string $currentMeetingStatus,
430 array $userEventBeforeChange,
435 if (!Loader::includeModule(
'crm'))
440 $previousMeetingStatus = $userEventBeforeChange[
'MEETING_STATUS'] ??
null;
443 $currentMeetingStatus === Dictionary::MEETING_STATUS[
'Yes']
444 && $previousMeetingStatus === Dictionary::MEETING_STATUS[
'Question']
445 && $userId === $ownerId
448 self::onSharingCrmEventConfirmed(
449 (
int)$userEventBeforeChange[
'PARENT_ID'],
450 $userEventBeforeChange[
'DATE_FROM'] ??
null,
451 $userEventBeforeChange[
'TZ_FROM'] ??
null,
456 $currentMeetingStatus === Dictionary::MEETING_STATUS[
'No']
458 $previousMeetingStatus === Dictionary::MEETING_STATUS[
'Question']
459 || $previousMeetingStatus === Dictionary::MEETING_STATUS[
'Yes']
463 self::onSharingCrmEventDeclined((
int)$userEventBeforeChange[
'PARENT_ID']);
474 private static function onSharingCrmEventConfirmed(
int $eventId, ?
string $dateFrom, ?
string $timezone): void
476 $crmDealLink = self::getCrmDealLink($eventId);
478 $activity = \CCrmActivity::GetByCalendarEventId($eventId,
false);
480 if ($crmDealLink && $activity)
482 (
new Sharing\Crm\NotifyManager($crmDealLink, Sharing\Crm\NotifyManager::NOTIFY_TYPE_EVENT_CONFIRMED))
483 ->sendSharedCrmActionsEvent(
486 \CCrmOwnerType::Activity,
492 private static function onSharingCrmEventDeclined(
int $eventId): void
494 $sharingFactory =
new Sharing\Link\Factory();
497 $eventLink = $sharingFactory->getEventLinkByEventId($eventId);
500 $crmDealLink = $sharingFactory->getLinkByHash($eventLink->getParentLinkHash());
503 $event = (
new Mappers\Event())->getById($eventId);
505 $completeActivityStatus = Sharing\Crm\ActivityManager::STATUS_CANCELED_BY_MANAGER;
507 $userId = \CCalendar::GetUserId();
508 if ($userId === 0 || $userId === $event->getEventHost()->getId())
510 $completeActivityStatus = Sharing\Crm\ActivityManager::STATUS_CANCELED_BY_CLIENT;
513 (
new Sharing\Crm\ActivityManager($eventId))
514 ->completeSharedCrmActivity($completeActivityStatus)
517 if ($crmDealLink->getContactId() > 0)
519 Crm\Integration\Calendar\Notification\Manager::getSenderInstance($crmDealLink)
520 ->setCrmDealLink($crmDealLink)
521 ->setEventLink($eventLink)
523 ->sendCrmSharingCancelled()
528 $email = CUser::GetByID($eventLink->getHostId())->Fetch()[
'PERSONAL_MAILBOX'] ??
null;
529 if (!is_string($email))
534 $eventLink->setCanceledTimestamp(time());
535 (
new Sharing\Notification\Mail())
536 ->setEventLink($eventLink)
538 ->notifyAboutMeetingCancelled($email)
545 public static function onSharingEventEdited(
int $eventId, array $previousFields): void
548 $event = (
new Mappers\Event())->getById($eventId);
549 if ($event instanceof Event)
551 $oldEvent = Event::fromBuilder(
new EventBuilderFromArray($previousFields));
552 if ($event->getSpecialLabel() === Dictionary::EVENT_TYPE[
'shared'])
554 self::onSharingCommonEventEdited($event, $oldEvent);
556 else if ($event->getSpecialLabel() === Dictionary::EVENT_TYPE[
'shared_crm'])
558 self::onSharingCrmEventEdited($event, $oldEvent);
563 private static function onSharingCommonEventEdited(Event $event, Event $oldEvent): void
565 $sharingFactory =
new Sharing\Link\Factory();
568 $eventLink = $sharingFactory->getEventLinkByEventId($event->getId());
574 $host = CUser::GetByID($eventLink->getHostId())->Fetch();
575 $email = $host[
'PERSONAL_MAILBOX'] ??
null;
576 $phone = $host[
'PERSONAL_PHONE'] ??
null;
577 $userContact = !empty($email) ? $email : $phone;
579 $notificationService =
null;
580 if ($userContact && self::isEmailCorrect($userContact))
582 $notificationService = (
new Sharing\Notification\Mail())
583 ->setEventLink($eventLink)
585 ->setOldEvent($oldEvent)
589 if ($notificationService !==
null)
591 $notificationService->notifyAboutSharingEventEdit($userContact);
595 private static function onSharingCrmEventEdited(Event $event, Event $oldEvent): void
597 if (!Loader::includeModule(
'crm'))
602 (
new Sharing\Crm\ActivityManager($event->getId()))
606 $sharingFactory =
new Sharing\Link\Factory();
609 $eventLink = $sharingFactory->getEventLinkByEventId($event->getId());
610 if (!$eventLink instanceof Sharing\Link\EventLink)
616 $crmDealLink = $sharingFactory->getLinkByHash($eventLink->getParentLinkHash());
617 if (!$crmDealLink instanceof Sharing\Link\CrmDealLink)
622 if ($crmDealLink->getContactId() > 0)
624 Crm\Integration\Calendar\Notification\Manager::getSenderInstance($crmDealLink)
625 ->setCrmDealLink($crmDealLink)
626 ->setEventLink($eventLink)
628 ->setOldEvent($oldEvent)
629 ->sendCrmSharingEdited()
634 $email = CUser::GetByID($eventLink->getHostId())->Fetch()[
'PERSONAL_MAILBOX'] ??
null;
635 if (!is_string($email))
640 (
new Sharing\Notification\Mail())
641 ->setEventLink($eventLink)
643 ->setOldEvent($oldEvent)
644 ->notifyAboutSharingEventEdit($email)
649 public static function onSharingEventDeleted(
int $eventId,
string $eventType): void
652 $eventLink = (
new Sharing\Link\Factory())->getEventLinkByEventId($eventId);
657 if ($eventType === Dictionary::EVENT_TYPE[
'shared'])
659 self::onSharingCommonEventDeclined($eventLink);
661 else if ($eventType === Dictionary::EVENT_TYPE[
'shared_crm'])
663 self::onSharingCrmEventDeclined($eventId);
669 public static function onSharingCommonEventDeclined(Sharing\Link\EventLink $eventLink)
673 $event = (
new Mappers\Event())->getById($eventLink->getEventId());
675 $host = CUser::GetByID($eventLink->getHostId())->Fetch();
676 $email = $host[
'PERSONAL_MAILBOX'] ??
null;
677 $phone = $host[
'PERSONAL_PHONE'] ??
null;
678 $userContact = !empty($email) ? $email : $phone;
680 $notificationService =
null;
681 if ($userContact && self::isEmailCorrect($userContact))
683 $notificationService = (
new Sharing\Notification\Mail())
684 ->setEventLink($eventLink)
689 if ($notificationService !==
null)
691 $notificationService->notifyAboutMeetingCancelled($userContact);
697 $userId = \CCalendar::GetUserId();
698 if ($userId !== 0 && $userId !== $eventLink->getHostId())
700 $ownerId = $eventLink->getOwnerId();
701 $event = EventTable::query()
703 ->where(
'PARENT_ID', $eventLink->getEventId())
704 ->whereIn(
'EVENT_TYPE', self::getSharingEventTypes())
705 ->where(
'OWNER_ID', $ownerId)
709 if ($event[
'ID'] ??
false)
711 EventTable::update((
int)$event[
'ID'], [
'MEETING_STATUS' => Dictionary::MEETING_STATUS[
'No']]);
721 private static function updateEventSharingLink(
Sharing\
Link\
EventLink $eventLink, array $fields): void
723 if (!empty($fields[
'DATE_TO']))
729 $eventLink->setDateExpire($expireDate);
739 private static function getCrmDealLink(
int $eventId): ?Link\CrmDealLink
741 $sharingLinkFactory =
new Sharing\Link\Factory();
743 $eventLink = $sharingLinkFactory->getEventLinkByEventId($eventId);
744 if ($eventLink instanceof Sharing\Link\EventLink)
747 $crmDealLink = $sharingLinkFactory->getLinkByHash($eventLink->getParentLinkHash());
748 if ($crmDealLink instanceof Sharing\Link\CrmDealLink)
757 private function doesEventHasCorrectTime(): bool
759 $start =
new DateTime($this->event->getStart()->toString());
760 $end =
new DateTime($this->event->getEnd()->toString());
766 if ($fromTs < time())
771 $ownerDate = new \DateTime(
'now',
new \DateTimeZone(
'UTC'));
773 $holidays = $this->getYearHolidays();
774 $intersectedHolidays = array_filter($holidays,
static fn($holiday) => in_array($holiday, [
775 $ownerDate->setTimestamp($fromTs + $offset)->format(
'j.m'),
776 $ownerDate->setTimestamp($toTs + $offset)->format(
'j.m'),
779 if (!empty($intersectedHolidays))
787 private function getYearHolidays(): array
789 return explode(
',', \COption::GetOptionString(
'calendar',
'year_holidays',
Loc::getMessage(
'EC_YEAR_HOLIDAYS_DEFAULT')));
792 private function doesEventSatisfyRule(): bool
794 $start =
new DateTime($this->event->getStart()->toString());
795 $end =
new DateTime($this->event->getEnd()->toString());
799 $rule = $this->link->getSharingRule();
800 $eventDurationMinutes = ($toTs - $fromTs) / 60;
801 if ($eventDurationMinutes !== $rule->getSlotSize())
807 foreach ($rule->getRanges() as $range)
809 foreach ($range->getWeekdays() as $weekday)
811 $availableTime[$weekday] ??= [];
812 $availableTime[$weekday][] = [
813 'from' => $range->getFrom(),
814 'to' => $range->getTo(),
822 ), $availableTime[$weekday]);
824 if (!empty($intersected))
826 $from = min(array_column($intersected,
'from'));
827 $to = max(array_column($intersected,
'to'));
829 $availableTime[$weekday] = [...$notIntersected, [
838 $minutesFrom = ($fromTs % 86400) / 60;
839 $minutesTo = ($toTs % 86400) / 60;
840 $weekday = (int)gmdate(
'N', $fromTs) % 7;
841 foreach ($availableTime[$weekday] as $range)
843 if ($minutesFrom >= $range[
'from'] - $offset && $minutesTo <= $range[
'to'] - $offset)
852 private function separate($take, $array): array
854 return array_reduce($array, fn($s, $e) => $take($e) ? [[...$s[0], $e], $s[1]] : [$s[0], [...$s[1], $e]], [[], []]);
860 private function checkUserAccessibility(array $userIds): bool
862 $start =
new DateTime($this->event->getStart()->toString());
863 $end =
new DateTime($this->event->getEnd()->toString());
867 return (
new SharingAccessibilityManager([
868 'userIds' => $userIds,
869 'timestampFrom' => $fromTs,
870 'timestampTo' => $toTs,
871 ]))->checkUsersAccessibility();
878 private static function getSectionId($userId)
880 $result = \CCalendarSect::GetList([
882 'OWNER_ID' => $userId,
883 'CAL_TYPE' =>
'user',
890 $createdSection = \CCalendarSect::CreateDefault([
892 'ownerId' => $userId,
894 $result[] = $createdSection;
897 return $result[0][
'ID'];
903 private function notifyEventDeleted()
905 return \CCalendarNotify::Send([
906 'mode' =>
'cancel_sharing',
907 'userId' => $this->hostId,
908 'guestId' => $this->ownerId,
909 'eventId' => $this->event->getId(),
910 'from' => $this->event->getStart()->toString(),
911 'to' => $this->event->getEnd()->toString(),
912 'name' => $this->event->getName(),
919 $event = (
new Mappers\Event)->getById($eventLink->getEventId());
922 $event = \CCalendarEvent::GetList([
924 'ID' => $event->getId(),
926 'fetchAttendees' =>
true,
927 'checkPermissions' =>
false,
928 'parseRecursion' =>
false,
929 'setDefaultLimit' =>
false,
933 $event = $event[0] ??
null;
936 $event[
'ATTENDEES'] = [$eventLink->getOwnerId(), $eventLink->getHostId()];
937 \CCalendar::SaveEvent([
938 'arFields' => $event,
939 'userId' => $eventLink->getOwnerId(),
940 'checkPermission' =>
false,
941 'sendInvitations' =>
true