403Webshell
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/lib/rooms/

Upload File :
current_dir [ Writeable] document_root [ Writeable]

 

Command :


[ Back ]     

Current File : /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/calendar/lib/rooms/occupancychecker.php
<?php
/**
 * Bitrix Framework
 * @package bitrix
 * @subpackage calendar
 * @copyright 2001-2023 Bitrix
 */

namespace Bitrix\Calendar\Rooms;

use Bitrix\Main\Config\Option;
use Bitrix\Main\Error;
use Bitrix\Main\ObjectException;
use Bitrix\Main\Result;
use Bitrix\Main\Type\DateTime;

class OccupancyChecker
{
	private const OPTION_NAME = 'isOccupancyCheckerEnabled';
	private const OPTION_ENABLED = 'Y';
	private const OPTION_DISABLED = 'N';

	private const CHECK_LIMIT = 2 * 365 * 86400;
	private const DISTURBING_EVENTS_LIMIT = 3;
	private const KEY_PART_START = 'start';
	private const KEY_PART_END = 'end';

	private array $timeline = [];
	private int $checkEventId = 0;
	private array $cachedEvents = [];

	public function __construct()
	{

	}

	/**
	 * Checks if saving event location booking has intersection with other events location booking.
	 * If so, returns \Bitrix\Main\Result object with disturbing event data.
	 * @param array $event
	 * @return Result
	 */
	public function check(array $event): Result
	{
		$result = new Result();

		if (!$this->canCheck($event))
		{
			return $result;
		}

		$location = Util::parseLocation($event['LOCATION']['NEW']);
		$roomId = $location['room_id'];

		if (!$roomId)
		{
			return $result;
		}

		$eventsForCheck = $this->getEventsForCheck($event);

		$existingEvents = $this->getExistingEvents($roomId, $event);

		$this->fillTimeline($event, $eventsForCheck, $existingEvents);
		asort($this->timeline, SORT_NUMERIC);

		$checkingEventsStartedCount = 0;
		$existingEventsStartedCount = 0;
		$disturbingEventDates = [];
		$isDisturbingEventsAmountOverShowLimit = false;
		foreach ($this->timeline as $key => $value)
		{
			[$eventId, $repeatNumber, $isEnd] = $this->parseTimelineKey($key);
			if ($eventId === $this->checkEventId)
			{
				if ($isEnd)
				{
					$checkingEventsStartedCount--;
				}
				else
				{
					$checkingEventsStartedCount++;
				}
			}
			else if ($isEnd)
			{
				$existingEventsStartedCount--;
			}
			else
			{
				$existingEventsStartedCount++;
			}

			if ($checkingEventsStartedCount > 0 && $existingEventsStartedCount > 0)
			{
				$disturbingEvent = $this->cachedEvents[$this->getCachedEventKey($eventId, $repeatNumber)];;
				if (count($disturbingEventDates) >= self::DISTURBING_EVENTS_LIMIT)
				{
					$isDisturbingEventsAmountOverShowLimit = true;
					break;
				}
				$disturbingEventDate = $this->getEventFormattedDate($disturbingEvent);
				if (!in_array($disturbingEventDate, $disturbingEventDates, true))
				{
					$disturbingEventDates[] = $disturbingEventDate;
				}
			}
		}

		if (!empty($disturbingEventDates))
		{
			$result->addError(new Error('ROOM IS OCCUPIED'));
			$result->setData([
				'disturbingEventsFormatted' => implode(', ', $disturbingEventDates),
				'isDisturbingEventsAmountOverShowLimit' => $isDisturbingEventsAmountOverShowLimit,
			]);
		}

		return $result;
	}

	/**
	 * @param array $event
	 * @return bool
	 */
	private function canCheck(array $event): bool
	{
		return
			$this->isEnabled()
			&& !empty($event['LOCATION']['NEW'])
			&& !empty($event['DATE_FROM_TS_UTC'])
			&& !empty($event['DATE_TO_TS_UTC'])
			&& !empty($event['RRULE'])
			&& !empty($event['DATE_FROM'])
			&& !empty($event['DATE_TO'])
		;
	}

	/**
	 * @return bool
	 */
	private function isEnabled(): bool
	{
		return Option::get('calendar', self::OPTION_NAME, self::OPTION_ENABLED, '-') !== self::OPTION_DISABLED;
	}

	/**
	 * @param array $event
	 * @return array
	 */
	protected function getEventsForCheck(array $event): array
	{
		$checkedEvents = [];
		$toLimit = $this->getCheckLimit($event);

		\CCalendarEvent::ParseRecursion(
			$checkedEvents,
			$event,
			[
				'userId' => \CCalendar::GetCurUserId(),
				'fromLimit' => null,
				'toLimitTs' => $toLimit,
				'loadLimit' =>  null,
				'instanceCount' => false,
				'preciseLimits' => false,
				'checkPermission' => false,
			]
		);

		return $checkedEvents;
	}

	/**
	 * @param array $event
	 * @return int
	 */
	private function getCheckLimit(array $event): int
	{
		return min($event['DATE_FROM_TS_UTC'] + self::CHECK_LIMIT, $event['DATE_TO_TS_UTC']);
	}

	/**
	 * @param array $event
	 * @return array
	 * @throws ObjectException
	 */
	private function getEventTimestamps(array $event): array
	{
		$dateFrom = new DateTime($event['DATE_FROM']);
		$dateTo = new DateTime($event['DATE_TO']);
		$timezoneFrom = $event['TZ_FROM'] ?? null;
		$timezoneTo = $event['TZ_TO'] ?? null;
		$timestampFrom = \Bitrix\Calendar\Util::getDateTimestampUtc($dateFrom, $timezoneFrom);
		$timestampTo = \Bitrix\Calendar\Util::getDateTimestampUtc($dateTo, $timezoneTo);
		if (($timestampFrom === $timestampTo) || (($event['DT_SKIP_TIME'] ?? null) === 'Y'))
		{
			$timestampTo += \CCalendar::GetDayLen();
		}

		//This is done to check weak inequality
		$timestampFrom++;
		$timestampTo--;

		return [$timestampFrom, $timestampTo];
	}

	/**
	 * @param int $roomId
	 * @param array $event
	 * @return array|null
	 */
	protected function getExistingEvents(int $roomId, array $event): ?array
	{
		$arSelect = [
			'ID',
			'RRULE',
			'DT_SKIP_TIME',
			'DT_LENGTH',
			'PARENT_ID',
			'DATE_FROM',
			'DATE_TO',
			'PARENT_ID',
			'CAL_TYPE',
			'TZ_FROM',
			'TZ_TO',
			'TZ_OFFSET_FROM',
			'TZ_OFFSET_TO',
			'DATE_FROM_TS_UTC',
			'DATE_TO_TS_UTC',
			'CREATED_BY',
			'ACCESSIBILITY',
			'REMIND',
			'MEETING_HOST',
			'MEETING_STATUS',
			'IMPORTANCE',
			'PRIVATE_EVENT',
		];

		$toLimit = $this->getCheckLimit($event);

		$sections = [$roomId];

		$additionalLocationConnection = AccessibilityManager::getAdditionalLocationAccessibilityConnection($roomId);
		if (!empty($additionalLocationConnection))
		{
			$sections = [
				...$sections,
				...$additionalLocationConnection,
			];
		}

		return \CCalendarEvent::GetList(
			[
				'arSelect' => $arSelect,
				'arFilter' => [
					'SECTION' => $sections,
					'FROM_LIMIT' => $event['DATE_FROM'],
					'TO_LIMIT' => DateTime::createFromTimestamp($toLimit)->toString(),
					'DELETED' => 'N',
					'ACTIVE' => 'Y'
				],
				'parseRecursion' => true,
				'fetchAttendees' => false,
				'fetchMeetings' => false,
				'setDefaultLimit' => false,
				'limit' => null,
				'checkPermissions' => false,
				'parseDescription' => false,
				'fetchSection' => false,
				'getUserfields' => false,
			]
		);
	}

	/**
	 * @param array $event
	 * @param array $eventsForCheck
	 * @param array $existingEvents
	 * @return void
	 */
	private function fillTimeline(array $event, array $eventsForCheck, array $existingEvents): void
	{
		if (!empty($event['ID']) && $event['ID'] > 0)
		{
			$this->checkEventId = $event['ID'];
		}

		$currentIndex = 1;
		$previousEventId = 0;
		foreach ($existingEvents as $existingEvent)
		{
			if (!$this->canFindIntersections($event, $existingEvent))
			{
				continue;
			}

			$currentEventId = (int)$existingEvent['PARENT_ID'];
			if ($currentEventId !== $previousEventId)
			{
				$previousEventId = $currentEventId;
				$currentIndex = 1;
			}

			try
			{
				[$timestampFrom, $timestampTo] = $this->getEventTimestamps($existingEvent);

				$this->timeline[$this->getTimelineKey($currentEventId, $currentIndex, false)] = $timestampFrom;
				$this->timeline[$this->getTimelineKey($currentEventId, $currentIndex, true)] = $timestampTo;
				$this->cachedEvents[$this->getCachedEventKey($currentEventId, $currentIndex)] = [
					'ID' => $currentEventId,
					'DATE_FROM' => $existingEvent['DATE_FROM'],
					'TZ_FROM' => $existingEvent['TZ_FROM'],
				];
				$currentIndex++;
			}
			catch (ObjectException $exception)
			{
			}
		}

		$currentIndex = 1;
		foreach ($eventsForCheck as $eventForCheck)
		{
			if (empty($eventForCheck['DATE_FROM']) || empty($eventForCheck['DATE_TO']))
			{
				continue;
			}
			[$timestampFrom, $timestampTo] = $this->getEventTimestamps($eventForCheck);

			$this->timeline[$this->getTimelineKey($this->checkEventId, $currentIndex, false)] = $timestampFrom;
			$this->timeline[$this->getTimelineKey($this->checkEventId, $currentIndex, true)] = $timestampTo;
			$this->cachedEvents[$this->getCachedEventKey($eventForCheck['ID'], $currentIndex)] = [
				'ID' => $eventForCheck['ID'],
				'DATE_FROM' => $eventForCheck['DATE_FROM'],
				'TZ_FROM' => $eventForCheck['TZ_FROM'],
			];
			$currentIndex++;
		}
	}

	/**
	 * @param array $event
	 * @param array $existingEvent
	 * @return bool
	 */
	private function canFindIntersections(array $event, array $existingEvent): bool
	{
		return
			($event['PARENT_ID'] ?? null) !== (int)($existingEvent['PARENT_ID'] ?? null)
			&& !empty($existingEvent['PARENT_ID'])
			&& !empty($existingEvent['DATE_FROM'])
			&& !empty($existingEvent['DATE_TO'])
			&& !empty($existingEvent['ID'])
		;
	}

	/**
	 * @param int $eventId
	 * @param int $repeatNumber
	 * @param bool $isEnd
	 * @return string
	 */
	private function getTimelineKey(int $eventId, int $repeatNumber, bool $isEnd): string
	{
		return $eventId . '_' . $repeatNumber . '_' . ($isEnd ? self::KEY_PART_END : self::KEY_PART_START);
	}

	/**
	 * @param string $key
	 * @return array
	 */
	private function parseTimelineKey(string $key): array
	{
		$res =  explode('_', $key);
		return [(int)$res[0], (int)$res[1], ($res[2] === self::KEY_PART_END)];
	}

	/**
	 * @param int $eventId
	 * @param int $repeatNumber
	 * @return string
	 */
	private function getCachedEventKey(int $eventId, int $repeatNumber): string
	{
		return $eventId . '_' . $repeatNumber;
	}

	/**
	 * @param array $event
	 * @return string
	 */
	private function getEventFormattedDate(array $event): string
	{
		$eventTimezone = null;
		if (!empty($event['TZ_FROM']))
		{
			$eventTimezone = new \DateTimeZone($event['TZ_FROM']);
		}
		$result = '';
		try
		{
			$result = \Bitrix\Calendar\Util::formatEventDate(
				new DateTime($event['DATE_FROM'], null, $eventTimezone)
			);
		}
		catch (ObjectException $e)
		{
		}

		return $result;
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit