19 private static $statisticIncluded =
null;
20 private static $saleIncluded =
null;
34 public static function addProduct(array $product, array $basketFields = [], array $options = [])
38 if (empty($product[
'PRODUCT_ID']))
43 $productId = (int)$product[
'PRODUCT_ID'];
46 $result->addError(
new Main\
Error(Main\Localization\
Loc::getMessage(
'BX_CATALOG_PRODUCT_BASKET_ERR_NO_PRODUCT')));
50 $product[
'MODULE'] =
'catalog';
51 $product[
'PRODUCT_PROVIDER_CLASS'] = self::getDefaultProviderName();
53 if (!empty($basketFields))
54 $product = array_merge($product, $basketFields);
56 if (self::$saleIncluded ===
null)
57 self::$saleIncluded = Loader::includeModule(
'sale');
59 if (!self::$saleIncluded)
66 if (!empty($basketFields[
'LID']))
67 $siteId = $basketFields[
'LID'];
76 $basketClass = $registry->getBasketClassName();
80 $options[
'CHECK_PERMISSIONS'] =
'Y';
81 $options[
'USE_MERGE'] = (isset($options[
'USE_MERGE']) && $options[
'USE_MERGE'] ==
'N' ?
'N' :
'Y');
82 $options[
'CHECK_CRAWLERS'] =
'Y';
84 $result = static::add($basket, $product, $context, $options);
86 if ($result->isSuccess())
88 $saveResult = $basket->save();
89 if ($saveResult->isSuccess())
91 $resultData = $result->getData();
92 if (!empty($resultData[
'BASKET_ITEM']))
94 $item = $resultData[
'BASKET_ITEM'];
97 if (self::$statisticIncluded ===
null)
98 self::$statisticIncluded = Main\Loader::includeModule(
'statistic');
100 if (self::$statisticIncluded)
102 \CStatistic::Set_Event(
103 'sale2basket',
'catalog', $item->getField(
'DETAIL_PAGE_URL')
106 $result->setData(array(
107 'ID' => $item->getId()
124 $result->addErrors($saveResult->getErrors());
128 unset($basket, $context, $siteId);
148 $options[
'CHECK_CRAWLERS'] =
'N';
149 return static::add($basket, $fields, $context, $options);
167 if (!is_array($options))
168 $options = [
'USE_MERGE' => ($options ?
'Y' :
'N')];
169 $options[
'CHECK_PERMISSIONS'] =
'Y';
170 $options[
'CHECK_CRAWLERS'] =
'Y';
171 return static::add($basket, $fields, $context, $options);
188 private static function add(
Sale\
BasketBase $basket, array $fields, array $context, array $options = [])
192 if (!isset($options[
'CHECK_CRAWLERS']) || $options[
'CHECK_CRAWLERS'] ==
'Y')
194 $validBuyer = static::checkCurrentUser();
195 if (!$validBuyer->isSuccess())
197 $result->addErrors($validBuyer->getErrors());
203 if (empty($fields[
'PRODUCT_ID']))
205 $result->addError(
new Main\Error(
Loc::getMessage(
'BX_CATALOG_PRODUCT_BASKET_ERR_NO_PRODUCT')));
208 $productId = (int)$fields[
'PRODUCT_ID'];
211 $result->addError(
new Main\Error(Main\Localization\
Loc::getMessage(
'BX_CATALOG_PRODUCT_BASKET_ERR_NO_PRODUCT')));
214 unset($fields[
'PRODUCT_ID']);
216 if (empty($fields[
'QUANTITY']))
218 $result->addError(
new Main\
Error(
Loc::getMessage(
'BX_CATALOG_PRODUCT_BASKET_ERR_EMPTY_QUANTITY')));
221 $quantity = (float)$fields[
'QUANTITY'];
224 $result->addError(
new Main\
Error(
Loc::getMessage(
'BX_CATALOG_PRODUCT_BASKET_ERR_EMPTY_QUANTITY')));
228 if (self::$saleIncluded ===
null)
229 self::$saleIncluded = Loader::includeModule(
'sale');
231 if (!self::$saleIncluded)
241 if (array_key_exists(
'MODULE', $fields))
243 $module = $fields[
'MODULE'];
244 unset($fields[
'MODULE']);
248 $module ===
'catalog'
249 && !isset($fields[
'PRODUCT_PROVIDER_CLASS'])
252 $fields[
'PRODUCT_PROVIDER_CLASS'] = self::getDefaultProviderName();
256 'PRODUCT_PROVIDER_CLASS' =>
true,
257 'CALLBACK_FUNC' =>
true,
258 'PAY_CALLBACK_FUNC' =>
true,
261 $presets = array_merge($presets, array_intersect_key($fields, $transferFields));
262 $fields = array_diff_key($fields, $transferFields);
263 unset($transferFields);
265 $propertyList = (!empty($fields[
'PROPS']) && is_array($fields[
'PROPS']) ? $fields[
'PROPS'] : []);
266 if (array_key_exists(
'PROPS', $fields))
267 unset($fields[
'PROPS']);
269 if ($module ==
'catalog')
271 $elementFilter = array(
274 'ACTIVE_DATE' =>
'Y',
275 'CHECK_PERMISSIONS' =>
'N'
278 if (!empty($options[
'CHECK_PERMISSIONS']) && $options[
'CHECK_PERMISSIONS'] ==
"Y")
280 $elementFilter[
'CHECK_PERMISSIONS'] =
'Y';
281 $elementFilter[
'MIN_PERMISSION'] =
'R';
282 if (isset($context[
'USER_ID']))
283 $elementFilter[
'PERMISSIONS_BY'] = $context[
'USER_ID'];
286 $iterator = \CIBlockElement::GetList(
299 if (!($elementFields = $iterator->GetNext()))
301 $result->addError(
new Main\
Error(
Loc::getMessage(
'BX_CATALOG_PRODUCT_BASKET_ERR_NO_IBLOCK_ELEMENT')));
305 $iterator = Catalog\ProductTable::getList(array(
307 'ID',
'TYPE',
'AVAILABLE',
'CAN_BUY_ZERO',
'QUANTITY_TRACE',
'QUANTITY',
308 'WEIGHT',
'WIDTH',
'HEIGHT',
'LENGTH',
309 'MEASURE',
'BARCODE_MULTI'
311 'filter' => array(
'=ID' => $productId)
313 $productFields = $iterator->fetch();
315 if (empty($productFields))
323 && (
string)Main\
Config\Option::get(
'catalog',
'show_catalog_tab_with_offers') !=
'Y'
326 $result->addError(
new Main\
Error(
Loc::getMessage(
'BX_CATALOG_PRODUCT_BASKET_ERR_CANNOT_ADD_SKU')));
331 $result->addError(
new Main\
Error(
Loc::getMessage(
'BX_CATALOG_PRODUCT_BASKET_ERR_PRODUCT_RUN_OUT')));
336 $skuInfo = \CCatalogSku::GetProductInfo($productId, $elementFields[
'IBLOCK_ID']);
339 $result->addError(
new Main\
Error(
Loc::getMessage(
'BX_CATALOG_PRODUCT_BASKET_ERR_PRODUCT_BAD_TYPE')));
344 $parentIterator = \CIBlockElement::GetList(
347 'ID' => $skuInfo[
'ID'],
348 'IBLOCK_ID' => $skuInfo[
'IBLOCK_ID'],
350 'ACTIVE_DATE' =>
'Y',
351 'CHECK_PERMISSIONS' =>
'N'
355 array(
'ID',
'IBLOCK_ID',
'XML_ID')
357 $parent = $parentIterator->Fetch();
363 elseif (mb_strpos($elementFields[
"~XML_ID"],
'#') ===
false)
365 $elementFields[
"~XML_ID"] = $parent[
'XML_ID'].
'#'.$elementFields[
"~XML_ID"];
367 unset($parent, $parentIterator);
373 $allSets = \CCatalogProductSet::getAllSetsByProduct($productId, \CCatalogProductSet::TYPE_SET);
376 $result->addError(
new Main\
Error(
Loc::getMessage(
'BX_CATALOG_PRODUCT_BASKET_ERR_NO_PRODUCT_SET')));
379 $set = current($allSets);
382 foreach ($set[
'ITEMS'] as $item)
384 if ($item[
'ITEM_ID'] != $item[
'OWNER_ID'])
385 $itemIds[$item[
'ITEM_ID']] = $item[
'ITEM_ID'];
389 $result->addError(
new Main\
Error(
Loc::getMessage(
'BX_CATALOG_PRODUCT_BASKET_ERR_NO_PRODUCT_SET')));
396 'ACTIVE_DATE' =>
'Y',
397 'CHECK_PERMISSIONS' =>
'N'
399 if (!empty($options[
'CHECK_PERMISSIONS']) && $options[
'CHECK_PERMISSIONS'] ==
"Y")
401 $setFilter[
'CHECK_PERMISSIONS'] =
'Y';
402 $setFilter[
'MIN_PERMISSION'] =
'R';
403 if (isset($context[
'USER_ID']))
404 $setFilter[
'PERMISSIONS_BY'] = $context[
'USER_ID'];
407 $iterator = \CIBlockElement::GetList(
412 array(
'ID',
'IBLOCK_ID')
414 while ($row = $iterator->Fetch())
416 if (isset($itemIds[$row[
'ID']]))
417 unset($itemIds[$row[
'ID']]);
419 unset($row, $iterator);
420 if (!empty($itemIds))
422 $result->addError(
new Main\
Error(
Loc::getMessage(
'BX_CATALOG_PRODUCT_BASKET_ERR_NO_PRODUCT_SET_ITEMS')));
427 $propertyIndex = self::getPropertyIndex(
'CATALOG.XML_ID', $propertyList);
428 if (!isset($fields[
'CATALOG_XML_ID']) || $propertyIndex ===
null)
430 $iBlockXmlID = (string)\CIBlock::GetArrayByID($elementFields[
'IBLOCK_ID'],
'XML_ID');
431 if ($iBlockXmlID !==
'')
433 $fields[
'CATALOG_XML_ID'] = $iBlockXmlID;
434 $propertyData = array(
435 'NAME' =>
'Catalog XML_ID',
436 'CODE' =>
'CATALOG.XML_ID',
437 'VALUE' => $iBlockXmlID
439 if ($propertyIndex !==
null)
440 $propertyList[$propertyIndex] = $propertyData;
442 $propertyList[] = $propertyData;
443 unset($propertyData);
448 $propertyIndex = self::getPropertyIndex(
'PRODUCT.XML_ID', $propertyList);
449 if (!isset($fields[
'PRODUCT_XML_ID']) || $propertyIndex ===
null)
451 $fields[
'PRODUCT_XML_ID'] = $elementFields[
'~XML_ID'];
452 $propertyData = array(
453 'NAME' =>
'Product XML_ID',
454 'CODE' =>
'PRODUCT.XML_ID',
455 'VALUE' => $elementFields[
'~XML_ID']
457 if ($propertyIndex !==
null)
458 $propertyList[$propertyIndex] = $propertyData;
460 $propertyList[] = $propertyData;
461 unset($propertyData);
463 unset($propertyIndex);
466 $productFields[
'MEASURE'] = (int)$productFields[
'MEASURE'];
467 $productFields[
'MEASURE_NAME'] =
'';
468 $productFields[
'MEASURE_CODE'] = 0;
469 if ($productFields[
'MEASURE'] <= 0)
471 $measure = \CCatalogMeasure::getDefaultMeasure(
true,
true);
472 $productFields[
'MEASURE_NAME'] = $measure[
'~SYMBOL_RUS'];
473 $productFields[
'MEASURE_CODE'] = $measure[
'CODE'];
478 $measureIterator = \CCatalogMeasure::getList(
480 [
'ID' => $productFields[
'MEASURE']],
483 [
'ID',
'SYMBOL_RUS',
'CODE']
485 $measure = $measureIterator->Fetch();
486 unset($measureIterator);
487 if (!empty($measure))
489 $productFields[
'MEASURE_NAME'] = $measure[
'SYMBOL_RUS'];
490 $productFields[
'MEASURE_CODE'] = $measure[
'CODE'];
495 if (isset($options[
'FILL_PRODUCT_PROPERTIES']) && $options[
'FILL_PRODUCT_PROPERTIES'] ===
'Y')
499 self::fillOfferProperties($propertyList, $productId, $elementFields[
'IBLOCK_ID']);
503 $fields[
'TYPE'] = Sale\Internals\Catalog\ProductTypeMapper::getType((
int)$productFields[
'TYPE']);
506 'DETAIL_PAGE_URL' => $elementFields[
'~DETAIL_PAGE_URL'],
507 'BARCODE_MULTI' => $productFields[
'BARCODE_MULTI'],
508 'WEIGHT' => (float)$productFields[
'WEIGHT'],
510 'WIDTH' => $productFields[
'WIDTH'],
511 'HEIGHT' => $productFields[
'HEIGHT'],
512 'LENGTH' => $productFields[
'LENGTH']
514 'MEASURE_ID' => $productFields[
'MEASURE'],
515 'MEASURE_NAME' => $productFields[
'MEASURE_NAME'],
516 'MEASURE_CODE' => $productFields[
'MEASURE_CODE']
519 unset($productFields);
522 if (static::isCompatibilityEventAvailable())
524 $eventFields = array_merge($presets, $fields);
525 $eventFields[
'MODULE'] = $module;
526 $eventFields[
'PROPS'] = $propertyList;
528 $eventResult = static::runCompatibilityEvent($eventFields);
529 if ($eventResult ===
false)
534 foreach ($eventResult as $key => $value)
536 if (isset($presets[$key]))
538 if ($presets[$key] !== $value)
540 $presets[$key] = $value;
543 elseif (!isset($fields[$key]) || $fields[$key] !== $value)
545 $fields[$key] = $value;
550 $propertyList = $eventResult[
'PROPS'];
556 if (!isset($options[
'USE_MERGE']) || $options[
'USE_MERGE'] ===
'Y')
558 $basketItem = $basket->getExistsItem($module, $productId, $propertyList);
563 $fields[
'QUANTITY'] = $basketItem->isDelay() ? $quantity : $basketItem->getQuantity() + $quantity;
567 $fields[
'QUANTITY'] = $quantity;
568 $fields[
'DELAY'] ??=
'N';
569 $basketCode = !empty($fields[
'BASKET_CODE']) ? $fields[
'BASKET_CODE'] :
null;
570 $basketItem = $basket->createItem($module, $productId, $basketCode);
575 throw new Main\ObjectNotFoundException(
'BasketItem');
579 $propertyCollection = $basketItem->getPropertyCollection();
580 if ($propertyCollection)
582 $propertyCollection->redefine($propertyList);
585 $r = $basketItem->setFields($presets);
586 if (!$r->isSuccess())
588 $result->addErrors($r->getErrors());
593 if(!empty($elementFields[
'~NAME']))
595 $basketItem->setField(
'NAME', $elementFields[
'~NAME']);
598 $r = $basketItem->setField(
'QUANTITY', $fields[
'QUANTITY']);
599 if (!$r->isSuccess())
601 $result->addErrors($r->getErrors());
604 unset($fields[
'QUANTITY']);
606 $settableFields = array_fill_keys($basketItem::getSettableFields(),
true);
607 $basketFields = array_intersect_key($fields, $settableFields);
609 if (!empty($basketFields))
611 $r = $basketItem->setFields($basketFields);
612 if (!$r->isSuccess())
614 $result->addErrors($r->getErrors());
619 $result->setData([
'BASKET_ITEM' => $basketItem]);
627 private static function isCompatibilityEventAvailable()
629 return Main\Config\Option::get(
'sale',
'expiration_processing_events',
'N') ===
'Y';
636 private static function runCompatibilityEvent(array $fields)
638 foreach (GetModuleEvents(
"sale",
"OnBeforeBasketAdd",
true) as $event)
640 if (ExecuteModuleEventEx($event, array(&$fields)) ===
false)
653 private static function fillOfferProperties(array &$propertyList, $id, $iblockId)
655 static $properties = [];
657 $skuInfo = \CCatalogSku::GetInfoByOfferIBlock($iblockId);
660 $skuPropertyId = $skuInfo[
'SKU_PROPERTY_ID'];
661 $parentIblockId = $skuInfo[
'PRODUCT_IBLOCK_ID'];
664 if (!isset($properties[$iblockId]))
666 $properties[$iblockId] = [];
667 $iterator = Iblock\PropertyTable::getList([
673 '=IBLOCK_ID' => $iblockId,
675 '@PROPERTY_TYPE' => [
676 Iblock\PropertyTable::TYPE_ELEMENT,
677 Iblock\PropertyTable::TYPE_LIST,
678 Iblock\PropertyTable::TYPE_STRING,
686 while ($row = $iterator->fetch())
688 $row[
'ID'] = (int)$row[
'ID'];
689 if ($row[
'ID'] == $skuPropertyId)
691 $properties[$iblockId][] = $row[
'CODE'] ?? $row[
'ID'];
693 unset($row, $iterator);
695 if (empty($properties[$iblockId]))
698 $offerProperties = \CIBlockPriceTools::GetOfferProperties(
701 $properties[$iblockId]
703 unset($parentIblockId, $skuPropertyId);
705 if (empty($offerProperties))
709 if (!empty($propertyList))
711 foreach ($propertyList as $row)
713 if (!isset($row[
'CODE']))
715 $index = (string)$row[
'CODE'];
718 $codeMap[$index] =
true;
722 foreach ($offerProperties as $row)
724 $index = (string)$row[
'CODE'];
725 if (isset($codeMap[$index]))
727 $codeMap[$index] =
true;
728 $propertyList[] = $row;
730 unset($index, $row, $codeMap, $offerProperties);
740 private static function getPropertyIndex($code, array $propertyList = array())
742 $propertyIndex =
null;
743 if (empty($propertyList))
744 return $propertyIndex;
746 foreach ($propertyList as $index => $propertyData)
748 if (!empty($propertyData[
'CODE']) && $code == $propertyData[
'CODE'])
750 $propertyIndex = $index;
754 unset($index, $propertyData);
756 return $propertyIndex;
764 return "\Bitrix\Catalog\Product\CatalogProvider";
774 $result = static::checkCurrentUser();
775 return $result->isSuccess();
784 private static function checkCurrentUser()
788 if (self::$statisticIncluded ===
null)
789 self::$statisticIncluded = Main\Loader::includeModule(
'statistic');
791 if (!self::$statisticIncluded)
794 if (isset($_SESSION[
'SESS_SEARCHER_ID']) && (
int)$_SESSION[
'SESS_SEARCHER_ID'] > 0)