1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
occupancychecker.php
См. документацию.
1<?php
8
9namespace Bitrix\Calendar\Rooms;
10
11use Bitrix\Main\Config\Option;
12use Bitrix\Main\Error;
13use Bitrix\Main\ObjectException;
14use Bitrix\Main\Result;
15use Bitrix\Main\Type\DateTime;
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 if ($isEnd)
86 {
87 $existingEventsStartedCount--;
88 }
89 else
90 {
91 $existingEventsStartedCount++;
92 }
93
94 if ($checkingEventsStartedCount > 0 && $existingEventsStartedCount > 0)
95 {
96 $disturbingEvent = $this->cachedEvents[$this->getCachedEventKey($eventId, $repeatNumber)];;
97 if (count($disturbingEventDates) >= self::DISTURBING_EVENTS_LIMIT)
98 {
99 $isDisturbingEventsAmountOverShowLimit = true;
100 break;
101 }
102 $disturbingEventDate = $this->getEventFormattedDate($disturbingEvent);
103 if (!in_array($disturbingEventDate, $disturbingEventDates, true))
104 {
105 $disturbingEventDates[] = $disturbingEventDate;
106 }
107 }
108 }
109
110 if (!empty($disturbingEventDates))
111 {
112 $result->addError(new Error('ROOM IS OCCUPIED'));
113 $result->setData([
114 'disturbingEventsFormatted' => implode(', ', $disturbingEventDates),
115 'isDisturbingEventsAmountOverShowLimit' => $isDisturbingEventsAmountOverShowLimit,
116 ]);
117 }
118
119 return $result;
120 }
121
126 private function canCheck(array $event): bool
127 {
128 return
129 $this->isEnabled()
130 && !empty($event['LOCATION']['NEW'])
131 && !empty($event['DATE_FROM_TS_UTC'])
132 && !empty($event['DATE_TO_TS_UTC'])
133 && !empty($event['RRULE'])
134 && !empty($event['DATE_FROM'])
135 && !empty($event['DATE_TO'])
136 ;
137 }
138
142 private function isEnabled(): bool
143 {
144 return Option::get('calendar', self::OPTION_NAME, self::OPTION_ENABLED, '-') !== self::OPTION_DISABLED;
145 }
146
151 protected function getEventsForCheck(array $event): array
152 {
153 $checkedEvents = [];
154 $toLimit = $this->getCheckLimit($event);
155
156 \CCalendarEvent::ParseRecursion(
157 $checkedEvents,
158 $event,
159 [
160 'userId' => \CCalendar::GetCurUserId(),
161 'fromLimit' => null,
162 'toLimitTs' => $toLimit,
163 'loadLimit' => null,
164 'instanceCount' => false,
165 'preciseLimits' => false,
166 'checkPermission' => false,
167 ]
168 );
169
170 return $checkedEvents;
171 }
172
177 private function getCheckLimit(array $event): int
178 {
179 return min($event['DATE_FROM_TS_UTC'] + self::CHECK_LIMIT, $event['DATE_TO_TS_UTC']);
180 }
181
187 private function getEventTimestamps(array $event): array
188 {
189 $dateFrom = new DateTime($event['DATE_FROM']);
190 $dateTo = new DateTime($event['DATE_TO']);
191 $timezoneFrom = $event['TZ_FROM'] ?? null;
192 $timezoneTo = $event['TZ_TO'] ?? null;
193 $timestampFrom = \Bitrix\Calendar\Util::getDateTimestampUtc($dateFrom, $timezoneFrom);
194 $timestampTo = \Bitrix\Calendar\Util::getDateTimestampUtc($dateTo, $timezoneTo);
195 if (($timestampFrom === $timestampTo) || (($event['DT_SKIP_TIME'] ?? null) === 'Y'))
196 {
197 $timestampTo += \CCalendar::GetDayLen();
198 }
199
200 //This is done to check weak inequality
201 $timestampFrom++;
202 $timestampTo--;
203
204 return [$timestampFrom, $timestampTo];
205 }
206
212 protected function getExistingEvents(int $roomId, array $event): ?array
213 {
214 $arSelect = [
215 'ID',
216 'RRULE',
217 'DT_SKIP_TIME',
218 'DT_LENGTH',
219 'PARENT_ID',
220 'DATE_FROM',
221 'DATE_TO',
222 'PARENT_ID',
223 'CAL_TYPE',
224 'TZ_FROM',
225 'TZ_TO',
226 'TZ_OFFSET_FROM',
227 'TZ_OFFSET_TO',
228 'DATE_FROM_TS_UTC',
229 'DATE_TO_TS_UTC',
230 'CREATED_BY',
231 'ACCESSIBILITY',
232 'REMIND',
233 'MEETING_HOST',
234 'MEETING_STATUS',
235 'IMPORTANCE',
236 'PRIVATE_EVENT',
237 ];
238
239 $toLimit = $this->getCheckLimit($event);
240
241 $sections = [$roomId];
242
243 $additionalLocationConnection = AccessibilityManager::getAdditionalLocationAccessibilityConnection($roomId);
244 if (!empty($additionalLocationConnection))
245 {
246 $sections = [
247 ...$sections,
248 ...$additionalLocationConnection,
249 ];
250 }
251
252 return \CCalendarEvent::GetList(
253 [
254 'arSelect' => $arSelect,
255 'arFilter' => [
256 'SECTION' => $sections,
257 'FROM_LIMIT' => $event['DATE_FROM'],
258 'TO_LIMIT' => DateTime::createFromTimestamp($toLimit)->toString(),
259 'DELETED' => 'N',
260 'ACTIVE' => 'Y'
261 ],
262 'parseRecursion' => true,
263 'fetchAttendees' => false,
264 'fetchMeetings' => false,
265 'setDefaultLimit' => false,
266 'limit' => null,
267 'checkPermissions' => false,
268 'parseDescription' => false,
269 'fetchSection' => false,
270 'getUserfields' => false,
271 ]
272 );
273 }
274
281 private function fillTimeline(array $event, array $eventsForCheck, array $existingEvents): void
282 {
283 if (!empty($event['ID']) && $event['ID'] > 0)
284 {
285 $this->checkEventId = $event['ID'];
286 }
287
288 $currentIndex = 1;
289 $previousEventId = 0;
290 foreach ($existingEvents as $existingEvent)
291 {
292 if (!$this->canFindIntersections($event, $existingEvent))
293 {
294 continue;
295 }
296
297 $currentEventId = (int)$existingEvent['PARENT_ID'];
298 if ($currentEventId !== $previousEventId)
299 {
300 $previousEventId = $currentEventId;
301 $currentIndex = 1;
302 }
303
304 try
305 {
306 [$timestampFrom, $timestampTo] = $this->getEventTimestamps($existingEvent);
307
308 $this->timeline[$this->getTimelineKey($currentEventId, $currentIndex, false)] = $timestampFrom;
309 $this->timeline[$this->getTimelineKey($currentEventId, $currentIndex, true)] = $timestampTo;
310 $this->cachedEvents[$this->getCachedEventKey($currentEventId, $currentIndex)] = [
311 'ID' => $currentEventId,
312 'DATE_FROM' => $existingEvent['DATE_FROM'],
313 'TZ_FROM' => $existingEvent['TZ_FROM'],
314 ];
315 $currentIndex++;
316 }
317 catch (ObjectException $exception)
318 {
319 }
320 }
321
322 $currentIndex = 1;
323 foreach ($eventsForCheck as $eventForCheck)
324 {
325 if (empty($eventForCheck['DATE_FROM']) || empty($eventForCheck['DATE_TO']))
326 {
327 continue;
328 }
329 [$timestampFrom, $timestampTo] = $this->getEventTimestamps($eventForCheck);
330
331 $this->timeline[$this->getTimelineKey($this->checkEventId, $currentIndex, false)] = $timestampFrom;
332 $this->timeline[$this->getTimelineKey($this->checkEventId, $currentIndex, true)] = $timestampTo;
333 $this->cachedEvents[$this->getCachedEventKey($eventForCheck['ID'], $currentIndex)] = [
334 'ID' => $eventForCheck['ID'],
335 'DATE_FROM' => $eventForCheck['DATE_FROM'],
336 'TZ_FROM' => $eventForCheck['TZ_FROM'],
337 ];
338 $currentIndex++;
339 }
340 }
341
347 private function canFindIntersections(array $event, array $existingEvent): bool
348 {
349 return
350 ($event['PARENT_ID'] ?? null) !== (int)($existingEvent['PARENT_ID'] ?? null)
351 && !empty($existingEvent['PARENT_ID'])
352 && !empty($existingEvent['DATE_FROM'])
353 && !empty($existingEvent['DATE_TO'])
354 && !empty($existingEvent['ID'])
355 ;
356 }
357
364 private function getTimelineKey(int $eventId, int $repeatNumber, bool $isEnd): string
365 {
366 return $eventId . '_' . $repeatNumber . '_' . ($isEnd ? self::KEY_PART_END : self::KEY_PART_START);
367 }
368
373 private function parseTimelineKey(string $key): array
374 {
375 $res = explode('_', $key);
376 return [(int)$res[0], (int)$res[1], ($res[2] === self::KEY_PART_END)];
377 }
378
384 private function getCachedEventKey(int $eventId, int $repeatNumber): string
385 {
386 return $eventId . '_' . $repeatNumber;
387 }
388
393 private function getEventFormattedDate(array $event): string
394 {
395 $eventTimezone = null;
396 if (!empty($event['TZ_FROM']))
397 {
398 $eventTimezone = new \DateTimeZone($event['TZ_FROM']);
399 }
400 $result = '';
401 try
402 {
403 $result = \Bitrix\Calendar\Util::formatEventDate(
404 new DateTime($event['DATE_FROM'], null, $eventTimezone)
405 );
406 }
407 catch (ObjectException $e)
408 {
409 }
410
411 return $result;
412 }
413}
static getAdditionalLocationAccessibilityConnection(int $roomId)
Определения accessibilitymanager.php:192
getExistingEvents(int $roomId, array $event)
Определения occupancychecker.php:212
getEventsForCheck(array $event)
Определения occupancychecker.php:151
static getDateTimestampUtc(DateTime $date, ?string $eventTimezone=null)
Определения util.php:774
Определения error.php:15
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
Определения file_new.php:804
$res
Определения filter_act.php:7
$result
Определения get_property_values.php:14
$event
Определения prolog_after.php:141
if(empty($signedUserToken)) $key
Определения quickway.php:257
</p ></td >< td valign=top style='border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0cm 2.0pt 0cm 2.0pt;height:9.0pt'>< p class=Normal align=center style='margin:0cm;margin-bottom:.0001pt;text-align:center;line-height:normal'>< a name=ТекстовоеПоле54 ></a ><?=($taxRate > count( $arTaxList) > 0) ? $taxRate."%"
Определения waybill.php:936
$location
Определения options.php:2729