54 $this->accessController = AccessController::getCurrent();
76 parent::getDefaultPreFilters(),
86 if (
$action->getName() ===
'getSkuTreeProperties')
92 $this->accessController->check(ActionDictionary::ACTION_CATALOG_READ)
93 || $this->accessController->check(ActionDictionary::ACTION_CATALOG_VIEW)
96 return parent::processBeforeAction(
$action);
112 'ID' => $variationId,
114 'ACTIVE_DATE' =>
'Y',
115 'CHECK_PERMISSIONS' =>
'Y',
116 'MIN_PERMISSION' =>
'R',
129 $skuRepository = ServiceContainer::getSkuRepository($element[
'IBLOCK_ID']);
136 $sku = $skuRepository->getEntityById($variationId);
143 return $this->prepareResponse($sku,
$options);
151 '=PRODUCT_BARCODE' => $barcode,
153 'ACTIVE_DATE' =>
'Y',
154 'CHECK_PERMISSIONS' =>
'Y',
155 'MIN_PERMISSION' =>
'R',
164 return (
int)$product[
'ID'];
182 'ACTIVE_DATE' =>
'Y',
183 'CHECK_PERMISSIONS' =>
'Y',
184 'MIN_PERMISSION' =>
'R',
188 [
'ID',
'IBLOCK_ID',
'TYPE']
198 $sku = $this->loadSkuById((
int)$element[
'IBLOCK_ID'], (
int)$element[
'ID']);
202 $sku = $this->loadFirstSkuForProduct((
int)$element[
'IBLOCK_ID'], (
int)$element[
'ID']);
212 return $this->prepareResponse($sku,
$options);
217 $skuRepository = ServiceContainer::getSkuRepository(
$iblockId);
223 return $skuRepository->getEntityById($skuId);
231 private function loadFirstSkuForProduct(
int $iblockId,
int $productId): ?BaseSku
233 $productRepository = ServiceContainer::getProductRepository(
$iblockId);
234 if (!$productRepository)
240 $product = $productRepository->getEntityById($productId);
246 return $product->getSkuCollection()->getFirst([$this,
'isActiveSku']);
263 $basketItem = $builder->createItem();
264 $basketItem->setSku($sku);
266 $priceId = (int)(
$options[
'priceId'] ?? 0);
269 $basketItem->setPriceGroupId($priceId);
274 $basketItem->setDetailUrlManagerType(
$options[
'urlBuilder']);
277 $formFields = $basketItem->getFields();
283 if ($basketItem->getPriceItem() && $basketItem->getPriceItem()->hasPrice())
285 $basePrice = $basketItem->getPriceItem()->getPrice();
286 $price = $basketItem->getPriceItem()->getPrice();
287 $currency = $basketItem->getPriceItem()->getCurrency();
293 $basePrice = round($basePrice, $decimals);
294 $price = \CCurrencyLang::CurrencyFormat($basePrice,
$options[
'currency'],
false);
303 $purchasingPrice = $sku->
getField(
'PURCHASING_PRICE');
304 $purchasingCurrency = $sku->
getField(
'PURCHASING_CURRENCY');
305 if ($purchasingCurrency !==
$options[
'currency'])
307 $purchasingPrice = \CCurrencyRates::ConvertCurrency(
312 $purchasingCurrency =
$options[
'currency'];
315 $productProps = $this->getProductProperties($sku);
319 'ID' => $formFields[
'skuId'],
320 'SKU_ID' => $formFields[
'skuId'],
321 'PRODUCT_ID' => $formFields[
'productId'],
322 'CUSTOMIZED' => $isCustomized,
323 'NAME' => $formFields[
'name'],
324 'MEASURE_CODE' => (string)$formFields[
'measureCode'],
325 'MEASURE_RATIO' => $formFields[
'measureRatio'],
326 'MEASURE_NAME' => $formFields[
'measureName'],
327 'PURCHASING_PRICE' => $purchasingPrice,
328 'PURCHASING_CURRENCY' => $purchasingCurrency,
329 'BARCODE' => $barcode ?
$barcode->getBarcode() :
'',
330 'COMMON_STORE_AMOUNT' => $sku->
getField(
'QUANTITY'),
331 'COMMON_STORE_RESERVED' => $sku->
getField(
'QUANTITY_RESERVED'),
333 'BASE_PRICE' => $basePrice,
335 'PROPERTIES' => $formFields[
'properties'],
336 'VAT_ID' => $formFields[
'taxId'],
337 'VAT_INCLUDED' => $formFields[
'taxIncluded'],
338 'TAX_RATE' => $formFields[
'taxRate'],
339 'TAX_RATE_FORMATTED' => $this->formatTaxRate($formFields[
'taxRate']),
340 'TAX_INCLUDED' => $formFields[
'taxIncluded'],
341 'TAX_INCLUDED_FORMATTED' => $this->formatTaxIncluded($formFields[
'taxIncluded']),
342 'BRANDS' => $this->getProductBrand($sku),
343 'WEIGHT' => $formFields[
'weight'],
344 'DIMENSIONS' => $formFields[
'dimensions'],
345 'PRODUCT_PROPERTIES' => $productProps,
354 'ID' => $previewImage->getId(),
355 'SRC' => Tools::getImageSrc($previewImage->getFileStructure(),
true),
356 'WIDTH' => $previewImage->getField(
'WIDTH'),
357 'HEIGHT' => $previewImage->getField(
'HEIGHT'),
361 $formResult = $basketItem->getResult();
363 'skuId' => $formFields[
'skuId'],
364 'productId' => $formFields[
'productId'],
365 'image' => $formResult[
'image'],
366 'detailUrl' => $formResult[
'detailUrl'],
369 $fields[
'DETAIL_URL'] = $formResult[
'detailUrl'];
370 $fields[
'IMAGE_INFO'] = $formResult[
'image'];
371 $fields[
'SKU_TREE'] = $formResult[
'skuTree'];
375 ($formResult[
'skuTree'] !==
'')
376 ? Json::decode($formResult[
'skuTree'])
387 private function formatTaxRate(?
float $rate): string
391 return Loc::getMessage(
'PRODUCT_SELECTOR_PRODUCT_NOT_TAX');
397 private function formatTaxIncluded(
string $taxIncluded): string
399 return ($taxIncluded ===
'Y')
400 ? Loc::getMessage(
'PRODUCT_SELECTOR_PRODUCT_TAX_INCLUDED')
401 : Loc::getMessage(
'PRODUCT_SELECTOR_PRODUCT_TAX_NOT_INCLUDED');
404 private function getProductIdByBarcode(
string $barcode): ?int
407 'filter' => [
'=BARCODE' => $barcode],
408 'select' => [
'PRODUCT_ID'],
412 if ($barcode = $barcodeRaw->fetch())
420 private function getProductBrand($sku): ?
array
428 $brand = $product->getPropertyCollection()->findByCodeLazy(
'BRAND_FOR_FACEBOOK');
434 $userType = \CIBlockProperty::GetUserType($brand->getUserType());
435 $userTypeMethod = $userType[
'GetUIEntityEditorProperty'];
436 $propertySettings = $brand->getSettings();
437 $propertyValues = $brand->getPropertyValueCollection()->getValues();
438 $description = $userTypeMethod($propertySettings, $propertyValues);
441 $selectedBrandItems = [];
443 foreach ($propertyBrandItems as $propertyBrandItem)
445 if (in_array($propertyBrandItem[
'VALUE'], $propertyValues,
true))
447 $selectedBrandItems[] = $propertyBrandItem;
451 return $selectedBrandItems;
454 private function getProductProperties(BaseSku $sku):
array
458 foreach ($columns as $columnName)
460 $emptyProps[$columnName] =
'';
464 $parent = $sku->getParent();
477 $skuId = $sku->getId();
478 $skuIblockId = $sku->getIblockId();
479 if ($skuId && $skuIblockId)
484 return array_merge($emptyProps, $productProps, $skuProps);
491 !\CIBlockSectionRights::UserHasRightTo(
$iblockId, 0,
'section_element_bind')
492 || !$this->accessController->check(ActionDictionary::ACTION_PRODUCT_ADD)
495 $this->
addError(
new Error(Loc::getMessage(
'PRODUCT_SELECTOR_ERROR_NO_PERMISSIONS_FOR_CREATION')));
500 $productFactory = ServiceContainer::getProductFactory(
$iblockId);
501 if (!$productFactory)
503 $this->
addError(
new Error(Loc::getMessage(
'PRODUCT_SELECTOR_ERROR_WRONG_IBLOCK_ID')));
508 $skuRepository = ServiceContainer::getSkuRepository(
$iblockId);
512 $product = $productFactory
519 ? $product->getSkuCollection()->create()
520 : $product->getSkuCollection()->getFirst()
523 if (!empty(
$fields[
'BARCODE']))
527 if ($productId !==
null)
529 $elementRaw = ElementTable::getList([
530 'filter' => [
'=ID' => $productId],
531 'select' => [
'NAME'],
536 if ($element = $elementRaw->fetch())
538 $name = $element[
'NAME'];
544 'PRODUCT_SELECTOR_ERROR_BARCODE_EXIST',
558 $sku->getBarcodeCollection()->setSimpleBarcodeValue(
$fields[
'BARCODE']);
564 $productName =
$fields[
'NAME'] ??
'';
566 if ($productName !==
'')
568 $fields[
'CODE'] = (new \CIBlockElement())->generateMnemonicCode($productName,
$iblockId);
574 $elementRaw = ElementTable::getList([
575 'filter' => [
'=CODE' =>
$fields[
'CODE']],
580 if ($elementRaw->fetch())
586 if (!empty(
$fields[
'MEASURE_CODE']))
588 $fields[
'MEASURE'] = $this->getMeasureIdByCode(
$fields[
'MEASURE_CODE']);
592 $measure = MeasureTable::getRow([
594 'filter' => [
'=IS_DEFAULT' =>
'Y'],
598 $fields[
'MEASURE'] = $measure[
'ID'];
602 if (!$this->accessController->check(ActionDictionary::ACTION_PRICE_EDIT))
610 $sku->setField(
'MEASURE',
$fields[
'MEASURE']);
612 if (Option::get(
'catalog',
'default_product_vat_included') ===
'Y')
620 'PRICE' => (float)
$fields[
'PRICE'],
623 if (isset(
$fields[
'CURRENCY']))
625 $basePrice[
'CURRENCY'] =
$fields[
'CURRENCY'];
631 ->getPriceCollection()
649 'id' => $sku->getId(),
653 public function updateSkuAction(
int $id,
array $updateFields,
array $oldFields = []): ?
array
655 if (empty($updateFields) || $id <= 0)
660 $repositoryFacade = ServiceContainer::getRepositoryFacade();
661 if (!$repositoryFacade)
666 $sku = $repositoryFacade->loadVariation($id);
669 $this->
addError(
new Error(Loc::getMessage(
'PRODUCT_SELECTOR_ERROR_SKU_NOT_EXIST')));
673 $parentProduct = $sku->getParent();
676 !$this->accessController->check(ActionDictionary::ACTION_PRODUCT_EDIT)
677 || !\CIBlockElementRights::UserHasRightTo($parentProduct->getIblockId(), $parentProduct->getId(),
'element_edit')
680 $this->
addError(
new Error(Loc::getMessage(
'PRODUCT_SELECTOR_ERROR_NO_PERMISSIONS_FOR_UPDATE')));
686 !$this->accessController->check(ActionDictionary::ACTION_PRICE_EDIT)
687 || !\CIBlockElementRights::UserHasRightTo($parentProduct->getIblockId(), $parentProduct->getId(),
'element_edit_price')
690 unset($updateFields[
'PRICES']);
693 $result = $this->saveSku($sku, $updateFields, $oldFields);
702 'id' => $sku->getId(),
708 return $this->updateSkuAction($id, $updateFields);
711 public function saveMorePhotoAction(
int $productId,
int $variationId,
int $iblockId,
array $imageValues): ?
array
714 !$this->accessController->check(ActionDictionary::ACTION_PRODUCT_EDIT)
715 || !\CIBlockElementRights::UserHasRightTo(
$iblockId, $productId,
'element_edit')
718 $this->
addError(
new Error(Loc::getMessage(
'PRODUCT_SELECTOR_ERROR_NO_PERMISSIONS_FOR_UPDATE')));
723 $productRepository = ServiceContainer::getProductRepository(
$iblockId);
724 if (!$productRepository)
726 $this->
addError(
new Error(Loc::getMessage(
'PRODUCT_SELECTOR_ERROR_WRONG_IBLOCK_ID')));
732 $product = $productRepository->getEntityById($productId);
735 $this->
addError(
new Error(Loc::getMessage(
'PRODUCT_SELECTOR_ERROR_PRODUCT_NOT_EXIST')));
742 if ($productId === $variationId)
748 $entity = $product->getSkuCollection()->findById($variationId);
753 $this->
addError(
new Error(Loc::getMessage(
'PRODUCT_SELECTOR_ERROR_SKU_NOT_EXIST')));
760 $property =
$entity->getPropertyCollection()->findByCodeLazy(MorePhotoImage::CODE);
761 foreach ($imageValues as
$key => $newImage)
763 $newImage = $this->prepareMorePhotoValue($newImage,
$entity);
764 if (empty($newImage))
769 if (!$property || !$property->isActive())
771 if (empty($previewPicture))
773 $previewPicture = $newImage;
777 $detailPicture = $newImage;
781 if (
$key === DetailImage::CODE)
783 $detailPicture = $newImage;
787 $previewPicture = $newImage;
791 $values[
$key] = $newImage;
795 $entity->getImageCollection()->setValues($values);
797 if (isset($detailPicture) && is_array($detailPicture))
799 $entity->getImageCollection()->getDetailImage()->setFileStructure($detailPicture);
802 if (isset($previewPicture) && is_array($previewPicture))
804 $entity->getImageCollection()->getPreviewImage()->setFileStructure($previewPicture);
815 $this->commitPendingCollection();
817 return (
new ImageInput(
$entity))->getFormattedField();
820 private function prepareMorePhotoValue($imageValue, BaseIblockElementEntity
$entity)
822 if (empty($imageValue))
827 if (!empty($imageValue[
'token']))
829 return $this->prepareMorePhotoValueByToken($imageValue,
$entity);
832 if (is_string($imageValue))
836 static $signer =
null;
837 if ($signer ===
null)
839 $signer =
new Signer;
844 catch (BadSignatureException $e)
851 is_array($imageValue)
852 && !empty($imageValue[
'data'])
853 && is_array($imageValue[
'data'])
856 return \CIBlock::makeFileArray($imageValue[
'data']);
860 is_array($imageValue)
861 && !empty($imageValue[
'base64Encoded'])
862 && is_array($imageValue[
'base64Encoded'])
865 $content = (string)($imageValue[
'base64Encoded'][
'content'] ??
'');
868 $fileName = (string)($imageValue[
'base64Encoded'][
'filename'] ??
'');
871 return $fileInfo ?:
null;
878 private function prepareMorePhotoValueByToken(
array $image, BaseIblockElementEntity
$entity): ?
array
880 $token = $image[
'token'] ??
null;
886 $fileId = $this->getFileIdByToken($token,
$entity);
889 return \CIBlock::makeFileArray($fileId,
false,
null, [
'allow_file_id' =>
true]);
895 private function getFileIdByToken(
string $token, BaseIblockElementEntity
$entity): ?int
897 $uploader = $this->getUploader(
$entity);
898 $pendingFile = $uploader->getPendingFiles([$token])->get($token);
900 if ($pendingFile && $pendingFile->isValid())
902 $this->addPendingFileToCollection($pendingFile);
904 return $pendingFile->getFileId();
910 private function getUploader(BaseIblockElementEntity
$entity): Uploader
912 if ($this->uploader ===
null)
914 $fileController =
new ProductController([
915 'productId' =>
$entity->getId(),
918 $this->uploader = (
new Uploader($fileController));
921 return $this->uploader;
924 private function addPendingFileToCollection(PendingFile $pendingFile): void
926 $this->getPendingFilesCollection()->add($pendingFile);
929 private function commitPendingCollection(): void
931 $this->getPendingFilesCollection()->makePersistent();
934 private function getPendingFilesCollection(): PendingFileCollection
936 if ($this->pendingFileCollection ===
null)
938 $this->pendingFileCollection =
new PendingFileCollection();
941 return $this->pendingFileCollection;
944 private function saveSku(BaseSku $sku,
array $fields = [],
array $oldFields = []): Result
946 if ($sku->isNew() && empty(
$fields[
'CODE']))
948 $productName =
$fields[
'NAME'] ??
'';
950 if ($productName !==
'')
952 $fields[
'CODE'] = $this->prepareProductCode($productName);
956 if (!empty(
$fields[
'MEASURE_CODE']))
958 $fields[
'MEASURE'] = $this->getMeasureIdByCode(
$fields[
'MEASURE_CODE']);
962 unset(
$fields[
'IBLOCK_SECTION_ID']);
968 $priceCollection = $sku->getPriceCollection();
969 foreach (
$fields[
'PRICES'] as $groupId => $price)
971 $priceCollection->setValues([
973 'PRICE' => (
float)$price[
'PRICE'],
974 'CURRENCY' => $price[
'CURRENCY'] ??
null,
982 $skuId = $this->getProductIdByBarcode(
$fields[
'BARCODE']);
983 if ($skuId !==
null && $sku->getId() !== $skuId)
987 $elementRaw = ElementTable::getList([
988 'filter' => [
'=ID' => $skuId],
989 'select' => [
'NAME'],
994 if ($element = $elementRaw->fetch())
996 $name = $element[
'NAME'];
1002 'PRODUCT_SELECTOR_ERROR_BARCODE_EXIST',
1014 $updateBarcodeItem =
null;
1015 $barcodeCollection = $sku->getBarcodeCollection();
1016 if (isset($oldFields[
'BARCODE']))
1018 $updateBarcodeItem = $barcodeCollection->getItemByBarcode($oldFields[
'BARCODE']);
1021 if ($updateBarcodeItem)
1023 if (empty(
$fields[
'BARCODE']))
1025 $barcodeCollection->remove($updateBarcodeItem);
1029 $updateBarcodeItem->setBarcode(
$fields[
'BARCODE']);
1037 ->setBarcode(
$fields[
'BARCODE'])
1040 $barcodeCollection->add($barcodeItem);
1045 $parentProduct = $sku->getParent();
1049 $parentProduct->getPropertyCollection()->setValues([
'BRAND_FOR_FACEBOOK' =>
$fields[
'BRANDS']]);
1052 if (isset($sectionId))
1054 $parentProduct->setField(
'IBLOCK_SECTION_ID', $sectionId);
1059 && $parentProduct->getSkuCollection()->count() === 1
1062 $this->changeProductName($parentProduct,
$fields[
'NAME']);
1065 return $parentProduct->save();
1068 private function changeProductName(BaseProduct $parentProduct,
string $newName): void
1070 $skuTreeEntity = ServiceContainer::make(
'sku.tree', [
1071 'iblockId' => $parentProduct->getIblockId(),
1073 $skuTree = $skuTreeEntity->load([$parentProduct->getId()]);
1074 if (empty($skuTree))
1076 $parentProduct->setField(
'NAME', $newName);
1081 $skuTreeElement = reset($skuTree);
1082 $existingValues = $skuTreeElement[
'EXISTING_VALUES'] ??
null;
1083 if (!$existingValues)
1085 $parentProduct->setField(
'NAME', $newName);
1090 $hasFilledProperty =
false;
1091 foreach ($existingValues as $existingValue)
1093 $hasFilledProperty = $existingValue[0] !== 0;
1094 if ($hasFilledProperty)
1099 if (!$hasFilledProperty)
1101 $parentProduct->setField(
'NAME', $newName);
1105 private function getMeasureIdByCode(
string $code): ?int
1107 $measure = MeasureTable::getRow([
1109 'filter' => [
'=CODE' =>
$code],
1113 return (
int) $measure[
'ID'];
1119 private function getMeasureCodeById(
string $id): ?string
1121 $measure = MeasureTable::getRow([
1122 'select' => [
'CODE'],
1123 'filter' => [
'=ID' => $id],
1126 return $measure[
'CODE'] ??
null;
1129 private function prepareProductCode(
$name): string
1131 return mb_strtolower(\CUtil::translit(
1135 'replace_space' =>
'_',
1136 'replace_other' =>
'',
1138 )).
'_'.random_int(0, 1000);
1143 $productFactory = ServiceContainer::getProductFactory(
$iblockId);
1144 if (!$productFactory)
1146 $this->
addError(
new Error(Loc::getMessage(
'PRODUCT_SELECTOR_ERROR_WRONG_IBLOCK_ID')));
1153 return $imageField->getFormattedField();
1158 $productFactory = ServiceContainer::getProductFactory(
$iblockId);
1159 if (!$productFactory)
1161 $this->
addError(
new Error(Loc::getMessage(
'PRODUCT_SELECTOR_ERROR_WRONG_IBLOCK_ID')));
1166 $repositoryFacade = ServiceContainer::getRepositoryFacade();
1169 if ($repositoryFacade && $skuId !==
null)
1171 $sku = $repositoryFacade->loadVariation($skuId);
1176 $sku = $productFactory->createEntity();
1180 $imageField->disableAutoSaving();
1182 return $imageField->getComponentResponse();
1187 $skuTree = ServiceContainer::make(
'sku.tree', [
1193 return $skuTree->getTreeProperties();
1201 if (!Loader::includeModule(
'ui'))
1216 if (!isset($selectedItems[0]))
1221 $item = $selectedItems[0];
1222 if (($item[
'hidden'] ??
null) ===
true)
1232 return (
bool)\CUserOptions::GetOption(
'mobile',
'iOsLastActivityDate')
1233 || (bool)\CUserOptions::GetOption(
'mobile',
'AndroidLastActivityDate')