Your IP : 18.226.172.234


Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/iblock/lib/component/
Upload File :
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/iblock/lib/component/elementlist.php

<?php

namespace Bitrix\Iblock\Component;

use Bitrix\Iblock;
use Bitrix\Catalog;
use Bitrix\Main;
use Bitrix\Main\Loader;
use Bitrix\Main\Text;
use Bitrix\Main\Web\Json;
use Bitrix\Main\Type\Collection;
use Bitrix\Currency;

/**
 * @global \CUser $USER
 * @global \CMain $APPLICATION
 */

abstract class ElementList extends Base
{
	private $multiIblockMode = false;
	private $paginationMode = false;
	protected $navigation = false;
	protected $pagerParameters = array();

	/**
	 * Multi iblock mode setter.
	 * Enable it if you use data from different iblocks.
	 *
	 * @param $state
	 * @return $this
	 */
	protected function setMultiIblockMode($state)
	{
		$this->multiIblockMode = (bool)$state;

		return $this;
	}

	/**
	 * Return if multi iblock mode enabled.
	 *
	 * @return bool
	 */
	public function isMultiIblockMode()
	{
		return (bool)$this->multiIblockMode;
	}

	/**
	 * Pagination mode setter.
	 * Enable it if you use page navigation.
	 *
	 * @param $state
	 * @return $this
	 */
	protected function setPaginationMode($state)
	{
		$this->paginationMode = (bool)$state;

		return $this;
	}

	/**
	 * Return if pagination mode enabled.
	 *
	 * @return bool
	 */
	public function isPaginationMode()
	{
		return (bool)$this->paginationMode;
	}

	public function onPrepareComponentParams($params)
	{
		if (!is_array($params))
		{
			$params = [];
		}
		$params = parent::onPrepareComponentParams($params);
		$this->makeMagicWithPageNavigation();

		// compatibility with 'ELEMENT_COUNT' components
		if (isset($params['ELEMENT_COUNT']))
		{
			$params['PAGE_ELEMENT_COUNT'] = $params['ELEMENT_COUNT'];
		}

		// PREDICT_ELEMENT_COUNT - hidden parameter to get elements count from "PRODUCT_ROW_VARIANTS" instead of "PAGE_ELEMENT_COUNT"
		if (isset($params['PREDICT_ELEMENT_COUNT']) && $params['PREDICT_ELEMENT_COUNT'] === 'Y' && !empty($params['PRODUCT_ROW_VARIANTS']))
		{
			$isBigData = $this->request->get('bigData') === 'Y';
			$params['PRODUCT_ROW_VARIANTS'] = static::parseJsonParameter($params['PRODUCT_ROW_VARIANTS']);
			$params['PAGE_ELEMENT_COUNT'] = static::predictElementCountByVariants($params['PRODUCT_ROW_VARIANTS'], $isBigData);
		}

		$params['PAGE_ELEMENT_COUNT'] = (int)($params['PAGE_ELEMENT_COUNT'] ?? 0);
		$params['ELEMENT_COUNT'] = (int)($params['ELEMENT_COUNT'] ?? 0);
		$params['LINE_ELEMENT_COUNT'] = (int)($params['LINE_ELEMENT_COUNT'] ?? 3);

		$params['INCLUDE_SUBSECTIONS'] ??= '';
		if (!in_array(
			$params['INCLUDE_SUBSECTIONS'],
			[
				'Y',
				'A',
				'N',
			]
		))
		{
			$params['INCLUDE_SUBSECTIONS'] = 'Y';
		}

		$params['HIDE_NOT_AVAILABLE'] ??= '';
		if ($params['HIDE_NOT_AVAILABLE'] !== 'Y' && $params['HIDE_NOT_AVAILABLE'] !== 'L')
		{
			$params['HIDE_NOT_AVAILABLE'] = 'N';
		}

		$params['HIDE_NOT_AVAILABLE_OFFERS'] ??= '';
		if ($params['HIDE_NOT_AVAILABLE_OFFERS'] !== 'Y' && $params['HIDE_NOT_AVAILABLE_OFFERS'] !== 'L')
		{
			$params['HIDE_NOT_AVAILABLE_OFFERS'] = 'N';
		}

		$params['FILTER_NAME'] = trim((string)($params['FILTER_NAME'] ?? ''));
		// ajax request doesn't have access to page $GLOBALS
		if (isset($params['GLOBAL_FILTER']))
		{
			$this->globalFilter = $params['GLOBAL_FILTER'];
		}
		else
		{
			if (
				$params['FILTER_NAME'] !== ''
				&& preg_match(self::PARAM_TITLE_MASK, $params['FILTER_NAME'])
				&& isset($GLOBALS[$params['FILTER_NAME']])
				&& is_array($GLOBALS[$params['FILTER_NAME']])
			)
			{
				$this->globalFilter = $GLOBALS[$params['FILTER_NAME']];
			}

			if (isset($this->globalFilter['FACET_OPTIONS']) && count($this->globalFilter) == 1)
			{
				unset($this->globalFilter['FACET_OPTIONS']);
			}

			// save global filter for ajax request params
			$this->arResult['ORIGINAL_PARAMETERS']['GLOBAL_FILTER'] = $this->globalFilter;
		}

		$productMappingFilter = [];
		if (
			Loader::includeModule('catalog')
			&& Catalog\Product\SystemField\ProductMapping::isAllowed()
		)
		{
			$productMappingFilter = Catalog\Product\SystemField\ProductMapping::getExtendedFilterByArea(
				[],
				Catalog\Product\SystemField\ProductMapping::MAP_LANDING
			);
		}
		$params['CACHE_FILTER'] = isset($params['CACHE_FILTER']) && $params['CACHE_FILTER'] === 'Y';
		if (
			!$params['CACHE_FILTER']
			&& !empty($this->globalFilter)
			&& array_diff_assoc($this->globalFilter, $productMappingFilter)
		)
		{
			$params['CACHE_TIME'] = 0;
		}

		$params = $this->prepareElementSortRow(
			$params,
			['ORDER' => 'ELEMENT_SORT_FIELD', 'DIRECTION' => 'ELEMENT_SORT_ORDER'],
			['ORDER' => 'SORT', 'DIRECTION' => 'asc']
		);

		$params = $this->prepareElementSortRow(
			$params,
			['ORDER' => 'ELEMENT_SORT_FIELD2', 'DIRECTION' => 'ELEMENT_SORT_ORDER2'],
			['ORDER' => 'ID', 'DIRECTION' => 'desc']
		);

		$params['PAGER_BASE_LINK_ENABLE'] = (string)($params['PAGER_BASE_LINK_ENABLE'] ?? '');
		$params['PAGER_TITLE'] = (string)($params['PAGER_TITLE'] ?? '');
		$params['PAGER_TEMPLATE'] = (string)($params['PAGER_TEMPLATE'] ?? '');
		$params['PAGER_PARAMS_NAME'] = trim((string)($params['PAGER_PARAMS_NAME'] ?? ''));
		if (
			$params['PAGER_PARAMS_NAME'] !== ''
			&& preg_match(self::PARAM_TITLE_MASK, $params['PAGER_PARAMS_NAME'])
		)
		{
			$this->pagerParameters = $GLOBALS[$params['PAGER_PARAMS_NAME']] ?? [];

			if (!is_array($this->pagerParameters))
			{
				$this->pagerParameters = [];
			}
		}

		if (Loader::includeModule('catalog') && isset($params['CUSTOM_FILTER']) && is_string($params['CUSTOM_FILTER']))
		{
			try
			{
				$params['CUSTOM_FILTER'] = $this->parseCondition(Json::decode($params['CUSTOM_FILTER']), $params);
			}
			catch (\Exception $e)
			{
				$params['CUSTOM_FILTER'] = array();
			}
		}
		else
		{
			$params['CUSTOM_FILTER'] = array();
		}

		if ($this->isPaginationMode())
		{
			$this->getPaginationParams($params);
		}

		$this->getSpecificIblockParams($params);

		$params['CALCULATE_SKU_MIN_PRICE'] = (isset($params['CALCULATE_SKU_MIN_PRICE']) && $params['CALCULATE_SKU_MIN_PRICE'] === 'Y');

		return $params;
	}

	/**
	 * @param array $params
	 * @param array $orderRow
	 * @param array $default
	 * @return array
	 */
	protected function prepareElementSortRow(array $params, array $orderRow, array $default): array
	{
		$order = (isset($orderRow['ORDER']) ? trim($orderRow['ORDER']) : '');
		$direction = (isset($orderRow['DIRECTION']) ? trim($orderRow['DIRECTION']) : '');
		if (empty($params) || $order === '' || $direction === '')
		{
			return $params;
		}
		if (empty($params[$order]))
		{
			$params[$order] = $default['ORDER'] ?? 'SORT';
		}
		$params[$order] = strtoupper($params[$order]);

		if ($params[$order] === 'ID' && !empty($params[$direction]) && is_array($params[$direction]))
		{
			Collection::normalizeArrayValuesByInt($params[$direction], false);
			if (empty($params[$direction]))
			{
				$params[$direction] = $default['DIRECTION'] ?? 'desc';
			}
		}
		else
		{
			if (empty($params[$direction]) || !preg_match(self::SORT_ORDER_MASK, $params[$direction]))
			{
				$params[$direction] = $default['DIRECTION'] ?? 'desc';
			}
		}

		return $params;
	}

	protected function checkProductIblock(array $product): bool
	{
		$result = true;
		if (!$this->isMultiIblockMode())
		{
			$result = ($product['PRODUCT_IBLOCK_ID'] == $this->arParams['IBLOCK_ID']);
		}
		return $result;
	}

	protected static function predictElementCountByVariants($variants, $isBigData = false)
	{
		$count = 0;
		$templateVariantsMap = static::getTemplateVariantsMap();

		if (!empty($variants))
		{
			foreach ($variants as $variant)
			{
				foreach ($templateVariantsMap as $variantInfo)
				{
					if ((int)$variantInfo['VARIANT'] === (int)$variant['VARIANT'])
					{
						if (
							($isBigData && $variant['BIG_DATA'])
							|| (!$isBigData && !$variant['BIG_DATA'])
						)
						{
							$count += (int)$variantInfo['COUNT'];
						}

						break;
					}
				}
			}
		}

		return $count;
	}

	private function makeMagicWithPageNavigation()
	{
		if ($this->request->isAjaxRequest())
		{
			foreach ($this->request->getPostList() as $name => $value)
			{
				if (preg_match('%^PAGEN_(\d+)$%', $name, $m))
				{
					global $NavNum;
					$NavNum = (int)$m[1] - 1;
					return;
				}
			}
		}
	}

	protected function getPaginationParams(&$params)
	{
		$params['DISPLAY_TOP_PAGER'] = isset($params['DISPLAY_TOP_PAGER']) && $params['DISPLAY_TOP_PAGER'] === 'Y';
		$params['DISPLAY_BOTTOM_PAGER'] = !isset($params['DISPLAY_BOTTOM_PAGER']) || $params['DISPLAY_BOTTOM_PAGER'] !== 'N';
		$params['LAZY_LOAD'] = isset($params['LAZY_LOAD']) && $params['LAZY_LOAD'] === 'Y' ? 'Y' : 'N';

		if ($params['DISPLAY_TOP_PAGER'] || $params['DISPLAY_BOTTOM_PAGER'] || $params['LAZY_LOAD'] === 'Y')
		{
			\CPageOption::SetOptionString('main', 'nav_page_in_session', 'N');
			$params['PAGER_TITLE'] = isset($params['PAGER_TITLE']) ? trim($params['PAGER_TITLE']) : '';
			$params['PAGER_SHOW_ALWAYS'] = isset($params['PAGER_SHOW_ALWAYS']) && $params['PAGER_SHOW_ALWAYS'] === 'Y';
			$params['PAGER_TEMPLATE'] = isset($params['PAGER_TEMPLATE']) ? trim($params['PAGER_TEMPLATE']) : '';
			$params['PAGER_DESC_NUMBERING'] = isset($params['PAGER_DESC_NUMBERING']) && $params['PAGER_DESC_NUMBERING'] === 'Y';
			$params['PAGER_DESC_NUMBERING_CACHE_TIME'] = (int)$params['PAGER_DESC_NUMBERING_CACHE_TIME'];
			$params['PAGER_SHOW_ALL'] = isset($params['PAGER_SHOW_ALL']) && $params['PAGER_SHOW_ALL'] === 'Y';
			$params['LOAD_ON_SCROLL'] = isset($params['LOAD_ON_SCROLL']) && $params['LOAD_ON_SCROLL'] === 'Y' ? 'Y' : 'N';
			$params['MESS_BTN_LAZY_LOAD'] = isset($params['MESS_BTN_LAZY_LOAD']) ? trim($params['MESS_BTN_LAZY_LOAD']) : '';
		}
		else
		{
			$this->setPaginationMode(false);
			$params['PAGER_SHOW_ALWAYS'] = false;
			$params['PAGER_SHOW_ALL'] = false;
			$params['LOAD_ON_SCROLL'] = 'N';
		}
		if ($params['LAZY_LOAD'] === 'Y' && $params['LOAD_ON_SCROLL'] === 'Y')
		{
			$params['DEFERRED_LOAD'] = isset($params['DEFERRED_LOAD']) && $params['DEFERRED_LOAD'] === 'Y' ? 'Y' : 'N';
		}
		else
		{
			$params['DEFERRED_LOAD'] = 'N';
		}
	}

	protected function getSpecificIblockParams(&$params)
	{
		if ($this->isMultiIblockMode())
		{
			$parameters = $this->getMultiIblockParams($params);
		}
		else
		{
			$parameters = $this->getSingleIblockParams($params);
		}

		$this->storage['IBLOCK_PARAMS'] = $parameters;
	}

	/**
	 * Process iblock component parameters for single iblock with disabled multi-iblock-mode.
	 *
	 * @param $params
	 * @return array
	 */
	protected function getMultiIblockParams(&$params)
	{
		$usePropertyFeatures = Iblock\Model\PropertyFeature::isEnabledFeatures();

		$params['PROPERTY_CODE'] = array();
		$params['CART_PROPERTIES'] = array();
		$params['SHOW_PRODUCTS'] = $params['SHOW_PRODUCTS'] ?? array();

		foreach ($params as $name => $prop)
		{
			$match = array();
			if (preg_match('/^PROPERTY_CODE_(\d+)$/', $name, $match))
			{
				$iblockId = (int)$match[1];
				if ($iblockId <= 0)
					continue;

				if (!empty($params[$name]) && is_array($params[$name]))
				{
					foreach ($params[$name] as $k => $v)
					{
						if ($v == '')
						{
							unset($params[$name][$k]);
						}
					}

					$params['PROPERTY_CODE'][$iblockId] = $params[$name];
				}
				unset($params[$match[0]]);
			}
			elseif (preg_match('/^CART_PROPERTIES_(\d+)$/', $name, $match))
			{
				$iblockId = (int)$match[1];
				if ($iblockId <= 0)
					continue;

				if (!empty($params[$name]) && is_array($params[$name]))
				{
					foreach ($params[$name] as $k => $v)
					{
						if ($v == '' || $v === '-')
						{
							unset($params[$name][$k]);
						}
					}
					$params['CART_PROPERTIES'][$iblockId] = $params[$name];
				}
				unset($params[$match[0]]);
			}
			elseif (preg_match('/^OFFER_TREE_PROPS_(\d+)$/', $name, $match))
			{
				$iblockId = (int)$match[1];
				if ($iblockId <= 0)
					continue;

				if (!empty($params[$name]) && is_array($params[$name]))
				{
					foreach ($params[$name] as $k => $v)
					{
						if ($v == '' || $v === '-')
						{
							unset($params[$name][$k]);
						}
					}

					$params['OFFER_TREE_PROPS'][$iblockId] = $params[$name];
				}
				unset($params[$match[0]]);
			}
			elseif (preg_match('/^SHOW_PRODUCTS_(\d+)$/', $name, $match))
			{
				$iblockId = (int)$match[1];
				if ($iblockId <= 0)
					continue;

				if ($params[$name] === 'Y')
				{
					$params['SHOW_PRODUCTS'][$iblockId] = true;
				}

				unset($params[$match[0]]);
			}
			unset($match);
		}

		$parameters = array();

		if (!empty($params['SHOW_PRODUCTS']))
		{
			foreach (array_keys($params['SHOW_PRODUCTS']) as $iblockId)
			{
				$catalog = \CCatalogSku::GetInfoByProductIBlock($iblockId);

				// product iblock parameters
				$parameters[$iblockId] = array(
					'PROPERTY_CODE' => $params['PROPERTY_CODE'][$iblockId] ?? array(),
					'CART_PROPERTIES' => (!$usePropertyFeatures && isset($params['CART_PROPERTIES'][$iblockId])
						? $params['CART_PROPERTIES'][$iblockId]
						: array()
					)
				);

				// offers iblock parameters
				if (!empty($catalog))
				{
					$parameters[$iblockId]['OFFERS_FIELD_CODE'] = array('ID', 'CODE', 'NAME', 'SORT', 'PREVIEW_PICTURE', 'DETAIL_PICTURE');
					$parameters[$iblockId]['OFFERS_PROPERTY_CODE'] = $params['PROPERTY_CODE'][$catalog['IBLOCK_ID']] ?? array();
					$parameters[$iblockId]['OFFERS_CART_PROPERTIES'] = (!$usePropertyFeatures && isset($params['CART_PROPERTIES'][$catalog['IBLOCK_ID']])
						? $params['CART_PROPERTIES'][$catalog['IBLOCK_ID']]
						: array()
					);
					$parameters[$iblockId]['OFFERS_TREE_PROPS'] = (!$usePropertyFeatures && isset($params['OFFER_TREE_PROPS'][$catalog['IBLOCK_ID']])
						? $params['OFFER_TREE_PROPS'][$catalog['IBLOCK_ID']]
						: []
					);
				}
			}
		}

		return $parameters;
	}

	/**
	 * Process iblock component parameters for single iblock with disabled multi-iblock-mode.
	 *
	 * @param $params
	 * @return array
	 */
	protected function getSingleIblockParams(&$params)
	{
		$usePropertyFeatures = Iblock\Model\PropertyFeature::isEnabledFeatures();

		if (!isset($params['PROPERTY_CODE']) || !is_array($params['PROPERTY_CODE']))
		{
			$params['PROPERTY_CODE'] = array();
		}

		foreach ($params['PROPERTY_CODE'] as $k => $v)
		{
			if ($v == '')
			{
				unset($params['PROPERTY_CODE'][$k]);
			}
		}

		if (!isset($params['OFFERS_FIELD_CODE']))
		{
			$params['OFFERS_FIELD_CODE'] = array();
		}
		elseif (!is_array($params['OFFERS_FIELD_CODE']))
		{
			$params['OFFERS_FIELD_CODE'] = array($params['OFFERS_FIELD_CODE']);
		}

		foreach ($params['OFFERS_FIELD_CODE'] as $key => $value)
		{
			if ($value == '')
			{
				unset($params['OFFERS_FIELD_CODE'][$key]);
			}
		}

		if (!isset($params['OFFERS_PROPERTY_CODE']))
		{
			$params['OFFERS_PROPERTY_CODE'] = array();
		}
		elseif (!is_array($params['OFFERS_PROPERTY_CODE']))
		{
			$params['OFFERS_PROPERTY_CODE'] = array($params['OFFERS_PROPERTY_CODE']);
		}

		foreach ($params['OFFERS_PROPERTY_CODE'] as $key => $value)
		{
			if ($value == '')
			{
				unset($params['OFFERS_PROPERTY_CODE'][$key]);
			}
		}

		$cartProperties = [];
		$offersCartProperties = [];
		$offersTreeProperties = [];
		if (!$usePropertyFeatures)
		{
			if (!isset($params['PRODUCT_PROPERTIES']) || !is_array($params['PRODUCT_PROPERTIES']))
			{
				$params['PRODUCT_PROPERTIES'] = array();
			}

			foreach ($params['PRODUCT_PROPERTIES'] as $k => $v)
			{
				if ($v == '')
				{
					unset($params['PRODUCT_PROPERTIES'][$k]);
				}
			}
			$cartProperties = $params['PRODUCT_PROPERTIES'];

			if (!isset($params['OFFERS_CART_PROPERTIES']) || !is_array($params['OFFERS_CART_PROPERTIES']))
			{
				$params['OFFERS_CART_PROPERTIES'] = array();
			}

			foreach ($params['OFFERS_CART_PROPERTIES'] as $i => $pid)
			{
				if ($pid == '')
				{
					unset($params['OFFERS_CART_PROPERTIES'][$i]);
				}
			}
			$offersCartProperties = $params['OFFERS_CART_PROPERTIES'];

			if (!isset($params['OFFER_TREE_PROPS']))
			{
				$params['OFFER_TREE_PROPS'] = array();
			}
			elseif (!is_array($params['OFFER_TREE_PROPS']))
			{
				$params['OFFER_TREE_PROPS'] = array($params['OFFER_TREE_PROPS']);
			}

			foreach ($params['OFFER_TREE_PROPS'] as $key => $value)
			{
				$value = (string)$value;
				if ($value == '' || $value === '-')
				{
					unset($params['OFFER_TREE_PROPS'][$key]);
				}
			}

			if (empty($params['OFFER_TREE_PROPS']) && !empty($params['OFFERS_CART_PROPERTIES']))
			{
				$params['OFFER_TREE_PROPS'] = $params['OFFERS_CART_PROPERTIES'];
				foreach ($params['OFFER_TREE_PROPS'] as $key => $value)
				{
					if ($value === '-')
					{
						unset($params['OFFER_TREE_PROPS'][$key]);
					}
				}
			}
			$offersTreeProperties = $params['OFFER_TREE_PROPS'];
		}

		return array(
			$params['IBLOCK_ID'] => array(
				'PROPERTY_CODE' => $params['PROPERTY_CODE'],
				'CART_PROPERTIES' => $cartProperties,
				'OFFERS_FIELD_CODE' => $params['OFFERS_FIELD_CODE'],
				'OFFERS_PROPERTY_CODE' => $params['OFFERS_PROPERTY_CODE'],
				'OFFERS_CART_PROPERTIES' => $offersCartProperties,
				'OFFERS_TREE_PROPS' => $offersTreeProperties
			)
		);
	}

	/**
	 * Returns list of product ids which will be showed on first hit.
	 * @return array
	 */
	protected function getProductIds()
	{
		if ($this->isEmptyStartLoad())
		{
			return [];
		}
		return parent::getProductIds();
	}

	/**
	 * @return bool
	 */
	protected function isEmptyStartLoad(): bool
	{
		return (
			isset($this->arParams['LAZY_LOAD'])
			&& $this->arParams['LAZY_LOAD'] === 'Y'
			&& isset($this->arParams['LOAD_ON_SCROLL'])
			&& $this->arParams['LOAD_ON_SCROLL'] === 'Y'
			&& isset($this->arParams['DEFERRED_LOAD'])
			&& $this->arParams['DEFERRED_LOAD'] === 'Y'
		);
	}

	// some logic of \CComponentAjax to execute in component_epilog
	public function prepareLinks(&$data)
	{
		$addParam = \CAjax::GetSessionParam($this->arParams['AJAX_ID']);

		$regexpLinks = '/(<a\s[^>]*?>.*?<\/a>)/is'.BX_UTF_PCRE_MODIFIER;
		$regexpParams = '/([\w\-]+)\s*=\s*([\"\'])(.*?)\2/is'.BX_UTF_PCRE_MODIFIER;

		$this->checkPcreLimit($data);
		$arData = preg_split($regexpLinks, $data, -1, PREG_SPLIT_DELIM_CAPTURE);

		$dataCount = count($arData);
		if ($dataCount < 2)
			return;

		$ignoreAttributes = array(
			'onclick' => true,
			'target' => true
		);
		$search = array(
			$addParam.'&',
			$addParam,
			'AJAX_CALL=Y&',
			'AJAX_CALL=Y'
		);
		$dataChanged = false;

		for ($i = 1; $i < $dataCount; $i += 2)
		{
			if (!preg_match('/^<a\s([^>]*?)>(.*?)<\/a>$/is'.BX_UTF_PCRE_MODIFIER, $arData[$i], $match))
				continue;

			$params = $match[1];

			if (!preg_match_all($regexpParams, $params, $linkParams))
				continue;

			$strAdditional = ' ';
			$urlKey = -1;
			$ignoreLink = false;

			foreach ($linkParams[0] as $key => $value)
			{
				if ($value == '')
					continue;

				$paramName = mb_strtolower($linkParams[1][$key]);

				if ($paramName === 'href')
				{
					$urlKey = $key;
				}
				elseif (isset($ignoreAttributes[$paramName]))
				{
					$ignoreLink = true;
					break;
				}
				else
				{
					$strAdditional .= $value.' ';
				}
			}

			if ($urlKey >= 0 && !$ignoreLink)
			{
				$url = Text\Converter::getHtmlConverter()->decode($linkParams[3][$urlKey]);
				$url = str_replace($search, '', $url);

				if ($this->isAjaxURL($url))
				{
					$realUrl = $url;

					$pos = mb_strpos($url, '#');
					if ($pos !== false)
					{
						$realUrl = mb_substr($realUrl, 0, $pos);
					}

					$realUrl .= mb_strpos($url, '?') === false ? '?' : '&';
					$realUrl .= $addParam;

					$arData[$i] = \CAjax::GetLinkEx($realUrl, $url, $match[2], 'comp_'.$this->arParams['AJAX_ID'], $strAdditional);

					$dataChanged = true;
				}
			}
		}

		if ($dataChanged)
		{
			$data = implode('', $arData);
		}
	}

	private function checkPcreLimit($data)
	{
		$pcreBacktrackLimit = (int)ini_get('pcre.backtrack_limit');
		$textLen = strlen($data);
		$textLen++;

		if ($pcreBacktrackLimit > 0 && $pcreBacktrackLimit < $textLen)
		{
			@ini_set('pcre.backtrack_limit', $textLen);
			$pcreBacktrackLimit = intval(ini_get('pcre.backtrack_limit'));
		}

		return $pcreBacktrackLimit >= $textLen;
	}

	private function isAjaxURL($url)
	{
		if (preg_match('/^(#|mailto:|javascript:|callto:)/', $url))
			return false;

		if (mb_strpos($url, '://') !== false)
			return false;

		$url = preg_replace('/#.*/', '', $url);

		if (mb_strpos($url, '?') !== false)
		{
			$url = mb_substr($url, 0, mb_strpos($url, '?'));
		}

		if (mb_substr($url, -4) != '.php')
		{
			if (mb_substr($url, -1) != '/')
			{
				$url .= '/';
			}

			$url .= 'index.php';
		}

		$currentUrl = $this->arParams['CURRENT_BASE_PAGE'];

		if (mb_strpos($currentUrl, '?') !== false)
		{
			$currentUrl = mb_substr($currentUrl, 0, mb_strpos($currentUrl, '?'));
		}

		if (mb_substr($currentUrl, -4) != '.php')
		{
			if (mb_substr($currentUrl, -1) != '/')
			{
				$currentUrl .= '/';
			}

			$currentUrl .= 'index.php';
		}

		$currentUrlDirName = dirname($currentUrl);
		$currentUrlBaseName = basename($currentUrl);
		$dirName = dirname($url);

		if (
			($dirName == $currentUrlDirName || $dirName == '' || $dirName == '.')
			&& basename($url) == $currentUrlBaseName
		)
		{
			return true;
		}

		return false;
	}

	protected function initQueryFields()
	{
		parent::initQueryFields();
		$this->initSubQuery();
	}

	protected function initSubQuery()
	{
		$this->storage['SUB_FILTER'] = array();

		if (
			$this->useCatalog
			&& !$this->isMultiIblockMode()
			&& $this->offerIblockExist($this->arParams['IBLOCK_ID'])
		)
		{
			$catalogFilter = array();
			foreach ($this->globalFilter as $key => $value)
			{
				if (\CProductQueryBuilder::isCatalogFilterField($key))
				{
					//TODO: remove this hack after new catalog.section filter
					if ($key === '=PRODUCT_UF_PRODUCT_MAPPING')
					{
						continue;
					}
					$catalogFilter[$key] = $value;
					unset($this->globalFilter[$key]);
				}
			}

			$iblock = $this->storage['CATALOGS'][$this->arParams['IBLOCK_ID']];
			$offersFilterExists = !empty($this->globalFilter['OFFERS']) && is_array($this->globalFilter['OFFERS']);

			if ($offersFilterExists)
			{
				$this->storage['SUB_FILTER'] = array_merge($this->globalFilter['OFFERS'], $catalogFilter);
				$this->storage['SUB_FILTER']['IBLOCK_ID'] = $iblock['IBLOCK_ID'];
				$this->storage['SUB_FILTER']['ACTIVE_DATE'] = 'Y';
				$this->storage['SUB_FILTER']['ACTIVE'] = 'Y';

				if ($this->arParams['HIDE_NOT_AVAILABLE'] === 'Y')
				{
					$this->storage['SUB_FILTER']['AVAILABLE'] = 'Y';
				}

				$this->filterFields['=ID'] = \CIBlockElement::SubQuery(
					'PROPERTY_'.$iblock['SKU_PROPERTY_ID'],
					$this->storage['SUB_FILTER']
				);
			}
			elseif (!empty($catalogFilter))
			{
				$this->storage['SUB_FILTER'] = $catalogFilter;
				$this->storage['SUB_FILTER']['IBLOCK_ID'] = $iblock['IBLOCK_ID'];
				$this->storage['SUB_FILTER']['ACTIVE_DATE'] = 'Y';
				$this->storage['SUB_FILTER']['ACTIVE'] = 'Y';

				$this->filterFields[] = array(
					'LOGIC' => 'OR',
					array($catalogFilter),
					'=ID' => \CIBlockElement::SubQuery(
						'PROPERTY_'.$iblock['SKU_PROPERTY_ID'],
						$this->storage['SUB_FILTER']
					),
				);
			}
		}
	}

	protected function getIblockElements($elementIterator)
	{
		$iblockElements = array();

		if (!empty($elementIterator))
		{
			while ($element = $elementIterator->GetNext())
			{
				$this->processElement($element);
				$iblockElements[$element['ID']] = $element;
			}
		}

		return $iblockElements;
	}

	protected function modifyDisplayProperties($iblock, &$iblockElements)
	{
		if (!empty($iblockElements))
		{
			$iblockParams = $this->storage['IBLOCK_PARAMS'][$iblock];
			$propertyCodes = $iblockParams['PROPERTY_CODE'];
			$productProperties = $iblockParams['CART_PROPERTIES'];
			$getPropertyCodes = !empty($propertyCodes);
			$getProductProperties = $this->arParams['ADD_PROPERTIES_TO_BASKET'] === 'Y' && !empty($productProperties);
			$getIblockProperties = $getPropertyCodes || $getProductProperties;

			if ($getIblockProperties || ($this->useCatalog && $this->useDiscountCache))
			{
				$propFilter = array(
					'ID' => array_keys($iblockElements),
					'IBLOCK_ID' => $iblock
				);
				\CIBlockElement::GetPropertyValuesArray($iblockElements, $iblock, $propFilter);

				if ($getPropertyCodes)
				{
					$propertyList = $this->getPropertyList($iblock, $propertyCodes);
				}

				foreach ($iblockElements as &$element)
				{
					if ($this->useCatalog && $this->useDiscountCache)
					{
						if ($this->storage['USE_SALE_DISCOUNTS'])
							Catalog\Discount\DiscountManager::setProductPropertiesCache($element['ID'], $element["PROPERTIES"]);
						else
							\CCatalogDiscount::SetProductPropertiesCache($element['ID'], $element['PROPERTIES']);
					}

					if ($getIblockProperties)
					{
						if (!empty($propertyList))
						{
							foreach ($propertyList as $pid)
							{
								if (!isset($element['PROPERTIES'][$pid]))
									continue;

								$prop =& $element['PROPERTIES'][$pid];
								$isArr = is_array($prop['VALUE']);
								if (
									($isArr && !empty($prop['VALUE']))
									|| (!$isArr && (string)$prop['VALUE'] !== '')
									|| Tools::isCheckboxProperty($prop)
								)
								{
									$element['DISPLAY_PROPERTIES'][$pid] = \CIBlockFormatProperties::GetDisplayValue($element, $prop);
								}
								unset($prop);
							}
							unset($pid);
						}

						if ($getProductProperties)
						{
							$element['PRODUCT_PROPERTIES'] = \CIBlockPriceTools::GetProductProperties(
								$iblock,
								$element['ID'],
								$productProperties,
								$element['PROPERTIES']
							);

							if (!empty($element['PRODUCT_PROPERTIES']))
							{
								$element['PRODUCT_PROPERTIES_FILL'] = \CIBlockPriceTools::getFillProductProperties($element['PRODUCT_PROPERTIES']);
							}
						}
					}
				}
				unset($element);

				\CIBlockFormatProperties::clearCache();
				Tools::clearCache();
			}
		}
	}

	protected function getFilter()
	{
		$filterFields = parent::getFilter();
		$filterFields['ACTIVE'] = 'Y';

		if ($this->arParams['HIDE_NOT_AVAILABLE'] === 'Y')
		{
			$filterFields['AVAILABLE'] = 'Y';
		}

		if (!empty($this->arParams['CUSTOM_FILTER']))
		{
			$filterFields[] = $this->arParams['CUSTOM_FILTER'];
		}

		if (!empty($this->arParams['FILTER_IDS']))
		{
			$filterFields['!ID'] = $this->arParams['FILTER_IDS'];
		}

		return $filterFields;
	}

	protected function getSort()
	{
		$sortFields = $this->getCustomSort();
		if (empty($sortFields))
		{
			if (
				(
					$this->isIblockCatalog
					|| (
						$this->isMultiIblockMode()
						|| (!$this->isMultiIblockMode() && $this->offerIblockExist($this->arParams['IBLOCK_ID']))
					)
				)
				&& $this->arParams['HIDE_NOT_AVAILABLE'] === 'L'
			)
			{
				$sortFields['AVAILABLE'] = 'desc,nulls';
			}

			$field = strtoupper($this->arParams['ELEMENT_SORT_FIELD']);
			if (!isset($sortFields[$field]))
			{
				$sortFields[$field] = $this->arParams['ELEMENT_SORT_ORDER'];
			}

			$field = strtoupper($this->arParams['ELEMENT_SORT_FIELD2']);
			if (!isset($sortFields[$field]))
			{
				$sortFields[$field] = $this->arParams['ELEMENT_SORT_ORDER2'];
			}
			unset($field);
			if (!isset($sortFields['ID']))
			{
				$sortFields['ID'] = 'DESC';
			}
		}

		return $sortFields;
	}

	protected function getCustomSort(): array
	{
		$result = [];

		if (!empty($this->arParams['CUSTOM_ELEMENT_SORT']) && is_array($this->arParams['CUSTOM_ELEMENT_SORT']))
		{
			foreach ($this->arParams['CUSTOM_ELEMENT_SORT'] as $field => $value)
			{
				$field = strtoupper($field);
				if (isset($result[$field]))
				{
					continue;
				}
				if ($field === 'ID' && !empty($value) && is_array($value))
				{
					Collection::normalizeArrayValuesByInt($value, false);
					if (empty($value))
					{
						continue;
					}
				}
				else
				{
					if (!is_string($value))
					{
						continue;
					}
					if (!preg_match(self::SORT_ORDER_MASK, $value))
					{
						continue;
					}
				}

				$result[$field] = $value;
			}
			unset($field, $value);
		}

		return $result;
	}

	protected function getElementList($iblockId, $products)
	{
		// initialLoad case with only deferred bigData items
		if (is_array($this->navParams) && isset($this->navParams['nTopCount']) && $this->navParams['nTopCount'] == 0)
		{
			return false;
		}

		$elementIterator = parent::getElementList($iblockId, $products);

		if (!empty($elementIterator) && $this->isPaginationMode())
		{
			$this->initNavString($elementIterator);
		}

		return $elementIterator;
	}

	protected function initNavString(\CIBlockResult $elementIterator)
	{
		$navComponentParameters = array();

		if ($this->arParams['PAGER_BASE_LINK_ENABLE'] === 'Y')
		{
			$pagerBaseLink = trim($this->arParams['PAGER_BASE_LINK']) ?: $this->arResult['SECTION_PAGE_URL'];

			if ($this->pagerParameters && isset($this->pagerParameters['BASE_LINK']))
			{
				$pagerBaseLink = $this->pagerParameters['BASE_LINK'];
				unset($this->pagerParameters['BASE_LINK']);
			}

			$navComponentParameters['BASE_LINK'] = \CHTTP::urlAddParams($pagerBaseLink, $this->pagerParameters, array('encode' => true));
		}
		else
		{
			$uri = new Main\Web\Uri($this->arParams['CURRENT_BASE_PAGE']);
			$uri->deleteParams(array(
				'PAGEN_'.$elementIterator->NavNum,
				'SIZEN_'.$elementIterator->NavNum,
				'SHOWALL_'.$elementIterator->NavNum,
				'PHPSESSID',
				'clear_cache',
				'bitrix_include_areas'
			));
			$navComponentParameters['BASE_LINK'] = $uri->getUri();
		}

		$this->arResult['NAV_STRING'] = $elementIterator->GetPageNavStringEx(
			$navComponentObject,
			$this->arParams['PAGER_TITLE'],
			$this->arParams['PAGER_TEMPLATE'],
			$this->arParams['PAGER_SHOW_ALWAYS'],
			$this,
			$navComponentParameters
		);
		$this->arResult['NAV_CACHED_DATA'] = null;
		$this->arResult['NAV_RESULT'] = $elementIterator;
		$this->arResult['NAV_PARAM'] = $navComponentParameters;
	}

	protected function chooseOffer($offers, $iblockId)
	{
		if (empty($offers) || empty($this->storage['CATALOGS'][$iblockId]))
			return;
		$uniqueSortHash = array();
		$filteredOffers = array();
		$filteredElements = array();
		$filteredByProperty = $this->getFilteredOffersByProperty($iblockId);

		if (!$this->isMultiIblockMode() && !empty($this->storage['SUB_FILTER']))
		{
			$catalog = $this->storage['CATALOGS'][$iblockId];
			$this->storage['SUB_FILTER']['=PROPERTY_'.$catalog['SKU_PROPERTY_ID']] = array_keys($this->elementLinks);
			$filteredOffers = Iblock\Component\Filters::getFilteredOffersByProduct(
				$catalog['IBLOCK_ID'],
				$catalog['SKU_PROPERTY_ID'],
				$this->storage['SUB_FILTER']
			);
			unset($catalog);
		}

		foreach ($offers as &$offer)
		{
			$elementId = $offer['LINK_ELEMENT_ID'];

			if (!isset($this->elementLinks[$elementId]))
				continue;

			if (!isset($uniqueSortHash[$elementId]))
			{
				$uniqueSortHash[$elementId] = array();
			}

			$uniqueSortHash[$elementId][$offer['SORT_HASH']] = true;

			if ($this->elementLinks[$elementId]['OFFER_ID_SELECTED'] == 0 && $offer['CAN_BUY'])
			{
				if (isset($filteredOffers[$elementId]))
				{
					if (isset($filteredOffers[$elementId][$offer['ID']]))
					{
						$this->elementLinks[$elementId]['OFFER_ID_SELECTED'] = $offer['ID'];
						$filteredElements[$elementId] = true;
					}
				}
				elseif (isset($filteredByProperty[$elementId]))
				{
					if (isset($filteredByProperty[$elementId][$offer['ID']]))
					{
						$this->elementLinks[$elementId]['OFFER_ID_SELECTED'] = $offer['ID'];
						$filteredElements[$elementId] = true;
					}
				}
				else
				{
					$this->elementLinks[$elementId]['OFFER_ID_SELECTED'] = $offer['ID'];
				}
			}
			unset($elementId);
		}

		if (!empty($filteredOffers))
		{
			$this->arResult['FILTERED_OFFERS_ID'] = array();
		}

		foreach ($this->elementLinks as &$element)
		{
			if (isset($filteredOffers[$element['ID']]))
			{
				$this->arResult['FILTERED_OFFERS_ID'][$element['ID']] = $filteredOffers[$element['ID']];
			}

			if ($element['OFFER_ID_SELECTED'] == 0 || isset($filteredElements[$element['ID']]))
				continue;

			if (count($uniqueSortHash[$element['ID']]) < 2)
			{
				$element['OFFER_ID_SELECTED'] = 0;
			}
		}
	}

	protected function getFilteredOffersByProperty($iblockId)
	{
		$offers = array();
		if (empty($this->storage['CATALOGS'][$iblockId]))
			return $offers;

		if (!$this->isMultiIblockMode())
		{
			$filter = $this->getOffersPropFilter($this->arParams['CUSTOM_FILTER']);
			if (!empty($filter))
			{
				$catalog = $this->storage['CATALOGS'][$iblockId];
				$offers = Iblock\Component\Filters::getFilteredOffersByProduct(
					$catalog['IBLOCK_ID'],
					$catalog['SKU_PROPERTY_ID'],
					array(
						'=PROPERTY_'.$catalog['SKU_PROPERTY_ID'] => array_keys($this->elementLinks),
						$filter
					)
				);
			}
		}

		return $offers;
	}

	protected function getOffersPropFilter(array $level)
	{
		$filter = array();
		$checkLogic = true;

		if (!empty($level))
		{
			foreach ($level as $prop)
			{
				if (is_array($prop))
				{
					$filter[] = $this->getOffersPropFilter($prop);
				}
				elseif ($prop instanceOf \CIBlockElement)
				{
					$checkLogic = false;
					$filter = $prop->arFilter;
				}
			}

			if ($checkLogic && is_array($filter) && count($filter) > 1)
			{
				$filter['LOGIC'] = $level['LOGIC'];
			}
		}

		return $filter;
	}

	protected function getAdditionalCacheId()
	{
		return array(
			$this->globalFilter,
			$this->productIdMap,
			$this->arParams['CACHE_GROUPS'] === 'N' ? false : $this->getUserGroupsCacheId(),
			$this->navigation,
			$this->pagerParameters
		);
	}

	protected function getComponentCachePath()
	{
		return '/'.$this->getSiteId().$this->getRelativePath();
	}

	protected function makeOutputResult()
	{
		parent::makeOutputResult();
		$this->arResult['PRICES'] = $this->storage['PRICES'];
		$this->arResult['ITEMS'] = $this->elements;
		$this->arResult['ELEMENTS'] = array_keys($this->elementLinks);
	}

	public function loadData()
	{
		$this->initNavParams();
		parent::loadData();
	}

	protected function deferredLoadAction()
	{
		$this->prepareDeferredParams();
		parent::deferredLoadAction();
	}

	protected function prepareDeferredParams()
	{
		$this->arParams['~PRODUCT_ROW_VARIANTS'] = $this->arParams['~DEFERRED_PRODUCT_ROW_VARIANTS'];
		$this->arParams['PRODUCT_ROW_VARIANTS'] = static::parseJsonParameter($this->arParams['~PRODUCT_ROW_VARIANTS']);

		if (isset($this->arParams['PREDICT_ELEMENT_COUNT']) && $this->arParams['PREDICT_ELEMENT_COUNT'] === 'Y')
		{
			$this->arParams['PAGE_ELEMENT_COUNT'] = static::predictElementCountByVariants($this->arParams['PRODUCT_ROW_VARIANTS']);
		}
		else
		{
			$this->arParams['PAGE_ELEMENT_COUNT'] = $this->arParams['DEFERRED_PAGE_ELEMENT_COUNT'];
		}

		$this->arParams['PAGE_ELEMENT_COUNT'] = (int)$this->arParams['PAGE_ELEMENT_COUNT'];
	}

	/**
	 * Initialization of page navigation parameters.
	 */
	protected function initNavParams()
	{
		if ($this->isPaginationMode())
		{
			if (
				$this->arParams['PAGE_ELEMENT_COUNT'] > 0
				&& (
					$this->arParams['DISPLAY_TOP_PAGER']
					|| $this->arParams['DISPLAY_BOTTOM_PAGER']
					|| $this->arParams['LAZY_LOAD'] === 'Y'
				)
			)
			{
				$this->navParams = array(
					'nPageSize' => $this->arParams['PAGE_ELEMENT_COUNT'],
					'bDescPageNumbering' => $this->arParams['PAGER_DESC_NUMBERING'],
					'bShowAll' => $this->arParams['PAGER_SHOW_ALL']
				);
				$this->navigation = \CDBResult::GetNavParams($this->navParams);

				if ($this->navigation['PAGEN'] == 0 && $this->arParams['PAGER_DESC_NUMBERING_CACHE_TIME'] > 0)
				{
					$this->arParams['CACHE_TIME'] = $this->arParams['PAGER_DESC_NUMBERING_CACHE_TIME'];
				}
			}
			else
			{
				$this->navParams = array(
					'nTopCount' => $this->arParams['PAGE_ELEMENT_COUNT'],
					'bDescPageNumbering' => $this->arParams['PAGER_DESC_NUMBERING'],
				);
				$this->navigation = false;
			}
		}
		else
		{
			$this->navParams = array('nTopCount' => $this->arParams['PAGE_ELEMENT_COUNT']);
			$this->navigation = false;
		}
	}

	protected function prepareTemplateParams()
	{
		parent::prepareTemplateParams();
		$params =& $this->arParams;

		if ($params['LINE_ELEMENT_COUNT'] < 2)
		{
			$params['LINE_ELEMENT_COUNT'] = 2;
		}

		if ($params['LINE_ELEMENT_COUNT'] > 5)
		{
			$params['LINE_ELEMENT_COUNT'] = 5;
		}

		if ($params['ADD_TO_BASKET_ACTION'] != 'BUY')
		{
			$params['ADD_TO_BASKET_ACTION'] = 'ADD';
		}

		if (
			(empty($params['PRODUCT_ROW_VARIANTS']) || !is_array($params['PRODUCT_ROW_VARIANTS']))
			&& isset($params['~PRODUCT_ROW_VARIANTS'])
		)
		{
			$params['PRODUCT_ROW_VARIANTS'] = static::parseJsonParameter($params['~PRODUCT_ROW_VARIANTS']);
		}

		if (empty($params['PRODUCT_ROW_VARIANTS']))
		{
			$params['PRODUCT_ROW_VARIANTS'] = static::predictRowVariants($params['LINE_ELEMENT_COUNT'], $params['PAGE_ELEMENT_COUNT']);
		}

		if (empty($params['PRODUCT_BLOCKS_ORDER']))
		{
			$params['PRODUCT_BLOCKS_ORDER'] = 'price,props,sku,quantityLimit,quantity,buttons';
		}

		if (is_string($params['PRODUCT_BLOCKS_ORDER']))
		{
			$params['PRODUCT_BLOCKS_ORDER'] = explode(',', $params['PRODUCT_BLOCKS_ORDER']);
		}

		$params['PRODUCT_DISPLAY_MODE'] = isset($params['PRODUCT_DISPLAY_MODE']) && $params['PRODUCT_DISPLAY_MODE'] === 'Y' ? 'Y' : 'N';

		if ($this->isMultiIblockMode())
		{
			$this->getTemplateMultiIblockParams($params);
		}
		else
		{
			$this->getTemplateSingleIblockParams($params);
		}
	}

	protected static function parseJsonParameter($jsonString)
	{
		$parameter = [];

		if (!empty($jsonString) && is_string($jsonString))
		{
			try
			{
				$parameter = Json::decode(str_replace("'", '"', $jsonString));
			}
			catch (\Exception $e) {}
		}

		return $parameter;
	}

	/**
	 * Process iblock template parameters for multi iblock mode.
	 *
	 * @param $params
	 */
	protected function getTemplateMultiIblockParams(&$params)
	{
		$params['ADDITIONAL_PICT_PROP'] = array();
		$params['LABEL_PROP'] = array();
		$params['LABEL_PROP_MOBILE'] = array();
		$params['PROPERTY_CODE_MOBILE'] = array();
		$params['ENLARGE_PROP'] = array();
		$params['OFFER_TREE_PROPS'] = array();

		foreach ($params as $name => $prop)
		{
			if (preg_match('/^ADDITIONAL_PICT_PROP_(\d+)$/', $name, $match))
			{
				$iblockId = (int)$match[1];
				if ($iblockId <= 0)
					continue;

				if ($params[$name] != '' && $params[$name] != '-')
				{
					$params['ADDITIONAL_PICT_PROP'][$iblockId] = $params[$name];
				}
				unset($params[$match[0]]);
			}
			elseif (preg_match('/^LABEL_PROP_(\d+)$/', $name, $match))
			{
				$iblockId = (int)$match[1];
				if ($iblockId <= 0)
					continue;

				if (!empty($params[$name]))
				{
					if (!is_array($params[$name]))
					{
						$params[$name] = array($params[$name]);
					}

					foreach ($params[$name] as $k => $v)
					{
						if ($v == '')
						{
							unset($params[$name][$k]);
						}
					}

					$params['LABEL_PROP'][$iblockId] = $params[$name];
				}
			}
			elseif (preg_match('/^LABEL_PROP_MOBILE_(\d+)$/', $name, $match))
			{
				$iblockId = (int)$match[1];
				if ($iblockId <= 0)
					continue;

				if (!empty($params[$name]) && is_array($params[$name]))
				{
					foreach ($params[$name] as $k => $v)
					{
						if ($v == '')
						{
							unset($params[$name][$k]);
						}
					}

					if (!empty($params[$name]))
					{
						$params[$name] = array_flip($params[$name]);
					}

					$params['LABEL_PROP_MOBILE'][$iblockId] = $params[$name];
				}
				unset($params[$match[0]]);
			}
			elseif (preg_match('/^PROPERTY_CODE_MOBILE_(\d+)$/', $name, $match))
			{
				$iblockId = (int)$match[1];
				if ($iblockId <= 0)
					continue;

				if (!empty($params[$name]) && is_array($params[$name]))
				{
					foreach ($params[$name] as $k => $v)
					{
						if ($v == '')
						{
							unset($params[$name][$k]);
						}
					}

					if (!empty($params[$name]))
					{
						$params[$name] = array_flip($params[$name]);
					}

					$params['PROPERTY_CODE_MOBILE'][$iblockId] = $params[$name];
				}
				unset($params[$match[0]]);
			}
			elseif (preg_match('/^ENLARGE_PROP_(\d+)$/', $name, $match))
			{
				$iblockId = (int)$match[1];
				if ($iblockId <= 0)
					continue;

				if ($params[$name] != '' && $params[$name] != '-')
				{
					$params['ENLARGE_PROP'][$iblockId] = $params[$name];
				}
				unset($params[$match[0]]);
			}
		}

		if (!empty($params['SHOW_PRODUCTS']))
		{
			$usePropertyFeatures = Iblock\Model\PropertyFeature::isEnabledFeatures();

			foreach (array_keys($params['SHOW_PRODUCTS']) as $iblockId)
			{
				if (!isset($this->storage['IBLOCK_PARAMS'][$iblockId]) || !is_array($this->storage['IBLOCK_PARAMS'][$iblockId]))
				{
					$this->storage['IBLOCK_PARAMS'][$iblockId] = array();
				}

				// product iblock parameters
				$this->storage['IBLOCK_PARAMS'][$iblockId]['ADD_PICT_PROP'] = $params['ADDITIONAL_PICT_PROP'][$iblockId] ?? '';
				$this->storage['IBLOCK_PARAMS'][$iblockId]['LABEL_PROP'] = $params['LABEL_PROP'][$iblockId] ?? array();
				$this->storage['IBLOCK_PARAMS'][$iblockId]['LABEL_PROP_MOBILE'] = $params['LABEL_PROP_MOBILE'][$iblockId] ?? array();
				$this->storage['IBLOCK_PARAMS'][$iblockId]['PROPERTY_CODE_MOBILE'] = $params['PROPERTY_CODE_MOBILE'][$iblockId] ?? array();
				$this->storage['IBLOCK_PARAMS'][$iblockId]['ENLARGE_PROP'] = $params['ENLARGE_PROP'][$iblockId] ?? '';

				// offers iblock parameters
				$catalog = \CCatalogSku::GetInfoByProductIBlock($iblockId);
				if (!empty($catalog))
				{
					$this->storage['IBLOCK_PARAMS'][$iblockId]['OFFERS_ADD_PICT_PROP'] = $params['ADDITIONAL_PICT_PROP'][$catalog['IBLOCK_ID']] ?? '';
				}
			}

			unset($usePropertyFeatures);
		}
	}

	/**
	 * Process iblock template parameters for single iblock with disabled multi-iblock-mode.
	 *
	 * @param $params
	 */
	protected function getTemplateSingleIblockParams(&$params)
	{
		$params['ADD_PICT_PROP'] = isset($params['ADD_PICT_PROP']) ? trim($params['ADD_PICT_PROP']) : '';
		if ($params['ADD_PICT_PROP'] === '-')
		{
			$params['ADD_PICT_PROP'] = '';
		}

		if (!isset($params['LABEL_PROP']) || !is_array($params['LABEL_PROP']))
		{
			$params['LABEL_PROP'] = array();
		}

		if (!isset($params['LABEL_PROP_MOBILE']) || !is_array($params['LABEL_PROP_MOBILE']))
		{
			$params['LABEL_PROP_MOBILE'] = array();
		}

		if (!empty($params['LABEL_PROP_MOBILE']))
		{
			$params['LABEL_PROP_MOBILE'] = array_flip($params['LABEL_PROP_MOBILE']);
		}

		if (!isset($params['PROPERTY_CODE_MOBILE']) || !is_array($params['PROPERTY_CODE_MOBILE']))
		{
			$params['PROPERTY_CODE_MOBILE'] = array();
		}

		if (!empty($params['PROPERTY_CODE_MOBILE']))
		{
			$params['PROPERTY_CODE_MOBILE'] = array_flip($params['PROPERTY_CODE_MOBILE']);
		}

		$params['ENLARGE_PROP'] = isset($params['ENLARGE_PROP']) ? trim($params['ENLARGE_PROP']) : '';
		if ($params['ENLARGE_PROP'] === '-')
		{
			$params['ENLARGE_PROP'] = '';
		}

		$params['OFFER_ADD_PICT_PROP'] = isset($params['OFFER_ADD_PICT_PROP']) ? trim($params['OFFER_ADD_PICT_PROP']) : '';
		if ($params['OFFER_ADD_PICT_PROP'] === '-')
		{
			$params['OFFER_ADD_PICT_PROP'] = '';
		}

		if (!isset($this->storage['IBLOCK_PARAMS'][$params['IBLOCK_ID']]) || !is_array($this->storage['IBLOCK_PARAMS'][$params['IBLOCK_ID']]))
		{
			$this->storage['IBLOCK_PARAMS'][$params['IBLOCK_ID']] = array();
		}

		$this->storage['IBLOCK_PARAMS'][$params['IBLOCK_ID']]['ADD_PICT_PROP'] = $params['ADD_PICT_PROP'];
		$this->storage['IBLOCK_PARAMS'][$params['IBLOCK_ID']]['LABEL_PROP'] = $params['LABEL_PROP'];
		$this->storage['IBLOCK_PARAMS'][$params['IBLOCK_ID']]['LABEL_PROP_MOBILE'] = $params['LABEL_PROP_MOBILE'];
		$this->storage['IBLOCK_PARAMS'][$params['IBLOCK_ID']]['PROPERTY_CODE_MOBILE'] = $params['PROPERTY_CODE_MOBILE'];
		$this->storage['IBLOCK_PARAMS'][$params['IBLOCK_ID']]['ENLARGE_PROP'] = $params['ENLARGE_PROP'];
		$this->storage['IBLOCK_PARAMS'][$params['IBLOCK_ID']]['OFFERS_ADD_PICT_PROP'] = $params['OFFER_ADD_PICT_PROP'];
		unset($skuTreeProperties);
	}

	public static function getDefaultVariantId()
	{
		$variantId = 0;
		$templateVariantsMap = static::getTemplateVariantsMap();

		if (!empty($templateVariantsMap))
		{
			foreach ($templateVariantsMap as $key => $variant)
			{
				if (isset($variant['DEFAULT']) && $variant['DEFAULT'] === 'Y')
				{
					$variantId = $variant['VARIANT'];
					break;
				}
			}
		}

		return $variantId;
	}

	public static function predictRowVariants($lineElementCount, $pageElementCount)
	{
		if ($pageElementCount <= 0)
		{
			return array();
		}

		$templateVariantsMap = static::getTemplateVariantsMap();

		if (empty($templateVariantsMap))
		{
			return array();
		}

		$variantId = self::getDefaultVariantId();

		foreach ($templateVariantsMap as $key => $variant)
		{
			if ($variant['COUNT'] == $lineElementCount && $variant['ENLARGED_POS'] === false)
			{
				$variantId = $key;
				break;
			}
		}

		return array_fill(
			0,
			ceil($pageElementCount / $templateVariantsMap[$variantId]['COUNT']),
			array('VARIANT' => $variantId, 'BIG_DATA' => false)
		);
	}

	protected function checkTemplateTheme()
	{
		parent::checkTemplateTheme();

		if ($this->isPaginationMode())
		{
			if (isset($this->arResult['NAV_PARAM']) && is_array($this->arResult['NAV_PARAM']))
			{
				$this->arResult['NAV_PARAM']['TEMPLATE_THEME'] = $this->arParams['TEMPLATE_THEME'];
			}

			if (!empty($this->arResult['NAV_RESULT']) && empty($this->arResult['NAV_STRING']))
			{
				/** @var \CBitrixComponent $navComponentObject */
				$this->arResult['NAV_STRING'] = $this->arResult['NAV_RESULT']->GetPageNavStringEx(
					$navComponentObject,
					$this->arParams['PAGER_TITLE'],
					$this->arParams['PAGER_TEMPLATE'],
					$this->arParams['PAGER_SHOW_ALWAYS'],
					$this,
					$this->arResult['NAV_PARAM']
				);
			}
		}
	}

	protected function getTemplateDefaultParams()
	{
		$defaultParams = parent::getTemplateDefaultParams();
		$defaultParams['PRODUCT_BLOCKS_ORDER'] = 'price,props,sku,quantity,buttons';
		$defaultParams['PRODUCT_ROW_VARIANTS'] = array();
		$defaultParams['PROPERTY_CODE_MOBILE'] = array();
		$defaultParams['SHOW_SLIDER'] = 'Y';
		$defaultParams['SLIDER_INTERVAL'] = 3000;
		$defaultParams['ENLARGE_PRODUCT'] = 'STRICT';
		$defaultParams['ENLARGE_PROP'] = '';
		$defaultParams['ADD_TO_BASKET_ACTION'] = 'ADD';
		$defaultParams['MESS_BTN_LAZY_LOAD'] = '';

		return $defaultParams;
	}

	protected function editTemplateData()
	{
		$this->arResult['CURRENCIES'] = $this->getTemplateCurrencies();

		if (!empty($this->arResult['ITEMS']))
		{
			$this->arResult['DEFAULT_PICTURE'] = $this->getTemplateEmptyPreview();
			$this->arResult['SKU_PROPS'] = $this->getTemplateSkuPropList();
			$this->editTemplateItems($this->arResult['ITEMS']);
			$this->sortItemsByTemplateVariants();
		}

		$this->arResult['BIG_DATA'] = [];
		if ($this->request->getRequestMethod() === 'GET')
		{
			$this->arResult['BIG_DATA'] = $this->getBigDataInfo();
		}
	}

	/**
	 * Creating sequence of variants to show
	 */
	protected function sortItemsByTemplateVariants()
	{
		$rows = array();
		$variantsMap = static::getTemplateVariantsMap();
		$isBigData = $this->getAction() === 'bigDataLoad';

		if ($this->arParams['ENLARGE_PRODUCT'] === 'PROP')
		{
			$enlargedIndexMap = $this->getEnlargedIndexMap();
		}

		if (!empty($this->arParams['PRODUCT_ROW_VARIANTS']))
		{
			$showItems = false;

			foreach ($this->arParams['PRODUCT_ROW_VARIANTS'] as $variant)
			{
				if (
					(!$isBigData && !$variant['BIG_DATA'])
					|| ($isBigData && $variant['BIG_DATA'])
				)
				{
					$showItems = true;
					break;
				}
			}
		}
		else
		{
			$showItems = true;
		}

		if ($showItems)
		{
			$variantParam = false;
			$itemsCounter = 0;
			$itemsLength = count($this->arResult['ITEMS']);

			while (($itemsRemaining = $itemsLength - $itemsCounter) > 0)
			{
				if ($variantParam === false)
				{
					$variantParam = reset($this->arParams['PRODUCT_ROW_VARIANTS']);
				}

				//	skip big_data rows on initial load and not_big_data rows on deferred load
				if (!empty($variantParam))
				{
					if (
						$isBigData && !$variantParam['BIG_DATA']
						|| !$isBigData && $variantParam['BIG_DATA']
					)
					{
						$variantParam = next($this->arParams['PRODUCT_ROW_VARIANTS']);
						// if last variant is not suitable - should reset again
						if ($variantParam === false)
						{
							$variantParam = reset($this->arParams['PRODUCT_ROW_VARIANTS']);
						}

						if ($variantParam === false)
							break;
						else
							continue;
					}
				}

				if (
					$variantParam === false
					|| !isset($variantsMap[$variantParam['VARIANT']])
					|| ($variantsMap[$variantParam['VARIANT']]['SHOW_ONLY_FULL'] && $variantsMap[$variantParam['VARIANT']]['COUNT'] > $itemsRemaining)
				)
				{
					// default variant
					$variant = $variantsMap[self::getDefaultVariantId()];
				}
				else
				{
					$variant = $variantsMap[$variantParam['VARIANT']];
				}

				// sorting by property $arResult['ITEMS'] for proper elements enlarge
				if ($this->arParams['ENLARGE_PRODUCT'] === 'PROP' && $variant['ENLARGED_POS'] !== false)
				{
					if (!empty($enlargedIndexMap))
					{
						$overallPos = $itemsCounter + $variant['ENLARGED_POS'];
						$overallPosKey = array_search($overallPos, $enlargedIndexMap);
						if ($overallPosKey === false)
						{
							$closestPos = false;
							$closestPosKey = false;
							$enlargedPosInRange = array_intersect($enlargedIndexMap , range($itemsCounter, $itemsCounter + $variant['COUNT']));

							if (!empty($enlargedPosInRange))
							{
								foreach ($enlargedPosInRange as $key => $posInRange)
								{
									if ($closestPos === false || abs($overallPos - $closestPos) > abs($posInRange - $overallPos))
									{
										$closestPos = $posInRange;
										$closestPosKey = $key;
									}
								}

								$temporary = array($this->arResult['ITEMS'][$closestPos]);
								unset($this->arResult['ITEMS'][$closestPos], $enlargedIndexMap[$closestPosKey]);
								array_splice($this->arResult['ITEMS'], $overallPos, 0, $temporary);
							}
						}
						else
						{
							unset($enlargedIndexMap[$overallPosKey]);
						}
					}
				}

				$rows[] = $variant;
				$itemsCounter += $variant['COUNT'];
				$variantParam = next($this->arParams['PRODUCT_ROW_VARIANTS']);
			}
		}

		$this->arResult['ITEM_ROWS'] = $rows;
	}

	/**
	 * Return array of big data settings.
	 *
	 * @return array
	 */
	protected function getBigDataInfo()
	{
		$rows = array();
		$count = 0;
		$rowsRange = array();
		$variantsMap = static::getTemplateVariantsMap();

		if (!empty($this->arParams['PRODUCT_ROW_VARIANTS']))
		{
			foreach ($this->arParams['PRODUCT_ROW_VARIANTS'] as $key => $row)
			{
				if ($row['BIG_DATA'])
				{
					$rows[$key] = $row;

					if (isset($variantsMap[$row['VARIANT']]))
					{
						$count += (int)$variantsMap[$row['VARIANT']]['COUNT'];
					}

					$rowsRange[] = $count;
				}
			}
		}

		$shownIds = array();
		if (!empty($this->elements))
		{
			foreach ($this->elements as $element)
			{
				$shownIds[] = $element['ID'];
			}
		}

		return array(
			'enabled' => $count > 0,
			'rows' => $rows,
			'count' => $count,
			'rowsRange' => $rowsRange,
			'shownIds' => $shownIds,
			'js' => array(
				'cookiePrefix' => \COption::GetOptionString('main', 'cookie_name', 'BITRIX_SM'),
				'cookieDomain' => Main\Web\Cookie::getCookieDomain(),
				'serverTime' => $count > 0 ? time() : 0,
			),
			'params' => $this->getBigDataServiceRequestParams(($this->arParams['RCM_TYPE'] ?? ''))
		);
	}

	// getting positions of enlarged elements
	protected function getEnlargedIndexMap()
	{
		$enlargedIndexMap = array();

		foreach ($this->arResult['ITEMS'] as $key => $item)
		{
			if ($item['ENLARGED'] === 'Y')
			{
				$enlargedIndexMap[] = $key;
			}
		}

		return $enlargedIndexMap;
	}

	public static function getTemplateVariantsMap()
	{
		return array(
			array(
				'VARIANT' => 0,
				'TYPE' => 'CARD',
				'COLS' => 1,
				'CLASS' => 'product-item-list-col-1',
				'CODE' => '1',
				'ENLARGED_POS' => false,
				'SHOW_ONLY_FULL' => false,
				'COUNT' => 1,
				'DEFAULT' => 'N'
			),
			array(
				'VARIANT' => 1,
				'TYPE' => 'CARD',
				'COLS' => 2,
				'CLASS' => 'product-item-list-col-2',
				'CODE' => '2',
				'ENLARGED_POS' => false,
				'SHOW_ONLY_FULL' => false,
				'COUNT' => 2,
				'DEFAULT' => 'N'
			),
			array(
				'VARIANT' => 2,
				'TYPE' => 'CARD',
				'COLS' => 3,
				'CLASS' => 'product-item-list-col-3',
				'CODE' => '3',
				'ENLARGED_POS' => false,
				'SHOW_ONLY_FULL' => false,
				'COUNT' => 3,
				'DEFAULT' => 'Y'
			),
			array(
				'VARIANT' => 3,
				'TYPE' => 'CARD',
				'COLS' => 4,
				'CLASS' => 'product-item-list-col-4',
				'CODE' => '4',
				'ENLARGED_POS' => false,
				'SHOW_ONLY_FULL' => false,
				'COUNT' => 4,
				'DEFAULT' => 'N'
			),
			array(
				'VARIANT' => 4,
				'TYPE' => 'CARD',
				'COLS' => 4,
				'CLASS' => 'product-item-list-col-1-4',
				'CODE' => '1-4',
				'ENLARGED_POS' => 0,
				'SHOW_ONLY_FULL' => false,
				'COUNT' => 5,
				'DEFAULT' => 'N'
			),
			array(
				'VARIANT' => 5,
				'TYPE' => 'CARD',
				'COLS' => 4,
				'CLASS' => 'product-item-list-col-4-1',
				'CODE' => '4-1',
				'ENLARGED_POS' => 4,
				'SHOW_ONLY_FULL' => true,
				'COUNT' => 5,
				'DEFAULT' => 'N'
			),
			array(
				'VARIANT' => 6,
				'TYPE' => 'CARD',
				'COLS' => 6,
				'CLASS' => 'product-item-list-col-6',
				'CODE' => '6',
				'ENLARGED_POS' => false,
				'SHOW_ONLY_FULL' => false,
				'COUNT' => 6,
				'DEFAULT' => 'N'
			),
			array(
				'VARIANT' => 7,
				'TYPE' => 'CARD',
				'COLS' => 6,
				'CLASS' => 'product-item-list-col-1-6',
				'CODE' => '1-6',
				'ENLARGED_POS' => 0,
				'SHOW_ONLY_FULL' => false,
				'COUNT' => 7,
				'DEFAULT' => 'N'
			),
			array(
				'VARIANT' => 8,
				'TYPE' => 'CARD',
				'COLS' => 6,
				'CLASS' => 'product-item-list-col-6-1',
				'CODE' => '6-1',
				'ENLARGED_POS' => 6,
				'SHOW_ONLY_FULL' => true,
				'COUNT' => 7,
				'DEFAULT' => 'N'
			),
			array(
				'VARIANT' => 9,
				'TYPE' => 'LINE',
				'COLS' => 1,
				'CLASS' => 'product-item-line-list',
				'CODE' => 'line',
				'ENLARGED_POS' => false,
				'SHOW_ONLY_FULL' => false,
				'COUNT' => 1,
				'DEFAULT' => 'N'
			)
		);
	}

	public function getTemplateSkuPropList()
	{
		$skuPropList = array();

		if ($this->arResult['MODULES']['catalog'] && !empty($this->storage['IBLOCK_PARAMS']))
		{
			$elementIndex = array_keys($this->elements);

			foreach ($this->storage['IBLOCK_PARAMS'] as $iblockId => $iblockParams)
			{
				$skuPropList[$iblockId] = array();
				$sku = \CCatalogSku::GetInfoByProductIBlock($iblockId);
				$boolSku = !empty($sku) && is_array($sku);

				if ($boolSku && !empty($iblockParams['OFFERS_TREE_PROPS']) && $this->arParams['PRODUCT_DISPLAY_MODE'] === 'Y')
				{
					$skuPropList[$iblockId] = \CIBlockPriceTools::getTreeProperties(
						$sku,
						$iblockParams['OFFERS_TREE_PROPS'],
						array(
							'PICT' => $this->arResult['DEFAULT_PICTURE'],
							'NAME' => '-'
						)
					);

					if (!empty($skuPropList[$iblockId]))
					{
						if (!empty($this->productWithOffers[$iblockId]))
						{
							$skuPropIds = array();
							foreach ($skuPropList[$iblockId] as $property)
							{
								$skuPropIds[$property['CODE']] = array(
									'ID' => $property['ID'],
									'CODE' => $property['CODE'],
									'PROPERTY_TYPE' => $property['PROPERTY_TYPE'],
									'USER_TYPE' => $property['USER_TYPE']
								);
							}
							unset($property);

							$needValues = array();
							foreach ($elementIndex as $index)
							{
								if ($this->elements[$index]['IBLOCK_ID'] != $iblockId)
									continue;
								if ($this->elements[$index]['PRODUCT']['TYPE'] != Catalog\ProductTable::TYPE_SKU)
									continue;
								if (empty($this->elements[$index]['OFFERS']))
									continue;
								foreach ($this->elements[$index]['OFFERS'] as $offer)
								{
									foreach ($skuPropIds as $property)
									{
										if (isset($offer['DISPLAY_PROPERTIES'][$property['CODE']]))
										{
											if (!isset($needValues[$property['ID']]))
												$needValues[$property['ID']] = array();
											$valueId = ($property['PROPERTY_TYPE'] == Iblock\PropertyTable::TYPE_LIST
												? $offer['DISPLAY_PROPERTIES'][$property['CODE']]['VALUE_ENUM_ID']
												: $offer['DISPLAY_PROPERTIES'][$property['CODE']]['VALUE']
											);
											$needValues[$property['ID']][$valueId] = $valueId;
											unset($valueId);
										}
									}
									unset($property);
								}
								unset($offer);
							}
							unset($index);

							if (!empty($needValues))
								\CIBlockPriceTools::getTreePropertyValues($skuPropList[$iblockId], $needValues);
							unset($needValues);

							unset($skuPropIds);
						}
					}
					else
					{
						$this->arParams['PRODUCT_DISPLAY_MODE'] = 'N';
					}
				}
			}
		}

		return $skuPropList;
	}

	protected function editTemplateItems(&$items)
	{
		$enableCompatible = $this->isEnableCompatible();
		foreach ($items as $key => &$item)
		{
			$iblockParams = $this->storage['IBLOCK_PARAMS'][$item['IBLOCK_ID']];

			if (!isset($item['CATALOG_QUANTITY']))
			{
				$item['CATALOG_QUANTITY'] = 0;
			}

			$item['CATALOG_QUANTITY'] = $item['CATALOG_QUANTITY'] > 0 && is_float($item['ITEM_MEASURE_RATIOS'][$item['ITEM_MEASURE_RATIO_SELECTED']]['RATIO'])
				? (float)$item['CATALOG_QUANTITY']
				: (int)$item['CATALOG_QUANTITY'];

			$item['CATALOG'] = false;
			$item['CATALOG_SUBSCRIPTION'] = ($item['CATALOG_SUBSCRIPTION'] ?? '') === 'Y' ? 'Y' : 'N';

			$item['BIG_DATA'] = $this->getAction() === 'bigDataLoad';

			\CIBlockPriceTools::getLabel($item, $iblockParams['LABEL_PROP']);
			$item['LABEL_PROP_MOBILE'] = $iblockParams['LABEL_PROP_MOBILE'];
			$item['PROPERTY_CODE_MOBILE'] = $iblockParams['PROPERTY_CODE_MOBILE'];
			static::checkEnlargedData($item, $iblockParams['ENLARGE_PROP']);

			if ($this->arParams['SHOW_SLIDER'] === 'Y')
			{
				$this->editTemplateProductSlider($item, $item['IBLOCK_ID'], 5, true, array($this->arResult['DEFAULT_PICTURE']));
			}

			$this->editTemplateProductPictures($item);
			$this->editTemplateCatalogInfo($item);

			if ($item['CATALOG'] && !empty($item['OFFERS']))
			{
				if ($this->arParams['PRODUCT_DISPLAY_MODE'] === 'Y')
				{
					$this->editTemplateOfferProps($item);
					$this->editTemplateJsOffers($item);
				}

				if ($this->arParams['CALCULATE_SKU_MIN_PRICE'] || $this->arParams['PRODUCT_DISPLAY_MODE'] !== 'Y')
				{
					$baseCurrency = '';
					if ($this->arResult['MODULES']['catalog'] && !isset($this->arResult['CONVERT_CURRENCY']['CURRENCY_ID']))
					{
						$baseCurrency = Currency\CurrencyManager::getBaseCurrency();
					}

					$currency = $this->arResult['CONVERT_CURRENCY']['CURRENCY_ID'] ?? $baseCurrency;

					$item['ITEM_START_PRICE'] = null;
					$item['ITEM_START_PRICE_SELECTED'] = null;
					if ($enableCompatible)
						$item['MIN_PRICE'] = false;

					$minPrice = null;
					$minPriceIndex = null;
					foreach (array_keys($item['OFFERS']) as $index)
					{
						if (!$item['OFFERS'][$index]['CAN_BUY'] || $item['OFFERS'][$index]['ITEM_PRICE_SELECTED'] === null)
							continue;

						$currentPrice = $item['OFFERS'][$index]['ITEM_PRICES'][$item['OFFERS'][$index]['ITEM_PRICE_SELECTED']];
						if ($currentPrice['CURRENCY'] != $currency)
						{
							$priceScale = \CCurrencyRates::ConvertCurrency(
								$currentPrice['RATIO_PRICE'],
								$currentPrice['CURRENCY'],
								$currency
							);
						}
						else
						{
							$priceScale = $currentPrice['RATIO_PRICE'];
						}
						if ($minPrice === null || $minPrice > $priceScale)
						{
							$minPrice = $priceScale;
							$minPriceIndex = $index;
						}
						unset($priceScale, $currentPrice);
					}
					unset($index);

					if ($minPriceIndex !== null)
					{
						$minOffer = $item['OFFERS'][$minPriceIndex];
						$item['ITEM_START_PRICE_SELECTED'] = $minPriceIndex;
						$item['ITEM_START_PRICE'] = $minOffer['ITEM_PRICES'][$minOffer['ITEM_PRICE_SELECTED']];
						if ($enableCompatible)
						{
							$item['MIN_PRICE'] = array(
								'CATALOG_MEASURE_RATIO' => $minOffer['ITEM_MEASURE_RATIOS'][$minOffer['ITEM_MEASURE_RATIO_SELECTED']]['RATIO'],
								'CATALOG_MEASURE' => $minOffer['ITEM_MEASURE']['ID'],
								'CATALOG_MEASURE_NAME' => $minOffer['ITEM_MEASURE']['TITLE'],
								'~CATALOG_MEASURE_NAME' => $minOffer['ITEM_MEASURE']['~TITLE'],
								'VALUE' => $item['ITEM_START_PRICE']['RATIO_BASE_PRICE'],
								'DISCOUNT_VALUE' => $item['ITEM_START_PRICE']['RATIO_PRICE'],
								'PRINT_VALUE' => $item['ITEM_START_PRICE']['PRINT_RATIO_BASE_PRICE'],
								'PRINT_DISCOUNT_VALUE' => $item['ITEM_START_PRICE']['PRINT_RATIO_PRICE'],
								'DISCOUNT_DIFF' => $item['ITEM_START_PRICE']['RATIO_DISCOUNT'],
								'PRINT_DISCOUNT_DIFF' => $item['ITEM_START_PRICE']['PRINT_RATIO_DISCOUNT'],
								'DISCOUNT_DIFF_PERCENT' => $item['ITEM_START_PRICE']['PERCENT'],
								'CURRENCY' => $item['ITEM_START_PRICE']['CURRENCY']
							);
						}
						unset($minOffer);
					}
					unset($minPriceIndex, $minPrice);

					unset($baseCurrency, $currency);
				}
			}

			if (
				$this->arResult['MODULES']['catalog']
				&& $item['CATALOG']
				&& (
					$item['CATALOG_TYPE'] == Catalog\ProductTable::TYPE_PRODUCT
					|| $item['CATALOG_TYPE'] == Catalog\ProductTable::TYPE_SET
				)
			)
			{
				if ($enableCompatible)
				{
					if ($item['ITEM_PRICE_SELECTED'] === null)
					{
						$item['RATIO_PRICE'] = null;
						$item['MIN_BASIS_PRICE'] = null;
					}
					else
					{
						$itemPrice = $item['ITEM_PRICES'][$item['ITEM_PRICE_SELECTED']];
						$item['RATIO_PRICE'] = array(
							'VALUE' => $itemPrice['RATIO_BASE_PRICE'],
							'DISCOUNT_VALUE' => $itemPrice['RATIO_PRICE'],
							'PRINT_VALUE' => $itemPrice['PRINT_RATIO_BASE_PRICE'],
							'PRINT_DISCOUNT_VALUE' => $itemPrice['PRINT_RATIO_PRICE'],
							'DISCOUNT_DIFF' => $itemPrice['RATIO_DISCOUNT'],
							'PRINT_DISCOUNT_DIFF' => $itemPrice['PRINT_RATIO_DISCOUNT'],
							'DISCOUNT_DIFF_PERCENT' => $itemPrice['PERCENT'],
							'CURRENCY' => $itemPrice['CURRENCY']
						);
						$item['MIN_BASIS_PRICE'] = array(
							'VALUE' => $itemPrice['BASE_PRICE'],
							'DISCOUNT_VALUE' => $itemPrice['PRICE'],
							'PRINT_VALUE' => $itemPrice['PRINT_BASE_PRICE'],
							'PRINT_DISCOUNT_VALUE' => $itemPrice['PRINT_PRICE'],
							'DISCOUNT_DIFF' => $itemPrice['DISCOUNT'],
							'PRINT_DISCOUNT_DIFF' => $itemPrice['PRINT_DISCOUNT'],
							'DISCOUNT_DIFF_PERCENT' => $itemPrice['PERCENT'],
							'CURRENCY' => $itemPrice['CURRENCY']
						);
						unset($itemPrice);
					}
				}
			}

			if (!empty($item['DISPLAY_PROPERTIES']))
			{
				foreach ($item['DISPLAY_PROPERTIES'] as $propKey => $displayProp)
				{
					{
						if ($displayProp['PROPERTY_TYPE'] === 'F')
						{
							unset($item['DISPLAY_PROPERTIES'][$propKey]);
						}
					}
				}
			}

			$item['LAST_ELEMENT'] = 'N';
		}

		end($items);
		$items[key($items)]['LAST_ELEMENT'] = 'Y';
	}

	protected function editTemplateProductPictures(&$item)
	{
		$iblockParams = $this->storage['IBLOCK_PARAMS'][$item['IBLOCK_ID']];
		$productPictures = \CIBlockPriceTools::getDoublePicturesForItem($item, $iblockParams['ADD_PICT_PROP']);

		if (empty($productPictures['PICT']))
		{
			$productPictures['PICT'] = $this->arResult['DEFAULT_PICTURE'];
		}

		if (empty($productPictures['SECOND_PICT']))
		{
			$productPictures['SECOND_PICT'] = $productPictures['PICT'];
		}

		$item['PREVIEW_PICTURE'] = $productPictures['PICT'];
		$item['PREVIEW_PICTURE_SECOND'] = $productPictures['SECOND_PICT'];
		$item['SECOND_PICT'] = true;
		$item['PRODUCT_PREVIEW'] = $productPictures['PICT'];
		$item['PRODUCT_PREVIEW_SECOND'] = $productPictures['SECOND_PICT'];
	}

	protected function editTemplateJsOffers(&$item)
	{
		$matrix = array();
		$boolSkuDisplayProperties = false;
		$intSelected = -1;

		foreach ($item['OFFERS'] as $offerKey => $offer)
		{
			if ($item['OFFER_ID_SELECTED'] > 0)
			{
				$foundOffer = ($item['OFFER_ID_SELECTED'] == $offer['ID']);
			}
			else
			{
				$foundOffer = $offer['CAN_BUY'];
			}

			if ($foundOffer && $intSelected == -1)
			{
				$intSelected = $offerKey;
			}

			unset($foundOffer);

			$skuProps = false;
			if (!empty($offer['DISPLAY_PROPERTIES']))
			{
				$boolSkuDisplayProperties = true;
				$skuProps = array();
				foreach ($offer['DISPLAY_PROPERTIES'] as $oneProp)
				{
					if ($oneProp['PROPERTY_TYPE'] === 'F')
						continue;

					$skuProps[] = array(
						'CODE' => $oneProp['CODE'],
						'NAME' => $oneProp['NAME'],
						'VALUE' => $oneProp['DISPLAY_VALUE']
					);
				}
				unset($oneProp);
			}

			$ratioSelectedIndex = $offer['ITEM_MEASURE_RATIO_SELECTED'];
			$oneRow = array(
				'ID' => $offer['ID'],
				'NAME' => ($offer['~NAME'] ?? ''),
				'TREE' => $offer['TREE'],
				'DISPLAY_PROPERTIES' => $skuProps,

				// compatible prices
				'PRICE' => ($offer['RATIO_PRICE'] ?? $offer['MIN_PRICE'] ?? 0),
				'BASIS_PRICE' => ($offer['MIN_PRICE'] ?? 0),

				// new prices
				'ITEM_PRICE_MODE' => $offer['ITEM_PRICE_MODE'],
				'ITEM_PRICES' => $offer['ITEM_PRICES'],
				'ITEM_PRICE_SELECTED' => $offer['ITEM_PRICE_SELECTED'],
				'ITEM_QUANTITY_RANGES' => $offer['ITEM_QUANTITY_RANGES'],
				'ITEM_QUANTITY_RANGE_SELECTED' => $offer['ITEM_QUANTITY_RANGE_SELECTED'],
				'ITEM_MEASURE_RATIOS' => $offer['ITEM_MEASURE_RATIOS'],
				'ITEM_MEASURE_RATIO_SELECTED' => $ratioSelectedIndex,
				'SECOND_PICT' => $offer['SECOND_PICT'],
				'OWNER_PICT' => $offer['OWNER_PICT'],
				'PREVIEW_PICTURE' => $offer['PREVIEW_PICTURE'],
				'PREVIEW_PICTURE_SECOND' => $offer['PREVIEW_PICTURE_SECOND'],
				'CHECK_QUANTITY' => $offer['CHECK_QUANTITY'],
				'MAX_QUANTITY' => $offer['PRODUCT']['QUANTITY'],
				'STEP_QUANTITY' => $offer['ITEM_MEASURE_RATIOS'][$ratioSelectedIndex]['RATIO'], // deprecated
				'QUANTITY_FLOAT' => is_float($offer['ITEM_MEASURE_RATIOS'][$ratioSelectedIndex]['RATIO']), //deprecated
				'MEASURE' => $offer['ITEM_MEASURE']['TITLE'],
				'CAN_BUY' => $offer['CAN_BUY'],
				'CATALOG_SUBSCRIBE' => $offer['PRODUCT']['SUBSCRIBE']
			);
			unset($ratioSelectedIndex);

			if (isset($offer['MORE_PHOTO_COUNT']) && $offer['MORE_PHOTO_COUNT'] > 0)
			{
				$oneRow['MORE_PHOTO'] = $offer['MORE_PHOTO'];
				$oneRow['MORE_PHOTO_COUNT'] = $offer['MORE_PHOTO_COUNT'];
			}

			$matrix[$offerKey] = $oneRow;
		}

		if ($intSelected == -1)
		{
			$intSelected = 0;
		}

		if (!$matrix[$intSelected]['OWNER_PICT'])
		{
			$item['PREVIEW_PICTURE'] = $matrix[$intSelected]['PREVIEW_PICTURE'];
			$item['PREVIEW_PICTURE_SECOND'] = $matrix[$intSelected]['PREVIEW_PICTURE_SECOND'];
		}

		$item['JS_OFFERS'] = $matrix;
		$item['OFFERS_SELECTED'] = $intSelected;
		$item['OFFERS_PROPS_DISPLAY'] = $boolSkuDisplayProperties;
	}

	protected function editTemplateOfferProps(&$item)
	{
		$matrix = array();
		$newOffers = array();
		$double = array();
		$item['OFFERS_PROP'] = false;
		$item['SKU_TREE_VALUES'] = array();

		$iblockParams = $this->storage['IBLOCK_PARAMS'][$item['IBLOCK_ID']];
		$skuPropList = [];
		if (isset($this->arResult['SKU_PROPS'][$item['IBLOCK_ID']]))
		{
			$skuPropList = $this->arResult['SKU_PROPS'][$item['IBLOCK_ID']];
		}
		$skuPropIds = array_keys($skuPropList);
		$matrixFields = array_fill_keys($skuPropIds, false);

		foreach ($item['OFFERS'] as $offerKey => $offer)
		{
			$offer['ID'] = (int)$offer['ID'];

			if (isset($double[$offer['ID']]))
				continue;

			$row = array();
			foreach ($skuPropIds as $code)
			{
				$row[$code] = $this->getTemplatePropCell($code, $offer, $matrixFields, $skuPropList);
			}

			$matrix[$offerKey] = $row;

			\CIBlockPriceTools::clearProperties($offer['DISPLAY_PROPERTIES'], $iblockParams['OFFERS_TREE_PROPS']);
			\CIBlockPriceTools::setRatioMinPrice($offer, false);

			if ($this->arParams['SHOW_SLIDER'] === 'Y')
			{
				$this->editTemplateOfferSlider($offer, $item['IBLOCK_ID'], 5, true, $item['MORE_PHOTO']);
			}

			$offerPictures = \CIBlockPriceTools::getDoublePicturesForItem($offer, $iblockParams['OFFERS_ADD_PICT_PROP']);
			$offer['OWNER_PICT'] = empty($offerPictures['PICT']);
			$offer['PREVIEW_PICTURE'] = false;
			$offer['PREVIEW_PICTURE_SECOND'] = false;
			$offer['SECOND_PICT'] = true;

			if (!$offer['OWNER_PICT'])
			{
				if (empty($offerPictures['SECOND_PICT']))
				{
					$offerPictures['SECOND_PICT'] = $offerPictures['PICT'];
				}

				$offer['PREVIEW_PICTURE'] = $offerPictures['PICT'];
				$offer['PREVIEW_PICTURE_SECOND'] = $offerPictures['SECOND_PICT'];
			}

			if ($iblockParams['OFFERS_ADD_PICT_PROP'] != '' && isset($offer['DISPLAY_PROPERTIES'][$iblockParams['OFFERS_ADD_PICT_PROP']]))
			{
				unset($offer['DISPLAY_PROPERTIES'][$iblockParams['OFFERS_ADD_PICT_PROP']]);
			}
			$offer['TREE'] = [];

			$double[$offer['ID']] = true;
			$newOffers[$offerKey] = $offer;
		}

		$item['OFFERS'] = $newOffers;

		$usedFields = array();
		$sortFields = array();

		$matrixKeys = array_keys($matrix);
		foreach ($skuPropIds as $propCode)
		{
			$boolExist = $matrixFields[$propCode];
			foreach ($matrixKeys as $offerKey)
			{
				if ($boolExist)
				{
					$propId = $this->arResult['SKU_PROPS'][$item['IBLOCK_ID']][$propCode]['ID'];
					$value = $matrix[$offerKey][$propCode]['VALUE'];

					if (!isset($item['SKU_TREE_VALUES'][$propId]))
					{
						$item['SKU_TREE_VALUES'][$propId] = array();
					}

					$item['SKU_TREE_VALUES'][$propId][$value] = true;
					$item['OFFERS'][$offerKey]['TREE']['PROP_'.$propId] = $value;
					$item['OFFERS'][$offerKey]['SKU_SORT_'.$propCode] = $matrix[$offerKey][$propCode]['SORT'];
					$usedFields[$propCode] = true;
					$sortFields['SKU_SORT_'.$propCode] = SORT_NUMERIC;
					unset($value, $propId);
				}
				else
				{
					unset($matrix[$offerKey][$propCode]);
				}
			}
			unset($offerKey);
		}
		unset($propCode, $matrixKeys);

		$item['OFFERS_PROP'] = $usedFields;
		$item['OFFERS_PROP_CODES'] = !empty($usedFields) ? base64_encode(serialize(array_keys($usedFields))) : '';

		Collection::sortByColumn($item['OFFERS'], $sortFields);
	}

	/**
	 * @return void
	 */
	protected function initIblockPropertyFeatures()
	{
		if (!Iblock\Model\PropertyFeature::isEnabledFeatures())
			return;

		foreach (array_keys($this->storage['IBLOCK_PARAMS']) as $iblockId)
		{
			$this->loadDisplayPropertyCodes($iblockId);
			$this->loadBasketPropertyCodes($iblockId);
			$this->loadOfferTreePropertyCodes($iblockId);
		}
		unset($iblockId);
	}

	/**
	 * @param int $iblockId
	 * @return void
	 */
	protected function loadDisplayPropertyCodes($iblockId)
	{
		$list = Iblock\Model\PropertyFeature::getListPageShowPropertyCodes(
			$iblockId,
			['CODE' => 'Y']
		);
		if ($list === null)
			$list = [];
		$this->storage['IBLOCK_PARAMS'][$iblockId]['PROPERTY_CODE'] = $list;
		if ($this->useCatalog)
		{
			$list = Iblock\Model\PropertyFeature::getListPageShowPropertyCodes(
				$this->getOffersIblockId($iblockId),
				['CODE' => 'Y']
			);
			if ($list === null)
				$list = [];
			$this->storage['IBLOCK_PARAMS'][$iblockId]['OFFERS_PROPERTY_CODE'] = $list;
		}
		unset($list);
	}
}