Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
occupancychecker.php
1<?php
10
16
18{
19 private const OPTION_NAME = 'isOccupancyCheckerEnabled';
20 private const OPTION_ENABLED = 'Y';
21 private const OPTION_DISABLED = 'N';
22
23 private const CHECK_LIMIT = 2 * 365 * 86400;
24 private const DISTURBING_EVENTS_LIMIT = 3;
25 private const KEY_PART_START = 'start';
26 private const KEY_PART_END = 'end';
27
28 private array $timeline = [];
29 private int $checkEventId = 0;
30 private array $cachedEvents = [];
31
32 public function __construct()
33 {
34
35 }
36
43 public function check(array $event): Result
44 {
45 $result = new Result();
46
47 if (!$this->canCheck($event))
48 {
49 return $result;
50 }
51
52 $location = Util::parseLocation($event['LOCATION']['NEW']);
53 $roomId = $location['room_id'];
54
55 if (!$roomId)
56 {
57 return $result;
58 }
59
60 $eventsForCheck = $this->getEventsForCheck($event);
61
62 $existingEvents = $this->getExistingEvents($roomId, $event);
63
64 $this->fillTimeline($event, $eventsForCheck, $existingEvents);
65 asort($this->timeline, SORT_NUMERIC);
66
67 $checkingEventsStartedCount = 0;
68 $existingEventsStartedCount = 0;
69 $disturbingEventDates = [];
70 $isDisturbingEventsAmountOverShowLimit = false;
71 foreach ($this->timeline as $key => $value)
72 {
73 [$eventId, $repeatNumber, $isEnd] = $this->parseTimelineKey($key);
74 if ($eventId === $this->checkEventId)
75 {
76 if ($isEnd)
77 {
78 $checkingEventsStartedCount--;
79 }
80 else
81 {
82 $checkingEventsStartedCount++;
83 }
84 }
85 else
86 {
87 if ($isEnd)
88 {
89 $existingEventsStartedCount--;
90 }
91 else
92 {
93 $existingEventsStartedCount++;
94 }
95 }
96
97 if ($checkingEventsStartedCount > 0 && $existingEventsStartedCount > 0)
98 {
99 $disturbingEvent = $this->cachedEvents[$this->getCachedEventKey($eventId, $repeatNumber)];;
100 if (count($disturbingEventDates) >= self::DISTURBING_EVENTS_LIMIT)
101 {
102 $isDisturbingEventsAmountOverShowLimit = true;
103 break;
104 }
105 $disturbingEventDate = $this->getEventFormattedDate($disturbingEvent);
106 if (!in_array($disturbingEventDate, $disturbingEventDates))
107 {
108 $disturbingEventDates[] = $disturbingEventDate;
109 }
110 }
111 }
112
113 if (!empty($disturbingEventDates))
114 {
115 $result->addError(new Error('ROOM IS OCCUPIED'));
116 $result->setData([
117 'disturbingEventsFormatted' => implode(', ', $disturbingEventDates),
118 'isDisturbingEventsAmountOverShowLimit' => $isDisturbingEventsAmountOverShowLimit,
119 ]);
120 }
121
122 return $result;
123 }
124
129 private function canCheck(array $event): bool
130 {
131 return
132 $this->isEnabled()
133 && !empty($event['LOCATION']['NEW'])
134 && !empty($event['DATE_FROM_TS_UTC'])
135 && !empty($event['DATE_TO_TS_UTC'])
136 && !empty($event['RRULE'])
137 && !empty($event['DATE_FROM'])
138 && !empty($event['DATE_TO'])
139 ;
140 }
141
145 private function isEnabled(): bool
146 {
147 return Option::get('calendar', self::OPTION_NAME, self::OPTION_DISABLED, '-') === self::OPTION_ENABLED;
148 }
149
154 protected function getEventsForCheck(array $event): array
155 {
156 $checkedEvents = [];
157 $toLimit = $this->getCheckLimit($event);
158
159 \CCalendarEvent::ParseRecursion(
160 $checkedEvents,
161 $event,
162 [
163 'userId' => \CCalendar::GetCurUserId(),
164 'fromLimit' => null,
165 'toLimitTs' => $toLimit,
166 'loadLimit' => null,
167 'instanceCount' => false,
168 'preciseLimits' => false,
169 'checkPermission' => false,
170 ]
171 );
172
173 return $checkedEvents;
174 }
175
180 private function getCheckLimit(array $event): int
181 {
182 return min($event['DATE_FROM_TS_UTC'] + self::CHECK_LIMIT, $event['DATE_TO_TS_UTC']);
183 }
184
190 private function getEventTimestamps(array $event): array
191 {
192 $dateFrom = new DateTime($event['DATE_FROM']);
193 $dateTo = new DateTime($event['DATE_TO']);
194 $timezoneFrom = $event['TZ_FROM'] ?? null;
195 $timezoneTo = $event['TZ_TO'] ?? null;
196 $timestampFrom = \Bitrix\Calendar\Util::getDateTimestampUtc($dateFrom, $timezoneFrom);
197 $timestampTo = \Bitrix\Calendar\Util::getDateTimestampUtc($dateTo, $timezoneTo);
198 if (($timestampFrom === $timestampTo) || (($event['DT_SKIP_TIME'] ?? null) === 'Y'))
199 {
200 $timestampTo += \CCalendar::GetDayLen();
201 }
202
203 //This is done to check weak inequality
204 $timestampFrom++;
205 $timestampTo--;
206
207 return [$timestampFrom, $timestampTo];
208 }
209
215 protected function getExistingEvents(int $roomId, array $event): ?array
216 {
217 $arSelect = [
218 'ID',
219 'RRULE',
220 'DT_SKIP_TIME',
221 'DT_LENGTH',
222 'PARENT_ID',
223 'DATE_FROM',
224 'DATE_TO',
225 'PARENT_ID',
226 'TZ_FROM',
227 'TZ_TO',
228 'TZ_OFFSET_FROM',
229 'TZ_OFFSET_TO',
230 'DATE_FROM_TS_UTC',
231 'DATE_TO_TS_UTC',
232 'CREATED_BY',
233 'ACCESSIBILITY',
234 'REMIND',
235 'MEETING_HOST',
236 'MEETING_STATUS',
237 'IMPORTANCE',
238 ];
239
240 $toLimit = $this->getCheckLimit($event);
241
242 return \CCalendarEvent::GetList(
243 [
244 'arSelect' => $arSelect,
245 'arFilter' => [
246 'SECTION' => [$roomId],
247 'FROM_LIMIT' => $event['DATE_FROM'],
248 'TO_LIMIT' => DateTime::createFromTimestamp($toLimit)->toString(),
249 'DELETED' => 'N',
250 'ACTIVE' => 'Y'
251 ],
252 'parseRecursion' => true,
253 'fetchAttendees' => false,
254 'fetchMeetings' => false,
255 'setDefaultLimit' => false,
256 'limit' => null,
257 'checkPermissions' => false,
258 'parseDescription' => false,
259 'fetchSection' => false,
260 'getUserfields' => false,
261 ]
262 );
263 }
264
271 private function fillTimeline(array $event, array $eventsForCheck, array $existingEvents): void
272 {
273 if (!empty($event['ID']) && $event['ID'] > 0)
274 {
275 $this->checkEventId = $event['ID'];
276 }
277
278 $currentIndex = 1;
279 $previousEventId = 0;
280 foreach ($existingEvents as $existingEvent)
281 {
282 if (!$this->canFindIntersections($event, $existingEvent))
283 {
284 continue;
285 }
286
287 $currentEventId = (int)$existingEvent['PARENT_ID'];
288 if ($currentEventId !== $previousEventId)
289 {
290 $previousEventId = $currentEventId;
291 $currentIndex = 1;
292 }
293
294 try
295 {
296 [$timestampFrom, $timestampTo] = $this->getEventTimestamps($existingEvent);
297
298 $this->timeline[$this->getTimelineKey($currentEventId, $currentIndex, false)] = $timestampFrom;
299 $this->timeline[$this->getTimelineKey($currentEventId, $currentIndex, true)] = $timestampTo;
300 $this->cachedEvents[$this->getCachedEventKey($currentEventId, $currentIndex)] = [
301 'ID' => $currentEventId,
302 'DATE_FROM' => $existingEvent['DATE_FROM'],
303 'TZ_FROM' => $existingEvent['TZ_FROM'],
304 ];
305 $currentIndex++;
306 }
307 catch (ObjectException $exception)
308 {
309 }
310 }
311
312 $currentIndex = 1;
313 foreach ($eventsForCheck as $eventForCheck)
314 {
315 if (empty($eventForCheck['DATE_FROM']) || empty($eventForCheck['DATE_TO']))
316 {
317 continue;
318 }
319 [$timestampFrom, $timestampTo] = $this->getEventTimestamps($eventForCheck);
320
321 $this->timeline[$this->getTimelineKey($this->checkEventId, $currentIndex, false)] = $timestampFrom;
322 $this->timeline[$this->getTimelineKey($this->checkEventId, $currentIndex, true)] = $timestampTo;
323 $this->cachedEvents[$this->getCachedEventKey($eventForCheck['ID'], $currentIndex)] = [
324 'ID' => $eventForCheck['ID'],
325 'DATE_FROM' => $eventForCheck['DATE_FROM'],
326 'TZ_FROM' => $eventForCheck['TZ_FROM'],
327 ];
328 $currentIndex++;
329 }
330 }
331
337 private function canFindIntersections(array $event, array $existingEvent): bool
338 {
339 return
340 ($event['PARENT_ID'] ?? null) !== (int)($existingEvent['PARENT_ID'] ?? null)
341 && !empty($existingEvent['PARENT_ID'])
342 && !empty($existingEvent['DATE_FROM'])
343 && !empty($existingEvent['DATE_TO'])
344 && !empty($existingEvent['ID'])
345 ;
346 }
347
354 private function getTimelineKey(int $eventId, int $repeatNumber, bool $isEnd): string
355 {
356 return $eventId . '_' . $repeatNumber . '_' . ($isEnd ? self::KEY_PART_END : self::KEY_PART_START);
357 }
358
363 private function parseTimelineKey(string $key): array
364 {
365 $res = explode('_', $key);
366 return [(int)$res[0], (int)$res[1], ($res[2] === self::KEY_PART_END)];
367 }
368
374 private function getCachedEventKey(int $eventId, int $repeatNumber): string
375 {
376 return $eventId . '_' . $repeatNumber;
377 }
378
383 private function getEventFormattedDate(array $event): string
384 {
385 $eventTimezone = null;
386 if (!empty($event['TZ_FROM']))
387 {
388 $eventTimezone = new \DateTimeZone($event['TZ_FROM']);
389 }
390 $result = '';
391 try
392 {
393 $result = \Bitrix\Calendar\Util::formatEventDate(
394 new DateTime($event['DATE_FROM'], null, $eventTimezone)
395 );
396 }
397 catch (ObjectException $e)
398 {
399 }
400
401 return $result;
402 }
403}
getExistingEvents(int $roomId, array $event)
static createFromTimestamp($timestamp)
Definition datetime.php:246