1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
baseform.php
См. документацию.
1<?php
2
4
16use Bitrix\Catalog\v2\Property\Property;
17use Bitrix\Crm;
23use Bitrix\Main;
38use Bitrix\Main\Page\Asset;
43use Bitrix\Highloadblock as Highload;
45
46abstract class BaseForm
47{
48 public const GRID_FIELD_PREFIX = 'SKU_GRID_';
49 public const SERVICE_GRID_PREFIX = 'SERVICE_GRID_';
50 public const PROPERTY_FIELD_PREFIX = 'PROPERTY_';
51 public const PRICE_FIELD_PREFIX = 'CATALOG_GROUP_';
52 public const CURRENCY_FIELD_PREFIX = 'CATALOG_CURRENCY_';
53 public const MORE_PHOTO = 'MORE_PHOTO';
54 public const NOT_SELECTED_VAT_ID_VALUE = 'D';
55
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';
60
61
62 protected const CONTROL_NAME_WITH_CODE = 'name-code';
63 protected const CONTROL_IBLOCK_SECTION = 'iblock_section';
64
65 public const SCOPE_SHOP = 'shop';
66 public const SCOPE_CRM = 'crm';
67
68 public const CREATION_MODE = 'CREATION';
69 public const EDIT_MODE = 'EDIT';
70
72 protected $entity;
74 protected array $params = [];
75
77 protected ?array $descriptions = null;
79 protected ?array $propertyDescriptions = null;
80
82 protected $urlBuilder;
83
84 protected bool $crmIncluded = false;
85
88
90 {
91 $this->crmIncluded = Loader::includeModule('crm');
92 $this->accessController = AccessController::getCurrent();
93 $this->entity = $entity;
94 $this->params = $this->getPreparedParams($params);
95
96 $this->initUrlBuilder();
97 }
98
99 protected function getPreparedParams(array $params): array
100 {
101 $allowedBuilderTypes = [
104 ];
105 $allowedScopeList = [
106 self::SCOPE_SHOP,
107 ];
108 if ($this->crmIncluded)
109 {
110 $allowedBuilderTypes[] = Crm\Product\Url\ProductBuilder::TYPE_ID;
111 $allowedScopeList[] = self::SCOPE_CRM;
112 }
113
114 $params['BUILDER_CONTEXT'] = (string)($params['BUILDER_CONTEXT'] ?? '');
115 if (!in_array($params['BUILDER_CONTEXT'], $allowedBuilderTypes, true))
116 {
117 $params['BUILDER_CONTEXT'] = Url\ShopBuilder::TYPE_ID;
118 }
119
120 $params['SCOPE'] = (string)($params['SCOPE'] ?? '');
121 if ($params['SCOPE'] === '')
122 {
123 $params['SCOPE'] = $this->getScopeByUrl();
124 }
125
126 if (!in_array($params['SCOPE'], $allowedScopeList))
127 {
128 $params['SCOPE'] = self::SCOPE_SHOP;
129 }
130
131 $params['MODE'] = $params['MODE'] ?? '';
132 if ($params['MODE'] !== self::CREATION_MODE && $params['MODE'] !== self::EDIT_MODE)
133 {
134 $params['MODE'] = $this->entity->isNew() ? self::CREATION_MODE : self::EDIT_MODE;
135 }
136
137 return $params;
138 }
139
140 protected function isEntityCreationForm(): bool
141 {
142 return $this->params['MODE'] === self::CREATION_MODE;
143 }
144
145 protected function getScopeByUrl(): string
146 {
147 $result = '';
148
149 $currentPath = Context::getCurrent()->getRequest()->getRequestUri();
150 if (strncmp($currentPath, '/shop/', 6) === 0)
151 {
152 $result = self::SCOPE_SHOP;
153 }
154 elseif ($this->crmIncluded)
155 {
156 if (strncmp($currentPath, '/crm/', 5) === 0)
157 {
158 $result = self::SCOPE_CRM;
159 }
160 }
161
162 return $result;
163 }
164
165 protected function initUrlBuilder(): void
166 {
167 $this->urlBuilder = BuilderManager::getInstance()->getBuilder($this->params['BUILDER_CONTEXT']);
168 $this->urlBuilder->setIblockId($this->entity->getIblockId());
169 }
170
176 public function isCardAllowed(): bool
177 {
178 switch ($this->params['SCOPE'])
179 {
180 case self::SCOPE_SHOP:
182 break;
183 case self::SCOPE_CRM:
184 $result = false;
185 if ($this->crmIncluded)
186 {
187 $result = Crm\Settings\LayoutSettings::getCurrent()->isFullCatalogEnabled();
188 }
189 break;
190 default:
191 $result = false;
192 break;
193 }
194
195 return $result;
196 }
197
203 public function isReadOnly(): bool
204 {
205 if (State::isExternalCatalog())
206 {
207 return true;
208 }
209
210 return
211 !$this->accessController->check(ActionDictionary::ACTION_PRODUCT_CARD_EDIT)
212 && !$this->isAllowedEditFields()
213 ;
214 }
215
221 public function isAllowedEditFields(): bool
222 {
223 if ($this->isEntityCreationForm())
224 {
225 return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_ADD);
226 }
227
228 return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_EDIT);
229 }
230
236 public function isCardSettingsEditable(): bool
237 {
238 return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_CARD_EDIT);
239 }
240
246 public function isEnabledSetSettingsForAll(): bool
247 {
248 return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_CARD_SETTINGS_FOR_USERS_SET);
249 }
250
256 public function isPricesEditable(): bool
257 {
258 return
259 (
260 $this->accessController->check(ActionDictionary::ACTION_PRICE_EDIT)
261 || $this->isEntityCreationForm()
262 )
263 && $this->isAllowedEditFields()
264 ;
265 }
266
272 public function isPurchasingPriceAllowed(): bool
273 {
274 return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_PURCHASE_INFO_VIEW);
275 }
276
282 public function isVisibilityEditable(): bool
283 {
284 return
285 $this->accessController->check(ActionDictionary::ACTION_PRODUCT_PUBLIC_VISIBILITY_SET)
286 && $this->isAllowedEditFields()
287 ;
288 }
289
295 public function isInventoryManagementAccess(): bool
296 {
297 return $this->accessController->check(ActionDictionary::ACTION_INVENTORY_MANAGEMENT_ACCESS);
298 }
299
300 protected function prepareFieldName(string $name): string
301 {
302 return $name;
303 }
304
310 public function getControllers(): array
311 {
312 return [
313 [
314 'name' => 'FIELD_CONFIGURATOR_CONTROLLER',
315 'type' => 'field_configurator',
316 'config' => [],
317 ],
318 [
319 'name' => 'GOOGLE_MAP_CONTROLLER',
320 'type' => 'google_map',
321 'config' => [],
322 ],
323 [
324 'name' => 'EMPLOYEE_CONTROLLER',
325 'type' => 'employee',
326 'config' => [],
327 ],
328 [
329 'name' => 'VARIATION_LINK_CONTROLLER',
330 'type' => 'variation_link',
331 'config' => [],
332 ],
333 [
334 'name' => 'USER_CONTROLLER',
335 'type' => 'user',
336 'config' => [],
337 ],
338 [
339 'name' => 'CRM_CONTROLLER',
340 'type' => 'binding_to_crm_element',
341 'config' => [],
342 ],
343 [
344 'name' => 'IBLOCK_ELEMENT_CONTROLLER',
345 'type' => 'iblock_element',
346 'config' => [],
347 ],
348 ];
349 }
350
358 public function getValues(bool $allowDefaultValues = true, array $descriptions = null): array
359 {
360 $values = [];
361 if ($descriptions === null)
362 {
364 }
365
366 if ($allowDefaultValues)
367 {
368 foreach ($descriptions as $field)
369 {
370 $values[$field['name']] = $this->getFieldValue($field)
371 ?? $field['defaultValue']
372 ?? '';
373 }
374 }
375 else
376 {
377 foreach ($descriptions as $field)
378 {
379 $values[$field['name']] = $this->getFieldValue($field) ?? '';
380 }
381 }
382
383 $additionalValues = $this->getAdditionalValues($values, $descriptions);
384
385 if (!empty($additionalValues))
386 {
387 $values = array_merge($values, $additionalValues);
388 }
389
390 return $values;
391 }
392
393 public function getVariationGridId(): string
394 {
395 $iblockInfo = ServiceContainer::getIblockInfo($this->entity->getIblockId());
396
397 if ($iblockInfo)
398 {
399 return 'catalog-product-variation-grid-' . $iblockInfo->getProductIblockId();
400 }
401
402 return 'catalog-product-variation-grid';
403 }
404
405 public function getVariationGridClassName(): string
406 {
407 return GridVariationForm::class;
408 }
409
410 public function getVariationGridJsComponentName(): string
411 {
412 return 'BX.Catalog.VariationGrid';
413 }
414
415 public function getCardSettings(): array
416 {
417 $gridColumnSettings = $this->getCardSettingsItems();
418
419 $activeSettings = [];
420 $options = new \Bitrix\Main\Grid\Options($this->getVariationGridId());
421 $allUsedColumns = $options->getUsedColumns();
422 if (!empty($allUsedColumns))
423 {
424 foreach ($gridColumnSettings as $setting => $columns)
425 {
426 if (empty(array_diff($columns['ITEMS'], $allUsedColumns)))
427 {
428 $activeSettings[] = $setting;
429 }
430 }
431 }
432
433 $config = $this->getCardUserConfig();
434 if (!empty($config['CATALOG_PARAMETERS']))
435 {
436 $activeSettings[] = 'CATALOG_PARAMETERS';
437 }
438
439 $items = [];
440 $settingList = array_keys($gridColumnSettings);
441 if ($this->entity->getType() !== ProductTable::TYPE_SERVICE)
442 {
443 $settingList = array_merge(
444 $settingList,
445 [
446 'CATALOG_PARAMETERS',
447 ]
448 );
449 }
450 foreach ($settingList as $setting)
451 {
452 $items[] = [
453 'id' => $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,
459 ];
460 }
461
462 $seoLink = [
463 'id' => 'SEO',
464 'title' => Loc::getMessage('CATALOG_C_F_VARIATION_SETTINGS_SEO_TITLE'),
465 'disabled' => $this->isEntityCreationForm(),
466 'disabledCheckbox' => true,
467 'desc' => '',
468 'url' => '',
469 'action' => 'slider',
470 ];
471
472 if ($this->entity->getId())
473 {
474 $seoLink['url'] = $this->urlBuilder->getElementSeoUrl($this->entity->getId());
475 }
476
477 $items[] = $seoLink;
478
479 return $items;
480 }
481
482 protected function isInventoryButtonAllowed(): bool
483 {
484 return $this->entity->getType() !== ProductTable::TYPE_SERVICE;
485 }
486
487 protected function getCardSettingsItems(): array
488 {
489 return [];
490 }
491
492 public function getCardConfigId(): string
493 {
494 $iblockInfo = ServiceContainer::getIblockInfo($this->entity->getIblockId());
495
496 if ($iblockInfo)
497 {
498 return 'catalog-entity-card-config-' . $iblockInfo->getProductIblockId();
499 }
500
501 return 'catalog-entity-card-config';
502 }
503
504 public function getCardUserConfig(): array
505 {
506 return \CUserOptions::getOption('catalog', $this->getCardConfigId(), []);
507 }
508
509 public function saveCardUserConfig(array $config): bool
510 {
511 return \CUserOptions::setOption('catalog', $this->getCardConfigId(), $config);
512 }
513
514 public function getVariationIblockId(): ?int
515 {
516 $iblockInfo = ServiceContainer::getIblockInfo($this->entity->getIblockId());
517
518 if ($iblockInfo)
519 {
520 return (int)$iblockInfo->getSkuIblockId() ?: $iblockInfo->getProductIblockId();
521 }
522
523 return null;
524 }
525
526 protected function getAdditionalValues(array $values, array $descriptions = []): array
527 {
528 $additionalValues = [];
529
530 foreach ($descriptions as $description)
531 {
532 if (!isset($description['type']) || !in_array($description['type'], ['custom', 'money', 'multimoney', 'user'], true))
533 {
534 continue;
535 }
536
537 $value = $values[$description['name']] ?? null;
538 $descriptionData = $description['data'] ?? [];
539
540 if (!empty($description['settings']['USER_TYPE']))
541 {
542 $description['settings']['PROPERTY_USER_TYPE'] = \CIBlockProperty::GetUserType(
543 $description['settings']['USER_TYPE']
544 );
545 }
546
547 $propertySettings = $description['settings'] ?? [];
548
549 if ($description['type'] === 'custom')
550 {
551 if ($this->isCustomLinkProperty($propertySettings))
552 {
553 $params = [
554 'SETTINGS' => $propertySettings,
555 'VALUE' => $value,
556 'FIELD_NAME' => $description['name'],
557 'ELEMENT_ID' => $this->entity->getId() ? (string)$this->entity->getId() : 'n' . mt_rand(),
558 ];
559 $paramsSingle = $params;
560 $paramsSingle['SETTINGS']['MULTIPLE'] = 'N';
561 $paramsMultiple = $params;
562 $paramsMultiple['SETTINGS']['MULTIPLE'] = 'Y';
563
564 $viewMethod = $propertySettings['PROPERTY_USER_TYPE'][self::USER_TYPE_GET_VIEW_METHOD] ?? null;
565 if ($viewMethod && is_callable($viewMethod))
566 {
567 $additionalValues[$descriptionData['view']] = $viewMethod($params);
568 }
569
570 $editMethod = $propertySettings['PROPERTY_USER_TYPE'][self::USER_TYPE_GET_EDIT_METHOD] ?? null;
571 if ($editMethod && is_callable($editMethod))
572 {
573 $additionalValues[$descriptionData['edit']] = $editMethod($params);
574 $additionalValues[$descriptionData['editList']]['SINGLE'] = $editMethod($paramsSingle);
575 $additionalValues[$descriptionData['editList']]['MULTIPLE'] = $editMethod($paramsMultiple);
576 }
577 }
578 elseif ($propertySettings['PROPERTY_TYPE'] === PropertyTable::TYPE_ELEMENT)
579 {
580 $namesList = [];
582 if (!empty($value))
583 {
584 $elementData = ElementTable::getList([
585 'select' => ['NAME'],
586 'filter' => ['ID' => $value],
587 ]);
588 while ($element = $elementData->fetch())
589 {
590 $namesList[] = $element['NAME'];
591 }
592 unset($element, $elementData);
593 }
594 $viewValue = implode(', ', $namesList);
595 $additionalValues[$descriptionData['view']] = HtmlFilter::encode($viewValue);
596 $paramsSingle = $propertySettings;
597 $paramsSingle['MULTIPLE'] = 'N';
598 $paramsMultiple = $propertySettings;
599 $paramsMultiple['MULTIPLE'] = 'Y';
600 $propertyConfig = [
601 'FIELD_NAME' => $description['name'],
602 'CHANGE_EVENTS' => [
603 'onChangeIblockElement',
604 ],
605 ];
606 $additionalValues[$descriptionData['edit']] = Iblock\UI\Input\Element::renderSelector(
607 $propertySettings,
608 $value,
609 $propertyConfig
610 );
611 $additionalValues[$descriptionData['editList']]['SINGLE'] = Iblock\UI\Input\Element::renderSelector(
612 $paramsSingle,
613 $value,
614 $propertyConfig
615 );
616 $additionalValues[$descriptionData['editList']]['MULTIPLE'] = Iblock\UI\Input\Element::renderSelector(
617 $paramsMultiple,
618 $value,
619 $propertyConfig
620 );
621 }
622 elseif ($propertySettings['PROPERTY_TYPE'] === PropertyTable::TYPE_SECTION)
623 {
624 $namesList = [];
626 if (!empty($value))
627 {
628 $elementData = Iblock\SectionTable::getList([
629 'select' => ['NAME'],
630 'filter' => ['ID' => $value],
631 ]);
632 while ($element = $elementData->fetch())
633 {
634 $namesList[] = $element['NAME'];
635 }
636 unset($element, $elementData);
637 }
638 $viewValue = implode(', ', $namesList);
639 $additionalValues[$descriptionData['view']] = HtmlFilter::encode($viewValue);
640 $paramsSingle = $propertySettings;
641 $paramsSingle['MULTIPLE'] = 'N';
642 $paramsMultiple = $propertySettings;
643 $paramsMultiple['MULTIPLE'] = 'Y';
644 $propertyConfig = [
645 'FIELD_NAME' => $description['name'],
646 'CHANGE_EVENTS' => [
647 'onChangeIblockElement',
648 ],
649 ];
650 $additionalValues[$descriptionData['edit']] = Iblock\UI\Input\Section::renderSelector(
651 $propertySettings,
652 $value,
653 $propertyConfig
654 );
655 $additionalValues[$descriptionData['editList']]['SINGLE'] = Iblock\UI\Input\Section::renderSelector(
656 $paramsSingle,
657 $value,
658 $propertyConfig
659 );
660 $additionalValues[$descriptionData['editList']]['MULTIPLE'] = Iblock\UI\Input\Section::renderSelector(
661 $paramsMultiple,
662 $value,
663 $propertyConfig
664 );
665 }
666 elseif ($propertySettings['PROPERTY_TYPE'] === PropertyTable::TYPE_FILE)
667 {
668 if ($description['propertyCode'] === self::MORE_PHOTO)
669 {
670 $value = $this->getEntityViewPictureValues($this->entity);
671 $editValue = $this->getEntityEditPictureValues($this->entity);
672
673 if (!$description['multiple'] && isset($value[0]))
674 {
675 $value = $value[0];
676 $editValue = $editValue[0];
677 }
678 }
679 else
680 {
681 $editValue = $value;
682 }
683
684 $isImageInput = $this->isImageProperty($description['settings']);
685
686 $descriptionSingle = $description;
687 $descriptionSingle['settings']['MULTIPLE'] = 'N';
688 $descriptionSingle['multiple'] = false;
689 $descriptionMultiple = $description;
690 $descriptionMultiple['settings']['MULTIPLE'] = 'Y';
691 $descriptionMultiple['multiple'] = true;
692
693 if ($isImageInput)
694 {
695 $additionalValues[$descriptionData['view']] = $this->getImagePropertyViewHtml($value);
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]);
701 }
702 else
703 {
704 $controlId = $description['name'] . '_uploader';
705
706 // for empty value fill as empty string - need for component extensions
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);
710
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);
714 }
715 }
716 else
717 {
718 if (
719 $propertySettings['USER_TYPE'] === 'FileMan'
720 || $propertySettings['USER_TYPE'] === 'DiskFile'
721 )
722 {
723 $value = [
724 'VALUE' => null,
725 'DESCRIPTION' => '',
726 ];
727 }
728
729 $params = [
730 'SETTINGS' => $propertySettings,
731 'VALUE' => $value,
732 'FIELD_NAME' => $description['name'],
733 'ELEMENT_ID' => $this->entity->getId() ? (string)$this->entity->getId() : 'n' . mt_rand(),
734 ];
735
736 if ($propertySettings['USER_TYPE'] === 'map_google')
737 {
738 $params['WIDTH'] = '95%';
739 $params['HEIGHT'] = '400px';
740 }
741
742 $paramsSingle = $params;
743 if ($description['multiple'])
744 {
745 $paramsSingle['VALUE'] = $value[0] ?? '';
746 }
747 else
748 {
749 $paramsSingle['VALUE'] = $value;
750 }
751 $paramsSingle['SETTINGS']['MULTIPLE'] = 'N';
752 if ($value === '')
753 {
754 $singleValueToMultiple = [];
755 }
756 else
757 {
758 $singleValueToMultiple = [$value];
759 }
760 $paramsMultiple = $params;
761 $paramsMultiple['VALUE'] = $description['multiple'] ? $value : $singleValueToMultiple;
762 $paramsMultiple['SETTINGS']['MULTIPLE'] = 'Y';
763
764 $viewMethod = $propertySettings['PROPERTY_USER_TYPE'][self::USER_TYPE_GET_VIEW_METHOD] ?? null;
765 if ($viewMethod && is_callable($viewMethod))
766 {
767 $additionalValues[$descriptionData['viewList']]['SINGLE'] = $viewMethod($paramsSingle);
768 $additionalValues[$descriptionData['viewList']]['MULTIPLE'] = $viewMethod($paramsMultiple);
769 $additionalValues[$descriptionData['view']] = $viewMethod($params);
770 }
771
772 $editMethod = $propertySettings['PROPERTY_USER_TYPE'][self::USER_TYPE_GET_EDIT_METHOD] ?? null;
773 if ($editMethod && is_callable($editMethod))
774 {
775 $additionalValues[$descriptionData['editList']]['SINGLE'] = $editMethod($paramsSingle);
776 $additionalValues[$descriptionData['editList']]['MULTIPLE'] = $editMethod($paramsMultiple);
777 $additionalValues[$descriptionData['edit']] = $editMethod($params);
778 }
779 }
780 }
781 elseif (in_array($description['type'], ['money', 'multimoney'], true) && Loader::includeModule('currency'))
782 {
783 $formatMethod = $propertySettings['PROPERTY_USER_TYPE'][self::USER_TYPE_FORMAT_VALUE_METHOD] ?? null;
784 if ($formatMethod && is_callable($formatMethod))
785 {
786 if ($description['type'] === 'money')
787 {
788 $additionalMoneyValues = $this->getAdditionalMoneyValues($value, $formatMethod);
789
790 $additionalValues[$descriptionData['currencyCode']] = $additionalMoneyValues['currencyCode'];
791 $additionalValues[$descriptionData['amount']] = $additionalMoneyValues['amount'];
792 $additionalValues[$descriptionData['formatted']] = $additionalMoneyValues['formatted'];
793 $additionalValues[$descriptionData['formattedWithCurrency']] = $additionalMoneyValues['formattedWithCurrency'];
794 }
795 else
796 {
797 foreach ($value as $currentValueElement)
798 {
799 $additionalMoneyValues = $this->getAdditionalMoneyValues($currentValueElement, $formatMethod);
800
801 $additionalValues[$descriptionData['currencyCode']][] = $additionalMoneyValues['currencyCode'];
802 $additionalValues[$descriptionData['amount']][] = $additionalMoneyValues['amount'];
803 $additionalValues[$descriptionData['formatted']][] = $additionalMoneyValues['formatted'];
804 $additionalValues[$descriptionData['formattedWithCurrency']][] = $additionalMoneyValues['formattedWithCurrency'];
805 }
806 }
807 }
808 }
809 elseif ($description['type'] === 'user')
810 {
812 'filter' => ['=ID' => $value],
813 'select' => [
814 'ID', 'LOGIN', 'PERSONAL_PHOTO',
815 'NAME', 'SECOND_NAME', 'LAST_NAME',
816 'WORK_POSITION',
817 ],
818 'limit' => 1,
819 ]);
820
821 if ($user = $userData->fetch())
822 {
823 $pathToProfile = $this->params['PATH_TO']['USER_PROFILE'];
824 if ($pathToProfile)
825 {
826 $additionalValues['PATH_TO_USER_PROFILE'] = $pathToProfile;
827 $additionalValues['PATH_TO_' . $description['name']] = \CComponentEngine::MakePathFromTemplate(
828 $pathToProfile,
829 ['user_id' => $user['ID']]
830 );
831 }
832 $additionalValues[$description['name'] . '_PERSONAL_PHOTO'] = $user['PERSONAL_PHOTO'];
833 $additionalValues[$description['name'] . '_WORK_POSITION'] = $user['WORK_POSITION'];
834
835 $additionalValues[$description['name'] . '_FORMATTED_NAME'] = \CUser::FormatName(
836 \CSite::GetNameFormat(false),
837 [
838 'LOGIN' => $user['LOGIN'],
839 'NAME' => $user['NAME'],
840 'LAST_NAME' => $user['LAST_NAME'],
841 'SECOND_NAME' => $user['SECOND_NAME'],
842 ],
843 true,
844 false
845 );
846
847 if ((int)$user['PERSONAL_PHOTO'] > 0)
848 {
849 $file = new \CFile();
850 $fileInfo = $file->ResizeImageGet(
851 (int)$user['PERSONAL_PHOTO'],
852 ['width' => 60, 'height' => 60],
854 );
855 if (is_array($fileInfo) && isset($fileInfo['src']))
856 {
857 $additionalValues[$description['name'] . '_PHOTO_URL'] = $fileInfo['src'];
858 }
859 }
860 }
861 }
862 }
863
864 return $additionalValues;
865 }
866
867 public function isImageProperty(array $propertySettings): bool
868 {
869 $fileTypes = (string)$propertySettings['FILE_TYPE'];
870 $imageExtensions = explode(',', \CFile::GetImageExtensions());
871 $fileExtensions = explode(',', $fileTypes);
872 $fileExtensions = array_map('trim', $fileExtensions);
873
874 $diffExtensions = array_diff($fileExtensions, $imageExtensions);
875 return empty($diffExtensions);
876 }
877
878 private function isCustomLinkProperty(array $property): bool
879 {
880 if (!isset($property['USER_TYPE']))
881 {
882 return false;
883 }
884
885 $userTypes = [
888 'employee' => true,
890 ];
891
892 return isset($userTypes[$property['USER_TYPE']]);
893 }
894
895 private function getAdditionalMoneyValues(string $value, callable $formatMethod): array
896 {
897 $additionalValues = [];
898
899 $formattedValues = $formatMethod($value);
900 $amount = $formattedValues['AMOUNT'];
901 if ($formattedValues['AMOUNT'] !== '' && $formattedValues['DECIMALS'] !== '')
902 {
903 $amount .= '.' . $formattedValues['DECIMALS'];
904 }
905 $currency = $formattedValues['CURRENCY'];
906
907 $additionalValues['currencyCode'] = $currency;
908 $additionalValues['amount'] = $amount;
909 $additionalValues['formatted'] = \CCurrencyLang::CurrencyFormat($amount, $currency, false);
910 $additionalValues['formattedWithCurrency'] = \CCurrencyLang::CurrencyFormat($amount, $currency, true);
911
912 return $additionalValues;
913 }
914
915 private function getImageValuesForEntity(BaseIblockElementEntity $entity): array
916 {
917 $values = [];
918
919 if ($entity instanceof HasPropertyCollection)
920 {
921 $morePhotoProperty = $entity->getPropertyCollection()->findByCode(self::MORE_PHOTO);
922 if ($morePhotoProperty)
923 {
924 $morePhotoValues = $morePhotoProperty->getPropertyValueCollection()->getValues();
925 if (!empty($morePhotoValues))
926 {
927 if (!is_array($morePhotoValues))
928 {
929 $morePhotoValues = [$morePhotoValues];
930 }
931 $values = array_merge($values, $morePhotoValues);
932 }
933 }
934 }
935
936 $previewPicture = $entity->getField('PREVIEW_PICTURE');
937 if ($previewPicture)
938 {
939 $values = array_merge([$previewPicture], $values);
940 }
941
942 $detailPicture = $entity->getField('DETAIL_PICTURE');
943 if ($detailPicture)
944 {
945 $values = array_merge([$detailPicture], $values);
946 }
947
948 return $values;
949 }
950
951 private function getEntityEditPictureValues(BaseIblockElementEntity $entity): array
952 {
953 return $this->getImageValuesForEntity($entity);
954 }
955
956 private function getEntityViewPictureValues(BaseIblockElementEntity $entity): array
957 {
958 return $this->getImageValuesForEntity($entity);
959 }
960
961 protected function getFieldValue(array $field)
962 {
963 if ($field['entity'] === 'product')
964 {
965 return $this->getProductFieldValue($field);
966 }
967
968 if ($field['entity'] === 'property')
969 {
970 return $this->getPropertyFieldValue($field);
971 }
972
973 return null;
974 }
975
976 public function getConfig(): array
977 {
978 $config = $this->collectFieldConfigs();
979
980 foreach ($config as &$column)
981 {
982 usort(
983 $column['elements'],
984 static function ($a, $b)
985 {
986 $sortA = $a['sort'] ?? PHP_INT_MAX;
987 $sortB = $b['sort'] ?? PHP_INT_MAX;
988
989 return $sortA <=> $sortB;
990 }
991 );
992 }
993
994 return array_values($config);
995 }
996
1000 public function getHiddenFields(): array
1001 {
1002 $hiddenFields = [];
1003
1004 if ($this->isQuantityTraceSettingDisabled())
1005 {
1006 $hiddenFields[] = 'QUANTITY_TRACE';
1007 }
1008
1009 return $hiddenFields;
1010 }
1011
1015 public function isQuantityTraceSettingDisabled(): bool
1016 {
1017 $isQuantityTraceExplicitlyDisabled = $this->entity->getField('QUANTITY_TRACE') === 'N';
1018 $isWithOrdersMode = Loader::includeModule('crm') && \CCrmSaleHelper::isWithOrdersMode();
1019 $isInventoryManagementUsed = State::isUsedInventoryManagement();
1020
1021 return (!$isWithOrdersMode && !$isInventoryManagementUsed)
1022 || ($isInventoryManagementUsed && !$isQuantityTraceExplicitlyDisabled);
1023 }
1024
1025 protected function collectFieldConfigs(): array
1026 {
1027 $leftWidth = 30;
1028
1029 $result = [
1030 'left' => [
1031 'name' => 'left',
1032 'type' => 'column',
1033 'data' => [
1034 'width' => $leftWidth,
1035 ],
1036 'elements' => [
1037 [
1038 'name' => 'main',
1039 'title' => Loc::getMessage('CATALOG_C_F_MAIN_SECTION_TITLE'),
1040 'type' => 'section',
1041 'elements' => $this->getMainConfigElements(),
1042 'data' => [
1043 'isRemovable' => false,
1044 ],
1045 'sort' => 100,
1046 ],
1047 [
1048 'name' => 'properties',
1049 'title' => Loc::getMessage('CATALOG_C_F_PROPERTIES_SECTION_TITLE'),
1050 'type' => 'section',
1051 'elements' => $this->getPropertiesConfigElements(),
1052 'data' => [
1053 'isRemovable' => false,
1054 ],
1055 'sort' => 200,
1056 ],
1057 ],
1058 ],
1059 'right' => [
1060 'name' => 'right',
1061 'type' => 'column',
1062 'data' => [
1063 'width' => 100 - $leftWidth,
1064 ],
1065 'elements' => [],
1066 ],
1067 ];
1068
1069 $catalogParameters = $this->getCatalogParametersSectionConfig();
1070 if (!empty($catalogParameters))
1071 {
1072 $result['right']['elements'][] = $catalogParameters;
1073 }
1074
1075 return $result;
1076 }
1077
1078 protected function getMainConfigElements(): array
1079 {
1080 return array_merge(
1081 [
1082 ['name' => 'NAME-CODE'],
1083 ['name' => 'DETAIL_TEXT'],
1084 ],
1085 Product\SystemField::getFieldsByRestrictions(
1086 [
1087 'TYPE' => $this->entity->getType(),
1088 'IBLOCK_ID' => $this->entity->getIblockId(),
1089 ],
1090 [
1091 'RESULT_MODE' => Product\SystemField::DESCRIPTION_MODE_UI_FORM_EDITOR,
1092 ]
1093 )
1094 );
1095 }
1096
1098 {
1099 $catalogParameters = [
1100 ['name' => 'QUANTITY_TRACE'],
1101 ['name' => 'CAN_BUY_ZERO'],
1102 ['name' => 'SUBSCRIBE'],
1103 ];
1104
1105 if ($this->isQuantityTraceSettingDisabled())
1106 {
1107 array_shift($catalogParameters);
1108 }
1109
1110 return [
1111 'name' => 'catalog_parameters',
1112 'title' => Loc::getMessage('CATALOG_C_F_STORE_SECTION_TITLE'),
1113 'type' => 'section',
1114 'elements' => $catalogParameters,
1115 'data' => [
1116 'isRemovable' => false,
1117 ],
1118 'sort' => 200,
1119 ];
1120 }
1121
1122 public function getDescriptions(): array
1123 {
1124 if ($this->descriptions === null)
1125 {
1126 $this->descriptions = $this->buildDescriptions();
1127 }
1128
1129 return $this->descriptions;
1130 }
1131
1132 protected function buildDescriptions(): array
1133 {
1134 $fieldBlocks = [];
1135
1136 $fieldBlocks[] = $this->getTableDescriptions($this->getElementTableMap());
1137 $fieldBlocks[] = $this->getTableDescriptions(ProductTable::getMap());
1138 $fieldBlocks[] = $this->getIblockPropertiesDescriptions();
1139 $fieldBlocks[] = $this->getProductSystemFieldDescriptions();
1140 $fieldBlocks[] = $this->getUserFieldDescriptions();
1141
1142 return array_merge(...$fieldBlocks);
1143 }
1144
1145 protected function getElementTableMap(): array
1146 {
1147 $elementTableMap = ElementTable::getMap();
1148 unset($elementTableMap['NAME'], $elementTableMap['CODE']);
1149
1150 return $elementTableMap;
1151 }
1152
1153 protected function getNameCodeDescription(): array
1154 {
1155 return [
1156 [
1157 'entity' => 'product',
1158 'name' => 'NAME-CODE',
1159 'originalName' => 'NAME-CODE',
1160 'title' => Loc::getMessage('ELEMENT_ENTITY_NAME_FIELD'),
1161 'type' => 'name-code',
1162 'editable' => $this->isAllowedEditFields(),
1163 'required' => 'true',
1164 'placeholders' => [
1165 'creation' => Loc::getMessage('CATALOG_C_F_NEW_PRODUCT_PLACEHOLDER'),
1166 ],
1167 'defaultValue' => null,
1168 'optionFlags' => 1,
1169 ],
1170 ];
1171 }
1172
1173 private function getTableDescriptions(array $tableMap): array
1174 {
1175 $descriptions = [];
1176
1177 $allowedFields = $this->getTableElementsWhiteList();
1178
1180 foreach ($tableMap as $field)
1181 {
1182 $fieldName = $field->getName();
1183
1184 if (!isset($allowedFields[$fieldName]))
1185 {
1186 continue;
1187 }
1188
1189 $description = [
1190 'entity' => 'product',
1191 'name' => $this->prepareFieldName($fieldName),
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(),
1199 'optionFlags' => 1, // showAlways
1200 ];
1201
1202 if ($field instanceof EnumField)
1203 {
1204 if ($this->isSpecificCatalogField($fieldName))
1205 {
1206 $items = $this->getCatalogEnumFields($field->getName());
1207 }
1208 else
1209 {
1210 $items = $this->getCommonEnumFields($field);
1211 }
1212
1213 $description['data']['items'] = $items;
1214 }
1215
1216 if ($description['type'] === 'custom')
1217 {
1218 $description['data'] += $this->getCustomControlParameters($description['name']);
1219 }
1220 elseif ($description['type'] === 'user')
1221 {
1222 $description['data'] = [
1223 'enableEditInView' => false,
1224 'formated' => $description['name'] . '_FORMATTED_NAME',
1225 'position' => $description['name'] . '_WORK_POSITION',
1226 'photoUrl' => $description['name'] . '_PHOTO_URL',
1227 'showUrl' => 'PATH_TO_' . $description['name'],
1228 'pathToProfile' => 'PATH_TO_USER_PROFILE',
1229 ];
1230 }
1231 elseif ($fieldName === 'MEASURE')
1232 {
1233 $measureList = [];
1234 $defaultMeasure = null;
1235
1236 foreach ($this->getMeasures() as $measure)
1237 {
1238 $measureId = (int)$measure['ID'];
1239 $measureTitle = $measure['MEASURE_TITLE'];
1240
1241 if (empty($measureTitle))
1242 {
1243 $measureTitle = \CCatalogMeasureClassifier::getMeasureTitle($measure['CODE']);
1244 }
1245
1246 $measureList[] = [
1247 'NAME' => HtmlFilter::encode($measureTitle),
1248 'VALUE' => $measureId,
1249 ];
1250
1251 if ($measure['IS_DEFAULT'] === 'Y')
1252 {
1253 $defaultMeasure = $measureId;
1254 }
1255 }
1256
1257 $description['defaultValue'] = $defaultMeasure;
1258 $description['data']['items'] = $measureList;
1259 $description['type'] = 'list';
1260 }
1261 elseif ($fieldName === 'VAT_ID')
1262 {
1263 $defaultVat = $this->getDefaultVat();
1264 $description['defaultValue'] = $defaultVat['ID'];
1265
1266 $vatList[] = [
1267 'VALUE' => $defaultVat['ID'],
1268 'NAME' => $defaultVat['NAME'],
1269 ];
1270
1271 if ($defaultVat['ID'] !== self::NOT_SELECTED_VAT_ID_VALUE && !Loader::includeModule('bitrix24'))
1272 {
1273 $vatList[] = [
1274 'VALUE' => self::NOT_SELECTED_VAT_ID_VALUE,
1275 'NAME' => Loc::getMessage("CATALOG_PRODUCT_CARD_VARIATION_GRID_NOT_SELECTED"),
1276 ];
1277 }
1278
1279 foreach ($this->getVats() as $vat)
1280 {
1281 if ($vat['RATE'] === $defaultVat['RATE'] && $vat['EXCLUDE_VAT'] === $defaultVat['EXCLUDE_VAT'])
1282 {
1283 continue;
1284 }
1285
1286 $vatList[] = [
1287 'VALUE' => $vat['ID'],
1288 'NAME' => htmlspecialcharsbx($vat['NAME']),
1289 ];
1290 }
1291
1292 $description['data']['items'] = $vatList;
1293 $description['type'] = 'list';
1294 }
1295 elseif ($fieldName === 'VAT_INCLUDED')
1296 {
1297 if (Option::get('catalog', 'default_product_vat_included') === 'Y')
1298 {
1299 $description['defaultValue'] = ProductTable::STATUS_YES;
1300 }
1301 }
1302 elseif ($field instanceof TextField)
1303 {
1304 $description['buttons'] = [];
1305 $description['postFormSettings'] = [
1306 'isAiImageEnabled' => false,
1307 'isDnDEnabled' => false,
1308 ];
1309
1310 if ($fieldName === 'DETAIL_TEXT')
1311 {
1312 $description['copilotIntegrationParams'] = [
1313 'isMentionUnavailable' => true,
1314 'isCopilotTextEnabledBySettings' => Settings::isTextProductCardAvailable(),
1315 'copilotParams' => [
1316 'contextId' => 'catalog_product_card_detail_description',
1317 'moduleId' => 'catalog',
1318 'category' => 'product_description',
1319 'isCopilotEnabled' => true,
1320 ],
1321 ];
1322
1323 $description['buttons'][] = 'Copilot';
1324 }
1325 }
1326
1328 }
1329
1330 return $descriptions;
1331 }
1332
1333 private function getTableElementsWhiteList(): array
1334 {
1335 static $whiteList = null;
1336
1337 if ($whiteList === null)
1338 {
1339 $whiteList = $this->getIblockElementFieldsList();
1340
1341 if ($this->showCatalogProductFields())
1342 {
1343 $whiteList = array_merge($whiteList, $this->getCatalogProductFieldsList());
1344 }
1345
1346 if ($this->showSpecificCatalogParameters())
1347 {
1348 $whiteList = array_merge($whiteList, $this->getSpecificCatalogFieldsList());
1349 }
1350
1351 if ($this->showSubscribeCatalogParameters())
1352 {
1353 $whiteList = array_diff($whiteList, ['WEIGHT', 'WIDTH', 'LENGTH', 'HEIGHT']);
1354 $whiteList = array_merge($whiteList, $this->getSubscribeCatalogFieldList());
1355 }
1356
1357 $whiteList = array_fill_keys($whiteList, true);
1358 }
1359
1360 return $whiteList;
1361 }
1362
1363 protected function getIblockElementFieldsList(): array
1364 {
1365 return [
1366 'ID',
1367 'IBLOCK_ID',
1368 // ToDo
1369 // 'IBLOCK_SECTION_ID',
1370 'TIMESTAMP_X',
1371 'MODIFIED_BY',
1372 'DATE_CREATE',
1373 'CREATED_BY',
1374 'ACTIVE',
1375 'ACTIVE_FROM',
1376 'ACTIVE_TO',
1377 'SORT',
1378 'NAME',
1379 'PREVIEW_TEXT',
1380 // 'PREVIEW_TEXT_TYPE',
1381 'DETAIL_TEXT',
1382 // 'DETAIL_TEXT_TYPE',
1383 'XML_ID',
1384 'CODE',
1385 ];
1386 }
1387
1388 protected function showCatalogProductFields(): bool
1389 {
1390 return false;
1391 }
1392
1394 {
1395 return [
1396 'QUANTITY',
1397 'QUANTITY_RESERVED',
1398 'VAT_ID',
1399 'VAT_INCLUDED',
1400 // 'PURCHASING_PRICE',
1401 // 'PURCHASING_CURRENCY',
1402 // 'BARCODE_MULTI',
1403 // 'QUANTITY_RESERVED',
1404 'WEIGHT',
1405 'WIDTH',
1406 'LENGTH',
1407 'HEIGHT',
1408 'MEASURE',
1409 // 'TYPE',
1410 // 'AVAILABLE',
1411 // 'BUNDLE',
1412 ];
1413 }
1414
1415 protected function showSpecificCatalogParameters(): bool
1416 {
1417 return false;
1418 }
1419
1420 private function getSpecificCatalogFieldsList(): array
1421 {
1422 return [
1423 'QUANTITY_TRACE',
1424 'CAN_BUY_ZERO',
1425 'SUBSCRIBE',
1426 ];
1427 }
1428
1429 private function getFieldTypeByObject(ScalarField $field): string
1430 {
1431 $fieldName = $field->getName();
1432
1433 if ($fieldName === 'PREVIEW_PICTURE' || $fieldName === 'DETAIL_PICTURE')
1434 {
1435 return 'custom';
1436 }
1437
1438 if ($fieldName === 'PREVIEW_TEXT' || $fieldName === 'DETAIL_TEXT')
1439 {
1440 return 'html';
1441 }
1442
1443 if ($fieldName === 'MODIFIED_BY' || $fieldName === 'CREATED_BY')
1444 {
1445 return 'user';
1446 }
1447
1448 switch (get_class($field))
1449 {
1450 case IntegerField::class:
1451 case FloatField::class:
1452 $fieldType = 'number';
1453 break;
1454
1455 case BooleanField::class:
1456 $fieldType = 'boolean';
1457 break;
1458
1459 case EnumField::class:
1460 $fieldType = 'list';
1461 break;
1462
1463 case DateField::class:
1464 case DatetimeField::class:
1465 $fieldType = 'datetime';
1466 break;
1467
1468 case TextField::class:
1469 $fieldType = 'textarea';
1470 break;
1471
1472 case StringField::class:
1473 default:
1474 $fieldType = 'text';
1475 }
1476
1477 return $fieldType;
1478 }
1479
1480 private function isEditableField(ScalarField $field): bool
1481 {
1482 if (!$this->isAllowedEditFields())
1483 {
1484 return false;
1485 }
1486
1487 if (in_array(
1488 $field->getName(),
1489 [
1490 'IBLOCK_ID',
1491 'MODIFIED_BY',
1492 'CREATED_BY',
1493 'AVAILABLE',
1494 'DATE_CREATE',
1495 'TIMESTAMP_X',
1496 ],
1497 true
1498 ))
1499 {
1500 return false;
1501 }
1502
1503 if (in_array($field->getName(), ['QUANTITY', 'QUANTITY_RESERVED'], true) && State::isUsedInventoryManagement())
1504 {
1505 return false;
1506 }
1507
1508 return !$field->isPrimary() && !$field->isAutocomplete();
1509 }
1510
1511 private function isRequiredField(ScalarField $field): bool
1512 {
1513 if ($field->getName() === 'IBLOCK_ID')
1514 {
1515 return false;
1516 }
1517
1518 return $field->isRequired();
1519 }
1520
1521 private function getFieldPlaceholders(ScalarField $field): ?array
1522 {
1523 if ($field->getName() === 'NAME')
1524 {
1525 return [
1526 'creation' => Loc::getMessage('CATALOG_C_F_NEW_PRODUCT_PLACEHOLDER'),
1527 ];
1528 }
1529
1530 return null;
1531 }
1532
1533 protected function showSubscribeCatalogParameters(): bool
1534 {
1535 $iblockInfo = ServiceContainer::getIblockInfo($this->entity->getIblockId());
1536
1537 if ($iblockInfo)
1538 {
1539 return $iblockInfo->hasSubscription();
1540 }
1541
1542 return false;
1543 }
1544
1545 private function getSubscribeCatalogFieldList(): array
1546 {
1547 return [
1548 'PRICE_TYPE',
1549 'RECUR_SCHEME_LENGTH',
1550 'RECUR_SCHEME_TYPE',
1551 'TRIAL_PRICE_ID',
1552 'WITHOUT_ORDER',
1553 ];
1554 }
1555
1556 private function isSpecificCatalogField(string $fieldName): bool
1557 {
1558 static $catalogEnumFields = null;
1559
1560 if ($catalogEnumFields === null)
1561 {
1562 $catalogEnumFields = array_fill_keys(
1563 $this->getSpecificCatalogFieldsList(),
1564 true
1565 );
1566 }
1567
1568 return isset($catalogEnumFields[$fieldName]);
1569 }
1570
1571 protected function getCatalogEnumFields(string $fieldName): array
1572 {
1573 $defaultValue = null;
1574
1575 switch ($fieldName)
1576 {
1577 case 'QUANTITY_TRACE':
1578 $defaultValue = Option::get('catalog', 'default_quantity_trace') === 'Y';
1579 break;
1580
1581 case 'CAN_BUY_ZERO':
1582 $defaultValue = Option::get('catalog', 'default_can_buy_zero') === 'Y';
1583 break;
1584
1585 case 'SUBSCRIBE':
1586 $defaultValue = Option::get('catalog', 'default_subscribe') === 'Y';
1587 break;
1588 }
1589
1590 return [
1591 [
1592 'NAME' => Loc::getMessage(
1593 'CATALOG_C_F_DEFAULT',
1594 [
1595 '#VALUE#' => $defaultValue
1596 ? Loc::getMessage('CATALOG_C_F_YES')
1597 : Loc::getMessage('CATALOG_C_F_NO'),
1598 ]
1599 ),
1601 ],
1602 [
1603 'NAME' => Loc::getMessage('CATALOG_C_F_YES'),
1604 'VALUE' => ProductTable::STATUS_YES,
1605 ],
1606 [
1607 'NAME' => Loc::getMessage('CATALOG_C_F_NO'),
1608 'VALUE' => ProductTable::STATUS_NO,
1609 ],
1610 ];
1611 }
1612
1613 private function getCommonEnumFields(EnumField $field): array
1614 {
1615 $items = [];
1616
1617 foreach ((array)$field->getValues() as $value)
1618 {
1619 $items[] = [
1620 'NAME' => $value,
1621 'VALUE' => $value,
1622 ];
1623 }
1624
1625 return $items;
1626 }
1627
1629 {
1630 return Product\SystemField::getUiDescriptions([
1631 'TYPE' => $this->entity->getType(),
1632 'IBLOCK_ID' => $this->entity->getIblockId(),
1633 ]);
1634 }
1635
1636 protected function getUserFieldDescriptions(): array
1637 {
1638 $filter = [
1639 '=ENTITY_ID' => ProductTable::getUfId(),
1640 ];
1641 $allSystemFields = Product\SystemField::getFieldNamesByRestrictions([]);
1642 if (!empty($allSystemFields))
1643 {
1644 $filter['!@FIELD_NAME'] = $allSystemFields;
1645 }
1646
1647 $result = [];
1648 $iterator = UserFieldTable::getList([
1649 'select' => array_merge(
1650 ['*'],
1652 ),
1653 'filter' => $filter,
1654 'order' => [
1655 'SORT' => 'ASC',
1656 'ID' => 'ASC',
1657 ],
1658 'runtime' => [
1659 UserFieldTable::getLabelsReference('', Loc::getCurrentLang()),
1660 ],
1661 ]);
1662 while ($row = $iterator->fetch())
1663 {
1664 $description = [
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),
1671 'editable' => true,
1672 'required' => $row['MANDATORY'] === 'Y',
1673 'multiple' => $row['MULTIPLE'] === 'Y',
1674 'placeholders' => null,
1675 'defaultValue' => $row['SETTINGS']['DEFAULT_VALUE'] ?? '',
1676 'optionFlags' => 1, // showAlways
1677 'options' => [
1678 'showCode' => 'true',
1679 ],
1680 'data' => [],
1681 ];
1682
1683 switch ($description['type'])
1684 {
1686 $description['data'] += $this->getCustomControlParameters($description['name']);
1687 break;
1689 case Control\Type::LIST:
1690 $description['data'] += $this->getUserFieldListItems($row);
1691 break;
1692 }
1693
1695 }
1696
1697 return $result;
1698 }
1699
1700 private function getUserFieldListItems(array $userField): array
1701 {
1702 if ($userField['USER_TYPE_ID'] === UserField\Types\EnumType::USER_TYPE_ID)
1703 {
1704 return $this->getUserFieldEnumItems($userField);
1705 }
1706 elseif (
1707 Loader::includeModule('highloadblock')
1708 && $userField['USER_TYPE_ID'] === \CUserTypeHlblock::USER_TYPE_ID
1709 )
1710 {
1711 return $this->getUserFieldHighloadblockItems($userField);
1712 }
1713
1714 return [];
1715 }
1716
1717 private function getUserFieldEnumItems(array $userField): array
1718 {
1719 $list = [];
1720
1721 $showNoValue = (
1722 $userField['MANDATORY'] !== 'Y'
1723 ||
1724 $userField['SETTINGS']['SHOW_NO_VALUE'] !== 'N'
1725 );
1726
1727 if (
1728 $showNoValue
1729 &&
1730 (
1731 $userField['SETTINGS']['DISPLAY'] !== 'CHECKBOX'
1732 ||
1733 $userField['MULTIPLE'] !== 'Y'
1734 )
1735 )
1736 {
1737 $list[] = [
1738 'ID' => '0',
1739 'VALUE' => '0',
1740 'NAME' => Loc::getMessage('CATALOG_PRODUCT_CARD_USERFIELD_MESS_EMPTY_VALUE')
1741 ];
1742 }
1743
1744 $iterator = UserField\Types\EnumType::getList($userField);
1745 while ($value = $iterator->Fetch())
1746 {
1747 $list[] = [
1748 'ID' => $value['ID'],
1749 'VALUE' => $value['ID'],
1750 'NAME' => $value['VALUE'],
1751 ];
1752 }
1753 unset($value, $iterator);
1754
1755 return (!empty($list) ? ['items' => $list] : []);
1756 }
1757
1758 private function getUserFieldHighloadblockItems(array $userField): array
1759 {
1760 $list = [];
1761 if (
1762 $userField['MANDATORY'] === 'N'
1763 && $userField['MULTIPLE'] === 'N'
1764 )
1765 {
1766 $list[] = [
1767 'ID' => '0',
1768 'VALUE' => '0',
1769 'NAME' => Loc::getMessage('CATALOG_PRODUCT_CARD_USERFIELD_MESS_EMPTY_VALUE')
1770 ];
1771 }
1772
1773 $entity = Highload\HighloadBlockTable::compileEntity($userField['SETTINGS']['HLBLOCK_ID']);
1774 $fieldsList = $entity->getFields();
1775 if (isset($fieldsList['ID']) && isset($fieldsList['UF_NAME']))
1776 {
1777 $entityDataClass = $entity->getDataClass();
1778 $iterator = $entityDataClass::getList([
1779 'select' => [
1780 'ID',
1781 'UF_NAME',
1782 ],
1783 'order' => [
1784 'UF_NAME' => 'ASC',
1785 ],
1786 ]);
1787 while ($value = $iterator->fetch())
1788 {
1789 $list[] = [
1790 'ID' => $value['ID'],
1791 'VALUE' => $value['ID'],
1792 'NAME' => $value['UF_NAME'],
1793 ];
1794 }
1795 unset($value, $iterator);
1796 unset($entityDataClass, $entity);
1797 }
1798
1799 return (!empty($list) ? ['items' => $list] : []);
1800 }
1801
1803 {
1804 if ($this->propertyDescriptions === null)
1805 {
1806 $this->propertyDescriptions = $this->buildIblockPropertiesDescriptions();
1807 }
1808
1810 }
1811
1813 {
1815 $unavailableUserTypes = $this->getUnavailableUserTypes();
1816
1817 foreach ($this->entity->getPropertyCollection() as $property)
1818 {
1819 if (in_array($property->getUserType(), $unavailableUserTypes, true))
1820 {
1821 continue;
1822 }
1823 if ($property->isActive())
1824 {
1825 $propertyDescriptions[] = $this->getPropertyDescription($property);
1826 }
1827 }
1828
1829 return $propertyDescriptions;
1830 }
1831
1832 protected function getUnavailableUserTypes(): array
1833 {
1834 return [
1835 'DiskFile',
1836 'TopicID',
1838 ];
1839 }
1840
1841 public static function preparePropertyName(string $name = ''): string
1842 {
1843 return self::PROPERTY_FIELD_PREFIX . $name;
1844 }
1845
1846 public static function preparePropertyNameFromProperty(Property $property): string
1847 {
1848 $name = $property->getCode() === self::MORE_PHOTO ? self::MORE_PHOTO : $property->getId();
1849
1850 return static::preparePropertyName($name);
1851 }
1852
1853 protected function getPropertyDescription(Property $property): array
1854 {
1855 $description = [
1856 'entity' => 'property',
1857 'name' => static::preparePropertyNameFromProperty($property),
1858 'propertyId' => $property->getId(),
1859 'propertyCode' => $property->getCode(),
1860 'title' => $property->getName(),
1861 'editable' => true,
1862 'required' => $property->isRequired(),
1863 'multiple' => $property->isMultiple(),
1864 'defaultValue' => $property->getDefaultValue(),
1865 'settings' => $property->getSettings(),
1866 'type' => null,
1867 ];
1868
1869 if ($property->getUserType() === Iblock\PropertyTable::USER_TYPE_SEQUENCE)
1870 {
1871 $userTypeSettings = $property->getSetting('USER_TYPE_SETTINGS');
1872 $description['editable'] = $userTypeSettings['write'] === 'Y';
1873 }
1874
1875 $nonEditableUserTypes = [
1876 'UserID',
1877 'FileMan',
1878 ];
1879 if (in_array($property->getUserType(), $nonEditableUserTypes, true))
1880 {
1881 $description['editable'] = false;
1882 }
1883
1884 if ($description['propertyCode'] === self::MORE_PHOTO)
1885 {
1886 $description['optionFlags'] = 1; // showAlways
1887 $description['hint'] = Loc::getMessage('CATALOG_PRODUCT_CARD_MORE_PHOTO_SIZE');
1888 $description['hintHtml'] = true;
1889 }
1890
1891 if ($description['multiple'] && !is_array($description['defaultValue']))
1892 {
1893 $description['defaultValue'] = $description['defaultValue'] === null ? [] : [$description['defaultValue']];
1894 }
1895
1896 // remove it after PropertyTable::TYPE_ELEMENT refactoring
1897 if ($property->getPropertyType() === PropertyTable::TYPE_ELEMENT)
1898 {
1899 Asset::getInstance()->addJs('/bitrix/js/main/utils.js');
1900 }
1901
1902 if ($property->getUserType())
1903 {
1904 $specificDescription = $this->getUserTypePropertyDescription($property);
1905 }
1906 else
1907 {
1908 $specificDescription = $this->getGeneralPropertyDescription($property);
1909 }
1910
1911 $specificDescription['data']['isPublic'] = $property->isPublic();
1912
1913 if (!$this->isAllowedEditFields())
1914 {
1915 unset($specificDescription['editable']);
1916 $description['editable'] = false;
1917 }
1918
1919 return array_merge($description, $specificDescription);
1920 }
1921
1922 private function getPropertyType(Property $property): string
1923 {
1924 switch ($property->getPropertyType())
1925 {
1927 // ToDo no multiple textarea right now
1928 // if ($property->isMultiple())
1929 // {
1930 // $fieldType = 'multifield';
1931 // }
1932 if ((int)$property->getSetting('ROW_COUNT') > 1)
1933 {
1934 $fieldType = 'textarea';
1935 }
1936 else
1937 {
1938 $fieldType = $property->isMultiple() ? 'multitext' : 'text';
1939 }
1940
1941 break;
1942
1944 // ToDo no multiple number right now
1945 $fieldType = $property->isMultiple() ? 'multinumber' : 'number';
1946 break;
1947
1949 $fieldType = $property->isMultiple() ? 'multilist' : 'list';
1950 break;
1951
1952 // case TextField::class:
1953 // $fieldType = 'textarea';
1954 // break;
1955
1959 $fieldType = 'custom';
1960 break;
1961
1962 default:
1963 $fieldType = 'text';
1964 }
1965
1966 return $fieldType;
1967 }
1968
1969 protected function getHiddenPropertyCodes(): array
1970 {
1971 return [];
1972 }
1973
1975 {
1976 $elements = [];
1977 $hiddenCodesMap = array_fill_keys($this->getHiddenPropertyCodes(), true);
1978 foreach ($this->entity->getPropertyCollection() as $property)
1979 {
1980 if (isset($hiddenCodesMap[$property->getCode()]))
1981 {
1982 continue;
1983 }
1984
1985 $elements[] = [
1986 'name' => static::preparePropertyNameFromProperty($property),
1987 ];
1988 }
1989
1990 return $elements;
1991 }
1992
1993 protected function getGeneralPropertyDescription(Property $property): array
1994 {
1995 $type = $this->getPropertyType($property);
1996
1997 $description = [
1998 'type' => $type,
1999 'data' => [
2000 'isProductProperty' => true,
2001 ],
2002 ];
2003
2004 if ($type === 'custom')
2005 {
2006 $name = static::preparePropertyNameFromProperty($property);
2007 $description['data'] += $this->getCustomControlParameters($name);
2008 }
2009
2010 if ($type === 'textarea')
2011 {
2012 $description['lineCount'] = (int)($property->getSetting('ROW_COUNT') ?? 1);
2013 }
2014
2015 if ($property->getPropertyType() === PropertyTable::TYPE_LIST)
2016 {
2017 $description['data']['enableEmptyItem'] = true;
2018 $description['data']['items'] = [];
2019
2020 $propertyEnumIterator = \CIBlockProperty::GetPropertyEnum(
2021 $property->getId(),
2022 [
2023 'SORT' => 'ASC',
2024 'VALUE' => 'ASC',
2025 'ID' => 'ASC',
2026 ]
2027 );
2028 while ($enum = $propertyEnumIterator->fetch())
2029 {
2030 $description['data']['items'][] = [
2031 'NAME' => $enum['VALUE'],
2032 'VALUE' => $enum['ID'],
2033 'ID' => $enum['ID'],
2034 ];
2035 }
2036
2037 if (count($description['data']['items']) === 1
2038 && $description['data']['items'][0]['NAME'] === 'Y')
2039 {
2040 $description['type'] = 'boolean';
2041 $description['data']['value'] = $description['data']['items'][0]['VALUE'];
2042 }
2043 }
2044
2045 return $description;
2046 }
2047
2048 protected function getUserTypePropertyDescription(Property $property): array
2049 {
2050 $propertySettings = $this->getPropertySettings($property);
2051
2052 if (
2053 $property->getPropertyType() === PropertyTable::TYPE_STRING
2054 && $property->getUserType() === PropertyTable::USER_TYPE_HTML
2055 )
2056 {
2057 $defaultValue = $property->getDefaultValue();
2058
2059 if ($defaultValue)
2060 {
2061 if ($property->isMultiple())
2062 {
2063 foreach ($defaultValue as &$item)
2064 {
2065 $item = $item['TEXT'] ?? null;
2066 }
2067 }
2068 else
2069 {
2070 $defaultValue = $defaultValue['TEXT'] ?? null;
2071 }
2072 }
2073
2074 return [
2075 'type' => 'html',
2076 'defaultValue' => $defaultValue,
2077 ];
2078 }
2079
2080 $userTypeMethod = $propertySettings['PROPERTY_USER_TYPE'][self::USER_TYPE_METHOD] ?? null;
2081 if ($userTypeMethod && is_callable($userTypeMethod))
2082 {
2083 $values = $property->getPropertyValueCollection()->getValues();
2084 $description = $userTypeMethod($propertySettings, $values);
2085
2086 if ($property->getCode() === 'CML2_LINK')
2087 {
2088 $description['editable'] = false;
2089 }
2090
2091 $specialTypes = ['custom', 'money', 'multimoney'];
2092 if (in_array($description['type'], $specialTypes, true))
2093 {
2094 $name = static::preparePropertyNameFromProperty($property);
2095 $descriptionData = $description['data'] ?? [];
2096
2097 if ($description['type'] === 'custom')
2098 {
2099 $descriptionData += $this->getCustomControlParameters($name);
2100 }
2101 elseif ($description['type'] === 'money' || $description['type'] === 'multimoney')
2102 {
2103 $descriptionData['affectedFields'] = [
2104 $name . '[CURRENCY]',
2105 $name . '[AMOUNT]',
2106 ];
2107 $descriptionData['currency'] = [
2108 'name' => $name . '[CURRENCY]',
2109 'items' => $this->getCurrencyList(),
2110 ];
2111 $descriptionData['amount'] = $name . '[AMOUNT]';
2112 $descriptionData['currencyCode'] = $name . '[CURRENCY]';
2113 $descriptionData['formatted'] = $name . '[FORMATTED_AMOUNT]';
2114 $descriptionData['formattedWithCurrency'] = $name . '[FORMATTED_AMOUNT_WITH_CURRENCY]';
2115 }
2116
2117 $description['data'] = $descriptionData;
2118 }
2119
2120 if (empty($description['data']))
2121 {
2122 $description['data'] = [];
2123 }
2124
2125 $description['data']['isProductProperty'] = true;
2126
2127 return $description;
2128 }
2129
2130 return [];
2131 }
2132
2133 protected function getCurrencyList(): array
2134 {
2135 static $currencyList = null;
2136
2137 if ($currencyList === null)
2138 {
2139 $currencyList = [];
2140
2141 foreach (CurrencyManager::getNameList() as $code => $name)
2142 {
2143 $currencyList[] = [
2144 'VALUE' => $code,
2145 'NAME' => htmlspecialcharsbx($name),
2146 ];
2147 }
2148 }
2149
2150 return $currencyList;
2151 }
2152
2153 protected function getPropertySettings(Property $property): array
2154 {
2155 $propertySettings = $property->getSettings();
2156
2157 if (!empty($propertySettings['USER_TYPE']))
2158 {
2159 $propertySettings['PROPERTY_USER_TYPE'] = \CIBlockProperty::GetUserType($propertySettings['USER_TYPE']);
2160 }
2161
2162 return $propertySettings;
2163 }
2164
2165 protected function getImagePropertyViewHtml($value): string
2166 {
2167 $fileCount = 0;
2168
2169 // single scalar property
2170 if (!empty($value) && !is_array($value))
2171 {
2172 $value = [$value];
2173 }
2174
2175 if (is_array($value))
2176 {
2177 $fileCount = min(count($value), 3);
2178 $value = reset($value);
2179 }
2180
2181 $imageSrc = null;
2182
2183 if (!empty($value))
2184 {
2185 $image = \CFile::GetFileArray($value);
2186 if ($image)
2187 {
2188 $imageSrc = $image['SRC'];
2189 }
2190 }
2191
2192 switch ($fileCount)
2193 {
2194 case 3:
2195 $multipleClass = ' ui-image-input-img-block-multiple';
2196 break;
2197
2198 case 2:
2199 $multipleClass = ' ui-image-input-img-block-double';
2200 break;
2201
2202 case 0:
2203 $multipleClass = ' ui-image-input-img-block-empty';
2204 break;
2205
2206 case 1:
2207 default:
2208 $multipleClass = '';
2209 break;
2210 }
2211
2212 if ($imageSrc)
2213 {
2214 $imageSrc = " src=\"{$imageSrc}\"";
2215
2216 return <<<HTML
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}>
2221 </div>
2222 </div>
2223 </div>
2224 HTML;
2225 }
2226
2227 return '';
2228 }
2229
2233 protected function getApplication(): \CMain
2234 {
2235 global $APPLICATION;
2236
2237 return $APPLICATION;
2238 }
2239
2240 protected function getImageComponent(array $params): string
2241 {
2242 ob_start();
2243
2244 $this->getApplication()->includeComponent('bitrix:ui.image.input', '', $params);
2245
2246 return ob_get_clean();
2247 }
2248
2249 protected function getFilePropertyEditHtml($description, $value, $controlId, bool $multipleForList = null): string
2250 {
2251 if ($multipleForList === null)
2252 {
2253 $multiple = $description['settings']['MULTIPLE'];
2254 }
2255 else
2256 {
2257 $multiple = $multipleForList ? 'Y' : 'N';
2258 }
2259
2260 ob_start();
2261
2262 $this->getApplication()->IncludeComponent(
2263 'bitrix:main.file.input',
2264 '.default',
2265 [
2266 'INPUT_NAME' => $description['name'],
2267 'INPUT_NAME_UNSAVED' => $description['name'] . '_tmp',
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,
2275 ]
2276 );
2277
2278 return ob_get_clean();
2279 }
2280
2281 protected function getFilePropertyViewHtml($description, $value, $controlId, bool $multipleForList = null)
2282 {
2283 $cid = FileInputUtility::instance()->registerControl('', $controlId);
2284 $signer = new \Bitrix\Main\Security\Sign\Signer();
2285 $signature = $signer->getSignature($cid, 'main.file.input');
2286 if (!empty($value))
2287 {
2288 if (is_array($value))
2289 {
2290 foreach ($value as $elementOfValue)
2291 {
2292 FileInputUtility::instance()->registerFile($cid, $elementOfValue);
2293 }
2294 }
2295 else
2296 {
2297 FileInputUtility::instance()->registerFile($cid, $value);
2298 }
2299 }
2300
2301 if ($multipleForList === null)
2302 {
2303 $multiple = $description['settings']['MULTIPLE'];
2304 }
2305 else
2306 {
2307 $multiple = $multipleForList ? 'Y' : 'N';
2308 }
2309
2310 ob_start();
2311
2312 $this->getApplication()->IncludeComponent(
2313 'bitrix:main.field.file',
2314 '',
2315 [
2316 'userField' => [
2317 'ID' => $description['settings']['ID'],
2319 'FIELD_NAME' => $description['name'],
2320 'USER_TYPE_ID' => UserField\Types\FileType::USER_TYPE_ID,
2321 'XML_ID' => $description['settings']['XML_ID'],
2322 'SORT' => $description['settings']['SORT'],
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'],
2329 'VALUE' => $value,
2330 ],
2331 'additionalParameters' => [
2332 'mode' => 'main.view',
2333 'CONTEXT' => 'UI_EDITOR',
2334 'URL_TEMPLATE' => '/bitrix/components/bitrix/main.file.input/ajax.php?'
2335 . 'mfi_mode=down'
2336 . '&fileID=#file_id#'
2337 . '&cid=' . $cid
2338 . '&sessid=' . bitrix_sessid()
2339 . '&s=' . $signature,
2340 ],
2341 ]
2342 );
2343
2344 if (empty($value))
2345 {
2346 ob_end_clean();
2347
2348 return '';
2349 }
2350
2351 return ob_get_clean();
2352 }
2353
2354 protected function getImagePropertyEditHtml(array $property, $value): string
2355 {
2356 $inputName = $this->getFilePropertyInputName($property);
2357
2358 if ($value && (!is_array($value) || isset($value['ID'])))
2359 {
2360 $value = [$value];
2361 }
2362
2363 $fileValues = [];
2364
2365 if (!empty($value) && is_array($value))
2366 {
2367 foreach ($value as $valueItem)
2368 {
2369 if (is_array($valueItem))
2370 {
2371 $fileId = $valueItem['ID'];
2372 }
2373 else
2374 {
2375 $fileId = $valueItem;
2376 }
2377 $propName = str_replace('n#IND#', $fileId, $inputName);
2378 $fileValues[$propName] = $fileId;
2379 }
2380 }
2381
2382 $fileType = $property['settings']['FILE_TYPE'] ?? null;
2383
2384 $fileParams = [
2385 'name' => $inputName,
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,
2391
2392 'upload' => true,
2393 'medialib' => false,
2394 'fileDialog' => true,
2395 'cloud' => true,
2396 ];
2397
2398 return $this->getImageComponent([
2399 'FILE_SETTINGS' => $fileParams,
2400 'FILE_VALUES' => $fileValues,
2401 'LOADER_PREVIEW' => $this->getImagePropertyViewHtml($value),
2402 ]);
2403 }
2404
2405 protected function getFilePropertyInputName(array $property): string
2406 {
2407 $inputName = $property['name'] ?? '';
2408
2409 if (isset($property['settings']['MULTIPLE']) && $property['settings']['MULTIPLE'] === 'Y')
2410 {
2411 $inputName .= '[n#IND#]';
2412 }
2413
2414 return $inputName;
2415 }
2416
2417 protected function getProductFieldValue(array $field)
2418 {
2419 $value = $this->entity->getField($field['originalName']);
2420
2421 if ($field['originalName'] === 'PREVIEW_TEXT')
2422 {
2423 $detailTextType = $this->entity->getField('PREVIEW_TEXT_TYPE');
2424 if ($detailTextType !== 'html')
2425 {
2426 $value = HtmlFilter::encode($value);
2427 }
2428 }
2429
2430 if ($field['originalName'] === 'DETAIL_TEXT')
2431 {
2432 $detailTextType = $this->entity->getField('DETAIL_TEXT_TYPE');
2433 if ($detailTextType !== 'html')
2434 {
2435 $value = HtmlFilter::encode($value);
2436 }
2437 }
2438
2439 if ($field['originalName'] === 'VAT_ID' && $value === null && !$this->entity->isNew())
2440 {
2441 $value = self::NOT_SELECTED_VAT_ID_VALUE;
2442 }
2443
2444 if (
2445 (
2446 $field['originalName'] === 'ACTIVE_FROM'
2447 || $field['originalName'] === 'ACTIVE_TO'
2448 )
2449 && !($this instanceof GridVariationForm)
2450 && !empty($value)
2451 )
2452 {
2453 $value = $value->format(\Bitrix\Main\Type\DateTime::getFormat());
2454 }
2455
2456 if (
2457 (
2458 $field['originalName'] === 'TIMESTAMP_X'
2459 || $field['originalName'] === 'DATE_CREATE'
2460 )
2461 && !empty($value)
2462 )
2463 {
2464 $value = $value->format(\Bitrix\Main\Type\DateTime::getFormat());
2465 }
2466
2467 if ($field['originalName'] === 'NAME-CODE')
2468 {
2469 $value = [
2470 'NAME' => $this->entity->getField('NAME'),
2471 'CODE' => $this->entity->getField('CODE'),
2472 ];
2473 }
2474
2475 return $value;
2476 }
2477
2478 protected function getPropertyFieldValue(array $field)
2479 {
2481 $property = $this->entity->getPropertyCollection()->findById($field['propertyId']);
2482 $value = $property ? $property->getPropertyValueCollection()->getValues() : null;
2483
2484 if (!isset($field['type']))
2485 {
2486 return $value;
2487 }
2488
2489 if ($field['type'] === 'html')
2490 {
2491 if ($field['multiple'])
2492 {
2493 foreach ($value as &$item)
2494 {
2495 $item = $item['TEXT'] ?? null;
2496 }
2497 }
2498 else
2499 {
2500 $value = $value['TEXT'] ?? null;
2501 }
2502 }
2503 elseif ($property && $property->getUserType() === PropertyTable::USER_TYPE_SEQUENCE)
2504 {
2505 if ($field['multiple'])
2506 {
2507 foreach ($value as $valueItemKey => $valueItem)
2508 {
2509 if ($valueItem > 0)
2510 {
2511 $value[$valueItemKey] = (int)$value;
2512 }
2513 else
2514 {
2515 $value[$valueItemKey] = $this->getSequence(
2516 $property->getId(),
2517 $property->getSetting('IBLOCK_ID')
2518 );
2519 }
2520 }
2521 }
2522 else
2523 {
2524 if ($value > 0)
2525 {
2526 $value = (int)$value;
2527 }
2528 else
2529 {
2530 $value = $this->getSequence(
2531 $property->getId(),
2532 $property->getSetting('IBLOCK_ID')
2533 );
2534 }
2535 }
2536 }
2537
2538 return $value;
2539 }
2540
2541 protected function getSequence(int $propertyId, int $propertyIblockId): int
2542 {
2543 static $sequenceList = [];
2544
2545 if (empty($sequenceList[$propertyId]))
2546 {
2547 $sequence = new \CIBlockSequence($propertyIblockId, $propertyId);
2548 $isAjaxRequest = \Bitrix\Main\Context::getCurrent()->getRequest()->isAjaxRequest();
2549 $sequenceList[$propertyId] = $isAjaxRequest ? $sequence->getCurrent() : $sequence->getNext();
2550 }
2551
2552 return $sequenceList[$propertyId];
2553 }
2554
2555 protected function getMeasures(): array
2556 {
2557 static $measures = null;
2558
2559 if ($measures === null)
2560 {
2561 $params = [
2562 'select' => [
2563 'ID',
2564 'CODE',
2565 'MEASURE_TITLE',
2566 'IS_DEFAULT',
2567 ],
2568 ];
2569
2570 $measures = \Bitrix\Catalog\MeasureTable::getList($params)->fetchAll();
2571 }
2572
2573 return $measures;
2574 }
2575
2576 protected function getVats(): array
2577 {
2578 static $vats = null;
2579
2580 if ($vats === null)
2581 {
2582 $vats = Catalog\VatTable::getList([
2583 'select' => ['ID', 'NAME', 'RATE', 'EXCLUDE_VAT'],
2584 'filter' => ['=ACTIVE' => 'Y'],
2585 ])->fetchAll();
2586 }
2587
2588 return $vats;
2589 }
2590
2591 protected function getDefaultVat(): array
2592 {
2593 $iblockVatId = $this->entity->getIblockInfo()->getVatId();
2594
2595 foreach ($this->getVats() as $vat)
2596 {
2597 if ((int)$vat['ID'] === $iblockVatId)
2598 {
2599 $vat['NAME'] = Loc::getMessage(
2600 'CATALOG_C_F_DEFAULT',
2601 ['#VALUE#' => htmlspecialcharsbx($vat['NAME'])]
2602 );
2603
2604 return $vat;
2605 }
2606 }
2607
2608 return [
2609 'ID' => self::NOT_SELECTED_VAT_ID_VALUE,
2610 'RATE' => null,
2611 'EXCLUDE_VAT' => null,
2612 'NAME' => Loc::getMessage(
2613 'CATALOG_C_F_DEFAULT',
2614 ['#VALUE#' => Loc::getMessage('CATALOG_PRODUCT_CARD_VARIATION_GRID_NOT_SELECTED')]
2615 ),
2616 ];
2617 }
2618
2619 protected function getCustomControlParameters(string $fieldName): array
2620 {
2621 return [
2622 'view' => $fieldName . '[VIEW_HTML]',
2623 'edit' => $fieldName . '[EDIT_HTML]',
2624 'editList' => $fieldName . '[EDIT_HTML_LIST]',
2625 'viewList' => $fieldName . '[VIEW_HTML_LIST]',
2626 ];
2627 }
2628
2629 protected function getUserFieldType(array $userField): string
2630 {
2631 $isMultiple = $userField['MULTIPLE'] === 'Y';
2632 switch ($userField['USER_TYPE_ID'])
2633 {
2634 case UserField\Types\BooleanType::USER_TYPE_ID:
2635 $result = Control\Type::BOOLEAN;
2636 break;
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;
2640 break;
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;
2644 break;
2645 case UserField\Types\EnumType::USER_TYPE_ID:
2646 $result = $isMultiple ? Control\Type::MULTI_LIST : Control\Type::LIST;
2647 break;
2648 case UserField\Types\FileType::USER_TYPE_ID:
2649 $result = Control\Type::CUSTOM;
2650 break;
2651 case UserField\Types\StringFormattedType::USER_TYPE_ID:
2652 $result = Control\Type::TEXTAREA; // TODO: need replace
2653 break;
2654 case UserField\Types\StringType::USER_TYPE_ID:
2655 $result = $isMultiple ? Control\Type::MULTI_TEXT : Control\Type::TEXT;
2656 break;
2657 case UserField\Types\UrlType::USER_TYPE_ID:
2658 $result = Control\Type::LINK;
2659 break;
2660 default:
2661 if (
2662 Loader::includeModule('highloadblock')
2663 && $userField['USER_TYPE_ID'] === \CUserTypeHlblock::USER_TYPE_ID
2664 )
2665 {
2666 $result = $isMultiple ? Control\Type::MULTI_LIST : Control\Type::LIST;
2667 }
2668 else
2669 {
2670 $result = Control\Type::TEXT;
2671 }
2672 }
2673
2674 return $result;
2675 }
2676}
$currentPath
Определения access_edit.php:61
$type
Определения options.php:106
if($_SERVER $defaultValue['REQUEST_METHOD']==="GET" &&!empty($RestoreDefaults) && $bizprocPerms==="W" &&check_bitrix_sessid())
Определения options.php:32
$allowedFields
Определения push.php:9
global $APPLICATION
Определения include.php:80
isQuantityTraceSettingDisabled()
Определения baseform.php:1015
isEnabledSetSettingsForAll()
Определения baseform.php:246
getGeneralPropertyDescription(Property $property)
Определения baseform.php:1993
getPropertyDescription(Property $property)
Определения baseform.php:1853
getIblockPropertiesDescriptions()
Определения baseform.php:1802
showSpecificCatalogParameters()
Определения baseform.php:1415
getImagePropertyViewHtml($value)
Определения baseform.php:2165
getFieldValue(array $field)
Определения baseform.php:961
const PRICE_FIELD_PREFIX
Определения baseform.php:51
const CURRENCY_FIELD_PREFIX
Определения baseform.php:52
getPreparedParams(array $params)
Определения baseform.php:99
const NOT_SELECTED_VAT_ID_VALUE
Определения baseform.php:54
getPropertiesConfigElements()
Определения baseform.php:1974
const SERVICE_GRID_PREFIX
Определения baseform.php:49
const CREATION_MODE
Определения baseform.php:68
const CONTROL_IBLOCK_SECTION
Определения baseform.php:63
__construct(BaseIblockElementEntity $entity, array $params=[])
Определения baseform.php:89
array $propertyDescriptions
Определения baseform.php:79
getUserTypePropertyDescription(Property $property)
Определения baseform.php:2048
getProductSystemFieldDescriptions()
Определения baseform.php:1628
static preparePropertyNameFromProperty(Property $property)
Определения baseform.php:1846
getVariationGridJsComponentName()
Определения baseform.php:410
isImageProperty(array $propertySettings)
Определения baseform.php:867
getCatalogProductFieldsList()
Определения baseform.php:1393
buildIblockPropertiesDescriptions()
Определения baseform.php:1812
getAdditionalValues(array $values, array $descriptions=[])
Определения baseform.php:526
const PROPERTY_FIELD_PREFIX
Определения baseform.php:50
const GRID_FIELD_PREFIX
Определения baseform.php:48
static preparePropertyName(string $name='')
Определения baseform.php:1841
getValues(bool $allowDefaultValues=true, array $descriptions=null)
Определения baseform.php:358
prepareFieldName(string $name)
Определения baseform.php:300
getCatalogEnumFields(string $fieldName)
Определения baseform.php:1571
isInventoryManagementAccess()
Определения baseform.php:295
getVariationGridClassName()
Определения baseform.php:405
array $descriptions
Определения baseform.php:77
getPropertySettings(Property $property)
Определения baseform.php:2153
saveCardUserConfig(array $config)
Определения baseform.php:509
const CONTROL_NAME_WITH_CODE
Определения baseform.php:62
getCatalogParametersSectionConfig()
Определения baseform.php:1097
showSubscribeCatalogParameters()
Определения baseform.php:1533
static isProductCardSliderEnabled()
Определения state.php:906
static isTextProductCardAvailable()
Определения settings.php:17
static getMap()
Определения product.php:112
const STATUS_YES
Определения product.php:66
static getUfId()
Определения product.php:433
const STATUS_NO
Определения product.php:67
const TYPE_SERVICE
Определения product.php:76
const STATUS_DEFAULT
Определения product.php:68
const USER_FIELD_ENTITY_ID
Определения product.php:64
static getMap()
Определения elementtable.php:93
const USER_TYPE_HTML
Определения propertytable.php:79
const USER_TYPE_ELEMENT_AUTOCOMPLETE
Определения propertytable.php:82
const TYPE_ELEMENT
Определения propertytable.php:68
const TYPE_FILE
Определения propertytable.php:67
const USER_TYPE_SKU
Определения propertytable.php:83
const TYPE_STRING
Определения propertytable.php:65
const USER_TYPE_XML_ID
Определения propertytable.php:77
const USER_TYPE_SEQUENCE
Определения propertytable.php:81
const TYPE_LIST
Определения propertytable.php:70
const TYPE_SECTION
Определения propertytable.php:69
const TYPE_NUMBER
Определения propertytable.php:66
static renderSelector(array $property, array|int|string|null $values, array $config)
Определения Element.php:14
static renderSelector(array $property, array|int|string|null $values, array $config)
Определения Section.php:14
Определения ini.php:6
static unformatInt(string $str)
Определения ini.php:19
Определения loader.php:13
static getList(array $parameters=array())
Определения datamanager.php:431
static encode($string, $flags=ENT_COMPAT, $doubleEncode=true)
Определения htmlfilter.php:12
static normalizeArrayValuesByInt(&$map, $sorted=true)
Определения collection.php:150
static getLabelsSelect(string $referenceName=null)
Определения userfield.php:138
static getLabelsReference(string $referenceName=null, string $languageId=null)
Определения userfield.php:103
const CUSTOM
Определения type.php:22
const MULTI_LIST
Определения type.php:18
const LIST
Определения type.php:17
Определения main.php:3772
const USER_TYPE_ID
Определения cusertypehlblock.php:14
$options
Определения commerceml2.php:49
if(errorBox) return true
Определения file_new.php:1035
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
Определения file_new.php:804
$result
Определения get_property_values.php:14
if(Loader::includeModule( 'bitrix24')) elseif(Loader::includeModule('intranet') &&CIntranetUtils::getPortalZone() !=='ru') $description
Определения .description.php:24
$filter
Определения iblock_catalog_list.php:54
$inputName
Определения options.php:197
if(!is_null($config))($config as $configItem)(! $configItem->isVisible()) $code
Определения options.php:195
const BX_RESIZE_IMAGE_EXACT
Определения constants.php:12
bitrix_sessid()
Определения tools.php:4656
htmlspecialcharsbx($string, $flags=ENT_COMPAT, $doubleEncode=true)
Определения tools.php:2701
$name
Определения menu_edit.php:35
Определения basket.php:2
$value
Определения Param.php:39
Определения culture.php:9
$user
Определения mysql_to_pgsql.php:33
if(!function_exists("bx_hmac")) $amount
Определения payment.php:30
return false
Определения prolog_main_admin.php:185
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
Определения prolog_main_admin.php:393
$config
Определения quickway.php:69
</p ></td >< td valign=top style='border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0cm 2.0pt 0cm 2.0pt;height:9.0pt'>< p class=Normal align=center style='margin:0cm;margin-bottom:.0001pt;text-align:center;line-height:normal'>< a name=ТекстовоеПоле54 ></a ><?=($taxRate > count( $arTaxList) > 0) ? $taxRate."%"
Определения waybill.php:936
$currency
Определения template.php:266
$vat
Определения template.php:273
if($inWords) echo htmlspecialcharsbx(Number2Word_Rus(roundEx($totalVatSum $params['CURRENCY']
Определения template.php:799
$items
Определения template.php:224
else $a
Определения template.php:137
getMeasures($arBasketItems)
Определения include.php:360
$iterator
Определения yandex_run.php:610
$vatList
Определения yandex_run.php:916