Your IP : 18.226.185.28


Current Path : /home/bitrix/ext_www/easy-comfort.com.ua/bitrix/modules/catalog/general/
Upload File :
Current File : /home/bitrix/ext_www/easy-comfort.com.ua/bitrix/modules/catalog/general/product.php

<?
/** @global \CMain $APPLICATION */
use Bitrix\Main\Localization\Loc,
	Bitrix\Main,
	Bitrix\Currency,
	Bitrix\Catalog,
	Bitrix\Sale;

Loc::loadMessages(__FILE__);

class CAllCatalogProduct
{
	const TYPE_PRODUCT = Catalog\ProductTable::TYPE_PRODUCT;
	const TYPE_SET = Catalog\ProductTable::TYPE_SET;
	const TYPE_SKU = Catalog\ProductTable::TYPE_SKU;
	const TYPE_OFFER = Catalog\ProductTable::TYPE_OFFER;
	const TYPE_FREE_OFFER = Catalog\ProductTable::TYPE_FREE_OFFER;
	const TYPE_EMPTY_SKU = Catalog\ProductTable::TYPE_EMPTY_SKU;

	const TIME_PERIOD_HOUR = Catalog\ProductTable::PAYMENT_PERIOD_HOUR;
	const TIME_PERIOD_DAY = Catalog\ProductTable::PAYMENT_PERIOD_DAY;
	const TIME_PERIOD_WEEK = Catalog\ProductTable::PAYMENT_PERIOD_WEEK;
	const TIME_PERIOD_MONTH = Catalog\ProductTable::PAYMENT_PERIOD_MONTH;
	const TIME_PERIOD_QUART = Catalog\ProductTable::PAYMENT_PERIOD_QUART;
	const TIME_PERIOD_SEMIYEAR = Catalog\ProductTable::PAYMENT_PERIOD_SEMIYEAR;
	const TIME_PERIOD_YEAR = Catalog\ProductTable::PAYMENT_PERIOD_YEAR;
	const TIME_PERIOD_DOUBLE_YEAR = Catalog\ProductTable::PAYMENT_PERIOD_DOUBLE_YEAR;

	/** @deprecated deprecated since catalog 17.6.3 */
	protected static $arProductCache = array();

	/** @deprecated deprecated since catalog 17.0.11 */
	protected static $usedCurrency = null;
	/** @deprecated deprecated since catalog 17.5.1 */
	protected static $optimalPriceWithVat = true;
	/** @deprecated deprecated since catalog 17.5.1 */
	protected static $useDiscount = true;

	protected static $saleIncluded = null;
	protected static $useSaleDiscount = null;
	protected static $vatCache = array();

	private static $existPriceTypeDiscounts = false;

	/**
	 * @deprecated deprecated since catalog 17.0.11
	 * @see \Bitrix\Catalog\Product\Price\Calculation::setConfig()
	 *
	 * @param string $currency
	 * @return void
	 */
	public static function setUsedCurrency($currency)
	{
		Catalog\Product\Price\Calculation::setConfig(array('CURRENCY' => $currency));
	}

	/**
	 * @deprecated deprecated since catalog 17.0.11
	 * @see \Bitrix\Catalog\Product\Price\Calculation::getConfig()
	 *
	 * @return null|string
	 */
	public static function getUsedCurrency()
	{
		$config = Catalog\Product\Price\Calculation::getConfig();
		return $config['CURRENCY'];
	}

	/**
	 * @deprecated deprecated since catalog 17.0.11
	 * @see \Bitrix\Catalog\Product\Price\Calculation::setConfig()
	 *
	 * @return void
	 */
	public static function clearUsedCurrency()
	{
		Catalog\Product\Price\Calculation::setConfig(array('CURRENCY' => null));
	}

	/**
	 * @deprecated deprecated since catalog 17.5.1
	 * @see \Bitrix\Catalog\Product\Price\Calculation::setConfig()
	 *
	 * @param bool $mode
	 * @return void
	 */
	public static function setPriceVatIncludeMode($mode)
	{
		Catalog\Product\Price\Calculation::setConfig(array('RESULT_WITH_VAT' => $mode));
	}

	/**
	 * @deprecated deprecated since catalog 17.5.1
	 * @see \Bitrix\Catalog\Product\Price\Calculation::setConfig()
	 *
	 * @return bool
	 */
	public static function getPriceVatIncludeMode()
	{
		return Catalog\Product\Price\Calculation::isIncludingVat();
	}

	/**
	 * @deprecated deprecated since catalog 17.5.1
	 * @see \Bitrix\Catalog\Product\Price\Calculation::setConfig()
	 *
	 * @param bool $use
	 * @return void
	 */
	public static function setUseDiscount($use)
	{
		Catalog\Product\Price\Calculation::setConfig(array('USE_DISCOUNTS' => $use));
	}

	/**
	 * @deprecated deprecated since catalog 17.5.1
	 * @see \Bitrix\Catalog\Product\Price\Calculation::getConfig()
	 *
	 * @return bool
	 */
	public static function getUseDiscount()
	{
		return Catalog\Product\Price\Calculation::isAllowedUseDiscounts();
	}

	/**
	 * @deprecated deprecated since catalog 17.6.3
	 *
	 * @return void
	 */
	public static function ClearCache()
	{
		self::$arProductCache = [];
		self::$vatCache = [];
	}

	/**
	 * @param array $product
	 * @return bool
	 */
	public static function isAvailable($product)
	{
		$result = true;
		if (!empty($product) && is_array($product))
		{
			if (isset($product['QUANTITY']) && isset($product['QUANTITY_TRACE']) && isset($product['CAN_BUY_ZERO']))
			{
				$result = !((float)$product['QUANTITY'] <= 0 && $product['QUANTITY_TRACE'] == 'Y' && $product['CAN_BUY_ZERO'] == 'N');
			}
		}
		return $result;
	}

	/**
	 * @deprecated deprecated since catalog 15.5.2
	 * @see \Bitrix\Catalog\ProductTable::isExistProduct()
	 *
	 * @param int $intID
	 * @return bool
	 */
	public static function IsExistProduct($intID)
	{
		return Catalog\ProductTable::isExistProduct($intID);
	}

	public static function CheckFields($ACTION, &$arFields, $ID = 0)
	{
		global $APPLICATION;

		$arMsg = array();
		$boolResult = true;

		$ACTION = strtoupper($ACTION);
		$ID = (int)$ID;
		if ($ACTION == "ADD" && (!is_set($arFields, "ID") || (int)$arFields["ID"]<=0))
		{
			$arMsg[] = array('id' => 'ID','text' => Loc::getMessage('KGP_EMPTY_ID'));
			$boolResult = false;
		}
		if ($ACTION != "ADD" && $ID <= 0)
		{
			$arMsg[] = array('id' => 'ID','text' => Loc::getMessage('KGP_EMPTY_ID'));
			$boolResult = false;
		}

		$clearFields = array(
			'NEGATIVE_AMOUNT_TRACE',
			'~NEGATIVE_AMOUNT_TRACE',
			'~TYPE',
			'~AVAILABLE'
		);
		if ($ACTION =='UPDATE')
		{
			$clearFields[] = 'ID';
			$clearFields[] = '~ID';
		}
		if ($ACTION == 'ADD')
		{
			$clearFields[] = 'BUNDLE';
			$clearFields[] = '~BUNDLE';
		}

		foreach ($clearFields as $fieldName)
		{
			if (array_key_exists($fieldName, $arFields))
				unset($arFields[$fieldName]);
		}
		unset($fieldName, $clearFields);

		if ('ADD' == $ACTION)
		{
			if (!array_key_exists('SUBSCRIBE', $arFields))
				$arFields['SUBSCRIBE'] = '';
			if (!isset($arFields['TYPE']))
				$arFields['TYPE'] = Catalog\ProductTable::TYPE_PRODUCT;
			$arFields['BUNDLE'] = Catalog\ProductTable::STATUS_NO;
		}

		if (is_set($arFields, "ID") || $ACTION=="ADD")
			$arFields["ID"] = (int)$arFields["ID"];
		if (is_set($arFields, "QUANTITY") || $ACTION=="ADD")
			$arFields["QUANTITY"] = doubleval($arFields["QUANTITY"]);
		if (is_set($arFields, "QUANTITY_RESERVED") || $ACTION=="ADD")
			$arFields["QUANTITY_RESERVED"] = doubleval($arFields["QUANTITY_RESERVED"]);
		if (is_set($arFields, "OLD_QUANTITY"))
			$arFields["OLD_QUANTITY"] = doubleval($arFields["OLD_QUANTITY"]);
		if (is_set($arFields, "WEIGHT") || $ACTION=="ADD")
			$arFields["WEIGHT"] = doubleval($arFields["WEIGHT"]);
		if (is_set($arFields, "WIDTH") || $ACTION=="ADD")
			$arFields["WIDTH"] = doubleval($arFields["WIDTH"]);
		if (is_set($arFields, "LENGTH") || $ACTION=="ADD")
			$arFields["LENGTH"] = doubleval($arFields["LENGTH"]);
		if (is_set($arFields, "HEIGHT") || $ACTION=="ADD")
			$arFields["HEIGHT"] = doubleval($arFields["HEIGHT"]);

		if (is_set($arFields, "VAT_ID") || $ACTION=="ADD")
			$arFields["VAT_ID"] = intval($arFields["VAT_ID"]);
		if ((is_set($arFields, "VAT_INCLUDED") || $ACTION=="ADD") && ($arFields["VAT_INCLUDED"] != "Y"))
			$arFields["VAT_INCLUDED"] = "N";

		if ((is_set($arFields, "QUANTITY_TRACE") || $ACTION=="ADD") && ($arFields["QUANTITY_TRACE"] != "Y" && $arFields["QUANTITY_TRACE"] != "N"))
			$arFields["QUANTITY_TRACE"] = "D";
		if ((is_set($arFields, "CAN_BUY_ZERO") || $ACTION=="ADD") && ($arFields["CAN_BUY_ZERO"] != "Y" && $arFields["CAN_BUY_ZERO"] != "N"))
			$arFields["CAN_BUY_ZERO"] = "D";
		if (isset($arFields['CAN_BUY_ZERO']))
			$arFields['NEGATIVE_AMOUNT_TRACE'] = $arFields['CAN_BUY_ZERO'];

		if ((is_set($arFields, "PRICE_TYPE") || $ACTION=="ADD") && ($arFields["PRICE_TYPE"] != "R") && ($arFields["PRICE_TYPE"] != "T"))
			$arFields["PRICE_TYPE"] = "S";

		if ((is_set($arFields, "RECUR_SCHEME_TYPE") || $ACTION=="ADD") && (strlen($arFields["RECUR_SCHEME_TYPE"]) <= 0 || !in_array($arFields["RECUR_SCHEME_TYPE"], Catalog\ProductTable::getPaymentPeriods(false))))
		{
			$arFields["RECUR_SCHEME_TYPE"] = self::TIME_PERIOD_DAY;
		}

		if ((is_set($arFields, "RECUR_SCHEME_LENGTH") || $ACTION=="ADD") && (intval($arFields["RECUR_SCHEME_LENGTH"])<=0))
			$arFields["RECUR_SCHEME_LENGTH"] = 0;

		if ((is_set($arFields, "TRIAL_PRICE_ID") || $ACTION=="ADD") && (intval($arFields["TRIAL_PRICE_ID"])<=0))
			$arFields["TRIAL_PRICE_ID"] = false;

		if ((is_set($arFields, "WITHOUT_ORDER") || $ACTION=="ADD") && ($arFields["WITHOUT_ORDER"] != "Y"))
			$arFields["WITHOUT_ORDER"] = "N";

		if ((is_set($arFields, "SELECT_BEST_PRICE") || $ACTION=="ADD") && ($arFields["SELECT_BEST_PRICE"] != "N"))
			$arFields["SELECT_BEST_PRICE"] = "Y";

		$existPurchasingPrice = array_key_exists('PURCHASING_PRICE', $arFields);
		$existPurchasingCurrency = array_key_exists('PURCHASING_CURRENCY', $arFields);
		if ($ACTION == 'ADD')
		{
			$purchasingPrice = false;
			$purchasingCurrency = false;

			if ($existPurchasingPrice)
			{
				$purchasingPrice = static::checkPriceValue($arFields['PURCHASING_PRICE']);
				if ($purchasingPrice !== false)
				{
					$purchasingCurrency = static::checkPriceCurrency($arFields['PURCHASING_CURRENCY']);
					if ($purchasingCurrency === false)
					{
						$arMsg[] = array('id' => 'PURCHASING_CURRENCY','text' => Loc::getMessage('BT_MOD_CATALOG_PROD_ERR_COST_CURRENCY'));
						$boolResult = false;
					}
				}
			}

			$arFields['PURCHASING_PRICE'] = $purchasingPrice;
			$arFields['PURCHASING_CURRENCY'] = $purchasingCurrency;
			unset($purchasingCurrency, $purchasingPrice);
		}
		else
		{
			if ($existPurchasingPrice || $existPurchasingCurrency)
			{
				if ($existPurchasingPrice)
				{
					$arFields['PURCHASING_PRICE'] = static::checkPriceValue($arFields['PURCHASING_PRICE']);
					if ($arFields['PURCHASING_PRICE'] === false)
					{
						$arFields['PURCHASING_CURRENCY'] = false;
					}
					else
					{
						if ($existPurchasingCurrency)
						{
							$purchasingCurrency = static::checkPriceCurrency($arFields['PURCHASING_CURRENCY']);
							if ($purchasingCurrency === false)
							{
								$arMsg[] = array('id' => 'PURCHASING_CURRENCY', 'text' => Loc::getMessage('BT_MOD_CATALOG_PROD_ERR_COST_CURRENCY'));
								$boolResult = false;
							}
							else
							{
								$arFields['PURCHASING_CURRENCY'] = $purchasingCurrency;
							}
							unset($purchasingCurrency);
						}
					}
				}
				elseif ($existPurchasingCurrency)
				{
					$purchasingCurrency = static::checkPriceCurrency($arFields['PURCHASING_CURRENCY']);
					if ($purchasingCurrency === false)
					{
						$arMsg[] = array('id' => 'PURCHASING_CURRENCY', 'text' => Loc::getMessage('BT_MOD_CATALOG_PROD_ERR_COST_CURRENCY'));
						$boolResult = false;
					}
					else
					{
						$arFields['PURCHASING_CURRENCY'] = $purchasingCurrency;
					}
					unset($purchasingCurrency);
				}
			}
		}
		unset($existPurchasingCurrency, $existPurchasingPrice);

		if ((is_set($arFields, 'BARCODE_MULTI') || 'ADD' == $ACTION) && 'Y' != $arFields['BARCODE_MULTI'])
			$arFields['BARCODE_MULTI'] = 'N';
		if (array_key_exists('SUBSCRIBE', $arFields))
		{
			if ('Y' != $arFields['SUBSCRIBE'] && 'N' != $arFields['SUBSCRIBE'])
				$arFields['SUBSCRIBE'] = 'D';
		}
		if (array_key_exists('BUNDLE', $arFields))
			$arFields['BUNDLE'] = ($arFields['BUNDLE'] == Catalog\ProductTable::STATUS_YES ? Catalog\ProductTable::STATUS_YES : Catalog\ProductTable::STATUS_NO);

		if ($boolResult)
		{
			$availableFieldsList = array(
				'QUANTITY',
				'QUANTITY_TRACE',
				'CAN_BUY_ZERO'
			);
			$needCalculateAvailable = false;
			$copyFields = $arFields;
			if (isset($copyFields['QUANTITY_TRACE']) && $copyFields['QUANTITY_TRACE'] == 'D')
				$copyFields['QUANTITY_TRACE'] = Main\Config\Option::get('catalog', 'default_quantity_trace');
			if (isset($copyFields['CAN_BUY_ZERO']) && $copyFields['CAN_BUY_ZERO'] == 'D')
				$copyFields['CAN_BUY_ZERO'] = Main\Config\Option::get('catalog', 'default_can_buy_zero');

			if (!isset($arFields['AVAILABLE']))
			{
				if (
					!isset($arFields['TYPE'])
					|| $arFields['TYPE'] == Catalog\ProductTable::TYPE_PRODUCT
					|| $arFields['TYPE'] == Catalog\ProductTable::TYPE_OFFER
					|| $arFields['TYPE'] == Catalog\ProductTable::TYPE_FREE_OFFER
				)
				{
					if (
						$ACTION == 'ADD'
						&& (
							$arFields['TYPE'] == Catalog\ProductTable::TYPE_PRODUCT
							|| $arFields['TYPE'] == Catalog\ProductTable::TYPE_OFFER
						)
						&& !isset($arFields['AVAILABLE'])
					)
					{
						$needCalculateAvailable = true;
					}
					elseif ($ACTION == 'UPDATE')
					{
						$needFields = array();
						foreach ($availableFieldsList as $availableField)
						{
							if (isset($arFields[$availableField]))
								$needCalculateAvailable = true;
							else
								$needFields[] = $availableField;
						}
						unset($availableField);
						if ($needCalculateAvailable && !empty($needFields))
						{
							$product = $productIterator = Catalog\ProductTable::getList(array(
								'select' => $needFields,
								'filter' => array('=ID' => $ID)
							))->fetch();
							if (!empty($product) && is_array($product))
							{
								foreach ($availableFieldsList as $availableField)
								{
									if (isset($copyFields[$availableField]))
										continue;
									$copyFields[$availableField] = $product[$availableField];
								}
								unset($availableField);
							}
							unset($product);
						}
						unset($needFields);
					}
				}
				elseif (isset($arFields['TYPE']) && $arFields['TYPE'] == CCatalogProduct::TYPE_SKU)
				{
					$offerList = CCatalogSku::getOffersList(array($ID), 0, array('ACTIVE' => 'Y'), array('ID'));
					if (!empty($offerList[$ID]))
					{
						$skuAvailable = false;
						$offerIterator = Catalog\ProductTable::getList(array(
							'select' => array('ID', 'QUANTITY', 'QUANTITY_TRACE', 'CAN_BUY_ZERO'),
							'filter' => array('@ID' => array_keys($offerList[$ID]))
						));
						while ($offer = $offerIterator->fetch())
						{
							if (Catalog\ProductTable::calculateAvailable($offer) == Catalog\ProductTable::STATUS_YES)
								$skuAvailable = true;
						}
						unset($offer, $offerIterator);
						if ($skuAvailable)
						{
							$arFields['AVAILABLE'] = 'Y';
							$arFields['QUANTITY'] = '0';
							$arFields['QUANTITY_TRACE'] = 'N';
							$arFields['CAN_BUY'] = 'Y';
						}
						else
						{
							$arFields['AVAILABLE'] = 'N';
							$arFields['QUANTITY'] = '0';
							$arFields['QUANTITY_TRACE'] = 'Y';
							$arFields['CAN_BUY'] = 'N';
						}
					}
					else
					{
						$arFields['AVAILABLE'] = 'N';
					}
					unset($offerList);
				}
			}
			if ($needCalculateAvailable)
				$arFields['AVAILABLE'] = Catalog\ProductTable::calculateAvailable($copyFields);
			unset($copyFields);
		}

		if (!$boolResult)
		{
			$obError = new CAdminException($arMsg);
			$APPLICATION->ThrowException($obError);
		}
		return $boolResult;
	}

	/**
	 * @deprecated deprecated since catalog 17.6.0
	 * @see \Bitrix\Catalog\Model\Product::add
	 *
	 * @param array $fields
	 * @param bool $checkExist
	 * @return bool
	 */
	public static function Add($fields, $checkExist = true)
	{
		$existProduct = false;
		$checkExist = ($checkExist !== false);

		if (empty($fields['ID']))
			return false;
		$fields['ID'] = (int)$fields['ID'];
		if ($fields['ID'] <= 0)
			return false;

		if ($checkExist)
		{
			$data = Catalog\Model\Product::getCacheItem($fields['ID'], true);
			if (!empty($data))
				$existProduct = !empty($data['ID']);
			unset($data);
		}

		self::normalizeFields($fields);

		if ($existProduct)
			$result = Catalog\Model\Product::update($fields['ID'], $fields);
		else
			$result = Catalog\Model\Product::add($fields);
		$success = $result->isSuccess();
		if (!$success)
			self::convertErrors($result);
		unset($result);

		return $success;
	}

	/**
	 * @deprecated deprecated since catalog 17.6.0
	 * @see \Bitrix\Catalog\Model\Product::update
	 *
	 * @param int $id
	 * @param array $fields
	 * @return bool
	 */
	public static function Update($id, $fields)
	{
		$id = (int)$id;
		if ($id <= 0)
			return false;
		if (!is_array($fields))
			return false;

		self::normalizeFields($fields);

		$result = Catalog\Model\Product::update($id, $fields);
		$success = $result->isSuccess();
		if (!$success)
			self::convertErrors($result);
		unset($result);

		return $success;
	}

	/**
	 * @deprecated deprecated since catalog 17.6.0
	 * @see \Bitrix\Catalog\Model\Product::delete
	 *
	 * @param int $id
	 * @return bool
	 */
	public static function Delete($id)
	{
		$id = (int)$id;
		if ($id <= 0)
			return false;

		$result = Catalog\Model\Product::delete($id);

		return $result->isSuccess();
	}

	public static function ParseQueryBuildField($field)
	{
		$field = (string)$field;
		if ($field == '')
			return false;
		$field = strtoupper($field);
		if (strncmp($field, 'CATALOG_', 8) != 0)
			return false;

		$iNum = 0;
		$field = substr($field, 8);
		$p = strrpos($field, '_');
		if ($p !== false && $p > 0)
		{
			$iNum = (int)substr($field, $p+1);
			if ($iNum > 0)
				$field = substr($field, 0, $p);
		}
		return array(
			'FIELD' => $field,
			'NUM' => $iNum
		);
	}

	/**
	 * @deprecated deprecated since catalog 17.6.2
	 * @see Catalog\Model\Product::getList
	 *
	 * @param int $ID
	 * @return array|false
	 */
	public static function GetByID($ID)
	{
		$ID = (int)$ID;
		if ($ID <= 0)
			return false;

		$iterator = Catalog\Model\Product::getList([
			'select' => [
				'ID', 'QUANTITY', 'QUANTITY_RESERVED', 'QUANTITY_TRACE', 'QUANTITY_TRACE_ORIG', 'WEIGHT', 'WIDTH', 'LENGTH', 'HEIGHT', 'MEASURE',
				'VAT_ID', 'VAT_INCLUDED', 'CAN_BUY_ZERO', 'CAN_BUY_ZERO_ORIG', 'NEGATIVE_AMOUNT_TRACE', 'NEGATIVE_AMOUNT_TRACE_ORIG',
				'PRICE_TYPE', 'RECUR_SCHEME_TYPE', 'RECUR_SCHEME_LENGTH', 'TRIAL_PRICE_ID', 'WITHOUT_ORDER', 'SELECT_BEST_PRICE',
				'TMP_ID', 'PURCHASING_PRICE', 'PURCHASING_CURRENCY', 'BARCODE_MULTI', 'SUBSCRIBE', 'SUBSCRIBE_ORIG',
				'TYPE', 'BUNDLE', 'AVAILABLE', 'TIMESTAMP_X'
			],
			'filter' => ['=ID' => $ID]
		]);
		$result = $iterator->fetch();
		unset($iterator);
		if (empty($result))
			return false;
		if ($result['TIMESTAMP_X'] !== null and $result['TIMESTAMP_X'] instanceof Main\Type\DateTime)
		{
			/** @noinspection PhpUndefinedMethodInspection */
			$result['TIMESTAMP_X'] = $result['TIMESTAMP_X']->toString();
		}
		return $result;
	}

	/**
	 * @deprecated deprecated since catalog 17.6.0
	 *
	 * @param $ID
	 * @param bool $boolAllValues
	 * @return array|bool
	 */
	public static function GetByIDEx($ID, $boolAllValues = false)
	{
		$boolAllValues = ($boolAllValues === true);
		$ID = (int)$ID;
		if ($ID <= 0)
			return false;
		$arFilter = array("ID" => $ID, "ACTIVE" => "Y", "ACTIVE_DATE" => "Y");

		$dbIBlockElement = CIBlockElement::GetList(array(), $arFilter);
		if ($arIBlockElement = $dbIBlockElement->GetNext())
		{
			if ($arIBlock = CIBlock::GetArrayByID($arIBlockElement["IBLOCK_ID"]))
			{
				$arIBlockElement["IBLOCK_ID"] = $arIBlock["ID"];
				$arIBlockElement["IBLOCK_NAME"] = htmlspecialcharsbx($arIBlock["NAME"]);
				$arIBlockElement["~IBLOCK_NAME"] = $arIBlock["NAME"];
				$arIBlockElement["PROPERTIES"] = false;
				$dbProps = CIBlockElement::GetProperty($arIBlock["ID"], $ID, "sort", "asc", array("ACTIVE"=>"Y", "NON_EMPTY"=>"Y"));
				if ($arProp = $dbProps->Fetch())
				{
					$arAllProps = array();
					do
					{
						$strID = (strlen($arProp["CODE"])>0 ? $arProp["CODE"] : $arProp["ID"]);
						if (is_array($arProp["VALUE"]))
						{
							foreach ($arProp["VALUE"] as &$strOneValue)
							{
								$strOneValue = htmlspecialcharsbx($strOneValue);
							}
							if (isset($strOneValue))
								unset($strOneValue);
						}
						else
						{
							$arProp["VALUE"] = htmlspecialcharsbx($arProp["VALUE"]);
						}

						if (is_array($arProp["DEFAULT_VALUE"]))
						{
							foreach ($arProp["DEFAULT_VALUE"] as $index => $value)
							{
								if (is_string($value))
									$arProp["DEFAULT_VALUE"][$index] = htmlspecialcharsbx($value);
							}
						}
						else
						{
							$arProp["DEFAULT_VALUE"] = htmlspecialcharsbx($arProp["DEFAULT_VALUE"]);
						}

						if ($boolAllValues && 'Y' == $arProp['MULTIPLE'])
						{
							if (!isset($arAllProps[$strID]))
							{
								$arAllProps[$strID] = array(
									"NAME" => htmlspecialcharsbx($arProp["NAME"]),
									"VALUE" => array($arProp["VALUE"]),
									"VALUE_ENUM" => array(htmlspecialcharsbx($arProp["VALUE_ENUM"])),
									"VALUE_XML_ID" => array(htmlspecialcharsbx($arProp["VALUE_XML_ID"])),
									"DEFAULT_VALUE" => $arProp["DEFAULT_VALUE"],
									"SORT" => htmlspecialcharsbx($arProp["SORT"]),
									"MULTIPLE" => $arProp['MULTIPLE'],
								);
							}
							else
							{
								$arAllProps[$strID]['VALUE'][] = $arProp["VALUE"];
								$arAllProps[$strID]['VALUE_ENUM'][] = htmlspecialcharsbx($arProp["VALUE_ENUM"]);
								$arAllProps[$strID]['VALUE_XML_ID'][] = htmlspecialcharsbx($arProp["VALUE_XML_ID"]);
							}
						}
						else
						{
							$arAllProps[$strID] = array(
								"NAME" => htmlspecialcharsbx($arProp["NAME"]),
								"VALUE" => $arProp["VALUE"],
								"VALUE_ENUM" => htmlspecialcharsbx($arProp["VALUE_ENUM"]),
								"VALUE_XML_ID" => htmlspecialcharsbx($arProp["VALUE_XML_ID"]),
								"DEFAULT_VALUE" => $arProp["DEFAULT_VALUE"],
								"SORT" => htmlspecialcharsbx($arProp["SORT"]),
								"MULTIPLE" => $arProp['MULTIPLE'],
							);
						}
					}
					while($arProp = $dbProps->Fetch());

					$arIBlockElement["PROPERTIES"] = $arAllProps;
				}

				// bugfix: 2007-07-31 by Sigurd
				$arIBlockElement["PRODUCT"] = CCatalogProduct::GetByID($ID);

				$dbPrices = CPrice::GetList(array("SORT" => "ASC"), array("PRODUCT_ID" => $ID));
				if ($arPrices = $dbPrices->Fetch())
				{
					$arAllPrices = array();
					do
					{
						$arAllPrices[$arPrices["CATALOG_GROUP_ID"]] = array(
							"EXTRA_ID" => intval($arPrices["EXTRA_ID"]),
							"PRICE" => doubleval($arPrices["PRICE"]),
							"CURRENCY" => htmlspecialcharsbx($arPrices["CURRENCY"])
						);
					}
					while($arPrices = $dbPrices->Fetch());

					$arIBlockElement["PRICES"] = $arAllPrices;
				}

				return $arIBlockElement;
			}
		}

		return false;
	}

	/**
	 * @deprecated deprecated since catalog 15.0.0
	 * @see \CCatalogProductProvider
	 *
	 * @param int $ProductID
	 * @param int|float $DeltaQuantity
	 * @return bool
	 */
	public static function QuantityTracer($ProductID, $DeltaQuantity)
	{
		$boolClearCache = false;

		$ProductID = (int)$ProductID;
		if ($ProductID <= 0)
			return false;
		$DeltaQuantity = (float)$DeltaQuantity;
		if ($DeltaQuantity==0)
			return false;

		$rsProducts = CCatalogProduct::GetList(
			array(),
			array('ID' => $ProductID),
			false,
			false,
			array('ID', 'CAN_BUY_ZERO', 'NEGATIVE_AMOUNT_TRACE', 'QUANTITY_TRACE', 'QUANTITY', 'ELEMENT_IBLOCK_ID')
		);
		if (($arProduct = $rsProducts->Fetch())
			&& ($arProduct["QUANTITY_TRACE"]=="Y"))
		{
			$strAllowNegativeAmount = $arProduct["NEGATIVE_AMOUNT_TRACE"];

			$arFields = array();
			$arFields["QUANTITY"] = (float)$arProduct["QUANTITY"] - $DeltaQuantity;

			if ('Y' != $arProduct['CAN_BUY_ZERO'])
			{
				if (defined("BX_COMP_MANAGED_CACHE"))
				{
					$boolClearCache = (0 >= $arFields["QUANTITY"]*$arProduct["QUANTITY"]);
				}
			}

			if ('Y' != $arProduct['CAN_BUY_ZERO'] || 'Y' != $strAllowNegativeAmount)
			{
				if (0 >= $arFields["QUANTITY"])
					$arFields["QUANTITY"] = 0;
			}

			$arFields['OLD_QUANTITY'] = $arProduct["QUANTITY"];
			CCatalogProduct::Update($arProduct["ID"], $arFields);

			if ($boolClearCache)
				CIBlock::clearIblockTagCache($arProduct['ELEMENT_IBLOCK_ID']);

			$arProduct['OLD_QUANTITY'] = $arFields['OLD_QUANTITY'];
			$arProduct['QUANTITY'] = $arFields['QUANTITY'];
			$arProduct['ALLOW_NEGATIVE_AMOUNT'] = $strAllowNegativeAmount;
			$arProduct['DELTA'] = $DeltaQuantity;
			foreach (GetModuleEvents("catalog", "OnProductQuantityTrace", true) as $arEvent)
			{
				ExecuteModuleEventEx($arEvent, array($arProduct["ID"], $arProduct));
			}

			return true;
		}

		return false;
	}

	/**
	 * @param int $productID
	 * @param int|float $quantity
	 * @param array $arUserGroups
	 * @return bool|float|int
	 */
	public static function GetNearestQuantityPrice($productID, $quantity = 1, $arUserGroups = array())
	{
		static $eventOnGetExists = null;
		static $eventOnResultExists = null;

		global $APPLICATION;

		if ($eventOnGetExists === true || $eventOnGetExists === null)
		{
			foreach (GetModuleEvents('catalog', 'OnGetNearestQuantityPrice', true) as $arEvent)
			{
				$eventOnGetExists = true;
				$mxResult = ExecuteModuleEventEx(
					$arEvent,
					array(
						$productID,
						$quantity,
						$arUserGroups
					)
				);
				if ($mxResult !== true)
					return $mxResult;
			}
			if ($eventOnGetExists === null)
				$eventOnGetExists = false;
		}

		// Check input params
		$productID = (int)$productID;
		if ($productID <= 0)
		{
			$APPLICATION->ThrowException(Loc::getMessage("BT_MOD_CATALOG_PROD_ERR_PRODUCT_ID_ABSENT"), "NO_PRODUCT_ID");
			return false;
		}

		$quantity = (float)$quantity;
		if ($quantity <= 0)
		{
			$APPLICATION->ThrowException(Loc::getMessage("BT_MOD_CATALOG_PROD_ERR_QUANTITY_ABSENT"), "NO_QUANTITY");
			return false;
		}

		if (!is_array($arUserGroups) && (int)$arUserGroups.'|' == (string)$arUserGroups.'|')
			$arUserGroups = array((int)$arUserGroups);

		if (!is_array($arUserGroups))
			$arUserGroups = array();

		if (!in_array(2, $arUserGroups))
			$arUserGroups[] = 2;

		$quantityDifference = -1;
		$nearestQuantity = -1;

		// Find nearest quantity
		$priceTypeList = self::getAllowedPriceTypes($arUserGroups);
		if (empty($priceTypeList))
			return false;

		$iterator = Catalog\PriceTable::getList(array(
			'select' => array('ID', 'QUANTITY_FROM', 'QUANTITY_TO'),
			'filter' => array(
				'=PRODUCT_ID' => $productID,
				'@CATALOG_GROUP_ID' => $priceTypeList,
			)
		));
		while ($arPriceList = $iterator->fetch())
		{
			$arPriceList['QUANTITY_FROM'] = (float)$arPriceList['QUANTITY_FROM'];
			$arPriceList['QUANTITY_TO'] = (float)$arPriceList['QUANTITY_TO'];
			if ($quantity >= $arPriceList["QUANTITY_FROM"]
				&& ($quantity <= $arPriceList["QUANTITY_TO"] || $arPriceList["QUANTITY_TO"] == 0))
			{
				$nearestQuantity = $quantity;
				break;
			}

			if ($quantity < $arPriceList["QUANTITY_FROM"])
			{
				$nearestQuantity_tmp = $arPriceList["QUANTITY_FROM"];
				$quantityDifference_tmp = $arPriceList["QUANTITY_FROM"] - $quantity;
			}
			else
			{
				$nearestQuantity_tmp = $arPriceList["QUANTITY_TO"];
				$quantityDifference_tmp = $quantity - $arPriceList["QUANTITY_TO"];
			}

			if ($quantityDifference < 0 || $quantityDifference_tmp < $quantityDifference)
			{
				$quantityDifference = $quantityDifference_tmp;
				$nearestQuantity = $nearestQuantity_tmp;
			}
		}
		unset($arPriceList, $iterator);
		unset($priceTypeList);

		if ($eventOnResultExists === true || $eventOnResultExists === null)
		{
			foreach (GetModuleEvents('catalog', 'OnGetNearestQuantityPriceResult', true) as $arEvent)
			{
				$eventOnResultExists = true;
				if (ExecuteModuleEventEx($arEvent, array(&$nearestQuantity)) === false)
					return false;
			}
			if ($eventOnResultExists === null)
				$eventOnResultExists = false;
		}

		return ($nearestQuantity > 0 ? $nearestQuantity : false);
	}

	/**
	 * @param int $intProductID
	 * @param int|float $quantity
	 * @param array $arUserGroups
	 * @param string $renewal
	 * @param array $priceList
	 * @param bool|string $siteID
	 * @param bool|array $arDiscountCoupons
	 * @return array|bool
	 */
	public static function GetOptimalPrice($intProductID, $quantity = 1, $arUserGroups = array(), $renewal = "N", $priceList = array(), $siteID = false, $arDiscountCoupons = false)
	{
		static $eventOnGetExists = null;
		static $eventOnResultExists = null;

		global $APPLICATION;

		if ($eventOnGetExists === true || $eventOnGetExists === null)
		{
			foreach (GetModuleEvents('catalog', 'OnGetOptimalPrice', true) as $arEvent)
			{
				$eventOnGetExists = true;
				$mxResult = ExecuteModuleEventEx(
					$arEvent,
					array(
						$intProductID,
						$quantity,
						$arUserGroups,
						$renewal,
						$priceList,
						$siteID,
						$arDiscountCoupons
					)
				);
				if ($mxResult !== true)
				{
					self::updateUserHandlerOptimalPrice(
						$mxResult,
						['PRODUCT_ID' => $intProductID]
					);
					return $mxResult;
				}
			}
			if ($eventOnGetExists === null)
				$eventOnGetExists = false;
		}

		$intProductID = (int)$intProductID;
		if ($intProductID <= 0)
		{
			$APPLICATION->ThrowException(Loc::getMessage("BT_MOD_CATALOG_PROD_ERR_PRODUCT_ID_ABSENT"), "NO_PRODUCT_ID");
			return false;
		}

		$quantity = (float)$quantity;
		if ($quantity <= 0)
		{
			$APPLICATION->ThrowException(Loc::getMessage("BT_MOD_CATALOG_PROD_ERR_QUANTITY_ABSENT"), "NO_QUANTITY");
			return false;
		}

		if (!is_array($arUserGroups) && (int)$arUserGroups.'|' == (string)$arUserGroups.'|')
			$arUserGroups = array((int)$arUserGroups);

		if (!is_array($arUserGroups))
			$arUserGroups = array();

		if (!in_array(2, $arUserGroups))
			$arUserGroups[] = 2;
		Main\Type\Collection::normalizeArrayValuesByInt($arUserGroups);

		$renewal = ($renewal == 'Y' ? 'Y' : 'N');

		if ($siteID === false)
			$siteID = SITE_ID;

		$resultCurrency = Catalog\Product\Price\Calculation::getCurrency();
		if (empty($resultCurrency))
		{
			$APPLICATION->ThrowException(Loc::getMessage("BT_MOD_CATALOG_PROD_ERR_NO_RESULT_CURRENCY"));
			return false;
		}

		$intIBlockID = (int)CIBlockElement::GetIBlockByID($intProductID);
		if ($intIBlockID <= 0)
		{
			$APPLICATION->ThrowException(
				Loc::getMessage(
					'BT_MOD_CATALOG_PROD_ERR_ELEMENT_ID_NOT_FOUND',
					array('#ID#' => $intProductID)
				),
				'NO_ELEMENT'
			);
			return false;
		}

		if (!isset($priceList) || !is_array($priceList))
			$priceList = array();

		if (empty($priceList))
		{
			$priceTypeList = self::getAllowedPriceTypes($arUserGroups);
			if (empty($priceTypeList))
				return false;

			$iterator = Catalog\PriceTable::getList(array(
				'select' => array('ID', 'CATALOG_GROUP_ID', 'PRICE', 'CURRENCY'),
				'filter' => array(
					'=PRODUCT_ID' => $intProductID,
					'@CATALOG_GROUP_ID' => $priceTypeList,
					array(
						'LOGIC' => 'OR',
						'<=QUANTITY_FROM' => $quantity,
						'=QUANTITY_FROM' => null
					),
					array(
						'LOGIC' => 'OR',
						'>=QUANTITY_TO' => $quantity,
						'=QUANTITY_TO' => null
					)
				)
			));
			while ($row = $iterator->fetch())
			{
				$row['ELEMENT_IBLOCK_ID'] = $intIBlockID;
				$priceList[] = $row;
			}
			unset($row, $iterator);
			unset($priceTypeList);
		}
		else
		{
			foreach (array_keys($priceList) as $priceIndex)
				$priceList[$priceIndex]['ELEMENT_IBLOCK_ID'] = $intIBlockID;
			unset($priceIndex);
		}

		if (empty($priceList))
			return false;

		$vat = CCatalogProduct::GetVATDataByID($intProductID);
		if (!empty($vat))
		{
			$vat['RATE'] = (float)$vat['RATE'] * 0.01;
		}
		else
		{
			$vat = array('RATE' => 0.0, 'VAT_INCLUDED' => 'N');
		}
		unset($iterator);

		$isNeedDiscounts = Catalog\Product\Price\Calculation::isAllowedUseDiscounts();
		$resultWithVat = Catalog\Product\Price\Calculation::isIncludingVat();
		if ($isNeedDiscounts)
		{
			if ($arDiscountCoupons === false)
				$arDiscountCoupons = CCatalogDiscountCoupon::GetCoupons();
		}

//		$boolDiscountVat = ('N' != COption::GetOptionString('catalog', 'discount_vat', 'Y'));
		$boolDiscountVat = true;

		$minimalPrice = array();

		if (self::$saleIncluded === null)
			self::initSaleSettings();
		$isNeedleToMinimizeCatalogGroup = self::isNeedleToMinimizeCatalogGroup($priceList);

		foreach ($priceList as $priceData)
		{
			$priceData['VAT_RATE'] = $vat['RATE'];
			$priceData['VAT_INCLUDED'] = $vat['VAT_INCLUDED'];

			$currentPrice = (float)$priceData['PRICE'];
			if ($boolDiscountVat)
			{
				if ($priceData['VAT_INCLUDED'] == 'N')
					$currentPrice *= (1 + $priceData['VAT_RATE']);
			}
			else
			{
				if ($priceData['VAT_INCLUDED'] == 'Y')
					$currentPrice /= (1 + $priceData['VAT_RATE']);
			}
			if ($priceData['CURRENCY'] != $resultCurrency)
				$currentPrice = CCurrencyRates::ConvertCurrency($currentPrice, $priceData['CURRENCY'], $resultCurrency);
			$currentPrice = Catalog\Product\Price\Calculation::roundPrecision($currentPrice);

			$result = array(
				'BASE_PRICE' => $currentPrice,
				'COMPARE_PRICE' => $currentPrice,
				'PRICE' => $currentPrice,
				'CURRENCY' => $resultCurrency,
				'DISCOUNT_LIST' => array(),
				'RAW_PRICE' => $priceData
			);
			if ($isNeedDiscounts)
			{
				$arDiscounts = CCatalogDiscount::GetDiscount(
					$intProductID,
					$intIBlockID,
					$priceData['CATALOG_GROUP_ID'],
					$arUserGroups,
					$renewal,
					$siteID,
					$arDiscountCoupons
				);

				$discountResult = CCatalogDiscount::applyDiscountList($currentPrice, $resultCurrency, $arDiscounts);
				unset($arDiscounts);
				if ($discountResult === false)
					return false;
				$result['PRICE'] = $discountResult['PRICE'];
				$result['COMPARE_PRICE'] = $discountResult['PRICE'];
				$result['DISCOUNT_LIST'] = $discountResult['DISCOUNT_LIST'];
				unset($discountResult);
			}
			elseif($isNeedleToMinimizeCatalogGroup)
			{
				$calculateData = $priceData;
				$calculateData['PRICE'] = $currentPrice;
				$calculateData['CURRENCY'] = $resultCurrency;
				$possibleSalePrice = self::getPossibleSalePrice(
					$intProductID,
					$calculateData,
					$quantity,
					$siteID,
					$arUserGroups,
					$arDiscountCoupons
				);
				unset($calculateData);
				if ($possibleSalePrice === null)
					return false;
				$result['COMPARE_PRICE'] = $possibleSalePrice;
				unset($possibleSalePrice);
			}

			if ($boolDiscountVat)
			{
				if (!$resultWithVat)
				{
					$result['PRICE'] /= (1 + $priceData['VAT_RATE']);
					$result['COMPARE_PRICE'] /= (1 + $priceData['VAT_RATE']);
					$result['BASE_PRICE'] /= (1 + $priceData['VAT_RATE']);
				}
			}
			else
			{
				if ($resultWithVat)
				{
					$result['PRICE'] *= (1 + $priceData['VAT_RATE']);
					$result['COMPARE_PRICE'] *= (1 + $priceData['VAT_RATE']);
					$result['BASE_PRICE'] *= (1 + $priceData['VAT_RATE']);
				}
			}

			$result['UNROUND_PRICE'] = $result['PRICE'];
			$result['UNROUND_BASE_PRICE'] = $result['BASE_PRICE'];
			if (Catalog\Product\Price\Calculation::isComponentResultMode())
			{
				$result['BASE_PRICE'] = Catalog\Product\Price::roundPrice(
					$priceData['CATALOG_GROUP_ID'],
					$result['BASE_PRICE'],
					$resultCurrency
				);
				$result['PRICE'] = Catalog\Product\Price::roundPrice(
					$priceData['CATALOG_GROUP_ID'],
					$result['PRICE'],
					$resultCurrency
				);
				if (
					empty($result['DISCOUNT_LIST'])
					|| Catalog\Product\Price\Calculation::compare($result['BASE_PRICE'], $result['PRICE'], '<=')
				)
				{
					$result['BASE_PRICE'] = $result['PRICE'];
				}
				$result['COMPARE_PRICE'] = $result['PRICE'];
			}

			if (empty($minimalPrice) || $minimalPrice['COMPARE_PRICE'] > $result['COMPARE_PRICE'])
			{
				$minimalPrice = $result;
			}

			unset($currentPrice, $result);
		}
		unset($priceData);
		unset($vat);

		$discountValue = ($minimalPrice['BASE_PRICE'] - $minimalPrice['PRICE']);

		$arResult = array(
			'PRICE' => $minimalPrice['RAW_PRICE'],
			'RESULT_PRICE' => array(
				'PRICE_TYPE_ID' => $minimalPrice['RAW_PRICE']['CATALOG_GROUP_ID'],
				'BASE_PRICE' => $minimalPrice['BASE_PRICE'],
				'DISCOUNT_PRICE' => $minimalPrice['PRICE'],
				'CURRENCY' => $resultCurrency,
				'DISCOUNT' => $discountValue,
				'PERCENT' => (
					$minimalPrice['BASE_PRICE'] > 0 && $discountValue > 0
					? round((100*$discountValue)/$minimalPrice['BASE_PRICE'], 0)
					: 0
				),
				'VAT_RATE' => $minimalPrice['RAW_PRICE']['VAT_RATE'],
				'VAT_INCLUDED' => ($resultWithVat ? 'Y' : 'N'),
				'UNROUND_BASE_PRICE' => $minimalPrice['UNROUND_BASE_PRICE'],
				'UNROUND_DISCOUNT_PRICE' => $minimalPrice['UNROUND_PRICE']
			),
			'DISCOUNT_PRICE' => $minimalPrice['PRICE'],
			'DISCOUNT' => array(),
			'DISCOUNT_LIST' => array(),
			'PRODUCT_ID' => $intProductID
		);
		if (!empty($minimalPrice['DISCOUNT_LIST']))
		{
			reset($minimalPrice['DISCOUNT_LIST']);
			$arResult['DISCOUNT'] = current($minimalPrice['DISCOUNT_LIST']);
			$arResult['DISCOUNT_LIST'] = $minimalPrice['DISCOUNT_LIST'];
		}
		unset($minimalPrice);

		if ($eventOnResultExists === true || $eventOnResultExists === null)
		{
			foreach (GetModuleEvents('catalog', 'OnGetOptimalPriceResult', true) as $arEvent)
			{
				$eventOnResultExists = true;
				if (ExecuteModuleEventEx($arEvent, array(&$arResult)) === false)
					return false;
			}
			if ($eventOnResultExists === null)
				$eventOnResultExists = false;
		}

		return $arResult;
	}

	public static function GetOptimalPriceList(array $products, $arUserGroups = array(), $renewal = "N", $priceList = array(), $siteID = false, $needCoupons = true)
	{
		static $eventOnGetExists = null;
		static $eventOnResultExists = null;

		$needCoupons = ($needCoupons === true);

		$resultList = array();

		$iblockListId = array();
		$productIblockGetIdList = array();
		$ignoreList = array();

		$useDiscount = !\CCatalogDiscount::isUsedSaleDiscountOnly();

		foreach ($products as $productId => $productData)
		{
			Catalog\Product\Price\Calculation::setConfig(
				array(
					'USE_DISCOUNTS' => (isset($productData['BUNDLE_CHILD']) && $productData['BUNDLE_CHILD'] === true ? false : $useDiscount),
				)
			);

			foreach (GetModuleEvents('catalog', 'OnGetOptimalPrice', true) as $arEvent)
			{
				if (!empty($productData['QUANTITY_LIST']))
				{
					foreach ($productData['QUANTITY_LIST'] as $basketCode => $quantity)
					{
						//TODO: remove this hack after refactoring new provider
						if ($quantity <= 0)
							continue;
						$mxResult = ExecuteModuleEventEx(
							$arEvent,
							array(
								$productId,
								$quantity,
								$arUserGroups,
								$renewal,
								$priceList,
								$siteID,
								$needCoupons ? false : array()
							)
						);
						if ($mxResult !== true)
						{
							self::updateUserHandlerOptimalPrice(
								$mxResult,
								['PRODUCT_ID' => $productId]
							);
							$resultList[$productId][$productData['BASKET_CODE']] = $mxResult;
							$ignoreList[$productId."|".$quantity] = true;
							continue 3;
						}
					}
				}
			}

			if (!empty($productData['QUANTITY_LIST']))
			{
				foreach ($productData['QUANTITY_LIST'] as $basketCode => $quantity)
				{
					$resultList[$productId][$basketCode] = false;
				}
			}
			else
			{
				$resultList[$productId][$productData['BASKET_CODE']] = false;
			}

			if (!isset($iblockListId[$productId]) && isset($productData['IBLOCK_ID']) && $productData['IBLOCK_ID'] > 0)
			{
				$iblockListId[$productId] = $productData['IBLOCK_ID'];
			}

			if (!isset($iblockListId[$productId]))
			{
				$productIblockGetIdList[] = $productId;
			}
		}

		global $APPLICATION;

		if (!is_array($arUserGroups) && (int)$arUserGroups.'|' == (string)$arUserGroups.'|')
			$arUserGroups = array((int)$arUserGroups);

		if (!is_array($arUserGroups))
			$arUserGroups = array();

		if (!in_array(2, $arUserGroups))
			$arUserGroups[] = 2;
		Main\Type\Collection::normalizeArrayValuesByInt($arUserGroups);

		$renewal = ($renewal == 'Y' ? 'Y' : 'N');

		if ($siteID === false)
			$siteID = SITE_ID;

		$resultCurrency = Catalog\Product\Price\Calculation::getCurrency();
		if (empty($resultCurrency))
		{
			$APPLICATION->ThrowException(Loc::getMessage("BT_MOD_CATALOG_PROD_ERR_NO_RESULT_CURRENCY"));
			return false;
		}

		if (!empty($productIblockGetIdList))
		{
			$iblockIdList = CIBlockElement::GetIBlockByIDList($productIblockGetIdList);
			if (!empty($iblockIdList) && is_array($iblockIdList))
			{
				$iblockListId = $iblockIdList + $iblockListId;
			}
		}

		if (!isset($priceList) || !is_array($priceList))
			$priceList = array();

		if (empty($priceList))
		{
			$priceTypeList = self::getAllowedPriceTypes($arUserGroups);
			if (empty($priceTypeList))
			{
				if (!empty($resultList))
				{
					return $resultList;
				}

				return false;
			}

			$iterator = Catalog\PriceTable::getList(array(
				'select' => array('ID', 'CATALOG_GROUP_ID', 'PRICE', 'CURRENCY', 'QUANTITY_FROM', 'QUANTITY_TO', 'PRODUCT_ID'),
				'filter' => array(
					'=PRODUCT_ID' => array_keys($products),
					'@CATALOG_GROUP_ID' => $priceTypeList
				),
			));
			while ($row = $iterator->fetch())
			{
				$row['ELEMENT_IBLOCK_ID'] = $iblockListId[$row['PRODUCT_ID']];

				if (isset($products[$row['PRODUCT_ID']]))
				{
					$productData = $products[$row['PRODUCT_ID']];
					if (!empty($productData['QUANTITY_LIST']))
					{
						foreach ($productData['QUANTITY_LIST'] as $basketCode => $quantity)
						{
							if(isset($ignoreList[$row['PRODUCT_ID']."|".$quantity]))
							{
								continue 2;
							}
						}
					}

					$quantityList = array();
					if (!isset($productData['QUANTITY']))
					{
						$quantityList = array($productData['QUANTITY']);
					}

					if (!empty($productData['QUANTITY_LIST']))
					{
						$quantityList = $productData['QUANTITY_LIST'];
					}

					foreach ($quantityList as $basketCode => $quantity)
					{
						$checkQuantity = abs(floatval($quantity));
						if (($row['QUANTITY_FROM'] <= $checkQuantity || empty($row['QUANTITY_FROM']))
							&& ($row['QUANTITY_TO'] >= $checkQuantity || empty($row['QUANTITY_TO'])))
						{
							$row['QUANTITY'] = floatval($quantity);
							$row['BASKET_CODE'] = $basketCode;
							$priceList[] = $row;
						}
					}
				}
			}
			unset($row, $iterator);
			unset($cacheKey);
		}
		else
		{
			foreach ($priceList as $priceIndex => $priceData)
			{
				$priceList[$priceIndex]['ELEMENT_IBLOCK_ID'] = $iblockListId[$priceData['PRODUCT_ID']];
			}
			unset($priceIndex);
		}

		if (empty($priceList))
		{
			if (!empty($resultList))
			{
				return $resultList;
			}

			return false;
		}

		\Bitrix\Main\Type\Collection::sortByColumn($priceList, 'BASKET_CODE');

		$vatList = CCatalogProduct::GetVATDataByIDList(array_keys($products));
		if (!empty($vatList))
		{
			foreach ($vatList as $productId => $vatValue)
			{
				if ($vatValue === false)
				{
					$vatList[$productId] = array('RATE' => 0.0, 'VAT_INCLUDED' => 'N');
				}
				else
				{
					$vatList[$productId]['RATE'] = (float)$vatList[$productId]['RATE'] * 0.01;
				}
			}
		}

		$isNeedDiscounts = Catalog\Product\Price\Calculation::isAllowedUseDiscounts();
		$resultWithVat = Catalog\Product\Price\Calculation::isIncludingVat();
		$boolDiscountVat = ('N' != COption::GetOptionString('catalog', 'discount_vat', 'Y'));

		$discountList = array();

		if (self::$saleIncluded === null)
			self::initSaleSettings();
		$isNeedleToMinimizeCatalogGroup = self::isNeedleToMinimizeCatalogGroup($priceList);

		$lastProductId = false;
		$lastBasketCode = false;
		$ignoreProductIdList = array();
		$coupons = array();
		$minimalPrice = array();

		foreach ($priceList as $priceData)
		{
			$productId = $priceData['PRODUCT_ID'];
			$basketCode = $priceData['BASKET_CODE'];

			if (in_array($productId, $ignoreProductIdList))
			{
				continue;
			}

			if ($lastBasketCode != $basketCode)
			{
				if ($lastBasketCode !== false)
				{
					foreach (GetModuleEvents('catalog', 'OnGetOptimalPriceResult', true) as $arEvent)
					{
						if (ExecuteModuleEventEx($arEvent, array(&$resultList[$lastProductId][$lastBasketCode])) === false)
						{
							continue;
						}
					}

					$productHash = array(
						'MODULE' => 'catalog',
						'PRODUCT_ID' => $lastProductId,
						'BASKET_ID' => $lastBasketCode
					);
					if (!empty($resultList[$lastProductId][$lastBasketCode]['DISCOUNT_LIST']))
					{
						$applyCoupons = array();
						foreach ($resultList[$lastProductId][$lastBasketCode]['DISCOUNT_LIST'] as $discount)
						{
							if (!empty($discount['COUPON']))
							{
								$applyCoupons[] = $discount['COUPON'];
							}
						}
						if (!empty($applyCoupons))
						{
							$resultApply = Sale\DiscountCouponsManager::setApplyByProduct($productHash, $applyCoupons);
						}
					}
				}

				if ($isNeedDiscounts && $needCoupons)
				{
					$coupons = static::getCoupons($productId, $basketCode);
				}

				$lastBasketCode = $basketCode;
				$lastProductId = $productId;

				Catalog\Product\Price\Calculation::setConfig(
					array(
						'USE_DISCOUNTS' => (isset($products[$productId]['BUNDLE_CHILD']) && $products[$productId]['BUNDLE_CHILD'] === true ? false : $useDiscount),
					)
				);
				$isNeedDiscounts = Catalog\Product\Price\Calculation::isAllowedUseDiscounts();
			}

			$vat = $vatList[$priceData['PRODUCT_ID']];

			$priceData['VAT_RATE'] = $vat['RATE'];
			$priceData['VAT_INCLUDED'] = $vat['VAT_INCLUDED'];

			$currentPrice = (float)$priceData['PRICE'];
			if ($boolDiscountVat)
			{
				if ($priceData['VAT_INCLUDED'] == 'N')
					$currentPrice *= (1 + $priceData['VAT_RATE']);
			}
			else
			{
				if ($priceData['VAT_INCLUDED'] == 'Y')
					$currentPrice /= (1 + $priceData['VAT_RATE']);
			}

			if ($priceData['CURRENCY'] != $resultCurrency)
				$currentPrice = CCurrencyRates::ConvertCurrency($currentPrice, $priceData['CURRENCY'], $resultCurrency);
			$currentPrice = Catalog\Product\Price\Calculation::roundPrecision($currentPrice);

			$result = array(
				'BASE_PRICE' => $currentPrice,
				'COMPARE_PRICE' => $currentPrice,
				'PRICE' => $currentPrice,
				'CURRENCY' => $resultCurrency,
				'DISCOUNT_LIST' => array(),
				'RAW_PRICE' => $priceData
			);

			if ($isNeedDiscounts)
			{
				$discountList[$priceData['PRODUCT_ID']] = \CCatalogDiscount::GetDiscount(
					$productId,
					$iblockListId[$priceData['PRODUCT_ID']],
					$priceData['CATALOG_GROUP_ID'],
					$arUserGroups,
					$renewal,
					$siteID,
					$coupons
				);

				$discountResult = \CCatalogDiscount::applyDiscountList($currentPrice, $resultCurrency, $discountList[$priceData['PRODUCT_ID']]);
				if ($discountResult === false)
				{
					$ignoreProductIdList[] = $productId;
					$resultList[$productId][$basketCode] = false;
					continue;
				}

				$result['PRICE'] = $discountResult['PRICE'];
				$result['COMPARE_PRICE'] = $discountResult['PRICE'];
				$result['DISCOUNT_LIST'] = $discountResult['DISCOUNT_LIST'];
				unset($discountResult);
			}
			elseif($isNeedleToMinimizeCatalogGroup)
			{
				if (!isset($products[$productId]['QUANTITY_LIST'][$basketCode]))
					continue;

				$calculateData = $priceData;
				$calculateData['PRICE'] = $currentPrice;
				$calculateData['CURRENCY'] = $resultCurrency;
				$possibleSalePrice = self::getPossibleSalePrice(
					$productId,
					$calculateData,
					$products[$productId]['QUANTITY_LIST'][$basketCode],
					$siteID,
					$arUserGroups,
					($needCoupons ? false: [])
				);
				unset($calculateData);
				if ($possibleSalePrice === null)
					continue;
				$result['COMPARE_PRICE'] = $possibleSalePrice;
				unset($possibleSalePrice);
			}

			if ($boolDiscountVat)
			{
				if (!$resultWithVat)
				{
					$result['PRICE'] /= (1 + $priceData['VAT_RATE']);
					$result['COMPARE_PRICE'] /= (1 + $priceData['VAT_RATE']);
					$result['BASE_PRICE'] /= (1 + $priceData['VAT_RATE']);
				}
			}
			else
			{
				if ($resultWithVat)
				{
					$result['PRICE'] *= (1 + $priceData['VAT_RATE']);
					$result['COMPARE_PRICE'] *= (1 + $priceData['VAT_RATE']);
					$result['BASE_PRICE'] *= (1 + $priceData['VAT_RATE']);
				}
			}

			$result['UNROUND_PRICE'] = $result['PRICE'];
			$result['UNROUND_BASE_PRICE'] = $result['BASE_PRICE'];
			if (Catalog\Product\Price\Calculation::isComponentResultMode())
			{
				$result['BASE_PRICE'] = Catalog\Product\Price::roundPrice(
					$priceData['CATALOG_GROUP_ID'],
					$result['BASE_PRICE'],
					$resultCurrency
				);
				$result['PRICE'] = Catalog\Product\Price::roundPrice(
					$priceData['CATALOG_GROUP_ID'],
					$result['PRICE'],
					$resultCurrency
				);
				if (
					empty($result['DISCOUNT_LIST'])
					|| Catalog\Product\Price\Calculation::compare($result['BASE_PRICE'], $result['PRICE'], '<=')
				)
				{
					$result['BASE_PRICE'] = $result['PRICE'];
				}
				$result['COMPARE_PRICE'] = $result['PRICE'];
			}

			if (
				empty($minimalPrice[$basketCode])
				|| $minimalPrice[$basketCode]['COMPARE_PRICE'] > $result['COMPARE_PRICE']
			)
			{
				$minimalPrice[$basketCode] = $result;
			}

			unset($currentPrice, $result);

			$discountValue = ($minimalPrice[$basketCode]['BASE_PRICE'] - $minimalPrice[$basketCode]['PRICE']);

			$productResult = array(
				'PRICE' => $minimalPrice[$basketCode]['RAW_PRICE'],
				'RESULT_PRICE' => array(
					'PRICE_TYPE_ID' => $minimalPrice[$basketCode]['RAW_PRICE']['CATALOG_GROUP_ID'],
					'BASE_PRICE' => $minimalPrice[$basketCode]['BASE_PRICE'],
					'DISCOUNT_PRICE' => $minimalPrice[$basketCode]['PRICE'],
					'CURRENCY' => $resultCurrency,
					'DISCOUNT' => $discountValue,
					'PERCENT' => (
						$minimalPrice[$basketCode]['BASE_PRICE'] > 0 && $discountValue > 0
						? round((100 * $discountValue)/$minimalPrice[$basketCode]['BASE_PRICE'], 0)
						: 0
					),
					'VAT_RATE' => $minimalPrice[$basketCode]['RAW_PRICE']['VAT_RATE'],
					'VAT_INCLUDED' => ($resultWithVat ? 'Y' : 'N'),
					'UNROUND_BASE_PRICE' => $minimalPrice[$basketCode]['UNROUND_BASE_PRICE'],
					'UNROUND_DISCOUNT_PRICE' => $minimalPrice[$basketCode]['UNROUND_PRICE']
				),
				'DISCOUNT_PRICE' => $minimalPrice[$basketCode]['PRICE'],
				'DISCOUNT' => array(),
				'DISCOUNT_LIST' => array(),
				'PRODUCT_ID' => $productId
			);

			if (!empty($minimalPrice[$basketCode]['DISCOUNT_LIST']))
			{
				reset($minimalPrice[$basketCode]['DISCOUNT_LIST']);
				$productResult['DISCOUNT'] = current($minimalPrice[$basketCode]['DISCOUNT_LIST']);
				$productResult['DISCOUNT_LIST'] = $minimalPrice[$basketCode]['DISCOUNT_LIST'];
			}

			$resultList[$productId][$priceData['BASKET_CODE']] = $productResult;

		}
		unset($minimalPrice);
		unset($priceData);
		unset($vat);

		if ($lastBasketCode !== false)
		{
			foreach (GetModuleEvents('catalog', 'OnGetOptimalPriceResult', true) as $arEvent)
			{
				if (ExecuteModuleEventEx($arEvent, array(&$resultList[$lastProductId][$lastBasketCode])) === false)
				{
					break;
				}
			}

			$productHash = array(
				'MODULE' => 'catalog',
				'PRODUCT_ID' => $lastProductId,
				'BASKET_ID' => $lastBasketCode
			);
			if (!empty($resultList[$lastProductId][$lastBasketCode]['DISCOUNT_LIST']))
			{
				$applyCoupons = array();
				foreach ($resultList[$lastProductId][$lastBasketCode]['DISCOUNT_LIST'] as $discount)
				{
					if (!empty($discount['COUPON']))
					{
						$applyCoupons[] = $discount['COUPON'];
					}
				}
				if (!empty($applyCoupons))
				{
					Sale\DiscountCouponsManager::setApplyByProduct($productHash, $applyCoupons);
				}
			}
		}

		return $resultList;
	}

	/**
	 * @param $productId
	 * @param $basketCode
	 *
	 * @return array|bool
	 */
	private static function getCoupons($productId, $basketCode)
	{
		$productHash = array(
			'MODULE' => 'catalog',
			'PRODUCT_ID' => $productId,
			'BASKET_ID' => $basketCode
		);
		$coupons = Sale\DiscountCouponsManager::getForApply(array('MODULE_ID' => 'catalog'), $productHash);
		if (!empty($coupons))
		{
			$coupons = array_keys($coupons);
		}

		return $coupons;
	}

	/**
	 * @param float $price
	 * @param string $currency
	 * @param array $discounts
	 * @return bool|float
	 */
	public static function CountPriceWithDiscount($price, $currency, $discounts)
	{
		static $eventOnGetExists = null;
		static $eventOnResultExists = null;

		if ($eventOnGetExists === true || $eventOnGetExists === null)
		{
			foreach (GetModuleEvents('catalog', 'OnCountPriceWithDiscount', true) as $arEvent)
			{
				$eventOnGetExists = true;
				$mxResult = ExecuteModuleEventEx($arEvent, array($price, $currency, $discounts));
				if ($mxResult !== true)
					return $mxResult;
			}
			if ($eventOnGetExists === null)
				$eventOnGetExists = false;
		}

		$currency = CCurrency::checkCurrencyID($currency);
		if ($currency === false)
			return false;

		$price = (float)$price;
		if ($price <= 0)
			return $price;

		$currentMinPrice = $price;
		if (!empty($discounts) && is_array($discounts))
		{
			$result = CCatalogDiscount::applyDiscountList($price, $currency, $discounts);
			if ($result === false)
				return false;

			$currentMinPrice = $result['PRICE'];
		}

		if ($eventOnResultExists === true || $eventOnResultExists === null)
		{
			foreach (GetModuleEvents('catalog', 'OnCountPriceWithDiscountResult', true) as $arEvent)
			{
				$eventOnResultExists = true;
				if (ExecuteModuleEventEx($arEvent, array(&$currentMinPrice)) === false)
					return false;
			}
			if ($eventOnResultExists === null)
				$eventOnResultExists = false;
		}

		return $currentMinPrice;
	}

	public static function GetProductSections($ID)
	{
		/** @global CStackCacheManager $stackCacheManager */
		global $stackCacheManager;

		$ID = (int)$ID;
		if ($ID <= 0)
			return false;

		$cacheTime = CATALOG_CACHE_DEFAULT_TIME;
		if (defined('CATALOG_CACHE_TIME'))
			$cacheTime = intval(CATALOG_CACHE_TIME);

		$arProductSections = array();

		$dbElementSections = CIBlockElement::GetElementGroups($ID, false, array('ID', 'ADDITIONAL_PROPERTY_ID'));
		while ($arElementSections = $dbElementSections->Fetch())
		{
			if ((int)$arElementSections['ADDITIONAL_PROPERTY_ID'] > 0)
				continue;
			$arSectionsTmp = array();

			$strCacheKey = "p".$arElementSections["ID"];

			$stackCacheManager->SetLength("catalog_group_parents", 50);
			$stackCacheManager->SetTTL("catalog_group_parents", $cacheTime);
			if ($stackCacheManager->Exist("catalog_group_parents", $strCacheKey))
			{
				$arSectionsTmp = $stackCacheManager->Get("catalog_group_parents", $strCacheKey);
			}
			else
			{
				$dbSection = CIBlockSection::GetList(
					array(),
					array('ID' => $arElementSections["ID"]),
					false,
					array(
						'ID',
						'IBLOCK_ID',
						'LEFT_MARGIN',
						'RIGHT_MARGIN',
					)
				);
				if ($arSection = $dbSection->Fetch())
				{
					$dbSectionTree = CIBlockSection::GetList(
						array("LEFT_MARGIN" => "DESC"),
						array(
							"IBLOCK_ID" => $arSection["IBLOCK_ID"],
							"ACTIVE" => "Y",
							"GLOBAL_ACTIVE" => "Y",
							"IBLOCK_ACTIVE" => "Y",
							"<=LEFT_BORDER" => $arSection["LEFT_MARGIN"],
							">=RIGHT_BORDER" => $arSection["RIGHT_MARGIN"]
						),
						false,
						array('ID')
					);
					while ($arSectionTree = $dbSectionTree->Fetch())
					{
						$arSectionTree["ID"] = intval($arSectionTree["ID"]);
						$arSectionsTmp[] = $arSectionTree["ID"];
					}
					unset($arSectionTree, $dbSectionTree);
				}
				unset($arSection, $dbSection);

				$stackCacheManager->Set("catalog_group_parents", $strCacheKey, $arSectionsTmp);
			}

			$arProductSections = array_merge($arProductSections, $arSectionsTmp);
		}
		unset($arElementSections, $dbElementSections);

		$arProductSections = array_unique($arProductSections);

		return $arProductSections;
	}

	public static function OnIBlockElementDelete($ProductID)
	{
		$result = Catalog\Model\Product::delete($ProductID);

		return $result->isSuccess();
	}

	/**
	 * @deprecated deprecated since catalog 17.6.3
	 *
	 * @param array $arFields
	 */
	public static function OnAfterIBlockElementUpdate($arFields) {}

	public static function CheckProducts($arItemIDs)
	{
		if (!is_array($arItemIDs))
			$arItemIDs = array($arItemIDs);
		Main\Type\Collection::normalizeArrayValuesByInt($arItemIDs);
		if (empty($arItemIDs))
			return false;
		$arProductList = array();
		$rsProducts = CCatalogProduct::GetList(
			array(),
			array('@ID' => $arItemIDs),
			false,
			false,
			array('ID')
		);
		while ($arProduct = $rsProducts->Fetch())
		{
			$arProduct['ID'] = (int)$arProduct['ID'];
			$arProductList[$arProduct['ID']] = true;
		}
		if (empty($arProductList))
			return false;
		$boolFlag = true;
		foreach ($arItemIDs as &$intItemID)
		{
			if (!isset($arProductList[$intItemID]))
			{
				$boolFlag = false;
				break;
			}
		}
		unset($intItemID);
		return $boolFlag;
	}

	/**
	 * Return payment period list.
	 *
	 * @deprecated deprected since catalog 17.0.0
	 * @see \Bitrix\Catalog\ProductTable::getPaymentPeriods
	 *
	 * @param bool $boolFull		With description.
	 * @return array
	 */
	public static function GetTimePeriodTypes($boolFull = false)
	{
		return Catalog\ProductTable::getPaymentPeriods($boolFull);
	}

	/**
	 * Update result user handlers for event OnGetOptimalPrice.
	 *
	 * @param array &$userResult		Optimal price array.
	 * @param array $params             GetOptimalPrice parameters.
	 * @return void
	 */
	private static function updateUserHandlerOptimalPrice(&$userResult, array $params)
	{
		global $APPLICATION;

		if (empty($userResult) || !is_array($userResult))
		{
			$userResult = false;
			return;
		}
		if (
			(empty($userResult['PRICE']) || !is_array($userResult['PRICE']))
			&& ((empty($userResult['RESULT_PRICE']) || !is_array($userResult['RESULT_PRICE'])))
		)
		{
			$userResult = false;
			return;
		}

		$resultCurrency = Catalog\Product\Price\Calculation::getCurrency();
		if (empty($resultCurrency))
		{
			$APPLICATION->ThrowException(Loc::getMessage("BT_MOD_CATALOG_PROD_ERR_NO_RESULT_CURRENCY"));
			$userResult = false;
			return;
		}

		if (!isset($userResult['PRODUCT_ID']))
			$userResult['PRODUCT_ID'] = $params['PRODUCT_ID'];

		$oldDiscountExist = !empty($userResult['DISCOUNT']) && is_array($userResult['DISCOUNT']);
		if ($oldDiscountExist)
		{
			if (empty($userResult['DISCOUNT']['MODULE_ID']))
				$userResult['DISCOUNT']['MODULE_ID'] = 'catalog';
			if ($userResult['DISCOUNT']['CURRENCY'] != $resultCurrency)
				Catalog\DiscountTable::convertCurrency($userResult['DISCOUNT'], $resultCurrency);
		}

		if (!isset($userResult['DISCOUNT_LIST']) || !is_array($userResult['DISCOUNT_LIST']))
		{
			$userResult['DISCOUNT_LIST'] = [];
			if ($oldDiscountExist)
				$userResult['DISCOUNT_LIST'][] = $userResult['DISCOUNT'];
		}
		unset($oldDiscountExist);

		foreach ($userResult['DISCOUNT_LIST'] as &$discount)
		{
			if (empty($discount['MODULE_ID']))
				$discount['MODULE_ID'] = 'catalog';
			if ($discount['CURRENCY'] != $resultCurrency)
				Catalog\DiscountTable::convertCurrency($discount, $resultCurrency);
		}
		unset($discount);

		if (isset($userResult['PRICE']) && is_array($userResult['PRICE']))
		{
			if (!isset($userResult['PRICE']['VAT_RATE']))
			{
				$vat = CCatalogProduct::GetVATDataByID($userResult['PRODUCT_ID']);
				if (!empty($vat))
					$vat['RATE'] = (float)$vat['RATE'] * 0.01;
				else
					$vat = ['RATE' => 0.0, 'VAT_INCLUDED' => 'Y'];
				$userResult['PRICE']['VAT_RATE'] = $vat['RATE'];
				$userResult['PRICE']['VAT_INCLUDED'] = $vat['VAT_INCLUDED'];
				unset($vat);
			}
		}
		if (empty($userResult['RESULT_PRICE']) || !is_array($userResult['RESULT_PRICE']))
		{
			$userResult['RESULT_PRICE'] = CCatalogDiscount::calculateDiscountList(
				$userResult['PRICE'],
				$resultCurrency,
				$userResult['DISCOUNT_LIST'],
				Catalog\Product\Price\Calculation::isIncludingVat()
			);
		}

		if (!isset($userResult['RESULT_PRICE']['CURRENCY']))
			$userResult['RESULT_PRICE']['CURRENCY'] = $resultCurrency;

		if (!isset($userResult['RESULT_PRICE']['PRICE_TYPE_ID']))
		{
			if (isset($userResult['PRICE']['CATALOG_GROUP_ID']))
				$userResult['RESULT_PRICE']['PRICE_TYPE_ID'] = $userResult['PRICE']['CATALOG_GROUP_ID'];
		}

		$componentResultMode = Catalog\Product\Price\Calculation::isComponentResultMode();

		if (!isset($userResult['RESULT_PRICE']['UNROUND_DISCOUNT_PRICE']))
		{
			$userResult['RESULT_PRICE']['UNROUND_DISCOUNT_PRICE'] = $userResult['RESULT_PRICE']['DISCOUNT_PRICE'];
			if ($componentResultMode)
			{
				$userResult['RESULT_PRICE']['DISCOUNT_PRICE'] = Catalog\Product\Price::roundPrice(
					$userResult['RESULT_PRICE']['PRICE_TYPE_ID'],
					$userResult['RESULT_PRICE']['DISCOUNT_PRICE'],
					$userResult['RESULT_PRICE']['CURRENCY']
				);
			}
		}

		if (!isset($userResult['RESULT_PRICE']['UNROUND_BASE_PRICE']))
		{
			$userResult['RESULT_PRICE']['UNROUND_BASE_PRICE'] = $userResult['RESULT_PRICE']['BASE_PRICE'];
			if ($componentResultMode)
			{
				$userResult['RESULT_PRICE']['BASE_PRICE'] = Catalog\Product\Price::roundPrice(
					$userResult['RESULT_PRICE']['PRICE_TYPE_ID'],
					$userResult['RESULT_PRICE']['BASE_PRICE'],
					$userResult['RESULT_PRICE']['CURRENCY']
				);
			}
		}

		if ($componentResultMode)
		{
			if (
				empty($userResult['DISCOUNT_LIST'])
				|| Catalog\Product\Price\Calculation::compare(
					$userResult['RESULT_PRICE']['BASE_PRICE'],
					$userResult['RESULT_PRICE']['DISCOUNT_PRICE'],
					'<='
				))
			{
				$userResult['RESULT_PRICE']['BASE_PRICE'] = $userResult['RESULT_PRICE']['DISCOUNT_PRICE'];
			}
		}

		$discountValue = $userResult['RESULT_PRICE']['BASE_PRICE'] - $userResult['RESULT_PRICE']['DISCOUNT_PRICE'];
		$userResult['RESULT_PRICE']['DISCOUNT'] = $discountValue;
		$userResult['RESULT_PRICE']['PERCENT'] = (
		$userResult['RESULT_PRICE']['BASE_PRICE'] > 0 && $discountValue > 0
			? round((100*$discountValue)/$userResult['RESULT_PRICE']['BASE_PRICE'], 0)
			: 0
		);
		unset($discountValue);

		if (!isset($userResult['RESULT_PRICE']['VAT_RATE']))
		{
			if (isset($userResult['PRICE']['VAT_RATE']))
			{
				$userResult['RESULT_PRICE']['VAT_RATE'] = $userResult['PRICE']['VAT_RATE'];
				$userResult['RESULT_PRICE']['VAT_INCLUDED'] = $userResult['PRICE']['VAT_INCLUDED'];
			}
			else
			{
				$vat = CCatalogProduct::GetVATDataByID($userResult['PRODUCT_ID']);
				if (!empty($vat))
					$vat['RATE'] = (float)$vat['RATE'] * 0.01;
				else
					$vat = ['RATE' => 0.0, 'VAT_INCLUDED' => 'Y'];
				$userResult['RESULT_PRICE']['VAT_RATE'] = $vat['RATE'];
				$userResult['RESULT_PRICE']['VAT_INCLUDED'] = $vat['VAT_INCLUDED'];
				unset($vat);
			}
		}

		$userResult['DISCOUNT_PRICE'] = $userResult['RESULT_PRICE']['DISCOUNT_PRICE'];
	}

	/**
	* @deprecated deprecated since catalog 15.0.0
	* @see CCatalogDiscount::applyDiscountList()
	* @see CCatalogDiscount::primaryDiscountFilter()
	 *
	 * @param array &$arDiscount
	 * @param array &$arPriceDiscount
	 * @param array &$arDiscSave
	 * @param array &$arParams
	 *
	 * @return void
	*/
	protected static function __PrimaryDiscountFilter(&$arDiscount, &$arPriceDiscount, &$arDiscSave, &$arParams)
	{
		if (isset($arParams['PRICE']) && isset($arParams['CURRENCY']))
		{
			$arParams['PRICE'] = (float)$arParams['PRICE'];
			$arParams['BASE_PRICE'] = $arParams['PRICE'];
			if ($arParams['PRICE'] > 0)
			{
				$arPriceDiscount = array();
				$arDiscSave = array();

				foreach ($arDiscount as $arOneDiscount)
				{
					$changeData = ($arParams['CURRENCY'] != $arOneDiscount['CURRENCY']);
					/** @noinspection PhpUnusedLocalVariableInspection */
					$dblDiscountValue = 0.0;
					$arOneDiscount['PRIORITY'] = (int)$arOneDiscount['PRIORITY'];
					if (CCatalogDiscount::TYPE_FIX == $arOneDiscount['VALUE_TYPE'])
					{
						$dblDiscountValue = (
							!$changeData
							? $arOneDiscount['VALUE']
							: round(
								CCurrencyRates::ConvertCurrency($arOneDiscount['VALUE'], $arOneDiscount['CURRENCY'], $arParams['CURRENCY']),
								CATALOG_VALUE_PRECISION
							)
						);
						if ($arParams['PRICE'] < $dblDiscountValue)
							continue;
						$arOneDiscount['DISCOUNT_CONVERT'] = $dblDiscountValue;
						if ($changeData)
							$arOneDiscount['VALUE'] = $arOneDiscount['DISCOUNT_CONVERT'];
					}
					elseif (CCatalogDiscount::TYPE_SALE == $arOneDiscount['VALUE_TYPE'])
					{
						$dblDiscountValue = (
							!$changeData
							? $arOneDiscount['VALUE']
							: round(
								CCurrencyRates::ConvertCurrency($arOneDiscount['VALUE'], $arOneDiscount['CURRENCY'], $arParams['CURRENCY']),
								CATALOG_VALUE_PRECISION
							)
						);
						if ($arParams['PRICE'] <= $dblDiscountValue)
							continue;
						$arOneDiscount['DISCOUNT_CONVERT'] = $dblDiscountValue;
						if ($changeData)
							$arOneDiscount['VALUE'] = $arOneDiscount['DISCOUNT_CONVERT'];
					}
					elseif (CCatalogDiscount::TYPE_PERCENT == $arOneDiscount['VALUE_TYPE'])
					{
						if (100 < $arOneDiscount["VALUE"])
							continue;
						if ($arOneDiscount['TYPE'] == CCatalogDiscount::ENTITY_ID && $arOneDiscount["MAX_DISCOUNT"] > 0)
						{
							$dblDiscountValue = (
								!$changeData
								? $arOneDiscount['MAX_DISCOUNT']
								: round(
									CCurrencyRates::ConvertCurrency($arOneDiscount['MAX_DISCOUNT'], $arOneDiscount['CURRENCY'], $arParams['CURRENCY']),
									CATALOG_VALUE_PRECISION
								)
							);
							$arOneDiscount['DISCOUNT_CONVERT'] = $dblDiscountValue;
							if ($changeData)
								$arOneDiscount['MAX_DISCOUNT'] = $arOneDiscount['DISCOUNT_CONVERT'];
						}
					}
					if ($changeData)
						$arOneDiscount['CURRENCY'] = $arParams['CURRENCY'];
					if ($arOneDiscount['TYPE'] == CCatalogDiscountSave::ENTITY_ID)
					{
						$arDiscSave[] = $arOneDiscount;
					}
					else
					{
						$arPriceDiscount[$arOneDiscount['PRIORITY']][] = $arOneDiscount;
					}
				}

				if (!empty($arPriceDiscount))
					krsort($arPriceDiscount);
			}
		}
	}

	/**
	* @deprecated deprecated since catalog 15.0.0
	* @see CCatalogDiscount::applyDiscountList()
	* @see CCatalogDiscount::calculatePriorityLevel()
	 *
	 * @param array &$arDiscounts
	 * @param array &$arResultDiscount
	 * @param array &$arParams
	 *
	 * @return bool
	*/
	protected static function __CalcOnePriority(&$arDiscounts, &$arResultDiscount, &$arParams)
	{
		$boolResult = false;
		if (isset($arParams['PRICE']) && isset($arParams['CURRENCY']))
		{
			$arParams['PRICE'] = (float)$arParams['PRICE'];
			$arParams['BASE_PRICE'] = (float)$arParams['BASE_PRICE'];
			if ($arParams['PRICE'] > 0)
			{
				$dblCurrentPrice = $arParams['PRICE'];
				do
				{
					$dblMinPrice = -1;
					$strMinKey = -1;
					$boolApply = false;
					foreach ($arDiscounts as $strDiscountKey => $arOneDiscount)
					{
						$boolDelete = false;
						$dblPriceTmp = -1;
						switch($arOneDiscount['VALUE_TYPE'])
						{
						case CCatalogDiscount::TYPE_PERCENT:
							$dblTempo = round((
								CCatalogDiscount::getUseBasePrice()
								? $arParams['BASE_PRICE']
								: $dblCurrentPrice
								)*$arOneDiscount['VALUE']/100,
								CATALOG_VALUE_PRECISION
							);
							if (isset($arOneDiscount['DISCOUNT_CONVERT']))
							{
								if ($dblTempo > $arOneDiscount['DISCOUNT_CONVERT'])
									$dblTempo = $arOneDiscount['DISCOUNT_CONVERT'];
							}
							$dblPriceTmp = $dblCurrentPrice - $dblTempo;
							break;
						case CCatalogDiscount::TYPE_FIX:
							if ($arOneDiscount['DISCOUNT_CONVERT'] > $dblCurrentPrice)
							{
								$boolDelete = true;
							}
							else
							{
								$dblPriceTmp = $dblCurrentPrice - $arOneDiscount['DISCOUNT_CONVERT'];
							}
							break;
						case CCatalogDiscount::TYPE_SALE:
							if (!($arOneDiscount['DISCOUNT_CONVERT'] < $dblCurrentPrice))
							{
								$boolDelete = true;
							}
							else
							{
								$dblPriceTmp = $arOneDiscount['DISCOUNT_CONVERT'];
							}
							break;
						}
						if ($boolDelete)
						{
							unset($arDiscounts[$strDiscountKey]);
						}
						else
						{
							if (-1 == $dblMinPrice || $dblMinPrice > $dblPriceTmp)
							{
								$dblMinPrice = $dblPriceTmp;
								$strMinKey = $strDiscountKey;
								$boolApply = true;
							}
						}
					}
					if ($boolApply)
					{
						$dblCurrentPrice = $dblMinPrice;
						$arResultDiscount[] = $arDiscounts[$strMinKey];
						if ('Y' == $arDiscounts[$strMinKey]['LAST_DISCOUNT'])
						{
							$arDiscounts = array();
							$arParams['LAST_DISCOUNT'] = 'Y';
						}
						unset($arDiscounts[$strMinKey]);
					}
				} while (!empty($arDiscounts));
				if ($boolApply)
				{
					$arParams['PRICE'] = $dblCurrentPrice;
				}
				$boolResult = true;
			}
		}
		return $boolResult;
	}

	/**
	* @deprecated deprecated since catalog 15.0.0
	* @see CCatalogDiscount::applyDiscountList()
	* @see CCatalogDiscount::calculateDiscSave()
	 *
	 * @param array &$arDiscSave
	 * @param array &$arResultDiscount
	 * @param array &$arParams
	 *
	 * @return bool
	*/
	protected static function __CalcDiscSave(&$arDiscSave, &$arResultDiscount, &$arParams)
	{
		$boolResult = false;
		if (isset($arParams['PRICE']) && isset($arParams['CURRENCY']))
		{
			$arParams['PRICE'] = (float)$arParams['PRICE'];
			if (0 < $arParams['PRICE'])
			{
				$dblCurrentPrice = $arParams['PRICE'];
				$dblMinPrice = -1;
				$strMinKey = -1;
				$boolApply = false;
				foreach ($arDiscSave as $strDiscountKey => $arOneDiscount)
				{
					$dblPriceTmp = -1;
					$boolDelete = false;
					switch($arOneDiscount['VALUE_TYPE'])
					{
					case CCatalogDiscountSave::TYPE_PERCENT:
						$dblPriceTmp = round($dblCurrentPrice*(1 - $arOneDiscount['VALUE']/100.0), CATALOG_VALUE_PRECISION);
						break;
					case CCatalogDiscountSave::TYPE_FIX:
						if ($arOneDiscount['DISCOUNT_CONVERT'] > $dblCurrentPrice)
						{
							$boolDelete = true;
						}
						else
						{
							$dblPriceTmp = $dblCurrentPrice - $arOneDiscount['DISCOUNT_CONVERT'];
						}
						break;
					}
					if (!$boolDelete)
					{
						if (-1 == $dblMinPrice || $dblMinPrice > $dblPriceTmp)
						{
							$dblMinPrice = $dblPriceTmp;
							$strMinKey = $strDiscountKey;
							$boolApply = true;
						}
					}
				}
				if ($boolApply)
				{
					$arParams['PRICE'] = $dblMinPrice;
					$arResultDiscount[] = $arDiscSave[$strMinKey];
				}
				$boolResult = true;
			}
		}
		return $boolResult;
	}

	protected static function getQueryBuildCurrencyScale($filter, $priceTypeId)
	{
		$result = array();
		if (!isset($filter['CATALOG_CURRENCY_SCALE_'.$priceTypeId]))
			return $result;
		$currencyId = Currency\CurrencyManager::checkCurrencyID($filter['CATALOG_CURRENCY_SCALE_'.$priceTypeId]);
		if ($currencyId === false)
			return $result;

		$currency = CCurrency::GetByID($currencyId);
		if (empty($currency))
			return $result;

		$result['CURRENCY'] = $currency['CURRENCY'];
		$result['BASE_RATE'] = $currency['CURRENT_BASE_RATE'];

		return $result;
	}

	protected static function getQueryBuildPriceScaled($prices, $scale)
	{
		$result = array();
		$scale = (float)$scale;
		if (!is_array($prices))
			$prices = array($prices);
		if (empty($prices) || $scale <= 0)
			return $result;
		foreach ($prices as &$value)
			$result[] = (float)$value*$scale;
		unset($value);
		return $result;
	}

	protected static function initSaleSettings()
	{
		if (self::$saleIncluded === null)
			self::$saleIncluded = Main\Loader::includeModule('sale');
		if (self::$saleIncluded)
		{
			self::$useSaleDiscount = (string)Main\Config\Option::get('sale', 'use_sale_discount_only') == 'Y';
			if (self::$useSaleDiscount)
			{
				//TODO: replace runtime to reference after sale 17.5.2 will be stable
				$row = Sale\Internals\DiscountEntitiesTable::getList(array(
					'select' => array('ID'),
					'filter' => array(
						'=MODULE_ID' => 'catalog',
						'=ENTITY' => 'PRICE',
						'=FIELD_ENTITY' => 'CATALOG_GROUP_ID',
						'=FIELD_TABLE' => 'CATALOG_GROUP_ID',
						'=ACTIVE_DISCOUNT.ACTIVE' => 'Y'
					),
					'runtime' => array(
						new Main\Entity\ReferenceField(
							'ACTIVE_DISCOUNT',
							'Bitrix\Sale\Internals\Discount',
							array('=this.DISCOUNT_ID' => 'ref.ID'),
							array('join_type' => 'LEFT')
						)
					),
					'limit' => 1,
				))->fetch();
				self::$existPriceTypeDiscounts = !empty($row);
				unset($row);
			}
		}
	}

	private static function isNeedleToMinimizeCatalogGroup(array $priceList)
	{
		if (self::$saleIncluded === null)
			self::initSaleSettings();

		if (
			!self::$saleIncluded ||
			!self::$useSaleDiscount ||
			count($priceList) < 2
		)
			return false;

		return self::$existPriceTypeDiscounts;
	}

	private static function getPossibleSalePrice($intProductID, array $priceData, $quantity, $siteID, array $userGroups, $coupons)
	{
		$possibleSalePrice = null;

		if (empty($priceData))
			return $possibleSalePrice;

		$isCompatibilityUsed = Sale\Compatible\DiscountCompatibility::isUsed();
		Sale\Compatible\DiscountCompatibility::stopUsageCompatible();

		$freezeCoupons = (empty($coupons) && is_array($coupons));

		if ($freezeCoupons)
			Sale\DiscountCouponsManager::freezeCouponStorage();

		/** @var \Bitrix\Sale\Basket $basket */
		static $basket = null,
			/** @var \Bitrix\Sale\BasketItem $basketItem */
			$basketItem = null;

		if ($basket !== null)
		{
			if ($basket->getSiteId() != $siteID)
			{
				$basket = null;
				$basketItem = null;
			}
		}
		if ($basket === null)
		{
			$basket = Sale\Basket::create($siteID);
			$basketItem = $basket->createItem('catalog', $intProductID);
		}

		$fields = array(
			'PRODUCT_ID' => $intProductID,
			'QUANTITY' => $quantity,
			'LID' => $siteID,
			'PRODUCT_PRICE_ID' => $priceData['ID'],
			'PRICE' => $priceData['PRICE'],
			'BASE_PRICE' => $priceData['PRICE'],
			'DISCOUNT_PRICE' => 0,
			'CURRENCY' => $priceData['PRICE'],
			'CAN_BUY' => 'Y',
			'DELAY' => 'N',
			'PRICE_TYPE_ID' => (int)$priceData['CATALOG_GROUP_ID']
		);

		/** @noinspection PhpInternalEntityUsedInspection */
		$basketItem->setFieldsNoDemand($fields);

		$discount = Sale\Discount::buildFromBasket($basket, new Sale\Discount\Context\UserGroup($userGroups));

		$discount->setExecuteModuleFilter(array('all', 'catalog'));
		$discount->calculate();

		$calcResults = $discount->getApplyResult(true);
		if ($calcResults && !empty($calcResults['PRICES']['BASKET']))
		{
			$possibleSalePrice = reset($calcResults['PRICES']['BASKET']);
			$possibleSalePrice = $possibleSalePrice['PRICE'];
		}

		if ($freezeCoupons)
			Sale\DiscountCouponsManager::unFreezeCouponStorage();

		if ($isCompatibilityUsed === true)
		{
			Sale\Compatible\DiscountCompatibility::revertUsageCompatible();
		}

		return $possibleSalePrice;
	}

	private static function checkPriceValue($price)
	{
		$result = false;

		if ($price !== null && $price !== false)
		{
			if (is_string($price))
			{
				$price = str_replace(',', '.', $price);
				if ($price !== '' && is_numeric($price))
				{
					$price = (float)$price;
					if (is_finite($price))
						$result = $price;
				}
			}
			elseif (
				is_int($price)
				|| (is_float($price) && is_finite($price))
			)
			{
				$result = $price;
			}
		}

		return $result;
	}

	private static function checkPriceCurrency($currency)
	{
		$result = false;
		if ($currency !== null && $currency !== false && $currency !== '')
			$result = $currency;
		return $result;
	}

	/**
	 * @param array $userGroups
	 * @return array
	 */
	private static function getAllowedPriceTypes(array $userGroups)
	{
		static $priceTypeCache = array();

		Main\Type\Collection::normalizeArrayValuesByInt($userGroups, true);
		if (empty($userGroups))
			return array();

		$cacheKey = 'U'.implode('_', $userGroups);
		if (!isset($priceTypeCache[$cacheKey]))
		{
			$priceTypeCache[$cacheKey] = array();
			$priceIterator = Catalog\GroupAccessTable::getList(array(
				'select' => array('CATALOG_GROUP_ID'),
				'filter' => array('@GROUP_ID' => $userGroups, '=ACCESS' => Catalog\GroupAccessTable::ACCESS_BUY),
				'order' => array('CATALOG_GROUP_ID' => 'ASC')
			));
			while ($priceType = $priceIterator->fetch())
			{
				$priceTypeId = (int)$priceType['CATALOG_GROUP_ID'];
				$priceTypeCache[$cacheKey][$priceTypeId] = $priceTypeId;
				unset($priceTypeId);
			}
			unset($priceType, $priceIterator);
		}

		return $priceTypeCache[$cacheKey];
	}

	private static function convertErrors(Main\Entity\Result $result)
	{
		global $APPLICATION;

		$oldMessages = array();
		foreach ($result->getErrorMessages() as $errorText)
			$oldMessages[] = array('text' => $errorText);
		unset($errorText);

		if (!empty($oldMessages))
		{
			$error = new CAdminException($oldMessages);
			$APPLICATION->ThrowException($error);
			unset($error);
		}
		unset($oldMessages);
	}

	private static function normalizeFields(array &$fields)
	{
		if (isset($fields['QUANTITY']) && is_string($fields['QUANTITY']) && $fields['QUANTITY'] === '')
			$fields['QUANTITY'] = 0;
		if (isset($fields['QUANTITY_RESERVED']) && is_string($fields['QUANTITY_RESERVED']) && $fields['QUANTITY_RESERVED'] === '')
			$fields['QUANTITY_RESERVED'] = 0;
		if (isset($fields['WEIGHT']) && is_string($fields['WEIGHT']) && $fields['WEIGHT'] === '')
			$fields['WEIGHT'] = 0;
	}
}