Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
calendarentryajax.php
1<?php
3
16use Bitrix\Intranet;
18
19Loc::loadMessages($_SERVER['DOCUMENT_ROOT'].BX_ROOT.'/modules/calendar/lib/controller/calendarajax.php');
20
25{
26 protected const DIRECTION_PREVIOUS = 'previous';
27 protected const DIRECTION_NEXT = 'next';
28 protected const DIRECTION_BOTH = 'both';
29
30 public function configureActions(): array
31 {
32 return [];
33 }
34
35 public function getNearestEventsAction()
36 {
37 if (Loader::includeModule('intranet') && !\Bitrix\Intranet\Util::isIntranetUser())
38 {
39 return [];
40 }
41
42 $request = $this->getRequest();
43 $calendarType = $request->getPost('type');
44 $futureDaysAmount = (int)$request->getPost('futureDaysAmount');
45 $maxEntryAmount = (int)$request->getPost('maxEntryAmount');
46
47 $entries = \CCalendar::getNearestEventsList([
48 'bCurUserList' => true,
49 'fromLimit' => \CCalendar::Date(time(), false),
50 'toLimit' => \CCalendar::Date(time() + \CCalendar::DAY_LENGTH * $futureDaysAmount, false),
51 'type' => $calendarType,
52 'maxAmount' => $maxEntryAmount
53 ]
54 );
55
56 return [
57 'entries' => $entries,
58 ];
59 }
60
61 public function loadEntriesAction()
62 {
63 $request = $this->getRequest();
64 $monthFrom = (int)$request->getPost('month_from');
65 $yearFrom = (int)$request->getPost('year_from');
66 $monthTo = (int)$request->getPost('month_to');
67 $yearTo = (int)$request->getPost('year_to');
68 $ownerId = (int)$request->getPost('ownerId');
69 $calendarType = $request->getPost('type');
70
71 $direction = $request->getPost('direction');
72 if (!in_array($direction, [self::DIRECTION_PREVIOUS, self::DIRECTION_NEXT, self::DIRECTION_BOTH], true))
73 {
74 $direction = null;
75 }
76
77 $activeSectionIds = is_array($request->getPost('active_sect'))
78 ? $request->getPost('active_sect')
79 : [];
80 $additionalSectionIds = is_array($request->getPost('sup_sect'))
81 ? $request->getPost('sup_sect')
82 : [];
83
84 $sections = [];
85 $limits = \CCalendarEvent::getLimitDates($yearFrom, $monthFrom, $yearTo, $monthTo);
86
87 $connections = false;
88 $fetchTasks = false;
89 $sectionIdList = [];
90
91 foreach(array_unique(array_merge($activeSectionIds, $additionalSectionIds)) as $sectId)
92 {
93 if ($sectId === 'tasks')
94 {
95 $fetchTasks = true;
96 }
97 elseif ((int)$sectId > 0)
98 {
99 $sectionIdList[] = (int)$sectId;
100 }
101 }
102
103 if (!empty($sectionIdList))
104 {
105 $sect = \CCalendarSect::GetList([
106 'arFilter' => [
107 'ID' => $sectionIdList,
108 'ACTIVE' => 'Y'
109 ],
110 'checkPermissions' => true
111 ]);
112 foreach($sect as $section)
113 {
114 $sections[] = (int)$section['ID'];
115 }
116 }
117
118 $isBoundaryOfPastReached = false;
119 $isBoundaryOfFutureReached = false;
120 $entries = [];
121 if (!empty($sections))
122 {
123 $entries = $this->getEntries($sections, $limits);
124
125 if (
126 $direction === self::DIRECTION_BOTH
127 && count($this->getShownEntries($entries)) < 5
128 )
129 {
130 $isBoundaryOfPastReached = true;
131 $isBoundaryOfFutureReached = true;
132 //Load all events
133 $limits = [
134 'from' => false,
135 'to' => false,
136 ];
137 $entries = $this->getEntries($sections, $limits);
138
139 if (!empty($entries))
140 {
141 $earliestEvent = $this->getEarliestEvent($entries);
142 $timestamp = strtotime($earliestEvent['DATE_FROM']);
143 if($timestamp < strtotime("01.$monthFrom.$yearFrom"))
144 {
145 $yearFrom = (int)date('Y', $timestamp);
146 $monthFrom = (int)date('m', $timestamp);
147 }
148
149 $latestEvent = $this->getLatestEvent($entries);
150 $timestamp = strtotime($latestEvent['DATE_FROM']);
151 if($timestamp > strtotime("01.$monthTo.$yearTo"))
152 {
153 $yearTo = (int)date('Y', $timestamp);
154 $monthTo = (int)date('m', $timestamp);
155 [$yearTo, $monthTo] = $this->getValidYearAndMonth($yearTo, $monthTo + 1);
156 }
157 }
158 }
159
160 if (
161 ($direction === self::DIRECTION_PREVIOUS)
162 && !$this->hasArrayEntriesInMonth($entries, $yearFrom, $monthFrom)
163 )
164 {
165 //Load one month further
166 [$yearFrom, $monthFrom] = $this->getValidYearAndMonth($yearFrom, $monthFrom - 1);
167 $entries = $this->getEntries($sections, \CCalendarEvent::getLimitDates($yearFrom, $monthFrom, $yearTo, $monthTo));
168
169 if (!$this->hasArrayEntriesInMonth($entries, $yearFrom, $monthFrom))
170 {
171 //Load half year further
172 [$yearFrom, $monthFrom] = $this->getValidYearAndMonth($yearFrom, $monthFrom - 5);
173 $limits = \CCalendarEvent::getLimitDates($yearFrom, $monthFrom, $yearTo, $monthTo);
174 $entries = $this->getEntries($sections, $limits);
175
176 if (!$this->hasArrayEntriesInRange($entries, $yearFrom, $monthFrom, (int)$request->getPost('year_from'), (int)$request->getPost('month_from')))
177 {
178 $isBoundaryOfPastReached = true;
179 //Load all events
180 $limits['from'] = false;
181 $entries = $this->getEntries($sections, $limits);
182
183 if (!empty($entries))
184 {
185 $earliestEvent = $this->getEarliestEvent($entries);
186 $timestamp = strtotime($earliestEvent['DATE_FROM']);
187 $yearFrom = (int)date('Y', $timestamp);
188 $monthFrom = (int)date('m', $timestamp);
189 }
190 }
191 }
192 }
193
194 if (
195 ($direction === self::DIRECTION_NEXT)
196 && !$this->hasArrayEntriesInMonth($entries, $yearTo, $monthTo - 1)
197 )
198 {
199 //Load one month further
200 [$yearTo, $monthTo] = $this->getValidYearAndMonth($yearTo, $monthTo + 1);
201 $entries = $this->getEntries($sections, \CCalendarEvent::getLimitDates($yearFrom, $monthFrom, $yearTo, $monthTo));
202
203 if (!$this->hasArrayEntriesInMonth($entries, $yearTo, $monthTo - 1))
204 {
205 //Load half year further
206 [$yearTo, $monthTo] = $this->getValidYearAndMonth($yearTo, $monthTo + 5);
207 $limits = \CCalendarEvent::getLimitDates($yearFrom, $monthFrom, $yearTo, $monthTo);
208 $entries = $this->getEntries($sections, $limits);
209
210 if (!$this->hasArrayEntriesInRange($entries, (int)$request->getPost('year_to'), (int)$request->getPost('month_to') - 1, $yearTo, $monthTo - 1))
211 {
212 $isBoundaryOfFutureReached = true;
213 //Load all events
214 $limits['to'] = false;
215 $entries = $this->getEntries($sections, $limits);
216
217 if (!empty($entries))
218 {
219 $latestEvent = $this->getLatestEvent($entries);
220 $timestamp = strtotime($latestEvent['DATE_FROM']);
221 $yearTo = (int)date('Y', $timestamp);
222 $monthTo = (int)date('m', $timestamp);
223 [$yearTo, $monthTo] = $this->getValidYearAndMonth($yearTo, $monthTo + 1);
224 }
225 }
226 }
227 }
228 }
229
230 $userId = \CCalendar::GetUserId();
231 $accessController = new EventAccessController($userId);
232 foreach ($entries as $key => $entry)
233 {
234 $eventModel = EventModel::createFromArray($entry);
235 $canEditEventInParentSection = $accessController->check(ActionDictionary::ACTION_EVENT_EDIT, $eventModel);
236 $canEditEventInCurrentSection = $accessController->check(ActionDictionary::ACTION_EVENT_EDIT, $eventModel, [
237 'checkCurrentEvent' => 'Y',
238 ]);
239 $entries[$key]['permissions'] = [
240 'edit' => $canEditEventInParentSection && $canEditEventInCurrentSection,
241 ];
242 }
243
244 // **** GET TASKS ****
245 if ($fetchTasks)
246 {
247 $tasksEntries = \CCalendar::getTaskList(
248 [
249 'type' => $calendarType,
250 'ownerId' => $ownerId,
251 ]
252 );
253
254 if (!empty($tasksEntries))
255 {
256 $entries = array_merge($entries, $tasksEntries);
257 }
258 }
259
260 $response = [
261 'entries' => $entries,
262 'userIndex' => \CCalendarEvent::getUserIndex(),
263 'isBoundaryOfPastReached' => $isBoundaryOfPastReached,
264 'isBoundaryOfFutureReached' => $isBoundaryOfFutureReached,
265 ];
266 if (is_array($connections))
267 {
268 $response['connections'] = $connections;
269 }
270
271 if (
272 (int)$request->getPost('month_from') !== $monthFrom
273 || (int)$request->getPost('year_from') !== $yearFrom
274 )
275 {
276 $response['newYearFrom'] = $yearFrom;
277 $response['newMonthFrom'] = $monthFrom;
278 }
279
280 if (
281 (int)$request->getPost('month_to') !== $monthTo
282 || (int)$request->getPost('year_to') !== $yearTo
283 )
284 {
285 $response['newYearTo'] = $yearTo;
286 $response['newMonthTo'] = $monthTo;
287 }
288
289 return $response;
290 }
291
292 protected function getShownEntries(array $entries): array
293 {
294 return CalendarFilter::filterByShowDeclined($entries);
295 }
296
297 protected function getEntries(array $sections, array $limits): array
298 {
299 return \CCalendarEvent::GetList(
300 [
301 'arFilter' => [
302 'SECTION' => $sections,
303 'FROM_LIMIT' => $limits['from'],
304 'TO_LIMIT' => $limits['to'],
305 ],
306 'parseRecursion' => true,
307 'fetchAttendees' => true,
308 'userId' => \CCalendar::GetCurUserId(),
309 'setDefaultLimit' => false,
310 ]
311 );
312 }
313
314 protected function getValidYearAndMonth(int $year, int $month): array
315 {
316 if ($month <= 0)
317 {
318 return [$year - 1, $month + 12];
319 }
320
321 if ($month > 12)
322 {
323 return [$year + 1, $month - 12];
324 }
325
326 return [$year, $month];
327 }
328
329 protected function hasArrayEntriesInMonth(array $entries, int $yearFrom, int $monthFrom): bool
330 {
331 return $this->hasArrayEntriesInRange($entries, $yearFrom, $monthFrom, $yearFrom, $monthFrom);
332 }
333
334 protected function hasArrayEntriesInRange(array $entries, int $yearFrom, int $monthFrom, int $yearTo, int $monthTo): bool
335 {
336 $monthsFrom = $yearFrom * 12 + $monthFrom;
337 $monthsTo = $yearTo * 12 + $monthTo;
338 $settings = UserSettings::get();
339 $showDeclined = $settings['showDeclined'];
340 foreach ($entries as $entry)
341 {
342 if (!$showDeclined && $entry['MEETING_STATUS'] === 'N')
343 {
344 continue;
345 }
346
347 $timestamp = strtotime($entry['DATE_FROM']);
348 $entryYear = (int)date('Y', $timestamp);
349 $entryMonth = (int)date('m', $timestamp);
350 $entryMonths = $entryYear * 12 + $entryMonth;
351
352 if ($entryMonths >= $monthsFrom && $entryMonths <= $monthsTo)
353 {
354 return true;
355 }
356 }
357 return false;
358 }
359
360 protected function getEarliestEvent(array $entries): array
361 {
362 return array_reduce($entries, static function($firstEntry, $entry) {
363 if (!$firstEntry)
364 {
365 return $entry;
366 }
367 if (strtotime($entry['DATE_FROM']) < strtotime($firstEntry['DATE_FROM']))
368 {
369 return $entry;
370 }
371 return $firstEntry;
372 });
373 }
374
375 protected function getLatestEvent(array $entries): array
376 {
377 return array_reduce($entries, static function($lastEntry, $entry) {
378 if (!$lastEntry)
379 {
380 return $entry;
381 }
382 if (strtotime($entry['DATE_FROM']) > strtotime($lastEntry['DATE_FROM']))
383 {
384 return $entry;
385 }
386 return $lastEntry;
387 });
388 }
389
390 public function moveEventAction()
391 {
392 $request = $this->getRequest();
393 $userId = \CCalendar::getCurUserId();
394 $id = (int)$request->getPost('id');
395 $sectionId = (int)$request->getPost('section');
396
397 if ($id)
398 {
399 $eventModel = \CCalendarEvent::getEventModelForPermissionCheck($id, [], $userId);
400 }
401 else
402 {
403 $section = \CCalendarSect::GetById($sectionId);
404
405 $eventModel =
406 EventModel::createNew()
407 ->setOwnerId((int)($section['OWNER_ID'] ?? 0))
408 ->setSectionId($sectionId ?? 0)
409 ->setSectionType($section['TYPE'] ?? '')
410 ;
411 }
412 $accessController = new EventAccessController($userId);
413 if (
414 (!$id && !$accessController->check(ActionDictionary::ACTION_EVENT_ADD, $eventModel))
415 || ($id && !$accessController->check(ActionDictionary::ACTION_EVENT_EDIT, $eventModel))
416 )
417 {
418 $this->addError(new Error(Loc::getMessage('EC_ACCESS_DENIED'), 'move_entry_access_denied'));
419 }
420
421 $sectionList = Internals\SectionTable::getList([
422 'filter' => [
423 '=ACTIVE' => 'Y',
424 '=ID' => $sectionId
425 ],
426 'select' => [
427 'ID',
428 'CAL_TYPE',
429 'OWNER_ID',
430 'NAME'
431 ]
432 ]
433 );
434
435 if (!($section = $sectionList->fetch()))
436 {
437 $this->addError(new Error(Loc::getMessage('EC_SECTION_NOT_FOUND'), 'edit_entry_section_not_found'));
438 }
439
440 if (
441 $section['CAL_TYPE'] !== 'group'
442 && Loader::includeModule('intranet') && !Intranet\Util::isIntranetUser($userId)
443 )
444 {
445 $this->addError(new Error(Loc::getMessage('EC_ACCESS_DENIED'), 'edit_entry_extranet_access_denied'));
446 }
447
448
449 if (empty($this->getErrors()))
450 {
451 $entry = Internals\EventTable::getList(
452 [
453 "filter" => [
454 "=ID" => $id,
455 "=DELETED" => 'N',
456 "=SECTION_ID" => $sectionId
457 ],
458 "select" => ["ID", "CAL_TYPE"]
459 ]
460 )->fetch();
461
462 if (Loader::includeModule('intranet'))
463 {
464 if ($entry['CAL_TYPE'] !== 'group' && !Intranet\Util::isIntranetUser($userId))
465 {
466 $this->addError(new Error(Loc::getMessage('EC_ACCESS_DENIED'), 'edit_entry_extranet_access_denied'));
467 }
468 }
469 }
470
471 $requestUid = (int)$request->getPost('requestUid');
472 $reload = $request->getPost('recursive') === 'Y';
473 $sendInvitesToDeclined = $request->getPost('sendInvitesAgain') === 'Y';
474 $skipTime = $request->getPost('skip_time') === 'Y';
475 $dateFrom = $request->getPost('date_from');
476 $dateTo = $request->getPost('date_to');
477 $timezone = $request->getPost('timezone');
478 $attendees = $request->getPost('attendees');
479 $location = trim((string) $request->getPost('location'));
480 $isPlannerFeatureEnabled = Bitrix24Manager::isPlannerFeatureEnabled();
481
482 $locationBusyWarning = false;
483 $busyWarning = false;
484
485 if (empty($this->getErrors()))
486 {
487 $arFields = [
488 "ID" => $id,
489 "DATE_FROM" => \CCalendar::Date(\CCalendar::Timestamp($dateFrom), !$skipTime),
490 "SKIP_TIME" => $skipTime
491 ];
492
493 if (!empty($dateTo))
494 {
495 $arFields["DATE_TO"] = \CCalendar::Date(\CCalendar::Timestamp($dateTo), !$skipTime);
496 }
497
498 if (!$skipTime && $request->getPost('set_timezone') === 'Y' && $timezone)
499 {
500 $arFields["TZ_FROM"] = $timezone;
501 $arFields["TZ_TO"] = $timezone;
502 }
503
504 if (
505 $isPlannerFeatureEnabled
506 && !empty($location)
507 && !Rooms\AccessibilityManager::checkAccessibility($location, ['fields' => $arFields])
508 )
509 {
510 $locationBusyWarning = true;
511 $reload = true;
512 }
513
514 if (
515 $isPlannerFeatureEnabled
516 && is_array($attendees)
517 && $request->getPost('is_meeting') === 'Y'
518 )
519 {
520 $timezoneName = \CCalendar::GetUserTimezoneName(\CCalendar::GetUserId());
521 $timezoneOffset = Util::getTimezoneOffsetUTC($timezoneName);
522 $timestampFrom = \CCalendar::TimestampUTC($arFields["DATE_FROM"]) - $timezoneOffset;
523 $timestampTo = \CCalendar::TimestampUTC($arFields["DATE_TO"]) - $timezoneOffset;
524 if (!empty($this->getBusyUsersIds($attendees, $id, $timestampFrom, $timestampTo)))
525 {
526 $busyWarning = true;
527 $reload = true;
528 }
529 }
530
531 if (!$busyWarning && !$locationBusyWarning)
532 {
533 if ($request->getPost('recursive') === 'Y')
534 {
535 \CCalendar::SaveEventEx(
536 [
537 'arFields' => $arFields,
538 'silentErrorMode' => false,
539 'recursionEditMode' => 'this',
540 'currentEventDateFrom' => \CCalendar::Date(
541 \CCalendar::Timestamp($request->getPost('current_date_from')),
542 false
543 ),
544 'sendInvitesToDeclined' => $sendInvitesToDeclined,
545 'requestUid' => $requestUid
546 ]
547 );
548 }
549 else
550 {
551 $id = \CCalendar::SaveEvent(
552 [
553 'arFields' => $arFields,
554 'silentErrorMode' => false,
555 'sendInvitesToDeclined' => $sendInvitesToDeclined,
556 'requestUid' => $requestUid
557 ]
558 );
559 }
560 }
561 }
562
563 return [
564 'id' => $id,
565 'reload' => $reload,
566 'busy_warning' => $busyWarning,
567 'location_busy_warning' => $locationBusyWarning
568 ];
569 }
570
571 public function editEntryAction()
572 {
573 $response = [];
574 $request = $this->getRequest();
575
576 $id = (int)$request['id'];
577 $sectionId = (int)$request['section'];
578 $requestUid = (int)$request['requestUid'];
579 $userId = \CCalendar::getCurUserId();
580 $isPlannerFeatureEnabled = Bitrix24Manager::isPlannerFeatureEnabled();
581 $checkCurrentUsersAccessibility = !$id || $request->getPost('checkCurrentUsersAccessibility') !== 'N';
582
583 if ($id)
584 {
585 $eventModel = \CCalendarEvent::getEventModelForPermissionCheck($id, [], $userId);
586 }
587 else
588 {
589 $section = \CCalendarSect::GetById($sectionId, false);
590
591 $eventModel =
592 EventModel::createNew()
593 ->setOwnerId((int)($section['OWNER_ID'] ?? 0))
594 ->setSectionId($sectionId)
595 ->setSectionType($section['CAL_TYPE'] ?? '')
596 ;
597 }
598 $accessController = new EventAccessController($userId);
599 if (
600 (!$id && !$accessController->check(ActionDictionary::ACTION_EVENT_ADD, $eventModel))
601 || ($id && !$accessController->check(ActionDictionary::ACTION_EVENT_EDIT, $eventModel))
602 )
603 {
604 $this->addError(new Error(Loc::getMessage('EC_ACCESS_DENIED'), 'edit_entry_access_denied'));
605 }
606
607 if (!empty($this->getErrors()))
608 {
609 return $response;
610 }
611
612 $sectionList = Internals\SectionTable::getList([
613 'filter' => [
614 '=ACTIVE' => 'Y',
615 '=ID' => $sectionId
616 ],
617 'select' => [
618 'ID',
619 'CAL_TYPE',
620 'OWNER_ID',
621 'NAME'
622 ]
623 ]
624 );
625
626 if (!($section = $sectionList->fetch()))
627 {
628 $this->addError(new Error(Loc::getMessage('EC_SECTION_NOT_FOUND'), 'edit_entry_section_not_found'));
629 }
630
631 if (
632 $section['CAL_TYPE'] !== 'group'
633 && Loader::includeModule('intranet') && !Intranet\Util::isIntranetUser($userId)
634 )
635 {
636 $this->addError(new Error(Loc::getMessage('EC_ACCESS_DENIED'), 'edit_entry_extranet_access_denied'));
637 }
638
639 if (!empty($this->getErrors()))
640 {
641 return $response;
642 }
643
644 // Default name for events
645 $name = trim($request['name']);
646 if (empty($name))
647 {
648 $name = Loc::getMessage('EC_DEFAULT_EVENT_NAME_V2');
649 }
650 $reminderList = \CCalendarReminder::prepareReminder($request['reminder']);
651
652 $rrule = $this->prepareRecurringRule($request['EVENT_RRULE'], $request['rrule_endson']);
653
654 // Date & Time
655 $dateFrom = $request['date_from'];
656 $dateTo = $request['date_to'];
657 $skipTime = isset($request['skip_time']) && $request['skip_time'] === 'Y';
658 if (!$skipTime)
659 {
660 $dateFrom .= ' '.$request['time_from'];
661 $dateTo .= ' '.$request['time_to'];
662 }
663 $dateFrom = trim($dateFrom);
664 $dateTo = trim($dateTo);
665
666 if (
667 (int)(new \DateTime())->setTimestamp(\CCalendar::Timestamp($dateFrom))->format('Y') > 9999
668 || (int)(new \DateTime())->setTimestamp(\CCalendar::Timestamp($dateTo))->format('Y') > 9999
669 )
670 {
671 $this->addError(new Error(Loc::getMessage('EC_JS_EV_FROM_ERR')));
672 return false;
673 }
674
675 // Timezone
676 $tzFrom = $request['tz_from'];
677 $tzTo = $request['tz_to'];
678 if (!$tzFrom && isset($request['default_tz']))
679 {
680 $tzFrom = $request['default_tz'];
681 }
682 if (!$tzTo && isset($request['default_tz']))
683 {
684 $tzTo = $request['default_tz'];
685 }
686
687 if (isset($request['default_tz']) && (string)$request['default_tz'] !== '')
688 {
689 \CCalendar::SaveUserTimezoneName(\CCalendar::GetUserId(), $request['default_tz']);
690 }
691
692 $entryFields = [
693 'ID' => $id,
694 'DATE_FROM' => $dateFrom,
695 'DATE_TO' => $dateTo,
696 'SKIP_TIME' => $skipTime,
697 'TZ_FROM' => $tzFrom,
698 'TZ_TO' => $tzTo,
699 'NAME' => $name,
700 'DESCRIPTION' => trim($request['desc']),
701 'SECTIONS' => [$sectionId],
702 'COLOR' => $request['color'],
703 'ACCESSIBILITY' => $request['accessibility'],
704 'IMPORTANCE' => $request['importance'] ?? 'normal',
705 'PRIVATE_EVENT' => $request['private_event'] === 'Y',
706 'RRULE' => $rrule,
707 'LOCATION' => $request['location'],
708 'REMIND' => $reminderList,
709 'SECTION_CAL_TYPE' => $section['CAL_TYPE'],
710 'SECTION_OWNER_ID' => $section['OWNER_ID']
711 ];
712
713 $codes = [];
714 if (isset($request['attendeesEntityList']) && is_array($request['attendeesEntityList']))
715 {
716 $codes = Util::convertEntitiesToCodes($request['attendeesEntityList']);
717 }
718
719 $accessCodes = \CCalendarEvent::handleAccessCodes($codes, ['userId' => $userId]);
720
721 $entryFields['IS_MEETING'] =
722 $accessCodes !== ['U'.$userId] || in_array($entryFields['SECTION_CAL_TYPE'], ['group', 'company_calendar'], true)
723 ;
724
725 $entryFields['ATTENDEES_CODES'] = $accessCodes;
726 $entryFields['ATTENDEES'] = \CCalendar::GetDestinationUsers($accessCodes);
727 $response['reload'] = true;
728
729 if ($request['exclude_users'] && !empty($entryFields['ATTENDEES']))
730 {
731 $excludeUsers = explode(',', $request['exclude_users']);
732 $entryFields['ATTENDEES_CODES'] = [];
733
734 if (!empty($excludeUsers))
735 {
736 $entryFields['ATTENDEES'] = array_diff($entryFields['ATTENDEES'], $excludeUsers);
737 foreach($entryFields['ATTENDEES'] as $attendee)
738 {
739 $entryFields['ATTENDEES_CODES'][] = 'U'. (int)$attendee;
740 }
741 }
742 }
743 else
744 {
745 $excludeUsers = [];
746 }
747
748 $meetingHost = (int)$request['meeting_host'];
749 $chatId = (int)$request['chat_id'];
750
751 if ($meetingHost)
752 {
753 $entryFields['MEETING_HOST'] = $meetingHost;
754 }
755 else
756 {
757 $entryFields['MEETING_HOST'] = \CCalendar::GetUserId();
758 }
759
760 $entryFields['MEETING'] = [
761 'HOST_NAME' => \CCalendar::GetUserName($entryFields['MEETING_HOST']),
762 'NOTIFY' => $request['meeting_notify'] === 'Y',
763 'REINVITE' => $request['meeting_reinvite'] === 'Y',
764 'ALLOW_INVITE' => $request['allow_invite'] === 'Y',
765 'MEETING_CREATOR' => $entryFields['MEETING_HOST'],
766 'HIDE_GUESTS' => $request['hide_guests'] === 'Y'
767 ];
768
769 $recurrenceEventMode = !empty($request['rec_edit_mode']) ? $request['rec_edit_mode'] : null;
770 $currentEventDate = !empty($request['current_date_from'])
771 ? \CCalendar::Date(\CCalendar::Timestamp($request['current_date_from']), false)
772 : null
773 ;
774
775
776 if ($chatId)
777 {
778 $entryFields['MEETING']['CHAT_ID'] = $chatId;
779 }
780
781 if (!Rooms\AccessibilityManager::checkAccessibility($entryFields['LOCATION'], ['fields' => $entryFields]))
782 {
783 $this->addError(new Error(Loc::getMessage('EC_LOCATION_BUSY'), 'edit_entry_location_busy'));
784 }
785
786 if ($entryFields['IS_MEETING'] && $isPlannerFeatureEnabled)
787 {
788 $attendees = [];
789 if ($checkCurrentUsersAccessibility)
790 {
791 $attendees = $entryFields['ATTENDEES'];
792 }
793 else if (is_array($request['newAttendeesList']))
794 {
795 $attendees = array_diff($request['newAttendeesList'], $excludeUsers);
796 }
797
798 $timezoneFrom = !empty($tzFrom) ? $tzFrom : \CCalendar::GetUserTimezoneName(\CCalendar::GetUserId());
799 $timezoneTo = !empty($tzTo) ? $tzTo : $timezoneFrom;
800 $timezoneOffsetFrom = Util::getTimezoneOffsetUTC($timezoneFrom);
801 $timezoneOffsetTo = Util::getTimezoneOffsetUTC($timezoneTo);
802 $timestampFrom = \CCalendar::TimestampUTC($dateFrom) - $timezoneOffsetFrom;
803 $timestampTo = \CCalendar::TimestampUTC($dateTo) - $timezoneOffsetTo;
804 if ($skipTime)
805 {
806 $timestampTo += \CCalendar::GetDayLen();
807 }
808 $busyUsers = $this->getBusyUsersIds($attendees, $id, $timestampFrom, $timestampTo);
809 if (!empty($busyUsers))
810 {
811 $response['busyUsersList'] = \CCalendarEvent::getUsersDetails($busyUsers);
812 $busyUserName = current($response['busyUsersList'])['DISPLAY_NAME'];
813 $this->addError(new Error(Loc::getMessage('EC_USER_BUSY', ['#USER#' => $busyUserName]), 'edit_entry_user_busy'));
814 }
815 }
816
817 // Userfields for event
818 $arUFFields = [];
819 foreach($request as $field => $value)
820 {
821 if (mb_strpos($field, 'UF_') === 0)
822 {
823 $arUFFields[$field] = $value;
824 }
825 }
826
827 if (!empty($this->getErrors()))
828 {
829 return $response;
830 }
831
832 $newId = false;
833 try
834 {
835 $newId = \CCalendar::SaveEvent([
836 'arFields' => $entryFields,
837 'UF' => $arUFFields,
838 'silentErrorMode' => false,
839 'recursionEditMode' => $recurrenceEventMode,
840 'currentEventDateFrom' => $currentEventDate,
841 'sendInvitesToDeclined' => $request['sendInvitesAgain'] === 'Y',
842 'requestUid' => $requestUid,
843 'checkLocationOccupancy' => ($request['doCheckOccupancy'] ?? 'N') === 'Y',
844 ]);
845 }
846 catch (Rooms\OccupancyCheckerException $e)
847 {
848 $this->addError(new Error(Loc::getMessage('EC_LOCATION_BUSY_RECURRENCE'), 'edit_entry_location_busy_recurrence'));
849 $this->addError(new Error($e->getMessage(), 'edit_entry_location_repeat_busy'));
850 }
851
852 $errors = \CCalendar::GetErrors();
853 $eventList = [];
854 $eventIdList = [$newId];
855
856 if ($newId && empty($errors))
857 {
858 $response['entryId'] = $newId;
859
860 $filter = [
861 'ID' => $newId,
862 'FROM_LIMIT' => \CCalendar::Date(
863 \CCalendar::Timestamp($entryFields['DATE_FROM']) -
864 \CCalendar::DAY_LENGTH * 10, false
865 ),
866 'TO_LIMIT' => \CCalendar::Date(
867 \CCalendar::Timestamp($entryFields['DATE_TO']) +
868 \CCalendar::DAY_LENGTH * 90, false
869 )
870 ];
871
872 $eventList = \CCalendarEvent::GetList(
873 [
874 'arFilter' => $filter,
875 'parseRecursion' => true,
876 'fetchAttendees' => true,
877 'userId' => \CCalendar::GetUserId(),
878 ]
879 );
880
881 if ($entryFields['IS_MEETING'])
882 {
883 \Bitrix\Main\FinderDestTable::merge(
884 [
885 'CONTEXT' => Util::getUserSelectorContext(),
887 $accessCodes,
888 ['U'. \CCalendar::GetUserId()]
889 )
890 ]
891 );
892 }
893
894 if (isset($_REQUEST['rec_edit_mode']) && in_array($_REQUEST['rec_edit_mode'], ['this', 'next']))
895 {
896 unset($filter['ID']);
897 $filter['RECURRENCE_ID'] = ($eventList && $eventList[0] && $eventList[0]['RECURRENCE_ID']) ? $eventList[0]['RECURRENCE_ID'] : $newId;
898
899 $resRelatedEvents = \CCalendarEvent::GetList(
900 [
901 'arFilter' => $filter,
902 'parseRecursion' => true,
903 'fetchAttendees' => true,
904 'userId' => \CCalendar::GetUserId()
905 ]
906 );
907
908 foreach($resRelatedEvents as $ev)
909 {
910 $eventIdList[] = $ev['ID'];
911 }
912 $eventList = array_merge($eventList, $resRelatedEvents);
913 }
914 else if ($id && $eventList && $eventList[0] && \CCalendarEvent::CheckRecurcion($eventList[0]))
915 {
916 $recId = $eventList[0]['RECURRENCE_ID'] ?? $eventList[0]['ID'];
917
918 if ($eventList[0]['RECURRENCE_ID'] && $eventList[0]['RECURRENCE_ID'] !== $eventList[0]['ID'])
919 {
920 unset($filter['RECURRENCE_ID']);
921 $filter['ID'] = $eventList[0]['RECURRENCE_ID'];
922 $resRelatedEvents = \CCalendarEvent::GetList(
923 [
924 'arFilter' => $filter,
925 'parseRecursion' => true,
926 'fetchAttendees' => true,
927 'userId' => \CCalendar::GetUserId(),
928 ]
929 );
930 $eventIdList[] = $eventList[0]['RECURRENCE_ID'];
931 $eventList = array_merge($eventList, $resRelatedEvents);
932 }
933
934 if ($recId)
935 {
936 unset($filter['ID']);
937 $filter['RECURRENCE_ID'] = $recId;
938 $resRelatedEvents = \CCalendarEvent::GetList(
939 [
940 'arFilter' => $filter,
941 'parseRecursion' => true,
942 'fetchAttendees' => true,
943 'userId' => \CCalendar::GetUserId(),
944 ]
945 );
946
947 foreach($resRelatedEvents as $ev)
948 {
949 $eventIdList[] = $ev['ID'];
950 }
951 $eventList = array_merge($eventList, $resRelatedEvents);
952 }
953 }
954 }
955 else if (is_iterable($errors))
956 {
957 foreach ($errors as $error)
958 {
959 if (is_string($error))
960 {
961 $this->addError(new Error($error, 'send_invite_failed'));
962 }
963 }
964 }
965
966 $pathToCalendar = \CCalendar::GetPathForCalendarEx($userId);
967 foreach($eventList as $ind => $event)
968 {
969 $eventList[$ind]['~URL'] = \CHTTP::urlAddParams($pathToCalendar, ['EVENT_ID' => $event['ID']]);
970 }
971
972 $response['eventList'] = $eventList;
973 $response['eventIdList'] = $eventIdList;
974 $response['countEventWithEmailGuestAmount'] = Bitrix24Manager::getCountEventWithEmailGuestAmount();
975
976 $userSettings = UserSettings::get($userId);
977 $userSettings['defaultReminders'][$skipTime ? 'fullDay' : 'withTime'] = $reminderList;
978 UserSettings::set($userSettings, $userId);
979
980 return $response;
981 }
982
983 private function getBusyUsersIds(array $attendees, int $curEventId, int $fromTs, int $toTs): array
984 {
985 $usersToCheck = $this->getUsersToCheck($attendees);
986 if (empty($usersToCheck))
987 {
988 return [];
989 }
990
991 return (new Accessibility())
992 ->setCheckPermissions(false)
993 ->setSkipEventId($curEventId)
994 ->getBusyUsersIds($usersToCheck, $fromTs, $toTs);
995 }
996
997 private function getUsersToCheck(array $attendees): array
998 {
999 $usersToCheck = [];
1000 foreach ($attendees as $attId)
1001 {
1002 if ((int)$attId !== \CCalendar::GetUserId())
1003 {
1004 $userSettings = UserSettings::get((int)$attId);
1005 if ($userSettings && $userSettings['denyBusyInvitation'])
1006 {
1007 $usersToCheck[] = (int)$attId;
1008 }
1009 }
1010 }
1011 return $usersToCheck;
1012 }
1013
1014 private function prepareRecurringRule(array $rrule = null, ?string $endMode = 'never'): ?array
1015 {
1016 if (empty($rrule) || !is_array($rrule))
1017 {
1018 return null;
1019 }
1020 if (!isset($rrule['INTERVAL']) && $rrule['FREQ'] !== 'NONE')
1021 {
1022 $rrule['INTERVAL'] = 1;
1023 }
1024 if ($endMode === 'never')
1025 {
1026 unset($rrule['COUNT'], $rrule['UNTIL']);
1027 }
1028 elseif ($endMode === 'count')
1029 {
1030 if ((int)$rrule['COUNT'] <= 0)
1031 {
1032 $rrule['COUNT'] = 10;
1033 }
1034 unset($rrule['UNTIL']);
1035 }
1036 elseif ($endMode === 'until')
1037 {
1038 unset($rrule['COUNT']);
1039 }
1040
1041 return $rrule;
1042 }
1043}
const DIRECTION_BOTH
editEntryAction()
getEntries(array $sections, array $limits)
getShownEntries(array $entries)
getValidYearAndMonth(int $year, int $month)
loadEntriesAction()
moveEventAction()
hasArrayEntriesInMonth(array $entries, int $yearFrom, int $monthFrom)
hasArrayEntriesInRange(array $entries, int $yearFrom, int $monthFrom, int $yearTo, int $monthTo)
const DIRECTION_PREVIOUS
configureActions()
getNearestEventsAction()
getEarliestEvent(array $entries)
const DIRECTION_NEXT
getLatestEvent(array $entries)
static checkAccessibility(string $locationId='', array $params=[])
static set($settings=[], $userId=false)
static get($userId=null)
static getTimezoneOffsetUTC(string $timezoneName)
Definition util.php:733
static getUserSelectorContext()
Definition util.php:119
static convertEntitiesToCodes($entityList=[])
Definition util.php:162
static convertRights($rights, $excludeCodes=[])
static loadMessages($file)
Definition loc.php:64
static getMessage($code, $replace=null, $language=null)
Definition loc.php:29