• Модуль: catalog
  • Путь к файлу: ~/bitrix/modules/catalog/lib/product/basket.php
  • Класс: BitrixCatalogProductBasket
  • Вызов: Basket::add
static function add(SaleBasketBase $basket, array $fields, array $context, array $options = [])
{
	$result = new MainResult();

	if (!isset($options['CHECK_CRAWLERS']) || $options['CHECK_CRAWLERS'] == 'Y')
	{
		$validBuyer = static::checkCurrentUser();
		if (!$validBuyer->isSuccess())
		{
			$result->addErrors($validBuyer->getErrors());
			return $result;
		}
		unset($validBuyer);
	}

	if (empty($fields['PRODUCT_ID']))
	{
		$result->addError(new MainError(Loc::getMessage('BX_CATALOG_PRODUCT_BASKET_ERR_NO_PRODUCT')));
		return $result;
	}
	$productId = (int)$fields['PRODUCT_ID'];
	if ($productId <= 0)
	{
		$result->addError(new MainError(MainLocalizationLoc::getMessage('BX_CATALOG_PRODUCT_BASKET_ERR_NO_PRODUCT')));
		return $result;
	}
	unset($fields['PRODUCT_ID']);

	if (empty($fields['QUANTITY']))
	{
		$result->addError(new MainError(Loc::getMessage('BX_CATALOG_PRODUCT_BASKET_ERR_EMPTY_QUANTITY')));
		return $result;
	}
	$quantity = (float)$fields['QUANTITY'];
	if ($quantity <= 0)
	{
		$result->addError(new MainError(Loc::getMessage('BX_CATALOG_PRODUCT_BASKET_ERR_EMPTY_QUANTITY')));
		return $result;
	}

	if (self::$saleIncluded === null)
		self::$saleIncluded = Loader::includeModule('sale');

	if (!self::$saleIncluded)
	{
		$result->addError(new MainError(Loc::getMessage('BX_CATALOG_PRODUCT_BASKET_ERR_NO_SALE')));
		return $result;
	}

	$module = 'catalog';

	$presets = ['PRODUCT_ID' => $productId];

	if (array_key_exists('MODULE', $fields))
	{
		$module = $fields['MODULE'];
		unset($fields['MODULE']);
	}

	if (
		$module === 'catalog'
		&& !isset($fields['PRODUCT_PROVIDER_CLASS'])
	)
	{
		$fields['PRODUCT_PROVIDER_CLASS'] = self::getDefaultProviderName();
	}

	$transferFields = [
		'PRODUCT_PROVIDER_CLASS' => true,
		'CALLBACK_FUNC' => true,
		'PAY_CALLBACK_FUNC' => true,
		'SUBSCRIBE' => true
	];
	$presets = array_merge($presets, array_intersect_key($fields, $transferFields));
	$fields = array_diff_key($fields, $transferFields);
	unset($transferFields);

	$propertyList = (!empty($fields['PROPS']) && is_array($fields['PROPS']) ? $fields['PROPS'] : []);
	if (array_key_exists('PROPS', $fields))
		unset($fields['PROPS']);

	if ($module == 'catalog')
	{
		$elementFilter = array(
			'ID' => $productId,
			'ACTIVE' => 'Y',
			'ACTIVE_DATE' => 'Y',
			'CHECK_PERMISSIONS' => 'N'
		);

		if (!empty($options['CHECK_PERMISSIONS']) && $options['CHECK_PERMISSIONS'] == "Y")
		{
			$elementFilter['CHECK_PERMISSIONS'] = 'Y';
			$elementFilter['MIN_PERMISSION'] = 'R';
			if (isset($context['USER_ID']))
				$elementFilter['PERMISSIONS_BY'] = $context['USER_ID'];
		}

		$iterator = CIBlockElement::GetList(
			array(),
			$elementFilter,
			false,
			false,
			array(
				"ID",
				"IBLOCK_ID",
				"XML_ID",
				"NAME",
				"DETAIL_PAGE_URL",
			)
		);
		if (!($elementFields = $iterator->GetNext()))
		{
			$result->addError(new MainError(Loc::getMessage('BX_CATALOG_PRODUCT_BASKET_ERR_NO_IBLOCK_ELEMENT')));
			return $result;
		}

		$iterator = CatalogProductTable::getList(array(
			'select' => array(
				'ID', 'TYPE', 'AVAILABLE', 'CAN_BUY_ZERO', 'QUANTITY_TRACE', 'QUANTITY',
				'WEIGHT', 'WIDTH', 'HEIGHT', 'LENGTH',
				'MEASURE', 'BARCODE_MULTI'
			),
			'filter' => array('=ID' => $productId)
		));
		$productFields = $iterator->fetch();
		unset($iterator);
		if (empty($productFields))
		{
			$result->addError(new MainError(Loc::getMessage('BX_CATALOG_PRODUCT_BASKET_ERR_NO_PRODUCT')));
			return $result;
		}

		if (
			($productFields['TYPE'] == CatalogProductTable::TYPE_SKU || $productFields['TYPE'] == CatalogProductTable::TYPE_EMPTY_SKU)
			&& (string)MainConfigOption::get('catalog', 'show_catalog_tab_with_offers') != 'Y'
		)
		{
			$result->addError(new MainError(Loc::getMessage('BX_CATALOG_PRODUCT_BASKET_ERR_CANNOT_ADD_SKU')));
			return $result;
		}
		if ($productFields['AVAILABLE'] != CatalogProductTable::STATUS_YES)
		{
			$result->addError(new MainError(Loc::getMessage('BX_CATALOG_PRODUCT_BASKET_ERR_PRODUCT_RUN_OUT')));
			return $result;
		}
		if ($productFields['TYPE'] == CatalogProductTable::TYPE_OFFER)
		{
			$skuInfo = CCatalogSku::GetProductInfo($productId, $elementFields['IBLOCK_ID']);
			if (empty($skuInfo))
			{
				$result->addError(new MainError(Loc::getMessage('BX_CATALOG_PRODUCT_BASKET_ERR_PRODUCT_BAD_TYPE')));
				return $result;
			}
			else
			{
				$parentIterator = CIBlockElement::GetList(
					array(),
					array(
						'ID' => $skuInfo['ID'],
						'IBLOCK_ID' => $skuInfo['IBLOCK_ID'],
						'ACTIVE' => 'Y',
						'ACTIVE_DATE' => 'Y',
						'CHECK_PERMISSIONS' => 'N'
					),
					false,
					false,
					array('ID', 'IBLOCK_ID', 'XML_ID')
				);
				$parent = $parentIterator->Fetch();
				if (empty($parent))
				{
					$result->addError(new MainError(Loc::getMessage('BX_CATALOG_PRODUCT_BASKET_ERR_NO_PRODUCT')));
					return $result;
				}
				elseif (mb_strpos($elementFields["~XML_ID"], '#') === false)
				{
					$elementFields["~XML_ID"] = $parent['XML_ID'].'#'.$elementFields["~XML_ID"];
				}
				unset($parent, $parentIterator);
			}
		}

		if ($productFields['TYPE'] == CatalogProductTable::TYPE_SET)
		{
			$allSets = CCatalogProductSet::getAllSetsByProduct($productId, CCatalogProductSet::TYPE_SET);
			if (empty($allSets))
			{
				$result->addError(new MainError(Loc::getMessage('BX_CATALOG_PRODUCT_BASKET_ERR_NO_PRODUCT_SET')));
				return $result;
			}
			$set = current($allSets);
			unset($allSets);
			$itemIds = array();
			foreach ($set['ITEMS'] as $item)
			{
				if ($item['ITEM_ID'] != $item['OWNER_ID'])
					$itemIds[$item['ITEM_ID']] = $item['ITEM_ID'];
			}
			if (empty($itemIds))
			{
				$result->addError(new MainError(Loc::getMessage('BX_CATALOG_PRODUCT_BASKET_ERR_NO_PRODUCT_SET')));
				return $result;
			}

			$setFilter = array(
				'ID' => $itemIds,
				'ACTIVE' => 'Y',
				'ACTIVE_DATE' => 'Y',
				'CHECK_PERMISSIONS' => 'N'
			);
			if (!empty($options['CHECK_PERMISSIONS']) && $options['CHECK_PERMISSIONS'] == "Y")
			{
				$setFilter['CHECK_PERMISSIONS'] = 'Y';
				$setFilter['MIN_PERMISSION'] = 'R';
				if (isset($context['USER_ID']))
					$setFilter['PERMISSIONS_BY'] = $context['USER_ID'];
			}

			$iterator = CIBlockElement::GetList(
				array(),
				$setFilter,
				false,
				false,
				array('ID', 'IBLOCK_ID')
			);
			while ($row = $iterator->Fetch())
			{
				if (isset($itemIds[$row['ID']]))
					unset($itemIds[$row['ID']]);
			}
			unset($row, $iterator);
			if (!empty($itemIds))
			{
				$result->addError(new MainError(Loc::getMessage('BX_CATALOG_PRODUCT_BASKET_ERR_NO_PRODUCT_SET_ITEMS')));
				return $result;
			}
		}

		$propertyIndex = self::getPropertyIndex('CATALOG.XML_ID', $propertyList);
		if (!isset($fields['CATALOG_XML_ID']) || $propertyIndex === null)
		{
			$iBlockXmlID = (string)CIBlock::GetArrayByID($elementFields['IBLOCK_ID'], 'XML_ID');
			if ($iBlockXmlID !== '')
			{
				$fields['CATALOG_XML_ID'] = $iBlockXmlID;
				$propertyData = array(
					'NAME' => 'Catalog XML_ID',
					'CODE' => 'CATALOG.XML_ID',
					'VALUE' => $iBlockXmlID
				);
				if ($propertyIndex !== null)
					$propertyList[$propertyIndex] = $propertyData;
				else
					$propertyList[] = $propertyData;
				unset($propertyData);
			}
			unset($iBlockXmlID);
		}

		$propertyIndex = self::getPropertyIndex('PRODUCT.XML_ID', $propertyList);
		if (!isset($fields['PRODUCT_XML_ID']) || $propertyIndex === null)
		{
			$fields['PRODUCT_XML_ID'] = $elementFields['~XML_ID'];
			$propertyData = array(
				'NAME' => 'Product XML_ID',
				'CODE' => 'PRODUCT.XML_ID',
				'VALUE' => $elementFields['~XML_ID']
			);
			if ($propertyIndex !== null)
				$propertyList[$propertyIndex] = $propertyData;
			else
				$propertyList[] = $propertyData;
			unset($propertyData);
		}
		unset($propertyIndex);

		//TODO: change to d7 measure class
		$productFields['MEASURE'] = (int)$productFields['MEASURE'];
		$productFields['MEASURE_NAME'] = '';
		$productFields['MEASURE_CODE'] = 0;
		if ($productFields['MEASURE'] <= 0)
		{
			$measure = CCatalogMeasure::getDefaultMeasure(true, true);
			$productFields['MEASURE_NAME'] = $measure['~SYMBOL_RUS'];
			$productFields['MEASURE_CODE'] = $measure['CODE'];
			unset($measure);
		}
		else
		{
			$measureIterator = CCatalogMeasure::getList(
				[],
				['ID' => $productFields['MEASURE']],
				false,
				false,
				['ID', 'SYMBOL_RUS', 'CODE']
			);
			$measure = $measureIterator->Fetch();
			unset($measureIterator);
			if (!empty($measure))
			{
				$productFields['MEASURE_NAME'] = $measure['SYMBOL_RUS'];
				$productFields['MEASURE_CODE'] = $measure['CODE'];
			}
			unset($measure);
		}

		if (isset($options['FILL_PRODUCT_PROPERTIES']) && $options['FILL_PRODUCT_PROPERTIES'] === 'Y')
		{
			if ($productFields['TYPE'] == CatalogProductTable::TYPE_OFFER)
			{
				self::fillOfferProperties($propertyList, $productId, $elementFields['IBLOCK_ID']);
			}
		}

		$fields['TYPE'] = SaleInternalsCatalogProductTypeMapper::getType((int)$productFields['TYPE']);

		$fields += [
			'DETAIL_PAGE_URL' => $elementFields['~DETAIL_PAGE_URL'],
			'BARCODE_MULTI' => $productFields['BARCODE_MULTI'],
			'WEIGHT' => (float)$productFields['WEIGHT'],
			'DIMENSIONS' => [
				'WIDTH' => $productFields['WIDTH'],
				'HEIGHT' => $productFields['HEIGHT'],
				'LENGTH' => $productFields['LENGTH']
			],
			'MEASURE_ID' => $productFields['MEASURE'],
			'MEASURE_NAME' => $productFields['MEASURE_NAME'],
			'MEASURE_CODE' => $productFields['MEASURE_CODE']
		];

		unset($productFields);
	}

	if (static::isCompatibilityEventAvailable())
	{
		$eventFields = array_merge($presets, $fields);
		$eventFields['MODULE'] = $module;
		$eventFields['PROPS'] = $propertyList;

		$eventResult = static::runCompatibilityEvent($eventFields);
		if ($eventResult === false)
		{
			return $result;
		}

		foreach ($eventResult as $key => $value)
		{
			if (isset($presets[$key]))
			{
				if ($presets[$key] !== $value)
				{
					$presets[$key] = $value;
				}
			}
			elseif (!isset($fields[$key]) || $fields[$key] !== $value)
			{
				$fields[$key] = $value;
			}
		}
		unset($key, $value);

		$propertyList = $eventResult['PROPS'];
		unset($eventResult);
	}

	$basketItem = null;
	// using merge by default
	if (!isset($options['USE_MERGE']) || $options['USE_MERGE'] === 'Y')
	{
		$basketItem = $basket->getExistsItem($module, $productId, $propertyList);
	}

	if ($basketItem)
	{
		$fields['QUANTITY'] = $basketItem->isDelay() ? $quantity : $basketItem->getQuantity() + $quantity;
	}
	else
	{
		$fields['QUANTITY'] = $quantity;
		$fields['DELAY'] ??= 'N';
		$basketCode = !empty($fields['BASKET_CODE']) ? $fields['BASKET_CODE'] : null;
		$basketItem = $basket->createItem($module, $productId, $basketCode);
	}

	if (!$basketItem)
	{
		throw new MainObjectNotFoundException('BasketItem');
	}

	/** @var SaleBasketPropertiesCollection $propertyCollection */
	$propertyCollection = $basketItem->getPropertyCollection();
	if ($propertyCollection)
	{
		$propertyCollection->redefine($propertyList);
	}

	$r = $basketItem->setFields($presets);
	if (!$r->isSuccess())
	{
		$result->addErrors($r->getErrors());
		return $result;
	}

	//If error happend while setting quantity field we will know the name of product.
	if(!empty($elementFields['~NAME']))
	{
		$basketItem->setField('NAME', $elementFields['~NAME']);
	}

	$r = $basketItem->setField('QUANTITY', $fields['QUANTITY']);
	if (!$r->isSuccess())
	{
		$result->addErrors($r->getErrors());
		return $result;
	}
	unset($fields['QUANTITY']);

	$settableFields = array_fill_keys($basketItem::getSettableFields(), true);
	$basketFields = array_intersect_key($fields, $settableFields);

	if (!empty($basketFields))
	{
		$r = $basketItem->setFields($basketFields);
		if (!$r->isSuccess())
		{
			$result->addErrors($r->getErrors());
			return $result;
		}
	}

	$result->setData(['BASKET_ITEM' => $basketItem]);

	return $result;
}