Server IP : 80.87.202.40 / Your IP : 216.73.216.169 Web Server : Apache System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64 User : bitrix ( 600) PHP Version : 8.2.27 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/calendar/classes/general/ |
Upload File : |
<?php /** var CMain $APPLICATION */ use Bitrix\Calendar\Access\ActionDictionary; use Bitrix\Calendar\Access\EventAccessController; use Bitrix\Calendar\Access\Model\EventModel; use Bitrix\Calendar\Access\Model\SectionModel; use Bitrix\Calendar\Access\Model\TypeModel; use Bitrix\Calendar\Access\SectionAccessController; use Bitrix\Calendar\Access\TypeAccessController; use Bitrix\Calendar\Core\Base\Date; use Bitrix\Calendar\Core; use Bitrix\Calendar\Core\Event\Tools\Dictionary; use Bitrix\Calendar\Integration\Bitrix24\FeatureDictionary; use Bitrix\Calendar\Integration\Pull\PushCommand; use Bitrix\Calendar\Integration\SocialNetwork\Collab\CollabFeature; use Bitrix\Calendar\Integration\SocialNetwork\Collab\Collabs; use Bitrix\Calendar\Integration\SocialNetwork\Collab\UserCollabs; use Bitrix\Calendar\Integration\Tasks\TaskQueryParameter; use Bitrix\Calendar\Internals\EventTable; use Bitrix\Calendar\Sharing\SharingEventManager; use Bitrix\Calendar\Sync; use Bitrix\Calendar\Sync\Factories\FactoriesCollection; use Bitrix\Calendar\Sync\Google; use Bitrix\Calendar\Core\Event\Tools\UidGenerator; use Bitrix\Calendar\OpenEvents; use Bitrix\Calendar\Sync\Managers\Synchronization; use Bitrix\Calendar\Sync\Util\Context; use Bitrix\Calendar\Ui\CountersManager; use Bitrix\Calendar\UserSettings; use Bitrix\Calendar\Util; use Bitrix\Main; use Bitrix\Main\ArgumentException; use Bitrix\Main\DI\ServiceLocator; use Bitrix\Main\Loader; use Bitrix\Main\Localization\Loc; use Bitrix\Main\ORM\Query\Query; use Bitrix\Main\Type; use Bitrix\Calendar\Integration\Bitrix24Manager; use Bitrix\Calendar\Rooms; use Bitrix\Calendar\Sharing; use Bitrix\Tasks\Internals\Task\Status; use Bitrix\Tasks\Provider\TaskList; use Bitrix\Tasks\Provider\TaskQuery; IncludeModuleLangFile(__FILE__); class CCalendar { const INTEGRATION_GOOGLE_API = "googleapi", CALENDAR_MAX_TIMESTAMP = 2145938400, DEFAULT_TASK_COLOR = '#ff5b55', TASK_SECTION_ID = '1_tasks', CALENDAR_CHAT_ENTITY_TYPE = 'CALENDAR', DAY_LENGTH = 86400; // 60 * 60 * 24 public const EDIT_PREFIX = 'EDIT'; private static $id = false, $instance, $CALENDAR_MAX_DATE, $CALENDAR_MIN_DATE, $type, $arTypes, $ownerId = 0, $settings, $siteId, $userSettings = [], $pathToUser, $bOwner, $userId, $curUserId, $userMeetingSection, $meetingSections = [], $crmSections = [], $offset, $arTimezoneOffsets = [], $perm = [], $isArchivedGroup = false, $userNameTemplate = "#NAME# #LAST_NAME#", $bSuperpose, $bExtranet, $bIntranet, $bWebservice, $userTimezoneList = [], $showTasks, $viewTaskPath = '', $editTaskPath = '', $actionUrl, $path = '', $outerUrl, $accessNames = [], $bSocNet, $bAnonym, $allowReserveMeeting = true, $SectionsControlsDOMId = 'sidebar', $arAccessTask = [], $ownerNames = [], $cachePath = "calendar/", $cacheTime = 2592000, // 30 days by default $bCache = true, $readOnly, $pathesForSite = false, $pathes = [], // links for several sites $arUserDepartment = [], $bAMPM = false, $bWideDate = false, $arExchEnabledCache = [], $silentErrorMode = false, $weekStart, $bCurUserSocNetAdmin, $serverPath, $pathesList = array('path_to_user','path_to_user_calendar','path_to_group','path_to_group_calendar','path_to_vr','path_to_rm'), $pathesListEx = null, $isGoogleApiEnabled = null, $isOffice365ApiEnabled = null, $errors = [], $timezones = [], $userLanguageId = [], $userList = [] ; function Init($params) { global $USER, $APPLICATION; $access = new CAccess(); $access->UpdateCodes(); if (!$USER || !is_object($USER)) { $USER = new CUser; } // Owner params self::$siteId = $params['siteId'] ?? SITE_ID; self::$type = $params['type']; self::$arTypes = CCalendarType::GetList(); self::$bIntranet = self::IsIntranetEnabled(); self::$bSocNet = self::IsSocNet(); self::$userId = (isset($params['userId']) && $params['userId'] > 0) ? (int)$params['userId'] : self::GetCurUserId(true); self::$bOwner = self::$type === 'user' || self::$type === 'group'; self::$settings = self::GetSettings(); self::$userSettings = Bitrix\Calendar\UserSettings::get(); self::$pathesForSite = self::GetPathes(self::$siteId); self::$pathToUser = self::$pathesForSite['path_to_user']; self::$bSuperpose = $params['allowSuperpose'] != false && self::$bSocNet; self::$bAnonym = !$USER || !$USER->IsAuthorized(); self::$userNameTemplate = self::$settings['user_name_template']; self::$bAMPM = IsAmPmMode(); self::$bWideDate = str_contains(FORMAT_DATETIME, 'MMMM'); self::$id = $this->GetId(); if (isset($params['SectionControlsDOMId'])) { self::$SectionsControlsDOMId = $params['SectionControlsDOMId']; } if (self::$bOwner && isset($params['ownerId']) && $params['ownerId'] > 0) { self::$ownerId = (int)$params['ownerId']; } self::$showTasks = (self::$type === 'user' || self::$type === 'group') && !Util::isCollabUser(self::$userId) && ($params['showTasks'] ?? '') !== false && $params['viewTaskPath'] && Loader::includeModule('tasks') && self::$userSettings['showTasks'] !== 'N' ; if (self::$showTasks) { self::$viewTaskPath = $params['viewTaskPath']; self::$editTaskPath = $params['editTaskPath']; } self::GetPermissions(array( 'type' => self::$type, 'bOwner' => self::$bOwner, 'userId' => self::$userId, 'ownerId' => self::$ownerId, )); // Cache params if (isset($params['cachePath'])) { self::$cachePath = $params['cachePath']; } if (isset($params['cacheTime'])) { self::$cacheTime = $params['cacheTime']; } self::$bCache = self::$cacheTime > 0; // Urls $page = preg_replace( [ "/EVENT_ID=.*?\&/i", "/EVENT_DATE=.*?\&/i", "/CHOOSE_MR=.*?\&/i", "/action=.*?\&/i", "/bx_event_calendar_request=.*?\&/i", "/clear_cache=.*?\&/i", "/bitrix_include_areas=.*?\&/i", "/bitrix_show_mode=.*?\&/i", "/back_url_admin=.*?\&/i", "/IFRAME=.*?\&/i", "/IFRAME_TYPE=.*?\&/i", ], "", $params['pageUrl'].'&' ); $page = preg_replace( [ "/^(.*?)&$/i", "/^(.*?)\?$/i", ], "\$1", $page ); self::$actionUrl = $page; self::$path = empty(self::$ownerId) ? self::GetServerPath().$page : self::GetPath(self::$type, self::$ownerId, true); self::$outerUrl = $APPLICATION->GetCurPageParam('', [ "action", "bx_event_calendar_request", "clear_cache", "bitrix_include_areas", "bitrix_show_mode", "back_url_admin", "EVENT_ID", "EVENT_DATE", "CHOOSE_MR", ], false); // *** Meeting room params *** $RMiblockId = self::$settings['rm_iblock_id']; self::$allowReserveMeeting = $params["allowResMeeting"] && $RMiblockId > 0; if(self::$allowReserveMeeting && !$USER->IsAdmin() && (CIBlock::GetPermission($RMiblockId) < "R")) { self::$allowReserveMeeting = false; } } public function Show($params = []) { global $APPLICATION; $arType = false; foreach(self::$arTypes as $type) { if(self::$type === $type['XML_ID']) { $arType = $type; } } if (!$arType) { $APPLICATION->ThrowException('[EC_WRONG_TYPE] '.Loc::getMessage('EC_WRONG_TYPE'), 'calendar_wrong_type'); return false; } $init_month = false; $init_year = false; $startupEvent = false; //Show new event dialog if (isset($_GET['EVENT_ID'])) { if($this->doOpenEventInEditMode($_GET['EVENT_ID'])) { $eventId = $this->getEditEventId($_GET['EVENT_ID']); $startupEvent = self::GetStartUpEvent($eventId); if ($startupEvent) { $startupEvent['EDIT'] = true; if ($startupEvent['DT_FROM'] ?? false) { $ts = self::Timestamp($startupEvent['DT_FROM']); $init_month = date('m', $ts); $init_year = date('Y', $ts); } } } // Show popup event at start else { $eventId = (int)$_GET['EVENT_ID']; $isSharing = (bool)($_GET['IS_SHARING'] ?? null) === true; $startupEvent = self::GetStartUpEvent($eventId, $isSharing); if ($startupEvent) { $eventFromTs = self::Timestamp($startupEvent['DATE_FROM']); $currentDateTs = self::Timestamp($_GET['EVENT_DATE'] ?? null); if ($currentDateTs > $eventFromTs) { $startupEvent['~CURRENT_DATE'] = self::Date($currentDateTs, false); $init_month = date('m', $currentDateTs); $init_year = date('Y', $currentDateTs); } else { $init_month = date('m', $eventFromTs); $init_year = date('Y', $eventFromTs); } } } } if (!$init_month && !$init_year && ($params["initDate"] ?? false) <> '' && mb_strpos($params["initDate"], '.') !== false) { $ts = self::Timestamp($params["initDate"]); $init_month = date('m', $ts); $init_year = date('Y', $ts); } if (!$init_month) { $init_month = date("m"); } if (!$init_year) { $init_year = date("Y"); } $id = $this->GetId(); $weekHolidays = []; if (isset(self::$settings['week_holidays'])) { $days = array('MO' => 0, 'TU' => 1, 'WE' => 2,'TH' => 3,'FR' => 4,'SA' => 5,'SU' => 6); foreach(self::$settings['week_holidays'] as $day) { $weekHolidays[] = $days[$day]; } } else { $weekHolidays = array(5, 6); } $yearHolidays = []; if (isset(self::$settings['year_holidays'])) { foreach(explode(',', self::$settings['year_holidays']) as $date) { $date = trim($date); $ardate = explode('.', $date); if (count($ardate) == 2 && $ardate[0] && $ardate[1]) { $yearHolidays[] = (int)$ardate[0] . '.' . ((int)$ardate[1] - 1); } } } $yearWorkdays = []; if (isset(self::$settings['year_workdays'])) { foreach(explode(',', self::$settings['year_workdays']) as $date) { $date = trim($date); $ardate = explode('.', $date); if (count($ardate) === 2 && $ardate[0] && $ardate[1]) { $yearWorkdays[] = (int)$ardate[0] . '.' . ((int)$ardate[1] - 1); } } } $isPersonalCalendarContext = self::IsPersonal(self::$type, self::$ownerId, self::$userId); $bExchange = self::IsExchangeEnabled() && self::$type === 'user'; $bExchangeConnected = $bExchange && CDavExchangeCalendar::IsExchangeEnabledForUser(self::$ownerId); $bWebservice = self::IsWebserviceEnabled(); $bExtranet = self::IsExtranetEnabled(); $isExtranetUser = Loader::includeModule('intranet') && !\Bitrix\Intranet\Util::isIntranetUser(); $isCollabUser = Util::isCollabUser(self::$userId); $userTimezoneOffsetUTC = self::GetCurrentOffsetUTC(self::$userId); $userTimezoneName = self::GetUserTimezoneName(self::$userId, false); $userTimezoneDefault = ''; // We don't have default timezone for this offset for this user // We will ask him but we should suggest some suitable for his offset if (!$userTimezoneName) { $userTimezoneDefault = self::GetGoodTimezoneForOffset($userTimezoneOffsetUTC); } $JSConfig = [ 'id' => $id, 'type' => self::$type, 'userId' => self::$userId, 'userName' => self::GetUserName(self::$userId), // deprecated 'ownerId' => self::$ownerId, 'ownerName' => self::GetOwnerName(self::$type, self::$ownerId), 'user' => [ 'id' => self::$userId, 'name' => self::GetUserName(self::$userId), 'url' => self::GetUserUrl(self::$userId), 'avatar' => self::GetUserAvatarSrc(self::$userId), 'smallAvatar' => self::GetUserAvatarSrc(self::$userId, array('AVATAR_SIZE' => 18)), ], 'perm' => $arType['PERM'], // Permissions from type 'locationAccess' => Rooms\Util::getLocationAccess(self::$userId), 'permEx' => self::$perm, 'showTasks' => self::$showTasks, 'sectionControlsDOMId' => self::$SectionsControlsDOMId, 'week_holidays' => $weekHolidays, 'year_holidays' => $yearHolidays, 'year_workdays' => $yearWorkdays, 'init_month' => $init_month, 'init_year' => $init_year, 'pathToUser' => self::$pathToUser, 'path' => self::$path, 'actionUrl' => self::$actionUrl, 'settings' => self::$settings, 'userSettings' => self::$userSettings, 'bAnonym' => self::$bAnonym, 'bIntranet' => self::$bIntranet, 'bWebservice' => $bWebservice, 'bExtranet' => $bExtranet, 'bSocNet' => self::$bSocNet, 'bExchange' => $bExchangeConnected, 'startupEvent' => $startupEvent, 'workTime' => [self::$settings['work_time_start'], self::$settings['work_time_end']], // Decrecated !! 'userWorkTime' => [self::$settings['work_time_start'], self::$settings['work_time_end']], 'meetingRooms' => Rooms\IBlockMeetingRoom::getMeetingRoomList([ 'RMiblockId' => self::$settings['rm_iblock_id'], 'pathToMR' => self::$pathesForSite['path_to_rm'], ]), 'allowResMeeting' => self::$allowReserveMeeting, 'bAMPM' => self::$bAMPM, 'WDControllerCID' => 'UFWD' . $id, 'userTimezoneOffsetUTC' => $userTimezoneOffsetUTC, 'userTimezoneName' => $userTimezoneName, 'userTimezoneDefault' => $userTimezoneDefault, 'sectionCustomization' => UserSettings::getSectionCustomization(self::$userId), 'locationFeatureEnabled' => !$isCollabUser && Bitrix24Manager::isFeatureEnabled(FeatureDictionary::CALENDAR_LOCATION), 'plannerFeatureEnabled' => Bitrix24Manager::isPlannerFeatureEnabled(), 'eventWithEmailGuestEnabled' => Bitrix24Manager::isFeatureEnabled(FeatureDictionary::CALENDAR_EVENTS_WITH_EMAIL_GUESTS), 'sharingFeatureLimitEnable' => Bitrix24Manager::isFeatureEnabled(FeatureDictionary::CALENDAR_SHARING), 'projectFeatureEnabled' => false, 'isSharingFeatureEnabled' => \Bitrix\Calendar\Sharing\SharingFeature::isEnabled(), 'payAttentionToNewSharingFeature' => \Bitrix\Calendar\Sharing\Helper::payAttentionToNewSharingFeature(), 'showAfterSyncAccent' => isset($_GET['googleAuthSuccess']) && $_GET['googleAuthSuccess'] === 'y', 'isExtranetUser' => $isExtranetUser, 'isGoogleApplicationRefused' => COption::GetOptionString('calendar', 'isGoogleApplicationRefused', 'N'), 'showGoogleApplicationRefused' => CUserOptions::getOption('calendar', 'showGoogleApplicationRefused', 'Y'), 'useAirDesign' => defined('AIR_SITE_TEMPLATE'), 'isBitrix24Template' => SITE_TEMPLATE_ID === 'bitrix24', ]; if (Loader::includeModule('socialnetwork')) { $projectLimitFeatureId = \Bitrix\Socialnetwork\Helper\Feature::PROJECTS_GROUPS; $JSConfig['projectFeatureEnabled'] = \Bitrix\Socialnetwork\Helper\Feature::isFeatureEnabled($projectLimitFeatureId) || \Bitrix\Socialnetwork\Helper\Feature::canTurnOnTrial($projectLimitFeatureId) ; } if (self::$type === 'user' && (int)self::$userId !== (int)self::$ownerId) { $JSConfig['ownerUser'] = array( 'id' => self::$ownerId, 'name' => self::GetUserName(self::$ownerId), 'url' => self::GetUserUrl(self::$ownerId), 'avatar' => self::GetUserAvatarSrc(self::$ownerId), 'smallAvatar' => self::GetUserAvatarSrc(self::$ownerId, array('AVATAR_SIZE' => 18)), ); } $culture = \Bitrix\Main\Context::getCurrent()?->getCulture(); $JSConfig['dayOfWeekMonthFormat'] = $culture?->getDayOfWeekMonthFormat(); $JSConfig['dayMonthFormat'] = $culture?->getDayMonthFormat(); $JSConfig['longDateFormat'] = $culture?->getLongDateFormat(); $placementParams = false; if (Loader::includeModule('rest')) { $placementParams = [ 'gridPlacementCode' => \CCalendarRestService::PLACEMENT_GRID_VIEW, 'gridPlacementList' => \Bitrix\Rest\PlacementTable::getHandlersList(\CCalendarRestService::PLACEMENT_GRID_VIEW), 'serviceUrl' => '/bitrix/components/bitrix/app.layout/lazyload.ajax.php?&site='.SITE_ID.'&'.bitrix_sessid_get(), ]; } $JSConfig['placementParams'] = $placementParams; $isGroupCalendar = self::$type === Core\Event\Tools\Dictionary::CALENDAR_TYPE['group']; $isCollabCalendar = $isGroupCalendar && Collabs::getInstance()->getCollabIfExists(self::$ownerId); if ( (self::$type === Dictionary::CALENDAR_TYPE['user'] && self::$userId === self::$ownerId) || self::$type === Dictionary::CALENDAR_TYPE['group'] ) { $groupIds = self::$type === Dictionary::CALENDAR_TYPE['group'] ? [self::$ownerId] : []; $JSConfig['counters'] = CountersManager::getValues((int)self::$userId, $groupIds); $JSConfig['filterId'] = \Bitrix\Calendar\Ui\CalendarFilter::getFilterId( self::$type, self::$ownerId, self::$userId ); } else if ( self::$type === 'company_calendar' || self::$type === 'calendar_company' || self::$type === 'company' || self::$type === 'group' ) { $JSConfig['filterId'] = \Bitrix\Calendar\Ui\CalendarFilter::getFilterId( self::$type, self::$ownerId, self::$userId ); } // Access permissions for type $typeModel = TypeModel::createFromXmlId(self::$type); $accessController = new TypeAccessController(self::$userId); if ($accessController->check(ActionDictionary::ACTION_TYPE_ACCESS, $typeModel, [])) { $JSConfig['TYPE_ACCESS'] = $arType['ACCESS']; } if ($isPersonalCalendarContext) { $syncInfoParams = [ 'userId' => self::$userId, 'type' => self::$type, ]; $JSConfig['syncInfo'] = CCalendarSync::GetSyncInfo($syncInfoParams); $JSConfig['syncLinks'] = CCalendarSync::getSyncLinks(); $JSConfig['caldav_link_all'] = self::GetServerPath(); $JSConfig['isRuZone'] = \Bitrix\Calendar\Util::checkRuZone(); $JSConfig['isSetSyncGoogleSettings'] = self::isGoogleApiEnabled(); $JSConfig['isSetSyncOffice365Settings'] = self::isOffice365ApiEnabled(); $JSConfig['isIphoneConnected'] = self::isIphoneConnected(); $JSConfig['isMacConnected'] = self::isMacConnected(); $JSConfig['isIcloudConnected'] = ($JSConfig['syncInfo']['icloud'] ?? false) ? $JSConfig['syncInfo']['icloud']['connected'] : false ; $JSConfig['isGoogleConnected'] = ($JSConfig['syncInfo']['google'] ?? false) ? $JSConfig['syncInfo']['google']['connected'] : false ; } else { $JSConfig['caldav_link_all'] = self::GetServerPath(); $JSConfig['isRuZone'] = \Bitrix\Calendar\Util::checkRuZone(); $JSConfig['syncInfo'] = false; $JSConfig['isIphoneConnected'] = false; $JSConfig['isMacConnected'] = false; $JSConfig['isIcloudConnected'] = false; $JSConfig['isGoogleConnected'] = false; } self::$userMeetingSection = self::GetCurUserMeetingSection(); $defaultHiddenSections = []; $sections = []; $categoryList = Rooms\Categories\Manager::getCategoryList(); [$sectionList, $collabSectionList, $followedSectionList, $roomsList] = self::getSectionsInfo($isCollabUser); $sectionIdList = []; foreach ($sectionList as $section) { if (!in_array((int)$section['ID'], $sectionIdList, true)) { $sections[] = $section; $sectionIdList[] = (int)$section['ID']; } if ( ($section['CAL_TYPE'] !== self::$type || self::$ownerId !== (int)$section['OWNER_ID']) && !($isCollabUser && $section['IS_COLLAB']) ) { $defaultHiddenSections[] = (int)$section['ID']; } } $hiddenSections = UserSettings::getHiddenSections( self::$userId, [ 'type' => self::$type, 'ownerId' => self::$ownerId, 'isPersonalCalendarContext' => $isPersonalCalendarContext, 'defaultHiddenSections' => $defaultHiddenSections, ] ); $readOnly = !self::$perm['edit'] && !self::$perm['section_edit']; if (self::$type === 'user' && self::$ownerId !== (int)self::$userId) { $readOnly = true; } if (self::$bAnonym) { $readOnly = true; } $bCreateDefault = self::hasToCreateDefaultCalendar($sections); $groupOrUser = self::$type === 'user' || self::$type === 'group'; if ($groupOrUser) { $noEditAccessedCalendars = true; } foreach ($sections as $i => $section) { $canEdit = $accessController->check(ActionDictionary::ACTION_TYPE_EDIT, $typeModel, []); // We check access only for main sections because we can't edit superposed section if ( $groupOrUser && $section['CAL_TYPE'] === self::$type && (int)$section['OWNER_ID'] === (int)self::$ownerId ) { if ($noEditAccessedCalendars && $section['PERM']['edit']) { $noEditAccessedCalendars = false; } if ( $readOnly && $canEdit && ($section['PERM']['edit'] || $section['PERM']['edit_section']) && !self::$isArchivedGroup ) { $readOnly = false; } } if (in_array($section['ID'], $followedSectionList)) { $sections[$i]['SUPERPOSED'] = true; } $type = $sections[$i]['CAL_TYPE']; if ($type === 'user') { $path = CComponentEngine::MakePathFromTemplate( self::$pathesForSite['path_to_user_calendar'], ["user_id" => $sections[$i]['OWNER_ID']] ); } elseif ($type === 'group') { $groupId = $sections[$i]['OWNER_ID']; $path = CComponentEngine::MakePathFromTemplate( self::$pathesForSite['path_to_group_calendar'], ['group_id' => $groupId] ); if ($sections[$i]['IS_COLLAB']) { $trackingCollabs[] = $groupId; } else { $trackingGroups[] = $groupId; } } else { $path = self::$pathesForSite['path_to_type_'.$type]; } $sections[$i]['LINK'] = $path; } if (!empty($collabSectionList)) { $noEditAccessedCalendars = self::checkCollabSectionAccess($collabSectionList); } if ($groupOrUser && $noEditAccessedCalendars && !$bCreateDefault) { $readOnly = true; } self::$readOnly = $readOnly; $JSConfig['trackingUsersList'] = UserSettings::getTrackingUsers(self::$userId); // **** GET TASKS **** if (self::$showTasks) { $JSConfig['viewTaskPath'] = self::$viewTaskPath; $JSConfig['editTaskPath'] = self::$editTaskPath; } // We don't have any section if ($bCreateDefault) { $fullSectionsList = $groupOrUser ? self::GetSectionList(['checkPermissions' => false, 'getPermissions' => false]) : [] ; // Section exists but it closed to this user (Ref. mantis:#64037) if ( !empty($fullSectionsList) && self::GetOwnerId() !== self::GetUserId() ) { $readOnly = true; } else { $defCalendar = CCalendarSect::CreateDefault([ 'type' => self::GetType(), 'ownerId' => self::GetOwnerId(), ]); $sections[] = $defCalendar; self::$userMeetingSection = $defCalendar['ID']; } } else if (!CCalendarSect::containsLocalSection($sectionList, self::$type)) { $sectionsWithoutPermissions = CCalendarSect::GetList([ 'arFilter' => [ 'CAL_TYPE' => self::$type, 'OWNER_ID' => self::$ownerId, 'ACTIVE' => 'Y', 'EXTERNAL_TYPE' => CCalendarSect::EXTERNAL_TYPE_LOCAL, ], 'limit' => 1, 'checkPermissions' => false, 'getPermissions' => false, ]); if (empty($sectionsWithoutPermissions)) { $defCalendar = CCalendarSect::CreateDefault([ 'type' => self::GetType(), 'ownerId' => self::GetOwnerId(), ]); $sections[] = $defCalendar; self::$userMeetingSection = $defCalendar['ID']; } } //check if we need to create default calendar for user when he opens location calendar if (self::$type === 'location') { $userSections = array_filter($sectionList, static function ($section) { return $section['CAL_TYPE'] === 'user' && (int)$section['OWNER_ID'] === self::$userId; }); if (empty($userSections)) { $defUserCalendar = CCalendarSect::CreateDefault([ 'type' => 'user', 'ownerId' => self::$userId, ]); if ($defUserCalendar) { $sections[] = $defUserCalendar; self::$userMeetingSection = $defUserCalendar['ID']; } } } $typeAccessController = new TypeAccessController(self::$userId); if ($typeAccessController->check(ActionDictionary::ACTION_TYPE_EDIT, TypeModel::createFromXmlId(self::$type))) { $JSConfig['new_section_access'] = CCalendarSect::GetDefaultAccess(self::$type, self::$ownerId); } $colors = ['#86B100','#0092CC','#00AFC7','#E89B06','#00B38C','#DE2B24','#BD7AC9','#838FA0','#C3612C','#E97090']; $JSConfig['hiddenSections'] = $hiddenSections; $JSConfig['readOnly'] = $readOnly; $JSConfig['hideSettingsHintLocation'] = CUserOptions::GetOption('calendar', 'hideSettingsHintLocation'); // access $JSConfig['accessNames'] = self::GetAccessNames(); $JSConfig['sectionAccessTasks'] = self::$type === 'location' ? self::GetAccessTasks('calendar_section', 'location') : self::GetAccessTasks() ; $JSConfig['typeAccessTasks'] = self::$type === 'location' ? self::GetAccessTasks('calendar_type', 'location') : self::GetAccessTasks('calendar_type') ; $JSConfig['bSuperpose'] = self::$bSuperpose; $sharing = match (self::$type) { Core\Event\Tools\Dictionary::CALENDAR_TYPE['group'] => new Sharing\SharingGroup( self::$ownerId, (int)self::$userId ), default => new Sharing\Sharing(self::$userId), }; $JSConfig['sharing'] = $sharing->getLinkInfo(); $JSConfig['sharingOptions'] = $isGroupCalendar ? null : $sharing->getOptions(); $JSConfig['isCollabUser'] = $isCollabUser; $JSConfig['isCollabCalendar'] = $isCollabCalendar; $JSConfig['isCollabFeatureEnabled'] = CollabFeature::isAvailable(); $userSettings = UserSettings::get(self::$ownerId); $meetSectionId = (int)$userSettings['meetSection']; $meetSection = CCalendarSect::GetById($meetSectionId); $hasRightsForMeetSection = false; if (is_array($meetSection)) { $sectionAccessController = new SectionAccessController(self::$userId); $sectionModel = SectionModel::createFromArray($meetSection); $action = ActionDictionary::ACTION_SECTION_EDIT; $hasRightsForMeetSection = $sectionAccessController->check($action, $sectionModel); } if ($meetSectionId && $hasRightsForMeetSection) { $JSConfig['meetSectionId'] = $meetSectionId; } $selectedUserCodes = array('U'.self::$userId); if (self::$type === 'user') { $selectedUserCodes[] = 'U'.self::$ownerId; } $additionalParams = array( 'socnetDestination' => self::GetSocNetDestination(false, $selectedUserCodes), 'locationList' => $roomsList, 'timezoneList' => self::GetTimezoneList(), 'defaultColorsList' => $colors, 'formSettings' => array( 'slider_main' => UserSettings::getFormSettings('slider_main'), ), ); // Append Javascript files and CSS files, and some base configs if (self::$type === 'location') { CCalendarSceleton::InitJS( $JSConfig, array( 'sections' => $sections, 'rooms' => $roomsList, 'categories' => $categoryList, ), $additionalParams ); } else { CCalendarSceleton::InitJS( $JSConfig, array( 'sections' => $sections, ), $additionalParams ); } } /** * @return void * @throws Main\Access\Exception\UnknownActionException * @throws Main\LoaderException */ public function checkViewPermissions(): void { global $APPLICATION; $arType = false; foreach(self::$arTypes as $type) { if(self::$type === $type['XML_ID']) { $arType = $type; } } if (!$arType) { $APPLICATION->ThrowException('[EC_WRONG_TYPE] '.Loc::getMessage('EC_WRONG_TYPE'), 'calendar_wrong_type'); return; } if (!$this->checkPermissions()) { $APPLICATION->ThrowException(Loc::getMessage("EC_ACCESS_DENIED")); } } /** * @return bool * @throws Main\Access\Exception\UnknownActionException * @throws Main\LoaderException */ protected function checkPermissions(): bool { $typeModel = TypeModel::createFromXmlId(self::$type); $accessController = new TypeAccessController(self::$userId); if (!$accessController->check(ActionDictionary::ACTION_TYPE_VIEW, $typeModel, [])) { return false; } $isExternalUser = Loader::includeModule('intranet') && !\Bitrix\Intranet\Util::isIntranetUser(); $isCollaber = Util::isCollabUser(self::$userId); if (self::$type === Dictionary::CALENDAR_TYPE['user'] && $isExternalUser && !$isCollaber) { return false; } if (self::$type === Dictionary::CALENDAR_TYPE['user'] && $isCollaber && self::$userId !== self::$ownerId) { return false; } if (self::$type === Dictionary::CALENDAR_TYPE['group'] && !$this->checkGroupPermissions()) { return false; } return true; } /** * @return bool * @throws Main\LoaderException */ protected function checkGroupPermissions(): bool { if (!Loader::includeModule('socialnetwork')) { return false; } $groupId = self::$ownerId; $featurePerms = CSocNetFeaturesPerms::CurrentUserCanPerformOperation( SONET_ENTITY_GROUP, [$groupId], 'calendar', 'view_all' ); $canViewGroup = is_array($featurePerms) && isset($featurePerms[$groupId]) && $featurePerms[$groupId]; if (!$canViewGroup) { $featurePerms = CSocNetFeaturesPerms::CurrentUserCanPerformOperation( SONET_ENTITY_GROUP, [$groupId], 'calendar', 'view' ); $canViewGroup = is_array($featurePerms) && isset($featurePerms[$groupId]) && $featurePerms[$groupId]; } return $canViewGroup; } protected function doOpenEventInEditMode(string $id): bool { return str_starts_with($id, self::EDIT_PREFIX); } protected function getEditEventId(string $id): int { return (int)mb_substr($id, strlen(self::EDIT_PREFIX)); } public static function SetDisplayedSuperposed($userId = false, $idList = []) { if (class_exists('CUserOptions') && $userId) { $idList = array_unique(array_map('intval', $idList)); CUserOptions::SetOption("calendar", "superpose_displayed", serialize($idList)); \Bitrix\Calendar\Util::addPullEvent( PushCommand::ChangeSectionSubscription, $userId ); } } public static function DeleteSection($id) { if (self::IsExchangeEnabled(self::GetCurUserId()) && self::$type === 'user') { $oSect = CCalendarSect::GetById($id); // For exchange we change only calendar name if ($oSect && $oSect['IS_EXCHANGE'] && $oSect['DAV_EXCH_CAL']) { $exchRes = CDavExchangeCalendar::DoDeleteCalendar($oSect['OWNER_ID'], $oSect['DAV_EXCH_CAL']); if ($exchRes !== true) { return self::CollectExchangeErrors($exchRes); } } } return CCalendarSect::Delete($id); } public static function CollectExchangeErrors($arErrors = []) { if (empty($arErrors) || !is_array($arErrors)) { return '[EC_NO_EXCH] ' . Loc::getMessage('EC_NO_EXCHANGE_SERVER'); } $str = ""; foreach ($arErrors as $error) { $str .= "[" . $error[0] . "] " . $error[1] . "\n"; } return $str; } /** * @param $id * @param bool $doExternalSync * @param array $params * params['sendNotification'] send notifications * params['checkPermissions'] check permissions * params['recursionMode'] check event recurrence (this|all) * @return bool|string * @throws Main\Access\Exception\UnknownActionException */ public static function DeleteEvent($id, $doExternalSync = true, $params = []) { global $CACHE_MANAGER; $id = (int)$id; $markDeleted = $params['markDeleted'] ?? true; $originalFrom = $params['originalFrom'] ?? null; if (!$id) { return false; } $checkPermissions = $params['checkPermissions'] ?? true; if (!isset(self::$userId)) { self::$userId = self::GetCurUserId(); } self::SetOffset(); $res = CCalendarEvent::GetList([ 'arFilter' => ["ID" => $id], 'parseRecursion' => false, 'setDefaultLimit' => false, 'fetchAttendees' => true, 'checkPermissions' => $checkPermissions, ]); if (!empty($res[0]) && $event = $res[0]) { if (!isset(self::$type)) { self::$type = $event['CAL_TYPE']; } if (!isset(self::$ownerId)) { self::$ownerId = $event['OWNER_ID']; } $accessController = new EventAccessController(self::$userId); $eventModel = \CCalendarEvent::getEventModelForPermissionCheck((int)$event['ID'], $event, self::$userId); if ($checkPermissions && !$accessController->check(ActionDictionary::ACTION_EVENT_DELETE, $eventModel)) { return Loc::getMessage('EC_ACCESS_DENIED'); } CCalendarSect::UpdateModificationLabel($event['SECT_ID']); if ($doExternalSync !== false && $event['SECT_ID']) { $bGoogleApi = self::isGoogleApiEnabled() && $event['CAL_TYPE'] === 'user'; $bCalDav = self::IsCalDAVEnabled() && $event['CAL_TYPE'] === 'user'; $bExchangeEnabled = self::IsExchangeEnabled() && $event['CAL_TYPE'] === 'user'; if ($bExchangeEnabled || $bCalDav || $bGoogleApi) { $res = CCalendarSync::DoDeleteToDav([ 'bCalDav' => $bCalDav, 'bExchangeEnabled' => $bExchangeEnabled, 'sectionId' => $event['SECT_ID'], ], $event); if ($res !== true && self::$silentErrorMode) { self::ThrowError($res); } } } $sendNotification = $params['sendNotification'] ?? (($params['recursionMode'] ?? null) !== 'all'); $userId = !empty($params['userId']) ? (int)$params['userId'] : self::$userId; $res = CCalendarEvent::Delete([ 'id' => $id, 'Event' => $event, 'bMarkDeleted' => $markDeleted, 'originalFrom' => $originalFrom, 'userId' => $userId, 'sendNotification' => $sendNotification, 'requestUid' => $params['requestUid'] ?? null, ]); if (($params['recursionMode'] ?? null) !== 'this' && !empty($event['RECURRENCE_ID'])) { self::DeleteEvent($event['RECURRENCE_ID'], $doExternalSync, [ 'sendNotification' => $sendNotification, 'originalFrom' => $originalFrom, ]); } if (CCalendarEvent::CheckRecurcion($event)) { $events = CCalendarEvent::GetEventsByRecId($id); foreach($events as $ev) { self::DeleteEvent($ev['ID'], $doExternalSync, [ 'sendNotification' => $sendNotification, 'originalFrom' => $originalFrom, ]); } } if (isset($params['recursionMode']) && $params['recursionMode'] === 'all' && !empty($event['ATTENDEE_LIST'])) { foreach($event['ATTENDEE_LIST'] as $attendee) { if ($attendee['status'] !== 'N') { $CACHE_MANAGER->ClearByTag('calendar_user_'.$attendee["id"]); CCalendarNotify::Send([ "mode" => 'cancel_all', "name" => $event['NAME'], "from" => $event['DATE_FROM'], "guestId" => $attendee["id"], "eventId" => $event['PARENT_ID'], "userId" => $userId ?? $event['MEETING_HOST'], "fields" => $event, ]); } } } return $res; } return false; } public static function SetOffset($userId = false, $value = 0) { if ($userId === false) { self::$offset = $value; } else { self::$arTimezoneOffsets[$userId] = $value; } } public static function CollectCalDAVErros($arErrors = []) { if (empty($arErrors) || !is_array($arErrors)) { return '[EC_NO_EXCH] ' . Loc::getMessage('EC_NO_CAL_DAV'); } $str = ""; foreach ($arErrors as $error) { $str .= "[". $error[0]."] ". $error[1]."\n"; } return $str; } public static function GetPathForCalendarEx($userId = 0) { $userId = (int)$userId; $cacheId = 'calendar_path_settings_'.$userId; $obCache = new CPHPCache; if($obCache->InitCache(3600 * 6, $cacheId, '/calendar/'.$cacheId)) { $calendarUrl = $obCache->GetVars(); } else { $obCache->StartDataCache(); $bExtranet = Loader::includeModule('extranet'); // It's extranet user if ($bExtranet && self::IsExtranetUser($userId)) { $siteId = CExtranet::GetExtranetSiteID(); } else { $siteId = $bExtranet && !self::IsExtranetUser($userId) ? CSite::GetDefSite() : self::GetSiteId(); if ( self::$siteId == $siteId && isset(self::$pathesForSite) && is_array(self::$pathesForSite) ) { self::$pathes[$siteId] = self::$pathesForSite; } } if (!isset(self::$pathes[$siteId]) || !is_array(self::$pathes[$siteId])) { self::$pathes[$siteId] = self::GetPathes($siteId); } $calendarUrl = self::$pathes[$siteId]['path_to_user_calendar'] ?? ''; $calendarUrl = str_replace(array('#user_id#', '#USER_ID#'), $userId, $calendarUrl); $calendarUrl = self::GetServerPath().$calendarUrl; $obCache->EndDataCache($calendarUrl); } return $calendarUrl; } public static function IsExtranetUser($userId = 0) { if (!$userId) { return true; } $departments = self::GetUserDepartment($userId); return empty($departments); } public static function GetUserDepartment($userId = 0) { if (!isset(self::$arUserDepartment[$userId])) { $rsUser = CUser::GetByID($userId); if($arUser = $rsUser->Fetch()) { self::SetUserDepartment($userId, $arUser["UF_DEPARTMENT"]); } } return self::$arUserDepartment[$userId]; } public static function SetUserDepartment($userId = 0, $dep = []) { if (!is_array($dep)) { $dep = []; } self::$arUserDepartment[$userId] = $dep; } public static function HandleImCallback($module, $tag, $value, $arNotify) { $userId = self::GetCurUserId(); if ($module === "calendar" && $userId) { $arTag = explode("|", $tag); $eventId = (int)$arTag[2]; if ($arTag[0] === "CALENDAR" && $arTag[1] === "INVITE" && $eventId && $userId) { CCalendarEvent::SetMeetingStatus([ 'userId' => $userId, 'eventId' => $eventId, 'status' => $value === 'Y' ? 'Y' : 'N', 'personalNotification' => true, ]); return $value === 'Y' ? Loc::getMessage('EC_PROP_CONFIRMED_TEXT_Y') : Loc::getMessage('EC_PROP_CONFIRMED_TEXT_N'); } } } public static function SetSettings($settings = [], $clearOptions = false) { $arPathes = self::GetPathesList(); $optionNames = [ 'work_time_start', 'work_time_end', 'year_holidays', 'year_workdays', 'week_holidays', 'week_start', 'user_name_template', 'sync_by_push', 'user_show_login', 'rm_iblock_type', 'rm_iblock_id', 'denied_superpose_types', 'pathes_for_sites', 'pathes', 'dep_manager_sub', 'forum_id', 'rm_for_sites', ]; $optionNames = array_merge($optionNames, $arPathes); if (isset($settings['rm_iblock_ids']) && !$settings['rm_for_sites']) { foreach($settings['rm_iblock_ids'] as $site => $value) { COption::SetOptionString("calendar", 'rm_iblock_id', $value, false, $site); } } foreach($optionNames as $opt) { if ($clearOptions) { COption::RemoveOption("calendar", $opt); } else if (isset($settings[$opt])) { if ($opt === 'rm_iblock_id' && !$settings['rm_for_sites']) { continue; } if ($opt === 'sync_by_push') { if (self::isOffice365ApiEnabled() || self::isGoogleApiEnabled()) { \CAgent::RemoveAgent("\\Bitrix\\Calendar\\Sync\\Managers\\DataExchangeManager::importAgent();", 'calendar'); \CAgent::RemoveAgent("\\Bitrix\\Calendar\\Sync\\Managers\\PushWatchingManager::renewWatchChannels();", 'calendar'); if ($settings[$opt]) { // legacy \CAgent::RemoveAgent( "\\Bitrix\\Calendar\\Sync\\GoogleApiPush::clearPushChannels();", "calendar" ); // actual \CAgent::AddAgent( "\\Bitrix\\Calendar\\Sync\\Managers\\PushWatchingManager::renewWatchChannels();", 'calendar', 'N', 3600 ); } else { global $DB; // legacy \CAgent::RemoveAgent("\\Bitrix\\Calendar\\Sync\\GoogleApiPush::processPush();", "calendar"); \CAgent::RemoveAgent("\\Bitrix\\Calendar\\Sync\\GoogleApiPush::renewWatchChannels();", "calendar"); $DB->Query("DELETE FROM b_agent WHERE NAME LIKE '%GoogleApiPush::checkPushChannel%'"); // actual \CAgent::AddAgent( "\\Bitrix\\Calendar\\Sync\\Managers\\DataExchangeManager::importAgent();", 'calendar', 'N', 180 ); } } } if ($opt === 'pathes' && is_array($settings[$opt])) { $sitesPathes = $settings[$opt]; $ar = []; $arAffectedSites = []; foreach($sitesPathes as $s => $pathes) { $affect = false; foreach($arPathes as $path) { if ($pathes[$path] != $settings[$path]) { $ar[$path] = $pathes[$path] ?? $settings[$path]; $affect = true; } } if ($affect && !in_array($s, $arAffectedSites)) { $arAffectedSites[] = $s; COption::SetOptionString("calendar", 'pathes_'.$s, serialize($ar)); } else { COption::RemoveOption("calendar", 'pathes_'.$s); } } COption::SetOptionString("calendar", 'pathes_sites', serialize($arAffectedSites)); continue; } else if ($opt === 'denied_superpose_types' && is_array($settings[$opt])) { $settings[$opt] = serialize($settings[$opt]); } else if ($opt === 'week_holidays' && is_array($settings[$opt])) { $settings[$opt] = implode( '|', array_intersect(array_unique($settings[$opt]), ['SU','MO','TU','WE','TH','FR','SA']) ); } COption::SetOptionString("calendar", $opt, $settings[$opt]); } } } public static function IsBitrix24() { return \Bitrix\Main\ModuleManager::isModuleInstalled('bitrix24'); } public static function ReminderAgent($eventId = 0, $userId = 0, $viewPath = '', $calendarType = '', $ownerId = 0, $index = 0) { CCalendarReminder::ReminderAgent($eventId, $userId, $viewPath, $calendarType, $ownerId, $index); } public static function GetMaxTimestamp() { return self::CALENDAR_MAX_TIMESTAMP; } public static function SetMaxPlannerUsers(int $maxPlannerUsers): void { \Bitrix\Main\Config\Option::set('calendar', 'maxPlannerUsers', $maxPlannerUsers); } public static function GetMaxPlannerUsers(): int { return (int)\Bitrix\Main\Config\Option::get('calendar', 'maxPlannerUsers', 0); } public static function GetOwnerName($type = '', $ownerId = '') { $type = mb_strtolower($type); $key = $type.'_'.$ownerId; if (isset(self::$ownerNames[$key])) { return self::$ownerNames[$key]; } $ownerName = ''; if($type === 'user') { $ownerName = self::GetUserName($ownerId); } elseif($type === 'group') { // Get group name if (!Loader::includeModule("socialnetwork")) { return $ownerName; } if ($arGroup = CSocNetGroup::GetByID($ownerId)) { $ownerName = $arGroup["~NAME"]; } } else { // Get type name $arTypes = CCalendarType::GetList(array("arFilter" => array("XML_ID" => $type))); $ownerName = $arTypes[0]['NAME']; } self::$ownerNames[$key] = $ownerName; $ownerName = is_string($ownerName) ? trim($ownerName) : ''; return $ownerName; } public static function GetTimezoneOffset($timezoneId, $dateTimestamp = false) { $offset = 0; if ($timezoneId) { try { $oTz = new DateTimeZone($timezoneId); if ($oTz) { $offset = $oTz->getOffset(new DateTime($dateTimestamp ? "@$dateTimestamp" : "now", $oTz)); } } catch(Exception $e) { } } return $offset; } public static function isDaylightSavingTimezone(string $timezoneId): string { $result = 'N'; try { $timezone = new DateTimeZone($timezoneId); $currentTime = new DateTime('now', $timezone); $transitions = $timezone->getTransitions( $currentTime->getTimestamp(), $currentTime->getTimestamp() + 365 * self::DAY_LENGTH ); foreach ($transitions as $transition) { if ($transition['isdst'] === true) { return 'Y'; } } } catch(Exception $e) { } return $result; } public static function GetAbsentEvents($params) { if (!isset($params['arUserIds'])) return false; return CCalendarEvent::GetAbsent($params['arUserIds'], $params); } /** * @deprecated */ public static function GetAccessibilityForUsers($params) { if (!isset($params['checkPermissions'])) { $params['checkPermissions'] = true; } $res = CCalendarEvent::GetAccessibilityForUsers([ 'users' => $params['users'], 'from' => $params['from'], 'to' => $params['to'], 'curEventId' => $params['curEventId'] ?? null, 'checkPermissions' => $params['checkPermissions'], ]); // Fetch absence from intranet if (isset($params['getFromHR']) && self::IsIntranetEnabled()) { $resHR = CIntranetUtils::GetAbsenceData( array( 'DATE_START' => $params['from'], 'DATE_FINISH' => $params['to'], 'USERS' => $params['users'], 'PER_USER' => true, 'SELECT' => array('ID', 'DATE_ACTIVE_FROM', 'DATE_ACTIVE_TO'), ), BX_INTRANET_ABSENCE_HR ); foreach($resHR as $userId => $forUser) { if (!isset($res[$userId]) || !is_array($res[$userId])) { $res[$userId] = []; } foreach($forUser as $event) { $res[$userId][] = [ 'FROM_HR' => true, 'ID' => $event['ID'], 'DT_FROM' => $event['DATE_ACTIVE_FROM'], 'DT_TO' => $event['DATE_ACTIVE_TO'], 'ACCESSIBILITY' => 'absent', 'IMPORTANCE' => 'normal', "FROM" => self::Timestamp($event['DATE_ACTIVE_FROM']), "TO" => self::Timestamp($event['DATE_ACTIVE_TO']), ]; } } } return $res; } public static function GetNearestEventsList($params = []) { $type = $params['bCurUserList'] ? 'user' : $params['type']; $isFromRest = ($params['fromRest'] ?? false) === true; // Get current user id if (!isset($params['userId']) || $params['userId'] <= 0) { $curUserId = self::GetCurUserId(); } else { $curUserId = (int)$params['userId']; } $accessController = new TypeAccessController($curUserId); if (!$accessController->check(ActionDictionary::ACTION_TYPE_VIEW, TypeModel::createFromXmlId($type))) { return 'access_denied'; } if ( $params['bCurUserList'] && ( $curUserId <= 0 || ( class_exists('CSocNetFeatures') && !CSocNetFeatures::IsActiveFeature(SONET_ENTITY_USER, $curUserId, "calendar") ) ) ) { return 'inactive_feature'; } $maxAmount = isset($params['maxAmount']) && (int)$params['maxAmount'] > 0 ? (int)$params['maxAmount'] : 75 ; if ($type === 'user' && OpenEvents\Feature::getInstance()->isAvailable()) { $type = ['user', Core\Event\Tools\Dictionary::CALENDAR_TYPE['open_event']]; } $arFilter = [ 'CAL_TYPE' => $type, 'FROM_LIMIT' => $params['fromLimit'], 'TO_LIMIT' => $params['toLimit'], 'DELETED' => 'N', 'ACTIVE_SECTION' => 'Y', ]; if ($params['bCurUserList']) { $arFilter['OWNER_ID'] = $curUserId; } if (isset($params['sectionId']) && $params['sectionId']) { $arFilter["SECTION"] = $params['sectionId']; } $selectFields = [ 'ID', 'PARENT_ID', 'NAME', 'OWNER_ID', 'RRULE', 'EXDATE', 'DATE_FROM', 'DATE_TO', 'TZ_FROM', 'TZ_TO', 'TZ_OFFSET_FROM', 'TZ_OFFSET_TO', 'IS_MEETING', 'MEETING_STATUS', 'CAL_TYPE', 'DT_LENGTH', 'DT_SKIP_TIME', 'SECTION_ID', 'DATE_FROM_TS_UTC', 'DATE_TO_TS_UTC', ]; if ($isFromRest) { $selectFields = ['*']; } $eventsList = CCalendarEvent::GetList([ 'arFilter' => $arFilter, 'arSelect' => $selectFields, 'parseRecursion' => true, 'fetchAttendees' => $isFromRest, 'userId' => $curUserId, 'fetchMeetings' => $type === 'user', 'preciseLimits' => true, 'skipDeclined' => true, 'getUserfields' => $isFromRest, ]); $pathToCalendar = self::GetPathForCalendarEx($curUserId); if (self::Date(time(), false) === $params['fromLimit']) { $limitTime = time(); } else { $limitTime = self::Timestamp($params['fromLimit']); } $limitTime -= (int)date("Z", $limitTime); $entryList = []; foreach ($eventsList as $event) { if ($event['IS_MEETING'] && $event["MEETING_STATUS"] === 'N') { continue; } if ($type === 'user' && !$event['IS_MEETING'] && $event['CAL_TYPE'] !== 'user') { continue; } $fromTs = self::Timestamp($event['DATE_FROM']); $toTs = $fromTs + $event['DT_LENGTH']; $toTsUtc = $toTs - $event['TZ_OFFSET_FROM']; if ($toTsUtc >= $limitTime) { if ($event['DT_SKIP_TIME'] !== "Y") { $fromTs -= $event['~USER_OFFSET_FROM']; $toTs -= $event['~USER_OFFSET_TO']; } $event['DATE_FROM'] = self::Date($fromTs, $event['DT_SKIP_TIME'] !== 'Y'); $event['DATE_TO'] = self::Date($toTs, $event['DT_SKIP_TIME'] !== 'Y'); unset($event['TZ_FROM'], $event['TZ_TO'], $event['TZ_OFFSET_FROM'], $event['TZ_OFFSET_TO']); $event['DT_FROM_TS'] = $fromTs; $event['DT_TO_TS'] = $toTs; $event['~URL'] = \CHTTP::urlAddParams($pathToCalendar, [ 'EVENT_ID' => $event['ID'], 'EVENT_DATE' => self::Date($fromTs, false), ]); $event['~WEEK_DAY'] = FormatDate("D", $fromTs); $event['~FROM_TO_HTML'] = self::GetFromToHtml( $fromTs, $toTs, $event['DT_SKIP_TIME'] === 'Y', $event['DT_LENGTH'] ); $entryList[] = $event; } } // Sort by DATE_FROM_TS usort($entryList, static function($a, $b){ if ($a['DT_FROM_TS'] === $b['DT_FROM_TS']) { return 0; } return $a['DT_FROM_TS'] < $b['DT_FROM_TS'] ? -1 : 1; }); array_splice($entryList, $maxAmount); return $entryList; } public static function GetTextLocation($loc = '') { return Rooms\Util::getTextLocation($loc); } public static function ParseLocation($location = '') { return Rooms\Util::parseLocation($location); } /* * * * RESERVE MEETING ROOMS * * * */ public static function GetUserPermissionsForCalendar($calendarId, $userId) { [$sectionId, $entityType, $entityId] = $calendarId; $entityType = mb_strtolower($entityType); $accessController = new SectionAccessController((int)$userId); $sectionModel = SectionModel::createFromId((int)$sectionId) ->setType($entityType) ->setOwnerId((int)$entityId) ; $request = [ ActionDictionary::ACTION_SECTION_EDIT => [], ActionDictionary::ACTION_SECTION_EVENT_VIEW_FULL => [], ActionDictionary::ACTION_SECTION_EVENT_VIEW_TIME => [], ActionDictionary::ACTION_SECTION_EVENT_VIEW_TITLE => [], ]; $result = $accessController->batchCheck($request, $sectionModel); $res = [ 'bAccess' => $result[ActionDictionary::ACTION_SECTION_EVENT_VIEW_TIME], 'bReadOnly' => !$result[ActionDictionary::ACTION_SECTION_EDIT], ]; if ($res['bReadOnly']) { if ($result[ActionDictionary::ACTION_SECTION_EVENT_VIEW_TIME]) { $res['privateStatus'] = 'time'; } if ($result[ActionDictionary::ACTION_SECTION_EVENT_VIEW_TITLE]) { $res['privateStatus'] = 'title'; } } return $res; } public static function GetDayLen() { return self::DAY_LENGTH; } public static function UnParseTextLocation($loc = '') { return Rooms\Util::unParseTextLocation($loc); } public static function ClearExchangeHtml($html = "") { // Echange in chrome puts chr(13) instead of \n $html = str_replace(chr(13), "\n", trim($html, chr(13))); $html = preg_replace("/(\s|\S)*<a\s*name=\"bm_begin\"><\/a>/isu","", $html); $html = preg_replace("/<br>(\n|\r)+/isu","<br>", $html); return self::ParseHTMLToBB($html); } public static function ParseHTMLToBB($html = "") { $id = AddEventHandler("main", "TextParserBeforeTags", Array("CCalendar", "_ParseHack")); $TextParser = new CTextParser(); $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"); $html = $TextParser->convertText($html); $html = htmlspecialcharsback($html); // Replace BR $html = preg_replace("/\<br\s*\/*\>/isu","\n", $html); //replace /p && /div to \n $html = preg_replace("/\<\/(p|div)\>/isu","\n", $html); // Kill $html = preg_replace("/ /isu","", $html); // For images in Office 365 $html = preg_replace( "#<img[^>]+src\\s*=[\\s'\"]*((cid):[.\\-_:a-z0-9@]+)*[\\s'\"]*[^>]*>#isu", "[img]\\1[/img]", $html ); // Kill tags $html = preg_replace("/\<([^>]*?)>/isu","", $html); // Clean multiple \n symbols $html = preg_replace("/\n[\s\n]+\n/", "\n" , $html); $html = htmlspecialcharsbx($html); RemoveEventHandler("main", "TextParserBeforeTags", $id); return $html; } public static function WeekDayByInd($i, $binv = true) { if ($binv) { $arDays = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']; } else { $arDays = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']; } return $arDays[$i] ?? false; } public static function IndByWeekDay(string $weekday): int { $weekdays = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']; $weekdays = array_combine($weekdays, array_keys(array_values($weekdays))); return $weekdays[$weekday] ?? 0; } public static function SaveEvent($params = []) { $res = self::SaveEventEx($params); if (is_array($res) && isset($res['originalDavXmlId'])) { return $res; } if (is_array($res) && isset($res['id'])) { return $res['id']; } return $res; } /** * @throws Main\ObjectException */ public static function SaveEventEx($params = []) { $arFields = $params['arFields']; if (self::$type && !isset($arFields['CAL_TYPE'])) { $arFields['CAL_TYPE'] = self::$type; } elseif (isset($arFields['SECTION_CAL_TYPE']) && !isset($arFields['CAL_TYPE'])) { $arFields['CAL_TYPE'] = $arFields['SECTION_CAL_TYPE']; } if (self::$bOwner && !isset($arFields['OWNER_ID'])) { $arFields['OWNER_ID'] = self::$ownerId; } elseif (isset($arFields['SECTION_OWNER_ID']) && !isset($arFields['OWNER_ID'])) { $arFields['OWNER_ID'] = $arFields['SECTION_OWNER_ID']; } if (!isset($arFields['SKIP_TIME']) && isset($arFields['DT_SKIP_TIME'])) { $arFields['SKIP_TIME'] = $arFields['DT_SKIP_TIME'] === 'Y'; } //flags for synchronize the instance of a recurring event //modeSync - edit mode instance for avoid unnecessary request (patch) //editParentEvents - editing the parent event of the following $params['modeSync'] = true; $params['editInstance'] = $params['editInstance'] ?? false; $params['editNextEvents'] = $params['editNextEvents'] ?? false; $params['editParentEvents'] = $params['editParentEvents'] ?? false; $params['editEntryUntil'] = $params['editEntryUntil'] ?? false; $params['originalDavXmlId'] = $params['originalDavXmlId'] ?? null; $params['originalFrom'] = $params['originalFrom'] ?? null; $params['instanceTz'] = $params['instanceTz'] ?? null; $params['syncCaldav'] = $params['syncCaldav'] ?? false; $params['sendInvitesToDeclined'] = $params['sendInvitesToDeclined'] ?? false; $params['autoDetectSection'] = $params['autoDetectSection'] ?? false; $userId = $params['userId'] ?? self::getCurUserId(); $accessController = new EventAccessController($userId); $result = []; if (isset($arFields['SECTION_ID'])) { $sectionId = (int)$arFields['SECTION_ID']; } else { $sectionId = (!empty($arFields['SECTIONS']) && is_array($arFields['SECTIONS'])) ? $arFields['SECTIONS'][0] : (int)($arFields['SECTIONS'] ?? null); } $bPersonal = self::IsPersonal($arFields['CAL_TYPE'] ?? null, $arFields['OWNER_ID'] ?? null, $userId); $checkPermission = !isset($params['checkPermission']) || $params['checkPermission'] !== false; $silentErrorModePrev = self::$silentErrorMode; self::SetSilentErrorMode(); if ( isset($arFields['DT_FROM'], $arFields['DT_TO']) && !isset($arFields['DATE_FROM']) && !isset($arFields['DATE_TO']) ) { $arFields['DATE_FROM'] = $arFields['DT_FROM']; $arFields['DATE_TO'] = $arFields['DT_TO']; unset($arFields['DT_FROM'], $arFields['DT_TO']); } // Fetch current event $curEvent = false; $bNew = !isset($arFields['ID']) || !$arFields['ID']; if (!$bNew) { $curEvent = self::getCurrentEventForSaving((int)$arFields['ID'], $userId, $checkPermission); if ( in_array( $curEvent['EVENT_TYPE'] ?? '', Sharing\SharingEventManager::getSharingEventTypes(), true ) ) { unset( $arFields['SECTIONS'], $arFields['DATE_FROM'], $arFields['DATE_TO'], $arFields['SKIP_TIME'], $arFields['TZ_FROM'], $arFields['TZ_TO'], ); $arFields['RRULE'] = [ 'FREQ' => 'NONE', 'INTERVAL' => 1, ]; } if (!isset($arFields['DATE_FROM']) && isset($curEvent['DATE_FROM'])) { $arFields['DATE_FROM'] = $curEvent['DATE_FROM']; } if (!isset($arFields['DATE_TO']) && isset($curEvent['DATE_TO'])) { $arFields['DATE_TO'] = $curEvent['DATE_TO']; } $canChangeDateRecurrenceEvent = isset($params['recursionEditMode']) && in_array($params['recursionEditMode'], ['all', ''], true) && (($arFields['DATE_FROM'] ?? null) !== ($curEvent['DATE_FROM'] ?? null)) && ($arFields['RRULE']['FREQ'] ?? null) !== 'NONE' ; if ($canChangeDateRecurrenceEvent) { $arFields['DATE_FROM'] = self::GetOriginalDate( $arFields['DATE_FROM'], $curEvent['DATE_FROM'], $arFields['TZ_FROM'] ?? null ); $arFields['DATE_TO'] = self::GetOriginalDate( $arFields['DATE_TO'], $curEvent['DATE_TO'], $arFields['TZ_TO'] ?? null ); } $bPersonal = $bPersonal && self::IsPersonal($curEvent['CAL_TYPE'], $curEvent['OWNER_ID'], $userId); $arFields['CAL_TYPE'] = $curEvent['CAL_TYPE']; $arFields['OWNER_ID'] = $curEvent['OWNER_ID']; $arFields['CREATED_BY'] = $curEvent['CREATED_BY']; $arFields['ACTIVE'] = $curEvent['ACTIVE'] ?? null; $eventModel = CCalendarEvent::getEventModelForPermissionCheck((int)($curEvent['ID'] ?? 0), $curEvent, $userId); $accessCheckResult = $accessController->check(ActionDictionary::ACTION_EVENT_EDIT, $eventModel); $bChangeMeeting = !$checkPermission || $accessCheckResult; if (!$bChangeMeeting) { return Loc::getMessage('EC_ACCESS_DENIED'); } if (!isset($arFields['NAME'])) { $arFields['NAME'] = $curEvent['NAME']; } if (!isset($arFields['DESCRIPTION'])) { $arFields['DESCRIPTION'] = $curEvent['DESCRIPTION']; } if (!isset($arFields['COLOR']) && $curEvent['COLOR']) { $arFields['COLOR'] = $curEvent['COLOR']; } if (!isset($arFields['TEXT_COLOR']) && !empty($curEvent['TEXT_COLOR'])) { $arFields['TEXT_COLOR'] = $curEvent['TEXT_COLOR']; } if (!isset($arFields['SECTIONS'])) { $arFields['SECTIONS'] = [$curEvent['SECT_ID']]; $sectionId = !empty($arFields['SECTIONS']) ? $arFields['SECTIONS'][0] : 0; } if (!isset($arFields['IS_MEETING'])) { $arFields['IS_MEETING'] = $curEvent['IS_MEETING']; } if (!isset($arFields['MEETING_HOST'])) { $arFields['MEETING_HOST'] = $curEvent['MEETING_HOST']; } if (!isset($arFields['MEETING_STATUS'])) { $arFields['MEETING_STATUS'] = $curEvent['MEETING_STATUS']; } if (!isset($arFields['ACTIVE']) && isset($curEvent['ACTIVE'])) { $arFields['ACTIVE'] = $curEvent['ACTIVE']; } if (!isset($arFields['PRIVATE_EVENT'])) { $arFields['PRIVATE_EVENT'] = $curEvent['PRIVATE_EVENT']; } if (!isset($arFields['ACCESSIBILITY'])) { $arFields['ACCESSIBILITY'] = $curEvent['ACCESSIBILITY']; } if (!isset($arFields['IMPORTANCE'])) { $arFields['IMPORTANCE'] = $curEvent['IMPORTANCE']; } if (!isset($arFields['SKIP_TIME'])) { $arFields['SKIP_TIME'] = $curEvent['DT_SKIP_TIME'] === 'Y'; } if (!isset($arFields['TZ_FROM'])) { $arFields['TZ_FROM'] = $curEvent['TZ_FROM']; } if (!isset($arFields['TZ_TO'])) { $arFields['TZ_TO'] = $curEvent['TZ_TO']; } if (!isset($arFields['RELATIONS'])) { $arFields['RELATIONS'] = $curEvent['RELATIONS']; } if (!isset($arFields['MEETING'])) { $arFields['MEETING'] = $curEvent['MEETING']; } if (!isset($arFields['SYNC_STATUS']) && $curEvent['SYNC_STATUS']) { $arFields['SYNC_STATUS'] = $curEvent['SYNC_STATUS']; } if (!isset($arFields['EVENT_TYPE']) && $curEvent['EVENT_TYPE']) { $arFields['EVENT_TYPE'] = $curEvent['EVENT_TYPE']; } $arFields['MEETING']['LANGUAGE_ID'] = self::getUserLanguageId((int)$userId); if ( !isset($arFields['ATTENDEES']) && !isset($arFields['ATTENDEES_CODES']) && $arFields['IS_MEETING'] && !empty($curEvent['ATTENDEE_LIST']) && is_array($curEvent['ATTENDEE_LIST']) ) { $arFields['ATTENDEES'] = []; foreach ($curEvent['ATTENDEE_LIST'] as $attendee) { $arFields['ATTENDEES'][] = $attendee['id']; } } if (!isset($arFields['ATTENDEES_CODES']) && $arFields['IS_MEETING']) { $arFields['ATTENDEES_CODES'] = $curEvent['ATTENDEES_CODES']; } if (!isset($arFields['LOCATION']) && $curEvent['LOCATION'] !== "") { $arFields['LOCATION'] = [ 'OLD' => $curEvent['LOCATION'], 'NEW' => $curEvent['LOCATION'], ]; //if location wasn't change when updating event $parsedLoc = Bitrix\Calendar\Rooms\Util::parseLocation($curEvent['LOCATION']); if ($parsedLoc['room_event_id']) { $arFields['LOCATION']['NEW'] = 'calendar_' . $parsedLoc['room_id']; } } if ($arFields['IS_MEETING'] && !$bPersonal && $arFields['CAL_TYPE'] === 'user') { $arFields['SECTION_ID'] = $curEvent['SECT_ID']; $arFields['SECTIONS'] = [$curEvent['SECT_ID']]; } // If it's attendee but modifying called from CalDav methods if ( (!empty($params['bSilentAccessMeeting']) || (isset($params['fromWebservice']) && $params['fromWebservice'] === true) ) && !empty($curEvent['IS_MEETING']) && ($curEvent['PARENT_ID'] !== $curEvent['ID']) ) { // TODO: It called when changes caused in google/webservise side but can't be // TODO: implemented because user is only attendee, not the owner of the event //Todo: we have to update such events back to revert changes from google return true; // CalDav will return 204 } if (!isset($arFields["RRULE"]) && $curEvent["RRULE"] !== '' && ($params['fromWebservice'] ?? null) !== true) { $arFields["RRULE"] = CCalendarEvent::ParseRRULE($curEvent["RRULE"]); } if ( (($params['fromWebservice'] ?? null) === true) && $arFields["RRULE"] === -1 && CCalendarEvent::CheckRecurcion($curEvent) ) { $arFields["RRULE"] = CCalendarEvent::ParseRRULE($curEvent['RRULE']); } if (!isset($arFields['EXDATE']) && !empty($arFields["RRULE"])) { $arFields['EXDATE'] = $curEvent['EXDATE']; } else if ( isset($arFields['EXDATE'], $curEvent['EXDATE']) && $arFields['EXDATE'] && $curEvent['EXDATE'] && !empty($arFields["RRULE"]) ) { $arFields['EXDATE'] = self::mergeExcludedDates($curEvent['EXDATE'], $arFields['EXDATE']); } if ($curEvent) { $params['currentEvent'] = $curEvent; } } // $bPersonal should not be reason to skip section rights check // but if it removed, extranet users can not create events in groups, cause EventAddRule:42 // there section model creation contains bug, which not respect owner_id specific for groups elseif ($checkPermission && $sectionId > 0 && !$bPersonal) { $section = CCalendarSect::GetList(['arFilter' => ['ID' => $sectionId], 'checkPermissions' => false, 'getPermissions' => false, ])[0] ?? null; if ($section) { $arFields['CAL_TYPE'] = $section['CAL_TYPE']; } else { return self::ThrowError(Loc::getMessage('EC_ACCESS_DENIED')); } if (($section['IS_COLLAB'] ?? null) && Util::isCollabUser($userId)) { $userCollabIds = UserCollabs::getInstance()->getIds($userId); if (!in_array((int)$section['OWNER_ID'], $userCollabIds, true)) { return self::ThrowError(Loc::getMessage('EC_ACCESS_DENIED')); } } else { $newEventModel = EventModel::createNew() ->setOwnerId((int)$arFields['OWNER_ID']) ->setSectionId((int)$sectionId) ->setSectionType($arFields['CAL_TYPE']); if (!$accessController->check(ActionDictionary::ACTION_EVENT_ADD, $newEventModel)) { return self::ThrowError(Loc::getMessage('EC_ACCESS_DENIED')); } } } if ($params['autoDetectSection'] && $sectionId <= 0) { $sectionId = false; if ($arFields['CAL_TYPE'] === 'user') { $sectionId = self::GetMeetingSection($arFields['OWNER_ID'], true); if ($sectionId) { $res = CCalendarSect::GetList( [ 'arFilter' => [ 'CAL_TYPE' => $arFields['CAL_TYPE'], 'OWNER_ID' => $arFields['OWNER_ID'], 'ID' => $sectionId, ], ] ); if (!$res || !$res[0] || CCalendarSect::CheckGoogleVirtualSection($res[0]['GAPI_CALENDAR_ID'])) { $sectionId = false; } } else { $sectionId = false; } if ($sectionId) { $arFields['SECTIONS'] = [$sectionId]; } } if (!$sectionId) { if (empty($arFields['CAL_TYPE']) || empty($arFields['OWNER_ID'])) { return false; } $sectRes = CCalendarSect::GetSectionForOwner( $arFields['CAL_TYPE'], $arFields['OWNER_ID'], $params['autoCreateSection'] ); if ($sectRes['sectionId'] > 0) { $sectionId = $sectRes['sectionId']; $arFields['SECTIONS'] = [$sectionId]; if ($sectRes['autoCreated']) { $params['bAffectToDav'] = false; } } else { return false; } } } if (isset($arFields["RRULE"])) { $arFields["RRULE"] = CCalendarEvent::CheckRRULE($arFields["RRULE"]); } if (!empty($arFields['TZ_FROM']) && is_string($arFields['TZ_FROM'])) { $tzFrom = Util::prepareTimezone($arFields['TZ_FROM']); if ( !empty($arFields['TZ_TO']) && is_string($arFields['TZ_TO']) && $arFields['TZ_TO'] !== $arFields['TZ_FROM'] ) { $tzTo = Util::prepareTimezone($arFields['TZ_TO']); $arFields['TZ_TO'] = $tzTo->getName(); } else { $arFields['TZ_TO'] = $tzFrom->getName(); } $arFields['TZ_FROM'] = $tzFrom->getName(); } if ($bNew && !$params['editInstance'] && !($arFields['DAV_XML_ID'] ?? null)) { $arFields['DAV_XML_ID'] = UidGenerator::createInstance() ->setDate( new Date( Util::getDateObject( $arFields['DATE_FROM'], $arFields['SKIP_TIME'] ?? null, $arFields['TZ_FROM'] ?? null, ) ) ) ->setPortalName(Util::getServerName()) ->setUserId((int)$arFields['OWNER_ID']) ->getUidWithDate(); } elseif ($params['editInstance']) { $arFields['DAV_XML_ID'] = $params['currentEvent']['DAV_XML_ID']; } // Set version if (!isset($arFields['VERSION']) || ($arFields['VERSION'] <= ($curEvent['VERSION'] ?? null))) { $arFields['VERSION'] = ($curEvent['VERSION'] ?? null) ? $curEvent['VERSION'] + 1 : 1 ; } if ($params['autoDetectSection'] && $sectionId <= 0 && $arFields['OWNER_ID'] > 0) { $res = CCalendarSect::GetList( [ 'arFilter' => [ 'CAL_TYPE' => $arFields['CAL_TYPE'], 'OWNER_ID' => $arFields['OWNER_ID'], ], 'checkPermissions' => false, ] ); if ($res && is_array($res) && isset($res[0])) { $sectionId = $res[0]['ID']; } else { $defCalendar = CCalendarSect::CreateDefault(array( 'type' => $arFields['CAL_TYPE'], 'ownerId' => $arFields['OWNER_ID'], )); $sectionId = $defCalendar['ID']; self::SetCurUserMeetingSection($defCalendar['ID']); $params['bAffectToDav'] = false; } if ($sectionId > 0) { $arFields['SECTIONS'] = [$sectionId]; } else { return false; } } $bExchange = self::IsExchangeEnabled() && $arFields['CAL_TYPE'] === 'user'; $bCalDav = self::IsCalDAVEnabled() && $arFields['CAL_TYPE'] === 'user'; if ( (($params['editNextEvents'] ?? null) === false && ($params['recursionEditMode'] ?? null) === 'next') || (in_array($params['recursionEditMode'] ?? null, ['this', 'skip']) && ($params['editInstance'] ?? null) === false) ) { $params['modeSync'] = false; if (($params['editParentEvents'] ?? null) === true) { $params['modeSync'] = true; } } if ( ( ($params['bAffectToDav'] ?? null) !== false && ($bExchange || $bCalDav) && $sectionId > 0 && !(isset($params['dontSyncParent']) && $params['dontSyncParent']) && ($params['overSaving'] ?? false) !== true ) || $params['syncCaldav'] ) { $davParams = [ 'bCalDav' => $bCalDav, 'bExchange' => $bExchange, 'sectionId' => $sectionId, 'modeSync' => $params['modeSync'], 'editInstance' => $params['editInstance'], 'originalDavXmlId' => $params['originalDavXmlId'], 'instanceTz' => $params['instanceTz'], 'editParentEvents' => $params['editParentEvents'], 'editNextEvents' => $params['editNextEvents'], 'syncCaldav' => $params['syncCaldav'], 'parentDateFrom' => ($params['parentDateFrom'] ?? null), 'parentDateTo' => ($params['parentDateTo'] ?? null), ]; $res = CCalendarSync::DoSaveToDav( $arFields, $davParams, $curEvent); if ($res !== true && self::$silentErrorMode === true) { self::ThrowError($res); } } $params['arFields'] = $arFields; $params['userId'] = $userId; $params['path'] = self::GetPath($arFields['CAL_TYPE'], $arFields['OWNER_ID'], 1); $isSharingEvent = isset($curEvent['EVENT_TYPE']) && in_array($curEvent['EVENT_TYPE'], SharingEventManager::getSharingEventTypes(), true) ; $params['isSharingEvent'] = $isSharingEvent; if (!empty($arFields['ID']) && $isSharingEvent) { SharingEventManager::onSharingEventEdit($arFields); } if ( $curEvent && in_array(($params['recursionEditMode'] ?? null), ['this', 'next'], true) && CCalendarEvent::CheckRecurcion($curEvent) ) { // Edit only current instance of the set of recurrent events if ($params['recursionEditMode'] === 'this') { // 1. Edit current reccurent event: exclude current date $excludeDates = CCalendarEvent::GetExDate($curEvent['EXDATE']); $excludeDate = self::Date( self::Timestamp($params['currentEventDateFrom'] ?? $arFields['DATE_FROM']), false ); $excludeDates[] = $excludeDate; $saveEventData = [ 'recursionEditMode' => 'skip', 'silentErrorMode' => $params['silentErrorMode'], 'sendInvitesToDeclined' => $params['sendInvitesToDeclined'], 'sendInvitations' => false, 'sendEditNotification' => false, 'userId' => $userId, 'requestUid' => $params['requestUid'] ?? null, 'checkPermission' => $checkPermission, ]; $arFieldsCurrent = [ 'ID' => $curEvent["ID"], 'EXDATE' => CCalendarEvent::SetExDate($excludeDates), ]; if ( !empty($params['arFields']['SECTIONS'][0]) && (int)$curEvent['SECTION_ID'] !== (int)$params['arFields']['SECTIONS'][0] ) { $arFieldsCurrent['SECTIONS'] = $params['arFields']['SECTIONS']; $arFieldsCurrent['CAL_TYPE'] = $params['arFields']['CAL_TYPE']; $arFieldsCurrent['OWNER_ID'] = $userId; } $saveEventData['arFields'] = $arFieldsCurrent; // Save current event $id = self::SaveEvent($saveEventData); // 2. Copy event with new changes, but without recursion $newParams = $params; if (!($newParams['arFields']['MEETING']['REINVITE'] ?? null)) { $newParams['saveAttendeesStatus'] = true; } $newParams['arFields']['RECURRENCE_ID'] = $curEvent['RECURRENCE_ID'] ?: $newParams['arFields']['ID']; $newParams['arFields']['ORIGINAL_RECURSION_ID'] = (int)($curEvent['ORIGINAL_RECURSION_ID'] ?? $newParams['arFields']['ID']); unset( $newParams['arFields']['ID'], $newParams['arFields']['DAV_XML_ID'], $newParams['arFields']['G_EVENT_ID'], $newParams['arFields']['SYNC_STATUS'], $newParams['arFields']['CAL_DAV_LABEL'], $newParams['arFields']['RRULE'], $newParams['arFields']['EXDATE'], $newParams['recursionEditMode'], ); $newParams['arFields']['REMIND'] = $params['currentEvent']['REMIND']; $fromTs = self::Timestamp($newParams['currentEventDateFrom']); $currentFromTs = self::Timestamp($newParams['arFields']['DATE_FROM']); $length = self::Timestamp($newParams['arFields']['DATE_TO']) - self::Timestamp($newParams['arFields']['DATE_FROM']); if (!isset($newParams['arFields']['DATE_FROM'], $newParams['arFields']['DATE_TO'])) { $length = $curEvent['DT_LENGTH']; $currentFromTs = self::Timestamp($curEvent['DATE_FROM']); } $instanceDate = !isset($newParams['arFields']['DATE_FROM']) || self::Date(self::Timestamp($curEvent['DATE_FROM']), false) === self::Date($currentFromTs, false); if ($newParams['arFields']['SKIP_TIME']) { if ($instanceDate) { $newParams['arFields']['DATE_FROM'] = self::Date($fromTs, false); $newParams['arFields']['DATE_TO'] = self::Date($fromTs + $length - self::GetDayLen(), false); } else { $newParams['arFields']['DATE_FROM'] = self::Date($currentFromTs, false); $newParams['arFields']['DATE_TO'] = self::Date($currentFromTs + $length - self::GetDayLen(), false); } } elseif ($instanceDate) { $newFromTs = self::DateWithNewTime($currentFromTs, $fromTs); $newParams['arFields']['DATE_FROM'] = self::Date($newFromTs); $newParams['arFields']['DATE_TO'] = self::Date($newFromTs + $length); } $eventMod = $curEvent; if (!isset($eventMod['~DATE_FROM'])) { $eventMod['~DATE_FROM'] = $eventMod['DATE_FROM']; } $eventMod['DATE_FROM'] = $newParams['currentEventDateFrom']; $commentXmlId = CCalendarEvent::GetEventCommentXmlId($eventMod); $newParams['arFields']['RELATIONS'] = array('COMMENT_XML_ID' => $commentXmlId); $newParams['editInstance'] = true; $newParams['sendEditNotification'] = true; //original instance date start $newParams['arFields']['ORIGINAL_DATE_FROM'] = self::GetOriginalDate( $params['currentEvent']['DATE_FROM'], $eventMod['DATE_FROM'], $newParams['arFields']['TZ_FROM'] ); $newParams['originalDavXmlId'] = $params['currentEvent']['G_EVENT_ID']; $newParams['instanceTz'] = $params['currentEvent']['TZ_FROM']; $newParams['parentDateFrom'] = $params['currentEvent']['DATE_FROM']; $newParams['parentDateTo'] = $params['currentEvent']['DATE_TO']; $newParams['requestUid'] = $params['requestUid'] ?? null; $newParams['sendInvitesToDeclined'] = $params['sendInvitesToDeclined'] ?? null; // check instance changes from original recurrent event $eventMod['DATE_FROM'] = $newParams['arFields']['ORIGINAL_DATE_FROM']; $eventMod['DATE_TO'] = self::Date( self::Timestamp($eventMod['DATE_FROM'], false) + $curEvent['DT_LENGTH'] ); unset($eventMod['RRULE']); $instanceChanges = CCalendarEvent::CheckEntryChanges($newParams['arFields'], $eventMod); if (!empty($instanceChanges)) { $newParams['instanceChanges'] = $instanceChanges; } $result['recEventId'] = self::SaveEvent($newParams); (new Core\Managers\EventOriginalRecursion())->add( $result['recEventId'] ?? 0, $newParams['arFields']['ORIGINAL_RECURSION_ID'] ?? 0, ); } // Edit all next instances of the set of recurrent events elseif(($params['recursionEditMode']) === 'next') { $currentDateTimestamp = self::Timestamp($params['currentEventDateFrom'] ?? null); // Copy event with new changes $newParams = $params; $recId = $curEvent['RECURRENCE_ID'] ?: $newParams['arFields']['ID']; $newParams['arFields']['RELATIONS'] ??= []; $newParams['arFields']['RELATIONS'] = [ 'ORIGINAL_RECURSION_ID' => $curEvent['RELATIONS']['ORIGINAL_RECURSION_ID'] ?? $recId, ]; $newParams['arFields']['ORIGINAL_RECURSION_ID'] = (int)($curEvent['ORIGINAL_RECURSION_ID'] ?? $recId); if (empty($newParams['arFields']['MEETING']['REINVITE'])) { $newParams['saveAttendeesStatus'] = true; } $currentFromTs = self::Timestamp($newParams['arFields']['DATE_FROM'] ?? null); $length = self::Timestamp($newParams['arFields']['DATE_TO']) - self::Timestamp($newParams['arFields']['DATE_FROM']); if (!isset($newParams['arFields']['DATE_FROM'], $newParams['arFields']['DATE_TO'])) { $length = $curEvent['DT_LENGTH']; $currentFromTs = self::Timestamp($curEvent['DATE_FROM']); } // Check location changes [$isRecurrentLocationChanged, $newParams] = self::checkRecurrenceLocationChanges($newParams, $curEvent); // Check name changes $isRecurrentNameChanged = !empty($newParams['arFields']['NAME']) && ($curEvent['NAME'] !== $newParams['arFields']['NAME']); // Check time changes $isRecurrentTimeChanged = (self::Timestamp($curEvent['DATE_FROM'], false) % self::DAY_LENGTH) !== ($currentFromTs % self::DAY_LENGTH); // Check attendees changes $isRecurrentAttendeesChanged = self::checkRecurrenceAttendeesChanges($newParams, $curEvent); $instanceDate = !isset($newParams['arFields']['DATE_FROM']) || self::Date(self::Timestamp($curEvent['DATE_FROM']), false) === self::Date($currentFromTs, false) ; if ($newParams['arFields']['SKIP_TIME']) { if ($instanceDate) { $newParams['arFields']['DATE_FROM'] = self::Date($currentDateTimestamp, false); $newParams['arFields']['DATE_TO'] = self::Date($currentDateTimestamp + $length, false); } else { $newParams['arFields']['DATE_FROM'] = self::Date($currentFromTs, false); $newParams['arFields']['DATE_TO'] = self::Date($currentFromTs + $length, false); } } elseif ($instanceDate) { $newFromTs = self::DateWithNewTime($currentFromTs, $currentDateTimestamp); $newParams['arFields']['DATE_FROM'] = self::Date($newFromTs); $newParams['arFields']['DATE_TO'] = self::Date($newFromTs + $length); } if (isset($curEvent['EXDATE']) && $curEvent['EXDATE'] !== '') { $newParams['arFields']['EXDATE'] = $curEvent['EXDATE']; } if (!isset($newParams['arFields']['REMIND']) && !empty($curEvent['REMIND'])) { $newParams['arFields']['REMIND'] = $curEvent['REMIND']; } if (isset($newParams['arFields']['RRULE']['COUNT']) && $newParams['arFields']['RRULE']['COUNT'] > 0) { $countParams = [ 'rrule' => $newParams['arFields']['RRULE'], 'dateFrom' => $curEvent['DATE_FROM'], 'dateTo' => $newParams['arFields']['DATE_FROM'], 'timeZone' => $curEvent['TZ_FROM'], ]; $newParams['arFields']['RRULE']['COUNT'] = self::CountNumberFollowEvents($countParams); unset($newParams['arFields']['RRULE']['UNTIL'], $newParams['arFields']['RRULE']['~UNTIL']); } if ( isset($newParams['arFields']['RRULE']['FREQ'], $curEvent['RRULE']['FREQ']) && $newParams['arFields']['RRULE']['FREQ'] === 'WEEKLY' && $curEvent['RRULE']['FREQ'] === 'WEEKLY' && $newParams['arFields']['RRULE']['BYDAY'] === $curEvent['RRULE']['BYDAY'] ) { $currentDate = new Type\Date($params['currentEventDateFrom']); $currentFromDate = new Type\Date($newParams['arFields']['DATE_FROM']); $currentDateWeekday = self::WeekDayByInd($currentDate->format('N')); $currentFromDateWeekday = self::WeekDayByInd($currentFromDate->format('N')); if (isset($newParams['arFields']['RRULE']['BYDAY'][$currentDateWeekday])) { unset($newParams['arFields']['RRULE']['BYDAY'][$currentDateWeekday]); } $newParams['arFields']['RRULE']['BYDAY'][$currentFromDateWeekday] = $currentFromDateWeekday; } // Check if it's first instance of the series, so we shouldn't create another event if (self::Date(self::Timestamp($curEvent['DATE_FROM']), false) === self::Date($currentDateTimestamp, false)) { $newParams['recursionEditMode'] = 'skip'; } else { // 1. Edit current recurrent event: set finish date with date of current instance $arFieldsCurrent = [ "ID" => $curEvent["ID"], "RRULE" => CCalendarEvent::ParseRRULE($curEvent['RRULE']), ]; $arFieldsCurrent['RRULE']['UNTIL'] = self::Date($currentDateTimestamp - self::GetDayLen(), false); unset($arFieldsCurrent['RRULE']['~UNTIL'], $arFieldsCurrent['RRULE']['COUNT']); if ( !empty($params['arFields']['SECTIONS'][0]) && (int)$curEvent['SECTION_ID'] !== (int)$params['arFields']['SECTIONS'][0] ) { $arFieldsCurrent['SECTIONS'] = $params['arFields']['SECTIONS']; } // Save current event $id = self::SaveEvent([ 'arFields' => $arFieldsCurrent, 'silentErrorMode' => $params['silentErrorMode'] ?? null, 'recursionEditMode' => 'skip', 'sendInvitations' => false, 'sendEditNotification' => false, 'sendInvitesToDeclined' => $params['sendInvitesToDeclined'] ?? null, 'userId' => $userId, 'editNextEvents' => true, 'editParentEvents' => true, 'checkPermission' => $checkPermission, 'requestUid' => $params['requestUid'] ?? null, 'checkLocationOccupancyFields' => $newParams['arFields'], 'checkLocationOccupancy' => $params['checkLocationOccupancy'] ?? false, ]); unset( $newParams['arFields']['ID'], $newParams['arFields']['DAV_XML_ID'], $newParams['arFields']['G_EVENT_ID'], $newParams['recursionEditMode'] ); } if (empty($newParams['arFields']['DAV_XML_ID'])) { $newParams['arFields']['DAV_XML_ID'] = UidGenerator::createInstance() ->setPortalName(Util::getServerName()) ->setDate(new Date( Util::getDateObject( $newParams['arFields']['ORIGINAL_DATE_FROM'] ?? null, $newParams['arFields']['SKIP_TIME'] ?? null, $newParams['arFields']['TZ_FROM'] ?? null ) )) ->setUserId((int)($newParams['arFields']['OWNER_ID'] ?? null)) ->getUidWithDate() ; } $newParams['sendInvitesToDeclined'] = $params['sendInvitesToDeclined']; $newParams['editNextEvents'] = true; $newParams['previousRecurrentId'] = $recId; $result = self::SaveEvent($newParams); if (!is_array($result)) { $result = [ 'id' => $result, 'recEventId' => $result, ]; } (new Core\Managers\EventOriginalRecursion())->add( $result['id'] ?? 0, $newParams['arFields']['ORIGINAL_RECURSION_ID'] ?? 0, ); if ($recId) { $recRelatedEvents = CCalendarEvent::GetEventsByRecId($recId, false); foreach($recRelatedEvents as $recRelatedEvent) { if ($recRelatedEvent['ID'] === $result['id']) { continue; } if ((int)$recRelatedEvent['ID'] !== (int)$recRelatedEvent['PARENT_ID']) { continue; } $evFromTs = self::Timestamp($recRelatedEvent['DATE_FROM'], false); $instanceLength = $recRelatedEvent['DT_LENGTH'] ?? $length; if ($evFromTs > $currentDateTimestamp) { $newParams['arFields']['ID'] = $recRelatedEvent['ID']; $newParams['arFields']['RRULE'] = CCalendarEvent::ParseRRULE($recRelatedEvent['RRULE']); /* * Set correct date for related * if full day event - set related date * else if recurrent event time change - set related date and recurrent event time * else - keep related date and related time */ if ($newParams['arFields']['SKIP_TIME']) { $newParams['arFields']['DATE_FROM'] = self::Date($evFromTs, false); $newParams['arFields']['DATE_TO'] = self::Date(self::Timestamp($recRelatedEvent['DATE_TO']), false); } else if ($isRecurrentTimeChanged) { $newFromTs = self::DateWithNewTime($currentFromTs, $evFromTs); $newParams['arFields']['DATE_FROM'] = self::Date($newFromTs); $newParams['arFields']['DATE_TO'] = self::Date($newFromTs + $length); } else { $newParams['arFields']['DATE_FROM'] = self::Date($evFromTs); $newParams['arFields']['DATE_TO'] = self::Date($evFromTs + $instanceLength); } /* * Set correct location for related * Entry condition - location on recurrent was not changed * if related has no location or different - keep it */ $newRecurrentLocation = null; if (!$isRecurrentLocationChanged) { $newRecurrentLocation = $newParams['arFields']['LOCATION'] ?? ''; if (!empty($recRelatedEvent['LOCATION'])) { $parsedRelatedLocation = Rooms\Util::parseLocation($recRelatedEvent['LOCATION']); if (!empty($parsedRelatedLocation['room_event_id'])) { $recRelatedEvent['LOCATION'] = 'calendar_' . $parsedRelatedLocation['room_id']; } } $newParams['arFields']['LOCATION'] = $recRelatedEvent['LOCATION'] ?? ''; } /* * Set correct name for related */ if (!$isRecurrentNameChanged) { $newParams['arFields']['NAME'] = $recRelatedEvent['NAME'] ?? $newParams['arFields']['NAME']; } /* * Set correct attendees for related * If recurrent attendees has not changed - keep related attendees */ if (!$isRecurrentAttendeesChanged) { $newParams['arFields']['ATTENDEES_CODES'] = $recRelatedEvent['ATTENDEES_CODES'] ?? $newParams['arFields']['ATTENDEES_CODES'] ; } /* * Set additional params */ $newParams['arFields']['RECURRENCE_ID'] = $result['id']; $newParams['originalDavXmlId'] = $result['originalDavXmlId']; $parentDateTime = self::Date($currentFromTs); $newParams['arFields']['ORIGINAL_DATE_FROM'] = self::GetOriginalDate( $parentDateTime, $recRelatedEvent['ORIGINAL_DATE_FROM'] ?? $newParams['currentEventDateFrom'], $result['instanceTz'] ); $newParams['instanceTz'] = $result['instanceTz']; $newParams['editInstance'] = true; unset($newParams['arFields']['EXDATE']); if (isset($newParams['arFields']['RELATIONS']['ORIGINAL_RECURSION_ID'])) { unset($newParams['arFields']['RELATIONS']); } self::SaveEvent($newParams); if ($newRecurrentLocation !== null) { $newParams['arFields']['LOCATION'] = $newRecurrentLocation; } } } } } } else { if (($params['recursionEditMode'] ?? null) !== 'all') { $params['recursionEditMode'] = 'skip'; } else { $params['recursionEditMode'] = ''; } $id = CCalendarEvent::Edit($params); if ($id) { $UFs = $params['UF'] ?? null; if(!empty($UFs) && is_array($UFs)) { CCalendarEvent::UpdateUserFields($id, $UFs, false); if (!empty($arFields['IS_MEETING']) && !empty($UFs['UF_WEBDAV_CAL_EVENT'])) { $UF = $GLOBALS['USER_FIELD_MANAGER']->GetUserFields("CALENDAR_EVENT", $id, LANGUAGE_ID); self::UpdateUFRights( $UFs['UF_WEBDAV_CAL_EVENT'], $arFields['ATTENDEES_CODES'] ?? null, $UF['UF_WEBDAV_CAL_EVENT'] ?? null ); } CCalendarEvent::updateSearchIndex((int)$id, [ 'updateAllByParent' => true, ]); } } if ($params['editNextEvents'] === true && $params['editParentEvents'] === false) { $result['originalDate'] = $params['arFields']['DATE_FROM']; $result['originalDavXmlId'] = $params['arFields']['DAV_XML_ID']; $result['instanceTz'] = $params['arFields']['TZ_FROM']; $result['recEventId'] = $id; } // Here we should select all events connected with edited via RECURRENCE_ID: // It could be original source event (without RECURRENCE_ID) or sibling events if ( $curEvent && !$params['recursionEditMode'] && !($params['arFields']['RECURRENCE_ID'] ?? null) && CCalendarEvent::CheckRecurcion($curEvent) ) { $events = []; $recId = $curEvent['RECURRENCE_ID'] ?: $curEvent['ID']; /** * @deprecated potentially */ // if ($curEvent['RECURRENCE_ID'] && $curEvent['RECURRENCE_ID'] !== $curEvent['ID']) // { // $masterEvent = CCalendarEvent::GetById($curEvent['RECURRENCE_ID']); // if ($masterEvent) // { // $events[] = $masterEvent; // } // } $currentFromTs = self::Timestamp($params['arFields']['DATE_FROM'], false); $length = self::Timestamp($params['arFields']['DATE_TO'], false) - self::Timestamp($params['arFields']['DATE_FROM'], false); if (!isset($params['arFields']['DATE_FROM'], $params['arFields']['DATE_TO'])) { $length = $curEvent['DT_LENGTH']; $currentFromTs = self::Timestamp($curEvent['DATE_FROM']); } // Check location changes [$isRecurrentLocationChanged, $params] = self::checkRecurrenceLocationChanges($params, $curEvent); // Check name changes $isRecurrentNameChanged = !empty($params['arFields']['NAME']) && ($curEvent['NAME'] !== $params['arFields']['NAME']); // Check time changes $isRecurrentTimeChanged = (self::Timestamp($curEvent['DATE_FROM'], false) % self::DAY_LENGTH) !== ($currentFromTs % self::DAY_LENGTH); // Check attendees changes $isRecurrentAttendeesChanged = self::checkRecurrenceAttendeesChanges($params, $curEvent); if ($recId) { $instances = CCalendarEvent::GetEventsByRecId($recId, false); if ($instances) { $events = array_merge($events, $instances); } } foreach($events as $ev) { if ($ev['ID'] === $curEvent['ID']) { continue; } if ($ev['PARENT_ID'] !== $ev['ID']) { continue; } $newParams = $params; $newParams['arFields']['ID'] = $ev['ID']; $newParams['arFields']['RECURRENCE_ID'] = $ev['RECURRENCE_ID']; $newParams['arFields']['DAV_XML_ID'] = $ev['DAV_XML_ID']; $newParams['arFields']['G_EVENT_ID'] = $ev['G_EVENT_ID']; $newParams['arFields']['ORIGINAL_DATE_FROM'] = self::GetOriginalDate($arFields['DATE_FROM'], $ev['ORIGINAL_DATE_FROM'], $arFields['TZ_FROM']); $newParams['arFields']['CAL_DAV_LABEL'] = $ev['CAL_DAV_LABEL']; $newParams['arFields']['RRULE'] = CCalendarEvent::ParseRRULE($ev['RRULE']); $newParams['recursionEditMode'] = 'skip'; $newParams['currentEvent'] = $ev; $eventFromTs = self::Timestamp($ev['DATE_FROM']); $instanceLength = $ev['DT_LENGTH'] ?? $length; /* * Set correct date for related * if full day event - set related date * else if recurrent event time change - set related date and recurrent event time * else - keep related date and related time */ if ($newParams['arFields']['SKIP_TIME']) { $newParams['arFields']['DATE_FROM'] = $ev['DATE_FROM']; $newParams['arFields']['DATE_TO'] = self::Date($eventFromTs + $length, false); } else if ($isRecurrentTimeChanged) { $newFromTs = self::DateWithNewTime($currentFromTs, $eventFromTs); $newParams['arFields']['DATE_FROM'] = self::Date($newFromTs); $newParams['arFields']['DATE_TO'] = self::Date($newFromTs + $length); } else { $newParams['arFields']['DATE_FROM'] = self::Date($eventFromTs); $newParams['arFields']['DATE_TO'] = self::Date($eventFromTs + $instanceLength); } /* * Set correct location for related * Entry condition - location on recurrent was not changed * if related has no location or different - keep it */ if (!$isRecurrentLocationChanged) { if (!empty($ev['LOCATION'])) { $parsedRelatedLocation = Rooms\Util::parseLocation($ev['LOCATION']); if (!empty($parsedRelatedLocation['room_event_id'])) { $ev['LOCATION'] = 'calendar_' . $parsedRelatedLocation['room_id']; } } $newParams['arFields']['LOCATION'] = $ev['LOCATION'] ?? ''; } /* * Set correct name for related */ if (!$isRecurrentNameChanged) { $newParams['arFields']['NAME'] = $ev['NAME'] ?? $newParams['arFields']['NAME']; } /* * Set correct attendees for related * If recurrent attendees has not changed - keep related attendees */ if (!$isRecurrentAttendeesChanged) { $newParams['arFields']['ATTENDEES_CODES'] = $ev['ATTENDEES_CODES'] ?? $newParams['arFields']['ATTENDEES_CODES'] ; } if (isset($ev['EXDATE']) && $ev['EXDATE']) { $newParams['arFields']['EXDATE'] = $ev['EXDATE']; } if (isset($newParams['arFields']['RELATIONS']['ORIGINAL_RECURSION_ID'])) { unset($newParams['arFields']['RELATIONS']); } self::SaveEvent($newParams); } } if ($id) { self::syncChange($id, $arFields, $params, $curEvent ?: null); } $arFields['ID'] = $id; if (($params['overSaving'] ?? false) !== true) { foreach(GetModuleEvents("calendar", "OnAfterCalendarEventEdit", true) as $arEvent) { ExecuteModuleEventEx($arEvent, array($arFields, $bNew, $userId)); } } } self::SetSilentErrorMode($silentErrorModePrev); $result['id'] = $id ?? null; return $result; } private static function CountNumberFollowEvents($params) { $curCount = self::CountPastEvents($params); $count = (int)$params['rrule']['COUNT'] - $curCount; return (string)$count; } public static function getUserLanguageId(?int $userId): string { if (!$userId) { return LANGUAGE_ID; } if (isset(self::$userLanguageId[$userId])) { return self::$userLanguageId[$userId]; } $user = Main\UserTable::query() ->where('ID', $userId) ->setSelect(['NOTIFICATION_LANGUAGE_ID']) ->exec() ->fetch() ; self::$userLanguageId[$userId] = $user['NOTIFICATION_LANGUAGE_ID'] ?? LANGUAGE_ID; return self::$userLanguageId[$userId]; } public static function CountPastEvents($params) { $curCount = 0; $dateFromTz = !empty($params['timeZone']) ? new \DateTimeZone($params['timeZone']) : new \DateTimeZone("UTC"); $dateToTz = !empty($params['timeZone']) ? new \DateTimeZone($params['timeZone']) : new \DateTimeZone("UTC"); $dateFrom = new Main\Type\DateTime(date('Ymd His',self::Timestamp($params['dateFrom'])), 'Ymd His', $dateFromTz); $dateTo = new Main\Type\DateTime(date('Ymd His',self::Timestamp($params['dateTo'])), 'Ymd His', $dateToTz); $parentInfoDate = getdate($dateFrom->getTimestamp()); $dateTo->setTime($parentInfoDate['hours'], $parentInfoDate['minutes']); $diff = $dateFrom->getDiff($dateTo); if ($params['rrule']['FREQ'] === 'DAILY') { $diff = (int)$diff->format('%a'); $curCount = $diff / (int)$params['rrule']['INTERVAL']; } if ($params['rrule']['FREQ'] === 'WEEKLY') { $diff = (int)$diff->format('%a'); for ($i = 0; $i < $diff; $i++) { $timestamp = $dateFrom->getTimestamp(); $date = getdate($timestamp); $weekday = mb_strtoupper(mb_substr($date['weekday'], 0, 2)); if (in_array($weekday, $params['rrule']['BYDAY'], true)) { $curCount++; } $dateFrom = $dateFrom->add('+1 day'); } } if ($params['rrule']['FREQ'] === 'MONTHLY') { $diff = (int)$diff->format('%m'); $curCount = $diff / (int)$params['rrule']['INTERVAL']; } if ($params['rrule']['FREQ'] === 'YEARLY') { $diff = (int)$diff->format('%y'); $curCount = $diff / (int)$params['rrule']['INTERVAL']; } return $curCount; } public static function ThrowError($str) { if (self::$silentErrorMode) { self::$errors[] = $str; return false; } global $APPLICATION; echo '<!-- BX_EVENT_CALENDAR_ACTION_ERROR:'.$str.'-->'; $APPLICATION->ThrowException($str); } public static function GetErrors() { return self::$errors; } private static ?array $tasksForUpdateUFRights = null; private static function getTasksForUpdateUFRights(): array { if (!Loader::includeModule('webdav')) { return []; } if (self::$tasksForUpdateUFRights === null) { self::$tasksForUpdateUFRights = CWebDavIblock::GetTasks() ?? []; } return self::$tasksForUpdateUFRights; } public static function UpdateUFRights($files, $rights, $ufEntity = []) { global $USER; $arTasks = self::getTasksForUpdateUFRights(); if (!is_array($rights) || empty($rights)) { return false; } if ($files===null || $files===false) { return false; } if (!is_array($files)) { $files = array($files); } if (empty($files)) { return false; } if (!Loader::includeModule('iblock') || !Loader::includeModule('webdav')) { return false; } $arFiles = []; foreach($files as $id) { $id = (int)$id; if ($id) { $arFiles[] = $id; } } if (empty($arFiles)) { return false; } $arCodes = []; foreach($rights as $value) { if (mb_strpos($value, 'SG') === 0) { $arCodes[] = $value . '_K'; } $arCodes[] = $value; } $arCodes = array_unique($arCodes); $i = 0; $arViewRights = $arEditRights = []; $curUserID = 'U'.$USER->GetID(); foreach($arCodes as $right) { if ($curUserID == $right) // do not override owner's rights continue; $key = 'n' . $i++; $arViewRights[$key] = array( 'GROUP_CODE' => $right, 'TASK_ID' => $arTasks['R'], ); } $ibe = new CIBlockElement(); $dbWDFile = $ibe->GetList([], array('ID' => $arFiles, 'SHOW_NEW' => 'Y'), false, false, array('ID', 'NAME', 'SECTION_ID', 'IBLOCK_ID', 'WF_NEW')); $iblockIds = []; if ($dbWDFile) { while ($arWDFile = $dbWDFile->Fetch()) { $id = $arWDFile['ID']; if ($arWDFile['WF_NEW'] === 'Y') { $ibe->Update($id, ['BP_PUBLISHED' => 'Y']); } if (CIBlock::GetArrayByID($arWDFile['IBLOCK_ID'], "RIGHTS_MODE") === "E") { $ibRights = CWebDavIblock::_get_ib_rights_object('ELEMENT', $id, $arWDFile['IBLOCK_ID']); $ibRights->SetRights(CWebDavTools::appendRights($ibRights, $arViewRights, $arTasks)); if(empty($iblockIds[$arWDFile['IBLOCK_ID']])) $iblockIds[$arWDFile['IBLOCK_ID']] = $arWDFile['IBLOCK_ID']; } } global $CACHE_MANAGER; foreach ($iblockIds as $iblockId) { $CACHE_MANAGER->ClearByTag('iblock_id_' . $iblockId); } unset($iblockId); } } public static function TempUser($TmpUser = false, $create = true, $ID = false) { global $USER; if ($create && $TmpUser === false && (!$USER || !is_object($USER))) { $USER = new CUser; if ($ID && (int)$ID > 0) { $USER->Authorize((int)$ID); } return $USER; } if (!$create && $USER && is_object($USER)) { unset($USER); return false; } return false; } public static function SaveSection($params) { $type = $params['arFields']['CAL_TYPE'] ?? self::$type; // Exchange if (($params['bAffectToDav'] ?? null) !== false && $type === 'user' && self::IsExchangeEnabled(self::$ownerId)) { $exchRes = true; $ownerId = $params['arFields']['OWNER_ID'] ?? self::$ownerId; if ($params['arFields']['IS_EXCHANGE']) { $exchRes = CDavExchangeCalendar::DoAddCalendar($ownerId, $params['arFields']); } if ($exchRes !== true) { if (!is_array($exchRes) || !isset($exchRes['XML_ID'])) { return self::ThrowError(self::CollectExchangeErrors($exchRes)); } // // It's ok, we successfuly save event to exchange calendar - and save it to DB $params['arFields']['DAV_EXCH_CAL'] = $exchRes['XML_ID']; $params['arFields']['DAV_EXCH_MOD'] = $exchRes['MODIFICATION_LABEL']; } } // Save here $id = (int)CCalendarSect::Edit($params); self::ClearCache(['section_list', 'event_list']); return $id; } public static function ClearCache($arPath = []) { global $CACHE_MANAGER; $CACHE_MANAGER->ClearByTag("CALENDAR_EVENT_LIST"); if (empty($arPath)) { $arPath = [ 'access_tasks', 'type_list', 'section_list', 'attendees_list', 'event_list', ]; } elseif (!is_array($arPath)) { $arPath = [$arPath]; } if (!empty($arPath)) { $cache = new CPHPCache; foreach($arPath as $path) { if ($path) { $cache->CleanDir(self::CachePath() . $path); } } } } public static function CachePath() { return self::$cachePath; } // * * * * * * * * * * * * CalDAV + Exchange * * * * * * * * * * * * * * * * public static function SyncCalendarItems($connectionType, $calendarId, $arCalendarItems): array { $arResult = []; self::$silentErrorMode = true; [$sectionId, $entityType, $entityId] = $calendarId; $entityType = mb_strtolower($entityType); if ($connectionType === Bitrix\Calendar\Sync\Caldav\Helper::EXCHANGE_TYPE) { $xmlIdField = 'DAV_EXCH_LABEL'; } elseif ($connectionType === Bitrix\Calendar\Sync\Caldav\Helper::CALDAV_TYPE) { $xmlIdField = 'CAL_DAV_LABEL'; } else { return []; } $eventsList = CCalendarEvent::GetList([ 'arSelect' => [ ...CCalendarEvent::$defaultSelectEvent, 'RECURRENCE_ID', 'DAV_XML_ID', 'DAV_EXCH_LABEL', 'CAL_DAV_LABEL', ], 'arFilter' => [ 'CAL_TYPE' => $entityType, 'OWNER_ID' => $entityId, 'SECTION' => $sectionId, ], 'getUserfields' => false, 'parseRecursion' => false, 'fetchAttendees' => false, 'fetchMeetings' => false, 'userId' => $entityType === 'user' ? $entityId : 0, ]); foreach ($eventsList as $event) { $eventXmlId = $event['DAV_XML_ID']; if ($event['RECURRENCE_ID'] && $instanceChangeKey = self::FindSyncInstance($event)) { $arCalendarItems[$eventXmlId] = $instanceChangeKey; } if (isset($arCalendarItems[$eventXmlId])) { if ($event[$xmlIdField] !== $arCalendarItems[$eventXmlId]) { $arResult[] = [ 'XML_ID' => $eventXmlId, 'ID' => $event['ID'], ]; } unset($arCalendarItems[$eventXmlId]); } elseif ($connectionType === Bitrix\Calendar\Sync\Caldav\Helper::EXCHANGE_TYPE) { if ((int)$event['ID'] === (int)$event['PARENT_ID']) { self::DeleteCalendarEvent($event["ID"], self::$userId); } } else { self::DeleteCalendarEvent($event["ID"], self::$userId); } } foreach ($arCalendarItems as $key => $value) { $arResult[] = [ 'XML_ID' => $key, 'ID' => 0, ]; } self::$silentErrorMode = false; return $arResult; } private static function FindSyncInstance($event) { $exchangeScheme = COption::GetOptionString('dav', 'exchange_scheme', 'http'); $exchangeServer = COption::GetOptionString('dav', 'exchange_server', ''); $exchangePort = COption::GetOptionString('dav', 'exchange_port', '80'); $exchangeUsername = COption::GetOptionString('dav', 'exchange_username', ''); $exchangePassword = COption::GetOptionString('dav', 'exchange_password', ''); if (empty($exchangeServer)) { return ''; } $exchange = new CDavExchangeCalendar($exchangeScheme, $exchangeServer, $exchangePort, $exchangeUsername, $exchangePassword); $params = [ 'dateTo' => $event['DATE_TO'], 'parentDateTo' => $event['DATE_TO'], 'dateFrom' => $event['DATE_FROM'], 'parentDateFrom' => $event['DATE_FROM'], 'parentTz' => $event['TZ_FROM'], 'changekey' => $event['DAV_EXCH_LABEL'], ]; [ , $changeKey] = $exchange->FindInstance($params); return $changeKey; } public static function DeleteCalendarEvent($eventId, $userId, $oEvent = false) { return CCalendarEvent::Delete(array( 'id' => $eventId, 'userId' => $userId, 'bMarkDeleted' => true, 'Event' => $oEvent, )); } public static function Color($color = '', $defaultColor = true) { if ((string)$color !== '') { $color = ltrim(trim(preg_replace('/\W/', '', $color)), "#"); if (mb_strlen($color) > 6) { $color = mb_substr($color, 0, 6); } elseif(mb_strlen($color) < 6) { $color = ''; } } $color = '#'.$color; // Default color $DEFAULT_COLOR = '#9dcf00'; if ($color === '#') { if ($defaultColor === true) { $color = $DEFAULT_COLOR; } elseif($defaultColor) { $color = $defaultColor; } else { $color = ''; } } return $color; } // Called from CalDav, Exchange methods public static function FormatTime($h = 0, $m = 0) { $m = (int)$m; if ($m > 59) { $m = 59; } elseif ($m < 0) { $m = 0; } if ($m < 10) { $m = '0' . $m; } $h = (int)$h; if ($h > 24) { $h = 24; } if ($h < 0) { $h = 0; } if (IsAmPmMode()) { $ampm = 'am'; if ($h == 0) { $h = 12; } else if ($h == 12) { $ampm = 'pm'; } else if ($h > 12) { $ampm = 'pm'; $h -= 12; } $res = $h.':'.$m.' '.$ampm; } else { $res = (($h < 10) ? '0' : '').$h.':'.$m; } return $res; } // Called from SaveEvent: try to save event in Exchange or to Dav Server and if it's Ok, return true public static function GetUserId() { if (!self::$userId) { self::$userId = self::GetCurUserId(); } return self::$userId; } // Called from CalDav sync methods public static function GetUserAvatarSrc($user = [], $params = []) { if (!is_array($user) && (int)$user > 0) { $user = self::GetUser($user); } $avatar_src = self::GetUserAvatar($user, $params); if ($avatar_src === false) { $avatar_src = (isset($params['fillAvatar']) && $params['fillAvatar'] === false ? '' : '/bitrix/images/1.gif'); } return $avatar_src; } public static function GetUserAvatar($user = [], $params = []) { if (!is_array($user) && (int)$user > 0) { $user = self::GetUser($user); } if (!empty($user["PERSONAL_PHOTO"])) { if (empty($params['AVATAR_SIZE'])) { $params['AVATAR_SIZE'] = 42; } $arFileTmp = CFile::ResizeImageGet( $user["PERSONAL_PHOTO"], array('width' => $params['AVATAR_SIZE'], 'height' => $params['AVATAR_SIZE']), BX_RESIZE_IMAGE_EXACT, false, false, true ); $avatar_src = $arFileTmp['src']; } else { $avatar_src = false; } return $avatar_src; } public static function GetUserUrl($userId = 0, $pathToUser = "") { if ($pathToUser == '') { if (self::$pathToUser == '') { if (empty(self::$pathesForSite)) { self::$pathesForSite = self::GetPathes(SITE_ID); } self::$pathToUser = self::$pathesForSite['path_to_user']; } $pathToUser = self::$pathToUser; } return CUtil::JSEscape(CComponentEngine::MakePathFromTemplate($pathToUser, array("user_id" => $userId, "USER_ID" => $userId))); } public static function GetAccessTasksByName($binging = 'calendar_section', $name = 'calendar_denied') { $arTasks = self::GetAccessTasks($binging); foreach($arTasks as $id => $task) { if ($task['name'] == $name) { return $id; } } return false; } public static function GetAccessTasks($binging = 'calendar_section', $type = '') { \Bitrix\Main\Localization\Loc::loadLanguageFile($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/calendar/admin/task_description.php"); if (isset(self::$arAccessTask[$binging]) && is_array(self::$arAccessTask[$binging])) { return self::$arAccessTask[$binging]; } $bIntranet = self::IsIntranetEnabled(); $arTasks = []; $res = CTask::GetList(Array('ID' => 'asc'), Array('MODULE_ID' => 'calendar', 'BINDING' => $binging)); while($arRes = $res->Fetch()) { if($type === 'location') { if ( mb_strtolower($arRes['NAME']) === 'calendar_view_time' || mb_strtolower($arRes['NAME']) === 'calendar_view_title' ) { continue; } $name = ''; if ($arRes['SYS']) { if ( mb_strtolower($arRes['NAME']) === 'calendar_edit' || mb_strtolower($arRes['NAME']) === 'calendar_view' || mb_strtolower($arRes['NAME']) === 'calendar_type_edit' || mb_strtolower($arRes['NAME']) === 'calendar_type_view') { $name = Loc::getMessage('TASK_NAME_LOCATION_'.mb_strtoupper($arRes['NAME'])); } else { $name = Loc::getMessage('TASK_NAME_'.mb_strtoupper($arRes['NAME'])); } } } else { if ( !$bIntranet && ( mb_strtolower($arRes['NAME']) === 'calendar_view_time' || mb_strtolower($arRes['NAME']) === 'calendar_view_title' ) ) { continue; } $name = ''; if ($arRes['SYS']) { $name = Loc::getMessage('TASK_NAME_' . mb_strtoupper($arRes['NAME'])); } } if ($name == '') { $name = $arRes['NAME']; } $arTasks[$arRes['ID']] = array( 'name' => $arRes['NAME'], 'title' => $name, ); } return $arTasks; } public static function PushAccessNames($arCodes = []) { foreach($arCodes as $code) { if (!array_key_exists($code, self::$accessNames)) { self::$accessNames[$code] = null; } } } public static function GetOuterUrl() { return self::$outerUrl; } public static function AddConnection($connection, $type = 'caldav') { if((!self::CheckCalDavUrl($connection['link'], $connection['user_name'], $connection['pass']))) { return Loc::getMessage('EC_CAL_OPERATION_CANNOT_BE_PERFORMED'); } $arFields = [ 'ENTITY_TYPE' => 'user', 'ENTITY_ID' => $connection['user_id'], 'ACCOUNT_TYPE' => Bitrix\Calendar\Sync\Caldav\Helper::CALDAV_TYPE, 'NAME' => $connection['name'], 'SERVER' => $connection['link'], 'SERVER_USERNAME' => $connection['user_name'], 'SERVER_PASSWORD' => $connection['pass'], ]; \CDavConnection::ParseFields($arFields); $davConnection = \CDavConnection::getList( ['ID' => 'ASC'], [ 'SERVER_HOST' => $arFields['SERVER_HOST'], 'SERVER_PATH' => $arFields['SERVER_PATH'], 'ENTITY_ID' => $arFields['ENTITY_ID'], ], false, ['nTopCount' => 1] ); if ($con = $davConnection->fetch()) { \CDavConnection::Update($con['ID'], $arFields); return true; } \CDavConnection::Add($arFields); return true; } public static function CheckCalDavUrl($url, $username, $password) { $arServer = [ 'host' => null, 'scheme' => null, 'port' => null, 'path' => null, ]; $parsedUrl = parse_url($url); $arServer = array_merge($arServer, $parsedUrl); // Mantis #71074 if ( mb_strpos(mb_strtolower($_SERVER['SERVER_NAME']), mb_strtolower($arServer['host'])) !== false || mb_strpos(mb_strtolower($_SERVER['HTTP_HOST']), mb_strtolower($arServer['host'])) !== false ) { return false; } if (Loader::includeModule("dav")) { return \CDavGroupdavClientCalendar::DoCheckCalDAVServer($arServer["scheme"], $arServer["host"], $arServer["port"], $username, $password, $arServer["path"]); } return false; } public static function RemoveConnection(array $params = []) { if (Loader::includeModule('dav')) { $sections = self::getSectionsByConnectionId($params['id']); $connection = CDavConnection::GetList( ["ID" => "ASC"], ["ID" => $params['id']] ); if (is_array($sections)) { foreach ($sections as $section) { if ($params['del_calendars'] /*&& $section['IS_LOCAL'] !== 'Y'*/) { CCalendarSect::Delete($section['ID'], false); } else { self::markSectionLikeDelete($section['ID']); } } } \CDavConnection::Delete($params['id']); if (is_array($connection)) { /** @var Google\Helper $googleHelper */ $googleHelper = ServiceLocator::getInstance()->get('calendar.service.google.helper'); $caldavHelper = ServiceLocator::getInstance()->get('calendar.service.caldav.helper'); $connectionType = $caldavHelper->isYandex($connection['SERVER_HOST']) ? Bitrix\Calendar\Sync\Caldav\Helper::YANDEX_TYPE : Bitrix\Calendar\Sync\Caldav\Helper::CALDAV_TYPE ; $connectionName = $googleHelper->isGoogleConnection($connection['ACCOUNT_TYPE']) ? 'google' : $connectionType . $connection['ID'] ; Util::addPullEvent( PushCommand::DeleteSyncConnection, $connection['ENTITY_ID'], [ 'syncInfo' => [ $connectionName => [ 'type' => $connectionType, ], ], 'requestUid' => Util::getRequestUid(), ] ); } } } public static function GetTypeByExternalId($externalId = false) { if ($externalId) { $res = CCalendarType::GetList(array('arFilter' => array('EXTERNAL_ID' => $externalId))); if ($res && $res[0]) { return $res[0]['XML_ID']; } } return false; } public static function SetCurUserMeetingSection($userMeetingSection) { self::$userMeetingSection = $userMeetingSection; } public static function CacheTime($time = false) { if ($time !== false) { self::$cacheTime = $time; } return self::$cacheTime; } public static function _ParseHack(&$text, &$TextParser) { $text = preg_replace(array("/\</isu", "/\>/isu"),array('<', '>'),$text); $text = preg_replace("/\<br\s*\/*\>/isu","", $text); $text = preg_replace("/\<(\w+)[^>]*\>(.+?)\<\/\\1[^>]*\>/isu","\\2",$text); $text = preg_replace("/\<*\/li\>/isu","", $text); $text = str_replace(array("<", ">"),array("<", ">"),$text); $TextParser->allow = []; return true; } public static function IsSocnetAdmin() { if (!isset(self::$bCurUserSocNetAdmin)) { self::$bCurUserSocNetAdmin = Loader::includeModule('socialnetwork') && self::IsSocNet() && CSocNetUser::IsCurrentUserModuleAdmin() ; } return self::$bCurUserSocNetAdmin; } public static function GetMaxDate() { if (!self::$CALENDAR_MAX_DATE) { $date = new DateTime(); $date->setDate(2038, 1, 1); self::$CALENDAR_MAX_DATE = self::Date($date->getTimestamp(), false); } return self::$CALENDAR_MAX_DATE; } public static function GetMinDate() { if (!self::$CALENDAR_MIN_DATE) { $date = new DateTime(); $date->setDate(1970, 1, 1); self::$CALENDAR_MIN_DATE = self::Date($date->getTimestamp(), false); } return self::$CALENDAR_MIN_DATE; } public static function GetDestinationUsers($codes, $fetchUsers = false) { if (!Main\Loader::includeModule('socialnetwork')) { return []; } $users = \CSocNetLogDestination::getDestinationUsers($codes, $fetchUsers); if ($fetchUsers) { foreach ($users as $i => $user) { $users[$i]['FORMATTED_NAME'] = self::GetUserName($user); $users[$i]['ID'] = (int)$user['ID']; } } else { foreach ($users as &$user) { if(is_numeric($user)) { $user = (int)$user; } } } return $users; } public static function GetAttendeesMessage($cnt = 0) { if ( ($cnt % 100) > 10 && ($cnt % 100) < 20 ) { $suffix = 5; } else { $suffix = $cnt % 10; } return Loc::getMessage("EC_ATTENDEE_".$suffix, Array("#NUM#" => $cnt)); } public static function GetMoreAttendeesMessage($cnt = 0) { if ( ($cnt % 100) > 10 && ($cnt % 100) < 20 ) { $suffix = 5; } else { $suffix = $cnt % 10; } return Loc::getMessage("EC_ATTENDEE_MORE_".$suffix, Array("#NUM#" => $cnt)); } public static function GetFromToHtml( $fromTs = false, $toTs = false, $skipTime = false, $dtLength = 0, $forRrule = false, $languageId = null ) { if ((int)$fromTs != $fromTs) { $fromTs = self::Timestamp($fromTs); } if ((int)$toTs != $toTs) { $toTs = self::Timestamp($toTs); } if ($toTs < $fromTs) { $toTs = $fromTs; } // Formats $formatShort = self::DFormat(false); $formatFull = self::DFormat(true); $formatTime = str_replace($formatShort, '', $formatFull); $formatTime = $formatTime == $formatFull ? "H:i" : str_replace(':s', '', $formatTime); $html = ''; $formatFull = str_replace(':s', '', $formatFull); if ($skipTime) { if ((int)$dtLength === self::DAY_LENGTH || !$dtLength) // One full day event { if (!$forRrule) { $html = FormatDate([ "tomorrow" => "tomorrow", "today" => "today", "yesterday" => "yesterday", "-" => $formatShort, "" => $formatShort, ], $fromTs, time() + CTimeZone::GetOffset(), $languageId); $html .= ', '; } $html .= Loc::getMessage('EC_VIEW_FULL_DAY', null, $languageId); } else // Event for several days { $from = FormatDate([ "tomorrow" => "tomorrow", "today" => "today", "yesterday" => "yesterday", "-" => $formatShort, "" => $formatShort, ], $fromTs, time() + CTimeZone::GetOffset(), $languageId); $to = FormatDate([ "tomorrow" => "tomorrow", "today" => "today", "yesterday" => "yesterday", "-" => $formatShort, "" => $formatShort, ], $toTs - self::DAY_LENGTH, time() + CTimeZone::GetOffset(), $languageId); $html = Loc::getMessage( 'EC_VIEW_DATE_FROM_TO', ['#DATE_FROM#' => $from, '#DATE_TO#' => $to], $languageId ); } } else { // Event during one day if(date('dmY', $fromTs) == date('dmY', $toTs)) { if (!$forRrule) { $html = FormatDate([ "tomorrow" => "tomorrow", "today" => "today", "yesterday" => "yesterday", "-" => $formatShort, "" => $formatShort, ], $fromTs, time() + CTimeZone::GetOffset(), $languageId); $html .= ', '; } $html .= Loc::getMessage( 'EC_VIEW_TIME_FROM_TO_TIME', [ '#TIME_FROM#' => FormatDate($formatTime, $fromTs, false, $languageId), '#TIME_TO#' => FormatDate($formatTime, $toTs, false, $languageId), ], $languageId ); } else { $html = Loc::getMessage( 'EC_VIEW_DATE_FROM_TO', [ '#DATE_FROM#' => FormatDate($formatFull, $fromTs, time() + CTimeZone::GetOffset(), $languageId), '#DATE_TO#' => FormatDate($formatFull, $toTs, time() + CTimeZone::GetOffset(), $languageId), ], $languageId ); } } return $html; } public static function GetSocNetDestination($user_id = false, $selected = [], $userList = []) { if (!Loader::includeModule("socialnetwork")) { return false; } global $CACHE_MANAGER; if (!is_array($selected)) { $selected = []; } if (method_exists('CSocNetLogDestination','GetDestinationSort')) { $DESTINATION = array( 'LAST' => [], 'DEST_SORT' => CSocNetLogDestination::GetDestinationSort(array("DEST_CONTEXT" => \Bitrix\Calendar\Util::getUserSelectorContext())), ); CSocNetLogDestination::fillLastDestination($DESTINATION['DEST_SORT'], $DESTINATION['LAST']); } else { $DESTINATION = array( 'LAST' => array( 'SONETGROUPS' => CSocNetLogDestination::GetLastSocnetGroup(), 'DEPARTMENT' => CSocNetLogDestination::GetLastDepartment(), 'USERS' => CSocNetLogDestination::GetLastUser(), ), ); } if (!$user_id) { $user_id = self::GetCurUserId(); } $cacheTtl = defined("BX_COMP_MANAGED_CACHE") ? 3153600 : 3600*4; $cacheId = 'calendar_dest_'.$user_id; $cacheDir = '/calendar/socnet_destination/'.SITE_ID.'/'.$user_id; $obCache = new CPHPCache; if($obCache->InitCache($cacheTtl, $cacheId, $cacheDir)) { $DESTINATION['SONETGROUPS'] = $obCache->GetVars(); } else { $obCache->StartDataCache(); $DESTINATION['SONETGROUPS'] = CSocNetLogDestination::GetSocnetGroup(Array('features' => array("calendar", array("view")))); if(defined("BX_COMP_MANAGED_CACHE")) { $CACHE_MANAGER->StartTagCache($cacheDir); foreach($DESTINATION['SONETGROUPS'] as $val) { $CACHE_MANAGER->RegisterTag("sonet_features_G_".$val["entityId"]); $CACHE_MANAGER->RegisterTag("sonet_group_".$val["entityId"]); } $CACHE_MANAGER->RegisterTag("sonet_user2group_U".$user_id); $CACHE_MANAGER->EndTagCache(); } $obCache->EndDataCache($DESTINATION['SONETGROUPS']); } $destinationUserList = []; $DESTINATION['SELECTED'] = []; if (!empty($userList)) { foreach ($userList as $userId) { $DESTINATION['SELECTED']['U'.$userId] = "users"; $DESTINATION['LAST']['USERS']['U'.$userId] = 'U'.$userId; } } foreach ($selected as $ind => $code) { if (str_starts_with($code, 'DR')) { $DESTINATION['SELECTED'][$code] = "department"; } elseif (str_starts_with($code, 'UA')) { $DESTINATION['SELECTED'][$code] = "groups"; } elseif (str_starts_with($code, 'SG')) { $DESTINATION['SELECTED'][$code] = "sonetgroups"; } elseif (str_starts_with($code, 'U')) { $DESTINATION['SELECTED'][$code] = "users"; $destinationUserList[] = (int)str_replace('U', '', $code); } } // intranet structure $arStructure = CSocNetLogDestination::GetStucture(); $DESTINATION['DEPARTMENT'] = $arStructure['department']; $DESTINATION['DEPARTMENT_RELATION'] = $arStructure['department_relation']; $DESTINATION['DEPARTMENT_RELATION_HEAD'] = $arStructure['department_relation_head']; if (Loader::includeModule('extranet') && !CExtranet::IsIntranetUser(SITE_ID, $user_id)) { $DESTINATION['EXTRANET_USER'] = 'Y'; $DESTINATION['USERS'] = CSocNetLogDestination::GetExtranetUser(); $DESTINATION['USERS'] = array_merge($DESTINATION['USERS'], CSocNetLogDestination::GetUsers(['id' => [$user_id]])); } else { if (is_array($DESTINATION['LAST']['USERS'])) { foreach ($DESTINATION['LAST']['USERS'] as $value) { $destinationUserList[] = (int)str_replace('U', '', $value); } } if (!empty($userList)) { $destinationUserList = array_unique(array_merge($destinationUserList, $userList)); } $DESTINATION['EXTRANET_USER'] = 'N'; $DESTINATION['USERS'] = CSocNetLogDestination::GetUsers(Array('id' => $destinationUserList)); } $users = []; foreach ($DESTINATION['USERS'] as $key => $entry) { if ($entry['isExtranet'] === 'N') { $users[$key] = $entry; } } $DESTINATION['USERS'] = $users; return $DESTINATION; } public static function SaveUserTimezoneName($user, $tzName = '') { if (!is_array($user) && (int)$user > 0) { $user = self::GetUser($user, true); } CUserOptions::SetOption("calendar", "timezone".self::GetCurrentOffsetUTC($user['ID']), $tzName, false, $user['ID']); } public static function OnSocNetGroupDelete($groupId) { $groupId = (int)$groupId; if ($groupId > 0) { $event = EventTable::query() ->setSelect(['ID', 'OWNER_ID', 'CAL_TYPE', 'DELETED']) ->where('OWNER_ID', $groupId) ->where('CAL_TYPE', Dictionary::CALENDAR_TYPE['group']) ->where('DELETED', 'N') ->setLimit(1) ->exec()->fetch() ; if (!empty($event)) { global $APPLICATION; $APPLICATION->ThrowException( Loc::getMessage('EC_DECLINE_GROUP_DELETING_WITH_EVENTS'), 'CALENDAR_NOT_EMPTY' ); return false; } $res = CCalendarSect::GetList( array( 'arFilter' => array( 'CAL_TYPE' => 'group', 'OWNER_ID' => $groupId, ), 'checkPermissions' => false, ) ); foreach($res as $sect) { CCalendarSect::Delete($sect['ID'], false); } } return true; } /** * Handles last caldav activity from mobile devices * * @param \Bitrix\Main\Event $event Event. * @return null */ public static function OnDavCalendarSync(\Bitrix\Main\Event $event) { $calendarId = $event->getParameter('id'); $userAgent = mb_strtolower($event->getParameter('agent')); $agent = false; [$sectionId, $entityType, $entityId] = $calendarId; $arAgentsMap = [ 'android' => 'android', // Android/iOS CardDavBitrix24 'iphone' => 'iphone', // Apple iPhone iCal 'davkit' => 'mac', // Apple iCal 'mac os' => 'mac', // Apple iCal (Mac Os X > 10.8) 'mac_os_x' => 'mac', // Apple iCal (Mac Os X > 10.8) 'mac+os+x' => 'mac', // Apple iCal (Mac Os X > 10.10) 'macos' => 'mac', // Apple iCal (Mac Os X > 11) 'dataaccess' => 'iphone', // Apple addressbook iPhone //'sunbird' => 'sunbird', // Mozilla Sunbird 'ios' => 'iphone', ]; foreach ($arAgentsMap as $pattern => $name) { if (mb_strpos($userAgent, $pattern) !== false) { $agent = $name; break; } } if ($entityType === 'user' && $agent) { self::SaveSyncDate($entityId, $agent); } } /** * Saves date of last successful sync * * @param int $userId User Id * @param string $syncType Type of synchronization. * @return null */ public static function SaveSyncDate($userId, $syncType) { $syncTypes = array('iphone', 'android', 'mac', 'exchange', 'outlook'); if (in_array($syncType, $syncTypes)) { if (!CUserOptions::GetOption('calendar', 'last_sync_'.$syncType, false, $userId)) { AddEventToStatFile('calendar', 'sync_connection_connected', $syncType, '', 'client_connection'); } CUserOptions::SetOption("calendar", "last_sync_".$syncType, self::Date(time()), false, $userId); Util::addPullEvent( PushCommand::RefreshSyncStatus, $userId, [ 'syncInfo' => [ $syncType => [ 'status' => true, 'type' => $syncType, 'connected' => true, 'syncOffset' => 0, ], ], 'requestUid' => Util::getRequestUid(), ]); } } /** * @param $userId * @param $syncType * @param $sectionId */ public static function SaveMultipleSyncDate($userId, $syncType, $sectionId): void { $syncTypes = ['outlook']; if (in_array($syncType, $syncTypes, true)) { if (!CUserOptions::GetOption('calendar', 'last_sync_'.$syncType, false, $userId)) { AddEventToStatFile('calendar', 'sync_connection_connected', $syncType, '', 'client_connection'); } $options = CUserOptions::GetOption("calendar", "last_sync_".$syncType, false, $userId); if (!is_array($options)) { unset($options); } $options[$sectionId] = self::Date(time()); CUserOptions::SetOption("calendar", "last_sync_".$syncType, $options, false, $userId); Util::addPullEvent( PushCommand::RefreshSyncStatus, $userId, [ 'syncInfo' => [ $syncType => [ 'status' => true, 'type' => $syncType, 'connected' => true, 'syncOffset' => 0, ], ], 'requestUid' => Util::getRequestUid(), ]); } } public static function OnExchangeCalendarSync(\Bitrix\Main\Event $event) { self::SaveSyncDate($event->getParameter('userId'), 'exchange'); } /** * Updates counter in left menu in b24, sets amount of requests for meeting for current user or * set of users * * @param int|array $users array of user's ids or user id as an int * @param array $eventIds array of integer event ids * @param array $groupIds array of integer group ids to recalculate */ public static function UpdateCounter($users = false, array $eventIds = [], array $groupIds = []): void { if (!$users) { $users = array(self::GetCurUserId()); } elseif(!is_array($users)) { $users = array($users); } $ids = []; foreach($users as $user) { if ((int)$user) { $ids[] = (int)$user; } } \Bitrix\Calendar\Internals\Counter\CounterService::addEvent( \Bitrix\Calendar\Internals\Counter\Event\EventDictionary::EVENT_ATTENDEES_UPDATED, [ 'user_ids' => $ids, 'event_ids' => $eventIds, 'group_ids' => $groupIds, ], ); } private static function GetInstance() { if (!isset(self::$instance)) { $c = __CLASS__; self::$instance = new $c; } return self::$instance; } public static function IsIntranetEnabled() { if (!isset(self::$bIntranet)) { self::$bIntranet = IsModuleInstalled('intranet'); } return self::$bIntranet; } public static function IsSocNet() { if (!isset(self::$bSocNet)) { if (!Loader::includeModule('socialnetwork')) { self::$bSocNet = false; return false; } self::$bSocNet = class_exists('CSocNetUserToGroup') && CBXFeatures::IsFeatureEnabled("Calendar") && self::IsIntranetEnabled(); } return self::$bSocNet; } public static function GetCurUserId($refresh = false): int { global $USER; if (!isset(self::$curUserId) || !is_numeric(self::$curUserId) || $refresh ) { self::$curUserId = (is_object($USER) && $USER->IsAuthorized()) ? (int)$USER->GetId() : 0 ; } return self::$curUserId; } public static function GetSettings($params = []) { if (!is_array($params)) { $params = []; } if ( isset(self::$settings) && !empty(self::$settings) && ($params['request'] ?? '') === false ) { return self::$settings; } $pathes_for_sites = COption::GetOptionString('calendar', 'pathes_for_sites', true); if (($params['forseGetSitePathes'] ?? false) || !$pathes_for_sites) { $pathes = self::GetPathes($params['site'] ?? false); } else { $pathes = []; } if (!isset($params['getDefaultForEmpty']) || $params['getDefaultForEmpty'] !== false) { $params['getDefaultForEmpty'] = true; } $siteId = $params['site'] ?? SITE_ID; $resMeetingCommonForSites = COption::GetOptionString('calendar', 'rm_for_sites', true); $siteIdForResMeet = !$resMeetingCommonForSites && $siteId ? $siteId : false; self::$settings = [ 'work_time_start' => COption::GetOptionString('calendar', 'work_time_start', 9), 'work_time_end' => COption::GetOptionString('calendar', 'work_time_end', 19), 'year_holidays' => COption::GetOptionString('calendar', 'year_holidays', Loc::getMessage('EC_YEAR_HOLIDAYS_DEFAULT')), 'year_workdays' => COption::GetOptionString('calendar', 'year_workdays', Loc::getMessage('EC_YEAR_WORKDAYS_DEFAULT')), 'week_holidays' => array_filter( explode('|', COption::GetOptionString('calendar', 'week_holidays', 'SA|SU')) ), 'week_start' => COption::GetOptionString('calendar', 'week_start', 'MO'), 'user_name_template' => self::GetUserNameTemplate($params['getDefaultForEmpty']), 'sync_by_push' => COption::GetOptionString('calendar', 'sync_by_push', false), 'user_show_login' => COption::GetOptionString('calendar', 'user_show_login', true), 'path_to_user' => COption::GetOptionString('calendar', 'path_to_user', "/company/personal/user/#user_id#/"), 'path_to_user_calendar' => COption::GetOptionString('calendar', 'path_to_user_calendar', "/company/personal/user/#user_id#/calendar/"), 'path_to_group' => COption::GetOptionString('calendar', 'path_to_group', "/workgroups/group/#group_id#/"), 'path_to_group_calendar' => COption::GetOptionString('calendar', 'path_to_group_calendar', "/workgroups/group/#group_id#/calendar/"), 'path_to_vr' => COption::GetOptionString('calendar', 'path_to_vr', ""), 'path_to_rm' => COption::GetOptionString('calendar', 'path_to_rm', ""), 'rm_iblock_type' => COption::GetOptionString('calendar', 'rm_iblock_type', ""), 'rm_iblock_id' => COption::GetOptionString('calendar', 'rm_iblock_id', "", $siteIdForResMeet, !!$siteIdForResMeet), 'dep_manager_sub' => COption::GetOptionString('calendar', 'dep_manager_sub', true), 'denied_superpose_types' => unserialize(COption::GetOptionString('calendar', 'denied_superpose_types', serialize([])), ['allowed_classes' => false]), 'pathes_for_sites' => $pathes_for_sites, 'pathes' => $pathes, 'forum_id' => COption::GetOptionString('calendar', 'forum_id', ""), 'rm_for_sites' => COption::GetOptionString('calendar', 'rm_for_sites', true), ]; $arPathes = self::GetPathesList(); foreach($arPathes as $pathName) { if (!isset(self::$settings[$pathName])) { self::$settings[$pathName] = COption::GetOptionString('calendar', $pathName, ""); } } if(self::$settings['work_time_start'] > 23) { self::$settings['work_time_start'] = 23; } if (self::$settings['work_time_end'] <= self::$settings['work_time_start']) { self::$settings['work_time_end'] = self::$settings['work_time_start'] + 1; } if (self::$settings['work_time_end'] > 23.30) { self::$settings['work_time_end'] = 23.30; } if (empty(self::$settings['forum_id'])) { self::$settings['forum_id'] = COption::GetOptionString("tasks", "task_forum_id", ""); if (empty(self::$settings['forum_id']) && Loader::includeModule("forum")) { $db = CForumNew::GetListEx(); if ($ar = $db->GetNext()) { self::$settings['forum_id'] = $ar["ID"]; } } COption::SetOptionString("calendar", "forum_id", self::$settings['forum_id']); } return self::$settings; } public static function GetPathes($forSite = null) { $pathes = []; $pathes_for_sites = COption::GetOptionString('calendar', 'pathes_for_sites', true); if ($forSite === null) { $arAffectedSites = COption::GetOptionString('calendar', 'pathes_sites', false); if ($arAffectedSites && CheckSerializedData($arAffectedSites)) { $arAffectedSites = unserialize($arAffectedSites, ['allowed_classes' => false]); } } elseif (is_array($forSite)) { $arAffectedSites = $forSite; } else { $arAffectedSites = [$forSite]; } if(is_array($arAffectedSites) && !empty($arAffectedSites)) { foreach($arAffectedSites as $s) { $ar = COption::GetOptionString("calendar", 'pathes_'.$s, false); if ($ar && CheckSerializedData($ar)) { $ar = unserialize($ar, ['allowed_classes' => false]); if(is_array($ar)) { $pathes[$s] = $ar; } } } } if ($forSite !== false) { $result = []; if (isset($pathes[$forSite]) && is_array($pathes[$forSite])) { $result = $pathes[$forSite]; } $arPathes = self::GetPathesList(); foreach($arPathes as $pathName) { $val = $result[$pathName] ?? ''; if (empty($val) || $pathes_for_sites) { if (!isset($SET)) { $SET = self::GetSettings(); } $val = $SET[$pathName] ?? null; $result[$pathName] = $val; } } return $result; } return $pathes; } public static function GetPathesList() { if (!self::$pathesListEx) { self::$pathesListEx = self::$pathesList; $arTypes = CCalendarType::GetList(array('checkPermissions' => false)); foreach ($arTypes as $type) { if ($type['XML_ID'] !== 'user' && $type['XML_ID'] !== 'group') { self::$pathesList[] = 'path_to_type_'. $type['XML_ID']; } } } return self::$pathesList; } public static function GetUserNameTemplate($fromSite = true) { $user_name_template = COption::GetOptionString('calendar', 'user_name_template', ''); if ($fromSite && empty($user_name_template)) { $user_name_template = CSite::GetNameFormat(false); } return $user_name_template; } public static function SetUserSettings($settings = [], $userId = false) { UserSettings::set($settings, $userId); } public static function GetUserSettings($userId = false) { return UserSettings::get($userId); } public static function GetPermissions($Params = []) { global $USER; $type = $Params['type'] ?? self::$type; $ownerId = (int)($Params['ownerId'] ?? self::$ownerId); $userId = (int)($Params['userId'] ?? self::$userId); $bView = true; $accessController = new TypeAccessController($userId); $typeModel = TypeModel::createFromXmlId($type); $request = [ ActionDictionary::ACTION_TYPE_VIEW => [], ActionDictionary::ACTION_TYPE_EDIT => [], ]; $result = $accessController->batchCheck($request, $typeModel); if ($type === 'user' && $ownerId !== $userId) { $bEdit = false; $bEditSection = false; } else { $bView = $result[ActionDictionary::ACTION_TYPE_VIEW]; $bEdit = $result[ActionDictionary::ACTION_TYPE_EDIT]; $bEditSection = $result[ActionDictionary::ACTION_TYPE_EDIT]; } if (($type === 'group') && !$USER->CanDoOperation('edit_php')) { $keyOwner = 'SG' . $ownerId.'_A'; $keyMod = 'SG' . $ownerId.'_E'; $keyMember = 'SG' . $ownerId.'_K'; $codes = Util::getUserAccessCodes($userId); if (Loader::includeModule("socialnetwork")) { $group = CSocNetGroup::getByID($ownerId); if( !empty($group['CLOSED']) && $group['CLOSED'] === 'Y' && \Bitrix\Main\Config\Option::get('socialnetwork', 'work_with_closed_groups', 'N') === 'N' ) { self::$isArchivedGroup = true; } } if (in_array($keyOwner, $codes, true))// Is owner { $bEdit = true; $bEditSection = true; } elseif(!self::$isArchivedGroup && in_array($keyMod, $codes, true))// Is moderator { $bEdit = true; $bEditSection = true; } elseif(!self::$isArchivedGroup && in_array($keyMember, $codes, true))// Is member { $bEdit = true; $bEditSection = false; } else { $bEdit = false; $bEditSection = false; } } if (($Params['setProperties'] ?? '') !== false) { self::$perm['view'] = $bView; self::$perm['edit'] = $bEdit; self::$perm['section_edit'] = $bEditSection; } return [ 'view' => $bView, 'edit' => $bEdit, 'section_edit' => $bEditSection, ]; } /** * @param string $calType * @param int $ownerId * @param int $entryId * @param string $dateFrom * * @return string */ public static function getEntryUrl(string $calType, int $ownerId, int $entryId, string $dateFrom): string { $uri = new Main\Web\Uri(Util::getPathToCalendar($ownerId, $calType)); try { $dateFormatted = Util::getDateObject($dateFrom)->format('d.m.Y'); } catch (Main\ObjectException) { $dateFormatted = $dateFrom; } $uri->addParams([ 'EVENT_ID' => $entryId, 'EVENT_DATE' => urlencode($dateFormatted), ]); return $uri->getUri(); } public static function GetPath($type = '', $ownerId = '', $hard = false) { return self::GetServerPath() . Util::getPathToCalendar((int)$ownerId, $type); } public static function GetSiteId() { if (!self::$siteId) { self::$siteId = SITE_ID; } return self::$siteId; } public static function GetServerPath() { if (!isset(self::$serverPath)) { self::$serverPath = (CMain::IsHTTPS() ? "https://" : "http://").self::GetServerName(); } return self::$serverPath; } public static function GetServerName() { $server_name = ''; if (defined("SITE_SERVER_NAME") && SITE_SERVER_NAME <> '') { $server_name = SITE_SERVER_NAME; } if (!$server_name) { $server_name = COption::GetOptionString("main", "server_name", ""); } if (!$server_name) { $server_name = $_SERVER['HTTP_HOST']; } $server_name = rtrim($server_name, '/'); if (!preg_match('/^[a-z0-9\.\-]+$/i', $server_name)) // cyrillic domain hack { $converter = new CBXPunycode('UTF-8'); $host = $converter->Encode($server_name); $server_name = $host; } return $server_name; } public static function GetStartUpEvent($eventId = false, $isSharing = false) { if ($eventId) { if ($isSharing) { $res = [self::getDeletedSharedEvent($eventId)]; } else { $res = CCalendarEvent::GetList( array( 'arFilter' => array( "PARENT_ID" => $eventId, "OWNER_ID" => self::$userId, "IS_MEETING" => 1, "DELETED" => "N", ), 'parseRecursion' => false, 'fetchAttendees' => true, 'fetchMeetings' => true, 'checkPermissions' => true, 'setDefaultLimit' => false, ) ); } if (!$res || !is_array($res[0])) { $res = CCalendarEvent::GetList( array( 'arFilter' => array( "ID" => $eventId, "DELETED" => "N", ), 'parseRecursion' => false, 'userId' => self::$userId, 'fetchAttendees' => false, 'fetchMeetings' => true, ) ); } if ($res && isset($res[0]) && ($event = $res[0])) { if ( $event['MEETING_STATUS'] === 'Y' || $event['MEETING_STATUS'] === 'N' || $event['MEETING_STATUS'] === 'Q' ) { $_GET['CONFIRM'] ??= null; if ( $event['IS_MEETING'] && (int)self::$userId === (int)self::$ownerId && self::$type === 'user' && ($_GET['CONFIRM'] === 'Y' || $_GET['CONFIRM'] === 'N') ) { CCalendarEvent::SetMeetingStatus(array( 'userId' => self::$userId, 'eventId' => $event['ID'], 'status' => $_GET['CONFIRM'] === 'Y' ? 'Y' : 'N', 'personalNotification' => true, )); } } if ($event['RRULE']) { $event['RRULE'] = CCalendarEvent::ParseRRULE($event['RRULE']); } $event['~userIndex'] = CCalendarEvent::getUserIndex(); return $event; } CCalendarNotify::ClearNotifications($eventId); } return false; } public static function getDeletedSharedEvent(int $entryId): ?array { /** @var Sharing\Link\EventLink $eventLink */ $eventLink = (new Sharing\Link\Factory)->getDeletedEventLinkByEventId($entryId); if (!$eventLink) { return null; } $result = EventTable::query() ->setSelect(['*']) ->where('OWNER_ID', $eventLink->getOwnerId()) ->where(Query::filter() ->logic('or') ->where([ ['DELETED', 'Y'], ['MEETING_STATUS', Core\Event\Tools\Dictionary::MEETING_STATUS['No']], ]) ) ->where(Query::filter() ->logic('or') ->where([ ['ID', $entryId], ['PARENT_ID', $entryId], ]) ) ->whereIn('EVENT_TYPE', SharingEventManager::getSharingEventTypes()) ->exec() ; $event = $result->fetch() ?: null; if ($event) { $canceledUserId = (int)$event["MEETING_HOST"]; if ($event['MEETING_STATUS'] === Core\Event\Tools\Dictionary::MEETING_STATUS['No']) { $canceledUserId = (int)$event["OWNER_ID"]; $event['canceledByManager'] = true; } $host = Sharing\Helper::getOwnerInfo($canceledUserId); $event['HOST_NAME'] = trim($host['name'] . ' ' . $host['lastName']); $event['timestampFromUTC'] = Sharing\Helper::getEventTimestampUTC($event['DATE_FROM'], $event['TZ_FROM']); $event['timestampToUTC'] = Sharing\Helper::getEventTimestampUTC($event['DATE_TO'], $event['TZ_TO']); $event['canceledUserId'] = $canceledUserId; } return $event; } public static function Timestamp($date, $bRound = true, $bTime = true) { $timestamp = MakeTimeStamp($date, self::TSFormat($bTime ? "FULL" : "SHORT")); if ($bRound) { $timestamp = self::RoundTimestamp($timestamp); } return $timestamp; } public static function TimestampUTC(string $date): int { $dateTime = self::createDateTimeObjectFromString($date, 'UTC'); return (int)$dateTime->format('U'); } public static function createDateTimeObjectFromString(string $date, ?string $timezone = null) { try { $parsedDateTime = ParseDateTime($date); $hours = (int)($parsedDateTime['HH'] ?? $parsedDateTime['H'] ?? 0); if (isset($parsedDateTime['TT']) || isset($parsedDateTime['T'])) { $amPm = $parsedDateTime['TT'] ?? $parsedDateTime['T']; if (strcasecmp('pm', $amPm) === 0) { if ($hours < 12) { $hours += 12; } } else { $hours %= 12; } } $dateTime = (new \DateTime('now', $timezone ? new \DateTimeZone($timezone) : null)) ->setDate($parsedDateTime['YYYY'], $parsedDateTime['MM'], $parsedDateTime['DD']) ->setTime($hours, $parsedDateTime['MI'] ?? 0); } catch (\TypeError) { $dateTime = new \DateTime($date, $timezone ? new \DateTimeZone($timezone) : null ); } finally { return $dateTime; } } public static function TSFormat($format = "FULL") { return CSite::GetDateFormat($format); } public static function RoundTimestamp($ts) { return round($ts / 60) * 60; // We don't need for seconds here } public static function IsPersonal($type = false, $ownerId = false, $userId = false) { if (!$type) { $type = self::$type; } if(!$ownerId) { $ownerId = self::$ownerId; } if(!$userId) { $userId = self::$userId; } return $type === 'user' && $ownerId == $userId; } public static function IsExchangeEnabled($userId = false) { if (isset(self::$arExchEnabledCache[$userId])) { return self::$arExchEnabledCache[$userId]; } if (!IsModuleInstalled('dav') || COption::GetOptionString("dav", "agent_calendar") !== "Y") { $res = false; } elseif (!Loader::includeModule('dav')) { $res = false; } elseif ($userId === false) { $res = CDavExchangeCalendar::IsExchangeEnabled(); } else { $res = CDavExchangeCalendar::IsExchangeEnabled() && CDavExchangeCalendar::IsExchangeEnabledForUser($userId); } self::$arExchEnabledCache[$userId] = $res; return $res; } public static function isGoogleApiEnabled() { if (!isset(self::$isGoogleApiEnabled)) { self::$isGoogleApiEnabled = Loader::includeModule('dav') && Loader::includeModule('socialservices') && ( is_null(\Bitrix\Main\Config\Configuration::getValue("calendar_integration")) || \Bitrix\Main\Config\Configuration::getValue("calendar_integration") === self::INTEGRATION_GOOGLE_API ); if (self::$isGoogleApiEnabled && !self::IsBitrix24()) { self::$isGoogleApiEnabled = ( CSocServGoogleOAuth::GetOption('google_appid') !== '' && CSocServGoogleOAuth::GetOption('google_appsecret') !== '' ) || CSocServGoogleOAuth::GetOption('google_sync_proxy') === 'Y' ; } } return self::$isGoogleApiEnabled; } public static function IsCalDAVEnabled() { if (!IsModuleInstalled('dav') || COption::GetOptionString("dav", "agent_calendar_caldav") !== "Y") { return false; } return Loader::includeModule('dav') && CDavGroupdavClientCalendar::IsCalDAVEnabled(); } public static function isIphoneConnected() { $info = CCalendarSync::GetSyncInfoItem(self::$userId, 'iphone'); return $info['connected']; } public static function isMacConnected() { $info = CCalendarSync::GetSyncInfoItem(self::$userId, 'mac'); return $info['connected']; } public static function IsWebserviceEnabled() { if (!isset(self::$bWebservice)) { self::$bWebservice = IsModuleInstalled('webservice'); } return self::$bWebservice; } public static function IsExtranetEnabled() { if (!isset(self::$bExtranet)) { self::$bExtranet = Loader::includeModule('extranet') && CExtranet::IsExtranetSite(); } return self::$bExtranet; } public static function GetCurrentOffsetUTC($userId = false) { if (!$userId && self::$userId) { $userId = self::$userId; } return (int)(date("Z") + self::GetOffset($userId)); } public static function GetOffset($userId = false) { if ($userId > 0) { if (!isset(self::$arTimezoneOffsets[$userId])) { $offset = CTimeZone::GetOffset($userId, true); self::$arTimezoneOffsets[$userId] = $offset; } else { $offset = self::$arTimezoneOffsets[$userId]; } } else if (!isset(self::$offset)) { $offset = CTimeZone::GetOffset(null, true); self::$offset = $offset; } else { $offset = self::$offset; } return $offset; } public static function GetUserTimezoneName($user, $getDefault = true) { if (isset(self::$userTimezoneList[$user]) && !is_array($user) && (int)$user > 0) { return self::$userTimezoneList[$user]; } if (is_array($user) && (int)$user['ID'] > 0 && isset(self::$userTimezoneList[$user['ID']])) { return self::$userTimezoneList[$user['ID']]; } if (!is_array($user) && (int)$user > 0) { $user = self::GetUser((int)$user, true); } if (\CTimezone::OptionEnabled() && $user && is_array($user)) { $offset = isset($user['TIME_ZONE_OFFSET']) ? (int)(date('Z') + $user['TIME_ZONE_OFFSET']) : self::GetCurrentOffsetUTC($user['ID']); $tzName = CUserOptions::GetOption( "calendar", "timezone" . $offset, false, $user['ID'] ); if ($tzName === 'undefined' || $tzName === 'false') { $tzName = false; } if (!$tzName && ($user['AUTO_TIME_ZONE'] ?? '') !== 'Y' && $user['TIME_ZONE']) { $tzName = $user['TIME_ZONE']; } } else { $offset = date('Z'); $tzName = date_default_timezone_get(); } try { new DateTimeZone($tzName); } catch (\Throwable) { $tzName = false; } if (!$tzName && $getDefault) { $tzName = self::GetGoodTimezoneForOffset($offset); } if ($user && is_array($user) && $user['ID']) { self::$userTimezoneList[$user['ID']] = $tzName; } return $tzName; } public static function GetUser($userId, $bPhoto = false) { $userId = (int)$userId; return self::$userList[$userId] ?? current(self::GetUserList([$userId])); } public static function GetUserList(array $userIdList): array { $result = []; $userIdToRequest = self::GetNotRequestedUserIdList($userIdList); if (!empty($userIdToRequest)) { $userList = Main\UserTable::query() ->setSelect([ 'ID', 'NAME', 'LAST_NAME', 'SECOND_NAME', 'LOGIN', 'PERSONAL_PHOTO', 'TIME_ZONE', 'EMAIL', 'EXTERNAL_AUTH_ID', 'NOTIFICATION_LANGUAGE_ID', ]) ->whereIn('ID', $userIdToRequest) ->exec() ->fetchAll() ; foreach ($userIdToRequest as $userId) { self::$userList[$userId] = false; } foreach ($userList as $user) { $user['ID'] = (int)$user['ID']; $user['COLLAB_USER'] = Util::isCollabUser($user['ID']); self::$userList[$user['ID']] = $user; } } foreach ($userIdList as $userId) { $result[] = self::$userList[$userId]; } return $result; } private static function GetNotRequestedUserIdList(array $userIdList): array { $result = []; foreach ($userIdList as $userId) { $userId = (int)$userId; if (!isset(self::$userList[$userId])) { $result[] = $userId; } } return $result; } public static function GetGoodTimezoneForOffset($offset) { $timezones = self::GetTimezoneList(); $goodTz = []; $result = false; foreach($timezones as $tz) { if ($tz['offset'] == $offset) { $goodTz[] = $tz; if (LANGUAGE_ID == 'ru') { if (preg_match('/(kaliningrad|moscow|samara|yekaterinburg|novosibirsk|krasnoyarsk|irkutsk|yakutsk|vladivostok)/i', $tz['timezone_id'])) { $result = $tz['timezone_id']; break; } } elseif (mb_strpos($tz['timezone_id'], 'Europe') !== false) { $result = $tz['timezone_id']; break; } } } if (!$result && !empty($goodTz)) { $result = $goodTz[0]['timezone_id']; } if (!$result) { $result = date_default_timezone_get(); } return $result; } public static function GetTimezoneList() { if (empty(self::$timezones)) { self::$timezones = []; $aExcept = ["Etc/", "GMT", "UCT", "HST", "PST", "MST", "CST", "EST", "CET", "MET", "WET", "EET", "PRC", "ROC", "ROK", "W-SU"]; foreach(DateTimeZone::listIdentifiers() as $tz) { foreach($aExcept as $ex) { if(str_starts_with($tz, $ex)) { continue 2; } } try { $oTz = new DateTimeZone($tz); self::$timezones[$tz] = [ 'timezone_id' => $tz, 'offset' => $oTz->getOffset(new DateTime("now", $oTz)), ]; } catch(Exception $e) { } } uasort(self::$timezones, static function($a, $b){ if($a['offset'] === $b['offset']) { return strcmp($a['timezone_id'], $b['timezone_id']); } return $a['offset'] < $b['offset'] ? -1 : 1; }); foreach(self::$timezones as $k => $z) { $offset = $z['offset']; $hours = floor(abs($offset) / 3600); if ($z['timezone_id'] === 'UTC') { self::$timezones[$k]['title'] = $z['timezone_id']; } else { self::$timezones[$k]['title'] = '(UTC' . ($offset !== 0 ? ' ' . ($offset < 0 ? '-' : '+') .sprintf("%02d", $hours) . ':' . sprintf("%02d", abs($offset)/60 - $hours * 60) : '' ) . ') ' . $z['timezone_id']; } } } return self::$timezones; } public static function GetUserName($user) { if (!is_array($user) && (int)$user > 0) { $user = self::GetUser($user); } if (!$user || !is_array($user)) { return ''; } return CUser::FormatName(self::$userNameTemplate, $user, true, false); } public static function IsAdmin(): bool { GLOBAL $USER; return $USER->IsAdmin(); } public static function GetWeekStart() { if (!isset(self::$weekStart)) { $days = ['1' => 'MO', '2' => 'TU', '3' => 'WE', '4' => 'TH', '5' => 'FR', '6' => 'SA', '0' => 'SU']; $cultureWeekStart = \Bitrix\Main\Context::getCurrent()?->getCulture()?->getWeekStart(); self::$weekStart = $days[$cultureWeekStart]; if (!in_array(self::$weekStart, $days)) { self::$weekStart = 'MO'; } } return self::$weekStart; } public static function Date($timestamp, $bTime = true, $bRound = true, $bCutSeconds = false) { if ($bRound) { $timestamp = self::RoundTimestamp($timestamp); } $format = self::DFormat($bTime); if ($bTime && $bCutSeconds) { $format = str_replace(':s', '', $format); } return FormatDate($format, $timestamp); } public static function DFormat($bTime = true) { return CDatabase::DateFormatToPHP(CSite::GetDateFormat($bTime ? "FULL" : "SHORT", SITE_ID)); } public static function DateWithNewTime($timestampTime, $timestampDate) { return mktime(date("H", $timestampTime), date("i", $timestampTime), 0, date("m", $timestampDate), date("d", $timestampDate), date("Y", $timestampDate)); } public static function GetCurUserMeetingSection($bCreate = false) { if (!isset(self::$userMeetingSection) || !self::$userMeetingSection) { self::$userMeetingSection = self::GetMeetingSection(self::$userId, $bCreate); } return self::$userMeetingSection; } public static function GetMeetingSection($userId, $autoCreate = false) { if (!$userId || is_array($userId)) { return false; } if (isset(self::$meetingSections[$userId])) { return self::$meetingSections[$userId]; } $result = false; $meetingSectionId = false; if ($userId > 0) { $set = UserSettings::get($userId); $meetingSectionId = (int)$set['meetSection']; $result = self::getSectionForUser($meetingSectionId, $userId, $autoCreate); } foreach(\Bitrix\Main\EventManager::getInstance()->findEventHandlers("calendar", "OnGetMeetingSectionForUser") as $event) { ExecuteModuleEventEx($event, [$userId, &$result]); } if ($result && $meetingSectionId !== $result) { $set['meetSection'] = $result; UserSettings::set($set, $userId); } self::$meetingSections[$userId] = $result; return $result; } public static function GetCrmSection($userId, $autoCreate = false) { if (!$userId || is_array($userId)) { return false; } if (isset(self::$crmSections[$userId])) { return self::$crmSections[$userId]; } $result = false; $crmSectionId = false; if ($userId > 0) { $set = UserSettings::get($userId); $crmSectionId = (int)$set['crmSection']; $result = self::getSectionForUser($crmSectionId, $userId, $autoCreate); } if ($result && $crmSectionId !== $result) { $set['crmSection'] = $result; UserSettings::set($set, $userId); } self::$crmSections[$userId] = $result; return $result; } private static function getSectionForUser($result, $userId, $autoCreate = false) { $section = false; if ($result) { $sectionQueryResult = \Bitrix\Calendar\Internals\SectionTable::query() ->setSelect(['ID', 'CAL_TYPE', 'OWNER_ID', 'ACTIVE']) ->where('ID', $result) ->where('CAL_TYPE', Dictionary::CALENDAR_TYPE['user']) ->where('OWNER_ID', (int)$userId) ->where('ACTIVE', 'Y') ->setLimit(1) ->exec()->fetch() ; $section = !empty($sectionQueryResult) ? (int)$sectionQueryResult['ID'] : false; } if ($result && !$section) { $result = false; } if (!$result) { $sectionQueryResult = \Bitrix\Calendar\Internals\SectionTable::query() ->setSelect(['ID', 'CAL_TYPE', 'OWNER_ID', 'ACTIVE']) ->where('CAL_TYPE', Dictionary::CALENDAR_TYPE['user']) ->where('OWNER_ID', (int)$userId) ->where('ACTIVE', 'Y') ->setLimit(1) ->exec()->fetch() ; if (!empty($sectionQueryResult) && $sectionQueryResult['ID']) { $result = (int)$sectionQueryResult['ID']; } if (!$result && $autoCreate) { $defCalendar = CCalendarSect::CreateDefault([ 'type' => Dictionary::CALENDAR_TYPE['user'], 'ownerId' => $userId, ]); if ($defCalendar && $defCalendar['ID'] > 0) { $result = (int)$defCalendar['ID']; } } } return $result; } public static function GetSectionList($params = []) { $type = $params['CAL_TYPE'] ?? self::$type; $arFilter = [ 'CAL_TYPE' => $type, ]; if (isset($params['OWNER_ID'])) { $arFilter['OWNER_ID'] = $params['OWNER_ID']; } elseif ($type === 'user' || $type === 'group') { $arFilter['OWNER_ID'] = self::GetOwnerId(); } if (isset($params['ACTIVE'])) { $arFilter['ACTIVE'] = $params['ACTIVE']; } if (!empty($params['ADDITIONAL_IDS'])) { $arFilter['ADDITIONAL_IDS'] = $params['ADDITIONAL_IDS']; } $sectionList = CCalendarSect::GetList([ 'arFilter' => $arFilter, 'checkPermissions' => ($params['checkPermissions'] ?? null), 'getPermissions' => ($params['getPermissions'] ?? null), ]); if ( $type === 'user' || (is_array($type) && in_array(Core\Event\Tools\Dictionary::CALENDAR_TYPE['user'], $type, true)) ) { $sectionIdList = []; foreach ($sectionList as $section) { $sectionIdList[] = (int)$section['ID']; } $sectionLinkList = !empty($sectionIdList) ? CCalendarSect::getSectionConnectionList($sectionIdList) : []; if (!empty($sectionLinkList)) { foreach ($sectionList as $i => $section) { $sectionList[$i]['connectionLinks'] = []; foreach ($sectionLinkList as $sectionLink) { if ($sectionLink->getSectionId() === (int)$section['ID']) { $sectionList[$i]['connectionLinks'][] = [ 'id' => $sectionLink->getConnectionId(), 'active' => $sectionLink->getActive() ? 'Y' : 'N', 'isPrimary' => $sectionLink->getIsPrimary() ? 'Y' : 'N', ]; } } } } } if (($params['getImages'] ?? null)) { $sectionList = self::fetchIconsForSectionList($sectionList); } return $sectionList; } public static function fetchIconsForSectionList($sectionList) { $SECTION_IMG_SIZE = 28; $userIdList = []; $groupIdList = []; $userIndexList = []; $groupListIndex = []; foreach ($sectionList as $section) { $ownerId = (int)$section['OWNER_ID']; if ( $section['CAL_TYPE'] === 'user' && !in_array($ownerId, $userIdList) ) { $userIdList[] = $ownerId; } elseif ($section['CAL_TYPE'] === 'group' && !in_array($ownerId, $groupIdList)) { $groupIdList[] = $ownerId; } } if (!empty($userIdList)) { $userIndexList = \CCalendarEvent::getUsersDetails($userIdList); } if (!empty($groupIdList) && Loader::includeModule("socialnetwork")) { $res = Bitrix\Socialnetwork\WorkgroupTable::getList([ 'filter' => [ '=ACTIVE' => 'Y', '@ID' => $groupIdList, ], 'select' => ['ID', 'IMAGE_ID'], ]); while ($workgroupFields = $res->fetch()) { if (!empty($workgroupFields["IMAGE_ID"])) { $arFileTmp = CFile::ResizeImageGet( $workgroupFields["IMAGE_ID"], ['width' => $SECTION_IMG_SIZE, 'height' => $SECTION_IMG_SIZE], BX_RESIZE_IMAGE_EXACT, false, false, true ); $workgroupFields['IMAGE'] = $arFileTmp['src']; } $groupListIndex[$workgroupFields['ID']] = $workgroupFields; } } foreach ($sectionList as $k => $section) { $ownerId = (int)$section['OWNER_ID']; if ($section['CAL_TYPE'] === 'user' && isset($userIndexList[$ownerId]) && !empty($userIndexList[$ownerId]['AVATAR']) && $userIndexList[$ownerId]['AVATAR'] !== '/bitrix/images/1.gif' ) { $sectionList[$k]['IMAGE'] = $userIndexList[$ownerId]['AVATAR']; } elseif ( $section['CAL_TYPE'] === 'group' && isset($groupListIndex[$ownerId]) && !empty($groupListIndex[$ownerId]['IMAGE']) ) { $sectionList[$k]['IMAGE'] = $groupListIndex[$ownerId]['IMAGE']; } $pathesForSite = self::getPathes(SITE_ID); if ($section['CAL_TYPE'] === 'user') { $sectionList[$k]['LINK'] = str_replace( ['#user_id#', '#USER_ID#'], $section['OWNER_ID'], $pathesForSite['path_to_user_calendar'] ); } else if($section['CAL_TYPE'] === 'group') { $sectionList[$k]['LINK'] = str_replace( ['#group_id#', '#GROUP_ID#'], $section['OWNER_ID'], $pathesForSite['path_to_user_calendar'] ); } else { $path = $pathesForSite['path_to_type_'.$section['CAL_TYPE']]; $sectionList[$k]['LINK'] = $path; } } return $sectionList; } public static function GetOwnerId() { return self::$ownerId; } /** * @param array $params * @param array $arAttendees * * @return array|null */ public static function GetEventList($params, &$arAttendees) { $type = $params['type'] ?? self::$type; $ownerId = isset($params['ownerId']) ? (int)$params['ownerId'] : self::$ownerId; $userId = isset($params['userId']) ? (int)$params['userId'] : self::$userId; if (empty($params['section'])) { return []; } $arFilter = []; if (isset($params['fromLimit'])) { $arFilter["FROM_LIMIT"] = $params['fromLimit']; } if (isset($params['toLimit'])) { $arFilter["TO_LIMIT"] = $params['toLimit']; } $arFilter["OWNER_ID"] = $ownerId; if ($type === 'user') { $fetchMeetings = in_array(self::GetMeetingSection($ownerId), $params['section'], true); } else { $fetchMeetings = in_array(self::GetCurUserMeetingSection(), $params['section'], true); if ($type) { $arFilter['CAL_TYPE'] = $type; } } $res = CCalendarEvent::GetList([ 'arFilter' => $arFilter, 'parseRecursion' => true, 'fetchAttendees' => true, 'userId' => $userId, 'fetchMeetings' => $fetchMeetings, 'setDefaultLimit' => false, 'limit' => $params['limit'] ?? null, 'getUserfields' => true, ]); $result = []; foreach ($res as $event) { if (in_array((int)$event['SECT_ID'], $params['section'], true)) { unset($event['~DESCRIPTION']); $result[] = $event; } } return $result; } public static function getTaskList(TaskQueryParameter $parameter) { if (!Loader::includeModule('tasks')) { return []; } $res = []; $userSettings = Bitrix\Calendar\UserSettings::get(); $filter = [ '!STATUS' => [ Status::DEFERRED, ], 'CHECK_PERMISSIONS' => 'Y', ]; if ($userSettings['showCompletedTasks'] === 'N') { $filter['!STATUS'][] = Status::COMPLETED; } if ($parameter->isUserType()) { $filter['DOER'] = $parameter->getOwnerId(); } elseif ($parameter->isGroupType()) { $filter['GROUP_ID'] = $parameter->getOwnerId(); } $tzEnabled = CTimeZone::Enabled(); if ($tzEnabled) { CTimeZone::Disable(); } $query = (new TaskQuery($parameter->getUserId())) ->setSelect([ 'ID', 'TITLE', 'DESCRIPTION', 'CREATED_DATE', 'DEADLINE', 'START_DATE_PLAN', 'END_DATE_PLAN', 'DATE_START', 'CLOSED_DATE', 'STATUS_CHANGED_DATE', 'STATUS', 'REAL_STATUS', 'CREATED_BY', 'GROUP_ID', ]) ->setOrder(['START_DATE_PLAN' => 'ASC']) ->setWhere($filter); $tasks = (new TaskList())->getList($query); $offset = self::GetOffset(); foreach ($tasks as $task) { $dtFrom = null; $dtTo = null; $skipFromOffset = false; $skipToOffset = false; if (isset($task["START_DATE_PLAN"]) && $task["START_DATE_PLAN"]) { $dtFrom = self::CutZeroTime($task["START_DATE_PLAN"]); } if (isset($task["END_DATE_PLAN"]) && $task["END_DATE_PLAN"]) { $dtTo = self::CutZeroTime($task["END_DATE_PLAN"]); } if (!isset($dtFrom) && isset($task["DATE_START"])) { $dtFrom = self::CutZeroTime($task["DATE_START"]); } if (!isset($dtTo) && isset($task["CLOSED_DATE"])) { $dtTo = self::CutZeroTime($task["CLOSED_DATE"]); } if ( !isset($dtTo) && isset($task["STATUS_CHANGED_DATE"]) && in_array( (int)$task["REAL_STATUS"], [Status::SUPPOSEDLY_COMPLETED, Status::COMPLETED, Status::DEFERRED, Status::DECLINED], true ) ) { $dtTo = self::CutZeroTime($task["STATUS_CHANGED_DATE"]); } if (isset($dtTo)) { $ts = self::Timestamp($dtTo); // Correction display logic for harmony with Tasks interfaces if (date("H:i", $ts) === '00:00') { $dtTo = self::Date($ts - 24 * 60 * 60); } } elseif (isset($task["DEADLINE"])) { $dtTo = self::CutZeroTime($task["DEADLINE"]); $ts = self::Timestamp($dtTo); // Correction display logic for harmony with Tasks interfaces if (date("H:i", $ts) === '00:00') { $dtTo = self::Date($ts - 24 * 60 * 60); } if (!isset($dtFrom)) { $skipFromOffset = true; $dtFrom = self::Date(time(), false); } } if (!isset($dtTo)) { $dtTo = self::Date(time(), false); } if (!isset($dtFrom)) { $dtFrom = $dtTo; } $dtFromTS = self::Timestamp($dtFrom); $dtToTS = self::Timestamp($dtTo); if ($dtToTS < $dtFromTS) { $dtToTS = $dtFromTS; $dtTo = self::Date($dtToTS, true); } $skipTime = date("H:i", $dtFromTS) === '00:00' && date("H:i", $dtToTS) === '00:00'; if (!$skipTime && $offset != 0) { if (!$skipFromOffset) { $dtFromTS += $offset; $dtFrom = self::Date($dtFromTS, true); } if (!$skipToOffset) { $dtToTS += $offset; $dtTo = self::Date($dtToTS, true); } } $res[] = [ "ID" => $task["ID"], "~TYPE" => "tasks", "NAME" => $task["TITLE"], "DATE_FROM" => $dtFrom, "DATE_TO" => $dtTo, "DT_SKIP_TIME" => $skipTime ? 'Y' : 'N', "CAN_EDIT" => CTasks::CanCurrentUserEdit($task), ]; } if ($tzEnabled) { CTimeZone::Enable(); } return $res; } public static function CutZeroTime($date) { if (preg_match('/.*\s\d\d:\d\d:\d\d/i', $date)) { $date = trim($date); if (mb_substr($date, -9) === ' 00:00:00') { return mb_substr($date, 0, -9); } if (mb_substr($date, -3) === ':00') { return mb_substr($date, 0, -3); } } return $date; } public static function GetType() { return self::$type; } public static function GetAccessNames() { $codes = []; foreach (self::$accessNames as $code => $name) { if ($name === null) { $codes[] = $code; } } if ($codes) { $access = new CAccess(); $names = $access->GetNames($codes); foreach($names as $code => $name) { self::$accessNames[$code] = trim(htmlspecialcharsbx($name['name'])); } self::$accessNames['UA'] = Loc::getMessage('EC_ENTITY_SELECTOR_ALL_EMPLOYEES'); } return self::$accessNames; } public static function SetSilentErrorMode($silentErrorMode = true) { self::$silentErrorMode = $silentErrorMode; } public function GetId() { return self::$id ?: 'EC'.rand(); } /** * @param $parentDateTime * @param $instanceDateTime * @param $timeZone * @param $format * @param $originalInstanceDate * * @return string * @throws Main\ObjectException */ public static function GetOriginalDate( $parentDateTime, $instanceDateTime, $timeZone = null, $format = null ): string { CTimeZone::Disable(); $format = $format ?? Main\Type\Date::convertFormatToPhp(FORMAT_DATETIME); $parentTimestamp = Util::getDateObject($parentDateTime, false, $timeZone)->getTimestamp(); $baseTimeZone = date_default_timezone_get(); if ($timeZone) { date_default_timezone_set($timeZone); } $parentInfoDate = getdate($parentTimestamp); /** @var Type\DateTime $instanceDateTime */ $instanceDateTime = Util::getDateObject($instanceDateTime, false, $timeZone); $eventDate = $instanceDateTime->setTime($parentInfoDate['hours'], $parentInfoDate['minutes'])->format($format); if ($baseTimeZone) { date_default_timezone_set($baseTimeZone); } CTimeZone::Enable(); return $eventDate; } public static function getSectionListAvailableForUser($userId, $additionalSectionIdList = [], $params = []) { return self::GetSectionList([ 'CAL_TYPE' => 'user', 'OWNER_ID' => $userId, 'ACTIVE' => 'Y', 'ADDITIONAL_IDS' => array_merge($additionalSectionIdList, UserSettings::getFollowedSectionIdList($userId)), ...$params, ]); } public static function setOwnerId($userId) { self::$ownerId = $userId; } public static function isOffice365ApiEnabled(): ?bool { if (!isset(self::$isOffice365ApiEnabled)) { self::$isOffice365ApiEnabled = Loader::includeModule('dav') && Loader::includeModule('socialservices') ; if (self::$isOffice365ApiEnabled && !self::IsBitrix24()) { self::$isOffice365ApiEnabled = CSocServGoogleOAuth::GetOption('office365_appid') !== '' && CSocServGoogleOAuth::GetOption('office365_appid') !== '' ; } } return self::$isOffice365ApiEnabled; } /** * @param int $id * @param array $arFields * @param array $params * @param array|null $curEvent * * @return Sync\Util\Result|null * * @throws ArgumentException * @throws Core\Base\BaseException * @throws Main\LoaderException * @throws Main\ObjectNotFoundException * @throws Main\ObjectPropertyException * @throws Main\SystemException * * @todo temporary resolve. This method will change when we change a common design. */ public static function syncChange(int $id, array $arFields, array $params, ?array $curEvent): ?Sync\Util\Result { /** @var Bitrix\Calendar\Core\Mappers\Factory $mapperFactory */ $mapperFactory = ServiceLocator::getInstance()->get('calendar.service.mappers.factory'); /** @var Core\Event\Event $event */ $event = $mapperFactory->getEvent()->resetCacheById($id)->getById($id); if (!$event) { return null; } if ( $curEvent && !empty($curEvent['SECT_ID']) && $event->getSection() && (int)$curEvent['SECT_ID'] !== $event->getSection()->getId() ) { return self::changeCalendarSync($event, $curEvent, $params); } $factories = FactoriesCollection::createBySection( $event->getSection() ); if ($factories->count() === 0) { return null; } $syncManager = new Synchronization($factories); $context = new Context([]); if ( $params['originalFrom'] && (int)$arFields['OWNER_ID'] === (int)$params['userId'] ) { $context->add('sync', 'originalFrom', $params['originalFrom']); $connection = (new Bitrix\Calendar\Core\Mappers\Connection())->getMap([ '=ACCOUNT_TYPE' => $params['originalFrom'], '=ENTITY_TYPE' => $event->getCalendarType(), '=ENTITY_ID' => $event->getOwner()->getId(), ])->fetch(); if ($connection) { $syncManager->upEventVersion( $event, $connection, $arFields['VERSION'] ?? 1 ); } } $pushManager = new Sync\Managers\PushManager(); try { /** @var Sync\Factories\FactoryBase $factory */ foreach ($factories as $factory) { $pushManager->unLockConnection($factory->getConnection()); $pushManager->lockConnection($factory->getConnection(), 30); } if (($params['recursionEditMode'] ?? null) === 'skip') { if ($event->isInstance()) { $params['editInstance'] = $event->isInstance(); $params['modeSync'] = true; } if (!empty($params['modeSync'])) { $recurrenceSyncMode = ($params['editInstance'] << 2) | ($params['editNextEvents'] << 1) | ($params['editEntryUntil'] << 1) | $params['editParentEvents'] ; switch ($recurrenceSyncMode) { case Sync\Dictionary::RECURRENCE_SYNC_MODE['exception']: if ($event->getMeetingStatus() !== 'N') { $result = empty($curEvent) ? $syncManager->createInstance($event, $context) : $syncManager->updateInstance($event, $context) ; } break; case Sync\Dictionary::RECURRENCE_SYNC_MODE['deleteInstance']: $context->add('diff', 'EXDATE', $curEvent['EXDATE']); $result = $syncManager->deleteInstance($event, $context); break; case Sync\Dictionary::RECURRENCE_SYNC_MODE['exceptionNewSeries']: $syncManager->deleteInstanceEventConnection($event); if ($event->getMeetingStatus() !== 'N') { $result = $syncManager->createInstance($event, $context); } break; default: if ($event->getMeetingStatus() !== 'N') { $result = empty($curEvent) ? $syncManager->createEvent($event, $context) : $syncManager->updateEvent($event, $context) ; } } } } elseif (empty($curEvent)) { if ($event->isInstance()) { $attendeeMasterEvent = $mapperFactory->getEvent()->getMap([ '=PARENT_ID' => $event->getRecurrenceId(), '=OWNER_ID' => $event->getOwner()->getId(), '=CAL_TYPE' => 'user', ])->fetch(); if ($attendeeMasterEvent) { $result = $syncManager->reCreateRecurrence($attendeeMasterEvent, $context); } else { $result = (new Sync\Util\Result())->addError( new Main\Error("Master event not found", 404) ); } } else if ($event->getMeetingStatus() !== 'N') { if ( !empty($params['editNextEvents']) && !empty($params['previousRecurrentId']) && $event->getExcludedDateCollection()?->count() ) { self::removeIncorrectRecurrentExDates($event->getExcludedDateCollection(), $params['previousRecurrentId']); } $result = $syncManager->createEvent($event, $context); } } else { $syncManager->updateEvent($event, $context); } } catch(Throwable $e) { throw $e; } return $result ?? null; } /** * @param Core\Event\Properties\ExcludedDatesCollection $exDatesCollection * @param int $recId * @return void * @throws Main\ObjectException */ private static function removeIncorrectRecurrentExDates( Core\Event\Properties\ExcludedDatesCollection $exDatesCollection, int $recId, ): void { $exDatesToRemove = []; $recEvents = CCalendarEvent::GetEventsByRecId($recId, false); foreach ($recEvents as $recEvent) { if ((int)$recEvent['ID'] !== (int)$recEvent['PARENT_ID']) { continue; } $exDatesToRemove[] = new Date( Util::getDateObject( $recEvent['ORIGINAL_DATE_FROM'] ?? $recEvent['DATE_FROM'], ($recEvent['DT_SKIP_TIME'] ?? null) === 'Y', $recEvent['TZ_FROM'], ) ); } /** @var Date $exDate */ foreach ($exDatesToRemove as $exDate) { $exDatesCollection->removeDateFromCollection($exDate); } } public static function changeCalendarSync(Core\Event\Event $event, array $currentEvent, array $params) { $result = null; /** @var Bitrix\Calendar\Core\Mappers\Factory $mapperFactory */ $mapperFactory = ServiceLocator::getInstance()->get('calendar.service.mappers.factory'); $pushManager = new Sync\Managers\PushManager(); /** @var Core\Section\Section $oldSection */ $oldSection = $mapperFactory->getSection()->getById($currentEvent['SECTION_ID']); $oldFactories = FactoriesCollection::createBySection($oldSection); if ($oldFactories->count() > 0) { $syncManager = new Synchronization($oldFactories); $context = new Context(); foreach ($oldFactories as $factory) { $pushManager->unLockConnection($factory->getConnection()); $pushManager->lockConnection($factory->getConnection()); } $eventId = (int)$currentEvent['ID']; if ($currentEvent['RECURRENCE_ID'] && $currentEvent['ORIGINAL_DATE_FROM']) { $masterEvent = \Bitrix\Calendar\Internals\EventTable::query() ->setSelect(['ID']) ->where('DELETED', 'N') ->where('PARENT_ID', $currentEvent['RECURRENCE_ID']) ->where('OWNER_ID', $currentEvent['OWNER_ID']) ->exec()->fetch() ; if (!empty($masterEvent['ID'])) { $eventId = (int)$masterEvent['ID']; } } /** @var Core\Event\Event $eventToDelete */ $eventToDelete = $mapperFactory->getEvent()->getById($eventId); if ($eventToDelete) { $clonedEvent = (new Core\Builders\EventCloner($event))->build(); $clonedEvent->setSection($oldSection); $syncManager->deleteEvent($clonedEvent, $context); } } $newFactories = FactoriesCollection::createBySection($event->getSection()); if ($newFactories->count() > 0) { foreach ($newFactories as $factory) { $pushManager->unLockConnection($factory->getConnection()); $pushManager->lockConnection($factory->getConnection()); } $syncManager = new Synchronization($newFactories); $context = new Context(); if ($event->isInstance()) { $masterEvent = $mapperFactory->getEvent()->getMap([ '=PARENT_ID' => $event->getRecurrenceId(), '=OWNER_ID' => $event->getOwner()->getId(), ])->fetch(); $result = $syncManager->createRecurrence($masterEvent, $context); } else if ($event->getRecurringRule()) { $result = $syncManager->createRecurrence($event, $context); } else { $result = $syncManager->createEvent($event, $context); } } return $result; } /** * @param $connectionId * @return array|false|mixed */ private static function getSectionsByConnectionId($connectionId) { return \CCalendarSect::GetList([ 'arFilter' => [ 'CAL_TYPE' => 'user', 'OWNER_ID' => self::$ownerId, 'CAL_DAV_CON' => $connectionId, ], ]); } /** * @param $sectionId */ private static function markSectionLikeDelete(int $sectionId): void { CCalendarSect::Edit([ 'arFields' => [ "ID" => $sectionId, "CAL_DAV_CON" => '', 'CAL_DAV_CAL' => '', 'CAL_DAV_MOD' => '', ], ]); } private static function mergeExcludedDates($currentExDates, $newExDates) { if (is_string($currentExDates)) { $currentExDates = explode(';', $currentExDates); } if (is_string($newExDates)) { $newExDates = explode(';', $newExDates); } return implode(';', array_unique(array_merge($currentExDates, $newExDates))); } /** * @param array $params * @param array $curEvent * @return array */ private static function checkRecurrenceLocationChanges(array $params, array $curEvent): array { $isRecurrentLocationChanged = false; if (!empty($params['arFields']['LOCATION']) && is_string($params['arFields']['LOCATION'])) { $parsedLocation = Rooms\Util::parseLocation($params['arFields']['LOCATION']); if (!empty($parsedLocation['room_event_id'])) { $params['arFields']['LOCATION'] = 'calendar_' . $parsedLocation['room_id']; } $isRecurrentLocationChanged = true; if (!empty($curEvent['LOCATION']) && is_string($curEvent['LOCATION'])) { $currentLocation = $curEvent['LOCATION']; $parsedCurrentLocation = Rooms\Util::parseLocation($curEvent['LOCATION']); if (!empty($parsedCurrentLocation['room_event_id'])) { $currentLocation = 'calendar_' . $parsedCurrentLocation['room_id']; } $isRecurrentLocationChanged = $params['arFields']['LOCATION'] !== $currentLocation; } } return [$isRecurrentLocationChanged, $params]; } /** * @param array $params * @param array $curEvent * @return bool */ private static function checkRecurrenceAttendeesChanges(array $params, array $curEvent): bool { $isRecurrentAttendeesChanged = false; if ( !empty($params['arFields']['ATTENDEES_CODES']) && is_array($params['arFields']['ATTENDEES_CODES']) && !empty($curEvent['ATTENDEES_CODES']) && is_array($curEvent['ATTENDEES_CODES']) ) { $firstDiff = array_diff($params['arFields']['ATTENDEES_CODES'], $curEvent['ATTENDEES_CODES']); $secondDiff = array_diff($curEvent['ATTENDEES_CODES'], $params['arFields']['ATTENDEES_CODES']); $isRecurrentAttendeesChanged = !empty($firstDiff) || !empty($secondDiff); } return $isRecurrentAttendeesChanged; } private static function checkCollabSectionAccess(array $collabSections): bool { $noEditAccessedCalendars = true; foreach ($collabSections as $section) { if ($noEditAccessedCalendars && $section['PERM']['edit']) { $noEditAccessedCalendars = false; break; } } return $noEditAccessedCalendars; } public static function getCurrentEventForSaving(int $eventId, $userId, $checkPermission) { $result = CCalendarEvent::GetList( [ 'arFilter' => [ 'ID' => $eventId, 'DELETED' => 'N', ], 'parseRecursion' => false, 'fetchAttendees' => true, 'fetchMeetings' => false, 'userId' => $userId, 'checkPermissions' => $checkPermission, 'loadOriginalRecursion' => true, ] ); if ($result) { return $result[0]; } return $result; } /** * @throws Main\Access\Exception\UnknownActionException */ public static function hasTypeAccess(): bool { $typeModel = TypeModel::createFromXmlId(self::$type); return (new TypeAccessController(self::$userId)) ->check(ActionDictionary::ACTION_TYPE_ACCESS, $typeModel, []) ; } public static function isReadOnly(array $sections, array $collabSectionList = []): bool { $permission = self::GetPermissions([ 'type' => self::$type, 'ownerId' => self::$ownerId, 'userId' => self::$userId, ]); $readOnly = !$permission['edit'] && !$permission['section_edit']; if (self::$type === Dictionary::CALENDAR_TYPE['user'] && self::$userId !== self::$ownerId) { $readOnly = true; } if (self::$bAnonym) { $readOnly = true; } $groupOrUser = self::$type === Dictionary::CALENDAR_TYPE['user'] || self::$type === Dictionary::CALENDAR_TYPE['group'] ; $noEditAccessedCalendars = $groupOrUser; if (self::hasToCreateDefaultCalendar($sections)) { $sections[] = \CCalendarSect::CreateDefault([ 'type' => self::$type, 'ownerId' => self::$ownerId, ]); } foreach ($sections as $section) { if ( $groupOrUser && $section['CAL_TYPE'] === self::$type && (int)$section['OWNER_ID'] === (int)self::$ownerId ) { if ($noEditAccessedCalendars && $section['PERM']['edit']) { $noEditAccessedCalendars = false; } if ($readOnly && ($section['PERM']['edit'] || $section['PERM']['edit_section'])) { $readOnly = false; } } } if (!empty($collabSectionList)) { $noEditAccessedCalendars = self::checkCollabSectionAccess($collabSectionList); } if ($groupOrUser && $noEditAccessedCalendars) { $readOnly = true; } return $readOnly; } /** * @throws Main\ObjectPropertyException * @throws Main\SystemException * @throws ArgumentException */ public static function getSectionsInfo($isCollabUser): array { $followedSectionList = UserSettings::getFollowedSectionIdList(self::$userId); $roomsList = Rooms\Manager::getRoomsList(); $collabSectionList = []; if (self::$type === Dictionary::CALENDAR_TYPE['location']) { $sectionList = $roomsList ?? []; } else { $owners = [self::$ownerId]; $types = [self::$type]; if (self::$type === 'user' && OpenEvents\Feature::getInstance()->isAvailable()) { $types[] = Core\Event\Tools\Dictionary::CALENDAR_TYPE['open_event']; $owners[] = 0; } $sectionList = self::getSectionList([ 'CAL_TYPE' => $types, 'OWNER_ID' => $owners, 'ACTIVE' => 'Y', 'ADDITIONAL_IDS' => $followedSectionList, 'checkPermissions' => true, 'getPermissions' => true, 'getImages' => true, ]); if (self::$type === Dictionary::CALENDAR_TYPE['user'] && $isCollabUser) { $userCollabIds = UserCollabs::getInstance()->getIds(self::$userId); $collabSectionList = self::getSectionList([ 'CAL_TYPE' => Dictionary::CALENDAR_TYPE['group'], 'OWNER_ID' => $userCollabIds, 'ACTIVE' => 'Y', 'checkPermissions' => true, 'getPermissions' => true, ]); } } $sectionList = array_merge( $sectionList, self::getSectionListAvailableForUser(self::$userId), $collabSectionList ); return [$sectionList, $collabSectionList, $followedSectionList, $roomsList]; } public static function hasToCreateDefaultCalendar(array $sections): bool { $createDefault = !self::$bAnonym; if (self::$type === Dictionary::CALENDAR_TYPE['user']) { $createDefault = self::$userId === (int)self::$ownerId; } foreach ($sections as $section) { if ( $createDefault && $section['CAL_TYPE'] === self::$type && (int)$section['OWNER_ID'] === (int)self::$ownerId ) { return false; } } return $createDefault; } }