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/catalog/lib/component/

Upload File :
current_dir [ Writeable] document_root [ Writeable]

 

Command :


[ Back ]     

Current File : /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/catalog/lib/component/baseform.php
<?php

namespace Bitrix\Catalog\Component;

use Bitrix\Catalog;
use Bitrix\Catalog\Access\AccessController;
use Bitrix\Catalog\Access\ActionDictionary;
use Bitrix\Catalog\Integration\AI\Settings;
use Bitrix\Catalog\Url;
use Bitrix\Catalog\Config\State;
use Bitrix\Catalog\Product;
use Bitrix\Catalog\ProductTable;
use Bitrix\Catalog\v2\BaseIblockElementEntity;
use Bitrix\Catalog\v2\IoC\ServiceContainer;
use Bitrix\Catalog\v2\Property\HasPropertyCollection;
use Bitrix\Catalog\v2\Property\Property;
use Bitrix\Crm;
use Bitrix\Currency\CurrencyManager;
use Bitrix\Iblock;
use Bitrix\Iblock\ElementTable;
use Bitrix\Iblock\PropertyTable;
use Bitrix\Iblock\Url\AdminPage\BuilderManager;
use Bitrix\Main;
use Bitrix\Main\Config\Ini;
use Bitrix\Main\Config\Option;
use Bitrix\Main\Context;
use Bitrix\Main\Loader;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ORM\Fields\BooleanField;
use Bitrix\Main\ORM\Fields\DateField;
use Bitrix\Main\ORM\Fields\DatetimeField;
use Bitrix\Main\ORM\Fields\EnumField;
use Bitrix\Main\ORM\Fields\FloatField;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\ScalarField;
use Bitrix\Main\ORM\Fields\StringField;
use Bitrix\Main\ORM\Fields\TextField;
use Bitrix\Main\Page\Asset;
use Bitrix\Main\Text\HtmlFilter;
use Bitrix\Main\UI\FileInputUtility;
use Bitrix\Main\UserField;
use Bitrix\Main\UserFieldTable;
use Bitrix\Highloadblock as Highload;
use Bitrix\UI\EntityForm\Control;

abstract class BaseForm
{
	public const GRID_FIELD_PREFIX = 'SKU_GRID_';
	public const SERVICE_GRID_PREFIX = 'SERVICE_GRID_';
	public const PROPERTY_FIELD_PREFIX = 'PROPERTY_';
	public const PRICE_FIELD_PREFIX = 'CATALOG_GROUP_';
	public const CURRENCY_FIELD_PREFIX = 'CATALOG_CURRENCY_';
	public const MORE_PHOTO = 'MORE_PHOTO';
	public const NOT_SELECTED_VAT_ID_VALUE = 'D';

	private const USER_TYPE_METHOD = 'GetUIEntityEditorProperty';
	private const USER_TYPE_GET_VIEW_METHOD = 'GetUIEntityEditorPropertyViewHtml';
	private const USER_TYPE_GET_EDIT_METHOD = 'GetUIEntityEditorPropertyEditHtml';
	private const USER_TYPE_FORMAT_VALUE_METHOD = 'getFormattedValue';


	protected const CONTROL_NAME_WITH_CODE = 'name-code';
	protected const CONTROL_IBLOCK_SECTION = 'iblock_section';

	public const SCOPE_SHOP = 'shop';
	public const SCOPE_CRM = 'crm';

	public const CREATION_MODE = 'CREATION';
	public const EDIT_MODE = 'EDIT';

	/** @var \Bitrix\Catalog\v2\BaseIblockElementEntity */
	protected $entity;
	/** @var array */
	protected array $params = [];

	/** @var array|null */
	protected ?array $descriptions = null;
	/** @var array|null */
	protected ?array $propertyDescriptions = null;

	/** @var null|Url\ShopBuilder|Url\InventoryBuilder|Crm\Product\Url\ProductBuilder */
	protected $urlBuilder;

	protected bool $crmIncluded = false;

	/** @var AccessController */
	protected $accessController;

	public function __construct(BaseIblockElementEntity $entity, array $params = [])
	{
		$this->crmIncluded = Loader::includeModule('crm');
		$this->accessController = AccessController::getCurrent();
		$this->entity = $entity;
		$this->params = $this->getPreparedParams($params);

		$this->initUrlBuilder();
	}

	protected function getPreparedParams(array $params): array
	{
		$allowedBuilderTypes = [
			Url\ShopBuilder::TYPE_ID,
			Url\InventoryBuilder::TYPE_ID,
		];
		$allowedScopeList = [
			self::SCOPE_SHOP,
		];
		if ($this->crmIncluded)
		{
			$allowedBuilderTypes[] = Crm\Product\Url\ProductBuilder::TYPE_ID;
			$allowedScopeList[] = self::SCOPE_CRM;
		}

		$params['BUILDER_CONTEXT'] = (string)($params['BUILDER_CONTEXT'] ?? '');
		if (!in_array($params['BUILDER_CONTEXT'], $allowedBuilderTypes, true))
		{
			$params['BUILDER_CONTEXT'] = Url\ShopBuilder::TYPE_ID;
		}

		$params['SCOPE'] = (string)($params['SCOPE'] ?? '');
		if ($params['SCOPE'] === '')
		{
			$params['SCOPE'] = $this->getScopeByUrl();
		}

		if (!in_array($params['SCOPE'], $allowedScopeList))
		{
			$params['SCOPE'] = self::SCOPE_SHOP;
		}

		$params['MODE'] = $params['MODE'] ?? '';
		if ($params['MODE'] !== self::CREATION_MODE && $params['MODE'] !== self::EDIT_MODE)
		{
			$params['MODE'] = $this->entity->isNew() ? self::CREATION_MODE : self::EDIT_MODE;
		}

		return $params;
	}

	protected function isEntityCreationForm(): bool
	{
		return $this->params['MODE'] === self::CREATION_MODE;
	}

	protected function getScopeByUrl(): string
	{
		$result = '';

		$currentPath = Context::getCurrent()->getRequest()->getRequestUri();
		if (strncmp($currentPath, '/shop/', 6) === 0)
		{
			$result = self::SCOPE_SHOP;
		}
		elseif ($this->crmIncluded)
		{
			if (strncmp($currentPath, '/crm/', 5) === 0)
			{
				$result = self::SCOPE_CRM;
			}
		}

		return $result;
	}

	protected function initUrlBuilder(): void
	{
		$this->urlBuilder = BuilderManager::getInstance()->getBuilder($this->params['BUILDER_CONTEXT']);
		$this->urlBuilder->setIblockId($this->entity->getIblockId());
	}

	/**
	 * Returns true, if public product card is allowed.
	 *
	 * @return bool
	 */
	public function isCardAllowed(): bool
	{
		switch ($this->params['SCOPE'])
		{
			case self::SCOPE_SHOP:
				$result = \Bitrix\Catalog\Config\State::isProductCardSliderEnabled();
				break;
			case self::SCOPE_CRM:
				$result = false;
				if ($this->crmIncluded)
				{
					$result = Crm\Settings\LayoutSettings::getCurrent()->isFullCatalogEnabled();
				}
				break;
			default:
				$result = false;
				break;
		}

		return $result;
	}

	/**
	 * Returns true, if current user can't modify product.
	 *
	 * @return bool
	 */
	public function isReadOnly(): bool
	{
		if (State::isExternalCatalog())
		{
			return true;
		}

		return
			!$this->accessController->check(ActionDictionary::ACTION_PRODUCT_CARD_EDIT)
			&& !$this->isAllowedEditFields()
		;
	}

	/**
	 * Returns true, if current user can create new product or modify existing product.
	 *
	 * @return bool
	 */
	public function isAllowedEditFields(): bool
	{
		if ($this->isEntityCreationForm())
		{
			return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_ADD);
		}

		return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_EDIT);
	}

	/**
	 * Returns true, if current user can modify card settings.
	 *
	 * @return bool
	 */
	public function isCardSettingsEditable(): bool
	{
		return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_CARD_EDIT);
	}

	/**
	 * Returns true, if current user can write card settings for all users.
	 *
	 * @return bool
	 */
	public function isEnabledSetSettingsForAll(): bool
	{
		return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_CARD_SETTINGS_FOR_USERS_SET);
	}

	/**
	 * Returns true, if current user can modify product prices.
	 *
	 * @return bool
	 */
	public function isPricesEditable(): bool
	{
		return
			(
				$this->accessController->check(ActionDictionary::ACTION_PRICE_EDIT)
				|| $this->isEntityCreationForm()
			)
			&& $this->isAllowedEditFields()
		;
	}

	/**
	 * Return true, if current user can view purchasing prices.
	 *
	 * @return bool
	 */
	public function isPurchasingPriceAllowed(): bool
	{
		return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_PURCHASE_INFO_VIEW);
	}

	/**
	 * Returns true, if current user can modify product's visibility.
	 *
	 * @return bool
	 */
	public function isVisibilityEditable(): bool
	{
		return
			$this->accessController->check(ActionDictionary::ACTION_PRODUCT_PUBLIC_VISIBILITY_SET)
			&& $this->isAllowedEditFields()
		;
	}

	/**
	 * Returns true, if current user can change inventory management setting.
	 *
	 * @return bool
	 */
	public function isInventoryManagementAccess(): bool
	{
		return $this->accessController->check(ActionDictionary::ACTION_INVENTORY_MANAGEMENT_ACCESS);
	}

	protected function prepareFieldName(string $name): string
	{
		return $name;
	}

	/**
	 * Returns controller's list for product card.
	 *
	 * @return array[]
	 */
	public function getControllers(): array
	{
		return [
			[
				'name' => 'FIELD_CONFIGURATOR_CONTROLLER',
				'type' => 'field_configurator',
				'config' => [],
			],
			[
				'name' => 'GOOGLE_MAP_CONTROLLER',
				'type' => 'google_map',
				'config' => [],
			],
			[
				'name' => 'EMPLOYEE_CONTROLLER',
				'type' => 'employee',
				'config' => [],
			],
			[
				'name' => 'VARIATION_LINK_CONTROLLER',
				'type' => 'variation_link',
				'config' => [],
			],
			[
				'name' => 'USER_CONTROLLER',
				'type' => 'user',
				'config' => [],
			],
			[
				'name' => 'CRM_CONTROLLER',
				'type' => 'binding_to_crm_element',
				'config' => [],
			],
			[
				'name' => 'IBLOCK_ELEMENT_CONTROLLER',
				'type' => 'iblock_element',
				'config' => [],
			],
		];
	}

	/**
	 * Returns product field values.
	 *
	 * @param bool $allowDefaultValues Can use default values.
	 * @param array|null $descriptions Field descriptions.
	 * @return array
	 */
	public function getValues(bool $allowDefaultValues = true, array $descriptions = null): array
	{
		$values = [];
		if ($descriptions === null)
		{
			$descriptions = $this->getDescriptions();
		}

		if ($allowDefaultValues)
		{
			foreach ($descriptions as $field)
			{
				$values[$field['name']] = $this->getFieldValue($field)
					?? $field['defaultValue']
					?? '';
			}
		}
		else
		{
			foreach ($descriptions as $field)
			{
				$values[$field['name']] = $this->getFieldValue($field) ?? '';
			}
		}

		$additionalValues = $this->getAdditionalValues($values, $descriptions);

		if (!empty($additionalValues))
		{
			$values = array_merge($values, $additionalValues);
		}

		return $values;
	}

	public function getVariationGridId(): string
	{
		$iblockInfo = ServiceContainer::getIblockInfo($this->entity->getIblockId());

		if ($iblockInfo)
		{
			return 'catalog-product-variation-grid-' . $iblockInfo->getProductIblockId();
		}

		return 'catalog-product-variation-grid';
	}

	public function getVariationGridClassName(): string
	{
		return GridVariationForm::class;
	}

	public function getVariationGridJsComponentName(): string
	{
		return 'BX.Catalog.VariationGrid';
	}

	public function getCardSettings(): array
	{
		$gridColumnSettings = $this->getCardSettingsItems();

		$activeSettings = [];
		$options = new \Bitrix\Main\Grid\Options($this->getVariationGridId());
		$allUsedColumns = $options->getUsedColumns();
		if (!empty($allUsedColumns))
		{
			foreach ($gridColumnSettings as $setting => $columns)
			{
				if (empty(array_diff($columns['ITEMS'], $allUsedColumns)))
				{
					$activeSettings[] = $setting;
				}
			}
		}

		$config = $this->getCardUserConfig();
		if (!empty($config['CATALOG_PARAMETERS']))
		{
			$activeSettings[] = 'CATALOG_PARAMETERS';
		}

		$items = [];
		$settingList = array_keys($gridColumnSettings);
		if ($this->entity->getType() !== ProductTable::TYPE_SERVICE)
		{
			$settingList = array_merge(
				$settingList,
				[
					'CATALOG_PARAMETERS',
				]
			);
		}
		foreach ($settingList as $setting)
		{
			$items[] = [
				'id' => $setting,
				'checked' => in_array($setting, $activeSettings, true),
				'title' => $gridColumnSettings[$setting]['TITLE'] ?? Loc::getMessage('CATALOG_C_F_VARIATION_SETTINGS_' . $setting . '_TITLE'),
				'desc' => $gridColumnSettings[$setting]['DESCRIPTION'] ?? Loc::getMessage('CATALOG_C_F_VARIATION_SETTINGS_' . $setting . '_DESC'),
				'action' => isset($gridColumnSettings[$setting]) ? 'grid' : 'card',
				'columns' => $gridColumnSettings[$setting] ?? null,
			];
		}

		$seoLink = [
			'id' => 'SEO',
			'title' => Loc::getMessage('CATALOG_C_F_VARIATION_SETTINGS_SEO_TITLE'),
			'disabled' => $this->isEntityCreationForm(),
			'disabledCheckbox' => true,
			'desc' => '',
			'url' => '',
			'action' => 'slider',
		];

		if ($this->entity->getId())
		{
			$seoLink['url'] = $this->urlBuilder->getElementSeoUrl($this->entity->getId());
		}

		$items[] = $seoLink;

		return $items;
	}

	protected function isInventoryButtonAllowed(): bool
	{
		return $this->entity->getType() !== ProductTable::TYPE_SERVICE;
	}

	protected function getCardSettingsItems(): array
	{
		return [];
	}

	public function getCardConfigId(): string
	{
		$iblockInfo = ServiceContainer::getIblockInfo($this->entity->getIblockId());

		if ($iblockInfo)
		{
			return 'catalog-entity-card-config-' . $iblockInfo->getProductIblockId();
		}

		return 'catalog-entity-card-config';
	}

	public function getCardUserConfig(): array
	{
		return \CUserOptions::getOption('catalog', $this->getCardConfigId(), []);
	}

	public function saveCardUserConfig(array $config): bool
	{
		return \CUserOptions::setOption('catalog', $this->getCardConfigId(), $config);
	}

	public function getVariationIblockId(): ?int
	{
		$iblockInfo = ServiceContainer::getIblockInfo($this->entity->getIblockId());

		if ($iblockInfo)
		{
			return (int)$iblockInfo->getSkuIblockId() ?: $iblockInfo->getProductIblockId();
		}

		return null;
	}

	protected function getAdditionalValues(array $values,  array $descriptions = []): array
	{
		$additionalValues = [];

		foreach ($descriptions as $description)
		{
			if (!isset($description['type']) || !in_array($description['type'], ['custom', 'money', 'multimoney', 'user'], true))
			{
				continue;
			}

			$value = $values[$description['name']] ?? null;
			$descriptionData = $description['data'] ?? [];

			if (!empty($description['settings']['USER_TYPE']))
			{
				$description['settings']['PROPERTY_USER_TYPE'] = \CIBlockProperty::GetUserType(
					$description['settings']['USER_TYPE']
				);
			}

			$propertySettings = $description['settings'] ?? [];

			if ($description['type'] === 'custom')
			{
				if ($this->isCustomLinkProperty($propertySettings))
				{
					$params = [
						'SETTINGS' => $propertySettings,
						'VALUE' => $value,
						'FIELD_NAME' => $description['name'],
						'ELEMENT_ID' => $this->entity->getId() ? (string)$this->entity->getId() : 'n' . mt_rand(),
					];
					$paramsSingle = $params;
					$paramsSingle['SETTINGS']['MULTIPLE'] = 'N';
					$paramsMultiple = $params;
					$paramsMultiple['SETTINGS']['MULTIPLE'] = 'Y';

					$viewMethod = $propertySettings['PROPERTY_USER_TYPE'][self::USER_TYPE_GET_VIEW_METHOD] ?? null;
					if ($viewMethod && is_callable($viewMethod))
					{
						$additionalValues[$descriptionData['view']] = $viewMethod($params);
					}

					$editMethod = $propertySettings['PROPERTY_USER_TYPE'][self::USER_TYPE_GET_EDIT_METHOD] ?? null;
					if ($editMethod && is_callable($editMethod))
					{
						$additionalValues[$descriptionData['edit']] = $editMethod($params);
						$additionalValues[$descriptionData['editList']]['SINGLE'] = $editMethod($paramsSingle);
						$additionalValues[$descriptionData['editList']]['MULTIPLE'] = $editMethod($paramsMultiple);
					}
				}
				elseif ($propertySettings['PROPERTY_TYPE'] === PropertyTable::TYPE_ELEMENT)
				{
					$namesList = [];
					Main\Type\Collection::normalizeArrayValuesByInt($value, false);
					if (!empty($value))
					{
						$elementData = ElementTable::getList([
							'select' => ['NAME'],
							'filter' => ['ID' => $value],
						]);
						while ($element = $elementData->fetch())
						{
							$namesList[] = $element['NAME'];
						}
						unset($element, $elementData);
					}
					$viewValue = implode(', ', $namesList);
					$additionalValues[$descriptionData['view']] = HtmlFilter::encode($viewValue);
					$paramsSingle = $propertySettings;
					$paramsSingle['MULTIPLE'] = 'N';
					$paramsMultiple = $propertySettings;
					$paramsMultiple['MULTIPLE'] = 'Y';
					$propertyConfig = [
						'FIELD_NAME' => $description['name'],
						'CHANGE_EVENTS' => [
							'onChangeIblockElement',
						],
					];
					$additionalValues[$descriptionData['edit']] = Iblock\UI\Input\Element::renderSelector(
						$propertySettings,
						$value,
						$propertyConfig
					);
					$additionalValues[$descriptionData['editList']]['SINGLE'] = Iblock\UI\Input\Element::renderSelector(
						$paramsSingle,
						$value,
						$propertyConfig
					);
					$additionalValues[$descriptionData['editList']]['MULTIPLE'] = Iblock\UI\Input\Element::renderSelector(
						$paramsMultiple,
						$value,
						$propertyConfig
					);
				}
				elseif ($propertySettings['PROPERTY_TYPE'] === PropertyTable::TYPE_SECTION)
				{
					$namesList = [];
					Main\Type\Collection::normalizeArrayValuesByInt($value, false);
					if (!empty($value))
					{
						$elementData = Iblock\SectionTable::getList([
							'select' => ['NAME'],
							'filter' => ['ID' => $value],
						]);
						while ($element = $elementData->fetch())
						{
							$namesList[] = $element['NAME'];
						}
						unset($element, $elementData);
					}
					$viewValue = implode(', ', $namesList);
					$additionalValues[$descriptionData['view']] = HtmlFilter::encode($viewValue);
					$paramsSingle = $propertySettings;
					$paramsSingle['MULTIPLE'] = 'N';
					$paramsMultiple = $propertySettings;
					$paramsMultiple['MULTIPLE'] = 'Y';
					$propertyConfig = [
						'FIELD_NAME' => $description['name'],
						'CHANGE_EVENTS' => [
							'onChangeIblockElement',
						],
					];
					$additionalValues[$descriptionData['edit']] = Iblock\UI\Input\Section::renderSelector(
						$propertySettings,
						$value,
						$propertyConfig
					);
					$additionalValues[$descriptionData['editList']]['SINGLE'] = Iblock\UI\Input\Section::renderSelector(
						$paramsSingle,
						$value,
						$propertyConfig
					);
					$additionalValues[$descriptionData['editList']]['MULTIPLE'] = Iblock\UI\Input\Section::renderSelector(
						$paramsMultiple,
						$value,
						$propertyConfig
					);
				}
				elseif ($propertySettings['PROPERTY_TYPE'] === PropertyTable::TYPE_FILE)
				{
					if ($description['propertyCode'] === self::MORE_PHOTO)
					{
						$value = $this->getEntityViewPictureValues($this->entity);
						$editValue = $this->getEntityEditPictureValues($this->entity);

						if (!$description['multiple'] && isset($value[0]))
						{
							$value = $value[0];
							$editValue = $editValue[0];
						}
					}
					else
					{
						$editValue = $value;
					}

					$isImageInput = $this->isImageProperty($description['settings']);

					$descriptionSingle = $description;
					$descriptionSingle['settings']['MULTIPLE'] = 'N';
					$descriptionSingle['multiple'] = false;
					$descriptionMultiple = $description;
					$descriptionMultiple['settings']['MULTIPLE'] = 'Y';
					$descriptionMultiple['multiple'] = true;

					if ($isImageInput)
					{
						$additionalValues[$descriptionData['view']] = $this->getImagePropertyViewHtml($value);
						$additionalValues[$descriptionData['viewList']]['SINGLE'] = $this->getImagePropertyViewHtml(is_array($value) ? $value[0] ?? null : $value);
						$additionalValues[$descriptionData['viewList']]['MULTIPLE'] = $this->getImagePropertyViewHtml(is_array($value) ? $value : [$value]);
						$additionalValues[$descriptionData['edit']] = $this->getImagePropertyEditHtml($description, $editValue);
						$additionalValues[$descriptionData['editList']]['SINGLE'] = $this->getImagePropertyEditHtml($descriptionSingle, is_array($editValue) ? $editValue[0] ?? null : $editValue);
						$additionalValues[$descriptionData['editList']]['MULTIPLE'] = $this->getImagePropertyEditHtml($descriptionMultiple, is_array($editValue) ? $editValue : [$editValue]);
					}
					else
					{
						$controlId = $description['name'] . '_uploader';

						$additionalValues[$descriptionData['view']] = '';
						$additionalValues[$descriptionData['viewList']]['SINGLE'] = '';
						$additionalValues[$descriptionData['viewList']]['MULTIPLE'] = '';

						if (!empty($value))
						{
							$additionalValues[$descriptionData['view']] = $this->getFilePropertyViewHtml($description, $value, $controlId);
							$additionalValues[$descriptionData['viewList']]['SINGLE'] = $this->getFilePropertyViewHtml($description, is_array($value) ? $value[0] ?? null : $value, $controlId, false);
							$additionalValues[$descriptionData['viewList']]['MULTIPLE'] = $this->getFilePropertyViewHtml($description, is_array($value) ? $value : [$value], $controlId, true);
						}

						$additionalValues[$descriptionData['edit']] = $this->getFilePropertyEditHtml($description, $value, $controlId);
						$additionalValues[$descriptionData['editList']]['SINGLE'] = $this->getFilePropertyEditHtml($description, is_array($value) ? $value[0] ?? null : $value, $controlId, false);
						$additionalValues[$descriptionData['editList']]['MULTIPLE'] = $this->getFilePropertyEditHtml($description, is_array($value) ? $value : [$value], $controlId, true);
					}
				}
				else
				{
					if (
						$propertySettings['USER_TYPE'] === 'FileMan'
						|| $propertySettings['USER_TYPE'] === 'DiskFile'
					)
					{
						$value = [
							'VALUE' => null,
							'DESCRIPTION' => '',
						];
					}

					$params = [
						'SETTINGS' => $propertySettings,
						'VALUE' => $value,
						'FIELD_NAME' => $description['name'],
						'ELEMENT_ID' => $this->entity->getId() ? (string)$this->entity->getId() : 'n' . mt_rand(),
					];

					if ($propertySettings['USER_TYPE'] === 'map_google')
					{
						$params['WIDTH'] = '95%';
						$params['HEIGHT'] = '400px';
					}

					$paramsSingle = $params;
					if ($description['multiple'])
					{
						$paramsSingle['VALUE'] = $value[0] ?? '';
					}
					else
					{
						$paramsSingle['VALUE'] = $value;
					}
					$paramsSingle['SETTINGS']['MULTIPLE'] = 'N';
					if ($value === '')
					{
						$singleValueToMultiple = [];
					}
					else
					{
						$singleValueToMultiple = [$value];
					}
					$paramsMultiple = $params;
					$paramsMultiple['VALUE'] = $description['multiple'] ? $value : $singleValueToMultiple;
					$paramsMultiple['SETTINGS']['MULTIPLE'] = 'Y';

					$viewMethod = $propertySettings['PROPERTY_USER_TYPE'][self::USER_TYPE_GET_VIEW_METHOD] ?? null;
					if ($viewMethod && is_callable($viewMethod))
					{
						$additionalValues[$descriptionData['viewList']]['SINGLE'] = $viewMethod($paramsSingle);
						$additionalValues[$descriptionData['viewList']]['MULTIPLE'] = $viewMethod($paramsMultiple);
						$additionalValues[$descriptionData['view']] = $viewMethod($params);
					}

					$editMethod = $propertySettings['PROPERTY_USER_TYPE'][self::USER_TYPE_GET_EDIT_METHOD] ?? null;
					if ($editMethod && is_callable($editMethod))
					{
						$additionalValues[$descriptionData['editList']]['SINGLE'] = $editMethod($paramsSingle);
						$additionalValues[$descriptionData['editList']]['MULTIPLE'] = $editMethod($paramsMultiple);
						$additionalValues[$descriptionData['edit']] = $editMethod($params);
					}
				}
			}
			elseif (in_array($description['type'], ['money', 'multimoney'], true) && Loader::includeModule('currency'))
			{
				$formatMethod = $propertySettings['PROPERTY_USER_TYPE'][self::USER_TYPE_FORMAT_VALUE_METHOD] ?? null;
				if ($formatMethod && is_callable($formatMethod))
				{
					if ($description['type'] === 'money')
					{
						$additionalMoneyValues = $this->getAdditionalMoneyValues($value, $formatMethod);

						$additionalValues[$descriptionData['currencyCode']] = $additionalMoneyValues['currencyCode'];
						$additionalValues[$descriptionData['amount']] = $additionalMoneyValues['amount'];
						$additionalValues[$descriptionData['formatted']] = $additionalMoneyValues['formatted'];
						$additionalValues[$descriptionData['formattedWithCurrency']] = $additionalMoneyValues['formattedWithCurrency'];
					}
					else
					{
						foreach ($value as $currentValueElement)
						{
							$additionalMoneyValues = $this->getAdditionalMoneyValues($currentValueElement, $formatMethod);

							$additionalValues[$descriptionData['currencyCode']][] = $additionalMoneyValues['currencyCode'];
							$additionalValues[$descriptionData['amount']][] = $additionalMoneyValues['amount'];
							$additionalValues[$descriptionData['formatted']][] = $additionalMoneyValues['formatted'];
							$additionalValues[$descriptionData['formattedWithCurrency']][] = $additionalMoneyValues['formattedWithCurrency'];
						}
					}
				}
			}
			elseif ($description['type'] === 'user')
			{
				$userData = \Bitrix\Main\UserTable::getList([
					'filter' => ['=ID' => $value],
					'select' => [
						'ID', 'LOGIN', 'PERSONAL_PHOTO',
						'NAME', 'SECOND_NAME', 'LAST_NAME',
						'WORK_POSITION',
					],
					'limit' => 1,
				]);

				if ($user = $userData->fetch())
				{
					$pathToProfile = $this->params['PATH_TO']['USER_PROFILE'];
					if ($pathToProfile)
					{
						$additionalValues['PATH_TO_USER_PROFILE'] = $pathToProfile;
						$additionalValues['PATH_TO_' . $description['name']] = \CComponentEngine::MakePathFromTemplate(
							$pathToProfile,
							['user_id' => $user['ID']]
						);
					}
					$additionalValues[$description['name'] . '_PERSONAL_PHOTO'] = $user['PERSONAL_PHOTO'];
					$additionalValues[$description['name'] . '_WORK_POSITION'] = $user['WORK_POSITION'];

					$additionalValues[$description['name'] . '_FORMATTED_NAME'] = \CUser::FormatName(
						\CSite::GetNameFormat(false),
						[
							'LOGIN' => $user['LOGIN'],
							'NAME' => $user['NAME'],
							'LAST_NAME' => $user['LAST_NAME'],
							'SECOND_NAME' => $user['SECOND_NAME'],
						],
						true,
						false
					);

					if ((int)$user['PERSONAL_PHOTO'] > 0)
					{
						$file = new \CFile();
						$fileInfo = $file->ResizeImageGet(
							(int)$user['PERSONAL_PHOTO'],
							['width' => 60, 'height' => 60],
							BX_RESIZE_IMAGE_EXACT
						);
						if (is_array($fileInfo) && isset($fileInfo['src']))
						{
							$additionalValues[$description['name'] . '_PHOTO_URL'] = $fileInfo['src'];
						}
					}
				}
			}
		}

		return $additionalValues;
	}

	public function isImageProperty(array $propertySettings): bool
	{
		$fileTypes = (string)$propertySettings['FILE_TYPE'];
		$imageExtensions = explode(',', \CFile::GetImageExtensions());
		$fileExtensions = explode(',', $fileTypes);
		$fileExtensions = array_map('trim', $fileExtensions);

		$diffExtensions = array_diff($fileExtensions, $imageExtensions);
		return empty($diffExtensions);
	}

	private function isCustomLinkProperty(array $property): bool
	{
		if (!isset($property['USER_TYPE']))
		{
			return false;
		}

		$userTypes = [
			PropertyTable::USER_TYPE_XML_ID => true,
			PropertyTable::USER_TYPE_ELEMENT_AUTOCOMPLETE => true,
			'employee' => true,
			PropertyTable::USER_TYPE_SKU => true,
		];

		return isset($userTypes[$property['USER_TYPE']]);
	}

	private function getAdditionalMoneyValues(string $value, callable $formatMethod): array
	{
		$additionalValues = [];

		$formattedValues = $formatMethod($value);
		$amount = $formattedValues['AMOUNT'];
		if ($formattedValues['AMOUNT'] !== '' && $formattedValues['DECIMALS'] !== '')
		{
			$amount .= '.' . $formattedValues['DECIMALS'];
		}
		$currency = $formattedValues['CURRENCY'];

		$additionalValues['currencyCode'] = $currency;
		$additionalValues['amount'] = $amount;
		$additionalValues['formatted'] = \CCurrencyLang::CurrencyFormat($amount, $currency, false);
		$additionalValues['formattedWithCurrency'] = \CCurrencyLang::CurrencyFormat($amount, $currency, true);

		return $additionalValues;
	}

	private function getImageValuesForEntity(BaseIblockElementEntity $entity): array
	{
		$values = [];

		if ($entity instanceof HasPropertyCollection)
		{
			$morePhotoProperty = $entity->getPropertyCollection()->findByCode(self::MORE_PHOTO);
			if ($morePhotoProperty)
			{
				$morePhotoValues = $morePhotoProperty->getPropertyValueCollection()->getValues();
				if (!empty($morePhotoValues))
				{
					if (!is_array($morePhotoValues))
					{
						$morePhotoValues = [$morePhotoValues];
					}
					$values = array_merge($values, $morePhotoValues);
				}
			}
		}

		$previewPicture = $entity->getField('PREVIEW_PICTURE');
		if ($previewPicture)
		{
			$values = array_merge([$previewPicture], $values);
		}

		$detailPicture = $entity->getField('DETAIL_PICTURE');
		if ($detailPicture)
		{
			$values = array_merge([$detailPicture], $values);
		}

		return $values;
	}

	private function getEntityEditPictureValues(BaseIblockElementEntity $entity): array
	{
		return $this->getImageValuesForEntity($entity);
	}

	private function getEntityViewPictureValues(BaseIblockElementEntity $entity): array
	{
		return $this->getImageValuesForEntity($entity);
	}

	protected function getFieldValue(array $field)
	{
		if ($field['entity'] === 'product')
		{
			return $this->getProductFieldValue($field);
		}

		if ($field['entity'] === 'property')
		{
			return $this->getPropertyFieldValue($field);
		}

		return null;
	}

	public function getConfig(): array
	{
		$config = $this->collectFieldConfigs();

		foreach ($config as &$column)
		{
			usort(
				$column['elements'],
				static function ($a, $b)
				{
					$sortA = $a['sort'] ?? PHP_INT_MAX;
					$sortB = $b['sort'] ?? PHP_INT_MAX;

					return $sortA <=> $sortB;
				}
			);
		}

		return array_values($config);
	}

	/**
	 * @return array
	 */
	public function getHiddenFields(): array
	{
		$hiddenFields = [];

		if ($this->isQuantityTraceSettingDisabled())
		{
			$hiddenFields[] = 'QUANTITY_TRACE';
		}

		return $hiddenFields;
	}

	/**
	 * @return bool
	 */
	public function isQuantityTraceSettingDisabled(): bool
	{
		$isQuantityTraceExplicitlyDisabled = $this->entity->getField('QUANTITY_TRACE') === 'N';
		$isWithOrdersMode = Loader::includeModule('crm') && \CCrmSaleHelper::isWithOrdersMode();
		$isInventoryManagementUsed = State::isUsedInventoryManagement();

		return (!$isWithOrdersMode && !$isInventoryManagementUsed)
			|| ($isInventoryManagementUsed && !$isQuantityTraceExplicitlyDisabled);
	}

	protected function collectFieldConfigs(): array
	{
		$leftWidth = 30;

		$result = [
			'left' => [
				'name' => 'left',
				'type' => 'column',
				'data' => [
					'width' => $leftWidth,
				],
				'elements' => [
					[
						'name' => 'main',
						'title' => Loc::getMessage('CATALOG_C_F_MAIN_SECTION_TITLE'),
						'type' => 'section',
						'elements' => $this->getMainConfigElements(),
						'data' => [
							'isRemovable' => false,
						],
						'sort' => 100,
					],
					[
						'name' => 'properties',
						'title' => Loc::getMessage('CATALOG_C_F_PROPERTIES_SECTION_TITLE'),
						'type' => 'section',
						'elements' => $this->getPropertiesConfigElements(),
						'data' => [
							'isRemovable' => false,
						],
						'sort' => 200,
					],
				],
			],
			'right' => [
				'name' => 'right',
				'type' => 'column',
				'data' => [
					'width' => 100 - $leftWidth,
				],
				'elements' => [],
			],
		];

		$catalogParameters = $this->getCatalogParametersSectionConfig();
		if (!empty($catalogParameters))
		{
			$result['right']['elements'][] = $catalogParameters;
		}

		return $result;
	}

	protected function getMainConfigElements(): array
	{
		return array_merge(
			[
				['name' => 'NAME-CODE'],
				['name' => 'DETAIL_TEXT'],
			],
			Product\SystemField::getFieldsByRestrictions(
				[
					'TYPE' => $this->entity->getType(),
					'IBLOCK_ID' => $this->entity->getIblockId(),
				],
				[
					'RESULT_MODE' => Product\SystemField::DESCRIPTION_MODE_UI_FORM_EDITOR,
				]
			)
		);
	}

	protected function getCatalogParametersSectionConfig(): array
	{
		$catalogParameters = [
			['name' => 'QUANTITY_TRACE'],
			['name' => 'CAN_BUY_ZERO'],
			['name' => 'SUBSCRIBE'],
		];

		if ($this->isQuantityTraceSettingDisabled())
		{
			array_shift($catalogParameters);
		}

		return [
			'name' => 'catalog_parameters',
			'title' => Loc::getMessage('CATALOG_C_F_STORE_SECTION_TITLE'),
			'type' => 'section',
			'elements' => $catalogParameters,
			'data' => [
				'isRemovable' => false,
			],
			'sort' => 200,
		];
	}

	public function getDescriptions(): array
	{
		if ($this->descriptions === null)
		{
			$this->descriptions = $this->buildDescriptions();
		}

		return $this->descriptions;
	}

	protected function buildDescriptions(): array
	{
		$fieldBlocks = [];

		$fieldBlocks[] = $this->getTableDescriptions($this->getElementTableMap());
		$fieldBlocks[] = $this->getTableDescriptions(ProductTable::getMap());
		$fieldBlocks[] = $this->getIblockPropertiesDescriptions();
		$fieldBlocks[] = $this->getProductSystemFieldDescriptions();
		$fieldBlocks[] = $this->getUserFieldDescriptions();

		return array_merge(...$fieldBlocks);
	}

	protected function getElementTableMap(): array
	{
		$elementTableMap = ElementTable::getMap();
		unset($elementTableMap['NAME'], $elementTableMap['CODE']);

		return $elementTableMap;
	}

	protected function getNameCodeDescription(): array
	{
		return [
			[
				'entity' => 'product',
				'name' => 'NAME-CODE',
				'originalName' => 'NAME-CODE',
				'title' => Loc::getMessage('ELEMENT_ENTITY_NAME_FIELD'),
				'type' => 'name-code',
				'editable' => $this->isAllowedEditFields(),
				'required' => 'true',
				'placeholders' => [
					'creation' => Loc::getMessage('CATALOG_C_F_NEW_PRODUCT_PLACEHOLDER'),
				],
				'defaultValue' => null,
				'optionFlags' => 1,
			],
		];
	}

	private function getTableDescriptions(array $tableMap): array
	{
		$descriptions = [];

		$allowedFields = $this->getTableElementsWhiteList();

		/** @var ScalarField $field */
		foreach ($tableMap as $field)
		{
			$fieldName = $field->getName();

			if (!isset($allowedFields[$fieldName]))
			{
				continue;
			}

			$description = [
				'entity' => 'product',
				'name' => $this->prepareFieldName($fieldName),
				'originalName' => $fieldName,
				'title' => $field->getTitle(),
				'type' => $this->getFieldTypeByObject($field),
				'editable' => $this->isEditableField($field),
				'required' => $this->isRequiredField($field),
				'placeholders' => $this->getFieldPlaceholders($field),
				'defaultValue' => $field->getDefaultValue(),
				'optionFlags' => 1, // showAlways
			];

			if ($field instanceof EnumField)
			{
				if ($this->isSpecificCatalogField($fieldName))
				{
					$items = $this->getCatalogEnumFields($field->getName());
				}
				else
				{
					$items = $this->getCommonEnumFields($field);
				}

				$description['data']['items'] = $items;
			}

			if ($description['type'] === 'custom')
			{
				$description['data'] += $this->getCustomControlParameters($description['name']);
			}
			elseif ($description['type'] === 'user')
			{
				$description['data'] = [
					'enableEditInView' => false,
					'formated' => $description['name'] . '_FORMATTED_NAME',
					'position' => $description['name'] . '_WORK_POSITION',
					'photoUrl' => $description['name'] . '_PHOTO_URL',
					'showUrl' => 'PATH_TO_' . $description['name'],
					'pathToProfile' => 'PATH_TO_USER_PROFILE',
				];
			}
			elseif ($fieldName === 'MEASURE')
			{
				$measureList = [];
				$defaultMeasure = null;

				foreach ($this->getMeasures() as $measure)
				{
					$measureId = (int)$measure['ID'];
					$measureTitle = $measure['MEASURE_TITLE'];

					if (empty($measureTitle))
					{
						$measureTitle = \CCatalogMeasureClassifier::getMeasureTitle($measure['CODE']);
					}

					$measureList[] = [
						'NAME' => HtmlFilter::encode($measureTitle),
						'VALUE' => $measureId,
					];

					if ($measure['IS_DEFAULT'] === 'Y')
					{
						$defaultMeasure = $measureId;
					}
				}

				$description['defaultValue'] = $defaultMeasure;
				$description['data']['items'] = $measureList;
				$description['type'] = 'list';
			}
			elseif ($fieldName === 'VAT_ID')
			{
				$defaultVat = $this->getDefaultVat();
				$description['defaultValue'] = $defaultVat['ID'];

				$vatList[] = [
					'VALUE' => $defaultVat['ID'],
					'NAME' => $defaultVat['NAME'],
				];

				if ($defaultVat['ID'] !== self::NOT_SELECTED_VAT_ID_VALUE && !Loader::includeModule('bitrix24'))
				{
					$vatList[] = [
						'VALUE' => self::NOT_SELECTED_VAT_ID_VALUE,
						'NAME' => Loc::getMessage("CATALOG_PRODUCT_CARD_VARIATION_GRID_NOT_SELECTED"),
					];
				}

				foreach ($this->getVats() as $vat)
				{
					if ($vat['RATE'] === $defaultVat['RATE'] && $vat['EXCLUDE_VAT'] === $defaultVat['EXCLUDE_VAT'])
					{
						continue;
					}

					$vatList[] = [
						'VALUE' => $vat['ID'],
						'NAME' => htmlspecialcharsbx($vat['NAME']),
					];
				}

				$description['data']['items'] = $vatList;
				$description['type'] = 'list';
			}
			elseif ($fieldName === 'VAT_INCLUDED')
			{
				if (Option::get('catalog', 'default_product_vat_included') === 'Y')
				{
					$description['defaultValue'] = ProductTable::STATUS_YES;
				}
			}
			elseif ($field instanceof TextField)
			{
				$description['buttons'] = [];
				$description['postFormSettings'] = [
					'isAiImageEnabled' => false,
					'isDnDEnabled' => false,
				];

				if ($fieldName === 'DETAIL_TEXT')
				{
					$description['copilotIntegrationParams'] = [
						'isMentionUnavailable' => true,
						'isCopilotTextEnabledBySettings' => Settings::isTextProductCardAvailable(),
						'copilotParams' => [
							'contextId' => 'catalog_product_card_detail_description',
							'moduleId' => 'catalog',
							'category' => 'product_description',
							'isCopilotEnabled' => true,
						],
					];

					$description['buttons'][] = 'Copilot';
				}
			}

			$descriptions[] = $description;
		}

		return $descriptions;
	}

	private function getTableElementsWhiteList(): array
	{
		static $whiteList = null;

		if ($whiteList === null)
		{
			$whiteList = $this->getIblockElementFieldsList();

			if ($this->showCatalogProductFields())
			{
				$whiteList = array_merge($whiteList, $this->getCatalogProductFieldsList());
			}

			if ($this->showSpecificCatalogParameters())
			{
				$whiteList = array_merge($whiteList, $this->getSpecificCatalogFieldsList());
			}

			if ($this->showSubscribeCatalogParameters())
			{
				$whiteList = array_diff($whiteList, ['WEIGHT', 'WIDTH', 'LENGTH', 'HEIGHT']);
				$whiteList = array_merge($whiteList, $this->getSubscribeCatalogFieldList());
			}

			$whiteList = array_fill_keys($whiteList, true);
		}

		return $whiteList;
	}

	protected function getIblockElementFieldsList(): array
	{
		return [
			'ID',
			'IBLOCK_ID',
			// ToDo
			// 'IBLOCK_SECTION_ID',
			'TIMESTAMP_X',
			'MODIFIED_BY',
			'DATE_CREATE',
			'CREATED_BY',
			'ACTIVE',
			'ACTIVE_FROM',
			'ACTIVE_TO',
			'SORT',
			'NAME',
			'PREVIEW_TEXT',
			// 'PREVIEW_TEXT_TYPE',
			'DETAIL_TEXT',
			// 'DETAIL_TEXT_TYPE',
			'XML_ID',
			'CODE',
		];
	}

	protected function showCatalogProductFields(): bool
	{
		return false;
	}

	protected function getCatalogProductFieldsList(): array
	{
		return [
			'QUANTITY',
			'QUANTITY_RESERVED',
			'VAT_ID',
			'VAT_INCLUDED',
			// 'PURCHASING_PRICE',
			// 'PURCHASING_CURRENCY',
			// 'BARCODE_MULTI',
			// 'QUANTITY_RESERVED',
			'WEIGHT',
			'WIDTH',
			'LENGTH',
			'HEIGHT',
			'MEASURE',
			// 'TYPE',
			// 'AVAILABLE',
			// 'BUNDLE',
		];
	}

	protected function showSpecificCatalogParameters(): bool
	{
		return false;
	}

	private function getSpecificCatalogFieldsList(): array
	{
		return [
			'QUANTITY_TRACE',
			'CAN_BUY_ZERO',
			'SUBSCRIBE',
		];
	}

	private function getFieldTypeByObject(ScalarField $field): string
	{
		$fieldName = $field->getName();

		if ($fieldName === 'PREVIEW_PICTURE' || $fieldName === 'DETAIL_PICTURE')
		{
			return 'custom';
		}

		if ($fieldName === 'PREVIEW_TEXT' || $fieldName === 'DETAIL_TEXT')
		{
			return 'html';
		}

		if ($fieldName === 'MODIFIED_BY' || $fieldName === 'CREATED_BY')
		{
			return 'user';
		}

		switch (get_class($field))
		{
			case IntegerField::class:
			case FloatField::class:
				$fieldType = 'number';
				break;

			case BooleanField::class:
				$fieldType = 'boolean';
				break;

			case EnumField::class:
				$fieldType = 'list';
				break;

			case DateField::class:
			case DatetimeField::class:
				$fieldType = 'datetime';
				break;

			case TextField::class:
				$fieldType = 'textarea';
				break;

			case StringField::class:
			default:
				$fieldType = 'text';
		}

		return $fieldType;
	}

	private function isEditableField(ScalarField $field): bool
	{
		if (!$this->isAllowedEditFields())
		{
			return false;
		}

		if (in_array(
			$field->getName(),
			[
				'IBLOCK_ID',
				'MODIFIED_BY',
				'CREATED_BY',
				'AVAILABLE',
				'DATE_CREATE',
				'TIMESTAMP_X',
			],
			true
		))
		{
			return false;
		}

		if (in_array($field->getName(), ['QUANTITY', 'QUANTITY_RESERVED'], true) && State::isUsedInventoryManagement())
		{
			return false;
		}

		return !$field->isPrimary() && !$field->isAutocomplete();
	}

	private function isRequiredField(ScalarField $field): bool
	{
		if ($field->getName() === 'IBLOCK_ID')
		{
			return false;
		}

		return $field->isRequired();
	}

	private function getFieldPlaceholders(ScalarField $field): ?array
	{
		if ($field->getName() === 'NAME')
		{
			return [
				'creation' => Loc::getMessage('CATALOG_C_F_NEW_PRODUCT_PLACEHOLDER'),
			];
		}

		return null;
	}

	protected function showSubscribeCatalogParameters(): bool
	{
		$iblockInfo = ServiceContainer::getIblockInfo($this->entity->getIblockId());

		if ($iblockInfo)
		{
			return $iblockInfo->hasSubscription();
		}

		return false;
	}

	private function getSubscribeCatalogFieldList(): array
	{
		return [
			'PRICE_TYPE',
			'RECUR_SCHEME_LENGTH',
			'RECUR_SCHEME_TYPE',
			'TRIAL_PRICE_ID',
			'WITHOUT_ORDER',
		];
	}

	private function isSpecificCatalogField(string $fieldName): bool
	{
		static $catalogEnumFields = null;

		if ($catalogEnumFields === null)
		{
			$catalogEnumFields = array_fill_keys(
				$this->getSpecificCatalogFieldsList(),
				true
			);
		}

		return isset($catalogEnumFields[$fieldName]);
	}

	protected function getCatalogEnumFields(string $fieldName): array
	{
		$defaultValue = null;

		switch ($fieldName)
		{
			case 'QUANTITY_TRACE':
				$defaultValue = Option::get('catalog', 'default_quantity_trace') === 'Y';
				break;

			case 'CAN_BUY_ZERO':
				$defaultValue = Option::get('catalog', 'default_can_buy_zero') === 'Y';
				break;

			case 'SUBSCRIBE':
				$defaultValue = Option::get('catalog', 'default_subscribe') === 'Y';
				break;
		}

		return [
			[
				'NAME' => Loc::getMessage(
					'CATALOG_C_F_DEFAULT',
					[
						'#VALUE#' => $defaultValue
							? Loc::getMessage('CATALOG_C_F_YES')
							: Loc::getMessage('CATALOG_C_F_NO'),
					]
				),
				'VALUE' => ProductTable::STATUS_DEFAULT,
			],
			[
				'NAME' => Loc::getMessage('CATALOG_C_F_YES'),
				'VALUE' => ProductTable::STATUS_YES,
			],
			[
				'NAME' => Loc::getMessage('CATALOG_C_F_NO'),
				'VALUE' => ProductTable::STATUS_NO,
			],
		];
	}

	private function getCommonEnumFields(EnumField $field): array
	{
		$items = [];

		foreach ((array)$field->getValues() as $value)
		{
			$items[] = [
				'NAME' => $value,
				'VALUE' => $value,
			];
		}

		return $items;
	}

	protected function getProductSystemFieldDescriptions(): array
	{
		return Product\SystemField::getUiDescriptions([
			'TYPE' => $this->entity->getType(),
			'IBLOCK_ID' => $this->entity->getIblockId(),
		]);
	}

	protected function getUserFieldDescriptions(): array
	{
		$filter = [
			'=ENTITY_ID' => ProductTable::getUfId(),
		];
		$allSystemFields = Product\SystemField::getFieldNamesByRestrictions([]);
		if (!empty($allSystemFields))
		{
			$filter['!@FIELD_NAME'] = $allSystemFields;
		}

		$result = [];
		$iterator = UserFieldTable::getList([
			'select' => array_merge(
				['*'],
				UserFieldTable::getLabelsSelect()
			),
			'filter' => $filter,
			'order' => [
				'SORT' => 'ASC',
				'ID' => 'ASC',
			],
			'runtime' => [
				UserFieldTable::getLabelsReference('', Loc::getCurrentLang()),
			],
		]);
		while ($row = $iterator->fetch())
		{
			$description = [
				'entity' => 'product',
				'name' => $row['FIELD_NAME'],
				'originalName' => $row['FIELD_NAME'],
				'title' => $row['EDIT_FORM_LABEL'] ?? $row['FIELD_NAME'],
				'hint' => $row['HELP_MESSAGE'],
				'type' => $this->getUserFieldType($row),
				'editable' => true,
				'required' => $row['MANDATORY'] === 'Y',
				'multiple' => $row['MULTIPLE'] === 'Y',
				'placeholders' => null,
				'defaultValue' => $row['SETTINGS']['DEFAULT_VALUE'] ?? '',
				'optionFlags' => 1, // showAlways
				'options' => [
					'showCode' => 'true',
				],
				'data' => [],
			];

			switch ($description['type'])
			{
				case Control\Type::CUSTOM:
					$description['data'] += $this->getCustomControlParameters($description['name']);
					break;
				case Control\Type::MULTI_LIST:
				case Control\Type::LIST:
					$description['data'] += $this->getUserFieldListItems($row);
					break;
			}

			$result[] = $description;
		}

		return $result;
	}

	private function getUserFieldListItems(array $userField): array
	{
		if ($userField['USER_TYPE_ID'] === UserField\Types\EnumType::USER_TYPE_ID)
		{
			return $this->getUserFieldEnumItems($userField);
		}
		elseif (
			Loader::includeModule('highloadblock')
			&& $userField['USER_TYPE_ID'] === \CUserTypeHlblock::USER_TYPE_ID
		)
		{
			return $this->getUserFieldHighloadblockItems($userField);
		}

		return [];
	}

	private function getUserFieldEnumItems(array $userField): array
	{
		$list = [];

		$showNoValue = (
			$userField['MANDATORY'] !== 'Y'
			||
			$userField['SETTINGS']['SHOW_NO_VALUE'] !== 'N'
		);

		if (
			$showNoValue
			&&
			(
				$userField['SETTINGS']['DISPLAY'] !== 'CHECKBOX'
				||
				$userField['MULTIPLE'] !== 'Y'
			)
		)
		{
			$list[] = [
				'ID' => '0',
				'VALUE' => '0',
				'NAME' => Loc::getMessage('CATALOG_PRODUCT_CARD_USERFIELD_MESS_EMPTY_VALUE')
			];
		}

		$iterator = UserField\Types\EnumType::getList($userField);
		while ($value = $iterator->Fetch())
		{
			$list[] = [
				'ID' => $value['ID'],
				'VALUE' => $value['ID'],
				'NAME' => $value['VALUE'],
			];
		}
		unset($value, $iterator);

		return (!empty($list) ? ['items' => $list] : []);
	}

	private function getUserFieldHighloadblockItems(array $userField): array
	{
		$list = [];
		if (
			$userField['MANDATORY'] === 'N'
			&& $userField['MULTIPLE'] === 'N'
		)
		{
			$list[] = [
				'ID' => '0',
				'VALUE' => '0',
				'NAME' => Loc::getMessage('CATALOG_PRODUCT_CARD_USERFIELD_MESS_EMPTY_VALUE')
			];
		}

		$entity = Highload\HighloadBlockTable::compileEntity($userField['SETTINGS']['HLBLOCK_ID']);
		$fieldsList = $entity->getFields();
		if (isset($fieldsList['ID']) && isset($fieldsList['UF_NAME']))
		{
			$entityDataClass = $entity->getDataClass();
			$iterator = $entityDataClass::getList([
				'select' => [
					'ID',
					'UF_NAME',
				],
				'order' => [
					'UF_NAME' => 'ASC',
				],
			]);
			while ($value = $iterator->fetch())
			{
				$list[] = [
					'ID' => $value['ID'],
					'VALUE' => $value['ID'],
					'NAME' => $value['UF_NAME'],
				];
			}
			unset($value, $iterator);
			unset($entityDataClass, $entity);
		}

		return (!empty($list) ? ['items' => $list] : []);
	}

	public function getIblockPropertiesDescriptions(): array
	{
		if ($this->propertyDescriptions === null)
		{
			$this->propertyDescriptions = $this->buildIblockPropertiesDescriptions();
		}

		return $this->propertyDescriptions;
	}

	protected function buildIblockPropertiesDescriptions(): array
	{
		$propertyDescriptions = [];
		$unavailableUserTypes = $this->getUnavailableUserTypes();

		foreach ($this->entity->getPropertyCollection() as $property)
		{
			if (in_array($property->getUserType(), $unavailableUserTypes, true))
			{
				continue;
			}
			if ($property->isActive())
			{
				$propertyDescriptions[] = $this->getPropertyDescription($property);
			}
		}

		return $propertyDescriptions;
	}

	protected function getUnavailableUserTypes(): array
	{
		return [
			'DiskFile',
			'TopicID',
			PropertyTable::USER_TYPE_SKU,
		];
	}

	public static function preparePropertyName(string $name = ''): string
	{
		return self::PROPERTY_FIELD_PREFIX . $name;
	}

	public static function preparePropertyNameFromProperty(Property $property): string
	{
		$name = $property->getCode() === self::MORE_PHOTO ? self::MORE_PHOTO : $property->getId();

		return static::preparePropertyName($name);
	}

	protected function getPropertyDescription(Property $property): array
	{
		$description = [
			'entity' => 'property',
			'name' => static::preparePropertyNameFromProperty($property),
			'propertyId' => $property->getId(),
			'propertyCode' => $property->getCode(),
			'title' => $property->getName(),
			'editable' => true,
			'required' => $property->isRequired(),
			'multiple' => $property->isMultiple(),
			'defaultValue' => $property->getDefaultValue(),
			'settings' => $property->getSettings(),
			'type' => null,
		];

		if ($property->getUserType() === Iblock\PropertyTable::USER_TYPE_SEQUENCE)
		{
			$userTypeSettings = $property->getSetting('USER_TYPE_SETTINGS');
			$description['editable'] = $userTypeSettings['write'] === 'Y';
		}

		$nonEditableUserTypes = [
			'UserID',
			'FileMan',
		];
		if (in_array($property->getUserType(), $nonEditableUserTypes, true))
		{
			$description['editable'] = false;
		}

		if ($description['propertyCode'] === self::MORE_PHOTO)
		{
			$description['optionFlags'] = 1; // showAlways
			$description['hint'] = Loc::getMessage('CATALOG_PRODUCT_CARD_MORE_PHOTO_SIZE');
			$description['hintHtml'] = true;
		}

		if ($description['multiple'] && !is_array($description['defaultValue']))
		{
			$description['defaultValue'] = $description['defaultValue'] === null ? [] : [$description['defaultValue']];
		}

		// remove it after PropertyTable::TYPE_ELEMENT refactoring
		if ($property->getPropertyType() === PropertyTable::TYPE_ELEMENT)
		{
			Asset::getInstance()->addJs('/bitrix/js/main/utils.js');
		}

		if ($property->getUserType())
		{
			$specificDescription = $this->getUserTypePropertyDescription($property);
		}
		else
		{
			$specificDescription = $this->getGeneralPropertyDescription($property);
		}

		$specificDescription['data']['isPublic'] = $property->isPublic();

		if (!$this->isAllowedEditFields())
		{
			unset($specificDescription['editable']);
			$description['editable'] = false;
		}

		return array_merge($description, $specificDescription);
	}

	private function getPropertyType(Property $property): string
	{
		switch ($property->getPropertyType())
		{
			case PropertyTable::TYPE_STRING:
				// ToDo no multiple textarea right now
				// if ($property->isMultiple())
				// {
				// 	$fieldType = 'multifield';
				// }
				if ((int)$property->getSetting('ROW_COUNT') > 1)
				{
					$fieldType = 'textarea';
				}
				else
				{
					$fieldType = $property->isMultiple() ? 'multitext' : 'text';
				}

				break;

			case PropertyTable::TYPE_NUMBER:
				// ToDo no multiple number right now
				$fieldType = $property->isMultiple() ? 'multinumber' : 'number';
				break;

			case PropertyTable::TYPE_LIST:
				$fieldType = $property->isMultiple() ? 'multilist' : 'list';
				break;

			// case TextField::class:
			// 	$fieldType = 'textarea';
			// 	break;

			case PropertyTable::TYPE_ELEMENT:
			case PropertyTable::TYPE_FILE:
			case PropertyTable::TYPE_SECTION:
				$fieldType = 'custom';
				break;

			default:
				$fieldType = 'text';
		}

		return $fieldType;
	}

	protected function getHiddenPropertyCodes(): array
	{
		return [];
	}

	protected function getPropertiesConfigElements(): array
	{
		$elements = [];
		$hiddenCodesMap = array_fill_keys($this->getHiddenPropertyCodes(), true);
		foreach ($this->entity->getPropertyCollection() as $property)
		{
			if (isset($hiddenCodesMap[$property->getCode()]))
			{
				continue;
			}

			$elements[] = [
				'name' => static::preparePropertyNameFromProperty($property),
			];
		}

		return $elements;
	}

	protected function getGeneralPropertyDescription(Property $property): array
	{
		$type = $this->getPropertyType($property);

		$description = [
			'type' => $type,
			'data' => [
				'isProductProperty' => true,
			],
		];

		if ($type === 'custom')
		{
			$name = static::preparePropertyNameFromProperty($property);
			$description['data'] += $this->getCustomControlParameters($name);
		}

		if ($type === 'textarea')
		{
			$description['lineCount'] = (int)($property->getSetting('ROW_COUNT') ?? 1);
		}

		if ($property->getPropertyType() === PropertyTable::TYPE_LIST)
		{
			$description['data']['enableEmptyItem'] = true;
			$description['data']['items'] = [];

			$propertyEnumIterator = \CIBlockProperty::GetPropertyEnum(
				$property->getId(),
				[
					'SORT' => 'ASC',
					'VALUE' => 'ASC',
					'ID' => 'ASC',
				]
			);
			while ($enum = $propertyEnumIterator->fetch())
			{
				$description['data']['items'][] = [
					'NAME' => $enum['VALUE'],
					'VALUE' => $enum['ID'],
					'ID' => $enum['ID'],
				];
			}

			if (count($description['data']['items']) === 1
				&& $description['data']['items'][0]['NAME'] === 'Y')
			{
				$description['type'] = 'boolean';
				$description['data']['value'] = $description['data']['items'][0]['VALUE'];
			}
		}

		return $description;
	}

	protected function getUserTypePropertyDescription(Property $property): array
	{
		$propertySettings = $this->getPropertySettings($property);

		if (
			$property->getPropertyType() === PropertyTable::TYPE_STRING
			&& $property->getUserType() === PropertyTable::USER_TYPE_HTML
		)
		{
			$defaultValue = $property->getDefaultValue();

			if ($defaultValue)
			{
				if ($property->isMultiple())
				{
					foreach ($defaultValue as &$item)
					{
						$item = $item['TEXT'] ?? null;
					}
				}
				else
				{
					$defaultValue = $defaultValue['TEXT'] ?? null;
				}
			}

			return [
				'type' => 'html',
				'defaultValue' => $defaultValue,
			];
		}

		$userTypeMethod = $propertySettings['PROPERTY_USER_TYPE'][self::USER_TYPE_METHOD] ?? null;
		if ($userTypeMethod && is_callable($userTypeMethod))
		{
			$values = $property->getPropertyValueCollection()->getValues();
			$description = $userTypeMethod($propertySettings, $values);

			if ($property->getCode() === 'CML2_LINK')
			{
				$description['editable'] = false;
			}

			$specialTypes = ['custom', 'money', 'multimoney'];
			if (in_array($description['type'], $specialTypes, true))
			{
				$name = static::preparePropertyNameFromProperty($property);
				$descriptionData = $description['data'] ?? [];

				if ($description['type'] === 'custom')
				{
					$descriptionData += $this->getCustomControlParameters($name);
				}
				elseif ($description['type'] === 'money' || $description['type'] === 'multimoney')
				{
					$descriptionData['affectedFields'] = [
						$name . '[CURRENCY]',
						$name . '[AMOUNT]',
					];
					$descriptionData['currency'] = [
						'name' => $name . '[CURRENCY]',
						'items' => $this->getCurrencyList(),
					];
					$descriptionData['amount'] = $name . '[AMOUNT]';
					$descriptionData['currencyCode'] = $name . '[CURRENCY]';
					$descriptionData['formatted'] = $name . '[FORMATTED_AMOUNT]';
					$descriptionData['formattedWithCurrency'] = $name . '[FORMATTED_AMOUNT_WITH_CURRENCY]';
				}

				$description['data'] = $descriptionData;
			}

			if (empty($description['data']))
			{
				$description['data'] = [];
			}

			$description['data']['isProductProperty'] = true;

			return $description;
		}

		return [];
	}

	protected function getCurrencyList(): array
	{
		static $currencyList = null;

		if ($currencyList === null)
		{
			$currencyList = [];

			foreach (CurrencyManager::getNameList() as $code => $name)
			{
				$currencyList[] = [
					'VALUE' => $code,
					'NAME' => htmlspecialcharsbx($name),
				];
			}
		}

		return $currencyList;
	}

	protected function getPropertySettings(Property $property): array
	{
		$propertySettings = $property->getSettings();

		if (!empty($propertySettings['USER_TYPE']))
		{
			$propertySettings['PROPERTY_USER_TYPE'] = \CIBlockProperty::GetUserType($propertySettings['USER_TYPE']);
		}

		return $propertySettings;
	}

	protected function getImagePropertyViewHtml($value): string
	{
		$fileCount = 0;

		// single scalar property
		if (!empty($value) && !is_array($value))
		{
			$value = [$value];
		}

		if (is_array($value))
		{
			$fileCount = min(count($value), 3);
			$value = reset($value);
		}

		$imageSrc = null;

		if (!empty($value))
		{
			$image = \CFile::GetFileArray($value);
			if ($image)
			{
				$imageSrc = $image['SRC'];
			}
		}

		switch ($fileCount)
		{
			case 3:
				$multipleClass = ' ui-image-input-img-block-multiple';
				break;

			case 2:
				$multipleClass = ' ui-image-input-img-block-double';
				break;

			case 0:
				$multipleClass = ' ui-image-input-img-block-empty';
				break;

			case 1:
			default:
				$multipleClass = '';
				break;
		}

		if ($imageSrc)
		{
			$imageSrc = " src=\"{$imageSrc}\"";

			return <<<HTML
				<div class="ui-image-input-img-block{$multipleClass}">
					<div class="ui-image-input-img-block-inner">
						<div class="ui-image-input-img-item">
							<img class="ui-image-input-img"{$imageSrc}>
						</div>
					</div>
				</div>
				HTML;
		}

		return '';
	}

	/**
	 * @return \CMain
	 */
	protected function getApplication(): \CMain
	{
		global $APPLICATION;

		return $APPLICATION;
	}

	protected function getImageComponent(array $params): string
	{
		ob_start();

		$this->getApplication()->includeComponent('bitrix:ui.image.input', '', $params);

		return ob_get_clean();
	}

	protected function getFilePropertyEditHtml($description, $value, $controlId, bool $multipleForList = null): string
	{
		if ($multipleForList === null)
		{
			$multiple = $description['settings']['MULTIPLE'];
		}
		else
		{
			$multiple = $multipleForList ? 'Y' : 'N';
		}

		ob_start();

		$this->getApplication()->IncludeComponent(
			'bitrix:main.file.input',
			'.default',
			[
				'INPUT_NAME' => $description['name'],
				'INPUT_NAME_UNSAVED' => $description['name'] . '_tmp',
				'INPUT_VALUE' => $value,
				'MULTIPLE' => $multiple,
				'MODULE_ID' => 'catalog',
				'ALLOW_UPLOAD' => 'F',
				'ALLOW_UPLOAD_EXT' => $description['settings']['FILE_TYPE'],
				'MAX_FILE_SIZE' => Ini::unformatInt((string)ini_get('upload_max_filesize')),
				'CONTROL_ID' => $controlId,
			]
		);

		return ob_get_clean();
	}

	protected function getFilePropertyViewHtml($description, $value, $controlId, bool $multipleForList = null)
	{
		$cid = FileInputUtility::instance()->registerControl('', $controlId);
		$signer = new \Bitrix\Main\Security\Sign\Signer();
		$signature = $signer->getSignature($cid, 'main.file.input');
		if (is_array($value))
		{
			foreach ($value as $elementOfValue)
			{
				FileInputUtility::instance()->registerFile($cid, $elementOfValue);
			}
		}
		else
		{
			FileInputUtility::instance()->registerFile($cid, $value);
		}

		if ($multipleForList === null)
		{
			$multiple = $description['settings']['MULTIPLE'];
		}
		else
		{
			$multiple = $multipleForList ? 'Y' : 'N';
		}

		ob_start();

		$this->getApplication()->IncludeComponent(
			'bitrix:main.field.file',
			'',
			[
				'userField' => [
					'ID' => $description['settings']['ID'],
					'VALUE' => $value,
					'USER_TYPE_ID' => 'file',
					'MULTIPLE' => $multiple,
				],
				'additionalParameters' => [
					'mode' => 'main.view',
					'CONTEXT' => 'UI_EDITOR',
					'URL_TEMPLATE' => '/bitrix/components/bitrix/main.file.input/ajax.php?'
						. 'mfi_mode=down'
						. '&fileID=#file_id#'
						. '&cid=' . $cid
						. '&sessid=' . bitrix_sessid()
						. '&s=' . $signature,
				],
			]
		);

		return ob_get_clean();
	}

	protected function getImagePropertyEditHtml(array $property, $value): string
	{
		$inputName = $this->getFilePropertyInputName($property);

		if ($value && (!is_array($value) || isset($value['ID'])))
		{
			$value = [$value];
		}

		$fileValues = [];

		if (!empty($value) && is_array($value))
		{
			foreach ($value as $valueItem)
			{
				if (is_array($valueItem))
				{
					$fileId = $valueItem['ID'];
				}
				else
				{
					$fileId = $valueItem;
				}
				$propName = str_replace('n#IND#', $fileId, $inputName);
				$fileValues[$propName] = $fileId;
			}
		}

		$fileType = $property['settings']['FILE_TYPE'] ?? null;

		$fileParams = [
			'name' => $inputName,
			'id' => $inputName . '_' . random_int(1, 1000000),
			'description' => $property['settings']['WITH_DESCRIPTION'] ?? 'Y',
			'allowUpload' => $fileType ? 'F' : 'I',
			'allowUploadExt' => $fileType,
			'maxCount' => ($property['settings']['MULTIPLE'] ?? 'N') !== 'Y' ? 1 : null,

			'upload' => true,
			'medialib' => false,
			'fileDialog' => true,
			'cloud' => true,
		];

		return $this->getImageComponent([
			'FILE_SETTINGS' => $fileParams,
			'FILE_VALUES' => $fileValues,
			'LOADER_PREVIEW' => $this->getImagePropertyViewHtml($value),
		]);
	}

	protected function getFilePropertyInputName(array $property): string
	{
		$inputName = $property['name'] ?? '';

		if (isset($property['settings']['MULTIPLE']) && $property['settings']['MULTIPLE'] === 'Y')
		{
			$inputName .= '[n#IND#]';
		}

		return $inputName;
	}

	protected function getProductFieldValue(array $field)
	{
		$value = $this->entity->getField($field['originalName']);

		if ($field['originalName'] === 'PREVIEW_TEXT')
		{
			$detailTextType = $this->entity->getField('PREVIEW_TEXT_TYPE');
			if ($detailTextType !== 'html')
			{
				$value = HtmlFilter::encode($value);
			}
		}

		if ($field['originalName'] === 'DETAIL_TEXT')
		{
			$detailTextType = $this->entity->getField('DETAIL_TEXT_TYPE');
			if ($detailTextType !== 'html')
			{
				$value = HtmlFilter::encode($value);
			}
		}

		if ($field['originalName'] === 'VAT_ID' && $value === null && !$this->entity->isNew())
		{
			$value = self::NOT_SELECTED_VAT_ID_VALUE;
		}

		if (
			(
				$field['originalName'] === 'ACTIVE_FROM'
				|| $field['originalName'] === 'ACTIVE_TO'
			)
			&& !($this instanceof GridVariationForm)
			&& !empty($value)
		)
		{
			$value = $value->format(\Bitrix\Main\Type\DateTime::getFormat());
		}

		if (
			(
				$field['originalName'] === 'TIMESTAMP_X'
				|| $field['originalName'] === 'DATE_CREATE'
			)
			&& !empty($value)
		)
		{
			$value = $value->format(\Bitrix\Main\Type\DateTime::getFormat());
		}

		if ($field['originalName'] === 'NAME-CODE')
		{
			$value = [
				'NAME' => $this->entity->getField('NAME'),
				'CODE' => $this->entity->getField('CODE'),
			];
		}

		return $value;
	}

	protected function getPropertyFieldValue(array $field)
	{
		/** @var Property $property */
		$property = $this->entity->getPropertyCollection()->findById($field['propertyId']);
		$value = $property ? $property->getPropertyValueCollection()->getValues() : null;

		if (!isset($field['type']))
		{
			return $value;
		}

		if ($field['type'] === 'html')
		{
			if ($field['multiple'])
			{
				foreach ($value as &$item)
				{
					$item = $item['TEXT'] ?? null;
				}
			}
			else
			{
				$value = $value['TEXT'] ?? null;
			}
		}
		elseif ($property && $property->getUserType() === PropertyTable::USER_TYPE_SEQUENCE)
		{
			if ($field['multiple'])
			{
				foreach ($value as $valueItemKey => $valueItem)
				{
					if ($valueItem > 0)
					{
						$value[$valueItemKey] = (int)$value;
					}
					else
					{
						$value[$valueItemKey] = $this->getSequence(
							$property->getId(),
							$property->getSetting('IBLOCK_ID')
						);
					}
				}
			}
			else
			{
				if ($value > 0)
				{
					$value = (int)$value;
				}
				else
				{
					$value = $this->getSequence(
						$property->getId(),
						$property->getSetting('IBLOCK_ID')
					);
				}
			}
		}

		return $value;
	}

	protected function getSequence(int $propertyId, int $propertyIblockId): int
	{
		static $sequenceList = [];

		if (empty($sequenceList[$propertyId]))
		{
			$sequence = new \CIBlockSequence($propertyIblockId, $propertyId);
			$isAjaxRequest = \Bitrix\Main\Context::getCurrent()->getRequest()->isAjaxRequest();
			$sequenceList[$propertyId] = $isAjaxRequest ? $sequence->getCurrent() : $sequence->getNext();
		}

		return $sequenceList[$propertyId];
	}

	protected function getMeasures(): array
	{
		static $measures = null;

		if ($measures === null)
		{
			$params = [
				'select' => [
					'ID',
					'CODE',
					'MEASURE_TITLE',
					'IS_DEFAULT',
				],
			];

			$measures = \Bitrix\Catalog\MeasureTable::getList($params)->fetchAll();
		}

		return $measures;
	}

	protected function getVats(): array
	{
		static $vats = null;

		if ($vats === null)
		{
			$vats = Catalog\VatTable::getList([
				'select' => ['ID', 'NAME', 'RATE', 'EXCLUDE_VAT'],
				'filter' => ['=ACTIVE' => 'Y'],
			])->fetchAll();
		}

		return $vats;
	}

	protected function getDefaultVat(): array
	{
		$iblockVatId = $this->entity->getIblockInfo()->getVatId();

		foreach ($this->getVats() as $vat)
		{
			if ((int)$vat['ID'] === $iblockVatId)
			{
				$vat['NAME'] = Loc::getMessage(
					'CATALOG_C_F_DEFAULT',
					['#VALUE#' => htmlspecialcharsbx($vat['NAME'])]
				);

				return $vat;
			}
		}

		return [
			'ID' => self::NOT_SELECTED_VAT_ID_VALUE,
			'RATE' => null,
			'EXCLUDE_VAT' => null,
			'NAME' => Loc::getMessage(
				'CATALOG_C_F_DEFAULT',
				['#VALUE#' => Loc::getMessage('CATALOG_PRODUCT_CARD_VARIATION_GRID_NOT_SELECTED')]
			),
		];
	}

	protected function getCustomControlParameters(string $fieldName): array
	{
		return [
			'view' => $fieldName . '[VIEW_HTML]',
			'edit' => $fieldName . '[EDIT_HTML]',
			'editList' => $fieldName . '[EDIT_HTML_LIST]',
			'viewList' => $fieldName . '[VIEW_HTML_LIST]',
		];
	}

	protected function getUserFieldType(array $userField): string
	{
		$isMultiple = $userField['MULTIPLE'] === 'Y';
		switch ($userField['USER_TYPE_ID'])
		{
			case UserField\Types\BooleanType::USER_TYPE_ID:
				$result = Control\Type::BOOLEAN;
				break;
			case UserField\Types\DateTimeType::USER_TYPE_ID:
			case UserField\Types\DateType::USER_TYPE_ID:
				$result = $isMultiple ? Control\Type::MULTI_DATETIME : Control\Type::DATETIME;
				break;
			case UserField\Types\DoubleType::USER_TYPE_ID:
			case UserField\Types\IntegerType::USER_TYPE_ID:
				$result = $isMultiple ? Control\Type::MULTI_NUMBER : Control\Type::NUMBER;
				break;
			case UserField\Types\EnumType::USER_TYPE_ID:
				$result = $isMultiple ? Control\Type::MULTI_LIST : Control\Type::LIST;
				break;
			case UserField\Types\FileType::USER_TYPE_ID:
				$result = Control\Type::CUSTOM;
				break;
			case UserField\Types\StringFormattedType::USER_TYPE_ID:
				$result = Control\Type::TEXTAREA; // TODO: need replace
				break;
			case UserField\Types\StringType::USER_TYPE_ID:
				$result = $isMultiple ? Control\Type::MULTI_TEXT : Control\Type::TEXT;
				break;
			case UserField\Types\UrlType::USER_TYPE_ID:
				$result = Control\Type::LINK;
				break;
			default:
				if (
					Loader::includeModule('highloadblock')
					&& $userField['USER_TYPE_ID'] === \CUserTypeHlblock::USER_TYPE_ID
				)
				{
					$result = $isMultiple ? Control\Type::MULTI_LIST : Control\Type::LIST;
				}
				else
				{
					$result = Control\Type::TEXT;
				}
		}

		return $result;
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit