Your IP : 3.142.94.139
<?
namespace Bitrix\Iblock\Component;
use \Bitrix\Main;
use \Bitrix\Main\Loader;
use \Bitrix\Main\Error;
use \Bitrix\Main\ErrorCollection;
use \Bitrix\Main\Localization\Loc;
use \Bitrix\Currency;
use \Bitrix\Iblock;
use \Bitrix\Catalog;
/**
* @global \CUser $USER
* @global \CMain $APPLICATION
*/
Loc::loadMessages(__FILE__);
abstract class Base extends \CBitrixComponent
{
const ACTION_BUY = 'BUY';
const ACTION_ADD_TO_BASKET = 'ADD2BASKET';
const ACTION_SUBSCRIBE = 'SUBSCRIBE_PRODUCT';
const ACTION_ADD_TO_COMPARE = 'ADD_TO_COMPARE_LIST';
const ACTION_DELETE_FROM_COMPARE = 'DELETE_FROM_COMPARE_LIST';
const ERROR_TEXT = 1;
const ERROR_404 = 2;
const PARAM_TITLE_MASK = '/^[A-Za-z_][A-Za-z01-9_]*$/';
const SORT_ORDER_MASK = '/^(asc|desc|nulls)(,asc|,desc|,nulls){0,1}$/i';
private $action = '';
private $cacheUsage = true;
private $extendedMode = true;
/** @var ErrorCollection */
protected $errorCollection;
protected $separateLoading = false;
protected $selectFields = array();
protected $filterFields = array();
protected $sortFields = array();
/** @var array Array of ids to show directly */
protected $productIds = array();
protected $productIdMap = array();
protected $iblockProducts = array();
protected $elements = array();
protected $elementLinks = array();
protected $productWithOffers = array();
protected $productWithPrices = array();
protected $globalFilter = array();
protected $navParams = false;
protected $useCatalog = false;
protected $isIblockCatalog = false;
protected $useDiscountCache = false;
/** @var bool Fill old format $arResult and enable deprecated functionality for existing components (catalog.section, catalog.element, etc) */
protected $compatibleMode = false;
protected $oldData = array();
/** @var array Item prices (new format) */
protected $prices = array();
protected $calculatePrices = array();
protected $measures = array();
protected $ratios = array();
protected $quantityRanges = array();
protected $storage = array();
protected $recommendationIdToProduct = array();
/**
* Base constructor.
* @param \CBitrixComponent|null $component Component object if exists.
*/
public function __construct($component = null)
{
parent::__construct($component);
$this->errorCollection = new ErrorCollection();
}
/**
* Return current action.
*
* @return string
*/
public function getAction()
{
return $this->action;
}
/**
* Action setter.
*
* @param string $action Action code.
* @return void
*/
protected function setAction($action)
{
$this->action = $action;
}
/**
* Return true if errors exist.
*
* @return bool
*/
protected function hasErrors()
{
return (bool)count($this->errorCollection);
}
/**
* Errors processing depending on error codes.
*
* @return bool
*/
protected function processErrors()
{
if (!empty($this->errorCollection))
{
/** @var Error $error */
foreach ($this->errorCollection as $error)
{
$code = $error->getCode();
if ($code == self::ERROR_404)
{
Tools::process404(
trim($this->arParams['MESSAGE_404']) ?: $error->getMessage(),
true,
$this->arParams['SET_STATUS_404'] === 'Y',
$this->arParams['SHOW_404'] === 'Y',
$this->arParams['FILE_404']
);
}
elseif ($code == self::ERROR_TEXT)
{
ShowError($error->getMessage());
}
}
}
return false;
}
/**
* Cache usage setter. Enable it to ignore cache.
*
* @param bool $state Cache usage mode.
* @return $this
*/
protected function setCacheUsage($state)
{
$this->cacheUsage = (bool)$state;
return $this;
}
/**
* Check if cache disabled.
*
* @return bool
*/
public function isCacheDisabled()
{
return (bool)$this->cacheUsage === false;
}
/**
* Extended mode setter.
* Enabled - adds result_modifier.php template logic in component class.
* In both cases(true or false) result_modifier.php will be included.
*
* @param bool $state New extended mode.
* @return $this
*/
protected function setExtendedMode($state)
{
$this->extendedMode = (bool)$state;
return $this;
}
/**
* Check if extended mode is enabled.
*
* @return bool
*/
public function isExtendedMode()
{
return (bool)$this->extendedMode;
}
/**
* Enable/disable fill old keys in result data and use of outdated settings. Strict use only for catalog.element, .section, .top, etc.
*
* @param bool $state Enable/disable state.
* @return void
*/
protected function setCompatibleMode($state)
{
$this->compatibleMode = (bool)$state;
}
/**
* Return state filling old keys in result data. This method makes no sense for the new components.
*
* @return bool
*/
public function isEnableCompatible()
{
return $this->compatibleMode;
}
/**
* @param $state
* @return void
*/
protected function setSeparateLoading($state)
{
$this->separateLoading = (bool)$state;
}
/**
* @return bool
*/
protected function isSeparateLoading()
{
return $this->separateLoading;
}
/**
* Return settings script path with modified time postfix.
*
* @param string $componentPath Path to component.
* @param string $settingsName Settings name.
* @return string
*/
public static function getSettingsScript($componentPath, $settingsName)
{
if ($settingsName === 'filter_conditions')
{
if (Loader::includeModule('catalog'))
{
\CJSCore::Init(['core_condtree']);
}
}
$path = $componentPath.'/settings/'.$settingsName.'/script.js';
$file = new Main\IO\File(Main\Application::getDocumentRoot().$path);
return $path.'?'.$file->getModificationTime();
}
/**
* Processing of component parameters.
*
* @param array $params Raw component parameters values.
* @return mixed
*/
public function onPrepareComponentParams($params)
{
if (!isset($params['CURRENT_BASE_PAGE']))
{
$uri = new Main\Web\Uri($this->request->getRequestUri());
$uri->deleteParams(Main\HttpRequest::getSystemParameters());
$params['CURRENT_BASE_PAGE'] = $uri->getUri();
}
// parent component params for correct template load through ajax
if (!isset($params['PARENT_NAME']) && $parent = $this->getParent())
{
$params['PARENT_NAME'] = $parent->getName();
$params['PARENT_TEMPLATE_NAME'] = $parent->getTemplateName();
$params['PARENT_TEMPLATE_PAGE'] = $parent->getTemplatePage();
}
// save original parameters for further ajax requests
$this->arResult['ORIGINAL_PARAMETERS'] = $params;
if (isset($params['CUSTOM_SITE_ID']))
{
$this->setSiteId($params['CUSTOM_SITE_ID']);
}
// for AJAX_MODE set original ajax_id from initial load
if (isset($params['AJAX_MODE']) && $params['AJAX_MODE'] === 'Y')
{
$ajaxId = $this->request->get('AJAX_ID');
if (!empty($ajaxId))
{
$params['AJAX_ID'] = $ajaxId;
}
unset($ajaxId);
}
if (!isset($params['CACHE_TIME']))
{
$params['CACHE_TIME'] = 36000000;
}
$params['IBLOCK_ID'] = isset($params['IBLOCK_ID']) ? (int)$params['IBLOCK_ID'] : 0;
$params['SECTION_ID'] = isset($params['SECTION_ID']) ? (int)$params['SECTION_ID'] : 0;
$params['SECTION_CODE'] = isset($params['SECTION_CODE']) ? trim($params['SECTION_CODE']) : '';
$params['SECTION_URL'] = isset($params['SECTION_URL']) ? trim($params['SECTION_URL']) : '';
$params['STRICT_SECTION_CHECK'] = isset($params['STRICT_SECTION_CHECK']) && $params['STRICT_SECTION_CHECK'] === 'Y';
$params['CHECK_LANDING_PRODUCT_SECTION'] = (isset($params['CHECK_LANDING_PRODUCT_SECTION']) && $params['CHECK_LANDING_PRODUCT_SECTION'] === 'Y');
$params['DETAIL_URL'] = isset($params['DETAIL_URL']) ? trim($params['DETAIL_URL']) : '';
$params['BASKET_URL'] = isset($params['BASKET_URL']) ? trim($params['BASKET_URL']) : '/personal/basket.php';
$params['HIDE_DETAIL_URL'] = isset($params['HIDE_DETAIL_URL']) && $params['HIDE_DETAIL_URL'] === 'Y';
$params['ACTION_VARIABLE'] = isset($params['ACTION_VARIABLE']) ? trim($params['ACTION_VARIABLE']) : '';
if ($params['ACTION_VARIABLE'] == '' || !preg_match(self::PARAM_TITLE_MASK, $params['ACTION_VARIABLE']))
{
$params['ACTION_VARIABLE'] = 'action';
}
$params['PRODUCT_ID_VARIABLE'] = isset($params['PRODUCT_ID_VARIABLE']) ? trim($params['PRODUCT_ID_VARIABLE']) : '';
if ($params['PRODUCT_ID_VARIABLE'] == '' || !preg_match(self::PARAM_TITLE_MASK, $params['PRODUCT_ID_VARIABLE']))
{
$params['PRODUCT_ID_VARIABLE'] = 'id';
}
$params['ACTION_COMPARE_VARIABLE'] = isset($params['ACTION_COMPARE_VARIABLE']) ? trim($params['ACTION_COMPARE_VARIABLE']) : '';
if ($params['ACTION_COMPARE_VARIABLE'] == '' || !preg_match(self::PARAM_TITLE_MASK, $params['ACTION_COMPARE_VARIABLE']))
{
$params['ACTION_COMPARE_VARIABLE'] = $params['ACTION_VARIABLE'];
}
$params['PRODUCT_QUANTITY_VARIABLE'] = isset($params['PRODUCT_QUANTITY_VARIABLE']) ? trim($params['PRODUCT_QUANTITY_VARIABLE']) : '';
if ($params['PRODUCT_QUANTITY_VARIABLE'] == '' || !preg_match(self::PARAM_TITLE_MASK, $params['PRODUCT_QUANTITY_VARIABLE']))
{
$params['PRODUCT_QUANTITY_VARIABLE'] = 'quantity';
}
$params['PRODUCT_PROPS_VARIABLE'] = isset($params['PRODUCT_PROPS_VARIABLE']) ? trim($params['PRODUCT_PROPS_VARIABLE']) : '';
if ($params['PRODUCT_PROPS_VARIABLE'] == '' || !preg_match(self::PARAM_TITLE_MASK, $params['PRODUCT_PROPS_VARIABLE']))
{
$params['PRODUCT_PROPS_VARIABLE'] = 'prop';
}
// landing mode
if (
isset($params['ALLOW_SEO_DATA'])
&& ($params['ALLOW_SEO_DATA'] === 'Y' || $params['ALLOW_SEO_DATA'] === 'N')
)
{
$params['SET_TITLE'] = $params['ALLOW_SEO_DATA'] === 'Y';
$params['SET_BROWSER_TITLE'] = $params['ALLOW_SEO_DATA'];
$params['SET_META_KEYWORDS'] = $params['ALLOW_SEO_DATA'];
$params['SET_META_DESCRIPTION'] = $params['ALLOW_SEO_DATA'];
}
else
{
$params['SET_TITLE'] = $params['SET_TITLE'] !== 'N';
$params['SET_BROWSER_TITLE'] = isset($params['SET_BROWSER_TITLE']) && $params['SET_BROWSER_TITLE'] === 'N' ? 'N' : 'Y';
$params['SET_META_KEYWORDS'] = isset($params['SET_META_KEYWORDS']) && $params['SET_META_KEYWORDS'] === 'N' ? 'N' : 'Y';
$params['SET_META_DESCRIPTION'] = isset($params['SET_META_DESCRIPTION']) && $params['SET_META_DESCRIPTION'] === 'N' ? 'N' : 'Y';
}
$params['SET_LAST_MODIFIED'] = isset($params['SET_LAST_MODIFIED']) && $params['SET_LAST_MODIFIED'] === 'Y';
$params['ADD_SECTIONS_CHAIN'] = isset($params['ADD_SECTIONS_CHAIN']) && $params['ADD_SECTIONS_CHAIN'] === 'Y';
$params['DISPLAY_COMPARE'] = isset($params['DISPLAY_COMPARE']) && $params['DISPLAY_COMPARE'] === 'Y';
$params['COMPARE_PATH'] = isset($params['COMPARE_PATH']) ? trim($params['COMPARE_PATH']) : '';
$params['COMPARE_NAME'] = isset($params['COMPARE_NAME']) ? trim($params['COMPARE_NAME']) : 'CATALOG_COMPARE_LIST';
$params['USE_COMPARE_LIST'] = (isset($params['USE_COMPARE_LIST']) && $params['USE_COMPARE_LIST'] === 'Y' ? 'Y' : 'N');
$params['USE_PRICE_COUNT'] = isset($params['USE_PRICE_COUNT']) && $params['USE_PRICE_COUNT'] === 'Y';
$params['SHOW_PRICE_COUNT'] = isset($params['SHOW_PRICE_COUNT']) ? (int)$params['SHOW_PRICE_COUNT'] : 1;
if ($params['SHOW_PRICE_COUNT'] <= 0)
{
$params['SHOW_PRICE_COUNT'] = 1;
}
$params['FILL_ITEM_ALL_PRICES'] = isset($params['FILL_ITEM_ALL_PRICES']) && $params['FILL_ITEM_ALL_PRICES'] === 'Y';
$params['USE_PRODUCT_QUANTITY'] = isset($params['USE_PRODUCT_QUANTITY']) && $params['USE_PRODUCT_QUANTITY'] === 'Y';
$params['ADD_PROPERTIES_TO_BASKET'] = isset($params['ADD_PROPERTIES_TO_BASKET']) && $params['ADD_PROPERTIES_TO_BASKET'] === 'N' ? 'N' : 'Y';
if (Iblock\Model\PropertyFeature::isEnabledFeatures())
$params['ADD_PROPERTIES_TO_BASKET'] = 'Y';
if ($params['ADD_PROPERTIES_TO_BASKET'] === 'N')
{
$params['PRODUCT_PROPERTIES'] = array();
$params['OFFERS_CART_PROPERTIES'] = array();
}
$params['PARTIAL_PRODUCT_PROPERTIES'] = isset($params['PARTIAL_PRODUCT_PROPERTIES']) && $params['PARTIAL_PRODUCT_PROPERTIES'] === 'Y' ? 'Y' : 'N';
if (empty($params['OFFERS_SORT_FIELD']))
{
$params['OFFERS_SORT_FIELD'] = 'sort';
}
if (!preg_match(self::SORT_ORDER_MASK, $params['OFFERS_SORT_ORDER']))
{
$params['OFFERS_SORT_ORDER'] = 'asc';
}
if (empty($params['OFFERS_SORT_FIELD2']))
{
$params['OFFERS_SORT_FIELD2'] = 'id';
}
if (!preg_match(self::SORT_ORDER_MASK, $params['OFFERS_SORT_ORDER2']))
{
$params['OFFERS_SORT_ORDER2'] = 'desc';
}
$params['PRICE_VAT_INCLUDE'] = !(isset($params['PRICE_VAT_INCLUDE']) && $params['PRICE_VAT_INCLUDE'] === 'N');
$params['CONVERT_CURRENCY'] = isset($params['CONVERT_CURRENCY']) && $params['CONVERT_CURRENCY'] === 'Y' ? 'Y' : 'N';
$params['CURRENCY_ID'] = isset($params['CURRENCY_ID']) ? trim($params['CURRENCY_ID']) : '';
if ($params['CURRENCY_ID'] === '' || $params['CONVERT_CURRENCY'] === 'N')
{
$params['CONVERT_CURRENCY'] = 'N';
$params['CURRENCY_ID'] = '';
}
$params['OFFERS_LIMIT'] = isset($params['OFFERS_LIMIT']) ? (int)$params['OFFERS_LIMIT'] : 0;
if ($params['OFFERS_LIMIT'] < 0)
{
$params['OFFERS_LIMIT'] = 0;
}
$params['CACHE_GROUPS'] = trim($params['CACHE_GROUPS']);
if ($params['CACHE_GROUPS'] !== 'N')
{
$params['CACHE_GROUPS'] = 'Y';
}
if (isset($params['~PRICE_CODE']))
{
$params['PRICE_CODE'] = $params['~PRICE_CODE'];
}
if (!isset($params['PRICE_CODE']) || !is_array($params['PRICE_CODE']))
{
$params['PRICE_CODE'] = array();
}
$params['SHOW_FROM_SECTION'] = isset($params['SHOW_FROM_SECTION']) && $params['SHOW_FROM_SECTION'] === 'Y' ? 'Y' : 'N';
if ($params['SHOW_FROM_SECTION'] === 'Y')
{
$params['SECTION_ELEMENT_ID'] = isset($params['SECTION_ELEMENT_ID']) ? (int)$params['SECTION_ELEMENT_ID'] : 0;
$params['SECTION_ELEMENT_CODE'] = isset($params['SECTION_ELEMENT_CODE']) ? trim($params['SECTION_ELEMENT_CODE']) : '';
$params['DEPTH'] = isset($params['DEPTH']) ? (int)$params['DEPTH'] : 0;
if (empty($params['SECTION_ID']))
{
if ($params['SECTION_CODE'] !== '')
{
$sectionId = $this->getSectionIdByCode($params['SECTION_CODE']);
}
else
{
$sectionId = $this->getSectionIdByElement($params['SECTION_ELEMENT_ID'], $params['SECTION_ELEMENT_CODE']);
}
$params['SECTION_ID'] = $sectionId;
}
}
if (!isset($params['FILTER_IDS']))
{
$params['FILTER_IDS'] = array();
}
elseif (!is_array($params['FILTER_IDS']))
{
$params['FILTER_IDS'] = array($params['FILTER_IDS']);
}
return $params;
}
/**
* Check necessary modules for component.
*
* @return bool
*/
protected function checkModules()
{
$this->useCatalog = Loader::includeModule('catalog');
$this->storage['MODULES'] = array(
'iblock' => true,
'catalog' => $this->useCatalog,
'currency' => $this->useCatalog
);
return true;
}
/**
* Fill discount cache before price calculation.
*
* @return void
*/
protected function initCatalogDiscountCache()
{
if ($this->useCatalog && $this->useDiscountCache && !empty($this->elementLinks))
{
foreach ($this->iblockProducts as $iblock => $products)
{
if ($this->storage['USE_SALE_DISCOUNTS'])
{
Catalog\Discount\DiscountManager::preloadPriceData($products, $this->storage['PRICES_ALLOW']);
Catalog\Discount\DiscountManager::preloadProductDataToExtendOrder($products, $this->getUserGroups());
}
else
{
\CCatalogDiscount::SetProductSectionsCache($products);
\CCatalogDiscount::SetDiscountProductCache($products, array('IBLOCK_ID' => $iblock, 'GET_BY_ID' => 'Y'));
}
}
}
}
/**
* Clear discount cache.
*
* @return void
*/
protected function clearCatalogDiscountCache()
{
if ($this->useCatalog && $this->useDiscountCache)
{
\CCatalogDiscount::ClearDiscountCache(array(
'PRODUCT' => true,
'SECTIONS' => true,
'PROPERTIES' => true
));
}
}
/**
* Check the settings for the output price currency.
*
* @return void
*/
protected function initCurrencyConvert()
{
$this->storage['CONVERT_CURRENCY'] = array();
if ($this->arParams['CONVERT_CURRENCY'] === 'Y')
{
$correct = false;
if (Loader::includeModule('currency'))
{
$this->storage['MODULES']['currency'] = true;
$correct = Currency\CurrencyManager::isCurrencyExist($this->arParams['CURRENCY_ID']);
}
if ($correct)
{
$this->storage['CONVERT_CURRENCY'] = array(
'CURRENCY_ID' => $this->arParams['CURRENCY_ID']
);
}
else
{
$this->arParams['CONVERT_CURRENCY'] = 'N';
$this->arParams['CURRENCY_ID'] = '';
}
unset($correct);
}
}
/**
* Check offers iblock.
*
* @param int $iblockId Iblock Id.
* @return bool
*/
protected function offerIblockExist($iblockId)
{
if (empty($this->storage['CATALOGS'][$iblockId]))
return false;
$catalog = $this->storage['CATALOGS'][$iblockId];
if (empty($catalog['CATALOG_TYPE']))
return false;
return $catalog['CATALOG_TYPE'] == \CCatalogSku::TYPE_FULL || $catalog['CATALOG_TYPE'] == \CCatalogSku::TYPE_PRODUCT;
}
/**
* Load used iblocks info to component storage.
*
* @return void
*/
protected function initCatalogInfo()
{
$catalogs = array();
if ($this->useCatalog)
{
$this->storage['SHOW_CATALOG_WITH_OFFERS'] = (string)Main\Config\Option::get('catalog', 'show_catalog_tab_with_offers') === 'Y';
$this->storage['USE_SALE_DISCOUNTS'] = (string)Main\Config\Option::get('sale', 'use_sale_discount_only') === 'Y';
foreach (array_keys($this->iblockProducts) as $iblockId)
{
$catalog = \CCatalogSku::GetInfoByIBlock($iblockId);
if (!empty($catalog) && is_array($catalog))
{
$this->isIblockCatalog = $this->isIblockCatalog || $catalog['CATALOG_TYPE'] != \CCatalogSku::TYPE_PRODUCT;
$catalogs[$iblockId] = $catalog;
}
}
}
$this->storage['CATALOGS'] = $catalogs;
}
protected function getProductInfo($productId)
{
if (!$this->useCatalog)
return null;
$productId = (int)$productId;
if ($productId <= 0)
return null;
$iblockId = (int)\CIBlockElement::GetIBlockByID($productId);
if ($iblockId <= 0)
return null;
$iterator = Catalog\ProductTable::getList([
'select' => ['ID', 'TYPE'],
'filter' => ['=ID' => $productId]
]);
$row = $iterator->fetch();
unset($iterator);
if (empty($row))
return null;
$row['ID'] = (int)$row['ID'];
$row['TYPE'] = (int)$row['TYPE'];
if (
$row['TYPE'] == Catalog\ProductTable::TYPE_EMPTY_SKU
|| $row['TYPE'] == Catalog\ProductTable::TYPE_FREE_OFFER
)
return null;
$row['ELEMENT_IBLOCK_ID'] = $iblockId;
$row['PRODUCT_IBLOCK_ID'] = 0;
if (isset($this->storage['CATALOGS'][$iblockId]))
{
if ($this->storage['CATALOGS'][$iblockId]['CATALOG_TYPE'] == \CCatalogSku::TYPE_CATALOG)
$row['PRODUCT_IBLOCK_ID'] = $this->storage['CATALOGS'][$iblockId]['IBLOCK_ID'];
else
$row['PRODUCT_IBLOCK_ID'] = $this->storage['CATALOGS'][$iblockId]['PRODUCT_IBLOCK_ID'];
return $row;
}
$catalog = \CCatalogSku::GetInfoByIBlock($iblockId);
if (empty($catalog) || !is_array($catalog))
return null;
if ($catalog['CATALOG_TYPE'] == \CCatalogSku::TYPE_PRODUCT)
return null;
if ($catalog['CATALOG_TYPE'] == \CCatalogSku::TYPE_OFFERS)
{
$iblockId = $catalog['PRODUCT_IBLOCK_ID'];
$catalog = \CCatalogSku::GetInfoByIBlock($iblockId);
}
if (!isset($this->storage['CATALOGS']))
$this->storage['CATALOGS'] = [];
$this->storage['CATALOGS'][$iblockId] = $catalog;
unset($catalog);
if ($this->storage['CATALOGS'][$iblockId]['CATALOG_TYPE'] == \CCatalogSku::TYPE_CATALOG)
$row['PRODUCT_IBLOCK_ID'] = $this->storage['CATALOGS'][$iblockId]['IBLOCK_ID'];
else
$row['PRODUCT_IBLOCK_ID'] = $this->storage['CATALOGS'][$iblockId]['PRODUCT_IBLOCK_ID'];
return $row;
}
/**
* Load catalog prices in component storage.
*
* @return void
*/
protected function initPrices()
{
// This function returns array with prices description and access rights
// in case catalog module n/a prices get values from element properties
$this->storage['PRICES'] = \CIBlockPriceTools::GetCatalogPrices(
isset($this->arParams['IBLOCK_ID']) && $this->arParams['IBLOCK_ID'] > 0 ? $this->arParams['IBLOCK_ID'] : false,
$this->arParams['PRICE_CODE']
);
$this->storage['PRICES_ALLOW'] = \CIBlockPriceTools::GetAllowCatalogPrices($this->storage['PRICES']);
$this->storage['PRICES_CAN_BUY'] = array();
$this->storage['PRICES_MAP'] = array();
foreach ($this->storage['PRICES'] as $priceType)
{
$this->storage['PRICES_MAP'][$priceType['ID']] = $priceType['CODE'];
if ($priceType['CAN_BUY'])
$this->storage['PRICES_CAN_BUY'][$priceType['ID']] = $priceType['ID'];
}
$this->storage['PRICE_TYPES'] = array();
if ($this->useCatalog)
$this->storage['PRICE_TYPES'] = \CCatalogGroup::GetListArray();
$this->useDiscountCache = false;
if ($this->useCatalog)
{
if (!empty($this->storage['CATALOGS']) && !empty($this->storage['PRICES_ALLOW']))
$this->useDiscountCache = true;
}
if ($this->useCatalog && $this->useDiscountCache)
{
$this->useDiscountCache = \CIBlockPriceTools::SetCatalogDiscountCache(
$this->storage['PRICES_ALLOW'],
$this->getUserGroups()
);
}
if ($this->useCatalog)
Catalog\Product\Price::loadRoundRules($this->storage['PRICES_ALLOW']);
}
/**
* Load catalog vats in component storage.
*
* @return void
*/
protected function initVats()
{
$this->storage['VATS'] = [];
$this->storage['IBLOCKS_VAT'] = [];
if ($this->useCatalog)
{
$iterator = Catalog\VatTable::getList([
'select' => ['ID', 'RATE'],
'order' => ['ID' => 'ASC']
]);
while ($row = $iterator->fetch())
$this->storage['VATS'][(int)$row['ID']] = (float)$row['RATE'];
unset($row, $iterator);
if (!empty($this->storage['CATALOGS']))
{
foreach ($this->storage['CATALOGS'] as $catalog)
{
$this->storage['IBLOCKS_VAT'][$catalog['IBLOCK_ID']] = 0;
if ($catalog['PRODUCT_IBLOCK_ID'] > 0)
$this->storage['IBLOCKS_VAT'][$catalog['PRODUCT_IBLOCK_ID']] = 0;
}
unset($catalog);
$iterator = Catalog\CatalogIblockTable::getList([
'select' => ['IBLOCK_ID', 'VAT_ID'],
'filter' => ['@IBLOCK_ID' => array_keys($this->storage['IBLOCKS_VAT'])]
]);
while ($row = $iterator->fetch())
$this->storage['IBLOCKS_VAT'][(int)$row['IBLOCK_ID']] = (int)$row['VAT_ID'];
unset($row, $iterator);
}
}
}
/**
* @return void
*/
protected function initIblockPropertyFeatures()
{
}
/**
* Initialize and data process of iblock elements.
*
* @return void
*/
protected function initElementList()
{
$this->storage['CURRENCY_LIST'] = array();
$this->storage['DEFAULT_MEASURE'] = $this->getDefaultMeasure();
$this->initQueryFields();
foreach ($this->iblockProducts as $iblock => $products)
{
$elementIterator = $this->getElementList($iblock, $products);
$iblockElements = $this->getIblockElements($elementIterator);
if (!empty($iblockElements) && !$this->hasErrors())
{
$this->modifyDisplayProperties($iblock, $iblockElements);
$this->elements = array_merge($this->elements, array_values($iblockElements));
$this->iblockProducts[$iblock] = array_keys($iblockElements);
}
unset($elementIterator, $iblockElements, $element);
}
}
/**
* Return elements.
*
* @param \CIBlockResult $elementIterator Iterator.
* @return mixed
*/
abstract protected function getIblockElements($elementIterator);
/**
* Sort elements by original position (in case when product ids used in GetList).
*
* @return void
*/
protected function sortElementList()
{
if (!empty($this->productIdMap) && is_array($this->productIdMap))
{
$sortedElements = array();
foreach (array_keys($this->productIdMap) as $productId)
{
$parentId = $this->productIdMap[$productId];
foreach ($this->elements as $element)
{
if ($element['ID'] == $parentId)
{
$sortedElements[$productId] = $element;
break;
}
}
}
$this->elements = array_values($sortedElements);
}
}
/**
* Create link to elements for fast access.
*
* @return void
*/
protected function makeElementLinks()
{
if (!empty($this->elements))
{
foreach ($this->elements as $index => $element)
{
$this->elementLinks[$element['ID']] =& $this->elements[$index];
}
}
}
/**
* Return array of iblock element ids to show for "initialLoad" action.
*
* @return bool|array
*/
protected function getProductIds()
{
return false;
}
/**
* Return array of iblock element ids to show for "bigDataLoad" action.
*
* @return array
*/
protected function getBigDataProductIds()
{
$shownIds = $this->request->get('shownIds');
if (!empty($shownIds) && is_array($shownIds))
{
$this->arParams['FILTER_IDS'] += $shownIds;
}
$this->arParams['PAGE_ELEMENT_COUNT'] = $this->request->get('count') ?: 20;
$this->arParams['FILTER'] = $this->arParams['FILTER'] ?: array('PAYED');
$this->arParams['BY'] = $this->arParams['BY'] ?: 'AMOUNT';
$this->arParams['PERIOD'] = (int)$this->arParams['PERIOD'] ?: 30;
$this->arParams['DEPTH'] = (int)$this->arParams['DEPTH'] ?: 2;
// general filter
$this->filterFields = $this->getFilter();
$this->filterFields['IBLOCK_ID'] = $this->arParams['IBLOCK_ID'];
$this->prepareElementQueryFields();
// try cloud
$ids = $this->request->get('items') ?: array();
if (!empty($ids))
{
$recommendationId = $this->request->get('rid');
$ids = $this->filterByParams($ids, $this->arParams['FILTER_IDS']);
foreach ($ids as $id)
{
$this->recommendationIdToProduct[$id] = $recommendationId;
}
}
// try bestsellers
if (Main\Loader::includeModule('sale') && count($ids) < $this->arParams['PAGE_ELEMENT_COUNT'])
{
$ids = $this->getBestSellersRecommendation($ids);
}
// try most viewed
if ($this->useCatalog && count($ids) < $this->arParams['PAGE_ELEMENT_COUNT'])
{
$ids = $this->getMostViewedRecommendation($ids);
}
// try random
if (count($ids) < $this->arParams['PAGE_ELEMENT_COUNT'])
{
$ids = $this->getRandomRecommendation($ids);
}
// limit
$ids = array_slice($ids, 0, $this->arParams['PAGE_ELEMENT_COUNT']);
return $ids;
}
/**
* Return recommended best seller products ids.
*
* @param array $ids Products id.
* @return array
*/
protected function getBestSellersRecommendation($ids)
{
// increase element count
$this->arParams['PAGE_ELEMENT_COUNT'] = $this->arParams['PAGE_ELEMENT_COUNT'] * 10;
$bestsellers = $this->getBestSellersProductIds();
$this->arParams['PAGE_ELEMENT_COUNT'] = $this->arParams['PAGE_ELEMENT_COUNT'] / 10;
if (!empty($bestsellers))
{
$recommendationId = 'bestsellers';
$bestsellers = Main\Analytics\Catalog::getProductIdsByOfferIds($bestsellers);
$bestsellers = $this->filterByParams($bestsellers, $this->arParams['FILTER_IDS']);
foreach ($bestsellers as $id)
{
if (!isset($this->recommendationIdToProduct[$id]))
{
$this->recommendationIdToProduct[$id] = $recommendationId;
}
}
$ids = array_unique(array_merge($ids, $bestsellers));
}
return $ids;
}
/**
* Return recommended most viewed products ids.
*
* @param array $ids Products id.
* @return array
*/
protected function getMostViewedRecommendation($ids)
{
$mostViewed = array();
$recommendationId = 'mostviewed';
$result = Catalog\CatalogViewedProductTable::getList(array(
'select' => array(
'ELEMENT_ID',
new Main\Entity\ExpressionField('SUM_HITS', 'SUM(%s)', 'VIEW_COUNT')
),
'filter' => array(
'=SITE_ID' => $this->getSiteId(),
'>ELEMENT_ID' => 0,
'>DATE_VISIT' => new Main\Type\DateTime(date('Y-m-d H:i:s', strtotime('-30 days')), 'Y-m-d H:i:s')
),
'order' => array('SUM_HITS' => 'DESC'),
'limit' => $this->arParams['PAGE_ELEMENT_COUNT'] * 10
));
while ($row = $result->fetch())
{
$mostViewed[] = $row['ELEMENT_ID'];
}
unset($row, $result);
$mostViewed = $this->filterByParams($mostViewed, $this->arParams['FILTER_IDS']);
foreach ($mostViewed as $id)
{
if (!isset($this->recommendationIdToProduct[$id]))
{
$this->recommendationIdToProduct[$id] = $recommendationId;
}
}
return array_unique(array_merge($ids, $mostViewed));
}
/**
* Return random products ids.
*
* @param array $ids Products id.
* @return array
*/
protected function getRandomRecommendation($ids)
{
$limit = $this->getRecommendationLimit($ids);
if ($limit <= 0)
{
return $ids;
}
$randomIds = array();
$recommendationId = 'random';
$filter = $this->filterFields;
$filterIds = array_merge($ids, $this->arParams['FILTER_IDS']);
if (!empty($filterIds))
{
$filter['!ID'] = $filterIds;
}
if ($this->arParams['SHOW_FROM_SECTION'] === 'Y' && !empty($this->arParams['SECTION_ID']))
{
$filter['SECTION_ID'] = $this->arParams['SECTION_ID'];
}
$elementIterator = \CIBlockElement::GetList(array('RAND' => 'ASC'), $filter, false, array('nTopCount' => $limit), array('ID'));
while ($element = $elementIterator->Fetch())
{
$randomIds[] = $element['ID'];
}
if (!empty($randomIds))
{
$this->setCacheUsage(false);
}
foreach ($randomIds as $id)
{
if (!isset($this->recommendationIdToProduct[$id]))
{
$this->recommendationIdToProduct[$id] = $recommendationId;
}
}
return array_merge($ids, $randomIds);
}
/**
* Filter correct product ids.
*
* @param array $ids Items ids.
* @param array $filterIds Filtered ids.
* @param bool $useSectionFilter Check filter by section.
* @return array
*/
protected function filterByParams($ids, $filterIds = array(), $useSectionFilter = true)
{
if (empty($ids))
{
return array();
}
$ids = array_values(array_unique($ids));
// remove duplicates of already showed items
if (!empty($filterIds))
{
$ids = array_diff($ids, $filterIds);
}
if (!empty($ids))
{
$filter = $this->filterFields;
$filter['ID'] = $ids;
$correctIds = array();
$elementIterator = \CIBlockElement::GetList(array(), $filter, false, false, array('ID'));
while ($element = $elementIterator->Fetch())
{
$correctIds[] = $element['ID'];
}
if ($useSectionFilter && !empty($correctIds) && $this->arParams['SHOW_FROM_SECTION'] === 'Y')
{
$correctIds = $this->filterIdBySection(
$correctIds,
$this->arParams['IBLOCK_ID'],
$this->arParams['SECTION_ID'],
$this->arParams['PAGE_ELEMENT_COUNT'],
$this->arParams['DEPTH']
);
}
$correctIds = array_flip($correctIds);
// remove invalid items
foreach ($ids as $key => $id)
{
if (!isset($correctIds[$id]))
{
unset($ids[$key]);
}
}
return array_values($ids);
}
else
{
return array();
}
}
/**
* Return section ID by CODE.
*
* @param string $sectionCode Iblock section code.
* @return int
*/
protected function getSectionIdByCode($sectionCode = '')
{
$sectionId = 0;
$sectionCode = (string)$sectionCode;
if ($sectionCode === '')
{
return $sectionId;
}
$sectionFilter = array(
'@IBLOCK_ID' => $this->arParams['IBLOCK_ID'],
'=IBLOCK.ACTIVE' => 'Y',
'=CODE' => $sectionCode
);
$section = Iblock\SectionTable::getList(array(
'select' => array('ID'),
'filter' => $sectionFilter
))->fetch();
if (!empty($section))
{
$sectionId = (int)$section['ID'];
}
return $sectionId;
}
/**
* Return section ID by element ID.
*
* @param int $elementId Iblock element id.
* @param string $elementCode Iblock element code.
* @return int
*/
protected function getSectionIdByElement($elementId, $elementCode = '')
{
$sectionId = 0;
$elementId = (int)$elementId;
$elementCode = (string)$elementCode;
$filter = array('=IBLOCK_ID' => $this->arParams['IBLOCK_ID']);
if ($elementId > 0)
{
$filter['=ID'] = $elementId;
}
elseif ($elementCode !== '')
{
$filter['=CODE'] = $elementCode;
}
else
{
return $sectionId;
}
$itemIterator = Iblock\ElementTable::getList(array(
'select' => array('ID', 'IBLOCK_SECTION_ID'),
'filter' => $filter
));
if ($item = $itemIterator->fetch())
{
$sectionId = (int)$item['IBLOCK_SECTION_ID'];
}
return $sectionId;
}
protected function filterIdBySection($elementIds, $iblockId, $sectionId, $limit, $depth = 0)
{
$map = array();
Main\Type\Collection::normalizeArrayValuesByInt($elementIds);
if (empty($elementIds))
return $map;
$iblockId = (int)$iblockId;
$sectionId = (int)$sectionId;
$limit = (int)$limit;
$depth = (int)$depth;
if ($iblockId <= 0 ||$depth < 0)
return $map;
$subSections = array();
if ($depth > 0)
{
$parentSectionId = Catalog\Product\Viewed::getParentSection($sectionId, $depth);
if ($parentSectionId !== null)
{
$subSections[$parentSectionId] = $parentSectionId;
}
unset($parentSectionId);
}
if (empty($subSections) && $sectionId <= 0)
{
$getListParams = array(
'select' => array('ID'),
'filter' => array(
'@ID' => $elementIds,
'=IBLOCK_ID' => $iblockId,
'=WF_STATUS_ID' => 1,
'=WF_PARENT_ELEMENT_ID' => null
),
);
if ($limit > 0)
{
$getListParams['limit'] = $limit;
}
$iterator = Iblock\ElementTable::getList($getListParams);
}
else
{
if (empty($subSections))
{
$subSections[$sectionId] = $sectionId;
}
$sectionQuery = new Main\Entity\Query(Iblock\SectionTable::getEntity());
$sectionQuery->setTableAliasPostfix('_parent');
$sectionQuery->setSelect(array('ID', 'LEFT_MARGIN', 'RIGHT_MARGIN'));
$sectionQuery->setFilter(array('@ID' => $subSections));
$subSectionQuery = new Main\Entity\Query(Iblock\SectionTable::getEntity());
$subSectionQuery->setTableAliasPostfix('_sub');
$subSectionQuery->setSelect(array('ID'));
$subSectionQuery->setFilter(array('=IBLOCK_ID' => $iblockId));
$subSectionQuery->registerRuntimeField(
'',
new Main\Entity\ReferenceField(
'BS',
Main\Entity\Base::getInstanceByQuery($sectionQuery),
array('>=this.LEFT_MARGIN' => 'ref.LEFT_MARGIN', '<=this.RIGHT_MARGIN' => 'ref.RIGHT_MARGIN'),
array('join_type' => 'INNER')
)
);
$sectionElementQuery = new Main\Entity\Query(Iblock\SectionElementTable::getEntity());
$sectionElementQuery->setSelect(array('IBLOCK_ELEMENT_ID'));
$sectionElementQuery->setGroup(array('IBLOCK_ELEMENT_ID'));
$sectionElementQuery->setFilter(array('=ADDITIONAL_PROPERTY_ID' => null));
$sectionElementQuery->registerRuntimeField(
'',
new Main\Entity\ReferenceField(
'BSUB',
Main\Entity\Base::getInstanceByQuery($subSectionQuery),
array('=this.IBLOCK_SECTION_ID' => 'ref.ID'),
array('join_type' => 'INNER')
)
);
$elementQuery = new Main\Entity\Query(Iblock\ElementTable::getEntity());
$elementQuery->setSelect(array('ID'));
$elementQuery->setFilter(array('=IBLOCK_ID' => $iblockId, '=WF_STATUS_ID' => 1, '=WF_PARENT_ELEMENT_ID' => null));
$elementQuery->registerRuntimeField(
'',
new Main\Entity\ReferenceField(
'BSE',
Main\Entity\Base::getInstanceByQuery($sectionElementQuery),
array('=this.ID' => 'ref.IBLOCK_ELEMENT_ID'),
array('join_type' => 'INNER')
)
);
if ($limit > 0)
{
$elementQuery->setLimit($limit);
}
$iterator = $elementQuery->exec();
unset($elementQuery, $sectionElementQuery, $subSectionQuery, $sectionQuery);
}
while ($row = $iterator->fetch())
{
$map[] = $row['ID'];
}
unset($row, $iterator);
return $map;
}
/**
* Return random element ids to fill partially empty space in row when lack of big data elements.
* Does not fill rows with no big data elements at all.
*
* @param array $ids
* @return int
*/
protected function getRecommendationLimit($ids)
{
$limit = 0;
$idsCount = count($ids);
$rowsRange = $this->request->get('rowsRange');
if (!empty($rowsRange))
{
foreach ($rowsRange as $range)
{
$range = (int)$range;
if ($range > $idsCount)
{
$limit = $range - $idsCount;
break;
}
}
}
else
{
$limit = $this->arParams['PAGE_ELEMENT_COUNT'] - $idsCount;
}
return $limit;
}
protected function getBigDataServiceRequestParams($type = '')
{
$params = array(
'uid' => $_COOKIE['BX_USER_ID'],
'aid' => Main\Analytics\Counter::getAccountId(),
'count' => max($this->arParams['PAGE_ELEMENT_COUNT'] * 2, 30)
);
// random choices
if ($type === 'any_similar')
{
$possible = array('similar_sell', 'similar_view', 'similar');
$type = $possible[array_rand($possible)];
}
elseif ($type === 'any_personal')
{
$possible = array('bestsell', 'personal');
$type = $possible[array_rand($possible)];
}
elseif ($type === 'any')
{
$possible = array('similar_sell', 'similar_view', 'similar', 'bestsell', 'personal');
$type = $possible[array_rand($possible)];
}
// configure
switch ($type)
{
case 'bestsell':
$params['op'] = 'sim_domain_items';
$params['type'] = 'order';
$params['domain'] = Main\Context::getCurrent()->getServer()->getHttpHost();
break;
case 'personal':
$params['op'] = 'recommend';
break;
case 'similar_sell':
$params['op'] = 'simitems';
$params['eid'] = $this->arParams['RCM_PROD_ID'];
$params['type'] = 'order';
break;
case 'similar_view':
$params['op'] = 'simitems';
$params['eid'] = $this->arParams['RCM_PROD_ID'];
$params['type'] = 'view';
break;
case 'similar':
$params['op'] = 'simitems';
$params['eid'] = $this->arParams['RCM_PROD_ID'];
break;
default:
$params['op'] = 'recommend';
}
$iblocks = array();
if (!empty($this->storage['IBLOCK_PARAMS']))
{
$iblocks = array_keys($this->storage['IBLOCK_PARAMS']);
}
else
{
$iblockList = array();
/* catalog */
$iblockIterator = Catalog\CatalogIblockTable::getList(array(
'select' => array('IBLOCK_ID', 'PRODUCT_IBLOCK_ID')
));
while ($iblock = $iblockIterator->fetch())
{
$iblock['IBLOCK_ID'] = (int)$iblock['IBLOCK_ID'];
$iblock['PRODUCT_IBLOCK_ID'] = (int)$iblock['PRODUCT_IBLOCK_ID'];
$iblockList[$iblock['IBLOCK_ID']] = $iblock['IBLOCK_ID'];
if ($iblock['PRODUCT_IBLOCK_ID'] > 0)
{
$iblockList[$iblock['PRODUCT_IBLOCK_ID']] = $iblock['PRODUCT_IBLOCK_ID'];
}
}
/* iblock */
$iblockIterator = Iblock\IblockSiteTable::getList(array(
'select' => array('IBLOCK_ID'),
'filter' => array('@IBLOCK_ID' => $iblockList, '=SITE_ID' => $this->getSiteId())
));
while ($iblock = $iblockIterator->fetch())
{
$iblocks[] = $iblock['IBLOCK_ID'];
}
}
$params['ib'] = join('.', $iblocks);
return $params;
}
/**
* Return best seller product ids.
*
* @return array
*/
protected function getBestSellersProductIds()
{
$productIds = array();
$filter = $this->getBestSellersFilter();
if (!empty($filter))
{
$productIterator = \CSaleProduct::GetBestSellerList(
$this->arParams['BY'],
array(),
$filter,
$this->arParams['PAGE_ELEMENT_COUNT']
);
while($product = $productIterator->fetch())
{
$productIds[] = $product['PRODUCT_ID'];
}
}
return $productIds;
}
protected function getBestSellersFilter()
{
$filter = array();
if (!empty($this->arParams['FILTER']))
{
$filter = array('=LID' => $this->getSiteId());
$subFilter = array('LOGIC' => 'OR');
$statuses = array(
'CANCELED' => true,
'ALLOW_DELIVERY' => true,
'PAYED' => true,
'DEDUCTED' => true
);
if ($this->arParams['PERIOD'] > 0)
{
$date = ConvertTimeStamp(AddToTimeStamp(array('DD' => '-'.$this->arParams['PERIOD'])));
if (!empty($date))
{
foreach ($this->arParams['FILTER'] as $field)
{
if (isset($statuses[$field]))
{
$subFilter[] = array(
'>=DATE_'.$field => $date,
'='.$field => 'Y'
);
}
else
{
if (empty($this->storage['ORDER_STATUS']) || in_array($field, $this->storage['ORDER_STATUS']))
{
$subFilter[] = array(
'=STATUS_ID' => $field,
'>=DATE_UPDATE' => $date,
);
}
}
}
unset($field);
}
}
else
{
foreach ($this->arParams['FILTER'] as $field)
{
if (isset($statuses[$field]))
{
$subFilter[] = array(
'='.$field => 'Y'
);
}
else
{
if (empty($this->storage['ORDER_STATUS']) || in_array($field, $this->storage['ORDER_STATUS']))
{
$subFilter[] = array(
'=STATUS_ID' => $field,
);
}
}
}
unset($field);
}
$filter[] = $subFilter;
}
return $filter;
}
/**
* Return array of iblock element ids to show for "initialLoad" action.
*
* @return array
*/
protected function getDeferredProductIds()
{
return array();
}
protected function getProductIdMap($productIds)
{
if ($productIds === false)
{
return false;
}
return $this->useCatalog ? static::getProductsMap($productIds) : $productIds;
}
/**
* Returns ids map: SKU_PRODUCT_ID => PRODUCT_ID.
*
* @param array $originalIds Input products ids.
* @return array
*/
public static function getProductsMap(array $originalIds = array())
{
if (empty($originalIds))
{
return array();
}
$result = array();
$productList = \CCatalogSku::getProductList($originalIds);
if ($productList === false)
{
$productList = array();
}
foreach ($originalIds as $id)
{
$result[$id] = isset($productList[$id]) ? $productList[$id]['ID'] : (int)$id;
}
return $result;
}
/**
* Return array map of iblock products.
* 3 following cases to process $productIdMap:
* ~ $productIdMap is array with ids - show elements with presented ids
* ~ $productIdMap is empty array - nothing to show
* ~ $productIdMap === false - show elements via filter(e.g. $arParams['IBLOCK_ID'], arParams['ELEMENT_ID'])
*
* @return array
*/
protected function getProductsSeparatedByIblock()
{
$iblockItems = array();
if (!empty($this->productIdMap) && is_array($this->productIdMap))
{
$itemsIterator = Iblock\ElementTable::getList(array(
'select' => array('ID', 'IBLOCK_ID'),
'filter' => array('@ID' => $this->productIdMap)
));
while ($item = $itemsIterator->fetch())
{
$item['ID'] = (int)$item['ID'];
$item['IBLOCK_ID'] = (int)$item['IBLOCK_ID'];
if (!isset($iblockItems[$item['IBLOCK_ID']]))
{
$iblockItems[$item['IBLOCK_ID']] = array();
}
$iblockItems[$item['IBLOCK_ID']][] = $item['ID'];
}
unset($item, $itemsIterator);
}
elseif ($this->productIdMap === false)
{
$iblockItems[$this->arParams['IBLOCK_ID']] = $this->arParams['ELEMENT_ID'];
}
return $iblockItems;
}
/**
* Return default measure.
*
* @return array|null
*/
protected function getDefaultMeasure()
{
$defaultMeasure = array();
if ($this->useCatalog)
{
$defaultMeasure = \CCatalogMeasure::getDefaultMeasure(true, true);
}
return $defaultMeasure;
}
/**
* Return \CIBlockResult iterator for current iblock ID.
*
* @param int $iblockId
* @param array|int $products
* @return \CIBlockResult|int
*/
protected function getElementList($iblockId, $products)
{
$selectFields = $this->getIblockSelectFields($iblockId);
$filterFields = $this->filterFields;
if ($iblockId > 0)
{
$filterFields['IBLOCK_ID'] = $iblockId;
}
if (!empty($products))
{
$filterFields['ID'] = $products;
}
$globalFilter = [];
if (!empty($this->globalFilter))
$globalFilter = $this->convertFilter($this->globalFilter);
$iteratorParams = [
'select' => $selectFields,
'filter' => array_merge($globalFilter, $filterFields),
'order' => $this->sortFields,
'navigation' => $this->navParams
];
if ($this->isSeparateLoading() && $iblockId > 0)
{
$elementIterator = $this->getSeparateList($iteratorParams);
}
else
{
$elementIterator = $this->getFullIterator($iteratorParams);
}
unset($iteratorParams);
$elementIterator->SetUrlTemplates($this->arParams['DETAIL_URL']);
return $elementIterator;
}
/**
* @param array $params
* @return \CIBlockResult
*/
protected function getSeparateList(array $params)
{
$list = [];
$selectFields = ['ID', 'IBLOCK_ID'];
if (!empty($params['order']))
{
$selectFields = array_unique(array_merge(
$selectFields,
array_keys($params['order'])
));
}
$iterator = \CIBlockElement::GetList(
$params['order'],
$params['filter'],
false,
$params['navigation'],
$selectFields
);
while ($row = $iterator->Fetch())
{
$id = (int)$row['ID'];
$list[$id] = $row;
}
unset($row);
if (!empty($list))
{
$fullIterator = \CIBlockElement::GetList(
[],
['IBLOCK_ID' => $params['filter']['IBLOCK_ID'], 'ID' => array_keys($list), 'SITE_ID' => $this->getSiteId()],
false,
false,
$params['select']
);
while ($row = $fullIterator->Fetch())
{
$id = (int)$row['ID'];
$list[$id] = $list[$id] + $row;
}
unset($row, $fullIterator);
$iterator->InitFromArray(array_values($list));
}
return $iterator;
}
/**
* @param array $params
* @return \CIBlockResult
*/
protected function getFullIterator(array $params)
{
return \CIBlockElement::GetList(
$params['order'],
$params['filter'],
false,
$params['navigation'],
$params['select']
);
}
/**
* Initialization of general query fields.
*
* @return void
*/
protected function initQueryFields()
{
$this->selectFields = $this->getSelect();
$this->filterFields = $this->getFilter();
$this->sortFields = $this->getSort();
$this->prepareElementQueryFields();
}
/**
* Return select fields to execute.
*
* @return array
*/
protected function getSelect()
{
$result = [
'ID', 'IBLOCK_ID', 'CODE', 'XML_ID', 'NAME', 'ACTIVE', 'DATE_ACTIVE_FROM', 'DATE_ACTIVE_TO', 'SORT',
'PREVIEW_TEXT', 'PREVIEW_TEXT_TYPE', 'DETAIL_TEXT', 'DETAIL_TEXT_TYPE', 'DATE_CREATE', 'CREATED_BY', 'TAGS',
'TIMESTAMP_X', 'MODIFIED_BY', 'IBLOCK_SECTION_ID', 'DETAIL_PAGE_URL', 'DETAIL_PICTURE', 'PREVIEW_PICTURE'
];
$checkPriceProperties = (
!$this->useCatalog
|| (
isset($this->arParams['IBLOCK_ID'])
&& $this->arParams['IBLOCK_ID'] > 0
&& !isset($this->storage['CATALOGS'][$this->arParams['IBLOCK_ID']])
)
);
if ($checkPriceProperties && !empty($this->storage['PRICES']))
{
foreach ($this->storage['PRICES'] as $row)
{
if (!empty($row['SELECT']))
$result[] = $row['SELECT'];
}
}
return $result;
}
/**
* Return filter fields to execute.
*
* @return array
*/
protected function getFilter()
{
return array(
'IBLOCK_LID' => $this->getSiteId(),
'ACTIVE_DATE' => 'Y',
'CHECK_PERMISSIONS' => 'Y',
'MIN_PERMISSION' => 'R'
);
}
/**
* Return sort fields to execute.
*
* @return array
*/
protected function getSort()
{
return array();
}
/**
* Prepare element getList parameters.
*
* @return void
*/
protected function prepareElementQueryFields()
{
$result = $this->prepareQueryFields($this->selectFields, $this->filterFields, $this->sortFields);
$this->selectFields = $result['SELECT'];
$this->filterFields = $result['FILTER'];
$this->sortFields = $result['ORDER'];
if (!empty($this->globalFilter))
{
$result = $this->prepareQueryFields([], $this->globalFilter, []);
$this->globalFilter = $result['FILTER'];
}
unset($result);
}
/**
* Prepare select, filter, order.
*
* @param array $select
* @param array $filter
* @param array $order
* @return array
*/
protected function prepareQueryFields(array $select, array $filter, array $order)
{
if ($this->useCatalog)
{
$select = $this->convertSelect($select);
$order = $this->convertOrder($order);
$filter = $this->convertFilter($filter);
$filter = \CProductQueryBuilder::modifyFilterFromOrder(
$filter,
$order,
['QUANTITY' => $this->arParams['SHOW_PRICE_COUNT']]
);
}
if (!empty($order))
{
foreach (array_keys($order) as $field)
$select[] = mb_strtoupper($field);
unset($field);
}
if (!empty($select))
$select = array_unique($select);
return [
'SELECT' => $select,
'FILTER' => $filter,
'ORDER' => $order
];
}
/**
* @deprecated
* @see \Bitrix\Iblock\Component\Base::prepareElementQueryFields
*/
protected function initPricesQuery()
{
$this->prepareElementQueryFields();
}
/**
* Return select product fields to execute.
*
* @param int $iblockId
* @param array $selectFields
* @return array
*/
protected function getProductSelect($iblockId, array $selectFields)
{
if (!$this->useCatalog)
return $selectFields;
$additionalFields = $this->getProductFields($iblockId);
$result = $selectFields;
if (!empty($additionalFields))
{
$result = array_merge($result, $additionalFields);
$result = array_unique($result);
}
unset($additionalFields);
return $result;
}
/**
* Returns product fields for iblock.
*
* @param int $iblockId
* @return array
*/
protected function getProductFields($iblockId)
{
if (!$this->isIblockCatalog && !$this->offerIblockExist($iblockId))
return [];
$result = [
'TYPE', 'AVAILABLE', 'BUNDLE',
'QUANTITY', 'QUANTITY_TRACE', 'CAN_BUY_ZERO', 'MEASURE',
'SUBSCRIBE',
'VAT_ID', 'VAT_INCLUDED',
'WEIGHT', 'WIDTH', 'LENGTH', 'HEIGHT',
'PAYMENT_TYPE', 'RECUR_SCHEME_LENGTH', 'RECUR_SCHEME_TYPE',
'TRIAL_PRICE_ID'
];
if ($this->isEnableCompatible())
{
$result = array_merge(
$result,
[
'QUANTITY_TRACE_RAW', 'CAN_BUY_ZERO_RAW', 'SUBSCRIBE_RAW',
'PURCHASING_PRICE', 'PURCHASING_CURRENCY',
'BARCODE_MULTI',
'WITHOUT_ORDER'
]
);
}
return $result;
}
/**
* Convert old product selected fields to new.
*
* @param array $select
* @return array
*/
protected function convertSelect(array $select)
{
if (!$this->useCatalog)
return $select;
return \CProductQueryBuilder::convertOldSelect($select);
}
/**
* Convert old product filter keys to new.
*
* @param array $filter
* @return array
*/
protected function convertFilter(array $filter)
{
if (!$this->useCatalog)
return $filter;
return \CProductQueryBuilder::convertOldFilter($filter);
}
/**
* Convert old product order keys to new.
*
* @param array $order
* @return array
*/
protected function convertOrder(array $order)
{
if (!$this->useCatalog)
return $order;
return \CProductQueryBuilder::convertOldOrder($order);
}
protected function getIblockSelectFields($iblockId)
{
if (!$this->useCatalog)
return $this->selectFields;
return $this->getProductSelect($iblockId, $this->selectFields);
}
/**
* Return parsed conditions array.
*
* @param $condition
* @param $params
* @return array
*/
protected function parseCondition($condition, $params)
{
$result = array();
if (!empty($condition) && is_array($condition))
{
if ($condition['CLASS_ID'] === 'CondGroup')
{
if (!empty($condition['CHILDREN']))
{
foreach ($condition['CHILDREN'] as $child)
{
$childResult = $this->parseCondition($child, $params);
// is group
if ($child['CLASS_ID'] === 'CondGroup')
{
$result[] = $childResult;
}
// same property names not overrides each other
elseif (isset($result[key($childResult)]))
{
$fieldName = key($childResult);
if (!isset($result['LOGIC']))
{
$result = array(
'LOGIC' => $condition['DATA']['All'],
array($fieldName => $result[$fieldName])
);
}
$result[][$fieldName] = $childResult[$fieldName];
}
else
{
$result += $childResult;
}
}
if (!empty($result))
{
$this->parsePropertyCondition($result, $condition, $params);
if (count($result) > 1)
{
$result['LOGIC'] = $condition['DATA']['All'];
}
}
}
}
else
{
$result += $this->parseConditionLevel($condition, $params);
}
}
return $result;
}
protected function parseConditionLevel($condition, $params)
{
$result = array();
if (!empty($condition) && is_array($condition))
{
$name = $this->parseConditionName($condition);
if (!empty($name))
{
$operator = $this->parseConditionOperator($condition);
$value = $this->parseConditionValue($condition, $name);
$result[$operator.$name] = $value;
if ($name === 'SECTION_ID')
{
$result['INCLUDE_SUBSECTIONS'] = isset($params['INCLUDE_SUBSECTIONS']) && $params['INCLUDE_SUBSECTIONS'] === 'N' ? 'N' : 'Y';
if (isset($params['INCLUDE_SUBSECTIONS']) && $params['INCLUDE_SUBSECTIONS'] === 'A')
{
$result['SECTION_GLOBAL_ACTIVE'] = 'Y';
}
$result = array($result);
}
}
}
return $result;
}
protected function parseConditionName(array $condition)
{
$name = '';
$conditionNameMap = array(
'CondIBXmlID' => 'XML_ID',
'CondIBSection' => 'SECTION_ID',
'CondIBDateActiveFrom' => 'DATE_ACTIVE_FROM',
'CondIBDateActiveTo' => 'DATE_ACTIVE_TO',
'CondIBSort' => 'SORT',
'CondIBDateCreate' => 'DATE_CREATE',
'CondIBCreatedBy' => 'CREATED_BY',
'CondIBTimestampX' => 'TIMESTAMP_X',
'CondIBModifiedBy' => 'MODIFIED_BY',
'CondIBTags' => 'TAGS',
'CondCatQuantity' => 'QUANTITY',
'CondCatWeight' => 'WEIGHT'
);
if (isset($conditionNameMap[$condition['CLASS_ID']]))
{
$name = $conditionNameMap[$condition['CLASS_ID']];
}
elseif (mb_strpos($condition['CLASS_ID'], 'CondIBProp') !== false)
{
$name = $condition['CLASS_ID'];
}
return $name;
}
protected function parseConditionOperator($condition)
{
$operator = '';
switch ($condition['DATA']['logic'])
{
case 'Equal':
$operator = '';
break;
case 'Not':
$operator = '!';
break;
case 'Contain':
$operator = '%';
break;
case 'NotCont':
$operator = '!%';
break;
case 'Great':
$operator = '>';
break;
case 'Less':
$operator = '<';
break;
case 'EqGr':
$operator = '>=';
break;
case 'EqLs':
$operator = '<=';
break;
}
return $operator;
}
protected function parseConditionValue($condition, $name)
{
$value = $condition['DATA']['value'];
switch ($name)
{
case 'DATE_ACTIVE_FROM':
case 'DATE_ACTIVE_TO':
case 'DATE_CREATE':
case 'TIMESTAMP_X':
$value = ConvertTimeStamp($value, 'FULL');
break;
}
return $value;
}
protected function parsePropertyCondition(array &$result, array $condition, $params)
{
if (!empty($result))
{
$subFilter = array();
foreach ($result as $name => $value)
{
if (!empty($result[$name]) && is_array($result[$name]))
{
$this->parsePropertyCondition($result[$name], $condition, $params);
}
else
{
if (($ind = mb_strpos($name, 'CondIBProp')) !== false)
{
list($prefix, $iblock, $propertyId) = explode(':', $name);
$operator = $ind > 0? mb_substr($prefix, 0, $ind) : '';
$catalogInfo = \CCatalogSku::GetInfoByIBlock($iblock);
if (!empty($catalogInfo))
{
if (
$catalogInfo['CATALOG_TYPE'] != \CCatalogSku::TYPE_CATALOG
&& $catalogInfo['IBLOCK_ID'] == $iblock
)
{
$subFilter[$operator.'PROPERTY_'.$propertyId] = $value;
}
else
{
$result[$operator.'PROPERTY_'.$propertyId] = $value;
}
}
unset($result[$name]);
}
}
}
if (!empty($subFilter) && !empty($catalogInfo))
{
$offerPropFilter = array(
'IBLOCK_ID' => $catalogInfo['IBLOCK_ID'],
'ACTIVE_DATE' => 'Y',
'ACTIVE' => 'Y'
);
if ($params['HIDE_NOT_AVAILABLE_OFFERS'] === 'Y')
{
$offerPropFilter['HIDE_NOT_AVAILABLE'] = 'Y';
}
elseif ($params['HIDE_NOT_AVAILABLE_OFFERS'] === 'L')
{
$offerPropFilter[] = array(
'LOGIC' => 'OR',
'AVAILABLE' => 'Y',
'SUBSCRIBE' => 'Y'
);
}
if (count($subFilter) > 1)
{
$subFilter['LOGIC'] = $condition['DATA']['All'];
$subFilter = array($subFilter);
}
$result['=ID'] = \CIBlockElement::SubQuery(
'PROPERTY_'.$catalogInfo['SKU_PROPERTY_ID'],
$offerPropFilter + $subFilter
);
}
}
}
/**
* Process element data to set in $arResult.
*
* @param array &$element
* @return void
*/
protected function processElement(array &$element)
{
$this->modifyElementCommonData($element);
$this->modifyElementPrices($element);
$this->setElementPanelButtons($element);
}
/**
* Fill various common fields for element.
*
* @param array &$element Element data.
* @return void
*/
protected function modifyElementCommonData(array &$element)
{
$element['ID'] = (int)$element['ID'];
$element['IBLOCK_ID'] = (int)$element['IBLOCK_ID'];
if ($this->arParams['HIDE_DETAIL_URL'])
{
$element['DETAIL_PAGE_URL'] = $element['~DETAIL_PAGE_URL'] = '';
}
if ($this->isEnableCompatible())
{
$element['ACTIVE_FROM'] = (isset($element['DATE_ACTIVE_FROM']) ? $element['DATE_ACTIVE_FROM'] : null);
$element['ACTIVE_TO'] = (isset($element['DATE_ACTIVE_TO']) ? $element['DATE_ACTIVE_TO'] : null);
}
$ipropValues = new Iblock\InheritedProperty\ElementValues($element['IBLOCK_ID'], $element['ID']);
$element['IPROPERTY_VALUES'] = $ipropValues->getValues();
Iblock\Component\Tools::getFieldImageData(
$element,
array('PREVIEW_PICTURE', 'DETAIL_PICTURE'),
Iblock\Component\Tools::IPROPERTY_ENTITY_ELEMENT,
'IPROPERTY_VALUES'
);
if (isset($element['~TYPE']))
{
$productFields = $this->getProductFields($element['IBLOCK_ID']);
$translateFields = $this->getCompatibleProductFields();
$element['PRODUCT'] = array(
'TYPE' => (int)$element['~TYPE'],
'AVAILABLE' => $element['~AVAILABLE'],
'BUNDLE' => $element['~BUNDLE'],
'QUANTITY' => $element['~QUANTITY'],
'QUANTITY_TRACE' => $element['~QUANTITY_TRACE'],
'CAN_BUY_ZERO' => $element['~CAN_BUY_ZERO'],
'MEASURE' => (int)$element['~MEASURE'],
'SUBSCRIBE' => $element['~SUBSCRIBE'],
'VAT_ID' => (int)$element['~VAT_ID'],
'VAT_RATE' => 0,
'VAT_INCLUDED' => $element['~VAT_INCLUDED'],
'WEIGHT' => (float)$element['~WEIGHT'],
'WIDTH' => (float)$element['~WIDTH'],
'LENGTH' => (float)$element['~LENGTH'],
'HEIGHT' => (float)$element['~HEIGHT'],
'PAYMENT_TYPE' => $element['~PAYMENT_TYPE'],
'RECUR_SCHEME_TYPE' => $element['~RECUR_SCHEME_TYPE'],
'RECUR_SCHEME_LENGTH' => (int)$element['~RECUR_SCHEME_LENGTH'],
'TRIAL_PRICE_ID' => (int)$element['~TRIAL_PRICE_ID']
);
$vatId = 0;
$vatRate = 0;
if ($element['PRODUCT']['VAT_ID'] > 0)
$vatId = $element['PRODUCT']['VAT_ID'];
elseif ($this->storage['IBLOCKS_VAT'][$element['IBLOCK_ID']] > 0)
$vatId = $this->storage['IBLOCKS_VAT'][$element['IBLOCK_ID']];
if ($vatId > 0 && isset($this->storage['VATS'][$vatId]))
$vatRate = $this->storage['VATS'][$vatId];
$element['PRODUCT']['VAT_RATE'] = $vatRate;
unset($vatRate, $vatId);
if ($this->isEnableCompatible())
{
foreach ($translateFields as $currentKey => $oldKey)
$element[$oldKey] = $element[$currentKey];
unset($currentKey, $oldKey);
$element['~CATALOG_VAT'] = $element['PRODUCT']['VAT_RATE'];
$element['CATALOG_VAT'] = $element['PRODUCT']['VAT_RATE'];
}
else
{
// temporary (compatibility custom templates)
$element['~CATALOG_TYPE'] = $element['PRODUCT']['TYPE'];
$element['CATALOG_TYPE'] = $element['PRODUCT']['TYPE'];
$element['~CATALOG_QUANTITY'] = $element['PRODUCT']['QUANTITY'];
$element['CATALOG_QUANTITY'] = $element['PRODUCT']['QUANTITY'];
$element['~CATALOG_QUANTITY_TRACE'] = $element['PRODUCT']['QUANTITY_TRACE'];
$element['CATALOG_QUANTITY_TRACE'] = $element['PRODUCT']['QUANTITY_TRACE'];
$element['~CATALOG_CAN_BUY_ZERO'] = $element['PRODUCT']['CAN_BUY_ZERO'];
$element['CATALOG_CAN_BUY_ZERO'] = $element['PRODUCT']['CAN_BUY_ZERO'];
$element['~CATALOG_SUBSCRIBE'] = $element['PRODUCT']['SUBSCRIBE'];
$element['CATALOG_SUBSCRIBE'] = $element['PRODUCT']['SUBSCRIBE'];
}
foreach ($productFields as $field)
unset($element[$field], $element['~'.$field]);
unset($field);
}
else
{
$element['PRODUCT'] = array(
'TYPE' => null,
'AVAILABLE' => null
);
}
$element['PROPERTIES'] = array();
$element['DISPLAY_PROPERTIES'] = array();
$element['PRODUCT_PROPERTIES'] = array();
$element['PRODUCT_PROPERTIES_FILL'] = array();
$element['OFFERS'] = array();
$element['OFFER_ID_SELECTED'] = 0;
if (!empty($this->storage['CATALOGS'][$element['IBLOCK_ID']]))
$element['CHECK_QUANTITY'] = $this->isNeedCheckQuantity($element['PRODUCT']);
if ($this->getAction() === 'bigDataLoad')
{
$element['RCM_ID'] = $this->recommendationIdToProduct[$element['ID']];
}
}
/**
* Add Hermitage button links for element.
*
* @param array &$element Element data.
* @return void
*/
protected function setElementPanelButtons(&$element)
{
$buttons = \CIBlock::GetPanelButtons(
$element['IBLOCK_ID'],
$element['ID'],
$element['IBLOCK_SECTION_ID'],
array('SECTION_BUTTONS' => false, 'SESSID' => false, 'CATALOG' => true)
);
$element['EDIT_LINK'] = $buttons['edit']['edit_element']['ACTION_URL'];
$element['DELETE_LINK'] = $buttons['edit']['delete_element']['ACTION_URL'];
}
/**
* Process element display properties by iblock parameters.
*
* @param int $iblock Iblock ID.
* @param array &$iblockElements Items.
* @return void
*/
protected function modifyDisplayProperties($iblock, &$iblockElements)
{
}
protected function getPropertyList($iblock, $propertyCodes)
{
$propertyList = array();
if (empty($propertyCodes))
return $propertyList;
$propertyCodes = array_fill_keys($propertyCodes, true);
$propertyIterator = Iblock\PropertyTable::getList(array(
'select' => array('ID', 'CODE', 'SORT'),
'filter' => array('=IBLOCK_ID' => $iblock, '=ACTIVE' => 'Y'),
'order' => array('SORT' => 'ASC', 'ID' => 'ASC')
));
while ($property = $propertyIterator->fetch())
{
$code = (string)$property['CODE'];
if ($code == '')
{
$code = $property['ID'];
}
if (!isset($propertyCodes[$code]))
continue;
$propertyList[] = $code;
}
return $propertyList;
}
/**
* Clear products data.
*
* @return void
*/
protected function clearItems()
{
$this->prices = array();
$this->measures = array();
$this->ratios = array();
$this->quantityRanges = array();
$this->oldData = array();
}
/**
* Load measure ratios for items.
*
* @param array $itemIds Items id list.
*
* @return void
*/
protected function loadMeasureRatios(array $itemIds)
{
if (empty($itemIds))
return;
Main\Type\Collection::normalizeArrayValuesByInt($itemIds, true);
if (empty($itemIds))
return;
$emptyRatioIds = array_fill_keys($itemIds, true);
$iterator = Catalog\MeasureRatioTable::getList(array(
'select' => array('ID', 'RATIO', 'IS_DEFAULT', 'PRODUCT_ID'),
'filter' => array('@PRODUCT_ID' => $itemIds),
'order' => array('PRODUCT_ID' => 'ASC')// not add 'RATIO' => 'ASC' - result will be resorted after load prices
));
while ($row = $iterator->fetch())
{
$ratio = ((float)$row['RATIO'] > (int)$row['RATIO'] ? (float)$row['RATIO'] : (int)$row['RATIO']);
if ($ratio > CATALOG_VALUE_EPSILON)
{
$row['RATIO'] = $ratio;
$row['ID'] = (int)$row['ID'];
$id = (int)$row['PRODUCT_ID'];
if (!isset($this->ratios[$id]))
$this->ratios[$id] = array();
$this->ratios[$id][$row['ID']] = $row;
unset($emptyRatioIds[$id]);
unset($id);
}
unset($ratio);
}
unset($row, $iterator);
if (!empty($emptyRatioIds))
{
$emptyRatio = $this->getEmptyRatio();
foreach (array_keys($emptyRatioIds) as $id)
{
$this->ratios[$id] = array(
$emptyRatio['ID'] => $emptyRatio
);
}
unset($id, $emptyRatio);
}
unset($emptyRatioIds);
}
/**
* Return default empty ratio (unexist in database).
*
* @return array
*/
protected function getEmptyRatio()
{
return array(
'ID' => 0,
'RATIO' => 1,
'IS_DEFAULT' => 'Y'
);
}
/**
* Init measure for items.
*
* @param array &$items Items list.
* @return void
*/
protected function initItemsMeasure(array &$items)
{
if (empty($items))
return;
foreach (array_keys($items) as $index)
{
if (!isset($items[$index]['PRODUCT']['MEASURE']))
continue;
if ($items[$index]['PRODUCT']['MEASURE'] > 0)
{
$items[$index]['ITEM_MEASURE'] = array(
'ID' => $items[$index]['PRODUCT']['MEASURE'],
'TITLE' => '',
'~TITLE' => ''
);
}
else
{
$items[$index]['ITEM_MEASURE'] = array(
'ID' => null,
'TITLE' => $this->storage['DEFAULT_MEASURE']['SYMBOL_RUS'],
'~TITLE' => $this->storage['DEFAULT_MEASURE']['~SYMBOL_RUS']
);
}
}
unset($index);
}
/**
* Return measure ids for items.
*
* @param array $items Items data.
* @return array
*/
protected function getMeasureIds(array $items)
{
$result = array();
if (!empty($items))
{
foreach (array_keys($items) as $itemId)
{
if (!isset($items[$itemId]['ITEM_MEASURE']))
continue;
$measureId = (int)$items[$itemId]['ITEM_MEASURE']['ID'];
if ($measureId > 0)
$result[$measureId] = $measureId;
unset($measureId);
}
unset($itemId);
}
return $result;
}
/**
* Load measures data.
*
* @param array $measureIds
* @return void
*/
protected function loadMeasures(array $measureIds)
{
if (empty($measureIds))
return;
Main\Type\Collection::normalizeArrayValuesByInt($measureIds, true);
if (empty($measureIds))
return;
$measureIterator = \CCatalogMeasure::getList(
array(),
array('@ID' => $measureIds),
false,
false,
array('ID', 'SYMBOL_RUS')
);
while ($measure = $measureIterator->GetNext())
{
$measure['ID'] = (int)$measure['ID'];
$measure['TITLE'] = $measure['SYMBOL_RUS'];
$measure['~TITLE'] = $measure['~SYMBOL_RUS'];
unset($measure['SYMBOL_RUS'], $measure['~SYMBOL_RUS']);
$this->measures[$measure['ID']] = $measure;
}
unset($measure, $measureIterator);
}
/**
* Load prices for items.
*
* @param array $itemIds Item ids.
* @return void
*/
protected function loadPrices(array $itemIds)
{
if (empty($itemIds))
return;
Main\Type\Collection::normalizeArrayValuesByInt($itemIds, true);
if (empty($itemIds))
return;
if (empty($this->storage['PRICES_ALLOW']))
return;
$enableCompatible = $this->isEnableCompatible();
$ratioList = array_fill_keys($itemIds, array());
$quantityList = array_fill_keys($itemIds, array());
$select = array(
'ID', 'PRODUCT_ID', 'CATALOG_GROUP_ID', 'PRICE', 'CURRENCY',
'QUANTITY_FROM', 'QUANTITY_TO'
);
if ($enableCompatible)
$select[] = 'EXTRA_ID';
$pagedItemIds = array_chunk($itemIds, 500);
foreach ($pagedItemIds as $pageIds)
{
if (empty($pageIds))
continue;
$iterator = Catalog\PriceTable::getList(array(
'select' => $select,
'filter' => array('@PRODUCT_ID' => $pageIds, '@CATALOG_GROUP_ID' => $this->storage['PRICES_ALLOW']),
'order' => array('PRODUCT_ID' => 'ASC', 'CATALOG_GROUP_ID' => 'ASC')
));
while ($row = $iterator->fetch())
{
$id = (int)$row['PRODUCT_ID'];
unset($row['PRODUCT_ID']);
if (!isset($this->prices[$id]))
{
$this->prices[$id] = array(
'RATIO' => array(),
'QUANTITY' => array(),
'SIMPLE' => array()
);
}
if ($row['QUANTITY_FROM'] !== null || $row['QUANTITY_TO'] !== null)
{
$hash = $this->getQuantityRangeHash($row);
if (!isset($quantityList[$id][$hash]))
{
$quantityList[$id][$hash] = array(
'HASH' => $hash,
'QUANTITY_FROM' => $row['QUANTITY_FROM'],
'QUANTITY_TO' => $row['QUANTITY_TO'],
'SORT_FROM' => (int)$row['QUANTITY_FROM'],
'SORT_TO' => ($row['QUANTITY_TO'] === null ? INF : (int)$row['QUANTITY_TO'])
);
}
if (!isset($this->prices[$id]['QUANTITY'][$hash]))
{
$this->prices[$id]['QUANTITY'][$hash] = array();
}
$this->prices[$id]['QUANTITY'][$hash][$row['CATALOG_GROUP_ID']] = $row;
unset($hash);
}
elseif ($row['MEASURE_RATIO_ID'] === null && $row['QUANTITY_FROM'] === null && $row['QUANTITY_TO'] === null)
{
$this->prices[$id]['SIMPLE'][$row['CATALOG_GROUP_ID']] = $row;
}
$this->storage['CURRENCY_LIST'][$row['CURRENCY']] = $row['CURRENCY'];
unset($id);
}
unset($row, $iterator);
}
unset($pageIds, $pagedItemIds);
foreach ($itemIds as $id)
{
if (isset($this->prices[$id]))
{
foreach ($this->prices[$id] as $key => $data)
{
if (empty($data))
unset($this->prices[$id][$key]);
}
unset($key, $data);
if (count($this->prices[$id]) !== 1)
{
unset($this->prices[$id]);
}
else
{
if (!empty($this->prices[$id]['QUANTITY']))
{
$productQuantity = $quantityList[$id];
Main\Type\Collection::sortByColumn(
$productQuantity,
array('SORT_FROM' => SORT_ASC, 'SORT_TO' => SORT_ASC),
'', null, true
);
$this->quantityRanges[$id] = $productQuantity;
unset($productQuantity);
if (count($this->ratios[$id]) > 1)
$this->compactItemRatios($id);
}
if (!empty($this->prices[$id]['SIMPLE']))
{
$range = $this->getFullQuantityRange();
$this->quantityRanges[$id] = array(
$range['HASH'] => $range
);
unset($range);
if (count($this->ratios[$id]) > 1)
$this->compactItemRatios($id);
}
}
}
}
unset($id);
unset($quantityList, $ratioList);
unset($enableCompatible);
}
protected function calculateItemPrices(array &$items)
{
if (empty($items))
return;
$enableCompatible = $this->isEnableCompatible();
if ($enableCompatible)
$this->initCompatibleFields($items);
foreach (array_keys($items) as $index)
{
$id = $items[$index]['ID'];
if (!isset($this->calculatePrices[$id]))
continue;
if (empty($this->prices[$id]))
continue;
$productPrices = $this->prices[$id];
$result = array(
'ITEM_PRICE_MODE' => null,
'ITEM_PRICES' => array(),
'ITEM_PRICES_CAN_BUY' => false
);
if ($this->arParams['FILL_ITEM_ALL_PRICES'])
$result['ITEM_ALL_PRICES'] = array();
$priceBlockIndex = 0;
if (!empty($productPrices['QUANTITY']))
{
$result['ITEM_PRICE_MODE'] = Catalog\ProductTable::PRICE_MODE_QUANTITY;
$ratio = current($this->ratios[$id]);
foreach ($this->quantityRanges[$id] as $range)
{
$priceBlock = $this->calculatePriceBlock(
$items[$index],
$productPrices['QUANTITY'][$range['HASH']],
$ratio['RATIO'],
$this->arParams['USE_PRICE_COUNT'] || $this->checkQuantityRange($range)
);
if (!empty($priceBlock))
{
$minimalPrice = ($this->arParams['FILL_ITEM_ALL_PRICES']
? $priceBlock['MINIMAL_PRICE']
: $priceBlock
);
if ($minimalPrice['QUANTITY_FROM'] === null)
{
$minimalPrice['MIN_QUANTITY'] = $ratio['RATIO'];
}
else
{
$minimalPrice['MIN_QUANTITY'] = $ratio['RATIO'] * ((int)($minimalPrice['QUANTITY_FROM']/$ratio['RATIO']));
if ($minimalPrice['MIN_QUANTITY'] < $minimalPrice['QUANTITY_FROM'])
$minimalPrice['MIN_QUANTITY'] += $ratio['RATIO'];
}
$result['ITEM_PRICES'][$priceBlockIndex] = $minimalPrice;
if (isset($this->storage['PRICES_CAN_BUY'][$minimalPrice['PRICE_TYPE_ID']]))
$result['ITEM_PRICES_CAN_BUY'] = true;
if ($this->arParams['FILL_ITEM_ALL_PRICES'])
{
$priceBlock['ALL_PRICES']['MIN_QUANTITY'] = $minimalPrice['MIN_QUANTITY'];
$result['ITEM_ALL_PRICES'][$priceBlockIndex] = $priceBlock['ALL_PRICES'];
}
unset($minimalPrice);
$priceBlockIndex++;
}
unset($priceBlock);
}
unset($range);
unset($ratio);
}
if (!empty($productPrices['SIMPLE']))
{
$result['ITEM_PRICE_MODE'] = Catalog\ProductTable::PRICE_MODE_SIMPLE;
$ratio = current($this->ratios[$id]);
$priceBlock = $this->calculatePriceBlock(
$items[$index],
$productPrices['SIMPLE'],
$ratio['RATIO'],
true
);
if (!empty($priceBlock))
{
$minimalPrice = ($this->arParams['FILL_ITEM_ALL_PRICES']
? $priceBlock['MINIMAL_PRICE']
: $priceBlock
);
$minimalPrice['MIN_QUANTITY'] = $ratio['RATIO'];
$result['ITEM_PRICES'][$priceBlockIndex] = $minimalPrice;
if (isset($this->storage['PRICES_CAN_BUY'][$minimalPrice['PRICE_TYPE_ID']]))
$result['ITEM_PRICES_CAN_BUY'] = true;
if ($this->arParams['FILL_ITEM_ALL_PRICES'])
{
$priceBlock['ALL_PRICES']['MIN_QUANTITY'] = $minimalPrice['MIN_QUANTITY'];
$result['ITEM_ALL_PRICES'][$priceBlockIndex] = $priceBlock['ALL_PRICES'];
}
unset($minimalPrice);
$priceBlockIndex++;
}
unset($priceBlock);
unset($ratio);
}
$this->prices[$id] = $result;
if (isset($items[$index]['ACTIVE']) && $items[$index]['ACTIVE'] === 'N')
{
$items[$index]['CAN_BUY'] = false;
}
else
{
$items[$index]['CAN_BUY'] = $result['ITEM_PRICES_CAN_BUY'] && $items[$index]['PRODUCT']['AVAILABLE'] === 'Y';
}
unset($priceBlockIndex, $result);
unset($productPrices);
if ($enableCompatible)
$this->resortOldPrices($id);
}
unset($index);
}
protected function transferItems(array &$items)
{
if (empty($items))
return;
$enableCompatible = $this->isEnableCompatible();
$urls = $this->storage['URLS'];
foreach (array_keys($items) as $index)
{
$itemId = $items[$index]['ID'];
// measure
if (!empty($items[$index]['ITEM_MEASURE']))
{
$id = (int)$items[$index]['ITEM_MEASURE']['ID'];
if (isset($this->measures[$id]))
{
$items[$index]['ITEM_MEASURE']['TITLE'] = $this->measures[$id]['TITLE'];
$items[$index]['ITEM_MEASURE']['~TITLE'] = $this->measures[$id]['~TITLE'];
}
unset($id);
}
// prices
$items[$index]['ITEM_MEASURE_RATIOS'] = $this->ratios[$itemId];
$items[$index]['ITEM_MEASURE_RATIO_SELECTED'] = $this->searchItemSelectedRatioId($itemId);
$items[$index]['ITEM_QUANTITY_RANGES'] = $this->quantityRanges[$itemId];
$items[$index]['ITEM_QUANTITY_RANGE_SELECTED'] = $this->searchItemSelectedQuantityRangeHash($itemId);
if (!empty($this->prices[$itemId]))
{
$items[$index] = array_merge($items[$index], $this->prices[$itemId]);
if (!empty($items[$index]['ITEM_PRICES']))
{
switch ($items[$index]['ITEM_PRICE_MODE'])
{
case Catalog\ProductTable::PRICE_MODE_SIMPLE:
$items[$index]['ITEM_PRICE_SELECTED'] = 0;
break;
case Catalog\ProductTable::PRICE_MODE_QUANTITY:
foreach (array_keys($items[$index]['ITEM_PRICES']) as $priceIndex)
{
if ($items[$index]['ITEM_PRICES'][$priceIndex]['QUANTITY_HASH'] == $items[$index]['ITEM_QUANTITY_RANGE_SELECTED'])
{
$items[$index]['ITEM_PRICE_SELECTED'] = $priceIndex;
break;
}
}
break;
case Catalog\ProductTable::PRICE_MODE_RATIO:
foreach (array_keys($items[$index]['ITEM_PRICES']) as $priceIndex)
{
if ($items[$index]['ITEM_PRICES'][$priceIndex]['MEASURE_RATIO_ID'] == $items[$index]['ITEM_MEASURE_RATIO_SELECTED'])
{
$items[$index]['ITEM_PRICE_SELECTED'] = $priceIndex;
break;
}
}
break;
}
}
}
// compatibility
if ($enableCompatible)
{
// old links to buy, add to basket, etc
$id = $items[$index]['ID'];
$items[$index]['~BUY_URL'] = str_replace('#ID#', $id, $urls['~BUY_URL_TEMPLATE']);
$items[$index]['BUY_URL'] = str_replace('#ID#', $id, $urls['BUY_URL_TEMPLATE']);
$items[$index]['~ADD_URL'] = str_replace('#ID#', $id, $urls['~ADD_URL_TEMPLATE']);
$items[$index]['ADD_URL'] = str_replace('#ID#', $id, $urls['ADD_URL_TEMPLATE']);
$items[$index]['~SUBSCRIBE_URL'] = str_replace('#ID#', $id, $urls['~SUBSCRIBE_URL_TEMPLATE']);
$items[$index]['SUBSCRIBE_URL'] = str_replace('#ID#', $id, $urls['SUBSCRIBE_URL_TEMPLATE']);
if ($this->arParams['DISPLAY_COMPARE'])
{
$items[$index]['~COMPARE_URL'] = str_replace('#ID#', $id, $urls['~COMPARE_URL_TEMPLATE']);
$items[$index]['COMPARE_URL'] = str_replace('#ID#', $id, $urls['COMPARE_URL_TEMPLATE']);
$items[$index]['~COMPARE_DELETE_URL'] = str_replace('#ID#', $id, $urls['~COMPARE_DELETE_URL_TEMPLATE']);
$items[$index]['COMPARE_DELETE_URL'] = str_replace('#ID#', $id, $urls['COMPARE_DELETE_URL_TEMPLATE']);
}
unset($id);
// old measure
$items[$index]['CATALOG_MEASURE_NAME'] = $items[$index]['ITEM_MEASURE']['TITLE'];
$items[$index]['~CATALOG_MEASURE_NAME'] = $items[$index]['ITEM_MEASURE']['~TITLE'];
// old measure ratio
$items[$index]['CATALOG_MEASURE_RATIO'] = $items[$index]['ITEM_MEASURE_RATIOS'][$items[$index]['ITEM_MEASURE_RATIO_SELECTED']]['RATIO'];
// old fields
if (!empty($this->oldData[$itemId]))
$items[$index] = array_merge($this->oldData[$itemId], $items[$index]);
}
unset($itemId);
}
unset($index);
unset($urls, $enableCompatible);
}
/**
* Calculate price block (simple price, quantity range, etc).
*
* @param array $product Product data.
* @param array $priceBlock Prices.
* @param int|float $ratio Measure ratio value.
* @param bool $defaultBlock Save result to old keys (PRICES, PRICE_MATRIX, MIN_PRICE).
* @return array|null
*/
protected function calculatePriceBlock(array $product, array $priceBlock, $ratio, $defaultBlock = false)
{
if (empty($product) || empty($priceBlock))
return null;
$enableCompatible = $defaultBlock && $this->isEnableCompatible();
if ($enableCompatible && !$this->arParams['USE_PRICE_COUNT'])
$this->fillCompatibleRawPriceFields($product['ID'], $priceBlock);
$userGroups = $this->getUserGroups();
$baseCurrency = Currency\CurrencyManager::getBaseCurrency();
/** @var null|array $minimalPrice */
$minimalPrice = null;
/** @var null|array $minimalBuyerPrice */
$minimalBuyerPrice = null;
$fullPrices = array();
$currencyConvert = $this->arParams['CONVERT_CURRENCY'] === 'Y';
$resultCurrency = ($currencyConvert ? $this->storage['CONVERT_CURRENCY']['CURRENCY_ID'] : null);
$vatRate = (float)$product['PRODUCT']['VAT_RATE'];
$percentVat = $vatRate * 0.01;
$percentPriceWithVat = 1 + $percentVat;
$vatInclude = $product['PRODUCT']['VAT_INCLUDED'] === 'Y';
$oldPrices = array();
$oldMinPrice = false;
$oldMatrix = false;
if ($enableCompatible && $this->arParams['USE_PRICE_COUNT'])
{
$oldMatrix = $this->getCompatibleFieldValue($product['ID'], 'PRICE_MATRIX');
if (empty($oldMatrix))
{
$oldMatrix = $this->getEmptyPriceMatrix();
$oldMatrix['AVAILABLE'] = $product['PRODUCT']['AVAILABLE'];
}
}
foreach ($priceBlock as $rawPrice)
{
$priceType = (int)$rawPrice['CATALOG_GROUP_ID'];
$price = (float)$rawPrice['PRICE'];
if (!$vatInclude)
$price *= $percentPriceWithVat;
$currency = $rawPrice['CURRENCY'];
$changeCurrency = $currencyConvert && $currency !== $resultCurrency;
if ($changeCurrency)
{
$price = \CCurrencyRates::ConvertCurrency($price, $currency, $resultCurrency);
$currency = $resultCurrency;
}
$discounts = array();
if (\CIBlockPriceTools::isEnabledCalculationDiscounts())
{
\CCatalogDiscountSave::Disable();
$discounts = \CCatalogDiscount::GetDiscount(
$product['ID'],
$product['IBLOCK_ID'],
array($priceType),
$userGroups,
'N',
$this->getSiteId(),
array()
);
\CCatalogDiscountSave::Enable();
}
$discountPrice = \CCatalogProduct::CountPriceWithDiscount(
$price,
$currency,
$discounts
);
if ($discountPrice !== false)
{
$priceWithVat = array(
'UNROUND_BASE_PRICE' => $price,
'UNROUND_PRICE' => $discountPrice,
'BASE_PRICE' => Catalog\Product\Price::roundPrice(
$priceType,
$price,
$currency
),
'PRICE' => Catalog\Product\Price::roundPrice(
$priceType,
$discountPrice,
$currency
)
);
$price /= $percentPriceWithVat;
$discountPrice /= $percentPriceWithVat;
$priceWithoutVat = array(
'UNROUND_BASE_PRICE' => $price,
'UNROUND_PRICE' => $discountPrice,
'BASE_PRICE' => Catalog\Product\Price::roundPrice(
$priceType,
$price,
$currency
),
'PRICE' => Catalog\Product\Price::roundPrice(
$priceType,
$discountPrice,
$currency
)
);
if ($this->arParams['PRICE_VAT_INCLUDE'])
$priceRow = $priceWithVat;
else
$priceRow = $priceWithoutVat;
$priceRow['ID'] = $rawPrice['ID'];
$priceRow['PRICE_TYPE_ID'] = $rawPrice['CATALOG_GROUP_ID'];
$priceRow['CURRENCY'] = $currency;
if (
empty($discounts)
|| ($priceRow['BASE_PRICE'] <= $priceRow['PRICE'])
)
{
$priceRow['BASE_PRICE'] = $priceRow['PRICE'];
$priceRow['DISCOUNT'] = 0;
$priceRow['PERCENT'] = 0;
}
else
{
$priceRow['DISCOUNT'] = $priceRow['BASE_PRICE'] - $priceRow['PRICE'];
$priceRow['PERCENT'] = roundEx(100*$priceRow['DISCOUNT']/$priceRow['BASE_PRICE'], 0);
}
if ($this->arParams['PRICE_VAT_SHOW_VALUE'])
$priceRow['VAT'] = ($vatRate > 0 ? $priceWithVat['PRICE'] - $priceWithoutVat['PRICE'] : 0);
if ($this->arParams['FILL_ITEM_ALL_PRICES'])
$fullPrices[$priceType] = $priceRow;
$priceRow['QUANTITY_FROM'] = $rawPrice['QUANTITY_FROM'];
$priceRow['QUANTITY_TO'] = $rawPrice['QUANTITY_TO'];
$priceRow['QUANTITY_HASH'] = $this->getQuantityRangeHash($rawPrice);
$priceRow['MEASURE_RATIO_ID'] = $rawPrice['MEASURE_RATIO_ID'];
$priceRow['PRICE_SCALE'] = \CCurrencyRates::ConvertCurrency(
$priceRow['PRICE'],
$priceRow['CURRENCY'],
$baseCurrency
);
if ($minimalPrice === null || $minimalPrice['PRICE_SCALE'] > $priceRow['PRICE_SCALE'])
$minimalPrice = $priceRow;
if (isset($this->storage['PRICES_CAN_BUY'][$priceRow['PRICE_TYPE_ID']]))
{
if ($minimalBuyerPrice === null || $minimalBuyerPrice['PRICE_SCALE'] > $priceRow['PRICE_SCALE'])
$minimalBuyerPrice = $priceRow;
}
if ($enableCompatible)
{
if ($this->arParams['USE_PRICE_COUNT'])
{
$rowIndex = $this->getQuantityRangeHash($rawPrice);
$oldMatrix['ROWS'][$rowIndex] = array(
'QUANTITY_FROM' => (float)$rawPrice['QUANTITY_FROM'],
'QUANTITY_TO' => (float)$rawPrice['QUANTITY_TO']
);
if (!isset($oldMatrix['MATRIX'][$priceType]))
{
$oldMatrix['MATRIX'][$priceType] = array();
$oldMatrix['COLS'][$priceType] = $this->storage['PRICE_TYPES'][$priceType];
}
$oldMatrix['MATRIX'][$priceType][$rowIndex] = array(
'ID' => $priceRow['ID'],
'PRICE' => $priceRow['BASE_PRICE'],
'DISCOUNT_PRICE' => $priceRow['PRICE'],
'UNROUND_DISCOUNT_PRICE' => $priceRow['UNROUND_PRICE'],
'CURRENCY' => $priceRow['CURRENCY'],
'VAT_RATE' => $percentVat
);
if ($changeCurrency)
{
$oldMatrix['MATRIX'][$priceType][$rowIndex]['ORIG_CURRENCY'] = $rawPrice['CURRENCY'];
$oldMatrix['MATRIX'][$priceType][$rowIndex]['ORIG_PRICE'] = \CCurrencyRates::ConvertCurrency(
$priceRow['BASE_PRICE'],
$priceRow['CURRENCY'],
$rawPrice['CURRENCY']
);
$oldMatrix['MATRIX'][$priceType][$rowIndex]['ORIG_DISCOUNT_PRICE'] = \CCurrencyRates::ConvertCurrency(
$priceRow['PRICE'],
$priceRow['CURRENCY'],
$rawPrice['CURRENCY']
);
$oldMatrix['MATRIX'][$priceType][$rowIndex]['ORIG_VAT_RATE'] = $percentVat; // crazy key, but above all the compatibility
}
}
else
{
$priceCode = $this->storage['PRICES_MAP'][$priceType];
$oldPriceRow = array(
'PRICE_ID' => $priceRow['PRICE_TYPE_ID'],
'ID' => $priceRow['ID'],
'CAN_ACCESS' => ($this->storage['PRICES'][$priceCode]['CAN_VIEW'] ? 'Y' : 'N'),
'CAN_BUY' => ($this->storage['PRICES'][$priceCode]['CAN_BUY'] ? 'Y' : 'N'),
'MIN_PRICE' => 'N',
'CURRENCY' => $priceRow['CURRENCY'],
'VALUE_VAT' => $priceWithVat['UNROUND_BASE_PRICE'],
'VALUE_NOVAT' => $priceWithoutVat['UNROUND_BASE_PRICE'],
'DISCOUNT_VALUE_VAT' => $priceWithVat['UNROUND_PRICE'],
'DISCOUNT_VALUE_NOVAT' => $priceWithoutVat['UNROUND_PRICE'],
'ROUND_VALUE_VAT' => $priceWithVat['PRICE'],
'ROUND_VALUE_NOVAT' => $priceWithoutVat['PRICE'],
'VALUE' => $priceRow['BASE_PRICE'],
'UNROUND_DISCOUNT_VALUE' => $priceRow['UNROUND_PRICE'],
'DISCOUNT_VALUE' => $priceRow['PRICE'],
'DISCOUNT_DIFF' => $priceRow['DISCOUNT'],
'DISCOUNT_DIFF_PERCENT' => $priceRow['PERCENT']
);
$oldPriceRow['VATRATE_VALUE'] = $oldPriceRow['VALUE_VAT'] - $oldPriceRow['VALUE_NOVAT'];
$oldPriceRow['DISCOUNT_VATRATE_VALUE'] = $oldPriceRow['DISCOUNT_VALUE_VAT'] - $oldPriceRow['DISCOUNT_VALUE_NOVAT'];
$oldPriceRow['ROUND_VATRATE_VALUE'] = $oldPriceRow['ROUND_VALUE_VAT'] - $oldPriceRow['ROUND_VALUE_NOVAT'];
if ($changeCurrency)
$oldPriceRow['ORIG_CURRENCY'] = $rawPrice['CURRENCY'];
$oldPrices[$priceCode] = $oldPriceRow;
unset($oldPriceRow);
}
}
}
unset($discounts);
unset($priceType);
}
unset($price);
$minimalPriceId = null;
if (is_array($minimalBuyerPrice))
$minimalPrice = $minimalBuyerPrice;
if (is_array($minimalPrice))
{
unset($minimalPrice['PRICE_SCALE']);
$minimalPriceId = $minimalPrice['PRICE_TYPE_ID'];
$prepareFields = array(
'BASE_PRICE', 'PRICE', 'DISCOUNT'
);
if ($this->arParams['PRICE_VAT_SHOW_VALUE'])
$prepareFields[] = 'VAT';
foreach ($prepareFields as $fieldName)
{
$minimalPrice['PRINT_'.$fieldName] = \CCurrencyLang::CurrencyFormat(
$minimalPrice[$fieldName],
$minimalPrice['CURRENCY'],
true
);
$minimalPrice['RATIO_'.$fieldName] = $minimalPrice[$fieldName]*$ratio;
$minimalPrice['PRINT_RATIO_'.$fieldName] = \CCurrencyLang::CurrencyFormat(
$minimalPrice['RATIO_'.$fieldName],
$minimalPrice['CURRENCY'],
true
);
}
unset($fieldName);
if ($this->arParams['FILL_ITEM_ALL_PRICES'])
{
foreach (array_keys($fullPrices) as $priceType)
{
foreach ($prepareFields as $fieldName)
{
$fullPrices[$priceType]['PRINT_'.$fieldName] = \CCurrencyLang::CurrencyFormat(
$fullPrices[$priceType][$fieldName],
$fullPrices[$priceType]['CURRENCY'],
true
);
$fullPrices[$priceType]['RATIO_'.$fieldName] = $fullPrices[$priceType][$fieldName]*$ratio;
$fullPrices[$priceType]['PRINT_RATIO_'.$fieldName] = \CCurrencyLang::CurrencyFormat(
$minimalPrice['RATIO_'.$fieldName],
$minimalPrice['CURRENCY'],
true
);
}
unset($fieldName);
}
unset($priceType);
}
unset($prepareFields);
}
if ($enableCompatible)
{
if ($this->arParams['USE_PRICE_COUNT'])
{
$oldMatrix['CAN_BUY'] = array_values($this->storage['PRICES_CAN_BUY']);
$this->oldData[$product['ID']]['PRICE_MATRIX'] = $oldMatrix;
}
else
{
$convertFields = array(
'VALUE_NOVAT', 'VALUE_VAT', 'VATRATE_VALUE',
'DISCOUNT_VALUE_NOVAT', 'DISCOUNT_VALUE_VAT', 'DISCOUNT_VATRATE_VALUE'
);
$prepareFields = array(
'VALUE_NOVAT', 'VALUE_VAT', 'VATRATE_VALUE',
'DISCOUNT_VALUE_NOVAT', 'DISCOUNT_VALUE_VAT', 'DISCOUNT_VATRATE_VALUE',
'VALUE', 'DISCOUNT_VALUE', 'DISCOUNT_DIFF'
);
if (!empty($oldPrices))
{
foreach (array_keys($oldPrices) as $index)
{
foreach ($prepareFields as $fieldName)
$oldPrices[$index]['PRINT_'.$fieldName] = \CCurrencyLang::CurrencyFormat(
$oldPrices[$index][$fieldName],
$oldPrices[$index]['CURRENCY'],
true
);
unset($fieldName);
if (isset($oldPrices[$index]['ORIG_CURRENCY']))
{
foreach ($convertFields as $fieldName)
$oldPrices[$index]['ORIG_' . $fieldName] = \CCurrencyRates::ConvertCurrency(
$oldPrices[$index][$fieldName],
$oldPrices[$index]['CURRENCY'],
$oldPrices[$index]['ORIG_CURRENCY']
);
unset($fieldName);
}
if ($oldPrices[$index]['PRICE_ID'] === $minimalPriceId)
{
$oldPrices[$index]['MIN_PRICE'] = 'Y';
$oldMinPrice = $oldPrices[$index];
}
}
unset($index);
}
unset($prepareFields);
$this->oldData[$product['ID']]['PRICES'] = $oldPrices;
$this->oldData[$product['ID']]['MIN_PRICE'] = $oldMinPrice;
}
}
unset($oldMatrix, $oldMinPrice, $oldPrices);
if (!$this->arParams['FILL_ITEM_ALL_PRICES'])
return $minimalPrice;
return array(
'MINIMAL_PRICE' => $minimalPrice,
'ALL_PRICES' => array(
'QUANTITY_FROM' => $minimalPrice['QUANTITY_FROM'],
'QUANTITY_TO' => $minimalPrice['QUANTITY_TO'],
'QUANTITY_HASH' => $minimalPrice['QUANTITY_HASH'],
'MEASURE_RATIO_ID' => $minimalPrice['MEASURE_RATIO_ID'],
'PRICES' => $fullPrices
)
);
}
protected function searchItemSelectedRatioId($id)
{
if (!isset($this->ratios[$id]))
return null;
$minimal = null;
$minimalRatio = null;
$result = null;
foreach ($this->ratios[$id] as $ratio)
{
if ($minimalRatio === null || $minimalRatio > $ratio['RATIO'])
{
$minimalRatio = $ratio['RATIO'];
$minimal = $ratio['ID'];
}
if ($ratio['IS_DEFAULT'] === 'Y')
{
$result = $ratio['ID'];
break;
}
}
unset($ratio);
return ($result === null ? $minimal : $result);
}
protected function compactItemRatios($id)
{
$ratioId = $this->searchItemSelectedRatioId($id);
if ($ratioId === null)
return;
$this->ratios[$id] = array(
$ratioId => $this->ratios[$id][$ratioId]
);
}
protected function getQuantityRangeHash(array $range)
{
return ($range['QUANTITY_FROM'] === null ? 'ZERO' : $range['QUANTITY_FROM']).
'-'.($range['QUANTITY_TO'] === null ? 'INF' : $range['QUANTITY_TO']);
}
protected function getFullQuantityRange()
{
return array(
'HASH' => $this->getQuantityRangeHash(array('QUANTITY_FROM' => null, 'QUANTITY_TO' => null)),
'QUANTITY_FROM' => null,
'QUANTITY_TO' => null,
'SORT_FROM' => 0,
'SORT_TO' => INF
);
}
protected function searchItemSelectedQuantityRangeHash($id)
{
if (empty($this->quantityRanges[$id]))
return null;
foreach ($this->quantityRanges[$id] as $range)
{
if ($this->checkQuantityRange($range))
return $range['HASH'];
}
reset($this->quantityRanges[$id]);
$firsrRange = current($this->quantityRanges[$id]);
return $firsrRange['HASH'];
}
/**
* Load URLs for different actions to storage.
*
* @return void
*/
protected function initUrlTemplates()
{
$actionVar = $this->arParams['ACTION_VARIABLE'];
$productIdVar = $this->arParams['PRODUCT_ID_VARIABLE'];
$compareActionVar = $this->arParams['ACTION_COMPARE_VARIABLE'];
$clearParams = Main\HttpRequest::getSystemParameters();
$clearParams[] = $actionVar;
$clearParams[] = $productIdVar;
$clearParams[] = $compareActionVar;
$clearParams[] = '';
if (!empty($this->arParams['CUSTOM_CURRENT_PAGE']))
{
$pageUrl = $this->arParams['CUSTOM_CURRENT_PAGE'];
}
else
{
if ($this->request->isAjaxRequest())
{
$pageUrl = $this->arParams['CURRENT_BASE_PAGE'];
}
else
{
$pageUrl = Main\Application::getInstance()->getContext()->getRequest()->getDecodedUri();
}
}
$currentUri = new Main\Web\Uri($pageUrl);
if ($this->arParams['USE_COMPARE_LIST'] == 'N' && $this->arParams['COMPARE_PATH'] != '')
{
$compareUri = new Main\Web\Uri($this->arParams['COMPARE_PATH']);
}
else
{
$compareUri = $currentUri;
}
$currentUri->deleteParams($clearParams);
$compareUri->deleteParams($clearParams);
$urls = [];
$urls['BUY_URL_TEMPLATE'] = $currentUri->addParams([$actionVar => self::ACTION_BUY, $productIdVar => '#ID#'])->getUri();
$urls['ADD_URL_TEMPLATE'] = $currentUri->addParams([$actionVar => self::ACTION_ADD_TO_BASKET, $productIdVar => '#ID#'])->getUri();
$urls['SUBSCRIBE_URL_TEMPLATE'] = $currentUri->addParams([$actionVar => self::ACTION_SUBSCRIBE, $productIdVar => '#ID#'])->getUri();
$urls['COMPARE_URL_TEMPLATE'] = $compareUri->addParams([$compareActionVar => self::ACTION_ADD_TO_COMPARE, $productIdVar => '#ID#'])->getUri();
$urls['COMPARE_DELETE_URL_TEMPLATE'] = $compareUri->addParams([$compareActionVar => self::ACTION_DELETE_FROM_COMPARE, $productIdVar => '#ID#'])->getUri();
unset($compareUri, $currentUri, $clearParams);
foreach (array_keys($urls) as $index)
{
$value = str_replace('%23ID%23', '#ID#', $urls[$index]); // format compatibility
$urls['~'.$index] = $value;
$urls[$index] = Main\Text\HtmlFilter::encode($value, ENT_QUOTES);
}
unset($index);
$this->storage['URLS'] = $urls;
}
/**
* Process element prices.
*
* @param array &$element Item data.
* @return void
*/
protected function modifyElementPrices(&$element)
{
$enableCompatible = $this->isEnableCompatible();
$id = $element['ID'];
$iblockId = $element['IBLOCK_ID'];
$catalog = !empty($this->storage['CATALOGS'][$element['IBLOCK_ID']])
? $this->storage['CATALOGS'][$element['IBLOCK_ID']]
: array();
$element['ITEM_PRICE_MODE'] = null;
$element['ITEM_PRICES'] = array();
$element['ITEM_QUANTITY_RANGES'] = array();
$element['ITEM_MEASURE_RATIOS'] = array();
$element['ITEM_MEASURE'] = array();
$element['ITEM_MEASURE_RATIO_SELECTED'] = null;
$element['ITEM_QUANTITY_RANGE_SELECTED'] = null;
$element['ITEM_PRICE_SELECTED'] = null;
if (!empty($catalog))
{
if (!isset($this->productWithOffers[$iblockId]))
$this->productWithOffers[$iblockId] = array();
if ($element['PRODUCT']['TYPE'] == Catalog\ProductTable::TYPE_SKU)
{
$this->productWithOffers[$iblockId][$id] = $id;
if ($this->storage['SHOW_CATALOG_WITH_OFFERS'] && $enableCompatible)
{
$this->productWithPrices[$id] = $id;
$this->calculatePrices[$id] = $id;
}
}
if (in_array(
$element['PRODUCT']['TYPE'],
array(
Catalog\ProductTable::TYPE_PRODUCT,
Catalog\ProductTable::TYPE_SET,
Catalog\ProductTable::TYPE_OFFER
)
))
{
$this->productWithPrices[$id] = $id;
$this->calculatePrices[$id] = $id;
}
if (isset($this->productWithPrices[$id]))
{
if ($element['PRODUCT']['MEASURE'] > 0)
{
$element['ITEM_MEASURE'] = array(
'ID' => $element['PRODUCT']['MEASURE'],
'TITLE' => '',
'~TITLE' => ''
);
}
else
{
$element['ITEM_MEASURE'] = array(
'ID' => null,
'TITLE' => $this->storage['DEFAULT_MEASURE']['SYMBOL_RUS'],
'~TITLE' => $this->storage['DEFAULT_MEASURE']['~SYMBOL_RUS']
);
}
if ($enableCompatible)
{
$element['CATALOG_MEASURE'] = $element['ITEM_MEASURE']['ID'];
$element['CATALOG_MEASURE_NAME'] = $element['ITEM_MEASURE']['TITLE'];
$element['~CATALOG_MEASURE_NAME'] = $element['ITEM_MEASURE']['~TITLE'];
}
}
}
else
{
$element['PRICES'] = \CIBlockPriceTools::GetItemPrices(
$element['IBLOCK_ID'],
$this->storage['PRICES'],
$element,
$this->arParams['PRICE_VAT_INCLUDE'],
$this->storage['CONVERT_CURRENCY']
);
if (!empty($element['PRICES']))
{
$element['MIN_PRICE'] = \CIBlockPriceTools::getMinPriceFromList($element['PRICES']);
}
$element['CAN_BUY'] = !empty($element['PRICES']);
}
}
/**
* Load, calculate and fill data (prices, measures, discounts, deprecated fields) for simple products.
*
* @return void.
*/
protected function processProducts()
{
$this->initItemsMeasure($this->elements);
$this->loadMeasures($this->getMeasureIds($this->elements));
$this->loadMeasureRatios($this->productWithPrices);
$this->loadPrices($this->productWithPrices);
$this->calculateItemPrices($this->elements);
$this->transferItems($this->elements);
}
/**
* Load, calculate and fill data (prices, measures, discounts, deprecated fields) for offers.
* Link offers to products.
*
* @return void
*/
protected function processOffers()
{
if ($this->useCatalog && !empty($this->iblockProducts))
{
$offers = array();
$paramStack = array();
$enableCompatible = $this->isEnableCompatible();
if ($enableCompatible)
{
$paramStack['USE_PRICE_COUNT'] = $this->arParams['USE_PRICE_COUNT'];
$paramStack['SHOW_PRICE_COUNT'] = $this->arParams['SHOW_PRICE_COUNT'];
$this->arParams['USE_PRICE_COUNT'] = false;
$this->arParams['SHOW_PRICE_COUNT'] = 1;
}
foreach (array_keys($this->iblockProducts) as $iblock)
{
if (!empty($this->productWithOffers[$iblock]))
{
$iblockOffers = $this->getIblockOffers($iblock);
if (!empty($iblockOffers))
{
$offersId = array_keys($iblockOffers);
$this->initItemsMeasure($iblockOffers);
$this->loadMeasures($this->getMeasureIds($iblockOffers));
$this->loadMeasureRatios($offersId);
$this->loadPrices($offersId);
$this->calculateItemPrices($iblockOffers);
$this->transferItems($iblockOffers);
$this->modifyOffers($iblockOffers);
$this->chooseOffer($iblockOffers, $iblock);
$offers = array_merge($offers, $iblockOffers);
}
unset($iblockOffers);
}
}
if ($enableCompatible)
{
$this->arParams['USE_PRICE_COUNT'] = $paramStack['USE_PRICE_COUNT'];
$this->arParams['SHOW_PRICE_COUNT'] = $paramStack['SHOW_PRICE_COUNT'];
}
unset($enableCompatible, $paramStack);
}
}
/**
* Return offers array for current iblock.
*
* @param $iblockId
* @return array
*/
protected function getIblockOffers($iblockId)
{
$offers = array();
$iblockParams = $this->storage['IBLOCK_PARAMS'][$iblockId];
$enableCompatible = $this->isEnableCompatible();
if (
$this->useCatalog
&& $this->offerIblockExist($iblockId)
&& !empty($this->productWithOffers[$iblockId])
)
{
$catalog = $this->storage['CATALOGS'][$iblockId];
$productProperty = 'PROPERTY_'.$catalog['SKU_PROPERTY_ID'];
$productPropertyValue = $productProperty.'_VALUE';
$offersFilter = $this->getOffersFilter($catalog['IBLOCK_ID']);
$offersFilter[$productProperty] = $this->productWithOffers[$iblockId];
$offersSelect = array(
'ID' => 1,
'IBLOCK_ID' => 1,
$productProperty => 1,
'PREVIEW_PICTURE' => 1,
'DETAIL_PICTURE' => 1
);
if (!empty($iblockParams['OFFERS_FIELD_CODE']))
{
foreach ($iblockParams['OFFERS_FIELD_CODE'] as $code)
$offersSelect[$code] = 1;
unset($code);
}
$offersSelect = $this->getProductSelect($iblockId, array_keys($offersSelect));
$getListParams = $this->prepareQueryFields($offersSelect, $offersFilter, $this->getOffersSort());
$offersSelect = $getListParams['SELECT'];
$offersFilter = $getListParams['FILTER'];
$offersOrder = $getListParams['ORDER'];
unset($getListParams);
$checkFields = array();
foreach (array_keys($offersOrder) as $code)
{
$code = mb_strtoupper($code);
if ($code == 'ID' || $code == 'AVAILABLE')
continue;
$checkFields[] = $code;
}
unset($code);
$productFields = $this->getProductFields($iblockId);
$translateFields = $this->getCompatibleProductFields();
$offersId = array();
$offersCount = array();
$iterator = \CIBlockElement::GetList(
$offersOrder,
$offersFilter,
false,
false,
$offersSelect
);
while($row = $iterator->GetNext())
{
$row['ID'] = (int)$row['ID'];
$row['IBLOCK_ID'] = (int)$row['IBLOCK_ID'];
$productId = (int)$row[$productPropertyValue];
if ($productId <= 0)
continue;
if ($enableCompatible && $this->arParams['OFFERS_LIMIT'] > 0)
{
$offersCount[$productId]++;
if($offersCount[$productId] > $this->arParams['OFFERS_LIMIT'])
continue;
}
$row['SORT_HASH'] = 'ID';
if (!empty($checkFields))
{
$checkValues = '';
foreach ($checkFields as $code)
$checkValues .= (isset($row[$code]) ? $row[$code] : '').'|';
unset($code);
if ($checkValues != '')
$row['SORT_HASH'] = md5($checkValues);
unset($checkValues);
}
$row['LINK_ELEMENT_ID'] = $productId;
$row['PROPERTIES'] = array();
$row['DISPLAY_PROPERTIES'] = array();
$row['PRODUCT'] = array(
'TYPE' => (int)$row['~TYPE'],
'AVAILABLE' => $row['~AVAILABLE'],
'BUNDLE' => $row['~BUNDLE'],
'QUANTITY' => $row['~QUANTITY'],
'QUANTITY_TRACE' => $row['~QUANTITY_TRACE'],
'CAN_BUY_ZERO' => $row['~CAN_BUY_ZERO'],
'MEASURE' => (int)$row['~MEASURE'],
'SUBSCRIBE' => $row['~SUBSCRIBE'],
'VAT_ID' => (int)$row['~VAT_ID'],
'VAT_RATE' => 0,
'VAT_INCLUDED' => $row['~VAT_INCLUDED'],
'WEIGHT' => (float)$row['~WEIGHT'],
'WIDTH' => (float)$row['~WIDTH'],
'LENGTH' => (float)$row['~LENGTH'],
'HEIGHT' => (float)$row['~HEIGHT'],
'PAYMENT_TYPE' => $row['~PAYMENT_TYPE'],
'RECUR_SCHEME_TYPE' => $row['~RECUR_SCHEME_TYPE'],
'RECUR_SCHEME_LENGTH' => (int)$row['~RECUR_SCHEME_LENGTH'],
'TRIAL_PRICE_ID' => (int)$row['~TRIAL_PRICE_ID']
);
$vatId = 0;
$vatRate = 0;
if ($row['PRODUCT']['VAT_ID'] > 0)
$vatId = $row['PRODUCT']['VAT_ID'];
elseif ($this->storage['IBLOCKS_VAT'][$catalog['IBLOCK_ID']] > 0)
$vatId = $this->storage['IBLOCKS_VAT'][$catalog['IBLOCK_ID']];
if ($vatId > 0 && isset($this->storage['VATS'][$vatId]))
$vatRate = $this->storage['VATS'][$vatId];
$row['PRODUCT']['VAT_RATE'] = $vatRate;
unset($vatRate, $vatId);
if ($enableCompatible)
{
foreach ($translateFields as $currentKey => $oldKey)
$row[$oldKey] = $row[$currentKey];
unset($currentKey, $oldKey);
$row['~CATALOG_VAT'] = $row['PRODUCT']['VAT_RATE'];
$row['CATALOG_VAT'] = $row['PRODUCT']['VAT_RATE'];
}
else
{
// temporary (compatibility custom templates)
$row['~CATALOG_TYPE'] = $row['PRODUCT']['TYPE'];
$row['CATALOG_TYPE'] = $row['PRODUCT']['TYPE'];
$row['~CATALOG_QUANTITY'] = $row['PRODUCT']['QUANTITY'];
$row['CATALOG_QUANTITY'] = $row['PRODUCT']['QUANTITY'];
$row['~CATALOG_QUANTITY_TRACE'] = $row['PRODUCT']['QUANTITY_TRACE'];
$row['CATALOG_QUANTITY_TRACE'] = $row['PRODUCT']['QUANTITY_TRACE'];
$row['~CATALOG_CAN_BUY_ZERO'] = $row['PRODUCT']['CAN_BUY_ZERO'];
$row['CATALOG_CAN_BUY_ZERO'] = $row['PRODUCT']['CAN_BUY_ZERO'];
$row['~CATALOG_SUBSCRIBE'] = $row['PRODUCT']['SUBSCRIBE'];
$row['CATALOG_SUBSCRIBE'] = $row['PRODUCT']['SUBSCRIBE'];
}
foreach ($productFields as $field)
unset($row[$field], $row['~'.$field]);
unset($field);
if ($row['PRODUCT']['TYPE'] == Catalog\ProductTable::TYPE_OFFER)
$this->calculatePrices[$row['ID']] = $row['ID'];
$row['ITEM_PRICE_MODE'] = null;
$row['ITEM_PRICES'] = array();
$row['ITEM_QUANTITY_RANGES'] = array();
$row['ITEM_MEASURE_RATIOS'] = array();
$row['ITEM_MEASURE'] = array();
$row['ITEM_MEASURE_RATIO_SELECTED'] = null;
$row['ITEM_QUANTITY_RANGE_SELECTED'] = null;
$row['ITEM_PRICE_SELECTED'] = null;
$row['CHECK_QUANTITY'] = $this->isNeedCheckQuantity($row['PRODUCT']);
if ($row['PRODUCT']['MEASURE'] > 0)
{
$row['ITEM_MEASURE'] = array(
'ID' => $row['PRODUCT']['MEASURE'],
'TITLE' => '',
'~TITLE' => ''
);
}
else
{
$row['ITEM_MEASURE'] = array(
'ID' => null,
'TITLE' => $this->storage['DEFAULT_MEASURE']['SYMBOL_RUS'],
'~TITLE' => $this->storage['DEFAULT_MEASURE']['~SYMBOL_RUS']
);
}
if ($enableCompatible)
{
$row['CATALOG_MEASURE'] = $row['ITEM_MEASURE']['ID'];
$row['CATALOG_MEASURE_NAME'] = $row['ITEM_MEASURE']['TITLE'];
$row['~CATALOG_MEASURE_NAME'] = $row['ITEM_MEASURE']['~TITLE'];
}
$row['PROPERTIES'] = array();
$row['DISPLAY_PROPERTIES'] = array();
Iblock\Component\Tools::getFieldImageData(
$row,
array('PREVIEW_PICTURE', 'DETAIL_PICTURE'),
Iblock\Component\Tools::IPROPERTY_ENTITY_ELEMENT,
''
);
$offersId[$row['ID']] = $row['ID'];
$offers[$row['ID']] = $row;
}
unset($row, $iterator);
if (!empty($offersId))
{
$loadPropertyCodes = $iblockParams['OFFERS_PROPERTY_CODE'];
if (Iblock\Model\PropertyFeature::isEnabledFeatures())
$loadPropertyCodes = array_merge($loadPropertyCodes, $iblockParams['OFFERS_TREE_PROPS']);
$propertyList = $this->getPropertyList($catalog['IBLOCK_ID'], $loadPropertyCodes);
unset($loadPropertyCodes);
if (!empty($propertyList) || $this->useDiscountCache)
{
\CIBlockElement::GetPropertyValuesArray($offers, $catalog['IBLOCK_ID'], $offersFilter);
foreach ($offers as &$row)
{
if ($this->useDiscountCache)
{
if ($this->storage['USE_SALE_DISCOUNTS'])
Catalog\Discount\DiscountManager::setProductPropertiesCache($row['ID'], $row["PROPERTIES"]);
else
\CCatalogDiscount::SetProductPropertiesCache($row['ID'], $row["PROPERTIES"]);
}
if (!empty($propertyList))
{
foreach ($propertyList as $pid)
{
if (!isset($row["PROPERTIES"][$pid]))
continue;
$prop = &$row["PROPERTIES"][$pid];
$boolArr = is_array($prop["VALUE"]);
if (
($boolArr && !empty($prop["VALUE"])) ||
(!$boolArr && (string)$prop["VALUE"] !== '')
)
{
$row["DISPLAY_PROPERTIES"][$pid] = \CIBlockFormatProperties::GetDisplayValue($row, $prop, "catalog_out");
}
unset($boolArr, $prop);
}
unset($pid);
}
}
unset($row);
}
if ($this->useDiscountCache)
{
if ($this->storage['USE_SALE_DISCOUNTS'])
{
Catalog\Discount\DiscountManager::preloadPriceData($offersId, $this->storage['PRICES_ALLOW']);
Catalog\Discount\DiscountManager::preloadProductDataToExtendOrder($offersId, $this->getUserGroups());
}
else
{
\CCatalogDiscount::SetProductSectionsCache($offersId);
\CCatalogDiscount::SetDiscountProductCache($offersId, array('IBLOCK_ID' => $catalog['IBLOCK_ID'], 'GET_BY_ID' => 'Y'));
}
}
}
unset($offersId);
}
return $offers;
}
protected function getOffersFilter($iblockId)
{
$offersFilter = array(
'IBLOCK_ID' => $iblockId,
'ACTIVE' => 'Y',
'ACTIVE_DATE' => 'Y',
'CHECK_PERMISSIONS' => 'N'
);
if ($this->arParams['HIDE_NOT_AVAILABLE_OFFERS'] === 'Y')
{
$offersFilter['AVAILABLE'] = 'Y';
}
elseif ($this->arParams['HIDE_NOT_AVAILABLE_OFFERS'] === 'L')
{
$offersFilter['CUSTOM_FILTER'] = array(
'LOGIC' => 'OR',
'AVAILABLE' => 'Y',
'SUBSCRIBE' => 'Y'
);
}
if (!$this->arParams['USE_PRICE_COUNT'])
{
$offersFilter['SHOW_PRICE_COUNT'] = $this->arParams['SHOW_PRICE_COUNT'];
}
return $offersFilter;
}
/**
* Return offers sort fields to execute.
*
* @return array
*/
protected function getOffersSort()
{
$offersOrder = array(
mb_strtoupper($this->arParams['OFFERS_SORT_FIELD']) => $this->arParams['OFFERS_SORT_ORDER'],
mb_strtoupper($this->arParams['OFFERS_SORT_FIELD2']) => $this->arParams['OFFERS_SORT_ORDER2']
);
if (!isset($offersOrder['ID']))
$offersOrder['ID'] = 'DESC';
return $offersOrder;
}
protected function modifyOffers($offers)
{
//$urls = $this->storage['URLS'];
foreach ($offers as &$offer)
{
$elementId = $offer['LINK_ELEMENT_ID'];
if (!isset($this->elementLinks[$elementId]))
continue;
$offer['CAN_BUY'] = $this->elementLinks[$elementId]['ACTIVE'] === 'Y' && $offer['CAN_BUY'];
$this->elementLinks[$elementId]['OFFERS'][] = $offer;
unset($elementId, $offer);
}
}
abstract protected function chooseOffer($offers, $iblockId);
protected function initResultCache()
{
if (
$this->arParams['CONVERT_CURRENCY'] === 'Y'
&& !empty($this->storage['CURRENCY_LIST'])
&& defined('BX_COMP_MANAGED_CACHE')
)
{
$this->storage['CURRENCY_LIST'][$this->storage['CONVERT_CURRENCY']['CURRENCY_ID']] = $this->storage['CONVERT_CURRENCY']['CURRENCY_ID'];
$taggedCache = Main\Application::getInstance()->getTaggedCache();
foreach ($this->storage['CURRENCY_LIST'] as $oneCurrency)
{
$taggedCache->registerTag('currency_id_'.$oneCurrency);
}
unset($oneCurrency);
unset($taggedCache);
}
unset($this->storage['CURRENCY_LIST']);
$this->setResultCacheKeys($this->getCacheKeys());
}
protected function getCacheKeys()
{
return array();
}
/**
* All iblock/section/element/offer initializations starts here.
* If have no errors - result showed in $arResult.
*/
protected function processResultData()
{
$this->iblockProducts = $this->getProductsSeparatedByIblock();
$this->checkIblock();
if ($this->hasErrors())
return;
$this->initCurrencyConvert();
$this->initCatalogInfo();
$this->initIblockPropertyFeatures();
$this->initPrices();
$this->initVats();
$this->initUrlTemplates();
$this->initElementList();
if (!$this->hasErrors())
{
$this->sortElementList();
$this->makeElementLinks();
$this->prepareData();
$this->filterPureOffers();
$this->makeOutputResult();
}
}
/**
* Check for correct iblocks.
*/
protected function checkIblock()
{
if (!empty($this->iblockProducts))
{
$iblocks = array();
$iblockIterator = Iblock\IblockSiteTable::getList(array(
'select' => array('IBLOCK_ID'),
'filter' => array(
'=IBLOCK_ID' => array_keys($this->iblockProducts),
'=SITE_ID' => $this->getSiteId(),
'=IBLOCK.ACTIVE' => 'Y'
)
));
while ($iblock = $iblockIterator->fetch())
{
$iblocks[$iblock['IBLOCK_ID']] = true;
}
foreach ($this->iblockProducts as $iblock => $products)
{
if (!isset($iblocks[$iblock]))
{
unset($this->iblockProducts[$iblock]);
}
}
if (empty($this->iblockProducts))
{
$this->abortResultCache();
$this->errorCollection->setError(new Error(Loc::getMessage('INVALID_IBLOCK'), self::ERROR_TEXT));
}
}
}
protected function prepareData()
{
$this->clearItems();
$this->initCatalogDiscountCache();
$this->processProducts();
$this->processOffers();
$this->makeOutputResult();
$this->clearItems();
}
protected function filterPureOffers()
{
if (!empty($this->productIds) && is_array($this->productIds))
{
foreach ($this->productIds as $productId)
{
// check if it's element
if ($this->productIdMap[$productId] == $productId)
{
continue;
}
if (isset($this->elementLinks[$this->productIdMap[$productId]]) && !empty($this->elementLinks[$this->productIdMap[$productId]]['OFFERS']))
{
// clear all unwanted offers
foreach ($this->elementLinks[$this->productIdMap[$productId]]['OFFERS'] as $key => $offer)
{
if ($offer['ID'] != $productId)
{
unset($this->elementLinks[$this->productIdMap[$productId]]['OFFERS'][$key]);
}
}
}
}
}
}
/**
* Set component data from storage to $arResult.
*/
protected function makeOutputResult()
{
$this->arResult = array_merge($this->arResult, $this->storage['URLS']);
$this->arResult['CONVERT_CURRENCY'] = $this->storage['CONVERT_CURRENCY'];
$this->arResult['CATALOGS'] = $this->storage['CATALOGS'];
$this->arResult['MODULES'] = $this->storage['MODULES'];
$this->arResult['PRICES_ALLOW'] = $this->storage['PRICES_ALLOW'];
if ($this->isEnableCompatible())
{
if ($this->arParams['IBLOCK_ID'] > 0)
{
$this->arResult['CATALOG'] = false;
if (
!empty($this->storage['CATALOGS'][$this->arParams['IBLOCK_ID']])
&& is_array($this->storage['CATALOGS'][$this->arParams['IBLOCK_ID']])
)
{
$this->arResult['CATALOG'] = $this->storage['CATALOGS'][$this->arParams['IBLOCK_ID']];
}
}
}
}
/**
* Process of buy/add-to-basket/etc actions.
*/
protected function processLinkAction()
{
global $APPLICATION;
if ($this->request->get($this->arParams['ACTION_VARIABLE'].self::ACTION_BUY) !== null)
{
$action = self::ACTION_BUY;
}
elseif ($this->request->get($this->arParams['ACTION_VARIABLE'].self::ACTION_ADD_TO_BASKET))
{
$action = self::ACTION_ADD_TO_BASKET;
}
else
{
$action = mb_strtoupper($this->request->get($this->arParams['ACTION_VARIABLE']));
}
$productId = (int)$this->request->get($this->arParams['PRODUCT_ID_VARIABLE']);
if (
($action == self::ACTION_ADD_TO_BASKET || $action == self::ACTION_BUY || $action == self::ACTION_SUBSCRIBE)
&& Loader::includeModule('sale')
&& Loader::includeModule('catalog')
)
{
$addByAjax = $this->request->get('ajax_basket') === 'Y';
if ($addByAjax)
{
$this->request->set(Main\Text\Encoding::convertEncoding($this->request->toArray(), 'UTF-8', SITE_CHARSET));
}
list($successfulAdd, $errorMsg) = $this->addProductToBasket($productId, $action);
if ($addByAjax)
{
if ($successfulAdd)
{
$addResult = array(
'STATUS' => 'OK',
'MESSAGE' => Loc::getMessage('CATALOG_SUCCESSFUL_ADD_TO_BASKET')
);
}
else
{
$addResult = array(
'STATUS' => 'ERROR',
'MESSAGE' => $errorMsg
);
}
$APPLICATION->RestartBuffer();
header('Content-Type: application/json');
echo Main\Web\Json::encode($addResult);
die();
}
else
{
if ($successfulAdd)
{
$pathRedirect = $action == self::ACTION_BUY
? $this->arParams['BASKET_URL']
: $APPLICATION->GetCurPageParam('', array(
$this->arParams['PRODUCT_ID_VARIABLE'],
$this->arParams['ACTION_VARIABLE'],
$this->arParams['PRODUCT_QUANTITY_VARIABLE'],
$this->arParams['PRODUCT_PROPS_VARIABLE']
));
LocalRedirect($pathRedirect);
}
else
{
$this->errorCollection->setError(new Error($errorMsg, self::ERROR_TEXT));
}
}
}
}
protected function checkProductSection($productId, $sectionId = 0, $sectionCode = '')
{
$successfulAdd = true;
$errorMsg = '';
if (!empty($productId) && ($sectionId > 0 || !empty($sectionCode)))
{
$productsMap = $this->getProductIdMap([$productId]);
if (!empty($productsMap[$productId]))
{
$sectionId = (int)$sectionId;
$sectionCode = (string)$sectionCode;
$filter = ['ID' => $productsMap[$productId]];
$element = false;
if ($sectionId > 0)
{
$filter['SECTION_ID'] = $sectionId;
$filter['INCLUDE_SUBSECTIONS'] = 'Y';
$elementIterator = \CIBlockElement::GetList(array(), $filter, false, false, array('ID'));
$element = $elementIterator->Fetch();
unset($elementIterator);
}
elseif ($sectionCode != '')
{
$iblockId = (int)\CIBlockElement::GetIBlockByID($productsMap[$productId]);
if ($iblockId > 0)
{
$sectionIterator = \CIBlockSection::GetList(
[],
['IBLOCK_ID' => $iblockId, '=CODE' => $sectionCode],
false,
['ID', 'IBLOCK_ID']
);
$section = $sectionIterator->Fetch();
unset($sectionIterator);
if (!empty($section))
{
$filter['SECTION_ID'] = (int)$section['ID'];
$filter['INCLUDE_SUBSECTIONS'] = 'Y';
$elementIterator = \CIBlockElement::GetList(array(), $filter, false, false, array('ID'));
$element = $elementIterator->Fetch();
unset($elementIterator);
}
unset($section);
}
unset($iblockId);
}
if (empty($element))
{
$successfulAdd = false;
$errorMsg = Loc::getMessage('CATALOG_PRODUCT_NOT_FOUND');
}
}
}
return [$successfulAdd, $errorMsg];
}
protected function addProductToBasket($productId, $action)
{
/** @global \CMain $APPLICATION */
global $APPLICATION;
$successfulAdd = true;
$errorMsg = '';
$quantity = 0;
$productProperties = array();
$productId = (int)$productId;
if ($productId <= 0)
{
$errorMsg = Loc::getMessage('CATALOG_PRODUCT_ID_IS_ABSENT');
$successfulAdd = false;
}
if ($successfulAdd)
{
$product = $this->getProductInfo($productId);
if (empty($product))
{
$errorMsg = Loc::getMessage('CATALOG_PRODUCT_NOT_FOUND');
$successfulAdd = false;
}
}
if ($successfulAdd)
{
if ($this->arParams['CHECK_LANDING_PRODUCT_SECTION'])
{
list($successfulAdd, $errorMsg) = $this->checkProductSection(
$productId, $this->arParams['SECTION_ID'], $this->arParams['SECTION_CODE']
);
}
}
if ($successfulAdd)
{
if ($this->arParams['ADD_PROPERTIES_TO_BASKET'] === 'Y')
{
$this->initIblockPropertyFeatures();
$iblockParams = $this->storage['IBLOCK_PARAMS'][$product['PRODUCT_IBLOCK_ID']];
if ($product['TYPE'] == Catalog\ProductTable::TYPE_OFFER)
{
$skuAddProps = $this->request->get('basket_props') ?: '';
if (!empty($iblockParams['OFFERS_CART_PROPERTIES']) || !empty($skuAddProps))
{
$productProperties = \CIBlockPriceTools::GetOfferProperties(
$productId,
$product['PRODUCT_IBLOCK_ID'],
$iblockParams['OFFERS_CART_PROPERTIES'],
$skuAddProps
);
}
unset($skuAddProps);
}
else
{
if (!empty($iblockParams['CART_PROPERTIES']))
{
$productPropsVar = $this->request->get($this->arParams['PRODUCT_PROPS_VARIABLE']);
if (is_array($productPropsVar))
{
$productProperties = \CIBlockPriceTools::CheckProductProperties(
$product['PRODUCT_IBLOCK_ID'],
$productId,
$iblockParams['CART_PROPERTIES'],
$productPropsVar,
$this->arParams['PARTIAL_PRODUCT_PROPERTIES'] === 'Y'
);
if (!is_array($productProperties))
{
$errorMsg = Loc::getMessage('CATALOG_PARTIAL_BASKET_PROPERTIES_ERROR');
$successfulAdd = false;
}
}
else
{
$errorMsg = Loc::getMessage('CATALOG_EMPTY_BASKET_PROPERTIES_ERROR');
$successfulAdd = false;
}
unset($productPropsVar);
}
}
unset($iblockParams);
}
}
if ($successfulAdd)
{
if ($this->arParams['USE_PRODUCT_QUANTITY'])
{
$quantity = (float)$this->request->get($this->arParams['PRODUCT_QUANTITY_VARIABLE']);
}
if ($quantity <= 0)
{
$ratioIterator = \CCatalogMeasureRatio::getList(
array(),
array('PRODUCT_ID' => $productId),
false,
false,
array('PRODUCT_ID', 'RATIO')
);
if ($ratio = $ratioIterator->Fetch())
{
$intRatio = (int)$ratio['RATIO'];
$floatRatio = (float)$ratio['RATIO'];
$quantity = $floatRatio > $intRatio ? $floatRatio : $intRatio;
}
}
if ($quantity <= 0)
{
$quantity = 1;
}
}
if ($successfulAdd)
{
$rewriteFields = $this->getRewriteFields($action);
if (isset($rewriteFields['SUBSCRIBE']) && $rewriteFields['SUBSCRIBE'] == 'Y')
{
if (!SubscribeProduct($productId, $rewriteFields, $productProperties))
{
if ($ex = $APPLICATION->GetException())
{
$errorMsg = $ex->GetString();
}
else
{
$errorMsg = Loc::getMessage('CATALOG_ERROR2BASKET');
}
$successfulAdd = false;
}
}
else
{
$product = [
'PRODUCT_ID' => $productId,
'QUANTITY' => $quantity
];
if (!empty($productProperties))
$product['PROPS'] = $productProperties;
$basketResult = Catalog\Product\Basket::addProduct($product, $rewriteFields);
if (!$basketResult->isSuccess())
{
$errorMsg = implode('; ', $basketResult->getErrorMessages());
$successfulAdd = false;
}
unset($basketResult);
}
}
return array($successfulAdd, $errorMsg);
}
protected function getRewriteFields($action)
{
$rewriteFields = [];
if ($action === self::ACTION_ADD_TO_BASKET || $action === self::ACTION_BUY)
{
$rewriteFields['DELAY'] = 'N';
}
if ($action == self::ACTION_SUBSCRIBE)
{
$notify = unserialize(Main\Config\Option::get('sale', 'subscribe_prod', ''));
if (!empty($notify[$this->getSiteId()]) && $notify[$this->getSiteId()]['use'] === 'Y')
{
$rewriteFields['SUBSCRIBE'] = 'Y';
$rewriteFields['CAN_BUY'] = 'N';
}
}
return $rewriteFields;
}
/**
* This method executes when "deferredLoad" action chosen.
* Override getDeferredProductIds method to return needed product ids array.
*/
protected function deferredLoadAction()
{
$this->productIds = $this->getDeferredProductIds();
// if no products - show empty response
if (empty($this->productIds))
{
static::sendJsonAnswer();
}
$this->productIdMap = $this->getProductIdMap($this->productIds);
$this->loadData();
}
/**
* This method executes when "bigDataLoad" action is chosen.
*/
protected function bigDataLoadAction()
{
$this->initBigDataLastUsage();
$this->productIds = $this->getBigDataProductIds();
// if no products - show empty response
if (empty($this->productIds))
{
static::sendJsonAnswer();
}
$this->productIdMap = $this->getProductIdMap($this->productIds);
$this->loadData();
}
/**
* Mark last usage of BigData.
*/
protected function initBigDataLastUsage()
{
$lastUsage = Main\Config\Option::get('main', 'rcm_component_usage', 0);
if ($lastUsage == 0 || (time() - $lastUsage) > 3600)
{
Main\Config\Option::set('main', 'rcm_component_usage', time());
}
}
/**
* This method executes when "initialLoad" action is chosen.
*/
protected function initialLoadAction()
{
$this->productIds = $this->getProductIds();
$this->productIdMap = $this->getProductIdMap($this->productIds);
$this->loadData();
}
/**
* Show cached component data or load if outdated.
* If extended mode enabled - uses result_modifier.php logic in component (be careful not to duplicate it).
*/
protected function loadData()
{
if ($this->isCacheDisabled() || $this->startResultCache(false, $this->getAdditionalCacheId(), $this->getComponentCachePath()))
{
$this->processResultData();
if (!$this->hasErrors())
{
if ($this->isExtendedMode())
{
$this->initComponentTemplate();
$this->applyTemplateModifications();
}
$this->initResultCache();
$this->includeComponentTemplate();
$this->clearCatalogDiscountCache();
}
}
}
/**
* Return component cache identifier.
*
* @return mixed
*/
abstract protected function getAdditionalCacheId();
/**
* Return component cache path.
*
* @return mixed
*/
abstract protected function getComponentCachePath();
public function getTemplateEmptyPreview()
{
$emptyPreview = false;
$documentRoot = Main\Application::getDocumentRoot();
$emptyPreviewPath = $this->getTemplate()->GetFolder().'/images/no_photo.png';
$file = new Main\IO\File($documentRoot.$emptyPreviewPath);
if ($file->isExists())
{
$size = getimagesize($documentRoot.$emptyPreviewPath);
if (!empty($size))
{
$emptyPreview = array(
'ID' => 0,
'SRC' => $emptyPreviewPath,
'WIDTH' => (int)$size[0],
'HEIGHT' => (int)$size[1]
);
}
}
return $emptyPreview;
}
protected function sliceItemsForSlider(&$items)
{
$rows = array();
while (!empty($items))
{
$rows[] = array_splice($items, 0, $this->arParams['LINE_ELEMENT_COUNT']);
}
$items = $rows;
}
protected function getTemplateCurrencies()
{
$currencies = array();
if ($this->arResult['MODULES']['currency'])
{
if (isset($this->arResult['CONVERT_CURRENCY']['CURRENCY_ID']))
{
$currencyFormat = \CCurrencyLang::GetFormatDescription($this->arResult['CONVERT_CURRENCY']['CURRENCY_ID']);
$currencies = array(
array(
'CURRENCY' => $this->arResult['CONVERT_CURRENCY']['CURRENCY_ID'],
'FORMAT' => array(
'FORMAT_STRING' => $currencyFormat['FORMAT_STRING'],
'DEC_POINT' => $currencyFormat['DEC_POINT'],
'THOUSANDS_SEP' => $currencyFormat['THOUSANDS_SEP'],
'DECIMALS' => $currencyFormat['DECIMALS'],
'THOUSANDS_VARIANT' => $currencyFormat['THOUSANDS_VARIANT'],
'HIDE_ZERO' => $currencyFormat['HIDE_ZERO']
)
)
);
unset($currencyFormat);
}
else
{
$currencyIterator = Currency\CurrencyTable::getList(array(
'select' => array('CURRENCY')
));
while ($currency = $currencyIterator->fetch())
{
$currencyFormat = \CCurrencyLang::GetFormatDescription($currency['CURRENCY']);
$currencies[] = array(
'CURRENCY' => $currency['CURRENCY'],
'FORMAT' => array(
'FORMAT_STRING' => $currencyFormat['FORMAT_STRING'],
'DEC_POINT' => $currencyFormat['DEC_POINT'],
'THOUSANDS_SEP' => $currencyFormat['THOUSANDS_SEP'],
'DECIMALS' => $currencyFormat['DECIMALS'],
'THOUSANDS_VARIANT' => $currencyFormat['THOUSANDS_VARIANT'],
'HIDE_ZERO' => $currencyFormat['HIDE_ZERO']
)
);
}
unset($currencyFormat, $currency, $currencyIterator);
}
}
return $currencies;
}
/**
* Send answer for AJAX request.
*
* @param array $result
*/
public static function sendJsonAnswer(array $result = array())
{
global $APPLICATION;
if (!empty($result))
{
$result['JS'] = Main\Page\Asset::getInstance()->getJs();
}
$APPLICATION->RestartBuffer();
echo Main\Web\Json::encode($result);
\CMain::FinalActions();
die();
}
/**
* Action preparing to execute in doAction method with postfix "Action".
* E.g. action "initialLoad" calls "initialLoadAction".
*
* @return string
*/
protected function prepareAction()
{
if (
$this->request->get($this->arParams['ACTION_VARIABLE']) !== null
&& $this->request->get($this->arParams['PRODUCT_ID_VARIABLE']) !== null
)
{
$action = 'processLink';
}
elseif ($this->request->isAjaxRequest() && $this->request->get('action') === 'deferredLoad')
{
$action = $this->request->get('bigData') === 'Y' ? 'bigDataLoad' : 'deferredLoad';
}
else
{
$action = 'initialLoad';
}
return $action;
}
/**
* Action executor.
*/
protected function doAction()
{
$action = $this->getAction();
if (is_callable(array($this, $action.'Action')))
{
call_user_func(array($this, $action.'Action'));
}
}
/**
* @return bool
*/
public function executeComponent()
{
$this->checkModules();
if ($this->hasErrors())
{
return $this->processErrors();
}
$action = $this->prepareAction();
$this->setAction($action);
$this->doAction();
if ($this->hasErrors())
{
return $this->processErrors();
}
return isset($this->arResult['ID']) ? $this->arResult['ID'] : false;
}
public function applyTemplateModifications()
{
$this->prepareTemplateParams();
$this->checkTemplateTheme();
$this->editTemplateData();
return $this->arParams;
}
protected function prepareTemplateParams()
{
$params =& $this->arParams;
$defaultParams = $this->getTemplateDefaultParams();
$params = array_merge($defaultParams, $params);
$params['SHOW_OLD_PRICE'] = $params['SHOW_OLD_PRICE'] === 'Y' ? 'Y' : 'N';
$params['SHOW_CLOSE_POPUP'] = $params['SHOW_CLOSE_POPUP'] === 'Y' ? 'Y' : 'N';
$params['SHOW_DISCOUNT_PERCENT'] = $params['SHOW_DISCOUNT_PERCENT'] === 'Y' ? 'Y' : 'N';
$params['DISCOUNT_PERCENT_POSITION'] = trim($params['DISCOUNT_PERCENT_POSITION']) ?: 'bottom-right';
$params['LABEL_PROP_POSITION'] = trim($params['LABEL_PROP_POSITION']) ?: 'top-left';
$params['PRODUCT_SUBSCRIPTION'] = $params['PRODUCT_SUBSCRIPTION'] === 'N' ? 'N' : 'Y';
$params['MESS_BTN_BUY'] = trim($params['MESS_BTN_BUY']);
$params['MESS_BTN_ADD_TO_BASKET'] = trim($params['MESS_BTN_ADD_TO_BASKET']);
$params['MESS_BTN_SUBSCRIBE'] = trim($params['MESS_BTN_SUBSCRIBE']);
$params['MESS_BTN_DETAIL'] = trim($params['MESS_BTN_DETAIL']);
$params['MESS_NOT_AVAILABLE'] = trim($params['MESS_NOT_AVAILABLE']);
$params['MESS_BTN_COMPARE'] = trim($params['MESS_BTN_COMPARE']);
$params['SHOW_SLIDER'] = $params['SHOW_SLIDER'] === 'N' ? 'N' : 'Y';
$params['SLIDER_INTERVAL'] = (int)$params['SLIDER_INTERVAL'] ?: 5000;
$params['SLIDER_PROGRESS'] = $params['SLIDER_PROGRESS'] === 'Y' ? 'Y' : 'N';
$params['USE_ENHANCED_ECOMMERCE'] = $params['USE_ENHANCED_ECOMMERCE'] === 'Y' ? 'Y' : 'N';
$params['DATA_LAYER_NAME'] = trim($params['DATA_LAYER_NAME']);
$params['BRAND_PROPERTY'] = $params['BRAND_PROPERTY'] !== '-' ? trim($params['BRAND_PROPERTY']) : '';
if (!isset($params['SHOW_MAX_QUANTITY']) || !in_array($params['SHOW_MAX_QUANTITY'], array('Y', 'M', 'N')))
{
$params['SHOW_MAX_QUANTITY'] = 'N';
}
$params['RELATIVE_QUANTITY_FACTOR'] = (int)$params['RELATIVE_QUANTITY_FACTOR'] > 0 ? (int)$params['RELATIVE_QUANTITY_FACTOR'] : 5;
}
protected function getTemplateDefaultParams()
{
return array(
'TEMPLATE_THEME' => 'blue',
'SHOW_MAX_QUANTITY' => 'N',
'SHOW_OLD_PRICE' => 'N',
'SHOW_CLOSE_POPUP' => 'N',
'SHOW_DISCOUNT_PERCENT' => 'N',
'DISCOUNT_PERCENT_POSITION' => 'bottom-right',
'LABEL_PROP' => array(),
'LABEL_PROP_MOBILE' => array(),
'LABEL_PROP_POSITION' => 'top-left',
'PRODUCT_SUBSCRIPTION' => 'Y',
'MESS_BTN_BUY' => '',
'MESS_BTN_ADD_TO_BASKET' => '',
'MESS_BTN_SUBSCRIBE' => '',
'MESS_BTN_DETAIL' => '',
'MESS_NOT_AVAILABLE' => '',
'MESS_BTN_COMPARE' => '',
'SHOW_SLIDER' => 'N',
'SLIDER_INTERVAL' => 5000,
'SLIDER_PROGRESS' => 'N',
'USE_ENHANCED_ECOMMERCE' => 'N',
'DATA_LAYER_NAME' => 'dataLayer',
'BRAND_PROPERTY' => ''
);
}
protected function checkTemplateTheme()
{
$theme =& $this->arParams['TEMPLATE_THEME'];
$theme = (string)$theme;
if ($theme != '')
{
$theme = preg_replace('/[^a-zA-Z0-9_\-\(\)\!]/', '', $theme);
if ($theme === 'site')
{
$siteId = $this->getSiteId();
$templateId = Main\Config\Option::get('main', 'wizard_template_id', 'eshop_bootstrap', $siteId);
$templateId = preg_match('/^eshop_adapt/', $templateId) ? 'eshop_adapt' : $templateId;
$theme = Main\Config\Option::get('main', 'wizard_'.$templateId.'_theme_id', 'blue', $siteId);
}
if ($theme != '')
{
$documentRoot = Main\Application::getDocumentRoot();
$templateFolder = $this->getTemplate()->GetFolder();
$themesFolder = new Main\IO\Directory($documentRoot.$templateFolder.'/themes/');
if ($themesFolder->isExists())
{
$file = new Main\IO\File($documentRoot.$templateFolder.'/themes/'.$theme.'/style.css');
if (!$file->isExists())
{
$theme = '';
}
}
}
}
if ($theme == '')
{
$theme = 'blue';
}
}
protected function editTemplateData()
{
//
}
public static function checkEnlargedData(&$item, $propertyCode)
{
if (!empty($item) && is_array($item))
{
$item['ENLARGED'] = 'N';
$propertyCode = (string)$propertyCode;
if ($propertyCode !== '' && isset($item['PROPERTIES'][$propertyCode]))
{
$prop = $item['PROPERTIES'][$propertyCode];
if (!empty($prop['VALUE']))
{
$item['ENLARGED'] = 'Y';
}
}
}
}
protected function editTemplateProductSlider(&$item, $iblock, $limit = 0, $addDetailToSlider = true, $default = array())
{
$propCode = $this->storage['IBLOCK_PARAMS'][$iblock]['ADD_PICT_PROP'];
$slider = \CIBlockPriceTools::getSliderForItem($item, $propCode, $addDetailToSlider);
if (empty($slider))
{
$slider = $default;
}
if ($limit > 0)
{
$slider = array_slice($slider, 0, $limit);
}
$item['SHOW_SLIDER'] = true;
$item['MORE_PHOTO'] = $slider;
$item['MORE_PHOTO_COUNT'] = count($slider);
}
protected function editTemplateOfferSlider(&$item, $iblock, $limit = 0, $addDetailToSlider = true, $default = array())
{
$propCode = $this->storage['IBLOCK_PARAMS'][$iblock]['OFFERS_ADD_PICT_PROP'];
$slider = \CIBlockPriceTools::getSliderForItem($item, $propCode, $addDetailToSlider);
if (empty($slider))
{
$slider = $default;
}
if ($limit > 0)
{
$slider = array_slice($slider, 0, $limit);
}
$item['MORE_PHOTO'] = $slider;
$item['MORE_PHOTO_COUNT'] = count($slider);
}
protected function editTemplateCatalogInfo(&$item)
{
if ($this->arResult['MODULES']['catalog'])
{
$item['CATALOG'] = true;
if ($this->isEnableCompatible())
$item['CATALOG_TYPE'] = $item['PRODUCT']['TYPE'];
}
else
{
if ($this->isEnableCompatible())
$item['CATALOG_TYPE'] = 0;
$item['OFFERS'] = array();
}
}
protected function getTemplatePropCell($code, $offer, &$matrixFields, $skuPropList)
{
$cell = array(
'VALUE' => 0,
'SORT' => PHP_INT_MAX,
'NA' => true
);
if (isset($offer['DISPLAY_PROPERTIES'][$code]))
{
$matrixFields[$code] = true;
$cell['NA'] = false;
if ($skuPropList[$code]['USER_TYPE'] === 'directory')
{
$intValue = $skuPropList[$code]['XML_MAP'][$offer['DISPLAY_PROPERTIES'][$code]['VALUE']];
$cell['VALUE'] = $intValue;
}
elseif ($skuPropList[$code]['PROPERTY_TYPE'] === 'L')
{
$cell['VALUE'] = (int)$offer['DISPLAY_PROPERTIES'][$code]['VALUE_ENUM_ID'];
}
elseif ($skuPropList[$code]['PROPERTY_TYPE'] === 'E')
{
$cell['VALUE'] = (int)$offer['DISPLAY_PROPERTIES'][$code]['VALUE'];
}
$cell['SORT'] = $skuPropList[$code]['VALUES'][$cell['VALUE']]['SORT'];
}
return $cell;
}
protected function getOffersIblockId($iblockId)
{
if (!$this->useCatalog)
return null;
if (!isset($this->storage['CATALOGS'][$iblockId]))
return null;
if (
$this->storage['CATALOGS'][$iblockId]['CATALOG_TYPE'] != \CCatalogSku::TYPE_PRODUCT
&& $this->storage['CATALOGS'][$iblockId]['CATALOG_TYPE'] != \CCatalogSku::TYPE_FULL
)
return null;
return $this->storage['CATALOGS'][$iblockId]['IBLOCK_ID'];
}
/**
* @param int $iblockId
* @return void
*/
protected function loadDisplayPropertyCodes($iblockId)
{
}
protected function loadBasketPropertyCodes($iblockId)
{
if (!$this->useCatalog)
return;
if (!isset($this->storage['CATALOGS'][$iblockId]))
return;
switch ($this->storage['CATALOGS'][$iblockId]['CATALOG_TYPE'])
{
case \CCatalogSku::TYPE_CATALOG:
$list = Catalog\Product\PropertyCatalogFeature::getBasketPropertyCodes(
$iblockId,
['CODE' => 'Y']
);
if ($list === null)
$list = [];
$this->storage['IBLOCK_PARAMS'][$iblockId]['CART_PROPERTIES'] = $list;
unset($list);
$this->storage['IBLOCK_PARAMS'][$iblockId]['OFFERS_CART_PROPERTIES'] = [];
break;
case \CCatalogSku::TYPE_PRODUCT:
$this->storage['IBLOCK_PARAMS'][$iblockId]['CART_PROPERTIES'] = [];
$list = Catalog\Product\PropertyCatalogFeature::getBasketPropertyCodes(
$this->getOffersIblockId($iblockId),
['CODE' => 'Y']
);
if ($list === null)
$list = [];
$this->storage['IBLOCK_PARAMS'][$iblockId]['OFFERS_CART_PROPERTIES'] = $list;
unset($list);
break;
case \CCatalogSku::TYPE_FULL:
$list = Catalog\Product\PropertyCatalogFeature::getBasketPropertyCodes(
$iblockId,
['CODE' => 'Y']
);
if ($list === null)
$list = [];
$this->storage['IBLOCK_PARAMS'][$iblockId]['CART_PROPERTIES'] = $list;
$list = Catalog\Product\PropertyCatalogFeature::getBasketPropertyCodes(
$this->getOffersIblockId($iblockId),
['CODE' => 'Y']
);
if ($list === null)
$list = [];
$this->storage['IBLOCK_PARAMS'][$iblockId]['OFFERS_CART_PROPERTIES'] = $list;
unset($list);
break;
case \CCatalogSku::TYPE_OFFERS:
$this->storage['IBLOCK_PARAMS'][$iblockId]['CART_PROPERTIES'] = [];
$this->storage['IBLOCK_PARAMS'][$iblockId]['OFFERS_CART_PROPERTIES'] = [];
break;
default:
break;
}
}
protected function loadOfferTreePropertyCodes($iblockId)
{
if (!$this->useCatalog)
return;
if (!isset($this->storage['CATALOGS'][$iblockId]))
return;
switch ($this->storage['CATALOGS'][$iblockId]['CATALOG_TYPE'])
{
case \CCatalogSku::TYPE_CATALOG:
case \CCatalogSku::TYPE_OFFERS:
$this->storage['IBLOCK_PARAMS'][$iblockId]['OFFERS_TREE_PROPS'] = [];
break;
case \CCatalogSku::TYPE_PRODUCT:
case \CCatalogSku::TYPE_FULL:
$list = Catalog\Product\PropertyCatalogFeature::getOfferTreePropertyCodes(
$this->storage['CATALOGS'][$iblockId]['IBLOCK_ID'],
['CODE' => 'Y']
);
if ($list === null)
$list = [];
$this->storage['IBLOCK_PARAMS'][$iblockId]['OFFERS_TREE_PROPS'] = $list;
unset($list);
break;
default:
break;
}
}
/* product tools */
/**
* Return true, if enable quantity trace and disable make out-of-stock items available for purchase.
*
* @param array $product Product data.
* @return bool
*/
protected function isNeedCheckQuantity(array $product)
{
return (
$product['QUANTITY_TRACE'] === Catalog\ProductTable::STATUS_YES
&& $product['CAN_BUY_ZERO'] === Catalog\ProductTable::STATUS_NO
);
}
/* product tools end */
/* user tools */
/**
* Return user groups. Now worked only with current user.
*
* @return array
*/
protected function getUserGroups()
{
/** @global \CUser $USER */
global $USER;
$result = array(2);
if (isset($USER) && $USER instanceof \CUser)
{
$result = $USER->GetUserGroupArray();
Main\Type\Collection::normalizeArrayValuesByInt($result, true);
}
return $result;
}
/**
* Return user groups string for cache id.
*
* @return string
*/
protected function getUserGroupsCacheId()
{
return implode(',', $this->getUserGroups());
}
/* user tools end */
/* compatibility tools */
/**
* Filling deprecated fields of items for compatibility with old templates.
* Strict use only for catalog.element, .section, .top, etc in compatible mode.
*
* @param array $items Product list.
* @return void
*/
protected function initCompatibleFields(array $items)
{
if (empty($items))
return;
$initFields = array(
'PRICES' => array(),
'PRICE_MATRIX' => false,
'MIN_PRICE' => false
);
if (!$this->arParams['USE_PRICE_COUNT'] && !empty($this->storage['PRICES']))
{
foreach ($this->storage['PRICES'] as $value)
{
if (!$value['CAN_VIEW'] && !$value['CAN_BUY'])
continue;
$priceType = $value['ID'];
$initFields['CATALOG_GROUP_ID_'.$priceType] = $priceType;
$initFields['~CATALOG_GROUP_ID_'.$priceType] = $priceType;
$initFields['CATALOG_GROUP_NAME_'.$priceType] = $value['TITLE'];
$initFields['~CATALOG_GROUP_NAME_'.$priceType] = $value['~TITLE'];
$initFields['CATALOG_CAN_ACCESS_'.$priceType] = ($value['CAN_VIEW'] ? 'Y' : 'N');
$initFields['~CATALOG_CAN_ACCESS_'.$priceType] = ($value['CAN_VIEW'] ? 'Y' : 'N');
$initFields['CATALOG_CAN_BUY_'.$priceType] = ($value['CAN_BUY'] ? 'Y' : 'N');
$initFields['~CATALOG_CAN_BUY_'.$priceType] = ($value['CAN_BUY'] ? 'Y' : 'N');
$initFields['CATALOG_PRICE_ID_'.$priceType] = null;
$initFields['~CATALOG_PRICE_ID_'.$priceType] = null;
$initFields['CATALOG_PRICE_'.$priceType] = null;
$initFields['~CATALOG_PRICE_'.$priceType] = null;
$initFields['CATALOG_CURRENCY_'.$priceType] = null;
$initFields['~CATALOG_CURRENCY_'.$priceType] = null;
$initFields['CATALOG_QUANTITY_FROM_'.$priceType] = null;
$initFields['~CATALOG_QUANTITY_FROM_'.$priceType] = null;
$initFields['CATALOG_QUANTITY_TO_'.$priceType] = null;
$initFields['~CATALOG_QUANTITY_TO_'.$priceType] = null;
$initFields['CATALOG_EXTRA_ID_'.$priceType] = null;
$initFields['~CATALOG_EXTRA_ID_'.$priceType] = null;
unset($priceType);
}
unset($value);
}
foreach (array_keys($items) as $index)
$this->oldData[$items[$index]['ID']] = $initFields;
unset($index, $initFields);
}
/**
* Fill deprecated raw price data from database.
* Strict use only for catalog.element, .section, .top, etc in compatible mode.
*
* @param int $id Item id.
* @param array $prices Price rows from database.
* @return void
*/
protected function fillCompatibleRawPriceFields($id, array $prices)
{
if (!isset($this->oldData[$id]) || empty($prices) || $this->arParams['USE_PRICE_COUNT'])
return;
foreach ($prices as $rawPrice)
{
$priceType = $rawPrice['CATALOG_GROUP_ID'];
$this->oldData[$id]['CATALOG_PRICE_ID_'.$priceType] = $rawPrice['ID'];
$this->oldData[$id]['~CATALOG_PRICE_ID_'.$priceType] = $rawPrice['ID'];
$this->oldData[$id]['CATALOG_PRICE_'.$priceType] = $rawPrice['PRICE'];
$this->oldData[$id]['~CATALOG_PRICE_'.$priceType] = $rawPrice['PRICE'];
$this->oldData[$id]['CATALOG_CURRENCY_'.$priceType] = $rawPrice['CURRENCY'];
$this->oldData[$id]['~CATALOG_CURRENCY_'.$priceType] = $rawPrice['CURRENCY'];
$this->oldData[$id]['CATALOG_QUANTITY_FROM_'.$priceType] = $rawPrice['QUANTITY_FROM'];
$this->oldData[$id]['~CATALOG_QUANTITY_FROM_'.$priceType] = $rawPrice['QUANTITY_FROM'];
$this->oldData[$id]['CATALOG_QUANTITY_TO_'.$priceType] = $rawPrice['QUANTITY_TO'];
$this->oldData[$id]['~CATALOG_QUANTITY_TO_'.$priceType] = $rawPrice['QUANTITY_TO'];
$this->oldData[$id]['CATALOG_EXTRA_ID_'.$priceType] = $rawPrice['EXTRA_ID'];
$this->oldData[$id]['~CATALOG_EXTRA_ID_'.$priceType] = $rawPrice['EXTRA_ID'];
unset($priceType);
}
unset($rawPrice);
}
/**
* Return deprecated field value for item.
* Strict use only for catalog.element, .section, .top, etc in compatible mode.
*
* @param int $id Item id.
* @param string $field Field name.
* @return null|mixed
*/
protected function getCompatibleFieldValue($id, $field)
{
if (!isset($this->oldData[$id]))
return null;
return (isset($this->oldData[$id][$field]) ? $this->oldData[$id][$field] : null);
}
/**
* Check quantity range for emulate CATALOG_SHOP_QUANTITY_* filter.
* Strict use only for catalog.element, .section, .top, etc in compatible mode.
*
* @param array $row Price row from database.
* @return bool
*/
protected function checkQuantityRange(array $row)
{
return (
($row['QUANTITY_FROM'] === null || $row['QUANTITY_FROM'] <= $this->arParams['SHOW_PRICE_COUNT'])
&& ($row['QUANTITY_TO'] === null || $row['QUANTITY_TO'] >= $this->arParams['SHOW_PRICE_COUNT'])
);
}
/**
* Returns old price result format for product with price ranges. Do not use this method.
*
* @return array
*/
protected function getEmptyPriceMatrix(): array
{
return array(
'ROWS' => array(),
'COLS' => array(),
'MATRIX' => array(),
'CAN_BUY' => array(),
'AVAILABLE' => 'N',
'CURRENCY_LIST' => array()
);
}
/**
* Resort old price format for compatibility. Do not use this method.
* @internl
*
* @param int $id Item id.
* @return void
*/
private function resortOldPrices($id)
{
if (empty($this->oldData[$id]['PRICES']) || count($this->oldData[$id]['PRICES']) < 2)
return;
foreach (array_keys($this->oldData[$id]['PRICES']) as $priceCode)
$this->oldData[$id]['PRICES'][$priceCode]['_SORT'] = $this->storage['PRICES'][$priceCode]['SORT'];
unset($priceCode);
Main\Type\Collection::sortByColumn(
$this->oldData[$id]['PRICES'],
array('_SORT' => SORT_ASC, 'PRICE_ID' => SORT_ASC),
'', null, true
);
foreach (array_keys($this->oldData[$id]['PRICES']) as $priceCode)
unset($this->oldData[$id]['PRICES'][$priceCode]['_SORT']);
unset($priceCode);
}
/**
* Returns old product keys.
*
* @return array
*/
protected function getCompatibleProductFields()
{
return [
'TYPE' => 'CATALOG_TYPE',
'AVAILABLE' => 'CATALOG_AVAILABLE',
'BUNDLE' => 'CATALOG_BUNDLE',
'QUANTITY' => 'CATALOG_QUANTITY',
'QUANTITY_TRACE' => 'CATALOG_QUANTITY_TRACE',
'CAN_BUY_ZERO' => 'CATALOG_CAN_BUY_ZERO',
'MEASURE' => 'CATALOG_MEASURE',
'SUBSCRIBE' => 'CATALOG_SUBSCRIBE',
'VAT_ID' => 'CATALOG_VAT_ID',
'VAT_INCLUDED' => 'CATALOG_VAT_INCLUDED',
'WEIGHT' => 'CATALOG_WEIGHT',
'WIDTH' => 'CATALOG_WIDTH',
'LENGTH' => 'CATALOG_LENGTH',
'HEIGHT' => 'CATALOG_HEIGHT',
'PAYMENT_TYPE' => 'CATALOG_PRICE_TYPE',
'RECUR_SCHEME_LENGTH' => 'CATALOG_RECUR_SCHEME_LENGTH',
'RECUR_SCHEME_TYPE' => 'CATALOG_RECUR_SCHEME_TYPE',
'QUANTITY_TRACE_RAW' => 'CATALOG_QUANTITY_TRACE_ORIG',
'CAN_BUY_ZERO_RAW' => 'CATALOG_CAN_BUY_ZERO_ORIG',
'SUBSCRIBE_RAW' => 'CATALOG_SUBSCRIBE_ORIG',
'PURCHASING_PRICE' => 'CATALOG_PURCHASING_PRICE',
'PURCHASING_CURRENCY' => 'CATALOG_PURCHASING_CURRENCY',
'BARCODE_MULTI' => 'CATALOG_BARCODE_MULTI',
'TRIAL_PRICE_ID' => 'CATALOG_TRIAL_PRICE_ID',
'WITHOUT_ORDER' => 'CATALOG_WITHOUT_ORDER',
'~TYPE' => '~CATALOG_TYPE',
'~AVAILABLE' => '~CATALOG_AVAILABLE',
'~BUNDLE' => '~CATALOG_BUNDLE',
'~QUANTITY' => '~CATALOG_QUANTITY',
'~QUANTITY_TRACE' => '~CATALOG_QUANTITY_TRACE',
'~CAN_BUY_ZERO' => '~CATALOG_CAN_BUY_ZERO',
'~MEASURE' => '~CATALOG_MEASURE',
'~SUBSCRIBE' => '~CATALOG_SUBSCRIBE',
'~VAT_ID' => '~CATALOG_VAT_ID',
'~VAT_INCLUDED' => '~CATALOG_VAT_INCLUDED',
'~WEIGHT' => '~CATALOG_WEIGHT',
'~WIDTH' => '~CATALOG_WIDTH',
'~LENGTH' => '~CATALOG_LENGTH',
'~HEIGHT' => '~CATALOG_HEIGHT',
'~PAYMENT_TYPE' => '~CATALOG_PRICE_TYPE',
'~RECUR_SCHEME_LENGTH' => '~CATALOG_RECUR_SCHEME_LENGTH',
'~RECUR_SCHEME_TYPE' => '~CATALOG_RECUR_SCHEME_TYPE',
'~QUANTITY_TRACE_RAW' => '~CATALOG_QUANTITY_TRACE_ORIG',
'~CAN_BUY_ZERO_RAW' => '~CATALOG_CAN_BUY_ZERO_ORIG',
'~SUBSCRIBE_RAW' => '~CATALOG_SUBSCRIBE_ORIG',
'~PURCHASING_PRICE' => '~CATALOG_PURCHASING_PRICE',
'~PURCHASING_CURRENCY' => '~CATALOG_PURCHASING_CURRENCY',
'~BARCODE_MULTI' => '~CATALOG_BARCODE_MULTI',
'~TRIAL_PRICE_ID' => '~CATALOG_TRIAL_PRICE_ID',
'~WITHOUT_ORDER' => '~CATALOG_WITHOUT_ORDER'
];
}
/* compatibility tools end */
}