1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
calendar_event.php
См. документацию.
1<?php
2IncludeModuleLangFile($_SERVER["DOCUMENT_ROOT"].BX_ROOT."/modules/calendar/classes/general/calendar.php");
3
25use Bitrix\Main\Entity\ReferenceField;
38use Bitrix\Disk\AttachedObject;
39use Bitrix\Disk\Uf\FileUserType;
45
47{
48 public static
50 $sendPush = true;
51
52 private static
53 $fields = [],
54 $userIndex = [],
55 $eventUserFields = [],
56 $attendeeBelongingToEvent = [],
57 $collabIdByParent = [],
58 $isAddIcalFailEmailError = false,
62 ;
63
64 public static $defaultSelectEvent = [
65 'ID',
66 'PARENT_ID',
67 'CREATED_BY',
68 'OWNER_ID',
69 'EVENT_TYPE',
70 'NAME',
71 'DATE_FROM',
72 'DATE_TO',
73 'TZ_FROM',
74 'TZ_TO',
75 'TZ_OFFSET_FROM',
76 'TZ_OFFSET_TO',
77 'DATE_FROM_TS_UTC',
78 'DATE_TO_TS_UTC',
79 'DT_SKIP_TIME',
80 'ACCESSIBILITY',
81 'IMPORTANCE',
82 'RRULE',
83 'EXDATE',
84 'SECTION_ID',
85 'CAL_TYPE',
86 'MEETING_STATUS',
87 'MEETING_HOST',
88 'IS_MEETING',
89 'DT_LENGTH',
90 'PRIVATE_EVENT',
91 ];
92
93 public static function CheckRRULE($recRule = [])
94 {
95 if (is_array($recRule) && isset($recRule['FREQ']) && $recRule['FREQ'] !== 'WEEKLY' && isset($recRule['BYDAY']))
96 {
97 unset($recRule['BYDAY']);
98 }
99
100 return $recRule;
101 }
102
121 public static function Edit($params = [])
122 {
123 global $DB, $CACHE_MANAGER;
124 $entryFields = $params['arFields'] ?? [];
125 $arAffectedSections = [];
126 $entryChanges = [];
127 $changedFields = [];
128 $sendInvitations = ($params['sendInvitations'] ?? null) !== false;
129 $sendEditNotification = ($params['sendEditNotification'] ?? null) !== false;
130 $checkLocationOccupancy = ($params['checkLocationOccupancy'] ?? null) === true;
131 $result = false;
132
133 // Get current user id
134 $userId = (isset($params['userId']) && (int)$params['userId'] > 0)
135 ? (int)$params['userId']
136 : CCalendar::GetCurUserId();
137
138 if (!$userId && isset($entryFields['CREATED_BY']))
139 {
140 $userId = (int)$entryFields['CREATED_BY'];
141 }
142
143 $isNewEvent = !isset($entryFields['ID']) || !$entryFields['ID'];
144 $entryFields['TIMESTAMP_X'] = CCalendar::Date(time(), true, false);
145
146 // Current event
147 $currentEvent = [];
148 if (!empty($entryFields['IS_MEETING']) && !isset($entryFields['ATTENDEES']) && isset($entryFields['ATTENDEES_CODES']))
149 {
150 $entryFields['ATTENDEES'] = \CCalendar::getDestinationUsers($entryFields['ATTENDEES_CODES']);
151 }
152
153 if (!$isNewEvent)
154 {
155 $currentEvent = $params['currentEvent'] ?? self::GetById($entryFields['ID'], $params['checkPermission'] ?? true);
156
157 if (!isset($entryFields['LOCATION']) || !is_array($entryFields['LOCATION']))
158 {
159 $entryFields['LOCATION'] = [
160 'NEW' => $entryFields['LOCATION'] ?? null,
161 ];
162 }
163
164 if (
165 isset($entryFields['MEETING'])
166 && is_array($entryFields['MEETING'])
167 && is_array($currentEvent['MEETING'])
168 && !isset($entryFields['MEETING']['CHAT_ID'])
169 && isset($currentEvent['MEETING']['CHAT_ID'])
170 )
171 {
172 $entryFields['MEETING']['CHAT_ID'] = $currentEvent['MEETING']['CHAT_ID'];
173 }
174
175 if (empty($entryFields['LOCATION']['OLD']))
176 {
177 $entryFields['LOCATION']['OLD'] = $currentEvent['LOCATION'] ?? null;
178 }
179
180 if (
181 !empty($currentEvent['IS_MEETING'])
182 && !isset($entryFields['ATTENDEES'])
183 && $currentEvent['PARENT_ID'] === $currentEvent['ID']
184 && !empty($entryFields['IS_MEETING'])
185 )
186 {
187 $entryFields['ATTENDEES'] = [];
188 $attendees = self::GetAttendees($currentEvent['PARENT_ID']);
189 if (!empty($attendees[$currentEvent['PARENT_ID']]))
190 {
191 foreach ($attendees[$currentEvent['PARENT_ID']] as $attendee)
192 {
193 $entryFields['ATTENDEES'][] = $attendee['USER_ID'];
194 }
195 }
196 }
197
198 if (!empty($currentEvent['PARENT_ID']))
199 {
200 $entryFields['PARENT_ID'] = (int)$currentEvent['PARENT_ID'];
201 }
202 }
203
204 if (self::CheckFields($entryFields, $currentEvent, $userId))
205 {
206 $attendees = (isset($entryFields['ATTENDEES']) && is_array($entryFields['ATTENDEES']))
207 ? $entryFields['ATTENDEES']
208 : [];
209
210 if (
211 ($entryFields['CAL_TYPE'] ?? null) !== Rooms\Manager::TYPE
212 && (empty($entryFields['PARENT_ID']) || $entryFields['PARENT_ID'] === $entryFields['ID'])
213 )
214 {
215 $fromTs = $entryFields['DATE_FROM_TS_UTC'] ?? null;
216 $toTs = $entryFields['DATE_TO_TS_UTC'] ?? null;
217 if (($entryFields['DT_SKIP_TIME'] ?? null) !== "Y")
218 {
219 $fromTs += (int)date('Z', $fromTs);
220 $toTs += (int)date('Z', $toTs);
221 }
222
223 $entryFields['LOCATION'] = self::checkLocationField($entryFields['LOCATION'] ?? null, $isNewEvent);
224
225 if ($checkLocationOccupancy)
226 {
227 self::checkLocationOccupancy($entryFields, $params, $currentEvent, $userId);
228 }
229
230 $entryFields['LOCATION'] = Bitrix\Calendar\Rooms\Util::setLocation(
231 $entryFields['LOCATION']['OLD'],
232 $entryFields['LOCATION']['NEW'],
233 [
234 // UTC timestamp + date('Z', $timestamp) /*offset of the server*/
235 'dateFrom' => CCalendar::Date($fromTs, $entryFields['DT_SKIP_TIME'] !== "Y"),
236 'dateTo' => CCalendar::Date($toTs, $entryFields['DT_SKIP_TIME'] !== "Y"),
237 'parentParams' => $params,
238 'name' => $entryFields['NAME'],
239 'persons' => count($attendees),
240 'attendees' => $attendees,
241 'bRecreateReserveMeetings' => ($entryFields['LOCATION']['RE_RESERVE'] ?? null) !== 'N',
242 'checkPermission' => $params['checkPermission'] ?? null,
243 ]
244 );
245 }
246 else
247 {
248 $entryFields['LOCATION'] = self::checkLocationField($entryFields['LOCATION'], $isNewEvent);
249 $entryFields['LOCATION'] = $entryFields['LOCATION']['NEW'];
250 }
251
252 // Section
253 if (isset($entryFields['SECTION_ID']))
254 {
255 $sectionId = (int)$entryFields['SECTION_ID'];
256 }
257 else
258 {
259 $sectionId = !empty($entryFields['SECTIONS'][0])
260 ? (int)$entryFields['SECTIONS'][0]
261 : false;
262 }
263
264 if (!$sectionId)
265 {
266 $sectionId = self::tryingToFindEventSection($isNewEvent, $entryFields, $userId, $currentEvent);
267 }
268
269 $entryFields['SECTION_ID'] = $sectionId;
270 $arAffectedSections[] = $sectionId;
271
272 $section = CCalendarSect::GetList(['arFilter' => ['ID' => $sectionId],
273 'checkPermissions' => false,
274 'getPermissions' => false,
275 ])[0] ?? null;
276
277 // Here we take type and owner parameters from section data
278 if ($section)
279 {
280 $entryFields['CAL_TYPE'] = $section['CAL_TYPE'];
281 $entryFields['OWNER_ID'] = $section['OWNER_ID'] ?? '';
282
283 if (
284 $section['CAL_TYPE'] === Dictionary::CALENDAR_TYPE['group']
285 && Collab\Entity\SectionEntityHelper::getIfCollab($sectionId)
286 && $entryFields['EVENT_TYPE'] !== Dictionary::EVENT_TYPE['shared_collab']
287 )
288 {
289 $entryFields['EVENT_TYPE'] = Dictionary::EVENT_TYPE['collab'];
290 }
291 }
292
293 if (($entryFields['CAL_TYPE'] ?? null) === 'user')
294 {
295 $CACHE_MANAGER->ClearByTag('calendar_user_'.$entryFields['OWNER_ID']);
296 }
297
298 if ($isNewEvent)
299 {
300 if (!isset($entryFields['CREATED_BY']))
301 {
302 $entryFields['CREATED_BY'] = (
303 !empty($entryFields['IS_MEETING'])
304 && ($entryFields['CAL_TYPE'] ?? null) === 'user'
305 && !empty($entryFields['OWNER_ID'])
306 ) ? $entryFields['OWNER_ID'] : $userId;
307 }
308
309 if (!isset($entryFields['DATE_CREATE']))
310 {
311 $entryFields['DATE_CREATE'] = $entryFields['TIMESTAMP_X'];
312 }
313 }
314 else
315 {
316 $arAffectedSections[] = $currentEvent['SECTION_ID'] ?? $currentEvent['SECT_ID'];
317 }
318
319 if (
320 !isset($entryFields['IS_MEETING'])
321 && isset($entryFields['ATTENDEES'])
322 && is_array($entryFields['ATTENDEES'])
323 && empty($entryFields['ATTENDEES'])
324 )
325 {
326 $entryFields['IS_MEETING'] = false;
327 }
328 if (!empty($entryFields['IS_MEETING']) && !$isNewEvent)
329 {
330 $entryChanges = self::CheckEntryChanges($entryFields, $currentEvent);
331 $changedFields = array_column($entryChanges, 'fieldKey');
332 }
333 else if (
334 !empty($entryFields['IS_MEETING'])
335 && !empty($params['editInstance'])
336 && !empty($params['instanceChanges'])
337 )
338 {
339 $entryChanges = $params['instanceChanges'];
340 $changedFields = array_column($entryChanges, 'fieldKey');
341 }
342
343 $attendeesCodes = $entryFields['ATTENDEES_CODES'] ?? null;
344 if (is_array($attendeesCodes))
345 {
346 $entryFields['ATTENDEES_CODES'] = implode(',', $attendeesCodes);
347 }
348
349 if ($entryFields['CAL_TYPE'] === Dictionary::CALENDAR_TYPE['open_event'])
350 {
351 // for open events meeting status stored for each attendee in event_attendee table
352 $entryFields['MEETING_STATUS'] = 'N';
353 }
354 else if (
355 !isset($entryFields['MEETING_STATUS'])
356 && !empty($entryFields['MEETING_HOST'])
357 && (int)$entryFields['MEETING_HOST'] === (int)($entryFields['CREATED_BY'] ?? null)
358 )
359 {
360 $entryFields['MEETING_STATUS'] = 'H';
361 }
362 else if (!isset($entryFields['MEETING_STATUS']) && !$currentEvent)
363 {
364 $entryFields['MEETING_STATUS'] = 'Y';
365 }
366
367 if (isset($entryFields['MEETING']) && is_array($entryFields['MEETING']))
368 {
369 $entryFields['~MEETING'] = $entryFields['MEETING'];
370 $entryFields['MEETING']['REINVITE'] = false;
371 $meetingHostSettings = UserSettings::get($entryFields['MEETING_HOST'] ?? null);
372 $entryFields['MEETING']['MAIL_FROM'] =
373 $entryFields['MEETING']['MAIL_FROM']
374 ?? $meetingHostSettings['sendFromEmail']
375 ?? null
376 ;
377 $entryFields['MEETING'] = serialize($entryFields['MEETING']);
378 }
379
380 if (isset($entryFields['RELATIONS']) && is_array($entryFields['RELATIONS']))
381 {
382 $entryFields['~RELATIONS'] = $entryFields['RELATIONS'];
383 $entryFields['RELATIONS'] = serialize($entryFields['RELATIONS']);
384 }
385
386 if (
387 isset($entryFields['REMIND'])
388 && (
389 $isNewEvent
390 || !$entryFields['IS_MEETING']
391 || (int)$entryFields['CREATED_BY'] === $userId
392 || ($params['updateReminders'] ?? null) === true
393 )
394 )
395 {
396 $reminderList = CCalendarReminder::prepareReminder($entryFields['REMIND']);
397 }
398 elseif (!empty($currentEvent['REMIND']))
399 {
400 $reminderList = CCalendarReminder::prepareReminder($currentEvent['REMIND']);
401 }
402 else
403 {
404 $reminderList = [];
405 }
406 $entryFields['REMIND'] = serialize($reminderList);
407
408 if (
409 isset($entryFields['SYNC_STATUS'])
410 && !in_array($entryFields['SYNC_STATUS'],Bitrix\Calendar\Sync\Google\Dictionary::SYNC_STATUS, true)
411 )
412 {
413 $entryFields['SYNC_STATUS'] = null;
414 }
415
416 if (isset($entryFields['EXDATE']) && is_array($entryFields['EXDATE']))
417 {
418 $entryFields['EXDATE'] = implode(';', $entryFields['EXDATE']);
419 }
420 $entryFields['EXDATE'] = !empty($entryFields['EXDATE'])
421 ? self::convertExDatesToInternalFormat($entryFields['EXDATE'])
422 : ''
423 ;
424
425 $entryFields['RRULE'] = self::convertRuleUntilToInternalFormat($entryFields['RRULE'] ?? null);
426
427 $AllFields = self::GetFields();
428 $dbFields = [];
429
430 foreach($entryFields as $field => $val)
431 {
432 if (
433 isset($AllFields[$field])
434 && $field !== "ID"
435 && is_scalar($val)
436 )
437 {
438 $dbFields[$field] = $val;
439 }
440 }
441
442 if (!empty($dbFields['NAME']))
443 {
444 $dbFields['NAME'] = Emoji::encode($dbFields['NAME']);
445 }
446 if (!empty($dbFields['DESCRIPTION']))
447 {
448 $dbFields['DESCRIPTION'] = Emoji::encode($dbFields['DESCRIPTION']);
449 }
450 if (!empty($dbFields['LOCATION']))
451 {
452 $dbFields['LOCATION'] = Emoji::encode($dbFields['LOCATION']);
453 }
454
455 CTimeZone::Disable();
456
457 $isOpenEvent = ($entryFields['CAL_TYPE'] ?? null) === Dictionary::CALENDAR_TYPE['open_event'];
458
459 if ($isNewEvent) // Add
460 {
461 $eventId = $DB->Add("b_calendar_event", $dbFields, ['DESCRIPTION', 'MEETING', 'EXDATE']);
462 }
463 else // Update
464 {
465 $eventId = $entryFields['ID'];
466 $strUpdate = $DB->PrepareUpdate("b_calendar_event", $dbFields);
467 $strSql =
468 "UPDATE b_calendar_event SET ".
469 $strUpdate.
470 " WHERE ID=". (int)$eventId;
471
472 $DB->QueryBind($strSql, array(
473 'DESCRIPTION' => Emoji::encode($entryFields['DESCRIPTION'] ?? ''),
474 'MEETING' => $entryFields['MEETING'] ?? null,
475 'EXDATE' => $entryFields['EXDATE'] ?? null,
476 ));
477 // update separated attendee fields
478 if ($isOpenEvent)
479 {
480 self::updateEventAttendee($eventId, $userId, $changedFields, $dbFields);
481 }
482 }
483
484 CTimeZone::Enable();
485
486 if (
487 $userId
488 && $params
489 && ($params['overSaving'] ?? null) !== true
490 && \Bitrix\Calendar\Sync\Util\RequestLogger::isEnabled()
491 )
492 {
493 $loggerParams = $params;
494 $loggerParams['arFields'] = $entryFields;
495 $loggerParams['loggerUuid'] = $eventId;
496
497 (new \Bitrix\Calendar\Sync\Util\RequestLogger($userId, 'portal_edit'))->write($loggerParams);
498 }
499
503// if ($isNewEvent && !isset($dbFields['DAV_XML_ID']))
504// {
505// $strSql =
506// "UPDATE b_calendar_event SET ".
507// $DB->PrepareUpdate("b_calendar_event", ['DAV_XML_ID' => (int)$eventId]).
508// " WHERE ID = ". (int)$eventId;
509// $DB->Query($strSql);
510// }
514// if (
515// !Util::isSectionStructureConverted()
516// && ($isNewEvent || $sectionId !== $currentEvent['SECTION_ID']))
517// {
518// self::ConnectEventToSection($eventId, $sectionId);
519// }
520
521 if (!empty($arAffectedSections))
522 {
523 CCalendarSect::UpdateModificationLabel($arAffectedSections);
524 }
525
526 if (
527 !empty($entryFields['IS_MEETING'])
528 || (!$isNewEvent && !empty($currentEvent['IS_MEETING']))
529 )
530 {
531 if (empty($entryFields['PARENT_ID']))
532 {
533 Internals\EventTable::update((int)$eventId, [
534 'PARENT_ID' => (int)$eventId,
535 ]);
536 }
537
538 if (
539 (empty($entryFields['PARENT_ID']) || $entryFields['PARENT_ID'] === $eventId)
540 && !$isOpenEvent
541 )
542 {
543 self::CreateChildEvents($eventId, $entryFields, $params, $entryChanges);
544 }
545
546 if ((empty($entryFields['PARENT_ID']) || $entryFields['PARENT_ID'] === $eventId) && !empty($entryFields['RECURRENCE_ID']))
547 {
548 self::UpdateParentEventExDate($entryFields['RECURRENCE_ID'], $entryFields['ORIGINAL_DATE_FROM'], $entryFields['ATTENDEES']);
549 }
550
551 if (empty($entryFields['PARENT_ID']))
552 {
553 $entryFields['PARENT_ID'] = (int)$eventId;
554 }
555 }
556 else if (($isNewEvent && empty($entryFields['PARENT_ID'])) || (!$isNewEvent && empty($currentEvent['PARENT_ID'])))
557 {
558 Internals\EventTable::update((int)$eventId, [
559 'PARENT_ID' => (int)$eventId,
560 ]);
561
562 if (empty($entryFields['PARENT_ID']))
563 {
564 $entryFields['PARENT_ID'] = (int)$eventId;
565 }
566 }
567
568 // Update reminders for event
570 'id' => $eventId,
571 'reminders' => $reminderList,
572 'arFields' => $entryFields,
573 'userId' => $userId,
574 ]);
575
576 if ((empty($entryFields['PARENT_ID']) || $entryFields['PARENT_ID'] === $eventId))
577 {
578 self::updateSearchIndex((int)$eventId, [
579 'userId' => $userId,
580 'updateAllByParent' => true,
581 'isNew' => $isNewEvent,
582 'changedFields' => $changedFields,
583 ]);
584 }
585
586 $nowUtc = time() - date('Z');
587 // Send invitations and notifications
588 if (
589 !empty($entryFields['IS_MEETING'])
590 && ($params['overSaving'] ?? null) !== true
591 )
592 {
593 $fromTo = self::GetEventFromToForUser($entryFields, $entryFields['OWNER_ID'] ?? null);
594
595 // If it's event in the past we're skipping notifications.
596 // The past is the past...
597 if (isset($entryFields['DATE_TO_TS_UTC']) && $entryFields['DATE_TO_TS_UTC'] > $nowUtc)
598 {
599 if (
600 $sendEditNotification
601 && (int)($entryFields['PARENT_ID'] ?? null) !== (int)$eventId
602 && !empty($entryChanges)
603 && (
604 ($entryFields['MEETING_STATUS'] ?? null) === 'Y'
605 || ($entryFields['MEETING_STATUS'] ?? null) === 'H'
606 )
607 )
608 {
609 // third problematic place
610
611 if (
612 (!empty($entryFields['MEETING_HOST']) && (int)$entryFields['MEETING_HOST'] === (int)$userId)
613 || self::checkAttendeeBelongsToEvent($entryFields['PARENT_ID'] ?? null, $userId)
614 )
615 {
616 $CACHE_MANAGER->ClearByTag('calendar_user_'.$entryFields['OWNER_ID']);
617 CCalendarNotify::Send([
618 'mode' => 'change_notify',
619 'name' => $entryFields['NAME'] ?? null,
620 "from" => $fromTo['DATE_FROM'] ?? null,
621 "to" => $fromTo['DATE_TO'] ?? null,
622 "location" => CCalendar::GetTextLocation($entryFields["LOCATION"] ?? null),
623 "guestId" => $entryFields['OWNER_ID'] ?? null,
624 "eventId" => $entryFields['PARENT_ID'] ?? null,
625 "userId" => \CCalendar::GetUserId(),
626 "fields" => $entryFields,
627 "isSharing" => ($entryFields['EVENT_TYPE'] ?? null) === Dictionary::EVENT_TYPE['shared'],
628 "entryChanges" => $entryChanges,
629 ]);
630 }
631 }
632 elseif (
633 (int)($entryFields['PARENT_ID'] ?? null) !== $eventId
634 && ($entryFields['MEETING_STATUS'] ?? null) === 'Q'
635 && $sendInvitations
636 )
637 {
638 $CACHE_MANAGER->ClearByTag('calendar_user_'.$entryFields['OWNER_ID'] ?? '');
639 CCalendarNotify::Send(array(
640 "mode" => 'invite',
641 "name" => $entryFields['NAME'] ?? null,
642 "from" => $fromTo['DATE_FROM'] ?? null,
643 "to" => $fromTo['DATE_TO'] ?? null,
644 "location" => CCalendar::GetTextLocation($entryFields["LOCATION"] ?? null),
645 "guestId" => $entryFields['OWNER_ID'] ?? null,
646 "eventId" => $entryFields['PARENT_ID'] ?? null,
647 "userId" => $entryFields['MEETING_HOST'] ?? \CCalendar::GetUserId(),
648 "isSharing" => ($entryFields['EVENT_TYPE'] ?? null) === Dictionary::EVENT_TYPE['shared'],
649 "fields" => $entryFields,
650 ));
651 }
652 }
653 }
654
655 if (
656 !empty($entryFields['IS_MEETING'])
657 && !empty($entryFields['ATTENDEES_CODES'])
658 && (int)($entryFields['PARENT_ID'] ?? null) === (int)$eventId
659 && ($params['overSaving'] ?? null) !== true
660 && isset($entryFields['DATE_TO_TS_UTC'])
661 && $entryFields['DATE_TO_TS_UTC'] > $nowUtc
662 )
663 {
665 'eventId' => $eventId,
666 'arFields' => $entryFields,
667 'attendeesCodes' => $attendeesCodes,
668 ]);
669 }
670
671 CCalendar::ClearCache('event_list');
672
673 if (($entryFields['ACCESSIBILITY'] ?? '') === 'absent')
674 {
675 (new \Bitrix\Calendar\Integration\Intranet\Absence())->cleanCache();
676 }
677
678 $result = $eventId;
679
680 if (!empty($entryFields['LOCATION']))
681 {
682 Rooms\Manager::setEventIdForLocation($eventId, $entryFields['LOCATION']);
683 }
684
685 if ($isNewEvent)
686 {
687 foreach(EventManager::getInstance()->findEventHandlers("calendar", "OnAfterCalendarEntryAdd") as $event)
688 {
690 $event,
691 [
692 $eventId,
693 $entryFields,
694 [],
695 ]
696 );
697 }
698
699 if (($entryFields['PARENT_ID'] ?? null) === $eventId && $entryFields['CAL_TYPE'] !== 'location')
700 {
702 }
703 }
704 else
705 {
706 foreach(EventManager::getInstance()->findEventHandlers("calendar", "OnAfterCalendarEntryUpdate") as $event)
707 {
709 $event,
710 [
711 $eventId,
712 $entryFields,
713 $currentEvent['ATTENDEE_LIST'] ?? null,
714 ]
715 );
716 }
717 }
718
719 $pullUserId = (isset($entryFields['OWNER_ID']) && (int)$entryFields['OWNER_ID'] > 0)
720 ? (int)$entryFields['OWNER_ID']
721 : $userId
722 ;
723
724 if (
725 $pullUserId > 0
726 && ($params['overSaving'] ?? null) !== true
727 && self::$sendPush
728 )
729 {
730 $entryFields['ID'] = $result;
731 $parentEventId = $entryFields['PARENT_ID'] ?? $result;
732
733 $entryFields = self::calculateUserOffset($pullUserId, $entryFields);
734
735 if (isset($params['attendeeStatuses']))
736 {
737 $attendeeList = $params['attendeeStatuses'];
738 }
739 else
740 {
741 // TODO: open event handle later
742 $attendeeListResult = $dbFields['CAL_TYPE'] === Dictionary::CALENDAR_TYPE['open_event']
743 ? self::getAttendeeList([], [$parentEventId])
744 : self::getAttendeeList([$parentEventId])
745 ;
746 $attendeeList = $attendeeListResult['attendeeList'][$parentEventId] ?? [];
747 }
748
749 $entryFields = self::PreHandleEvent($entryFields);
750 $skipTime = $entryFields['DT_SKIP_TIME'] === 'Y';
751 $dateFromFormatted = self::getDateInJsFormat(
752 CCalendar::createDateTimeObjectFromString($entryFields['DATE_FROM']),
753 $skipTime,
754 );
755 $dateToFormatted = self::getDateInJsFormat(
756 CCalendar::createDateTimeObjectFromString($entryFields['DATE_TO']),
757 $skipTime,
758 );
759 $isDaylightSavingTimezone = !empty($entryFields['TZ_FROM'])
760 ? CCalendar::isDaylightSavingTimezone($entryFields['TZ_FROM'])
761 : ''
762 ;
763
764 $collabId = 0;
765
766 if (
767 !empty($entryFields['EVENT_TYPE'])
768 && in_array(
769 $entryFields['EVENT_TYPE'],
770 [Dictionary::EVENT_TYPE['collab'], Dictionary::EVENT_TYPE['shared_collab']],
771 true
772 )
773 )
774 {
775 $collabId = self::getCollabIdByParentId($entryFields['PARENT_ID']);
776 }
777
779 PushCommand::EditEvent,
780 $pullUserId,
781 [
782 'fields' => array_merge($entryFields, [
783 'ATTENDEE_LIST' => $attendeeList,
784 'DATE_FROM_FORMATTED' => $dateFromFormatted,
785 'DATE_TO_FORMATTED' => $dateToFormatted,
786 'IS_DAYLIGHT_SAVING_TZ' => $isDaylightSavingTimezone,
787 'COLLAB_ID' => $collabId,
788 'permissions' => self::getEventPermissions($entryFields, $pullUserId),
789 ]),
790 'newEvent' => $isNewEvent,
791 'requestUid' => $params['requestUid'] ?? null,
792 ]
793 );
794 }
795 }
796
797 return $result;
798 }
799
806 public static function GetById($id, bool $checkPermissions = true, $loadOriginalRecursion = false)
807 {
808 if ($id > 0)
809 {
810 $event = self::GetList([
811 'arFilter' => [
812 'ID' => $id,
813 'DELETED' => 'N',
814 ],
815 'parseRecursion' => false,
816 'fetchAttendees' => $checkPermissions,
817 'checkPermissions' => $checkPermissions,
818 'setDefaultLimit' => false,
819 'loadOriginalRecursion' => $loadOriginalRecursion,
820 ]);
821 if (!empty($event[0]) && is_array($event[0]))
822 {
823 return $event[0];
824 }
825 }
826
827 return false;
828 }
829
830 public static function GetList($params = [])
831 {
832 $originalParams = $params;
833
834 $isIntranetEnabled = CCalendar::IsIntranetEnabled();
835 $checkPermissions = ($params['checkPermissions'] ?? null) !== false;
836 $bCache = CCalendar::CacheTime() > 0;
837 $params['setDefaultLimit'] = ($params['setDefaultLimit'] ?? null) === true;
838 $userId = (isset($params['userId']) && $params['userId']) ? (int)$params['userId'] : CCalendar::GetCurUserId();
839 $params['parseDescription'] = $params['parseDescription'] ?? true;
840 $params['fetchAttendees'] = ($params['fetchAttendees'] ?? null) !== false;
841 $resultEntryList = null;
842 $userIndex = null;
843
844 CTimeZone::Disable();
845 if ($bCache)
846 {
847 $cache = new CPHPCache;
848 $cacheId = 'eventlist'.md5(serialize($params)).CCalendar::GetOffset();
849 if ($checkPermissions)
850 {
851 $cacheId .= 'perm' . CCalendar::GetCurUserId() . '|';
852 }
853 if (CCalendar::IsSocNet() && CCalendar::IsSocnetAdmin())
854 {
855 $cacheId .= 'socnetAdmin|';
856 }
857 $cachePath = CCalendar::CachePath().'event_list';
858
859 if ($cache->InitCache(CCalendar::CacheTime(), $cacheId, $cachePath))
860 {
861 $cachedData = $cache->GetVars();
862 if (isset($cachedData['dateTimeFormat']) && $cachedData['dateTimeFormat'] === FORMAT_DATETIME)
863 {
864 $resultEntryList = $cachedData["resultEntryList"];
865 $userIndex = $cachedData["userIndex"];
866 }
867 }
868 }
869
870 if (!$bCache || !isset($resultEntryList))
871 {
872 $arFilter = $params['arFilter'];
873 $resultEntryList = [];
874
875 $listResult = self::getListOrm($params);
876 [$eventList, $parentMeetingIdList, $involvedUsersIdList, $openEventParentIdList] = $listResult;
877
878 if (!empty($params['fetchAttendees']) && !empty($parentMeetingIdList))
879 {
880 $attendeeListData = self::getAttendeeList($parentMeetingIdList, $openEventParentIdList);
881 $attendeeList = $attendeeListData['attendeeList'];
882 $involvedUsersIdList = array_unique(array_merge($involvedUsersIdList, $attendeeListData['userIdList']));
883 }
884 $userIndex = self::getUsersDetails($involvedUsersIdList);
885
886 $parentCollabConnections = self::getParentCollabConnections($eventList);
887
888 foreach ($eventList as $event)
889 {
890 $isOpenEvent = $event['CAL_TYPE'] === Dictionary::CALENDAR_TYPE['open_event'];
891 if (
892 $event['IS_MEETING']
893 && isset($attendeeList[$event['PARENT_ID']])
894 && $isIntranetEnabled
895 )
896 {
897 if ($isOpenEvent)
898 {
899 // keep only STATUS=Y attendees for open events
900 // cause there is no logic for decline user of open event
901 $event['ATTENDEE_LIST'] = array_filter(
902 $attendeeList[$event['PARENT_ID']],
903 static fn (array $a) => $a['status'] === 'Y'
904 );
905 }
906 else
907 {
908 $event['ATTENDEE_LIST'] = $attendeeList[$event['PARENT_ID']];
909 }
910 }
911 else if (!empty($params['fetchAttendees']))
912 {
913 // for open event we not need to HOST user presented in attendee list
914 if (!$isOpenEvent)
915 {
916 $event['ATTENDEE_LIST'] = [
917 [
918 'id' => (int)$event['MEETING_HOST'],
919 'entryId' => $event['ID'],
920 'status' => in_array($event['MEETING_STATUS'], ['Y', 'N', 'Q', 'H'])
921 ? $event['MEETING_STATUS']
922 : 'H'
923 ,
924 ],
925 ];
926 }
927 }
928 else
929 {
930 $event['ATTENDEE_LIST'] = [];
931 }
932
933 if ($checkPermissions)
934 {
935 $checkPermissionsForEvent = $userId !== (int)($event['CREATED_BY'] ?? null); // It's creator
936
937 // It's event in user's calendar
938 if (
939 $checkPermissionsForEvent
940 && ($event['CAL_TYPE'] ?? null) === 'user'
941 && $userId === (int)$event['OWNER_ID']
942 )
943 {
944 $checkPermissionsForEvent = false;
945 }
946
947 if (
948 $checkPermissionsForEvent
949 && $event['IS_MEETING']
950 && is_array($event['ATTENDEE_LIST'] ?? null)
951 && !empty($event['ATTENDEE_LIST'])
952 )
953 {
954 foreach($event['ATTENDEE_LIST'] as $attendee)
955 {
956 if ((int)$attendee['id'] === $userId)
957 {
958 $checkPermissionsForEvent = false;
959 break;
960 }
961 }
962 }
963
964 if ($checkPermissionsForEvent)
965 {
966 $event = self::ApplyAccessRestrictions($event, $userId);
967 }
968 }
969
970 if ($event !== false)
971 {
972 $event['COLLAB_ID'] = self::getCollabIdByEvent($event, $parentCollabConnections);
973
974 $event = self::PreHandleEvent($event, [
975 'parseDescription' => $params['parseDescription'],
976 ]);
977
978 if (!empty($params['parseRecursion']) && self::CheckRecurcion($event))
979 {
980 self::ParseRecursion($resultEntryList, $event, [
981 'userId' => $userId,
982 'fromLimit' => $arFilter["FROM_LIMIT"] ?? null,
983 'toLimit' => $arFilter["TO_LIMIT"] ?? null,
984 'loadLimit' => $params["limit"] ?? null,
985 'instanceCount' => $params['maxInstanceCount'] ?? false,
986 'preciseLimits' => $params['preciseLimits'] ?? false,
987 ]);
988 }
989 else
990 {
991 self::HandleEvent($resultEntryList, $event, $userId);
992 }
993 }
994 }
995
996 if ($bCache)
997 {
998 $cache->StartDataCache(CCalendar::CacheTime(), $cacheId, $cachePath);
999
1000 try
1001 {
1002 $cache->EndDataCache(
1003 [
1004 'resultEntryList' => $resultEntryList,
1005 'userIndex' => $userIndex,
1006 'dateTimeFormat' => FORMAT_DATETIME,
1007 ]
1008 );
1009 }
1010 catch (\Exception $e)
1011 {
1012 // A temporary fix for http://jabber.bx/view.php?id=215935
1013 $logger = new \Bitrix\Main\Diag\EventLogger('calendar', 'REMIND_DEBUG');
1014
1015 $logger->debug('Found Closure. Message: ' . $e->getMessage());
1016
1017 try
1018 {
1019 $logger->debug('Found Closure. Params: ' . \Bitrix\Main\Web\Json::encode($originalParams));
1020 $logger->debug(
1021 'Found Closure. To cache: ' . \Bitrix\Main\Web\Json::encode([
1022 'resultEntryList' => $resultEntryList,
1023 'userIndex' => $userIndex,
1024 'dateTimeFormat' => FORMAT_DATETIME,
1025 ])
1026 );
1027 }
1028 catch (\Throwable)
1029 {}
1030 }
1031 }
1032 }
1033
1034 if (is_array($userIndex))
1035 {
1036 foreach($userIndex as $userIndexId => $userIndexDetails)
1037 {
1038 self::$userIndex[$userIndexId] = $userIndexDetails;
1039 }
1040 }
1041
1042 CTimeZone::Enable();
1043
1044 return $resultEntryList;
1045 }
1046
1047 private static function getListOrm($params = [])
1048 {
1049 $eventList = [];
1050
1051 $userId = (isset($params['userId']) && $params['userId']) ? (int)$params['userId'] : CCalendar::GetCurUserId();
1052 $fetchSection = $params['fetchSection'] ?? null;
1053 $orderFields = $params['arOrder'] ?? [];
1054 $filterFields = $params['arFilter'] ?? [];
1055 $selectFields = $params['arSelect'] ?? [];
1056 $getUf = ($params['getUserfields'] ?? null) !== false;
1057 $eventFields = self::getEventFields();
1058
1059 if (isset($filterFields["DELETED"]) && ($filterFields["DELETED"] === false))
1060 {
1061 unset($filterFields["DELETED"]);
1062 }
1063 elseif (!isset($filterFields["DELETED"]))
1064 {
1065 $filterFields["DELETED"] = "N";
1066 }
1067
1068 if (($params['setDefaultLimit'] ?? null) !== false) // Deprecated
1069 {
1070 if (!isset($filterFields['FROM_LIMIT'])) // default 3 month back
1071 {
1072 $filterFields['FROM_LIMIT'] = CCalendar::Date(time() - 31 * 3 * 24 * 3600, false);
1073 }
1074
1075 if (!isset($filterFields['TO_LIMIT'])) // default one year into the future
1076 {
1077 $filterFields['TO_LIMIT'] = CCalendar::Date(time() + 365 * 24 * 3600, false);
1078 }
1079 }
1080
1081 $query = Internals\EventTable::query();
1082 $attendeesQuery = Internals\EventTable::query();
1083 $attendeeIds = [$userId];
1084 $joinOpenEvents = !empty(
1085 array_intersect(['ID', 'PARENT_ID', 'RECURRENCE_ID', 'CAL_TYPE', 'SECTION'], array_keys($filterFields))
1086 );
1087
1088 if (!empty($filterFields) && is_array($filterFields))
1089 {
1090 foreach ($filterFields as $key => $value)
1091 {
1092 if (is_string($value) && !$value)
1093 {
1094 continue;
1095 }
1096
1097 switch ($key)
1098 {
1099 case 'FROM_LIMIT':
1100 $timestamp = (int)CCalendar::Timestamp($value, false);
1101 if ($timestamp)
1102 {
1103 $query->where('DATE_TO_TS_UTC', '>=', $timestamp);
1104 $attendeesQuery->where('DATE_TO_TS_UTC', '>=', $timestamp);
1105 }
1106 break;
1107 case 'TO_LIMIT':
1108 $timestamp = (int)CCalendar::Timestamp($value, false);
1109 if ($timestamp)
1110 {
1111 $toTimestamp = $timestamp + CCalendar::GetDayLen() - 1;
1112 $query->where('DATE_FROM_TS_UTC', '<=', $toTimestamp);
1113 $attendeesQuery->where('DATE_FROM_TS_UTC', '<=', $toTimestamp);
1114 }
1115 break;
1116 case 'MEETING_HOST':
1117 case 'CREATED_BY':
1118 if (is_array($value))
1119 {
1120 $value = array_map(static function($item) {
1121 return (int)$item;
1122 }, $value);
1123
1124 if (empty($value))
1125 {
1126 $value = [''];
1127 }
1128
1129 $query->whereIn($key, $value);
1130 }
1131 else if ((int)$value)
1132 {
1133 $query->where($key, $value);
1134 }
1135 break;
1136 case 'ID':
1137 case 'PARENT_ID':
1138 case 'RECURRENCE_ID':
1139 if (!is_array($value))
1140 {
1141 $value = [$value];
1142 }
1143
1144 $value = array_map('intval', $value);
1145
1146 if (empty($value))
1147 {
1148 $value = [''];
1149 }
1150
1151 $query->whereIn($key, $value);
1152 $attendeesQuery->whereIn($key, $value);
1153
1154 break;
1155 case 'OWNER_ID':
1156 if (!is_array($value))
1157 {
1158 $value = [$value];
1159 }
1160
1161 $value = array_map('intval', $value);
1162
1163 if (empty($value))
1164 {
1165 $value = [''];
1166 }
1167 else
1168 {
1169 $attendeeIds = $value;
1170 }
1171
1172 $query->whereIn('OWNER_ID', $value);
1173
1174 break;
1175 case '>ID':
1176 if ((int)$value)
1177 {
1178 $query->where('ID', '>', $value);
1179 }
1180 break;
1181 case 'CAL_TYPE':
1182 if (!is_array($value))
1183 {
1184 $value = [$value];
1185 }
1186
1187 $joinOpenEvents = $joinOpenEvents && in_array(Dictionary::CALENDAR_TYPE['open_event'], $value, true);
1188
1189 $calTypes = array_diff($value, [Dictionary::CALENDAR_TYPE['open_event']]);
1190 if (!empty($calTypes))
1191 {
1192 $query->whereIn('CAL_TYPE', $calTypes);
1193 }
1194
1195 break;
1196 case 'SECTION':
1197 if (!is_array($value))
1198 {
1199 $value = [$value];
1200 }
1201
1202 if (is_array($value))
1203 {
1204 $sections = [];
1205 foreach ($value as $item)
1206 {
1207 if ((int)$item)
1208 {
1209 $sections[] = (int)$item;
1210 }
1211 }
1212
1213 if (!empty($sections))
1214 {
1215 $openEventSection = self::getOpenEventSection($userId);
1216 $joinOpenEvents = $joinOpenEvents && in_array($openEventSection?->getId(), $sections, true);
1217 $sections = array_diff($sections, [$openEventSection?->getId()]);
1218
1219 if (Util::isSectionStructureConverted())
1220 {
1221 $query->whereIn('SECTION_ID', $sections);
1222 }
1223 else
1224 {
1225 $query->whereIn('EVENT_SECT.SECT_ID', $sections);
1226 }
1227 }
1228 }
1229 break;
1230 case 'ACTIVE_SECTION':
1231 if ($value === 'Y' && Util::isSectionStructureConverted())
1232 {
1233 $query->where('SECTION.ACTIVE', $value);
1234 }
1235 break;
1236 case '*SEARCHABLE_CONTENT':
1237 $searchText = \Bitrix\Main\ORM\Query\Filter\Helper::matchAgainstWildcard($value);
1238 $query->whereMatch('SEARCHABLE_CONTENT', $searchText);
1239 break;
1240 case '*%SEARCHABLE_CONTENT':
1241 $query->whereLike('SEARCHABLE_CONTENT', '%' . $value . '%');
1242 break;
1243 case '=UF_CRM_CAL_EVENT':
1244 $query->where('UF_CRM_CAL_EVENT', $value);
1245 break;
1246 default:
1247 if (in_array($key, $eventFields, true))
1248 {
1249 if (is_array($value))
1250 {
1251 $query->whereIn($key, $value);
1252 }
1253 else
1254 {
1255 $query->where($key, $value);
1256 }
1257 }
1258 break;
1259 }
1260 }
1261 }
1262
1263 if (empty($selectFields))
1264 {
1265 $selectFields = ['*'];
1266 }
1267
1268 $attendeesQuery->setSelect([
1269 ...$selectFields,
1270 'ATTENDEES.*',
1271 'EVENT_OPTIONS.*',
1272 ]);
1273
1274 $joinSection = $fetchSection
1275 && ($filterFields['ACTIVE_SECTION'] ?? null) === 'Y'
1276 && Util::isSectionStructureConverted()
1277 ;
1278
1279 if ($joinSection)
1280 {
1281 $selectFields['SECTION_DAV_XML_ID'] = 'SECTION.CAL_DAV_CAL';
1282 }
1283
1284 if ($getUf)
1285 {
1286 $selectFields[] = 'UF_*';
1287 }
1288
1289 $query
1290 ->registerRuntimeField(
1291 new ReferenceField(
1292 'EVENT_OPTIONS',
1293 OpenEvents\Internals\OpenEventOptionTable::getEntity(),
1294 Join::on('this.ID', 'ref.EVENT_ID'),
1295 ),
1296 );
1297 $selectFields[] = 'EVENT_OPTIONS.*';
1298
1299 $query->setSelect($selectFields);
1300
1301 $orderList = [];
1302 foreach ($orderFields as $key => $order)
1303 {
1304 if (in_array($key, $eventFields, true))
1305 {
1306 $orderList[$key] = (mb_strtoupper($order) === 'DESC') ? 'DESC' : 'ASC';
1307 }
1308 }
1309
1310 if (!empty($orderList))
1311 {
1312 $query->setOrder($orderList);
1313 $attendeesQuery->setOrder($orderList);
1314 }
1315
1316 if (isset($params['limit']) && (int)$params['limit'] > 0)
1317 {
1318 $query->setLimit((int)$params['limit']);
1319 $attendeesQuery->setLimit((int)$params['limit']);
1320 }
1321
1322 if (($params['loadOriginalRecursion'] ?? null) === true)
1323 {
1324 self::applyLoadOriginalRecursionLogic($query);
1325 }
1326
1327 $queryResult = $query->exec();
1328 $hasAttendees = $query->getEntity()->hasField('ATTENDEES');
1329 $hasOriginalRecursion = $query->getEntity()->hasField('ORIGINAL_RECURSION');
1330
1331 $parentMeetingIdList = [];
1332 $involvedUsersIdList = [];
1333
1334 $openEventList = [];
1335
1336 while ($eventObject = $queryResult->fetchObject())
1337 {
1338 [$event, $parentMeetings, $involvedUsers] = self::prepareEventObject(
1339 $userId,
1340 $eventObject,
1341 $hasAttendees,
1342 $hasOriginalRecursion
1343 );
1344
1345 $parentMeetingIdList = [...$parentMeetingIdList, ...$parentMeetings];
1346 $involvedUsersIdList = [...$involvedUsersIdList, ...$involvedUsers];
1347
1348 $eventList[] = $event;
1349
1350 // if open event presented in query results
1351 // add it in separate array to check in next query processing
1352 if (!empty($event['CAL_TYPE']) && $event['CAL_TYPE'] === Dictionary::CALENDAR_TYPE['open_event'])
1353 {
1354 $openEventList[] = (int)$event['ID'];
1355 }
1356 }
1357
1358 if (!$joinOpenEvents)
1359 {
1360 return [$eventList, array_unique($parentMeetingIdList), array_unique($involvedUsersIdList), []];
1361 }
1362
1363 $attendeesQuery
1364 ->registerRuntimeField(
1365 (new ReferenceField(
1366 'ATTENDEES',
1367 Internals\EventAttendeeTable::getEntity(),
1368 Join::on('this.ID', 'ref.EVENT_ID')
1369 ->whereIn('ref.OWNER_ID', $attendeeIds)
1370 ->where('ref.MEETING_STATUS', 'Y')
1371 ->where('ref.DELETED', 'N')
1372 ,
1373 ))
1374 ->configureJoinType(Join::TYPE_INNER)
1375 ,
1376 )
1377 ->registerRuntimeField(
1378 new ReferenceField(
1379 'EVENT_OPTIONS',
1380 OpenEvents\Internals\OpenEventOptionTable::getEntity(),
1381 Join::on('this.ID', 'ref.EVENT_ID'),
1382 ),
1383 )
1384 ->where('DELETED', 'N')
1385 ->where('CAL_TYPE', Dictionary::CALENDAR_TYPE['open_event'])
1386 ;
1387
1388 $attendeesQueryResult = $attendeesQuery->exec();
1389 $hasAttendees = $attendeesQuery->getEntity()->hasField('ATTENDEES');
1390 $openEventParentMeetingIdList = [];
1391
1392 while ($eventObject = $attendeesQueryResult->fetchObject())
1393 {
1394 [$event, $parentMeetings, $involvedUsers] = self::prepareEventObject($userId, $eventObject, $hasAttendees);
1395
1396 $parentMeetingIdList = [...$parentMeetingIdList, ...$parentMeetings];
1397 $involvedUsersIdList = [...$involvedUsersIdList, ...$involvedUsers];
1398 $openEventParentMeetingIdList = [...$openEventParentMeetingIdList, ...$parentMeetings];
1399
1400 // replace same open_event from previous query with attendee field from current query to prevent duplicates
1401 // or add as new
1402 if (($key = array_search((int)$event['ID'], $openEventList, true)) !== false)
1403 {
1404 $eventList[$key] = $event;
1405 }
1406 else
1407 {
1408 $eventList[] = $event;
1409 }
1410 }
1411
1412 return [
1413 $eventList,
1414 array_unique($parentMeetingIdList),
1415 array_unique($involvedUsersIdList),
1416 array_unique($openEventParentMeetingIdList),
1417 ];
1418 }
1419
1420 private static function prepareEventObject(
1421 int $userId,
1422 Internals\EO_Event $eventObject,
1423 bool $hasAttendees,
1424 bool $hasOriginalRecursion = false
1425 ): array
1426 {
1427 $event = $eventObject->collectValues();
1428
1429 //we don't need it here
1430 unset($event['UTS_OBJECT']);
1431 unset($event['SECTION']);
1432
1433 // transform types from fetchObject-style to fetch-style
1434 // next booleans and strings fields sensitive for frontend
1435 // and also we need to support BC for rest api
1436 $stringFields = [
1437 'ID',
1438 'OWNER_ID',
1439 'CREATED_BY',
1440 'SECTION_ID',
1441 'TZ_OFFSET_FROM',
1442 'TZ_OFFSET_TO',
1443 'DATE_FROM_TS_UTC',
1444 'DATE_TO_TS_UTC',
1445 'MEETING_HOST',
1446 ];
1447 foreach ($stringFields as $sField)
1448 {
1449 if (!isset($event[$sField]))
1450 {
1451 continue;
1452 }
1453 $event[$sField] = (string)$event[$sField];
1454 }
1455
1456 $boolFields = [
1457 'ACTIVE',
1458 'DELETED',
1459 'DT_SKIP_TIME',
1460 ];
1461 foreach ($boolFields as $bField)
1462 {
1463 if (!isset($event[$bField]))
1464 {
1465 continue;
1466 }
1467 $event[$bField] = $event[$bField] === true ? 'Y' : 'N';
1468 }
1469
1470 // to prevent rest api method fails when try to on-fly create depth fields of MEETING array (like 0194353)
1471 $notEmptyFields = [
1472 'SYNC_STATUS' => '',
1473 'MEETING' => '',
1474 'EVENT_TYPE' => '',
1475 'ATTENDEES_CODES' => '',
1476 'RECURRENCE_ID' => 0,
1477 ];
1478 foreach ($notEmptyFields as $nesField => $emptyValue)
1479 {
1480 if (!isset($event[$nesField]))
1481 {
1482 continue;
1483 }
1484 $event[$nesField] = $event[$nesField] === $emptyValue ? null : $event[$nesField];
1485 }
1486
1487 $event['SECTION_DAV_XML_ID'] = $eventObject->getSection()?->getCalDavCal();
1488
1489 if (isset($event['PARENT_ID']))
1490 {
1491 $event['PARENT_ID'] = (string)$event['PARENT_ID'];
1492 }
1493
1494 $isFullDay = ($event['DT_SKIP_TIME'] ?? null) === 'Y';
1495
1496 if (!empty($event['DATE_FROM']))
1497 {
1498 $event['DATE_FROM_FORMATTED'] = self::getDateInJsFormat($event['DATE_FROM'], $isFullDay);
1499 $event['DATE_FROM'] = (string)$event['DATE_FROM'];
1500 }
1501
1502 if (!empty($event['DATE_TO']))
1503 {
1504 $event['DATE_TO_FORMATTED'] = self::getDateInJsFormat($event['DATE_TO'], $isFullDay);
1505 $event['DATE_TO'] = (string)$event['DATE_TO'];
1506 }
1507
1508 if (!empty($event['ORIGINAL_DATE_FROM']))
1509 {
1510 $event['ORIGINAL_DATE_FROM'] = (string)$event['ORIGINAL_DATE_FROM'];
1511 }
1512
1513 if (!empty($event['TIMESTAMP_X']))
1514 {
1515 $event['TIMESTAMP_X'] = (string)$event['TIMESTAMP_X'];
1516 }
1517
1518 if (!empty($event['DATE_CREATE']))
1519 {
1520 $event['DATE_CREATE'] = (string)$event['DATE_CREATE'];
1521 }
1522
1523 $event['IS_DAYLIGHT_SAVING_TZ'] = !empty($event['TZ_FROM'])
1525 : ''
1526 ;
1527
1528 $event['SECT_ID'] = $event['SECTION_ID'] ?? null;
1529 if (
1530 (int)($event['IS_MEETING'] ?? 0) > 0
1531 || ($event['CAL_TYPE'] ?? null) === Dictionary::CALENDAR_TYPE['open_event']
1532 )
1533 {
1534 $event['IS_MEETING'] = true;
1535 }
1536 else
1537 {
1538 $event['IS_MEETING'] = false;
1539 }
1540
1541 if (empty($event['NAME']))
1542 {
1543 $event['NAME'] = Loc::getMessage('EC_T_NEW_EVENT');
1544 }
1545 else
1546 {
1547 $event['NAME'] = Emoji::decode($event['NAME']);
1548 }
1549
1550 if (!empty($event['DESCRIPTION']))
1551 {
1552 $event['DESCRIPTION'] = Emoji::decode($event['DESCRIPTION']);
1553 }
1554
1555 if (!empty($event['LOCATION']))
1556 {
1557 $event['LOCATION'] = Emoji::decode($event['LOCATION']);
1558 }
1559
1560 if (!empty($event['DT_LENGTH']) && is_numeric($event['DT_LENGTH']))
1561 {
1562 $event['DT_LENGTH'] = (int)$event['DT_LENGTH'];
1563 }
1564
1565 $parentMeetingIdList = [];
1566 $involvedUsersIdList = [];
1567
1568 if (!empty($event['IS_MEETING']) && !empty($event['PARENT_ID']) && CCalendar::IsIntranetEnabled())
1569 {
1570 $parentMeetingIdList[] = $event['PARENT_ID'];
1571 }
1572
1573 if (!empty($event['CREATED_BY']))
1574 {
1575 $involvedUsersIdList[] = $event['CREATED_BY'];
1576 }
1577
1578 if (
1579 !empty($event['IS_MEETING'])
1580 && $event['CAL_TYPE'] === Dictionary::CALENDAR_TYPE['user']
1581 && (int)$event['OWNER_ID'] === $userId
1582 && !$event['SECTION_ID']
1583 )
1584 {
1585 $defaultMeetingSection = CCalendar::GetMeetingSection($userId);
1586 if (!$defaultMeetingSection || !CCalendarSect::GetById($defaultMeetingSection, false))
1587 {
1588 $sectRes = CCalendarSect::GetSectionForOwner($event['CAL_TYPE'], $userId);
1589 $defaultMeetingSection = $sectRes['sectionId'];
1590 }
1591
1592 // to support rest api BC cast to string
1593 $event['SECT_ID'] = (string)$defaultMeetingSection;
1594 $event['SECTION_ID'] = (string)$defaultMeetingSection;
1595 }
1596
1597 $eventOptions = $eventObject->get('EVENT_OPTIONS');
1598 $event['OPTIONS'] = $eventOptions ? $eventOptions->collectValues() : null;
1599
1600 // replace fields at host event from external attendee
1602 if ($hasAttendees && ($currentAttendee = $eventObject->get('ATTENDEES')))
1603 {
1604 //TODO: for test purposes keep it for a while
1605 // this rows should stored only on actual event table
1606// if (isset($event['OWNER_ID']))
1607// {
1608// $event['OWNER_ID'] = $currentAttendee->getOwnerId();
1609// }
1610// if (isset($event['CREATED_BY']))
1611// {
1612// $event['CREATED_BY'] = $currentAttendee->getCreatedBy();
1613// }
1614// $event['~TYPE'] = 'open_event';
1615 if (isset($event['MEETING_STATUS']))
1616 {
1617 $event['MEETING_STATUS'] = $currentAttendee->getMeetingStatus();
1618 }
1619 if (isset($event['SECTION_ID']))
1620 {
1621 // to support rest api BC cast to string
1622 $event['SECTION_ID'] = (string)$currentAttendee->getSectionId();
1623 $event['SECT_ID'] = (string)$currentAttendee->getSectionId();
1624 }
1625 if (!empty($currentAttendee->getColor()))
1626 {
1627 $event['COLOR'] = $currentAttendee->getColor();
1628 }
1629 if (isset($event['REMIND']))
1630 {
1631 $event['REMIND'] = $currentAttendee->getRemind();
1632 }
1633 if (isset($event['SYNC_STATUS']))
1634 {
1635 $event['SYNC_STATUS'] = $currentAttendee->getSyncStatus();
1636 }
1637 $event['CURRENT_ATTENDEE_ID'] = $currentAttendee->getId();
1638 $parentMeetingIdList[] = $event['ID'];
1639 }
1640
1642 if (
1643 $hasOriginalRecursion
1644 && $originalRecursion = $eventObject->get('ORIGINAL_RECURSION')
1645 )
1646 {
1647 $event['ORIGINAL_RECURSION_ID'] = $originalRecursion->getOriginalRecursionEventId();
1648 }
1649
1650 return [$event, $parentMeetingIdList, $involvedUsersIdList];
1651 }
1652
1653 private static function applyLoadOriginalRecursionLogic(Query $query): void
1654 {
1655 $query->registerRuntimeField(
1656 (new Reference(
1657 'ORIGINAL_RECURSION',
1658 Internals\EventOriginalRecursionTable::class,
1659 Join::on('this.PARENT_ID', 'ref.PARENT_EVENT_ID'),
1660 ))
1661 ->configureJoinType(Join::TYPE_LEFT)
1662 );
1663 $query->setSelect(
1664 array_merge(
1665 $query->getSelect(),
1666 ['ORIGINAL_RECURSION_ID' => 'ORIGINAL_RECURSION.ORIGINAL_RECURSION_EVENT_ID']
1667 )
1668 );
1669 }
1670
1671
1672 private static function getDateInJsFormat(DateTime|Bitrix\Main\Type\DateTime $date, $isFullDay): string
1673 {
1674 if ($isFullDay)
1675 {
1676 return $date->format('D M d Y');
1677 }
1678
1679 return $date->format('D M d Y H:i:s');
1680 }
1681
1682 private static function GetFields()
1683 {
1684 global $DB;
1685 if (!count(self::$fields))
1686 {
1688 self::$fields = array(
1689 "ID" => Array("FIELD_NAME" => "CE.ID", "FIELD_TYPE" => "int"),
1690 "PARENT_ID" => Array("FIELD_NAME" => "CE.PARENT_ID", "FIELD_TYPE" => "int"),
1691 "DELETED" => Array("FIELD_NAME" => "CE.DELETED", "FIELD_TYPE" => "string"),
1692 "CAL_TYPE" => Array("FIELD_NAME" => "CE.CAL_TYPE", "FIELD_TYPE" => "string"),
1693 "SYNC_STATUS" => Array("FIELD_NAME" => "CE.SYNC_STATUS", "FIELD_TYPE" => "string"),
1694 "OWNER_ID" => Array("FIELD_NAME" => "CE.OWNER_ID", "FIELD_TYPE" => "int"),
1695 "EVENT_TYPE" => Array("FIELD_NAME" => "CE.EVENT_TYPE", "FIELD_TYPE" => "string"),
1696 "CREATED_BY" => Array("FIELD_NAME" => "CE.CREATED_BY", "FIELD_TYPE" => "int"),
1697 "NAME" => Array("FIELD_NAME" => "CE.NAME", "FIELD_TYPE" => "string"),
1698 "DATE_FROM" => Array("FIELD_NAME" => $DB->DateToCharFunction("CE.DATE_FROM").' as DATE_FROM', "FIELD_TYPE" => "date"),
1699 "DATE_TO" => Array("FIELD_NAME" => $DB->DateToCharFunction("CE.DATE_TO").' as DATE_TO', "FIELD_TYPE" => "date"),
1700 "TZ_FROM" => Array("FIELD_NAME" => "CE.TZ_FROM", "FIELD_TYPE" => "string"),
1701 "TZ_TO" => Array("FIELD_NAME" => "CE.TZ_TO", "FIELD_TYPE" => "string"),
1702 "ORIGINAL_DATE_FROM" => Array("FIELD_NAME" => $DB->DateToCharFunction("CE.ORIGINAL_DATE_FROM").' as ORIGINAL_DATE_FROM', "FIELD_TYPE" => "date"),
1703 "TZ_OFFSET_FROM" => Array("FIELD_NAME" => "CE.TZ_OFFSET_FROM", "FIELD_TYPE" => "int"),
1704 "TZ_OFFSET_TO" => Array("FIELD_NAME" => "CE.TZ_OFFSET_TO", "FIELD_TYPE" => "int"),
1705 "DATE_FROM_TS_UTC" => Array("FIELD_NAME" => "CE.DATE_FROM_TS_UTC", "FIELD_TYPE" => "int"),
1706 "DATE_TO_TS_UTC" => Array("FIELD_NAME" => "CE.DATE_TO_TS_UTC", "FIELD_TYPE" => "int"),
1707
1708 "TIMESTAMP_X" => Array("FIELD_NAME" => $DB->DateToCharFunction("CE.TIMESTAMP_X").' as TIMESTAMP_X', "FIELD_TYPE" => "date"),
1709 "DATE_CREATE" => Array("FIELD_NAME" => $DB->DateToCharFunction("CE.DATE_CREATE").' as DATE_CREATE', "FIELD_TYPE" => "date"),
1710 "DESCRIPTION" => Array("FIELD_NAME" => "CE.DESCRIPTION", "FIELD_TYPE" => "string"),
1711 "DT_SKIP_TIME" => Array("FIELD_NAME" => "CE.DT_SKIP_TIME", "FIELD_TYPE" => "string"),
1712 "DT_LENGTH" => Array("FIELD_NAME" => "CE.DT_LENGTH", "FIELD_TYPE" => "int"),
1713 "PRIVATE_EVENT" => Array("FIELD_NAME" => "CE.PRIVATE_EVENT", "FIELD_TYPE" => "string"),
1714 "ACCESSIBILITY" => Array("FIELD_NAME" => "CE.ACCESSIBILITY", "FIELD_TYPE" => "string"),
1715 "IMPORTANCE" => Array("FIELD_NAME" => "CE.IMPORTANCE", "FIELD_TYPE" => "string"),
1716 "IS_MEETING" => Array("FIELD_NAME" => "CE.IS_MEETING", "FIELD_TYPE" => "string"),
1717 "MEETING_HOST" => Array("FIELD_NAME" => "CE.MEETING_HOST", "FIELD_TYPE" => "int"),
1718 "MEETING_STATUS" => Array("FIELD_NAME" => "CE.MEETING_STATUS", "FIELD_TYPE" => "string"),
1719 "MEETING" => Array("FIELD_NAME" => "CE.MEETING", "FIELD_TYPE" => "string"),
1720 "LOCATION" => Array("FIELD_NAME" => "CE.LOCATION", "FIELD_TYPE" => "string"),
1721 "REMIND" => Array("FIELD_NAME" => "CE.REMIND", "FIELD_TYPE" => "string"),
1722 "COLOR" => Array("FIELD_NAME" => "CE.COLOR", "FIELD_TYPE" => "string"),
1723 "RRULE" => Array("FIELD_NAME" => "CE.RRULE", "FIELD_TYPE" => "string"),
1724 "EXDATE" => Array("FIELD_NAME" => "CE.EXDATE", "FIELD_TYPE" => "string"),
1725 "ATTENDEES_CODES" => Array("FIELD_NAME" => "CE.ATTENDEES_CODES", "FIELD_TYPE" => "string"),
1726 "DAV_XML_ID" => Array("FIELD_NAME" => "CE.DAV_XML_ID", "FIELD_TYPE" => "string"), //
1727 "DAV_EXCH_LABEL" => Array("FIELD_NAME" => "CE.DAV_EXCH_LABEL", "FIELD_TYPE" => "string"), // Exchange sync label
1728 "G_EVENT_ID" => Array("FIELD_NAME" => "CE.G_EVENT_ID", "FIELD_TYPE" => "string"), // Google event id
1729 "CAL_DAV_LABEL" => Array("FIELD_NAME" => "CE.CAL_DAV_LABEL", "FIELD_TYPE" => "string"), // CalDAV sync label
1730 "VERSION" => Array("FIELD_NAME" => "CE.VERSION", "FIELD_TYPE" => "string"), // Version used for outlook sync
1731 "RECURRENCE_ID" => Array("FIELD_NAME" => "CE.RECURRENCE_ID", "FIELD_TYPE" => "int"),
1732 "RELATIONS" => Array("FIELD_NAME" => "CE.RELATIONS", "FIELD_TYPE" => "int"),
1733 "SEARCHABLE_CONTENT" => Array("FIELD_NAME" => "CE.SEARCHABLE_CONTENT", "FIELD_TYPE" => "string"),
1734 "SECTION_ID" => Array("FIELD_NAME" => "CE.SECTION_ID", "FIELD_TYPE" => "int"),
1735 );
1737 }
1738 return self::$fields;
1739 }
1740
1741 private static function getEventFields(): array
1742 {
1743 return [
1744 'ID',
1745 'PARENT_ID',
1746 'DELETED',
1747 'CAL_TYPE',
1748 'SYNC_STATUS',
1749 'OWNER_ID',
1750 'EVENT_TYPE',
1751 'CREATED_BY',
1752 'NAME',
1753 'DATE_FROM',
1754 'DATE_TO',
1755 'TZ_FROM',
1756 'TZ_TO',
1757 'ORIGINAL_DATE_FROM',
1758 'TZ_OFFSET_FROM',
1759 'TZ_OFFSET_TO',
1760 'DATE_FROM_TS_UTC',
1761 'DATE_TO_TS_UTC',
1762 'TIMESTAMP_X',
1763 'DATE_CREATE',
1764 'DESCRIPTION',
1765 'DT_SKIP_TIME',
1766 'DT_LENGTH',
1767 'PRIVATE_EVENT',
1768 'ACCESSIBILITY',
1769 'IMPORTANCE',
1770 'IS_MEETING',
1771 'MEETING_HOST',
1772 'MEETING_STATUS',
1773 'MEETING',
1774 'LOCATION',
1775 'REMIND',
1776 'COLOR',
1777 'RRULE',
1778 'EXDATE',
1779 'ATTENDEES_CODES',
1780 'DAV_XML_ID',
1781 'DAV_EXCH_LABEL',
1782 'G_EVENT_ID',
1783 'CAL_DAV_LABEL',
1784 'VERSION',
1785 'RECURRENCE_ID',
1786 'RELATIONS',
1787 'SECTION_ID',
1788 ];
1789 }
1790
1794 public static function ConnectEventToSection($eventId, $sectionId)
1795 {
1796 global $DB;
1797 $DB->Query(
1798 "DELETE FROM b_calendar_event_sect WHERE EVENT_ID=". (int)$eventId);
1799
1800 $DB->Query(
1801 "INSERT INTO b_calendar_event_sect(EVENT_ID, SECT_ID) ".
1802 "SELECT ". (int)$eventId .", ID ".
1803 "FROM b_calendar_section ".
1804 "WHERE ID=". (int)$sectionId);
1805 }
1806
1810 public static function getAttendeeList($entryIdList = [], array $openEventIdList = []): array
1811 {
1812 $attendeeList = [];
1813 $userIdList = [];
1814
1815 if (!CCalendar::IsSocNet())
1816 {
1817 return [
1818 'attendeeList' => $attendeeList,
1819 'userIdList' => $userIdList,
1820 ];
1821 }
1822
1823 $entryIdList = is_array($entryIdList)
1824 ? array_map('intval', array_unique($entryIdList))
1825 : [(int)$entryIdList]
1826 ;
1827
1828 if (empty($entryIdList) && empty($openEventIdList))
1829 {
1830 return [
1831 'attendeeList' => $attendeeList,
1832 'userIdList' => $userIdList,
1833 ];
1834 }
1835
1836 $queries = [];
1837 if (!empty($entryIdList))
1838 {
1839 $queries[] = Internals\EventTable::query()
1840 ->setSelect([
1841 'USER_ID' => 'OWNER_ID',
1842 'ID',
1843 'PARENT_ID',
1844 'EVENT_MEETING_STATUS' => 'MEETING_STATUS',
1845 'MEETING_HOST',
1846 ])
1847 ->where('ACTIVE', 'Y')
1848 ->where('CAL_TYPE', 'user')
1849 ->where('DELETED', 'N')
1850 ->whereIn('PARENT_ID', $entryIdList)
1851 ->exec()
1852 ;
1853 }
1854
1855 if (!empty($openEventIdList))
1856 {
1857 // for event which stored with external attendees in b_calendar_event_attendee
1858 $queries[] = Internals\EventTable::query()
1859 ->registerRuntimeField(
1860 (new ReferenceField(
1861 'EVENT_ATTENDEE',
1863 Join::on('this.ID', 'ref.EVENT_ID')
1864 ))->configureJoinType(Join::TYPE_INNER),
1865 )
1866 ->setSelect([
1867 'USER_ID' => 'EVENT_ATTENDEE.OWNER_ID',
1868 'ID',
1869 'PARENT_ID',
1870 'EVENT_MEETING_STATUS' => 'EVENT_ATTENDEE.MEETING_STATUS',
1871 'MEETING_HOST',
1872 'ATTENDEE_ID' => 'EVENT_ATTENDEE.ID',
1873 ])
1874 ->where('ACTIVE', 'Y')
1875 ->where('DELETED', 'N')
1876 ->whereIn('PARENT_ID', $openEventIdList)
1877 ->where(
1878 (new ConditionTree())
1879 ->logic(ConditionTree::LOGIC_OR)
1880 ->where('EVENT_ATTENDEE.MEETING_STATUS', 'Y')
1881 ->whereNot('CAL_TYPE', Dictionary::CALENDAR_TYPE['open_event'])
1882 )
1883 ->exec()
1884 ;
1885 }
1886
1887 // generator for union-like joining queries
1888 $queryCombineGenerator = static function (array $queryResults): \Generator
1889 {
1890 foreach ($queryResults as $queryResult)
1891 {
1892 while($item = $queryResult->fetch())
1893 {
1894 yield $item;
1895 }
1896 }
1897 };
1898
1899 // combine query result in one virtual set
1900 $combinedQuery = $queryCombineGenerator($queries);
1901
1902 while($entry = $combinedQuery->current())
1903 {
1904 $combinedQuery->next();
1905 $entry['USER_ID'] = (int)$entry['USER_ID'];
1906 if (!isset($attendeeList[$entry['PARENT_ID']]))
1907 {
1908 $attendeeList[$entry['PARENT_ID']] = [];
1909 }
1910 $entry["STATUS"] = trim($entry['EVENT_MEETING_STATUS']);
1911 if (
1912 ($entry['PARENT_ID'] === $entry['ID'] || $entry['USER_ID'] === $entry['MEETING_HOST'])
1913 && !isset($entry['ATTENDEE_ID'])
1914 )
1915 {
1916 $entry["STATUS"] = "H";
1917 }
1918 if (!isset($attendeeList[$entry['PARENT_ID']][$entry['USER_ID']])) {
1919 $attendeeList[$entry['PARENT_ID']][$entry['USER_ID']] = [
1920 'id' => $entry['USER_ID'],
1921 'entryId' => $entry['ID'],
1922 'status' => $entry["STATUS"],
1923 ];
1924 }
1925
1926 if (!in_array($entry['USER_ID'], $userIdList, true))
1927 {
1928 $userIdList[] = $entry['USER_ID'];
1929 }
1930 }
1931 // remove doubles attendees from events
1932 // which stored in both b_calendar_event & b_calendar_event_attendee tables
1933 $attendeeList = array_map(static fn($itemList) => array_values($itemList), $attendeeList);
1934
1935 return [
1936 'attendeeList' => $attendeeList,
1937 'userIdList' => $userIdList,
1938 ];
1939 }
1940
1941 public static function getUsersDetails($userIdList = [], $params = [])
1942 {
1943 $users = [];
1944 $userList = [];
1945 if (!empty($userIdList))
1946 {
1947 $userIdList = array_unique(array_map(static fn($userId) => (int)$userId, $userIdList));
1948
1949 $userList = CCalendar::GetUserList($userIdList);
1950 }
1951
1952 foreach ($userList as $userData)
1953 {
1954 $id = (int)$userData['ID'];
1955 if (!in_array($id, $userIdList, true))
1956 {
1957 continue;
1958 }
1959
1960 $users[$userData['ID']] = [
1961 'ID' => $userData['ID'],
1962 'DISPLAY_NAME' => CCalendar::GetUserName($userData),
1963 'URL' => CCalendar::GetUserUrl($userData['ID']),
1964 'AVATAR' => CCalendar::GetUserAvatarSrc($userData, $params),
1965 'EMAIL_USER' => $userData['EXTERNAL_AUTH_ID'] === 'email',
1966 'SHARING_USER' => $userData['EXTERNAL_AUTH_ID'] === Sharing\SharingUser::EXTERNAL_AUTH_ID,
1967 'COLLAB_USER' => $userData['COLLAB_USER'],
1968 ];
1969 }
1970
1971 return $users;
1972 }
1973
1974 public static function GetAttendees($eventIdList = [], $checkDeleted = true)
1975 {
1976 global $DB;
1977 $attendees = [];
1978
1979 if (CCalendar::IsSocNet())
1980 {
1981 $eventIdList = is_array($eventIdList) ? array_map('intval', array_unique($eventIdList)) : [(int)$eventIdList];
1982
1983 if (!empty($eventIdList))
1984 {
1985 $deletedCondition = $checkDeleted ? "CE.DELETED = 'N' AND" : '';
1986 $strSql = "
1987 SELECT
1988 CE.OWNER_ID AS USER_ID,
1989 CE.ID, CE.PARENT_ID, CE.MEETING_STATUS, CE.MEETING_HOST,
1990 U.LOGIN, U.NAME, U.LAST_NAME, U.SECOND_NAME, U.EMAIL, U.PERSONAL_PHOTO, U.WORK_POSITION, U.EXTERNAL_AUTH_ID,
1991 BUF.UF_DEPARTMENT
1992 FROM
1993 b_calendar_event CE
1994 LEFT JOIN b_user U ON (U.ID=CE.OWNER_ID)
1995 LEFT JOIN b_uts_user BUF ON (BUF.VALUE_ID = CE.OWNER_ID)
1996 WHERE
1997 CE.ACTIVE = 'Y' AND
1998 CE.CAL_TYPE = 'user' AND
1999 {$deletedCondition}
2000 CE.PARENT_ID in (".implode(',', $eventIdList).")";
2001
2002 $res = $DB->Query($strSql);
2003 while($entry = $res->Fetch())
2004 {
2005 $parentId = (int)$entry['PARENT_ID'];
2006 $attendeeId = (int)$entry['USER_ID'];
2007 if (!isset($attendees[$parentId]))
2008 {
2009 $attendees[$parentId] = [];
2010 }
2011 $entry["STATUS"] = trim($entry["MEETING_STATUS"]);
2012 if ($parentId === (int)$entry['ID'])
2013 {
2014 $entry["STATUS"] = "H";
2015 }
2016
2017 CCalendar::SetUserDepartment($attendeeId, (empty($entry['UF_DEPARTMENT'])
2018 ? []
2019 : unserialize($entry['UF_DEPARTMENT'], ['allowed_classes' => false])));
2020 $entry['DISPLAY_NAME'] = CCalendar::GetUserName($entry);
2021 $entry['URL'] = CCalendar::GetUserUrl($attendeeId);
2022 $entry['AVATAR'] = CCalendar::GetUserAvatarSrc($entry);
2023 $entry['EVENT_ID'] = $entry['ID'];
2024
2025 unset($entry['ID'], $entry['PARENT_ID'], $entry['UF_DEPARTMENT'], $entry['LOGIN']);
2026 $attendees[$parentId][] = $entry;
2027 }
2028 }
2029 }
2030
2031 return $attendees;
2032 }
2033
2034 public static function GetAttendeeIds(array $eventIdList): array
2035 {
2036 $eventIdList = array_map('intval', array_unique($eventIdList));
2037
2038 if (empty($eventIdList))
2039 {
2040 return [];
2041 }
2042
2043 $attendeeIds = [];
2044 $entries = Internals\EventTable::query()
2045 ->setSelect(['PARENT_ID', 'CAL_TYPE', 'OWNER_ID', 'ACTIVE', 'DELETED'])
2046 ->whereIn('PARENT_ID', $eventIdList)
2047 ->where('CAL_TYPE', Dictionary::CALENDAR_TYPE['user'])
2048 ->where('ACTIVE', 'Y')
2049 ->where('DELETED', 'N')
2050 ->exec()
2051 ->fetchAll()
2052 ;
2053
2054 foreach ($entries as $entry)
2055 {
2056 $parentId = $entry['PARENT_ID'];
2057 $attendeeIds[$parentId] ??= [];
2058 $attendeeIds[$parentId][] = (int)$entry['OWNER_ID'];
2059 }
2060
2061 return $attendeeIds;
2062 }
2063
2064 public static function ApplyAccessRestrictions($event, $userId = null)
2065 {
2066 if (!$userId)
2067 {
2068 $userId = CCalendar::GetUserId();
2069 }
2070
2071 $sectId = (int)$event['SECT_ID'];
2072 if (empty($event['ACCESSIBILITY']))
2073 {
2074 $event['ACCESSIBILITY'] = 'busy';
2075 }
2076
2077 $private = !empty($event['PRIVATE_EVENT']) && ($event['CAL_TYPE'] ?? null) === 'user';
2078 $isAttendee = false;
2079
2080 if (is_array($event['ATTENDEE_LIST'] ?? null))
2081 {
2082 foreach($event['ATTENDEE_LIST'] as $attendee)
2083 {
2084 if ((int)$attendee['id'] === (int)$userId)
2085 {
2086 $isAttendee = true;
2087 break;
2088 }
2089 }
2090 }
2091
2092 if (
2093 ($event['CAL_TYPE'] ?? null) === 'user'
2094 && !empty($event['IS_MEETING'])
2095 && (int)$event['OWNER_ID'] !== (int)$userId
2096 && $isAttendee
2097 )
2098 {
2099 $sectId = (int)CCalendar::GetMeetingSection($userId);
2100 }
2101
2102 $accessResult = self::checkEventAccessFromGetList($event, $sectId, $userId);
2103
2104 if ($private || (!$accessResult[ActionDictionary::ACTION_EVENT_VIEW_FULL] && !$isAttendee))
2105 {
2106 if ($private)
2107 {
2108 $event['NAME'] = '[' . Loc::getMessage('EC_ACCESSIBILITY_' . mb_strtoupper($event['ACCESSIBILITY'])) . ']';
2109 $event['IS_ACCESSIBLE_TO_USER'] = false;
2110
2111 if (!$accessResult[ActionDictionary::ACTION_EVENT_VIEW_TIME])
2112 {
2113 return false;
2114 }
2115 }
2116 else if (!$accessResult[ActionDictionary::ACTION_EVENT_VIEW_TITLE])
2117 {
2118 if ($accessResult[ActionDictionary::ACTION_EVENT_VIEW_TIME])
2119 {
2120 $event['NAME'] = '[' . Loc::getMessage('EC_ACCESSIBILITY_' . mb_strtoupper($event['ACCESSIBILITY'])) . ']';
2121 $event['IS_ACCESSIBLE_TO_USER'] = false;
2122 }
2123 else
2124 {
2125 return false;
2126 }
2127 }
2128 else
2129 {
2130 $event['NAME'] .= ' [' . Loc::getMessage('EC_ACCESSIBILITY_' . mb_strtoupper($event['ACCESSIBILITY'])) . ']';
2131 }
2132
2133 // Clear information about
2134 unset(
2135 $event['DESCRIPTION'],
2136 $event['LOCATION'],
2137 $event['REMIND'],
2138 $event['ATTENDEE_LIST'],
2139 $event['ATTENDEES_CODES'],
2140 $event['UF_CRM_CAL_EVENT'],
2141 $event['UF_WEBDAV_CAL_EVENT'],
2142 );
2143 }
2144
2145 return $event;
2146 }
2147
2148 public static function convertDateToCulture(string $str): string
2149 {
2150 if (CCalendar::DFormat(false) !== ExcludedDatesCollection::EXCLUDED_DATE_FORMAT)
2151 {
2152 if (preg_match_all("/(\d{2})\.(\d{2})\.(\d{4})/", $str, $matches))
2153 {
2154 foreach ($matches[0] as $index => $match)
2155 {
2156 $newValue = CCalendar::Date(
2157 mktime(
2158 0,
2159 0,
2160 0,
2161 $matches[2][$index],
2162 $matches[1][$index],
2163 $matches[3][$index]
2164 ),
2165 false
2166 );
2167 $str = str_replace($match, $newValue, $str);
2168 }
2169 }
2170 }
2171
2172 return $str;
2173 }
2174
2175 private static function convertExDatesToInternalFormat(string $exDateString): string
2176 {
2177 if (!empty($exDateString))
2178 {
2179 $exDates = explode(';', $exDateString);
2180 $result = [];
2181 foreach ($exDates as $exDate)
2182 {
2183 $result[] = self::convertDateToRecurrenceFormat($exDate);
2184 }
2185 $exDateString = implode(';', $result);
2186 }
2187
2188 return $exDateString;
2189 }
2190 private static function convertRuleUntilToInternalFormat(?string $untilString): ?string
2191 {
2192 if (!empty($untilString) && preg_match('/UNTIL=(.+)[;$]/U', $untilString, $matches))
2193 {
2194 $internalFormatedDate = self::convertDateToRecurrenceFormat($matches[1]);
2195 $untilString = str_replace($matches[1], $internalFormatedDate, $untilString);
2196 }
2197
2198 return $untilString;
2199 }
2200
2201 private static function convertDateToRecurrenceFormat(string $date = ''): string
2202 {
2203 if (CCalendar::DFormat(false) !== ExcludedDatesCollection::EXCLUDED_DATE_FORMAT)
2204 {
2205 $date = date(
2206 ExcludedDatesCollection::EXCLUDED_DATE_FORMAT,
2207 CCalendar::Timestamp($date)
2208 );
2209 }
2210
2211 return $date;
2212 }
2213
2214 private static function PreHandleEvent($item, $params = [])
2215 {
2216 if (!empty($item['LOCATION']))
2217 {
2218 $item['LOCATION'] = trim($item['LOCATION']);
2219 }
2220
2221 if (!empty($item['MEETING']))
2222 {
2223 $item['MEETING'] = unserialize($item['MEETING'], ['allowed_classes' => false]);
2224
2225 if (!is_array($item['MEETING']))
2226 {
2227 $item['MEETING'] = [];
2228 }
2229 }
2230
2231 if (!empty($item['RELATIONS']))
2232 {
2233 $item['RELATIONS'] = unserialize($item['RELATIONS'], ['allowed_classes' => false]);
2234
2235 if (!is_array($item['RELATIONS']))
2236 {
2237 $item['RELATIONS'] = [];
2238 }
2239 }
2240
2241 if (!empty($item['REMIND']))
2242 {
2243 $item['REMIND'] = unserialize($item['REMIND'], ['allowed_classes' => false]);
2244
2245 if (!is_array($item['REMIND']))
2246 {
2247 $item['REMIND'] = [];
2248 }
2249 }
2250
2251 if (!empty($item['IS_MEETING']) && !empty($item['MEETING']) && !is_array($item['MEETING']))
2252 {
2253 $item['MEETING'] = unserialize($item['MEETING'], ['allowed_classes' => false]);
2254
2255 if (!is_array($item['MEETING']))
2256 {
2257 $item['MEETING'] = [];
2258 }
2259 }
2260
2261 if (self::CheckRecurcion($item))
2262 {
2263 $item['EXDATE'] = !empty($item['EXDATE']) ? self::convertDateToCulture($item['EXDATE']) : '';
2264 $item['RRULE'] = self::ParseRRULE(self::convertDateToCulture($item['RRULE']));
2265 $item['~RRULE_DESCRIPTION'] = self::GetRRULEDescription($item);
2266 $tsFrom = CCalendar::Timestamp($item['DATE_FROM']);
2267 $tsTo = CCalendar::Timestamp($item['DATE_TO']);
2268 if (($tsTo - $tsFrom) > $item['DT_LENGTH'] + CCalendar::DAY_LENGTH)
2269 {
2270 $toTS = $tsFrom + $item['DT_LENGTH'];
2271 if (isset($item['DT_SKIP_TIME']) && $item['DT_SKIP_TIME'] === 'Y')
2272 {
2273 $toTS -= CCalendar::GetDayLen();
2274 }
2275 $item['DATE_TO'] = CCalendar::Date($toTS);
2276 }
2277 }
2278
2279 if (!empty($item['ATTENDEES_CODES']) && is_string($item['ATTENDEES_CODES']))
2280 {
2281 $item['ATTENDEES_CODES'] = explode(',', $item['ATTENDEES_CODES']);
2282 $item['attendeesEntityList'] = Util::convertCodesToEntities($item['ATTENDEES_CODES'] ?? null);
2283 }
2284
2285 if (
2286 !empty($item['IS_MEETING'])
2287 && (int)$item['ID'] === (int)$item['PARENT_ID']
2288 && !($item['CURRENT_ATTENDEE_ID'] ?? null)
2289 )
2290 {
2291 $item['MEETING_STATUS'] = 'H';
2292 }
2293
2294 $item['DT_SKIP_TIME'] = ($item['DT_SKIP_TIME'] ?? null) === 'Y' ? 'Y' : 'N';
2295
2296
2297 if (empty($item['IMPORTANCE']))
2298 {
2299 $item['IMPORTANCE'] = 'normal';
2300 }
2301
2302 $item['PRIVATE_EVENT'] = trim((string)($item['PRIVATE_EVENT'] ?? null));
2303
2304 $item['DESCRIPTION'] = trim((string)($item['DESCRIPTION'] ?? null));
2305
2306 if (!empty($params['parseDescription']))
2307 {
2308 $item['~DESCRIPTION'] = self::ParseText(
2309 $item['DESCRIPTION'],
2310 !empty($item['PARENT_ID']) ? $item['PARENT_ID'] : $item['ID'],
2311 $item['UF_WEBDAV_CAL_EVENT'] ?? null
2312 );
2313 }
2314
2315 if (isset($item['UF_CRM_CAL_EVENT']) && is_array($item['UF_CRM_CAL_EVENT']) && empty($item['UF_CRM_CAL_EVENT']))
2316 {
2317 $item['UF_CRM_CAL_EVENT'] = '';
2318 }
2319
2320 unset($item['SEARCHABLE_CONTENT']);
2321
2322 return $item;
2323 }
2324
2325 public static function CheckRecurcion($event)
2326 {
2327 return !empty($event['RRULE']);
2328 }
2329
2330 public static function ParseText($text = "", $eventId = 0, $arUFWDValue = [])
2331 {
2332 if ($text)
2333 {
2334 if (!is_object(self::$TextParser))
2335 {
2336 self::$TextParser = new CTextParser();
2337 self::$TextParser->allow = array(
2338 "HTML" => "N",
2339 "ANCHOR" => "Y",
2340 "BIU" => "Y",
2341 "IMG" => "Y",
2342 "QUOTE" => "Y",
2343 "CODE" => "Y",
2344 "FONT" => "Y",
2345 "LIST" => "Y",
2346 "SMILES" => "Y",
2347 "NL2BR" => "Y",
2348 "VIDEO" => "Y",
2349 "TABLE" => "Y",
2350 "CUT_ANCHOR" => "N",
2351 "ALIGN" => "Y",
2352 "USER" => "Y",
2353 );
2354 }
2355
2356 self::$TextParser->allow["USERFIELDS"] = self::getUFForParseText($eventId, $arUFWDValue);
2357 $text = self::$TextParser->convertText($text);
2358 $text = preg_replace("/<br \/>/i", "<br>", $text);
2359 }
2360 return $text;
2361 }
2362
2363 public static function getUFForParseText($eventId = 0, $arUFWDValue = []): array
2364 {
2365 $userFields = self::GetEventUserFields(['ID' => $eventId]);
2366 $userFields = [
2367 'UF_WEBDAV_CAL_EVENT' => $userFields['UF_WEBDAV_CAL_EVENT'] ?? [],
2368 ];
2369
2370 if (empty($arUFWDValue))
2371 {
2372 $arUFWDValue = $userFields['UF_WEBDAV_CAL_EVENT']['VALUE'] ?? [];
2373 }
2374
2375 $userFields['UF_WEBDAV_CAL_EVENT']['VALUE'] = $arUFWDValue;
2376 $userFields['UF_WEBDAV_CAL_EVENT']['ENTITY_VALUE_ID'] = $eventId;
2377
2378 return $userFields;
2379 }
2380
2381 public static function ParseRecursion(&$res, $event, $params = [])
2382 {
2383 $event['DT_LENGTH'] = (int)$event['DT_LENGTH'];// length in seconds
2384 $event['RRULE'] = self::ParseRRULE($event['RRULE']);
2385 $length = $event['DT_LENGTH'];
2386 $skipTime = $event['DT_SKIP_TIME'] === 'Y';
2387
2388 $rrule = $event['RRULE'];
2389 $exDate = self::GetExDate($event['EXDATE'] ?? null);
2390 $tsFrom = CCalendar::Timestamp($event['DATE_FROM']);
2391 $tsTo = CCalendar::Timestamp($event['DATE_TO']);
2392
2393 if (($tsTo - $tsFrom) > $event['DT_LENGTH'] + CCalendar::DAY_LENGTH)
2394 {
2395 $toTS = $tsFrom + $length;
2396 if ($skipTime)
2397 {
2398 $toTS -= CCalendar::GetDayLen();
2399 }
2400
2401 $event['DATE_TO'] = CCalendar::Date($toTS);
2402 }
2403
2404 $h24 = CCalendar::GetDayLen();
2405 $instanceCount = ($params['instanceCount'] && $params['instanceCount'] > 0) ? $params['instanceCount'] : false;
2406 $loadLimit = ($params['loadLimit'] && $params['loadLimit'] > 0) ? $params['loadLimit'] : false;
2407
2408 $preciseLimits = $params['preciseLimits'];
2409
2410 if ($length < 0) // Protection from infinite recursion
2411 {
2412 $length = $h24;
2413 }
2414
2415 // Time boundaries
2416 if (isset($params['fromLimitTs']))
2417 {
2418 $limitFromTS = (int)$params['fromLimitTs'];
2419 }
2420 else if (!empty($params['fromLimit']))
2421 {
2422 $limitFromTS = CCalendar::Timestamp($params['fromLimit']);
2423 }
2424 else
2425 {
2426 $limitFromTS = CCalendar::Timestamp(CCalendar::GetMinDate());
2427 }
2428
2429 if (isset($params['toLimitTs']))
2430 {
2431 $limitToTS = (int)$params['toLimitTs'];
2432 }
2433 else if (!empty($params['toLimit']))
2434 {
2435 $limitToTS = CCalendar::Timestamp($params['toLimit']);
2436 }
2437 else
2438 {
2439 $limitToTS = CCalendar::Timestamp(CCalendar::GetMaxDate());
2440 }
2441
2442 $evFromTS = CCalendar::Timestamp($event['DATE_FROM']);
2443
2444 $limitFromTS += $event['TZ_OFFSET_FROM'];
2445 $limitToTS += $event['TZ_OFFSET_TO'] + CCalendar::GetDayLen();
2446 $limitFromTSReal = $limitFromTS;
2447
2448
2449 if ($skipTime && $length > CCalendar::GetDayLen())
2450 {
2451 $limitFromTSReal += $length - CCalendar::GetDayLen();
2452 }
2453
2454 if ($limitFromTS < $event['DATE_FROM_TS_UTC'])
2455 {
2456 $limitFromTS = $event['DATE_FROM_TS_UTC'];
2457 }
2458
2459 $eventDateToTsUtc = $event['DATE_TO_TS_UTC'];
2460 if (isset($rrule['COUNT']))
2461 {
2462 $eventDateToTsUtc = self::calculateUntilForCountRRule($event);
2463 }
2464
2465 if ($limitToTS > $eventDateToTsUtc)
2466 {
2467 $limitToTS = $eventDateToTsUtc;
2468 }
2469
2470 $fromTS = self::getClosestRepetitionTs($limitFromTS, $evFromTS, $rrule);
2471
2472 $countOffset = 0;
2473 if (isset($rrule['COUNT']) && in_array($rrule['FREQ'], ['DAILY', 'MONTHLY', 'YEARLY'], true))
2474 {
2475 $lastRepetitionTs = $eventDateToTsUtc;
2476 if (!$skipTime)
2477 {
2478 $lastRepetitionTs -= $length;
2479 }
2480
2481 $untilDiff = self::calculateUntilForCountRRule($event, $fromTS - $evFromTS) - $lastRepetitionTs;
2482 $freqDuration = self::getFreqDuration($rrule['FREQ']);
2483 $countOffset = (int)($untilDiff / ($freqDuration * $rrule['INTERVAL']));
2484 }
2485
2486 if ($skipTime)
2487 {
2488 $event['~DATE_FROM'] = CCalendar::Date(CCalendar::Timestamp($event['DATE_FROM']), false);
2489 $event['~DATE_TO'] = CCalendar::Date(CCalendar::Timestamp($event['DATE_TO']), false);
2490 }
2491 else
2492 {
2493 $event['~DATE_FROM'] = $event['DATE_FROM'];
2494 $event['~DATE_TO'] = $event['DATE_TO'];
2495 }
2496
2497 $hour = (int)date('H', $fromTS);
2498 $min = (int)date('i', $fromTS);
2499 $sec = (int)date('s', $fromTS);
2500
2501 $orig_d = (int)date('d', $fromTS);
2502 $orig_m = (int)date('m', $fromTS);
2503 $orig_y = (int)date('Y', $fromTS);
2504
2505 $realCount = 0;
2506 $dispCount = 0;
2507
2508 while(true)
2509 {
2510 $d = (int)date('d', $fromTS);
2511 $m = (int)date('m', $fromTS);
2512 $y = (int)date('Y', $fromTS);
2513 $toTS = mktime($hour, $min, $sec + $length, $m, $d, $y);
2514
2515 if (
2516 (isset($rrule['COUNT']) && $rrule['COUNT'] > 0 && ($realCount + $countOffset) >= $rrule['COUNT'])
2517 || ($loadLimit && $dispCount >= $loadLimit)
2518 || ($fromTS > $limitToTS)
2519 || ($instanceCount && $dispCount >= $instanceCount)
2520 || (!$fromTS || $fromTS < $evFromTS - CCalendar::GetDayLen()) // Emergency exit (mantis: 56981)
2521 )
2522 {
2523 break;
2524 }
2525
2526 // Common handling
2527 $event['DATE_FROM'] = CCalendar::Date($fromTS, !$skipTime, false);
2528 $event['DATE_FROM_FORMATTED'] = self::getDateInJsFormat(
2529 CCalendar::createDateTimeObjectFromString($event['DATE_FROM']),
2530 $skipTime
2531 );
2532
2533 $event['RRULE'] = $rrule;
2534 $event['RINDEX'] = $realCount > 0 || $countOffset > 0
2535 ? $realCount + $countOffset
2536 : self::getFirstInstanceIndex($fromTS, $event['~DATE_FROM'])
2537 ;
2538
2539 $exclude = false;
2540
2541 if (!empty($exDate))
2542 {
2543 $fromDate = CCalendar::Date($fromTS, false);
2544 $exclude = in_array($fromDate, $exDate, true);
2545 }
2546
2547 if ($rrule['FREQ'] === 'WEEKLY')
2548 {
2549 $weekDay = CCalendar::WeekDayByInd(date("w", $fromTS));
2550
2551 if (!empty($rrule['BYDAY'][$weekDay]))
2552 {
2553 $realCount++;
2554
2555 if (($preciseLimits && $toTS >= $limitFromTSReal) || (!$preciseLimits && $toTS > $limitFromTS - $h24))
2556 {
2557 if (($event['DT_SKIP_TIME'] ?? null) === 'Y')
2558 {
2559 $toTS -= CCalendar::GetDayLen();
2560 }
2561
2562 $event['DATE_TO'] = CCalendar::Date($toTS - ($event['TZ_OFFSET_FROM'] - $event['TZ_OFFSET_TO']), !$skipTime, false);
2563 $event['DATE_TO_FORMATTED'] = self::getDateInJsFormat(
2564 CCalendar::createDateTimeObjectFromString($event['DATE_TO']),
2565 $skipTime
2566 );
2567
2568 if (!$exclude)
2569 {
2570 self::HandleEvent($res, $event, $params['userId']);
2571 $dispCount++;
2572 }
2573 }
2574 }
2575
2576 if (isset($weekDay) && $weekDay === 'SU')
2577 {
2578 $delta = ($rrule['INTERVAL'] - 1) * 7 + 1;
2579 }
2580 else
2581 {
2582 $delta = 1;
2583 }
2584
2585 $fromTS = mktime($hour, $min, $sec, $m, $d + $delta, $y);
2586 }
2587 else // DAILY, MONTHLY, YEARLY
2588 {
2589 $realCount++;
2590
2591 if (($event['DT_SKIP_TIME'] ?? null) === 'Y')
2592 {
2593 $toTS -= CCalendar::GetDayLen();
2594 }
2595
2596 if (
2597 ($preciseLimits && $toTS >= $limitFromTSReal)
2598 || (!$preciseLimits && $toTS > $limitFromTS - $h24)
2599 )
2600 {
2601 $event['DATE_TO'] = CCalendar::Date($toTS - ($event['TZ_OFFSET_FROM'] - $event['TZ_OFFSET_TO']), !$skipTime, false);
2602 $event['DATE_TO_FORMATTED'] = self::getDateInJsFormat(
2603 CCalendar::createDateTimeObjectFromString($event['DATE_TO']),
2604 $skipTime
2605 );
2606
2607 //$event['DATE_TO'] = CCalendar::Date($toTS, !$skipTime, false);
2608 if (!$exclude)
2609 {
2610 self::HandleEvent($res, $event, $params['userId']);
2611 $dispCount++;
2612 }
2613 }
2614
2615 switch ($rrule['FREQ'])
2616 {
2617 case 'DAILY':
2618 $fromTS = mktime($hour, $min, $sec, $m, $d + $rrule['INTERVAL'], $y);
2619 break;
2620 case 'MONTHLY':
2621 $durOffset = $realCount * $rrule['INTERVAL'];
2622
2623 $day = $orig_d;
2624 $month = $orig_m + $durOffset;
2625 $year = $orig_y;
2626
2627 if ($month > 12)
2628 {
2629 $delta_y = floor($month / 12);
2630 $delta_m = $month - $delta_y * 12;
2631
2632 $month = $delta_m;
2633 $year = $orig_y + $delta_y;
2634 }
2635
2636 // 1. Check only for 29-31 dates. 2.We are out of range in this month
2637 if ($orig_d > 28 && $orig_d > date("t", mktime($hour, $min, $sec, $month, 1, $year)))
2638 {
2639 $month++;
2640 $day = 1;
2641 }
2642
2643 $fromTS = mktime($hour, $min, $sec, $month, $day, $year);
2644 break;
2645 case 'YEARLY':
2646 $fromTS = mktime($hour, $min, $sec, $orig_m, $orig_d, $y + $rrule['INTERVAL']);
2647 break;
2648 }
2649 }
2650 }
2651 }
2652
2653 private static function getFirstInstanceIndex($fromTs, $eventFromDate): int
2654 {
2655 $eventTs = (int)CCalendar::Timestamp($eventFromDate);
2656
2657 return (($fromTs - $eventTs) / 86400) > 1 ? 1 : 0;
2658 }
2659
2660 public static function getClosestRepetitionTs(int $limitFromTS, int $evFromTS, array $rrule): int
2661 {
2662 $interval = (int)$rrule['INTERVAL'];
2663
2664 $hour = (int)date('H', $evFromTS);
2665 $min = (int)date('i', $evFromTS);
2666 $sec = (int)date('s', $evFromTS);
2667
2668 $orig_d = (int)date('d', $evFromTS);
2669 $orig_m = (int)date('m', $evFromTS);
2670 $orig_y = (int)date('Y', $evFromTS);
2671
2672 $closestDateTs = $limitFromTS;
2673
2674 $freqDuration = self::getFreqDuration($rrule['FREQ']);
2675
2676 if ($rrule['FREQ'] === 'DAILY')
2677 {
2678 $dayDifference = round(($limitFromTS - $evFromTS) / $freqDuration);
2679 $dayDifference -= $dayDifference % $interval;
2680 $closestDateTs = mktime($hour, $min, $sec, $orig_m, $orig_d + $dayDifference, $orig_y);
2681 }
2682
2683 if ($rrule['FREQ'] === 'MONTHLY')
2684 {
2685 $monthDifference = round(($limitFromTS - $evFromTS) / $freqDuration);
2686 $monthDifference -= $monthDifference % $interval;
2687 $closestDateTs = mktime($hour, $min, $sec, $orig_m + $monthDifference, $orig_d, $orig_y);
2688 }
2689
2690 if ($rrule['FREQ'] === 'YEARLY')
2691 {
2692 $yearDifference = round(($limitFromTS - $evFromTS) / $freqDuration);
2693 $yearDifference -= $yearDifference % $interval;
2694 $closestDateTs = mktime($hour, $min, $sec, $orig_m, $orig_d, $orig_y + $yearDifference);
2695 }
2696
2697 if ($rrule['FREQ'] === 'WEEKLY')
2698 {
2699 $dayDifference = round(($limitFromTS - $evFromTS) / $freqDuration);
2700 $count = round($dayDifference / (count($rrule['BYDAY']) * 7 * $interval) + 1);
2701 $daysCount = round(($count - 1) / count($rrule['BYDAY']) * 7 * $interval);
2702 $closestDateTs = mktime($hour, $min, $sec, $orig_m, $orig_d + $daysCount, $orig_y);
2703 }
2704
2705 $closestRepetitionDate = (int)date('d', $closestDateTs);
2706 $closestRepetitionMonth = (int)date('m', $closestDateTs);
2707 $closestRepetitionYear = (int)date('Y', $closestDateTs);
2708
2709 return mktime($hour, $min, $sec, $closestRepetitionMonth, $closestRepetitionDate, $closestRepetitionYear);
2710 }
2711
2712 public static function getFreqDuration(string $freq): int
2713 {
2714 if ($freq === 'DAILY' || $freq === 'WEEKLY')
2715 {
2716 return 60 * 60 * 24;
2717 }
2718
2719 if ($freq === 'MONTHLY')
2720 {
2721 return 60 * 60 * 24 * 30;
2722 }
2723
2724 if ($freq === 'YEARLY')
2725 {
2726 return 60 * 60 * 24 * 365;
2727 }
2728
2729 return 0;
2730 }
2731
2732 public static function ParseRRULE($rule = null)
2733 {
2734 $res = [];
2735 if (!$rule)
2736 {
2737 return $res;
2738 }
2739
2740 if (is_array($rule))
2741 {
2742 return isset($rule['FREQ'])
2743 ? $rule
2744 : $res;
2745 }
2746
2747 $arRule = explode(";", $rule);
2748 if (!is_array($arRule))
2749 {
2750 return $res;
2751 }
2752
2753 foreach($arRule as $par)
2754 {
2755 $arPar = explode("=", $par);
2756 if (!empty($arPar[0]))
2757 {
2758 switch($arPar[0])
2759 {
2760 case 'FREQ':
2761 if (in_array($arPar[1], ['DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY']))
2762 {
2763 $res['FREQ'] = $arPar[1];
2764 }
2765
2766 break;
2767 case 'COUNT':
2768 case 'INTERVAL':
2769 if ((int)$arPar[1] > 0)
2770 {
2771 $res[$arPar[0]] = (int)$arPar[1];
2772 }
2773
2774 break;
2775 case 'UNTIL':
2776 if (
2777 CCalendar::DFormat(false) !== ExcludedDatesCollection::EXCLUDED_DATE_FORMAT
2778 && $arPar[1][2] === '.'
2779 && $arPar[1][5] === '.'
2780 )
2781 {
2782 $arPar[1] = self::convertDateToCulture($arPar[1]);
2783 }
2784 $res['UNTIL'] = CCalendar::Timestamp($arPar[1])
2785 ? $arPar[1]
2786 : CCalendar::Date((int)$arPar[1], false, false)
2787 ;
2788
2789 break;
2790 case 'BYDAY':
2791 $res[$arPar[0]] = [];
2792 foreach(explode(',', $arPar[1]) as $day)
2793 {
2794 $matches = [];
2795 if (preg_match('/((\-|\+)?\d+)?(MO|TU|WE|TH|FR|SA|SU)/', $day, $matches))
2796 {
2797 $res[$arPar[0]][$matches[3]] =
2798 $matches[1] === ''
2799 ? $matches[3]
2800 : $matches[1];
2801 }
2802 }
2803 if (empty($res[$arPar[0]]))
2804 {
2805 unset($res[$arPar[0]]);
2806 }
2807
2808 break;
2809 case 'BYMONTHDAY':
2810 $res[$arPar[0]] = [];
2811 foreach(explode(',', $arPar[1]) as $day)
2812 {
2813 if (abs($day) > 0 && abs($day) <= 31)
2814 {
2815 $res[$arPar[0]][(int)$day] = (int)$day;
2816 }
2817 }
2818 if (empty($res[$arPar[0]]))
2819 {
2820 unset($res[$arPar[0]]);
2821 }
2822
2823 break;
2824 case 'BYYEARDAY':
2825 case 'BYSETPOS':
2826 $res[$arPar[0]] = [];
2827 foreach(explode(',', $arPar[1]) as $day)
2828 {
2829 if (abs($day) > 0 && abs($day) <= 366)
2830 {
2831 $res[$arPar[0]][(int)$day] = (int)$day;
2832 }
2833 }
2834 if (empty($res[$arPar[0]]))
2835 {
2836 unset($res[$arPar[0]]);
2837 }
2838
2839 break;
2840 case 'BYWEEKNO':
2841 $res[$arPar[0]] = [];
2842 foreach(explode(',', $arPar[1]) as $day)
2843 {
2844 if (abs($day) > 0 && abs($day) <= 53)
2845 {
2846 $res[$arPar[0]][(int)$day] = (int)$day;
2847 }
2848 }
2849 if (empty($res[$arPar[0]]))
2850 {
2851 unset($res[$arPar[0]]);
2852 }
2853
2854 break;
2855 case 'BYMONTH':
2856 $res[$arPar[0]] = [];
2857 foreach(explode(',', $arPar[1]) as $m)
2858 {
2859 if ($m > 0 && $m <= 12)
2860 {
2861 $res[$arPar[0]][(int)$m] = (int)$m;
2862 }
2863 }
2864 if (empty($res[$arPar[0]]))
2865 {
2866 unset($res[$arPar[0]]);
2867 }
2868
2869 break;
2870 }
2871 }
2872 }
2873
2874 if (
2875 $res['FREQ'] === 'WEEKLY'
2876 && (
2877 empty($res['BYDAY'])
2878 || !is_array($res['BYDAY'])
2879 )
2880 )
2881 {
2882 $res['BYDAY'] = ['MO' => 'MO'];
2883 }
2884
2885 if ($res['FREQ'] !== 'WEEKLY' && isset($res['BYDAY']))
2886 {
2887 unset($res['BYDAY']);
2888 }
2889
2890 $res['INTERVAL'] = (int)($res['INTERVAL'] ?? null);
2891 if ($res['INTERVAL'] <= 1)
2892 {
2893 $res['INTERVAL'] = 1;
2894 }
2895
2896 $res['~UNTIL'] = $res['UNTIL'] ?? null;
2897 if (($res['UNTIL'] ?? null) === CCalendar::GetMaxDate())
2898 {
2899 $res['~UNTIL'] = '';
2900 }
2901
2902 $res['UNTIL_TS'] = !empty($res['UNTIL']) && is_string($res['UNTIL'])
2903 ? CCalendar::TimestampUTC($res['UNTIL'])
2904 : null
2905 ;
2906
2907 return $res;
2908 }
2909
2914 public static function GetExDate($exDate = '')
2915 {
2916 $result = [];
2917 if (is_string($exDate))
2918 {
2919 $result = $exDate === '' ? [] : explode(';', $exDate);
2920 }
2921
2922 return $result ?: [];
2923 }
2924
2925 private static function HandleEvent(&$res, $event = [], $userId = null)
2926 {
2927 $userId = $userId ?: CCalendar::GetCurUserId();
2928
2929 $res[] = self::calculateUserOffset($userId, $event);
2930 }
2931
2932 private static function calculateUserOffset($userId, $event = [])
2933 {
2934 if (($event['DT_SKIP_TIME'] ?? null) === 'N')
2935 {
2936 $currentUserTimezone = \CCalendar::GetUserTimezoneName($userId);
2937
2938 $fromTs = \CCalendar::Timestamp($event['DATE_FROM']);
2939 $toTs = $fromTs + ($event['DT_LENGTH'] ?? null);
2940
2941 $event['~USER_OFFSET_FROM'] = CCalendar::GetTimezoneOffset(($event['TZ_FROM'] ?? null), $fromTs)
2942 - \CCalendar::GetTimezoneOffset($currentUserTimezone, $fromTs);
2943
2944 $event['~USER_OFFSET_TO'] = CCalendar::GetTimezoneOffset(($event['TZ_TO'] ?? null), $toTs)
2945 - \CCalendar::GetTimezoneOffset($currentUserTimezone, $toTs);
2946 }
2947 else
2948 {
2949 $event['~USER_OFFSET_FROM'] = 0;
2950 $event['~USER_OFFSET_TO'] = 0;
2951 }
2952
2953 return $event;
2954 }
2955
2956 public static function CheckFields(&$arFields, $currentEvent = [], $userId = false)
2957 {
2958 $arFields['ID'] = (int)($arFields['ID'] ?? null);
2959 $arFields['PARENT_ID'] = (int)($arFields['PARENT_ID'] ?? 0);
2960 $arFields['OWNER_ID'] = (int)($arFields['OWNER_ID'] ?? 0);
2961
2962 if (!isset($arFields['TIMESTAMP_X']))
2963 {
2964 $arFields['TIMESTAMP_X'] = CCalendar::Date(time(), true, false);
2965 }
2966
2967 if (!$userId)
2968 {
2969 $userId = CCalendar::GetCurUserId();
2970 }
2971
2972 if (!isset($arFields['DT_SKIP_TIME']) && isset($currentEvent['DT_SKIP_TIME']))
2973 {
2974 $arFields['DT_SKIP_TIME'] = $currentEvent['DT_SKIP_TIME'];
2975 }
2976 if (!isset($arFields['DATE_FROM']) && isset($currentEvent['DATE_FROM']))
2977 {
2978 $arFields['DATE_FROM'] = $currentEvent['DATE_FROM'];
2979 }
2980 if (!isset($arFields['DATE_TO']) && isset($currentEvent['DATE_TO']))
2981 {
2982 $arFields['DATE_TO'] = $currentEvent['DATE_TO'];
2983 }
2984
2985 $isNewEvent = !isset($arFields['ID']) || $arFields['ID'] <= 0;
2986 if (!isset($arFields['DATE_CREATE']) && $isNewEvent)
2987 {
2988 $arFields['DATE_CREATE'] = $arFields['TIMESTAMP_X'];
2989 }
2990
2991 // Skip time
2992 if (isset($arFields['SKIP_TIME']))
2993 {
2994 $arFields['DT_SKIP_TIME'] = $arFields['SKIP_TIME'] ? 'Y' : 'N';
2995 unset($arFields['SKIP_TIME']);
2996 }
2997 elseif (isset($arFields['DT_SKIP_TIME']) && $arFields['DT_SKIP_TIME'] !== 'Y' && $arFields['DT_SKIP_TIME'] !== 'N')
2998 {
2999 unset($arFields['DT_SKIP_TIME']);
3000 }
3001
3002 unset($arFields['DT_FROM'], $arFields['DT_TO']);
3003
3004 $arFields['DT_SKIP_TIME'] = ($arFields['DT_SKIP_TIME'] ?? null) !== 'Y' ? 'N' : 'Y';
3005 $fromTs = CCalendar::Timestamp($arFields['DATE_FROM'], false, $arFields['DT_SKIP_TIME'] !== 'Y');
3006 $toTs = CCalendar::Timestamp($arFields['DATE_TO'], false, $arFields['DT_SKIP_TIME'] !== 'Y');
3007
3008 $arFields['DATE_FROM'] = CCalendar::Date($fromTs);
3009 $arFields['DATE_TO'] = CCalendar::Date($toTs);
3010
3011 if (!$fromTs)
3012 {
3013 $arFields['DATE_FROM'] = FormatDate("SHORT", time());
3014 $fromTs = CCalendar::Timestamp($arFields['DATE_FROM'], false, false);
3015 if (!$toTs)
3016 {
3017 $arFields['DATE_TO'] = $arFields['DATE_FROM'];
3018 $toTs = $fromTs;
3019 $arFields['DT_SKIP_TIME'] = 'Y';
3020 }
3021 }
3022 elseif (!$toTs)
3023 {
3024 $arFields['DATE_TO'] = $arFields['DATE_FROM'];
3025 $toTs = $fromTs;
3026 }
3027
3028 if (($arFields['DT_SKIP_TIME'] ?? null) !== 'Y')
3029 {
3030 $arFields['DT_SKIP_TIME'] = 'N';
3031 if (!isset($arFields['TZ_FROM']) && isset($currentEvent['TZ_FROM']))
3032 {
3033 $arFields['TZ_FROM'] = $currentEvent['TZ_FROM'];
3034 }
3035 if (!isset($arFields['TZ_TO']) && isset($currentEvent['TZ_TO']))
3036 {
3037 $arFields['TZ_TO'] = $currentEvent['TZ_TO'];
3038 }
3039
3040 if (!isset($arFields['TZ_FROM']) && !isset($arFields['TZ_TO']))
3041 {
3042 $userTimezoneName = CCalendar::GetUserTimezoneName($userId, true);
3043 $arFields['TZ_FROM'] = $userTimezoneName;
3044 $arFields['TZ_TO'] = $userTimezoneName;
3045 }
3046
3047 if (!isset($arFields['TZ_OFFSET_FROM']))
3048 {
3049 $arFields['TZ_OFFSET_FROM'] = CCalendar::GetTimezoneOffset($arFields['TZ_FROM'], $fromTs);
3050 }
3051 if (!isset($arFields['TZ_OFFSET_TO']))
3052 {
3053 $arFields['TZ_OFFSET_TO'] = CCalendar::GetTimezoneOffset($arFields['TZ_TO'], $toTs);
3054 }
3055 }
3056
3057 if (!isset($arFields['TZ_OFFSET_FROM']))
3058 {
3059 $arFields['TZ_OFFSET_FROM'] = 0;
3060 }
3061 if (!isset($arFields['TZ_OFFSET_TO']))
3062 {
3063 $arFields['TZ_OFFSET_TO'] = 0;
3064 }
3065
3066 if (!isset($arFields['DATE_FROM_TS_UTC']))
3067 {
3068 $arFields['DATE_FROM_TS_UTC'] = $fromTs - $arFields['TZ_OFFSET_FROM'];
3069 }
3070 if (!isset($arFields['DATE_TO_TS_UTC']))
3071 {
3072 $arFields['DATE_TO_TS_UTC'] = $toTs - $arFields['TZ_OFFSET_TO'];
3073 }
3074
3075 if ($arFields['DATE_FROM_TS_UTC'] > $arFields['DATE_TO_TS_UTC'])
3076 {
3077 $arFields['DATE_TO'] = $arFields['DATE_FROM'];
3078 $arFields['DATE_TO_TS_UTC'] = $arFields['DATE_FROM_TS_UTC'];
3079 $arFields['TZ_OFFSET_TO'] = $arFields['TZ_OFFSET_FROM'];
3080 $arFields['TZ_TO'] = $arFields['TZ_FROM'];
3081 }
3082
3083 $h24 = 60 * 60 * 24; // 24 hours
3084 if (($arFields['DT_SKIP_TIME'] ?? null) === 'Y')
3085 {
3086 unset($arFields['TZ_FROM'], $arFields['TZ_TO'], $arFields['TZ_OFFSET_FROM'], $arFields['TZ_OFFSET_TO']);
3087 }
3088
3089 // Event length in seconds
3090 if ((int)$fromTs === (int)$toTs && date('H:i', $fromTs) === '00:00' && $arFields['DT_SKIP_TIME'] === 'Y') // One day
3091 {
3092 $arFields['DT_LENGTH'] = $h24;
3093 }
3094 else
3095 {
3096 $arFields['DT_LENGTH'] = (int)($arFields['DATE_TO_TS_UTC'] - $arFields['DATE_FROM_TS_UTC']);
3097 if (($arFields['DT_SKIP_TIME'] ?? null) === "Y") // We have dates without times
3098 {
3099 $arFields['DT_LENGTH'] += $h24;
3100 }
3101 }
3102
3103 if (empty($arFields['VERSION']))
3104 {
3105 $arFields['VERSION'] = 1;
3106 }
3107
3108 // Accessibility
3109 $arFields['ACCESSIBILITY'] = mb_strtolower(trim($arFields['ACCESSIBILITY'] ?? ''));
3110 if (!in_array($arFields['ACCESSIBILITY'], ['busy', 'quest', 'free', 'absent'], true))
3111 {
3112 $arFields['ACCESSIBILITY'] = 'busy';
3113 }
3114
3115 // Importance
3116 $arFields['IMPORTANCE'] = mb_strtolower(trim($arFields['IMPORTANCE'] ?? ''));
3117 if (!in_array($arFields['IMPORTANCE'], ['high', 'normal', 'low']))
3118 {
3119 $arFields['IMPORTANCE'] = 'normal';
3120 }
3121
3122 // Color
3123 $arFields['COLOR'] = CCalendar::Color($arFields['COLOR'] ?? null, false);
3124
3125 // Section
3126 if (
3127 isset($arFields['SECTIONS'])
3128 && !is_array($arFields['SECTIONS'])
3129 && (int)$arFields['SECTIONS'] > 0
3130 )
3131 {
3132 $arFields['SECTIONS'] = (array)((int)($arFields['SECTIONS'] ?? null));
3133 }
3134
3135 self::checkRecurringRuleField($arFields, $toTs, ($currentEvent['EXDATE'] ?? null));
3136
3137 // Location
3138 if (!isset($arFields['LOCATION']) || !is_array($arFields['LOCATION']))
3139 {
3140 $arFields['LOCATION'] = [
3141 "NEW" => is_string($arFields['LOCATION'] ?? null)
3142 ? $arFields['LOCATION']
3143 : "",
3144 ];
3145 }
3146
3147 // Private
3148 $arFields['PRIVATE_EVENT'] = isset($arFields['PRIVATE_EVENT']) && $arFields['PRIVATE_EVENT'];
3149
3150 return true;
3151 }
3152
3153 public static function CheckEntryChanges($newFields = [], $currentFields = [])
3154 {
3155 $changes = [];
3156 $fieldList = [
3157 'NAME',
3158 'DATE_FROM',
3159 'DATE_TO',
3160 'RRULE',
3161 'DESCRIPTION',
3162 'LOCATION',
3163 'IMPORTANCE',
3164 ];
3165
3166 foreach ($fieldList as $fieldKey)
3167 {
3168 if ($fieldKey === 'LOCATION')
3169 {
3170 if (
3171 is_array($newFields[$fieldKey] ?? null)
3172 && ($newFields[$fieldKey]['NEW'] ?? null) !== ($currentFields[$fieldKey] ?? null)
3173 && (CCalendar::GetTextLocation($newFields[$fieldKey]['NEW'] ?? '')) !== (CCalendar::GetTextLocation($currentFields[$fieldKey] ?? ''))
3174 )
3175 {
3176 $changes[] = [
3177 'fieldKey' => $fieldKey,
3178 'oldValue' => $currentFields[$fieldKey] ?? null,
3179 'newValue' => $newFields[$fieldKey]['NEW'] ?? null,
3180 ];
3181 }
3182 else if (
3183 !is_array($newFields[$fieldKey] ?? null)
3184 && ($newFields[$fieldKey] ?? null) !== ($currentFields[$fieldKey] ?? null)
3185 && (CCalendar::GetTextLocation($newFields[$fieldKey] ?? '')) !== (CCalendar::GetTextLocation($currentFields[$fieldKey] ?? ''))
3186 )
3187 {
3188 $changes[] = [
3189 'fieldKey' => $fieldKey,
3190 'oldValue' => $currentFields[$fieldKey],
3191 'newValue' => $newFields[$fieldKey],
3192 ];
3193 }
3194 }
3195 else if ($fieldKey === 'DATE_FROM')
3196 {
3197 if (
3198 $newFields[$fieldKey] !== $currentFields[$fieldKey]
3199 || ($newFields['TZ_FROM'] ?? null) !== ($currentFields['TZ_FROM'] ?? null)
3200 )
3201 {
3202 $changes[] = [
3203 'fieldKey' => $fieldKey,
3204 'oldValue' => $currentFields[$fieldKey],
3205 'newValue' => $newFields[$fieldKey],
3206 ];
3207 }
3208 }
3209 else if ($fieldKey === 'DATE_TO')
3210 {
3211 if (
3212 (
3213 $newFields['DATE_FROM'] === $currentFields['DATE_FROM']
3214 && ($newFields['TZ_FROM'] ?? null) === ($currentFields['TZ_FROM'] ?? null)
3215 )
3216 &&
3217 (
3218 $newFields[$fieldKey] !== $currentFields[$fieldKey]
3219 || ($newFields['TZ_TO'] ?? null) !== ($currentFields['TZ_TO'] ?? null)
3220 )
3221 )
3222 {
3223 $changes[] = [
3224 'fieldKey' => $fieldKey,
3225 'oldValue' => $currentFields[$fieldKey],
3226 'newValue' => $newFields[$fieldKey],
3227 ];
3228 }
3229 }
3230 else if ($fieldKey === 'IMPORTANCE')
3231 {
3232 if (
3233 $newFields[$fieldKey] !== $currentFields[$fieldKey]
3234 && $newFields[$fieldKey] === 'high'
3235 )
3236 {
3237 $changes[] = [
3238 'fieldKey' => $fieldKey,
3239 'oldValue' => $currentFields[$fieldKey],
3240 'newValue' => $newFields[$fieldKey],
3241 ];
3242 }
3243 }
3244 else if ($fieldKey === 'DESCRIPTION')
3245 {
3246 if (mb_strtolower(trim($newFields[$fieldKey])) !== mb_strtolower(trim($currentFields[$fieldKey])))
3247 {
3248 $changes[] = [
3249 'fieldKey' => $fieldKey,
3250 'oldValue' => $currentFields[$fieldKey],
3251 'newValue' => $newFields[$fieldKey],
3252 ];
3253 }
3254 }
3255 else if ($fieldKey === 'RRULE')
3256 {
3257 $newRule = self::ParseRRULE($newFields[$fieldKey] ?? null);
3258 $oldRule = self::ParseRRULE($currentFields[$fieldKey] ?? null);
3259
3260 if (
3261 (($newRule['FREQ'] ?? null) !== ($oldRule['FREQ'] ?? null))
3262 || (($newRule['INTERVAL'] ?? null) !== ($oldRule['INTERVAL'] ?? null))
3263 || (($newRule['BYDAY'] ?? null) !== ($oldRule['BYDAY'] ?? null))
3264 )
3265 {
3266 $changes[] = [
3267 'fieldKey' => $fieldKey,
3268 'oldValue' => $oldRule,
3269 'newValue' => $newRule,
3270 ];
3271 }
3272 }
3273
3274 else if (($newFields[$fieldKey] ?? null) !== ($currentFields[$fieldKey] ?? null))
3275 {
3276 $changes[] = [
3277 'fieldKey' => $fieldKey,
3278 'oldValue' => $currentFields[$fieldKey],
3279 'newValue' => $newFields[$fieldKey],
3280 ];
3281 }
3282 }
3283
3284 if (
3285 is_array($newFields['ATTENDEES_CODES'] ?? null)
3286 && is_array($currentFields['ATTENDEES_CODES'] ?? null)
3287 && (count(array_diff($newFields['ATTENDEES_CODES'], $currentFields['ATTENDEES_CODES']))
3288 || count(array_diff($currentFields['ATTENDEES_CODES'], $newFields['ATTENDEES_CODES'])))
3289 )
3290 {
3291 $changes[] = [
3292 'fieldKey' => 'ATTENDEES',
3293 'oldValue' => $currentFields['ATTENDEES_CODES'],
3294 'newValue' => $newFields['ATTENDEES_CODES'],
3295 ];
3296 }
3297
3298 return $changes;
3299 }
3300
3301 private static function PackRRule($RRule = [])
3302 {
3303 $strRes = "";
3304 if (is_array($RRule))
3305 {
3306 foreach($RRule as $key => $val)
3307 {
3308 $strRes .= $key . '=' . $val . ';';
3309 }
3310 }
3311
3312 return trim($strRes, ', ');
3313 }
3314
3315 //
3316
3325 private static function CreateChildEvents($parentId, $arFields, $params, $changeFields)
3326 {
3327 global $CACHE_MANAGER;
3328 $parentId = (int)$parentId;
3329 $isNewEvent = !isset($arFields['ID']) || $arFields['ID'] <= 0;
3330 $chatId = (int) ($arFields['~MEETING']['CHAT_ID'] ?? null) ;
3331 $involvedAttendees = []; // List of all attendees to invite or to exclude from event
3332 $isCalDavEnabled = CCalendar::IsCalDAVEnabled();
3333 $isPastEvent = ($arFields['DATE_TO_TS_UTC'] ?? null) < (time() - (int)date('Z'));
3334
3335 $userId = $params['userId'];
3336 $attendees = is_array($arFields['ATTENDEES'] ?? null) ? $arFields['ATTENDEES'] : []; // List of attendees for event
3337 $chat = null;
3338 $eventWithEmailGuestEnabled = Bitrix24Manager::isFeatureEnabled(FeatureDictionary::CALENDAR_EVENTS_WITH_EMAIL_GUESTS);
3339
3340 unset($params['dontSyncParent']);
3341
3342 if ($chatId > 0 && Loader::includeModule('im'))
3343 {
3344 $chat = new \CIMChat($userId);
3345 }
3346
3347 if (empty($attendees) && !($arFields['CAL_TYPE'] === 'user' && $arFields['OWNER_ID'] === $userId))
3348 {
3349 $attendees[] = (int)$arFields['CREATED_BY'];
3350 }
3351
3352 foreach($attendees as $userKey)
3353 {
3354 $involvedAttendees[] = (int)$userKey;
3355 }
3356
3357 $currentAttendeesIndex = [];
3358 $deletedAttendees = [];
3359 $affectedEventIds = [$parentId];
3360 if (!$isNewEvent)
3361 {
3362 $curAttendees = self::GetAttendees($parentId);
3363 $curAttendees = is_array(($curAttendees[$parentId] ?? null)) ? $curAttendees[$parentId] : [];
3364 foreach($curAttendees as $user)
3365 {
3366 $currentAttendeesIndex[$user['USER_ID']] = $user;
3367 if (
3368 (int)$user['USER_ID'] !== $arFields['MEETING_HOST']
3369 && (
3370 (int)$user['USER_ID'] !== (int)$arFields['OWNER_ID']
3371 || $arFields['CAL_TYPE'] !== 'user'
3372 )
3373 )
3374 {
3375 $deletedAttendees[$user['USER_ID']] = (int)$user['USER_ID'];
3376 $involvedAttendees[] = (int)$user['USER_ID'];
3377 }
3378 }
3379 }
3380 $involvedAttendees = array_unique($involvedAttendees);
3381 $userIndex = self::generateUserIndex($involvedAttendees, $arFields['MEETING_HOST']);
3382
3383 $attendeeStatuses = self::getAttendeeStatuses(
3384 $attendees,
3385 $arFields,
3386 $params,
3387 $currentAttendeesIndex,
3388 $isNewEvent
3389 );
3390 $params['attendeeStatuses'] = $attendeeStatuses;
3391
3392 foreach ($attendees as $userKey)
3393 {
3394 $clonedParams = $params;
3395 $attendeeId = (int)$userKey;
3396 $isNewAttendee = !empty($clonedParams['currentEvent']['ATTENDEE_LIST'])
3397 && is_array($clonedParams['currentEvent']['ATTENDEE_LIST'])
3398 && self::isNewAttendee($clonedParams['currentEvent']['ATTENDEE_LIST'], $attendeeId)
3399 ;
3400 $CACHE_MANAGER->ClearByTag('calendar_user_'.$attendeeId);
3401
3402 // Skip creation of child event if it's event inside his own user calendar
3403 if (
3404 $attendeeId
3405 && (($arFields['CAL_TYPE'] ?? null) !== 'user' || (int)($arFields['OWNER_ID'] ?? null) !== $attendeeId)
3406 )
3407 {
3408 $childParams = $clonedParams;
3409 $childParams['arFields']['CAL_TYPE'] = 'user';
3410 $childParams['arFields']['PARENT_ID'] = $parentId;
3411 $childParams['arFields']['OWNER_ID'] = $attendeeId;
3412 $childParams['arFields']['CREATED_BY'] = $attendeeId;
3413 $childParams['arFields']['CREATED'] = $arFields['DATE_CREATE'] ?? null;
3414 $childParams['arFields']['MODIFIED'] = $arFields['TIMESTAMP_X'] ?? null;
3415 $childParams['arFields']['ACCESSIBILITY'] = $arFields['ACCESSIBILITY'] ?? null;
3416 $childParams['arFields']['MEETING'] = $arFields['~MEETING'] ?? null;
3417 $childParams['arFields']['TEXT_LOCATION'] = CCalendar::GetTextLocation($arFields["LOCATION"] ?? null);
3418 $childParams['arFields']['MEETING_STATUS'] = $attendeeStatuses[$attendeeId]['status'];
3419 $childParams['arFields']['EVENT_TYPE'] = $arFields['EVENT_TYPE'] ?? null;
3420 $childParams['sendInvitations'] = $clonedParams['sendInvitations'] ?? null;
3421
3422 unset(
3423 $childParams['arFields']['SECTIONS'],
3424 $childParams['arFields']['SECTION_ID'],
3425 $childParams['currentEvent'],
3426 $childParams['updateReminders'],
3427 $childParams['arFields']['ID'],
3428 $childParams['arFields']['DAV_XML_ID'],
3429 $childParams['arFields']['G_EVENT_ID'],
3430 $childParams['arFields']['SYNC_STATUS']
3431 );
3432
3433 $isExchangeEnabled = CCalendar::IsExchangeEnabled($attendeeId);
3434
3435 if (
3436 isset($userIndex[$attendeeId])
3437 && $userIndex[$attendeeId]['EXTERNAL_AUTH_ID'] === 'email'
3438 && $isNewEvent
3439 && !$eventWithEmailGuestEnabled
3440 )
3441 {
3442 // Just skip external emil users if they are not allowed
3443 // We will show warning on the client's side
3444 continue;
3445 }
3446
3447 if (!empty($currentAttendeesIndex[$attendeeId]))
3448 {
3449 $childParams['arFields']['ID'] = $currentAttendeesIndex[$attendeeId]['EVENT_ID'];
3450
3451 if (empty($arFields['~MEETING']['REINVITE']))
3452 {
3453 $childParams['arFields']['MEETING_STATUS'] = $currentAttendeesIndex[$attendeeId]['STATUS'];
3454
3455 $childParams['sendInvitations'] = $childParams['sendInvitations'] && $currentAttendeesIndex[$attendeeId]['STATUS'] !== 'Q';
3456 }
3457
3458 if ($attendeeStatuses[$attendeeId]['sendInvitations'])
3459 {
3460 $childParams['sendInvitations'] = true;
3461 }
3462
3463 if (
3464 ($isExchangeEnabled || $isCalDavEnabled)
3465 && ($childParams['overSaving'] ?? false) !== true
3466 )
3467 {
3468 self::prepareArFieldBeforeSyncEvent($childParams);
3469 $childParams['currentEvent'] = self::GetById($childParams['arFields']['ID'], false);
3470
3471 $davParams = [
3472 'bCalDav' => $isCalDavEnabled,
3473 'bExchange' => $isExchangeEnabled,
3474 'sectionId' => (int)$childParams['currentEvent']['SECTION_ID'],
3475 'modeSync' => $clonedParams['modeSync'],
3476 'editInstance' => $clonedParams['editInstance'],
3477 'originalDavXmlId' => $childParams['currentEvent']['G_EVENT_ID'],
3478 'instanceTz' => $childParams['currentEvent']['TZ_FROM'],
3479 'editParentEvents' => $clonedParams['editParentEvents'],
3480 'editNextEvents' => $clonedParams['editNextEvents'],
3481 'syncCaldav' => $clonedParams['syncCaldav'],
3482 'parentDateFrom' => $childParams['currentEvent']['DATE_FROM'],
3483 'parentDateTo' => $childParams['currentEvent']['DATE_TO'],
3484 ];
3485 CCalendarSync::DoSaveToDav($childParams['arFields'], $davParams, $childParams['currentEvent']);
3486 }
3487 }
3488 else
3489 {
3490 $childSectId = CCalendar::GetMeetingSection($attendeeId, true);
3491 if ($childSectId)
3492 {
3493 $childParams['arFields']['SECTIONS'] = [$childSectId];
3494 }
3495
3496 if (!$clonedParams['editInstance'])
3497 {
3498 $childParams['arFields']['DAV_XML_ID'] = self::getUidForChildEvent($childParams['arFields']);
3499 }
3500
3501 $parentEvent = [];
3502 if (!empty($childParams['arFields']['RECURRENCE_ID']))
3503 {
3504 $parentEvent = Internals\EventTable::query()
3505 ->where('PARENT_ID' , (int)$childParams['arFields']['RECURRENCE_ID'])
3506 ->where('OWNER_ID' , (int)($childParams['arFields']['OWNER_ID'] ?? 0))
3507 ->setSelect([
3508 'DAV_XML_ID',
3509 'TZ_FROM',
3510 'DATE_FROM',
3511 'DATE_TO',
3512 ])
3513 ->setLimit(1)
3514 ->exec()
3515 ->fetch() ?: [];
3516 }
3517
3518 if ($parentEvent)
3519 {
3520 $childParams['arFields']['DAV_XML_ID'] = $parentEvent['DAV_XML_ID'] ?? null;
3521 }
3522 else
3523 {
3524 unset(
3525 $childParams['arFields']['ORIGINAL_DATE_FROM'],
3526 $childParams['arFields']['RECURRENCE_ID'],
3527 $clonedParams['recursionEditMode']
3528 );
3529
3530 $childParams['arFields']['DAV_XML_ID'] = UidGenerator::createInstance()
3531 ->setPortalName(Util::getServerName())
3532 ->setDate(new Date(Util::getDateObject(
3533 $childParams['arFields']['DATE_FROM'] ?? null,
3534 false,
3535 ($childParams['arFields']['TZ_FROM'] ?? null) ?: null
3536 )))
3537 ->setUserId((int)($childParams['arFields']['OWNER_ID'] ?? null))
3538 ->getUidWithDate();
3539 }
3540
3541
3542 // CalDav & Exchange
3543 if (
3544 ($isExchangeEnabled || $isCalDavEnabled)
3545 && ($childParams['overSaving'] ?? false) !== true
3546 )
3547 {
3548 $davParams = [
3549 'bCalDav' => $isCalDavEnabled,
3550 'bExchange' => $isExchangeEnabled,
3551 'sectionId' => $childSectId,
3552 'modeSync' => $clonedParams['modeSync'] ?? null,
3553 'editInstance' => $clonedParams['editInstance'] ?? null,
3554 'instanceTz' => $parentEvent['TZ_FROM'] ?? null,
3555 'editParentEvents' => $clonedParams['editParentEvents'] ?? null,
3556 'editNextEvents' => $clonedParams['editNextEvents'] ?? null,
3557 'syncCaldav' => $clonedParams['syncCaldav'] ?? null,
3558 'parentDateFrom' => $parentEvent['DATE_FROM'] ?? null,
3559 'parentDateTo' => $parentEvent['DATE_TO'] ?? null,
3560 ];
3561 CCalendarSync::DoSaveToDav($childParams['arFields'], $davParams);
3562 }
3563 }
3564
3565 $curEvent = null;
3566 if (!empty($childParams['arFields']['ID']))
3567 {
3568 $curEvent = self::GetList([
3569 'arFilter' => [
3570 "ID" => (int)$childParams['arFields']['ID'],
3571 "DELETED" => 'N',
3572 ],
3573 'checkPermissions' => false,
3574 'parseRecursion' => false,
3575 'fetchAttendees' => true,
3576 'fetchMeetings' => false,
3577 'userId' => $userId,
3578 ]);
3579 }
3580 if ($curEvent)
3581 {
3582 $curEvent = $curEvent[0];
3583 }
3584
3585 if (!empty($curEvent['COLOR']))
3586 {
3587 if (
3588 empty($childParams['arFields']['COLOR'])
3589 || ($curEvent['COLOR'] !== $childParams['arFields']['COLOR'])
3590 )
3591 {
3592 $childParams['arFields']['COLOR'] = $curEvent['COLOR'];
3593 }
3594 }
3595
3596 $id = self::Edit($childParams);
3597 $affectedEventIds[] = $id;
3598
3599 if (
3600 $userIndex[$attendeeId]
3601 && $userIndex[$attendeeId]['EXTERNAL_AUTH_ID'] === 'email'
3602 && ((!($clonedParams['fromWebservice'] ?? false)) || !empty($changeFields))
3603 && !$isPastEvent
3604 && ($childParams['overSaving'] ?? false) !== true
3605 )
3606 {
3607
3608 $sender = self::getSenderForIcal($userIndex, $childParams['arFields']['MEETING_HOST']);
3609
3610 if (empty($sender) || !$sender['ID'])
3611 {
3612 continue;
3613 }
3614
3615 if (!empty($email = self::getSenderEmailForIcal($arFields['MEETING'])) && !self::$isAddIcalFailEmailError)
3616 {
3617 $sender['EMAIL'] = $email;
3618 }
3619 else
3620 {
3621 CCalendar::ThrowError(GetMessage("EC_ICAL_NOTICE_DO_NOT_SET_EMAIL"));
3622 self::$isAddIcalFailEmailError = true;
3623 continue;
3624 }
3625
3626 $arFields['ID'] = $id;
3627 $invitationInfo = [];
3628
3629 if (!empty($currentAttendeesIndex[$attendeeId]))
3630 {
3631 $mailChangeFields = array_filter($changeFields,
3632 static fn (array $field) => !in_array(
3633 $field['fieldKey'],
3634 ['ATTENDEES', 'IMPORTANCE'],
3635 true,
3636 ),
3637 );
3638 if (!empty($mailChangeFields))
3639 {
3640 $invitationInfo = (new InvitationInfo(
3641 (int)$arFields['ID'],
3642 (int)$sender['ID'],
3643 (int)$attendeeId,
3644 InvitationInfo::TYPE_EDIT,
3645 $mailChangeFields,
3646 ))->toArray();
3647 }
3648 }
3649 else
3650 {
3651 $invitationInfo = (new InvitationInfo(
3652 (int)$arFields['ID'],
3653 (int)$sender['ID'],
3654 (int)$attendeeId,
3655 InvitationInfo::TYPE_REQUEST
3656 ))->toArray();
3657 }
3658
3659 SendingEmailNotification::sendMessageToQueue($invitationInfo);
3660 }
3661
3662 if (
3663 $chatId > 0
3664 && $chat
3665 && $isNewAttendee
3666 && $userIndex[$attendeeId]
3667 && $userIndex[$attendeeId]['EXTERNAL_AUTH_ID'] !== 'email'
3668 && $userIndex[$attendeeId]['EXTERNAL_AUTH_ID'] !== Sharing\SharingUser::EXTERNAL_AUTH_ID
3669 && $childParams['arFields']['MEETING_STATUS'] !== 'N'
3670 )
3671 {
3672 $chat->AddUser($chatId, $attendeeId, $hideHistory = true, $skipMessage = false);
3673 }
3674
3675 if ($id)
3676 {
3677 CCalendar::syncChange($id, $childParams['arFields'], $clonedParams, $curEvent);
3678 }
3679
3680 unset($deletedAttendees[$attendeeId]);
3681 }
3682 }
3683
3684 // Delete
3685 $eventIdToDelete = [];
3686 if (!$isNewEvent && !empty($deletedAttendees))
3687 {
3688 $isSharing = in_array(
3689 $arFields['EVENT_TYPE'] ?? '', Sharing\SharingEventManager::getSharingEventTypes(), true
3690 );
3691 if ($isSharing)
3692 {
3693 $notifyUserId = $userId;
3694 }
3695 else
3696 {
3697 $notifyUserId = $arFields['MEETING_HOST'] ?? null;
3698 }
3699 foreach($deletedAttendees as $attendeeId)
3700 {
3701 if ($chatId > 0 && $chat)
3702 {
3703 $chat->DeleteUser($chatId, $attendeeId, false);
3704 }
3705
3706 $att = $currentAttendeesIndex[$attendeeId];
3707 if (
3708 ($params['sendInvitations'] ?? null) !== false
3709 && ($att['STATUS'] ?? null) === 'Y'
3710 && !$isPastEvent
3711 )
3712 {
3713 $CACHE_MANAGER->ClearByTag('calendar_user_'.$att["USER_ID"]);
3714 $fromTo = self::GetEventFromToForUser($arFields, $att["USER_ID"]);
3716 'mode' => 'cancel',
3717 "name" => $arFields['NAME'] ?? null,
3718 "from" => $fromTo['DATE_FROM'] ?? null,
3719 "to" => $fromTo['DATE_TO'] ?? null,
3720 "location" => CCalendar::GetTextLocation($arFields["LOCATION"] ?? null),
3721 "guestId" => $att["USER_ID"] ?? null,
3722 "eventId" => $parentId,
3723 "userId" => $notifyUserId,
3724 "fields" => $arFields,
3725 ]);
3726 }
3727 //add pull event to update calendar grid after event delete
3728 $pullUserId = (int)$attendeeId;
3729 if (
3730 $pullUserId > 0
3731 && self::$sendPush
3732 )
3733 {
3734 Util::addPullEvent(
3735 PushCommand::DeleteEvent,
3736 $pullUserId,
3737 [
3738 'fields' => $arFields,
3739 'requestUid' => $params['userId'],
3740 ]
3741 );
3742 }
3743 CCalendarNotify::ClearNotifications($arFields['PARENT_ID'], $pullUserId);
3744 $affectedEventIds[] = $att['EVENT_ID'] ?? 0;
3745
3746 if ((int)($att['EVENT_ID'] ?? null))
3747 {
3748 $eventIdToDelete[] = (int)$att['EVENT_ID'];
3749 }
3750
3751 $currentEvent = self::GetList([
3752 'arFilter' => [
3753 "PARENT_ID" => $parentId,
3754 "OWNER_ID" => $attendeeId,
3755 "IS_MEETING" => 1,
3756 "DELETED" => "N",
3757 ],
3758 'parseRecursion' => false,
3759 'fetchAttendees' => true,
3760 'fetchMeetings' => true,
3761 'checkPermissions' => false,
3762 'setDefaultLimit' => false,
3763 ]);
3764 $currentEvent = $currentEvent[0];
3765
3766 $isExchangeEnabled = CCalendar::IsExchangeEnabled($attendeeId);
3767 if (($isExchangeEnabled || $isCalDavEnabled) && $currentEvent)
3768 {
3770 'bCalDav' => $isCalDavEnabled,
3771 'bExchangeEnabled' => $isExchangeEnabled,
3772 'sectionId' => $currentEvent['SECT_ID'] ?? null,
3773 ], $currentEvent);
3774 }
3775
3776 if ($currentEvent)
3777 {
3778 self::onEventDelete($currentEvent, $params);
3779 }
3780
3781 if (isset($att['EXTERNAL_AUTH_ID']) && $att['EXTERNAL_AUTH_ID'] === 'email' && !$isPastEvent)
3782 {
3783 if (empty($receiver['EMAIL']))
3784 {
3785 continue;
3786 }
3787
3788 $sender = self::getSenderForIcal($currentAttendeesIndex, $arFields['MEETING_HOST']);
3789 if ($email = self::getSenderEmailForIcal($arFields['MEETING']))
3790 {
3791 $sender['EMAIL'] = $email;
3792 }
3793 else
3794 {
3795 $meetingHostSettings = UserSettings::get($arFields['MEETING_HOST']);
3796 $sender['EMAIL'] = $meetingHostSettings['sendFromEmail'];
3797 }
3798 if (empty($sender['ID']) && isset($sender['USER_ID']))
3799 {
3800 $sender['ID'] = (int)$sender['USER_ID'];
3801 }
3802
3803 $invitationInfo = (new InvitationInfo(
3804 (int)$arFields['ID'],
3805 (int)$sender['ID'],
3806 (int)$receiver['ID'],
3807 InvitationInfo::TYPE_CANCEL
3808 ))->toArray();
3809
3810 SendingEmailNotification::sendMessageToQueue($invitationInfo);
3811 }
3812 }
3813 }
3814
3815
3816 if (!empty($eventIdToDelete))
3817 {
3818 Internals\EventTable::updateByFilter(
3819 [
3820 'ID' => $eventIdToDelete,
3821 '=PARENT_ID' => (int)$parentId,
3822 ],
3823 ['DELETED' => 'Y'],
3824 );
3825
3826 self::safeDeleteEventAttendees($parentId, $deletedAttendees);
3827 }
3828
3829 if (!empty($involvedAttendees))
3830 {
3831 $involvedAttendees = array_unique($involvedAttendees);
3832 CCalendar::UpdateCounter($involvedAttendees, array_unique($affectedEventIds));
3833 }
3834 }
3835
3836 public static function UpdateParentEventExDate($recurrenceId, $exDate, $attendeeIds)
3837 {
3838 global $CACHE_MANAGER;
3839
3840 $event = Internals\EventTable::query()
3841 ->setSelect(['PARENT_ID', 'EXDATE'])
3842 ->where('PARENT_ID', $recurrenceId)
3843 ->setLimit(1)
3844 ->exec()->fetch()
3845 ;
3846 $exDates = self::GetExDate($event['EXDATE']);
3847 $exDates[] = date(
3848 ExcludedDatesCollection::EXCLUDED_DATE_FORMAT,
3849 \CCalendar::Timestamp($exDate)
3850 );
3851 $exDates = array_unique($exDates);
3852 $strExDates = implode(';', $exDates);
3853
3854 Internals\EventTable::updateByFilter(
3855 ['=PARENT_ID' => (int)$recurrenceId],
3856 ['EXDATE' => $strExDates],
3857 );
3858
3859 if (is_array($attendeeIds))
3860 {
3861 foreach ($attendeeIds as $id)
3862 {
3863 $CACHE_MANAGER->ClearByTag('calendar_user_' . $id);
3864 }
3865 }
3866
3867 return true;
3868 }
3869
3870 public static function GetEventFromToForUser($params, $userId)
3871 {
3872 $skipTime = $params['DT_SKIP_TIME'] !== 'N';
3873
3874 $fromTs = CCalendar::Timestamp($params['DATE_FROM'], false, !$skipTime);
3875 $toTs = CCalendar::Timestamp($params['DATE_TO'], false, !$skipTime);
3876
3877 if (!$skipTime)
3878 {
3879 $fromTs -= (CCalendar::GetTimezoneOffset($params['TZ_FROM']) - CCalendar::GetCurrentOffsetUTC($userId));
3880 $toTs -= (CCalendar::GetTimezoneOffset($params['TZ_TO']) - CCalendar::GetCurrentOffsetUTC($userId));
3881 }
3882
3883 $dateFrom = CCalendar::Date($fromTs, !$skipTime);
3884 $dateTo = CCalendar::Date($toTs, !$skipTime);
3885
3886 return array(
3887 "DATE_FROM" => $dateFrom,
3888 "DATE_TO" => $dateTo,
3889 "TS_FROM" => $fromTs,
3890 "TS_TO" => $toTs,
3891 );
3892 }
3893
3894 public static function OnPullPrepareArFields($arFields = [])
3895 {
3896 $arFields['~DESCRIPTION'] = self::ParseText($arFields['DESCRIPTION']);
3897
3898 $arFields['~LOCATION'] = '';
3899 if (($arFields['LOCATION'] ?? null) !== '')
3900 {
3901 $arFields['~LOCATION'] = $arFields['LOCATION'];
3902 $arFields['LOCATION'] = CCalendar::GetTextLocation($arFields["LOCATION"]);
3903 }
3904
3905 if (isset($arFields['~MEETING']))
3906 {
3907 $arFields['MEETING'] = $arFields['~MEETING'];
3908 }
3909
3910 if (!empty($arFields['REMIND']) && !is_array($arFields['REMIND']))
3911 {
3912 $arFields['REMIND'] = unserialize($arFields['REMIND'], ['allowed_classes' => false]);
3913 }
3914 if (!is_array($arFields['REMIND'] ?? null))
3915 {
3916 $arFields['REMIND'] = [];
3917 }
3918
3919 $arFields['RRULE'] = self::ParseRRULE($arFields['RRULE']);
3920
3921 return $arFields;
3922 }
3923
3924 public static function UpdateUserFields($eventId, $arFields = [], $updateSearchIndex = true)
3925 {
3926 $eventId = (int)$eventId;
3927 if (!is_array($arFields) || empty($arFields) || $eventId <= 0)
3928 {
3929 return false;
3930 }
3931
3932 global $USER_FIELD_MANAGER;
3933 if ($USER_FIELD_MANAGER->CheckFields("CALENDAR_EVENT", $eventId, $arFields))
3934 {
3935 $USER_FIELD_MANAGER->Update("CALENDAR_EVENT", $eventId, $arFields);
3936 }
3937
3938 foreach(GetModuleEvents("calendar", "OnAfterCalendarEventUserFieldsUpdate", true) as $arEvent)
3939 {
3940 ExecuteModuleEventEx($arEvent, array($eventId, $arFields));
3941 }
3942
3943 if ($updateSearchIndex)
3944 {
3945 self::updateSearchIndex($eventId);
3946 }
3947
3948 return true;
3949 }
3950
3951 public static function Delete($params)
3952 {
3953 global $CACHE_MANAGER;
3954 $bCalDav = CCalendar::IsCalDAVEnabled();
3955 $id = (int)$params['id'];
3956 $sendNotification = ($params['sendNotification'] ?? null) !== false;
3957 $params['requestUid'] = $params['requestUid'] ?? null;
3958
3959 if ($id)
3960 {
3961 $userId = (isset($params['userId']) && (int)$params['userId'] > 0)
3962 ? (int)$params['userId']
3963 : CCalendar::GetCurUserId()
3964 ;
3965
3966 $arAffectedSections = [];
3967 $entry = $params['Event'] ?? null;
3968
3969 if (!isset($entry) || !is_array($entry))
3970 {
3971 CCalendar::SetOffset();
3972 $res = self::GetList([
3973 'arFilter' => [
3974 'ID' => $id,
3975 ],
3976 'parseRecursion' => false,
3977 ]);
3978 $entry = $res[0] ?? null;
3979 }
3980
3981 if ($entry)
3982 {
3983 $entry['PARENT_ID'] = $entry['PARENT_ID'] ?? null;
3984 if (!empty($entry['IS_MEETING']) && $entry['PARENT_ID'] !== $entry['ID'])
3985 {
3986 $parentEvent = self::GetList([
3987 'arFilter' => [
3988 "ID" => $entry['PARENT_ID'],
3989 ],
3990 'parseRecursion' => false,
3991 ]);
3992 $parentEvent = $parentEvent[0];
3993 if ($parentEvent)
3994 {
3995 $accessController = new EventAccessController($userId);
3996 $eventModel = self::getEventModelForPermissionCheck(
3997 (int)($entry['ID'] ?? 0),
3998 $entry,
3999 $userId
4000 );
4001
4002 $perm = $accessController->check(ActionDictionary::ACTION_EVENT_DELETE, $eventModel);
4003 if (!$perm)
4004 {
4005 if (in_array($entry['MEETING_STATUS'] ?? null,
4006 [
4007 Dictionary::MEETING_STATUS['Yes'],
4008 Dictionary::MEETING_STATUS['Question'],
4009 ], true)
4010 )
4011 {
4012 self::SetMeetingStatus([
4013 'userId' => $userId,
4014 'eventId' => $entry['ID'],
4015 'status' => 'N',
4016 'doSendMail' => false,
4017 ]);
4018 }
4019
4020 return true;
4021 }
4022
4023 return CCalendar::DeleteEvent($parentEvent['ID']);
4024 }
4025
4026 return false;
4027 }
4028 foreach(GetModuleEvents("calendar", "OnBeforeCalendarEventDelete", true) as $arEvent)
4029 {
4030 ExecuteModuleEventEx($arEvent, array($id, $entry));
4031 }
4032
4033 if (!empty($entry['PARENT_ID']))
4034 {
4036 }
4037 else
4038 {
4040 }
4041
4042 $sharingOwnerId = -1;
4043 $eventLink = null;
4044 $eventType = $entry['EVENT_TYPE'] ?? '';
4045
4046 if (in_array($eventType, Sharing\SharingEventManager::getSharingEventTypes(), true))
4047 {
4048 $eventId = (int)($entry['ID'] ?? 0);
4049 $initiatorId = CCalendar::GetCurUserId() !== 0
4051 : $userId
4052 ;
4053
4054 Sharing\SharingEventManager::onSharingEventDeleted(
4055 $eventId,
4056 $eventType,
4057 $initiatorId,
4058 );
4059
4060 $linkFactory = (new Sharing\Link\Factory());
4061
4063 $eventLink = $linkFactory->getEventLinkByEventId((int)($entry['PARENT_ID'] ?? $eventId));
4064 if ($eventLink)
4065 {
4066 $sharingOwnerId = $eventLink->getOwnerId();
4067 }
4068 }
4069
4070 $arAffectedSections[] = $entry['SECT_ID'];
4071 // Check location: if reserve meeting was reserved - clean reservation
4072 if (!empty($entry['LOCATION']))
4073 {
4074 $loc = Rooms\Util::parseLocation($entry['LOCATION']);
4075 if ($loc['mrevid'] || $loc['room_event_id'])
4076 {
4077 Rooms\Util::releaseLocation($loc);
4078 }
4079 }
4080
4081 if ($entry['CAL_TYPE'] === 'user')
4082 {
4083 $CACHE_MANAGER->ClearByTag('calendar_user_'.$entry['OWNER_ID']);
4084 }
4085
4086 if (!empty($entry['IS_MEETING']))
4087 {
4088 $isPastEvent = (int)$entry['DATE_TO_TS_UTC'] < (time() - (int)$entry['TZ_OFFSET_TO']);;
4089 CCalendarNotify::ClearNotifications($entry['PARENT_ID']);
4090
4091 if (Loader::includeModule("im"))
4092 {
4093 CIMNotify::DeleteBySubTag("CALENDAR|INVITE|".$entry['PARENT_ID']);
4094 CIMNotify::DeleteBySubTag("CALENDAR|STATUS|".$entry['PARENT_ID']);
4095 }
4096
4097 $involvedAttendees = [];
4098
4099 $CACHE_MANAGER->ClearByTag('calendar_user_'.$userId);
4100 $childEvents = self::GetList([
4101 'arFilter' => [
4102 "PARENT_ID" => $id,
4103 ],
4104 'parseRecursion' => false,
4105 'checkPermissions' => false,
4106 'setDefaultLimit' => false,
4107 ]);
4108
4109 foreach($childEvents as $chEvent)
4110 {
4111 $CACHE_MANAGER->ClearByTag('calendar_user_'.$chEvent["OWNER_ID"]);
4112 if (
4113 $chEvent["MEETING_STATUS"] !== "N"
4114 && $sendNotification
4115 && !$isPastEvent
4116 && $sharingOwnerId !== (int)$chEvent["OWNER_ID"]
4117 )
4118 {
4119 $fromTo = self::GetEventFromToForUser($entry, $chEvent["OWNER_ID"]);
4120 $sendCancelUserId = $userId;
4121 if ($userId === 0 && ($eventLink instanceof Sharing\Link\EventLink))
4122 {
4123 $sendCancelUserId = $eventLink->getHostId();
4124 }
4125 if (
4126 (!empty($chEvent['MEETING_HOST']) && (int)$chEvent['MEETING_HOST'] === $sendCancelUserId)
4127 || self::checkAttendeeBelongsToEvent($id, $sendCancelUserId)
4128 )
4129 {
4130 if (($eventLink instanceof Sharing\Link\EventLink))
4131 {
4132 \CCalendarNotify::Send([
4133 'mode' => 'cancel_sharing',
4134 'userId' => $sendCancelUserId,
4135 'guestId' => $chEvent['OWNER_ID'],
4136 'eventId' => $id,
4137 'name' => $chEvent['NAME'],
4138 'from' => $fromTo['DATE_FROM'],
4139 'to' => $fromTo['DATE_TO'],
4140 'isSharing' => true,
4141 ]);
4142 }
4143 else
4144 {
4145 // first problematic place
4146 \CCalendarNotify::Send([
4147 'mode' => 'cancel',
4148 'name' => $chEvent['NAME'],
4149 "from" => $fromTo["DATE_FROM"],
4150 "to" => $fromTo["DATE_TO"],
4151 "location" => CCalendar::GetTextLocation($chEvent["LOCATION"]),
4152 "guestId" => $chEvent["OWNER_ID"],
4153 "eventId" => $id,
4154 "userId" => $sendCancelUserId,
4155 ]);
4156 }
4157 }
4158 }
4159
4160 if ($chEvent["MEETING_STATUS"] === "Q")
4161 {
4162 $involvedAttendees[] = $chEvent["OWNER_ID"];
4163 }
4164
4165 $bExchange = CCalendar::IsExchangeEnabled($chEvent["OWNER_ID"]);
4166 if ($bExchange || $bCalDav)
4167 {
4169 [
4170 'bCalDav' => $bCalDav,
4171 'bExchangeEnabled' => $bExchange,
4172 'sectionId' => $chEvent['SECT_ID'],
4173 ],
4174 $chEvent
4175 );
4176 }
4177
4178 self::onEventDelete($chEvent, $params);
4179
4180 $isParent = $chEvent['ID'] === $chEvent['PARENT_ID'];
4181 if (
4182 !$isParent
4183 && !$isPastEvent
4184 && ICalUtil::isMailUser($chEvent['OWNER_ID'])
4185 )
4186 {
4187 if (!empty($chEvent['ATTENDEE_LIST']) && is_array($chEvent['ATTENDEE_LIST']))
4188 {
4189 $attendeeIds = [];
4190 foreach ($chEvent['ATTENDEE_LIST'] as $attendee)
4191 {
4192 $attendeeIds[] = $attendee['id'];
4193 }
4194 }
4195 $attendees = null;
4196 if (!empty($attendeeIds))
4197 {
4198 $attendees = ICalUtil::getIndexUsersById($attendeeIds);
4199 }
4200
4201 $sender = self::getSenderForIcal($attendees, $chEvent['MEETING_HOST']);
4202 if (!empty($chEvent['MEETING']['MAIL_FROM']))
4203 {
4204 $sender['EMAIL'] = $chEvent['MEETING']['MAIL_FROM'];
4205 $sender['MAIL_FROM'] = $chEvent['MEETING']['MAIL_FROM'];
4206 }
4207 else
4208 {
4209 continue;
4210 }
4211
4213 $chEvent['VERSION'] = (int)$chEvent['VERSION'] + 1;
4214
4215 $invitationInfo = (new InvitationInfo(
4216 (int)$chEvent['ID'],
4217 (int)$sender['ID'],
4218 (int)$chEvent['OWNER_ID'],
4219 InvitationInfo::TYPE_CANCEL
4220 ))->toArray();
4221
4222 SendingEmailNotification::sendMessageToQueue($invitationInfo);
4223 }
4224
4225 $pullUserId = (int)$chEvent['OWNER_ID'] > 0 ? (int)$chEvent['OWNER_ID'] : $userId;
4226 if (
4227 $pullUserId
4228 && self::$sendPush
4229 )
4230 {
4231 Util::addPullEvent(
4232 PushCommand::DeleteEvent,
4233 $pullUserId,
4234 [
4235 'fields' => $chEvent,
4236 'requestUid' => $params['requestUid'] ?? null,
4237 ]
4238 );
4239 }
4240 }
4241
4242 // Set flag
4243 if (!empty($params['bMarkDeleted']))
4244 {
4245 Internals\EventTable::updateByFilter(
4246 ['=PARENT_ID' => $id],
4247 ['DELETED' => 'Y'],
4248 );
4249 }
4250 else // Actual deleting
4251 {
4252 Internals\EventTable::deleteByFilter(['PARENT_ID' => $id]);
4253 }
4254
4255 if (!empty($involvedAttendees))
4256 {
4257 CCalendar::UpdateCounter($involvedAttendees, [$id]);
4258 }
4259 }
4260
4261 if (!$entry['IS_MEETING'] && $entry['CAL_TYPE'] === 'user')
4262 {
4263 self::onEventDelete($entry, $params);
4264 }
4265
4266 if (
4267 $params
4268 && is_array($params)
4269 && \Bitrix\Calendar\Sync\Util\RequestLogger::isEnabled()
4270 )
4271 {
4272 $loggerData = $params;
4273 unset($loggerData['Event']);
4274 $loggerData['loggerUuid'] = $id;
4275 (new \Bitrix\Calendar\Sync\Util\RequestLogger($userId, 'portal_delete'))->write($loggerData);
4276 }
4277
4278 if (!empty($params['bMarkDeleted']))
4279 {
4280 Internals\EventTable::update($id, ['DELETED' => 'Y']);
4281 }
4282 else
4283 {
4284 // Real deleting
4285 Internals\EventTable::delete($id);
4286 }
4287
4288 if (!empty($arAffectedSections))
4289 {
4290 CCalendarSect::UpdateModificationLabel($arAffectedSections);
4291 }
4292
4293 foreach(EventManager::getInstance()->findEventHandlers("calendar", "OnAfterCalendarEventDelete") as $event)
4294 {
4295 ExecuteModuleEventEx($event, [$id, $entry, $userId]);
4296 }
4297
4298 CCalendar::ClearCache('event_list');
4299
4300 if (($entry['ACCESSIBILITY'] ?? '') === 'absent')
4301 {
4302 (new \Bitrix\Calendar\Integration\Intranet\Absence())->cleanCache();
4303 }
4304
4305 $pullUserId = (int)$entry['OWNER_ID'] > 0 ? (int)$entry['OWNER_ID'] : $userId;
4306 if (
4307 $pullUserId
4308 && self::$sendPush
4309 )
4310 {
4311 Util::addPullEvent(
4312 PushCommand::DeleteEvent,
4313 $pullUserId,
4314 [
4315 'fields' => $entry,
4316 'requestUid' => $params['requestUid'] ?? null,
4317 ]
4318 );
4319 }
4320
4321 (new \Bitrix\Calendar\Event\Event\AfterCalendarEventDeleted($id))->emit();
4322
4323 return true;
4324 }
4325 }
4326
4327 return false;
4328 }
4329
4330 public static function SetMeetingStatusEx($params)
4331 {
4332 $doSendMail = $params['doSendMail'] ?? true;
4333 $reccurentMode = isset($params['reccurentMode'])
4334 && in_array($params['reccurentMode'], ['this', 'next', 'all'])
4335 ? $params['reccurentMode']
4336 : false;
4337
4338 $currentDateFrom = CCalendar::Date(CCalendar::Timestamp($params['currentDateFrom']), false);
4339 if ($reccurentMode && $currentDateFrom)
4340 {
4341 $event = self::GetById($params['parentId'], false);
4342 $recurrenceId = $event['RECURRENCE_ID'] ?? $event['ID'];
4343
4344 if ($reccurentMode !== 'all')
4345 {
4346 $res = CCalendar::SaveEventEx([
4347 'arFields' => [
4348 'ID' => $params['parentId'],
4349 ],
4350 'silentErrorMode' => false,
4351 'recursionEditMode' => $reccurentMode,
4352 'userId' => $event['MEETING_HOST'],
4353 'checkPermission' => false,
4354 'currentEventDateFrom' => $currentDateFrom,
4355 'sendEditNotification' => false,
4356 ]);
4357
4358 if (
4359 $res
4360 && isset($res['recEventId'])
4361 && $res['recEventId']
4362 )
4363 {
4364 self::SetMeetingStatus([
4365 'userId' => $params['attendeeId'],
4366 'eventId' => $res['recEventId'],
4367 'status' => $params['status'],
4368 'personalNotification' => true,
4369 'doSendMail' => $doSendMail,
4370 ]);
4371 }
4372 }
4373
4374 if ($reccurentMode === 'all' || $reccurentMode === 'next')
4375 {
4376 $recRelatedEvents = self::GetEventsByRecId($recurrenceId, false);
4377
4378 if ($reccurentMode === 'next')
4379 {
4380 $untilTimestamp = CCalendar::Timestamp($currentDateFrom);
4381 }
4382 else
4383 {
4384 $untilTimestamp = false;
4385 self::SetMeetingStatus([
4386 'userId' => $params['attendeeId'],
4387 'eventId' => $params['eventId'],
4388 'status' => $params['status'],
4389 'personalNotification' => true,
4390 'doSendMail' => $doSendMail,
4391 ]);
4392 }
4393
4394 foreach($recRelatedEvents as $ev)
4395 {
4396 if ((int)$ev['ID'] === (int)($params['eventId'] ?? 0))
4397 {
4398 continue;
4399 }
4400
4401 if ($reccurentMode === 'all'
4402 || (
4403 $untilTimestamp
4404 && CCalendar::Timestamp($ev['DATE_FROM']) > $untilTimestamp
4405 )
4406 )
4407 {
4408 self::SetMeetingStatus([
4409 'userId' => $params['attendeeId'],
4410 'eventId' => $ev['ID'],
4411 'status' => $params['status'],
4412 'doSendMail' => $doSendMail,
4413 ]);
4414 }
4415 }
4416 }
4417 }
4418 else
4419 {
4420 self::SetMeetingStatus([
4421 'userId' => $params['attendeeId'] ?? null,
4422 'eventId' => $params['eventId'] ?? null,
4423 'status' => $params['status'] ?? null,
4424 'doSendMail' => $doSendMail,
4425 ]);
4426 }
4427 }
4428
4429 public static function SetMeetingStatus($params)
4430 {
4431 $eventId = $params['eventId'] = (int)($params['eventId'] ?? 0);
4432 if (!$eventId)
4433 {
4434 return;
4435 }
4436
4437 CTimeZone::Disable();
4438 global $CACHE_MANAGER;
4439 $userId = $params['userId'] = (int)$params['userId'];
4440 $status = mb_strtoupper($params['status']);
4441 $doSendMail = $params['doSendMail'] ?? true;
4442 $prevStatus = null;
4443 if (!in_array($status, ["Q", "Y", "N", "H", "M"], true))
4444 {
4445 $status = $params['status'] = "Q";
4446 }
4447
4448 $event = self::GetList([
4449 'arFilter' => [
4450 "ID" => $eventId,
4451 "IS_MEETING" => 1,
4452 "DELETED" => "N",
4453 ],
4454 'parseRecursion' => false,
4455 'fetchAttendees' => true,
4456 'fetchMeetings' => true,
4457 'checkPermissions' => false,
4458 'setDefaultLimit' => false,
4459 ]);
4460
4461 if (!empty($event))
4462 {
4463 $event = $event[0];
4464 $prevStatus = $event['MEETING_STATUS'];
4465 }
4466
4467 if ($event && $event['IS_MEETING'] && (int)$event['PARENT_ID'] > 0)
4468 {
4469 if (in_array($event['EVENT_TYPE'], Sharing\SharingEventManager::getSharingEventTypes(), true))
4470 {
4471 $userEventForSharing = self::GetList([
4472 'arFilter' => [
4473 'PARENT_ID' => $event['PARENT_ID'],
4474 'OWNER_ID' => $userId,
4475 'IS_MEETING' => 1,
4476 'DELETED' => 'N',
4477 ],
4478 'checkPermissions' => false,
4479 ]);
4480
4481 if (!empty($userEventForSharing))
4482 {
4483 $userEventForSharing = $userEventForSharing[0];
4484 }
4485 }
4486
4487 if (ICalUtil::isMailUser($event['MEETING_HOST']))
4488 {
4489 if (\Bitrix\Main\Config\Option::get('calendar', 'log_mail_send_meeting_status', 'N') === 'Y')
4490 {
4491 (new Internals\Log\Logger('DEBUG_CALENDAR_MAIL_SEND_MEETING_STATUS'))
4492 ->log(['eventId' => $event['ID'], 'userId' => $userId, 'status' => $status], 10)
4493 ;
4494 }
4495 if ($doSendMail && $prevStatus !== $status)
4496 {
4497 IncomingEventManager::rehandleRequest([
4498 'event' => $event,
4499 'userId' => $userId,
4500 'answer' => $status === 'Y',
4501 ]);
4502 }
4503 }
4504
4505 if ($event['CURRENT_ATTENDEE_ID'] ?? null)
4506 {
4507 self::updateEventAttendeeMeetingStatus((int)$event['CURRENT_ATTENDEE_ID'], $status);
4508 }
4509
4510 Internals\EventTable::updateByFilter(
4511 [
4512 '=PARENT_ID' => (int)$event['PARENT_ID'],
4513 '=OWNER_ID' => $userId,
4514 '=CAL_TYPE' => Dictionary::CALENDAR_TYPE['user'],
4515 ],
4516 ['MEETING_STATUS' => $status],
4517 );
4518
4519 if ($status === 'Y')
4520 {
4521 self::ShowEventSection((int)$event['PARENT_ID'], $userId);
4522 }
4523
4524 CCalendarSect::UpdateModificationLabel($event['SECT_ID']);
4525
4526 // Clear invitation in messenger
4527 CCalendarNotify::ClearNotifications($event['PARENT_ID'], $userId);
4528
4529 // Add new notification in messenger
4530 if (!empty($params['personalNotification']) && CCalendar::getCurUserId() === $userId)
4531 {
4532 $fromTo = self::GetEventFromToForUser($event, $userId);
4533 CCalendarNotify::Send([
4534 'mode' => $status === "Y" ? 'status_accept' : 'status_decline',
4535 'name' => $event['NAME'],
4536 "from" => $fromTo["DATE_FROM"],
4537 "guestId" => $userId,
4538 "eventId" => $event['PARENT_ID'],
4539 "userId" => $userId,
4540 "markRead" => true,
4541 "fields" => $event,
4542 ]);
4543 }
4544
4545 $shouldNotifyExtranetInCollab = $status === 'Y'
4546 && $event['EVENT_TYPE'] === Dictionary::EVENT_TYPE['collab']
4548 ;
4549
4550 if ($shouldNotifyExtranetInCollab)
4551 {
4552 $fromTo = self::GetEventFromToForUser($event, $userId);
4553 CCalendarNotify::Send([
4554 'mode' => 'ics_link',
4555 'name' => $event['NAME'],
4556 'from' => $fromTo['DATE_FROM'],
4557 'guestId' => $userId,
4558 'eventId' => $event['PARENT_ID'],
4559 'userId' => $userId,
4560 'markRead' => true,
4561 'fields' => $event,
4562 ]);
4563 }
4564
4565 if (
4566 $status === 'Y'
4567 && ($params['sharingAutoAccept'] ?? null) === true
4568 && in_array($event['EVENT_TYPE'], Sharing\SharingEventManager::getSharingEventTypes(), true)
4569 )
4570 {
4571 $fromTo = self::GetEventFromToForUser($event, $userId);
4572 CCalendarNotify::Send([
4573 'mode' => 'status_accept',
4574 'name' => $event['NAME'],
4575 "from" => $fromTo["DATE_FROM"],
4576 "guestId" => (int)($event['MEETING_HOST'] ?? null),
4577 "eventId" => $event['PARENT_ID'],
4578 "userId" => $userId,
4579 "fields" => $event,
4580 'isSharing' => true,
4581 ]);
4582 }
4583
4584 $addedPullUserList = [];
4585 if (isset($event['ATTENDEE_LIST']) && is_array($event['ATTENDEE_LIST']))
4586 {
4587 foreach ($event['ATTENDEE_LIST'] as $attendee)
4588 {
4589 $fields = [
4590 'MEETING_STATUS' => $status,
4591 'USER_ID' => $userId,
4592 'PARENT_ID' => $event['PARENT_ID'] ?? $eventId,
4593 'ATTENDEES' => $event['ATTENDEES'] ?? null,
4594 'CAL_TYPE' => $event['CAL_TYPE'] ?? null,
4595 ];
4596
4598 PushCommand::SetMeetingStatus,
4599 $attendee['id'],
4600 [
4601 'fields' => $fields,
4602 'requestUid' => $params['requestUid'] ?? null,
4603 ]
4604 );
4605 $addedPullUserList[] = (int)$attendee['id'];
4606 }
4607 }
4608
4609 $pullUserId = (int)($event['OWNER_ID'] ?? $userId);
4610 if ($pullUserId && !in_array($pullUserId, $addedPullUserList, true))
4611 {
4612 $fields = [
4613 'MEETING_STATUS' => $status,
4614 'USER_ID' => $userId,
4615 'PARENT_ID' => $event['PARENT_ID'] ?? $eventId,
4616 'ATTENDEES' => $event['ATTENDEES'] ?? null,
4617 'CAL_TYPE' => $event['CAL_TYPE'] ?? null,
4618 ];
4619
4621 PushCommand::SetMeetingStatus,
4622 $pullUserId,
4623 [
4624 'fields' => $fields,
4625 'requestUid' => $params['requestUid'] ?? null,
4626 ]
4627 );
4628 }
4629
4630 // Notify author of event
4631 if (
4632 $event['MEETING']['NOTIFY']
4633 && (int)$event['MEETING_HOST']
4634 && $userId !== (int)$event['MEETING_HOST']
4635 && ($params['hostNotification'] ?? null) !== false
4636 )
4637 {
4638 if (self::checkAttendeeBelongsToEvent($event['PARENT_ID'], $userId))
4639 {
4640 // Send message to the author
4641 $fromTo = self::GetEventFromToForUser($event, $event['MEETING_HOST']);
4642 CCalendarNotify::Send([
4643 'mode' => $status === "Y" ? 'accept' : 'decline',
4644 'name' => $event['NAME'],
4645 "from" => $fromTo["DATE_FROM"],
4646 "to" => $fromTo["DATE_TO"],
4647 "location" => CCalendar::GetTextLocation($event["LOCATION"] ?? null),
4648 "guestId" => $userId,
4649 "eventId" => $event['PARENT_ID'],
4650 "userId" => $event['MEETING']['MEETING_CREATOR'] ?? $event['MEETING_HOST'],
4651 "fields" => $event,
4652 ]);
4653 }
4654
4655 if (
4656 !empty($userEventForSharing)
4657 && in_array($event['EVENT_TYPE'], Sharing\SharingEventManager::getSharingEventTypes(), true)
4658 )
4659 {
4660 Sharing\SharingEventManager::onSharingEventMeetingStatusChange(
4661 $userId,
4662 $status,
4663 $userEventForSharing,
4664 $params['sharingAutoAccept'] ?? false
4665 );
4666 }
4667
4668 }
4669 CCalendarSect::UpdateModificationLabel([$event['SECTIONS'][0] ?? null]);
4670
4671 if ($status === "N")
4672 {
4673 $childEvent = self::GetList([
4674 'arFilter' => [
4675 "PARENT_ID" => $event['PARENT_ID'],
4676 "CREATED_BY" => $userId,
4677 "IS_MEETING" => 1,
4678 "DELETED" => "N",
4679 ],
4680 'parseRecursion' => false,
4681 'fetchAttendees' => true,
4682 'checkPermissions' => false,
4683 'setDefaultLimit' => false,
4684 ]);
4685
4686 if ($childEvent && $childEvent[0])
4687 {
4688 $childEvent = $childEvent[0];
4689 $bCalDav = CCalendar::IsCalDAVEnabled();
4690 $bExchange = CCalendar::IsExchangeEnabled($userId);
4691
4692 if ($bExchange || $bCalDav)
4693 {
4695 'bCalDav' => $bCalDav,
4696 'bExchangeEnabled' => $bExchange,
4697 'sectionId' => $childEvent['SECT_ID'],
4698 ], $childEvent);
4699 }
4700
4701 self::onEventDelete($childEvent);
4702 }
4703 }
4704
4705 if ($status === "Y")
4706 {
4707 if (($params['affectRecRelatedEvents'] ?? null) !== false)
4708 {
4709 $event = self::GetList([
4710 'arFilter' => [
4711 "ID" => $eventId,
4712 "IS_MEETING" => 1,
4713 "DELETED" => "N",
4714 ],
4715 'parseRecursion' => false,
4716 'fetchAttendees' => true,
4717 'fetchMeetings' => true,
4718 'checkPermissions' => false,
4719 'setDefaultLimit' => false,
4720 ]);
4721
4722 if (!empty($event))
4723 {
4724 $event = $event[0];
4725 }
4726
4727 if (!empty($event['RECURRENCE_ID']))
4728 {
4729 $masterEvent = self::GetList([
4730 'arFilter' => [
4731 'PARENT_ID' => $event['RECURRENCE_ID'],
4732 'DELETED' => 'N',
4733 'OWNER_ID' => $userId,
4734 ],
4735 'parseRecursion' => false,
4736 'fetchAttendees' => false,
4737 'checkPermissions' => false,
4738 'setDefaultLimit' => false,
4739 ]);
4740 if (!empty($masterEvent))
4741 {
4742 $masterEvent = $masterEvent[0];
4743 }
4744
4745 if (($masterEvent['MEETING_STATUS'] ?? null) !== 'Y')
4746 {
4747 self::SetMeetingStatus([
4748 'userId' => $userId,
4749 'eventId' => $masterEvent['ID'],
4750 'status' => $status,
4751 'personalNotification' => true,
4752 'hostNotification' => true,
4753 'affectRecRelatedEvents' => false,
4754 'updateDescription' => $params['updateDescription'] ?? null,
4755 ]);
4756
4757 self::SetMeetingStatusForRecurrenceEvents(
4758 $event['RECURRENCE_ID'],
4759 $userId,
4760 $params['eventId'],
4761 $status,
4762 $params['updateDescription'] ?? null,
4763 );
4764 }
4765 }
4766
4767 if (!empty($event['RRULE']) && in_array($prevStatus, ['N', 'Q', 'H']))
4768 {
4769 self::SetMeetingStatusForRecurrenceEvents(
4770 $event['PARENT_ID'],
4771 $userId,
4772 $params['eventId'],
4773 $status,
4774 $params['updateDescription'] ?? null,
4775 );
4776 }
4777 }
4778
4779 if ($prevStatus === 'N')
4780 {
4781 CCalendar::syncChange(
4782 $eventId,
4783 [
4784 "MEETING_STATUS" => $status,
4785 ],
4786 [
4787 'userId' => $userId,
4788 'originalFrom' => null,
4789 ],
4790 null //$event
4791 );
4792 }
4793 }
4794
4795 if (($params['updateDescription'] ?? null) !== false)
4796 {
4797 if (!empty($event['RECURRENCE_ID']))
4798 {
4799 self::pushUpdateDescriptionToQueue($event['RECURRENCE_ID'], $userId, $status);
4800 }
4801 if (!empty($event['PARENT_ID']) && (int)$event['PARENT_ID'] !== (int)$event['RECURRENCE_ID'])
4802 {
4803 self::pushUpdateDescriptionToQueue($event['PARENT_ID'], $userId, $status);
4804 }
4805 }
4806
4808 'userId' => $userId,
4809 'eventId' => $eventId,
4810 'status' => $status,
4811 'event' => $event,
4812 ]);
4813
4814 CCalendar::UpdateCounter([$userId], [$eventId]);
4815
4816 $CACHE_MANAGER->ClearByTag('calendar_user_' . $userId);
4817
4818 $createdBy = (int)$event['CREATED_BY'];
4819 if ($createdBy !== $userId)
4820 {
4821 $CACHE_MANAGER->ClearByTag('calendar_user_' . $createdBy);
4822 }
4823
4824 if (($event['ACCESSIBILITY'] ?? '') === 'absent')
4825 {
4826 (new \Bitrix\Calendar\Integration\Intranet\Absence())->cleanCache();
4827 }
4828 }
4829 else
4830 {
4831 CCalendarNotify::ClearNotifications($eventId);
4832 }
4833
4834 CTimeZone::Enable();
4835 CCalendar::ClearCache(['attendees_list', 'event_list']);
4836
4837 (new \Bitrix\Calendar\Event\Event\AfterMeetingStatusChanged($eventId, $userId, $status))->emit();
4838 }
4839
4840 private static function updateEventAttendeeMeetingStatus(int $eventAttendeeId, string $meetingStatus): void
4841 {
4842 $updateResult = Internals\EventAttendeeTable::update(
4843 $eventAttendeeId,
4844 [
4845 'MEETING_STATUS' => $meetingStatus,
4846 ]
4847 );
4848 if ($updateResult->isSuccess())
4849 {
4850 }
4851 }
4852
4853 private static function updateEventAttendeeColor(int $eventAttendeeId, string $color): void
4854 {
4855 $updateResult = Internals\EventAttendeeTable::update(
4856 $eventAttendeeId,
4857 [
4858 'COLOR' => $color,
4859 ]
4860 );
4861 if ($updateResult->isSuccess())
4862 {
4863 }
4864 }
4865
4866 private static function getEventAttendeeIdByEventIdAndUserId(int $eventId, int $userId): ?int
4867 {
4868 $eventAttendee = Internals\EventAttendeeTable::getRow([
4869 'select' => ['ID'],
4870 'filter' => [
4871 'EVENT_ID' => $eventId,
4872 'OWNER_ID' => $userId,
4873 ],
4874 ]);
4875
4876 return $eventAttendee ? (int)$eventAttendee['ID'] : null;
4877 }
4878
4879 private static function safeDeleteEventAttendees(int $eventId, array $attendeeUserIds): void
4880 {
4881 $eventAttendeesResult = Internals\EventAttendeeTable::getList([
4882 'select' => ['ID'],
4883 'filter' => [
4884 'EVENT_ID' => $eventId,
4885 'OWNER_ID' => $attendeeUserIds,
4886 ],
4887 ]);
4888
4889 $eventAttendees = $eventAttendeesResult->fetchAll();
4890 if (empty($eventAttendees))
4891 {
4892 return;
4893 }
4894
4895 $eventAttendeeIds = array_column($eventAttendees, 'ID');
4896
4897 $updateResult = Internals\EventAttendeeTable::updateMulti($eventAttendeeIds, [
4898 'DELETED' => 'Y',
4899 ]);
4900
4901 if ($updateResult->isSuccess())
4902 {
4903 }
4904 }
4905
4906 private static function updateEventAttendee(
4907 int $parentEventId,
4908 int $userId,
4909 array $changedFields,
4910 array $dbFields
4911 ): void
4912 {
4913 if (in_array('COLOR', $changedFields, true) && isset($dbFields['COLOR']))
4914 {
4915 // color changed in all child events
4916 $allChildEventAttendees = Internals\EventAttendeeTable::getList([
4917 'select' => ['ID'],
4918 'filter' => [
4919 'EVENT_ID' => $parentEventId,
4920 ],
4921 ]);
4922 $eventAttendeeIds = array_column($allChildEventAttendees->fetchAll(), 'ID');
4923 // for event not stored in b_calendar_event_attendees
4924 if (!$eventAttendeeIds)
4925 {
4926 return;
4927 }
4928 $colorUpdateResult = Internals\EventAttendeeTable::updateMulti(
4929 $eventAttendeeIds,
4930 [
4931 'COLOR' => $dbFields['COLOR'],
4932 ],
4933 );
4934 if ($colorUpdateResult->isSuccess())
4935 {
4936 }
4937 }
4938
4939 // remind changed only for given user
4940 if (in_array('REMIND', $changedFields, true) && isset($dbFields['REMIND']))
4941 {
4942 $eventAttendeeId = self::getEventAttendeeIdByEventIdAndUserId($parentEventId, $userId);
4943 // for event not stored in b_calendar_event_attendees
4944 if (!$eventAttendeeId)
4945 {
4946 return;
4947 }
4948
4949 $remindUpdateResult = Internals\EventAttendeeTable::update(
4950 $eventAttendeeId,
4951 [
4952 'REMIND' => $dbFields['REMIND'],
4953 ],
4954 );
4955 if ($remindUpdateResult->isSuccess())
4956 {
4957 }
4958 }
4959 }
4960
4961 protected static function ShowEventSection(int $parentId, int $userId): void
4962 {
4963 $eventEO = Internals\EventTable::query()
4964 ->setSelect(['*'])
4965 ->where('PARENT_ID', $parentId)
4966 ->where('OWNER_ID', $userId)
4967 ->exec()->fetchObject();
4968
4969 if (is_null($eventEO))
4970 {
4971 return;
4972 }
4973
4975 $acceptedEvent = (new Mappers\Event())->getByEntityObject($eventEO);
4976
4977 if (is_null($acceptedEvent))
4978 {
4979 return;
4980 }
4981
4982 $acceptedSectionId = $acceptedEvent->getSection()->getId();
4983 $hiddenSectionIds = UserSettings::getHiddenSections($userId, [ 'isPersonalCalendarContext' => true ]);
4984 $newHiddenSectionIds = array_filter($hiddenSectionIds, static function($hiddenSectionId) use ($acceptedSectionId) {
4985 return !is_numeric($hiddenSectionId) || (int)$hiddenSectionId !== $acceptedSectionId;
4986 });
4987
4988 if (count($hiddenSectionIds) === count($newHiddenSectionIds))
4989 {
4990 return;
4991 }
4992
4993 UserSettings::saveHiddenSections($userId, $newHiddenSectionIds);
4994 Util::addPullEvent(
4995 PushCommand::HiddenSectionsUpdated,
4996 $userId,
4997 [
4998 'hiddenSections' => $newHiddenSectionIds,
4999 ]
5000 );
5001 }
5002
5004 int $recurrenceId,
5005 int $userId,
5006 int $eventId,
5007 string $status,
5008 ?bool $updateDescription = null,
5009 ): void
5010 {
5011 $recRelatedEvents = self::GetEventsByRecId($recurrenceId, false, $userId);
5012 foreach ($recRelatedEvents as $ev)
5013 {
5014 if ((int)$ev['ID'] === $eventId)
5015 {
5016 continue;
5017 }
5018
5019 self::SetMeetingStatus([
5020 'userId' => $userId,
5021 'eventId' => $ev['ID'],
5022 'status' => $status,
5023 'personalNotification' => false,
5024 'hostNotification' => false,
5025 'affectRecRelatedEvents' => false,
5026 'updateDescription' => $updateDescription,
5027 ]);
5028 }
5029 }
5030
5031 /*
5032 * $params['dateFrom']
5033 * $params['dateTo']
5034 *
5035 * */
5036
5037 public static function GetMeetingStatus($userId, $eventId)
5038 {
5039 $eventId = (int)$eventId;
5040 $userId = (int)$userId;
5041 $status = false;
5042 $event = self::GetById($eventId, false);
5043 if ($event && $event['IS_MEETING'] && (int)$event['PARENT_ID'] > 0)
5044 {
5045 if ((int)$event['OWNER_ID'] !== $userId)
5046 {
5047 $event = Internals\EventTable::query()
5048 ->setSelect(['MEETING_STATUS'])
5049 ->where('PARENT_ID', (int)$event['PARENT_ID'])
5050 ->where('OWNER_ID', $userId)
5051 ->setLimit(1)
5052 ->exec()->fetch()
5053 ;
5054 }
5055
5056 $status = $event['MEETING_STATUS'];
5057 }
5058 return $status;
5059 }
5060
5075 public static function GetAccessibilityForUsers($params = []): array
5076 {
5077 $curEventId = (int)$params['curEventId'];
5078 $curUserId = isset($params['userId']) ? (int)$params['userId'] : CCalendar::GetCurUserId();
5079 if (!is_array($params['users']) || !count($params['users']))
5080 {
5081 return [];
5082 }
5083
5084 if (!isset($params['checkPermissions']))
5085 {
5086 $params['checkPermissions'] = true;
5087 }
5088
5089 $users = [];
5090 $accessibility = [];
5091 foreach($params['users'] as $userId)
5092 {
5093 $userId = (int)$userId;
5094 if ($userId)
5095 {
5096 $users[] = $userId;
5097 $accessibility[$userId] = [];
5098 }
5099 }
5100
5101 if (empty($users))
5102 {
5103 return [];
5104 }
5105
5106 $events = self::GetList([
5107 'arFilter' => [
5108 "FROM_LIMIT" => $params['from'],
5109 "TO_LIMIT" => $params['to'],
5110 "CAL_TYPE" => 'user',
5111 "OWNER_ID" => $users,
5112 "ACTIVE_SECTION" => "Y",
5113 ],
5114 'arSelect' => self::$defaultSelectEvent,
5115 'parseRecursion' => true,
5116 'fetchAttendees' => false,
5117 'fetchSection' => true,
5118 'parseDescription' => false,
5119 'setDefaultLimit' => false,
5120 'checkPermissions' => $params['checkPermissions'],
5121 ]);
5122
5123 foreach ($events as $event)
5124 {
5125 if ($curEventId && ((int)$event["ID"] === $curEventId || (int)$event["PARENT_ID"] === $curEventId))
5126 {
5127 continue;
5128 }
5129 if ($event["ACCESSIBILITY"] === 'free')
5130 {
5131 continue;
5132 }
5133 if ($event["IS_MEETING"] && $event["MEETING_STATUS"] === "N")
5134 {
5135 continue;
5136 }
5137 if (CCalendarSect::CheckGoogleVirtualSection($event['SECTION_DAV_XML_ID']))
5138 {
5139 continue;
5140 }
5141
5142 $name = $event["NAME"];
5143 $accessController = new EventAccessController($curUserId);
5144 $eventModel = EventModel::createFromArray($event);
5145
5146 if (
5147 ($event['PRIVATE_EVENT'] && $event['CAL_TYPE'] === 'user' && $event['OWNER_ID'] !== $curUserId)
5148 || !$accessController->check(ActionDictionary::ACTION_EVENT_VIEW_TITLE, $eventModel)
5149 )
5150 {
5151 $name = '[' . Loc::getMessage('EC_ACCESSIBILITY_' . mb_strtoupper($event['ACCESSIBILITY'])) . ']';
5152 }
5153
5154 $accessibility[$event['OWNER_ID']][] = [
5155 "ID" => $event["ID"],
5156 "NAME" => $name,
5157 "DATE_FROM" => $event["DATE_FROM"],
5158 "DATE_TO" => $event["DATE_TO"],
5159 "DATE_FROM_TS_UTC" => $event["DATE_FROM_TS_UTC"],
5160 "DATE_TO_TS_UTC" => $event["DATE_TO_TS_UTC"],
5161 "~USER_OFFSET_FROM" => $event["~USER_OFFSET_FROM"],
5162 "~USER_OFFSET_TO" => $event["~USER_OFFSET_TO"],
5163 "DT_SKIP_TIME" => $event["DT_SKIP_TIME"],
5164 "TZ_FROM" => $event["TZ_FROM"],
5165 "TZ_TO" => $event["TZ_TO"],
5166 "ACCESSIBILITY" => $event["ACCESSIBILITY"],
5167 "IMPORTANCE" => $event["IMPORTANCE"],
5168 "EVENT_TYPE" => $event["EVENT_TYPE"],
5169 ];
5170 }
5171
5172 // foreach ($users as $userId)
5173 // {
5174 // $userSettings = UserSettings::get($userId);
5175 // $enableLunchTime = $userSettings['enableLunchTime'] === 'Y';
5176 //
5177 // if (!$enableLunchTime)
5178 // {
5179 // continue;
5180 // }
5181 //
5182 // $lunchStart = CCalendar::Timestamp("{$params['from']} {$userSettings['lunchStart']}");
5183 // $lunchEnd = CCalendar::Timestamp("{$params['from']} {$userSettings['lunchEnd']}");
5184 // $lunchLength = $lunchEnd - $lunchStart;
5185 //
5186 // $from = $lunchStart;
5187 // $to = CCalendar::Timestamp("{$params['to']} {$userSettings['lunchStart']}");
5188 // while ($from <= $to)
5189 // {
5190 // $dateFrom = (new \DateTime())->setTimestamp($from);
5191 // $dateTo = (new \DateTime())->setTimestamp($from + $lunchLength);
5192 // $lunchStart = DateTime::createFromPhp($dateFrom);
5193 // $lunchEnd = DateTime::createFromPhp($dateTo);
5194 // $eventTsFromUTC = Sharing\Helper::getEventTimestampUTC($lunchStart);
5195 // $eventTsToUTC = Sharing\Helper::getEventTimestampUTC($lunchEnd);
5196 // $accessibility[$userId][] = array(
5197 // 'ID' => -1,
5198 // 'NAME' => 'Lunch',
5199 // 'DATE_FROM' => $lunchStart,
5200 // 'DATE_TO' => $lunchEnd,
5201 // 'TZ_FROM' => $dateFrom->getTimezone()->getName(),
5202 // 'TZ_TO' => $dateTo->getTimezone()->getName(),
5203 // 'DATE_FROM_TS_UTC' => $eventTsFromUTC,
5204 // 'DATE_TO_TS_UTC' => $eventTsToUTC,
5205 // '~USER_OFFSET_FROM' => 0,
5206 // '~USER_OFFSET_TO' => 0,
5207 // 'ACCESSIBILITY' => 'busy',
5208 // 'IMPORTANCE' => 'normal',
5209 // 'DT_SKIP_TIME' => false,
5210 // );
5211 //
5212 // $from += \CCalendar::DAY_LENGTH;
5213 // }
5214 // }
5215
5216 return $accessibility;
5217 }
5218
5219 public static function GetAbsent($users = null, $params = []): array
5220 {
5221 // Can be called from agent... So we have to create $USER if it is not exists
5222 $tempUser = CCalendar::TempUser(false, true);
5223 $checkPermissions = $params['checkPermissions'] !== false;
5224 $curUserId = isset($params['userId']) ? (int)$params['userId'] : CCalendar::GetCurUserId();
5225 $arUsers = [];
5226
5227 if (is_array($users) && !empty($users))
5228 {
5229 foreach($users as $id)
5230 {
5231 if ($id > 0)
5232 {
5233 $arUsers[] = (int)$id;
5234 }
5235 }
5236 if (empty($arUsers))
5237 {
5238 $users = false;
5239 }
5240 }
5241
5242 $arFilter = [
5243 'DELETED' => 'N',
5244 'ACCESSIBILITY' => 'absent',
5245 'CAL_TYPE' => Dictionary::CALENDAR_TYPE['user'],
5246 ];
5247
5248 if ($users)
5249 {
5250 $arFilter['CREATED_BY'] = $users;
5251 }
5252
5253 if (isset($params['fromLimit']))
5254 {
5255 $arFilter['FROM_LIMIT'] = CCalendar::Date(CCalendar::Timestamp($params['fromLimit'], false), true, false);
5256 }
5257
5258 if (isset($params['toLimit']))
5259 {
5260 $arFilter['TO_LIMIT'] = CCalendar::Date(CCalendar::Timestamp($params['toLimit'], false), true, false);
5261 }
5262
5263 $eventList = self::GetList([
5264 'arFilter' => $arFilter,
5265 'arSelect' => self::$defaultSelectEvent,
5266 'parseRecursion' => true,
5267 'getUserfields' => false,
5268 'fetchAttendees' => false,
5269 'userId' => $curUserId,
5270 'preciseLimits' => true,
5271 'checkPermissions' => false,
5272 'parseDescription' => false,
5273 'skipDeclined' => true,
5274 ]);
5275
5276 $result = [];
5277 $settings = false;
5278
5279 foreach($eventList as $event)
5280 {
5281 $userId = (int)$event['CREATED_BY'];
5282 if (!empty($users) && !in_array($userId, $arUsers, true))
5283 {
5284 continue;
5285 }
5286
5287 if ($event['IS_MEETING'] && $event["MEETING_STATUS"] === 'N')
5288 {
5289 continue;
5290 }
5291
5292 if (
5293 $checkPermissions
5294 && ($event['CAL_TYPE'] !== 'user' || $curUserId !== (int)$event['OWNER_ID'])
5295 && $curUserId !== (int)$event['CREATED_BY']
5296 )
5297 {
5298 $sectId = (int)$event['SECTION_ID'];
5299 if (empty($event['ACCESSIBILITY']))
5300 {
5301 $event['ACCESSIBILITY'] = 'busy';
5302 }
5303
5304 if ($settings === false)
5305 {
5306 $settings = CCalendar::GetSettings(array('request' => false));
5307 }
5308 $private = $event['PRIVATE_EVENT'] && $event['CAL_TYPE'] === 'user';
5309
5311 $eventModel = EventModel::createFromArray($event);
5312 $eventModel->setSectionId((int)$sectId);
5313
5314 if ($private || !$accessController->check(ActionDictionary::ACTION_EVENT_VIEW_FULL, $eventModel))
5315 {
5316 $event = self::ApplyAccessRestrictions($event, $userId);
5317 }
5318 }
5319
5320 $skipTime = $event['DT_SKIP_TIME'] === 'Y';
5321 $fromTs = CCalendar::Timestamp($event['DATE_FROM'], false, !$skipTime);
5322 $toTs = CCalendar::Timestamp($event['DATE_TO'], false, !$skipTime);
5323 if ($event['DT_SKIP_TIME'] !== 'Y')
5324 {
5325 $fromTs -= $event['~USER_OFFSET_FROM'];
5326 $toTs -= $event['~USER_OFFSET_TO'];
5327 }
5328
5329 $result[] = [
5330 'ID' => $event['ID'],
5331 'NAME' => $event['NAME'],
5332 'DATE_FROM' => CCalendar::Date($fromTs, !$skipTime, false),
5333 'DATE_TO' => CCalendar::Date($toTs, !$skipTime, false),
5334 'DT_FROM_TS' => $fromTs,
5335 'DT_TO_TS' => $toTs,
5336 'CREATED_BY' => $userId,
5337 'DETAIL_TEXT' => '',
5338 'USER_ID' => $userId,
5339 ];
5340 }
5341
5342 // Sort by DATE_FROM_TS_UTC
5343 usort($result, static function($a, $b){
5344 if ($a['DT_FROM_TS'] === $b['DT_FROM_TS'])
5345 {
5346 return 0;
5347 }
5348 return $a['DT_FROM_TS'] < $b['DT_FROM_TS'] ? -1 : 1;
5349 });
5350
5351 CCalendar::TempUser($tempUser, false);
5352
5353 return $result;
5354 }
5355
5356 public static function DeleteEmpty(int $sectionId)
5357 {
5358 if (!$sectionId)
5359 {
5360 return;
5361 }
5362
5363 $query = Internals\EventTable::query()
5364 ->setSelect(['ID', 'LOCATION', 'SECTION_ID'])
5365 ->where('SECTION_ID', $sectionId)
5366 ->exec()
5367 ;
5368
5369 while ($event = $query->fetch())
5370 {
5371 $loc = $event['LOCATION'] ?? null;
5372 if ($loc && mb_strlen($loc) > 5 && mb_strpos($loc, 'ECMR_') === 0)
5373 {
5374 $loc = Rooms\Util::parseLocation($loc);
5375 if ($loc['mrid'] !== false && $loc['mrevid'] !== false) // Release MR
5376 {
5377 Rooms\Util::releaseLocation($loc);
5378 }
5379 }
5380 else if ($loc && mb_strlen($loc) > 9 && mb_strpos($loc, 'calendar_') === 0)
5381 {
5382 $loc = Rooms\Util::parseLocation($loc);
5383 if ($loc['room_id'] !== false && $loc['room_event_id'] !== false) // Release calendar_room
5384 {
5385 Rooms\Util::releaseLocation($loc);
5386 }
5387 }
5388 $itemIds[] = (int)$event['ID'];
5389 }
5390
5391 // Clean from 'b_calendar_event'
5392 if (!empty($itemIds))
5393 {
5394 Internals\EventTable::deleteByFilter([
5395 'ID' => $itemIds,
5396 ]);
5397 }
5398
5399 CCalendar::ClearCache([
5400 'section_list',
5401 'event_list',
5402 ]);
5403 }
5404
5405 public static function CleanEventsWithDeadParents()
5406 {
5407 global $DB;
5408 $strSql = "SELECT PARENT_ID from b_calendar_event where PARENT_ID in (SELECT ID from b_calendar_event where MEETING_STATUS='H' and DELETED='Y') AND DELETED='N'";
5409 $res = $DB->Query($strSql);
5410
5411 $strItems = "0";
5412 while($result = $res->Fetch())
5413 {
5414 $strItems .= ",". (int)$result['ID'];
5415 }
5416
5417 if ($strItems != "0")
5418 {
5419 $strSql =
5420 "UPDATE b_calendar_event SET ".
5421 $DB->PrepareUpdate("b_calendar_event", array("DELETED" => "Y")).
5422 " WHERE PARENT_ID in (".$strItems.")";
5423 $DB->Query($strSql);
5424 }
5425 CCalendar::ClearCache(['section_list', 'event_list']);
5426 }
5427
5428 public static function CanView($eventId, $userId)
5429 {
5430 if ((int)$eventId > 0)
5431 {
5432 Loader::includeModule("calendar");
5433 $event = self::GetList(
5434 array(
5435 'arFilter' => array(
5436 "ID" => $eventId,
5437 ),
5438 'parseRecursion' => false,
5439 'fetchAttendees' => true,
5440 'checkPermissions' => true,
5441 'userId' => $userId,
5442 )
5443 );
5444
5445 if (!$event || !is_array($event[0]))
5446 {
5447 $event = self::GetList(
5448 array(
5449 'arFilter' => array(
5450 "PARENT_ID" => $eventId,
5451 "CREATED_BY" => $userId,
5452 ),
5453 'parseRecursion' => false,
5454 'fetchAttendees' => true,
5455 'checkPermissions' => true,
5456 'userId' => $userId,
5457 )
5458 );
5459 }
5460
5461 // Event is not partly accessible - so it was not cleaned before by ApplyAccessRestrictions
5462 if (
5463 $event
5464 && is_array($event[0])
5465 && ($event[0]['IS_ACCESSIBLE_TO_USER'] ?? null) !== false
5466 && (
5467 isset($event[0]['DESCRIPTION'])
5468 || isset($event[0]['IS_MEETING'])
5469 || isset($event[0]['LOCATION'])
5470 )
5471 )
5472 {
5473 return true;
5474 }
5475
5476 }
5477
5478 return false;
5479 }
5480
5481 public static function GetEventUserFields($event)
5482 {
5483 global $USER_FIELD_MANAGER;
5484
5485 $eventId = !empty($event['PARENT_ID']) ? (int)$event['PARENT_ID'] : (int)($event['ID'] ?? null);
5486
5487 if (isset(self::$eventUserFields[$eventId]))
5488 {
5489 return self::$eventUserFields[$eventId];
5490 }
5491
5492 self::$eventUserFields[$eventId] = $USER_FIELD_MANAGER->GetUserFields('CALENDAR_EVENT', $eventId, LANGUAGE_ID);
5493
5494 return self::$eventUserFields[$eventId];
5495 }
5496
5497 public static function SetExDate($exDate = [], $untilTimestamp = false)
5498 {
5499 if ($untilTimestamp && !empty($exDate) && is_array($exDate))
5500 {
5501 $exDateRes = [];
5502
5503 foreach($exDate as $date)
5504 {
5505 if (CCalendar::Timestamp($date) <= $untilTimestamp)
5506 {
5507 $exDateRes[] = $date;
5508 }
5509 }
5510
5511 $exDate = $exDateRes;
5512 }
5513
5514 $exDate = array_unique($exDate);
5515
5516 return implode(';', $exDate);
5517 }
5518
5519 public static function GetEventsByRecId($recurrenceId, $checkPermissions = true, $userId = null)
5520 {
5521 if ($recurrenceId > 0)
5522 {
5523 $filter = [
5524 'RECURRENCE_ID' => $recurrenceId,
5525 'DELETED' => 'N',
5526 ];
5527 if ($userId)
5528 {
5529 $filter['OWNER_ID'] = $userId;
5530 }
5531
5532 return self::GetList([
5533 'arFilter' => $filter,
5534 'parseRecursion' => false,
5535 'fetchAttendees' => false,
5536 'checkPermissions' => $checkPermissions,
5537 'setDefaultLimit' => false,
5538 ]);
5539 }
5540 return [];
5541 }
5542
5553 {
5554 if (self::IsBrokenEventOfSeries($event))
5555 {
5556 $commentXmlId = $event['RELATIONS']['COMMENT_XML_ID'];
5557 $doesntHaveCommentsOrWereCommentsMoved = self::MoveCommentsToFirstRecurrence($event, $commentXmlId);
5558 if ($doesntHaveCommentsOrWereCommentsMoved)
5559 {
5560 self::CleanUpBrokenRecursiveExclusion($commentXmlId);
5561 }
5562
5563 $xmlId = $event['RECURRENCE_ID'] ?? null;
5564 preg_match('/EVENT_\d+_(.*)/', $commentXmlId, $matchesDate);
5565 $xmlDate = $matchesDate[1] ?? null;
5566
5567 $doesntHaveCommentsOrWereCommentsMovedAnother = false;
5568 if (!is_null($xmlId) && !is_null($xmlDate))
5569 {
5570 $anotherCommentXmlId = "EVENT_{$xmlId}_{$xmlDate}";
5571 $doesntHaveCommentsOrWereCommentsMovedAnother = self::MoveCommentsToFirstRecurrence($event, $anotherCommentXmlId);
5572 if ($doesntHaveCommentsOrWereCommentsMovedAnother)
5573 {
5574 self::CleanUpBrokenRecursiveExclusion($anotherCommentXmlId);
5575 }
5576 }
5577
5578 if ($doesntHaveCommentsOrWereCommentsMoved && $doesntHaveCommentsOrWereCommentsMovedAnother)
5579 {
5580 self::CleanUpBrokenRecursiveEvent($event);
5581 }
5582
5583 unset($event['ORIGINAL_DATE_FROM'], $event['RELATIONS']);
5584
5585 \CCalendar::ClearCache('event_list');
5586 }
5587
5588
5589 return $event;
5590 }
5591
5592 public static function IsBrokenEventOfSeries(array $event): bool
5593 {
5594 return !empty($event['RRULE']) && !empty($event['RELATIONS']['COMMENT_XML_ID']);
5595 }
5596
5597 public static function CleanUpBrokenRecursiveEvent(array $event): void
5598 {
5599 $rows = Internals\EventTable::query()
5600 ->setSelect(['ID'])
5601 ->whereNot('RRULE', '')
5602 ->where('PARENT_ID', $event['PARENT_ID'])
5603 ;
5604
5605 $events = $rows->fetchAll();
5606 if (empty($events))
5607 {
5608 return;
5609 }
5610
5611 $eventIds = array_map('intval', array_column($events, 'ID'));
5612 Internals\EventTable::updateMulti($eventIds, [
5613 'ORIGINAL_DATE_FROM' => null,
5614 'RELATIONS' => null,
5615 ]);
5616 }
5617
5618 public static function CleanUpBrokenRecursiveExclusion(string $commentXmlId): void
5619 {
5620 $rows = Internals\EventTable::query()
5621 ->setSelect(['ID', 'PARENT_ID', 'RECURRENCE_ID', 'ORIGINAL_DATE_FROM'])
5622 ->where('RRULE', '')
5623 ->where('RELATIONS', serialize([
5624 'COMMENT_XML_ID' => $commentXmlId,
5625 ]))
5626 ;
5627
5628 $events = $rows->fetchAll();
5629 foreach ($events as $brokenEvent)
5630 {
5631 Internals\EventTable::update($brokenEvent['ID'], [
5632 'RELATIONS' => serialize([
5633 'COMMENT_XML_ID' => self::GetEventCommentXmlId($brokenEvent),
5634 ]),
5635 ]);
5636 }
5637 }
5638
5639 public static function MoveCommentsToFirstRecurrence(array $event, string $currentEntityXmlId): bool
5640 {
5641 if (!Loader::includeModule('forum'))
5642 {
5643 return false;
5644 }
5645
5646 $forumId = \CCalendar::GetSettings()['forum_id'];
5647 $eventEntityId = $event['PARENT_ID'] ?: $event['ID'];
5648 $newEntityXmlId = "EVENT_$eventEntityId";
5649
5650 $tsFrom = $event['DATE_FROM_TS_UTC'];
5651 if (!empty($event['~DATE_FROM']))
5652 {
5653 $tsFrom = \CCalendar::TimestampUTC($event['~DATE_FROM']);
5654 }
5655
5656 $firstRecurrenceDate = Util::formatDateTimestampUTC($tsFrom);
5657 if (str_contains($event['EXDATE'], $firstRecurrenceDate))
5658 {
5659 $newEntityXmlId = "EVENT_{$event['RECURRENCE_ID']}_{$firstRecurrenceDate}";
5660 }
5661
5662 $currentFeed = new \Bitrix\Forum\Comments\Feed($forumId, [
5663 'type' => 'EV',
5664 'id' => $eventEntityId,
5665 'xml_id' => $currentEntityXmlId,
5666 ]);
5667
5668 return $currentFeed->moveEventCommentsToNewXmlId($newEntityXmlId);
5669 }
5670
5671 public static function GetEventCommentXmlId($event)
5672 {
5673 if (isset($event['RELATIONS']['ORIGINAL_RECURSION_ID']))
5674 {
5675 $date = CCalendar::Date(CCalendar::Timestamp($event['DATE_FROM']), false);
5676 return "EVENT_{$event['RELATIONS']['ORIGINAL_RECURSION_ID']}_$date";
5677 }
5678 if (isset($event['ORIGINAL_DATE_FROM'], $event['RECURRENCE_ID']))
5679 {
5680 $date = CCalendar::Date(CCalendar::Timestamp($event['ORIGINAL_DATE_FROM']), false);
5681 return "EVENT_{$event['RECURRENCE_ID']}_$date";
5682 }
5683
5684 $commentXmlId = "EVENT_" . ($event['PARENT_ID'] ?? $event['ID']);
5685
5686 if (
5687 self::CheckRecurcion($event)
5688 && (!isset($event['RINDEX']) || $event['RINDEX'] > 0)
5689 && (CCalendar::Date(CCalendar::Timestamp($event['DATE_FROM']), false)
5690 !== CCalendar::Date(CCalendar::Timestamp($event['~DATE_FROM'] ?? null), false))
5691 )
5692 {
5693 $commentXmlId .= '_'.CCalendar::Date(CCalendar::Timestamp($event['DATE_FROM']), false);
5694 }
5695
5696 return $commentXmlId;
5697 }
5698
5699 public static function ExtractDateFromCommentXmlId($xmlId = '')
5700 {
5701 $date = false;
5702 if ($xmlId)
5703 {
5704 $xmlAr = explode('_', $xmlId);
5705 if (is_array($xmlAr) && isset($xmlAr[2]) && $xmlAr[2])
5706 {
5707 $date = CCalendar::Date(CCalendar::Timestamp($xmlAr[2]), false);
5708 }
5709 }
5710 return $date;
5711 }
5712
5713 public static function GetRRULEDescription($event, $html = false, $showUntil = true, $languageId = null)
5714 {
5715 $res = '';
5716 if (!empty($event['RRULE']))
5717 {
5718 $event['RRULE'] = self::ParseRRULE($event['RRULE']);
5719
5720 if (!empty($event['RRULE']['BYDAY']))
5721 {
5722 $event['RRULE']['BYDAY'] = self::sortByDay($event['RRULE']['BYDAY']);
5723 }
5724
5725 switch($event['RRULE']['FREQ'])
5726 {
5727 case 'DAILY':
5728 if ((int)$event['RRULE']['INTERVAL'] === 1)
5729 {
5730 $res = Loc::getMessage('EC_RRULE_EVERY_DAY', null, $languageId);
5731 }
5732 else
5733 {
5734 $res = Loc::getMessage(
5735 'EC_RRULE_EVERY_DAY_1',
5736 ['#DAY#' => $event['RRULE']['INTERVAL']],
5737 $languageId
5738 );
5739 }
5740 break;
5741 case 'WEEKLY':
5742 $daysList = [];
5743 foreach ($event['RRULE']['BYDAY'] as $day)
5744 {
5745 $daysList[] = Loc::getMessage('EC_'.$day, null, $languageId);
5746 }
5747
5748 $daysList = implode(', ', $daysList);
5749 if ((int)$event['RRULE']['INTERVAL'] === 1)
5750 {
5751 $res = Loc::getMessage(
5752 'EC_RRULE_EVERY_WEEK',
5753 ['#DAYS_LIST#' => $daysList],
5754 $languageId
5755 );
5756 }
5757 else
5758 {
5759 $res = Loc::getMessage(
5760 'EC_RRULE_EVERY_WEEK_1',
5761 [
5762 '#WEEK#' => $event['RRULE']['INTERVAL'],
5763 '#DAYS_LIST#' => $daysList,
5764 ],
5765 $languageId
5766 );
5767 }
5768 break;
5769 case 'MONTHLY':
5770 if ((int)$event['RRULE']['INTERVAL'] === 1)
5771 {
5772 $res = Loc::getMessage('EC_RRULE_EVERY_MONTH', null, $languageId);
5773 }
5774 else
5775 {
5776 $res = Loc::getMessage(
5777 'EC_RRULE_EVERY_MONTH_1',
5778 [
5779 '#MONTH#' => $event['RRULE']['INTERVAL'],
5780 ],
5781 $languageId
5782 );
5783 }
5784 break;
5785 case 'YEARLY':
5786 $fromTs = CCalendar::Timestamp($event['DATE_FROM']);
5787 if ($event['DT_SKIP_TIME'] !== "Y")
5788 {
5789 $fromTs -= $event['~USER_OFFSET_FROM'] ?? 0;
5790 }
5791
5792 if ((int)$event['RRULE']['INTERVAL'] === 1)
5793 {
5794 $res = Loc::getMessage(
5795 'EC_RRULE_EVERY_YEAR',
5796 [
5797 '#DAY#' => FormatDate('j', $fromTs, false, $languageId), // day
5798 '#MONTH#' => FormatDate('n', $fromTs, false, $languageId), // month
5799 ],
5800 $languageId
5801 );
5802 }
5803 else
5804 {
5805 $res = Loc::getMessage(
5806 'EC_RRULE_EVERY_YEAR_1',
5807 [
5808 '#YEAR#' => $event['RRULE']['INTERVAL'],
5809 '#DAY#' => FormatDate('j', $fromTs, false, $languageId), // day
5810 '#MONTH#' => FormatDate('n', $fromTs, false, $languageId), // month
5811 ],
5812 $languageId
5813 );
5814 }
5815 break;
5816 }
5817
5818 if ($html)
5819 {
5820 $res .= '<br>';
5821 }
5822 else
5823 {
5824 $res .= ', ';
5825 }
5826
5827 if (isset($event['~DATE_FROM']))
5828 {
5829 $res .= Loc::getMessage(
5830 'EC_RRULE_FROM',
5831 ['#FROM_DATE#' => CCalendar::Date(CCalendar::Timestamp($event['~DATE_FROM']), false)],
5832 $languageId
5833 );
5834 }
5835 else
5836 {
5837 $res .= Loc::getMessage(
5838 'EC_RRULE_FROM',
5839 ['#FROM_DATE#' => CCalendar::Date(CCalendar::Timestamp($event['DATE_FROM']), false)],
5840 $languageId
5841 );
5842 }
5843
5844 if ($showUntil && ($event['RRULE']['UNTIL'] ?? null) != CCalendar::GetMaxDate())
5845 {
5846 $res .= ' ' . Loc::getMessage(
5847 'EC_RRULE_UNTIL',
5848 ['#UNTIL_DATE#' => CCalendar::Date(CCalendar::Timestamp($event['RRULE']['UNTIL']), false)],
5849 $languageId
5850 );
5851 }
5852 elseif ($showUntil && (($event['RRULE']['COUNT'] ?? null) > 0))
5853 {
5854 $res .= ', ' . Loc::getMessage(
5855 'EC_RRULE_COUNT',
5856 ['#COUNT#' => $event['RRULE']['COUNT']],
5857 $languageId
5858 );
5859 }
5860 }
5861
5862 return $res;
5863 }
5864
5865 public static function ExcludeInstance($eventId, $excludeDate)
5866 {
5867 global $CACHE_MANAGER;
5868
5869 $userId = \CCalendar::getCurUserId();
5870 $eventId = (int)$eventId;
5871 $excludeDateTs = CCalendar::Timestamp($excludeDate);
5872 $excludeDate = CCalendar::Date($excludeDateTs, false);
5873
5874 $event = self::GetList([
5875 'arFilter' => [
5876 "ID" => $eventId,
5877 "DELETED" => "N",
5878 ],
5879 'parseRecursion' => false,
5880 'fetchAttendees' => true,
5881 'setDefaultLimit' => false,
5882 ]);
5883 if ($event && is_array($event[0]))
5884 {
5885 $event = $event[0];
5886 }
5887
5888 if ($event && $excludeDate && self::CheckRecurcion($event))
5889 {
5890 $excludeDates = self::GetExDate($event['EXDATE']);
5891 $excludeDates[] = $excludeDate;
5892
5893 $id = CCalendar::SaveEvent([
5894 'arFields' => [
5895 'ID' => $event['ID'],
5896 'DATE_FROM' => $event['DATE_FROM'],
5897 'DATE_TO' => $event['DATE_TO'],
5898 'EXDATE' => self::SetExDate($excludeDates),
5899 ],
5900 'silentErrorMode' => false,
5901 'recursionEditMode' => 'skip',
5902 'editParentEvents' => true,
5903 ]);
5904
5905 if (!empty($event['ATTENDEE_LIST']) && is_array($event['ATTENDEE_LIST']))
5906 {
5907 foreach($event['ATTENDEE_LIST'] as $attendee)
5908 {
5909 if ($attendee['status'] === 'Y')
5910 {
5911 if ($event['DT_SKIP_TIME'] !== 'Y')
5912 {
5913 $excludeDate = CCalendar::Date(CCalendar::DateWithNewTime(CCalendar::Timestamp($event['DATE_FROM']), $excludeDateTs));
5914 }
5915
5916 $CACHE_MANAGER->ClearByTag('calendar_user_'.$attendee["id"]);
5917
5918 $meetingHost = $event['MEETING']['MEETING_CREATOR'] ?? $event['MEETING_HOST'];
5919 CCalendarNotify::Send([
5920 "mode" => 'cancel_this',
5921 "name" => $event['NAME'],
5922 "from" => $excludeDate,
5923 "guestId" => $attendee["id"],
5924 "eventId" => $event['PARENT_ID'],
5925 "userId" => $userId > 0 ? $userId : $meetingHost,
5926 "fields" => $event,
5927 ]);
5928 }
5929 }
5930 }
5931 }
5932 }
5933
5934 public static function getDiskUFFileNameList($valueList = [])
5935 {
5936 $result = [];
5937
5938 if (
5939 !empty($valueList)
5940 && is_array($valueList)
5941 && Loader::includeModule('disk')
5942 )
5943 {
5944 $attachedIdList = [];
5945 foreach($valueList as $value)
5946 {
5947 [$type, $realValue] = FileUserType::detectType($value);
5948 if ($type === FileUserType::TYPE_NEW_OBJECT)
5949 {
5950 $file = \Bitrix\Disk\File::loadById($realValue, array('STORAGE'));
5951 $result[] = strip_tags($file->getName());
5952 }
5953 else
5954 {
5955 $attachedIdList[] = $realValue;
5956 }
5957 }
5958
5959 if (!empty($attachedIdList))
5960 {
5961 $attachedObjects = AttachedObject::getModelList(array(
5962 'with' => array('OBJECT'),
5963 'filter' => array(
5964 'ID' => $attachedIdList,
5965 ),
5966 ));
5967 foreach($attachedObjects as $attachedObject)
5968 {
5969 $file = $attachedObject->getFile();
5970 $result[] = strip_tags($file->getName());
5971 }
5972 }
5973 }
5974
5975 return $result;
5976 }
5977
5978 public static function getSearchIndexContent($eventId)
5979 {
5980 $res = '';
5981 if ((int)$eventId > 0)
5982 {
5983 $event = Internals\EventTable::query()
5984 ->setSelect(['ID', 'DELETED', 'SEARCHABLE_CONTENT'])
5985 ->where('ID', (int)$eventId)
5986 ->where('DELETED', 'N')
5987 ->exec()->fetch()
5988 ;
5989
5990 $res = !empty($event['SEARCHABLE_CONTENT']) ? $event['SEARCHABLE_CONTENT'] : '';
5991 }
5992
5993 return $res;
5994 }
5995
5996 public static function updateSearchIndex($eventIdList = [], $params = []): void
5997 {
5998 if (isset($params['isNew']) && $params['isNew'] === false)
5999 {
6000 $targetChangedFields = ['NAME', 'DESCRIPTION', 'LOCATION'];
6001
6002 if (
6003 isset($params['changedFields'])
6004 && empty(array_intersect($params['changedFields'], $targetChangedFields))
6005 )
6006 {
6007 return;
6008 }
6009 }
6010
6011 if (isset($params['events']))
6012 {
6013 $events = $params['events'];
6014 }
6015 else
6016 {
6017 $events = self::getList([
6018 'arFilter' => [
6019 'ID' => $eventIdList,
6020 'DELETED' => 'N',
6021 ],
6022 'parseRecursion' => false,
6023 'fetchAttendees' => true,
6024 'checkPermissions' => false,
6025 'setDefaultLimit' => false,
6026 'userId' => $params['userId'] ?? null,
6027 ]);
6028 }
6029
6030 if (is_array($events))
6031 {
6032 $event = current($events);
6033 $eventId = (int)$event['ID'];
6034 $content = self::formatSearchIndexContent($event);
6035
6036 if (!empty($params['updateAllByParent']) && $eventId === (int)$event['PARENT_ID'])
6037 {
6038 Internals\EventTable::updateByFilter(
6039 ['=PARENT_ID' => $eventId],
6040 ['SEARCHABLE_CONTENT' => $content]
6041 );
6042 }
6043 else
6044 {
6045 Internals\EventTable::update($eventId, [
6046 ['SEARCHABLE_CONTENT' => $content],
6047 ]);
6048 }
6049 }
6050 }
6051
6052 public static function formatSearchIndexContent($entry = []): string
6053 {
6054 $content = '';
6055 if (!empty($entry))
6056 {
6057 $content = static::prepareToken(
6058 Emoji::encode($entry['NAME'])
6059 . ' '
6060 . Emoji::encode($entry['DESCRIPTION'])
6061 . ' '
6062 . Emoji::encode(\CCalendar::GetTextLocation($entry['LOCATION'] ?? ''))
6063 );
6064
6065 if ($entry['IS_MEETING'])
6066 {
6067 $attendeesWereHandled = false;
6068 if (!empty($entry['ATTENDEE_LIST']) && is_array($entry['ATTENDEE_LIST']))
6069 {
6070 foreach($entry['ATTENDEE_LIST'] as $attendee)
6071 {
6072 if (isset(self::$userIndex[$attendee['id']]))
6073 {
6074 $content .= ' '.static::prepareToken(self::$userIndex[$attendee['id']]['DISPLAY_NAME']);
6075 }
6076 }
6077 $attendeesWereHandled = true;
6078 }
6079
6080 if (!empty($entry['ATTENDEES_CODES']))
6081 {
6082 if ($attendeesWereHandled)
6083 {
6084 $attendeesCodes = [];
6085 foreach($entry['ATTENDEES_CODES'] as $code)
6086 {
6087 if (!str_starts_with($code, 'U'))
6088 {
6089 $attendeesCodes[] = $code;
6090 }
6091 }
6092 }
6093 else
6094 {
6095 $attendeesCodes = $entry['ATTENDEES_CODES'];
6096 }
6097 $content .= ' ' . static::prepareToken(
6098 implode(
6099 ' ',
6100 Bitrix\Socialnetwork\Item\LogIndex::getEntitiesName($attendeesCodes)
6101 )
6102 );
6103 }
6104 }
6105 else
6106 {
6107 $content .= ' ' . static::prepareToken(CCalendar::GetUserName($entry['CREATED_BY']));
6108 }
6109
6110 try
6111 {
6112 if (!empty($entry['UF_WEBDAV_CAL_EVENT']) && Loader::includeModule('disk'))
6113 {
6114 $fileNameList = self::getDiskUFFileNameList($entry['UF_WEBDAV_CAL_EVENT']);
6115 if (!empty($fileNameList))
6116 {
6117 $content .= ' '.static::prepareToken(implode(' ', $fileNameList));
6118 }
6119 }
6120 }
6121 catch (RuntimeException $e)
6122 {
6123 }
6124
6125 try
6126 {
6127 if (!empty($entry['UF_CRM_CAL_EVENT']) && Loader::includeModule('crm'))
6128 {
6129 $uf = $entry['UF_CRM_CAL_EVENT'];
6130
6131 foreach ($uf as $item)
6132 {
6133 $title = self::getCrmElementTitle($item);
6134 $content .= ' ' . static::prepareToken($title);
6135 }
6136 }
6137 }
6138 catch (RuntimeException $e)
6139 {
6140 }
6141 }
6142
6143 return $content;
6144 }
6145
6146 public static function getCrmElementTitle(string $item)
6147 {
6148 $crmElement = explode('_', $item);
6149 $type = $crmElement[0];
6150 $typeId = \CCrmOwnerType::ResolveID(\CCrmOwnerTypeAbbr::ResolveName($type));
6151
6152 return \CCrmOwnerType::GetCaption($typeId, $crmElement[1]);
6153 }
6154
6155 public static function GetCount()
6156 {
6157 global $DB;
6158 $count = 0;
6159 $res = $DB->Query('select count(*) as c from b_calendar_event');
6160
6161 if ($res = $res->Fetch())
6162 {
6163 $count = $res['c'];
6164 }
6165
6166 return $count;
6167 }
6168
6169 public static function updateColor($eventId, $color = '')
6170 {
6171 Internals\EventTable::update((int)$eventId, ['COLOR' => $color]);
6172
6173 $eventAttendeeId = self::getEventAttendeeIdByEventIdAndUserId($eventId, CCalendar::GetCurUserId());
6174 if ($eventAttendeeId)
6175 {
6176 self::updateEventAttendeeColor($eventAttendeeId, $color);
6177 }
6178 }
6179
6185 public static function prepareToken($token)
6186 {
6187 return str_rot13($token);
6188 }
6189
6190 public static function isFullTextIndexEnabled()
6191 {
6192 return COption::GetOptionString("calendar", "~ft_b_calendar_event", false);
6193 }
6194
6195 public static function getUserIndex()
6196 {
6197 return self::$userIndex;
6198 }
6199
6200 public static function getEventForViewInterface($entryId, $params = [])
6201 {
6202 $params['eventDate'] = ($params['eventDate'] ?? null);
6203 $params['timezoneOffset'] = ($params['timezoneOffset'] ?? null);
6204 $params['userId'] = ($params['userId'] ?? null);
6205
6206 $fromTs = \CCalendar::Timestamp($params['eventDate'], true, false) - $params['timezoneOffset'];
6207 $userDateFrom = \CCalendar::Date($fromTs);
6208
6209 $entry = self::GetList([
6210 'arFilter' => [
6211 "ID" => $entryId,
6212 "DELETED" => "N",
6213 "FROM_LIMIT" => $userDateFrom,
6214 "TO_LIMIT" => $userDateFrom,
6215 ],
6216 'parseRecursion' => true,
6217 'maxInstanceCount' => 1,
6218 'preciseLimits' => true,
6219 'fetchAttendees' => true,
6220 'checkPermissions' => true,
6221 'setDefaultLimit' => false,
6222 'getUserfields' => true,
6223 ]);
6224
6225 if (
6226 $params['eventDate']
6227 && isset($entry[0])
6228 && is_array($entry[0])
6229 && $entry[0]['RRULE']
6230 && $entry[0]['EXDATE']
6231 && in_array($params['eventDate'], self::GetExDate($entry[0]['EXDATE']))
6232 )
6233 {
6234 $entry = self::GetList([
6235 'arFilter' => [
6236 "RECURRENCE_ID" => $entryId,
6237 "DELETED" => "N",
6238 "FROM_LIMIT" => $params['eventDate'],
6239 "TO_LIMIT" => $params['eventDate'],
6240 ],
6241 'parseRecursion' => true,
6242 'maxInstanceCount' => 1,
6243 'preciseLimits' => true,
6244 'fetchAttendees' => true,
6245 'checkPermissions' => true,
6246 'setDefaultLimit' => false,
6247 'getUserfields' => true,
6248 ]);
6249 }
6250
6251 if (!$entry || !is_array($entry[0]))
6252 {
6253 $entry = self::GetList([
6254 'arFilter' => [
6255 "ID" => $entryId,
6256 "DELETED" => "N",
6257 ],
6258 'parseRecursion' => true,
6259 'maxInstanceCount' => 1,
6260 'fetchAttendees' => true,
6261 'checkPermissions' => true,
6262 'setDefaultLimit' => false,
6263 'getUserfields' => true,
6264 ]);
6265 }
6266
6267 // Here we can get events with wrong RRULE ('parseRecursion' => false)
6268 if (!$entry || !is_array($entry[0]))
6269 {
6270 $entry = self::GetList([
6271 'arFilter' => [
6272 "ID" => $entryId,
6273 "DELETED" => "N",
6274 ],
6275 'parseRecursion' => false,
6276 'fetchAttendees' => true,
6277 'checkPermissions' => true,
6278 'setDefaultLimit' => false,
6279 'getUserfields' => true,
6280 ]);
6281 }
6282
6283 if ($entry && is_array($entry[0]))
6284 {
6285 $entry = $entry[0];
6286 if ($entry['IS_MEETING'] && (int)$entry['PARENT_ID'] !== (int)$entry['ID'])
6287 {
6288 $parentEntry = false;
6289 $parentEntryList = self::GetList([
6290 'arFilter' => [
6291 "ID" => (int)$entry['PARENT_ID'],
6292 "FROM_LIMIT" => $userDateFrom,
6293 "TO_LIMIT" => $userDateFrom,
6294 ],
6295 'parseRecursion' => true,
6296 'maxInstanceCount' => 1,
6297 'preciseLimits' => true,
6298 'fetchAttendees' => true,
6299 'checkPermissions' => true,
6300 'setDefaultLimit' => false,
6301 'getUserfields' => true,
6302
6303 ]);
6304
6305 if (!empty($parentEntryList[0]) && is_array($parentEntryList[0]))
6306 {
6307 $parentEntry = $parentEntryList[0];
6308 }
6309
6310 if ($parentEntry)
6311 {
6312 if ($parentEntry['DELETED'] === 'Y')
6313 {
6314 self::CleanEventsWithDeadParents();
6315 $entry = false;
6316 }
6317
6318 if ((int)$parentEntry['MEETING_HOST'] === (int)$params['userId'])
6319 {
6320 $entry = $parentEntry;
6321 }
6322 }
6323 }
6324
6325 if (
6326 isset($entry['UF_WEBDAV_CAL_EVENT'])
6327 && is_array($entry['UF_WEBDAV_CAL_EVENT'])
6328 && empty($entry['UF_WEBDAV_CAL_EVENT'])
6329 )
6330 {
6331 $entry['UF_WEBDAV_CAL_EVENT'] = null;
6332 }
6333 }
6334
6335 if (
6336 ($entry['IS_MEETING'] ?? null)
6337 && !empty($entry['ATTENDEE_LIST'])
6338 && is_array($entry['ATTENDEE_LIST'])
6339 && (int)$entry['CREATED_BY'] !== $params['userId']
6340 && (int)$entry['OWNER_ID'] !== $params['userId']
6341 && ($params['recursion'] ?? null) !== false
6342 && ($entry['CAL_TYPE'] !== Dictionary::CALENDAR_TYPE['open_event'])
6343 )
6344 {
6345 foreach($entry['ATTENDEE_LIST'] as $attendee)
6346 {
6347 if ((int)$attendee['id'] === (int)$params['userId'])
6348 {
6349 $entry = self::GetList([
6350 'arFilter' => [
6351 'PARENT_ID' => $entry['PARENT_ID'],
6352 'OWNER_ID' => $params['userId'],
6353 'DELETED' => 'N',
6354 ],
6355 'parseRecursion' => false,
6356 'maxInstanceCount' => 1,
6357 'preciseLimits' => false,
6358 'fetchAttendees' => false,
6359 'checkPermissions' => true,
6360 'setDefaultLimit' => false,
6361 'getUserfields' => true,
6362 ]);
6363
6364 if ($entry && is_array($entry[0]) && $entry[0]['CAL_TYPE'] === 'location')
6365 {
6366 $params['recursion'] = false;
6367 $entry = self::getEventForViewInterface($entry[0]['PARENT_ID'], $params);
6368 }
6369 else if ($entry && is_array($entry[0]))
6370 {
6371 $params['recursion'] = false;
6372 $entry = self::getEventForViewInterface($entry[0]['ID'], $params);
6373 }
6374 }
6375 }
6376 }
6377
6378 return $entry;
6379 }
6380
6381 public static function getEventForEditInterface($entryId, $params = [])
6382 {
6383 $entry = self::GetList(
6384 [
6385 'arFilter' => [
6386 "ID" => $entryId,
6387 "DELETED" => "N",
6388 "FROM_LIMIT" => $params['eventDate'] ?? null,
6389 "TO_LIMIT" => $params['eventDate'] ?? null,
6390 ],
6391 'parseRecursion' => true,
6392 'maxInstanceCount' => 1,
6393 'preciseLimits' => true,
6394 'fetchAttendees' => true,
6395 'checkPermissions' => true,
6396 'setDefaultLimit' => false,
6397 ]
6398 );
6399
6400 if (!$entry || !is_array($entry[0]))
6401 {
6402 $entry = self::GetList(
6403 [
6404 'arFilter' => [
6405 "ID" => $entryId,
6406 "DELETED" => "N",
6407 ],
6408 'parseRecursion' => true,
6409 'maxInstanceCount' => 1,
6410 'fetchAttendees' => true,
6411 'checkPermissions' => true,
6412 'setDefaultLimit' => false,
6413 ]
6414 );
6415 }
6416
6417 // Here we can get events with wrong RRULE ('parseRecursion' => false)
6418 if (!$entry || !is_array($entry[0]))
6419 {
6420 $entry = self::GetList(
6421 [
6422 'arFilter' => [
6423 "ID" => $entryId,
6424 "DELETED" => "N",
6425 ],
6426 'parseRecursion' => false,
6427 'fetchAttendees' => true,
6428 'checkPermissions' => true,
6429 'setDefaultLimit' => false,
6430 ]
6431 );
6432 }
6433
6434 $entry = is_array($entry) ? $entry[0] : null;
6435
6436 if (is_array($entry) && $entry['ID'] !== $entry['PARENT_ID'])
6437 {
6438 return self::getEventForEditInterface($entry['PARENT_ID'], $params);
6439 }
6440
6441 return $entry;
6442 }
6443
6444 public static function handleAccessCodes($accessCodes = [], $params = [])
6445 {
6446 $accessCodes = is_array($accessCodes) ? $accessCodes : [];
6447 $userId = isset($params['userId']) ? $params['userId'] : \CCalendar::getCurUserId();
6448
6449 if (empty($accessCodes))
6450 {
6451 $accessCodes[] = 'U'.$userId;
6452 }
6453
6454 $accessCodes = array_unique($accessCodes);
6455
6456 return $accessCodes;
6457 }
6458
6466 public static function checkLocationOccupancy(
6467 mixed $entryFields,
6468 array $params,
6469 mixed $currentEvent,
6470 int $userId
6471 )
6472 {
6473 $entryFields['TZ_FROM'] ??= null;
6474 $entryFields['TZ_TO'] ??= null;
6475 $entryFields['TZ_OFFSET_FROM'] ??= 0;
6476 $entryFields['TZ_OFFSET_TO'] ??= 0;
6477
6478 $fieldsToCheckOccupancy = $entryFields;
6479 if (!empty($params['checkLocationOccupancyFields']))
6480 {
6481 $fieldsToCheckOccupancy = $params['checkLocationOccupancyFields'];
6482 $fieldsToCheckOccupancy['LOCATION'] = [
6483 'NEW' => $fieldsToCheckOccupancy['LOCATION'] ?? '',
6484 ];
6485 self::CheckFields($fieldsToCheckOccupancy, $currentEvent, $userId);
6486 }
6487
6488 $occupancyCheckResult = (new Rooms\OccupancyChecker())->check($fieldsToCheckOccupancy);
6489 if (!$occupancyCheckResult->isSuccess())
6490 {
6491 $disturbingEventsFormatted = $occupancyCheckResult->getData()['disturbingEventsFormatted'];
6492 if ($occupancyCheckResult->getData()['isDisturbingEventsAmountOverShowLimit'])
6493 {
6494 $message = Loc::getMessage('EC_LOCATION_REPEAT_BUSY_TOO_MANY', [
6495 '#DATES#' => $disturbingEventsFormatted,
6496 ]);
6497 }
6498 else
6499 {
6500 $message = Loc::getMessage('EC_LOCATION_REPEAT_BUSY', [
6501 '#DATES#' => $disturbingEventsFormatted,
6502 ]);
6503 }
6504
6506 }
6507 }
6508
6516 public static function tryingToFindEventSection(
6517 bool $isNewEvent,
6518 mixed $entryFields,
6519 int $userId,
6520 mixed $currentEvent
6521 ): mixed
6522 {
6523 // It's new event we have to find section where to put it automatically
6524 if ($isNewEvent)
6525 {
6526 if (
6527 !empty($entryFields['IS_MEETING'])
6528 && !empty($entryFields['PARENT_ID'])
6529 && ($entryFields['CAL_TYPE'] ?? null) === 'user'
6530 )
6531 {
6532 $sectionId = CCalendar::GetMeetingSection($entryFields['OWNER_ID'] ?? null);
6533 }
6534 else
6535 {
6536 $sectionId = CCalendarSect::GetLastUsedSection(
6537 $entryFields['CAL_TYPE'] ?? null, $entryFields['OWNER_ID'] ?? null, $userId
6538 );
6539 }
6540
6541 if ($sectionId)
6542 {
6543 $res = CCalendarSect::GetList([
6544 'arFilter' => [
6545 'CAL_TYPE' => $entryFields['CAL_TYPE'] ?? null, 'OWNER_ID' => $entryFields['OWNER_ID'] ?? null,
6546 'ID' => $sectionId,
6547 ],
6548 ]);
6549
6550 if (empty($res[0]))
6551 {
6552 $sectionId = false;
6553 }
6554 }
6555 else
6556 {
6557 $sectionId = false;
6558 }
6559
6560 if (empty($sectionId))
6561 {
6562 $sectRes = CCalendarSect::GetSectionForOwner($entryFields['CAL_TYPE'], $entryFields['OWNER_ID'], true);
6563 $sectionId = $sectRes['sectionId'];
6564 }
6565 }
6566 else
6567 {
6568 $sectionId = $currentEvent['SECTION_ID'] ?? $currentEvent['SECT_ID'];
6569 }
6570
6571 return $sectionId;
6572 }
6573
6582 public static function generateUserIndex(array $involvedAttendees, $meetingHost): array
6583 {
6584 $userIndex = [];
6585
6586 // Here we collecting information about EXTERNAL_AUTH_ID to
6587 // know if some of the users are external
6588 $orm = UserTable::getList([
6589 'filter' => [
6590 '=ID' => $involvedAttendees, '=ACTIVE' => 'Y',
6591 ], 'select' => [
6592 'ID', 'EXTERNAL_AUTH_ID', 'NAME', 'LAST_NAME', 'SECOND_NAME', 'LOGIN', 'EMAIL', 'TITLE',
6593 'UF_DEPARTMENT',
6594 ],
6595 ]);
6596
6597 while ($user = $orm->fetch())
6598 {
6599 if ($user['ID'] === ($meetingHost ?? null))
6600 {
6601 $user['STATUS'] = 'accepted';
6602 }
6603 else
6604 {
6605 $user['STATUS'] = 'needs_action';
6606 }
6607
6608 $userIndex[$user['ID']] = $user;
6609 }
6610
6611 return $userIndex;
6612 }
6613
6618 private static function getSenderEmailForIcal(string $serializedMeetingInfo = null): ?string
6619 {
6620 $meetingInfo = unserialize($serializedMeetingInfo, ['allowed_classes' => false]);
6621
6622 return !empty($meetingInfo) && !empty($meetingInfo['MAIL_FROM'])
6623 ? $meetingInfo['MAIL_FROM']
6624 : null;
6625 }
6626
6635 private static function getSenderForIcal($userIndex, $organizerId): ?array
6636 {
6637 if (!empty($userIndex) && !empty($userIndex[$organizerId]))
6638 {
6639 return $userIndex[$organizerId];
6640 }
6641
6642 $userOrm = UserTable::getList([
6643 'filter' => [
6644 '=ID' => $organizerId,
6645 '=ACTIVE' => 'Y',
6646 ],
6647 'select' => [
6648 'ID',
6649 'EXTERNAL_AUTH_ID',
6650 'NAME',
6651 'LAST_NAME',
6652 'SECOND_NAME',
6653 'LOGIN',
6654 'EMAIL',
6655 'TITLE',
6656 'UF_DEPARTMENT',
6657 ],
6658 ]);
6659
6660 if ($user = $userOrm->fetch())
6661 {
6662 return $user;
6663 }
6664
6665 return null;
6666 }
6667
6673 public static function updateEventFields(array $event, array $fields): void
6674 {
6675 global $DB;
6676
6677 if (!$fields)
6678 {
6679 return;
6680 }
6681
6682 CTimeZone::Disable();
6683
6684 $strSql = "UPDATE b_calendar_event SET ".
6685 $DB->PrepareUpdate("b_calendar_event", $fields)
6686 . " WHERE ID=" . (int)$event['ID'] . "; ";
6687 $DB->Query($strSql);
6688
6689
6690 CTimeZone::Enable();
6691 }
6692
6698 public static function updateSyncStatus(int $eventId, string $status): void
6699 {
6700 global $DB;
6701
6702 if (in_array($status, Bitrix\Calendar\Sync\Google\Dictionary::SYNC_STATUS, true))
6703 {
6704 $DB->Query(
6705 "UPDATE b_calendar_event"
6706 . " SET " . $DB->PrepareUpdate('b_calendar_event', ['SYNC_STATUS' => $status])
6707 . " WHERE ID = " . $eventId . ";"
6708 );
6709 }
6710 }
6711
6712 public static function checkLocationField($location, $isNewEvent)
6713 {
6714 $parsedNew = Bitrix\Calendar\Rooms\Util::parseLocation($location['NEW']);
6715 if (!empty($parsedNew['room_event_id']))
6716 {
6717 $location['NEW'] = 'calendar_' . $parsedNew['room_id'];
6718 }
6719
6720 if ($isNewEvent)
6721 {
6722 $location['OLD'] = '';
6723 }
6724
6725 return $location;
6726 }
6727
6732 public static function prepareArFieldBeforeSyncEvent(array &$childParams): void
6733 {
6734 if (is_string($childParams['arFields']['MEETING']))
6735 {
6736 $childParams['arFields']['MEETING'] = unserialize($childParams['arFields']['MEETING'], ['allowed_classes' => false]);
6737 }
6738
6739 $childParams['arFields']['MEETING']['LANGUAGE_ID'] = CCalendar::getUserLanguageId((int)$childParams['arFields']['OWNER_ID']);
6740 }
6741
6747 private static function getUidForChildEvent(array $event): string
6748 {
6749 return UidGenerator::createInstance()
6750 ->setPortalName(Util::getServerName())
6751 ->setDate(new Date(
6752 Util::getDateObject(
6753 $event['DATE_FROM'],
6754 ($event['SKIP_TIME'] ?? false),
6755 $event['TZ_FROM'] ?? null,
6756 )
6757 ))
6758 ->setUserId((int)$event['OWNER_ID'])
6759 ->getUidWithDate()
6760 ;
6761 }
6762
6776 private static function onEventDelete(
6777 array $eventData,
6778 array $params = []
6779 ): void
6780 {
6782 $mapperFactory = \Bitrix\Main\DI\ServiceLocator::getInstance()->get('calendar.service.mappers.factory');
6783 $exDate = null;
6784 $originalDate = null;
6785 if (empty($eventData))
6786 {
6787 return;
6788 }
6790 $section = $mapperFactory->getSection()->getById((int)$eventData['SECTION_ID']);
6791
6792 if ($section === null)
6793 {
6794 return;
6795 }
6796
6797 $factories = FactoriesCollection::createBySection($section);
6798
6799 if ($factories->count() === 0)
6800 {
6801 return;
6802 }
6803
6804 $recId = $eventData['RECURRENCE_ID'];
6805 if ($recId)
6806 {
6807 $exDate = $eventData['DATE_FROM'];
6808 $originalDate = $eventData['ORIGINAL_DATE_FROM'];
6809 $originalEventData = $eventData;
6810 $eventData = Internals\EventTable::getRow([
6811 'filter' => [
6812 '=PARENT_ID' => $eventData['RECURRENCE_ID'],
6813 '=OWNER_ID' => $eventData['OWNER_ID'],
6814 '=DELETED' => 'N',
6815 ],
6816 ]);
6817 }
6818 if ($eventData)
6819 {
6820 $event = (new \Bitrix\Calendar\Core\Builders\EventBuilderFromArray($eventData))->build();
6821 }
6822 else
6823 {
6824 return;
6825 }
6826
6827 $syncManager = new Synchronization($factories);
6828 $context = new Context([]);
6829
6830 // TODO: there's a dependence of the general on the particular
6831 if (
6832 !empty($params['originalFrom'])
6833 && (int)($params['userId'] ?? null) === (int)$eventData['OWNER_ID']
6834 )
6835 {
6836 $context->add('sync', 'originalFrom', $params['originalFrom']);
6837 $connection = $mapperFactory->getConnection()->getMap([
6838 '=ACCOUNT_TYPE' => $params['originalFrom'],
6839 '=ENTITY_TYPE' => $event->getCalendarType(),
6840 '=ENTITY_ID' => $event->getOwner()?->getId(),
6841 ])->fetch();
6842 if ($connection)
6843 {
6844 $syncManager->upEventVersion(
6845 $event,
6847 $arFields['VERSION'] ?? 1
6848 );
6849 }
6850 }
6851
6852 if ($exDate)
6853 {
6854 $exDate = new \Bitrix\Main\Type\Date(CCalendar::Date(CCalendar::Timestamp($exDate), false));
6855 $context->add('sync', 'excludeDate', new \Bitrix\Calendar\Core\Base\Date($exDate));
6856 }
6857
6858 if ($originalDate)
6859 {
6860 $originalDate = new \Bitrix\Main\Type\DateTime(CCalendar::Date(CCalendar::Timestamp($originalDate)));
6861 $context->add('sync', 'originalDate', new \Bitrix\Calendar\Core\Base\Date($originalDate));
6862 }
6863
6864 if ($recId)
6865 {
6866 $result = $syncManager->deleteInstance($event, $context);
6867 if (!empty($originalEventData) && !$result->isSuccess())
6868 {
6869 $originalEvent = (new \Bitrix\Calendar\Core\Builders\EventBuilderFromArray($originalEventData))->build();
6870 $syncManager->deleteEvent($originalEvent, $context);
6871 }
6872 }
6873 else
6874 {
6875 $syncManager->deleteEvent($event, $context);
6876 }
6877 }
6878
6879 private static function isNewAttendee($attendees, $currentId): bool
6880 {
6881 foreach ($attendees as $attendee)
6882 {
6883 if ((int)$attendee['id'] === (int)$currentId)
6884 {
6885 return false;
6886 }
6887 }
6888
6889 return true;
6890 }
6891
6892 public static function sortByDay(array $byDay)
6893 {
6894 uasort($byDay, function($a, $b){
6895 $map = [
6896 'MO' => 0,
6897 'TU' => 1,
6898 'WE' => 2,
6899 'TH' => 3,
6900 'FR' => 4,
6901 'SA' => 5,
6902 'SU' => 6,
6903 ];
6904
6905 return $map[$a] < $map[$b] ? -1 : 1;
6906 });
6907
6908 return $byDay;
6909 }
6910
6918 private static function checkRecurringRuleField(array &$arFields, int $toTs, $currentExDate): void
6919 {
6920 // Check rrules
6921 if (
6922 !empty($arFields['RRULE']['FREQ'])
6923 && in_array($arFields['RRULE']['FREQ'], ['HOURLY', 'DAILY', 'MONTHLY', 'YEARLY', 'WEEKLY'])
6924 )
6925 {
6926 // Interval
6927 if (isset($arFields['RRULE']['INTERVAL']) && (int)$arFields['RRULE']['INTERVAL'] > 1)
6928 $arFields['RRULE']['INTERVAL'] = (int)$arFields['RRULE']['INTERVAL'];
6929
6930 // Until date
6931
6932 $untilTs = CCalendar::Timestamp($arFields['RRULE']['UNTIL'] ?? null, false, false);
6933 if (isset($arFields['RRULE']['COUNT']))
6934 {
6935 $arFields['RRULE']['COUNT'] = (int)$arFields['RRULE']['COUNT'];
6936 }
6937
6938 if (!$untilTs)
6939 {
6940 $arFields['RRULE']['UNTIL'] = CCalendar::GetMaxDate();
6941 $untilTs = CCalendar::Timestamp($arFields['RRULE']['UNTIL'], false, false);
6942 }
6943 elseif ($untilTs + CCalendar::GetDayLen() < $toTs)
6944 {
6945 $untilTs = $toTs;
6946 }
6947
6948 $arFields['DATE_TO_TS_UTC'] = $untilTs + CCalendar::GetDayLen();
6949 if (isset($arFields['RRULE']['COUNT']))
6950 {
6951 $arFields['DATE_TO_TS_UTC'] = self::calculateUntilForCountRRule($arFields);
6952 }
6953
6954 $arFields['RRULE']['UNTIL'] = CCalendar::Date($untilTs, false);
6955 unset($arFields['RRULE']['~UNTIL']);
6956
6957 if (isset($arFields['RRULE']['BYDAY']))
6958 {
6959 if (is_array($arFields['RRULE']['BYDAY']))
6960 {
6961 $BYDAY = $arFields['RRULE']['BYDAY'];
6962 }
6963 else
6964 {
6965 $BYDAY = [];
6966 $days = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];
6967 $bydays = explode(',', $arFields['RRULE']['BYDAY']);
6968 foreach ($bydays as $day)
6969 {
6970 $day = mb_strtoupper($day);
6971 if (in_array($day, $days, true))
6972 {
6973 $BYDAY[] = $day;
6974 }
6975 }
6976 }
6977 $arFields['RRULE']['BYDAY'] = implode(',', $BYDAY);
6978 }
6979
6980 if (isset($arFields["EXDATE"]))
6981 {
6982 $excludeDates = self::GetExDate($arFields["EXDATE"]);
6983 }
6984 else
6985 {
6986 $excludeDates = self::GetExDate($currentExDate);
6987 }
6988
6989 if (!empty($excludeDates) && $untilTs)
6990 {
6991 $arFields["EXDATE"] = self::SetExDate($excludeDates, $untilTs);
6992 }
6993
6994 $arFields['RRULE'] = self::PackRRule($arFields['RRULE']);
6995 }
6996 else
6997 {
6998 $arFields['RRULE'] = '';
6999 $arFields['EXDATE'] = '';
7000 }
7001 }
7002
7003 protected static function calculateUntilForCountRRule(array $arFields, int $offsetTs = 0): int
7004 {
7005 $rrule = self::ParseRRULE($arFields['RRULE']);
7006 $interval = (int)$rrule['INTERVAL'];
7007 $count = (int)$rrule['COUNT'];
7008
7009 $fromTS = CCalendar::Timestamp($arFields['DATE_FROM']) + $offsetTs;
7010 $toTS = CCalendar::Timestamp($arFields['DATE_TO']) + $offsetTs;
7011 $length = $toTS - $fromTS;
7012
7013 $h = (int)date('H', $toTS);
7014 $min = (int)date('i', $toTS);
7015 $d = (int)date('d', $toTS);
7016 $m = (int)date('m', $toTS);
7017 $y = (int)date('Y', $toTS);
7018
7019 if ($rrule['FREQ'] === 'DAILY')
7020 {
7021 return mktime($h, $min, 0, $m, $d + $count * $interval, $y);
7022 }
7023 if ($rrule['FREQ'] === 'MONTHLY')
7024 {
7025 return mktime($h, $min, 0, $m + $count * $interval, $d, $y);
7026 }
7027 if ($rrule['FREQ'] === 'YEARLY')
7028 {
7029 return mktime($h, $min, 0, $m, $d, $y + $count * $interval);
7030 }
7031 if ($rrule['FREQ'] === 'WEEKLY')
7032 {
7033 $byDay = $rrule['BYDAY'];
7034
7035 if (!is_array($byDay))
7036 {
7037 $days = [];
7038 $weekDays = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];
7039
7040 foreach (explode(',', $byDay) as $day)
7041 {
7042 if (in_array($day, $weekDays, true))
7043 {
7044 $days[] = $day;
7045 }
7046 }
7047
7048 $byDay = $days;
7049 }
7050
7051 if (empty($byDay))
7052 {
7053 return 0;
7054 }
7055
7056 return mktime($h, $min, $length, $m, $d + round(($count - 1) / count($byDay) * 7 * $interval), $y);
7057 }
7058
7059 return 0;
7060 }
7061
7073 private static function pushUpdateDescriptionToQueue($PARENT_ID, $userId, $status): void
7074 {
7075 $message = (new \Bitrix\Calendar\Core\Queue\Message\Message())
7076 ->setBody([
7077 'parentId' => $PARENT_ID,
7078 'userId' => $userId,
7079 'meetingStatus' => $status,
7080 ])
7081 ->setRoutingKey('calendar:update_meeting_status');
7082 (new \Bitrix\Calendar\Core\Queue\Producer\Producer())->send($message);
7083 }
7084
7085 public static function getEventPermissions(array $event, int $userId = 0)
7086 {
7087 if ($userId <= 0)
7088 {
7089 $userId = CCalendar::GetUserId();
7090 }
7092 $eventModel = self::getEventModelForPermissionCheck((int)($event['ID'] ?? 0), $event, $userId);
7093
7094 $request = [
7095 ActionDictionary::ACTION_EVENT_VIEW_FULL => [],
7096 ActionDictionary::ACTION_EVENT_VIEW_TIME => [],
7097 ActionDictionary::ACTION_EVENT_VIEW_TITLE => [],
7098 ActionDictionary::ACTION_EVENT_VIEW_COMMENTS => [],
7099 ActionDictionary::ACTION_EVENT_EDIT => [],
7100 ActionDictionary::ACTION_EVENT_EDIT_LOCATION => [],
7101 ActionDictionary::ACTION_EVENT_EDIT_ATTENDEES => [],
7102 ActionDictionary::ACTION_EVENT_DELETE => [],
7103 ];
7104 $accessResult = $accessController->batchCheck($request, $eventModel);
7105
7106 return [
7107 'view_full' => $accessResult[ActionDictionary::ACTION_EVENT_VIEW_FULL],
7108 'view_time' => $accessResult[ActionDictionary::ACTION_EVENT_VIEW_TIME],
7109 'view_title' => $accessResult[ActionDictionary::ACTION_EVENT_VIEW_TITLE],
7110 'view_comments' => $accessResult[ActionDictionary::ACTION_EVENT_VIEW_COMMENTS],
7111 'edit' => $accessResult[ActionDictionary::ACTION_EVENT_EDIT],
7112 'editLocation' => $accessResult[ActionDictionary::ACTION_EVENT_EDIT_LOCATION],
7113 'editAttendees' => $accessResult[ActionDictionary::ACTION_EVENT_EDIT_ATTENDEES],
7114 'delete' => $accessResult[ActionDictionary::ACTION_EVENT_DELETE],
7115 ];
7116 }
7117
7118 public static function getEventModelForPermissionCheck(int $eventId, array $event = [], int $userId = 0): EventModel
7119 {
7120 if ($userId <= 0)
7121 {
7122 $userId = CCalendar::GetUserId();
7123 }
7124
7125 $userEvent = false;
7126 if ($eventId > 0)
7127 {
7128 $userEventResult = self::GetList([
7129 'arFilter' => [
7130 'PARENT_ID' => $eventId,
7131 'OWNER_ID' => $userId,
7132 'CAL_TYPE' => [Dictionary::CALENDAR_TYPE['user'], Dictionary::CALENDAR_TYPE['open_event']],
7133 'DELETED' => 'N',
7134 ],
7135 'arSelect' => self::$defaultSelectEvent,
7136 'parseRecursion' => false,
7137 'fetchMeetings' => false,
7138 'userId' => $userId,
7139 'checkPermissions' => false,
7140 'getPermissions' => false,
7141 ]);
7142
7143 if ($userEventResult)
7144 {
7145 $userEvent = $userEventResult[0];
7146 }
7147 }
7148
7149 if (
7150 !$userEvent
7151 && (
7152 empty($event)
7153 || ((int)($event['ID'] ?? 0) !== $eventId)
7154 )
7155 )
7156 {
7157 $currentEventResult = self::GetList([
7158 'arFilter' => [
7159 'ID' => $eventId,
7160 'DELETED' => 'N',
7161 ],
7162 'arSelect' => self::$defaultSelectEvent,
7163 'parseRecursion' => false,
7164 'fetchMeetings' => false,
7165 'userId' => $userId,
7166 'checkPermissions' => false,
7167 'getPermissions' => false,
7168 ]);
7169
7170 if ($currentEventResult)
7171 {
7172 $event = $currentEventResult[0];
7173 }
7174 }
7175
7176 return EventModel::createFromArray($userEvent ?: $event ?: []);
7177 }
7178
7179 public static function checkAttendeeBelongsToEvent($eventId, $userId)
7180 {
7181 if (empty($eventId) || empty($userId))
7182 {
7183 return false;
7184 }
7185
7186 if (isset(self::$attendeeBelongingToEvent[$eventId][$userId]))
7187 {
7188 return self::$attendeeBelongingToEvent[$eventId][$userId];
7189 }
7190
7191 $event = Internals\EventTable::query()
7192 ->setSelect(['ID'])
7193 ->where('PARENT_ID', $eventId)
7194 ->where('OWNER_ID', $userId)
7195 ->exec()->fetch()
7196 ;
7197
7198 if (!isset(self::$attendeeBelongingToEvent[$eventId]))
7199 {
7200 self::$attendeeBelongingToEvent[$eventId] = [];
7201 }
7202 if (!isset(self::$attendeeBelongingToEvent[$eventId][$userId]))
7203 {
7204 self::$attendeeBelongingToEvent[$eventId][$userId] = !empty($event);
7205 }
7206
7207 return self::$attendeeBelongingToEvent[$eventId][$userId];
7208 }
7209
7210 public static function getLimitDates(int $yearFrom, int $monthFrom, int $yearTo, int $monthTo): array
7211 {
7212 $userTimezoneName = \CCalendar::GetUserTimezoneName(\CCalendar::GetUserId());
7213 $offset = Util::getTimezoneOffsetUTC($userTimezoneName);
7214
7215 return [
7216 'from' => \CCalendar::Date(mktime(0, 0, 0, $monthFrom, 1, $yearFrom) - $offset, false),
7217 'to' => \CCalendar::Date(mktime(0, 0, 0, $monthTo, 1, $yearTo) - $offset, false),
7218 ];
7219 }
7220
7221 private static function getOpenEventSection(?int $userId = null): ?Section
7222 {
7223 if (!(OpenEvents\Feature::getInstance()->isAvailable($userId)))
7224 {
7225 return null;
7226 }
7227
7228 if (self::$openEventSection !== false)
7229 {
7230 return self::$openEventSection;
7231 }
7232
7234 $mapperFactory = ServiceLocator::getInstance()->get('calendar.service.mappers.factory');
7235
7236 $openEventSection = $mapperFactory->getSection()->getMap([
7237 '=CAL_TYPE' => Dictionary::CALENDAR_TYPE['open_event'],
7238 ])->fetch();
7239
7240 self::$openEventSection = $openEventSection;
7241
7242 return $openEventSection;
7243 }
7244
7245 private static function getParentCollabConnections(array $eventList): array
7246 {
7247 $parentIds = [];
7248 foreach ($eventList as $event)
7249 {
7250 $collabEventTypes = [
7251 Dictionary::EVENT_TYPE['collab'],
7252 Dictionary::EVENT_TYPE['shared_collab'],
7253 ];
7254
7255 $isCollabEvent = (
7256 !empty($event['EVENT_TYPE'])
7257 && in_array($event['EVENT_TYPE'], $collabEventTypes, true)
7258 );
7259
7260 if (!$isCollabEvent || $event['ID'] === $event['PARENT_ID'])
7261 {
7262 continue;
7263 }
7264
7265 $parentIds[$event['PARENT_ID']] = (int)$event['PARENT_ID'];
7266 }
7267
7268 if (empty($parentIds))
7269 {
7270 return $parentIds;
7271 }
7272
7273 $cachedCollabIdsByParent = array_intersect_key(self::$collabIdByParent, $parentIds);
7274 $cachedParentIds = array_keys($cachedCollabIdsByParent);
7275
7276 $nonCachedParentIds = array_diff($parentIds, $cachedParentIds);
7277
7278 if (empty($nonCachedParentIds))
7279 {
7280 return $cachedCollabIdsByParent;
7281 }
7282
7283 $eventCollection =
7284 Internals\EventTable::query()
7285 ->whereIn('ID', $nonCachedParentIds)
7286 ->where('CAL_TYPE', Dictionary::CALENDAR_TYPE['group'])
7287 ->setSelect(['ID', 'PARENT_ID', 'OWNER_ID'])
7288 ->fetchCollection()
7289 ;
7290
7291 $nonCachedCollabIdsByParent = array_combine(
7292 $eventCollection->getParentIdList(),
7293 $eventCollection->getOwnerIdList(),
7294 );
7295
7296 self::$collabIdByParent += $nonCachedCollabIdsByParent;
7297
7298 return $cachedCollabIdsByParent + $nonCachedCollabIdsByParent;
7299 }
7300
7301 private static function getCollabIdByEvent(array $event, array $parentCollabConnections): ?int
7302 {
7303 if (
7304 !($event['EVENT_TYPE'] ?? null)
7305 || !in_array(
7306 $event['EVENT_TYPE'],
7307 [Dictionary::EVENT_TYPE['collab'], Dictionary::EVENT_TYPE['shared_collab']],
7308 true
7309 )
7310 )
7311 {
7312 return null;
7313 }
7314
7315 $collabId = null;
7316 $parentId = (int)$event['PARENT_ID'];
7317 if ((int)$event['ID'] !== $parentId && !empty($parentCollabConnections[$parentId]))
7318 {
7319 $collabId = $parentCollabConnections[$parentId];
7320 }
7321 else if ($event['CAL_TYPE'] === Dictionary::CALENDAR_TYPE['group'])
7322 {
7323 $collabId = (int)$event['OWNER_ID'];
7324 }
7325
7326 return $collabId;
7327 }
7328
7337 private static function getCollabIdByParentId(int $parentId): int
7338 {
7339 if (isset(self::$collabIdByParent[$parentId]))
7340 {
7341 return self::$collabIdByParent[$parentId];
7342 }
7343
7344 $result = Internals\EventTable::query()
7345 ->setSelect(['ID', 'CAL_TYPE', 'OWNER_ID'])
7346 ->where('CAL_TYPE', Dictionary::CALENDAR_TYPE['group'])
7347 ->where('ID', $parentId)
7348 ->setLimit(1)
7349 ->exec()->fetch()
7350 ;
7351
7352 self::$collabIdByParent[$parentId] = (int)($result['OWNER_ID'] ?? 0);
7353
7354 return self::$collabIdByParent[$parentId];
7355 }
7356
7365 private static function checkEventAccessFromGetList(array $event, int $sectionId, int $userId): array
7366 {
7367 if (isset(self::$getListAccessCheck[$sectionId]))
7368 {
7369 return self::$getListAccessCheck[$sectionId];
7370 }
7371
7372 $eventModel = EventModel::createFromArray($event);
7373 $eventModel->setSectionId($sectionId);
7374
7375 $accessController = new EventAccessController($userId);
7376 $request = [
7377 ActionDictionary::ACTION_EVENT_VIEW_FULL => [],
7378 ActionDictionary::ACTION_EVENT_VIEW_TIME => [],
7379 ActionDictionary::ACTION_EVENT_VIEW_TITLE => [],
7380 ];
7381
7382 self::$getListAccessCheck[$sectionId] = $accessController->batchCheck($request, $eventModel);
7383
7384 return self::$getListAccessCheck[$sectionId];
7385 }
7386
7387 private static function getAttendeeStatuses(
7388 $attendees,
7389 $fields,
7390 $params,
7391 $currentAttendeesIndex,
7392 $isNewEvent
7393 ): array
7394 {
7395 $result = [];
7396
7397 foreach ($attendees as $attendee)
7398 {
7399 $attendeeId = (int)$attendee;
7400 $meetingStatus = 'Q';
7401 $sendInvitations = false;
7402
7403 if (
7404 (int)$fields['OWNER_ID'] === $attendeeId
7405 || (int)$fields['CREATED_BY'] === $attendeeId
7406 )
7407 {
7408 $meetingStatus = 'H';
7409 }
7410 elseif ($isNewEvent && (int)($fields['~MEETING']['MEETING_CREATOR'] ?? null) === $attendeeId)
7411 {
7412 $meetingStatus = 'Y';
7413 }
7414 elseif (
7415 !empty($params['saveAttendeesStatus'])
7416 && !empty($params['currentEvent']['ATTENDEE_LIST'])
7417 && is_array($params['currentEvent']['ATTENDEE_LIST'])
7418 )
7419 {
7420 foreach($params['currentEvent']['ATTENDEE_LIST'] as $currentAttendee)
7421 {
7422 if ((int)$currentAttendee['id'] === $attendeeId)
7423 {
7424 $meetingStatus = $currentAttendee['status'];
7425 break;
7426 }
7427 }
7428 }
7429
7430 if (
7431 !empty($currentAttendeesIndex[$attendeeId])
7432 && $params['sendInvitesToDeclined']
7433 && $meetingStatus === 'N'
7434 )
7435 {
7436 $meetingStatus = 'Q';
7437 $sendInvitations = true;
7438 }
7439
7440 $result[$attendeeId] = [
7441 'id' => $attendeeId,
7442 'status' => $meetingStatus,
7443 'sendInvitations' => $sendInvitations
7444 ];
7445 }
7446
7447 return $result;
7448 }
7449}
$connection
Определения actionsdefinitions.php:38
$count
Определения admin_tab.php:4
$type
Определения options.php:106
const BX_ROOT
Определения bx_root.php:3
$accessController
Определения options.php:23
if(!Loader::includeModule('catalog')) if(!AccessController::getCurrent() ->check(ActionDictionary::ACTION_PRICE_EDIT)) if(!check_bitrix_sessid()) $request
Определения catalog_reindex.php:36
if(!is_object($USER)||! $USER->IsAuthorized()) $userId
Определения check_mail.php:18
const TYPE
Определения manager.php:25
static setEventIdForLocation(int $id, ?string $location=null)
Определения manager.php:472
const SYNC_STATUS
Определения dictionary.php:16
static get($userId=null)
Определения usersettings.php:86
Определения util.php:21
static getTimezoneOffsetUTC(string $timezoneName)
Определения util.php:767
static addPullEvent(PushCommand $command, int $userId, array $params=[])
Определения util.php:385
static formatDateTimestampUTC(int $timestamp)
Определения util.php:760
static isCollabUser(int $userId)
Определения util.php:337
static get($moduleId, $name, $default="", $siteId=false)
Определения option.php:30
static getInstance()
Определения eventmanager.php:31
Определения loader.php:13
static includeModule($moduleName)
Определения loader.php:67
static getEntity()
Определения datamanager.php:65
static getList(array $parameters=array())
Определения datamanager.php:431
static on()
Определения join.php:34
const TYPE_INNER
Определения join.php:20
Определения emoji.php:10
static encode($text)
Определения emoji.php:17
Определения date.php:9
Определения user.php:48
static encode($data, $options=null)
Определения json.php:22
static CheckFields(&$arFields, $currentEvent=[], $userId=false)
Определения calendar_event.php:2956
static ParseText($text="", $eventId=0, $arUFWDValue=[])
Определения calendar_event.php:2330
static getLimitDates(int $yearFrom, int $monthFrom, int $yearTo, int $monthTo)
Определения calendar_event.php:7210
static MoveCommentsToFirstRecurrence(array $event, string $currentEntityXmlId)
Определения calendar_event.php:5639
static SetExDate($exDate=[], $untilTimestamp=false)
Определения calendar_event.php:5497
static checkLocationOccupancy(mixed $entryFields, array $params, mixed $currentEvent, int $userId)
Определения calendar_event.php:6466
static sortByDay(array $byDay)
Определения calendar_event.php:6892
static getEventForEditInterface($entryId, $params=[])
Определения calendar_event.php:6381
static $defaultSelectEvent
Определения calendar_event.php:64
static $TextParser
Определения calendar_event.php:49
static getEventForViewInterface($entryId, $params=[])
Определения calendar_event.php:6200
static updateSyncStatus(int $eventId, string $status)
Определения calendar_event.php:6698
static GetExDate($exDate='')
Определения calendar_event.php:2914
static GetCount()
Определения calendar_event.php:6155
static SetMeetingStatusForRecurrenceEvents(int $recurrenceId, int $userId, int $eventId, string $status, ?bool $updateDescription=null,)
Определения calendar_event.php:5003
static handleAccessCodes($accessCodes=[], $params=[])
Определения calendar_event.php:6444
static getUsersDetails($userIdList=[], $params=[])
Определения calendar_event.php:1941
static prepareArFieldBeforeSyncEvent(array &$childParams)
Определения calendar_event.php:6732
static getAttendeeList($entryIdList=[], array $openEventIdList=[])
Определения calendar_event.php:1810
static getClosestRepetitionTs(int $limitFromTS, int $evFromTS, array $rrule)
Определения calendar_event.php:2660
static getCrmElementTitle(string $item)
Определения calendar_event.php:6146
static IsBrokenEventOfSeries(array $event)
Определения calendar_event.php:5592
static CleanUpBrokenRecursiveExclusion(string $commentXmlId)
Определения calendar_event.php:5618
static DeleteEmpty(int $sectionId)
Определения calendar_event.php:5356
static GetAttendeeIds(array $eventIdList)
Определения calendar_event.php:2034
static CheckRRULE($recRule=[])
Определения calendar_event.php:93
static prepareToken($token)
Определения calendar_event.php:6185
static CheckRecurcion($event)
Определения calendar_event.php:2325
static GetEventFromToForUser($params, $userId)
Определения calendar_event.php:3870
static formatSearchIndexContent($entry=[])
Определения calendar_event.php:6052
static UpdateParentEventExDate($recurrenceId, $exDate, $attendeeIds)
Определения calendar_event.php:3836
static checkLocationField($location, $isNewEvent)
Определения calendar_event.php:6712
static FixCommentsIfEventIsBroken(array $event)
Определения calendar_event.php:5552
static CleanEventsWithDeadParents()
Определения calendar_event.php:5405
static GetById($id, bool $checkPermissions=true, $loadOriginalRecursion=false)
Определения calendar_event.php:806
static ParseRecursion(&$res, $event, $params=[])
Определения calendar_event.php:2381
static tryingToFindEventSection(bool $isNewEvent, mixed $entryFields, int $userId, mixed $currentEvent)
Определения calendar_event.php:6516
static SetMeetingStatus($params)
Определения calendar_event.php:4429
static isFullTextIndexEnabled()
Определения calendar_event.php:6190
static GetList($params=[])
Определения calendar_event.php:830
$getListAccessCheck
Определения calendar_event.php:61
static convertDateToCulture(string $str)
Определения calendar_event.php:2148
static getEventModelForPermissionCheck(int $eventId, array $event=[], int $userId=0)
Определения calendar_event.php:7118
static getSearchIndexContent($eventId)
Определения calendar_event.php:5978
static GetEventUserFields($event)
Определения calendar_event.php:5481
static ParseRRULE($rule=null)
Определения calendar_event.php:2732
static calculateUntilForCountRRule(array $arFields, int $offsetTs=0)
Определения calendar_event.php:7003
static ApplyAccessRestrictions($event, $userId=null)
Определения calendar_event.php:2064
static getFreqDuration(string $freq)
Определения calendar_event.php:2712
static UpdateUserFields($eventId, $arFields=[], $updateSearchIndex=true)
Определения calendar_event.php:3924
static ConnectEventToSection($eventId, $sectionId)
Определения calendar_event.php:1794
static updateEventFields(array $event, array $fields)
Определения calendar_event.php:6673
static getUserIndex()
Определения calendar_event.php:6195
static checkAttendeeBelongsToEvent($eventId, $userId)
Определения calendar_event.php:7179
$openEventSection
Определения calendar_event.php:60
static GetAttendees($eventIdList=[], $checkDeleted=true)
Определения calendar_event.php:1974
static CheckEntryChanges($newFields=[], $currentFields=[])
Определения calendar_event.php:3153
static CleanUpBrokenRecursiveEvent(array $event)
Определения calendar_event.php:5597
static ExtractDateFromCommentXmlId($xmlId='')
Определения calendar_event.php:5699
static OnPullPrepareArFields($arFields=[])
Определения calendar_event.php:3894
static updateColor($eventId, $color='')
Определения calendar_event.php:6169
static GetAbsent($users=null, $params=[])
Определения calendar_event.php:5219
static GetEventsByRecId($recurrenceId, $checkPermissions=true, $userId=null)
Определения calendar_event.php:5519
static GetAccessibilityForUsers($params=[])
Определения calendar_event.php:5075
static $sendPush
Определения calendar_event.php:50
static getEventPermissions(array $event, int $userId=0)
Определения calendar_event.php:7085
static getUFForParseText($eventId=0, $arUFWDValue=[])
Определения calendar_event.php:2363
static GetEventCommentXmlId($event)
Определения calendar_event.php:5671
static GetMeetingStatus($userId, $eventId)
Определения calendar_event.php:5037
static Edit($params=[])
Определения calendar_event.php:121
static ExcludeInstance($eventId, $excludeDate)
Определения calendar_event.php:5865
static generateUserIndex(array $involvedAttendees, $meetingHost)
Определения calendar_event.php:6582
static getDiskUFFileNameList($valueList=[])
Определения calendar_event.php:5934
static CanView($eventId, $userId)
Определения calendar_event.php:5428
static SetMeetingStatusEx($params)
Определения calendar_event.php:4330
static GetRRULEDescription($event, $html=false, $showUntil=true, $languageId=null)
Определения calendar_event.php:5713
static updateSearchIndex($eventIdList=[], $params=[])
Определения calendar_event.php:5996
static IsExchangeEnabled($userId=false)
Определения calendar.php:5301
static UpdateCounter($users=false, array $eventIds=[], array $groupIds=[])
Определения calendar.php:4631
static ThrowError($str)
Определения calendar.php:3286
static isDaylightSavingTimezone(string $timezoneId)
Определения calendar.php:1528
static GetDayLen()
Определения calendar.php:1852
static GetTextLocation($loc='')
Определения calendar.php:1801
static IsCalDAVEnabled()
Определения calendar.php:5356
static GetCurUserId($refresh=false)
Определения calendar.php:4698
static Date($timestamp, $bTime=true, $bRound=true, $bCutSeconds=false)
Определения calendar.php:5712
static GetMeetingSection($userId, $autoCreate=false)
Определения calendar.php:5748
static GetMaxDate()
Определения calendar.php:4101
static Timestamp($date, $bRound=true, $bTime=true)
Определения calendar.php:5219
static ClearCache($arPath=[])
Определения calendar.php:3478
static GetTimezoneOffset($timezoneId, $dateTimestamp=false)
Определения calendar.php:1508
static DeleteEvent($id, $doExternalSync=true, $params=[])
Определения calendar.php:1043
static OnChangeMeetingStatusEventEntry($params)
Определения calendar_livefeed.php:1148
static OnDeleteCalendarEventEntry($eventId)
Определения calendar_livefeed.php:1052
static OnEditCalendarEventEntry($params)
Определения calendar_livefeed.php:764
static Send($params)
Определения calendar_notify.php:13
static ClearNotifications($eventId=false, $userId=false)
Определения calendar_notify.php:948
static prepareReminder($reminder=[])
Определения calendar_reminder.php:431
static updateReminders($params=[])
Определения calendar_reminder.php:235
static UpdateModificationLabel($arId=[])
Определения calendar_sect.php:1999
static GetSectionForOwner($type, $ownerId, $autoCreate=true)
Определения calendar_sect.php:2097
static DoDeleteToDav($params, $event)
Определения calendar_sync.php:530
static DoSaveToDav(&$arFields, $params=[], $event=false)
Определения calendar_sync.php:322
static DeleteBySubTag($notifySubTag, $authorId=false, $pullActive=true)
Определения im_notify.php:1531
Определения textparser.php:21
static Disable()
Определения time.php:31
static Enable()
Определения time.php:36
global $CACHE_MANAGER
Определения clear_component_cache.php:7
$str
Определения commerceml2.php:63
$content
Определения commerceml.php:144
$arFields
Определения dblapprove.php:5
$userList
Определения discount_coupon_list.php:276
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
Определения file_new.php:804
$res
Определения filter_act.php:7
$perm
Определения options.php:169
global $USER_FIELD_MANAGER
Определения attempt.php:6
$result
Определения get_property_values.php:14
$query
Определения get_search.php:11
$filter
Определения iblock_catalog_list.php:54
$filterFields
Определения iblock_catalog_list.php:55
$selectFields
Определения iblock_catalog_list.php:160
$_SERVER["DOCUMENT_ROOT"]
Определения cron_frame.php:9
global $DB
Определения cron_frame.php:29
$context
Определения csv_new_setup.php:223
if(!is_null($config))($config as $configItem)(! $configItem->isVisible()) $code
Определения options.php:195
const FORMAT_DATETIME
Определения include.php:64
$status
Определения session.php:10
ExecuteModuleEventEx($arEvent, $arParams=[])
Определения tools.php:5214
FormatDate($format="", $timestamp=false, $now=false, ?string $languageId=null)
Определения tools.php:871
GetModuleEvents($MODULE_ID, $MESSAGE_ID, $bReturnArray=false)
Определения tools.php:5177
IncludeModuleLangFile($filepath, $lang=false, $bReturnArray=false)
Определения tools.php:3778
GetMessage($name, $aReplace=null)
Определения tools.php:3397
$name
Определения menu_edit.php:35
$map
Определения config.php:5
$value
Определения Param.php:39
int $chatId
Определения Param.php:36
toArray(bool $recursive=false)
Определения ActiveRecordImplementation.php:562
Определения culture.php:9
Определения ufield.php:9
string $sectionId
Определения columnfields.php:71
Определения chain.php:3
Определения cookie.php:3
$user
Определения mysql_to_pgsql.php:33
$order
Определения payment.php:8
$year
Определения payment.php:9
$email
Определения payment.php:49
$message
Определения payment.php:8
$settings
Определения product_settings.php:43
$event
Определения prolog_after.php:141
$delta
Определения prolog_main_admin.php:363
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
Определения prolog_main_admin.php:393
if(empty($signedUserToken)) $key
Определения quickway.php:257
$text
Определения template_pdf.php:79
</p ></td >< td valign=top style='border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0cm 2.0pt 0cm 2.0pt;height:9.0pt'>< p class=Normal align=center style='margin:0cm;margin-bottom:.0001pt;text-align:center;line-height:normal'>< a name=ТекстовоеПоле54 ></a ><?=($taxRate > count( $arTaxList) > 0) ? $taxRate."%"
Определения waybill.php:936
if($inWords) echo htmlspecialcharsbx(Number2Word_Rus(roundEx($totalVatSum $params['CURRENCY']
Определения template.php:799
else $a
Определения template.php:137
$title
Определения pdf.php:123
$val
Определения options.php:1793
$location
Определения options.php:2729
$matches
Определения index.php:22
$rows
Определения options.php:264
$arFilter
Определения user_search.php:106
$fields
Определения yandex_run.php:501