56 private const USER_TYPE_METHOD =
'GetUIEntityEditorProperty';
57 private const USER_TYPE_GET_VIEW_METHOD =
'GetUIEntityEditorPropertyViewHtml';
58 private const USER_TYPE_GET_EDIT_METHOD =
'GetUIEntityEditorPropertyEditHtml';
59 private const USER_TYPE_FORMAT_VALUE_METHOD =
'getFormattedValue';
91 $this->crmIncluded = Loader::includeModule(
'crm');
92 $this->accessController = AccessController::getCurrent();
101 $allowedBuilderTypes = [
105 $allowedScopeList = [
108 if ($this->crmIncluded)
110 $allowedBuilderTypes[] = Crm\Product\Url\ProductBuilder::TYPE_ID;
111 $allowedScopeList[] = self::SCOPE_CRM;
114 $params[
'BUILDER_CONTEXT'] = (string)(
$params[
'BUILDER_CONTEXT'] ??
'');
115 if (!in_array(
$params[
'BUILDER_CONTEXT'], $allowedBuilderTypes,
true))
126 if (!in_array(
$params[
'SCOPE'], $allowedScopeList))
128 $params[
'SCOPE'] = self::SCOPE_SHOP;
132 if (
$params[
'MODE'] !== self::CREATION_MODE &&
$params[
'MODE'] !== self::EDIT_MODE)
134 $params[
'MODE'] = $this->entity->isNew() ? self::CREATION_MODE : self::EDIT_MODE;
142 return $this->params[
'MODE'] === self::CREATION_MODE;
149 $currentPath = Context::getCurrent()->getRequest()->getRequestUri();
154 elseif ($this->crmIncluded)
167 $this->urlBuilder = BuilderManager::getInstance()->getBuilder($this->params[
'BUILDER_CONTEXT']);
168 $this->urlBuilder->setIblockId($this->entity->getIblockId());
178 switch ($this->params[
'SCOPE'])
180 case self::SCOPE_SHOP:
183 case self::SCOPE_CRM:
185 if ($this->crmIncluded)
187 $result = Crm\Settings\LayoutSettings::getCurrent()->isFullCatalogEnabled();
205 if (State::isExternalCatalog())
211 !$this->accessController->check(ActionDictionary::ACTION_PRODUCT_CARD_EDIT)
225 return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_ADD);
228 return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_EDIT);
238 return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_CARD_EDIT);
248 return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_CARD_SETTINGS_FOR_USERS_SET);
260 $this->accessController->check(ActionDictionary::ACTION_PRICE_EDIT)
261 || $this->isEntityCreationForm()
274 return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_PURCHASE_INFO_VIEW);
285 $this->accessController->check(ActionDictionary::ACTION_PRODUCT_PUBLIC_VISIBILITY_SET)
297 return $this->accessController->check(ActionDictionary::ACTION_INVENTORY_MANAGEMENT_ACCESS);
314 'name' =>
'FIELD_CONFIGURATOR_CONTROLLER',
315 'type' =>
'field_configurator',
319 'name' =>
'GOOGLE_MAP_CONTROLLER',
320 'type' =>
'google_map',
324 'name' =>
'EMPLOYEE_CONTROLLER',
325 'type' =>
'employee',
329 'name' =>
'VARIATION_LINK_CONTROLLER',
330 'type' =>
'variation_link',
334 'name' =>
'USER_CONTROLLER',
339 'name' =>
'CRM_CONTROLLER',
340 'type' =>
'binding_to_crm_element',
344 'name' =>
'IBLOCK_ELEMENT_CONTROLLER',
345 'type' =>
'iblock_element',
366 if ($allowDefaultValues)
371 ?? $field[
'defaultValue']
379 $values[$field[
'name']] = $this->
getFieldValue($field) ??
'';
385 if (!empty($additionalValues))
387 $values = array_merge($values, $additionalValues);
395 $iblockInfo = ServiceContainer::getIblockInfo($this->entity->getIblockId());
399 return 'catalog-product-variation-grid-' . $iblockInfo->getProductIblockId();
402 return 'catalog-product-variation-grid';
407 return GridVariationForm::class;
412 return 'BX.Catalog.VariationGrid';
419 $activeSettings = [];
421 $allUsedColumns =
$options->getUsedColumns();
422 if (!empty($allUsedColumns))
424 foreach ($gridColumnSettings as $setting => $columns)
426 if (empty(array_diff($columns[
'ITEMS'], $allUsedColumns)))
428 $activeSettings[] = $setting;
434 if (!empty(
$config[
'CATALOG_PARAMETERS']))
436 $activeSettings[] =
'CATALOG_PARAMETERS';
440 $settingList = array_keys($gridColumnSettings);
443 $settingList = array_merge(
446 'CATALOG_PARAMETERS',
450 foreach ($settingList as $setting)
454 'checked' => in_array($setting, $activeSettings,
true),
455 'title' => $gridColumnSettings[$setting][
'TITLE'] ?? Loc::getMessage(
'CATALOG_C_F_VARIATION_SETTINGS_' . $setting .
'_TITLE'),
456 'desc' => $gridColumnSettings[$setting][
'DESCRIPTION'] ?? Loc::getMessage(
'CATALOG_C_F_VARIATION_SETTINGS_' . $setting .
'_DESC'),
457 'action' => isset($gridColumnSettings[$setting]) ?
'grid' :
'card',
458 'columns' => $gridColumnSettings[$setting] ??
null,
464 'title' => Loc::getMessage(
'CATALOG_C_F_VARIATION_SETTINGS_SEO_TITLE'),
466 'disabledCheckbox' =>
true,
469 'action' =>
'slider',
472 if ($this->entity->getId())
474 $seoLink[
'url'] = $this->urlBuilder->getElementSeoUrl($this->entity->getId());
494 $iblockInfo = ServiceContainer::getIblockInfo($this->entity->getIblockId());
498 return 'catalog-entity-card-config-' . $iblockInfo->getProductIblockId();
501 return 'catalog-entity-card-config';
506 return \CUserOptions::getOption(
'catalog', $this->
getCardConfigId(), []);
511 return \CUserOptions::setOption(
'catalog', $this->
getCardConfigId(), $config);
516 $iblockInfo = ServiceContainer::getIblockInfo($this->entity->getIblockId());
520 return (
int)$iblockInfo->getSkuIblockId() ?: $iblockInfo->getProductIblockId();
528 $additionalValues = [];
532 if (!isset(
$description[
'type']) || !in_array(
$description[
'type'], [
'custom',
'money',
'multimoney',
'user'],
true))
542 $description[
'settings'][
'PROPERTY_USER_TYPE'] = \CIBlockProperty::GetUserType(
551 if ($this->isCustomLinkProperty($propertySettings))
554 'SETTINGS' => $propertySettings,
557 'ELEMENT_ID' => $this->entity->getId() ? (string)$this->entity->getId() :
'n' . mt_rand(),
560 $paramsSingle[
'SETTINGS'][
'MULTIPLE'] =
'N';
562 $paramsMultiple[
'SETTINGS'][
'MULTIPLE'] =
'Y';
564 $viewMethod = $propertySettings[
'PROPERTY_USER_TYPE'][self::USER_TYPE_GET_VIEW_METHOD] ??
null;
565 if ($viewMethod && is_callable($viewMethod))
567 $additionalValues[$descriptionData[
'view']] = $viewMethod(
$params);
570 $editMethod = $propertySettings[
'PROPERTY_USER_TYPE'][self::USER_TYPE_GET_EDIT_METHOD] ??
null;
571 if ($editMethod && is_callable($editMethod))
573 $additionalValues[$descriptionData[
'edit']] = $editMethod(
$params);
574 $additionalValues[$descriptionData[
'editList']][
'SINGLE'] = $editMethod($paramsSingle);
575 $additionalValues[$descriptionData[
'editList']][
'MULTIPLE'] = $editMethod($paramsMultiple);
584 $elementData = ElementTable::getList([
585 'select' => [
'NAME'],
586 'filter' => [
'ID' => $value],
588 while ($element = $elementData->fetch())
590 $namesList[] = $element[
'NAME'];
592 unset($element, $elementData);
594 $viewValue = implode(
', ', $namesList);
596 $paramsSingle = $propertySettings;
597 $paramsSingle[
'MULTIPLE'] =
'N';
598 $paramsMultiple = $propertySettings;
599 $paramsMultiple[
'MULTIPLE'] =
'Y';
603 'onChangeIblockElement',
629 'select' => [
'NAME'],
630 'filter' => [
'ID' => $value],
632 while ($element = $elementData->fetch())
634 $namesList[] = $element[
'NAME'];
636 unset($element, $elementData);
638 $viewValue = implode(
', ', $namesList);
640 $paramsSingle = $propertySettings;
641 $paramsSingle[
'MULTIPLE'] =
'N';
642 $paramsMultiple = $propertySettings;
643 $paramsMultiple[
'MULTIPLE'] =
'Y';
647 'onChangeIblockElement',
670 $value = $this->getEntityViewPictureValues($this->entity);
671 $editValue = $this->getEntityEditPictureValues($this->entity);
676 $editValue = $editValue[0];
687 $descriptionSingle[
'settings'][
'MULTIPLE'] =
'N';
688 $descriptionSingle[
'multiple'] =
false;
690 $descriptionMultiple[
'settings'][
'MULTIPLE'] =
'Y';
691 $descriptionMultiple[
'multiple'] =
true;
696 $additionalValues[$descriptionData[
'viewList']][
'SINGLE'] = $this->
getImagePropertyViewHtml(is_array($value) ? $value[0] ??
null : $value);
697 $additionalValues[$descriptionData[
'viewList']][
'MULTIPLE'] = $this->
getImagePropertyViewHtml(is_array($value) ? $value : [$value]);
698 $additionalValues[$descriptionData[
'edit']] = $this->getImagePropertyEditHtml(
$description, $editValue);
699 $additionalValues[$descriptionData[
'editList']][
'SINGLE'] = $this->getImagePropertyEditHtml($descriptionSingle, is_array($editValue) ? $editValue[0] ??
null : $editValue);
700 $additionalValues[$descriptionData[
'editList']][
'MULTIPLE'] = $this->getImagePropertyEditHtml($descriptionMultiple, is_array($editValue) ? $editValue : [$editValue]);
707 $additionalValues[$descriptionData[
'view']] = $this->getFilePropertyViewHtml(
$description, $value, $controlId);
708 $additionalValues[$descriptionData[
'viewList']][
'SINGLE'] = $this->getFilePropertyViewHtml(
$description, is_array($value) ? $value[0] ??
null : $value, $controlId,
false);
709 $additionalValues[$descriptionData[
'viewList']][
'MULTIPLE'] = $this->getFilePropertyViewHtml(
$description, is_array($value) ? $value : [$value], $controlId,
true);
711 $additionalValues[$descriptionData[
'edit']] = $this->getFilePropertyEditHtml(
$description, $value, $controlId);
712 $additionalValues[$descriptionData[
'editList']][
'SINGLE'] = $this->getFilePropertyEditHtml(
$description, is_array($value) ? $value[0] ??
null : $value, $controlId,
false);
713 $additionalValues[$descriptionData[
'editList']][
'MULTIPLE'] = $this->getFilePropertyEditHtml(
$description, is_array($value) ? $value : [$value], $controlId,
true);
719 $propertySettings[
'USER_TYPE'] ===
'FileMan'
720 || $propertySettings[
'USER_TYPE'] ===
'DiskFile'
730 'SETTINGS' => $propertySettings,
733 'ELEMENT_ID' => $this->entity->getId() ? (string)$this->entity->getId() :
'n' . mt_rand(),
736 if ($propertySettings[
'USER_TYPE'] ===
'map_google')
745 $paramsSingle[
'VALUE'] = $value[0] ??
'';
749 $paramsSingle[
'VALUE'] = $value;
751 $paramsSingle[
'SETTINGS'][
'MULTIPLE'] =
'N';
754 $singleValueToMultiple = [];
758 $singleValueToMultiple = [$value];
761 $paramsMultiple[
'VALUE'] =
$description[
'multiple'] ? $value : $singleValueToMultiple;
762 $paramsMultiple[
'SETTINGS'][
'MULTIPLE'] =
'Y';
764 $viewMethod = $propertySettings[
'PROPERTY_USER_TYPE'][self::USER_TYPE_GET_VIEW_METHOD] ??
null;
765 if ($viewMethod && is_callable($viewMethod))
767 $additionalValues[$descriptionData[
'viewList']][
'SINGLE'] = $viewMethod($paramsSingle);
768 $additionalValues[$descriptionData[
'viewList']][
'MULTIPLE'] = $viewMethod($paramsMultiple);
769 $additionalValues[$descriptionData[
'view']] = $viewMethod(
$params);
772 $editMethod = $propertySettings[
'PROPERTY_USER_TYPE'][self::USER_TYPE_GET_EDIT_METHOD] ??
null;
773 if ($editMethod && is_callable($editMethod))
775 $additionalValues[$descriptionData[
'editList']][
'SINGLE'] = $editMethod($paramsSingle);
776 $additionalValues[$descriptionData[
'editList']][
'MULTIPLE'] = $editMethod($paramsMultiple);
777 $additionalValues[$descriptionData[
'edit']] = $editMethod(
$params);
781 elseif (in_array(
$description[
'type'], [
'money',
'multimoney'],
true) && Loader::includeModule(
'currency'))
783 $formatMethod = $propertySettings[
'PROPERTY_USER_TYPE'][self::USER_TYPE_FORMAT_VALUE_METHOD] ??
null;
784 if ($formatMethod && is_callable($formatMethod))
788 $additionalMoneyValues = $this->getAdditionalMoneyValues($value, $formatMethod);
790 $additionalValues[$descriptionData[
'currencyCode']] = $additionalMoneyValues[
'currencyCode'];
791 $additionalValues[$descriptionData[
'amount']] = $additionalMoneyValues[
'amount'];
792 $additionalValues[$descriptionData[
'formatted']] = $additionalMoneyValues[
'formatted'];
793 $additionalValues[$descriptionData[
'formattedWithCurrency']] = $additionalMoneyValues[
'formattedWithCurrency'];
797 foreach ($value as $currentValueElement)
799 $additionalMoneyValues = $this->getAdditionalMoneyValues($currentValueElement, $formatMethod);
801 $additionalValues[$descriptionData[
'currencyCode']][] = $additionalMoneyValues[
'currencyCode'];
802 $additionalValues[$descriptionData[
'amount']][] = $additionalMoneyValues[
'amount'];
803 $additionalValues[$descriptionData[
'formatted']][] = $additionalMoneyValues[
'formatted'];
804 $additionalValues[$descriptionData[
'formattedWithCurrency']][] = $additionalMoneyValues[
'formattedWithCurrency'];
812 'filter' => [
'=ID' => $value],
814 'ID',
'LOGIN',
'PERSONAL_PHOTO',
815 'NAME',
'SECOND_NAME',
'LAST_NAME',
821 if (
$user = $userData->fetch())
823 $pathToProfile = $this->params[
'PATH_TO'][
'USER_PROFILE'];
826 $additionalValues[
'PATH_TO_USER_PROFILE'] = $pathToProfile;
827 $additionalValues[
'PATH_TO_' .
$description[
'name']] = \CComponentEngine::MakePathFromTemplate(
829 [
'user_id' =>
$user[
'ID']]
832 $additionalValues[
$description[
'name'] .
'_PERSONAL_PHOTO'] =
$user[
'PERSONAL_PHOTO'];
833 $additionalValues[
$description[
'name'] .
'_WORK_POSITION'] =
$user[
'WORK_POSITION'];
835 $additionalValues[
$description[
'name'] .
'_FORMATTED_NAME'] = \CUser::FormatName(
836 \CSite::GetNameFormat(
false),
838 'LOGIN' =>
$user[
'LOGIN'],
839 'NAME' =>
$user[
'NAME'],
840 'LAST_NAME' =>
$user[
'LAST_NAME'],
841 'SECOND_NAME' =>
$user[
'SECOND_NAME'],
847 if ((
int)
$user[
'PERSONAL_PHOTO'] > 0)
849 $file = new \CFile();
850 $fileInfo = $file->ResizeImageGet(
851 (
int)
$user[
'PERSONAL_PHOTO'],
852 [
'width' => 60,
'height' => 60],
855 if (is_array($fileInfo) && isset($fileInfo[
'src']))
857 $additionalValues[
$description[
'name'] .
'_PHOTO_URL'] = $fileInfo[
'src'];
864 return $additionalValues;
869 $fileTypes = (string)$propertySettings[
'FILE_TYPE'];
870 $imageExtensions = explode(
',', \CFile::GetImageExtensions());
871 $fileExtensions = explode(
',', $fileTypes);
872 $fileExtensions = array_map(
'trim', $fileExtensions);
874 $diffExtensions = array_diff($fileExtensions, $imageExtensions);
875 return empty($diffExtensions);
878 private function isCustomLinkProperty(
array $property): bool
880 if (!isset($property[
'USER_TYPE']))
892 return isset($userTypes[$property[
'USER_TYPE']]);
895 private function getAdditionalMoneyValues(
string $value, callable $formatMethod):
array
897 $additionalValues = [];
899 $formattedValues = $formatMethod($value);
900 $amount = $formattedValues[
'AMOUNT'];
901 if ($formattedValues[
'AMOUNT'] !==
'' && $formattedValues[
'DECIMALS'] !==
'')
903 $amount .=
'.' . $formattedValues[
'DECIMALS'];
905 $currency = $formattedValues[
'CURRENCY'];
907 $additionalValues[
'currencyCode'] =
$currency;
908 $additionalValues[
'amount'] =
$amount;
909 $additionalValues[
'formatted'] = \CCurrencyLang::CurrencyFormat(
$amount,
$currency,
false);
910 $additionalValues[
'formattedWithCurrency'] = \CCurrencyLang::CurrencyFormat(
$amount,
$currency,
true);
912 return $additionalValues;
915 private function getImageValuesForEntity(BaseIblockElementEntity
$entity):
array
919 if (
$entity instanceof HasPropertyCollection)
921 $morePhotoProperty =
$entity->getPropertyCollection()->findByCode(self::MORE_PHOTO);
922 if ($morePhotoProperty)
924 $morePhotoValues = $morePhotoProperty->getPropertyValueCollection()->getValues();
925 if (!empty($morePhotoValues))
927 if (!is_array($morePhotoValues))
929 $morePhotoValues = [$morePhotoValues];
931 $values = array_merge($values, $morePhotoValues);
936 $previewPicture =
$entity->getField(
'PREVIEW_PICTURE');
939 $values = array_merge([$previewPicture], $values);
942 $detailPicture =
$entity->getField(
'DETAIL_PICTURE');
945 $values = array_merge([$detailPicture], $values);
951 private function getEntityEditPictureValues(BaseIblockElementEntity
$entity):
array
953 return $this->getImageValuesForEntity(
$entity);
956 private function getEntityViewPictureValues(BaseIblockElementEntity
$entity):
array
958 return $this->getImageValuesForEntity(
$entity);
963 if ($field[
'entity'] ===
'product')
965 return $this->getProductFieldValue($field);
968 if ($field[
'entity'] ===
'property')
970 return $this->getPropertyFieldValue($field);
984 static function (
$a, $b)
986 $sortA =
$a[
'sort'] ?? PHP_INT_MAX;
987 $sortB = $b[
'sort'] ?? PHP_INT_MAX;
989 return $sortA <=> $sortB;
1006 $hiddenFields[] =
'QUANTITY_TRACE';
1009 return $hiddenFields;
1017 $isQuantityTraceExplicitlyDisabled = $this->entity->getField(
'QUANTITY_TRACE') ===
'N';
1018 $isWithOrdersMode = Loader::includeModule(
'crm') && \CCrmSaleHelper::isWithOrdersMode();
1019 $isInventoryManagementUsed = State::isUsedInventoryManagement();
1021 return (!$isWithOrdersMode && !$isInventoryManagementUsed)
1022 || ($isInventoryManagementUsed && !$isQuantityTraceExplicitlyDisabled);
1034 'width' => $leftWidth,
1039 'title' => Loc::getMessage(
'CATALOG_C_F_MAIN_SECTION_TITLE'),
1040 'type' =>
'section',
1043 'isRemovable' =>
false,
1048 'name' =>
'properties',
1049 'title' => Loc::getMessage(
'CATALOG_C_F_PROPERTIES_SECTION_TITLE'),
1050 'type' =>
'section',
1053 'isRemovable' =>
false,
1063 'width' => 100 - $leftWidth,
1070 if (!empty($catalogParameters))
1072 $result[
'right'][
'elements'][] = $catalogParameters;
1082 [
'name' =>
'NAME-CODE'],
1083 [
'name' =>
'DETAIL_TEXT'],
1085 Product\SystemField::getFieldsByRestrictions(
1087 'TYPE' => $this->entity->getType(),
1088 'IBLOCK_ID' => $this->entity->getIblockId(),
1091 'RESULT_MODE' =>
Product\SystemField::DESCRIPTION_MODE_UI_FORM_EDITOR,
1099 $catalogParameters = [
1100 [
'name' =>
'QUANTITY_TRACE'],
1101 [
'name' =>
'CAN_BUY_ZERO'],
1102 [
'name' =>
'SUBSCRIBE'],
1107 array_shift($catalogParameters);
1111 'name' =>
'catalog_parameters',
1112 'title' => Loc::getMessage(
'CATALOG_C_F_STORE_SECTION_TITLE'),
1113 'type' =>
'section',
1114 'elements' => $catalogParameters,
1116 'isRemovable' =>
false,
1124 if ($this->descriptions ===
null)
1142 return array_merge(...$fieldBlocks);
1148 unset($elementTableMap[
'NAME'], $elementTableMap[
'CODE']);
1150 return $elementTableMap;
1157 'entity' =>
'product',
1158 'name' =>
'NAME-CODE',
1159 'originalName' =>
'NAME-CODE',
1160 'title' => Loc::getMessage(
'ELEMENT_ENTITY_NAME_FIELD'),
1161 'type' =>
'name-code',
1163 'required' =>
'true',
1165 'creation' => Loc::getMessage(
'CATALOG_C_F_NEW_PRODUCT_PLACEHOLDER'),
1167 'defaultValue' =>
null,
1173 private function getTableDescriptions(
array $tableMap):
array
1180 foreach ($tableMap as $field)
1182 $fieldName = $field->getName();
1190 'entity' =>
'product',
1192 'originalName' => $fieldName,
1193 'title' => $field->getTitle(),
1194 'type' => $this->getFieldTypeByObject($field),
1195 'editable' => $this->isEditableField($field),
1196 'required' => $this->isRequiredField($field),
1197 'placeholders' => $this->getFieldPlaceholders($field),
1198 'defaultValue' => $field->getDefaultValue(),
1204 if ($this->isSpecificCatalogField($fieldName))
1210 $items = $this->getCommonEnumFields($field);
1223 'enableEditInView' =>
false,
1224 'formated' =>
$description[
'name'] .
'_FORMATTED_NAME',
1228 'pathToProfile' =>
'PATH_TO_USER_PROFILE',
1231 elseif ($fieldName ===
'MEASURE')
1234 $defaultMeasure =
null;
1238 $measureId = (int)$measure[
'ID'];
1239 $measureTitle = $measure[
'MEASURE_TITLE'];
1241 if (empty($measureTitle))
1243 $measureTitle = \CCatalogMeasureClassifier::getMeasureTitle($measure[
'CODE']);
1248 'VALUE' => $measureId,
1251 if ($measure[
'IS_DEFAULT'] ===
'Y')
1253 $defaultMeasure = $measureId;
1261 elseif ($fieldName ===
'VAT_ID')
1263 $defaultVat = $this->getDefaultVat();
1267 'VALUE' => $defaultVat[
'ID'],
1268 'NAME' => $defaultVat[
'NAME'],
1271 if ($defaultVat[
'ID'] !== self::NOT_SELECTED_VAT_ID_VALUE && !Loader::includeModule(
'bitrix24'))
1274 'VALUE' => self::NOT_SELECTED_VAT_ID_VALUE,
1275 'NAME' => Loc::getMessage(
"CATALOG_PRODUCT_CARD_VARIATION_GRID_NOT_SELECTED"),
1279 foreach ($this->getVats() as
$vat)
1281 if (
$vat[
'RATE'] === $defaultVat[
'RATE'] &&
$vat[
'EXCLUDE_VAT'] === $defaultVat[
'EXCLUDE_VAT'])
1287 'VALUE' =>
$vat[
'ID'],
1295 elseif ($fieldName ===
'VAT_INCLUDED')
1297 if (Option::get(
'catalog',
'default_product_vat_included') ===
'Y')
1302 elseif ($field instanceof TextField)
1306 'isAiImageEnabled' =>
false,
1307 'isDnDEnabled' =>
false,
1310 if ($fieldName ===
'DETAIL_TEXT')
1313 'isMentionUnavailable' =>
true,
1315 'copilotParams' => [
1316 'contextId' =>
'catalog_product_card_detail_description',
1317 'moduleId' =>
'catalog',
1318 'category' =>
'product_description',
1319 'isCopilotEnabled' =>
true,
1333 private function getTableElementsWhiteList():
array
1335 static $whiteList =
null;
1337 if ($whiteList ===
null)
1348 $whiteList = array_merge($whiteList, $this->getSpecificCatalogFieldsList());
1353 $whiteList = array_diff($whiteList, [
'WEIGHT',
'WIDTH',
'LENGTH',
'HEIGHT']);
1354 $whiteList = array_merge($whiteList, $this->getSubscribeCatalogFieldList());
1357 $whiteList = array_fill_keys($whiteList,
true);
1397 'QUANTITY_RESERVED',
1420 private function getSpecificCatalogFieldsList():
array
1429 private function getFieldTypeByObject(
ScalarField $field): string
1431 $fieldName = $field->
getName();
1433 if ($fieldName ===
'PREVIEW_PICTURE' || $fieldName ===
'DETAIL_PICTURE')
1438 if ($fieldName ===
'PREVIEW_TEXT' || $fieldName ===
'DETAIL_TEXT')
1443 if ($fieldName ===
'MODIFIED_BY' || $fieldName ===
'CREATED_BY')
1448 switch (get_class($field))
1450 case IntegerField::class:
1451 case FloatField::class:
1452 $fieldType =
'number';
1455 case BooleanField::class:
1456 $fieldType =
'boolean';
1459 case EnumField::class:
1460 $fieldType =
'list';
1463 case DateField::class:
1464 case DatetimeField::class:
1465 $fieldType =
'datetime';
1468 case TextField::class:
1469 $fieldType =
'textarea';
1472 case StringField::class:
1474 $fieldType =
'text';
1480 private function isEditableField(ScalarField $field): bool
1503 if (in_array($field->getName(), [
'QUANTITY',
'QUANTITY_RESERVED'],
true) && State::isUsedInventoryManagement())
1508 return !$field->isPrimary() && !$field->isAutocomplete();
1511 private function isRequiredField(ScalarField $field): bool
1513 if ($field->getName() ===
'IBLOCK_ID')
1518 return $field->isRequired();
1521 private function getFieldPlaceholders(ScalarField $field): ?
array
1523 if ($field->getName() ===
'NAME')
1526 'creation' => Loc::getMessage(
'CATALOG_C_F_NEW_PRODUCT_PLACEHOLDER'),
1535 $iblockInfo = ServiceContainer::getIblockInfo($this->entity->getIblockId());
1539 return $iblockInfo->hasSubscription();
1545 private function getSubscribeCatalogFieldList():
array
1549 'RECUR_SCHEME_LENGTH',
1550 'RECUR_SCHEME_TYPE',
1556 private function isSpecificCatalogField(
string $fieldName): bool
1558 static $catalogEnumFields =
null;
1560 if ($catalogEnumFields ===
null)
1562 $catalogEnumFields = array_fill_keys(
1563 $this->getSpecificCatalogFieldsList(),
1568 return isset($catalogEnumFields[$fieldName]);
1577 case 'QUANTITY_TRACE':
1578 $defaultValue = Option::get(
'catalog',
'default_quantity_trace') ===
'Y';
1581 case 'CAN_BUY_ZERO':
1582 $defaultValue = Option::get(
'catalog',
'default_can_buy_zero') ===
'Y';
1586 $defaultValue = Option::get(
'catalog',
'default_subscribe') ===
'Y';
1592 'NAME' => Loc::getMessage(
1593 'CATALOG_C_F_DEFAULT',
1596 ? Loc::getMessage(
'CATALOG_C_F_YES')
1597 : Loc::getMessage(
'CATALOG_C_F_NO'),
1603 'NAME' => Loc::getMessage(
'CATALOG_C_F_YES'),
1607 'NAME' => Loc::getMessage(
'CATALOG_C_F_NO'),
1630 return Product\SystemField::getUiDescriptions([
1631 'TYPE' => $this->entity->getType(),
1632 'IBLOCK_ID' => $this->entity->getIblockId(),
1641 $allSystemFields = Product\SystemField::getFieldNamesByRestrictions([]);
1642 if (!empty($allSystemFields))
1644 $filter[
'!@FIELD_NAME'] = $allSystemFields;
1649 'select' => array_merge(
1665 'entity' =>
'product',
1666 'name' => $row[
'FIELD_NAME'],
1667 'originalName' => $row[
'FIELD_NAME'],
1668 'title' => $row[
'EDIT_FORM_LABEL'] ?? $row[
'FIELD_NAME'],
1669 'hint' => $row[
'HELP_MESSAGE'],
1670 'type' => $this->getUserFieldType($row),
1672 'required' => $row[
'MANDATORY'] ===
'Y',
1673 'multiple' => $row[
'MULTIPLE'] ===
'Y',
1674 'placeholders' =>
null,
1675 'defaultValue' => $row[
'SETTINGS'][
'DEFAULT_VALUE'] ??
'',
1678 'showCode' =>
'true',
1690 $description[
'data'] += $this->getUserFieldListItems($row);
1700 private function getUserFieldListItems(
array $userField):
array
1702 if ($userField[
'USER_TYPE_ID'] ===
UserField\
Types\EnumType::USER_TYPE_ID)
1704 return $this->getUserFieldEnumItems($userField);
1707 Loader::includeModule(
'highloadblock')
1711 return $this->getUserFieldHighloadblockItems($userField);
1717 private function getUserFieldEnumItems(
array $userField):
array
1722 $userField[
'MANDATORY'] !==
'Y'
1724 $userField[
'SETTINGS'][
'SHOW_NO_VALUE'] !==
'N'
1731 $userField[
'SETTINGS'][
'DISPLAY'] !==
'CHECKBOX'
1733 $userField[
'MULTIPLE'] !==
'Y'
1740 'NAME' => Loc::getMessage(
'CATALOG_PRODUCT_CARD_USERFIELD_MESS_EMPTY_VALUE')
1744 $iterator = UserField\Types\EnumType::getList($userField);
1750 'NAME' =>
$value[
'VALUE'],
1755 return (!empty($list) ? [
'items' => $list] : []);
1758 private function getUserFieldHighloadblockItems(
array $userField):
array
1762 $userField[
'MANDATORY'] ===
'N'
1763 && $userField[
'MULTIPLE'] ===
'N'
1769 'NAME' => Loc::getMessage(
'CATALOG_PRODUCT_CARD_USERFIELD_MESS_EMPTY_VALUE')
1773 $entity = Highload\HighloadBlockTable::compileEntity($userField[
'SETTINGS'][
'HLBLOCK_ID']);
1774 $fieldsList =
$entity->getFields();
1775 if (isset($fieldsList[
'ID']) && isset($fieldsList[
'UF_NAME']))
1777 $entityDataClass =
$entity->getDataClass();
1792 'NAME' =>
$value[
'UF_NAME'],
1796 unset($entityDataClass,
$entity);
1799 return (!empty($list) ? [
'items' => $list] : []);
1804 if ($this->propertyDescriptions ===
null)
1817 foreach ($this->entity->getPropertyCollection() as $property)
1819 if (in_array($property->getUserType(), $unavailableUserTypes,
true))
1823 if ($property->isActive())
1843 return self::PROPERTY_FIELD_PREFIX .
$name;
1848 $name = $property->getCode() === self::MORE_PHOTO ? self::MORE_PHOTO : $property->getId();
1850 return static::preparePropertyName(
$name);
1856 'entity' =>
'property',
1857 'name' => static::preparePropertyNameFromProperty($property),
1858 'propertyId' => $property->getId(),
1859 'propertyCode' => $property->getCode(),
1860 'title' => $property->getName(),
1862 'required' => $property->isRequired(),
1863 'multiple' => $property->isMultiple(),
1864 'defaultValue' => $property->getDefaultValue(),
1865 'settings' => $property->getSettings(),
1871 $userTypeSettings = $property->getSetting(
'USER_TYPE_SETTINGS');
1872 $description[
'editable'] = $userTypeSettings[
'write'] ===
'Y';
1875 $nonEditableUserTypes = [
1879 if (in_array($property->getUserType(), $nonEditableUserTypes,
true))
1887 $description[
'hint'] = Loc::getMessage(
'CATALOG_PRODUCT_CARD_MORE_PHOTO_SIZE');
1899 Asset::getInstance()->addJs(
'/bitrix/js/main/utils.js');
1902 if ($property->getUserType())
1911 $specificDescription[
'data'][
'isPublic'] = $property->isPublic();
1915 unset($specificDescription[
'editable']);
1919 return array_merge(
$description, $specificDescription);
1922 private function getPropertyType(Property $property): string
1924 switch ($property->getPropertyType())
1932 if ((
int)$property->getSetting(
'ROW_COUNT') > 1)
1934 $fieldType =
'textarea';
1938 $fieldType = $property->isMultiple() ?
'multitext' :
'text';
1945 $fieldType = $property->isMultiple() ?
'multinumber' :
'number';
1949 $fieldType = $property->isMultiple() ?
'multilist' :
'list';
1959 $fieldType =
'custom';
1963 $fieldType =
'text';
1978 foreach ($this->entity->getPropertyCollection() as $property)
1980 if (isset($hiddenCodesMap[$property->getCode()]))
1986 'name' => static::preparePropertyNameFromProperty($property),
1995 $type = $this->getPropertyType($property);
2000 'isProductProperty' =>
true,
2004 if (
$type ===
'custom')
2006 $name = static::preparePropertyNameFromProperty($property);
2010 if (
$type ===
'textarea')
2012 $description[
'lineCount'] = (int)($property->getSetting(
'ROW_COUNT') ?? 1);
2020 $propertyEnumIterator = \CIBlockProperty::GetPropertyEnum(
2028 while ($enum = $propertyEnumIterator->fetch())
2031 'NAME' => $enum[
'VALUE'],
2032 'VALUE' => $enum[
'ID'],
2033 'ID' => $enum[
'ID'],
2061 if ($property->isMultiple())
2065 $item = $item[
'TEXT'] ??
null;
2080 $userTypeMethod = $propertySettings[
'PROPERTY_USER_TYPE'][self::USER_TYPE_METHOD] ??
null;
2081 if ($userTypeMethod && is_callable($userTypeMethod))
2083 $values = $property->getPropertyValueCollection()->getValues();
2084 $description = $userTypeMethod($propertySettings, $values);
2086 if ($property->getCode() ===
'CML2_LINK')
2091 $specialTypes = [
'custom',
'money',
'multimoney'];
2092 if (in_array(
$description[
'type'], $specialTypes,
true))
2094 $name = static::preparePropertyNameFromProperty($property);
2099 $descriptionData += $this->getCustomControlParameters(
$name);
2103 $descriptionData[
'affectedFields'] = [
2104 $name .
'[CURRENCY]',
2107 $descriptionData[
'currency'] = [
2108 'name' =>
$name .
'[CURRENCY]',
2111 $descriptionData[
'amount'] =
$name .
'[AMOUNT]';
2112 $descriptionData[
'currencyCode'] =
$name .
'[CURRENCY]';
2113 $descriptionData[
'formatted'] =
$name .
'[FORMATTED_AMOUNT]';
2114 $descriptionData[
'formattedWithCurrency'] =
$name .
'[FORMATTED_AMOUNT_WITH_CURRENCY]';
2135 static $currencyList =
null;
2137 if ($currencyList ===
null)
2141 foreach (CurrencyManager::getNameList() as
$code =>
$name)
2150 return $currencyList;
2155 $propertySettings = $property->getSettings();
2157 if (!empty($propertySettings[
'USER_TYPE']))
2159 $propertySettings[
'PROPERTY_USER_TYPE'] = \CIBlockProperty::GetUserType($propertySettings[
'USER_TYPE']);
2162 return $propertySettings;
2170 if (!empty($value) && !is_array($value))
2175 if (is_array($value))
2177 $fileCount = min(
count($value), 3);
2178 $value = reset($value);
2185 $image = \CFile::GetFileArray($value);
2188 $imageSrc = $image[
'SRC'];
2195 $multipleClass =
' ui-image-input-img-block-multiple';
2199 $multipleClass =
' ui-image-input-img-block-double';
2203 $multipleClass =
' ui-image-input-img-block-empty';
2208 $multipleClass =
'';
2214 $imageSrc =
" src=\"{$imageSrc}\"";
2217 <div
class=
"ui-image-input-img-block{$multipleClass}">
2218 <div
class=
"ui-image-input-img-block-inner">
2219 <div
class=
"ui-image-input-img-item">
2220 <img
class=
"ui-image-input-img"{$imageSrc}>
2233 protected function getApplication(): \
CMain
2240 protected function getImageComponent(
array $params): string
2244 $this->getApplication()->includeComponent(
'bitrix:ui.image.input',
'',
$params);
2246 return ob_get_clean();
2249 protected function getFilePropertyEditHtml(
$description, $value, $controlId,
bool $multipleForList =
null): string
2251 if ($multipleForList ===
null)
2257 $multiple = $multipleForList ?
'Y' :
'N';
2262 $this->getApplication()->IncludeComponent(
2263 'bitrix:main.file.input',
2268 'INPUT_VALUE' => $value,
2269 'MULTIPLE' => $multiple,
2270 'MODULE_ID' =>
'catalog',
2271 'ALLOW_UPLOAD' =>
'F',
2272 'ALLOW_UPLOAD_EXT' =>
$description[
'settings'][
'FILE_TYPE'],
2273 'MAX_FILE_SIZE' =>
Ini::unformatInt((
string)ini_get(
'upload_max_filesize')),
2274 'CONTROL_ID' => $controlId,
2278 return ob_get_clean();
2281 protected function getFilePropertyViewHtml(
$description, $value, $controlId,
bool $multipleForList =
null)
2284 $signer = new \Bitrix\Main\Security\Sign\Signer();
2285 $signature = $signer->getSignature($cid,
'main.file.input');
2288 if (is_array($value))
2290 foreach ($value as $elementOfValue)
2301 if ($multipleForList ===
null)
2307 $multiple = $multipleForList ?
'Y' :
'N';
2312 $this->getApplication()->IncludeComponent(
2313 'bitrix:main.field.file',
2320 'USER_TYPE_ID' => UserField\Types\FileType::USER_TYPE_ID,
2323 'MULTIPLE' => $multiple,
2324 'MANDATORY' =>
$description[
'settings'][
'IS_REQUIRED'],
2325 'SHOW_FILTER' =>
$description[
'settings'][
'FILTRABLE'],
2326 'SHOW_IN_LIST' =>
'Y',
2327 'EDIT_IN_LIST' =>
'Y',
2328 'IS_SEARCHABLE' =>
$description[
'settings'][
'SEARCHABLE'],
2331 'additionalParameters' => [
2332 'mode' =>
'main.view',
2333 'CONTEXT' =>
'UI_EDITOR',
2334 'URL_TEMPLATE' =>
'/bitrix/components/bitrix/main.file.input/ajax.php?'
2336 .
'&fileID=#file_id#'
2339 .
'&s=' . $signature,
2351 return ob_get_clean();
2354 protected function getImagePropertyEditHtml(
array $property, $value): string
2356 $inputName = $this->getFilePropertyInputName($property);
2358 if ($value && (!is_array($value) || isset($value[
'ID'])))
2365 if (!empty($value) && is_array($value))
2367 foreach ($value as $valueItem)
2369 if (is_array($valueItem))
2371 $fileId = $valueItem[
'ID'];
2375 $fileId = $valueItem;
2377 $propName = str_replace(
'n#IND#', $fileId,
$inputName);
2378 $fileValues[$propName] = $fileId;
2382 $fileType = $property[
'settings'][
'FILE_TYPE'] ??
null;
2386 'id' =>
$inputName .
'_' . random_int(1, 1000000),
2387 'description' => $property[
'settings'][
'WITH_DESCRIPTION'] ??
'Y',
2388 'allowUpload' => $fileType ?
'F' :
'I',
2389 'allowUploadExt' => $fileType,
2390 'maxCount' => ($property[
'settings'][
'MULTIPLE'] ??
'N') !==
'Y' ? 1 : null,
2393 'medialib' =>
false,
2394 'fileDialog' =>
true,
2398 return $this->getImageComponent([
2399 'FILE_SETTINGS' => $fileParams,
2400 'FILE_VALUES' => $fileValues,
2405 protected function getFilePropertyInputName(
array $property): string
2409 if (isset($property[
'settings'][
'MULTIPLE']) && $property[
'settings'][
'MULTIPLE'] ===
'Y')
2417 protected function getProductFieldValue(
array $field)
2419 $value = $this->entity->getField($field[
'originalName']);
2421 if ($field[
'originalName'] ===
'PREVIEW_TEXT')
2423 $detailTextType = $this->entity->getField(
'PREVIEW_TEXT_TYPE');
2424 if ($detailTextType !==
'html')
2430 if ($field[
'originalName'] ===
'DETAIL_TEXT')
2432 $detailTextType = $this->entity->getField(
'DETAIL_TEXT_TYPE');
2433 if ($detailTextType !==
'html')
2439 if ($field[
'originalName'] ===
'VAT_ID' && $value ===
null && !$this->entity->isNew())
2441 $value = self::NOT_SELECTED_VAT_ID_VALUE;
2446 $field[
'originalName'] ===
'ACTIVE_FROM'
2447 || $field[
'originalName'] ===
'ACTIVE_TO'
2449 && !($this instanceof GridVariationForm)
2453 $value =
$value->format(\Bitrix\Main\Type\DateTime::getFormat());
2458 $field[
'originalName'] ===
'TIMESTAMP_X'
2459 || $field[
'originalName'] ===
'DATE_CREATE'
2464 $value =
$value->format(\Bitrix\Main\Type\DateTime::getFormat());
2467 if ($field[
'originalName'] ===
'NAME-CODE')
2470 'NAME' => $this->entity->getField(
'NAME'),
2471 'CODE' => $this->entity->getField(
'CODE'),
2478 protected function getPropertyFieldValue(
array $field)
2481 $property = $this->entity->getPropertyCollection()->findById($field[
'propertyId']);
2482 $value = $property ? $property->getPropertyValueCollection()->getValues() :
null;
2484 if (!isset($field[
'type']))
2489 if ($field[
'type'] ===
'html')
2491 if ($field[
'multiple'])
2493 foreach ($value as &$item)
2495 $item = $item[
'TEXT'] ??
null;
2505 if ($field[
'multiple'])
2507 foreach ($value as $valueItemKey => $valueItem)
2511 $value[$valueItemKey] = (int)$value;
2515 $value[$valueItemKey] = $this->getSequence(
2517 $property->getSetting(
'IBLOCK_ID')
2530 $value = $this->getSequence(
2532 $property->getSetting(
'IBLOCK_ID')
2541 protected function getSequence(
int $propertyId,
int $propertyIblockId): int
2543 static $sequenceList = [];
2545 if (empty($sequenceList[$propertyId]))
2547 $sequence = new \CIBlockSequence($propertyIblockId, $propertyId);
2548 $isAjaxRequest = \Bitrix\Main\Context::getCurrent()->getRequest()->isAjaxRequest();
2549 $sequenceList[$propertyId] = $isAjaxRequest ? $sequence->getCurrent() : $sequence->getNext();
2552 return $sequenceList[$propertyId];
2557 static $measures =
null;
2559 if ($measures ===
null)
2570 $measures = \Bitrix\Catalog\MeasureTable::getList(
$params)->fetchAll();
2576 protected function getVats():
array
2578 static $vats =
null;
2582 $vats = Catalog\VatTable::getList([
2583 'select' => [
'ID',
'NAME',
'RATE',
'EXCLUDE_VAT'],
2584 'filter' => [
'=ACTIVE' =>
'Y'],
2591 protected function getDefaultVat():
array
2593 $iblockVatId = $this->entity->getIblockInfo()->getVatId();
2595 foreach ($this->getVats() as
$vat)
2597 if ((
int)
$vat[
'ID'] === $iblockVatId)
2599 $vat[
'NAME'] = Loc::getMessage(
2600 'CATALOG_C_F_DEFAULT',
2609 'ID' => self::NOT_SELECTED_VAT_ID_VALUE,
2611 'EXCLUDE_VAT' =>
null,
2612 'NAME' => Loc::getMessage(
2613 'CATALOG_C_F_DEFAULT',
2614 [
'#VALUE#' => Loc::getMessage(
'CATALOG_PRODUCT_CARD_VARIATION_GRID_NOT_SELECTED')]
2619 protected function getCustomControlParameters(
string $fieldName):
array
2622 'view' => $fieldName .
'[VIEW_HTML]',
2623 'edit' => $fieldName .
'[EDIT_HTML]',
2624 'editList' => $fieldName .
'[EDIT_HTML_LIST]',
2625 'viewList' => $fieldName .
'[VIEW_HTML_LIST]',
2629 protected function getUserFieldType(
array $userField): string
2631 $isMultiple = $userField[
'MULTIPLE'] ===
'Y';
2632 switch ($userField[
'USER_TYPE_ID'])
2634 case UserField\Types\BooleanType::USER_TYPE_ID:
2635 $result = Control\Type::BOOLEAN;
2637 case UserField\Types\DateTimeType::USER_TYPE_ID:
2638 case UserField\Types\DateType::USER_TYPE_ID:
2639 $result = $isMultiple ? Control\Type::MULTI_DATETIME : Control\Type::DATETIME;
2641 case UserField\Types\DoubleType::USER_TYPE_ID:
2642 case UserField\Types\IntegerType::USER_TYPE_ID:
2643 $result = $isMultiple ? Control\Type::MULTI_NUMBER : Control\Type::NUMBER;
2645 case UserField\Types\EnumType::USER_TYPE_ID:
2646 $result = $isMultiple ? Control\Type::MULTI_LIST : Control\Type::LIST;
2648 case UserField\Types\FileType::USER_TYPE_ID:
2649 $result = Control\Type::CUSTOM;
2651 case UserField\Types\StringFormattedType::USER_TYPE_ID:
2652 $result = Control\Type::TEXTAREA;
2654 case UserField\Types\StringType::USER_TYPE_ID:
2655 $result = $isMultiple ? Control\Type::MULTI_TEXT : Control\Type::TEXT;
2657 case UserField\Types\UrlType::USER_TYPE_ID:
2662 Loader::includeModule(
'highloadblock')
2666 $result = $isMultiple ? Control\Type::MULTI_LIST : Control\Type::LIST;