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/cvetdv.ru/bitrix/modules/catalog/lib/component/ |
Upload File : |
<?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; } }