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