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/im/lib/call/

Upload File :
current_dir [ Writeable] document_root [ Writeable]

 

Command :


[ Back ]     

Current File : /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/im/lib/call/call.php
<?php

namespace Bitrix\Im\Call;

use Bitrix\Im\Call\Integration\EntityFactory;
use Bitrix\Im\Call\Integration\EntityType;
use Bitrix\Im\Dialog;
use Bitrix\Im\Model\AliasTable;
use Bitrix\Im\Model\CallTable;
use Bitrix\Im\Model\CallUserTable;
use Bitrix\Im\V2\Call\CallFactory;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\Config\Option;
use Bitrix\Main\Loader;
use Bitrix\Main\Type\DateTime;
use Bitrix\Main\Event;
use Bitrix\Main\UserTable;
use Bitrix\Main\Web\JWT;
use Bitrix\Main\Error;
use Bitrix\Main\ErrorCollection;
use Bitrix\Call\Signaling;

class Call
{
	public const
		STATE_NEW = 'new',
		STATE_INVITING = 'inviting',
		STATE_ANSWERED = 'answered',
		STATE_FINISHED = 'finished'
	;

	public const
		TYPE_INSTANT = 1,
		TYPE_PERMANENT = 2,
		TYPE_LANGE = 3
	;

	public const
		SCHEME_CLASSIC = 1,
		SCHEME_JWT = 2
	;

	public const
		PROVIDER_PLAIN = 'Plain',
		PROVIDER_BITRIX = 'Bitrix',
		PROVIDER_VOXIMPLANT = 'Voximplant'
	;

	protected $id;
	protected $type;
	protected int $scheme;
	protected $initiatorId;
	protected ?int $actionUserId = null;
	protected $isPublic = false;
	protected $publicId;
	protected $provider;
	protected $entityType;
	protected $entityId;
	protected $parentId;
	protected $parentUuid;
	protected $state;
	/** @var DateTime|null */
	protected $startDate;
	/** @var DateTime|null */
	protected $endDate;
	protected $logUrl;
	protected $chatId;
	protected $uuid;
	protected $secretKey;
	protected $endpoint;

	/**
	 * Current record status
	 */
	protected bool $enableAudioRecord = false;

	/**
	 * Record will be analyzed with AI
	 */
	protected bool $enableAiAnalyze = false;

	protected ?Integration\AbstractEntity $associatedEntity = null;

	/** @var CallUser[] */
	protected $users;
	protected $userData;

	protected ?Signaling $signaling = null;

	protected ?ErrorCollection $errorCollection = null;

	/**
	 * Use one of the named constructors
	 */
	protected function __construct()
	{
	}

	/**
	 * @return int
	 */
	public function getId(): int
	{
		return (int)$this->id;
	}

	/**
	 * @return int
	 */
	public function getType(): int
	{
		return (int)$this->type;
	}

	/**
	 * @return int
	 */
	public function getScheme(): int
	{
		return $this->scheme;
	}

	/**
	 * @return string
	 */
	public function getProvider(): string
	{
		return $this->provider;
	}

	/**
	 * @return int
	 */
	public function getInitiatorId(): int
	{
		return $this->initiatorId;
	}

	/**
	 * @return int|null
	 */
	public function getActionUserId(): ?int
	{
		return $this->actionUserId;
	}

	//region Errors

	/**
	 * Add multiple errors
	 * @param Error[] $errors
	 */
	public function addErrors(array $errors): void
	{
		if (!$this->errorCollection instanceof ErrorCollection)
		{
			$this->errorCollection = new ErrorCollection();
		}
		$this->errorCollection->add($errors);
	}

	/**
	 * @return Error[]
	 */
	public function getErrors(): array
	{
		if ($this->errorCollection instanceof ErrorCollection)
		{
			return $this->errorCollection->toArray();
		}

		return [];
	}

	/**
	 * Upends stack of errors.
	 * @param Error $error Error message object.
	 * @return void
	 */
	public function addError(Error $error): void
	{
		if (!$this->errorCollection instanceof ErrorCollection)
		{
			$this->errorCollection = new ErrorCollection();
		}
		$this->errorCollection->add([$error]);
	}

	/**
	 * Tells true if error have happened.
	 * @return boolean
	 */
	public function hasErrors(): bool
	{
		if ($this->errorCollection instanceof ErrorCollection)
		{
			return !$this->errorCollection->isEmpty();
		}

		return false;
	}

	//endregion

	//region Users

	public function setActionUserId(int $byUserId): self
	{
		$this->actionUserId = $byUserId;
		return $this;
	}

	/**
	 * @param int $userId
	 * @return CallUser|null
	 */
	public function getUser($userId): ?CallUser
	{
		$this->loadUsers();
		return isset($this->users[$userId]) ? $this->users[$userId] : null;
	}

	/**
	 * Returns arrays of ids of the users, currently participating in the call.
	 * @return int[]
	 */
	public function getUsers(): array
	{
		$this->loadUsers();
		return array_keys($this->users);
	}

	/**
	 * Returns arrays of the users, currently participating in the call.
	 * @return CallUser[]
	 */
	public function getCallUsers(): array
	{
		$this->loadUsers();
		return $this->users;
	}

	/**
	 * Returns arrays of information about the users currently participating in the call.
	 * @return array
	 */
	public function getUserData(): array
	{
		if (!isset($this->userData))
		{
			$this->userData = $this->prepareUserData($this->getUsers());
		}

		return $this->userData;
	}

	/**
	 * @param int[] $users
	 * @return array
	 */
	public function prepareUserData(array $users): array
	{
		$userData = Util::getUsers($users);
		$userRoles = $this->getUserRoles($users);
		foreach ($userData as $userId => &$user)
		{
			$user['role'] = $userRoles[$userId];
		}

		return $userData;
	}

	/**
	 * Returns user's roles in the call.
	 *
	 * @param int[] $users
	 * @return array
	 */
	public function getUserRoles(array $users = []): array
	{
		if (empty($users))
		{
			$users = $this->getUsers();
		}
		$userRoles = [];

		$chatOwnerId = (int)$this->getAssociatedEntity()?->getOwnerId();
		$chatManagerIds = $this->getAssociatedEntity()?->getManagerIds() ?? [];
		foreach ($users as $userId)
		{
			$userId = (int)$userId;
			$userRoles[$userId] = match (true)
			{
				$userId === $chatOwnerId => 'ADMIN',
				in_array($userId, $chatManagerIds, true) => 'MANAGER',
				default => 'USER',
			};
		}

		return $userRoles;
	}

	/**
	 * Return true if a user is the part of the call.
	 *
	 * @param int $userId Id of the user.
	 * @return bool
	 */
	public function hasUser($userId): bool
	{
		$this->loadUsers();
		return isset($this->users[$userId]);
	}

	/**
	 * Adds new user to the call.
	 *
	 * @param int $newUserId
	 * @return CallUser|null
	 */
	public function addUser($newUserId): ?CallUser
	{
		$this->loadUsers();
		if ($this->users[$newUserId])
		{
			return $this->users[$newUserId];
		}

		if (count($this->users) >= $this->getMaxUsers())
		{
			return null;
		}

		$this->users[$newUserId] = CallUser::create([
			'CALL_ID' => $this->id,
			'USER_ID' => $newUserId,
			'STATE' => CallUser::STATE_IDLE,
			'LAST_SEEN' => null
		]);
		$this->users[$newUserId]->save();
		unset($this->userData);

		if ($this->associatedEntity)
		{
			$this->associatedEntity->onUserAdd($newUserId);
		}

		return $this->users[$newUserId];
	}

	public function removeUser($userId): void
	{
		$this->loadUsers();
		if($this->users[$userId])
		{
			CallUser::delete($this->id, $userId);
			unset($this->users[$userId]);
			unset($this->userData[$userId]);
		}
	}

	/**
	 * Call is considered active if it has at least:
	 *  - one user in ready state
	 *  - another user in ready or calling state
	 * @return bool
	 */
	public function hasActiveUsers(bool $strict = true): bool
	{
		$this->loadUsers();
		$states = [];

		foreach ($this->users as $user)
		{
			$userState = $user->getState();
			$states[$userState] = isset($states[$userState]) ? $states[$userState] + 1 : 1;
		}
		if (in_array($this->type, [static::TYPE_PERMANENT, static::TYPE_LANGE]) || !$strict)
		{
			 return $states[CallUser::STATE_READY] >= 1;
		}

		return $states[CallUser::STATE_READY] >= 2 || ($states[CallUser::STATE_READY] >= 1 && $states[CallUser::STATE_CALLING] >= 1);
	}

	//endregion

	public function getSignaling(): Signaling
	{
		if (is_null($this->signaling))
		{
			$this->signaling = new Signaling($this);
		}

		return $this->signaling;
	}

	/**
	 * @return Integration\AbstractEntity|Integration\Chat|null
	 */
	public function getAssociatedEntity(): ?Integration\AbstractEntity
	{
		return $this->associatedEntity;
	}

	/**
	 * @param string $entityType
	 * @param int $entityId
	 * @return void
	 * @throws ArgumentException
	 */
	public function setAssociatedEntity($entityType, $entityId): void
	{
		$entity = EntityFactory::createEntity($this, $entityType, $entityId);
		if (!$entity)
		{
			throw new ArgumentException("Unknown entity " . $entityType . "; " . $entityId);
		}

		$this->associatedEntity = $entity;
		$this->entityType = $entityType;
		$this->entityId = $entityId;
		if ($entityType == EntityType::CHAT)
		{
			$this->chatId = $entity->getChatId();
		}
		$this->save();

		$this->getSignaling()->sendAssociatedEntityReplaced($this->getCurrentUserId());
	}

	/**
	 * Returns true if specified user has access to the call.
	 *
	 * @param int $userId Id of the user.
	 * @return bool
	 */
	public function checkAccess($userId): bool
	{
		if (in_array($userId, $this->getUsers()))
		{
			return true;
		}
		if ($this->getAssociatedEntity()?->checkAccess($userId))
		{
			return true;
		}
		return false;
	}

	/**
	 * @return string
	 */
	public function getState(): string
	{
		return $this->state;
	}

	/**
	 * @return int|null
	 */
	public function getParentId(): ?int
	{
		return $this->parentId;
	}

	/**
	 * @return string|null
	 */
	public function getParentUuid(): ?string
	{
		return $this->parentUuid;
	}

	/**
	 * Returns id of the chat, associated with the call.
	 *
	 * @return int
	 */
	public function getChatId(): int
	{
		return $this->chatId;
	}

	public function getUuid()
	{
		return $this->uuid;
	}

	public function getSecretKey()
	{
		return $this->secretKey;
	}

	public function getEndpoint()
	{
		return $this->endpoint;
	}

	/**
	 * Returns date of the call start.
	 *
	 * @return DateTime
	 */
	public function getStartDate(): DateTime
	{
		return $this->startDate;
	}

	/**
	 * Returns date of the call end (if there is one).
	 *
	 * @return DateTime|null
	 */
	public function getEndDate(): ?DateTime
	{
		return $this->endDate;
	}

	/**
	 * Returns call duration.
	 * @return int
	 */
	public function getDuration(): int
	{
		if ($this->startDate)
		{
			$end = $this->endDate ?? new DateTime();
			return $end->getTimestamp() - $this->startDate->getTimestamp();
		}
		return -1;
	}

	/**
	 * Do need to record call.
	 * @return bool
	 */
	public function autoStartRecording(): bool
	{
		// by settings or tariif
		$enable = false;

		if (
			\Bitrix\Call\Integration\AI\CallAISettings::isCallAIEnable()
			&& \Bitrix\Call\Integration\AI\CallAISettings::isAutoStartRecordingEnable()
		)
		{
			// by user limit
			$minUserCount = (int)\Bitrix\Call\Integration\AI\CallAISettings::getRecordMinUsers();
			if ($minUserCount > 0)
			{
				$userCount = $this->getUserCount();
				if ($userCount && $userCount >= $minUserCount)
				{
					$enable = true;
				}
			}
		}

		return $enable;
	}

	public function getUserCount(): int
	{
		$userCount = 0;
		if ($this->associatedEntity)
		{
			$userCount = count($this->associatedEntity->getUsers());
		}
		if (!$userCount && $this->id)
		{
			$this->loadUsers();
			$userCount = count($this->users);
		}

		return $userCount;
	}

	/**
	 * Record call.
	 * @return bool
	 */
	public function isAudioRecordEnabled(): bool
	{
		return $this->enableAudioRecord;
	}

	/**
	 * Do need to record call.
	 * @return self
	 */
	public function enableAudioRecord(): self
	{
		$this->enableAudioRecord = true;
		return $this;
	}

	/**
	 * Disable record call.
	 * @return self
	 */
	public function disableAudioRecord(): self
	{
		$this->enableAudioRecord = false;
		return $this;
	}

	/**
	 * Analyze call with AI enabled.
	 * @return bool
	 */
	public function isAiAnalyzeEnabled(): bool
	{
		return $this->enableAiAnalyze;
	}

	/**
	 * Do AI analyze.
	 * @return self
	 */
	public function enableAiAnalyze(): self
	{
		$this->enableAiAnalyze = true;
		return $this;
	}

	/**
	 * Disable AI analyze.
	 * @return self
	 */
	public function disableAiAnalyze(): self
	{
		$this->enableAiAnalyze = false;
		return $this;
	}

	public function inviteUsers(int $senderId, array $toUserIds, $isLegacyMobile, $video = false, $sendPush = true): void
	{
		$this->getSignaling()->sendInvite(
			$senderId,
			$toUserIds,
			$isLegacyMobile,
			$video,
			$sendPush
		);
	}

	public function sendInviteUsers(int $senderId, array $toUserIds, $isLegacyMobile, $video = false, $sendPush = true): void
	{
		foreach ($toUserIds as $toUserId)
		{
			$this->getSignaling()->sendCallInviteToUser(
				$senderId,
				$toUserId,
				$isLegacyMobile,
				$video,
				$sendPush
			);
		}
	}

	/**
	 * @param string $state
	 */
	public function updateState($state): bool
	{
		if ($this->state == $state)
		{
			return false;
		}
		$prevState = $this->state;
		$this->state = $state;
		$updateResult = CallTable::updateState($this->getId(), $state);
		if (!$updateResult)
		{
			return false;
		}

		if ($this->associatedEntity)
		{
			$this->associatedEntity->onStateChange($state, $prevState);
		}

		return true;
	}

	public function setLogUrl(string $logUrl): void
	{
		$this->logUrl = $logUrl;
	}

	public function setEndpoint($endpoint): void
	{
		$this->endpoint = $endpoint;
	}

	public function finish(): void
	{
		$this->finishCall();
	}

	public function finishCall(): void
	{
		if ($this->endDate instanceof DateTime)
		{
			return;
		}

		$this->endDate = new DateTime();

		if ($this->updateState(static::STATE_FINISHED))
		{
			$this->loadUsers();
			foreach ($this->users as $callUser)
			{
				if ($callUser->getState() === CallUser::STATE_CALLING)
				{
					$callUser->updateState(CallUser::STATE_IDLE);
				}
			}
			$this->getSignaling()->sendFinish();
			$this->saveStat();

			$this->fireCallFinishedEvent();
		}
	}

	/**
	 * @event call:onCallStarted
	 * @return Event
	 */
	protected function fireCallStartedEvent(): Event
	{
		$event = new Event('call', 'onCallStarted', ['call' => $this]);
		$event->send();

		return $event;
	}

	/**
	 * @event call:onCallFinished
	 * @return Event
	 */
	protected function fireCallFinishedEvent(): Event
	{
		$event = new Event('call', 'onCallFinished', ['call' => $this]);
		$event->send();

		return $event;
	}

	public function getConnectionData(int $userId): ?array
	{
		return null;
	}

	public function toArray($currentUserId = 0, $withSecrets = false): array
	{
		$result = [
			'ID' => $this->id,
			'TYPE' => $this->type,
			'SCHEME' => $this->scheme,
			'INITIATOR_ID' => $this->initiatorId,
			'IS_PUBLIC' => $this->isPublic ? 'Y' : 'N',
			'PUBLIC_ID' => $this->publicId,
			'PROVIDER' => $this->provider,
			'ENTITY_TYPE' => $this->entityType,
			'ENTITY_ID' => $this->entityId,
			'PARENT_ID' => $this->parentId,
			'PARENT_UUID' => $this->parentUuid,
			'STATE' => $this->state,
			'START_DATE' => $this->startDate,
			'END_DATE' => $this->endDate,
			'LOG_URL' => $this->logUrl,
			'CHAT_ID' => $this->chatId,
			'ASSOCIATED_ENTITY' => ($this->associatedEntity) ? $this->associatedEntity->toArray($currentUserId) : [],
			'UUID' => $this->uuid,
			'ENDPOINT' => $this->endpoint,
			'RECORD_AUDIO' => $this->enableAudioRecord,
			'AI_ANALYZE' => $this->enableAiAnalyze,
		];
		if ($withSecrets)
		{
			$result['SECRET_KEY'] = $this->secretKey;
		}

		return $result;
	}

	public function save(): void
	{
		$fields = $this->toArray(0, true);
		unset($fields['ID']);

		if (!$this->id)
		{
			$insertResult = CallTable::add($fields);
			$this->id = $insertResult->getId();
		}
		else
		{
			CallTable::update($this->id, $fields);
		}
	}

	public function makeClone($newProvider = null): Call
	{
		$callFields = $this->toArray();
		$callFields['ID'] = null;
		$callFields['PUBLIC_ID'] = randString(10);
		$callFields['STATE'] = static::STATE_NEW;
		$callFields['PROVIDER'] = $newProvider ?? $callFields['PROVIDER'];
		$callFields['PARENT_ID'] = $this->id;

		$instance = CallFactory::createWithArray($callFields['PROVIDER'], $callFields);
		$instance->save();

		$instance->users = [];
		foreach ($this->getUsers() as $userId)
		{
			$instance->users[$userId] = CallUser::create([
				'CALL_ID' => $instance->id,
				'USER_ID' => $userId,
				'STATE' => $instance->users[$userId] ? $instance->users[$userId]->getState() : CallUser::STATE_IDLE,
				'LAST_SEEN' => null
			]);
			$instance->users[$userId]->save();
		}

		return $instance;
	}

	public function createChildCall(
		string $newUuid,
		string $entityId,
		string $newProvider = null,
		int $scheme = null,
		int $newInitiator = null,
	): Call
	{
		$callFields = $this->toArray();
		$callFields['ID'] = null;
		$callFields['UUID'] = $newUuid;
		$callFields['PUBLIC_ID'] = randString(10);
		$callFields['STATE'] = static::STATE_NEW;
		$callFields['PROVIDER'] = $newProvider ?? $callFields['PROVIDER'];
		$callFields['PARENT_ID'] = $this->id;
		$callFields['PARENT_UUID'] = $this->uuid;
		if ($scheme)
		{
			$callFields['SCHEME'] = $scheme;
		}

		if ($newInitiator)
		{
			$callFields['INITIATOR_ID'] = $newInitiator;
		}

		$instance = self::createCallInstance($callFields);

		$instance->associatedEntity = Integration\EntityFactory::createEntity($instance, EntityType::CHAT, $entityId);
		$instance->chatId = (int)$instance->associatedEntity->getChatId();
		$instance->entityId = $entityId;

		$instance->save();

		$instance->associatedEntity->onCallCreate();

		$instance->users = [];
		foreach ($this->getUsers() as $userId)
		{
			$instance->users[$userId] = CallUser::create([
				'CALL_ID' => $instance->id,
				'USER_ID' => $userId,
				'STATE' => $instance->users[$userId] ? $instance->users[$userId]->getState() : CallUser::STATE_IDLE,
				'LAST_SEEN' => null
			]);
			$instance->users[$userId]->save();
		}

		return $instance;
	}

	protected function loadUsers(): void
	{
		if (is_array($this->users))
		{
			return;
		}

		$this->users = [];

		$cursor = CallUserTable::getList(array(
			'filter' => array(
				'=CALL_ID' => $this->id
			)
		));

		while($row = $cursor->fetch())
		{
			$this->users[$row['USER_ID']] = CallUser::create($row);
		}
	}

	protected function saveStat()
	{
		$callLength = 0;
		if ($this->startDate instanceof DateTime && $this->endDate instanceof DateTime)
		{
			$callLength = $this->endDate->getTimestamp() - $this->startDate->getTimestamp();
		}
		$userCountChat = count($this->users);

		$usersActive = 0;
		$mobileUsers = 0;
		$externalUsers = 0;
		$screenShared = false;
		$recorded = false;
		$authTypes = UserTable::getList([
			'select' => ['ID', 'EXTERNAL_AUTH_ID'],
			'filter' => ['=ID' => $this->getUsers()]
		])->fetchAll();
		$authTypes = array_column($authTypes, 'EXTERNAL_AUTH_ID', 'ID');
		foreach ($this->users as $userId => $user)
		{
			if ($user->getLastSeen() != null)
			{
				$usersActive++;
			}
			if ($user->isUaMobile())
			{
				$mobileUsers++;
			}
			if ($authTypes[$userId] === Auth::AUTH_TYPE)
			{
				$externalUsers++;
				if ($user->getFirstJoined())
				{
					$userLateness = $user->getFirstJoined()->getTimestamp() - $this->startDate->getTimestamp();
					AddEventToStatFile("im", "im_call_finish", $this->id, $userLateness, "user_lateness", $userId);
				}
			}
			if ($user->wasRecorded())
			{
				$recorded = true;
			}
			if ($user->wasRecorded())
			{
				$screenShared = true;
			}
		}

		$chatType = null;
		$finishStatus = 'normal';
		if ($this->entityType === EntityType::CHAT)
		{
			if(is_numeric($this->entityId))
			{
				$chatType = 'private';
				// private chat, entity id === other user id
				$otherUserState =
					$this->getUser($this->entityId)
						? $this->getUser($this->entityId)->getState()
						: ''
				;

				if ($otherUserState == CallUser::STATE_DECLINED)
				{
					$finishStatus = 'declined';
				}
				else if ($otherUserState == CallUser::STATE_BUSY)
				{
					$finishStatus = 'busy';
				}
				else if ($otherUserState == CallUser::STATE_UNAVAILABLE || $otherUserState == CallUser::STATE_CALLING)
				{
					$finishStatus = 'unavailable';
				}
			}
			else
			{
				$chatId = Dialog::getChatId($this->entityId);
				$isVideoConf = (bool)AliasTable::getRow([
					'filter' => ['=ENTITY_ID' => $chatId, '=ENTITY_TYPE' => \Bitrix\Im\Alias::ENTITY_TYPE_VIDEOCONF]
				]);
				$chatType = 'group';
			}
		}

		if ($callLength > 30 && $finishStatus === 'normal')
		{
			\Bitrix\Im\Limit::incrementCounter(\Bitrix\Im\Limit::COUNTER_CALL_SUCCESS);
		}

		AddEventToStatFile("im", "im_call_finish", $this->id, $userCountChat, "user_count_chat", 0);
		AddEventToStatFile("im", "im_call_finish", $this->id, $usersActive, "user_count_call", 0);
		AddEventToStatFile("im", "im_call_finish", $this->id, $mobileUsers, "user_count_mobile", 0);
		AddEventToStatFile("im", "im_call_finish", $this->id, $externalUsers, "user_count_external", 0);
		AddEventToStatFile("im", "im_call_finish", $this->id, $callLength, "call_length", 0);
		AddEventToStatFile("im", "im_call_finish", $this->id, ($screenShared ? "Y" : "N"), "screen_shared", 0);
		AddEventToStatFile("im", "im_call_finish", $this->id, ($recorded ? "Y" : "N"), "recorded", 0);
		if($chatType)
		{
			AddEventToStatFile("im","im_call_finish", $this->id, $chatType, "chat_type", 0);
		}
		if (isset($isVideoConf))
		{
			AddEventToStatFile("im","im_call_finish", $this->id, ($isVideoConf ? "Y" : "N"), "is_videoconf", 0);
		}
		AddEventToStatFile("im","im_call_finish", $this->id, $finishStatus, "status", 0);
	}

	public static function isFeedbackAllowed(): bool
	{
		if (Loader::includeModule('bitrix24'))
		{
			return \CBitrix24::getPortalZone() == 'ru';
		}

		return Option::get('im', 'allow_call_feedback', 'N') === 'Y';
	}

	public function getMaxUsers(): int
	{
		return self::getMaxParticipants();
	}

	public function getLogToken(int $userId = 0, int $ttl = 3600) : string
	{
		$userId = $userId ?: $this->getCurrentUserId();
		if(!$userId)
		{
			return  '';
		}

		if (Loader::includeModule("bitrix24") && defined('BX24_HOST_NAME'))
		{
			$portalId = BX24_HOST_NAME;
		}
		else if (defined('IM_CALL_LOG_HOST'))
		{
			$portalId = \IM_CALL_LOG_HOST;
		}
		else
		{
			return '';
		}

		$secret = Option::get('im', 'call_log_secret');
		if ($secret == '')
		{
			return '';
		}

		return JWT::encode(
			[
				'prt' => $portalId,
				'call' => $this->getId(),
				'usr' => $userId,
				'exp' => (new DateTime())->getTimestamp() + $ttl
			],
			$secret
		);
	}

	public static function getLogService() : string
	{
		return (string)Option::get('im', 'call_log_service');
	}

	public static function getMaxParticipants(): int
	{
		if (static::isCallServerEnabled())
		{
			return static::getMaxCallServerParticipants();
		}

		return (int)Option::get('call', 'turn_server_max_users');
	}

	public static function getMaxCallServerParticipants(): int
	{
		if (Loader::includeModule('bitrix24'))
		{
			return (int)\Bitrix\Bitrix24\Feature::getVariable('im_max_call_participants');
		}
		return (int)Option::get('im', 'call_server_max_users');
	}

	public static function getMaxCallLimit(): int
	{
		if (!\Bitrix\Main\Loader::includeModule('bitrix24'))
		{
			return 0;
		}

		return (int)\Bitrix\Bitrix24\Feature::getVariable('im_call_extensions_limit');
	}

	/**
	 * Use this constructor only for creating new calls
	 */
	public static function createWithEntity(
		int $type,
		string $provider,
		string $entityType,
		string $entityId,
		int $initiatorId,
		?string $callUuid,
		?int $scheme = null
	): Call
	{
		$instance = new static();
		$instance->type = $type;
		$instance->initiatorId = $initiatorId;
		$instance->provider = $provider;
		$instance->entityType = $entityType;
		$instance->entityId = $entityId;
		$instance->uuid = !$callUuid && $provider === self::PROVIDER_PLAIN ? Util::generateUUID() : $callUuid;
		$instance->startDate = new DateTime();
		$instance->publicId = randString(10);
		$instance->state = static::STATE_NEW;

		if ($scheme && in_array($scheme, [self::SCHEME_CLASSIC, self::SCHEME_JWT], true))
		{
			$instance->scheme = $scheme;
		}
		else
		{
			$instance->scheme = self::SCHEME_CLASSIC;
			if (
				\Bitrix\Call\Settings::isNewCallsEnabled()
				&& (
					$instance->provider === self::PROVIDER_BITRIX
					|| ($instance->provider === self::PROVIDER_PLAIN && \Bitrix\Call\Settings::isPlainCallsUseNewScheme())
				)
			)
			{
				$instance->scheme = self::SCHEME_JWT;
			}
		}

		$instance->associatedEntity = Integration\EntityFactory::createEntity($instance, $entityType, $entityId);
		$instance->chatId = (int)$instance->associatedEntity->getChatId();

		$instance->enableAudioRecord = $instance->autoStartRecording();
		$instance->enableAiAnalyze = $instance->enableAudioRecord;

		$instance->save();

		// todo: remove when the calls are supported in the mobile
		$instance->associatedEntity->onCallCreate();

		$instance->users = [];
		foreach ($instance->associatedEntity->getUsers() as $userId)
		{
			$instance->users[$userId] = CallUser::create([
				'CALL_ID' => $instance->id,
				'USER_ID' => $userId,
				'STATE' => CallUser::STATE_UNAVAILABLE,
				'LAST_SEEN' => null
			]);
			$instance->users[$userId]->save();
		}

		$instance->initCall();

		self::sendCreateCallEvent($instance);

		return $instance;
	}

	protected static function sendCreateCallEvent(Call $instance): void
	{
		$event = new Event(
			'im',
			'onCallCreate',
			[
				'id' => $instance->id,
				'type' => $instance->type,
				'scheme' => $instance->scheme,
				'initiatorId' => $instance->initiatorId,
				'provider' => $instance->provider,
				'entityType' => $instance->entityType,
				'entityId' => $instance->entityId,
				'startDate' => $instance->startDate,
				'publicId' => $instance->publicId,
				'chatId' => $instance->chatId,
			]
		);
		$event->send();
	}

	protected function initCall(): void
	{
		if (!Loader::includeModule('call'))
		{
			return;
		}

		if ($this->scheme === self::SCHEME_JWT)
		{
			return;
		}

		$this->fireCallStartedEvent();
	}

	/**
	 * Creates new instance of the Call with values from the database.
	 *
	 * @param array $fields Call fields
	 * @return Call
	 */
	public static function createWithArray(array $fields): Call
	{
		$instance = self::createCallInstance($fields);

		$instance->initCall();

		return $instance;
	}

	/**
	 * Creates new instance of the Call with values from the database.
	 *
	 * @param array $fields Call fields
	 * @return Call
	 */
	public static function createCallInstance(array $fields): Call
	{
		$instance = new static();

		$instance->id = $fields['ID'];
		$instance->type = (int)$fields['TYPE'];
		$instance->initiatorId = (int)$fields['INITIATOR_ID'];
		$instance->isPublic = $fields['IS_PUBLIC'];
		$instance->publicId = $fields['PUBLIC_ID'];
		$instance->provider = $fields['PROVIDER'];
		$instance->entityType = $fields['ENTITY_TYPE'];
		$instance->entityId = $fields['ENTITY_ID'];
		$instance->startDate = isset($fields['START_DATE']) && $fields['START_DATE'] instanceof DateTime ? $fields['START_DATE'] : null;
		$instance->endDate = isset($fields['END_DATE']) && $fields['END_DATE'] instanceof DateTime ? $fields['END_DATE'] : null;
		$instance->parentId = (int)$fields['PARENT_ID'] ?: null;
		$instance->parentUuid = $fields['PARENT_UUID'] ?: null;
		$instance->state = $fields['STATE'];
		$instance->logUrl = $fields['LOG_URL'];
		$instance->chatId = (int)$fields['CHAT_ID'];
		$instance->uuid = $fields['UUID'];
		$instance->secretKey = $fields['SECRET_KEY'];
		$instance->endpoint = $fields['ENDPOINT'];

		$instance->scheme = self::SCHEME_CLASSIC;
		if (isset($fields['SCHEME']))
		{
			$instance->scheme = (int)($fields['SCHEME'] ?: self::SCHEME_CLASSIC);
		}
		elseif (
			\Bitrix\Call\Settings::isNewCallsEnabled()
			&& (
				$instance->provider === self::PROVIDER_BITRIX
				|| ($instance->provider === self::PROVIDER_PLAIN && \Bitrix\Call\Settings::isPlainCallsUseNewScheme())
			)
		)
		{
			$instance->scheme = self::SCHEME_JWT;
		}

		if ($instance->entityType && $instance->entityId)
		{
			$instance->associatedEntity = Integration\EntityFactory::createEntity($instance, $instance->entityType, $instance->entityId);
		}

		if (isset($fields['RECORD_AUDIO']))
		{
			$instance->enableAudioRecord = ($fields['RECORD_AUDIO'] === 'Y');
		}
		else
		{
			$instance->enableAudioRecord = $instance->autoStartRecording();
		}

		if (isset($fields['AI_ANALYZE']))
		{
			$instance->enableAiAnalyze = ($fields['AI_ANALYZE'] === 'Y');
		}
		else
		{
			$instance->enableAiAnalyze = $instance->enableAudioRecord;
		}

		return $instance;
	}

	public static function loadWithId($id): ?Call
	{
		$row = CallTable::getRowById($id);

		if (is_array($row))
		{
			return static::createWithArray($row);
		}

		return null;
	}

	public static function loadWithUuid($uuid): ?Call
	{
		$searchParams = [
			'filter' => [
				'UUID' => $uuid,
			],
			'limit' => 1,
		];

		$row = CallTable::getRow($searchParams);

		if (is_array($row))
		{
			return static::createWithArray($row);
		}

		return null;
	}

	public static function isCallServerEnabled(): bool
	{
		if (!Loader::includeModule('call'))
		{
			return false;
		}

		return (bool)Option::get("im", "call_server_enabled");
	}

	public static function getTurnServer(): string
	{
		if (Option::get('call', 'turn_server_self') == 'Y')
		{
			$turnServer = Option::get('call', 'turn_server');
		}
		else
		{
			$region = \Bitrix\Main\Application::getInstance()->getLicense()->getRegion();
			if (in_array($region, ['ru', 'by', 'kz']))
			{
				$turnServer = 'turn.bitrix24.tech';
			}
			else
			{
				$turnServer = 'turn.calls.bitrix24.com';
			}
		}

		return $turnServer;
	}

	public static function isBitrixCallEnabled(): bool
	{
		return self::isCallServerEnabled();
	}

	public static function isIosBetaEnabled(): bool
	{
		$isEnabled = Option::get('im', 'call_beta_ios', 'N');

		return $isEnabled === 'Y';
	}

	protected function getCurrentUserId() : int
	{
		return $GLOBALS['USER'] ? (int)$GLOBALS['USER']->getId() : 0;
	}

	public static function onVoximplantConferenceFinished(Event $event): void
	{
		$callId = $event->getParameter('CONFERENCE_CALL_ID');
		$logUrl = $event->getParameter('LOG_URL');
		if (!$logUrl)
		{
			return;
		}

		$call = Call::loadWithId($callId);
		if (!$call)
		{
			return;
		}
		$call->finish();
		$call->setLogUrl($logUrl);
		$call->save();
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit