1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
calendar.php
См. документацию.
1<?php
2
4
33use Bitrix\Main;
43use Bitrix\Tasks\Internals\Task\Status;
44use Bitrix\Tasks\Provider\TaskList;
45use Bitrix\Tasks\Provider\TaskQuery;
46
47IncludeModuleLangFile(__FILE__);
48
50{
51 const
54 DEFAULT_TASK_COLOR = '#ff5b55',
55 TASK_SECTION_ID = '1_tasks',
57 DAY_LENGTH = 86400; // 60 * 60 * 24
58
59 public const EDIT_PREFIX = 'EDIT';
60
61 private static
62 $id = false,
63 $instance,
64 $CALENDAR_MAX_DATE,
65 $CALENDAR_MIN_DATE,
66 $type,
67 $arTypes,
68 $ownerId = 0,
69 $settings,
70 $siteId,
71 $userSettings = [],
72 $pathToUser,
73 $bOwner,
74 $userId,
75 $curUserId,
76 $userMeetingSection,
77 $meetingSections = [],
78 $crmSections = [],
79 $offset,
80 $arTimezoneOffsets = [],
81 $perm = [],
82 $isArchivedGroup = false,
83 $userNameTemplate = "#NAME# #LAST_NAME#",
84 $bSuperpose,
85 $bExtranet,
86 $bIntranet,
87 $bWebservice,
88 $userTimezoneList = [],
89 $showTasks,
90 $viewTaskPath = '',
91 $editTaskPath = '',
92 $actionUrl,
93 $path = '',
94 $outerUrl,
95 $accessNames = [],
96 $bSocNet,
97 $bAnonym,
98 $allowReserveMeeting = true,
99 $SectionsControlsDOMId = 'sidebar',
100 $arAccessTask = [],
101 $ownerNames = [],
102 $cachePath = "calendar/",
103 $cacheTime = 2592000, // 30 days by default
104 $bCache = true,
105 $readOnly,
106 $pathesForSite = false,
107 $pathes = [], // links for several sites
108 $arUserDepartment = [],
109 $bAMPM = false,
110 $bWideDate = false,
111 $arExchEnabledCache = [],
112 $silentErrorMode = false,
113 $weekStart,
114 $bCurUserSocNetAdmin,
115 $serverPath,
116 $pathesList = array('path_to_user','path_to_user_calendar','path_to_group','path_to_group_calendar','path_to_vr','path_to_rm'),
117 $pathesListEx = null,
118 $isGoogleApiEnabled = null,
119 $isOffice365ApiEnabled = null,
120 $errors = [],
121 $timezones = [],
122 $userLanguageId = [],
123 $userList = []
124 ;
125
126 function Init($params)
127 {
128 global $USER, $APPLICATION;
129 $access = new CAccess();
130 $access->UpdateCodes();
131 if (!$USER || !is_object($USER))
132 {
133 $USER = new CUser;
134 }
135 // Owner params
136 self::$siteId = $params['siteId'] ?? SITE_ID;
137 self::$type = $params['type'];
138 self::$arTypes = CCalendarType::GetList();
139 self::$bIntranet = self::IsIntranetEnabled();
140 self::$bSocNet = self::IsSocNet();
141 self::$userId = (isset($params['userId']) && $params['userId'] > 0) ? (int)$params['userId'] : self::GetCurUserId(true);
142 self::$bOwner = self::$type === 'user' || self::$type === 'group';
143 self::$settings = self::GetSettings();
144 self::$userSettings = Bitrix\Calendar\UserSettings::get();
145 self::$pathesForSite = self::GetPathes(self::$siteId);
146 self::$pathToUser = self::$pathesForSite['path_to_user'];
147 self::$bSuperpose = $params['allowSuperpose'] != false && self::$bSocNet;
148 self::$bAnonym = !$USER || !$USER->IsAuthorized();
149 self::$userNameTemplate = self::$settings['user_name_template'];
150 self::$bAMPM = IsAmPmMode();
151 self::$bWideDate = str_contains(FORMAT_DATETIME, 'MMMM');
152 self::$id = $this->GetId();
153
154 if (isset($params['SectionControlsDOMId']))
155 {
156 self::$SectionsControlsDOMId = $params['SectionControlsDOMId'];
157 }
158
159 if (self::$bOwner && isset($params['ownerId']) && $params['ownerId'] > 0)
160 {
161 self::$ownerId = (int)$params['ownerId'];
162 }
163
164 self::$showTasks = (self::$type === 'user' || self::$type === 'group')
165 && !Util::isCollabUser(self::$userId)
166 && ($params['showTasks'] ?? '') !== false
167 && $params['viewTaskPath']
168 && Loader::includeModule('tasks')
169 && self::$userSettings['showTasks'] !== 'N'
170 ;
171
172 if (self::$showTasks)
173 {
174 self::$viewTaskPath = $params['viewTaskPath'];
175 self::$editTaskPath = $params['editTaskPath'];
176 }
177
178 self::GetPermissions(array(
179 'type' => self::$type,
180 'bOwner' => self::$bOwner,
181 'userId' => self::$userId,
182 'ownerId' => self::$ownerId,
183 ));
184
185 // Cache params
186 if (isset($params['cachePath']))
187 {
188 self::$cachePath = $params['cachePath'];
189 }
190 if (isset($params['cacheTime']))
191 {
192 self::$cacheTime = $params['cacheTime'];
193 }
194 self::$bCache = self::$cacheTime > 0;
195
196 // Urls
197 $page = preg_replace(
198 [
199 "/EVENT_ID=.*?\&/i",
200 "/EVENT_DATE=.*?\&/i",
201 "/CHOOSE_MR=.*?\&/i",
202 "/action=.*?\&/i",
203 "/bx_event_calendar_request=.*?\&/i",
204 "/clear_cache=.*?\&/i",
205 "/bitrix_include_areas=.*?\&/i",
206 "/bitrix_show_mode=.*?\&/i",
207 "/back_url_admin=.*?\&/i",
208 "/IFRAME=.*?\&/i",
209 "/IFRAME_TYPE=.*?\&/i",
210 ],
211 "", $params['pageUrl'].'&'
212 );
213 $page = preg_replace(
214 [
215 "/^(.*?)&$/i",
216 "/^(.*?)\?$/i",
217 ],
218 "\$1", $page
219 );
220 self::$actionUrl = $page;
221
222 self::$path = empty(self::$ownerId)
223 ? self::GetServerPath().$page
224 : self::GetPath(self::$type, self::$ownerId, true);
225
226 self::$outerUrl = $APPLICATION->GetCurPageParam('', [
227 "action",
228 "bx_event_calendar_request",
229 "clear_cache",
230 "bitrix_include_areas",
231 "bitrix_show_mode",
232 "back_url_admin",
233 "EVENT_ID",
234 "EVENT_DATE",
235 "CHOOSE_MR",
236 ], false);
237
238 // *** Meeting room params ***
239 $RMiblockId = self::$settings['rm_iblock_id'];
240 self::$allowReserveMeeting = $params["allowResMeeting"] && $RMiblockId > 0;
241
242 if(self::$allowReserveMeeting && !$USER->IsAdmin() && (CIBlock::GetPermission($RMiblockId) < "R"))
243 {
244 self::$allowReserveMeeting = false;
245 }
246 }
247
248 public function Show($params = [])
249 {
250 global $APPLICATION;
251 $arType = false;
252
253 foreach(self::$arTypes as $type)
254 {
255 if(self::$type === $type['XML_ID'])
256 {
257 $arType = $type;
258 }
259 }
260
261 if (!$arType)
262 {
263 $APPLICATION->ThrowException('[EC_WRONG_TYPE] '.Loc::getMessage('EC_WRONG_TYPE'), 'calendar_wrong_type');
264 return false;
265 }
266
267 $init_month = false;
268 $init_year = false;
269 $startupEvent = false;
270 //Show new event dialog
271 if (isset($_GET['EVENT_ID']))
272 {
273 if($this->doOpenEventInEditMode($_GET['EVENT_ID']))
274 {
275 $eventId = $this->getEditEventId($_GET['EVENT_ID']);
276 $startupEvent = self::GetStartUpEvent($eventId);
277 if ($startupEvent)
278 {
279 $startupEvent['EDIT'] = true;
280
281 if ($startupEvent['DT_FROM'] ?? false)
282 {
283 $ts = self::Timestamp($startupEvent['DT_FROM']);
284 $init_month = date('m', $ts);
285 $init_year = date('Y', $ts);
286 }
287 }
288 }
289 // Show popup event at start
290 else
291 {
292 $eventId = (int)$_GET['EVENT_ID'];
293 $isSharing = (bool)($_GET['IS_SHARING'] ?? null) === true;
294 $startupEvent = self::GetStartUpEvent($eventId, $isSharing);
295 if ($startupEvent)
296 {
297 $eventFromTs = self::Timestamp($startupEvent['DATE_FROM']);
298 $currentDateTs = self::Timestamp($_GET['EVENT_DATE'] ?? null);
299
300 if ($currentDateTs > $eventFromTs)
301 {
302 $startupEvent['~CURRENT_DATE'] = self::Date($currentDateTs, false);
303 $init_month = date('m', $currentDateTs);
304 $init_year = date('Y', $currentDateTs);
305 }
306 else
307 {
308 $init_month = date('m', $eventFromTs);
309 $init_year = date('Y', $eventFromTs);
310 }
311 }
312 }
313 }
314
315 if (!$init_month && !$init_year && ($params["initDate"] ?? false) <> '' && mb_strpos($params["initDate"], '.') !== false)
316 {
317 $ts = self::Timestamp($params["initDate"]);
318 $init_month = date('m', $ts);
319 $init_year = date('Y', $ts);
320 }
321
322 if (!$init_month)
323 {
324 $init_month = date("m");
325 }
326 if (!$init_year)
327 {
328 $init_year = date("Y");
329 }
330
331 $id = $this->GetId();
332
333 $weekHolidays = [];
334 if (isset(self::$settings['week_holidays']))
335 {
336 $days = array('MO' => 0, 'TU' => 1, 'WE' => 2,'TH' => 3,'FR' => 4,'SA' => 5,'SU' => 6);
337 foreach(self::$settings['week_holidays'] as $day)
338 {
339 $weekHolidays[] = $days[$day];
340 }
341 }
342 else
343 {
344 $weekHolidays = array(5, 6);
345 }
346
347 $yearHolidays = [];
348 if (isset(self::$settings['year_holidays']))
349 {
350 foreach(explode(',', self::$settings['year_holidays']) as $date)
351 {
352 $date = trim($date);
353 $ardate = explode('.', $date);
354 if (count($ardate) == 2 && $ardate[0] && $ardate[1])
355 {
356 $yearHolidays[] = (int)$ardate[0] . '.' . ((int)$ardate[1] - 1);
357 }
358 }
359 }
360
361 $yearWorkdays = [];
362 if (isset(self::$settings['year_workdays']))
363 {
364 foreach(explode(',', self::$settings['year_workdays']) as $date)
365 {
366 $date = trim($date);
367 $ardate = explode('.', $date);
368 if (count($ardate) === 2 && $ardate[0] && $ardate[1])
369 {
370 $yearWorkdays[] = (int)$ardate[0] . '.' . ((int)$ardate[1] - 1);
371 }
372 }
373 }
374
375 $isPersonalCalendarContext = self::IsPersonal(self::$type, self::$ownerId, self::$userId);
376 $bExchange = self::IsExchangeEnabled() && self::$type === 'user';
377 $bExchangeConnected = $bExchange && CDavExchangeCalendar::IsExchangeEnabledForUser(self::$ownerId);
378 $bWebservice = self::IsWebserviceEnabled();
379 $bExtranet = self::IsExtranetEnabled();
380 $isExtranetUser = Loader::includeModule('intranet') && !\Bitrix\Intranet\Util::isIntranetUser();
381 $isCollabUser = Util::isCollabUser(self::$userId);
382
383 $userTimezoneOffsetUTC = self::GetCurrentOffsetUTC(self::$userId);
384 $userTimezoneName = self::GetUserTimezoneName(self::$userId, false);
385 $userTimezoneDefault = '';
386
387 // We don't have default timezone for this offset for this user
388 // We will ask him but we should suggest some suitable for his offset
389 if (!$userTimezoneName)
390 {
391 $userTimezoneDefault = self::GetGoodTimezoneForOffset($userTimezoneOffsetUTC);
392 }
393
394 $JSConfig = [
395 'id' => $id,
396 'type' => self::$type,
397 'userId' => self::$userId,
398 'userName' => self::GetUserName(self::$userId), // deprecated
399 'ownerId' => self::$ownerId,
400 'ownerName' => self::GetOwnerName(self::$type, self::$ownerId),
401 'user' => [
402 'id' => self::$userId,
403 'name' => self::GetUserName(self::$userId),
404 'url' => self::GetUserUrl(self::$userId),
405 'avatar' => self::GetUserAvatarSrc(self::$userId),
406 'smallAvatar' => self::GetUserAvatarSrc(self::$userId, array('AVATAR_SIZE' => 18)),
407 ],
408 'perm' => $arType['PERM'], // Permissions from type
409 'locationAccess' => Rooms\Util::getLocationAccess(self::$userId),
410 'permEx' => self::$perm,
411 'showTasks' => self::$showTasks,
412 'sectionControlsDOMId' => self::$SectionsControlsDOMId,
413 'week_holidays' => $weekHolidays,
414 'year_holidays' => $yearHolidays,
415 'year_workdays' => $yearWorkdays,
416 'init_month' => $init_month,
417 'init_year' => $init_year,
418 'pathToUser' => self::$pathToUser,
419 'path' => self::$path,
420 'actionUrl' => self::$actionUrl,
421 'settings' => self::$settings,
422 'userSettings' => self::$userSettings,
423 'bAnonym' => self::$bAnonym,
424 'bIntranet' => self::$bIntranet,
425 'bWebservice' => $bWebservice,
426 'bExtranet' => $bExtranet,
427 'bSocNet' => self::$bSocNet,
428 'bExchange' => $bExchangeConnected,
429 'startupEvent' => $startupEvent,
430 'workTime' => [self::$settings['work_time_start'], self::$settings['work_time_end']], // Decrecated !!
431 'userWorkTime' => [self::$settings['work_time_start'], self::$settings['work_time_end']],
433 'RMiblockId' => self::$settings['rm_iblock_id'],
434 'pathToMR' => self::$pathesForSite['path_to_rm'],
435 ]),
436 'allowResMeeting' => self::$allowReserveMeeting,
437 'bAMPM' => self::$bAMPM,
438 'WDControllerCID' => 'UFWD' . $id,
439 'userTimezoneOffsetUTC' => $userTimezoneOffsetUTC,
440 'userTimezoneName' => $userTimezoneName,
441 'userTimezoneDefault' => $userTimezoneDefault,
442 'sectionCustomization' => UserSettings::getSectionCustomization(self::$userId),
443 'locationFeatureEnabled' => !$isCollabUser && Bitrix24Manager::isFeatureEnabled(FeatureDictionary::CALENDAR_LOCATION),
444 'plannerFeatureEnabled' => Bitrix24Manager::isPlannerFeatureEnabled(),
445 'eventWithEmailGuestEnabled' => Bitrix24Manager::isFeatureEnabled(FeatureDictionary::CALENDAR_EVENTS_WITH_EMAIL_GUESTS),
446 'sharingFeatureLimitEnable' => Bitrix24Manager::isFeatureEnabled(FeatureDictionary::CALENDAR_SHARING),
447 'projectFeatureEnabled' => false,
448 'isSharingFeatureEnabled' => \Bitrix\Calendar\Sharing\SharingFeature::isEnabled(),
449 'payAttentionToNewSharingFeature' => \Bitrix\Calendar\Sharing\Helper::payAttentionToNewSharingFeature(),
450 'showAfterSyncAccent' => isset($_GET['googleAuthSuccess']) && $_GET['googleAuthSuccess'] === 'y',
451 'isExtranetUser' => $isExtranetUser,
452 'isGoogleApplicationRefused' => COption::GetOptionString('calendar', 'isGoogleApplicationRefused', 'N'),
453 'showGoogleApplicationRefused' => CUserOptions::getOption('calendar', 'showGoogleApplicationRefused', 'Y'),
454 'useAirDesign' => defined('AIR_SITE_TEMPLATE'),
455 'isBitrix24Template' => SITE_TEMPLATE_ID === 'bitrix24',
456 ];
457
458 if (Loader::includeModule('socialnetwork'))
459 {
461
462 $JSConfig['projectFeatureEnabled'] =
465 ;
466 }
467
468 if (self::$type === 'user' && (int)self::$userId !== (int)self::$ownerId)
469 {
470 $JSConfig['ownerUser'] = array(
471 'id' => self::$ownerId,
472 'name' => self::GetUserName(self::$ownerId),
473 'url' => self::GetUserUrl(self::$ownerId),
474 'avatar' => self::GetUserAvatarSrc(self::$ownerId),
475 'smallAvatar' => self::GetUserAvatarSrc(self::$ownerId, array('AVATAR_SIZE' => 18)),
476 );
477 }
478
479 $culture = \Bitrix\Main\Context::getCurrent()?->getCulture();
480
481 $JSConfig['dayOfWeekMonthFormat'] = $culture?->getDayOfWeekMonthFormat();
482 $JSConfig['dayMonthFormat'] = $culture?->getDayMonthFormat();
483 $JSConfig['longDateFormat'] = $culture?->getLongDateFormat();
484
485 $placementParams = false;
486 if (Loader::includeModule('rest'))
487 {
488 $placementParams = [
489 'gridPlacementCode' => \CCalendarRestService::PLACEMENT_GRID_VIEW,
491 'serviceUrl' => '/bitrix/components/bitrix/app.layout/lazyload.ajax.php?&site='.SITE_ID.'&'.bitrix_sessid_get(),
492 ];
493 }
494 $JSConfig['placementParams'] = $placementParams;
495
496 $isGroupCalendar = self::$type === Core\Event\Tools\Dictionary::CALENDAR_TYPE['group'];
497 $isCollabCalendar = $isGroupCalendar && Collabs::getInstance()->getCollabIfExists(self::$ownerId);
498
499 if (
500 (self::$type === Dictionary::CALENDAR_TYPE['user'] && self::$userId === self::$ownerId)
501 || self::$type === Dictionary::CALENDAR_TYPE['group']
502 )
503 {
504 $groupIds = self::$type === Dictionary::CALENDAR_TYPE['group'] ? [self::$ownerId] : [];
505 $JSConfig['counters'] = CountersManager::getValues((int)self::$userId, $groupIds);
507 self::$type,
508 self::$ownerId,
509 self::$userId
510 );
511 }
512
513 else if (
514 self::$type === 'company_calendar'
515 || self::$type === 'calendar_company'
516 || self::$type === 'company'
517 || self::$type === 'group'
518 )
519 {
521 self::$type,
522 self::$ownerId,
523 self::$userId
524 );
525 }
526
527 // Access permissions for type
528 $typeModel = TypeModel::createFromXmlId(self::$type);
529 $accessController = new TypeAccessController(self::$userId);
530 if ($accessController->check(ActionDictionary::ACTION_TYPE_ACCESS, $typeModel, []))
531 {
532 $JSConfig['TYPE_ACCESS'] = $arType['ACCESS'];
533 }
534
535 if ($isPersonalCalendarContext)
536 {
537 $syncInfoParams = [
538 'userId' => self::$userId,
539 'type' => self::$type,
540 ];
541 $JSConfig['syncInfo'] = CCalendarSync::GetSyncInfo($syncInfoParams);
542 $JSConfig['syncLinks'] = CCalendarSync::getSyncLinks();
543 $JSConfig['caldav_link_all'] = self::GetServerPath();
544 $JSConfig['isRuZone'] = \Bitrix\Calendar\Util::checkRuZone();
545 $JSConfig['isSetSyncGoogleSettings'] = self::isGoogleApiEnabled();
546 $JSConfig['isSetSyncOffice365Settings'] = self::isOffice365ApiEnabled();
547 $JSConfig['isIphoneConnected'] = self::isIphoneConnected();
548 $JSConfig['isMacConnected'] = self::isMacConnected();
549 $JSConfig['isIcloudConnected'] = ($JSConfig['syncInfo']['icloud'] ?? false)
550 ? $JSConfig['syncInfo']['icloud']['connected']
551 : false
552 ;
553 $JSConfig['isGoogleConnected'] = ($JSConfig['syncInfo']['google'] ?? false)
554 ? $JSConfig['syncInfo']['google']['connected']
555 : false
556 ;
557 }
558 else
559 {
560 $JSConfig['caldav_link_all'] = self::GetServerPath();
561 $JSConfig['isRuZone'] = \Bitrix\Calendar\Util::checkRuZone();
562 $JSConfig['syncInfo'] = false;
563 $JSConfig['isIphoneConnected'] = false;
564 $JSConfig['isMacConnected'] = false;
565 $JSConfig['isIcloudConnected'] = false;
566 $JSConfig['isGoogleConnected'] = false;
567 }
568
569 self::$userMeetingSection = self::GetCurUserMeetingSection();
570
571 $defaultHiddenSections = [];
572 $sections = [];
574
575 [$sectionList, $collabSectionList, $followedSectionList, $roomsList] = self::getSectionsInfo($isCollabUser);
576
577 $sectionIdList = [];
578 foreach ($sectionList as $section)
579 {
580 if (!in_array((int)$section['ID'], $sectionIdList, true))
581 {
582 $sections[] = $section;
583 $sectionIdList[] = (int)$section['ID'];
584 }
585
586 if (
587 ($section['CAL_TYPE'] !== self::$type || self::$ownerId !== (int)$section['OWNER_ID'])
588 && !($isCollabUser && $section['IS_COLLAB'])
589 )
590 {
591 $defaultHiddenSections[] = (int)$section['ID'];
592 }
593 }
594
595 $hiddenSections = UserSettings::getHiddenSections(
596 self::$userId,
597 [
598 'type' => self::$type,
599 'ownerId' => self::$ownerId,
600 'isPersonalCalendarContext' => $isPersonalCalendarContext,
601 'defaultHiddenSections' => $defaultHiddenSections,
602 ]
603 );
604
605 $readOnly = !self::$perm['edit'] && !self::$perm['section_edit'];
606
607 if (self::$type === 'user' && self::$ownerId !== (int)self::$userId)
608 {
609 $readOnly = true;
610 }
611
612 if (self::$bAnonym)
613 {
614 $readOnly = true;
615 }
616
617 $bCreateDefault = self::hasToCreateDefaultCalendar($sections);
618
619 $groupOrUser = self::$type === 'user' || self::$type === 'group';
620 if ($groupOrUser)
621 {
622 $noEditAccessedCalendars = true;
623 }
624
625 foreach ($sections as $i => $section)
626 {
627 $canEdit = $accessController->check(ActionDictionary::ACTION_TYPE_EDIT, $typeModel, []);
628 // We check access only for main sections because we can't edit superposed section
629 if (
630 $groupOrUser
631 && $section['CAL_TYPE'] === self::$type
632 && (int)$section['OWNER_ID'] === (int)self::$ownerId
633 )
634 {
635 if ($noEditAccessedCalendars && $section['PERM']['edit'])
636 {
637 $noEditAccessedCalendars = false;
638 }
639
640 if (
641 $readOnly
642 && $canEdit
643 && ($section['PERM']['edit'] || $section['PERM']['edit_section'])
644 && !self::$isArchivedGroup
645 )
646 {
647 $readOnly = false;
648 }
649 }
650
651 if (in_array($section['ID'], $followedSectionList))
652 {
653 $sections[$i]['SUPERPOSED'] = true;
654 }
655
656 $type = $sections[$i]['CAL_TYPE'];
657 if ($type === 'user')
658 {
659 $path = CComponentEngine::MakePathFromTemplate(
660 self::$pathesForSite['path_to_user_calendar'],
661 ["user_id" => $sections[$i]['OWNER_ID']]
662 );
663 }
664 elseif ($type === 'group')
665 {
666 $groupId = $sections[$i]['OWNER_ID'];
667 $path = CComponentEngine::MakePathFromTemplate(
668 self::$pathesForSite['path_to_group_calendar'],
669 ['group_id' => $groupId]
670 );
671 if ($sections[$i]['IS_COLLAB'])
672 {
673 $trackingCollabs[] = $groupId;
674 }
675 else
676 {
677 $trackingGroups[] = $groupId;
678 }
679 }
680 else
681 {
682 $path = self::$pathesForSite['path_to_type_'.$type];
683 }
684 $sections[$i]['LINK'] = $path;
685 }
686
687 if (!empty($collabSectionList))
688 {
689 $noEditAccessedCalendars = self::checkCollabSectionAccess($collabSectionList);
690 }
691
692 if ($groupOrUser && $noEditAccessedCalendars && !$bCreateDefault)
693 {
694 $readOnly = true;
695 }
696
697 self::$readOnly = $readOnly;
698
699 $JSConfig['trackingUsersList'] = UserSettings::getTrackingUsers(self::$userId);
700
701 // **** GET TASKS ****
702 if (self::$showTasks)
703 {
704 $JSConfig['viewTaskPath'] = self::$viewTaskPath;
705 $JSConfig['editTaskPath'] = self::$editTaskPath;
706 }
707
708 // We don't have any section
709 if ($bCreateDefault)
710 {
711 $fullSectionsList = $groupOrUser
712 ? self::GetSectionList(['checkPermissions' => false, 'getPermissions' => false])
713 : []
714 ;
715 // Section exists but it closed to this user (Ref. mantis:#64037)
716 if (
717 !empty($fullSectionsList)
718 && self::GetOwnerId() !== self::GetUserId()
719 )
720 {
721 $readOnly = true;
722 }
723 else
724 {
725 $defCalendar = CCalendarSect::CreateDefault([
726 'type' => self::GetType(),
727 'ownerId' => self::GetOwnerId(),
728 ]);
729 $sections[] = $defCalendar;
730 self::$userMeetingSection = $defCalendar['ID'];
731 }
732 }
733 else if (!CCalendarSect::containsLocalSection($sectionList, self::$type))
734 {
735 $sectionsWithoutPermissions = CCalendarSect::GetList([
736 'arFilter' => [
737 'CAL_TYPE' => self::$type,
738 'OWNER_ID' => self::$ownerId,
739 'ACTIVE' => 'Y',
740 'EXTERNAL_TYPE' => CCalendarSect::EXTERNAL_TYPE_LOCAL,
741 ],
742 'limit' => 1,
743 'checkPermissions' => false,
744 'getPermissions' => false,
745 ]);
746
747 if (empty($sectionsWithoutPermissions))
748 {
749 $defCalendar = CCalendarSect::CreateDefault([
750 'type' => self::GetType(),
751 'ownerId' => self::GetOwnerId(),
752 ]);
753 $sections[] = $defCalendar;
754 self::$userMeetingSection = $defCalendar['ID'];
755 }
756 }
757 //check if we need to create default calendar for user when he opens location calendar
758 if (self::$type === 'location')
759 {
760 $userSections = array_filter($sectionList, static function ($section) {
761 return $section['CAL_TYPE'] === 'user' && (int)$section['OWNER_ID'] === self::$userId;
762 });
763 if (empty($userSections))
764 {
765 $defUserCalendar = CCalendarSect::CreateDefault([
766 'type' => 'user',
767 'ownerId' => self::$userId,
768 ]);
769 if ($defUserCalendar)
770 {
771 $sections[] = $defUserCalendar;
772 self::$userMeetingSection = $defUserCalendar['ID'];
773 }
774 }
775 }
776 $typeAccessController = new TypeAccessController(self::$userId);
777 if ($typeAccessController->check(ActionDictionary::ACTION_TYPE_EDIT, TypeModel::createFromXmlId(self::$type)))
778 {
779 $JSConfig['new_section_access'] = CCalendarSect::GetDefaultAccess(self::$type, self::$ownerId);
780 }
781
782 $colors = ['#86B100','#0092CC','#00AFC7','#E89B06','#00B38C','#DE2B24','#BD7AC9','#838FA0','#C3612C','#E97090'];
783
784 $JSConfig['hiddenSections'] = $hiddenSections;
785 $JSConfig['readOnly'] = $readOnly;
786 $JSConfig['hideSettingsHintLocation'] = CUserOptions::GetOption('calendar', 'hideSettingsHintLocation');
787 // access
788 $JSConfig['accessNames'] = self::GetAccessNames();
789 $JSConfig['sectionAccessTasks'] = self::$type === 'location'
790 ? self::GetAccessTasks('calendar_section', 'location')
791 : self::GetAccessTasks()
792 ;
793 $JSConfig['typeAccessTasks'] = self::$type === 'location'
794 ? self::GetAccessTasks('calendar_type', 'location')
795 : self::GetAccessTasks('calendar_type')
796 ;
797
798 $JSConfig['bSuperpose'] = self::$bSuperpose;
799
800 $sharing = match (self::$type)
801 {
803 self::$ownerId,
804 (int)self::$userId
805 ),
806 default => new Sharing\Sharing(self::$userId),
807 };
808 $JSConfig['sharing'] = $sharing->getLinkInfo();
809 $JSConfig['sharingOptions'] = $isGroupCalendar ? null : $sharing->getOptions();
810 $JSConfig['isCollabUser'] = $isCollabUser;
811 $JSConfig['isCollabCalendar'] = $isCollabCalendar;
812 $JSConfig['isCollabFeatureEnabled'] = CollabFeature::isAvailable();
813
814 $userSettings = UserSettings::get(self::$ownerId);
815 $meetSectionId = (int)$userSettings['meetSection'];
816
817 $meetSection = CCalendarSect::GetById($meetSectionId);
818 $hasRightsForMeetSection = false;
819 if (is_array($meetSection))
820 {
821 $sectionAccessController = new SectionAccessController(self::$userId);
822 $sectionModel = SectionModel::createFromArray($meetSection);
823 $action = ActionDictionary::ACTION_SECTION_EDIT;
824 $hasRightsForMeetSection = $sectionAccessController->check($action, $sectionModel);
825 }
826 if ($meetSectionId && $hasRightsForMeetSection)
827 {
828 $JSConfig['meetSectionId'] = $meetSectionId;
829 }
830
831 $selectedUserCodes = array('U'.self::$userId);
832 if (self::$type === 'user')
833 {
834 $selectedUserCodes[] = 'U'.self::$ownerId;
835 }
836
837 $additionalParams = array(
838 'socnetDestination' => self::GetSocNetDestination(false, $selectedUserCodes),
839 'locationList' => $roomsList,
840 'timezoneList' => self::GetTimezoneList(),
841 'defaultColorsList' => $colors,
842 'formSettings' => array(
843 'slider_main' => UserSettings::getFormSettings('slider_main'),
844 ),
845 );
846
847 // Append Javascript files and CSS files, and some base configs
848 if (self::$type === 'location')
849 {
851 $JSConfig,
852 array(
853 'sections' => $sections,
854 'rooms' => $roomsList,
855 'categories' => $categoryList,
856 ),
857 $additionalParams
858 );
859 }
860 else
861 {
863 $JSConfig,
864 array(
865 'sections' => $sections,
866 ),
867 $additionalParams
868 );
869 }
870 }
871
877 public function checkViewPermissions(): void
878 {
879 global $APPLICATION;
880 $arType = false;
881
882 foreach(self::$arTypes as $type)
883 {
884 if(self::$type === $type['XML_ID'])
885 {
886 $arType = $type;
887 }
888 }
889
890 if (!$arType)
891 {
892 $APPLICATION->ThrowException('[EC_WRONG_TYPE] '.Loc::getMessage('EC_WRONG_TYPE'), 'calendar_wrong_type');
893
894 return;
895 }
896
897 if (!$this->checkPermissions())
898 {
899 $APPLICATION->ThrowException(Loc::getMessage("EC_ACCESS_DENIED"));
900 }
901 }
902
908 protected function checkPermissions(): bool
909 {
910 $typeModel = TypeModel::createFromXmlId(self::$type);
911 $accessController = new TypeAccessController(self::$userId);
912
913 if (!$accessController->check(ActionDictionary::ACTION_TYPE_VIEW, $typeModel, []))
914 {
915 return false;
916 }
917
918 $isExternalUser = Loader::includeModule('intranet') && !\Bitrix\Intranet\Util::isIntranetUser();
919 $isCollaber = Util::isCollabUser(self::$userId);
920
921 if (self::$type === Dictionary::CALENDAR_TYPE['user'] && $isExternalUser && !$isCollaber)
922 {
923 return false;
924 }
925
926 if (self::$type === Dictionary::CALENDAR_TYPE['user'] && $isCollaber && self::$userId !== self::$ownerId)
927 {
928 return false;
929 }
930
931 if (self::$type === Dictionary::CALENDAR_TYPE['group'] && !$this->checkGroupPermissions())
932 {
933 return false;
934 }
935
936 return true;
937 }
938
943 protected function checkGroupPermissions(): bool
944 {
945 if (!Loader::includeModule('socialnetwork'))
946 {
947 return false;
948 }
949
950 $groupId = self::$ownerId;
951
952 $featurePerms = CSocNetFeaturesPerms::CurrentUserCanPerformOperation(
954 [$groupId],
955 'calendar',
956 'view_all'
957 );
958
959 $canViewGroup = is_array($featurePerms) && isset($featurePerms[$groupId]) && $featurePerms[$groupId];
960
961 if (!$canViewGroup)
962 {
963 $featurePerms = CSocNetFeaturesPerms::CurrentUserCanPerformOperation(
965 [$groupId],
966 'calendar',
967 'view'
968 );
969 $canViewGroup = is_array($featurePerms) && isset($featurePerms[$groupId]) && $featurePerms[$groupId];
970 }
971
972 return $canViewGroup;
973 }
974
975 protected function doOpenEventInEditMode(string $id): bool
976 {
977 return str_starts_with($id, self::EDIT_PREFIX);
978 }
979
980 protected function getEditEventId(string $id): int
981 {
982 return (int)mb_substr($id, strlen(self::EDIT_PREFIX));
983 }
984
985 public static function SetDisplayedSuperposed($userId = false, $idList = [])
986 {
987 if (class_exists('CUserOptions') && $userId)
988 {
989 $idList = array_unique(array_map('intval', $idList));
990 CUserOptions::SetOption("calendar", "superpose_displayed", serialize($idList));
992 PushCommand::ChangeSectionSubscription,
993 $userId
994 );
995 }
996 }
997
998 public static function DeleteSection($id)
999 {
1000 if (self::IsExchangeEnabled(self::GetCurUserId()) && self::$type === 'user')
1001 {
1002 $oSect = CCalendarSect::GetById($id);
1003 // For exchange we change only calendar name
1004 if ($oSect && $oSect['IS_EXCHANGE'] && $oSect['DAV_EXCH_CAL'])
1005 {
1006 $exchRes = CDavExchangeCalendar::DoDeleteCalendar($oSect['OWNER_ID'], $oSect['DAV_EXCH_CAL']);
1007 if ($exchRes !== true)
1008 {
1009 return self::CollectExchangeErrors($exchRes);
1010 }
1011 }
1012 }
1013
1014 return CCalendarSect::Delete($id);
1015 }
1016
1017 public static function CollectExchangeErrors($arErrors = [])
1018 {
1019 if (empty($arErrors) || !is_array($arErrors))
1020 {
1021 return '[EC_NO_EXCH] ' . Loc::getMessage('EC_NO_EXCHANGE_SERVER');
1022 }
1023
1024 $str = "";
1025 foreach ($arErrors as $error)
1026 {
1027 $str .= "[" . $error[0] . "] " . $error[1] . "\n";
1028 }
1029
1030 return $str;
1031 }
1032
1043 public static function DeleteEvent($id, $doExternalSync = true, $params = [])
1044 {
1045 global $CACHE_MANAGER;
1046
1047 $id = (int)$id;
1048 $markDeleted = $params['markDeleted'] ?? true;
1049 $originalFrom = $params['originalFrom'] ?? null;
1050 if (!$id)
1051 {
1052 return false;
1053 }
1054
1055 $checkPermissions = $params['checkPermissions'] ?? true;
1056 if (!isset(self::$userId))
1057 {
1058 self::$userId = self::GetCurUserId();
1059 }
1060
1061 self::SetOffset();
1062 $res = CCalendarEvent::GetList([
1063 'arFilter' => ["ID" => $id],
1064 'parseRecursion' => false,
1065 'setDefaultLimit' => false,
1066 'fetchAttendees' => true,
1067 'checkPermissions' => $checkPermissions,
1068 ]);
1069
1070 if (!empty($res[0]) && $event = $res[0])
1071 {
1072 if (!isset(self::$type))
1073 {
1074 self::$type = $event['CAL_TYPE'];
1075 }
1076
1077 if (!isset(self::$ownerId))
1078 {
1079 self::$ownerId = $event['OWNER_ID'];
1080 }
1081
1082 $accessController = new EventAccessController(self::$userId);
1083 $eventModel = \CCalendarEvent::getEventModelForPermissionCheck((int)$event['ID'], $event, self::$userId);
1084 if ($checkPermissions && !$accessController->check(ActionDictionary::ACTION_EVENT_DELETE, $eventModel))
1085 {
1086 return Loc::getMessage('EC_ACCESS_DENIED');
1087 }
1088
1089 CCalendarSect::UpdateModificationLabel($event['SECT_ID']);
1090
1091 if ($doExternalSync !== false && $event['SECT_ID'])
1092 {
1093 $bGoogleApi = self::isGoogleApiEnabled() && $event['CAL_TYPE'] === 'user';
1094 $bCalDav = self::IsCalDAVEnabled() && $event['CAL_TYPE'] === 'user';
1095 $bExchangeEnabled = self::IsExchangeEnabled() && $event['CAL_TYPE'] === 'user';
1096
1097 if ($bExchangeEnabled || $bCalDav || $bGoogleApi)
1098 {
1100 'bCalDav' => $bCalDav,
1101 'bExchangeEnabled' => $bExchangeEnabled,
1102 'sectionId' => $event['SECT_ID'],
1103 ], $event);
1104
1105 if ($res !== true && self::$silentErrorMode)
1106 {
1107 self::ThrowError($res);
1108 }
1109 }
1110 }
1111
1112 $sendNotification = $params['sendNotification'] ?? (($params['recursionMode'] ?? null) !== 'all');
1113 $userId = !empty($params['userId']) ? (int)$params['userId'] : self::$userId;
1114
1115 $res = CCalendarEvent::Delete([
1116 'id' => $id,
1117 'Event' => $event,
1118 'bMarkDeleted' => $markDeleted,
1119 'originalFrom' => $originalFrom,
1120 'userId' => $userId,
1121 'sendNotification' => $sendNotification,
1122 'requestUid' => $params['requestUid'] ?? null,
1123 ]);
1124
1125 if (($params['recursionMode'] ?? null) !== 'this' && !empty($event['RECURRENCE_ID']))
1126 {
1127 self::DeleteEvent($event['RECURRENCE_ID'], $doExternalSync, [
1128 'sendNotification' => $sendNotification,
1129 'originalFrom' => $originalFrom,
1130 ]);
1131 }
1132
1133 if (CCalendarEvent::CheckRecurcion($event))
1134 {
1135 $events = CCalendarEvent::GetEventsByRecId($id);
1136
1137 foreach($events as $ev)
1138 {
1139 self::DeleteEvent($ev['ID'], $doExternalSync, [
1140 'sendNotification' => $sendNotification,
1141 'originalFrom' => $originalFrom,
1142 ]);
1143 }
1144 }
1145
1146 if (isset($params['recursionMode']) && $params['recursionMode'] === 'all' && !empty($event['ATTENDEE_LIST']))
1147 {
1148 foreach($event['ATTENDEE_LIST'] as $attendee)
1149 {
1150 if ($attendee['status'] !== 'N')
1151 {
1152 $CACHE_MANAGER->ClearByTag('calendar_user_'.$attendee["id"]);
1153 CCalendarNotify::Send([
1154 "mode" => 'cancel_all',
1155 "name" => $event['NAME'],
1156 "from" => $event['DATE_FROM'],
1157 "guestId" => $attendee["id"],
1158 "eventId" => $event['PARENT_ID'],
1159 "userId" => $userId ?? $event['MEETING_HOST'],
1160 "fields" => $event,
1161 ]);
1162 }
1163 }
1164 }
1165
1166 return $res;
1167 }
1168
1169 return false;
1170 }
1171
1172 public static function SetOffset($userId = false, $value = 0)
1173 {
1174 if ($userId === false)
1175 {
1176 self::$offset = $value;
1177 }
1178 else
1179 {
1180 self::$arTimezoneOffsets[$userId] = $value;
1181 }
1182 }
1183
1184 public static function CollectCalDAVErros($arErrors = [])
1185 {
1186 if (empty($arErrors) || !is_array($arErrors))
1187 {
1188 return '[EC_NO_EXCH] ' . Loc::getMessage('EC_NO_CAL_DAV');
1189 }
1190
1191 $str = "";
1192 foreach ($arErrors as $error)
1193 {
1194 $str .= "[". $error[0]."] ". $error[1]."\n";
1195 }
1196
1197 return $str;
1198 }
1199
1200 public static function GetPathForCalendarEx($userId = 0)
1201 {
1202 $userId = (int)$userId;
1203
1204 $cacheId = 'calendar_path_settings_'.$userId;
1205 $obCache = new CPHPCache;
1206
1207 if($obCache->InitCache(3600 * 6, $cacheId, '/calendar/'.$cacheId))
1208 {
1209 $calendarUrl = $obCache->GetVars();
1210 }
1211 else
1212 {
1213 $obCache->StartDataCache();
1214
1215 $bExtranet = Loader::includeModule('extranet');
1216 // It's extranet user
1217 if ($bExtranet && self::IsExtranetUser($userId))
1218 {
1219 $siteId = CExtranet::GetExtranetSiteID();
1220 }
1221 else
1222 {
1223 $siteId = $bExtranet && !self::IsExtranetUser($userId)
1224 ? CSite::GetDefSite()
1225 : self::GetSiteId();
1226
1227 if (
1228 self::$siteId == $siteId
1229 && isset(self::$pathesForSite)
1230 && is_array(self::$pathesForSite)
1231 )
1232 {
1233 self::$pathes[$siteId] = self::$pathesForSite;
1234 }
1235 }
1236
1237 if (!isset(self::$pathes[$siteId]) || !is_array(self::$pathes[$siteId]))
1238 {
1239 self::$pathes[$siteId] = self::GetPathes($siteId);
1240 }
1241
1242 $calendarUrl = self::$pathes[$siteId]['path_to_user_calendar'] ?? '';
1243 $calendarUrl = str_replace(array('#user_id#', '#USER_ID#'), $userId, $calendarUrl);
1244 $calendarUrl = self::GetServerPath().$calendarUrl;
1245
1246 $obCache->EndDataCache($calendarUrl);
1247 }
1248
1249 return $calendarUrl;
1250 }
1251
1252 public static function IsExtranetUser($userId = 0)
1253 {
1254 if (!$userId)
1255 {
1256 return true;
1257 }
1258
1259 $departments = self::GetUserDepartment($userId);
1260
1261 return empty($departments);
1262 }
1263
1264 public static function GetUserDepartment($userId = 0)
1265 {
1266 if (!isset(self::$arUserDepartment[$userId]))
1267 {
1268 $rsUser = CUser::GetByID($userId);
1269 if($arUser = $rsUser->Fetch())
1270 {
1271 self::SetUserDepartment($userId, $arUser["UF_DEPARTMENT"]);
1272 }
1273 }
1274
1275 return self::$arUserDepartment[$userId];
1276 }
1277
1278 public static function SetUserDepartment($userId = 0, $dep = [])
1279 {
1280 if (!is_array($dep))
1281 {
1282 $dep = [];
1283 }
1284 self::$arUserDepartment[$userId] = $dep;
1285 }
1286
1287 public static function HandleImCallback($module, $tag, $value, $arNotify)
1288 {
1289 $userId = self::GetCurUserId();
1290 if ($module === "calendar" && $userId)
1291 {
1292 $arTag = explode("|", $tag);
1293 $eventId = (int)$arTag[2];
1294 if ($arTag[0] === "CALENDAR" && $arTag[1] === "INVITE" && $eventId && $userId)
1295 {
1296 CCalendarEvent::SetMeetingStatus([
1297 'userId' => $userId,
1298 'eventId' => $eventId,
1299 'status' => $value === 'Y' ? 'Y' : 'N',
1300 'personalNotification' => true,
1301 ]);
1302
1303 return $value === 'Y' ? Loc::getMessage('EC_PROP_CONFIRMED_TEXT_Y') : Loc::getMessage('EC_PROP_CONFIRMED_TEXT_N');
1304 }
1305 }
1306 }
1307
1308 public static function SetSettings($settings = [], $clearOptions = false)
1309 {
1310 $arPathes = self::GetPathesList();
1311 $optionNames = [
1312 'work_time_start',
1313 'work_time_end',
1314 'year_holidays',
1315 'year_workdays',
1316 'week_holidays',
1317 'week_start',
1318 'user_name_template',
1319 'sync_by_push',
1320 'user_show_login',
1321 'rm_iblock_type',
1322 'rm_iblock_id',
1323 'denied_superpose_types',
1324 'pathes_for_sites',
1325 'pathes',
1326 'dep_manager_sub',
1327 'forum_id',
1328 'rm_for_sites',
1329 ];
1330
1331 $optionNames = array_merge($optionNames, $arPathes);
1332 if (isset($settings['rm_iblock_ids']) && !$settings['rm_for_sites'])
1333 {
1334 foreach($settings['rm_iblock_ids'] as $site => $value)
1335 {
1336 COption::SetOptionString("calendar", 'rm_iblock_id', $value, false, $site);
1337 }
1338 }
1339
1340 foreach($optionNames as $opt)
1341 {
1342 if ($clearOptions)
1343 {
1344 COption::RemoveOption("calendar", $opt);
1345 }
1346 else if (isset($settings[$opt]))
1347 {
1348 if ($opt === 'rm_iblock_id' && !$settings['rm_for_sites'])
1349 {
1350 continue;
1351 }
1352
1353 if ($opt === 'sync_by_push')
1354 {
1355 if (self::isOffice365ApiEnabled() || self::isGoogleApiEnabled())
1356 {
1357 \CAgent::RemoveAgent("\\Bitrix\\Calendar\\Sync\\Managers\\DataExchangeManager::importAgent();", 'calendar');
1358 \CAgent::RemoveAgent("\\Bitrix\\Calendar\\Sync\\Managers\\PushWatchingManager::renewWatchChannels();", 'calendar');
1359
1360 if ($settings[$opt])
1361 {
1362 // legacy
1363 \CAgent::RemoveAgent(
1364 "\\Bitrix\\Calendar\\Sync\\GoogleApiPush::clearPushChannels();",
1365 "calendar"
1366 );
1367 // actual
1368 \CAgent::AddAgent(
1369 "\\Bitrix\\Calendar\\Sync\\Managers\\PushWatchingManager::renewWatchChannels();",
1370 'calendar',
1371 'N',
1372 3600
1373 );
1374 }
1375 else
1376 {
1377 global $DB;
1378 // legacy
1379 \CAgent::RemoveAgent("\\Bitrix\\Calendar\\Sync\\GoogleApiPush::processPush();", "calendar");
1380 \CAgent::RemoveAgent("\\Bitrix\\Calendar\\Sync\\GoogleApiPush::renewWatchChannels();", "calendar");
1381 $DB->Query("DELETE FROM b_agent WHERE NAME LIKE '%GoogleApiPush::checkPushChannel%'");
1382 // actual
1383 \CAgent::AddAgent(
1384 "\\Bitrix\\Calendar\\Sync\\Managers\\DataExchangeManager::importAgent();",
1385 'calendar',
1386 'N',
1387 180
1388 );
1389 }
1390 }
1391 }
1392
1393 if ($opt === 'pathes' && is_array($settings[$opt]))
1394 {
1395 $sitesPathes = $settings[$opt];
1396
1397 $ar = [];
1398 $arAffectedSites = [];
1399 foreach($sitesPathes as $s => $pathes)
1400 {
1401 $affect = false;
1402 foreach($arPathes as $path)
1403 {
1404 if ($pathes[$path] != $settings[$path])
1405 {
1406 $ar[$path] = $pathes[$path] ?? $settings[$path];
1407 $affect = true;
1408 }
1409 }
1410
1411 if ($affect && !in_array($s, $arAffectedSites))
1412 {
1413 $arAffectedSites[] = $s;
1414 COption::SetOptionString("calendar", 'pathes_'.$s, serialize($ar));
1415 }
1416 else
1417 {
1418 COption::RemoveOption("calendar", 'pathes_'.$s);
1419 }
1420 }
1421 COption::SetOptionString("calendar", 'pathes_sites', serialize($arAffectedSites));
1422 continue;
1423 }
1424
1425 else if ($opt === 'denied_superpose_types' && is_array($settings[$opt]))
1426 {
1427 $settings[$opt] = serialize($settings[$opt]);
1428 }
1429
1430 else if ($opt === 'week_holidays' && is_array($settings[$opt]))
1431 {
1432 $settings[$opt] = implode(
1433 '|',
1434 array_intersect(array_unique($settings[$opt]), ['SU','MO','TU','WE','TH','FR','SA'])
1435 );
1436 }
1437
1438 COption::SetOptionString("calendar", $opt, $settings[$opt]);
1439 }
1440 }
1441 }
1442
1443 public static function IsBitrix24()
1444 {
1445 return \Bitrix\Main\ModuleManager::isModuleInstalled('bitrix24');
1446 }
1447
1448 public static function ReminderAgent($eventId = 0, $userId = 0, $viewPath = '', $calendarType = '', $ownerId = 0, $index = 0)
1449 {
1450 CCalendarReminder::ReminderAgent($eventId, $userId, $viewPath, $calendarType, $ownerId, $index);
1451 }
1452
1453 public static function GetMaxTimestamp()
1454 {
1455 return self::CALENDAR_MAX_TIMESTAMP;
1456 }
1457
1458 public static function SetMaxPlannerUsers(int $maxPlannerUsers): void
1459 {
1460 \Bitrix\Main\Config\Option::set('calendar', 'maxPlannerUsers', $maxPlannerUsers);
1461 }
1462
1463 public static function GetMaxPlannerUsers(): int
1464 {
1465 return (int)\Bitrix\Main\Config\Option::get('calendar', 'maxPlannerUsers', 0);
1466 }
1467
1468 public static function GetOwnerName($type = '', $ownerId = '')
1469 {
1470 $type = mb_strtolower($type);
1471 $key = $type.'_'.$ownerId;
1472
1473 if (isset(self::$ownerNames[$key]))
1474 {
1475 return self::$ownerNames[$key];
1476 }
1477
1478 $ownerName = '';
1479 if($type === 'user')
1480 {
1481 $ownerName = self::GetUserName($ownerId);
1482 }
1483 elseif($type === 'group')
1484 {
1485 // Get group name
1486 if (!Loader::includeModule("socialnetwork"))
1487 {
1488 return $ownerName;
1489 }
1490
1491 if ($arGroup = CSocNetGroup::GetByID($ownerId))
1492 {
1493 $ownerName = $arGroup["~NAME"];
1494 }
1495 }
1496 else
1497 {
1498 // Get type name
1499 $arTypes = CCalendarType::GetList(array("arFilter" => array("XML_ID" => $type)));
1500 $ownerName = $arTypes[0]['NAME'];
1501 }
1502 self::$ownerNames[$key] = $ownerName;
1503 $ownerName = is_string($ownerName) ? trim($ownerName) : '';
1504
1505 return $ownerName;
1506 }
1507
1508 public static function GetTimezoneOffset($timezoneId, $dateTimestamp = false)
1509 {
1510 $offset = 0;
1511 if ($timezoneId)
1512 {
1513 try
1514 {
1515 $oTz = new DateTimeZone($timezoneId);
1516 if ($oTz)
1517 {
1518 $offset = $oTz->getOffset(new DateTime($dateTimestamp ? "@$dateTimestamp" : "now", $oTz));
1519 }
1520 }
1521 catch(Exception $e)
1522 {
1523 }
1524 }
1525 return $offset;
1526 }
1527
1528 public static function isDaylightSavingTimezone(string $timezoneId): string
1529 {
1530 $result = 'N';
1531
1532 try
1533 {
1534 $timezone = new DateTimeZone($timezoneId);
1535 $currentTime = new DateTime('now', $timezone);
1536
1537 $transitions = $timezone->getTransitions(
1538 $currentTime->getTimestamp(),
1539 $currentTime->getTimestamp() + 365 * self::DAY_LENGTH
1540 );
1541
1542 foreach ($transitions as $transition)
1543 {
1544 if ($transition['isdst'] === true)
1545 {
1546 return 'Y';
1547 }
1548 }
1549 }
1550 catch(Exception $e)
1551 {
1552 }
1553
1554 return $result;
1555 }
1556
1557 public static function GetAbsentEvents($params)
1558 {
1559 if (!isset($params['arUserIds']))
1560 return false;
1561
1562 return CCalendarEvent::GetAbsent($params['arUserIds'], $params);
1563 }
1564
1568 public static function GetAccessibilityForUsers($params)
1569 {
1570 if (!isset($params['checkPermissions']))
1571 {
1572 $params['checkPermissions'] = true;
1573 }
1574
1575 $res = CCalendarEvent::GetAccessibilityForUsers([
1576 'users' => $params['users'],
1577 'from' => $params['from'],
1578 'to' => $params['to'],
1579 'curEventId' => $params['curEventId'] ?? null,
1580 'checkPermissions' => $params['checkPermissions'],
1581 ]);
1582
1583 // Fetch absence from intranet
1584 if (isset($params['getFromHR']) && self::IsIntranetEnabled())
1585 {
1586 $resHR = CIntranetUtils::GetAbsenceData(
1587 array(
1588 'DATE_START' => $params['from'],
1589 'DATE_FINISH' => $params['to'],
1590 'USERS' => $params['users'],
1591 'PER_USER' => true,
1592 'SELECT' => array('ID', 'DATE_ACTIVE_FROM', 'DATE_ACTIVE_TO'),
1593 ),
1594 BX_INTRANET_ABSENCE_HR
1595 );
1596
1597 foreach($resHR as $userId => $forUser)
1598 {
1599 if (!isset($res[$userId]) || !is_array($res[$userId]))
1600 {
1601 $res[$userId] = [];
1602 }
1603
1604 foreach($forUser as $event)
1605 {
1606 $res[$userId][] = [
1607 'FROM_HR' => true,
1608 'ID' => $event['ID'],
1609 'DT_FROM' => $event['DATE_ACTIVE_FROM'],
1610 'DT_TO' => $event['DATE_ACTIVE_TO'],
1611 'ACCESSIBILITY' => 'absent',
1612 'IMPORTANCE' => 'normal',
1613 "FROM" => self::Timestamp($event['DATE_ACTIVE_FROM']),
1614 "TO" => self::Timestamp($event['DATE_ACTIVE_TO']),
1615 ];
1616 }
1617 }
1618 }
1619
1620 return $res;
1621 }
1622
1623 public static function GetNearestEventsList($params = [])
1624 {
1625 $type = $params['bCurUserList'] ? 'user' : $params['type'];
1626 $isFromRest = ($params['fromRest'] ?? false) === true;
1627
1628 // Get current user id
1629 if (!isset($params['userId']) || $params['userId'] <= 0)
1630 {
1631 $curUserId = self::GetCurUserId();
1632 }
1633 else
1634 {
1635 $curUserId = (int)$params['userId'];
1636 }
1637
1638 $accessController = new TypeAccessController($curUserId);
1639 if (!$accessController->check(ActionDictionary::ACTION_TYPE_VIEW, TypeModel::createFromXmlId($type)))
1640 {
1641 return 'access_denied';
1642 }
1643
1644 if (
1645 $params['bCurUserList']
1646 && (
1647 $curUserId <= 0
1648 || (
1649 class_exists('CSocNetFeatures')
1650 && !CSocNetFeatures::IsActiveFeature(SONET_ENTITY_USER, $curUserId, "calendar")
1651 )
1652 )
1653 )
1654 {
1655 return 'inactive_feature';
1656 }
1657
1658 $maxAmount = isset($params['maxAmount']) && (int)$params['maxAmount'] > 0
1659 ? (int)$params['maxAmount']
1660 : 75
1661 ;
1662
1663 if ($type === 'user' && OpenEvents\Feature::getInstance()->isAvailable())
1664 {
1665 $type = ['user', Core\Event\Tools\Dictionary::CALENDAR_TYPE['open_event']];
1666 }
1667
1668 $arFilter = [
1669 'CAL_TYPE' => $type,
1670 'FROM_LIMIT' => $params['fromLimit'],
1671 'TO_LIMIT' => $params['toLimit'],
1672 'DELETED' => 'N',
1673 'ACTIVE_SECTION' => 'Y',
1674 ];
1675
1676 if ($params['bCurUserList'])
1677 {
1678 $arFilter['OWNER_ID'] = $curUserId;
1679 }
1680
1681 if (isset($params['sectionId']) && $params['sectionId'])
1682 {
1683 $arFilter["SECTION"] = $params['sectionId'];
1684 }
1685
1686 $selectFields = [
1687 'ID',
1688 'PARENT_ID',
1689 'NAME',
1690 'OWNER_ID',
1691 'RRULE',
1692 'EXDATE',
1693 'DATE_FROM',
1694 'DATE_TO',
1695 'TZ_FROM',
1696 'TZ_TO',
1697 'TZ_OFFSET_FROM',
1698 'TZ_OFFSET_TO',
1699 'IS_MEETING',
1700 'MEETING_STATUS',
1701 'CAL_TYPE',
1702 'DT_LENGTH',
1703 'DT_SKIP_TIME',
1704 'SECTION_ID',
1705 'DATE_FROM_TS_UTC',
1706 'DATE_TO_TS_UTC',
1707 ];
1708
1709 if ($isFromRest)
1710 {
1711 $selectFields = ['*'];
1712 }
1713
1714 $eventsList = CCalendarEvent::GetList([
1715 'arFilter' => $arFilter,
1716 'arSelect' => $selectFields,
1717 'parseRecursion' => true,
1718 'fetchAttendees' => $isFromRest,
1719 'userId' => $curUserId,
1720 'fetchMeetings' => $type === 'user',
1721 'preciseLimits' => true,
1722 'skipDeclined' => true,
1723 'getUserfields' => $isFromRest,
1724 ]);
1725
1726 $pathToCalendar = self::GetPathForCalendarEx($curUserId);
1727
1728 if (self::Date(time(), false) === $params['fromLimit'])
1729 {
1730 $limitTime = time();
1731 }
1732 else
1733 {
1734 $limitTime = self::Timestamp($params['fromLimit']);
1735 }
1736
1737 $limitTime -= (int)date("Z", $limitTime);
1738 $entryList = [];
1739
1740 foreach ($eventsList as $event)
1741 {
1742 if ($event['IS_MEETING'] && $event["MEETING_STATUS"] === 'N')
1743 {
1744 continue;
1745 }
1746
1747 if ($type === 'user' && !$event['IS_MEETING'] && $event['CAL_TYPE'] !== 'user')
1748 {
1749 continue;
1750 }
1751
1752 $fromTs = self::Timestamp($event['DATE_FROM']);
1753 $toTs = $fromTs + $event['DT_LENGTH'];
1754
1755 $toTsUtc = $toTs - $event['TZ_OFFSET_FROM'];
1756
1757 if ($toTsUtc >= $limitTime)
1758 {
1759 if ($event['DT_SKIP_TIME'] !== "Y")
1760 {
1761 $fromTs -= $event['~USER_OFFSET_FROM'];
1762 $toTs -= $event['~USER_OFFSET_TO'];
1763 }
1764 $event['DATE_FROM'] = self::Date($fromTs, $event['DT_SKIP_TIME'] !== 'Y');
1765 $event['DATE_TO'] = self::Date($toTs, $event['DT_SKIP_TIME'] !== 'Y');
1766 unset($event['TZ_FROM'], $event['TZ_TO'], $event['TZ_OFFSET_FROM'], $event['TZ_OFFSET_TO']);
1767 $event['DT_FROM_TS'] = $fromTs;
1768 $event['DT_TO_TS'] = $toTs;
1769
1770 $event['~URL'] = \CHTTP::urlAddParams($pathToCalendar, [
1771 'EVENT_ID' => $event['ID'],
1772 'EVENT_DATE' => self::Date($fromTs, false),
1773 ]);
1774
1775 $event['~WEEK_DAY'] = FormatDate("D", $fromTs);
1776
1777 $event['~FROM_TO_HTML'] = self::GetFromToHtml(
1778 $fromTs,
1779 $toTs,
1780 $event['DT_SKIP_TIME'] === 'Y',
1781 $event['DT_LENGTH']
1782 );
1783
1784 $entryList[] = $event;
1785 }
1786 }
1787
1788 // Sort by DATE_FROM_TS
1789 usort($entryList, static function($a, $b){
1790 if ($a['DT_FROM_TS'] === $b['DT_FROM_TS'])
1791 {
1792 return 0;
1793 }
1794 return $a['DT_FROM_TS'] < $b['DT_FROM_TS'] ? -1 : 1;
1795 });
1796 array_splice($entryList, $maxAmount);
1797
1798 return $entryList;
1799 }
1800
1801 public static function GetTextLocation($loc = '')
1802 {
1803 return Rooms\Util::getTextLocation($loc);
1804 }
1805
1806 public static function ParseLocation($location = '')
1807 {
1808 return Rooms\Util::parseLocation($location);
1809 }
1810
1811 /* * * * RESERVE MEETING ROOMS * * * */
1812
1813 public static function GetUserPermissionsForCalendar($calendarId, $userId)
1814 {
1815 [$sectionId, $entityType, $entityId] = $calendarId;
1816 $entityType = mb_strtolower($entityType);
1817
1818 $accessController = new SectionAccessController((int)$userId);
1819 $sectionModel =
1820 SectionModel::createFromId((int)$sectionId)
1821 ->setType($entityType)
1822 ->setOwnerId((int)$entityId)
1823 ;
1824 $request = [
1825 ActionDictionary::ACTION_SECTION_EDIT => [],
1826 ActionDictionary::ACTION_SECTION_EVENT_VIEW_FULL => [],
1827 ActionDictionary::ACTION_SECTION_EVENT_VIEW_TIME => [],
1828 ActionDictionary::ACTION_SECTION_EVENT_VIEW_TITLE => [],
1829 ];
1830
1831 $result = $accessController->batchCheck($request, $sectionModel);
1832 $res = [
1833 'bAccess' => $result[ActionDictionary::ACTION_SECTION_EVENT_VIEW_TIME],
1834 'bReadOnly' => !$result[ActionDictionary::ACTION_SECTION_EDIT],
1835 ];
1836
1837 if ($res['bReadOnly'])
1838 {
1839 if ($result[ActionDictionary::ACTION_SECTION_EVENT_VIEW_TIME])
1840 {
1841 $res['privateStatus'] = 'time';
1842 }
1843 if ($result[ActionDictionary::ACTION_SECTION_EVENT_VIEW_TITLE])
1844 {
1845 $res['privateStatus'] = 'title';
1846 }
1847 }
1848
1849 return $res;
1850 }
1851
1852 public static function GetDayLen()
1853 {
1854 return self::DAY_LENGTH;
1855 }
1856
1857 public static function UnParseTextLocation($loc = '')
1858 {
1859 return Rooms\Util::unParseTextLocation($loc);
1860 }
1861
1862 public static function ClearExchangeHtml($html = "")
1863 {
1864 // Echange in chrome puts chr(13) instead of \n
1865 $html = str_replace(chr(13), "\n", trim($html, chr(13)));
1866 $html = preg_replace("/(\s|\S)*<a\s*name=\"bm_begin\"><\/a>/isu","", $html);
1867 $html = preg_replace("/<br>(\n|\r)+/isu","<br>", $html);
1868 return self::ParseHTMLToBB($html);
1869 }
1870
1871 public static function ParseHTMLToBB($html = "")
1872 {
1873 $id = AddEventHandler("main", "TextParserBeforeTags", Array("CCalendar", "_ParseHack"));
1874
1875 $TextParser = new CTextParser();
1876 $TextParser->allow = array("HTML" => "N", "BIU" => "Y", "IMG" => "Y", "QUOTE" => "Y", "CODE" => "Y", "FONT" => "N", "LIST" => "Y", "SMILES" => "Y", "NL2BR" => "Y", "VIDEO" => "Y", "TABLE" => "Y", "CUT_ANCHOR" => "Y", "ALIGN" => "Y");
1877 $html = $TextParser->convertText($html);
1878
1879 $html = htmlspecialcharsback($html);
1880 // Replace BR
1881 $html = preg_replace("/<br\s*\/*>/isu","\n", $html);
1882 //replace /p && /div to \n
1883 $html = preg_replace("/<\/(p|div)>/isu","\n", $html);
1884 // Kill &nbsp;
1885 $html = preg_replace("/&nbsp;/isu","", $html);
1886 // For images in Office 365
1887 $html = preg_replace(
1888 "#<img[^>]+src\\s*=[\\s'\"]*((cid):[.\\-_:a-z0-9@]+)*[\\s'\"]*[^>]*>#isu",
1889 "[img]\\1[/img]", $html
1890 );
1891 // Kill tags
1892 $html = preg_replace("/<([^>]*?)>/isu","", $html);
1893 // Clean multiple \n symbols
1894 $html = preg_replace("/\n[\s\n]+\n/", "\n" , $html);
1895
1896 $html = htmlspecialcharsbx($html);
1897
1898 RemoveEventHandler("main", "TextParserBeforeTags", $id);
1899
1900 return $html;
1901 }
1902
1903 public static function WeekDayByInd($i, $binv = true)
1904 {
1905 if ($binv)
1906 {
1907 $arDays = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];
1908 }
1909 else
1910 {
1911 $arDays = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'];
1912 }
1913 return $arDays[$i] ?? false;
1914 }
1915
1916 public static function IndByWeekDay(string $weekday): int
1917 {
1918 $weekdays = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];
1919 $weekdays = array_combine($weekdays, array_keys(array_values($weekdays)));
1920 return $weekdays[$weekday] ?? 0;
1921 }
1922
1923 public static function SaveEvent($params = [])
1924 {
1925 $res = self::SaveEventEx($params);
1926
1927 if (is_array($res) && isset($res['originalDavXmlId']))
1928 {
1929 return $res;
1930 }
1931
1932 if (is_array($res) && isset($res['id']))
1933 {
1934 return $res['id'];
1935 }
1936
1937 return $res;
1938 }
1939
1943 public static function SaveEventEx($params = [])
1944 {
1945 $arFields = $params['arFields'];
1946 if (self::$type && !isset($arFields['CAL_TYPE']))
1947 {
1948 $arFields['CAL_TYPE'] = self::$type;
1949 }
1950 elseif (isset($arFields['SECTION_CAL_TYPE']) && !isset($arFields['CAL_TYPE']))
1951 {
1952 $arFields['CAL_TYPE'] = $arFields['SECTION_CAL_TYPE'];
1953 }
1954 if (self::$bOwner && !isset($arFields['OWNER_ID']))
1955 {
1956 $arFields['OWNER_ID'] = self::$ownerId;
1957 }
1958 elseif (isset($arFields['SECTION_OWNER_ID']) && !isset($arFields['OWNER_ID']))
1959 {
1960 $arFields['OWNER_ID'] = $arFields['SECTION_OWNER_ID'];
1961 }
1962
1963 if (!isset($arFields['SKIP_TIME']) && isset($arFields['DT_SKIP_TIME']))
1964 {
1965 $arFields['SKIP_TIME'] = $arFields['DT_SKIP_TIME'] === 'Y';
1966 }
1967
1968 //flags for synchronize the instance of a recurring event
1969 //modeSync - edit mode instance for avoid unnecessary request (patch)
1970 //editParentEvents - editing the parent event of the following
1971 $params['modeSync'] = true;
1972 $params['editInstance'] = $params['editInstance'] ?? false;
1973 $params['editNextEvents'] = $params['editNextEvents'] ?? false;
1974 $params['editParentEvents'] = $params['editParentEvents'] ?? false;
1975 $params['editEntryUntil'] = $params['editEntryUntil'] ?? false;
1976 $params['originalDavXmlId'] = $params['originalDavXmlId'] ?? null;
1977 $params['originalFrom'] = $params['originalFrom'] ?? null;
1978 $params['instanceTz'] = $params['instanceTz'] ?? null;
1979 $params['syncCaldav'] = $params['syncCaldav'] ?? false;
1980 $params['sendInvitesToDeclined'] = $params['sendInvitesToDeclined'] ?? false;
1981 $params['autoDetectSection'] = $params['autoDetectSection'] ?? false;
1982 $userId = $params['userId'] ?? self::getCurUserId();
1984
1985 $result = [];
1986 if (isset($arFields['SECTION_ID']))
1987 {
1988 $sectionId = (int)$arFields['SECTION_ID'];
1989 }
1990 else
1991 {
1992 $sectionId =
1993 (!empty($arFields['SECTIONS']) && is_array($arFields['SECTIONS']))
1994 ? $arFields['SECTIONS'][0]
1995 : (int)($arFields['SECTIONS'] ?? null);
1996 }
1997
1998 $bPersonal = self::IsPersonal($arFields['CAL_TYPE'] ?? null, $arFields['OWNER_ID'] ?? null, $userId);
1999 $checkPermission = !isset($params['checkPermission']) || $params['checkPermission'] !== false;
2000 $silentErrorModePrev = self::$silentErrorMode;
2001 self::SetSilentErrorMode();
2002
2003 if (
2004 isset($arFields['DT_FROM'], $arFields['DT_TO'])
2005 && !isset($arFields['DATE_FROM'])
2006 && !isset($arFields['DATE_TO'])
2007 )
2008 {
2009 $arFields['DATE_FROM'] = $arFields['DT_FROM'];
2010 $arFields['DATE_TO'] = $arFields['DT_TO'];
2011 unset($arFields['DT_FROM'], $arFields['DT_TO']);
2012 }
2013
2014 // Fetch current event
2015 $curEvent = false;
2016 $bNew = !isset($arFields['ID']) || !$arFields['ID'];
2017 if (!$bNew)
2018 {
2019 $curEvent = self::getCurrentEventForSaving((int)$arFields['ID'], $userId, $checkPermission);
2020
2021 if (
2022 in_array(
2023 $curEvent['EVENT_TYPE'] ?? '',
2025 true
2026 )
2027 )
2028 {
2029 unset(
2030 $arFields['SECTIONS'],
2031 $arFields['DATE_FROM'],
2032 $arFields['DATE_TO'],
2033 $arFields['SKIP_TIME'],
2034 $arFields['TZ_FROM'],
2035 $arFields['TZ_TO'],
2036 );
2037 $arFields['RRULE'] = [
2038 'FREQ' => 'NONE',
2039 'INTERVAL' => 1,
2040 ];
2041 }
2042
2043 if (!isset($arFields['DATE_FROM']) && isset($curEvent['DATE_FROM']))
2044 {
2045 $arFields['DATE_FROM'] = $curEvent['DATE_FROM'];
2046 }
2047 if (!isset($arFields['DATE_TO']) && isset($curEvent['DATE_TO']))
2048 {
2049 $arFields['DATE_TO'] = $curEvent['DATE_TO'];
2050 }
2051
2052 $canChangeDateRecurrenceEvent = isset($params['recursionEditMode'])
2053 && in_array($params['recursionEditMode'], ['all', ''], true)
2054 && (($arFields['DATE_FROM'] ?? null) !== ($curEvent['DATE_FROM'] ?? null))
2055 && ($arFields['RRULE']['FREQ'] ?? null) !== 'NONE'
2056 ;
2057
2058 if ($canChangeDateRecurrenceEvent)
2059 {
2060 $arFields['DATE_FROM'] = self::GetOriginalDate(
2061 $arFields['DATE_FROM'],
2062 $curEvent['DATE_FROM'],
2063 $arFields['TZ_FROM'] ?? null
2064 );
2065 $arFields['DATE_TO'] = self::GetOriginalDate(
2066 $arFields['DATE_TO'],
2067 $curEvent['DATE_TO'],
2068 $arFields['TZ_TO'] ?? null
2069 );
2070 }
2071
2072 $bPersonal = $bPersonal && self::IsPersonal($curEvent['CAL_TYPE'], $curEvent['OWNER_ID'], $userId);
2073
2074 $arFields['CAL_TYPE'] = $curEvent['CAL_TYPE'];
2075 $arFields['OWNER_ID'] = $curEvent['OWNER_ID'];
2076 $arFields['CREATED_BY'] = $curEvent['CREATED_BY'];
2077 $arFields['ACTIVE'] = $curEvent['ACTIVE'] ?? null;
2078
2079 $eventModel = CCalendarEvent::getEventModelForPermissionCheck((int)($curEvent['ID'] ?? 0), $curEvent, $userId);
2080
2081 $accessCheckResult = $accessController->check(ActionDictionary::ACTION_EVENT_EDIT, $eventModel);
2082 $bChangeMeeting = !$checkPermission || $accessCheckResult;
2083
2084 if (!$bChangeMeeting)
2085 {
2086 return Loc::getMessage('EC_ACCESS_DENIED');
2087 }
2088
2089 if (!isset($arFields['NAME']))
2090 {
2091 $arFields['NAME'] = $curEvent['NAME'];
2092 }
2093 if (!isset($arFields['DESCRIPTION']))
2094 {
2095 $arFields['DESCRIPTION'] = $curEvent['DESCRIPTION'];
2096 }
2097 if (!isset($arFields['COLOR']) && $curEvent['COLOR'])
2098 {
2099 $arFields['COLOR'] = $curEvent['COLOR'];
2100 }
2101 if (!isset($arFields['TEXT_COLOR']) && !empty($curEvent['TEXT_COLOR']))
2102 {
2103 $arFields['TEXT_COLOR'] = $curEvent['TEXT_COLOR'];
2104 }
2105 if (!isset($arFields['SECTIONS']))
2106 {
2107 $arFields['SECTIONS'] = [$curEvent['SECT_ID']];
2108 $sectionId = !empty($arFields['SECTIONS']) ? $arFields['SECTIONS'][0] : 0;
2109 }
2110 if (!isset($arFields['IS_MEETING']))
2111 {
2112 $arFields['IS_MEETING'] = $curEvent['IS_MEETING'];
2113 }
2114 if (!isset($arFields['MEETING_HOST']))
2115 {
2116 $arFields['MEETING_HOST'] = $curEvent['MEETING_HOST'];
2117 }
2118 if (!isset($arFields['MEETING_STATUS']))
2119 {
2120 $arFields['MEETING_STATUS'] = $curEvent['MEETING_STATUS'];
2121 }
2122 if (!isset($arFields['ACTIVE']) && isset($curEvent['ACTIVE']))
2123 {
2124 $arFields['ACTIVE'] = $curEvent['ACTIVE'];
2125 }
2126 if (!isset($arFields['PRIVATE_EVENT']))
2127 {
2128 $arFields['PRIVATE_EVENT'] = $curEvent['PRIVATE_EVENT'];
2129 }
2130 if (!isset($arFields['ACCESSIBILITY']))
2131 {
2132 $arFields['ACCESSIBILITY'] = $curEvent['ACCESSIBILITY'];
2133 }
2134 if (!isset($arFields['IMPORTANCE']))
2135 {
2136 $arFields['IMPORTANCE'] = $curEvent['IMPORTANCE'];
2137 }
2138 if (!isset($arFields['SKIP_TIME']))
2139 {
2140 $arFields['SKIP_TIME'] = $curEvent['DT_SKIP_TIME'] === 'Y';
2141 }
2142 if (!isset($arFields['TZ_FROM']))
2143 {
2144 $arFields['TZ_FROM'] = $curEvent['TZ_FROM'];
2145 }
2146 if (!isset($arFields['TZ_TO']))
2147 {
2148 $arFields['TZ_TO'] = $curEvent['TZ_TO'];
2149 }
2150 if (!isset($arFields['RELATIONS']))
2151 {
2152 $arFields['RELATIONS'] = $curEvent['RELATIONS'];
2153 }
2154 if (!isset($arFields['MEETING']))
2155 {
2156 $arFields['MEETING'] = $curEvent['MEETING'];
2157 }
2158 if (!isset($arFields['SYNC_STATUS']) && $curEvent['SYNC_STATUS'])
2159 {
2160 $arFields['SYNC_STATUS'] = $curEvent['SYNC_STATUS'];
2161 }
2162 if (!isset($arFields['EVENT_TYPE']) && $curEvent['EVENT_TYPE'])
2163 {
2164 $arFields['EVENT_TYPE'] = $curEvent['EVENT_TYPE'];
2165 }
2166 $arFields['MEETING']['LANGUAGE_ID'] = self::getUserLanguageId((int)$userId);
2167
2168 if (
2169 !isset($arFields['ATTENDEES']) && !isset($arFields['ATTENDEES_CODES'])
2170 && $arFields['IS_MEETING']
2171 && !empty($curEvent['ATTENDEE_LIST'])
2172 && is_array($curEvent['ATTENDEE_LIST'])
2173 )
2174 {
2175 $arFields['ATTENDEES'] = [];
2176 foreach ($curEvent['ATTENDEE_LIST'] as $attendee)
2177 {
2178 $arFields['ATTENDEES'][] = $attendee['id'];
2179 }
2180 }
2181 if (!isset($arFields['ATTENDEES_CODES']) && $arFields['IS_MEETING'])
2182 {
2183 $arFields['ATTENDEES_CODES'] = $curEvent['ATTENDEES_CODES'];
2184 }
2185
2186 if (!isset($arFields['LOCATION']) && $curEvent['LOCATION'] !== "")
2187 {
2188 $arFields['LOCATION'] = [
2189 'OLD' => $curEvent['LOCATION'],
2190 'NEW' => $curEvent['LOCATION'],
2191 ];
2192
2193 //if location wasn't change when updating event
2194 $parsedLoc = Bitrix\Calendar\Rooms\Util::parseLocation($curEvent['LOCATION']);
2195 if ($parsedLoc['room_event_id'])
2196 {
2197 $arFields['LOCATION']['NEW'] = 'calendar_' . $parsedLoc['room_id'];
2198 }
2199 }
2200
2201 if ($arFields['IS_MEETING'] && !$bPersonal && $arFields['CAL_TYPE'] === 'user')
2202 {
2203 $arFields['SECTION_ID'] = $curEvent['SECT_ID'];
2204 $arFields['SECTIONS'] = [$curEvent['SECT_ID']];
2205 }
2206
2207 // If it's attendee but modifying called from CalDav methods
2208 if (
2209 (!empty($params['bSilentAccessMeeting'])
2210 || (isset($params['fromWebservice']) && $params['fromWebservice'] === true)
2211 )
2212 && !empty($curEvent['IS_MEETING'])
2213 && ($curEvent['PARENT_ID'] !== $curEvent['ID'])
2214 )
2215 {
2216 // TODO: It called when changes caused in google/webservise side but can't be
2217 // TODO: implemented because user is only attendee, not the owner of the event
2218 //Todo: we have to update such events back to revert changes from google
2219 return true; // CalDav will return 204
2220 }
2221
2222 if (!isset($arFields["RRULE"]) && $curEvent["RRULE"] !== '' && ($params['fromWebservice'] ?? null) !== true)
2223 {
2224 $arFields["RRULE"] = CCalendarEvent::ParseRRULE($curEvent["RRULE"]);
2225 }
2226
2227 if (
2228 (($params['fromWebservice'] ?? null) === true)
2229 && $arFields["RRULE"] === -1
2230 && CCalendarEvent::CheckRecurcion($curEvent)
2231 )
2232 {
2233 $arFields["RRULE"] = CCalendarEvent::ParseRRULE($curEvent['RRULE']);
2234 }
2235
2236 if (!isset($arFields['EXDATE']) && !empty($arFields["RRULE"]))
2237 {
2238 $arFields['EXDATE'] = $curEvent['EXDATE'];
2239 }
2240
2241 else if (
2242 isset($arFields['EXDATE'], $curEvent['EXDATE'])
2243 && $arFields['EXDATE']
2244 && $curEvent['EXDATE']
2245 && !empty($arFields["RRULE"])
2246 )
2247 {
2248 $arFields['EXDATE'] = self::mergeExcludedDates($curEvent['EXDATE'], $arFields['EXDATE']);
2249 }
2250
2251 if ($curEvent)
2252 {
2253 $params['currentEvent'] = $curEvent;
2254 }
2255 }
2256 // $bPersonal should not be reason to skip section rights check
2257 // but if it removed, extranet users can not create events in groups, cause EventAddRule:42
2258 // there section model creation contains bug, which not respect owner_id specific for groups
2259 elseif ($checkPermission && $sectionId > 0 && !$bPersonal)
2260 {
2261 $section = CCalendarSect::GetList(['arFilter' => ['ID' => $sectionId],
2262 'checkPermissions' => false,
2263 'getPermissions' => false,
2264 ])[0] ?? null;
2265
2266 if ($section)
2267 {
2268 $arFields['CAL_TYPE'] = $section['CAL_TYPE'];
2269 }
2270 else
2271 {
2272 return self::ThrowError(Loc::getMessage('EC_ACCESS_DENIED'));
2273 }
2274
2275 if (($section['IS_COLLAB'] ?? null) && Util::isCollabUser($userId))
2276 {
2277 $userCollabIds = UserCollabs::getInstance()->getIds($userId);
2278 if (!in_array((int)$section['OWNER_ID'], $userCollabIds, true))
2279 {
2280 return self::ThrowError(Loc::getMessage('EC_ACCESS_DENIED'));
2281 }
2282 }
2283 else
2284 {
2285 $newEventModel =
2286 EventModel::createNew()
2287 ->setOwnerId((int)$arFields['OWNER_ID'])
2288 ->setSectionId((int)$sectionId)
2289 ->setSectionType($arFields['CAL_TYPE']);
2290
2291 if (!$accessController->check(ActionDictionary::ACTION_EVENT_ADD, $newEventModel))
2292 {
2293 return self::ThrowError(Loc::getMessage('EC_ACCESS_DENIED'));
2294 }
2295 }
2296 }
2297
2298 if ($params['autoDetectSection'] && $sectionId <= 0)
2299 {
2300 $sectionId = false;
2301 if ($arFields['CAL_TYPE'] === 'user')
2302 {
2303 $sectionId = self::GetMeetingSection($arFields['OWNER_ID'], true);
2304 if ($sectionId)
2305 {
2306 $res = CCalendarSect::GetList(
2307 [
2308 'arFilter' => [
2309 'CAL_TYPE' => $arFields['CAL_TYPE'],
2310 'OWNER_ID' => $arFields['OWNER_ID'],
2311 'ID' => $sectionId,
2312 ],
2313 ]
2314 );
2315
2316
2317 if (!$res || !$res[0] || CCalendarSect::CheckGoogleVirtualSection($res[0]['GAPI_CALENDAR_ID']))
2318 {
2319 $sectionId = false;
2320 }
2321 }
2322 else
2323 {
2324 $sectionId = false;
2325 }
2326
2327 if ($sectionId)
2328 {
2329 $arFields['SECTIONS'] = [$sectionId];
2330 }
2331 }
2332
2333 if (!$sectionId)
2334 {
2335 if (empty($arFields['CAL_TYPE']) || empty($arFields['OWNER_ID']))
2336 {
2337 return false;
2338 }
2339
2340 $sectRes = CCalendarSect::GetSectionForOwner(
2341 $arFields['CAL_TYPE'],
2342 $arFields['OWNER_ID'],
2343 $params['autoCreateSection']
2344 );
2345 if ($sectRes['sectionId'] > 0)
2346 {
2347 $sectionId = $sectRes['sectionId'];
2348 $arFields['SECTIONS'] = [$sectionId];
2349 if ($sectRes['autoCreated'])
2350 {
2351 $params['bAffectToDav'] = false;
2352 }
2353 }
2354 else
2355 {
2356 return false;
2357 }
2358 }
2359 }
2360
2361 if (isset($arFields["RRULE"]))
2362 {
2363 $arFields["RRULE"] = CCalendarEvent::CheckRRULE($arFields["RRULE"]);
2364 }
2365
2366 if (!empty($arFields['TZ_FROM']) && is_string($arFields['TZ_FROM']))
2367 {
2368 $tzFrom = Util::prepareTimezone($arFields['TZ_FROM']);
2369
2370 if (
2371 !empty($arFields['TZ_TO'])
2372 && is_string($arFields['TZ_TO'])
2373 && $arFields['TZ_TO'] !== $arFields['TZ_FROM']
2374 )
2375 {
2376 $tzTo = Util::prepareTimezone($arFields['TZ_TO']);
2377 $arFields['TZ_TO'] = $tzTo->getName();
2378 }
2379 else
2380 {
2381 $arFields['TZ_TO'] = $tzFrom->getName();
2382 }
2383
2384 $arFields['TZ_FROM'] = $tzFrom->getName();
2385 }
2386
2387 if ($bNew && !$params['editInstance'] && !($arFields['DAV_XML_ID'] ?? null))
2388 {
2389 $arFields['DAV_XML_ID'] = UidGenerator::createInstance()
2390 ->setDate(
2391 new Date(
2393 $arFields['DATE_FROM'],
2394 $arFields['SKIP_TIME'] ?? null,
2395 $arFields['TZ_FROM'] ?? null,
2396 )
2397 )
2398 )
2399 ->setPortalName(Util::getServerName())
2400 ->setUserId((int)$arFields['OWNER_ID'])
2401 ->getUidWithDate();
2402 }
2403 elseif ($params['editInstance'])
2404 {
2405 $arFields['DAV_XML_ID'] = $params['currentEvent']['DAV_XML_ID'];
2406 }
2407
2408 // Set version
2409 if (!isset($arFields['VERSION']) || ($arFields['VERSION'] <= ($curEvent['VERSION'] ?? null)))
2410 {
2411 $arFields['VERSION'] = ($curEvent['VERSION'] ?? null)
2412 ? $curEvent['VERSION'] + 1
2413 : 1
2414 ;
2415 }
2416
2417 if ($params['autoDetectSection'] && $sectionId <= 0 && $arFields['OWNER_ID'] > 0)
2418 {
2419 $res = CCalendarSect::GetList(
2420 [
2421 'arFilter' => [
2422 'CAL_TYPE' => $arFields['CAL_TYPE'],
2423 'OWNER_ID' => $arFields['OWNER_ID'],
2424 ],
2425 'checkPermissions' => false,
2426 ]
2427 );
2428 if ($res && is_array($res) && isset($res[0]))
2429 {
2430 $sectionId = $res[0]['ID'];
2431 }
2432 else
2433 {
2434 $defCalendar = CCalendarSect::CreateDefault(array(
2435 'type' => $arFields['CAL_TYPE'],
2436 'ownerId' => $arFields['OWNER_ID'],
2437 ));
2438 $sectionId = $defCalendar['ID'];
2439 self::SetCurUserMeetingSection($defCalendar['ID']);
2440
2441 $params['bAffectToDav'] = false;
2442 }
2443 if ($sectionId > 0)
2444 {
2445 $arFields['SECTIONS'] = [$sectionId];
2446 }
2447 else
2448 {
2449 return false;
2450 }
2451 }
2452
2453 $bExchange = self::IsExchangeEnabled() && $arFields['CAL_TYPE'] === 'user';
2454 $bCalDav = self::IsCalDAVEnabled() && $arFields['CAL_TYPE'] === 'user';
2455
2456 if (
2457 (($params['editNextEvents'] ?? null) === false && ($params['recursionEditMode'] ?? null) === 'next')
2458 || (in_array($params['recursionEditMode'] ?? null, ['this', 'skip'])
2459 && ($params['editInstance'] ?? null) === false)
2460 )
2461 {
2462 $params['modeSync'] = false;
2463
2464 if (($params['editParentEvents'] ?? null) === true)
2465 {
2466 $params['modeSync'] = true;
2467 }
2468 }
2469
2470 if (
2471 (
2472 ($params['bAffectToDav'] ?? null) !== false
2473 && ($bExchange || $bCalDav)
2474 && $sectionId > 0
2475 && !(isset($params['dontSyncParent']) && $params['dontSyncParent'])
2476 && ($params['overSaving'] ?? false) !== true
2477 )
2478 || $params['syncCaldav']
2479 )
2480 {
2481 $davParams = [
2482 'bCalDav' => $bCalDav,
2483 'bExchange' => $bExchange,
2484 'sectionId' => $sectionId,
2485 'modeSync' => $params['modeSync'],
2486 'editInstance' => $params['editInstance'],
2487 'originalDavXmlId' => $params['originalDavXmlId'],
2488 'instanceTz' => $params['instanceTz'],
2489 'editParentEvents' => $params['editParentEvents'],
2490 'editNextEvents' => $params['editNextEvents'],
2491 'syncCaldav' => $params['syncCaldav'],
2492 'parentDateFrom' => ($params['parentDateFrom'] ?? null),
2493 'parentDateTo' => ($params['parentDateTo'] ?? null),
2494 ];
2495
2496 $res = CCalendarSync::DoSaveToDav( $arFields, $davParams, $curEvent);
2497
2498 if ($res !== true && self::$silentErrorMode === true)
2499 {
2500 self::ThrowError($res);
2501 }
2502 }
2503
2504 $params['arFields'] = $arFields;
2505 $params['userId'] = $userId;
2506 $params['path'] = self::GetPath($arFields['CAL_TYPE'], $arFields['OWNER_ID'], 1);
2507
2508 $isSharingEvent =
2509 isset($curEvent['EVENT_TYPE'])
2510 && in_array($curEvent['EVENT_TYPE'], SharingEventManager::getSharingEventTypes(), true)
2511 ;
2512
2513 $params['isSharingEvent'] = $isSharingEvent;
2514
2515 if (!empty($arFields['ID']) && $isSharingEvent)
2516 {
2518 }
2519
2520 if (
2521 $curEvent
2522 && in_array(($params['recursionEditMode'] ?? null), ['this', 'next'], true)
2523 && CCalendarEvent::CheckRecurcion($curEvent)
2524 )
2525 {
2526 // Edit only current instance of the set of recurrent events
2527 if ($params['recursionEditMode'] === 'this')
2528 {
2529 // 1. Edit current reccurent event: exclude current date
2530 $excludeDates = CCalendarEvent::GetExDate($curEvent['EXDATE']);
2531 $excludeDate = self::Date(
2532 self::Timestamp($params['currentEventDateFrom'] ?? $arFields['DATE_FROM']),
2533 false
2534 );
2535 $excludeDates[] = $excludeDate;
2536
2537 $saveEventData = [
2538 'recursionEditMode' => 'skip',
2539 'silentErrorMode' => $params['silentErrorMode'],
2540 'sendInvitesToDeclined' => $params['sendInvitesToDeclined'],
2541 'sendInvitations' => false,
2542 'sendEditNotification' => false,
2543 'userId' => $userId,
2544 'requestUid' => $params['requestUid'] ?? null,
2545 'checkPermission' => $checkPermission,
2546 ];
2547
2548 $arFieldsCurrent = [
2549 'ID' => $curEvent["ID"],
2550 'EXDATE' => CCalendarEvent::SetExDate($excludeDates),
2551 ];
2552
2553 if (
2554 !empty($params['arFields']['SECTIONS'][0])
2555 && (int)$curEvent['SECTION_ID'] !== (int)$params['arFields']['SECTIONS'][0]
2556 )
2557 {
2558 $arFieldsCurrent['SECTIONS'] = $params['arFields']['SECTIONS'];
2559 $arFieldsCurrent['CAL_TYPE'] = $params['arFields']['CAL_TYPE'];
2560 $arFieldsCurrent['OWNER_ID'] = $userId;
2561 }
2562
2563 $saveEventData['arFields'] = $arFieldsCurrent;
2564
2565 // Save current event
2566 $id = self::SaveEvent($saveEventData);
2567
2568 // 2. Copy event with new changes, but without recursion
2569 $newParams = $params;
2570
2571 if (!($newParams['arFields']['MEETING']['REINVITE'] ?? null))
2572 {
2573 $newParams['saveAttendeesStatus'] = true;
2574 }
2575
2576 $newParams['arFields']['RECURRENCE_ID'] = $curEvent['RECURRENCE_ID'] ?: $newParams['arFields']['ID'];
2577 $newParams['arFields']['ORIGINAL_RECURSION_ID'] = (int)($curEvent['ORIGINAL_RECURSION_ID'] ?? $newParams['arFields']['ID']);
2578
2579 unset(
2580 $newParams['arFields']['ID'],
2581 $newParams['arFields']['DAV_XML_ID'],
2582 $newParams['arFields']['G_EVENT_ID'],
2583 $newParams['arFields']['SYNC_STATUS'],
2584 $newParams['arFields']['CAL_DAV_LABEL'],
2585 $newParams['arFields']['RRULE'],
2586 $newParams['arFields']['EXDATE'],
2587 $newParams['recursionEditMode'],
2588 );
2589
2590 $newParams['arFields']['REMIND'] = $params['currentEvent']['REMIND'];
2591
2592 $fromTs = self::Timestamp($newParams['currentEventDateFrom']);
2593 $currentFromTs = self::Timestamp($newParams['arFields']['DATE_FROM']);
2594 $length = self::Timestamp($newParams['arFields']['DATE_TO']) - self::Timestamp($newParams['arFields']['DATE_FROM']);
2595
2596 if (!isset($newParams['arFields']['DATE_FROM'], $newParams['arFields']['DATE_TO']))
2597 {
2598 $length = $curEvent['DT_LENGTH'];
2599 $currentFromTs = self::Timestamp($curEvent['DATE_FROM']);
2600 }
2601
2602 $instanceDate = !isset($newParams['arFields']['DATE_FROM'])
2603 || self::Date(self::Timestamp($curEvent['DATE_FROM']), false) === self::Date($currentFromTs, false);
2604
2605 if ($newParams['arFields']['SKIP_TIME'])
2606 {
2607 if ($instanceDate)
2608 {
2609 $newParams['arFields']['DATE_FROM'] = self::Date($fromTs, false);
2610 $newParams['arFields']['DATE_TO'] = self::Date($fromTs + $length - self::GetDayLen(), false);
2611 }
2612 else
2613 {
2614 $newParams['arFields']['DATE_FROM'] = self::Date($currentFromTs, false);
2615 $newParams['arFields']['DATE_TO'] = self::Date($currentFromTs + $length - self::GetDayLen(), false);
2616 }
2617 }
2618 elseif ($instanceDate)
2619 {
2620 $newFromTs = self::DateWithNewTime($currentFromTs, $fromTs);
2621 $newParams['arFields']['DATE_FROM'] = self::Date($newFromTs);
2622 $newParams['arFields']['DATE_TO'] = self::Date($newFromTs + $length);
2623 }
2624
2625 $eventMod = $curEvent;
2626 if (!isset($eventMod['~DATE_FROM']))
2627 {
2628 $eventMod['~DATE_FROM'] = $eventMod['DATE_FROM'];
2629 }
2630
2631 $eventMod['DATE_FROM'] = $newParams['currentEventDateFrom'];
2632 $commentXmlId = CCalendarEvent::GetEventCommentXmlId($eventMod);
2633 $newParams['arFields']['RELATIONS'] = array('COMMENT_XML_ID' => $commentXmlId);
2634 $newParams['editInstance'] = true;
2635 $newParams['sendEditNotification'] = true;
2636
2637 //original instance date start
2638 $newParams['arFields']['ORIGINAL_DATE_FROM'] = self::GetOriginalDate(
2639 $params['currentEvent']['DATE_FROM'],
2640 $eventMod['DATE_FROM'],
2641 $newParams['arFields']['TZ_FROM']
2642 );
2643 $newParams['originalDavXmlId'] = $params['currentEvent']['G_EVENT_ID'];
2644 $newParams['instanceTz'] = $params['currentEvent']['TZ_FROM'];
2645 $newParams['parentDateFrom'] = $params['currentEvent']['DATE_FROM'];
2646 $newParams['parentDateTo'] = $params['currentEvent']['DATE_TO'];
2647 $newParams['requestUid'] = $params['requestUid'] ?? null;
2648 $newParams['sendInvitesToDeclined'] = $params['sendInvitesToDeclined'] ?? null;
2649
2650 // check instance changes from original recurrent event
2651 $eventMod['DATE_FROM'] = $newParams['arFields']['ORIGINAL_DATE_FROM'];
2652 $eventMod['DATE_TO'] = self::Date(
2653 self::Timestamp($eventMod['DATE_FROM'], false) + $curEvent['DT_LENGTH']
2654 );
2655 unset($eventMod['RRULE']);
2656
2657 $instanceChanges = CCalendarEvent::CheckEntryChanges($newParams['arFields'], $eventMod);
2658 if (!empty($instanceChanges))
2659 {
2660 $newParams['instanceChanges'] = $instanceChanges;
2661 }
2662
2663 $result['recEventId'] = self::SaveEvent($newParams);
2665 $result['recEventId'] ?? 0,
2666 $newParams['arFields']['ORIGINAL_RECURSION_ID'] ?? 0,
2667 );
2668 }
2669 // Edit all next instances of the set of recurrent events
2670 elseif(($params['recursionEditMode']) === 'next')
2671 {
2672 $currentDateTimestamp = self::Timestamp($params['currentEventDateFrom'] ?? null);
2673
2674 // Copy event with new changes
2675 $newParams = $params;
2676 $recId = $curEvent['RECURRENCE_ID'] ?: $newParams['arFields']['ID'];
2677
2678 $newParams['arFields']['RELATIONS'] ??= [];
2679 $newParams['arFields']['RELATIONS'] = [
2680 'ORIGINAL_RECURSION_ID' => $curEvent['RELATIONS']['ORIGINAL_RECURSION_ID'] ?? $recId,
2681 ];
2682 $newParams['arFields']['ORIGINAL_RECURSION_ID'] = (int)($curEvent['ORIGINAL_RECURSION_ID'] ?? $recId);
2683
2684 if (empty($newParams['arFields']['MEETING']['REINVITE']))
2685 {
2686 $newParams['saveAttendeesStatus'] = true;
2687 }
2688
2689 $currentFromTs = self::Timestamp($newParams['arFields']['DATE_FROM'] ?? null);
2690 $length = self::Timestamp($newParams['arFields']['DATE_TO']) - self::Timestamp($newParams['arFields']['DATE_FROM']);
2691
2692 if (!isset($newParams['arFields']['DATE_FROM'], $newParams['arFields']['DATE_TO']))
2693 {
2694 $length = $curEvent['DT_LENGTH'];
2695 $currentFromTs = self::Timestamp($curEvent['DATE_FROM']);
2696 }
2697
2698 // Check location changes
2699 [$isRecurrentLocationChanged, $newParams] = self::checkRecurrenceLocationChanges($newParams, $curEvent);
2700 // Check name changes
2701 $isRecurrentNameChanged = !empty($newParams['arFields']['NAME']) && ($curEvent['NAME'] !== $newParams['arFields']['NAME']);
2702 // Check time changes
2703 $isRecurrentTimeChanged = (self::Timestamp($curEvent['DATE_FROM'], false) % self::DAY_LENGTH) !== ($currentFromTs % self::DAY_LENGTH);
2704 // Check attendees changes
2705 $isRecurrentAttendeesChanged = self::checkRecurrenceAttendeesChanges($newParams, $curEvent);
2706
2707 $instanceDate = !isset($newParams['arFields']['DATE_FROM'])
2708 || self::Date(self::Timestamp($curEvent['DATE_FROM']), false) === self::Date($currentFromTs, false)
2709 ;
2710
2711 if ($newParams['arFields']['SKIP_TIME'])
2712 {
2713 if ($instanceDate)
2714 {
2715 $newParams['arFields']['DATE_FROM'] = self::Date($currentDateTimestamp, false);
2716 $newParams['arFields']['DATE_TO'] = self::Date($currentDateTimestamp + $length, false);
2717 }
2718 else
2719 {
2720 $newParams['arFields']['DATE_FROM'] = self::Date($currentFromTs, false);
2721 $newParams['arFields']['DATE_TO'] = self::Date($currentFromTs + $length, false);
2722 }
2723 }
2724 elseif ($instanceDate)
2725 {
2726 $newFromTs = self::DateWithNewTime($currentFromTs, $currentDateTimestamp);
2727 $newParams['arFields']['DATE_FROM'] = self::Date($newFromTs);
2728 $newParams['arFields']['DATE_TO'] = self::Date($newFromTs + $length);
2729 }
2730
2731 if (isset($curEvent['EXDATE']) && $curEvent['EXDATE'] !== '')
2732 {
2733 $newParams['arFields']['EXDATE'] = $curEvent['EXDATE'];
2734 }
2735
2736 if (!isset($newParams['arFields']['REMIND']) && !empty($curEvent['REMIND']))
2737 {
2738 $newParams['arFields']['REMIND'] = $curEvent['REMIND'];
2739 }
2740
2741 if (isset($newParams['arFields']['RRULE']['COUNT']) && $newParams['arFields']['RRULE']['COUNT'] > 0)
2742 {
2743 $countParams = [
2744 'rrule' => $newParams['arFields']['RRULE'],
2745 'dateFrom' => $curEvent['DATE_FROM'],
2746 'dateTo' => $newParams['arFields']['DATE_FROM'],
2747 'timeZone' => $curEvent['TZ_FROM'],
2748 ];
2749
2750 $newParams['arFields']['RRULE']['COUNT'] = self::CountNumberFollowEvents($countParams);
2751 unset($newParams['arFields']['RRULE']['UNTIL'], $newParams['arFields']['RRULE']['~UNTIL']);
2752 }
2753
2754 if (
2755 isset($newParams['arFields']['RRULE']['FREQ'], $curEvent['RRULE']['FREQ'])
2756 && $newParams['arFields']['RRULE']['FREQ'] === 'WEEKLY'
2757 && $curEvent['RRULE']['FREQ'] === 'WEEKLY'
2758 && $newParams['arFields']['RRULE']['BYDAY'] === $curEvent['RRULE']['BYDAY']
2759 )
2760 {
2761 $currentDate = new Type\Date($params['currentEventDateFrom']);
2762 $currentFromDate = new Type\Date($newParams['arFields']['DATE_FROM']);
2763 $currentDateWeekday = self::WeekDayByInd($currentDate->format('N'));
2764 $currentFromDateWeekday = self::WeekDayByInd($currentFromDate->format('N'));
2765
2766 if (isset($newParams['arFields']['RRULE']['BYDAY'][$currentDateWeekday]))
2767 {
2768 unset($newParams['arFields']['RRULE']['BYDAY'][$currentDateWeekday]);
2769 }
2770
2771 $newParams['arFields']['RRULE']['BYDAY'][$currentFromDateWeekday] = $currentFromDateWeekday;
2772 }
2773
2774 // Check if it's first instance of the series, so we shouldn't create another event
2775 if (self::Date(self::Timestamp($curEvent['DATE_FROM']), false) === self::Date($currentDateTimestamp, false))
2776 {
2777 $newParams['recursionEditMode'] = 'skip';
2778 }
2779 else
2780 {
2781 // 1. Edit current recurrent event: set finish date with date of current instance
2782 $arFieldsCurrent = [
2783 "ID" => $curEvent["ID"],
2784 "RRULE" => CCalendarEvent::ParseRRULE($curEvent['RRULE']),
2785 ];
2786 $arFieldsCurrent['RRULE']['UNTIL'] = self::Date($currentDateTimestamp - self::GetDayLen(), false);
2787 unset($arFieldsCurrent['RRULE']['~UNTIL'], $arFieldsCurrent['RRULE']['COUNT']);
2788
2789 if (
2790 !empty($params['arFields']['SECTIONS'][0])
2791 && (int)$curEvent['SECTION_ID'] !== (int)$params['arFields']['SECTIONS'][0]
2792 )
2793 {
2794 $arFieldsCurrent['SECTIONS'] = $params['arFields']['SECTIONS'];
2795 }
2796
2797 // Save current event
2798 $id = self::SaveEvent([
2799 'arFields' => $arFieldsCurrent,
2800 'silentErrorMode' => $params['silentErrorMode'] ?? null,
2801 'recursionEditMode' => 'skip',
2802 'sendInvitations' => false,
2803 'sendEditNotification' => false,
2804 'sendInvitesToDeclined' => $params['sendInvitesToDeclined'] ?? null,
2805 'userId' => $userId,
2806 'editNextEvents' => true,
2807 'editParentEvents' => true,
2808 'checkPermission' => $checkPermission,
2809 'requestUid' => $params['requestUid'] ?? null,
2810 'checkLocationOccupancyFields' => $newParams['arFields'],
2811 'checkLocationOccupancy' => $params['checkLocationOccupancy'] ?? false,
2812 ]);
2813
2814 unset(
2815 $newParams['arFields']['ID'],
2816 $newParams['arFields']['DAV_XML_ID'],
2817 $newParams['arFields']['G_EVENT_ID'],
2818 $newParams['recursionEditMode']
2819 );
2820 }
2821
2822 if (empty($newParams['arFields']['DAV_XML_ID']))
2823 {
2824 $newParams['arFields']['DAV_XML_ID'] = UidGenerator::createInstance()
2825 ->setPortalName(Util::getServerName())
2826 ->setDate(new Date(
2828 $newParams['arFields']['ORIGINAL_DATE_FROM'] ?? null,
2829 $newParams['arFields']['SKIP_TIME'] ?? null,
2830 $newParams['arFields']['TZ_FROM'] ?? null
2831 )
2832 ))
2833 ->setUserId((int)($newParams['arFields']['OWNER_ID'] ?? null))
2834 ->getUidWithDate()
2835 ;
2836 }
2837
2838 $newParams['sendInvitesToDeclined'] = $params['sendInvitesToDeclined'];
2839 $newParams['editNextEvents'] = true;
2840 $newParams['previousRecurrentId'] = $recId;
2841
2842 $result = self::SaveEvent($newParams);
2843
2844 if (!is_array($result))
2845 {
2846 $result = [
2847 'id' => $result,
2848 'recEventId' => $result,
2849 ];
2850 }
2852 $result['id'] ?? 0,
2853 $newParams['arFields']['ORIGINAL_RECURSION_ID'] ?? 0,
2854 );
2855
2856 if ($recId)
2857 {
2858 $recRelatedEvents = CCalendarEvent::GetEventsByRecId($recId, false);
2859
2860 foreach($recRelatedEvents as $recRelatedEvent)
2861 {
2862 if ($recRelatedEvent['ID'] === $result['id'])
2863 {
2864 continue;
2865 }
2866
2867 if ((int)$recRelatedEvent['ID'] !== (int)$recRelatedEvent['PARENT_ID'])
2868 {
2869 continue;
2870 }
2871
2872 $evFromTs = self::Timestamp($recRelatedEvent['DATE_FROM'], false);
2873 $instanceLength = $recRelatedEvent['DT_LENGTH'] ?? $length;
2874
2875 if ($evFromTs > $currentDateTimestamp)
2876 {
2877 $newParams['arFields']['ID'] = $recRelatedEvent['ID'];
2878 $newParams['arFields']['RRULE'] = CCalendarEvent::ParseRRULE($recRelatedEvent['RRULE']);
2879
2880 /*
2881 * Set correct date for related
2882 * if full day event - set related date
2883 * else if recurrent event time change - set related date and recurrent event time
2884 * else - keep related date and related time
2885 */
2886 if ($newParams['arFields']['SKIP_TIME'])
2887 {
2888 $newParams['arFields']['DATE_FROM'] = self::Date($evFromTs, false);
2889 $newParams['arFields']['DATE_TO'] = self::Date(self::Timestamp($recRelatedEvent['DATE_TO']), false);
2890 }
2891 else if ($isRecurrentTimeChanged)
2892 {
2893 $newFromTs = self::DateWithNewTime($currentFromTs, $evFromTs);
2894 $newParams['arFields']['DATE_FROM'] = self::Date($newFromTs);
2895 $newParams['arFields']['DATE_TO'] = self::Date($newFromTs + $length);
2896 }
2897 else
2898 {
2899 $newParams['arFields']['DATE_FROM'] = self::Date($evFromTs);
2900 $newParams['arFields']['DATE_TO'] = self::Date($evFromTs + $instanceLength);
2901 }
2902
2903 /*
2904 * Set correct location for related
2905 * Entry condition - location on recurrent was not changed
2906 * if related has no location or different - keep it
2907 */
2908 $newRecurrentLocation = null;
2909 if (!$isRecurrentLocationChanged)
2910 {
2911 $newRecurrentLocation = $newParams['arFields']['LOCATION'] ?? '';
2912
2913 if (!empty($recRelatedEvent['LOCATION']))
2914 {
2915 $parsedRelatedLocation = Rooms\Util::parseLocation($recRelatedEvent['LOCATION']);
2916 if (!empty($parsedRelatedLocation['room_event_id']))
2917 {
2918 $recRelatedEvent['LOCATION'] = 'calendar_' . $parsedRelatedLocation['room_id'];
2919 }
2920 }
2921
2922 $newParams['arFields']['LOCATION'] = $recRelatedEvent['LOCATION'] ?? '';
2923 }
2924
2925 /*
2926 * Set correct name for related
2927 */
2928 if (!$isRecurrentNameChanged)
2929 {
2930 $newParams['arFields']['NAME'] = $recRelatedEvent['NAME'] ?? $newParams['arFields']['NAME'];
2931 }
2932
2933 /*
2934 * Set correct attendees for related
2935 * If recurrent attendees has not changed - keep related attendees
2936 */
2937 if (!$isRecurrentAttendeesChanged)
2938 {
2939 $newParams['arFields']['ATTENDEES_CODES'] = $recRelatedEvent['ATTENDEES_CODES']
2940 ?? $newParams['arFields']['ATTENDEES_CODES']
2941 ;
2942 }
2943
2944 /*
2945 * Set additional params
2946 */
2947 $newParams['arFields']['RECURRENCE_ID'] = $result['id'];
2948 $newParams['originalDavXmlId'] = $result['originalDavXmlId'];
2949
2950 $parentDateTime = self::Date($currentFromTs);
2951 $newParams['arFields']['ORIGINAL_DATE_FROM'] = self::GetOriginalDate(
2952 $parentDateTime,
2953 $recRelatedEvent['ORIGINAL_DATE_FROM'] ?? $newParams['currentEventDateFrom'],
2954 $result['instanceTz']
2955 );
2956
2957 $newParams['instanceTz'] = $result['instanceTz'];
2958 $newParams['editInstance'] = true;
2959
2960 unset($newParams['arFields']['EXDATE']);
2961
2962 if (isset($newParams['arFields']['RELATIONS']['ORIGINAL_RECURSION_ID']))
2963 {
2964 unset($newParams['arFields']['RELATIONS']);
2965 }
2966
2967 self::SaveEvent($newParams);
2968
2969 if ($newRecurrentLocation !== null)
2970 {
2971 $newParams['arFields']['LOCATION'] = $newRecurrentLocation;
2972 }
2973 }
2974 }
2975 }
2976 }
2977 }
2978 else
2979 {
2980 if (($params['recursionEditMode'] ?? null) !== 'all')
2981 {
2982 $params['recursionEditMode'] = 'skip';
2983 }
2984 else
2985 {
2986 $params['recursionEditMode'] = '';
2987 }
2988
2989 $id = CCalendarEvent::Edit($params);
2990
2991 if ($id)
2992 {
2993 $UFs = $params['UF'] ?? null;
2994 if(!empty($UFs) && is_array($UFs))
2995 {
2996 CCalendarEvent::UpdateUserFields($id, $UFs, false);
2997
2998 if (!empty($arFields['IS_MEETING']) && !empty($UFs['UF_WEBDAV_CAL_EVENT']))
2999 {
3000 $UF = $GLOBALS['USER_FIELD_MANAGER']->GetUserFields("CALENDAR_EVENT", $id, LANGUAGE_ID);
3001 self::UpdateUFRights(
3002 $UFs['UF_WEBDAV_CAL_EVENT'],
3003 $arFields['ATTENDEES_CODES'] ?? null,
3004 $UF['UF_WEBDAV_CAL_EVENT'] ?? null
3005 );
3006 }
3007
3008 CCalendarEvent::updateSearchIndex((int)$id, [
3009 'updateAllByParent' => true,
3010 ]);
3011 }
3012 }
3013
3014 if ($params['editNextEvents'] === true && $params['editParentEvents'] === false)
3015 {
3016 $result['originalDate'] = $params['arFields']['DATE_FROM'];
3017 $result['originalDavXmlId'] = $params['arFields']['DAV_XML_ID'];
3018 $result['instanceTz'] = $params['arFields']['TZ_FROM'];
3019 $result['recEventId'] = $id;
3020 }
3021
3022 // Here we should select all events connected with edited via RECURRENCE_ID:
3023 // It could be original source event (without RECURRENCE_ID) or sibling events
3024 if (
3025 $curEvent
3026 && !$params['recursionEditMode']
3027 && !($params['arFields']['RECURRENCE_ID'] ?? null)
3028 && CCalendarEvent::CheckRecurcion($curEvent)
3029 )
3030 {
3031 $events = [];
3032 $recId = $curEvent['RECURRENCE_ID'] ?: $curEvent['ID'];
3033
3037// if ($curEvent['RECURRENCE_ID'] && $curEvent['RECURRENCE_ID'] !== $curEvent['ID'])
3038// {
3039// $masterEvent = CCalendarEvent::GetById($curEvent['RECURRENCE_ID']);
3040// if ($masterEvent)
3041// {
3042// $events[] = $masterEvent;
3043// }
3044// }
3045
3046 $currentFromTs = self::Timestamp($params['arFields']['DATE_FROM'], false);
3047 $length = self::Timestamp($params['arFields']['DATE_TO'], false) - self::Timestamp($params['arFields']['DATE_FROM'], false);
3048
3049 if (!isset($params['arFields']['DATE_FROM'], $params['arFields']['DATE_TO']))
3050 {
3051 $length = $curEvent['DT_LENGTH'];
3052 $currentFromTs = self::Timestamp($curEvent['DATE_FROM']);
3053 }
3054
3055 // Check location changes
3056 [$isRecurrentLocationChanged, $params] = self::checkRecurrenceLocationChanges($params, $curEvent);
3057 // Check name changes
3058 $isRecurrentNameChanged = !empty($params['arFields']['NAME']) && ($curEvent['NAME'] !== $params['arFields']['NAME']);
3059 // Check time changes
3060 $isRecurrentTimeChanged = (self::Timestamp($curEvent['DATE_FROM'], false) % self::DAY_LENGTH) !== ($currentFromTs % self::DAY_LENGTH);
3061 // Check attendees changes
3062 $isRecurrentAttendeesChanged = self::checkRecurrenceAttendeesChanges($params, $curEvent);
3063
3064
3065 if ($recId)
3066 {
3067 $instances = CCalendarEvent::GetEventsByRecId($recId, false);
3068
3069 if ($instances)
3070 {
3071 $events = array_merge($events, $instances);
3072 }
3073 }
3074
3075 foreach($events as $ev)
3076 {
3077 if ($ev['ID'] === $curEvent['ID'])
3078 {
3079 continue;
3080 }
3081
3082 if ($ev['PARENT_ID'] !== $ev['ID'])
3083 {
3084 continue;
3085 }
3086
3087 $newParams = $params;
3088
3089 $newParams['arFields']['ID'] = $ev['ID'];
3090 $newParams['arFields']['RECURRENCE_ID'] = $ev['RECURRENCE_ID'];
3091 $newParams['arFields']['DAV_XML_ID'] = $ev['DAV_XML_ID'];
3092 $newParams['arFields']['G_EVENT_ID'] = $ev['G_EVENT_ID'];
3093 $newParams['arFields']['ORIGINAL_DATE_FROM'] = self::GetOriginalDate($arFields['DATE_FROM'], $ev['ORIGINAL_DATE_FROM'], $arFields['TZ_FROM']);
3094 $newParams['arFields']['CAL_DAV_LABEL'] = $ev['CAL_DAV_LABEL'];
3095 $newParams['arFields']['RRULE'] = CCalendarEvent::ParseRRULE($ev['RRULE']);
3096 $newParams['recursionEditMode'] = 'skip';
3097 $newParams['currentEvent'] = $ev;
3098
3099 $eventFromTs = self::Timestamp($ev['DATE_FROM']);
3100 $instanceLength = $ev['DT_LENGTH'] ?? $length;
3101
3102 /*
3103 * Set correct date for related
3104 * if full day event - set related date
3105 * else if recurrent event time change - set related date and recurrent event time
3106 * else - keep related date and related time
3107 */
3108 if ($newParams['arFields']['SKIP_TIME'])
3109 {
3110 $newParams['arFields']['DATE_FROM'] = $ev['DATE_FROM'];
3111 $newParams['arFields']['DATE_TO'] = self::Date($eventFromTs + $length, false);
3112 }
3113 else if ($isRecurrentTimeChanged)
3114 {
3115 $newFromTs = self::DateWithNewTime($currentFromTs, $eventFromTs);
3116 $newParams['arFields']['DATE_FROM'] = self::Date($newFromTs);
3117 $newParams['arFields']['DATE_TO'] = self::Date($newFromTs + $length);
3118 }
3119 else
3120 {
3121 $newParams['arFields']['DATE_FROM'] = self::Date($eventFromTs);
3122 $newParams['arFields']['DATE_TO'] = self::Date($eventFromTs + $instanceLength);
3123 }
3124
3125 /*
3126 * Set correct location for related
3127 * Entry condition - location on recurrent was not changed
3128 * if related has no location or different - keep it
3129 */
3130 if (!$isRecurrentLocationChanged)
3131 {
3132 if (!empty($ev['LOCATION']))
3133 {
3134 $parsedRelatedLocation = Rooms\Util::parseLocation($ev['LOCATION']);
3135 if (!empty($parsedRelatedLocation['room_event_id']))
3136 {
3137 $ev['LOCATION'] = 'calendar_' . $parsedRelatedLocation['room_id'];
3138 }
3139 }
3140
3141 $newParams['arFields']['LOCATION'] = $ev['LOCATION'] ?? '';
3142 }
3143
3144 /*
3145 * Set correct name for related
3146 */
3147 if (!$isRecurrentNameChanged)
3148 {
3149 $newParams['arFields']['NAME'] = $ev['NAME'] ?? $newParams['arFields']['NAME'];
3150 }
3151
3152 /*
3153 * Set correct attendees for related
3154 * If recurrent attendees has not changed - keep related attendees
3155 */
3156 if (!$isRecurrentAttendeesChanged)
3157 {
3158 $newParams['arFields']['ATTENDEES_CODES'] = $ev['ATTENDEES_CODES']
3159 ?? $newParams['arFields']['ATTENDEES_CODES']
3160 ;
3161 }
3162
3163 if (isset($ev['EXDATE']) && $ev['EXDATE'])
3164 {
3165 $newParams['arFields']['EXDATE'] = $ev['EXDATE'];
3166 }
3167
3168 if (isset($newParams['arFields']['RELATIONS']['ORIGINAL_RECURSION_ID']))
3169 {
3170 unset($newParams['arFields']['RELATIONS']);
3171 }
3172
3173 self::SaveEvent($newParams);
3174 }
3175 }
3176
3177 if ($id)
3178 {
3179 self::syncChange($id, $arFields, $params, $curEvent ?: null);
3180 }
3181
3182 $arFields['ID'] = $id;
3183 if (($params['overSaving'] ?? false) !== true)
3184 {
3185 foreach(GetModuleEvents("calendar", "OnAfterCalendarEventEdit", true) as $arEvent)
3186 {
3187 ExecuteModuleEventEx($arEvent, array($arFields, $bNew, $userId));
3188 }
3189 }
3190 }
3191
3192 self::SetSilentErrorMode($silentErrorModePrev);
3193
3194 $result['id'] = $id ?? null;
3195
3196 return $result;
3197 }
3198
3199 private static function CountNumberFollowEvents($params)
3200 {
3201 $curCount = self::CountPastEvents($params);
3202
3203 $count = (int)$params['rrule']['COUNT'] - $curCount;
3204
3205 return (string)$count;
3206 }
3207
3208 public static function getUserLanguageId(?int $userId): string
3209 {
3210 if (!$userId)
3211 {
3212 return LANGUAGE_ID;
3213 }
3214
3215 if (isset(self::$userLanguageId[$userId]))
3216 {
3217 return self::$userLanguageId[$userId];
3218 }
3219
3221 ->where('ID', $userId)
3222 ->setSelect(['NOTIFICATION_LANGUAGE_ID'])
3223 ->exec()
3224 ->fetch()
3225 ;
3226
3227 self::$userLanguageId[$userId] = $user['NOTIFICATION_LANGUAGE_ID'] ?? LANGUAGE_ID;
3228
3229 return self::$userLanguageId[$userId];
3230 }
3231
3232 public static function CountPastEvents($params)
3233 {
3234 $curCount = 0;
3235
3236 $dateFromTz = !empty($params['timeZone']) ? new \DateTimeZone($params['timeZone']) : new \DateTimeZone("UTC");
3237 $dateToTz = !empty($params['timeZone']) ? new \DateTimeZone($params['timeZone']) : new \DateTimeZone("UTC");
3238 $dateFrom = new Main\Type\DateTime(date('Ymd His',self::Timestamp($params['dateFrom'])), 'Ymd His', $dateFromTz);
3239 $dateTo = new Main\Type\DateTime(date('Ymd His',self::Timestamp($params['dateTo'])), 'Ymd His', $dateToTz);
3240
3241 $parentInfoDate = getdate($dateFrom->getTimestamp());
3242 $dateTo->setTime($parentInfoDate['hours'], $parentInfoDate['minutes']);
3243
3244 $diff = $dateFrom->getDiff($dateTo);
3245
3246 if ($params['rrule']['FREQ'] === 'DAILY')
3247 {
3248 $diff = (int)$diff->format('%a');
3249 $curCount = $diff / (int)$params['rrule']['INTERVAL'];
3250 }
3251
3252 if ($params['rrule']['FREQ'] === 'WEEKLY')
3253 {
3254 $diff = (int)$diff->format('%a');
3255
3256 for ($i = 0; $i < $diff; $i++)
3257 {
3258 $timestamp = $dateFrom->getTimestamp();
3259 $date = getdate($timestamp);
3260 $weekday = mb_strtoupper(mb_substr($date['weekday'], 0, 2));
3261
3262 if (in_array($weekday, $params['rrule']['BYDAY'], true))
3263 {
3264 $curCount++;
3265 }
3266
3267 $dateFrom = $dateFrom->add('+1 day');
3268 }
3269 }
3270
3271 if ($params['rrule']['FREQ'] === 'MONTHLY')
3272 {
3273 $diff = (int)$diff->format('%m');
3274 $curCount = $diff / (int)$params['rrule']['INTERVAL'];
3275 }
3276
3277 if ($params['rrule']['FREQ'] === 'YEARLY')
3278 {
3279 $diff = (int)$diff->format('%y');
3280 $curCount = $diff / (int)$params['rrule']['INTERVAL'];
3281 }
3282
3283 return $curCount;
3284 }
3285
3286 public static function ThrowError($str)
3287 {
3288 if (self::$silentErrorMode)
3289 {
3290 self::$errors[] = $str;
3291 return false;
3292 }
3293
3294 global $APPLICATION;
3295 echo '<!-- BX_EVENT_CALENDAR_ACTION_ERROR:'.$str.'-->';
3296 $APPLICATION->ThrowException($str);
3297 }
3298
3299 public static function GetErrors()
3300 {
3301 return self::$errors;
3302 }
3303
3304 private static ?array $tasksForUpdateUFRights = null;
3305
3306 private static function getTasksForUpdateUFRights(): array
3307 {
3308 if (!Loader::includeModule('webdav'))
3309 {
3310 return [];
3311 }
3312
3313 if (self::$tasksForUpdateUFRights === null)
3314 {
3315 self::$tasksForUpdateUFRights = CWebDavIblock::GetTasks() ?? [];
3316 }
3317
3318 return self::$tasksForUpdateUFRights;
3319 }
3320
3321 public static function UpdateUFRights($files, $rights, $ufEntity = [])
3322 {
3323 global $USER;
3324 $arTasks = self::getTasksForUpdateUFRights();
3325
3326 if (!is_array($rights) || empty($rights))
3327 {
3328 return false;
3329 }
3330 if ($files===null || $files===false)
3331 {
3332 return false;
3333 }
3334 if (!is_array($files))
3335 {
3336 $files = array($files);
3337 }
3338 if (empty($files))
3339 {
3340 return false;
3341 }
3342 if (!Loader::includeModule('iblock') || !Loader::includeModule('webdav'))
3343 {
3344 return false;
3345 }
3346
3347 $arFiles = [];
3348 foreach($files as $id)
3349 {
3350 $id = (int)$id;
3351 if ($id)
3352 {
3353 $arFiles[] = $id;
3354 }
3355 }
3356
3357 if (empty($arFiles))
3358 {
3359 return false;
3360 }
3361
3362 $arCodes = [];
3363 foreach($rights as $value)
3364 {
3365 if (mb_strpos($value, 'SG') === 0)
3366 {
3367 $arCodes[] = $value . '_K';
3368 }
3369 $arCodes[] = $value;
3370 }
3371 $arCodes = array_unique($arCodes);
3372
3373 $i = 0;
3374 $arViewRights = $arEditRights = [];
3375 $curUserID = 'U'.$USER->GetID();
3376 foreach($arCodes as $right)
3377 {
3378 if ($curUserID == $right) // do not override owner's rights
3379 continue;
3380 $key = 'n' . $i++;
3381 $arViewRights[$key] = array(
3382 'GROUP_CODE' => $right,
3383 'TASK_ID' => $arTasks['R'],
3384 );
3385 }
3386
3387 $ibe = new CIBlockElement();
3388 $dbWDFile = $ibe->GetList([], array('ID' => $arFiles, 'SHOW_NEW' => 'Y'), false, false, array('ID', 'NAME', 'SECTION_ID', 'IBLOCK_ID', 'WF_NEW'));
3389 $iblockIds = [];
3390 if ($dbWDFile)
3391 {
3392 while ($arWDFile = $dbWDFile->Fetch())
3393 {
3394 $id = $arWDFile['ID'];
3395
3396 if ($arWDFile['WF_NEW'] === 'Y')
3397 {
3398 $ibe->Update($id, ['BP_PUBLISHED' => 'Y']);
3399 }
3400
3401 if (CIBlock::GetArrayByID($arWDFile['IBLOCK_ID'], "RIGHTS_MODE") === "E")
3402 {
3403 $ibRights = CWebDavIblock::_get_ib_rights_object('ELEMENT', $id, $arWDFile['IBLOCK_ID']);
3404 $ibRights->SetRights(CWebDavTools::appendRights($ibRights, $arViewRights, $arTasks));
3405 if(empty($iblockIds[$arWDFile['IBLOCK_ID']]))
3406 $iblockIds[$arWDFile['IBLOCK_ID']] = $arWDFile['IBLOCK_ID'];
3407 }
3408 }
3409
3410 global $CACHE_MANAGER;
3411
3412 foreach ($iblockIds as $iblockId)
3413 {
3414 $CACHE_MANAGER->ClearByTag('iblock_id_' . $iblockId);
3415 }
3416
3417 unset($iblockId);
3418 }
3419 }
3420
3421 public static function TempUser($TmpUser = false, $create = true, $ID = false)
3422 {
3423 global $USER;
3424 if ($create && $TmpUser === false && (!$USER || !is_object($USER)))
3425 {
3426 $USER = new CUser;
3427 if ($ID && (int)$ID > 0)
3428 {
3429 $USER->Authorize((int)$ID);
3430 }
3431
3432 return $USER;
3433 }
3434
3435 if (!$create && $USER && is_object($USER))
3436 {
3437 unset($USER);
3438 return false;
3439 }
3440 return false;
3441 }
3442
3443 public static function SaveSection($params)
3444 {
3445 $type = $params['arFields']['CAL_TYPE'] ?? self::$type;
3446
3447 // Exchange
3448 if (($params['bAffectToDav'] ?? null) !== false && $type === 'user' && self::IsExchangeEnabled(self::$ownerId))
3449 {
3450 $exchRes = true;
3451 $ownerId = $params['arFields']['OWNER_ID'] ?? self::$ownerId;
3452
3453 if ($params['arFields']['IS_EXCHANGE'])
3454 {
3455 $exchRes = CDavExchangeCalendar::DoAddCalendar($ownerId, $params['arFields']);
3456 }
3457
3458 if ($exchRes !== true)
3459 {
3460 if (!is_array($exchRes) || !isset($exchRes['XML_ID']))
3461 {
3462 return self::ThrowError(self::CollectExchangeErrors($exchRes));
3463 }
3464
3465 // // It's ok, we successfuly save event to exchange calendar - and save it to DB
3466 $params['arFields']['DAV_EXCH_CAL'] = $exchRes['XML_ID'];
3467 $params['arFields']['DAV_EXCH_MOD'] = $exchRes['MODIFICATION_LABEL'];
3468 }
3469 }
3470
3471 // Save here
3472 $id = (int)CCalendarSect::Edit($params);
3473 self::ClearCache(['section_list', 'event_list']);
3474
3475 return $id;
3476 }
3477
3478 public static function ClearCache($arPath = [])
3479 {
3480 global $CACHE_MANAGER;
3481
3482 $CACHE_MANAGER->ClearByTag("CALENDAR_EVENT_LIST");
3483
3484 if (empty($arPath))
3485 {
3486 $arPath = [
3487 'access_tasks',
3488 'type_list',
3489 'section_list',
3490 'attendees_list',
3491 'event_list',
3492 ];
3493 }
3494 elseif (!is_array($arPath))
3495 {
3496 $arPath = [$arPath];
3497 }
3498
3499 if (!empty($arPath))
3500 {
3501 $cache = new CPHPCache;
3502 foreach($arPath as $path)
3503 {
3504 if ($path)
3505 {
3506 $cache->CleanDir(self::CachePath() . $path);
3507 }
3508 }
3509 }
3510 }
3511
3512 public static function CachePath()
3513 {
3514 return self::$cachePath;
3515 }
3516
3517 // * * * * * * * * * * * * CalDAV + Exchange * * * * * * * * * * * * * * * *
3518
3519 public static function SyncCalendarItems($connectionType, $calendarId, $arCalendarItems): array
3520 {
3521 $arResult = [];
3522 self::$silentErrorMode = true;
3523
3524 [$sectionId, $entityType, $entityId] = $calendarId;
3525 $entityType = mb_strtolower($entityType);
3526
3527 if ($connectionType === Bitrix\Calendar\Sync\Caldav\Helper::EXCHANGE_TYPE)
3528 {
3529 $xmlIdField = 'DAV_EXCH_LABEL';
3530 }
3531 elseif ($connectionType === Bitrix\Calendar\Sync\Caldav\Helper::CALDAV_TYPE)
3532 {
3533 $xmlIdField = 'CAL_DAV_LABEL';
3534 }
3535 else
3536 {
3537 return [];
3538 }
3539
3540 $eventsList = CCalendarEvent::GetList([
3541 'arSelect' => [
3542 ...CCalendarEvent::$defaultSelectEvent,
3543 'RECURRENCE_ID',
3544 'DAV_XML_ID',
3545 'DAV_EXCH_LABEL',
3546 'CAL_DAV_LABEL',
3547 ],
3548 'arFilter' => [
3549 'CAL_TYPE' => $entityType,
3550 'OWNER_ID' => $entityId,
3551 'SECTION' => $sectionId,
3552 ],
3553 'getUserfields' => false,
3554 'parseRecursion' => false,
3555 'fetchAttendees' => false,
3556 'fetchMeetings' => false,
3557 'userId' => $entityType === 'user' ? $entityId : 0,
3558 ]);
3559
3560 foreach ($eventsList as $event)
3561 {
3562 $eventXmlId = $event['DAV_XML_ID'];
3563 if ($event['RECURRENCE_ID'] && $instanceChangeKey = self::FindSyncInstance($event))
3564 {
3565 $arCalendarItems[$eventXmlId] = $instanceChangeKey;
3566 }
3567
3568 if (isset($arCalendarItems[$eventXmlId]))
3569 {
3570 if ($event[$xmlIdField] !== $arCalendarItems[$eventXmlId])
3571 {
3572 $arResult[] = [
3573 'XML_ID' => $eventXmlId,
3574 'ID' => $event['ID'],
3575 ];
3576 }
3577
3578 unset($arCalendarItems[$eventXmlId]);
3579 }
3580 elseif ($connectionType === Bitrix\Calendar\Sync\Caldav\Helper::EXCHANGE_TYPE)
3581 {
3582 if ((int)$event['ID'] === (int)$event['PARENT_ID'])
3583 {
3584 self::DeleteCalendarEvent($event["ID"], self::$userId);
3585 }
3586 }
3587 else
3588 {
3589 self::DeleteCalendarEvent($event["ID"], self::$userId);
3590 }
3591 }
3592
3593 foreach ($arCalendarItems as $key => $value)
3594 {
3595 $arResult[] = [
3596 'XML_ID' => $key,
3597 'ID' => 0,
3598 ];
3599 }
3600
3601 self::$silentErrorMode = false;
3602
3603 return $arResult;
3604 }
3605
3606 private static function FindSyncInstance($event)
3607 {
3608 $exchangeScheme = COption::GetOptionString('dav', 'exchange_scheme', 'http');
3609 $exchangeServer = COption::GetOptionString('dav', 'exchange_server', '');
3610 $exchangePort = COption::GetOptionString('dav', 'exchange_port', '80');
3611 $exchangeUsername = COption::GetOptionString('dav', 'exchange_username', '');
3612 $exchangePassword = COption::GetOptionString('dav', 'exchange_password', '');
3613
3614 if (empty($exchangeServer))
3615 {
3616 return '';
3617 }
3618
3619 $exchange = new CDavExchangeCalendar($exchangeScheme, $exchangeServer, $exchangePort, $exchangeUsername, $exchangePassword);
3620
3621 $params = [
3622 'dateTo' => $event['DATE_TO'],
3623 'parentDateTo' => $event['DATE_TO'],
3624 'dateFrom' => $event['DATE_FROM'],
3625 'parentDateFrom' => $event['DATE_FROM'],
3626 'parentTz' => $event['TZ_FROM'],
3627 'changekey' => $event['DAV_EXCH_LABEL'],
3628 ];
3629
3630 [ , $changeKey] = $exchange->FindInstance($params);
3631
3632 return $changeKey;
3633 }
3634
3635 public static function DeleteCalendarEvent($eventId, $userId, $oEvent = false)
3636 {
3637 return CCalendarEvent::Delete(array(
3638 'id' => $eventId,
3639 'userId' => $userId,
3640 'bMarkDeleted' => true,
3641 'Event' => $oEvent,
3642 ));
3643 }
3644
3645 public static function Color($color = '', $defaultColor = true)
3646 {
3647 if ((string)$color !== '')
3648 {
3649 $color = ltrim(trim(preg_replace('/\W/', '', $color)), "#");
3650 if (mb_strlen($color) > 6)
3651 {
3652 $color = mb_substr($color, 0, 6);
3653 }
3654 elseif(mb_strlen($color) < 6)
3655 {
3656 $color = '';
3657 }
3658 }
3659 $color = '#'.$color;
3660
3661 // Default color
3662 $DEFAULT_COLOR = '#9dcf00';
3663 if ($color === '#')
3664 {
3665 if ($defaultColor === true)
3666 {
3667 $color = $DEFAULT_COLOR;
3668 }
3669 elseif($defaultColor)
3670 {
3671 $color = $defaultColor;
3672 }
3673 else
3674 {
3675 $color = '';
3676 }
3677 }
3678
3679 return $color;
3680 }
3681
3682 // Called from CalDav, Exchange methods
3683
3684 public static function FormatTime($h = 0, $m = 0)
3685 {
3686 $m = (int)$m;
3687
3688 if ($m > 59)
3689 {
3690 $m = 59;
3691 }
3692 elseif ($m < 0)
3693 {
3694 $m = 0;
3695 }
3696
3697 if ($m < 10)
3698 {
3699 $m = '0' . $m;
3700 }
3701
3702 $h = (int)$h;
3703 if ($h > 24)
3704 {
3705 $h = 24;
3706 }
3707 if ($h < 0)
3708 {
3709 $h = 0;
3710 }
3711
3712 if (IsAmPmMode())
3713 {
3714 $ampm = 'am';
3715
3716 if ($h == 0)
3717 {
3718 $h = 12;
3719 }
3720 else if ($h == 12)
3721 {
3722 $ampm = 'pm';
3723 }
3724 else if ($h > 12)
3725 {
3726 $ampm = 'pm';
3727 $h -= 12;
3728 }
3729
3730 $res = $h.':'.$m.' '.$ampm;
3731 }
3732 else
3733 {
3734 $res = (($h < 10) ? '0' : '').$h.':'.$m;
3735 }
3736 return $res;
3737 }
3738
3739 // Called from SaveEvent: try to save event in Exchange or to Dav Server and if it's Ok, return true
3740 public static function GetUserId()
3741 {
3742 if (!self::$userId)
3743 {
3744 self::$userId = self::GetCurUserId();
3745 }
3746 return self::$userId;
3747 }
3748
3749 // Called from CalDav sync methods
3750
3751 public static function GetUserAvatarSrc($user = [], $params = [])
3752 {
3753 if (!is_array($user) && (int)$user > 0)
3754 {
3755 $user = self::GetUser($user);
3756 }
3757
3758 $avatar_src = self::GetUserAvatar($user, $params);
3759 if ($avatar_src === false)
3760 {
3761 $avatar_src = (isset($params['fillAvatar']) && $params['fillAvatar'] === false ? '' : '/bitrix/images/1.gif');
3762 }
3763
3764 return $avatar_src;
3765 }
3766
3767 public static function GetUserAvatar($user = [], $params = [])
3768 {
3769 if (!is_array($user) && (int)$user > 0)
3770 {
3771 $user = self::GetUser($user);
3772 }
3773
3774 if (!empty($user["PERSONAL_PHOTO"]))
3775 {
3776 if (empty($params['AVATAR_SIZE']))
3777 {
3778 $params['AVATAR_SIZE'] = 42;
3779 }
3780 $arFileTmp = CFile::ResizeImageGet(
3781 $user["PERSONAL_PHOTO"],
3782 array('width' => $params['AVATAR_SIZE'], 'height' => $params['AVATAR_SIZE']),
3784 false,
3785 false,
3786 true
3787 );
3788 $avatar_src = $arFileTmp['src'];
3789 }
3790 else
3791 {
3792 $avatar_src = false;
3793 }
3794 return $avatar_src;
3795 }
3796
3797 public static function GetUserUrl($userId = 0, $pathToUser = "")
3798 {
3799 if ($pathToUser == '')
3800 {
3801 if (self::$pathToUser == '')
3802 {
3803 if (empty(self::$pathesForSite))
3804 {
3805 self::$pathesForSite = self::GetPathes(SITE_ID);
3806 }
3807 self::$pathToUser = self::$pathesForSite['path_to_user'];
3808 }
3809 $pathToUser = self::$pathToUser;
3810 }
3811
3812 return CUtil::JSEscape(CComponentEngine::MakePathFromTemplate($pathToUser, array("user_id" => $userId, "USER_ID" => $userId)));
3813 }
3814
3815 public static function GetAccessTasksByName($binging = 'calendar_section', $name = 'calendar_denied')
3816 {
3817 $arTasks = self::GetAccessTasks($binging);
3818
3819 foreach($arTasks as $id => $task)
3820 {
3821 if ($task['name'] == $name)
3822 {
3823 return $id;
3824 }
3825 }
3826
3827 return false;
3828 }
3829
3830 public static function GetAccessTasks($binging = 'calendar_section', $type = '')
3831 {
3832 \Bitrix\Main\Localization\Loc::loadLanguageFile($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/calendar/admin/task_description.php");
3833
3834 if (isset(self::$arAccessTask[$binging]) && is_array(self::$arAccessTask[$binging]))
3835 {
3836 return self::$arAccessTask[$binging];
3837 }
3838
3839 $bIntranet = self::IsIntranetEnabled();
3840 $arTasks = [];
3841 $res = CTask::GetList(Array('ID' => 'asc'), Array('MODULE_ID' => 'calendar', 'BINDING' => $binging));
3842 while($arRes = $res->Fetch())
3843 {
3844 if($type === 'location')
3845 {
3846 if (
3847 mb_strtolower($arRes['NAME']) === 'calendar_view_time'
3848 || mb_strtolower($arRes['NAME']) === 'calendar_view_title'
3849 )
3850 {
3851 continue;
3852 }
3853 $name = '';
3854 if ($arRes['SYS'])
3855 {
3856 if (
3857 mb_strtolower($arRes['NAME']) === 'calendar_edit'
3858 || mb_strtolower($arRes['NAME']) === 'calendar_view'
3859 || mb_strtolower($arRes['NAME']) === 'calendar_type_edit'
3860 || mb_strtolower($arRes['NAME']) === 'calendar_type_view')
3861 {
3862 $name = Loc::getMessage('TASK_NAME_LOCATION_'.mb_strtoupper($arRes['NAME']));
3863 }
3864 else
3865 {
3866 $name = Loc::getMessage('TASK_NAME_'.mb_strtoupper($arRes['NAME']));
3867 }
3868 }
3869 }
3870 else
3871 {
3872 if (
3873 !$bIntranet
3874 && (
3875 mb_strtolower($arRes['NAME']) === 'calendar_view_time'
3876 || mb_strtolower($arRes['NAME']) === 'calendar_view_title'
3877 )
3878 )
3879 {
3880 continue;
3881 }
3882
3883 $name = '';
3884 if ($arRes['SYS'])
3885 {
3886 $name = Loc::getMessage('TASK_NAME_' . mb_strtoupper($arRes['NAME']));
3887 }
3888
3889 }
3890 if ($name == '')
3891 {
3892 $name = $arRes['NAME'];
3893 }
3894
3895 $arTasks[$arRes['ID']] = array(
3896 'name' => $arRes['NAME'],
3897 'title' => $name,
3898 );
3899 }
3900
3901
3902 return $arTasks;
3903 }
3904
3905 public static function PushAccessNames($arCodes = [])
3906 {
3907 foreach($arCodes as $code)
3908 {
3909 if (!array_key_exists($code, self::$accessNames))
3910 {
3911 self::$accessNames[$code] = null;
3912 }
3913 }
3914 }
3915
3916 public static function GetOuterUrl()
3917 {
3918 return self::$outerUrl;
3919 }
3920
3921 public static function AddConnection($connection, $type = 'caldav')
3922 {
3923 if((!self::CheckCalDavUrl($connection['link'], $connection['user_name'], $connection['pass'])))
3924 {
3925 return Loc::getMessage('EC_CAL_OPERATION_CANNOT_BE_PERFORMED');
3926 }
3927
3928 $arFields = [
3929 'ENTITY_TYPE' => 'user',
3930 'ENTITY_ID' => $connection['user_id'],
3932 'NAME' => $connection['name'],
3933 'SERVER' => $connection['link'],
3934 'SERVER_USERNAME' => $connection['user_name'],
3935 'SERVER_PASSWORD' => $connection['pass'],
3936 ];
3937
3938 \CDavConnection::ParseFields($arFields);
3939
3940 $davConnection = \CDavConnection::getList(
3941 ['ID' => 'ASC'],
3942 [
3943 'SERVER_HOST' => $arFields['SERVER_HOST'],
3944 'SERVER_PATH' => $arFields['SERVER_PATH'],
3945 'ENTITY_ID' => $arFields['ENTITY_ID'],
3946 ],
3947 false,
3948 ['nTopCount' => 1]
3949 );
3950
3951 if ($con = $davConnection->fetch())
3952 {
3953 \CDavConnection::Update($con['ID'], $arFields);
3954
3955 return true;
3956 }
3957
3958 \CDavConnection::Add($arFields);
3959
3960 return true;
3961 }
3962
3963 public static function CheckCalDavUrl($url, $username, $password)
3964 {
3965 $arServer = [
3966 'host' => null,
3967 'scheme' => null,
3968 'port' => null,
3969 'path' => null,
3970 ];
3971 $parsedUrl = parse_url($url);
3972 $arServer = array_merge($arServer, $parsedUrl);
3973
3974 // Mantis #71074
3975 if (
3976 mb_strpos(mb_strtolower($_SERVER['SERVER_NAME']), mb_strtolower($arServer['host'])) !== false
3977 || mb_strpos(mb_strtolower($_SERVER['HTTP_HOST']), mb_strtolower($arServer['host'])) !== false
3978 )
3979 {
3980 return false;
3981 }
3982
3983 if (Loader::includeModule("dav"))
3984 {
3985 return \CDavGroupdavClientCalendar::DoCheckCalDAVServer($arServer["scheme"], $arServer["host"], $arServer["port"], $username, $password, $arServer["path"]);
3986 }
3987
3988 return false;
3989 }
3990
3991 public static function RemoveConnection(array $params = [])
3992 {
3993 if (Loader::includeModule('dav'))
3994 {
3995 $sections = self::getSectionsByConnectionId($params['id']);
3996 $connection = CDavConnection::GetList(
3997 ["ID" => "ASC"],
3998 ["ID" => $params['id']]
3999 );
4000
4001 if (is_array($sections))
4002 {
4003 foreach ($sections as $section)
4004 {
4005 if ($params['del_calendars'] /*&& $section['IS_LOCAL'] !== 'Y'*/)
4006 {
4007 CCalendarSect::Delete($section['ID'], false);
4008 }
4009 else
4010 {
4011 self::markSectionLikeDelete($section['ID']);
4012 }
4013 }
4014 }
4015
4016 \CDavConnection::Delete($params['id']);
4017
4018 if (is_array($connection))
4019 {
4021 $googleHelper = ServiceLocator::getInstance()->get('calendar.service.google.helper');
4022 $caldavHelper = ServiceLocator::getInstance()->get('calendar.service.caldav.helper');
4023 $connectionType = $caldavHelper->isYandex($connection['SERVER_HOST'])
4024 ? Bitrix\Calendar\Sync\Caldav\Helper::YANDEX_TYPE
4025 : Bitrix\Calendar\Sync\Caldav\Helper::CALDAV_TYPE
4026 ;
4027 $connectionName = $googleHelper->isGoogleConnection($connection['ACCOUNT_TYPE'])
4028 ? 'google'
4029 : $connectionType . $connection['ID']
4030 ;
4031 Util::addPullEvent(
4032 PushCommand::DeleteSyncConnection,
4033 $connection['ENTITY_ID'],
4034 [
4035 'syncInfo' => [
4036 $connectionName => [
4037 'type' => $connectionType,
4038 ],
4039 ],
4040 'requestUid' => Util::getRequestUid(),
4041 ]
4042 );
4043 }
4044 }
4045 }
4046
4047 public static function GetTypeByExternalId($externalId = false)
4048 {
4049 if ($externalId)
4050 {
4051 $res = CCalendarType::GetList(array('arFilter' => array('EXTERNAL_ID' => $externalId)));
4052 if ($res && $res[0])
4053 {
4054 return $res[0]['XML_ID'];
4055 }
4056 }
4057 return false;
4058 }
4059
4060 public static function SetCurUserMeetingSection($userMeetingSection)
4061 {
4062 self::$userMeetingSection = $userMeetingSection;
4063 }
4064
4065 public static function CacheTime($time = false)
4066 {
4067 if ($time !== false)
4068 {
4069 self::$cacheTime = $time;
4070 }
4071 return self::$cacheTime;
4072 }
4073
4074 public static function _ParseHack(&$text, &$TextParser)
4075 {
4076 $text = preg_replace(array("/\&lt;/isu", "/\&gt;/isu"),array('<', '>'),$text);
4077
4078 $text = preg_replace("/<br\s*\/*>/isu","", $text);
4079 $text = preg_replace("/<(\w+)[^>]*>(.+?)<\/\\1[^>]*>/isu","\\2",$text);
4080 $text = preg_replace("/<*\/li>/isu","", $text);
4081
4082 $text = str_replace(array("<", ">"),array("&lt;", "&gt;"),$text);
4083
4084 $TextParser->allow = [];
4085 return true;
4086 }
4087
4088 public static function IsSocnetAdmin()
4089 {
4090 if (!isset(self::$bCurUserSocNetAdmin))
4091 {
4092 self::$bCurUserSocNetAdmin = Loader::includeModule('socialnetwork')
4093 && self::IsSocNet()
4094 && CSocNetUser::IsCurrentUserModuleAdmin()
4095 ;
4096 }
4097
4098 return self::$bCurUserSocNetAdmin;
4099 }
4100
4101 public static function GetMaxDate()
4102 {
4103 if (!self::$CALENDAR_MAX_DATE)
4104 {
4105 $date = new DateTime();
4106 $date->setDate(2038, 1, 1);
4107 self::$CALENDAR_MAX_DATE = self::Date($date->getTimestamp(), false);
4108 }
4109 return self::$CALENDAR_MAX_DATE;
4110 }
4111
4112 public static function GetMinDate()
4113 {
4114 if (!self::$CALENDAR_MIN_DATE)
4115 {
4116 $date = new DateTime();
4117 $date->setDate(1970, 1, 1);
4118 self::$CALENDAR_MIN_DATE = self::Date($date->getTimestamp(), false);
4119 }
4120 return self::$CALENDAR_MIN_DATE;
4121 }
4122
4123 public static function GetDestinationUsers($codes, $fetchUsers = false)
4124 {
4125 if (!Main\Loader::includeModule('socialnetwork'))
4126 {
4127 return [];
4128 }
4129 $users = \CSocNetLogDestination::getDestinationUsers($codes, $fetchUsers);
4130
4131 if ($fetchUsers)
4132 {
4133 foreach ($users as $i => $user)
4134 {
4135 $users[$i]['FORMATTED_NAME'] = self::GetUserName($user);
4136 $users[$i]['ID'] = (int)$user['ID'];
4137 }
4138 }
4139 else
4140 {
4141 foreach ($users as &$user)
4142 {
4143 if(is_numeric($user))
4144 {
4145 $user = (int)$user;
4146 }
4147 }
4148 }
4149
4150 return $users;
4151 }
4152
4153 public static function GetAttendeesMessage($cnt = 0)
4154 {
4155 if (
4156 ($cnt % 100) > 10
4157 && ($cnt % 100) < 20
4158 )
4159 {
4160 $suffix = 5;
4161 }
4162 else
4163 {
4164 $suffix = $cnt % 10;
4165 }
4166
4167 return Loc::getMessage("EC_ATTENDEE_".$suffix, Array("#NUM#" => $cnt));
4168 }
4169
4170 public static function GetMoreAttendeesMessage($cnt = 0)
4171 {
4172 if (
4173 ($cnt % 100) > 10
4174 && ($cnt % 100) < 20
4175 )
4176 {
4177 $suffix = 5;
4178 }
4179 else
4180 {
4181 $suffix = $cnt % 10;
4182 }
4183
4184 return Loc::getMessage("EC_ATTENDEE_MORE_".$suffix, Array("#NUM#" => $cnt));
4185 }
4186
4187 public static function GetFromToHtml(
4188 $fromTs = false,
4189 $toTs = false,
4190 $skipTime = false,
4191 $dtLength = 0,
4192 $forRrule = false,
4193 $languageId = null
4194 )
4195 {
4196 if ((int)$fromTs != $fromTs)
4197 {
4198 $fromTs = self::Timestamp($fromTs);
4199 }
4200 if ((int)$toTs != $toTs)
4201 {
4202 $toTs = self::Timestamp($toTs);
4203 }
4204 if ($toTs < $fromTs)
4205 {
4206 $toTs = $fromTs;
4207 }
4208
4209 // Formats
4210 $formatShort = self::DFormat(false);
4211 $formatFull = self::DFormat(true);
4212 $formatTime = str_replace($formatShort, '', $formatFull);
4213 $formatTime = $formatTime == $formatFull ? "H:i" : str_replace(':s', '', $formatTime);
4214 $html = '';
4215
4216 $formatFull = str_replace(':s', '', $formatFull);
4217
4218 if ($skipTime)
4219 {
4220 if ((int)$dtLength === self::DAY_LENGTH || !$dtLength) // One full day event
4221 {
4222 if (!$forRrule)
4223 {
4224 $html = FormatDate([
4225 "tomorrow" => "tomorrow",
4226 "today" => "today",
4227 "yesterday" => "yesterday",
4228 "-" => $formatShort,
4229 "" => $formatShort,
4230 ], $fromTs, time() + CTimeZone::GetOffset(), $languageId);
4231 $html .= ', ';
4232 }
4233
4234 $html .= Loc::getMessage('EC_VIEW_FULL_DAY', null, $languageId);
4235 }
4236 else // Event for several days
4237 {
4238 $from = FormatDate([
4239 "tomorrow" => "tomorrow",
4240 "today" => "today",
4241 "yesterday" => "yesterday",
4242 "-" => $formatShort,
4243 "" => $formatShort,
4244 ], $fromTs, time() + CTimeZone::GetOffset(), $languageId);
4245
4246 $to = FormatDate([
4247 "tomorrow" => "tomorrow",
4248 "today" => "today",
4249 "yesterday" => "yesterday",
4250 "-" => $formatShort,
4251 "" => $formatShort,
4252 ], $toTs - self::DAY_LENGTH, time() + CTimeZone::GetOffset(), $languageId);
4253
4254 $html = Loc::getMessage(
4255 'EC_VIEW_DATE_FROM_TO',
4256 ['#DATE_FROM#' => $from, '#DATE_TO#' => $to],
4257 $languageId
4258 );
4259 }
4260 }
4261 else
4262 {
4263 // Event during one day
4264 if(date('dmY', $fromTs) == date('dmY', $toTs))
4265 {
4266 if (!$forRrule)
4267 {
4268 $html = FormatDate([
4269 "tomorrow" => "tomorrow",
4270 "today" => "today",
4271 "yesterday" => "yesterday",
4272 "-" => $formatShort,
4273 "" => $formatShort,
4274 ], $fromTs, time() + CTimeZone::GetOffset(), $languageId);
4275 $html .= ', ';
4276 }
4277
4278 $html .= Loc::getMessage(
4279 'EC_VIEW_TIME_FROM_TO_TIME',
4280 [
4281 '#TIME_FROM#' => FormatDate($formatTime, $fromTs, false, $languageId),
4282 '#TIME_TO#' => FormatDate($formatTime, $toTs, false, $languageId),
4283 ],
4284 $languageId
4285 );
4286 }
4287 else
4288 {
4289 $html = Loc::getMessage(
4290 'EC_VIEW_DATE_FROM_TO',
4291 [
4292 '#DATE_FROM#' => FormatDate($formatFull, $fromTs, time() + CTimeZone::GetOffset(), $languageId),
4293 '#DATE_TO#' => FormatDate($formatFull, $toTs, time() + CTimeZone::GetOffset(), $languageId),
4294 ],
4295 $languageId
4296 );
4297 }
4298 }
4299
4300 return $html;
4301 }
4302
4303 public static function GetSocNetDestination($user_id = false, $selected = [], $userList = [])
4304 {
4305 if (!Loader::includeModule("socialnetwork"))
4306 {
4307 return false;
4308 }
4309
4310 global $CACHE_MANAGER;
4311
4312 if (!is_array($selected))
4313 {
4314 $selected = [];
4315 }
4316
4317 if (method_exists('CSocNetLogDestination','GetDestinationSort'))
4318 {
4319 $DESTINATION = array(
4320 'LAST' => [],
4321 'DEST_SORT' => CSocNetLogDestination::GetDestinationSort(array("DEST_CONTEXT" => \Bitrix\Calendar\Util::getUserSelectorContext())),
4322 );
4323
4324 CSocNetLogDestination::fillLastDestination($DESTINATION['DEST_SORT'], $DESTINATION['LAST']);
4325 }
4326 else
4327 {
4328 $DESTINATION = array(
4329 'LAST' => array(
4330 'SONETGROUPS' => CSocNetLogDestination::GetLastSocnetGroup(),
4331 'DEPARTMENT' => CSocNetLogDestination::GetLastDepartment(),
4332 'USERS' => CSocNetLogDestination::GetLastUser(),
4333 ),
4334 );
4335 }
4336
4337 if (!$user_id)
4338 {
4339 $user_id = self::GetCurUserId();
4340 }
4341
4342 $cacheTtl = defined("BX_COMP_MANAGED_CACHE") ? 3153600 : 3600*4;
4343 $cacheId = 'calendar_dest_'.$user_id;
4344 $cacheDir = '/calendar/socnet_destination/'.SITE_ID.'/'.$user_id;
4345
4346 $obCache = new CPHPCache;
4347 if($obCache->InitCache($cacheTtl, $cacheId, $cacheDir))
4348 {
4349 $DESTINATION['SONETGROUPS'] = $obCache->GetVars();
4350 }
4351 else
4352 {
4353 $obCache->StartDataCache();
4354 $DESTINATION['SONETGROUPS'] = CSocNetLogDestination::GetSocnetGroup(Array('features' => array("calendar", array("view"))));
4355
4356 if(defined("BX_COMP_MANAGED_CACHE"))
4357 {
4358 $CACHE_MANAGER->StartTagCache($cacheDir);
4359 foreach($DESTINATION['SONETGROUPS'] as $val)
4360 {
4361 $CACHE_MANAGER->RegisterTag("sonet_features_G_".$val["entityId"]);
4362 $CACHE_MANAGER->RegisterTag("sonet_group_".$val["entityId"]);
4363 }
4364 $CACHE_MANAGER->RegisterTag("sonet_user2group_U".$user_id);
4365 $CACHE_MANAGER->EndTagCache();
4366 }
4367 $obCache->EndDataCache($DESTINATION['SONETGROUPS']);
4368 }
4369
4370 $destinationUserList = [];
4371 $DESTINATION['SELECTED'] = [];
4372
4373 if (!empty($userList))
4374 {
4375 foreach ($userList as $userId)
4376 {
4377 $DESTINATION['SELECTED']['U'.$userId] = "users";
4378 $DESTINATION['LAST']['USERS']['U'.$userId] = 'U'.$userId;
4379 }
4380 }
4381
4382 foreach ($selected as $ind => $code)
4383 {
4384 if (str_starts_with($code, 'DR'))
4385 {
4386 $DESTINATION['SELECTED'][$code] = "department";
4387 }
4388 elseif (str_starts_with($code, 'UA'))
4389 {
4390 $DESTINATION['SELECTED'][$code] = "groups";
4391 }
4392 elseif (str_starts_with($code, 'SG'))
4393 {
4394 $DESTINATION['SELECTED'][$code] = "sonetgroups";
4395 }
4396 elseif (str_starts_with($code, 'U'))
4397 {
4398 $DESTINATION['SELECTED'][$code] = "users";
4399 $destinationUserList[] = (int)str_replace('U', '', $code);
4400 }
4401 }
4402
4403 // intranet structure
4404 $arStructure = CSocNetLogDestination::GetStucture();
4405 $DESTINATION['DEPARTMENT'] = $arStructure['department'];
4406 $DESTINATION['DEPARTMENT_RELATION'] = $arStructure['department_relation'];
4407 $DESTINATION['DEPARTMENT_RELATION_HEAD'] = $arStructure['department_relation_head'];
4408
4409 if (Loader::includeModule('extranet') && !CExtranet::IsIntranetUser(SITE_ID, $user_id))
4410 {
4411 $DESTINATION['EXTRANET_USER'] = 'Y';
4412 $DESTINATION['USERS'] = CSocNetLogDestination::GetExtranetUser();
4413 $DESTINATION['USERS'] = array_merge($DESTINATION['USERS'], CSocNetLogDestination::GetUsers(['id' => [$user_id]]));
4414 }
4415 else
4416 {
4417 if (is_array($DESTINATION['LAST']['USERS']))
4418 {
4419 foreach ($DESTINATION['LAST']['USERS'] as $value)
4420 {
4421 $destinationUserList[] = (int)str_replace('U', '', $value);
4422 }
4423 }
4424
4425 if (!empty($userList))
4426 {
4427 $destinationUserList = array_unique(array_merge($destinationUserList, $userList));
4428 }
4429
4430 $DESTINATION['EXTRANET_USER'] = 'N';
4431 $DESTINATION['USERS'] = CSocNetLogDestination::GetUsers(Array('id' => $destinationUserList));
4432 }
4433
4434 $users = [];
4435 foreach ($DESTINATION['USERS'] as $key => $entry)
4436 {
4437 if ($entry['isExtranet'] === 'N')
4438 {
4439 $users[$key] = $entry;
4440 }
4441 }
4442 $DESTINATION['USERS'] = $users;
4443
4444 return $DESTINATION;
4445 }
4446
4447 public static function SaveUserTimezoneName($user, $tzName = '')
4448 {
4449 if (!is_array($user) && (int)$user > 0)
4450 {
4451 $user = self::GetUser($user, true);
4452 }
4453
4454 CUserOptions::SetOption("calendar", "timezone".self::GetCurrentOffsetUTC($user['ID']), $tzName, false, $user['ID']);
4455 }
4456
4457 public static function OnSocNetGroupDelete($groupId)
4458 {
4459 $groupId = (int)$groupId;
4460 if ($groupId > 0)
4461 {
4462 $event = EventTable::query()
4463 ->setSelect(['ID', 'OWNER_ID', 'CAL_TYPE', 'DELETED'])
4464 ->where('OWNER_ID', $groupId)
4465 ->where('CAL_TYPE', Dictionary::CALENDAR_TYPE['group'])
4466 ->where('DELETED', 'N')
4467 ->setLimit(1)
4468 ->exec()->fetch()
4469 ;
4470
4471 if (!empty($event))
4472 {
4473 global $APPLICATION;
4474 $APPLICATION->ThrowException(
4475 Loc::getMessage('EC_DECLINE_GROUP_DELETING_WITH_EVENTS'),
4476 'CALENDAR_NOT_EMPTY'
4477 );
4478
4479 return false;
4480 }
4481
4482 $res = CCalendarSect::GetList(
4483 array(
4484 'arFilter' => array(
4485 'CAL_TYPE' => 'group',
4486 'OWNER_ID' => $groupId,
4487 ),
4488 'checkPermissions' => false,
4489 )
4490 );
4491
4492 foreach($res as $sect)
4493 {
4494 CCalendarSect::Delete($sect['ID'], false);
4495 }
4496 }
4497
4498 return true;
4499 }
4500
4507 public static function OnDavCalendarSync(\Bitrix\Main\Event $event)
4508 {
4509 $calendarId = $event->getParameter('id');
4510 $userAgent = mb_strtolower($event->getParameter('agent'));
4511 $agent = false;
4512 [$sectionId, $entityType, $entityId] = $calendarId;
4513
4514 $arAgentsMap = [
4515 'android' => 'android', // Android/iOS CardDavBitrix24
4516 'iphone' => 'iphone', // Apple iPhone iCal
4517 'davkit' => 'mac', // Apple iCal
4518 'mac os' => 'mac', // Apple iCal (Mac Os X > 10.8)
4519 'mac_os_x' => 'mac', // Apple iCal (Mac Os X > 10.8)
4520 'mac+os+x' => 'mac', // Apple iCal (Mac Os X > 10.10)
4521 'macos' => 'mac', // Apple iCal (Mac Os X > 11)
4522 'dataaccess' => 'iphone', // Apple addressbook iPhone
4523 //'sunbird' => 'sunbird', // Mozilla Sunbird
4524 'ios' => 'iphone',
4525 ];
4526
4527 foreach ($arAgentsMap as $pattern => $name)
4528 {
4529 if (mb_strpos($userAgent, $pattern) !== false)
4530 {
4531 $agent = $name;
4532 break;
4533 }
4534 }
4535
4536 if ($entityType === 'user' && $agent)
4537 {
4538 self::SaveSyncDate($entityId, $agent);
4539 }
4540 }
4541
4549 public static function SaveSyncDate($userId, $syncType)
4550 {
4551 $syncTypes = array('iphone', 'android', 'mac', 'exchange', 'outlook');
4552 if (in_array($syncType, $syncTypes))
4553 {
4554 if (!CUserOptions::GetOption('calendar', 'last_sync_'.$syncType, false, $userId))
4555 {
4556 AddEventToStatFile('calendar', 'sync_connection_connected', $syncType, '', 'client_connection');
4557 }
4558
4559 CUserOptions::SetOption("calendar", "last_sync_".$syncType, self::Date(time()), false, $userId);
4560
4562 PushCommand::RefreshSyncStatus,
4563 $userId, [
4564 'syncInfo' => [
4565 $syncType => [
4566 'status' => true,
4567 'type' => $syncType,
4568 'connected' => true,
4569 'syncOffset' => 0,
4570 ],
4571 ],
4572 'requestUid' => Util::getRequestUid(),
4573 ]);
4574 }
4575 }
4576
4582 public static function SaveMultipleSyncDate($userId, $syncType, $sectionId): void
4583 {
4584 $syncTypes = ['outlook'];
4585 if (in_array($syncType, $syncTypes, true))
4586 {
4587 if (!CUserOptions::GetOption('calendar', 'last_sync_'.$syncType, false, $userId))
4588 {
4589 AddEventToStatFile('calendar', 'sync_connection_connected', $syncType, '', 'client_connection');
4590 }
4591
4592 $options = CUserOptions::GetOption("calendar", "last_sync_".$syncType, false, $userId);
4593
4594 if (!is_array($options))
4595 {
4596 unset($options);
4597 }
4598
4599 $options[$sectionId] = self::Date(time());
4600 CUserOptions::SetOption("calendar", "last_sync_".$syncType, $options, false, $userId);
4601
4603 PushCommand::RefreshSyncStatus,
4604 $userId, [
4605 'syncInfo' => [
4606 $syncType => [
4607 'status' => true,
4608 'type' => $syncType,
4609 'connected' => true,
4610 'syncOffset' => 0,
4611 ],
4612 ],
4613 'requestUid' => Util::getRequestUid(),
4614 ]);
4615 }
4616 }
4617
4619 {
4620 self::SaveSyncDate($event->getParameter('userId'), 'exchange');
4621 }
4622
4631 public static function UpdateCounter($users = false, array $eventIds = [], array $groupIds = []): void
4632 {
4633 if (!$users)
4634 {
4635 $users = array(self::GetCurUserId());
4636 }
4637 elseif(!is_array($users))
4638 {
4639 $users = array($users);
4640 }
4641
4642 $ids = [];
4643 foreach($users as $user)
4644 {
4645 if ((int)$user)
4646 {
4647 $ids[] = (int)$user;
4648 }
4649 }
4650
4652 \Bitrix\Calendar\Internals\Counter\Event\EventDictionary::EVENT_ATTENDEES_UPDATED,
4653 [
4654 'user_ids' => $ids,
4655 'event_ids' => $eventIds,
4656 'group_ids' => $groupIds,
4657 ],
4658 );
4659 }
4660
4661 private static function GetInstance()
4662 {
4663 if (!isset(self::$instance))
4664 {
4665 $c = __CLASS__;
4666 self::$instance = new $c;
4667 }
4668 return self::$instance;
4669 }
4670
4671
4672 public static function IsIntranetEnabled()
4673 {
4674 if (!isset(self::$bIntranet))
4675 {
4676 self::$bIntranet = IsModuleInstalled('intranet');
4677 }
4678 return self::$bIntranet;
4679 }
4680
4681 public static function IsSocNet()
4682 {
4683 if (!isset(self::$bSocNet))
4684 {
4685 if (!Loader::includeModule('socialnetwork'))
4686 {
4687 self::$bSocNet = false;
4688
4689 return false;
4690 }
4691
4692 self::$bSocNet = class_exists('CSocNetUserToGroup') && CBXFeatures::IsFeatureEnabled("Calendar") && self::IsIntranetEnabled();
4693 }
4694
4695 return self::$bSocNet;
4696 }
4697
4698 public static function GetCurUserId($refresh = false): int
4699 {
4700 global $USER;
4701
4702 if (!isset(self::$curUserId)
4703 || !is_numeric(self::$curUserId)
4704 || $refresh
4705 )
4706 {
4707 self::$curUserId =
4708 (is_object($USER) && $USER->IsAuthorized())
4709 ? (int)$USER->GetId()
4710 : 0
4711 ;
4712 }
4713
4714 return self::$curUserId;
4715 }
4716
4717 public static function GetSettings($params = [])
4718 {
4719 if (!is_array($params))
4720 {
4721 $params = [];
4722 }
4723 if (
4724 isset(self::$settings)
4725 && !empty(self::$settings)
4726 && ($params['request'] ?? '') === false
4727 )
4728 {
4729 return self::$settings;
4730 }
4731
4732 $pathes_for_sites = COption::GetOptionString('calendar', 'pathes_for_sites', true);
4733 if (($params['forseGetSitePathes'] ?? false) || !$pathes_for_sites)
4734 {
4735 $pathes = self::GetPathes($params['site'] ?? false);
4736 }
4737 else
4738 {
4739 $pathes = [];
4740 }
4741
4742 if (!isset($params['getDefaultForEmpty']) || $params['getDefaultForEmpty'] !== false)
4743 {
4744 $params['getDefaultForEmpty'] = true;
4745 }
4746
4747 $siteId = $params['site'] ?? SITE_ID;
4748 $resMeetingCommonForSites = COption::GetOptionString('calendar', 'rm_for_sites', true);
4749 $siteIdForResMeet = !$resMeetingCommonForSites && $siteId ? $siteId : false;
4750
4751 self::$settings = [
4752 'work_time_start' => COption::GetOptionString('calendar', 'work_time_start', 9),
4753 'work_time_end' => COption::GetOptionString('calendar', 'work_time_end', 19),
4754 'year_holidays' => COption::GetOptionString('calendar', 'year_holidays', Loc::getMessage('EC_YEAR_HOLIDAYS_DEFAULT')),
4755 'year_workdays' => COption::GetOptionString('calendar', 'year_workdays', Loc::getMessage('EC_YEAR_WORKDAYS_DEFAULT')),
4756 'week_holidays' => array_filter(
4757 explode('|', COption::GetOptionString('calendar', 'week_holidays', 'SA|SU'))
4758 ),
4759 'week_start' => COption::GetOptionString('calendar', 'week_start', 'MO'),
4760 'user_name_template' => self::GetUserNameTemplate($params['getDefaultForEmpty']),
4761 'sync_by_push' => COption::GetOptionString('calendar', 'sync_by_push', false),
4762 'user_show_login' => COption::GetOptionString('calendar', 'user_show_login', true),
4763 'path_to_user' => COption::GetOptionString('calendar', 'path_to_user', "/company/personal/user/#user_id#/"),
4764 'path_to_user_calendar' => COption::GetOptionString('calendar', 'path_to_user_calendar', "/company/personal/user/#user_id#/calendar/"),
4765 'path_to_group' => COption::GetOptionString('calendar', 'path_to_group', "/workgroups/group/#group_id#/"),
4766 'path_to_group_calendar' => COption::GetOptionString('calendar', 'path_to_group_calendar', "/workgroups/group/#group_id#/calendar/"),
4767 'path_to_vr' => COption::GetOptionString('calendar', 'path_to_vr', ""),
4768 'path_to_rm' => COption::GetOptionString('calendar', 'path_to_rm', ""),
4769 'rm_iblock_type' => COption::GetOptionString('calendar', 'rm_iblock_type', ""),
4770 'rm_iblock_id' => COption::GetOptionString('calendar', 'rm_iblock_id', "", $siteIdForResMeet, !!$siteIdForResMeet),
4771 'dep_manager_sub' => COption::GetOptionString('calendar', 'dep_manager_sub', true),
4772 'denied_superpose_types' => unserialize(COption::GetOptionString('calendar', 'denied_superpose_types', serialize([])), ['allowed_classes' => false]),
4773 'pathes_for_sites' => $pathes_for_sites,
4774 'pathes' => $pathes,
4775 'forum_id' => COption::GetOptionString('calendar', 'forum_id', ""),
4776 'rm_for_sites' => COption::GetOptionString('calendar', 'rm_for_sites', true),
4777 ];
4778
4779 $arPathes = self::GetPathesList();
4780 foreach($arPathes as $pathName)
4781 {
4782 if (!isset(self::$settings[$pathName]))
4783 {
4784 self::$settings[$pathName] = COption::GetOptionString('calendar', $pathName, "");
4785 }
4786 }
4787
4788 if(self::$settings['work_time_start'] > 23)
4789 {
4790 self::$settings['work_time_start'] = 23;
4791 }
4792 if (self::$settings['work_time_end'] <= self::$settings['work_time_start'])
4793 {
4794 self::$settings['work_time_end'] = self::$settings['work_time_start'] + 1;
4795 }
4796 if (self::$settings['work_time_end'] > 23.30)
4797 {
4798 self::$settings['work_time_end'] = 23.30;
4799 }
4800
4801 if (empty(self::$settings['forum_id']))
4802 {
4803 self::$settings['forum_id'] = COption::GetOptionString("tasks", "task_forum_id", "");
4804 if (empty(self::$settings['forum_id']) && Loader::includeModule("forum"))
4805 {
4806 $db = CForumNew::GetListEx();
4807 if ($ar = $db->GetNext())
4808 {
4809 self::$settings['forum_id'] = $ar["ID"];
4810 }
4811 }
4812 COption::SetOptionString("calendar", "forum_id", self::$settings['forum_id']);
4813 }
4814
4815 return self::$settings;
4816 }
4817
4818 public static function GetPathes($forSite = null)
4819 {
4820 $pathes = [];
4821 $pathes_for_sites = COption::GetOptionString('calendar', 'pathes_for_sites', true);
4822 if ($forSite === null)
4823 {
4824 $arAffectedSites = COption::GetOptionString('calendar', 'pathes_sites', false);
4825
4826 if ($arAffectedSites && CheckSerializedData($arAffectedSites))
4827 {
4828 $arAffectedSites = unserialize($arAffectedSites, ['allowed_classes' => false]);
4829 }
4830 }
4831 elseif (is_array($forSite))
4832 {
4833 $arAffectedSites = $forSite;
4834 }
4835 else
4836 {
4837 $arAffectedSites = [$forSite];
4838 }
4839
4840 if(is_array($arAffectedSites) && !empty($arAffectedSites))
4841 {
4842 foreach($arAffectedSites as $s)
4843 {
4844 $ar = COption::GetOptionString("calendar", 'pathes_'.$s, false);
4845 if ($ar && CheckSerializedData($ar))
4846 {
4847 $ar = unserialize($ar, ['allowed_classes' => false]);
4848 if(is_array($ar))
4849 {
4850 $pathes[$s] = $ar;
4851 }
4852 }
4853 }
4854 }
4855
4856 if ($forSite !== false)
4857 {
4858 $result = [];
4859 if (isset($pathes[$forSite]) && is_array($pathes[$forSite]))
4860 {
4861 $result = $pathes[$forSite];
4862 }
4863
4864 $arPathes = self::GetPathesList();
4865 foreach($arPathes as $pathName)
4866 {
4867 $val = $result[$pathName] ?? '';
4868 if (empty($val) || $pathes_for_sites)
4869 {
4870 if (!isset($SET))
4871 {
4872 $SET = self::GetSettings();
4873 }
4874 $val = $SET[$pathName] ?? null;
4875 $result[$pathName] = $val;
4876 }
4877 }
4878 return $result;
4879 }
4880 return $pathes;
4881 }
4882
4883 public static function GetPathesList()
4884 {
4885 if (!self::$pathesListEx)
4886 {
4887 self::$pathesListEx = self::$pathesList;
4888 $arTypes = CCalendarType::GetList(array('checkPermissions' => false));
4889 foreach ($arTypes as $type)
4890 {
4891 if ($type['XML_ID'] !== 'user' && $type['XML_ID'] !== 'group')
4892 {
4893 self::$pathesList[] = 'path_to_type_'. $type['XML_ID'];
4894 }
4895 }
4896 }
4897 return self::$pathesList;
4898 }
4899
4900 public static function GetUserNameTemplate($fromSite = true)
4901 {
4902 $user_name_template = COption::GetOptionString('calendar', 'user_name_template', '');
4903 if ($fromSite && empty($user_name_template))
4904 {
4905 $user_name_template = CSite::GetNameFormat(false);
4906 }
4907 return $user_name_template;
4908 }
4909
4910 public static function SetUserSettings($settings = [], $userId = false)
4911 {
4912 UserSettings::set($settings, $userId);
4913 }
4914
4915 public static function GetUserSettings($userId = false)
4916 {
4917 return UserSettings::get($userId);
4918 }
4919
4920 public static function GetPermissions($Params = [])
4921 {
4922 global $USER;
4923 $type = $Params['type'] ?? self::$type;
4924 $ownerId = (int)($Params['ownerId'] ?? self::$ownerId);
4925 $userId = (int)($Params['userId'] ?? self::$userId);
4926
4927 $bView = true;
4928
4930 $typeModel = TypeModel::createFromXmlId($type);
4931 $request = [
4932 ActionDictionary::ACTION_TYPE_VIEW => [],
4933 ActionDictionary::ACTION_TYPE_EDIT => [],
4934 ];
4935
4936 $result = $accessController->batchCheck($request, $typeModel);
4937
4938 if ($type === 'user' && $ownerId !== $userId)
4939 {
4940 $bEdit = false;
4941 $bEditSection = false;
4942 }
4943 else
4944 {
4945 $bView = $result[ActionDictionary::ACTION_TYPE_VIEW];
4946 $bEdit = $result[ActionDictionary::ACTION_TYPE_EDIT];
4947 $bEditSection = $result[ActionDictionary::ACTION_TYPE_EDIT];
4948 }
4949
4950 if (($type === 'group') && !$USER->CanDoOperation('edit_php'))
4951 {
4952 $keyOwner = 'SG' . $ownerId.'_A';
4953 $keyMod = 'SG' . $ownerId.'_E';
4954 $keyMember = 'SG' . $ownerId.'_K';
4955
4956 $codes = Util::getUserAccessCodes($userId);
4957
4958 if (Loader::includeModule("socialnetwork"))
4959 {
4960 $group = CSocNetGroup::getByID($ownerId);
4961 if(
4962 !empty($group['CLOSED']) && $group['CLOSED'] === 'Y' &&
4963 \Bitrix\Main\Config\Option::get('socialnetwork', 'work_with_closed_groups', 'N') === 'N'
4964 )
4965 {
4966 self::$isArchivedGroup = true;
4967 }
4968 }
4969
4970 if (in_array($keyOwner, $codes, true))// Is owner
4971 {
4972 $bEdit = true;
4973 $bEditSection = true;
4974 }
4975 elseif(!self::$isArchivedGroup && in_array($keyMod, $codes, true))// Is moderator
4976 {
4977 $bEdit = true;
4978 $bEditSection = true;
4979 }
4980 elseif(!self::$isArchivedGroup && in_array($keyMember, $codes, true))// Is member
4981 {
4982 $bEdit = true;
4983 $bEditSection = false;
4984 }
4985 else
4986 {
4987 $bEdit = false;
4988 $bEditSection = false;
4989 }
4990 }
4991
4992 if (($Params['setProperties'] ?? '') !== false)
4993 {
4994 self::$perm['view'] = $bView;
4995 self::$perm['edit'] = $bEdit;
4996 self::$perm['section_edit'] = $bEditSection;
4997 }
4998
4999 return [
5000 'view' => $bView,
5001 'edit' => $bEdit,
5002 'section_edit' => $bEditSection,
5003 ];
5004 }
5005
5014 public static function getEntryUrl(string $calType, int $ownerId, int $entryId, string $dateFrom): string
5015 {
5016 $uri = new Main\Web\Uri(Util::getPathToCalendar($ownerId, $calType));
5017
5018 try
5019 {
5020 $dateFormatted = Util::getDateObject($dateFrom)->format('d.m.Y');
5021 }
5022 catch (Main\ObjectException)
5023 {
5024 $dateFormatted = $dateFrom;
5025 }
5026
5027 $uri->addParams([
5028 'EVENT_ID' => $entryId,
5029 'EVENT_DATE' => urlencode($dateFormatted),
5030 ]);
5031
5032 return $uri->getUri();
5033 }
5034
5035 public static function GetPath($type = '', $ownerId = '', $hard = false)
5036 {
5037 return self::GetServerPath() . Util::getPathToCalendar((int)$ownerId, $type);
5038 }
5039
5040 public static function GetSiteId()
5041 {
5042 if (!self::$siteId)
5043 {
5044 self::$siteId = SITE_ID;
5045 }
5046 return self::$siteId;
5047 }
5048
5049 public static function GetServerPath()
5050 {
5051 if (!isset(self::$serverPath))
5052 {
5053 self::$serverPath = (CMain::IsHTTPS() ? "https://" : "http://").self::GetServerName();
5054 }
5055
5056 return self::$serverPath;
5057 }
5058
5059 public static function GetServerName()
5060 {
5061 $server_name = '';
5062 if (defined("SITE_SERVER_NAME") && SITE_SERVER_NAME <> '')
5063 {
5064 $server_name = SITE_SERVER_NAME;
5065 }
5066 if (!$server_name)
5067 {
5068 $server_name = COption::GetOptionString("main", "server_name", "");
5069 }
5070 if (!$server_name)
5071 {
5072 $server_name = $_SERVER['HTTP_HOST'];
5073 }
5074 $server_name = rtrim($server_name, '/');
5075 if (!preg_match('/^[a-z0-9\.\-]+$/i', $server_name)) // cyrillic domain hack
5076 {
5077 $converter = new CBXPunycode('UTF-8');
5078 $host = $converter->Encode($server_name);
5079 $server_name = $host;
5080 }
5081
5082 return $server_name;
5083 }
5084
5085 public static function GetStartUpEvent($eventId = false, $isSharing = false)
5086 {
5087 if ($eventId)
5088 {
5089 if ($isSharing)
5090 {
5091 $res = [self::getDeletedSharedEvent($eventId)];
5092 }
5093 else
5094 {
5095 $res = CCalendarEvent::GetList(
5096 array(
5097 'arFilter' => array(
5098 "PARENT_ID" => $eventId,
5099 "OWNER_ID" => self::$userId,
5100 "IS_MEETING" => 1,
5101 "DELETED" => "N",
5102 ),
5103 'parseRecursion' => false,
5104 'fetchAttendees' => true,
5105 'fetchMeetings' => true,
5106 'checkPermissions' => true,
5107 'setDefaultLimit' => false,
5108 )
5109 );
5110 }
5111
5112 if (!$res || !is_array($res[0]))
5113 {
5114 $res = CCalendarEvent::GetList(
5115 array(
5116 'arFilter' => array(
5117 "ID" => $eventId,
5118 "DELETED" => "N",
5119 ),
5120 'parseRecursion' => false,
5121 'userId' => self::$userId,
5122 'fetchAttendees' => false,
5123 'fetchMeetings' => true,
5124 )
5125 );
5126 }
5127
5128 if ($res && isset($res[0]) && ($event = $res[0]))
5129 {
5130 if (
5131 $event['MEETING_STATUS'] === 'Y'
5132 || $event['MEETING_STATUS'] === 'N'
5133 || $event['MEETING_STATUS'] === 'Q'
5134 )
5135 {
5136 $_GET['CONFIRM'] ??= null;
5137 if (
5138 $event['IS_MEETING']
5139 && (int)self::$userId === (int)self::$ownerId
5140 && self::$type === 'user'
5141 && ($_GET['CONFIRM'] === 'Y' || $_GET['CONFIRM'] === 'N')
5142 )
5143 {
5144 CCalendarEvent::SetMeetingStatus(array(
5145 'userId' => self::$userId,
5146 'eventId' => $event['ID'],
5147 'status' => $_GET['CONFIRM'] === 'Y' ? 'Y' : 'N',
5148 'personalNotification' => true,
5149 ));
5150 }
5151 }
5152
5153 if ($event['RRULE'])
5154 {
5155 $event['RRULE'] = CCalendarEvent::ParseRRULE($event['RRULE']);
5156 }
5157
5158 $event['~userIndex'] = CCalendarEvent::getUserIndex();
5159
5160 return $event;
5161 }
5162
5163 CCalendarNotify::ClearNotifications($eventId);
5164 }
5165
5166 return false;
5167 }
5168
5169 public static function getDeletedSharedEvent(int $entryId): ?array
5170 {
5172 $eventLink = (new Sharing\Link\Factory)->getDeletedEventLinkByEventId($entryId);
5173 if (!$eventLink)
5174 {
5175 return null;
5176 }
5177
5178 $result = EventTable::query()
5179 ->setSelect(['*'])
5180 ->where('OWNER_ID', $eventLink->getOwnerId())
5181 ->where(Query::filter()
5182 ->logic('or')
5183 ->where([
5184 ['DELETED', 'Y'],
5185 ['MEETING_STATUS', Core\Event\Tools\Dictionary::MEETING_STATUS['No']],
5186 ])
5187 )
5188 ->where(Query::filter()
5189 ->logic('or')
5190 ->where([
5191 ['ID', $entryId],
5192 ['PARENT_ID', $entryId],
5193 ])
5194 )
5195 ->whereIn('EVENT_TYPE', SharingEventManager::getSharingEventTypes())
5196 ->exec()
5197 ;
5198 $event = $result->fetch() ?: null;
5199
5200 if ($event)
5201 {
5202 $canceledUserId = (int)$event["MEETING_HOST"];
5203 if ($event['MEETING_STATUS'] === Core\Event\Tools\Dictionary::MEETING_STATUS['No'])
5204 {
5205 $canceledUserId = (int)$event["OWNER_ID"];
5206 $event['canceledByManager'] = true;
5207 }
5208 $host = Sharing\Helper::getOwnerInfo($canceledUserId);
5209
5210 $event['HOST_NAME'] = trim($host['name'] . ' ' . $host['lastName']);
5211 $event['timestampFromUTC'] = Sharing\Helper::getEventTimestampUTC($event['DATE_FROM'], $event['TZ_FROM']);
5212 $event['timestampToUTC'] = Sharing\Helper::getEventTimestampUTC($event['DATE_TO'], $event['TZ_TO']);
5213 $event['canceledUserId'] = $canceledUserId;
5214 }
5215
5216 return $event;
5217 }
5218
5219 public static function Timestamp($date, $bRound = true, $bTime = true)
5220 {
5221 $timestamp = MakeTimeStamp($date, self::TSFormat($bTime ? "FULL" : "SHORT"));
5222 if ($bRound)
5223 {
5224 $timestamp = self::RoundTimestamp($timestamp);
5225 }
5226
5227 return $timestamp;
5228 }
5229
5230 public static function TimestampUTC(string $date): int
5231 {
5232 $dateTime = self::createDateTimeObjectFromString($date, 'UTC');
5233
5234 return (int)$dateTime->format('U');
5235 }
5236
5237 public static function createDateTimeObjectFromString(string $date, ?string $timezone = null)
5238 {
5239 try
5240 {
5241 $parsedDateTime = ParseDateTime($date);
5242 $hours = (int)($parsedDateTime['HH'] ?? $parsedDateTime['H'] ?? 0);
5243 if (isset($parsedDateTime['TT']) || isset($parsedDateTime['T']))
5244 {
5245 $amPm = $parsedDateTime['TT'] ?? $parsedDateTime['T'];
5246 if (strcasecmp('pm', $amPm) === 0)
5247 {
5248 if ($hours < 12)
5249 {
5250 $hours += 12;
5251 }
5252 }
5253 else
5254 {
5255 $hours %= 12;
5256 }
5257 }
5258
5259 $dateTime = (new \DateTime('now', $timezone ? new \DateTimeZone($timezone) : null))
5260 ->setDate($parsedDateTime['YYYY'], $parsedDateTime['MM'], $parsedDateTime['DD'])
5261 ->setTime($hours, $parsedDateTime['MI'] ?? 0);
5262 }
5263 catch (\TypeError)
5264 {
5265 $dateTime = new \DateTime($date, $timezone ? new \DateTimeZone($timezone) : null );
5266 }
5267 finally
5268 {
5269 return $dateTime;
5270 }
5271 }
5272
5273 public static function TSFormat($format = "FULL")
5274 {
5275 return CSite::GetDateFormat($format);
5276 }
5277
5278 public static function RoundTimestamp($ts)
5279 {
5280 return round($ts / 60) * 60; // We don't need for seconds here
5281 }
5282
5283 public static function IsPersonal($type = false, $ownerId = false, $userId = false)
5284 {
5285 if (!$type)
5286 {
5287 $type = self::$type;
5288 }
5289 if(!$ownerId)
5290 {
5291 $ownerId = self::$ownerId;
5292 }
5293 if(!$userId)
5294 {
5295 $userId = self::$userId;
5296 }
5297
5298 return $type === 'user' && $ownerId == $userId;
5299 }
5300
5301 public static function IsExchangeEnabled($userId = false)
5302 {
5303 if (isset(self::$arExchEnabledCache[$userId]))
5304 {
5305 return self::$arExchEnabledCache[$userId];
5306 }
5307
5308 if (!IsModuleInstalled('dav') || COption::GetOptionString("dav", "agent_calendar") !== "Y")
5309 {
5310 $res = false;
5311 }
5313 {
5314 $res = false;
5315 }
5316 elseif ($userId === false)
5317 {
5318 $res = CDavExchangeCalendar::IsExchangeEnabled();
5319 }
5320 else
5321 {
5322 $res = CDavExchangeCalendar::IsExchangeEnabled() && CDavExchangeCalendar::IsExchangeEnabledForUser($userId);
5323 }
5324
5325 self::$arExchEnabledCache[$userId] = $res;
5326
5327 return $res;
5328 }
5329
5330 public static function isGoogleApiEnabled()
5331 {
5332 if (!isset(self::$isGoogleApiEnabled))
5333 {
5334 self::$isGoogleApiEnabled = Loader::includeModule('dav')
5335 && Loader::includeModule('socialservices')
5336 && (
5337 is_null(\Bitrix\Main\Config\Configuration::getValue("calendar_integration"))
5338 || \Bitrix\Main\Config\Configuration::getValue("calendar_integration") === self::INTEGRATION_GOOGLE_API
5339 );
5340
5341 if (self::$isGoogleApiEnabled && !self::IsBitrix24())
5342 {
5343 self::$isGoogleApiEnabled =
5344 (
5345 CSocServGoogleOAuth::GetOption('google_appid') !== ''
5346 && CSocServGoogleOAuth::GetOption('google_appsecret') !== ''
5347 )
5348 || CSocServGoogleOAuth::GetOption('google_sync_proxy') === 'Y'
5349 ;
5350 }
5351 }
5352
5353 return self::$isGoogleApiEnabled;
5354 }
5355
5356 public static function IsCalDAVEnabled()
5357 {
5358 if (!IsModuleInstalled('dav') || COption::GetOptionString("dav", "agent_calendar_caldav") !== "Y")
5359 {
5360 return false;
5361 }
5362
5363 return Loader::includeModule('dav') && CDavGroupdavClientCalendar::IsCalDAVEnabled();
5364 }
5365
5366 public static function isIphoneConnected()
5367 {
5368 $info = CCalendarSync::GetSyncInfoItem(self::$userId, 'iphone');
5369
5370 return $info['connected'];
5371 }
5372
5373 public static function isMacConnected()
5374 {
5375 $info = CCalendarSync::GetSyncInfoItem(self::$userId, 'mac');
5376
5377 return $info['connected'];
5378 }
5379
5380 public static function IsWebserviceEnabled()
5381 {
5382 if (!isset(self::$bWebservice))
5383 {
5384 self::$bWebservice = IsModuleInstalled('webservice');
5385 }
5386 return self::$bWebservice;
5387 }
5388
5389 public static function IsExtranetEnabled()
5390 {
5391 if (!isset(self::$bExtranet))
5392 {
5393 self::$bExtranet = Loader::includeModule('extranet') && CExtranet::IsExtranetSite();
5394 }
5395 return self::$bExtranet;
5396 }
5397
5398 public static function GetCurrentOffsetUTC($userId = false)
5399 {
5400 if (!$userId && self::$userId)
5401 {
5402 $userId = self::$userId;
5403 }
5404
5405 return (int)(date("Z") + self::GetOffset($userId));
5406 }
5407
5408 public static function GetOffset($userId = false)
5409 {
5410 if ($userId > 0)
5411 {
5412 if (!isset(self::$arTimezoneOffsets[$userId]))
5413 {
5414 $offset = CTimeZone::GetOffset($userId, true);
5415 self::$arTimezoneOffsets[$userId] = $offset;
5416 }
5417 else
5418 {
5419 $offset = self::$arTimezoneOffsets[$userId];
5420 }
5421 }
5422 else if (!isset(self::$offset))
5423 {
5424 $offset = CTimeZone::GetOffset(null, true);
5425 self::$offset = $offset;
5426 }
5427 else
5428 {
5429 $offset = self::$offset;
5430 }
5431
5432 return $offset;
5433 }
5434
5435 public static function GetUserTimezoneName($user, $getDefault = true)
5436 {
5437 if (isset(self::$userTimezoneList[$user]) && !is_array($user) && (int)$user > 0)
5438 {
5439 return self::$userTimezoneList[$user];
5440 }
5441
5442 if (is_array($user) && (int)$user['ID'] > 0 && isset(self::$userTimezoneList[$user['ID']]))
5443 {
5444 return self::$userTimezoneList[$user['ID']];
5445 }
5446
5447 if (!is_array($user) && (int)$user > 0)
5448 {
5449 $user = self::GetUser((int)$user, true);
5450 }
5451
5452 if (\CTimezone::OptionEnabled() && $user && is_array($user))
5453 {
5454 $offset = isset($user['TIME_ZONE_OFFSET'])
5455 ? (int)(date('Z') + $user['TIME_ZONE_OFFSET'])
5456 : self::GetCurrentOffsetUTC($user['ID']);
5457
5458 $tzName = CUserOptions::GetOption(
5459 "calendar",
5460 "timezone" . $offset,
5461 false,
5462 $user['ID']
5463 );
5464
5465 if ($tzName === 'undefined' || $tzName === 'false')
5466 {
5467 $tzName = false;
5468 }
5469 if (!$tzName && ($user['AUTO_TIME_ZONE'] ?? '') !== 'Y' && $user['TIME_ZONE'])
5470 {
5471 $tzName = $user['TIME_ZONE'];
5472 }
5473 }
5474 else
5475 {
5476 $offset = date('Z');
5477 $tzName = date_default_timezone_get();
5478 }
5479
5480 try
5481 {
5482 new DateTimeZone($tzName);
5483 }
5484 catch (\Throwable)
5485 {
5486 $tzName = false;
5487 }
5488
5489 if (!$tzName && $getDefault)
5490 {
5491 $tzName = self::GetGoodTimezoneForOffset($offset);
5492 }
5493
5494 if ($user && is_array($user) && $user['ID'])
5495 {
5496 self::$userTimezoneList[$user['ID']] = $tzName;
5497 }
5498
5499 return $tzName;
5500 }
5501
5502 public static function GetUser($userId, $bPhoto = false)
5503 {
5504 $userId = (int)$userId;
5505
5506 return self::$userList[$userId] ?? current(self::GetUserList([$userId]));
5507 }
5508
5509
5510 public static function GetUserList(array $userIdList): array
5511 {
5512 $result = [];
5513 $userIdToRequest = self::GetNotRequestedUserIdList($userIdList);
5514
5515 if (!empty($userIdToRequest))
5516 {
5517 $userList = Main\UserTable::query()
5518 ->setSelect([
5519 'ID',
5520 'NAME',
5521 'LAST_NAME',
5522 'SECOND_NAME',
5523 'LOGIN',
5524 'PERSONAL_PHOTO',
5525 'TIME_ZONE',
5526 'EMAIL',
5527 'EXTERNAL_AUTH_ID',
5528 'NOTIFICATION_LANGUAGE_ID',
5529 ])
5530 ->whereIn('ID', $userIdToRequest)
5531 ->exec()
5532 ->fetchAll()
5533 ;
5534
5535 foreach ($userIdToRequest as $userId)
5536 {
5537 self::$userList[$userId] = false;
5538 }
5539
5540 foreach ($userList as $user)
5541 {
5542 $user['ID'] = (int)$user['ID'];
5543 $user['COLLAB_USER'] = Util::isCollabUser($user['ID']);
5544
5545 self::$userList[$user['ID']] = $user;
5546 }
5547 }
5548
5549 foreach ($userIdList as $userId)
5550 {
5551 $result[] = self::$userList[$userId];
5552 }
5553
5554 return $result;
5555 }
5556
5557 private static function GetNotRequestedUserIdList(array $userIdList): array
5558 {
5559 $result = [];
5560
5561 foreach ($userIdList as $userId)
5562 {
5563 $userId = (int)$userId;
5564 if (!isset(self::$userList[$userId]))
5565 {
5566 $result[] = $userId;
5567 }
5568 }
5569
5570 return $result;
5571 }
5572
5573 public static function GetGoodTimezoneForOffset($offset)
5574 {
5575 $timezones = self::GetTimezoneList();
5576 $goodTz = [];
5577 $result = false;
5578
5579 foreach($timezones as $tz)
5580 {
5581 if ($tz['offset'] == $offset)
5582 {
5583 $goodTz[] = $tz;
5584 if (LANGUAGE_ID == 'ru')
5585 {
5586 if (preg_match('/(kaliningrad|moscow|samara|yekaterinburg|novosibirsk|krasnoyarsk|irkutsk|yakutsk|vladivostok)/i', $tz['timezone_id']))
5587 {
5588
5589 $result = $tz['timezone_id'];
5590 break;
5591 }
5592 }
5593 elseif (mb_strpos($tz['timezone_id'], 'Europe') !== false)
5594 {
5595 $result = $tz['timezone_id'];
5596 break;
5597 }
5598 }
5599 }
5600
5601 if (!$result && !empty($goodTz))
5602 {
5603 $result = $goodTz[0]['timezone_id'];
5604 }
5605
5606 if (!$result)
5607 {
5608 $result = date_default_timezone_get();
5609 }
5610
5611 return $result;
5612 }
5613
5614 public static function GetTimezoneList()
5615 {
5616 if (empty(self::$timezones))
5617 {
5618 self::$timezones = [];
5619 $aExcept = ["Etc/", "GMT", "UCT", "HST", "PST", "MST", "CST", "EST", "CET", "MET", "WET", "EET", "PRC", "ROC", "ROK", "W-SU"];
5620 foreach(DateTimeZone::listIdentifiers() as $tz)
5621 {
5622 foreach($aExcept as $ex)
5623 {
5624 if(str_starts_with($tz, $ex))
5625 {
5626 continue 2;
5627 }
5628 }
5629 try
5630 {
5631 $oTz = new DateTimeZone($tz);
5632 self::$timezones[$tz] = [
5633 'timezone_id' => $tz,
5634 'offset' => $oTz->getOffset(new DateTime("now", $oTz)),
5635 ];
5636 }
5637 catch(Exception $e)
5638 {
5639 }
5640 }
5641 uasort(self::$timezones, static function($a, $b){
5642 if($a['offset'] === $b['offset'])
5643 {
5644 return strcmp($a['timezone_id'], $b['timezone_id']);
5645 }
5646 return $a['offset'] < $b['offset'] ? -1 : 1;
5647 });
5648
5649 foreach(self::$timezones as $k => $z)
5650 {
5651 $offset = $z['offset'];
5652 $hours = floor(abs($offset) / 3600);
5653
5654 if ($z['timezone_id'] === 'UTC')
5655 {
5656 self::$timezones[$k]['title'] = $z['timezone_id'];
5657 }
5658 else
5659 {
5660 self::$timezones[$k]['title'] =
5661 '(UTC'
5662 . ($offset !== 0
5663 ? ' ' . ($offset < 0 ? '-' : '+')
5664 .sprintf("%02d", $hours)
5665 . ':' . sprintf("%02d", abs($offset)/60 - $hours * 60)
5666 : ''
5667 ) . ') ' . $z['timezone_id'];
5668 }
5669 }
5670 }
5671 return self::$timezones;
5672 }
5673
5674 public static function GetUserName($user)
5675 {
5676 if (!is_array($user) && (int)$user > 0)
5677 {
5678 $user = self::GetUser($user);
5679 }
5680 if (!$user || !is_array($user))
5681 {
5682 return '';
5683 }
5684
5685 return CUser::FormatName(self::$userNameTemplate, $user, true, false);
5686 }
5687
5688 public static function IsAdmin(): bool
5689 {
5690 GLOBAL $USER;
5691
5692 return $USER->IsAdmin();
5693 }
5694
5695 public static function GetWeekStart()
5696 {
5697 if (!isset(self::$weekStart))
5698 {
5699 $days = ['1' => 'MO', '2' => 'TU', '3' => 'WE', '4' => 'TH', '5' => 'FR', '6' => 'SA', '0' => 'SU'];
5700 $cultureWeekStart = \Bitrix\Main\Context::getCurrent()?->getCulture()?->getWeekStart();
5701 self::$weekStart = $days[$cultureWeekStart];
5702
5703 if (!in_array(self::$weekStart, $days))
5704 {
5705 self::$weekStart = 'MO';
5706 }
5707 }
5708
5709 return self::$weekStart;
5710 }
5711
5712 public static function Date($timestamp, $bTime = true, $bRound = true, $bCutSeconds = false)
5713 {
5714 if ($bRound)
5715 {
5716 $timestamp = self::RoundTimestamp($timestamp);
5717 }
5718
5719 $format = self::DFormat($bTime);
5720 if ($bTime && $bCutSeconds)
5721 {
5722 $format = str_replace(':s', '', $format);
5723 }
5724
5725 return FormatDate($format, $timestamp);
5726 }
5727
5728 public static function DFormat($bTime = true)
5729 {
5730 return CDatabase::DateFormatToPHP(CSite::GetDateFormat($bTime ? "FULL" : "SHORT", SITE_ID));
5731 }
5732
5733 public static function DateWithNewTime($timestampTime, $timestampDate)
5734 {
5735 return mktime(date("H", $timestampTime), date("i", $timestampTime), 0, date("m", $timestampDate), date("d", $timestampDate), date("Y", $timestampDate));
5736 }
5737
5738 public static function GetCurUserMeetingSection($bCreate = false)
5739 {
5740 if (!isset(self::$userMeetingSection) || !self::$userMeetingSection)
5741 {
5742 self::$userMeetingSection = self::GetMeetingSection(self::$userId, $bCreate);
5743 }
5744
5745 return self::$userMeetingSection;
5746 }
5747
5748 public static function GetMeetingSection($userId, $autoCreate = false)
5749 {
5750 if (!$userId || is_array($userId))
5751 {
5752 return false;
5753 }
5754
5755 if (isset(self::$meetingSections[$userId]))
5756 {
5757 return self::$meetingSections[$userId];
5758 }
5759
5760 $result = false;
5761 $meetingSectionId = false;
5762
5763 if ($userId > 0)
5764 {
5765 $set = UserSettings::get($userId);
5766
5767 $meetingSectionId = (int)$set['meetSection'];
5768
5769 $result = self::getSectionForUser($meetingSectionId, $userId, $autoCreate);
5770 }
5771
5772 foreach(\Bitrix\Main\EventManager::getInstance()->findEventHandlers("calendar", "OnGetMeetingSectionForUser") as $event)
5773 {
5774 ExecuteModuleEventEx($event, [$userId, &$result]);
5775 }
5776
5777 if ($result && $meetingSectionId !== $result)
5778 {
5779 $set['meetSection'] = $result;
5780 UserSettings::set($set, $userId);
5781 }
5782
5783 self::$meetingSections[$userId] = $result;
5784
5785 return $result;
5786 }
5787
5788 public static function GetCrmSection($userId, $autoCreate = false)
5789 {
5790 if (!$userId || is_array($userId))
5791 {
5792 return false;
5793 }
5794
5795 if (isset(self::$crmSections[$userId]))
5796 {
5797 return self::$crmSections[$userId];
5798 }
5799
5800 $result = false;
5801 $crmSectionId = false;
5802
5803 if ($userId > 0)
5804 {
5805 $set = UserSettings::get($userId);
5806
5807 $crmSectionId = (int)$set['crmSection'];
5808
5809 $result = self::getSectionForUser($crmSectionId, $userId, $autoCreate);
5810 }
5811
5812 if ($result && $crmSectionId !== $result)
5813 {
5814 $set['crmSection'] = $result;
5815 UserSettings::set($set, $userId);
5816 }
5817
5818 self::$crmSections[$userId] = $result;
5819
5820 return $result;
5821 }
5822
5823 private static function getSectionForUser($result, $userId, $autoCreate = false)
5824 {
5825 $section = false;
5826
5827 if ($result)
5828 {
5829 $sectionQueryResult = \Bitrix\Calendar\Internals\SectionTable::query()
5830 ->setSelect(['ID', 'CAL_TYPE', 'OWNER_ID', 'ACTIVE'])
5831 ->where('ID', $result)
5832 ->where('CAL_TYPE', Dictionary::CALENDAR_TYPE['user'])
5833 ->where('OWNER_ID', (int)$userId)
5834 ->where('ACTIVE', 'Y')
5835 ->setLimit(1)
5836 ->exec()->fetch()
5837 ;
5838 $section = !empty($sectionQueryResult) ? (int)$sectionQueryResult['ID'] : false;
5839 }
5840
5841 if ($result && !$section)
5842 {
5843 $result = false;
5844 }
5845
5846 if (!$result)
5847 {
5848 $sectionQueryResult = \Bitrix\Calendar\Internals\SectionTable::query()
5849 ->setSelect(['ID', 'CAL_TYPE', 'OWNER_ID', 'ACTIVE'])
5850 ->where('CAL_TYPE', Dictionary::CALENDAR_TYPE['user'])
5851 ->where('OWNER_ID', (int)$userId)
5852 ->where('ACTIVE', 'Y')
5853 ->setLimit(1)
5854 ->exec()->fetch()
5855 ;
5856
5857 if (!empty($sectionQueryResult) && $sectionQueryResult['ID'])
5858 {
5859 $result = (int)$sectionQueryResult['ID'];
5860 }
5861
5862 if (!$result && $autoCreate)
5863 {
5864 $defCalendar = CCalendarSect::CreateDefault([
5865 'type' => Dictionary::CALENDAR_TYPE['user'],
5866 'ownerId' => $userId,
5867 ]);
5868 if ($defCalendar && $defCalendar['ID'] > 0)
5869 {
5870 $result = (int)$defCalendar['ID'];
5871 }
5872 }
5873 }
5874
5875 return $result;
5876 }
5877
5878 public static function GetSectionList($params = [])
5879 {
5880 $type = $params['CAL_TYPE'] ?? self::$type;
5881
5882 $arFilter = [
5883 'CAL_TYPE' => $type,
5884 ];
5885
5886 if (isset($params['OWNER_ID']))
5887 {
5888 $arFilter['OWNER_ID'] = $params['OWNER_ID'];
5889 }
5890 elseif ($type === 'user' || $type === 'group')
5891 {
5892 $arFilter['OWNER_ID'] = self::GetOwnerId();
5893 }
5894
5895 if (isset($params['ACTIVE']))
5896 {
5897 $arFilter['ACTIVE'] = $params['ACTIVE'];
5898 }
5899
5900 if (!empty($params['ADDITIONAL_IDS']))
5901 {
5902 $arFilter['ADDITIONAL_IDS'] = $params['ADDITIONAL_IDS'];
5903 }
5904
5905 $sectionList = CCalendarSect::GetList([
5906 'arFilter' => $arFilter,
5907 'checkPermissions' => ($params['checkPermissions'] ?? null),
5908 'getPermissions' => ($params['getPermissions'] ?? null),
5909 ]);
5910
5911 if (
5912 $type === 'user'
5913 || (is_array($type) && in_array(Core\Event\Tools\Dictionary::CALENDAR_TYPE['user'], $type, true))
5914 )
5915 {
5916 $sectionIdList = [];
5917 foreach ($sectionList as $section)
5918 {
5919 $sectionIdList[] = (int)$section['ID'];
5920 }
5921
5922 $sectionLinkList = !empty($sectionIdList) ? CCalendarSect::getSectionConnectionList($sectionIdList) : [];
5923
5924 if (!empty($sectionLinkList))
5925 {
5926 foreach ($sectionList as $i => $section)
5927 {
5928 $sectionList[$i]['connectionLinks'] = [];
5929 foreach ($sectionLinkList as $sectionLink)
5930 {
5931 if ($sectionLink->getSectionId() === (int)$section['ID'])
5932 {
5933 $sectionList[$i]['connectionLinks'][] = [
5934 'id' => $sectionLink->getConnectionId(),
5935 'active' => $sectionLink->getActive() ? 'Y' : 'N',
5936 'isPrimary' => $sectionLink->getIsPrimary() ? 'Y' : 'N',
5937 ];
5938 }
5939 }
5940 }
5941 }
5942 }
5943
5944 if (($params['getImages'] ?? null))
5945 {
5946 $sectionList = self::fetchIconsForSectionList($sectionList);
5947 }
5948
5949 return $sectionList;
5950 }
5951
5952 public static function fetchIconsForSectionList($sectionList)
5953 {
5954 $SECTION_IMG_SIZE = 28;
5955 $userIdList = [];
5956 $groupIdList = [];
5957 $userIndexList = [];
5958 $groupListIndex = [];
5959
5960 foreach ($sectionList as $section)
5961 {
5962 $ownerId = (int)$section['OWNER_ID'];
5963 if (
5964 $section['CAL_TYPE'] === 'user'
5965 && !in_array($ownerId, $userIdList)
5966 )
5967 {
5968 $userIdList[] = $ownerId;
5969 }
5970 elseif ($section['CAL_TYPE'] === 'group'
5971 && !in_array($ownerId, $groupIdList))
5972 {
5973 $groupIdList[] = $ownerId;
5974 }
5975 }
5976
5977 if (!empty($userIdList))
5978 {
5979 $userIndexList = \CCalendarEvent::getUsersDetails($userIdList);
5980 }
5981
5982 if (!empty($groupIdList) && Loader::includeModule("socialnetwork"))
5983 {
5984 $res = Bitrix\Socialnetwork\WorkgroupTable::getList([
5985 'filter' => [
5986 '=ACTIVE' => 'Y',
5987 '@ID' => $groupIdList,
5988 ],
5989 'select' => ['ID', 'IMAGE_ID'],
5990 ]);
5991 while ($workgroupFields = $res->fetch())
5992 {
5993 if (!empty($workgroupFields["IMAGE_ID"]))
5994 {
5995 $arFileTmp = CFile::ResizeImageGet(
5996 $workgroupFields["IMAGE_ID"],
5997 ['width' => $SECTION_IMG_SIZE, 'height' => $SECTION_IMG_SIZE],
5999 false,
6000 false,
6001 true
6002 );
6003 $workgroupFields['IMAGE'] = $arFileTmp['src'];
6004 }
6005 $groupListIndex[$workgroupFields['ID']] = $workgroupFields;
6006 }
6007 }
6008
6009 foreach ($sectionList as $k => $section)
6010 {
6011 $ownerId = (int)$section['OWNER_ID'];
6012 if ($section['CAL_TYPE'] === 'user'
6013 && isset($userIndexList[$ownerId])
6014 && !empty($userIndexList[$ownerId]['AVATAR'])
6015 && $userIndexList[$ownerId]['AVATAR'] !== '/bitrix/images/1.gif'
6016 )
6017 {
6018 $sectionList[$k]['IMAGE'] = $userIndexList[$ownerId]['AVATAR'];
6019 }
6020 elseif (
6021 $section['CAL_TYPE'] === 'group'
6022 && isset($groupListIndex[$ownerId])
6023 && !empty($groupListIndex[$ownerId]['IMAGE'])
6024 )
6025 {
6026 $sectionList[$k]['IMAGE'] = $groupListIndex[$ownerId]['IMAGE'];
6027 }
6028
6029 $pathesForSite = self::getPathes(SITE_ID);
6030 if ($section['CAL_TYPE'] === 'user')
6031 {
6032 $sectionList[$k]['LINK'] = str_replace(
6033 ['#user_id#', '#USER_ID#'],
6034 $section['OWNER_ID'],
6035 $pathesForSite['path_to_user_calendar']
6036 );
6037 }
6038 else if($section['CAL_TYPE'] === 'group')
6039 {
6040 $sectionList[$k]['LINK'] = str_replace(
6041 ['#group_id#', '#GROUP_ID#'],
6042 $section['OWNER_ID'],
6043 $pathesForSite['path_to_user_calendar']
6044 );
6045 }
6046 else
6047 {
6048 $path = $pathesForSite['path_to_type_'.$section['CAL_TYPE']];
6049 $sectionList[$k]['LINK'] = $path;
6050 }
6051 }
6052 return $sectionList;
6053 }
6054
6055 public static function GetOwnerId()
6056 {
6057 return self::$ownerId;
6058 }
6059
6066 public static function GetEventList($params, &$arAttendees)
6067 {
6068 $type = $params['type'] ?? self::$type;
6069 $ownerId = isset($params['ownerId']) ? (int)$params['ownerId'] : self::$ownerId;
6070 $userId = isset($params['userId']) ? (int)$params['userId'] : self::$userId;
6071
6072 if (empty($params['section']))
6073 {
6074 return [];
6075 }
6076
6077 $arFilter = [];
6078 if (isset($params['fromLimit']))
6079 {
6080 $arFilter["FROM_LIMIT"] = $params['fromLimit'];
6081 }
6082 if (isset($params['toLimit']))
6083 {
6084 $arFilter["TO_LIMIT"] = $params['toLimit'];
6085 }
6086
6087 $arFilter["OWNER_ID"] = $ownerId;
6088
6089 if ($type === 'user')
6090 {
6091 $fetchMeetings = in_array(self::GetMeetingSection($ownerId), $params['section'], true);
6092 }
6093 else
6094 {
6095 $fetchMeetings = in_array(self::GetCurUserMeetingSection(), $params['section'], true);
6096 if ($type)
6097 {
6098 $arFilter['CAL_TYPE'] = $type;
6099 }
6100 }
6101
6102 $res = CCalendarEvent::GetList([
6103 'arFilter' => $arFilter,
6104 'parseRecursion' => true,
6105 'fetchAttendees' => true,
6106 'userId' => $userId,
6107 'fetchMeetings' => $fetchMeetings,
6108 'setDefaultLimit' => false,
6109 'limit' => $params['limit'] ?? null,
6110 'getUserfields' => true,
6111 ]);
6112
6113 $result = [];
6114 foreach ($res as $event)
6115 {
6116 if (in_array((int)$event['SECT_ID'], $params['section'], true))
6117 {
6118 unset($event['~DESCRIPTION']);
6119 $result[] = $event;
6120 }
6121 }
6122
6123 return $result;
6124 }
6125
6126 public static function getTaskList(TaskQueryParameter $parameter)
6127 {
6128 if (!Loader::includeModule('tasks'))
6129 {
6130 return [];
6131 }
6132
6133 $res = [];
6134 $userSettings = Bitrix\Calendar\UserSettings::get();
6135
6136 $filter = [
6137 '!STATUS' => [
6138 Status::DEFERRED,
6139 ],
6140 'CHECK_PERMISSIONS' => 'Y',
6141 ];
6142
6143 if ($userSettings['showCompletedTasks'] === 'N')
6144 {
6145 $filter['!STATUS'][] = Status::COMPLETED;
6146 }
6147 if ($parameter->isUserType())
6148 {
6149 $filter['DOER'] = $parameter->getOwnerId();
6150 }
6151 elseif ($parameter->isGroupType())
6152 {
6153 $filter['GROUP_ID'] = $parameter->getOwnerId();
6154 }
6155
6156 $tzEnabled = CTimeZone::Enabled();
6157 if ($tzEnabled)
6158 {
6159 CTimeZone::Disable();
6160 }
6161
6162 $query = (new TaskQuery($parameter->getUserId()))
6163 ->setSelect([
6164 'ID',
6165 'TITLE',
6166 'DESCRIPTION',
6167 'CREATED_DATE',
6168 'DEADLINE',
6169 'START_DATE_PLAN',
6170 'END_DATE_PLAN',
6171 'DATE_START',
6172 'CLOSED_DATE',
6173 'STATUS_CHANGED_DATE',
6174 'STATUS',
6175 'REAL_STATUS',
6176 'CREATED_BY',
6177 'GROUP_ID',
6178 ])
6179 ->setOrder(['START_DATE_PLAN' => 'ASC'])
6180 ->setWhere($filter);
6181
6182 $tasks = (new TaskList())->getList($query);
6183
6184 $offset = self::GetOffset();
6185 foreach ($tasks as $task)
6186 {
6187 $dtFrom = null;
6188 $dtTo = null;
6189
6190 $skipFromOffset = false;
6191 $skipToOffset = false;
6192
6193 if (isset($task["START_DATE_PLAN"]) && $task["START_DATE_PLAN"])
6194 {
6195 $dtFrom = self::CutZeroTime($task["START_DATE_PLAN"]);
6196 }
6197
6198 if (isset($task["END_DATE_PLAN"]) && $task["END_DATE_PLAN"])
6199 {
6200 $dtTo = self::CutZeroTime($task["END_DATE_PLAN"]);
6201 }
6202
6203 if (!isset($dtFrom) && isset($task["DATE_START"]))
6204 {
6205 $dtFrom = self::CutZeroTime($task["DATE_START"]);
6206 }
6207
6208 if (!isset($dtTo) && isset($task["CLOSED_DATE"]))
6209 {
6210 $dtTo = self::CutZeroTime($task["CLOSED_DATE"]);
6211 }
6212
6213 if (
6214 !isset($dtTo) && isset($task["STATUS_CHANGED_DATE"])
6215 && in_array(
6216 (int)$task["REAL_STATUS"],
6217 [Status::SUPPOSEDLY_COMPLETED, Status::COMPLETED, Status::DEFERRED, Status::DECLINED],
6218 true
6219 )
6220 )
6221 {
6222 $dtTo = self::CutZeroTime($task["STATUS_CHANGED_DATE"]);
6223 }
6224
6225 if (isset($dtTo))
6226 {
6227 $ts = self::Timestamp($dtTo); // Correction display logic for harmony with Tasks interfaces
6228 if (date("H:i", $ts) === '00:00')
6229 {
6230 $dtTo = self::Date($ts - 24 * 60 * 60);
6231 }
6232 }
6233 elseif (isset($task["DEADLINE"]))
6234 {
6235 $dtTo = self::CutZeroTime($task["DEADLINE"]);
6236 $ts = self::Timestamp($dtTo); // Correction display logic for harmony with Tasks interfaces
6237 if (date("H:i", $ts) === '00:00')
6238 {
6239 $dtTo = self::Date($ts - 24 * 60 * 60);
6240 }
6241
6242 if (!isset($dtFrom))
6243 {
6244 $skipFromOffset = true;
6245 $dtFrom = self::Date(time(), false);
6246 }
6247 }
6248
6249 if (!isset($dtTo))
6250 {
6251 $dtTo = self::Date(time(), false);
6252 }
6253
6254 if (!isset($dtFrom))
6255 {
6256 $dtFrom = $dtTo;
6257 }
6258
6259 $dtFromTS = self::Timestamp($dtFrom);
6260 $dtToTS = self::Timestamp($dtTo);
6261
6262 if ($dtToTS < $dtFromTS)
6263 {
6264 $dtToTS = $dtFromTS;
6265 $dtTo = self::Date($dtToTS, true);
6266 }
6267
6268 $skipTime = date("H:i", $dtFromTS) === '00:00' && date("H:i", $dtToTS) === '00:00';
6269 if (!$skipTime && $offset != 0)
6270 {
6271 if (!$skipFromOffset)
6272 {
6273 $dtFromTS += $offset;
6274 $dtFrom = self::Date($dtFromTS, true);
6275 }
6276
6277 if (!$skipToOffset)
6278 {
6279 $dtToTS += $offset;
6280 $dtTo = self::Date($dtToTS, true);
6281 }
6282 }
6283
6284 $res[] = [
6285 "ID" => $task["ID"],
6286 "~TYPE" => "tasks",
6287 "NAME" => $task["TITLE"],
6288 "DATE_FROM" => $dtFrom,
6289 "DATE_TO" => $dtTo,
6290 "DT_SKIP_TIME" => $skipTime ? 'Y' : 'N',
6291 "CAN_EDIT" => CTasks::CanCurrentUserEdit($task),
6292 ];
6293 }
6294
6295 if ($tzEnabled)
6296 {
6297 CTimeZone::Enable();
6298 }
6299
6300 return $res;
6301 }
6302
6303 public static function CutZeroTime($date)
6304 {
6305 if (preg_match('/.*\s\d\d:\d\d:\d\d/i', $date))
6306 {
6307 $date = trim($date);
6308 if (mb_substr($date, -9) === ' 00:00:00')
6309 {
6310 return mb_substr($date, 0, -9);
6311 }
6312 if (mb_substr($date, -3) === ':00')
6313 {
6314 return mb_substr($date, 0, -3);
6315 }
6316 }
6317 return $date;
6318 }
6319
6320 public static function GetType()
6321 {
6322 return self::$type;
6323 }
6324
6325 public static function GetAccessNames()
6326 {
6327 $codes = [];
6328 foreach (self::$accessNames as $code => $name)
6329 {
6330 if ($name === null)
6331 {
6332 $codes[] = $code;
6333 }
6334 }
6335
6336 if ($codes)
6337 {
6338 $access = new CAccess();
6339 $names = $access->GetNames($codes);
6340
6341 foreach($names as $code => $name)
6342 {
6343 self::$accessNames[$code] = trim(htmlspecialcharsbx($name['name']));
6344 }
6345 self::$accessNames['UA'] = Loc::getMessage('EC_ENTITY_SELECTOR_ALL_EMPLOYEES');
6346 }
6347
6348 return self::$accessNames;
6349 }
6350
6351 public static function SetSilentErrorMode($silentErrorMode = true)
6352 {
6353 self::$silentErrorMode = $silentErrorMode;
6354 }
6355
6356 public function GetId()
6357 {
6358 return self::$id ?: 'EC'.rand();
6359 }
6360
6371 public static function GetOriginalDate(
6372 $parentDateTime,
6373 $instanceDateTime,
6374 $timeZone = null,
6375 $format = null
6376 ): string
6377 {
6378 CTimeZone::Disable();
6380 $parentTimestamp = Util::getDateObject($parentDateTime, false, $timeZone)->getTimestamp();
6381 $baseTimeZone = date_default_timezone_get();
6382 if ($timeZone)
6383 {
6384 date_default_timezone_set($timeZone);
6385 }
6386 $parentInfoDate = getdate($parentTimestamp);
6388 $instanceDateTime = Util::getDateObject($instanceDateTime, false, $timeZone);
6389 $eventDate = $instanceDateTime->setTime($parentInfoDate['hours'], $parentInfoDate['minutes'])->format($format);
6390 if ($baseTimeZone)
6391 {
6392 date_default_timezone_set($baseTimeZone);
6393 }
6395
6396 return $eventDate;
6397 }
6398
6399 public static function getSectionListAvailableForUser($userId, $additionalSectionIdList = [], $params = [])
6400 {
6401 return self::GetSectionList([
6402 'CAL_TYPE' => 'user',
6403 'OWNER_ID' => $userId,
6404 'ACTIVE' => 'Y',
6405 'ADDITIONAL_IDS' => array_merge($additionalSectionIdList, UserSettings::getFollowedSectionIdList($userId)),
6406 ...$params,
6407 ]);
6408 }
6409
6410 public static function setOwnerId($userId)
6411 {
6412 self::$ownerId = $userId;
6413 }
6414
6415 public static function isOffice365ApiEnabled(): ?bool
6416 {
6417 if (!isset(self::$isOffice365ApiEnabled))
6418 {
6419 self::$isOffice365ApiEnabled = Loader::includeModule('dav')
6420 && Loader::includeModule('socialservices')
6421 ;
6422
6423 if (self::$isOffice365ApiEnabled && !self::IsBitrix24())
6424 {
6425 self::$isOffice365ApiEnabled = CSocServGoogleOAuth::GetOption('office365_appid') !== ''
6426 && CSocServGoogleOAuth::GetOption('office365_appid') !== ''
6427 ;
6428 }
6429 }
6430
6431 return self::$isOffice365ApiEnabled;
6432 }
6433
6451 public static function syncChange(int $id, array $arFields, array $params, ?array $curEvent): ?Sync\Util\Result
6452 {
6454 $mapperFactory = ServiceLocator::getInstance()->get('calendar.service.mappers.factory');
6456 $event = $mapperFactory->getEvent()->resetCacheById($id)->getById($id);
6457 if (!$event)
6458 {
6459 return null;
6460 }
6461
6462 if (
6463 $curEvent
6464 && !empty($curEvent['SECT_ID'])
6465 && $event->getSection()
6466 && (int)$curEvent['SECT_ID'] !== $event->getSection()->getId()
6467 )
6468 {
6469 return self::changeCalendarSync($event, $curEvent, $params);
6470 }
6471
6472 $factories = FactoriesCollection::createBySection(
6473 $event->getSection()
6474 );
6475
6476 if ($factories->count() === 0)
6477 {
6478 return null;
6479 }
6480
6481 $syncManager = new Synchronization($factories);
6482 $context = new Context([]);
6483
6484 if (
6485 $params['originalFrom']
6486 && (int)$arFields['OWNER_ID'] === (int)$params['userId']
6487 )
6488 {
6489 $context->add('sync', 'originalFrom', $params['originalFrom']);
6490
6491 $connection = (new Bitrix\Calendar\Core\Mappers\Connection())->getMap([
6492 '=ACCOUNT_TYPE' => $params['originalFrom'],
6493 '=ENTITY_TYPE' => $event->getCalendarType(),
6494 '=ENTITY_ID' => $event->getOwner()->getId(),
6495 ])->fetch();
6496
6497 if ($connection)
6498 {
6499 $syncManager->upEventVersion(
6500 $event,
6502 $arFields['VERSION'] ?? 1
6503 );
6504 }
6505 }
6506
6507 $pushManager = new Sync\Managers\PushManager();
6508 try
6509 {
6511 foreach ($factories as $factory)
6512 {
6513 $pushManager->unLockConnection($factory->getConnection());
6514 $pushManager->lockConnection($factory->getConnection(), 30);
6515 }
6516 if (($params['recursionEditMode'] ?? null) === 'skip')
6517 {
6518 if ($event->isInstance())
6519 {
6520 $params['editInstance'] = $event->isInstance();
6521 $params['modeSync'] = true;
6522 }
6523
6524 if (!empty($params['modeSync']))
6525 {
6526 $recurrenceSyncMode = ($params['editInstance'] << 2)
6527 | ($params['editNextEvents'] << 1)
6528 | ($params['editEntryUntil'] << 1)
6529 | $params['editParentEvents']
6530 ;
6531 switch ($recurrenceSyncMode)
6532 {
6533 case Sync\Dictionary::RECURRENCE_SYNC_MODE['exception']:
6534 if ($event->getMeetingStatus() !== 'N')
6535 {
6536 $result = empty($curEvent)
6537 ? $syncManager->createInstance($event, $context)
6538 : $syncManager->updateInstance($event, $context)
6539 ;
6540 }
6541 break;
6542 case Sync\Dictionary::RECURRENCE_SYNC_MODE['deleteInstance']:
6543 $context->add('diff', 'EXDATE', $curEvent['EXDATE']);
6544 $result = $syncManager->deleteInstance($event, $context);
6545 break;
6546 case Sync\Dictionary::RECURRENCE_SYNC_MODE['exceptionNewSeries']:
6547 $syncManager->deleteInstanceEventConnection($event);
6548 if ($event->getMeetingStatus() !== 'N')
6549 {
6550 $result = $syncManager->createInstance($event, $context);
6551 }
6552 break;
6553 default:
6554 if ($event->getMeetingStatus() !== 'N')
6555 {
6556 $result = empty($curEvent)
6557 ? $syncManager->createEvent($event, $context)
6558 : $syncManager->updateEvent($event, $context)
6559 ;
6560 }
6561 }
6562 }
6563 }
6564 elseif (empty($curEvent))
6565 {
6566 if ($event->isInstance())
6567 {
6568 $attendeeMasterEvent = $mapperFactory->getEvent()->getMap([
6569 '=PARENT_ID' => $event->getRecurrenceId(),
6570 '=OWNER_ID' => $event->getOwner()->getId(),
6571 '=CAL_TYPE' => 'user',
6572 ])->fetch();
6573
6574 if ($attendeeMasterEvent)
6575 {
6576 $result = $syncManager->reCreateRecurrence($attendeeMasterEvent, $context);
6577 }
6578 else
6579 {
6580 $result = (new Sync\Util\Result())->addError(
6581 new Main\Error("Master event not found", 404)
6582 );
6583 }
6584 }
6585 else if ($event->getMeetingStatus() !== 'N')
6586 {
6587 if (
6588 !empty($params['editNextEvents'])
6589 && !empty($params['previousRecurrentId'])
6590 && $event->getExcludedDateCollection()?->count()
6591 )
6592 {
6593 self::removeIncorrectRecurrentExDates($event->getExcludedDateCollection(), $params['previousRecurrentId']);
6594 }
6595
6596 $result = $syncManager->createEvent($event, $context);
6597 }
6598 }
6599 else
6600 {
6601 $syncManager->updateEvent($event, $context);
6602 }
6603 }
6604 catch(Throwable $e)
6605 {
6606 throw $e;
6607 }
6608
6609 return $result ?? null;
6610 }
6611
6618 private static function removeIncorrectRecurrentExDates(
6619 Core\Event\Properties\ExcludedDatesCollection $exDatesCollection,
6620 int $recId,
6621 ): void
6622 {
6623 $exDatesToRemove = [];
6624
6625 $recEvents = CCalendarEvent::GetEventsByRecId($recId, false);
6626
6627 foreach ($recEvents as $recEvent)
6628 {
6629 if ((int)$recEvent['ID'] !== (int)$recEvent['PARENT_ID'])
6630 {
6631 continue;
6632 }
6633
6634 $exDatesToRemove[] = new Date(
6635 Util::getDateObject(
6636 $recEvent['ORIGINAL_DATE_FROM'] ?? $recEvent['DATE_FROM'],
6637 ($recEvent['DT_SKIP_TIME'] ?? null) === 'Y',
6638 $recEvent['TZ_FROM'],
6639 )
6640 );
6641 }
6642
6644 foreach ($exDatesToRemove as $exDate)
6645 {
6646 $exDatesCollection->removeDateFromCollection($exDate);
6647 }
6648 }
6649
6650 public static function changeCalendarSync(Core\Event\Event $event, array $currentEvent, array $params)
6651 {
6652 $result = null;
6654 $mapperFactory = ServiceLocator::getInstance()->get('calendar.service.mappers.factory');
6655 $pushManager = new Sync\Managers\PushManager();
6657 $oldSection = $mapperFactory->getSection()->getById($currentEvent['SECTION_ID']);
6658
6659 $oldFactories = FactoriesCollection::createBySection($oldSection);
6660 if ($oldFactories->count() > 0)
6661 {
6662 $syncManager = new Synchronization($oldFactories);
6663 $context = new Context();
6664 foreach ($oldFactories as $factory)
6665 {
6666 $pushManager->unLockConnection($factory->getConnection());
6667 $pushManager->lockConnection($factory->getConnection());
6668 }
6669
6670 $eventId = (int)$currentEvent['ID'];
6671 if ($currentEvent['RECURRENCE_ID'] && $currentEvent['ORIGINAL_DATE_FROM'])
6672 {
6673 $masterEvent = \Bitrix\Calendar\Internals\EventTable::query()
6674 ->setSelect(['ID'])
6675 ->where('DELETED', 'N')
6676 ->where('PARENT_ID', $currentEvent['RECURRENCE_ID'])
6677 ->where('OWNER_ID', $currentEvent['OWNER_ID'])
6678 ->exec()->fetch()
6679 ;
6680
6681 if (!empty($masterEvent['ID']))
6682 {
6683 $eventId = (int)$masterEvent['ID'];
6684 }
6685 }
6686
6688 $eventToDelete = $mapperFactory->getEvent()->getById($eventId);
6689
6690 if ($eventToDelete)
6691 {
6692 $clonedEvent = (new Core\Builders\EventCloner($event))->build();
6693 $clonedEvent->setSection($oldSection);
6694 $syncManager->deleteEvent($clonedEvent, $context);
6695 }
6696 }
6697
6698 $newFactories = FactoriesCollection::createBySection($event->getSection());
6699 if ($newFactories->count() > 0)
6700 {
6701 foreach ($newFactories as $factory)
6702 {
6703 $pushManager->unLockConnection($factory->getConnection());
6704 $pushManager->lockConnection($factory->getConnection());
6705 }
6706
6707 $syncManager = new Synchronization($newFactories);
6708 $context = new Context();
6709
6710 if ($event->isInstance())
6711 {
6712 $masterEvent = $mapperFactory->getEvent()->getMap([
6713 '=PARENT_ID' => $event->getRecurrenceId(),
6714 '=OWNER_ID' => $event->getOwner()->getId(),
6715 ])->fetch();
6716
6717 $result = $syncManager->createRecurrence($masterEvent, $context);
6718 }
6719 else if ($event->getRecurringRule())
6720 {
6721 $result = $syncManager->createRecurrence($event, $context);
6722 }
6723 else
6724 {
6725 $result = $syncManager->createEvent($event, $context);
6726 }
6727 }
6728
6729 return $result;
6730 }
6731
6736 private static function getSectionsByConnectionId($connectionId)
6737 {
6738 return \CCalendarSect::GetList([
6739 'arFilter' => [
6740 'CAL_TYPE' => 'user',
6741 'OWNER_ID' => self::$ownerId,
6742 'CAL_DAV_CON' => $connectionId,
6743 ],
6744 ]);
6745 }
6746
6750 private static function markSectionLikeDelete(int $sectionId): void
6751 {
6753 'arFields' => [
6754 "ID" => $sectionId,
6755 "CAL_DAV_CON" => '',
6756 'CAL_DAV_CAL' => '',
6757 'CAL_DAV_MOD' => '',
6758 ],
6759 ]);
6760 }
6761
6762 private static function mergeExcludedDates($currentExDates, $newExDates)
6763 {
6764 if (is_string($currentExDates))
6765 {
6766 $currentExDates = explode(';', $currentExDates);
6767 }
6768 if (is_string($newExDates))
6769 {
6770 $newExDates = explode(';', $newExDates);
6771 }
6772
6773 return implode(';', array_unique(array_merge($currentExDates, $newExDates)));
6774 }
6775
6781 private static function checkRecurrenceLocationChanges(array $params, array $curEvent): array
6782 {
6783 $isRecurrentLocationChanged = false;
6784 if (!empty($params['arFields']['LOCATION']) && is_string($params['arFields']['LOCATION']))
6785 {
6786 $parsedLocation = Rooms\Util::parseLocation($params['arFields']['LOCATION']);
6787 if (!empty($parsedLocation['room_event_id']))
6788 {
6789 $params['arFields']['LOCATION'] = 'calendar_' . $parsedLocation['room_id'];
6790 }
6791
6792 $isRecurrentLocationChanged = true;
6793
6794 if (!empty($curEvent['LOCATION']) && is_string($curEvent['LOCATION']))
6795 {
6796 $currentLocation = $curEvent['LOCATION'];
6797 $parsedCurrentLocation = Rooms\Util::parseLocation($curEvent['LOCATION']);
6798 if (!empty($parsedCurrentLocation['room_event_id']))
6799 {
6800 $currentLocation = 'calendar_' . $parsedCurrentLocation['room_id'];
6801 }
6802
6803 $isRecurrentLocationChanged = $params['arFields']['LOCATION'] !== $currentLocation;
6804 }
6805 }
6806
6807 return [$isRecurrentLocationChanged, $params];
6808 }
6809
6815 private static function checkRecurrenceAttendeesChanges(array $params, array $curEvent): bool
6816 {
6817 $isRecurrentAttendeesChanged = false;
6818
6819 if (
6820 !empty($params['arFields']['ATTENDEES_CODES'])
6821 && is_array($params['arFields']['ATTENDEES_CODES'])
6822 && !empty($curEvent['ATTENDEES_CODES'])
6823 && is_array($curEvent['ATTENDEES_CODES'])
6824 )
6825 {
6826 $firstDiff = array_diff($params['arFields']['ATTENDEES_CODES'], $curEvent['ATTENDEES_CODES']);
6827 $secondDiff = array_diff($curEvent['ATTENDEES_CODES'], $params['arFields']['ATTENDEES_CODES']);
6828
6829 $isRecurrentAttendeesChanged = !empty($firstDiff) || !empty($secondDiff);
6830 }
6831
6832 return $isRecurrentAttendeesChanged;
6833 }
6834
6835 private static function checkCollabSectionAccess(array $collabSections): bool
6836 {
6837 $noEditAccessedCalendars = true;
6838
6839 foreach ($collabSections as $section)
6840 {
6841 if ($noEditAccessedCalendars && $section['PERM']['edit'])
6842 {
6843 $noEditAccessedCalendars = false;
6844
6845 break;
6846 }
6847 }
6848
6849 return $noEditAccessedCalendars;
6850 }
6851
6852 public static function getCurrentEventForSaving(int $eventId, $userId, $checkPermission)
6853 {
6854 $result = CCalendarEvent::GetList(
6855 [
6856 'arFilter' => [
6857 'ID' => $eventId,
6858 'DELETED' => 'N',
6859 ],
6860 'parseRecursion' => false,
6861 'fetchAttendees' => true,
6862 'fetchMeetings' => false,
6863 'userId' => $userId,
6864 'checkPermissions' => $checkPermission,
6865 'loadOriginalRecursion' => true,
6866 ]
6867 );
6868
6869 if ($result)
6870 {
6871 return $result[0];
6872 }
6873
6874 return $result;
6875 }
6876
6880 public static function hasTypeAccess(): bool
6881 {
6882 $typeModel = TypeModel::createFromXmlId(self::$type);
6883
6884 return (new TypeAccessController(self::$userId))
6885 ->check(ActionDictionary::ACTION_TYPE_ACCESS, $typeModel, [])
6886 ;
6887 }
6888
6889 public static function isReadOnly(array $sections, array $collabSectionList = []): bool
6890 {
6891 $permission = self::GetPermissions([
6892 'type' => self::$type,
6893 'ownerId' => self::$ownerId,
6894 'userId' => self::$userId,
6895 ]);
6896
6897 $readOnly = !$permission['edit'] && !$permission['section_edit'];
6898
6899 if (self::$type === Dictionary::CALENDAR_TYPE['user'] && self::$userId !== self::$ownerId)
6900 {
6901 $readOnly = true;
6902 }
6903
6904 if (self::$bAnonym)
6905 {
6906 $readOnly = true;
6907 }
6908
6909 $groupOrUser = self::$type === Dictionary::CALENDAR_TYPE['user']
6910 || self::$type === Dictionary::CALENDAR_TYPE['group']
6911 ;
6912 $noEditAccessedCalendars = $groupOrUser;
6913
6914 if (self::hasToCreateDefaultCalendar($sections))
6915 {
6916 $sections[] = \CCalendarSect::CreateDefault([
6917 'type' => self::$type,
6918 'ownerId' => self::$ownerId,
6919 ]);
6920 }
6921
6922 foreach ($sections as $section)
6923 {
6924 if (
6925 $groupOrUser
6926 && $section['CAL_TYPE'] === self::$type
6927 && (int)$section['OWNER_ID'] === (int)self::$ownerId
6928 )
6929 {
6930 if ($noEditAccessedCalendars && $section['PERM']['edit'])
6931 {
6932 $noEditAccessedCalendars = false;
6933 }
6934
6935 if ($readOnly && ($section['PERM']['edit'] || $section['PERM']['edit_section']))
6936 {
6937 $readOnly = false;
6938 }
6939 }
6940 }
6941
6942 if (!empty($collabSectionList))
6943 {
6944 $noEditAccessedCalendars = self::checkCollabSectionAccess($collabSectionList);
6945 }
6946
6947 if ($groupOrUser && $noEditAccessedCalendars)
6948 {
6949 $readOnly = true;
6950 }
6951
6952 return $readOnly;
6953 }
6954
6960 public static function getSectionsInfo($isCollabUser): array
6961 {
6962 $followedSectionList = UserSettings::getFollowedSectionIdList(self::$userId);
6963 $roomsList = Rooms\Manager::getRoomsList();
6964 $collabSectionList = [];
6965
6966 if (self::$type === Dictionary::CALENDAR_TYPE['location'])
6967 {
6968 $sectionList = $roomsList ?? [];
6969 }
6970 else
6971 {
6972 $owners = [self::$ownerId];
6973 $types = [self::$type];
6974 if (self::$type === 'user' && OpenEvents\Feature::getInstance()->isAvailable())
6975 {
6976 $types[] = Core\Event\Tools\Dictionary::CALENDAR_TYPE['open_event'];
6977 $owners[] = 0;
6978 }
6979
6980 $sectionList = self::getSectionList([
6981 'CAL_TYPE' => $types,
6982 'OWNER_ID' => $owners,
6983 'ACTIVE' => 'Y',
6984 'ADDITIONAL_IDS' => $followedSectionList,
6985 'checkPermissions' => true,
6986 'getPermissions' => true,
6987 'getImages' => true,
6988 ]);
6989
6990 if (self::$type === Dictionary::CALENDAR_TYPE['user'] && $isCollabUser)
6991 {
6992 $userCollabIds = UserCollabs::getInstance()->getIds(self::$userId);
6993
6994 $collabSectionList = self::getSectionList([
6995 'CAL_TYPE' => Dictionary::CALENDAR_TYPE['group'],
6996 'OWNER_ID' => $userCollabIds,
6997 'ACTIVE' => 'Y',
6998 'checkPermissions' => true,
6999 'getPermissions' => true,
7000 ]);
7001 }
7002 }
7003
7004 $sectionList = array_merge(
7005 $sectionList,
7006 self::getSectionListAvailableForUser(self::$userId),
7007 $collabSectionList
7008 );
7009
7010 return [$sectionList, $collabSectionList, $followedSectionList, $roomsList];
7011 }
7012
7013 public static function hasToCreateDefaultCalendar(array $sections): bool
7014 {
7015 $createDefault = !self::$bAnonym;
7016
7017 if (self::$type === Dictionary::CALENDAR_TYPE['user'])
7018 {
7019 $createDefault = self::$userId === (int)self::$ownerId;
7020 }
7021
7022 foreach ($sections as $section)
7023 {
7024 if (
7025 $createDefault
7026 && $section['CAL_TYPE'] === self::$type
7027 && (int)$section['OWNER_ID'] === (int)self::$ownerId
7028 )
7029 {
7030 return false;
7031 }
7032 }
7033
7034 return $createDefault;
7035 }
7036}
$connection
Определения actionsdefinitions.php:38
$con
Определения admin_tab.php:7
$count
Определения admin_tab.php:4
$arDays
Определения options.php:191
$arPathes
Определения options.php:293
while($arIBlock=$dbIBlock->Fetch()) $SET
Определения options.php:184
global $APPLICATION
Определения include.php:80
$accessController
Определения options.php:23
$arResult
Определения generate_coupon.php:16
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
static addEvent(string $type, array $data)
Определения counterservice.php:40
static getInstance()
Определения Feature.php:16
static getMeetingRoomList(array $params=[])
Определения iblockmeetingroom.php:20
static getRoomsList()
Определения manager.php:161
static payAttentionToNewSharingFeature()
Определения helper.php:46
static onSharingEventEdit(array $fields)
Определения sharingeventmanager.php:328
const CALDAV_TYPE
Определения helper.php:7
static getFilterId($type, $ownerId, $userId)
Определения calendarfilter.php:53
static getFormSettings($formType, $userId=false, ?string $entryType=null)
Определения usersettings.php:145
static getFollowedSectionIdList($userId=false)
Определения usersettings.php:330
static getSectionCustomization($userId=false)
Определения usersettings.php:288
static set($settings=[], $userId=false)
Определения usersettings.php:50
static get($userId=null)
Определения usersettings.php:86
static getHiddenSections($userId=false, $options=[])
Определения usersettings.php:254
static getTrackingUsers($userId=false, $params=[])
Определения usersettings.php:180
Определения util.php:21
static getUserSelectorContext()
Определения util.php:124
static addPullEvent(PushCommand $command, int $userId, array $params=[])
Определения util.php:385
static prepareTimezone(?string $tz=null)
Определения util.php:80
static checkRuZone()
Определения util.php:129
static getPathToCalendar(?int $ownerId, ?string $type)
Определения util.php:591
static getUserAccessCodes(int $userId)
Определения util.php:546
static getDateObject(string $date=null, ?bool $fullDay=true, ?string $tz='UTC')
Определения util.php:107
static getRequestUid()
Определения util.php:537
static isCollabUser(int $userId)
Определения util.php:337
static getServerName()
Определения util.php:683
static getValue($name)
Определения configuration.php:24
static get($moduleId, $name, $default="", $siteId=false)
Определения option.php:30
static set($moduleId, $name, $value="", $siteId="")
Определения option.php:261
static getInstance()
Определения eventmanager.php:31
Определения loader.php:13
static includeModule($moduleName)
Определения loader.php:67
static loadLanguageFile($file, $language=null, $normalize=true)
Определения loc.php:225
Определения date.php:9
static convertFormatToPhp($format)
Определения date.php:309
Определения uri.php:17
static getHandlersList($placement, $skipInstallCheck=false, int $userId=null)
Определения placement.php:266
static canTurnOnTrial(string $featureName)
Определения feature.php:70
static isFeatureEnabled(string $featureName, int $groupId=0)
Определения feature.php:21
const PROJECTS_GROUPS
Определения feature.php:14
Определения access.php:15
static GetListEx($arOrder=Array("SORT"=>"ASC"), $arFilter=Array(), $bCount=false, $iNum=0, $arAddParams=array())
Определения forum_new.php:936
static GetList($arOrder=['MODULE_ID'=> 'asc', 'LETTER'=> 'asc'], $arFilter=[])
Определения task.php:185
static IsFeatureEnabled($_1488512778)
Определения include.php:116
Определения punycode.php:4
static GetEventsByRecId($recurrenceId, $checkPermissions=true, $userId=null)
Определения calendar_event.php:5519
Определения calendar.php:50
static GetNearestEventsList($params=[])
Определения calendar.php:1623
static isMacConnected()
Определения calendar.php:5373
static SetOffset($userId=false, $value=0)
Определения calendar.php:1172
static CollectCalDAVErros($arErrors=[])
Определения calendar.php:1184
static getSectionsInfo($isCollabUser)
Определения calendar.php:6960
static FormatTime($h=0, $m=0)
Определения calendar.php:3684
static IsExchangeEnabled($userId=false)
Определения calendar.php:5301
const CALENDAR_MAX_TIMESTAMP
Определения calendar.php:53
checkPermissions()
Определения calendar.php:908
static Color($color='', $defaultColor=true)
Определения calendar.php:3645
static GetOffset($userId=false)
Определения calendar.php:5408
static GetPathes($forSite=null)
Определения calendar.php:4818
static SetUserDepartment($userId=0, $dep=[])
Определения calendar.php:1278
const EDIT_PREFIX
Определения calendar.php:59
static UnParseTextLocation($loc='')
Определения calendar.php:1857
static GetAccessibilityForUsers($params)
Определения calendar.php:1568
static DeleteCalendarEvent($eventId, $userId, $oEvent=false)
Определения calendar.php:3635
static IsPersonal($type=false, $ownerId=false, $userId=false)
Определения calendar.php:5283
static UpdateCounter($users=false, array $eventIds=[], array $groupIds=[])
Определения calendar.php:4631
static GetMoreAttendeesMessage($cnt=0)
Определения calendar.php:4170
static SaveSyncDate($userId, $syncType)
Определения calendar.php:4549
static OnExchangeCalendarSync(\Bitrix\Main\Event $event)
Определения calendar.php:4618
static getEntryUrl(string $calType, int $ownerId, int $entryId, string $dateFrom)
Определения calendar.php:5014
static GetEventList($params, &$arAttendees)
Определения calendar.php:6066
static GetSectionList($params=[])
Определения calendar.php:5878
static GetTypeByExternalId($externalId=false)
Определения calendar.php:4047
static SetUserSettings($settings=[], $userId=false)
Определения calendar.php:4910
static isIphoneConnected()
Определения calendar.php:5366
static ThrowError($str)
Определения calendar.php:3286
static IsSocNet()
Определения calendar.php:4681
static isDaylightSavingTimezone(string $timezoneId)
Определения calendar.php:1528
static SaveEventEx($params=[])
Определения calendar.php:1943
checkViewPermissions()
Определения calendar.php:877
static GetFromToHtml( $fromTs=false, $toTs=false, $skipTime=false, $dtLength=0, $forRrule=false, $languageId=null)
Определения calendar.php:4187
static SaveUserTimezoneName($user, $tzName='')
Определения calendar.php:4447
static OnSocNetGroupDelete($groupId)
Определения calendar.php:4457
static CacheTime($time=false)
Определения calendar.php:4065
const CALENDAR_CHAT_ENTITY_TYPE
Определения calendar.php:56
static GetAccessTasksByName($binging='calendar_section', $name='calendar_denied')
Определения calendar.php:3815
static CollectExchangeErrors($arErrors=[])
Определения calendar.php:1017
static GetOuterUrl()
Определения calendar.php:3916
static GetDayLen()
Определения calendar.php:1852
static IsIntranetEnabled()
Определения calendar.php:4672
static GetUserList(array $userIdList)
Определения calendar.php:5510
static PushAccessNames($arCodes=[])
Определения calendar.php:3905
static createDateTimeObjectFromString(string $date, ?string $timezone=null)
Определения calendar.php:5237
static GetDestinationUsers($codes, $fetchUsers=false)
Определения calendar.php:4123
GetId()
Определения calendar.php:6356
static CutZeroTime($date)
Определения calendar.php:6303
static fetchIconsForSectionList($sectionList)
Определения calendar.php:5952
static TimestampUTC(string $date)
Определения calendar.php:5230
static GetTextLocation($loc='')
Определения calendar.php:1801
static DateWithNewTime($timestampTime, $timestampDate)
Определения calendar.php:5733
static GetOwnerName($type='', $ownerId='')
Определения calendar.php:1468
static hasTypeAccess()
Определения calendar.php:6880
static GetServerName()
Определения calendar.php:5059
static GetUserPermissionsForCalendar($calendarId, $userId)
Определения calendar.php:1813
const TASK_SECTION_ID
Определения calendar.php:55
checkGroupPermissions()
Определения calendar.php:943
static GetStartUpEvent($eventId=false, $isSharing=false)
Определения calendar.php:5085
static CachePath()
Определения calendar.php:3512
getEditEventId(string $id)
Определения calendar.php:980
static OnDavCalendarSync(\Bitrix\Main\Event $event)
Определения calendar.php:4507
static TSFormat($format="FULL")
Определения calendar.php:5273
static UpdateUFRights($files, $rights, $ufEntity=[])
Определения calendar.php:3321
static GetPathForCalendarEx($userId=0)
Определения calendar.php:1200
static getTaskList(TaskQueryParameter $parameter)
Определения calendar.php:6126
static IsExtranetEnabled()
Определения calendar.php:5389
static GetUserNameTemplate($fromSite=true)
Определения calendar.php:4900
static GetWeekStart()
Определения calendar.php:5695
static GetUser($userId, $bPhoto=false)
Определения calendar.php:5502
static GetMaxTimestamp()
Определения calendar.php:1453
static GetUserAvatarSrc($user=[], $params=[])
Определения calendar.php:3751
static SetMaxPlannerUsers(int $maxPlannerUsers)
Определения calendar.php:1458
static GetTimezoneList()
Определения calendar.php:5614
static SyncCalendarItems($connectionType, $calendarId, $arCalendarItems)
Определения calendar.php:3519
static GetAbsentEvents($params)
Определения calendar.php:1557
static GetCurUserMeetingSection($bCreate=false)
Определения calendar.php:5738
static DeleteSection($id)
Определения calendar.php:998
static SetCurUserMeetingSection($userMeetingSection)
Определения calendar.php:4060
static GetType()
Определения calendar.php:6320
static IsCalDAVEnabled()
Определения calendar.php:5356
static ClearExchangeHtml($html="")
Определения calendar.php:1862
static IndByWeekDay(string $weekday)
Определения calendar.php:1916
static ParseLocation($location='')
Определения calendar.php:1806
static GetCurUserId($refresh=false)
Определения calendar.php:4698
static GetSettings($params=[])
Определения calendar.php:4717
doOpenEventInEditMode(string $id)
Определения calendar.php:975
static IsBitrix24()
Определения calendar.php:1443
static IsAdmin()
Определения calendar.php:5688
static GetMinDate()
Определения calendar.php:4112
static HandleImCallback($module, $tag, $value, $arNotify)
Определения calendar.php:1287
static AddConnection($connection, $type='caldav')
Определения calendar.php:3921
static GetErrors()
Определения calendar.php:3299
static GetMaxPlannerUsers()
Определения calendar.php:1463
static SaveMultipleSyncDate($userId, $syncType, $sectionId)
Определения calendar.php:4582
static RoundTimestamp($ts)
Определения calendar.php:5278
static ReminderAgent($eventId=0, $userId=0, $viewPath='', $calendarType='', $ownerId=0, $index=0)
Определения calendar.php:1448
const INTEGRATION_GOOGLE_API
Определения calendar.php:52
static GetSiteId()
Определения calendar.php:5040
static CheckCalDavUrl($url, $username, $password)
Определения calendar.php:3963
static getUserLanguageId(?int $userId)
Определения calendar.php:3208
static GetPathesList()
Определения calendar.php:4883
static _ParseHack(&$text, &$TextParser)
Определения calendar.php:4074
static GetCrmSection($userId, $autoCreate=false)
Определения calendar.php:5788
static GetAttendeesMessage($cnt=0)
Определения calendar.php:4153
static SaveEvent($params=[])
Определения calendar.php:1923
static GetUserTimezoneName($user, $getDefault=true)
Определения calendar.php:5435
static getSectionListAvailableForUser($userId, $additionalSectionIdList=[], $params=[])
Определения calendar.php:6399
Show($params=[])
Определения calendar.php:248
static IsExtranetUser($userId=0)
Определения calendar.php:1252
static GetGoodTimezoneForOffset($offset)
Определения calendar.php:5573
static GetPermissions($Params=[])
Определения calendar.php:4920
static isReadOnly(array $sections, array $collabSectionList=[])
Определения calendar.php:6889
static GetAccessNames()
Определения calendar.php:6325
static Date($timestamp, $bTime=true, $bRound=true, $bCutSeconds=false)
Определения calendar.php:5712
static GetMeetingSection($userId, $autoCreate=false)
Определения calendar.php:5748
static GetSocNetDestination($user_id=false, $selected=[], $userList=[])
Определения calendar.php:4303
static GetPath($type='', $ownerId='', $hard=false)
Определения calendar.php:5035
static SetSettings($settings=[], $clearOptions=false)
Определения calendar.php:1308
static GetMaxDate()
Определения calendar.php:4101
static Timestamp($date, $bRound=true, $bTime=true)
Определения calendar.php:5219
const DAY_LENGTH
Определения calendar.php:57
static GetServerPath()
Определения calendar.php:5049
static CountPastEvents($params)
Определения calendar.php:3232
Init($params)
Определения calendar.php:126
static GetUserName($user)
Определения calendar.php:5674
static GetUserUrl($userId=0, $pathToUser="")
Определения calendar.php:3797
static IsSocnetAdmin()
Определения calendar.php:4088
static GetCurrentOffsetUTC($userId=false)
Определения calendar.php:5398
static DFormat($bTime=true)
Определения calendar.php:5728
static GetAccessTasks($binging='calendar_section', $type='')
Определения calendar.php:3830
static GetUserId()
Определения calendar.php:3740
static SetDisplayedSuperposed($userId=false, $idList=[])
Определения calendar.php:985
static SetSilentErrorMode($silentErrorMode=true)
Определения calendar.php:6351
static ClearCache($arPath=[])
Определения calendar.php:3478
static isOffice365ApiEnabled()
Определения calendar.php:6415
static TempUser($TmpUser=false, $create=true, $ID=false)
Определения calendar.php:3421
static SaveSection($params)
Определения calendar.php:3443
static GetTimezoneOffset($timezoneId, $dateTimestamp=false)
Определения calendar.php:1508
static setOwnerId($userId)
Определения calendar.php:6410
const DEFAULT_TASK_COLOR
Определения calendar.php:54
static DeleteEvent($id, $doExternalSync=true, $params=[])
Определения calendar.php:1043
static ParseHTMLToBB($html="")
Определения calendar.php:1871
static GetUserDepartment($userId=0)
Определения calendar.php:1264
static getCurrentEventForSaving(int $eventId, $userId, $checkPermission)
Определения calendar.php:6852
static GetOwnerId()
Определения calendar.php:6055
static GetUserAvatar($user=[], $params=[])
Определения calendar.php:3767
static isGoogleApiEnabled()
Определения calendar.php:5330
static IsWebserviceEnabled()
Определения calendar.php:5380
static GetUserSettings($userId=false)
Определения calendar.php:4915
static hasToCreateDefaultCalendar(array $sections)
Определения calendar.php:7013
static WeekDayByInd($i, $binv=true)
Определения calendar.php:1903
static ReminderAgent($eventId=0, $userId=0, $viewPath='', $calendarType='', $ownerId=0, $index=0)
Определения calendar_reminder.php:13
const PLACEMENT_GRID_VIEW
Определения calendar_restservice.php:29
static InitJS($config=array(), $data=array(), $additionalParams=array())
Определения calendar_sceleton.php:8
static Edit($params)
Определения calendar_sect.php:994
static CreateDefault($params=[])
Определения calendar_sect.php:1279
static DoDeleteToDav($params, $event)
Определения calendar_sync.php:530
static GetSyncInfo($params=[])
Определения calendar_sync.php:849
static getSyncLinks()
Определения calendar_sync.php:1170
static GetSyncInfoItem($userId, $syncType)
Определения calendar_sync.php:941
static DoSaveToDav(&$arFields, $params=[], $event=false)
Определения calendar_sync.php:322
static urlAddParams($url, $add_params, $options=[])
Определения http.php:521
Определения iblockelement.php:9
Определения textparser.php:21
static Enable()
Определения time.php:36
global $CACHE_MANAGER
Определения clear_component_cache.php:7
$right
Определения options.php:8
$options
Определения commerceml2.php:49
$str
Определения commerceml2.php:63
$hours
Определения cron_html_pages.php:15
$arFields
Определения dblapprove.php:5
$arPath
Определения file_edit.php:72
else $bEdit
Определения file_edit_src.php:72
</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
$result
Определения get_property_values.php:14
$query
Определения get_search.php:11
if($ajaxMode) $ID
Определения get_user.php:27
$host
Определения .description.php:9
$iblockId
Определения iblock_catalog_edit.php:30
$filter
Определения iblock_catalog_list.php:54
$selectFields
Определения iblock_catalog_list.php:160
$_SERVER["DOCUMENT_ROOT"]
Определения cron_frame.php:9
global $DB
Определения cron_frame.php:29
global $USER
Определения csv_new_run.php:40
$context
Определения csv_new_setup.php:223
if(!is_null($config))($config as $configItem)(! $configItem->isVisible()) $code
Определения options.php:195
const BX_RESIZE_IMAGE_EXACT
Определения constants.php:12
if(file_exists($_SERVER['DOCUMENT_ROOT'] . "/urlrewrite.php")) $uri
Определения urlrewrite.php:61
const FORMAT_DATETIME
Определения include.php:64
$culture
Определения include.php:61
$z
Определения options.php:31
$arTasks
Определения options.php:777
if(!is_array($deviceNotifyCodes)) $access
Определения options.php:174
$arCodes
Определения options.php:154
if($NS['step']==6) if( $NS[ 'step']==7) if(COption::GetOptionInt('main', 'disk_space', 0) > 0) $info
Определения backup.php:924
CheckSerializedData($str, $max_depth=200)
Определения tools.php:4949
IsAmPmMode($returnConst=false)
Определения tools.php:803
ExecuteModuleEventEx($arEvent, $arParams=[])
Определения tools.php:5214
FormatDate($format="", $timestamp=false, $now=false, ?string $languageId=null)
Определения tools.php:871
htmlspecialcharsback($str)
Определения tools.php:2693
IsModuleInstalled($module_id)
Определения tools.php:5301
ParseDateTime($datetime, $format=false)
Определения tools.php:638
AddEventHandler($FROM_MODULE_ID, $MESSAGE_ID, $CALLBACK, $SORT=100, $FULL_PATH=false)
Определения tools.php:5165
AddEventToStatFile($module, $action, $tag, $label, $action_type='', $user_id=null)
Определения tools.php:3976
htmlspecialcharsbx($string, $flags=ENT_COMPAT, $doubleEncode=true)
Определения tools.php:2701
GetModuleEvents($MODULE_ID, $MESSAGE_ID, $bReturnArray=false)
Определения tools.php:5177
IncludeModuleLangFile($filepath, $lang=false, $bReturnArray=false)
Определения tools.php:3778
MakeTimeStamp($datetime, $format=false)
Определения tools.php:538
bitrix_sessid_get($varname='sessid')
Определения tools.php:4695
RemoveEventHandler($FROM_MODULE_ID, $MESSAGE_ID, $iEventHandlerKey)
Определения tools.php:5171
$name
Определения menu_edit.php:35
Определения culture.php:9
Определения chain.php:3
Определения collection.php:2
$user
Определения mysql_to_pgsql.php:33
$password
Определения mysql_to_pgsql.php:34
$host
Определения mysql_to_pgsql.php:32
$files
Определения mysql_to_pgsql.php:30
$GLOBALS['____1690880296']
Определения license.php:1
addError(string $class, string $message='')
Определения AccessErrorTrait.php:18
trait Error
Определения error.php:11
$entityId
Определения payment.php:4
$time
Определения payment.php:61
$arFiles
Определения options.php:60
$event
Определения prolog_after.php:141
return false
Определения prolog_main_admin.php:185
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
Определения prolog_main_admin.php:393
$ar
Определения options.php:199
if(empty($signedUserToken)) $key
Определения quickway.php:257
$text
Определения template_pdf.php:79
$i
Определения factura.php:643
$page
Определения order_form.php:33
</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
$val
Определения options.php:1793
$location
Определения options.php:2729
if(!Loader::includeModule('sale')) $pattern
Определения index.php:20
const SONET_ENTITY_GROUP
Определения include.php:117
const SONET_ENTITY_USER
Определения include.php:118
$arRes
Определения options.php:104
const SITE_ID
Определения sonet_set_content_view.php:12
$error
Определения subscription_card_product.php:20
$k
Определения template_pdf.php:567
$action
Определения file_dialog.php:21
$arFilter
Определения user_search.php:106
$rights
Определения options.php:4
$url
Определения iframe.php:7
$site
Определения yandex_run.php:614