Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/iblock/lib/component/ |
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/iblock/lib/component/base.php |
<?php 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 { public const ACTION_BUY = 'BUY'; public const ACTION_ADD_TO_BASKET = 'ADD2BASKET'; public const ACTION_SUBSCRIBE = 'SUBSCRIBE_PRODUCT'; public const ACTION_ADD_TO_COMPARE = 'ADD_TO_COMPARE_LIST'; public const ACTION_DELETE_FROM_COMPARE = 'DELETE_FROM_COMPARE_LIST'; public const ERROR_TEXT = 1; public const ERROR_404 = 2; public const PARAM_TITLE_MASK = '/^[A-Za-z_][A-Za-z01-9_]*$/'; public const SORT_ORDER_MASK = '/^(asc|desc|nulls)(,asc|,desc|,nulls)?$/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 !$this->cacheUsage; } /** * 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 $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 (!is_array($params)) { $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']) && is_string($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); } $params['AJAX_ID'] = trim((string)($params['AJAX_ID'] ?? '')); $params['CACHE_TIME'] = (int)($params['CACHE_TIME'] ?? 36000000); $params['IBLOCK_ID'] = (int)($params['IBLOCK_ID'] ?? 0); $params['SECTION_ID'] = (int)($params['SECTION_ID'] ?? 0); $params['SECTION_CODE'] = trim((string)($params['SECTION_CODE'] ?? '')); $params['SECTION_URL'] = trim((string)($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'] = trim((string)($params['DETAIL_URL'] ?? '')); $params['BASKET_URL'] = trim((string)($params['BASKET_URL'] ?? '')); if ($params['BASKET_URL'] === '') { $params['BASKET_URL'] = '/personal/basket.php'; } $params['SHOW_SKU_DESCRIPTION'] = $params['SHOW_SKU_DESCRIPTION'] ?? 'N'; $params['HIDE_DETAIL_URL'] = isset($params['HIDE_DETAIL_URL']) && $params['HIDE_DETAIL_URL'] === 'Y'; $params['ACTION_VARIABLE'] = trim((string)($params['ACTION_VARIABLE'] ?? '')); if ($params['ACTION_VARIABLE'] === '' || !preg_match(self::PARAM_TITLE_MASK, $params['ACTION_VARIABLE'])) { $params['ACTION_VARIABLE'] = 'action'; } $params['PRODUCT_ID_VARIABLE'] = trim((string)($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'] = trim((string)($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'] = trim((string)($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'] = trim((string)($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'] = trim((string)($params['COMPARE_PATH'] ?? '')); $params['COMPARE_NAME'] = trim((string)($params['COMPARE_NAME'] ?? '')); if ($params['COMPARE_NAME'] === '') { $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'] = (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'; $params['OFFERS_SORT_FIELD'] = trim((string)($params['OFFERS_SORT_FIELD'] ?? '')); if ($params['OFFERS_SORT_FIELD'] === '') { $params['OFFERS_SORT_FIELD'] = 'sort'; } $params['OFFERS_SORT_ORDER'] = trim((string)($params['OFFERS_SORT_ORDER'] ?? '')); if ( $params['OFFERS_SORT_ORDER'] === '' || !preg_match(self::SORT_ORDER_MASK, $params['OFFERS_SORT_ORDER']) ) { $params['OFFERS_SORT_ORDER'] = 'asc'; } $params['OFFERS_SORT_FIELD2'] = trim((string)($params['OFFERS_SORT_FIELD2'] ?? '')); if ($params['OFFERS_SORT_FIELD2'] === '') { $params['OFFERS_SORT_FIELD2'] = 'id'; } $params['OFFERS_SORT_ORDER2'] = trim((string)($params['OFFERS_SORT_ORDER2'] ?? '')); if ( $params['OFFERS_SORT_ORDER2'] === '' || !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'] ??= ''; if (!is_scalar($params['CURRENCY_ID'])) { $params['CURRENCY_ID'] = ''; } $params['CURRENCY_ID'] = trim((string)$params['CURRENCY_ID']); if ($params['CURRENCY_ID'] === '' || $params['CONVERT_CURRENCY'] === 'N') { $params['CONVERT_CURRENCY'] = 'N'; $params['CURRENCY_ID'] = ''; } $params['OFFERS_LIMIT'] = (int)($params['OFFERS_LIMIT'] ?? 0); if ($params['OFFERS_LIMIT'] < 0) { $params['OFFERS_LIMIT'] = 0; } $params['CACHE_GROUPS'] = trim((string)($params['CACHE_GROUPS'] ?? '')); if ($params['CACHE_GROUPS'] !== 'N') { $params['CACHE_GROUPS'] = 'Y'; } if (isset($params['~PRICE_CODE'])) { $params['PRICE_CODE'] = $params['~PRICE_CODE']; } $params['PRICE_CODE'] ??= []; if (!is_array($params['PRICE_CODE'])) { $params['PRICE_CODE'] = []; } $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'] = (int)($params['SECTION_ELEMENT_ID'] ?? 0); $params['SECTION_ELEMENT_CODE'] = trim((string)($params['SECTION_ELEMENT_CODE'] ?? '')); $params['DEPTH'] = (int)($params['DEPTH'] ?? 0); if (empty($params['SECTION_ID'])) { if ($params['SECTION_CODE'] !== '') { $sectionId = $this->getSectionIdByCode($params['SECTION_CODE'], $params['IBLOCK_ID']); } else { $sectionId = $this->getSectionIdByElement( $params['SECTION_ELEMENT_ID'], $params['SECTION_ELEMENT_CODE'], $params['IBLOCK_ID'] ); } $params['SECTION_ID'] = $sectionId; } } $params['FILTER_IDS'] ??= []; if (!is_array($params['FILTER_IDS'])) { $params['FILTER_IDS'] = [$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'] = Main\Config\Option::get('catalog', 'show_catalog_tab_with_offers') === 'Y'; $this->storage['USE_SALE_DISCOUNTS'] = 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'] = $this->arParams['FILTER'] ?: ['PAYED']; $this->arParams['BY'] ??= ''; $this->arParams['BY'] = $this->arParams['BY'] ?: 'AMOUNT'; $this->arParams['PERIOD'] ??= 0; $this->arParams['PERIOD'] = (int)$this->arParams['PERIOD'] ?: 30; $this->arParams['DEPTH'] ??= 0; $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 return array_slice($ids, 0, $this->arParams['PAGE_ELEMENT_COUNT']); } /** * 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 = '', int $iblockId = 0) { $sectionId = 0; $sectionCode = (string)$sectionCode; if ($sectionCode === '') { return $sectionId; } $sectionFilter = []; if ($iblockId > 0) { $sectionFilter['=IBLOCK_ID'] = $iblockId; } elseif (!empty($this->arParams['IBLOCK_ID'])) { $sectionFilter['@IBLOCK_ID'] = $this->arParams['IBLOCK_ID']; } if (empty($sectionFilter)) { return $sectionId; } $sectionFilter['=IBLOCK.ACTIVE'] = 'Y'; $sectionFilter['=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 = '', int $iblockId = 0) { $sectionId = 0; $elementId = (int)$elementId; $elementCode = (string)$elementCode; $filter = []; if ($iblockId > 0) { $filter['=IBLOCK_ID'] = $iblockId; } elseif (!empty($this->arParams['IBLOCK_ID'])) { $filter['=IBLOCK_ID'] = $this->arParams['IBLOCK_ID']; } if (empty($filter)) { return $sectionId; } 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'] ?? 0; } 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] = [ 'ID' => $row['ID'], 'IBLOCK_ID' => $row['IBLOCK_ID'], ]; } 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($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) { [$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'] = ($element['DATE_ACTIVE_FROM'] ?? null); $element['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); $element['PRODUCT']['USE_OFFERS'] = $element['PRODUCT']['TYPE'] == Catalog\ProductTable::TYPE_SKU; 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, 'USE_OFFERS' => false ); } $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'] ?? null); $element['DELETE_LINK'] = ($buttons['edit']['delete_element']['ACTION_URL'] ?? null); } /** * 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 = max((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; $this->loadMeasureRatios($itemIds); if (empty($this->storage['PRICES_ALLOW'])) return; $enableCompatible = $this->isEnableCompatible(); $quantityList = array_fill_keys($itemIds, array()); $select = array( 'ID', 'PRODUCT_ID', 'CATALOG_GROUP_ID', 'PRICE', 'CURRENCY', 'QUANTITY_FROM', 'QUANTITY_TO', 'PRICE_SCALE' ); 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 (!isset($row['MEASURE_RATIO_ID'])) { $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); 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'] ?? 1; // 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 (isset($this->arParams['PRICE_VAT_SHOW_VALUE']) && $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'] ?? null; $priceRow['PRICE_SCALE'] = \CCurrencyRates::ConvertCurrency( $priceRow['PRICE'], $priceRow['CURRENCY'], $baseCurrency ); $priceRow['BASE_PRICE_SCALE'] = $rawPrice['PRICE_SCALE']; if ( $minimalPrice === null || $minimalPrice['PRICE_SCALE'] > $priceRow['PRICE_SCALE'] ) { $minimalPrice = $priceRow; } elseif ( $minimalPrice['PRICE_SCALE'] == $priceRow['PRICE_SCALE'] && $minimalPrice['BASE_PRICE_SCALE'] > $priceRow['BASE_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; } elseif ( $minimalBuyerPrice['PRICE_SCALE'] == $priceRow['PRICE_SCALE'] && $minimalBuyerPrice['BASE_PRICE_SCALE'] > $priceRow['BASE_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']); unset($minimalPrice['BASE_PRICE_SCALE']); $minimalPriceId = $minimalPrice['PRICE_TYPE_ID']; $prepareFields = array( 'BASE_PRICE', 'PRICE', 'DISCOUNT' ); if (isset($this->arParams['PRICE_VAT_SHOW_VALUE']) && $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, Catalog\ProductTable::TYPE_SERVICE, ) )) { $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->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->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, 'CODE' => 1, $productProperty => 1, 'PREVIEW_PICTURE' => 1, 'DETAIL_PICTURE' => 1, ); if ($this->arParams['SHOW_SKU_DESCRIPTION'] === 'Y') { $offersSelect['PREVIEW_TEXT'] = 1; $offersSelect['DETAIL_TEXT'] = 1; $offersSelect['PREVIEW_TEXT_TYPE'] = 1; $offersSelect['DETAIL_TEXT_TYPE'] = 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 .= ($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); } unset($boolArr, $prop); } unset($pid); } } unset($row); } if (!empty($propertyList)) { \CIBlockFormatProperties::clearCache(); } 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)); } [$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'); \CMain::FinalActions(Main\Web\Json::encode($addResult)); } 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 checkProductIblock(array $product): bool { return true; } 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; } $product = []; 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']) { [$successfulAdd, $errorMsg] = $this->checkProductSection( $productId, $this->arParams['SECTION_ID'], $this->arParams['SECTION_CODE'] ); } } if ($successfulAdd) { if (!$this->checkProductIblock($product)) { $errorMsg = Loc::getMessage('CATALOG_PRODUCT_NOT_FOUND'); $successfulAdd = false; } } 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 { if ($this->arParams['PARTIAL_PRODUCT_PROPERTIES'] !== 'Y') { $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, [ 'USE_MERGE' => $this->isMergeProductWhenAddedBasket() ? 'Y' : 'N', ]); if (!$basketResult->isSuccess()) { $errorMsg = implode('; ', $basketResult->getErrorMessages()); $successfulAdd = false; } unset($basketResult); } } return array($successfulAdd, $errorMsg); } /** * Should merge products when adding to the basket (increase the quantity of products)? * * If not exists parameter 'USE_MERGE_WHEN_ADD_PRODUCT_TO_BASKET' return true * * @return bool */ public function isMergeProductWhenAddedBasket() { return ($this->arParams['USE_MERGE_WHEN_ADD_PRODUCT_TO_BASKET'] ?? 'Y') !== 'N'; } 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', ''), ['allowed_classes' => false]); 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, 'FILE_NAME' => 'no_photo.png', '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(); /* don't change this block, because delayed \CFile::ResizeImageGet is not started in cloud */ echo Main\Web\Json::encode($result); \CMain::FinalActions(); /* block end */ } /** * 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 int|false */ 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 $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) > 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 ($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 */ }