Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
product.php
1<?php
2
4
18
19final class Product extends Base
20{
21 public const BOOLEAN_VALUE_YES = 'Y';
22 public const BOOLEAN_VALUE_NO = 'N';
23 private array $productFieldNames = [];
24
29 public function getFields()
30 {
31 $this->loadFieldNames();
32
33 return array_merge($this->getFieldsIBlockElement(), $this->getFieldsCatalogProduct());
34 }
35
41 protected function prepareFieldAttributs($info, $attributs): array
42 {
43 $r = parent::prepareFieldAttributs($info, $attributs);
44
45 $r['NAME'] = $info['NAME'];
46 if ($info['TYPE'] === DataType::TYPE_PRODUCT_PROPERTY)
47 {
48 $r['IS_DYNAMIC'] = true;
49 $r['IS_MULTIPLE'] = in_array(Attributes::MULTIPLE, $attributs, true);
50 $r['PROPERTY_TYPE'] = $info['PROPERTY_TYPE'];
51 $r['USER_TYPE'] = $info['USER_TYPE'];
52 if (isset($info['VALUES']))
53 {
54 $r['VALUES'] = $info['VALUES'];
55 }
56 }
57
58 return $r;
59 }
60
64 private function getFieldsIBlockElement(): array
65 {
66 $fieldList = [
67 'ID' => [
68 'TYPE' => DataType::TYPE_INT,
69 'ATTRIBUTES' => [
70 Attributes::READONLY,
71 ],
72 ],
73 'CREATED_BY' => [
74 'TYPE' => DataType::TYPE_INT,
75 ],
76 'DATE_CREATE' => [
77 'TYPE' => DataType::TYPE_DATETIME,
78 ],
79 'MODIFIED_BY' => [
80 'TYPE' => DataType::TYPE_INT,
81 ],
82 'TIMESTAMP_X' => [
83 'TYPE' => DataType::TYPE_DATETIME,
84 'ATTRIBUTES' => [
85 Attributes::READONLY,
86 ],
87 ],
88 'ACTIVE' => [
89 'TYPE' => DataType::TYPE_CHAR,
90 ],
91 'DATE_ACTIVE_FROM' => [
92 'TYPE' => DataType::TYPE_DATETIME,
93 ],
94 'DATE_ACTIVE_TO' => [
95 'TYPE' => DataType::TYPE_DATETIME,
96 ],
97 'NAME' => [
98 'TYPE' => DataType::TYPE_STRING,
99 'ATTRIBUTES' => [
100 Attributes::REQUIRED_ADD,
101 ],
102 ],
103 'CODE' => [
104 'TYPE' => DataType::TYPE_STRING,
105 ],
106 'SORT' => [
107 'TYPE' => DataType::TYPE_INT,
108 ],
109 'PREVIEW_TEXT' => [
110 'TYPE' => DataType::TYPE_STRING,
111 ],
112 'PREVIEW_TEXT_TYPE' => [
113 'TYPE' => DataType::TYPE_STRING,
114 ],
115 'PREVIEW_PICTURE' => [
116 'TYPE' => DataType::TYPE_FILE,
117 ],
118 'DETAIL_TEXT' => [
119 'TYPE' => DataType::TYPE_STRING,
120 ],
121 'DETAIL_TEXT_TYPE' => [
122 'TYPE' => DataType::TYPE_STRING,
123 ],
124 'DETAIL_PICTURE' => [
125 'TYPE' => DataType::TYPE_FILE,
126 ],
127 'IBLOCK_ID' => [
128 'TYPE' => DataType::TYPE_INT,
129 'ATTRIBUTES' => [
130 Attributes::REQUIRED,
131 Attributes::IMMUTABLE,
132 ],
133 ],
134 'IBLOCK_SECTION_ID' => [
135 'TYPE' => DataType::TYPE_INT,
136 ],
137 'XML_ID' => [
138 'TYPE' => DataType::TYPE_STRING,
139 ],
140 ];
141
142 return $this->fillFieldNames($fieldList);
143 }
144
149 private function getFieldsIBlockPropertyValuesByFilter(array $filter): Result
150 {
151 $result = new Result();
152 $fieldsInfo = [];
153
154 $iblockId = (int)($filter['IBLOCK_ID'] ?? 0);
155
156 if ($iblockId <= 0)
157 {
158 $result->addError(new Error('parameter - iblockId is empty'));
159 }
160
161 if ($result->isSuccess())
162 {
163 $catalogInfo = \CCatalogSku::GetInfoByOfferIBlock($iblockId);
164 $skuPropertyId = $catalogInfo['SKU_PROPERTY_ID'] ?? null;
165 unset($catalogInfo);
166
167 $allowedTypes = array_fill_keys(self::getUserType(), true);
168
169 $cache = [
170 'ttl' => 86400,
171 ];
172
173 $iterator = PropertyTable::getList([
174 'select' => [
175 'ID',
176 'IBLOCK_ID',
177 'NAME',
178 'SORT',
179 'PROPERTY_TYPE',
180 'LIST_TYPE',
181 'MULTIPLE',
182 'LINK_IBLOCK_ID',
183 'IS_REQUIRED',
184 'USER_TYPE',
185 ],
186 'filter' => [
187 '=IBLOCK_ID' => $iblockId,
188 '=ACTIVE' => 'Y',
189 ],
190 'order' => [
191 'SORT' => 'ASC',
192 'ID' => 'ASC',
193 ],
194 'cache' => $cache,
195 ]);
196 while ($property = $iterator->fetch())
197 {
198 $property['ID'] = (int)$property['ID'];
199 $userType = (string)$property['USER_TYPE'];
200 if (
201 $userType !== ''
202 && !isset($allowedTypes[$property['PROPERTY_TYPE'] . ':' . $userType])
203 )
204 {
205 continue;
206 }
207
208 $info = [
209 'TYPE' => DataType::TYPE_PRODUCT_PROPERTY,
210 'PROPERTY_TYPE' => $property['PROPERTY_TYPE'],
211 'USER_TYPE' => $property['USER_TYPE'],
212 'ATTRIBUTES' => [Attributes::DYNAMIC],
213 'NAME' => $property['NAME'],
214 ];
215
216 if ($property['MULTIPLE'] === 'Y')
217 {
218 $info['ATTRIBUTES'][] = Attributes::MULTIPLE;
219 }
220 if ($property['IS_REQUIRED'] === 'Y')
221 {
222 $info['ATTRIBUTES'][] = Attributes::REQUIRED;
223 }
224
225 if (
226 $property['PROPERTY_TYPE'] === PropertyTable::TYPE_LIST
227 && $userType === ''
228 && $property['MULTIPLE'] === 'N'
229 )
230 {
231 $enumFilter = [
232 '=PROPERTY_ID' => $property['ID'],
233 ];
234 if (Iblock\PropertyEnumerationTable::getCount($enumFilter, $cache) === 1)
235 {
236 $variant = Iblock\PropertyEnumerationTable::getRow([
237 'select' => [
238 'ID',
239 'PROPERTY_ID',
240 'VALUE',
241 ],
242 'filter' => $enumFilter,
243 'cache' => $cache,
244 ]);
245 $info['BOOLEAN_VALUE_YES'] = [
246 'ID' => $variant['ID'],
247 'VALUE' => $variant['VALUE'],
248 ];
249 }
250 }
251
252 if ($this->isPropertyBoolean($info))
253 {
254 $info['USER_TYPE'] = Catalog\Controller\Enum::PROPERTY_USER_TYPE_BOOL_ENUM;
255 }
256
257 $canonicalName = 'PROPERTY_' . $property['ID'];
258 if ($property['ID'] === $skuPropertyId)
259 {
260 $info['CANONICAL_NAME'] = $canonicalName;
261 $fieldsInfo['PARENT_ID'] = $info;
262 }
263 else
264 {
265 $fieldsInfo[$canonicalName] = $info;
266 }
267 unset($canonicalName);
268 }
269 unset($property, $iterator);
270
271 $fieldsInfo['PROPERTY_*'] = [
272 'TYPE' => DataType::TYPE_PRODUCT_PROPERTY,
273 'ATTRIBUTES' => [
274 Attributes::READONLY,
275 Attributes::DYNAMIC,
276 ],
277 ];
278
279 $result->setData($fieldsInfo);
280 }
281
282 return $result;
283 }
284
288 private function getFieldsCatalogProductCommonFields(): array
289 {
290 $fieldList = [
291 'ID' => [
292 'TYPE' => DataType::TYPE_INT,
293 'ATTRIBUTES' => [
294 Attributes::READONLY,
295 ],
296 ],
297 'TIMESTAMP_X' => [
298 'TYPE' => DataType::TYPE_DATETIME,
299 ],
300 'PRICE_TYPE' => [
301 'TYPE' => DataType::TYPE_CHAR,
302 ],
303 'TYPE' => [
304 'TYPE' => DataType::TYPE_INT,
305 'ATTRIBUTES' => [
306 Attributes::READONLY,
307 ],
308 ],
309 'BUNDLE' => [
310 'TYPE' => DataType::TYPE_CHAR,
311 'ATTRIBUTES' => [
312 Attributes::READONLY,
313 ],
314 ],
315 ];
316
317 return $this->fillFieldNames($fieldList);
318 }
319
320 public function isAllowedProductTypeByIBlockId($productTypeId, $iblockId): Result
321 {
322 $result = new Result();
323
324 $iblockData = \CCatalogSku::GetInfoByIBlock($iblockId);
325 if (empty($iblockData))
326 {
327 $result->addError(new Error('iblock is not catalog'));
328 }
329 else
330 {
331 $allowedTypes = self::getProductTypes($iblockData['CATALOG_TYPE']);
332
333 if (!isset($allowedTypes[$productTypeId]))
334 {
335 $result->addError(new Error('productType is not allowed for this catalog'));
336 }
337 }
338
339 return $result;
340 }
341
346 private function getFieldsCatalogProductByFilter(array $filter): Result
347 {
348 $result = new Result();
349
350 $iblockId = (int)($filter['IBLOCK_ID'] ?? 0);
351 $productTypeId = (int)($filter['PRODUCT_TYPE'] ?? 0);
352
353 if ($iblockId <= 0)
354 {
355 $result->addError(new Error('parameter - iblockId is empty'));
356 }
357
358 if ($productTypeId <= 0)
359 {
360 $result->addError(new Error('parameter - productType is empty'));
361 }
362
363 if ($result->isSuccess())
364 {
365 $r = $this->isAllowedProductTypeByIBlockId($productTypeId, $iblockId);
366 if ($r->isSuccess())
367 {
368 $result->setData($this->getFieldsCatalogProductByType($productTypeId));
369 }
370 else
371 {
372 $result->addErrors($r->getErrors());
373 }
374 }
375
376 return $result;
377 }
378
382 private function getFieldsCatalogProduct(): array
383 {
384 $fieldList = [
385 'TYPE' => [
386 'TYPE' => DataType::TYPE_INT,
387 'ATTRIBUTES' => [
388 Attributes::READONLY,
389 ],
390 ],
391 'AVAILABLE' => [
392 'TYPE' => DataType::TYPE_CHAR,
393 'ATTRIBUTES' => [
394 Attributes::READONLY,
395 ],
396 ],
397 'BUNDLE' => [
398 'TYPE' => DataType::TYPE_CHAR,
399 'ATTRIBUTES' => [
400 Attributes::READONLY,
401 ],
402 ],
403 'QUANTITY' => [
404 'TYPE' => DataType::TYPE_FLOAT,
405 ],
406 'QUANTITY_RESERVED' => [
407 'TYPE' => DataType::TYPE_FLOAT,
408 ],
409 'QUANTITY_TRACE' => [
410 'TYPE' => DataType::TYPE_CHAR,
411 ],
412 'CAN_BUY_ZERO' => [
413 'TYPE' => DataType::TYPE_CHAR,
414 ],
415 'SUBSCRIBE' => [
416 'TYPE' => DataType::TYPE_CHAR,
417 ],
418 'VAT_ID' => [
419 'TYPE' => DataType::TYPE_INT,
420 ],
421 'VAT_INCLUDED' => [
422 'TYPE' => DataType::TYPE_CHAR,
423 ],
424 'PURCHASING_PRICE' => [
425 'TYPE' => DataType::TYPE_FLOAT,
426 ],
427 'PURCHASING_CURRENCY' => [
428 'TYPE' => DataType::TYPE_STRING,
429 ],
430 'BARCODE_MULTI' => [
431 'TYPE' => DataType::TYPE_CHAR,
432 ],
433 'WEIGHT' => [
434 'TYPE' => DataType::TYPE_FLOAT,
435 ],
436 'LENGTH' => [
437 'TYPE' => DataType::TYPE_FLOAT,
438 ],
439 'WIDTH' => [
440 'TYPE' => DataType::TYPE_FLOAT,
441 ],
442 'HEIGHT' => [
443 'TYPE' => DataType::TYPE_FLOAT,
444 ],
445 'MEASURE' => [
446 'TYPE' => DataType::TYPE_INT,
447 ],
448 'RECUR_SCHEME_LENGTH' => [
449 'TYPE' => DataType::TYPE_INT,
450 ],
451 'RECUR_SCHEME_TYPE' => [
452 'TYPE' => DataType::TYPE_CHAR,
453 ],
454 'TRIAL_PRICE_ID' => [
455 'TYPE' => DataType::TYPE_INT,
456 ],
457 'WITHOUT_ORDER' => [
458 'TYPE' => DataType::TYPE_CHAR,
459 ],
460 ];
461
462 if (Catalog\Config\State::isUsedInventoryManagement())
463 {
464 $lockFields = [
465 'QUANTITY',
466 'QUANTITY_RESERVED',
467 'PURCHASING_PRICE',
468 'PURCHASING_CURRENCY',
469 ];
470
471 foreach ($lockFields as $fieldName)
472 {
473 if (!isset($fieldList[$fieldName]['ATTRIBUTES']))
474 {
475 $fieldList[$fieldName]['ATTRIBUTES'] = [
476 Attributes::READONLY,
477 ];
478 }
479 else
480 {
481 $fieldList[$fieldName]['ATTRIBUTES'][] = Attributes::READONLY;
482 $fieldList[$fieldName]['ATTRIBUTES'] = array_unique($fieldList[$fieldName]['ATTRIBUTES']);
483 }
484 }
485 }
486
487 return $this->fillFieldNames($fieldList);
488 }
489
494 private function getFieldsCatalogProductByType(int $id): array
495 {
496 switch ($id)
497 {
499 $r = $this->getFieldsCatalogProductByTypeService();
500 break;
502 $r = $this->getFieldsCatalogProductByTypeProduct();
503 break;
505 $r = $this->getFieldsCatalogProductByTypeSet();
506 break;
509 $r = $this->getFieldsCatalogProductByTypeSKU();
510 break;
513 $r = $this->getFieldsCatalogProductByTypeOffer();
514 break;
515 default:
516 $r = [];
517 break;
518 }
519
520 return $r;
521 }
522
526 private function getFieldsCatalogProductByTypeService(): array
527 {
528 $fieldList = [
529 'AVAILABLE' => [
530 'TYPE' => DataType::TYPE_CHAR,
531 ],
532 ];
533
534 return $this->fillFieldNames($fieldList);
535 }
536
540 private function getFieldsCatalogProductByTypeProduct(): array
541 {
542 $fieldList = [
543 'AVAILABLE' => [
544 'TYPE' => DataType::TYPE_CHAR,
545 'ATTRIBUTES' => [
546 Attributes::READONLY,
547 ],
548 ],
549 'PURCHASING_PRICE' => [
550 'TYPE' => DataType::TYPE_STRING,
551 ],
552 'PURCHASING_CURRENCY' => [
553 'TYPE' => DataType::TYPE_STRING,
554 ],
555 'VAT_ID' => [
556 'TYPE' => DataType::TYPE_INT,
557 ],
558 'VAT_INCLUDED' => [
559 'TYPE' => DataType::TYPE_CHAR,
560 ],
561 'QUANTITY' => [
562 'TYPE' => DataType::TYPE_FLOAT,
563 ],
564 'QUANTITY_RESERVED' => [
565 'TYPE' => DataType::TYPE_FLOAT,
566 ],
567 'MEASURE' => [
568 'TYPE' => DataType::TYPE_INT,
569 ],
570 'QUANTITY_TRACE' => [
571 'TYPE' => DataType::TYPE_CHAR,
572 ],
573 'CAN_BUY_ZERO' => [
574 'TYPE' => DataType::TYPE_CHAR,
575 ],
576 'NEGATIVE_AMOUNT_TRACE' => [
577 'TYPE' => DataType::TYPE_CHAR,
578 'ATTRIBUTES' => [
579 Attributes::READONLY,
580 ],
581 ],
582 'SUBSCRIBE' => [
583 'TYPE' => DataType::TYPE_CHAR,
584 ],
585 'WEIGHT' => [
586 'TYPE' => DataType::TYPE_FLOAT,
587 ],
588 'LENGTH' => [
589 'TYPE' => DataType::TYPE_FLOAT,
590 ],
591 'WIDTH' => [
592 'TYPE' => DataType::TYPE_FLOAT,
593 ],
594 'HEIGHT' => [
595 'TYPE' => DataType::TYPE_FLOAT,
596 ],
597 ];
598
599 return $this->fillFieldNames($fieldList);
600 }
601
605 private function getFieldsCatalogProductByTypeSKU(): array
606 {
607 $fieldList = [
608 'AVAILABLE' => [
609 'TYPE' => DataType::TYPE_CHAR,
610 'ATTRIBUTES' => [
611 Attributes::READONLY,
612 ],
613 ],
614 ];
615
616 return $this->fillFieldNames($fieldList);
617 }
618
622 private function getFieldsCatalogProductByTypeOffer(): array
623 {
624 return $this->getFieldsCatalogProductByTypeProduct();
625 }
626
630 private function getFieldsCatalogProductByTypeSet(): array
631 {
632 $fieldList = [
633 'AVAILABLE' => [
634 'TYPE' => DataType::TYPE_CHAR,
635 'ATTRIBUTES' => [
636 Attributes::READONLY,
637 ],
638 ],
639 'PURCHASING_PRICE' => [
640 'TYPE' => DataType::TYPE_STRING,
641 ],
642 'PURCHASING_CURRENCY' => [
643 'TYPE' => DataType::TYPE_STRING,
644 ],
645 'VAT_ID' => [
646 'TYPE' => DataType::TYPE_INT,
647 ],
648 'VAT_INCLUDED' => [
649 'TYPE' => DataType::TYPE_CHAR,
650 ],
651 'QUANTITY' => [
652 'TYPE' => DataType::TYPE_FLOAT,
653 'ATTRIBUTES' => [
654 Attributes::READONLY,
655 ],
656 ],
657 'MEASURE' => [
658 'TYPE' => DataType::TYPE_INT,
659 'ATTRIBUTES' => [
660 Attributes::READONLY,
661 ],
662 ],
663 'QUANTITY_TRACE' => [
664 'TYPE' => DataType::TYPE_CHAR,
665 'ATTRIBUTES' => [
666 Attributes::READONLY,
667 ],
668 ],
669 'CAN_BUY_ZERO' => [
670 'TYPE' => DataType::TYPE_CHAR,
671 'ATTRIBUTES' => [
672 Attributes::READONLY,
673 ],
674 ],
675 'NEGATIVE_AMOUNT_TRACE' => [
676 'TYPE' => DataType::TYPE_CHAR,
677 'ATTRIBUTES' => [
678 Attributes::READONLY,
679 ],
680 ],
681 'SUBSCRIBE' => [
682 'TYPE' => DataType::TYPE_CHAR,
683 ],
684 'WEIGHT' => [
685 'TYPE' => DataType::TYPE_FLOAT,
686 'ATTRIBUTES' => [
687 Attributes::READONLY,
688 ],
689 ],
690 'LENGTH' => [
691 'TYPE' => DataType::TYPE_FLOAT,
692 ],
693 'WIDTH' => [
694 'TYPE' => DataType::TYPE_FLOAT,
695 ],
696 'HEIGHT' => [
697 'TYPE' => DataType::TYPE_FLOAT,
698 ],
699 ];
700
701 return $this->fillFieldNames($fieldList);
702 }
703
708 public function getFieldsByFilter(array $filter): Result
709 {
710 $result = new Result();
711
712 $iblockId = (int)($filter['IBLOCK_ID'] ?? 0);
713 $productTypeId = (int)($filter['PRODUCT_TYPE'] ?? 0);
714 if ($iblockId <= 0)
715 {
716 $result->addError(new Error('parameter - iblockId is empty'));
717 }
718
719 if ($productTypeId <= 0)
720 {
721 $result->addError(new Error('parameter - productType is empty'));
722 }
723
724 if ($result->isSuccess())
725 {
726 $this->loadFieldNames();
727
728 $r = $this->isAllowedProductTypeByIBlockId($productTypeId, $iblockId);
729 if ($r->isSuccess())
730 {
731 $propertyValues = $this->getFieldsIBlockPropertyValuesByFilter(['IBLOCK_ID' => $iblockId]);
732 $properties = [];
733 if ($propertyValues->isSuccess())
734 {
735 $properties = $propertyValues->getData();
736 unset($properties['PROPERTY_*']);
737 }
738 unset($propertyValues);
739 $result->setData(
740 array_merge(
741 $this->getFieldsIBlockElement(),
742 $properties,
743 $this->getFieldsCatalogProductCommonFields(),
744 $this->getFieldsCatalogProductByType($productTypeId)
745 )
746 );
747 unset($properties);
748 }
749 else
750 {
751 $result->addErrors($r->getErrors());
752 }
753 }
754
755 return $result;
756 }
757
762 private static function getProductTypes($catalogType): array
763 {
764 //TODO: remove after create \Bitrix\Catalog\Model\CatalogIblock
765 switch ($catalogType)
766 {
767 case \CCatalogSku::TYPE_CATALOG:
768 $result = [
772 ];
773 break;
774 case \CCatalogSku::TYPE_OFFERS:
775 $result = [
778 ];
779 break;
780 case \CCatalogSku::TYPE_FULL:
781 $result = [
787 ];
788 break;
789 case \CCatalogSku::TYPE_PRODUCT:
790 $result = [
793 ];
794 break;
795 default:
796 $result = [];
797 break;
798 }
799
800 return $result;
801 }
802
806 private static function getUserType(): array
807 {
808 return [
809 PropertyTable::TYPE_STRING . ':' . PROPERTYTable::USER_TYPE_DATE,
810 PropertyTable::TYPE_STRING . ':' . PropertyTable::USER_TYPE_DATETIME,
811 PropertyTable::TYPE_STRING . ':' . PropertyTable::USER_TYPE_HTML,
812 PropertyTable::TYPE_STRING . ':' . PropertyTable::USER_TYPE_XML_ID,
813 PropertyTable::TYPE_STRING . ':' . PropertyTable::USER_TYPE_DIRECTORY,
814 PropertyTable::TYPE_STRING . ':Money',
815 PropertyTable::TYPE_STRING . ':map_yandex',
816 PropertyTable::TYPE_STRING . ':map_google',
817 PropertyTable::TYPE_STRING . ':employee',
818 PropertyTable::TYPE_STRING . ':ECrm',
819 PropertyTable::TYPE_STRING . ':UserID',
820
821 PropertyTable::TYPE_NUMBER . ':' . PropertyTable::USER_TYPE_SEQUENCE,
822
823 PropertyTable::TYPE_ELEMENT . ':' . PropertyTable::USER_TYPE_ELEMENT_LIST,
824 PropertyTable::TYPE_ELEMENT . ':' . PropertyTable::USER_TYPE_ELEMENT_AUTOCOMPLETE,
825 PropertyTable::TYPE_ELEMENT . ':' . PropertyTable::USER_TYPE_SKU,
826
827 PropertyTable::TYPE_SECTION . ':' . PropertyTable::USER_TYPE_SECTION_AUTOCOMPLETE,
828
829 //TODO: support types
830 //'S:video',
831 //'S:TopicID',
832 //'S:FileMan',
833 //'S:DiskFile',
834 ];
835 }
836
837 public function internalizeFieldsList($arguments, $fieldsInfo = []): array
838 {
839 // param - IBLOCK_ID is reqired in filter
840 $iblockId = (int)($arguments['filter']['IBLOCK_ID'] ?? 0);
841
842 $propertyValues = $this->getFieldsIBlockPropertyValuesByFilter(['IBLOCK_ID' => $iblockId]);
843 $fieldsInfo = array_merge(
844 $this->getFields(),
845 ($propertyValues->isSuccess() ? $propertyValues->getData() : [])
846 );
847 unset($propertyValues);
848
849 return parent::internalizeFieldsList($arguments, $fieldsInfo);
850 }
851
852 public function internalizeFieldsAdd($fields, $fieldsInfo = []): array
853 {
854 // param - IBLOCK_ID is reqired in filter
855 $iblockId = (int)($fields['IBLOCK_ID'] ?? 0);
856
857 $propertyValues = $this->getFieldsIBlockPropertyValuesByFilter(['IBLOCK_ID' => $iblockId]);
858 $fieldsInfo = array_merge(
859 $this->getFields(),
860 ($propertyValues->isSuccess() ? $propertyValues->getData() : [])
861 );
862 unset($propertyValues);
863
864 return parent::internalizeFieldsAdd($fields, $fieldsInfo);
865 }
866
867 public function internalizeFieldsUpdate($fields, $fieldsInfo = []): array
868 {
869 // param - IBLOCK_ID is reqired in filter
870 $iblockId = (int)($fields['IBLOCK_ID'] ?? 0);
871
872 $propertyValues = $this->getFieldsIBlockPropertyValuesByFilter(['IBLOCK_ID' => $iblockId]);
873 $fieldsInfo = array_merge(
874 $this->getFields(),
875 ($propertyValues->isSuccess() ? $propertyValues->getData() : [])
876 );
877 unset($propertyValues);
878
879 return parent::internalizeFieldsUpdate($fields, $fieldsInfo);
880 }
881
882 protected function internalizeDateValue($value): Result
883 {
884 //API does not accept DataTime objects, so the ISO format is transformed into a format for a filter.
885
886 $r = new Result();
887
888 $date = $this->internalizeDate($value);
889
890 if ($date instanceof Date)
891 {
892 $value = $date->format('d.m.Y');
893 }
894 else
895 {
896 $r->addError(new Error('Wrong type data'));
897 }
898
899 if ($r->isSuccess())
900 {
901 $r->setData([$value]);
902 }
903
904 return $r;
905 }
906
907 protected function internalizeDateTimeValue($value): Result
908 {
909 //API does not accept DataTime objects, so the ISO format is transformed into a format for a filter.
910
911 $r = new Result();
912
913 $date = $this->internalizeDateTime($value);
914 if ($date instanceof DateTime)
915 {
916 $value = $date->format('d.m.Y H:i:s');
917 }
918 else
919 {
920 $r->addError(new Error('Wrong type datetime'));
921 }
922
923 if ($r->isSuccess())
924 {
925 $r->setData([$value]);
926 }
927
928 return $r;
929 }
930
931 protected function internalizeDateProductPropertyValue($value): Result
932 {
933 //API does not accept DataTime objects, so the ISO format is transformed into a format for a filter.
934
935 $r = new Result();
936
937 $date = $this->internalizeDate($value);
938
939 if ($date instanceof Date)
940 {
941 $value = $date->format('Y-m-d');
942 }
943 else
944 {
945 $r->addError(new Error('Wrong type data'));
946 }
947
948 if ($r->isSuccess())
949 {
950 $r->setData([$value]);
951 }
952
953 return $r;
954 }
955
957 {
958 //API does not accept DataTime objects, so the ISO format is transformed into a format for a filter.
959
960 $r = new Result();
961
962 $date = $this->internalizeDateTime($value);
963 if ($date instanceof DateTime)
964 {
965 $value = $date->format('Y-m-d H:i:s');
966 }
967 else
968 {
969 $r->addError(new Error('Wrong type datetime'));
970 }
971
972 if ($r->isSuccess())
973 {
974 $r->setData([$value]);
975 }
976
977 return $r;
978 }
979
980 protected function internalizeExtendedTypeValue($value, $info): Result
981 {
982 $r = new Result();
983
984 $type = $info['TYPE'] ?? '';
985
986 if ($type === DataType::TYPE_PRODUCT_PROPERTY)
987 {
988 $propertyType = $info['PROPERTY_TYPE'] ?? '';
989 $userType = $info['USER_TYPE'] ?? '';
990
991 $attrs = $info['ATTRIBUTES'] ?? [];
992 $isMultiple = in_array(Attributes::MULTIPLE, $attrs, true);
993
994 $r = $isMultiple ? $this->checkIndexedMultipleValue($value) : new Result();
995
996 if ($r->isSuccess())
997 {
998 $value = $isMultiple ? $value : [$value];
999
1000 if ($propertyType === PropertyTable::TYPE_STRING && $userType === PropertyTable::USER_TYPE_DATE)
1001 {
1002 array_walk($value, function(&$item) use ($r)
1003 {
1004 $date = $this->internalizeDateProductPropertyValue($item['VALUE']);
1005 if ($date->isSuccess())
1006 {
1007 $item['VALUE'] = $date->getData()[0];
1008 }
1009 else
1010 {
1011 $r->addErrors($date->getErrors());
1012 }
1013 });
1014 }
1015 elseif ($propertyType === PropertyTable::TYPE_STRING && $userType === PropertyTable::USER_TYPE_DATETIME)
1016 {
1017 array_walk($value, function(&$item) use ($r)
1018 {
1019 $date = $this->internalizeDateTimeProductPropertyValue($item['VALUE']);
1020 if ($date->isSuccess())
1021 {
1022 $item['VALUE'] = $date->getData()[0];
1023 }
1024 else
1025 {
1026 $r->addErrors($date->getErrors());
1027 }
1028 });
1029 }
1030 elseif ($propertyType === PropertyTable::TYPE_FILE && empty($userType))
1031 {
1032 array_walk($value, function(&$item) use ($r)
1033 {
1034 $date = $this->internalizeFileValue($item['VALUE']);
1035 if (!empty($date))
1036 {
1037 $item['VALUE'] = $date;
1038 }
1039 else
1040 {
1041 $r->addError(new Error('Wrong file date'));
1042 }
1043 });
1044 }
1045 elseif ($this->isPropertyBoolean($info))
1046 {
1047 $booleanValue = $value[0]['VALUE'];
1048 if ($booleanValue === self::BOOLEAN_VALUE_YES)
1049 {
1050 $value[0]['VALUE'] = $info['BOOLEAN_VALUE_YES']['ID'];
1051 }
1052 elseif ($booleanValue === self::BOOLEAN_VALUE_NO)
1053 {
1054 $value[0]['VALUE'] = null;
1055 }
1056 }
1057 //elseif ($propertyType === 'S' && $userType === 'HTML'){}
1058
1059 $value = $isMultiple? $value: $value[0];
1060 }
1061 }
1062
1063 if ($r->isSuccess())
1064 {
1065 $r->setData([$value]);
1066 }
1067
1068 return $r;
1069 }
1070
1071 public function internalizeArguments($name, $arguments): array
1072 {
1073 if (
1074 $name === 'getfieldsbyfilter'
1075 || $name === 'download'
1076 )
1077 {
1078 return $arguments;
1079 }
1080
1081 // Returns throw
1082 return parent::internalizeArguments($name, $arguments);
1083 }
1084
1085 protected function externalizeEmptyValue($name, $value, $fields, $fieldsInfo)
1086 {
1087 $fieldInfo = $fieldsInfo[$name] ?? [];
1088 if ($this->isPropertyBoolean($fieldInfo))
1089 {
1090 return self::BOOLEAN_VALUE_NO;
1091 }
1092
1093 return parent::externalizeEmptyValue($name, $value, $fields, $fieldsInfo);
1094 }
1095
1096 public function externalizeFieldsGet($fields, $fieldsInfo = []): array
1097 {
1098 // param - IBLOCK_ID is reqired in filter
1099 $iblockId = (int)($fields['IBLOCK_ID'] ?? 0);
1100 $productType = (int)($fields['TYPE'] ?? 0);
1101
1102 $propertyValues = $this->getFieldsIBlockPropertyValuesByFilter(['IBLOCK_ID' => $iblockId]);
1103 $product = $this->getFieldsCatalogProductByFilter(['IBLOCK_ID' => $iblockId, 'PRODUCT_TYPE' => $productType]);
1104
1105 if ($product->isSuccess())
1106 {
1107 $fieldsInfo = array_merge(
1108 $this->getFieldsIBlockElement(),
1109 ($propertyValues->isSuccess() ? $propertyValues->getData() : []),
1110 $this->getFieldsCatalogProductCommonFields(),
1111 $product->getData()
1112 );
1113 }
1114 else
1115 {
1116 // if it was not possible to determine the view fields by product type,
1117 // we get the default fields, all fields of the catalog and fields of the Information Block
1118
1119 $fieldsInfo = array_merge(
1120 $this->getFields(),
1121 ($propertyValues->isSuccess() ? $propertyValues->getData() : [])
1122 );
1123 }
1124 unset($product, $propertyValues);
1125
1126 return parent::externalizeFieldsGet($fields, $fieldsInfo);
1127 }
1128
1129 public function externalizeListFields($list, $fieldsInfo = []): array
1130 {
1131 // param - IBLOCK_ID is reqired in filter
1132 $iblockId = (int)($list[0]['IBLOCK_ID'] ?? 0);
1133
1134 $propertyValues = $this->getFieldsIBlockPropertyValuesByFilter(['IBLOCK_ID' => $iblockId]);
1135 $fieldsInfo = array_merge(
1136 $this->getFields(),
1137 ($propertyValues->isSuccess() ? $propertyValues->getData() : [])
1138 );
1139 unset($propertyValues);
1140
1141 return parent::externalizeListFields($list, $fieldsInfo);
1142 }
1143
1144 public function externalizeResult($name, $fields): array
1145 {
1146 if (
1147 $name === 'getfieldsbyfilter'
1148 || $name === 'download'
1149 )
1150 {
1151 return $fields;
1152 }
1153
1154 // Returns throw
1155 return parent::externalizeResult($name, $fields);
1156 }
1157
1158 public function convertKeysToSnakeCaseArguments($name, $arguments)
1159 {
1160 if ($name === 'getfieldsbyfilter')
1161 {
1162 if (isset($arguments['filter']))
1163 {
1164 $filter = $arguments['filter'];
1165 if (!empty($filter))
1166 {
1167 $arguments['filter'] = $this->convertKeysToSnakeCaseFilter($filter);
1168 }
1169 }
1170 }
1171 elseif ($name === 'download')
1172 {
1173 if (isset($arguments['fields']))
1174 {
1175 $fields = $arguments['fields'];
1176 if (!empty($fields))
1177 {
1178 $converter = new Converter(
1179 Converter::VALUES
1180 | Converter::TO_SNAKE
1181 | Converter::TO_SNAKE_DIGIT
1182 | Converter::TO_UPPER
1183 );
1184 $converterForKey = new Converter(
1185 Converter::KEYS
1186 | Converter::TO_SNAKE
1187 | Converter::TO_SNAKE_DIGIT
1188 | Converter::TO_UPPER
1189 );
1190
1191 $result = [];
1192 foreach ($converter->process($fields) as $key => $value)
1193 {
1194 $result[$converterForKey->process($key)] = $value;
1195 }
1196 $arguments['fields'] = $result;
1197 }
1198 }
1199 }
1200 else
1201 {
1202 parent::convertKeysToSnakeCaseArguments($name, $arguments);
1203 }
1204
1205 return $arguments;
1206 }
1207
1208 public function checkFieldsList($arguments): Result
1209 {
1210 $r = new Result();
1211
1212 $select = $arguments['select'] ?? [];
1213 if (!is_array($select))
1214 {
1215 $select = [];
1216 }
1217
1218 $error = [];
1219 if (!in_array('ID', $select))
1220 {
1221 $error[] = 'id';
1222 }
1223 if (!in_array('IBLOCK_ID', $select))
1224 {
1225 $error[] = 'iblockId';
1226 }
1227
1228 if (!empty($error))
1229 {
1230 $r->addError(new Error('Required select fields: ' . implode(', ', $error)));
1231 }
1232
1233 if (!isset($arguments['filter']['IBLOCK_ID']))
1234 {
1235 $r->addError(new Error('Required filter fields: iblockId'));
1236 }
1237
1238 return $r;
1239 }
1240
1241 public function checkArguments($name, $arguments): Result
1242 {
1243 if ($name === 'download')
1244 {
1245 $fields = $arguments['fields'];
1246 return $this->checkFieldsDownload($fields);
1247 }
1248 else
1249 {
1250 return parent::checkArguments($name, $arguments);
1251 }
1252 }
1253
1254 protected function checkFieldsDownload($fields): Result
1255 {
1256 $r = new Result();
1257
1258 $emptyFields = [];
1259
1260 if (!isset($fields['FIELD_NAME']))
1261 {
1262 $emptyFields[] = 'fieldName';
1263 }
1264
1265 if (!isset($fields['FILE_ID']))
1266 {
1267 $emptyFields[] = 'fileId';
1268 }
1269
1270 if (!isset($fields['PRODUCT_ID']))
1271 {
1272 $emptyFields[] = 'productId';
1273 }
1274
1275 if (!empty($emptyFields))
1276 {
1277 $r->addError(new Error('Required fields: '.implode(', ', $emptyFields)));
1278 }
1279
1280 return $r;
1281 }
1282
1283 protected function getActionUriToDownload(): string
1284 {
1285 return '/rest/catalog.product.download';
1286 }
1287
1288 protected function externalizeFileValue($name, $value, $fields): array
1289 {
1290 $productId = ($fields['ID'] ?? 0);
1291
1292 $data = [
1293 'fields' => [
1294 'fieldName' => Converter::toJson()
1295 ->process($name)
1296 ,
1297 'fileId' => $value,
1298 'productId' => $productId,
1299 ],
1300 ];
1301
1302 $uri = new \Bitrix\Main\Web\Uri($this->getActionUriToDownload());
1303
1304 return [
1305 'ID' => $value,
1306 'URL' => new \Bitrix\Main\Engine\Response\DataType\ContentUri(
1307 $uri->addParams($data)
1308 ->__toString()
1309 ),
1310 ];
1311 }
1312
1313 protected function externalizeExtendedTypeValue($name, $value, $fields, $fieldsInfo): Result
1314 {
1315 $r = new Result();
1316
1317 $info = $fieldsInfo[$name] ?? [];
1318 $type = $info['TYPE'] ?? '';
1319
1320 if ($type === DataType::TYPE_PRODUCT_PROPERTY)
1321 {
1322 $attrs = $info['ATTRIBUTES'] ?? [];
1323 $isMultiple = in_array(Attributes::MULTIPLE, $attrs, true);
1324
1325 $propertyType = $info['PROPERTY_TYPE'] ?? '';
1326 $userType = $info['USER_TYPE'] ?? '';
1327
1328 $value = $isMultiple? $value: [$value];
1329
1330 if ($propertyType === PropertyTable::TYPE_STRING && $userType === PropertyTable::USER_TYPE_DATE)
1331 {
1332 array_walk($value, function(&$item)use($r)
1333 {
1334 $date = $this->externalizeDateValue($item['VALUE']);
1335 if ($date->isSuccess())
1336 {
1337 $item['VALUE'] = $date->getData()[0];
1338 }
1339 else
1340 {
1341 $r->addErrors($date->getErrors());
1342 }
1343 });
1344 }
1345 elseif ($propertyType === PropertyTable::TYPE_STRING && $userType === PropertyTable::USER_TYPE_DATETIME)
1346 {
1347 array_walk($value, function(&$item) use($r)
1348 {
1349 $date = $this->externalizeDateTimeValue($item['VALUE']);
1350 if ($date->isSuccess())
1351 {
1352 $item['VALUE'] = $date->getData()[0];
1353 }
1354 else
1355 {
1356 $r->addErrors($date->getErrors());
1357 }
1358 });
1359 }
1360 elseif ($propertyType === PropertyTable::TYPE_FILE && empty($userType))
1361 {
1362 array_walk($value, function(&$item) use ($fields, $name)
1363 {
1364 $item['VALUE'] = $this->externalizeFileValue($name, $item['VALUE'], ['PRODUCT_ID' => $fields['ID']]);
1365 });
1366 }
1367 elseif ($this->isPropertyBoolean($info))
1368 {
1369 if ($value)
1370 {
1371 $value = self::BOOLEAN_VALUE_YES;
1372 }
1373 else
1374 {
1375 $value = self::BOOLEAN_VALUE_NO;
1376 }
1377 }
1378
1379 $value = $isMultiple? $value: $value[0];
1380 }
1381
1382 if ($r->isSuccess())
1383 {
1384 $r->setData([$value]);
1385 }
1386
1387 return $r;
1388 }
1389
1395 private function loadFieldNames(): void
1396 {
1397 if (!empty($this->productFieldNames))
1398 {
1399 return;
1400 }
1401
1402 $this->loadEntityFieldNames(Iblock\ElementTable::getMap());
1403 $this->loadEntityFieldNames(Catalog\ProductTable::getMap());
1404 }
1405
1412 private function loadEntityFieldNames(array $fieldList)
1413 {
1415 foreach ($fieldList as $field)
1416 {
1417 if ($field instanceof ScalarField)
1418 {
1419 $name = $field->getName();
1420 $title = $field->getTitle();
1421
1422 $this->productFieldNames[$name] = $title ?: $name;
1423 }
1424 }
1425 }
1426
1433 private function fillFieldNames(array $fieldList): array
1434 {
1435 foreach (array_keys($fieldList) as $id)
1436 {
1437 $fieldList[$id]['NAME'] = $this->productFieldNames[$id] ?? $id;
1438 }
1439
1440 return $fieldList;
1441 }
1442
1443 private function isPropertyBoolean(array $property): bool
1444 {
1445 if (($property['PROPERTY_TYPE'] ?? '') !== PropertyTable::TYPE_LIST)
1446 {
1447 return false;
1448 }
1449 $attributes = $property['ATTRIBUTES'] ?? [];
1450 if (!is_array($attributes))
1451 {
1452 $attributes = [];
1453 }
1454 if (in_array(Attributes::MULTIPLE, $attributes, true))
1455 {
1456 return false;
1457 }
1458 $userType = (string)($property['USER_TYPE'] ?? '');
1459 if ($userType !== '' && $userType !== Catalog\Controller\Enum::PROPERTY_USER_TYPE_BOOL_ENUM)
1460 {
1461 return false;
1462 }
1463 return (!empty($property['BOOLEAN_VALUE_YES']) && is_array($property['BOOLEAN_VALUE_YES']));
1464 }
1465
1466 protected function checkIndexedMultipleValue($values): Result
1467 {
1468 $r = new Result();
1469
1470 return
1471 $this->isIndexedArray($values)
1472 ? $r
1473 : $r->addError(new Error('For type Multiple field - value must be an Indexed array'))
1474 ;
1475 }
1476
1477 protected function isIndexedArray($ary): bool
1478 {
1479 if (!is_array($ary))
1480 {
1481 return false;
1482 }
1483
1484 $keys = array_keys($ary);
1485 foreach ($keys as $k)
1486 {
1487 if (!is_int($k))
1488 {
1489 return false;
1490 }
1491 }
1492 return true;
1493 }
1494}
externalizeExtendedTypeValue($name, $value, $fields, $fieldsInfo)
Definition product.php:1313
isAllowedProductTypeByIBlockId($productTypeId, $iblockId)
Definition product.php:320
internalizeFieldsList($arguments, $fieldsInfo=[])
Definition product.php:837
internalizeArguments($name, $arguments)
Definition product.php:1071
internalizeFieldsAdd($fields, $fieldsInfo=[])
Definition product.php:852
internalizeDateTimeProductPropertyValue($value)
Definition product.php:956
convertKeysToSnakeCaseArguments($name, $arguments)
Definition product.php:1158
externalizeFieldsGet($fields, $fieldsInfo=[])
Definition product.php:1096
checkArguments($name, $arguments)
Definition product.php:1241
internalizeFieldsUpdate($fields, $fieldsInfo=[])
Definition product.php:867
getFieldsByFilter(array $filter)
Definition product.php:708
externalizeFileValue($name, $value, $fields)
Definition product.php:1288
externalizeListFields($list, $fieldsInfo=[])
Definition product.php:1129
externalizeResult($name, $fields)
Definition product.php:1144
prepareFieldAttributs($info, $attributs)
Definition product.php:41
externalizeEmptyValue($name, $value, $fields, $fieldsInfo)
Definition product.php:1085
internalizeExtendedTypeValue($value, $info)
Definition product.php:980