File manager - Edit - /home/c14075/dragmet-ural.ru/www/bitrix/modules/calendar/lib/controller/calendarentryajax.php
Back
<? namespace Bitrix\Calendar\Controller; use Bitrix\Calendar\Rooms; use Bitrix\Calendar\Internals; use Bitrix\Calendar\UserSettings; use Bitrix\Calendar\Util; use Bitrix\Main\Error; use Bitrix\Main\Localization\Loc; use Bitrix\Calendar\Integration\Bitrix24Manager; use Bitrix\Intranet; use Bitrix\Main\Loader; Loc::loadMessages($_SERVER['DOCUMENT_ROOT'].BX_ROOT.'/modules/calendar/lib/controller/calendarajax.php'); /** * Class CalendarEntryAjax */ class CalendarEntryAjax extends \Bitrix\Main\Engine\Controller { public function configureActions(): array { return []; } public function getNearestEventsAction() { if (Loader::includeModule('intranet') && !\Bitrix\Intranet\Util::isIntranetUser()) { return []; } $request = $this->getRequest(); $calendarType = $request->getPost('type'); $futureDaysAmount = (int)$request->getPost('futureDaysAmount'); $maxEntryAmount = (int)$request->getPost('maxEntryAmount'); $entries = \CCalendar::getNearestEventsList([ 'bCurUserList' => true, 'fromLimit' => \CCalendar::Date(time(), false), 'toLimit' => \CCalendar::Date(time() + \CCalendar::DAY_LENGTH * $futureDaysAmount, false), 'type' => $calendarType, 'maxAmount' => $maxEntryAmount ] ); return [ 'entries' => $entries, ]; } public function loadEntriesAction() { $request = $this->getRequest(); $finish = false; $monthFrom = (int)$request->getPost('month_from'); $yearFrom = (int)$request->getPost('year_from'); $monthTo = (int)$request->getPost('month_to'); $yearTo = (int)$request->getPost('year_to'); $loadLimit = (int)$request->getPost('loadLimit'); $ownerId = (int)$request->getPost('ownerId'); $calendarType = $request->getPost('type'); $loadNext = $request->getPost('loadNext') === 'Y'; $loadPrevious = $request->getPost('loadPrevious') === 'Y'; $parseRecursion = true; $activeSectionIds = is_array($request->getPost('active_sect')) ? $request->getPost('active_sect') : []; $additionalSectionIds = is_array($request->getPost('sup_sect')) ? $request->getPost('sup_sect') : []; $params = [ 'type' => $calendarType, 'section' => [], 'fromLimit' => $monthFrom ? \CCalendar::Date(mktime(0, 0, 0, $monthFrom, 1, $yearFrom), false) : false, 'toLimit' => $monthTo ? \CCalendar::Date(mktime(0, 0, 0, $monthTo, 1, $yearTo), false) : false, ]; $connections = false; if ($loadNext|| $loadPrevious) { $params['limit'] = $loadLimit; $parseRecursion = false; } if ($request->getPost('cal_dav_data_sync') === 'Y' && \CCalendar::IsCalDAVEnabled()) { $config = []; \CCalendar::InitExternalCalendarsSyncParams($config); // \CDavGroupdavClientCalendar::DataSync("user", $ownerId); if ($config['connections']) { $connections = $config['connections']; } } $fetchTasks = false; $sectionIdList = []; $entries = []; foreach(array_unique(array_merge($activeSectionIds, $additionalSectionIds)) as $sectId) { if ($sectId === 'tasks') { $fetchTasks = true; } elseif ((int)$sectId > 0) { $sectionIdList[] = (int)$sectId; } } if (count($sectionIdList) > 0) { $sect = \CCalendarSect::GetList([ 'arFilter' => [ 'ID'=> $sectionIdList, 'ACTIVE' => 'Y' ], 'checkPermissions' => true ]); foreach($sect as $section) { $params['section'][] = (int)$section['ID']; } } if (count($params['section']) > 0) { $arFilter = [ 'SECTION' => $params['section'] ]; if (isset($params['fromLimit'])) { $arFilter["FROM_LIMIT"] = $params['fromLimit']; } if (isset($params['toLimit'])) { $arFilter["TO_LIMIT"] = $params['toLimit']; } if ($params['type'] === 'user') { $fetchMeetings = in_array(\CCalendar::GetMeetingSection($arFilter['OWNER_ID'] ?? null), $params['section']); } else { $fetchMeetings = in_array(\CCalendar::GetCurUserMeetingSection(), $params['section']); } $res = \CCalendarEvent::GetList( [ 'arFilter' => $arFilter, 'parseRecursion' => $parseRecursion, 'fetchAttendees' => true, 'userId' => \CCalendar::GetCurUserId(), 'fetchMeetings' => $fetchMeetings, 'setDefaultLimit' => false, 'limit' => ($params['limit'] ?? null) ] ); $finish = ($params['limit'] ?? false) && count($res) < $params['limit']; $lastDateTimestamp = 0; $firstDateTimestamp = INF; foreach($res as $entry) { if (in_array($entry['SECT_ID'], $params['section'])) { $entries[] = $entry; if ($loadNext && !\CCalendarEvent::CheckRecurcion($entry) && $entry['DATE_TO_TS_UTC'] > $lastDateTimestamp) { $lastDateTimestamp = $entry['DATE_TO_TS_UTC']; } elseif ($loadPrevious && !\CCalendarEvent::CheckRecurcion($entry) && $entry['DATE_FROM_TS_UTC'] < $lastDateTimestamp) { $firstDateTimestamp = $entry['DATE_FROM_TS_UTC']; } } } if ($loadNext) { $params['toLimit'] = \CCalendar::Date($lastDateTimestamp); } if ($loadPrevious) { $params['fromLimit'] = \CCalendar::Date($firstDateTimestamp); } if (!$parseRecursion) { foreach($entries as $entry) { if (in_array($entry['SECT_ID'], $params['section']) && \CCalendarEvent::CheckRecurcion($entry)) { \CCalendarEvent::ParseRecursion($entries, $entry, [ 'fromLimit' => $params['fromLimit'], 'toLimit' => $params['toLimit'], 'instanceCount' => false, 'preciseLimits' => true ]); } } } } // **** GET TASKS **** if ($fetchTasks) { $tasksEntries = \CCalendar::getTaskList( [ 'type' => $calendarType, 'ownerId' => $ownerId ] ); if (count($tasksEntries) > 0) { $entries = array_merge($entries, $tasksEntries); } } $response = [ 'entries' => $entries, 'userIndex' => \CCalendarEvent::getUserIndex(), ]; if (is_array($connections)) { $response['connections'] = $connections; } if (isset($params['limit']) && $params['limit']) { $response['finish'] = $finish; } return $response; } public function moveEventAction() { $request = $this->getRequest(); $userId = \CCalendar::getCurUserId(); $id = (int)$request->getPost('id'); $sectionId = (int)$request->getPost('section'); if ( (!$id && !\CCalendarSect::CanDo('calendar_add', $sectionId, $userId)) || ($id && !\CCalendarSect::CanDo('calendar_edit', $sectionId, $userId)) ) { $this->addError(new Error(Loc::getMessage('EC_ACCESS_DENIED'), 'move_entry_access_denied')); } $sectionList = Internals\SectionTable::getList([ 'filter' => [ '=ACTIVE' => 'Y', '=ID' => $sectionId ], 'select' => [ 'ID', 'CAL_TYPE', 'OWNER_ID', 'NAME' ] ] ); if (!($section = $sectionList->fetch())) { $this->addError(new Error(Loc::getMessage('EC_SECTION_NOT_FOUND'), 'edit_entry_section_not_found')); } if (!\CCalendarType::CanDo('calendar_type_edit', $section['CAL_TYPE'])) { $this->addError(new Error(Loc::getMessage('EC_ACCESS_DENIED'), 'type_access_denied')); } if ( $section['CAL_TYPE'] !== 'group' && Loader::includeModule('intranet') && !Intranet\Util::isIntranetUser($userId) ) { $this->addError(new Error(Loc::getMessage('EC_ACCESS_DENIED'), 'edit_entry_extranet_access_denied')); } if (empty($this->getErrors())) { $entry = Internals\EventTable::getList( [ "filter" => [ "=ID" => $id, "=DELETED" => 'N', "=SECTION_ID" => $sectionId ], "select" => ["ID", "CAL_TYPE"] ] )->fetch(); if (Loader::includeModule('intranet')) { if ($entry['CAL_TYPE'] !== 'group' && !Intranet\Util::isIntranetUser($userId)) { $this->addError(new Error(Loc::getMessage('EC_ACCESS_DENIED'), 'edit_entry_extranet_access_denied')); } } } $requestUid = (int)$request->getPost('requestUid'); $reload = $request->getPost('recursive') === 'Y'; $sendInvitesToDeclined = $request->getPost('sendInvitesAgain') === 'Y'; $skipTime = $request->getPost('skip_time') === 'Y'; $dateFrom = $request->getPost('date_from'); $dateTo = $request->getPost('date_to'); $timezone = $request->getPost('timezone'); $attendees = $request->getPost('attendees'); $location = trim((string) $request->getPost('location')); $isPlannerFeatureEnabled = Bitrix24Manager::isPlannerFeatureEnabled(); $locationBusyWarning = false; $busyWarning = false; if (empty($this->getErrors())) { $arFields = [ "ID" => $id, "DATE_FROM" => \CCalendar::Date(\CCalendar::Timestamp($dateFrom), !$skipTime), "SKIP_TIME" => $skipTime ]; if (!empty($dateTo)) { $arFields["DATE_TO"] = \CCalendar::Date(\CCalendar::Timestamp($dateTo), !$skipTime); } if (!$skipTime && $request->getPost('set_timezone') === 'Y' && $timezone) { $arFields["TZ_FROM"] = $timezone; $arFields["TZ_TO"] = $timezone; } if ( $isPlannerFeatureEnabled && !empty($location) && !Rooms\AccessibilityManager::checkAccessibility($location, ['fields' => $arFields]) ) { $locationBusyWarning = true; $reload = true; } if ( $isPlannerFeatureEnabled && is_array($attendees) && $request->getPost('is_meeting') === 'Y' ) { $fromTs = \CCalendar::Timestamp($arFields["DATE_FROM"]); $toTs = \CCalendar::Timestamp($arFields["DATE_TO"]); $fromTs -= \CCalendar::GetTimezoneOffset($timezone, $fromTs); $toTs -= \CCalendar::GetTimezoneOffset($timezone, $toTs); if (!empty($this->getBusyUsersIds($attendees, $id, $fromTs, $toTs))) { $busyWarning = true; $reload = true; } } if (!$busyWarning && !$locationBusyWarning) { if ($request->getPost('recursive') === 'Y') { \CCalendar::SaveEventEx( [ 'arFields' => $arFields, 'silentErrorMode' => false, 'recursionEditMode' => 'this', 'currentEventDateFrom' => \CCalendar::Date( \CCalendar::Timestamp($request->getPost('current_date_from')), false ), 'sendInvitesToDeclined' => $sendInvitesToDeclined, 'requestUid' => $requestUid ] ); } else { $id = \CCalendar::SaveEvent( [ 'arFields' => $arFields, 'silentErrorMode' => false, 'sendInvitesToDeclined' => $sendInvitesToDeclined, 'requestUid' => $requestUid ] ); } } } return [ 'id' => $id, 'reload' => $reload, 'busy_warning' => $busyWarning, 'location_busy_warning' => $locationBusyWarning ]; } public function editEntryAction() { $response = []; $request = $this->getRequest(); $id = (int)$request['id']; $sectionId = (int)$request['section']; $requestUid = (int)$request['requestUid']; $userId = \CCalendar::getCurUserId(); $isPlannerFeatureEnabled = Bitrix24Manager::isPlannerFeatureEnabled(); $checkCurrentUsersAccessibility = !$id || $request->getPost('checkCurrentUsersAccessibility') !== 'N'; if ( (!$id && !\CCalendarSect::CanDo('calendar_add', $sectionId, $userId)) || (!$id && !\CCalendarSect::CanDo('calendar_edit', $sectionId, $userId))) { $this->addError(new Error(Loc::getMessage('EC_ACCESS_DENIED'), 'edit_entry_access_denied')); } if (!empty($this->getErrors())) { return $response; } $sectionList = Internals\SectionTable::getList([ 'filter' => [ '=ACTIVE' => 'Y', '=ID' => $sectionId ], 'select' => [ 'ID', 'CAL_TYPE', 'OWNER_ID', 'NAME' ] ] ); if (!($section = $sectionList->fetch())) { $this->addError(new Error(Loc::getMessage('EC_SECTION_NOT_FOUND'), 'edit_entry_section_not_found')); } if (!\CCalendarType::CanDo('calendar_type_edit', $section['CAL_TYPE'])) { $this->addError(new Error(Loc::getMessage('EC_ACCESS_DENIED'), 'type_access_denied')); } if ( $section['CAL_TYPE'] !== 'group' && Loader::includeModule('intranet') && !Intranet\Util::isIntranetUser($userId) ) { $this->addError(new Error(Loc::getMessage('EC_ACCESS_DENIED'), 'edit_entry_extranet_access_denied')); } if (!empty($this->getErrors())) { return $response; } // Default name for events $name = trim($request['name']); if (empty($name)) { $name = Loc::getMessage('EC_DEFAULT_EVENT_NAME_V2'); } $reminderList = \CCalendarReminder::prepareReminder($request['reminder']); $rrule = $this->prepareRecurringRule($request['EVENT_RRULE'], $request['rrule_endson']); // Date & Time $dateFrom = $request['date_from']; $dateTo = $request['date_to']; $skipTime = isset($request['skip_time']) && $request['skip_time'] === 'Y'; if (!$skipTime) { $dateFrom .= ' '.$request['time_from']; $dateTo .= ' '.$request['time_to']; } $dateFrom = trim($dateFrom); $dateTo = trim($dateTo); // Timezone $tzFrom = $request['tz_from']; $tzTo = $request['tz_to']; if (!$tzFrom && isset($request['default_tz'])) { $tzFrom = $request['default_tz']; } if (!$tzTo && isset($request['default_tz'])) { $tzTo = $request['default_tz']; } if (isset($request['default_tz']) && $request['default_tz'] != '') { \CCalendar::SaveUserTimezoneName(\CCalendar::GetUserId(), $request['default_tz']); } $entryFields = [ 'ID' => $id, 'DATE_FROM' => $dateFrom, 'DATE_TO' => $dateTo, 'SKIP_TIME' => $skipTime, 'TZ_FROM' => $tzFrom, 'TZ_TO' => $tzTo, 'NAME' => $name, 'DESCRIPTION' => trim($request['desc']), 'SECTIONS' => [$sectionId], 'COLOR' => $request['color'], 'ACCESSIBILITY' => $request['accessibility'], 'IMPORTANCE' => $request['importance'] ?? 'normal', 'PRIVATE_EVENT' => $request['private_event'] === 'Y', 'RRULE' => $rrule, 'LOCATION' => $request['location'], 'REMIND' => $reminderList, 'SECTION_CAL_TYPE' => $section['CAL_TYPE'], 'SECTION_OWNER_ID' => $section['OWNER_ID'] ]; $codes = []; if (isset($request['attendeesEntityList']) && is_array($request['attendeesEntityList'])) { $codes = Util::convertEntitiesToCodes($request['attendeesEntityList']); } $accessCodes = \CCalendarEvent::handleAccessCodes($codes, ['userId' => $userId]); $entryFields['IS_MEETING'] = $accessCodes != ['U'.$userId]; $entryFields['ATTENDEES_CODES'] = $accessCodes; $entryFields['ATTENDEES'] = \CCalendar::GetDestinationUsers($accessCodes); $response['reload'] = true; if ($request['exclude_users'] && count($entryFields['ATTENDEES']) > 0) { $excludeUsers = explode(',', $request['exclude_users']); $entryFields['ATTENDEES_CODES'] = []; if (count($excludeUsers) > 0) { $entryFields['ATTENDEES'] = array_diff($entryFields['ATTENDEES'], $excludeUsers); foreach($entryFields['ATTENDEES'] as $attendee) { $entryFields['ATTENDEES_CODES'][] = 'U'. (int)$attendee; } } } else { $excludeUsers = []; } $meetingHost = (int)$request['meeting_host']; $chatId = (int)$request['chat_id']; if ($meetingHost) { $entryFields['MEETING_HOST'] = $meetingHost; } else { $entryFields['MEETING_HOST'] = \CCalendar::GetUserId(); } $entryFields['MEETING'] = [ 'HOST_NAME' => \CCalendar::GetUserName($entryFields['MEETING_HOST']), 'NOTIFY' => $request['meeting_notify'] === 'Y', 'REINVITE' => $request['meeting_reinvite'] === 'Y', 'ALLOW_INVITE' => $request['allow_invite'] === 'Y', 'MEETING_CREATOR' => $entryFields['MEETING_HOST'], 'HIDE_GUESTS' => $request['hide_guests'] === 'Y' ]; if ($chatId) { $entryFields['MEETING']['CHAT_ID'] = $chatId; } if (!Rooms\AccessibilityManager::checkAccessibility($entryFields['LOCATION'], ['fields' => $entryFields])) { $this->addError(new Error(Loc::getMessage('EC_LOCATION_BUSY'), 'edit_entry_location_busy')); } if ($entryFields['IS_MEETING'] && $isPlannerFeatureEnabled) { $attendees = []; if ($checkCurrentUsersAccessibility) { $attendees = $entryFields['ATTENDEES']; } else if (is_array($request['newAttendeesList'])) { $attendees = array_diff($request['newAttendeesList'], $excludeUsers); } $fromTs = \CCalendar::Timestamp($dateFrom); $toTs = \CCalendar::Timestamp($dateTo); $fromTs -= \CCalendar::GetTimezoneOffset($tzFrom, $fromTs); $toTs -= \CCalendar::GetTimezoneOffset($tzTo, $toTs); $busyUsers = $this->getBusyUsersIds($attendees, $id, $fromTs, $toTs); if (count($busyUsers) > 0) { $response['busyUsersList'] = \CCalendarEvent::getUsersDetails($busyUsers); $this->addError(new Error(Loc::getMessage('EC_USER_BUSY', ['#USER#' => \CCalendar::GetUserName($busyUsers[0])]), 'edit_entry_user_busy')); } } // Userfields for event $arUFFields = []; foreach($request as $field => $value) { if (mb_strpos($field, 'UF_') === 0) { $arUFFields[$field] = $value; } } if (!empty($this->getErrors())) { return $response; } $newId = \CCalendar::SaveEvent([ 'arFields' => $entryFields, 'UF' => $arUFFields, 'silentErrorMode' => false, 'recursionEditMode' => $request['rec_edit_mode'], 'currentEventDateFrom' => \CCalendar::Date(\CCalendar::Timestamp($request['current_date_from']), false), 'sendInvitesToDeclined' => $request['sendInvitesAgain'] === 'Y', 'requestUid' => $requestUid ]); $errors = \CCalendar::GetErrors(); $eventList = []; $eventIdList = [$newId]; if ($newId && !count($errors)) { $response['entryId'] = $newId; $filter = [ 'ID' => $newId, 'FROM_LIMIT' => \CCalendar::Date( \CCalendar::Timestamp($entryFields['DATE_FROM']) - \CCalendar::DAY_LENGTH * 10, false ), 'TO_LIMIT' => \CCalendar::Date( \CCalendar::Timestamp($entryFields['DATE_TO']) + \CCalendar::DAY_LENGTH * 90, false ) ]; $eventList = \CCalendarEvent::GetList( [ 'arFilter' => $filter, 'parseRecursion' => true, 'fetchAttendees' => true, 'userId' => \CCalendar::GetUserId(), ] ); if ($entryFields['IS_MEETING']) { \Bitrix\Main\FinderDestTable::merge( [ 'CONTEXT' => Util::getUserSelectorContext(), 'CODE' => \Bitrix\Main\FinderDestTable::convertRights( $accessCodes, ['U'. \CCalendar::GetUserId()] ) ] ); } if (isset($_REQUEST['rec_edit_mode']) && in_array($_REQUEST['rec_edit_mode'], ['this', 'next'])) { unset($filter['ID']); $filter['RECURRENCE_ID'] = ($eventList && $eventList[0] && $eventList[0]['RECURRENCE_ID']) ? $eventList[0]['RECURRENCE_ID'] : $newId; $resRelatedEvents = \CCalendarEvent::GetList( [ 'arFilter' => $filter, 'parseRecursion' => true, 'fetchAttendees' => true, 'userId' => \CCalendar::GetUserId() ] ); foreach($resRelatedEvents as $ev) { $eventIdList[] = $ev['ID']; } $eventList = array_merge($eventList, $resRelatedEvents); } else if ($id && $eventList && $eventList[0] && \CCalendarEvent::CheckRecurcion($eventList[0])) { $recId = $eventList[0]['RECURRENCE_ID'] ?? $eventList[0]['ID']; if ($eventList[0]['RECURRENCE_ID'] && $eventList[0]['RECURRENCE_ID'] !== $eventList[0]['ID']) { unset($filter['RECURRENCE_ID']); $filter['ID'] = $eventList[0]['RECURRENCE_ID']; $resRelatedEvents = \CCalendarEvent::GetList( [ 'arFilter' => $filter, 'parseRecursion' => true, 'fetchAttendees' => true, 'userId' => \CCalendar::GetUserId(), ] ); $eventIdList[] = $eventList[0]['RECURRENCE_ID']; $eventList = array_merge($eventList, $resRelatedEvents); } if ($recId) { unset($filter['ID']); $filter['RECURRENCE_ID'] = $recId; $resRelatedEvents = \CCalendarEvent::GetList( [ 'arFilter' => $filter, 'parseRecursion' => true, 'fetchAttendees' => true, 'userId' => \CCalendar::GetUserId(), ] ); foreach($resRelatedEvents as $ev) { $eventIdList[] = $ev['ID']; } $eventList = array_merge($eventList, $resRelatedEvents); } } } else if (is_iterable($errors)) { foreach ($errors as $error) { if (is_string($error)) { $this->addError(new Error($error, 'send_invite_failed')); } } } $pathToCalendar = \CCalendar::GetPathForCalendarEx($userId); foreach($eventList as $ind => $event) { $eventList[$ind]['~URL'] = \CHTTP::urlAddParams($pathToCalendar, ['EVENT_ID' => $event['ID']]); } $response['eventList'] = $eventList; $response['eventIdList'] = $eventIdList; $response['countEventWithEmailGuestAmount'] = Bitrix24Manager::getCountEventWithEmailGuestAmount(); $userSettings = UserSettings::get($userId); $userSettings['defaultReminders'][$skipTime ? 'fullDay' : 'withTime'] = $reminderList; UserSettings::set($userSettings, $userId); return $response; } private function getBusyUsersIds(array $attendees, int $curEventId, int $fromTs, int $toTs): array { $usersToCheck = $this->getUsersToCheck($attendees); if (empty($usersToCheck)) { return []; } $accessibility = \CCalendar::GetAccessibilityForUsers([ 'users' => $usersToCheck, 'from' => \CCalendar::Date($fromTs, false), // date or datetime in UTC 'to' => \CCalendar::Date($toTs, false), // date or datetime in UTC 'curEventId' => $curEventId, 'getFromHR' => true, 'checkPermissions' => false ]); $busyUsersList = []; foreach ($accessibility as $accUserId => $entries) { foreach ($entries as $entry) { $entFromTs = \CCalendar::Timestamp($entry['DATE_FROM']); $entToTs = \CCalendar::Timestamp($entry['DATE_TO']); if ($entry['DT_SKIP_TIME'] === 'Y') { $entToTs += \CCalendar::GetDayLen(); } $entFromTs -= \CCalendar::GetTimezoneOffset($entry['TZ_FROM'], $entFromTs); $entToTs -= \CCalendar::GetTimezoneOffset($entry['TZ_TO'], $entToTs); if ($entFromTs < $toTs && $entToTs > $fromTs) { $busyUsersList[] = $accUserId; } } } return $busyUsersList; } private function getUsersToCheck(array $attendees): array { $usersToCheck = []; foreach ($attendees as $attId) { if ((int)$attId !== \CCalendar::GetUserId()) { $userSettings = UserSettings::get((int)$attId); if ($userSettings && $userSettings['denyBusyInvitation']) { $usersToCheck[] = (int)$attId; } } } return $usersToCheck; } private function prepareRecurringRule(array $rrule = null, ?string $endMode = 'never'): ?array { if (empty($rrule) || !is_array($rrule)) { return null; } if (!isset($rrule['INTERVAL']) && $rrule['FREQ'] !== 'NONE') { $rrule['INTERVAL'] = 1; } if ($endMode === 'never') { unset($rrule['COUNT'], $rrule['UNTIL']); } elseif ($endMode === 'count') { if ((int)$rrule['COUNT'] <= 0) { $rrule['COUNT'] = 10; } unset($rrule['UNTIL']); } elseif ($endMode === 'until') { unset($rrule['COUNT']); } return $rrule; } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.27 |
proxy
|
phpinfo
|
Settings