%PDF- %PDF- 403WebShell
403Webshell
Server IP : 37.220.80.31  /  Your IP : 52.15.136.88
Web Server : Apache/2.4.52 (Ubuntu)
System : Linux 3051455-guretool.twc1.net 5.15.0-107-generic #117-Ubuntu SMP Fri Apr 26 12:26:49 UTC 2024 x86_64
User : www-root ( 1010)
PHP Version : 7.4.33
Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,
MySQL : OFF  |  cURL : ON  |  WGET : OFF  |  Perl : OFF  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /var/www/www-root/data/www/dev.artlot24.ru/bitrix/modules/catalog/general/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/www-root/data/www/dev.artlot24.ru/bitrix/modules/catalog/general/querybuilder.php
<?php

use Bitrix\Main;
use Bitrix\Catalog\GroupAccessTable;
use Bitrix\Catalog\ProductTable;
use Bitrix\Currency;

final class CProductQueryBuilder
{
	public const ENTITY_PRODUCT = 'PRODUCT';
	public const ENTITY_PRICE = 'PRICE';
	public const ENTITY_WARENHOUSE = 'WARENHOUSE';
	public const ENTITY_FLAT_PRICE = 'FLAT_PRICES';
	public const ENTITY_FLAT_WAREHNOUSE = 'FLAT_WARENHOUSES';
	public const ENTITY_FLAT_BARCODE = 'FLAT_BARCODE';
	public const ENTITY_OLD_PRODUCT = 'OLD_PRODUCT';
	public const ENTITY_OLD_PRICE = 'OLD_PRICE';
	public const ENTITY_OLD_STORE = 'OLD_STORE';
	private const ENTITY_CATALOG_IBLOCK = 'CATALOG_IBLOCK';
	private const ENTITY_VAT = 'VAT';

	private const FIELD_ALLOWED_SELECT = 0x0001;
	private const FIELD_ALLOWED_FILTER = 0x0002;
	private const FIELD_ALLOWED_ORDER = 0x0004;
	private const FIELD_ALLOWED_ALL = self::FIELD_ALLOWED_SELECT|self::FIELD_ALLOWED_FILTER|self::FIELD_ALLOWED_ORDER;

	private const FIELD_PATTERN_OLD_STORE = '/^CATALOG_STORE_AMOUNT_([0-9]+)$/';
	private const FIELD_PATTERN_OLD_PRICE_ROW = '/^CATALOG_GROUP_([0-9]+)$/';
	private const FIELD_PATTERN_OLD_PRICE = '/^CATALOG_([A-Z][A-Z_]+)+_([0-9]+)$/';
	private const FIELD_PATTERN_OLD_PRODUCT = '/^CATALOG_([A-Z][A-Z_]+)$/';
	private const FIELD_PATTERN_FLAT_ENTITY = '/^([A-Z][A-Z_]+)$/';
	private const FIELD_PATTERN_SEPARATE_ENTITY = '/^([A-Z][A-Z_]+)_([1-9][0-9]*)$/';

	private const ENTITY_TYPE_FLAT = 0x0001;
	private const ENTITY_TYPE_SEPARATE = 0x0002;

	private static $entityDescription = [];

	private static $entityFields = [];

	private static $options = [];

	/**
	 * @param array $filter
	 * @param array $options
	 * @return null|array
	 */
	public static function makeFilter(array $filter, array $options = []): ?array
	{
		$query = self::prepareQuery(['filter' => $filter], $options);
		if ($query === null)
		{
			return null;
		}
		if (empty($query['filter']) && empty($query['join']))
		{
			return null;
		}

		return $query;
	}

	/**
	 * @param array $parameters
	 * @param array $options
	 * @return array|null
	 */
	public static function makeQuery(array $parameters, array $options = []): ?array
	{
		$query = self::prepareQuery($parameters, $options);
		if ($query === null)
		{
			return null;
		}
		if (empty($query['select']) && empty($query['filter']) && empty($query['order']))
		{
			return null;
		}

		return $query;
	}

	/**
	 * @param string $field
	 * @return bool
	 */
	public static function isValidField(string $field): bool
	{
		$field = strtoupper($field);
		if ($field === '')
		{
			return false;
		}

		self::initEntityDescription();
		self::initEntityFields();

		$prepared = [];

		if (preg_match(self::FIELD_PATTERN_OLD_STORE, $field, $prepared))
		{
			return true;
		}
		if (preg_match(self::FIELD_PATTERN_OLD_PRICE_ROW, $field, $prepared))
		{
			return true;
		}
		if (preg_match(self::FIELD_PATTERN_OLD_PRICE, $field, $prepared))
		{
			return true;
		}
		if (preg_match(self::FIELD_PATTERN_OLD_PRODUCT, $field, $prepared))
		{
			return true;
		}
		if (preg_match(self::FIELD_PATTERN_SEPARATE_ENTITY, $field, $prepared))
		{
			if (self::searchFieldEntity($prepared[1], self::ENTITY_TYPE_SEPARATE))
			{
				return true;
			}
		}
		if (preg_match(self::FIELD_PATTERN_FLAT_ENTITY, $field, $prepared))
		{
			if (self::searchFieldEntity($prepared[1], self::ENTITY_TYPE_FLAT))
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * @param string $field
	 * @return bool
	 */
	public static function isRealFilterField(string $field): bool
	{
		self::initEntityDescription();
		self::initEntityFields();

		$prepareField = self::parseField($field);
		if (!self::checkPreparedField($prepareField))
		{
			return false;
		}
		if (!self::checkAllowedAction($prepareField['ALLOWED'], self::FIELD_ALLOWED_FILTER))
		{
			return false;
		}

		$description = self::getFieldDescription($prepareField['ENTITY'], $prepareField['FIELD']);
		if (empty($description))
		{
			return false;
		}

		if (isset($description['PHANTOM']))
		{
			return false;
		}

		return true;
	}

	/**
	 * @param string $field
	 * @return bool
	 */
	public static function isCatalogFilterField(string $field): bool
	{
		return self::isEntityFilterField(
			$field,
			[
				self::ENTITY_PRODUCT => true,
				self::ENTITY_OLD_PRODUCT => true,
				self::ENTITY_OLD_PRICE => true,
				self::ENTITY_PRICE => true,
				self::ENTITY_FLAT_PRICE => true,
				self::ENTITY_WARENHOUSE => true,
				self::ENTITY_FLAT_WAREHNOUSE => true,
				self::ENTITY_FLAT_BARCODE => true,
				self::ENTITY_OLD_STORE => true,
			]
		);
	}

	/**
	 * @param string $field
	 * @return bool
	 */
	public static function isProductFilterField(string $field): bool
	{
		return self::isEntityFilterField(
			$field,
			[
				self::ENTITY_PRODUCT => true,
				self::ENTITY_OLD_PRODUCT => true,
			]
		);
	}

	/**
	 * @param string $field
	 * @return bool
	 */
	public static function isPriceFilterField(string $field): bool
	{
		return self::isEntityFilterField(
			$field,
			[
				self::ENTITY_OLD_PRICE => true,
				self::ENTITY_PRICE => true,
				self::ENTITY_FLAT_PRICE => true,
			]
		);
	}

	/**
	 * @param string $field
	 * @return bool
	 */
	public static function isWarenhouseFilterField(string $field): bool
	{
		return self::isEntityFilterField(
			$field,
			[
				self::ENTITY_WARENHOUSE => true,
				self::ENTITY_FLAT_WAREHNOUSE => true,
				self::ENTITY_OLD_STORE => true,
			]
		);
	}

	/**
	 * @param array $filter
	 * @param array $order
	 * @param array $options
	 * @return array
	 */
	public static function modifyFilterFromOrder(array $filter, array $order, array $options): array
	{
		$result = $filter;

		self::initEntityDescription();
		self::initEntityFields();

		if (empty($order) || empty($options['QUANTITY']))
		{
			return $result;
		}

		foreach (array_keys($order) as $field)
		{
			$prepareField = self::parseField($field);
			if (!self::checkPreparedField($prepareField))
			{
				continue;
			}
			switch ($prepareField['ENTITY'])
			{
				case self::ENTITY_OLD_PRICE:
					if (
						$prepareField['FIELD'] === 'PRICE'
						|| $prepareField['FIELD'] === 'PRICE_SCALE'
						|| $prepareField['FIELD'] === 'CURRENCY'
					)
					{
						$filterFieldDescription = [
							'ENTITY' => $prepareField['ENTITY'],
							'FIELD' => 'SHOP_QUANTITY',
							'ENTITY_ID' => $prepareField['ENTITY_ID'],
						];
						$filterField = self::getField($filterFieldDescription, []);
						if (!empty($filterField))
						{
							$filterField = $filterField['ALIAS'];
							if (!isset($result[$filterField]))
							{
								$result[$filterField] = $options['QUANTITY'];
							}
						}
						unset($filterField, $filterFieldDescription);
					}
					break;
				case self::ENTITY_PRICE:
				case self::ENTITY_FLAT_PRICE:
					if (
						$prepareField['FIELD'] === 'PRICE'
						|| $prepareField['FIELD'] === 'SCALED_PRICE'
						|| $prepareField['FIELD'] === 'CURRENCY'
					)
					{
						$filterFieldDescription = [
							'ENTITY' => $prepareField['ENTITY'],
							'FIELD' => 'QUANTITY_RANGE_FILTER',
							'ENTITY_ID' => $prepareField['ENTITY_ID'],
						];
						$filterField = self::getField($filterFieldDescription, []);
						if (!empty($filterField))
						{
							$filterField = $filterField['ALIAS'];
							if (!isset($result[$filterField]))
							{
								$result[$filterField] = $options['QUANTITY'];
							}
						}
						unset($filterField, $filterFieldDescription);
					}
					break;
			}
		}

		return $result;
	}

	/**
	 * @param string $field
	 * @param int $useMode
	 * @return string|null
	 */
	public static function convertOldField(string $field, int $useMode): ?string
	{
		self::initEntityDescription();
		self::initEntityFields();

		$prepareField = self::parseField($field);
		if (!self::checkPreparedField($prepareField))
		{
			return null;
		}
		if (!self::checkAllowedAction($prepareField['ALLOWED'], $useMode))
		{
			return null;
		}

		$newEntity = null;
		switch ($prepareField['ENTITY'])
		{
			case self::ENTITY_OLD_PRODUCT:
				$newEntity = self::ENTITY_PRODUCT;
				break;
			case self::ENTITY_OLD_PRICE:
				$newEntity = self::ENTITY_PRICE;
				break;
			case self::ENTITY_OLD_STORE:
				$newEntity = self::ENTITY_WARENHOUSE;
				break;
		}
		if ($newEntity === null)
		{
			return null;
		}

		$description = self::getFieldDescription($prepareField['ENTITY'], $prepareField['FIELD']);
		if (empty($description))
		{
			return null;
		}

		if ($useMode === self::FIELD_ALLOWED_ORDER)
		{
			if (isset($description['ORDER_TRANSFORM']))
			{
				$description = self::getFieldDescription($prepareField['ENTITY'], $description['ORDER_TRANSFORM']);
				if (empty($description))
				{
					return null;
				}
			}
		}

		$newField = [
			'ENTITY' => $newEntity,
			'ENTITY_ID' => $prepareField['ENTITY_ID'],
			'FIELD' => $description['NEW_ID'] ?? $prepareField['FIELD'],
		];
		unset($newEntity, $prepareField);

		$description = self::getFieldDescription($newField['ENTITY'], $newField['FIELD']);
		if (empty($description))
		{
			return null;
		}

		return str_replace('#ENTITY_ID#', $newField['ENTITY_ID'], $description['ALIAS']);
	}

	/**
	 * @param array $select
	 * @return array
	 */
	public static function convertOldSelect(array $select): array
	{
		$result = [];

		if (empty($select))
		{
			return $result;
		}

		foreach ($select as $index => $field)
		{
			$newField = self::convertOldField($field, self::FIELD_ALLOWED_SELECT);
			$result[$index] = $newField === null ? $field : $newField;
		}
		unset($newField, $index, $field);

		return $result;
	}

	/**
	 * @param array $filter
	 * @return array
	 */
	public static function convertOldFilter(array $filter): array
	{
		$result = [];

		if (empty($filter))
			return $result;

		foreach ($filter as $field => $value)
		{
			if (is_object($value))
			{
				$result[$field] = $value;
			}
			elseif (is_numeric($field))
			{
				if (is_array($value))
					$result[$field] = self::convertOldFilter($value);
				else
					$result[$field] = $value;
			}
			else
			{
				$filterItem = \CIBlock::MkOperationFilter($field);
				$newField = self::convertOldField($filterItem['FIELD'], self::FIELD_ALLOWED_FILTER);
				if ($newField !== null)
					$result[$filterItem['PREFIX'].$newField] = $value;
				else
					$result[$field] = $value;
				unset($newField, $filterItem);
			}
		}
		unset($filed, $value);

		return $result;
	}

	/**
	 * @param array $order
	 * @return array
	 */
	public static function convertOldOrder(array $order): array
	{
		$result = [];

		if (empty($order))
			return $result;

		foreach ($order as $field => $direction)
		{
			$newField = self::convertOldField($field, self::FIELD_ALLOWED_ORDER);
			if ($newField === null)
				$result[$field] = $direction;
			else
				$result[$newField] = $direction;
		}
		unset($newField, $field, $direction);

		return $result;
	}

	/**
	 * @param string $entity
	 * @param array $options
	 * @return array|null
	 */
	public static function getEntityFieldAliases(string $entity, array $options = []): ?array
	{
		if ($entity === '')
		{
			return null;
		}

		$entity = strtoupper($entity);

		self::initEntityDescription();
		self::initEntityFields();

		if (!isset(self::$entityFields[$entity]))
		{
			return null;
		}

		$entityId = '';
		if (
			isset($options['ENTITY_ID'])
			&& (is_string($options['ENTITY_ID']) || is_int($options['ENTITY_ID']))
		)
		{
			$entityId = (string)$options['ENTITY_ID'];
		}

		$result = [];
		foreach (self::$entityFields[$entity] as $field)
		{
			$result[] = str_replace('#ENTITY_ID#', $entityId, $field['ALIAS']);
		}
		unset($field);

		return $result;
	}

	/**
	 * @return void
	 */
	private static function initEntityDescription()
	{
		if (!empty(self::$entityDescription))
			return;

		self::$entityDescription = [
			self::ENTITY_PRODUCT => [
				'NAME' => 'b_catalog_product',
				'ALIAS' => 'PRD',
				'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.ID = #ELEMENT#.ID)'
			],
			self::ENTITY_PRICE => [
				'NAME' => 'b_catalog_price',
				'ALIAS' => 'PRC_#ENTITY_ID#',
				'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.PRODUCT_ID = #ELEMENT#.ID and #ALIAS#.CATALOG_GROUP_ID = #ENTITY_ID##JOIN_MODIFY#)'
			],
			self::ENTITY_WARENHOUSE => [
				'NAME' => 'b_catalog_store_product',
				'ALIAS' => 'WHS_#ENTITY_ID#',
				'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.PRODUCT_ID = #ELEMENT#.ID and #ALIAS#.STORE_ID = #ENTITY_ID#)'
			],
			self::ENTITY_FLAT_PRICE => [
				'NAME' => 'b_catalog_price',
				'ALIAS' => 'PRC_FT',
				'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.PRODUCT_ID = #ELEMENT#.ID#JOIN_MODIFY#)'
			],
			self::ENTITY_FLAT_WAREHNOUSE => [
				'NAME' => 'b_catalog_store_product',
				'ALIAS' => 'WHS_FT',
				'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.PRODUCT_ID = #ELEMENT#.ID)'
			],
			self::ENTITY_FLAT_BARCODE => [
				'NAME' => 'b_catalog_store_barcode',
				'ALIAS' => 'BRC_FT',
				'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.PRODUCT_ID = #ELEMENT#.ID)'
			],
			self::ENTITY_OLD_PRODUCT => [
				'NAME' => 'b_catalog_product',
				'ALIAS' => 'CAT_PR',
				'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.ID = #ELEMENT#.ID)'
			],
			self::ENTITY_OLD_PRICE => [
				'NAME' => 'b_catalog_price',
				'ALIAS' => 'CAT_P#ENTITY_ID#',
				'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.PRODUCT_ID = #ELEMENT#.ID and #ALIAS#.CATALOG_GROUP_ID = #ENTITY_ID##JOIN_MODIFY#)'
			],
			self::ENTITY_OLD_STORE => [
				'NAME' => 'b_catalog_store_product',
				'ALIAS' => 'CAT_SP#ENTITY_ID#',
				'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.PRODUCT_ID = #ELEMENT#.ID and #ALIAS#.STORE_ID = #ENTITY_ID#)'
			],
			self::ENTITY_CATALOG_IBLOCK => [
				'NAME' => 'b_catalog_iblock',
				'ALIAS' => 'CAT_IB',
				'JOIN' => 'left join #NAME# as #ALIAS# on ((CAT_PR.VAT_ID IS NULL or CAT_PR.VAT_ID = 0) and #ALIAS#.IBLOCK_ID = #ELEMENT#.IBLOCK_ID)'
			],
			self::ENTITY_VAT => [
				'NAME' => 'b_catalog_vat',
				'ALIAS' => 'CAT_VAT',
				'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.ID = IF((CAT_PR.VAT_ID IS NULL OR CAT_PR.VAT_ID = 0), CAT_IB.VAT_ID, CAT_PR.VAT_ID))',
				'RELATION' => [self::ENTITY_CATALOG_IBLOCK]
			]
		];
	}

	/**
	 * @return void
	 */
	private static function initEntityFields()
	{
		if (!empty(self::$entityFields))
			return;

		self::$entityFields = [
			self::ENTITY_PRODUCT => [
				'TYPE' => [
					'NAME' => 'TYPE',
					'ALIAS' => 'TYPE',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'AVAILABLE' => [
					'NAME' => 'AVAILABLE',
					'ALIAS' => 'AVAILABLE',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL,
					'ORDER_DEFAULT' => 'DESC'
				],
				'BUNDLE' => [
					'NAME' => 'BUNDLE',
					'ALIAS' => 'BUNDLE',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'QUANTITY' => [
					'NAME' => 'QUANTITY',
					'ALIAS' => 'QUANTITY',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'QUANTITY_RESERVED' => [
					'NAME' => 'QUANTITY_RESERVED',
					'ALIAS' => 'QUANTITY_RESERVED',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'QUANTITY_TRACE' => [
					'NAME' => 'QUANTITY_TRACE',
					'ALIAS' => 'QUANTITY_TRACE',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL,
					'SELECT_EXPRESSION' => [__CLASS__, 'selectQuantityTrace'],
					'FILTER_PREPARE_VALUE_EXPRESSION' => [__CLASS__, 'prepareFilterQuantityTrace']
				],
				'QUANTITY_TRACE_RAW' => [
					'NAME' => 'QUANTITY_TRACE',
					'ALIAS' => 'QUANTITY_TRACE_RAW',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL,
				],
				'CAN_BUY_ZERO' => [
					'NAME' => 'CAN_BUY_ZERO',
					'ALIAS' => 'CAN_BUY_ZERO',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL,
					'SELECT_EXPRESSION' => [__CLASS__, 'selectCanBuyZero'],
					'FILTER_PREPARE_VALUE_EXPRESSION' => [__CLASS__, 'prepareFilterCanBuyZero']
				],
				'CAN_BUY_ZERO_RAW' => [
					'NAME' => 'CAN_BUY_ZERO',
					'ALIAS' => 'CAN_BUY_ZERO_RAW',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL,
				],
				'SUBSCRIBE' => [
					'NAME' => 'SUBSCRIBE',
					'ALIAS' => 'SUBSCRIBE',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL,
					'SELECT_EXPRESSION' => [__CLASS__, 'selectSubscribe'],
					'FILTER_PREPARE_VALUE_EXPRESSION' => [__CLASS__, 'prepareFilterSubscribe']
				],
				'SUBSCRIBE_RAW' => [
					'NAME' => 'SUBSCRIBE',
					'ALIAS' => 'SUBSCRIBE_RAW',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'VAT_ID' => [
					'NAME' => 'VAT_ID',
					'ALIAS' => 'VAT_ID',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'VAT_INCLUDED' => [
					'NAME' => 'VAT_INCLUDED',
					'ALIAS' => 'VAT_INCLUDED',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'PURCHASING_PRICE' => [
					'NAME' => 'PURCHASING_PRICE',
					'ALIAS' => 'PURCHASING_PRICE',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'PURCHASING_CURRENCY' => [
					'NAME' => 'PURCHASING_CURRENCY',
					'ALIAS' => 'PURCHASING_CURRENCY',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'BARCODE_MULTI' => [
					'NAME' => 'BARCODE_MULTI',
					'ALIAS' => 'BARCODE_MULTI',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'WEIGHT' => [
					'NAME' => 'WEIGHT',
					'ALIAS' => 'WEIGHT',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'WIDTH' => [
					'NAME' => 'WIDTH',
					'ALIAS' => 'WIDTH',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'LENGTH' => [
					'NAME' => 'LENGTH',
					'ALIAS' => 'LENGTH',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'HEIGHT' => [
					'NAME' => 'HEIGHT',
					'ALIAS' => 'HEIGHT',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'MEASURE' => [
					'NAME' => 'MEASURE',
					'ALIAS' => 'MEASURE',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'PAYMENT_TYPE' => [
					'NAME' => 'PRICE_TYPE',
					'ALIAS' => 'PAYMENT_TYPE',
					'TYPE' => '',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'RECUR_SCHEME_LENGTH' => [
					'NAME' => 'RECUR_SCHEME_LENGTH',
					'ALIAS' => 'RECUR_SCHEME_LENGTH',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'RECUR_SCHEME_TYPE' => [
					'NAME' => 'RECUR_SCHEME_TYPE',
					'ALIAS' => 'RECUR_SCHEME_TYPE',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'TRIAL_PRICE_ID' => [
					'NAME' => 'TRIAL_PRICE_ID',
					'ALIAS' => 'TRIAL_PRICE_ID',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'WITHOUT_ORDER' => [
					'NAME' => 'WITHOUT_ORDER',
					'ALIAS' => 'WITHOUT_ORDER',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				]
			],
			self::ENTITY_PRICE => [
				'PRICE' => [
					'NAME' => 'PRICE',
					'ALIAS' => 'PRICE_#ENTITY_ID#',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_ALL,
					'ORDER_NULLABLE' => true,
				],
				'CURRENCY' => [
					'NAME' => 'CURRENCY',
					'ALIAS' => 'CURRENCY_#ENTITY_ID#',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL,
					'ORDER_NULLABLE' => true,
				],
				'QUANTITY_FROM' => [
					'NAME' => 'QUANTITY_FROM',
					'ALIAS' => 'QUANTITY_FROM_#ENTITY_ID#',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_ALL,
					'ORDER_NULLABLE' => true,
				],
				'QUANTITY_TO' => [
					'NAME' => 'QUANTITY_TO',
					'ALIAS' => 'QUANTITY_TO_#ENTITY_ID#',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_ALL,
					'ORDER_NULLABLE' => true,
				],
				'SCALED_PRICE' => [
					'NAME' => 'PRICE_SCALE',
					'ALIAS' => 'SCALED_PRICE_#ENTITY_ID#',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_ALL,
					'ORDER_NULLABLE' => true,
				],
				'EXTRA_ID' => [
					'NAME' => 'EXTRA_ID',
					'ALIAS' => 'EXTRA_ID_#ENTITY_ID#',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'DEFAULT_PRICE_FILTER' => [
					'PHANTOM' => true,
					'NAME' => null,
					'ALIAS' => 'DEFAULT_PRICE_FILTER_#ENTITY_ID#',
					'TYPE' => null,
					'ALLOWED' => self::FIELD_ALLOWED_FILTER,
					'JOIN_MODIFY_EXPRESSION' => [__CLASS__, 'priceParametersFilter']
				],
				'QUANTITY_RANGE_FILTER' => [
					'PHANTOM' => true,
					'NAME' => null,
					'ALIAS' => 'QUANTITY_RANGE_FILTER_#ENTITY_ID#',
					'TYPE' => null,
					'ALLOWED' => self::FIELD_ALLOWED_FILTER,
					'JOIN_MODIFY_EXPRESSION' => [__CLASS__, 'priceParametersFilter']
				],
				'CURRENCY_FOR_SCALE' => [
					'PHANTOM' => true,
					'NAME' => null,
					'ALIAS' => 'CURRENCY_FOR_SCALE_#ENTITY_ID#',
					'TYPE' => null,
					'ALLOWED' => self::FIELD_ALLOWED_FILTER,
					'FILTER_MODIFY_EXPRESSION' => [__CLASS__, 'filterModifierCurrencyScale']
				]
			],
			self::ENTITY_WARENHOUSE => [
				'STORE_AMOUNT' => [
					'NAME' => 'AMOUNT',
					'ALIAS' => 'STORE_AMOUNT_#ENTITY_ID#',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_ALL,
					'ORDER_NULLABLE' => true,
				]
			],
			self::ENTITY_FLAT_PRICE => [
				'PRICE_TYPE' => [
					'NAME' => 'CATALOG_GROUP_ID',
					'ALIAS' => 'PRICE_TYPE',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_FILTER
				],
				'PRICE' => [
					'NAME' => 'PRICE',
					'ALIAS' => 'PRICE',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_FILTER,
					'ORDER_NULLABLE' => true,
				],
				'CURRENCY' => [
					'NAME' => 'CURRENCY',
					'ALIAS' => 'CURRENCY',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_FILTER,
					'ORDER_NULLABLE' => true,
				],
				'QUANTITY_FROM' => [
					'NAME' => 'QUANTITY_FROM',
					'ALIAS' => 'QUANTITY_FROM',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_FILTER,
					'ORDER_NULLABLE' => true,
				],
				'QUANTITY_TO' => [
					'NAME' => 'QUANTITY_TO',
					'ALIAS' => 'QUANTITY_TO',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_FILTER,
					'ORDER_NULLABLE' => true,
				],
				'SCALED_PRICE' => [
					'NAME' => 'PRICE_SCALE',
					'ALIAS' => 'SCALED_PRICE',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_FILTER,
					'ORDER_NULLABLE' => true,
				],
				'EXTRA_ID' => [
					'NAME' => 'EXTRA_ID',
					'ALIAS' => 'EXTRA_ID',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_FILTER
				],
				'DEFAULT_PRICE_FILTER' => [
					'PHANTOM' => true,
					'NAME' => null,
					'ALIAS' => 'DEFAULT_PRICE_FILTER',
					'TYPE' => null,
					'ALLOWED' => self::FIELD_ALLOWED_FILTER,
					'JOIN_MODIFY_EXPRESSION' => [__CLASS__, 'priceParametersFilter']
				],
				'QUANTITY_RANGE_FILTER' => [
					'PHANTOM' => true,
					'NAME' => null,
					'ALIAS' => 'QUANTITY_RANGE_FILTER',
					'TYPE' => null,
					'ALLOWED' => self::FIELD_ALLOWED_FILTER,
					'JOIN_MODIFY_EXPRESSION' => [__CLASS__, 'priceParametersFilter']
				],
				'CURRENCY_FOR_SCALE' => [
					'PHANTOM' => true,
					'NAME' => null,
					'ALIAS' => 'CURRENCY_FOR_SCALE',
					'TYPE' => null,
					'ALLOWED' => self::FIELD_ALLOWED_FILTER,
					'FILTER_MODIFY_EXPRESSION' => [__CLASS__, 'filterModifierCurrencyScale']
				]
			],
			self::ENTITY_FLAT_WAREHNOUSE => [
				'STORE_NUMBER' => [
					'NAME' => 'STORE_ID',
					'ALIAS' => 'STORE_NUMBER',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_FILTER
				],
				'STORE_AMOUNT' => [
					'NAME' => 'AMOUNT',
					'ALIAS' => 'STORE_AMOUNT',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_FILTER,
					'ORDER_NULLABLE' => true,
				]
			],
			self::ENTITY_FLAT_BARCODE => [
				'PRODUCT_BARCODE' => [
					'NAME' => 'BARCODE',
					'ALIAS' => 'PRODUCT_BARCODE',
					'TYPE' => 'string',
					'ALLOWED' => self::FIELD_ALLOWED_FILTER
				],
				'PRODUCT_BARCODE_STORE' => [
					'NAME' => 'STORE_ID',
					'ALIAS' => 'PRODUCT_BARCODE_STORE',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_FILTER
				],
				'PRODUCT_BARCODE_ORDER' => [
					'NAME' => 'ORDER_ID',
					'ALIAS' => 'PRODUCT_BARCODE_ORDER',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_FILTER
				]
			],
			self::ENTITY_OLD_PRODUCT => [
				'QUANTITY' => [
					'NAME' => 'QUANTITY',
					'ALIAS' => 'CATALOG_QUANTITY',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'QUANTITY_TRACE' => [
					'NAME' => 'QUANTITY_TRACE',
					'ALIAS' => 'CATALOG_QUANTITY_TRACE',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT,
					'SELECT_EXPRESSION' => [__CLASS__, 'selectQuantityTrace']
				],
				'QUANTITY_TRACE_ORIG' => [
					'NAME' => 'QUANTITY_TRACE',
					'ALIAS' => 'CATALOG_QUANTITY_TRACE_ORIG',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT,
					'NEW_ID' => 'QUANTITY_TRACE_RAW'
				],
				'WEIGHT' => [
					'NAME' => 'WEIGHT',
					'ALIAS' => 'CATALOG_WEIGHT',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'VAT_ID' => [
					'NAME' => 'VAT_ID',
					'ALIAS' => 'CATALOG_VAT_ID',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				],
				'VAT_INCLUDED' => [
					'NAME' => 'VAT_INCLUDED',
					'ALIAS' => 'CATALOG_VAT_INCLUDED',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				],
				'CAN_BUY_ZERO' => [
					'NAME' => 'CAN_BUY_ZERO',
					'ALIAS' => 'CATALOG_CAN_BUY_ZERO',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT,
					'SELECT_EXPRESSION' => [__CLASS__, 'selectCanBuyZero']
				],
				'CAN_BUY_ZERO_ORIG' => [
					'NAME' => 'CAN_BUY_ZERO',
					'ALIAS' => 'CATALOG_CAN_BUY_ZERO_ORIG',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT,
					'NEW_ID' => 'CAN_BUY_ZERO_RAW'
				],
				'PURCHASING_PRICE' => [
					'NAME' => 'PURCHASING_PRICE',
					'ALIAS' => 'CATALOG_PURCHASING_PRICE',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'PURCHASING_CURRENCY' => [
					'NAME' => 'PURCHASING_CURRENCY',
					'ALIAS' => 'CATALOG_PURCHASING_CURRENCY',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'QUANTITY_RESERVED' => [
					'NAME' => 'QUANTITY_RESERVED',
					'ALIAS' => 'CATALOG_QUANTITY_RESERVED',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				],
				'SUBSCRIBE' => [
					'NAME' => 'SUBSCRIBE',
					'ALIAS' => 'CATALOG_SUBSCRIBE',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT|self::FIELD_ALLOWED_FILTER,
					'SELECT_EXPRESSION' => [__CLASS__, 'selectSubscribe'],
					'FILTER_PREPARE_VALUE_EXPRESSION' => [__CLASS__, 'prepareFilterSubscribe']
				],
				'SUBSCRIBE_ORIG' => [
					'NAME' => 'SUBSCRIBE',
					'ALIAS' => 'CATALOG_SUBSCRIBE_ORIG',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT,
					'NEW_ID' => 'SUBSCRIBE_RAW'
				],
				'WIDTH' => [
					'NAME' => 'WIDTH',
					'ALIAS' => 'CATALOG_WIDTH',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				],
				'LENGTH' => [
					'NAME' => 'LENGTH',
					'ALIAS' => 'CATALOG_LENGTH',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				],
				'HEIGHT' => [
					'NAME' => 'HEIGHT',
					'ALIAS' => 'CATALOG_HEIGHT',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				],
				'MEASURE' => [
					'NAME' => 'MEASURE',
					'ALIAS' => 'CATALOG_MEASURE',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				],
				'TYPE' => [
					'NAME' => 'TYPE',
					'ALIAS' => 'CATALOG_TYPE',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'AVAILABLE' => [
					'NAME' => 'AVAILABLE',
					'ALIAS' => 'CATALOG_AVAILABLE',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL,
					'ORDER_DEFAULT' => 'DESC'
				],
				'BUNDLE' => [
					'NAME' => 'BUNDLE',
					'ALIAS' => 'CATALOG_BUNDLE',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL
				],
				'PRICE_TYPE' => [
					'NAME' => 'PRICE_TYPE',
					'ALIAS' => 'CATALOG_PRICE_TYPE',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT,
					'NEW_ID' => 'PAYMENT_TYPE'
				],
				'RECUR_SCHEME_LENGTH' => [
					'NAME' => 'RECUR_SCHEME_LENGTH',
					'ALIAS' => 'CATALOG_RECUR_SCHEME_LENGTH',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				],
				'RECUR_SCHEME_TYPE' => [
					'NAME' => 'RECUR_SCHEME_TYPE',
					'ALIAS' => 'CATALOG_RECUR_SCHEME_TYPE',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				],
				'TRIAL_PRICE_ID' => [
					'NAME' => 'TRIAL_PRICE_ID',
					'ALIAS' => 'CATALOG_TRIAL_PRICE_ID',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				],
				'WITHOUT_ORDER' => [
					'NAME' => 'WITHOUT_ORDER',
					'ALIAS' => 'CATALOG_WITHOUT_ORDER',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				],
				'SELECT_BEST_PRICE' => [
					'NAME' => 'SELECT_BEST_PRICE',
					'ALIAS' => 'CATALOG_SELECT_BEST_PRICE',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				],
				'NEGATIVE_AMOUNT_TRACE' => [
					'NAME' => 'NEGATIVE_AMOUNT_TRACE',
					'ALIAS' => 'CATALOG_NEGATIVE_AMOUNT_TRACE',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT,
					'SELECT_EXPRESSION' => [__CLASS__, 'selectNegativeAmountTrace']
				],
				'NEGATIVE_AMOUNT_TRACE_ORIG' => [
					'NAME' => 'NEGATIVE_AMOUNT_TRACE',
					'ALIAS' => 'CATALOG_NEGATIVE_AMOUNT_TRACE_ORIG',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				]
			],
			self::ENTITY_OLD_PRICE => [
				'ID' => [
					'NAME' => 'ID',
					'ALIAS' => 'CATALOG_PRICE_ID_#ENTITY_ID#',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				],
				'PRODUCT_ID' => [
					'NAME' => 'PRODUCT_ID',
					'ALIAS' => null,
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_FILTER
				],
				'CATALOG_GROUP_ID' => [
					'NAME' => 'CATALOG_GROUP_ID',
					'ALIAS' => 'CATALOG_GROUP_ID_#ENTITY_ID#',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT|self::FIELD_ALLOWED_FILTER
				],
				'PRICE' => [
					'NAME' => 'PRICE',
					'ALIAS' => 'CATALOG_PRICE_#ENTITY_ID#',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_ALL,
					'ORDER_TRANSFORM' => 'PRICE_SCALE'
				],
				'CURRENCY' => [
					'NAME' => 'CURRENCY',
					'ALIAS' => 'CATALOG_CURRENCY_#ENTITY_ID#',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_ALL,
					'ORDER_NULLABLE' => true,
				],
				'PRICE_SCALE' => [
					'NAME' => 'PRICE_SCALE',
					'ALIAS' => null,
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_ORDER|self::FIELD_ALLOWED_FILTER,
					'ORDER_NULLABLE' => true,
					'NEW_ID' => 'SCALED_PRICE'
				],
				'QUANTITY_FROM' => [
					'NAME' => 'QUANTITY_FROM',
					'ALIAS' => 'CATALOG_QUANTITY_FROM_#ENTITY_ID#',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				],
				'QUANTITY_TO' => [
					'NAME' => 'QUANTITY_TO',
					'ALIAS' => 'CATALOG_QUANTITY_TO_#ENTITY_ID#',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				],
				'EXTRA_ID' => [
					'NAME' => 'EXTRA_ID',
					'ALIAS' => 'CATALOG_EXTRA_ID_#ENTITY_ID#',
					'TYPE' => 'int',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				],
				'CATALOG_GROUP_NAME' => [
					'NAME' => null,
					'ALIAS' => 'CATALOG_GROUP_NAME_#ENTITY_ID#',
					'TYPE' => 'string',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT,
					'SELECT_EXPRESSION' => [__CLASS__, 'selectPriceTypeName']
				],
				'CATALOG_CAN_ACCESS' => [
					'NAME' => null,
					'ALIAS' => 'CATALOG_CAN_ACCESS_#ENTITY_ID#',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT,
					'SELECT_EXPRESSION' => [__CLASS__, 'selectPriceTypeAllowedView']
				],
				'CATALOG_CAN_BUY' => [
					'NAME' => null,
					'ALIAS' => 'CATALOG_CAN_BUY_#ENTITY_ID#',
					'TYPE' => 'char',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT,
					'SELECT_EXPRESSION' => [__CLASS__, 'selectPriceTypeAllowedBuy']
				],
				'CURRENCY_SCALE' => [
					'PHANTOM' => true,
					'NAME' => null,
					'ALIAS' => 'CATALOG_CURRENCY_SCALE_#ENTITY_ID#',
					'TYPE' => null,
					'ALLOWED' => self::FIELD_ALLOWED_FILTER,
					'FILTER_MODIFY_EXPRESSION' => [__CLASS__, 'filterModifierCurrencyScale'],
					'NEW_ID' => 'CURRENCY_FOR_SCALE'
				],
				'SHOP_QUANTITY' => [
					'PHANTOM' => true,
					'NAME' => null,
					'ALIAS' => 'CATALOG_SHOP_QUANTITY_#ENTITY_ID#',
					'TYPE' => null,
					'ALLOWED' => self::FIELD_ALLOWED_FILTER,
					'JOIN_MODIFY_EXPRESSION' => [__CLASS__, 'priceParametersFilter'],
					'NEW_ID' => 'DEFAULT_PRICE_FILTER'
				]
			],
			self::ENTITY_OLD_STORE => [
				'STORE_AMOUNT' => [
					'NAME' => 'AMOUNT',
					'ALIAS' => 'CATALOG_STORE_AMOUNT_#ENTITY_ID#',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_ALL,
					'ORDER_NULLABLE' => true,
				]
			],
			self::ENTITY_VAT => [
				'RATE' => [
					'NAME' => 'RATE',
					'ALIAS' => 'CATALOG_VAT',
					'TYPE' => 'float',
					'ALLOWED' => self::FIELD_ALLOWED_SELECT
				]
			]
		];
	}

	/**
	 * @param string $entity
	 * @param string $field
	 * @return int
	 */
	private static function getFieldAllowed(string $entity, string $field): int
	{
		if (!isset(self::$entityFields[$entity][$field]))
			return 0;
		if (!isset(self::$entityFields[$entity][$field]['ALLOWED']))
			return 0;
		return (self::$entityFields[$entity][$field]['ALLOWED']);
	}

	/**
	 * @param string $entity
	 * @return array|null
	 */
	private static function getFieldsAllowedToSelect(string $entity): ?array
	{
		$filter = function($field)
		{
			return (isset($field['ALLOWED']) && ($field['ALLOWED'] & self::FIELD_ALLOWED_SELECT > 0));
		};

		$result = array_filter(self::$entityFields[$entity], $filter);
		unset($filter);
		return (!empty($result) ? array_keys($result) : null);
	}

	/**
	 * @param string $field
	 * @return array|null
	 */
	private static function parseField(string $field): ?array
	{
		if ($field === '')
			return null;

		$field = mb_strtoupper($field);

		$entity = '';
		$entityId = 0;
		$prepared = [];
		$allowed = 0;
		$compatible = false;
		$checked = false;

		if (preg_match(self::FIELD_PATTERN_OLD_STORE, $field, $prepared))
		{
			$compatible = true;
			$entity = self::ENTITY_OLD_STORE;
			$field = 'STORE_AMOUNT';
			$entityId = (int)$prepared[1];
			$checked = ($entityId > 0);
		}
		elseif (preg_match(self::FIELD_PATTERN_OLD_PRICE_ROW, $field, $prepared))
		{
			$compatible = true;
			$entity = self::ENTITY_OLD_PRICE;
			$field = 'ID';
			$entityId = (int)$prepared[1];
			$checked = ($entityId > 0);
		}
		elseif (preg_match(self::FIELD_PATTERN_OLD_PRICE, $field, $prepared))
		{
			$compatible = true;
			$entity = self::ENTITY_OLD_PRICE;
			$field = $prepared[1];
			$entityId = (int)$prepared[2];
			$checked = ($entityId > 0);
		}
		elseif (preg_match(self::FIELD_PATTERN_OLD_PRODUCT, $field, $prepared))
		{
			$compatible = true;
			$entity = self::ENTITY_OLD_PRODUCT;
			$field = $prepared[1];
			$entityId = 0;
			$checked = true;
		}
		elseif (preg_match(self::FIELD_PATTERN_SEPARATE_ENTITY, $field, $prepared))
		{
			$entity = self::searchFieldEntity($prepared[1], self::ENTITY_TYPE_SEPARATE);
			if (!empty($entity))
			{
				$field = $prepared[1];
				$entityId = (int)$prepared[2];
				$checked = ($entityId > 0);
			}
		}
		elseif (preg_match(self::FIELD_PATTERN_FLAT_ENTITY, $field, $prepared))
		{
			$entity = self::searchFieldEntity($prepared[1], self::ENTITY_TYPE_FLAT);
			if (!empty($entity))
			{
				$field = $prepared[1];
				$entityId = 0;
				$checked = true;
			}
		}

		if ($checked)
		{
			$allowed = self::getFieldAllowed($entity, $field);
			if (empty($allowed))
				$checked = false;
		}

		if (!$checked)
			return null;

		return [
			'ENTITY' => $entity,
			'FIELD' => $field,
			'ENTITY_ID' => $entityId,
			'ALLOWED' => $allowed,
			'COMPATIBLE' => $compatible
		];
	}

	/**
	 * @param string $field
	 * @param int $type
	 * @return null|string
	 */
	private static function searchFieldEntity(string $field, int $type): ?string
	{
		$result = null;

		switch ($type)
		{
			case self::ENTITY_TYPE_FLAT:
				if (isset(self::$entityFields[self::ENTITY_PRODUCT][$field]))
					$result = self::ENTITY_PRODUCT;
				elseif (isset(self::$entityFields[self::ENTITY_FLAT_PRICE][$field]))
					$result = self::ENTITY_FLAT_PRICE;
				elseif (isset(self::$entityFields[self::ENTITY_FLAT_WAREHNOUSE][$field]))
					$result = self::ENTITY_FLAT_WAREHNOUSE;
				break;
			case self::ENTITY_TYPE_SEPARATE:
				if (isset(self::$entityFields[self::ENTITY_WARENHOUSE][$field]))
					$result = self::ENTITY_WARENHOUSE;
				elseif (isset(self::$entityFields[self::ENTITY_PRICE][$field]))
					$result = self::ENTITY_PRICE;
				break;
		}

		return $result;
	}

	/**
	 * @param int $allowed
	 * @param int $action
	 * @return bool
	 */
	private static function checkAllowedAction(int $allowed, int $action): bool
	{
		return ($allowed & $action) > 0;
	}

	/**
	 * @param string $field
	 * @param array $entityList
	 * @return bool
	 */
	private static function isEntityFilterField(string $field, array $entityList): bool
	{
		$result = false;

		if (!is_string($field))
			return $result;
		if (is_numeric($field))
			return $result;

		self::initEntityDescription();
		self::initEntityFields();

		$filterItem = \CIBlock::MkOperationFilter($field);
		$prepareField = self::parseField($filterItem['FIELD']);
		if (
			self::checkPreparedField($prepareField)
			&& self::checkAllowedAction($prepareField['ALLOWED'], self::FIELD_ALLOWED_FILTER)
		)
		{
			if (isset($entityList[$prepareField['ENTITY']]))
				$result = true;
		}
		unset($prepareField, $filterItem);

		return $result;
	}

	/**
	 * @param array $field
	 * @return string
	 */
	private static function getEntityIndex(array $field): string
	{
		return $field['ENTITY'].':'.$field['ENTITY_ID'];
	}

	/**
	 * Returns entity data for sql query.
	 *
	 * @param array $entity
	 * @return array|null
	 */
	private static function getEntityDescription(array $entity): ?array
	{
		if (!isset(self::$entityDescription[$entity['ENTITY']]))
			return null;
		$row = self::$entityDescription[$entity['ENTITY']];
		$row['ALIAS'] = str_replace('#ENTITY_ID#', $entity['ENTITY_ID'], $row['ALIAS']);

		$joinTemplates = [
			'#NAME#' => $row['NAME'],
			'#ALIAS#' => $row['ALIAS'],
			'#ENTITY_ID#' => $entity['ENTITY_ID']
		];
		$additionalAliases = self::getOption('ALIASES');
		if (!empty($additionalAliases))
			$joinTemplates = $joinTemplates + $additionalAliases;
		unset($additionalAliases);
		$row['JOIN'] = str_replace(
			array_keys($joinTemplates),
			array_values($joinTemplates),
			$row['JOIN']
		);
		unset($joinTemplates);

		return $row;
	}

	/**
	 * @param array $field
	 * @return string
	 */
	private static function getFieldIndex(array $field): string
	{
		return $field['ENTITY'].':'.$field['ENTITY_ID'].':'.$field['FIELD'];
	}

	/**
	 * @param array $field
	 * @return bool
	 */
	private static function isPhantomField(array $field): bool
	{
		return isset($field['PHANTOM']);
	}

	/**
	 * @param string $entity
	 * @param string $field
	 * @return null|array
	 */
	private static function getFieldDescription(string $entity, string $field): ?array
	{
		if (empty(self::$entityFields[$entity][$field]))
			return null;
		return self::$entityFields[$entity][$field];
	}

	/**
	 * @param array $queryItem
	 * @param array $options
	 * @return null|array
	 */
	private static function getField(array $queryItem, array $options): ?array
	{
		$whiteList = [
			'ALIAS' => true,
			'SELECT' => true,
			'FILTER' => true,
			'ORDER' => true,
			'ENTITY_DESCRIPTION' => true
		];

		$field = self::getFieldDescription($queryItem['ENTITY'], $queryItem['FIELD']);
		if (empty($field))
			return null;

		$entity = self::getEntityDescription($queryItem);
		if (empty($entity))
			return null;

		$fantomField = self::isPhantomField($field);

		$field['ALIAS'] = str_replace('#ENTITY_ID#', $queryItem['ENTITY_ID'], $field['ALIAS']);
		if (!$fantomField)
		{
			$field['FULL_NAME'] = $entity['ALIAS'].'.'.$field['NAME'];
		}

		if (
			self::checkAllowedAction($field['ALLOWED'], self::FIELD_ALLOWED_SELECT)
			&& isset($options['select'])
		)
		{
			if (!$fantomField)
			{
				$field['SELECT'] = '#FULL_NAME#';
				if (isset($field['SELECT_EXPRESSION']) && is_callable($field['SELECT_EXPRESSION']))
				{
					call_user_func_array(
						$field['SELECT_EXPRESSION'],
						[&$queryItem, &$entity, &$field]
					);
				}
				$field['SELECT'] = str_replace('#FULL_NAME#', $field['FULL_NAME'], $field['SELECT']);
			}
		}

		if (
			self::checkAllowedAction($field['ALLOWED'], self::FIELD_ALLOWED_FILTER)
			&& isset($options['filter'])
		)
		{
			if (isset($field['FILTER_PREPARE_VALUE_EXPRESSION']) && is_callable($field['FILTER_PREPARE_VALUE_EXPRESSION']))
			{
				call_user_func_array(
					$field['FILTER_PREPARE_VALUE_EXPRESSION'],
					[&$queryItem, &$entity, &$field]
				);
			}

			if (!$fantomField)
			{
				$valueType = 'string';
				if ($field['TYPE'] == 'int' || $field['TYPE'] == 'float')
					$valueType = 'number';
				elseif ($field['TYPE'] == 'char')
					$valueType = 'string_equal';

				$field['FILTER'] = \CIBlock::FilterCreate(
					'#FULL_NAME#',
					$queryItem['VALUES'],
					$valueType,
					$queryItem['OPERATION']
				);
				$field['FILTER'] = str_replace('#FULL_NAME#', $field['FULL_NAME'], $field['FILTER']);
				unset($valueType);
			}
		}

		if (
			self::checkAllowedAction($field['ALLOWED'], self::FIELD_ALLOWED_ORDER)
			&& isset($options['order'])
		)
		{
			if (!$fantomField)
			{
				$field['ORDER'] = \CIBlock::_Order(
					'#FULL_NAME#',
					$queryItem['ORDER'],
					$field['ORDER_DEFAULT'] ?? 'ASC',
					isset($field['ORDER_NULLABLE'])
				);
				$field['ORDER'] = str_replace('#FULL_NAME#', $field['FULL_NAME'], $field['ORDER']);
			}
		}

		if (isset($field['JOIN_MODIFY_EXPRESSION']) && is_callable($field['JOIN_MODIFY_EXPRESSION']))
		{
			call_user_func_array(
				$field['JOIN_MODIFY_EXPRESSION'],
				[&$queryItem, &$entity, &$field]
			);
		}
		if (isset($field['JOIN_MODIFY']))
		{
			$field['JOIN_MODIFY'] = str_replace('#TABLE#', $entity['ALIAS'], $field['JOIN_MODIFY']);
			$entity['JOIN_MODIFY'] = $field['JOIN_MODIFY'];
		}

		unset($fantomField);

		$field['ENTITY_DESCRIPTION'] = $entity;
		unset($entity);

		$result = array_intersect_key($field, $whiteList);
		unset($field, $whiteList);

		return $result;
	}

	/**
	 * @param array|null $field
	 * @return bool
	 */
	private static function checkPreparedField(?array $field): bool
	{
		if (empty($field))
			return false;
		if ($field['ENTITY_ID'] == 0)
		{
			if (
				$field['ENTITY'] != self::ENTITY_PRODUCT
				&& $field['ENTITY'] != self::ENTITY_FLAT_PRICE
				&& $field['ENTITY'] != self::ENTITY_FLAT_WAREHNOUSE
				&& $field['ENTITY'] != self::ENTITY_OLD_PRODUCT
			)
				return false;
		}
		return true;
	}

	/**
	 * @param array $field
	 * @return array
	 */
	private static function getFieldSignature(array $field): array
	{
		return [
			'ENTITY' => $field['ENTITY'],
			'FIELD' => $field['FIELD'],
			'ENTITY_ID' => $field['ENTITY_ID']
		];
	}

	/**
	 * @param array &$parameters
	 * @return void
	 */
	private static function prepareSelectedCompatibleFields(array &$parameters)
	{
		if ($parameters['compatible_mode'] && !empty($parameters['compatible_entities']))
		{
			foreach ($parameters['compatible_entities'] as $entity)
			{
				$list = self::getFieldsAllowedToSelect($entity['ENTITY']);
				if ($list === null)
					continue;
				foreach ($list as $fieldId)
				{
					$field = [
						'ENTITY' => $entity['ENTITY'],
						'FIELD' => $fieldId,
						'ENTITY_ID' => $entity['ENTITY_ID']
					];
					$parameters['select'][self::getFieldIndex($field)] = $field;
				}
				unset($field, $fieldId, $list);
			}
			unset($entity);
		}
		unset($parameters['compatible_mode'], $parameters['compatible_entities']);
	}

	/**
	 * @param array &$result
	 * @param array $field
	 * @return void
	 */
	private static function fillCompatibleEntities(array &$result, array $field)
	{
		if (!$field['COMPATIBLE'])
			return;
		$result['compatible_mode'] = true;
		$result['compatible_entities'][self::getEntityIndex($field)] = [
			'ENTITY' => $field['ENTITY'],
			'ENTITY_ID' => $field['ENTITY_ID']
		];
		// if compatible mode enabled - product always exists.
		$entity = [
			'ENTITY' => self::ENTITY_OLD_PRODUCT,
			'ENTITY_ID' => 0
		];
		$result['compatible_entities'][self::getEntityIndex($entity)] = $entity;
		$entity = [
			'ENTITY' => self::ENTITY_VAT,
			'ENTITY_ID' => 0
		];
		$result['compatible_entities'][self::getEntityIndex($entity)] = $entity;
		unset($entity);

	}

	/**
	 * @param array $parameters
	 * @param array $options
	 * @return array|null
	 */
	private static function prepareQuery(array $parameters, array $options): ?array
	{
		self::initEntityDescription();
		self::initEntityFields();

		self::setOptions($options);

		$result = [
			'compatible_mode' => false,
			'compatible_entities' => [],
			'select' => [],
			'filter' => [],
			'order' => []
		];

		if (!empty($parameters['select']) && is_array($parameters['select']))
		{
			foreach ($parameters['select'] as $field)
			{
				$prepareField = self::parseField($field);
				if (!self::checkPreparedField($prepareField))
					continue;
				if (!self::checkAllowedAction($prepareField['ALLOWED'], self::FIELD_ALLOWED_SELECT))
					continue;

				self::fillCompatibleEntities($result, $prepareField);
				$result['select'][self::getFieldIndex($prepareField)] = self::getFieldSignature($prepareField);
			}
			unset($prepareField, $field);
		}

		if (!empty($parameters['filter']) && is_array($parameters['filter']))
		{
			foreach (array_keys($parameters['filter']) as $key)
			{
				$filter = \CIBlock::MkOperationFilter($key);
				$prepareField = self::parseField($filter['FIELD']);
				if (!self::checkPreparedField($prepareField))
					continue;
				if (!self::checkAllowedAction($prepareField['ALLOWED'], self::FIELD_ALLOWED_FILTER))
					continue;

				self::fillCompatibleEntities($result, $prepareField);
				$prepareField = self::getFieldSignature($prepareField);
				$prepareField['OPERATION'] = $filter['OPERATION'];
				$prepareField['VALUES'] = $parameters['filter'][$key];
				$result['filter'][] = $prepareField;
			}
			unset($prepareField, $filter, $key);
		}

		if (!empty($parameters['order']) && is_array($parameters['order']))
		{
			foreach ($parameters['order'] as $index => $value)
			{
				if (empty($value) || !is_array($value))
					continue;

				$order = reset($value);
				$field = key($value);

				$prepareField = self::parseField($field);
				if (!self::checkPreparedField($prepareField))
					continue;
				if (!self::checkAllowedAction($prepareField['ALLOWED'], self::FIELD_ALLOWED_ORDER))
					continue;

				self::orderTransformField($prepareField);

				self::fillCompatibleEntities($result, $prepareField);
				$fieldIndex = self::getFieldIndex($prepareField);

				$prepareField = self::getFieldSignature($prepareField);
				$result['select'][$fieldIndex] = $prepareField;

				$prepareField['INDEX'] = $index;
				$prepareField['ORDER'] = $order;
				$result['order'][$fieldIndex] = $prepareField;
			}
			unset($order, $field, $index, $value);
		}

		self::prepareSelectedCompatibleFields($result);

		$result = self::build($result);
		self::clearOptions();
		return $result;
	}

	/**
	 * @return void
	 */
	private static function clearOptions()
	{
		self::$options = [];
	}

	/**
	 * @param array $options
	 * @return void
	 */
	private static function setOptions(array $options)
	{
		global $USER;

		if (!isset($options['ALIASES']))
			$options['ALIASES'] = [];
		if (!isset($options['ALIASES']['#ELEMENT#']))
			$options['ALIASES']['#ELEMENT#'] = 'BE';

		if (!isset($options['USER']))
			$options['USER'] = [];
		if (!isset($options['USER']['ID']))
			$options['USER']['ID'] = (\CCatalog::IsUserExists() ? $USER->GetID() : 0);
		$options['USER']['ID'] = (int)$options['USER']['ID'];

		self::$options = $options;
	}

	/**
	 * @param string $index
	 * @return mixed|null
	 */
	private static function getOption(string $index)
	{
		if (!isset(self::$options[$index]))
			return null;
		return self::$options[$index];
	}

	/**
	 * @param array $parameters
	 * @return array|null
	 */
	private static function build(array $parameters): ?array
	{
		$founded = false;
		$result = [
			'select' => [],
			'filter' => [],
			'order' => [],
			'join' => [],
			'join_modify' => []
		];

		if (!empty($parameters['select']))
		{
			self::buildSelect($result, $parameters['select']);
			if (!empty($result['select']))
				$founded = true;
		}
		if (!empty($parameters['filter']))
		{
			self::buildFilter($result, $parameters['filter']);
			if (!empty($result['filter']) || !empty($result['join']))
				$founded = true;
		}
		if (!empty($parameters['order']))
		{
			self::buildOrder($result, $parameters['order']);
			if (!empty($result['order']))
				$founded = true;
		}

		if (!$founded)
			return null;

		self::buildJoin($result);

		$result['join'] = array_values($result['join']);
		unset($result['join_modify']);

		return $result;
	}

	/**
	 * @param array &$result
	 * @param array $list
	 * @return void
	 */
	private static function buildSelect(array &$result, array $list)
	{
		foreach ($list as $item)
		{
			$field = self::getField($item, ['select' => true]);
			if (empty($field))
				continue;

			if (isset($field['SELECT']))
				$result['select'][] = $field['SELECT'].' as '.$field['ALIAS'];

			$item['ENTITY_DESCRIPTION'] = $field['ENTITY_DESCRIPTION'];
			self::addJoin($result, $item);
		}
		unset($item);
	}

	/**
	 * @param array &$result
	 * @param array $list
	 * @return void
	 */
	private static function buildFilter(array &$result, array $list)
	{
		self::filterModify($list);
		foreach ($list as $item)
		{
			$field = self::getField($item, ['filter' => true]);
			if (empty($field))
				continue;

			if (isset($field['FILTER']))
				$result['filter'][] = $field['FILTER'];

			$item['ENTITY_DESCRIPTION'] = $field['ENTITY_DESCRIPTION'];
			self::addJoin($result, $item);
		}
		unset($item);
	}

	/**
	 * @param array &$result
	 * @param array $list
	 * @return void
	 */
	private static function buildOrder(array &$result, array $list)
	{
		foreach ($list as $item)
		{
			$field = self::getField($item, ['order' => true]);
			if (empty($field))
				continue;

			if (isset($field['ORDER']))
				$result['order'][$item['INDEX']] = $field['ORDER'];

			$item['ENTITY_DESCRIPTION'] = $field['ENTITY_DESCRIPTION'];
			self::addJoin($result, $item);
		}
		unset($field, $item);
	}

	/**
	 * @param array &$result
	 * @return void
	 */
	private static function buildJoin(array &$result)
	{
		foreach (array_keys($result['join']) as $index)
		{
			$modifier = (isset($result['join_modify'][$index])
				? ' '.implode(' ', $result['join_modify'][$index])
				: ''
			);
			$result['join'][$index] = str_replace('#JOIN_MODIFY#', $modifier, $result['join'][$index]);
		}
		unset($modifier, $index);
	}

	/**
	 * @param array &$list
	 * @return void
	 */
	private static function filterModify(array &$list)
	{
		foreach (array_keys($list) as $index)
		{
			$item = $list[$index];
			$field = self::getFieldDescription($item['ENTITY'], $item['FIELD']);
			if (empty($field))
				continue;

			$entity = self::getEntityDescription($item);
			if (empty($entity))
				continue;

			if (isset($field['FILTER_MODIFY_EXPRESSION']) && is_callable($field['FILTER_MODIFY_EXPRESSION']))
			{
				call_user_func_array(
					$field['FILTER_MODIFY_EXPRESSION'],
					[&$list, $index, $entity, $field]
				);
			}
		}
		unset($field, $entity, $item, $index);
	}

	/**
	 * @param array &$result
	 * @param array $entity
	 * @return void
	 */
	private static function addJoin(array &$result, array $entity)
	{
		$index = self::getEntityIndex($entity);
		$description = $entity['ENTITY_DESCRIPTION'];

		if (!isset($result['join'][$index]))
		{
			if (!empty($description['RELATION']))
			{
				foreach ($description['RELATION'] as $item)
				{
					if (!is_array($item))
					{
						$item = [
							'ENTITY' => $item,
							'ENTITY_ID' => 0
						];
					}
					$ownerIndex = self::getEntityIndex($item);
					if (isset($result['join'][$ownerIndex]))
						continue;
					$owner = self::getEntityDescription($item);
					if (empty($owner))
						continue;
					$result['join'][$ownerIndex] = $owner['JOIN'];
				}
				unset($owner, $ownerIndex, $parent, $item);
			}
			$result['join'][$index] = $description['JOIN'];
		}
		if (!empty($description['JOIN_MODIFY']))
		{
			if (!isset($result['join_modify'][$index]))
				$result['join_modify'][$index] = [];
			$result['join_modify'][$index][] = $description['JOIN_MODIFY'];
		}
		unset($description, $index);
	}

	/**
	 * @param array &$item
	 * @return void
	 */
	private static function orderTransformField(array &$item)
	{
		$field = self::getFieldDescription($item['ENTITY'], $item['FIELD']);
		if (empty($field))
			return;
		if (isset($field['ORDER_TRANSFORM']))
			$item['FIELD'] = $field['ORDER_TRANSFORM'];
		unset($field);
	}

	/**
	 * Returns sql code for select QUANTITY_TRACE with converted default value.
	 *
	 * @param array &$parameters
	 * @param array &$entity
	 * @param array &$field
	 * @return void
	 *
	 * @noinspection PhpUnusedParameterInspection
	 */
	private static function selectQuantityTrace(array &$parameters, array &$entity, array &$field)
	{
		$field['SELECT'] = self::getReplaceSqlFunction(Main\Config\Option::get('catalog', 'default_quantity_trace'));
	}

	/**
	 * Returns sql code for select CAN_BUY_ZERO with converted default value.
	 *
	 * @param array &$parameters
	 * @param array &$entity
	 * @param array &$field
	 * @return void
	 *
	 * @noinspection PhpUnusedParameterInspection
	 */
	private static function selectCanBuyZero(array &$parameters, array &$entity, array &$field)
	{
		$field['SELECT'] = self::getReplaceSqlFunction(Main\Config\Option::get('catalog', 'default_can_buy_zero'));
	}

	/**
	 * Returns sql code for select NEGATIVE_AMOUNT_TRACE with converted default value.
	 *
	 * @param array &$parameters
	 * @param array &$entity
	 * @param array &$field
	 * @return void
	 *
	 * @noinspection PhpUnusedParameterInspection
	 */
	private static function selectNegativeAmountTrace(array &$parameters, array &$entity, array &$field)
	{
		$field['SELECT'] = self::getReplaceSqlFunction(Main\Config\Option::get('catalog', 'allow_negative_amount'));
	}

	/**
	 * Returns sql code for select SUBSCRIBE with converted default value.
	 *
	 * @param array &$parameters
	 * @param array &$entity
	 * @param array &$field
	 * @return void
	 *
	 * @noinspection PhpUnusedParameterInspection
	 */
	private static function selectSubscribe(array &$parameters, array &$entity, array &$field)
	{
		$field['SELECT'] = self::getReplaceSqlFunction(Main\Config\Option::get('catalog', 'default_subscribe'));
	}

	/**
	 * Returns sql code for select field with default value.
	 *
	 * @param string $defaultValue
	 * @return string
	 */
	private static function getReplaceSqlFunction(string $defaultValue): string
	{
		return 'IF (#FULL_NAME# = \''.ProductTable::STATUS_DEFAULT.'\', \''.$defaultValue.'\', #FULL_NAME#)';
	}

	/**
	 * @param array &$parameters
	 * @param array &$entity
	 * @param array &$field
	 * @return void
	 *
	 * @noinspection PhpUnusedParameterInspection
	 */
	private static function selectPriceTypeName(array &$parameters, array &$entity, array &$field)
	{
		$result = '';
		$id = $parameters['ENTITY_ID'];
		$fullPriceTypeList = \CCatalogGroup::GetListArray();
		if (!empty($fullPriceTypeList[$id]))
		{
			$result = (!empty($fullPriceTypeList[$id]['NAME_LANG'])
				? $fullPriceTypeList[$id]['NAME_LANG']
				: $fullPriceTypeList[$id]['NAME']
			);
			$connection = Main\Application::getInstance()->getConnection();
			$sqlHelper = $connection->getSqlHelper();
			$result = $sqlHelper->forSql($result);
			unset($sqlHelper, $connection);
		}
		unset($fullPriceTypeList, $id);
		$field['SELECT'] = '\''.$result.'\'';
		unset($result);
	}

	/**
	 * @param array &$parameters
	 * @param array &$entity
	 * @param array &$field
	 * @return void
	 *
	 * @noinspection PhpUnusedParameterInspection
	 */
	private static function selectPriceTypeAllowedView(array &$parameters, array &$entity, array &$field)
	{
		$parameters['ACCESS'] = GroupAccessTable::ACCESS_VIEW;
		$field['SELECT'] = self::getPriceTypeAccess($parameters);
	}

	/**
	 * @param array &$parameters
	 * @param array &$entity
	 * @param array &$field
	 * @return void
	 *
	 * @noinspection PhpUnusedParameterInspection
	 */
	private static function selectPriceTypeAllowedBuy(array &$parameters, array &$entity, array &$field)
	{
		$parameters['ACCESS'] = GroupAccessTable::ACCESS_BUY;
		$field['SELECT'] = self::getPriceTypeAccess($parameters);
	}

	/**
	 * @param array $parameters
	 * @return string
	 */
	private static function getPriceTypeAccess(array $parameters): string
	{
		$result = 'N';

		$user = self::getOption('USER');
		if (!empty($user))
		{
			if (empty($user['GROUPS']) || !is_array($user['GROUPS']))
				$user['GROUPS'] = self::getUserGroups($user['ID']);
			$iterator = GroupAccessTable::getList([
				'select' => ['ID'],
				'filter' => [
					'=CATALOG_GROUP_ID' => $parameters['ENTITY_ID'],
					'@GROUP_ID' => $user['GROUPS'],
					'=ACCESS' => $parameters['ACCESS']
				],
				'limit' => 1
			]);
			$row = $iterator->fetch();
			if (!empty($row))
				$result = 'Y';
			unset($row, $iterator);
		}
		unset($user);

		return '\''.$result.'\'';
	}

	/**
	 * @param array &$parameters
	 * @param array &$entity
	 * @param array &$field
	 * @return void
	 *
	 * @noinspection PhpUnusedParameterInspection
	 */
	private static function prepareFilterQuantityTrace(array &$parameters, array &$entity, array &$field)
	{
		$parameters['VALUES'] = self::addDefaultValue(
			$parameters['VALUES'],
			Main\Config\Option::get('catalog', 'default_quantity_trace')
		);
	}

	/**
	 * @param array &$parameters
	 * @param array &$entity
	 * @param array &$field
	 * @return void
	 *
	 * @noinspection PhpUnusedParameterInspection
	 */
	private static function prepareFilterCanBuyZero(array &$parameters, array &$entity, array &$field)
	{
		$parameters['VALUES'] = self::addDefaultValue(
			$parameters['VALUES'],
			Main\Config\Option::get('catalog', 'default_can_buy_zero')
		);
	}

	/**
	 * @param array &$parameters
	 * @param array &$entity
	 * @param array &$field
	 * @return void
	 *
	 * @noinspection PhpUnusedParameterInspection
	 */
	private static function prepareFilterSubscribe(array &$parameters, array &$entity, array &$field)
	{
		$parameters['VALUES'] = self::addDefaultValue(
			$parameters['VALUES'],
			Main\Config\Option::get('catalog', 'default_subscribe')
		);
	}

	/**
	 * Returns data for filtering with default values.
	 *
	 * @param mixed $values
	 * @param string $defaultValue
	 * @return mixed
	 */
	private static function addDefaultValue($values, string $defaultValue)
	{
		if (!is_array($values))
		{
			if ($values === $defaultValue)
			{
				$values = [$values, ProductTable::STATUS_DEFAULT];
			}
		}
		else
		{
			if (in_array($defaultValue, $values))
			{
				$values[] = ProductTable::STATUS_DEFAULT;
				$values = array_unique($values);
			}
		}
		return $values;
	}

	/**
	 * @param array &$parameters
	 * @param array &$entity
	 * @param array &$field
	 * @return void
	 *
	 * @noinspection PhpUnusedParameterInspection
	 */
	private static function priceParametersFilter(array &$parameters, array &$entity, array &$field)
	{
		if (empty($parameters['VALUES']))
			return;
		if (!is_string($parameters['VALUES']) && !is_int($parameters['VALUES']))
			return;
		$value = (int)$parameters['VALUES'];
		if ($value <= 0)
			return;

		$field['JOIN_MODIFY'] = ' and '.($parameters['OPERATION'] == 'N' ? 'not' : '').
			' ((#TABLE#.QUANTITY_FROM <= '.$value.' or #TABLE#.QUANTITY_FROM IS NULL)'.
			' and (#TABLE#.QUANTITY_TO >= '.$value.' or #TABLE#.QUANTITY_TO IS NULL))';
	}

	/**
	 * @param array &$filter
	 * @param int|string $filterKey
	 * @param array $entity
	 * @param array $field
	 * @return void
	 *
	 * @noinspection PhpUnusedParameterInspection
	 */
	private static function filterModifierCurrencyScale(array &$filter, $filterKey, array $entity, array $field)
	{
		$activeItem = $filter[$filterKey];

		if ($activeItem['FIELD'] !== 'CURRENCY_FOR_SCALE')
			return;
		if ($activeItem['OPERATION'] !== 'E' && $activeItem['OPERATION'] !== 'I')
			return;

		$value = $activeItem['VALUES'];
		if (!is_string($value))
			return;

		$currencyId = Currency\CurrencyManager::checkCurrencyID($value);
		if ($currencyId === false)
			return;

		$currency = \CCurrency::GetByID($currencyId);
		if (empty($currency))
			return;
		$currency['CURRENT_BASE_RATE'] = (float)$currency['CURRENT_BASE_RATE'];
		if ($currency['CURRENT_BASE_RATE'] <= 0)
			return;

		foreach (array_keys($filter) as $index)
		{
			if ($index == $filterKey)
				continue;
			$filterItem = $filter[$index];
			if (
				$filterItem['ENTITY'] != $activeItem['ENTITY']
				|| $filterItem['ENTITY_ID'] != $activeItem['ENTITY_ID']
			)
				continue;
			if ($filterItem['FIELD'] != 'PRICE')
				continue;
			if (is_array($filter[$index]['VALUES']))
			{
				$newPrices = [];
				foreach ($filter[$index]['VALUES'] as $oldPrice)
					$newPrices[] = (float)$oldPrice*$currency['CURRENT_BASE_RATE'];
				$filter[$index]['VALUES'] = $newPrices;
				unset($oldPrice, $newPrices);
			}
			else
			{
				$filter[$index]['VALUES'] = (float)$filter[$index]['VALUES']*$currency['CURRENT_BASE_RATE'];
			}
			$filter[$index]['FIELD'] = 'SCALED_PRICE';
		}
		unset($index);
	}

	/**
	 * @param int $userId
	 * @return array
	 */
	private static function getUserGroups(int $userId): array
	{
		return Main\UserTable::getUserGroupIds($userId);
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit