Your IP : 18.116.88.123


Current Path : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/modules/rest/lib/api/
Upload File :
Current File : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/modules/rest/lib/api/event.php

<?php
namespace Bitrix\Rest\Api;


use Bitrix\Bitrix24\Feature;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\ArgumentNullException;
use Bitrix\Main\Loader;
use Bitrix\Main\Type\DateTime;
use Bitrix\Rest\AccessException;
use Bitrix\Rest\AppTable;
use Bitrix\Rest\AuthTypeException;
use Bitrix\Rest\EventOfflineTable;
use Bitrix\Rest\EventTable;
use Bitrix\Rest\HandlerHelper;
use Bitrix\Rest\LicenseException;
use Bitrix\Rest\OAuth\Auth;
use Bitrix\Rest\RestException;
use Bitrix\Rest\Exceptions;

class Event extends \IRestService
{
	const FEATURE_EXTENDED_MODE = 'rest_offline_extended';

	/**
	 * Returns description of events REST API
	 *
	 * @return array
	 */
	public static function onRestServiceBuildDescription()
	{
		return array(
			\CRestUtil::GLOBAL_SCOPE => array(
				'events' => array(__CLASS__, 'eventsList'),
				'event.bind' => array(__CLASS__, 'eventBind'),
				'event.unbind' => array(__CLASS__, 'eventUnBind'),
				'event.get' => array(__CLASS__, 'eventGet'),
				'event.offline.get' => array(__CLASS__, 'eventOfflineGet'),
				'event.offline.clear' => array(__CLASS__, 'eventOfflineClear'),
				'event.offline.error' => array(__CLASS__, 'eventOfflineError'),
				'event.offline.list' => array(__CLASS__, 'eventOfflineList'),

				'event.test' => array(
					'callback' => array(__CLASS__, 'eventTest'),
					'options' => array()
				),
				\CRestUtil::EVENTS =>  array(
					'onOfflineEvent' => array(
						'rest',
						'onAfterOfflineEventCall',
						array(EventOfflineTable::class, 'prepareOfflineEvent'),
						array(
							"sendRefreshToken" => true,
							"disableOffline" => true,
							"allowOptions" => [
								'minTimeout' => 'int'
							],
						),
					)
				),
			),
		);
	}

	/**
	 * /rest/events method handler
	 *
	 * Administrator rights required
	 *
	 * Query format:
	 *
	 * SCOPE - limit events list by some scope
	 * FULL - get all events regardless of application scope
	 *
	 * @param array $query
	 * @param $n
	 * @param \CRestServer $server
	 *
	 * @return array
	 *
	 * @throws AuthTypeException
	 */
	public static function eventsList($query, $n, \CRestServer $server)
	{
		if($server->getAuthType() !== Auth::AUTH_TYPE)
		{
			throw new AuthTypeException();
		}

		$serviceDescription = $server->getServiceDescription();

		$scopeList = array(\CRestUtil::GLOBAL_SCOPE);
		$result = array();

		$query = array_change_key_case($query, CASE_UPPER);

		if(isset($query['SCOPE']))
		{
			if($query['SCOPE'] != '')
			{
				$scopeList = array($query['SCOPE']);
			}
		}
		elseif(isset($query['FULL']) && $query['FULL'])
		{
			$scopeList = array_keys($serviceDescription);
		}
		else
		{
			$scopeList = $server->getAuthScope();
			$scopeList[] = \CRestUtil::GLOBAL_SCOPE;
		}

		foreach ($serviceDescription as $scope => $scopeMethods)
		{
			if(in_array($scope, $scopeList) && isset($scopeMethods[\CRestUtil::EVENTS]))
			{
				$result = array_merge($result, array_keys($scopeMethods[\CRestUtil::EVENTS]));
			}
		}

		return $result;
	}


	/**
	 * /rest/event.bind method handler
	 *
	 * Administrator rights required
	 *
	 * Query format:
	 *
	 * - EVENT - event name
	 * - EVENT_TYPE = {online|offline} - type of event handling. Default: online
	 * - AUTH_TYPE - User ID, whose auth will be generated for handler. Useless for offline type. Default value is 0, which means getting auth for user, authorized when event is called
	 * - HANDLER - URL of event handler. Useless for offline type
	 *
	 * @param array $query
	 * @param $n
	 * @param \CRestServer $server
	 *
	 * @return bool
	 *
	 * @throws AccessException
	 * @throws ArgumentException
	 * @throws ArgumentNullException
	 * @throws AuthTypeException
	 * @throws RestException
	 * @throws \Exception
	 */
	public static function eventBind($query, $n, \CRestServer $server)
	{
		global $USER;

		if($server->getAuthType() !== \Bitrix\Rest\OAuth\Auth::AUTH_TYPE)
		{
			throw new AuthTypeException();
		}

		$query = array_change_key_case($query, CASE_UPPER);

		$eventName = mb_strtoupper($query['EVENT'] ?? '');
		$eventType = mb_strtolower($query['EVENT_TYPE'] ?? '');
		$eventUser = intval($query['AUTH_TYPE'] ?? null);
		$eventCallback = $query['HANDLER'] ?? '';
		$options = isset($query['OPTIONS']) && is_array($query['OPTIONS']) ? $query['OPTIONS'] : [];

		if($eventUser > 0)
		{
			if(!\CRestUtil::isAdmin() && $eventUser !== intval($USER->GetID()))
			{
				throw new AccessException('Event binding with AUTH_TYPE requires administrator access rights');
			}
		}
		elseif(!\CRestUtil::isAdmin())
		{
			$eventUser = intval($USER->GetID());
		}

		$authData = $server->getAuthData();

		$connectorId = isset($authData['auth_connector']) ? $authData['auth_connector'] : '';

		if($eventName == '')
		{
			throw new Exceptions\ArgumentNullException("EVENT");
		}

		if($eventType <> '')
		{
			if(!in_array($eventType, array(EventTable::TYPE_ONLINE, EventTable::TYPE_OFFLINE)))
			{
				throw new Exceptions\ArgumentException('Value must be one of {'.EventTable::TYPE_ONLINE.'|'.EventTable::TYPE_OFFLINE.'}', 'EVENT_TYPE');
			}
		}
		else
		{
			$eventType = EventTable::TYPE_ONLINE;
		}

		if($eventType === EventTable::TYPE_OFFLINE)
		{
			if(!\CRestUtil::isAdmin())
			{
				throw new AccessException('Offline events binding requires administrator access rights');
			}

			$eventCallback = '';
			$eventUser = 0;
		}
		elseif($eventCallback == '' && $eventType === EventTable::TYPE_ONLINE)
		{
			throw new Exceptions\ArgumentNullException("HANDLER");
		}

		$clientInfo = AppTable::getByClientId($server->getClientId());

		if($eventCallback == '' || HandlerHelper::checkCallback($eventCallback, $clientInfo))
		{
			$scopeList = $server->getAuthScope();
			$scopeList[] = \CRestUtil::GLOBAL_SCOPE;

			$serviceDescription = $server->getServiceDescription();

			foreach($scopeList as $scope)
			{
				if(
					isset($serviceDescription[$scope])
					&& is_array($serviceDescription[$scope][\CRestUtil::EVENTS])
					&& array_key_exists($eventName, $serviceDescription[$scope][\CRestUtil::EVENTS])
				)
				{
					$eventInfo = $serviceDescription[$scope][\CRestUtil::EVENTS][$eventName];
					if(is_array($eventInfo))
					{
						$eventHandlerFields = array(
							'APP_ID' => $clientInfo['ID'],
							'EVENT_NAME' => $eventName,
							'EVENT_HANDLER' => $eventCallback,
							'CONNECTOR_ID' => $connectorId,
							'OPTIONS' => []
						);

						if($eventUser > 0)
						{
							$eventHandlerFields['USER_ID'] = $eventUser;
						}

						if (
							$eventCallback === ''
							&& isset($eventInfo[3]['disableOffline'])
							&& $eventInfo[3]['disableOffline'] === true
						)
						{
							throw new RestException('Offline event cannot be registered for this event.', RestException::ERROR_ARGUMENT);
						}

						if (!empty($options) && isset($eventInfo[3]['allowOptions']) && is_array($eventInfo[3]['allowOptions']))
						{
							foreach ($eventInfo[3]['allowOptions'] as $code => $type)
							{
								if (isset($options[$code]))
								{
									if ($type === 'int')
									{
										$eventHandlerFields['OPTIONS'][$code] = (int) $options[$code];
									}
									elseif($type === 'str' && is_string($options[$code]))
									{
										$eventHandlerFields['OPTIONS'][$code] = $options[$code];
									}
								}
							}
						}

						$result = EventTable::add($eventHandlerFields);
						if($result->isSuccess())
						{
							\Bitrix\Rest\Event\Sender::bind($eventInfo[0], $eventInfo[1]);
						}
						else
						{
							$errorMessage = $result->getErrorMessages();
							throw new RestException('Unable to set event handler: '.implode('. ', $errorMessage), RestException::ERROR_CORE);
						}
					}

					return true;
				}
			}

			throw new RestException('Event not found', EventTable::ERROR_EVENT_NOT_FOUND);
		}
		else
		{
			return false;
		}
	}

	/**
	 * /rest/event.unbind method handler
	 *
	 * Returns count of unbinded events
	 *
	 * Administrator rights required
	 *
	 * Query format:
	 *
	 * - EVENT - event name
	 * - EVENT_TYPE = {online|offline} - type of event handling. Default: online
	 * - AUTH_TYPE - The same value as event.bind was called with. Useless for offline type. Default 0
	 * - HANDLER - URL of event handler. Useless for offline type
	 *
	 * @param array $query
	 * @param $n
	 * @param \CRestServer $server
	 *
	 * @return array
	 *
	 * @throws AccessException
	 * @throws ArgumentException
	 * @throws ArgumentNullException
	 * @throws AuthTypeException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 * @throws \Bitrix\Main\SystemException
	 * @throws \Exception
	 */
	public static function eventUnbind($query, $n, \CRestServer $server)
	{
		global $USER;

		if($server->getAuthType() !== Auth::AUTH_TYPE)
		{
			throw new AuthTypeException();
		}

		$query = array_change_key_case($query, CASE_UPPER);

		$eventName = mb_strtoupper($query['EVENT'] ?? '');
		$eventType = mb_strtolower($query['EVENT_TYPE'] ?? '');
		$eventCallback = $query['HANDLER'] ?? '';

		if($eventName == '')
		{
			throw new ArgumentNullException("EVENT");
		}

		if($eventType <> '')
		{
			if(!in_array($eventType, array(EventTable::TYPE_ONLINE, EventTable::TYPE_OFFLINE)))
			{
				throw new ArgumentException('Value must be one of {'.EventTable::TYPE_ONLINE.'|'.EventTable::TYPE_OFFLINE.'}', 'EVENT_TYPE');
			}
		}
		else
		{
			$eventType = EventTable::TYPE_ONLINE;
		}

		if($eventType === EventTable::TYPE_OFFLINE)
		{
			if(!\CRestUtil::isAdmin())
			{
				throw new AccessException('Offline events unbinding requires administrator access rights');
			}

			$eventCallback = '';
		}
		elseif($eventCallback == '')
		{
			throw new Exceptions\ArgumentNullException('HANDLER');
		}

		$clientInfo = AppTable::getByClientId($server->getClientId());

		$filter = array(
			'=APP_ID' => $clientInfo["ID"],
			'=EVENT_NAME' => $eventName,
			'=EVENT_HANDLER' => $eventCallback,
		);

		if($eventType === EventTable::TYPE_OFFLINE)
		{
			$authData = $server->getAuthData();
			$filter['=CONNECTOR_ID'] = isset($authData['auth_connector']) ? $authData['auth_connector'] : '';
		}
		else
		{
			if(isset($query['AUTH_TYPE']))
			{
				if(!\CRestUtil::isAdmin() && $query['AUTH_TYPE'] !== intval($USER->GetID()))
				{
					throw new AccessException('Event unbinding with AUTH_TYPE requires administrator access rights');
				}

				$filter['=USER_ID'] = intval($query['AUTH_TYPE']);
			}
			elseif(!\CRestUtil::isAdmin())
			{
				$filter['=USER_ID'] = intval($USER->GetID());
			}
		}

		$dbRes = EventTable::getList(array(
			'filter' => $filter,
			'select' => ['ID']
		));

		$cnt = 0;
		while($eventInfo = $dbRes->fetch())
		{
			$result = EventTable::delete($eventInfo["ID"]);
			if($result->isSuccess())
			{
				// we shouldn't make Unbind here, it'll be done during the first event call
				$cnt++;
			}
		}

		return array('count' => $cnt);
	}


	public static function eventGet($query, $n, \CRestServer $server)
	{
		global $USER;

		if($server->getAuthType() !== Auth::AUTH_TYPE)
		{
			throw new AuthTypeException();
		}

		$result = array();

		$clientInfo = AppTable::getByClientId($server->getClientId());

		$filter = array(
			"=APP_ID" => $clientInfo["ID"],
		);

		if(!\CRestUtil::isAdmin())
		{
			$filter['=USER_ID'] = $USER->GetID();
		}

		$dbRes = EventTable::getList(array(
			"filter" => $filter,
			'order' => array(
				"ID" => "ASC",
			),
		));
		while($eventHandler = $dbRes->fetch())
		{
			if($eventHandler['EVENT_HANDLER'] <> '')
			{
				$result[] = array(
					"event" => $eventHandler['EVENT_NAME'],
					"handler" => $eventHandler['EVENT_HANDLER'],
					"auth_type" => $eventHandler['USER_ID'],
					"offline" => 0
				);
			}
			else
			{
				$result[] = array(
					"event" => $eventHandler['EVENT_NAME'],
					"connector_id" => $eventHandler['CONNECTOR_ID'] === null ? '' : $eventHandler['CONNECTOR_ID'],
					"offline" => 1
				);
			}
		}

		return $result;
	}


	public static function eventTest($query, $n, \CRestServer $server)
	{
		if($server->getAuthType() !== Auth::AUTH_TYPE)
		{
			throw new AuthTypeException();
		}

		$clientInfo = AppTable::getByClientId($server->getClientId());

		foreach(GetModuleEvents("rest", "OnRestAppTest", true) as $event)
		{
			ExecuteModuleEventEx($event, array(array(
				"APP_ID" => $clientInfo["ID"],
				"QUERY" => $query
			)));
		}

		return 1;
	}


	public static function eventOfflineGet($query, $n, \CRestServer $server)
	{
		if ($server->getAuthType() !== Auth::AUTH_TYPE)
		{
			throw new AuthTypeException();
		}

		if (!\CRestUtil::isAdmin())
		{
			throw new AccessException();
		}

		$query = array_change_key_case($query, CASE_LOWER);

		$clearEvents = !isset($query['clear']) ? 1 : intval($query['clear']);
		$processId = isset($query['process_id']) ? trim($query['process_id']) : null;

		if (!$clearEvents && !static::isExtendedModeEnabled())
		{
			throw new LicenseException('extended offline events handling');
		}

		$filter = isset($query['filter']) ? $query['filter'] : array();
		$order = isset($query['order']) ? $query['order'] : array('TIMESTAMP_X' => 'ASC');
		$limit = isset($query['limit']) ? intval($query['limit']) : static::LIST_LIMIT;

		$getErrors = isset($query['error']) && intval($query['error']) === 1;

		$authData = $server->getAuthData();
		$connectorId = isset($authData['auth_connector']) ? $authData['auth_connector'] : '';

		$returnProcessId = !$clearEvents;

		if ($limit <= 0)
		{
			throw new Exceptions\ArgumentException('Value must be positive integer', 'LIMIT');
		}

		$queryFilter = static::sanitizeFilter($filter);

		$order = static::sanitizeOrder($order);

		$clientInfo = AppTable::getByClientId($server->getClientId());

		$queryFilter['=APP_ID'] = $clientInfo['ID'];
		$queryFilter['=CONNECTOR_ID'] = $connectorId;
		$queryFilter['=ERROR'] = $getErrors ? 1 : 0;

		if ($processId === null)
		{
			$queryFilter['=PROCESS_ID'] = '';
			$processId = EventOfflineTable::markEvents($queryFilter, $order, $limit);
		}
		else
		{
			$returnProcessId = true;
		}

		$queryFilter['=PROCESS_ID'] = $processId;

		$dbRes = EventOfflineTable::getList(array(
			'select' => array(
				'ID', 'TIMESTAMP_X', 'EVENT_NAME', 'EVENT_DATA', 'EVENT_ADDITIONAL', 'MESSAGE_ID'
			),
			'filter' => $queryFilter,
			'limit' => $limit,
			'order' => $order,
		));

		$result = array();

		while ($event = $dbRes->fetch())
		{
			/** @var DateTime $ts */
			$ts = $event['TIMESTAMP_X'];

			$event['TIMESTAMP_X'] = \CRestUtil::convertDateTime($ts->toString());

			if (isset($event['EVENT_ADDITIONAL'][Auth::PARAM_LOCAL_USER]))
			{
				$event['EVENT_ADDITIONAL'] = [
					'user_id' => $event['EVENT_ADDITIONAL'][Auth::PARAM_LOCAL_USER],
				];
			}

			$result[] = $event;
		}

		if ($clearEvents && count($result) > 0)
		{
			EventOfflineTable::clearEvents($processId, $clientInfo['ID'], $connectorId);
		}

		return array(
			'process_id' => $returnProcessId ? $processId : null,
			'events' => $result
		);
	}

	public static function eventOfflineClear($query, $n, \CRestServer $server)
	{
		if ($server->getAuthType() !== Auth::AUTH_TYPE)
		{
			throw new AuthTypeException();
		}

		if (!\CRestUtil::isAdmin())
		{
			throw new AccessException();
		}

		$query = array_change_key_case($query, CASE_LOWER);

		$processId = isset($query['process_id']) ? trim($query['process_id']) : null;

		$authData = $server->getAuthData();
		$connectorId = isset($authData['auth_connector']) ? $authData['auth_connector'] : '';

		if ($processId === null)
		{
			throw new Exceptions\ArgumentNullException('PROCESS_ID');
		}

		$clientInfo = AppTable::getByClientId($server->getClientId());

		if (isset($query['message_id']))
		{
			$listIds = false;
			if (!is_array($query['message_id']))
			{
				throw new Exceptions\ArgumentException('Value must be array of MESSAGE_ID values', 'message_id');
			}

			foreach($query['message_id'] as $messageId)
			{
				$messageId = trim($messageId);

				if (mb_strlen($messageId) !== 32)
				{
					throw new Exceptions\ArgumentException('Value must be array of MESSAGE_ID values', 'messsage_id');
				}

				$listIds[] = $messageId;
			}

			EventOfflineTable::clearEventsByMessageId($processId, $clientInfo['ID'], $connectorId, $listIds);
		}
		else
		{
			$listIds = false;
			if (isset($query['id']))
			{
				if (!is_array($query['id']))
				{
					throw new Exceptions\ArgumentException('Value must be array of integers', 'id');
				}

				foreach($query['id'] as $id)
				{
					$id = intval($id);

					if ($id <= 0)
					{
						throw new Exceptions\ArgumentException('Value must be array of integers', 'id');
					}

					$listIds[] = $id;
				}
			}

			EventOfflineTable::clearEvents($processId, $clientInfo['ID'], $connectorId, $listIds);
		}

		return true;
	}

	public static function eventOfflineError($query, $n, \CRestServer $server)
	{
		if($server->getAuthType() !== Auth::AUTH_TYPE)
		{
			throw new AuthTypeException();
		}

		if(!\CRestUtil::isAdmin())
		{
			throw new AccessException();
		}

		$query = array_change_key_case($query, CASE_LOWER);

		$processId = isset($query['process_id']) ? trim($query['process_id']) : null;
		$messageId = isset($query['message_id']) ? $query['message_id'] : null;

		$authData = $server->getAuthData();
		$connectorId = isset($authData['auth_connector']) ? $authData['auth_connector'] : '';

		if($processId === null)
		{
			throw new ArgumentNullException('PROCESS_ID');
		}

		if(!is_array($messageId))
		{
			throw new ArgumentException('Value must be array of MESSAGE_ID values', 'message_id');
		}

		$clientInfo = AppTable::getByClientId($server->getClientId());
		if(count($messageId) > 0)
		{
			EventOfflineTable::markError($processId, $clientInfo['ID'], $connectorId, $messageId);
		}

		return true;
	}

	public static function eventOfflineList($query, $n, \CRestServer $server)
	{
		if($server->getAuthType() !== Auth::AUTH_TYPE)
		{
			throw new AuthTypeException();
		}

		if(!\CRestUtil::isAdmin())
		{
			throw new AccessException();
		}

		$query = array_change_key_case($query, CASE_LOWER);

		$filter = isset($query['filter']) ? $query['filter'] : array();
		$order = isset($query['order']) ? $query['order'] : array('ID' => 'ASC');

		$authData = $server->getAuthData();
		$connectorId = isset($authData['auth_connector']) ? $authData['auth_connector'] : '';

		$queryFilter = static::sanitizeFilter($filter, array('ID', 'TIMESTAMP_X', 'EVENT_NAME', 'MESSAGE_ID', 'PROCESS_ID', 'ERROR'));

		$order = static::sanitizeOrder($order, array('ID', 'TIMESTAMP_X', 'EVENT_NAME', 'MESSAGE_ID', 'PROCESS_ID', 'ERROR'));

		$clientInfo = AppTable::getByClientId($server->getClientId());

		$queryFilter['=APP_ID'] = $clientInfo['ID'];

		$getEventQuery = EventOfflineTable::query();

		if ($connectorId === '')
		{
			$getEventQuery->where('CONNECTOR_ID', '');
		}
		else
		{
			$queryFilter['=CONNECTOR_ID'] = $connectorId;
		}

		$navParams = static::getNavData($n, true);

		$getEventQuery
			->setSelect(['ID', 'TIMESTAMP_X', 'EVENT_NAME', 'EVENT_DATA', 'EVENT_ADDITIONAL', 'MESSAGE_ID', 'PROCESS_ID', 'ERROR'])
			->setFilter($queryFilter)
			->setOrder($order)
			->setLimit($navParams['limit'])
			->setOffset($navParams['offset']);

		$result = array();
		$dbRes = $getEventQuery->exec();

		while($event = $dbRes->fetch())
		{
			/** @var DateTime $ts */
			$ts = $event['TIMESTAMP_X'];

			$event['TIMESTAMP_X'] = \CRestUtil::convertDateTime($ts->toString());

			if (isset($event['EVENT_ADDITIONAL'][Auth::PARAM_LOCAL_USER]))
			{
				$event['EVENT_ADDITIONAL'] = [
					'user_id' => $event['EVENT_ADDITIONAL'][Auth::PARAM_LOCAL_USER],
				];
			}

			$result[] = $event;
		}

		return static::setNavData($result, array(
			"count" => $getEventQuery->queryCountTotal(),
			"offset" => $navParams['offset']
		));
	}

	protected static function sanitizeFilter($filter, array $availableFields = null, $valueCallback = null, array $availableOperations = null)
	{
		static $defaultFields = array('ID', 'TIMESTAMP_X', 'EVENT_NAME', 'MESSAGE_ID');

		if($availableFields === null)
		{
			$availableFields = $defaultFields;
		}

		return parent::sanitizeFilter(
			$filter,
			$availableFields,
			function($field, $value)
			{
				switch($field)
				{
					case 'TIMESTAMP_X':

						return DateTime::createFromUserTime(\CRestUtil::unConvertDateTime($value));

					break;
				}
				return $value;
			}
		);
	}

	protected static function sanitizeOrder($order, array $availableFields = null)
	{
		static $defaultFields = array('ID', 'TIMESTAMP_X', 'EVENT_NAME', 'MESSAGE_ID');

		if($availableFields === null)
		{
			$availableFields = $defaultFields;
		}

		return parent::sanitizeOrder($order, $availableFields);
	}

	protected static function isExtendedModeEnabled()
	{
		return !Loader::includeModule('bitrix24')
			|| Feature::isFeatureEnabled(static::FEATURE_EXTENDED_MODE);
	}
}