Guest User

bitrix дебилы

a guest
Aug 21st, 2017
2,004
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 125.60 KB | None | 0 0
  1. <?
  2. namespace Bitrix\Iblock\Component;
  3.  
  4. use \Bitrix\Main;
  5. use \Bitrix\Main\Loader;
  6. use \Bitrix\Main\Error;
  7. use \Bitrix\Main\ErrorCollection;
  8. use \Bitrix\Main\Localization\Loc;
  9. use \Bitrix\Currency;
  10. use \Bitrix\Iblock;
  11. use \Bitrix\Catalog;
  12.  
  13. /**
  14.  * @global \CUser $USER
  15.  * @global \CMain $APPLICATION
  16.  */
  17.  
  18. Loc::loadMessages(__FILE__);
  19.  
  20. abstract class Base extends \CBitrixComponent
  21. {
  22.     const ACTION_BUY = 'BUY';
  23.     const ACTION_ADD_TO_BASKET = 'ADD2BASKET';
  24.     const ACTION_SUBSCRIBE = 'SUBSCRIBE_PRODUCT';
  25.     const ACTION_ADD_TO_COMPARE = 'ADD_TO_COMPARE_LIST';
  26.     const ACTION_DELETE_FROM_COMPARE = 'DELETE_FROM_COMPARE_LIST';
  27.  
  28.     const ERROR_TEXT = 1;
  29.     const ERROR_404 = 2;
  30.  
  31.     const PARAM_TITLE_MASK = '/^[A-Za-z_][A-Za-z01-9_]*$/';
  32.     const SORT_ORDER_MASK = '/^(asc|desc|nulls)(,asc|,desc|,nulls){0,1}$/i';
  33.  
  34.     private $action = '';
  35.     private $cacheUsage = true;
  36.     private $extendedMode = true;
  37.     /** @var ErrorCollection */
  38.     protected $errorCollection;
  39.  
  40.     protected $selectFields = array();
  41.     protected $filterFields = array();
  42.     protected $sortFields = array();
  43.  
  44.     /** @var array Array of ids to show directly */
  45.     protected $productIds = array();
  46.  
  47.     protected $productIdMap = array();
  48.     protected $iblockProducts = array();
  49.     protected $elements = array();
  50.     protected $elementLinks = array();
  51.  
  52.     protected $productWithOffers = array();
  53.     protected $productWithPrices = array();
  54.  
  55.     protected $globalFilter = array();
  56.     protected $navParams = false;
  57.  
  58.     protected $useCatalog = false;
  59.     protected $isIblockCatalog = false;
  60.     protected $useDiscountCache = false;
  61.  
  62.     /** @var bool Fill old format $arResult and enable deprecated functionality for existing components (catalog.section, catalog.element, etc) */
  63.     protected $compatibleMode = false;
  64.  
  65.     protected $oldData = array();
  66.     /** @var array Item prices (new format) */
  67.     protected $prices = array();
  68.     protected $calculatePrices = array();
  69.  
  70.     protected $measures = array();
  71.     protected $ratios = array();
  72.     protected $quantityRanges = array();
  73.  
  74.     protected $storage = array();
  75.     protected $recommendationIdToProduct = array();
  76.  
  77.     /**
  78.      * Base constructor.
  79.      * @param \CBitrixComponent|null $component     Component object if exists.
  80.      */
  81.     public function __construct($component = null)
  82.     {
  83.         parent::__construct($component);
  84.         $this->errorCollection = new ErrorCollection();
  85.     }
  86.  
  87.     /**
  88.      * Return current action.
  89.      *
  90.      * @return string
  91.      */
  92.     public function getAction()
  93.     {
  94.         return $this->action;
  95.     }
  96.  
  97.     /**
  98.      * Action setter.
  99.      *
  100.      * @param string $action        Action code.
  101.      * @return void
  102.      */
  103.     protected function setAction($action)
  104.     {
  105.         $this->action = $action;
  106.     }
  107.  
  108.     /**
  109.      * Return true if errors exist.
  110.      *
  111.      * @return bool
  112.      */
  113.     protected function hasErrors()
  114.     {
  115.         return (bool)count($this->errorCollection);
  116.     }
  117.  
  118.     /**
  119.      * Errors processing depending on error codes.
  120.      *
  121.      * @return bool
  122.      */
  123.     protected function processErrors()
  124.     {
  125.         if (!empty($this->errorCollection))
  126.         {
  127.             /** @var Error $error */
  128.             foreach ($this->errorCollection as $error)
  129.             {
  130.                 $code = $error->getCode();
  131.  
  132.                 if ($code == self::ERROR_404)
  133.                 {
  134.                     Tools::process404(
  135.                         trim($this->arParams['MESSAGE_404']) ?: $error->getMessage(),
  136.                         true,
  137.                         $this->arParams['SET_STATUS_404'] === 'Y',
  138.                         $this->arParams['SHOW_404'] === 'Y',
  139.                         $this->arParams['FILE_404']
  140.                     );
  141.                 }
  142.                 elseif ($code == self::ERROR_TEXT)
  143.                 {
  144.                     ShowError($error->getMessage());
  145.                 }
  146.             }
  147.         }
  148.  
  149.         return false;
  150.     }
  151.  
  152.     /**
  153.      * Cache usage setter. Enable it to ignore cache.
  154.      *
  155.      * @param bool $state   Cache usage mode.
  156.      * @return $this
  157.      */
  158.     protected function setCacheUsage($state)
  159.     {
  160.         $this->cacheUsage = (bool)$state;
  161.  
  162.         return $this;
  163.     }
  164.  
  165.     /**
  166.      * Check if cache disabled.
  167.      *
  168.      * @return bool
  169.      */
  170.     public function isCacheDisabled()
  171.     {
  172.         return (bool)$this->cacheUsage === false;
  173.     }
  174.  
  175.     /**
  176.      * Extended mode setter.
  177.      * Enabled - adds result_modifier.php template logic in component class.
  178.      * In both cases(true or false) result_modifier.php will be included.
  179.      *
  180.      * @param bool $state   New extended mode.
  181.      * @return $this
  182.      */
  183.     protected function setExtendedMode($state)
  184.     {
  185.         $this->extendedMode = (bool)$state;
  186.  
  187.         return $this;
  188.     }
  189.  
  190.     /**
  191.      * Check if extended mode is enabled.
  192.      *
  193.      * @return bool
  194.      */
  195.     public function isExtendedMode()
  196.     {
  197.         return (bool)$this->extendedMode;
  198.     }
  199.  
  200.     /**
  201.      * Enable/disable fill old keys in result data and use of outdated settings. Strict use only for catalog.element, .section, .top, etc.
  202.      *
  203.      * @param bool $state       Enable/disable state.
  204.      * @return void
  205.      */
  206.     protected function setCompatibleMode($state)
  207.     {
  208.         $this->compatibleMode = (bool)$state;
  209.     }
  210.  
  211.     /**
  212.      * Return state filling old keys in result data. This method makes no sense for the new components.
  213.      *
  214.      * @return bool
  215.      */
  216.     public function isEnableCompatible()
  217.     {
  218.         return $this->compatibleMode;
  219.     }
  220.  
  221.     /**
  222.      * Return settings script path with modified time postfix.
  223.      *
  224.      * @param string $componentPath     Path to component.
  225.      * @param string $settingsName      Settings name.
  226.      * @return string
  227.      */
  228.     public static function getSettingsScript($componentPath, $settingsName)
  229.     {
  230.         $path = $componentPath.'/settings/'.$settingsName.'/script.js';
  231.         $file = new Main\IO\File(Main\Application::getDocumentRoot().$path);
  232.  
  233.         return $path.'?'.$file->getModificationTime();
  234.     }
  235.  
  236.     /**
  237.      * Processing of component parameters.
  238.      *
  239.      * @param array $params         Raw component parameters values.
  240.      * @return mixed
  241.      */
  242.     public function onPrepareComponentParams($params)
  243.     {
  244.         if (!isset($params['CURRENT_BASE_PAGE']))
  245.         {
  246.             $uri = new Main\Web\Uri($this->request->getRequestUri());
  247.             $uri->deleteParams(Main\HttpRequest::getSystemParameters());
  248.             $params['CURRENT_BASE_PAGE'] = $uri->getUri();
  249.         }
  250.  
  251.         // parent component params for correct template load through ajax
  252.         if (!isset($params['PARENT_NAME']) && $parent = $this->getParent())
  253.         {
  254.             $params['PARENT_NAME'] = $parent->getName();
  255.             $params['PARENT_TEMPLATE_NAME'] = $parent->getTemplateName();
  256.             $params['PARENT_TEMPLATE_PAGE'] = $parent->getTemplatePage();
  257.         }
  258.  
  259.         // save original parameters for further ajax requests
  260.         $this->arResult['ORIGINAL_PARAMETERS'] = $params;
  261.  
  262.         // for AJAX_MODE set original ajax_id from initial load
  263.         if (isset($params['AJAX_MODE']) && $params['AJAX_MODE'] === 'Y')
  264.         {
  265.             $ajaxId = $this->request->get('AJAX_ID');
  266.             if (!empty($ajaxId))
  267.             {
  268.                 $params['AJAX_ID'] = $ajaxId;
  269.             }
  270.             unset($ajaxId);
  271.         }
  272.  
  273.         if (!isset($params['CACHE_TIME']))
  274.         {
  275.             $params['CACHE_TIME'] = 36000000;
  276.         }
  277.  
  278.         $params['IBLOCK_ID'] = isset($params['IBLOCK_ID']) ? (int)$params['IBLOCK_ID'] : 0;
  279.         $params['SECTION_ID'] = isset($params['SECTION_ID']) ? (int)$params['SECTION_ID'] : 0;
  280.         $params['SECTION_CODE'] = isset($params['SECTION_CODE']) ? trim($params['SECTION_CODE']) : '';
  281.         $params['SECTION_URL'] = isset($params['SECTION_URL']) ? trim($params['SECTION_URL']) : '';
  282.         $params['DETAIL_URL'] = isset($params['DETAIL_URL']) ? trim($params['DETAIL_URL']) : '';
  283.         $params['BASKET_URL'] = isset($params['BASKET_URL']) ? trim($params['BASKET_URL']) : '/personal/basket.php';
  284.  
  285.         $params['ACTION_VARIABLE'] = isset($params['ACTION_VARIABLE']) ? trim($params['ACTION_VARIABLE']) : '';
  286.         if ($params['ACTION_VARIABLE'] == '' || !preg_match(self::PARAM_TITLE_MASK, $params['ACTION_VARIABLE']))
  287.         {
  288.             $params['ACTION_VARIABLE'] = 'action';
  289.         }
  290.  
  291.         $params['PRODUCT_ID_VARIABLE'] = isset($params['PRODUCT_ID_VARIABLE']) ? trim($params['PRODUCT_ID_VARIABLE']) : '';
  292.         if ($params['PRODUCT_ID_VARIABLE'] == '' || !preg_match(self::PARAM_TITLE_MASK, $params['PRODUCT_ID_VARIABLE']))
  293.         {
  294.             $params['PRODUCT_ID_VARIABLE'] = 'id';
  295.         }
  296.  
  297.         $params['PRODUCT_QUANTITY_VARIABLE'] = isset($params['PRODUCT_QUANTITY_VARIABLE']) ? trim($params['PRODUCT_QUANTITY_VARIABLE']) : '';
  298.         if ($params['PRODUCT_QUANTITY_VARIABLE'] == '' || !preg_match(self::PARAM_TITLE_MASK, $params['PRODUCT_QUANTITY_VARIABLE']))
  299.         {
  300.             $params['PRODUCT_QUANTITY_VARIABLE'] = 'quantity';
  301.         }
  302.  
  303.         $params['PRODUCT_PROPS_VARIABLE'] = isset($params['PRODUCT_PROPS_VARIABLE']) ? trim($params['PRODUCT_PROPS_VARIABLE']) : '';
  304.         if ($params['PRODUCT_PROPS_VARIABLE'] == '' || !preg_match(self::PARAM_TITLE_MASK, $params['PRODUCT_PROPS_VARIABLE']))
  305.         {
  306.             $params['PRODUCT_PROPS_VARIABLE'] = 'prop';
  307.         }
  308.  
  309.         $params['SET_TITLE'] = $params['SET_TITLE'] !== 'N';
  310.         $params['SET_BROWSER_TITLE'] = isset($params['SET_BROWSER_TITLE']) && $params['SET_BROWSER_TITLE'] === 'N' ? 'N' : 'Y';
  311.         $params['SET_META_KEYWORDS'] = isset($params['SET_META_KEYWORDS']) && $params['SET_META_KEYWORDS'] === 'N' ? 'N' : 'Y';
  312.         $params['SET_META_DESCRIPTION'] = isset($params['SET_META_DESCRIPTION']) && $params['SET_META_DESCRIPTION'] === 'N' ? 'N' : 'Y';
  313.         $params['ADD_SECTIONS_CHAIN'] = isset($params['ADD_SECTIONS_CHAIN']) && $params['ADD_SECTIONS_CHAIN'] === 'Y';
  314.         $params['SET_LAST_MODIFIED'] = isset($params['SET_LAST_MODIFIED']) && $params['SET_LAST_MODIFIED'] === 'Y';
  315.         $params['DISPLAY_COMPARE'] = isset($params['DISPLAY_COMPARE']) && $params['DISPLAY_COMPARE'] === 'Y';
  316.         $params['COMPARE_PATH'] = isset($params['COMPARE_PATH']) ? trim($params['COMPARE_PATH']) : '';
  317.         $params['COMPARE_NAME'] = isset($params['COMPARE_NAME']) ? trim($params['COMPARE_NAME']) : 'CATALOG_COMPARE_LIST';
  318.  
  319.         $params['USE_PRICE_COUNT'] = isset($params['USE_PRICE_COUNT']) && $params['USE_PRICE_COUNT'] === 'Y';
  320.         $params['SHOW_PRICE_COUNT'] = isset($params['SHOW_PRICE_COUNT']) ? (int)$params['SHOW_PRICE_COUNT'] : 1;
  321.         if ($params['SHOW_PRICE_COUNT'] <= 0)
  322.         {
  323.             $params['SHOW_PRICE_COUNT'] = 1;
  324.         }
  325.         $params['FILL_ITEM_ALL_PRICES'] = isset($params['FILL_ITEM_ALL_PRICES']) && $params['FILL_ITEM_ALL_PRICES'] === 'Y';
  326.  
  327.         $params['USE_PRODUCT_QUANTITY'] = isset($params['USE_PRODUCT_QUANTITY']) && $params['USE_PRODUCT_QUANTITY'] === 'Y';
  328.  
  329.         $params['ADD_PROPERTIES_TO_BASKET'] = isset($params['ADD_PROPERTIES_TO_BASKET']) && $params['ADD_PROPERTIES_TO_BASKET'] === 'N' ? 'N' : 'Y';
  330.         if ($params['ADD_PROPERTIES_TO_BASKET'] === 'N')
  331.         {
  332.             $params['PRODUCT_PROPERTIES'] = array();
  333.             $params['OFFERS_CART_PROPERTIES'] = array();
  334.         }
  335.  
  336.         $params['PARTIAL_PRODUCT_PROPERTIES'] = isset($params['PARTIAL_PRODUCT_PROPERTIES']) && $params['PARTIAL_PRODUCT_PROPERTIES'] === 'Y' ? 'Y' : 'N';
  337.  
  338.         if (empty($params['OFFERS_SORT_FIELD']))
  339.         {
  340.             $params['OFFERS_SORT_FIELD'] = 'sort';
  341.         }
  342.  
  343.         if (!preg_match(self::SORT_ORDER_MASK, $params['OFFERS_SORT_ORDER']))
  344.         {
  345.             $params['OFFERS_SORT_ORDER'] = 'asc';
  346.         }
  347.  
  348.         if (empty($params['OFFERS_SORT_FIELD2']))
  349.         {
  350.             $params['OFFERS_SORT_FIELD2'] = 'id';
  351.         }
  352.  
  353.         if (!preg_match(self::SORT_ORDER_MASK, $params['OFFERS_SORT_ORDER2']))
  354.         {
  355.             $params['OFFERS_SORT_ORDER2'] = 'desc';
  356.         }
  357.  
  358.         $params['PRICE_VAT_INCLUDE'] = !(isset($params['PRICE_VAT_INCLUDE']) && $params['PRICE_VAT_INCLUDE'] === 'N');
  359.         $params['CONVERT_CURRENCY'] = isset($params['CONVERT_CURRENCY']) && $params['CONVERT_CURRENCY'] === 'Y' ? 'Y' : 'N';
  360.         $params['CURRENCY_ID'] = isset($params['CURRENCY_ID']) ? trim($params['CURRENCY_ID']) : '';
  361.  
  362.         if ($params['CURRENCY_ID'] === '' || $params['CONVERT_CURRENCY'] === 'N')
  363.         {
  364.             $params['CONVERT_CURRENCY'] = 'N';
  365.             $params['CURRENCY_ID'] = '';
  366.         }
  367.  
  368.         $params['OFFERS_LIMIT'] = isset($params['OFFERS_LIMIT']) ? (int)$params['OFFERS_LIMIT'] : 0;
  369.         if ($params['OFFERS_LIMIT'] < 0)
  370.         {
  371.             $params['OFFERS_LIMIT'] = 0;
  372.         }
  373.  
  374.         $params['CACHE_GROUPS'] = trim($params['CACHE_GROUPS']);
  375.         if ($params['CACHE_GROUPS'] !== 'N')
  376.         {
  377.             $params['CACHE_GROUPS'] = 'Y';
  378.         }
  379.  
  380.         if (!isset($params['PRICE_CODE']) || !is_array($params['PRICE_CODE']))
  381.         {
  382.             $params['PRICE_CODE'] = array();
  383.         }
  384.  
  385.         $params['SHOW_FROM_SECTION'] = isset($params['SHOW_FROM_SECTION']) && $params['SHOW_FROM_SECTION'] === 'Y' ? 'Y' : 'N';
  386.         if ($params['SHOW_FROM_SECTION'] === 'Y')
  387.         {
  388.             $params['SECTION_ELEMENT_ID'] = isset($params['SECTION_ELEMENT_ID']) ? (int)$params['SECTION_ELEMENT_ID'] : 0;
  389.             $params['SECTION_ELEMENT_CODE'] = isset($params['SECTION_ELEMENT_CODE']) ? trim($params['SECTION_ELEMENT_CODE']) : '';
  390.             $params['DEPTH'] = isset($params['DEPTH']) ? (int)$params['DEPTH'] : 0;
  391.  
  392.             if (empty($params['SECTION_ID']))
  393.             {
  394.                 if ($params['SECTION_CODE'] !== '')
  395.                 {
  396.                     $sectionId = $this->getSectionIdByCode($params['SECTION_CODE']);
  397.                 }
  398.                 else
  399.                 {
  400.                     $sectionId = $this->getSectionIdByElement($params['SECTION_ELEMENT_ID'], $params['SECTION_ELEMENT_CODE']);
  401.                 }
  402.  
  403.                 $params['SECTION_ID'] = $sectionId;
  404.             }
  405.         }
  406.  
  407.         if (!isset($params['FILTER_IDS']))
  408.         {
  409.             $params['FILTER_IDS'] = array();
  410.         }
  411.         elseif (!is_array($params['FILTER_IDS']))
  412.         {
  413.             $params['FILTER_IDS'] = array($params['FILTER_IDS']);
  414.         }
  415.  
  416.         return $params;
  417.     }
  418.  
  419.     /**
  420.      * Check necessary modules for component.
  421.      *
  422.      * @return bool
  423.      */
  424.     protected function checkModules()
  425.     {
  426.         $this->useCatalog = Loader::includeModule('catalog');
  427.         $this->storage['MODULES'] = array(
  428.             'iblock' => true,
  429.             'catalog' => $this->useCatalog,
  430.             'currency' => $this->useCatalog
  431.         );
  432.  
  433.         return true;
  434.     }
  435.  
  436.     /**
  437.      * Fill discount cache before price calculation.
  438.      *
  439.      * @return void
  440.      */
  441.     protected function initCatalogDiscountCache()
  442.     {
  443.         if ($this->useCatalog && $this->useDiscountCache && !empty($this->elementLinks))
  444.         {
  445.             foreach ($this->iblockProducts as $iblock => $products)
  446.             {
  447.                 if ($this->storage['USE_SALE_DISCOUNTS'])
  448.                 {
  449.                     Catalog\Discount\DiscountManager::preloadPriceData($products, $this->storage['PRICES_ALLOW']);
  450.                     Catalog\Discount\DiscountManager::preloadProductDataToExtendOrder($products, $this->getUserGroups());
  451.                 }
  452.                 else
  453.                 {
  454.                     \CCatalogDiscount::SetProductSectionsCache($products);
  455.                     \CCatalogDiscount::SetDiscountProductCache($products, array('IBLOCK_ID' => $iblock, 'GET_BY_ID' => 'Y'));
  456.                 }
  457.             }
  458.         }
  459.     }
  460.  
  461.     /**
  462.      * Clear discount cache.
  463.      *
  464.      * @return void
  465.      */
  466.     protected function clearCatalogDiscountCache()
  467.     {
  468.         if ($this->useCatalog && $this->useDiscountCache)
  469.         {
  470.             if (!$this->storage['USE_SALE_DISCOUNTS'])
  471.                 \CCatalogDiscount::ClearDiscountCache(array(
  472.                     'PRODUCT' => true,
  473.                     'SECTIONS' => true,
  474.                     'PROPERTIES' => true
  475.                 ));
  476.         }
  477.     }
  478.  
  479.     /**
  480.      * Check the settings for the output price currency.
  481.      *
  482.      * @return void
  483.      */
  484.     protected function initCurrencyConvert()
  485.     {
  486.         $this->storage['CONVERT_CURRENCY'] = array();
  487.  
  488.         if ($this->arParams['CONVERT_CURRENCY'] === 'Y')
  489.         {
  490.             $correct = false;
  491.             if (Loader::includeModule('currency'))
  492.             {
  493.                 $this->storage['MODULES']['currency'] = true;
  494.                 $correct = Currency\CurrencyManager::isCurrencyExist($this->arParams['CURRENCY_ID']);
  495.             }
  496.             if ($correct)
  497.             {
  498.                 $this->storage['CONVERT_CURRENCY'] = array(
  499.                     'CURRENCY_ID' => $this->arParams['CURRENCY_ID']
  500.                 );
  501.             }
  502.             else
  503.             {
  504.                 $this->arParams['CONVERT_CURRENCY'] = 'N';
  505.                 $this->arParams['CURRENCY_ID'] = '';
  506.             }
  507.             unset($correct);
  508.         }
  509.     }
  510.  
  511.     /**
  512.      * Check offers iblock.
  513.      *
  514.      * @param int $iblockId     Iblock Id.
  515.      * @return bool
  516.      */
  517.     protected function offerIblockExist($iblockId)
  518.     {
  519.         if (empty($this->storage['CATALOGS'][$iblockId]))
  520.             return false;
  521.  
  522.         $catalog = $this->storage['CATALOGS'][$iblockId];
  523.  
  524.         if (empty($catalog['CATALOG_TYPE']))
  525.             return false;
  526.  
  527.         return $catalog['CATALOG_TYPE'] == \CCatalogSku::TYPE_FULL || $catalog['CATALOG_TYPE'] == \CCatalogSku::TYPE_PRODUCT;
  528.     }
  529.  
  530.     /**
  531.      * Load used iblocks info to component storage.
  532.      *
  533.      * @return void
  534.      */
  535.     protected function initCatalogInfo()
  536.     {
  537.         $catalogs = array();
  538.  
  539.         if ($this->useCatalog)
  540.         {
  541.             $this->storage['SHOW_CATALOG_WITH_OFFERS'] = (string)Main\Config\Option::get('catalog', 'show_catalog_tab_with_offers') === 'Y';
  542.             $this->storage['USE_SALE_DISCOUNTS'] = (string)Main\Config\Option::get('sale', 'use_sale_discount_only') === 'Y';
  543.             foreach (array_keys($this->iblockProducts) as $iblockId)
  544.             {
  545.                 $catalog = \CCatalogSku::GetInfoByIBlock($iblockId);
  546.                 if (!empty($catalog) && is_array($catalog))
  547.                 {
  548.                     $this->isIblockCatalog = $this->isIblockCatalog || $catalog['CATALOG_TYPE'] != \CCatalogSku::TYPE_PRODUCT;
  549.                     $this->useDiscountCache = true;
  550.                     $catalogs[$iblockId] = $catalog;
  551.                 }
  552.             }
  553.         }
  554.  
  555.         $this->storage['CATALOGS'] = $catalogs;
  556.     }
  557.  
  558.     /**
  559.      * Load catalog prices in component storage.
  560.      *
  561.      * @return void
  562.      */
  563.     protected function initPrices()
  564.     {
  565.         // This function returns array with prices description and access rights
  566.         // in case catalog module n/a prices get values from element properties
  567.         $this->storage['PRICES'] = \CIBlockPriceTools::GetCatalogPrices(false, $this->arParams['PRICE_CODE']);
  568.         $this->storage['PRICES_ALLOW'] = \CIBlockPriceTools::GetAllowCatalogPrices($this->storage['PRICES']);
  569.         $this->storage['PRICES_CAN_BUY'] = array();
  570.         $this->storage['PRICES_MAP'] = array();
  571.         foreach ($this->storage['PRICES'] as $priceType)
  572.         {
  573.             $this->storage['PRICES_MAP'][$priceType['ID']] = $priceType['CODE'];
  574.             if ($priceType['CAN_BUY'])
  575.                 $this->storage['PRICES_CAN_BUY'][] = $priceType['ID'];
  576.         }
  577.  
  578.         $this->storage['PRICE_TYPES'] = array();
  579.         if ($this->useCatalog)
  580.             $this->storage['PRICE_TYPES'] = \CCatalogGroup::GetListArray();
  581.  
  582.         if ($this->useCatalog && $this->useDiscountCache && !empty($this->storage['PRICES_ALLOW']))
  583.         {
  584.             $this->useDiscountCache = \CIBlockPriceTools::SetCatalogDiscountCache(
  585.                 $this->storage['PRICES_ALLOW'],
  586.                 $this->getUserGroups()
  587.             );
  588.         }
  589.     }
  590.  
  591.     /**
  592.      * Initialize and data process of iblock elements.
  593.      *
  594.      * @return void
  595.      */
  596.     protected function initElementList()
  597.     {
  598.         $this->storage['CURRENCY_LIST'] = array();
  599.         $this->storage['DEFAULT_MEASURE'] = $this->getDefaultMeasure();
  600.  
  601.         $this->initQueryFields();
  602.  
  603.         foreach ($this->iblockProducts as $iblock => $products)
  604.         {
  605.             $elementIterator = $this->getElementList($iblock, $products);
  606.             $iblockElements = $this->getIblockElements($elementIterator);
  607.  
  608.             if (!empty($iblockElements) && !$this->hasErrors())
  609.             {
  610.                 $this->modifyDisplayProperties($iblock, $iblockElements);
  611.                 $this->elements = array_merge($this->elements, array_values($iblockElements));
  612.                 $this->iblockProducts[$iblock] = array_keys($iblockElements);
  613.             }
  614.  
  615.             unset($elementIterator, $iblockElements, $element);
  616.         }
  617.     }
  618.  
  619.     /**
  620.      * Return elements.
  621.      *
  622.      * @param \CIBlockResult $elementIterator       Iterator.
  623.      * @return mixed
  624.      */
  625.     abstract protected function getIblockElements($elementIterator);
  626.  
  627.     /**
  628.      * Sort elements by original position (in case when product ids used in GetList).
  629.      *
  630.      * @return void
  631.      */
  632.     protected function sortElementList()
  633.     {
  634.         if (!empty($this->productIdMap) && is_array($this->productIdMap))
  635.         {
  636.             $sortedElements = array();
  637.  
  638.             foreach (array_keys($this->productIdMap) as $productId)
  639.             {
  640.                 $parentId = $this->productIdMap[$productId];
  641.  
  642.                 foreach ($this->elements as $element)
  643.                 {
  644.                     if ($element['ID'] == $parentId)
  645.                     {
  646.                         $sortedElements[$productId] = $element;
  647.                         break;
  648.                     }
  649.                 }
  650.             }
  651.  
  652.             $this->elements = array_values($sortedElements);
  653.         }
  654.     }
  655.  
  656.     /**
  657.      * Create link to elements for fast access.
  658.      *
  659.      * @return void
  660.      */
  661.     protected function makeElementLinks()
  662.     {
  663.         if (!empty($this->elements))
  664.         {
  665.             foreach ($this->elements as $index => $element)
  666.             {
  667.                 $this->elementLinks[$element['ID']] =& $this->elements[$index];
  668.             }
  669.         }
  670.     }
  671.  
  672.     /**
  673.      * Return array of iblock element ids to show for "initialLoad" action.
  674.      *
  675.      * @return bool|array
  676.      */
  677.     protected function getProductIds()
  678.     {
  679.         return false;
  680.     }
  681.  
  682.     /**
  683.      * Return array of iblock element ids to show for "bigDataLoad" action.
  684.      *
  685.      * @return array
  686.      */
  687.     protected function getBigDataProductIds()
  688.     {
  689.         $shownIds = $this->request->get('shownIds');
  690.         if (!empty($shownIds) && is_array($shownIds))
  691.         {
  692.             $this->arParams['FILTER_IDS'] += $shownIds;
  693.         }
  694.  
  695.         $this->arParams['PAGE_ELEMENT_COUNT'] = $this->request->get('count') ?: 20;
  696.         $this->arParams['FILTER'] = $this->arParams['FILTER'] ?: array('PAYED');
  697.         $this->arParams['BY'] = $this->arParams['BY'] ?: 'AMOUNT';
  698.         $this->arParams['PERIOD'] = (int)$this->arParams['PERIOD'] ?: 30;
  699.         $this->arParams['DEPTH'] = (int)$this->arParams['DEPTH'] ?: 2;
  700.  
  701.         // general filter
  702.         $this->filterFields = $this->getFilter();
  703.         $this->filterFields['IBLOCK_ID'] = $this->arParams['IBLOCK_ID'];
  704.         $this->initPricesQuery();
  705.  
  706.         // try cloud
  707.         $ids = $this->request->get('items') ?: array();
  708.         if (!empty($ids))
  709.         {
  710.             $recommendationId = $this->request->get('rid');
  711.             $ids = $this->filterByParams($ids, $this->arParams['FILTER_IDS']);
  712.  
  713.             foreach ($ids as $id)
  714.             {
  715.                 $this->recommendationIdToProduct[$id] = $recommendationId;
  716.             }
  717.         }
  718.  
  719.         // try bestsellers
  720.         if (Main\Loader::includeModule('sale') && count($ids) < $this->arParams['PAGE_ELEMENT_COUNT'])
  721.         {
  722.             $ids = $this->getBestSellersRecommendation($ids);
  723.         }
  724.  
  725.         // try most viewed
  726.         if ($this->useCatalog && count($ids) < $this->arParams['PAGE_ELEMENT_COUNT'])
  727.         {
  728.             $ids = $this->getMostViewedRecommendation($ids);
  729.         }
  730.  
  731.         // try random
  732.         if (count($ids) < $this->arParams['PAGE_ELEMENT_COUNT'])
  733.         {
  734.             $ids = $this->getRandomRecommendation($ids);
  735.         }
  736.  
  737.         // limit
  738.         $ids = array_slice($ids, 0, $this->arParams['PAGE_ELEMENT_COUNT']);
  739.  
  740.         return $ids;
  741.     }
  742.  
  743.     /**
  744.      * Return recommended best seller products ids.
  745.      *
  746.      * @param array $ids        Products id.
  747.      * @return array
  748.      */
  749.     protected function getBestSellersRecommendation($ids)
  750.     {
  751.         // increase element count
  752.         $this->arParams['PAGE_ELEMENT_COUNT'] = $this->arParams['PAGE_ELEMENT_COUNT'] * 10;
  753.         $bestsellers = $this->getBestSellersProductIds();
  754.         $this->arParams['PAGE_ELEMENT_COUNT'] = $this->arParams['PAGE_ELEMENT_COUNT'] / 10;
  755.  
  756.         if (!empty($bestsellers))
  757.         {
  758.             $recommendationId = 'bestsellers';
  759.             $bestsellers = Main\Analytics\Catalog::getProductIdsByOfferIds($bestsellers);
  760.             $bestsellers = $this->filterByParams($bestsellers, $this->arParams['FILTER_IDS']);
  761.  
  762.             foreach ($bestsellers as $id)
  763.             {
  764.                 if (!isset($this->recommendationIdToProduct[$id]))
  765.                 {
  766.                     $this->recommendationIdToProduct[$id] = $recommendationId;
  767.                 }
  768.             }
  769.  
  770.             $ids = array_unique(array_merge($ids, $bestsellers));
  771.         }
  772.  
  773.         return $ids;
  774.     }
  775.  
  776.     /**
  777.      * Return recommended most viewed products ids.
  778.      *
  779.      * @param array $ids        Products id.
  780.      * @return array
  781.      */
  782.     protected function getMostViewedRecommendation($ids)
  783.     {
  784.         $mostViewed = array();
  785.         $recommendationId = 'mostviewed';
  786.  
  787.         $result = Catalog\CatalogViewedProductTable::getList(array(
  788.             'select' => array(
  789.                 'ELEMENT_ID',
  790.                 new Main\Entity\ExpressionField('SUM_HITS', 'SUM(%s)', 'VIEW_COUNT')
  791.             ),
  792.             'filter' => array(
  793.                 '=SITE_ID' => $this->getSiteId(),
  794.                 '>ELEMENT_ID' => 0,
  795.                 '>DATE_VISIT' => new Main\Type\DateTime(date('Y-m-d H:i:s', strtotime('-30 days')), 'Y-m-d H:i:s')
  796.             ),
  797.             'order' => array('SUM_HITS' => 'DESC'),
  798.             'limit' => $this->arParams['PAGE_ELEMENT_COUNT'] * 10
  799.         ));
  800.         while ($row = $result->fetch())
  801.         {
  802.             $mostViewed[] = $row['ELEMENT_ID'];
  803.         }
  804.         unset($row, $result);
  805.  
  806.         $mostViewed = $this->filterByParams($mostViewed, $this->arParams['FILTER_IDS']);
  807.  
  808.         foreach ($mostViewed as $id)
  809.         {
  810.             if (!isset($this->recommendationIdToProduct[$id]))
  811.             {
  812.                 $this->recommendationIdToProduct[$id] = $recommendationId;
  813.             }
  814.         }
  815.  
  816.         return array_unique(array_merge($ids, $mostViewed));
  817.     }
  818.  
  819.     /**
  820.      * Return random products ids.
  821.      *
  822.      * @param array $ids        Products id.
  823.      * @return array
  824.      */
  825.     protected function getRandomRecommendation($ids)
  826.     {
  827.         $limit = $this->getRecommendationLimit($ids);
  828.  
  829.         if ($limit <= 0)
  830.         {
  831.             return $ids;
  832.         }
  833.  
  834.         $randomIds = array();
  835.         $recommendationId = 'random';
  836.         $filter = $this->filterFields;
  837.  
  838.         $filterIds = array_merge($ids, $this->arParams['FILTER_IDS']);
  839.         if (!empty($filterIds))
  840.         {
  841.             $filter['!ID'] = $filterIds;
  842.         }
  843.  
  844.         if ($this->arParams['SHOW_FROM_SECTION'] === 'Y' && !empty($this->arParams['SECTION_ID']))
  845.         {
  846.             $filter['SECTION_ID'] = $this->arParams['SECTION_ID'];
  847.         }
  848.  
  849.         $elementIterator = \CIBlockElement::GetList(array('RAND' => 'ASC'), $filter, false, array('nTopCount' => $limit), array('ID'));
  850.         while ($element = $elementIterator->Fetch())
  851.         {
  852.             $randomIds[] = $element['ID'];
  853.         }
  854.  
  855.         if (!empty($randomIds))
  856.         {
  857.             $this->setCacheUsage(false);
  858.         }
  859.  
  860.         foreach ($randomIds as $id)
  861.         {
  862.             if (!isset($this->recommendationIdToProduct[$id]))
  863.             {
  864.                 $this->recommendationIdToProduct[$id] = $recommendationId;
  865.             }
  866.         }
  867.  
  868.         return array_merge($ids, $randomIds);
  869.     }
  870.  
  871.     /**
  872.      * Filter correct product ids.
  873.      *
  874.      * @param array $ids                Items ids.
  875.      * @param array $filterIds          Filtered ids.
  876.      * @param bool $useSectionFilter    Check filter by section.
  877.      * @return array
  878.      */
  879.     protected function filterByParams($ids, $filterIds = array(), $useSectionFilter = true)
  880.     {
  881.         if (empty($ids))
  882.         {
  883.             return array();
  884.         }
  885.  
  886.         $ids = array_values(array_unique($ids));
  887.         // remove duplicates of already showed items
  888.         if (!empty($filterIds))
  889.         {
  890.             $ids = array_diff($ids, $filterIds);
  891.         }
  892.  
  893.         if (!empty($ids))
  894.         {
  895.             $filter = $this->filterFields;
  896.             $filter['ID'] = $ids;
  897.  
  898.             $correctIds = array();
  899.             $elementIterator = \CIBlockElement::GetList(array(), $filter, false, false, array('ID'));
  900.             while ($element = $elementIterator->Fetch())
  901.             {
  902.                 $correctIds[] = $element['ID'];
  903.             }
  904.  
  905.             if ($useSectionFilter && !empty($correctIds) && $this->arParams['SHOW_FROM_SECTION'] === 'Y')
  906.             {
  907.                 $correctIds = $this->filterIdBySection(
  908.                     $correctIds,
  909.                     $this->arParams['IBLOCK_ID'],
  910.                     $this->arParams['SECTION_ID'],
  911.                     $this->arParams['PAGE_ELEMENT_COUNT'],
  912.                     $this->arParams['DEPTH']
  913.                 );
  914.             }
  915.  
  916.             $correctIds = array_flip($correctIds);
  917.             // remove invalid items
  918.             foreach ($ids as $key => $id)
  919.             {
  920.                 if (!isset($correctIds[$id]))
  921.                 {
  922.                     unset($ids[$key]);
  923.                 }
  924.             }
  925.  
  926.             return array_values($ids);
  927.         }
  928.         else
  929.         {
  930.             return array();
  931.         }
  932.     }
  933.  
  934.     /**
  935.      * Return section ID by CODE.
  936.      *
  937.      * @param string $sectionCode           Iblock section code.
  938.      * @return int
  939.      */
  940.     protected function getSectionIdByCode($sectionCode = '')
  941.     {
  942.         $sectionId = 0;
  943.         $sectionCode = (string)$sectionCode;
  944.  
  945.         if ($sectionCode === '')
  946.         {
  947.             return $sectionId;
  948.         }
  949.  
  950.         $sectionFilter = array(
  951.             '@IBLOCK_ID' => $this->arParams['IBLOCK_ID'],
  952.             '=IBLOCK.ACTIVE' => 'Y',
  953.             '=CODE' => $sectionCode
  954.         );
  955.         $section = Iblock\SectionTable::getList(array(
  956.             'select' => array('ID'),
  957.             'filter' => $sectionFilter
  958.         ))->fetch();
  959.         if (!empty($section))
  960.         {
  961.             $sectionId = (int)$section['ID'];
  962.         }
  963.  
  964.         return $sectionId;
  965.     }
  966.  
  967.     /**
  968.      * Return section ID by element ID.
  969.      *
  970.      * @param int $elementId                Iblock element id.
  971.      * @param string $elementCode           Iblock element code.
  972.      * @return int
  973.      */
  974.     protected function getSectionIdByElement($elementId, $elementCode = '')
  975.     {
  976.         $sectionId = 0;
  977.         $elementId = (int)$elementId;
  978.         $elementCode = (string)$elementCode;
  979.         $filter = array('=IBLOCK_ID' => $this->arParams['IBLOCK_ID']);
  980.  
  981.         if ($elementId > 0)
  982.         {
  983.             $filter['=ID'] = $elementId;
  984.         }
  985.         elseif ($elementCode !== '')
  986.         {
  987.             $filter['=CODE'] = $elementCode;
  988.         }
  989.         else
  990.         {
  991.             return $sectionId;
  992.         }
  993.  
  994.         $itemIterator = Iblock\ElementTable::getList(array(
  995.             'select' => array('ID', 'IBLOCK_SECTION_ID'),
  996.             'filter' => $filter
  997.         ));
  998.         if ($item = $itemIterator->fetch())
  999.         {
  1000.             $sectionId = (int)$item['IBLOCK_SECTION_ID'];
  1001.         }
  1002.  
  1003.         return $sectionId;
  1004.     }
  1005.  
  1006.     protected function filterIdBySection($elementIds, $iblockId, $sectionId, $limit, $depth = 0)
  1007.     {
  1008.         $map = array();
  1009.  
  1010.         Main\Type\Collection::normalizeArrayValuesByInt($elementIds);
  1011.  
  1012.         if (empty($elementIds))
  1013.             return $map;
  1014.  
  1015.         $iblockId = (int)$iblockId;
  1016.         $sectionId = (int)$sectionId;
  1017.         $limit = (int)$limit;
  1018.         $depth = (int)$depth;
  1019.  
  1020.         if ($iblockId <= 0 ||$depth < 0)
  1021.             return $map;
  1022.  
  1023.         $subSections = array();
  1024.         if ($depth > 0)
  1025.         {
  1026.             $parentSectionId = Catalog\Product\Viewed::getParentSection($sectionId, $depth);
  1027.             if ($parentSectionId !== null)
  1028.             {
  1029.                 $subSections[$parentSectionId] = $parentSectionId;
  1030.             }
  1031.             unset($parentSectionId);
  1032.         }
  1033.  
  1034.         if (empty($subSections) && $sectionId <= 0)
  1035.         {
  1036.             $getListParams = array(
  1037.                 'select' => array('ID'),
  1038.                 'filter' => array(
  1039.                     '@ID' => $elementIds,
  1040.                     '=IBLOCK_ID' => $iblockId,
  1041.                     '=WF_STATUS_ID' => 1,
  1042.                     '=WF_PARENT_ELEMENT_ID' => null
  1043.                 ),
  1044.             );
  1045.             if ($limit > 0)
  1046.             {
  1047.                 $getListParams['limit'] = $limit;
  1048.             }
  1049.  
  1050.             $iterator = Iblock\ElementTable::getList($getListParams);
  1051.         }
  1052.         else
  1053.         {
  1054.             if (empty($subSections))
  1055.             {
  1056.                 $subSections[$sectionId] = $sectionId;
  1057.             }
  1058.  
  1059.             $sectionQuery = new Main\Entity\Query(Iblock\SectionTable::getEntity());
  1060.             $sectionQuery->setTableAliasPostfix('_parent');
  1061.             $sectionQuery->setSelect(array('ID', 'LEFT_MARGIN', 'RIGHT_MARGIN'));
  1062.             $sectionQuery->setFilter(array('@ID' => $subSections));
  1063.  
  1064.             $subSectionQuery = new Main\Entity\Query(Iblock\SectionTable::getEntity());
  1065.             $subSectionQuery->setTableAliasPostfix('_sub');
  1066.             $subSectionQuery->setSelect(array('ID'));
  1067.             $subSectionQuery->setFilter(array('=IBLOCK_ID' => $iblockId));
  1068.             $subSectionQuery->registerRuntimeField(
  1069.                 '',
  1070.                 new Main\Entity\ReferenceField(
  1071.                     'BS',
  1072.                     Main\Entity\Base::getInstanceByQuery($sectionQuery),
  1073.                     array('>=this.LEFT_MARGIN' => 'ref.LEFT_MARGIN', '<=this.RIGHT_MARGIN' => 'ref.RIGHT_MARGIN'),
  1074.                     array('join_type' => 'INNER')
  1075.                 )
  1076.             );
  1077.  
  1078.             $sectionElementQuery = new Main\Entity\Query(Iblock\SectionElementTable::getEntity());
  1079.             $sectionElementQuery->setSelect(array('IBLOCK_ELEMENT_ID'));
  1080.             $sectionElementQuery->setGroup(array('IBLOCK_ELEMENT_ID'));
  1081.             $sectionElementQuery->setFilter(array('=ADDITIONAL_PROPERTY_ID' => null));
  1082.             $sectionElementQuery->registerRuntimeField(
  1083.                 '',
  1084.                 new Main\Entity\ReferenceField(
  1085.                     'BSUB',
  1086.                     Main\Entity\Base::getInstanceByQuery($subSectionQuery),
  1087.                     array('=this.IBLOCK_SECTION_ID' => 'ref.ID'),
  1088.                     array('join_type' => 'INNER')
  1089.                 )
  1090.             );
  1091.  
  1092.             $elementQuery = new Main\Entity\Query(Iblock\ElementTable::getEntity());
  1093.             $elementQuery->setSelect(array('ID'));
  1094.             $elementQuery->setFilter(array('=IBLOCK_ID' => $iblockId, '=WF_STATUS_ID' => 1, '=WF_PARENT_ELEMENT_ID' => null));
  1095.             $elementQuery->registerRuntimeField(
  1096.                 '',
  1097.                 new Main\Entity\ReferenceField(
  1098.                     'BSE',
  1099.                     Main\Entity\Base::getInstanceByQuery($sectionElementQuery),
  1100.                     array('=this.ID' => 'ref.IBLOCK_ELEMENT_ID'),
  1101.                     array('join_type' => 'INNER')
  1102.                 )
  1103.             );
  1104.             if ($limit > 0)
  1105.             {
  1106.                 $elementQuery->setLimit($limit);
  1107.             }
  1108.  
  1109.             $iterator = $elementQuery->exec();
  1110.  
  1111.             unset($elementQuery, $sectionElementQuery, $subSectionQuery, $sectionQuery);
  1112.         }
  1113.  
  1114.         while ($row = $iterator->fetch())
  1115.         {
  1116.             $map[] = $row['ID'];
  1117.         }
  1118.         unset($row, $iterator);
  1119.  
  1120.         return $map;
  1121.     }
  1122.  
  1123.     /**
  1124.      * Return random element ids to fill partially empty space in row when lack of big data elements.
  1125.      * Does not fill rows with no big data elements at all.
  1126.      *
  1127.      * @param array $ids
  1128.      * @return int
  1129.      */
  1130.     protected function getRecommendationLimit($ids)
  1131.     {
  1132.         $limit = 0;
  1133.         $idsCount = count($ids);
  1134.         $rowsRange = $this->request->get('rowsRange');
  1135.  
  1136.         if (!empty($rowsRange))
  1137.         {
  1138.             foreach ($rowsRange as $range)
  1139.             {
  1140.                 $range = (int)$range;
  1141.  
  1142.                 if ($range > $idsCount)
  1143.                 {
  1144.                     $limit = $range - $idsCount;
  1145.                     break;
  1146.                 }
  1147.             }
  1148.         }
  1149.         else
  1150.         {
  1151.             $limit = $this->arParams['PAGE_ELEMENT_COUNT'] - $idsCount;
  1152.         }
  1153.  
  1154.         return $limit;
  1155.     }
  1156.  
  1157.     protected function getBigDataServiceRequestParams($type = '')
  1158.     {
  1159.         $params = array(
  1160.             'uid' => $_COOKIE['BX_USER_ID'],
  1161.             'aid' => Main\Analytics\Counter::getAccountId(),
  1162.             'count' => max($this->arParams['PAGE_ELEMENT_COUNT'] * 2, 30)
  1163.         );
  1164.  
  1165.         // random choices
  1166.         if ($type === 'any_similar')
  1167.         {
  1168.             $possible = array('similar_sell', 'similar_view', 'similar');
  1169.             $type = $possible[array_rand($possible)];
  1170.         }
  1171.         elseif ($type === 'any_personal')
  1172.         {
  1173.             $possible = array('bestsell', 'personal');
  1174.             $type = $possible[array_rand($possible)];
  1175.         }
  1176.         elseif ($type === 'any')
  1177.         {
  1178.             $possible = array('similar_sell', 'similar_view', 'similar', 'bestsell', 'personal');
  1179.             $type = $possible[array_rand($possible)];
  1180.         }
  1181.  
  1182.         // configure
  1183.         switch ($type)
  1184.         {
  1185.             case 'bestsell':
  1186.                 $params['op'] = 'sim_domain_items';
  1187.                 $params['type'] = 'order';
  1188.                 $params['domain'] = Main\Context::getCurrent()->getServer()->getHttpHost();
  1189.                 break;
  1190.             case 'personal':
  1191.                 $params['op'] = 'recommend';
  1192.                 break;
  1193.             case 'similar_sell':
  1194.                 $params['op'] = 'simitems';
  1195.                 $params['eid'] = $this->arParams['RCM_PROD_ID'];
  1196.                 $params['type'] = 'order';
  1197.                 break;
  1198.             case 'similar_view':
  1199.                 $params['op'] = 'simitems';
  1200.                 $params['eid'] = $this->arParams['RCM_PROD_ID'];
  1201.                 $params['type'] = 'view';
  1202.                 break;
  1203.             case 'similar':
  1204.                 $params['op'] = 'simitems';
  1205.                 $params['eid'] = $this->arParams['RCM_PROD_ID'];
  1206.                 break;
  1207.             default:
  1208.                 $params['op'] = 'recommend';
  1209.         }
  1210.  
  1211.         $iblocks = array();
  1212.  
  1213.         if (!empty($this->storage['IBLOCK_PARAMS']))
  1214.         {
  1215.             $iblocks = array_keys($this->storage['IBLOCK_PARAMS']);
  1216.         }
  1217.         else
  1218.         {
  1219.             $iblockList = array();
  1220.             /* catalog */
  1221.             $iblockIterator = Catalog\CatalogIblockTable::getList(array(
  1222.                 'select' => array('IBLOCK_ID', 'PRODUCT_IBLOCK_ID')
  1223.             ));
  1224.             while ($iblock = $iblockIterator->fetch())
  1225.             {
  1226.                 $iblock['IBLOCK_ID'] = (int)$iblock['IBLOCK_ID'];
  1227.                 $iblock['PRODUCT_IBLOCK_ID'] = (int)$iblock['PRODUCT_IBLOCK_ID'];
  1228.                 $iblockList[$iblock['IBLOCK_ID']] = $iblock['IBLOCK_ID'];
  1229.  
  1230.                 if ($iblock['PRODUCT_IBLOCK_ID'] > 0)
  1231.                 {
  1232.                     $iblockList[$iblock['PRODUCT_IBLOCK_ID']] = $iblock['PRODUCT_IBLOCK_ID'];
  1233.                 }
  1234.             }
  1235.  
  1236.             /* iblock */
  1237.             $iblockIterator = Iblock\IblockSiteTable::getList(array(
  1238.                 'select' => array('IBLOCK_ID'),
  1239.                 'filter' => array('@IBLOCK_ID' => $iblockList, '=SITE_ID' => $this->getSiteId())
  1240.             ));
  1241.             while ($iblock = $iblockIterator->fetch())
  1242.             {
  1243.                 $iblocks[] = $iblock['IBLOCK_ID'];
  1244.             }
  1245.         }
  1246.  
  1247.         $params['ib'] = join('.', $iblocks);
  1248.  
  1249.         return $params;
  1250.     }
  1251.  
  1252.     /**
  1253.      * Return best seller product ids.
  1254.      *
  1255.      * @return array
  1256.      */
  1257.     protected function getBestSellersProductIds()
  1258.     {
  1259.         $productIds = array();
  1260.         $filter = $this->getBestSellersFilter();
  1261.  
  1262.         if (!empty($filter))
  1263.         {
  1264.             $productIterator = \CSaleProduct::GetBestSellerList(
  1265.                 $this->arParams['BY'],
  1266.                 array(),
  1267.                 $filter,
  1268.                 $this->arParams['PAGE_ELEMENT_COUNT']
  1269.             );
  1270.             while($product = $productIterator->fetch())
  1271.             {
  1272.                 $productIds[] = $product['PRODUCT_ID'];
  1273.             }
  1274.         }
  1275.  
  1276.         return $productIds;
  1277.     }
  1278.  
  1279.     protected function getBestSellersFilter()
  1280.     {
  1281.         $filter = array();
  1282.  
  1283.         if (!empty($this->arParams['FILTER']))
  1284.         {
  1285.             $filter = defined('SITE_ID') && !SITE_ID ? array('=LID' => $this->getSiteId()) : array();
  1286.             $subFilter = array('LOGIC' => 'OR');
  1287.  
  1288.             $statuses = array(
  1289.                 'CANCELED' => true,
  1290.                 'ALLOW_DELIVERY' => true,
  1291.                 'PAYED' => true,
  1292.                 'DEDUCTED' => true
  1293.             );
  1294.  
  1295.             if ($this->arParams['PERIOD'] > 0)
  1296.             {
  1297.                 $date = ConvertTimeStamp(AddToTimeStamp(array('DD' => '-'.$this->arParams['PERIOD'])));
  1298.                 if (!empty($date))
  1299.                 {
  1300.                     foreach ($this->arParams['FILTER'] as $field)
  1301.                     {
  1302.                         if (isset($statuses[$field]))
  1303.                         {
  1304.                             $subFilter[] = array(
  1305.                                 '>=DATE_'.$field => $date,
  1306.                                 '='.$field => 'Y'
  1307.                             );
  1308.                         }
  1309.                         else
  1310.                         {
  1311.                             if (empty($this->storage['ORDER_STATUS']) || in_array($field, $this->storage['ORDER_STATUS']))
  1312.                             {
  1313.                                 $subFilter[] = array(
  1314.                                     '=STATUS_ID' => $field,
  1315.                                     '>=DATE_UPDATE' => $date,
  1316.                                 );
  1317.                             }
  1318.                         }
  1319.                     }
  1320.                     unset($field);
  1321.                 }
  1322.             }
  1323.             else
  1324.             {
  1325.                 foreach ($this->arParams['FILTER'] as $field)
  1326.                 {
  1327.                     if (isset($statuses[$field]))
  1328.                     {
  1329.                         $subFilter[] = array(
  1330.                             '='.$field => 'Y'
  1331.                         );
  1332.                     }
  1333.                     else
  1334.                     {
  1335.                         if (empty($this->storage['ORDER_STATUS']) || in_array($field, $this->storage['ORDER_STATUS']))
  1336.                         {
  1337.                             $subFilter[] = array(
  1338.                                 '=STATUS_ID' => $field,
  1339.                             );
  1340.                         }
  1341.                     }
  1342.                 }
  1343.                 unset($field);
  1344.             }
  1345.  
  1346.             $filter[] = $subFilter;
  1347.         }
  1348.  
  1349.         return $filter;
  1350.     }
  1351.  
  1352.     /**
  1353.      * Return array of iblock element ids to show for "initialLoad" action.
  1354.      *
  1355.      * @return array
  1356.      */
  1357.     protected function getDeferredProductIds()
  1358.     {
  1359.         return array();
  1360.     }
  1361.  
  1362.     protected function getProductIdMap($productIds)
  1363.     {
  1364.         if ($productIds === false)
  1365.         {
  1366.             return false;
  1367.         }
  1368.  
  1369.         return $this->useCatalog ? static::getProductsMap($productIds) : $productIds;
  1370.     }
  1371.  
  1372.     /**
  1373.      * Returns ids map: SKU_PRODUCT_ID => PRODUCT_ID.
  1374.      *
  1375.      * @param array $originalIds            Input products ids.
  1376.      * @return array
  1377.      */
  1378.     public static function getProductsMap(array $originalIds = array())
  1379.     {
  1380.         if (empty($originalIds))
  1381.         {
  1382.             return array();
  1383.         }
  1384.  
  1385.         $result = array();
  1386.         $productList = \CCatalogSku::getProductList($originalIds);
  1387.         if ($productList === false)
  1388.         {
  1389.             $productList = array();
  1390.         }
  1391.  
  1392.         foreach ($originalIds as $id)
  1393.         {
  1394.             $result[$id] = isset($productList[$id]) ? $productList[$id]['ID'] : (int)$id;
  1395.         }
  1396.  
  1397.         return $result;
  1398.     }
  1399.  
  1400.     /**
  1401.      * Return array map of iblock products.
  1402.  
  1403.      * 3 following cases to process $productIdMap:
  1404.      * ~ $productIdMap is array with ids    - show elements with presented ids
  1405.      * ~ $productIdMap is empty array       - nothing to show
  1406.      * ~ $productIdMap === false                - show elements via filter(e.g. $arParams['IBLOCK_ID'],  arParams['ELEMENT_ID'])
  1407.      *
  1408.      * @return array
  1409.      */
  1410.     protected function getProductsSeparatedByIblock()
  1411.     {
  1412.         $iblockItems = array();
  1413.  
  1414.         if (!empty($this->productIdMap) && is_array($this->productIdMap))
  1415.         {
  1416.             $itemsIterator = Iblock\ElementTable::getList(array(
  1417.                 'select' => array('ID', 'IBLOCK_ID'),
  1418.                 'filter' => array('@ID' => $this->productIdMap)
  1419.             ));
  1420.             while ($item = $itemsIterator->fetch())
  1421.             {
  1422.                 $item['ID'] = (int)$item['ID'];
  1423.                 $item['IBLOCK_ID'] = (int)$item['IBLOCK_ID'];
  1424.  
  1425.                 if (!isset($iblockItems[$item['IBLOCK_ID']]))
  1426.                 {
  1427.                     $iblockItems[$item['IBLOCK_ID']] = array();
  1428.                 }
  1429.  
  1430.                 $iblockItems[$item['IBLOCK_ID']][] = $item['ID'];
  1431.             }
  1432.             unset($item, $itemsIterator);
  1433.         }
  1434.         elseif ($this->productIdMap === false)
  1435.         {
  1436.             $iblockItems[$this->arParams['IBLOCK_ID']] = $this->arParams['ELEMENT_ID'];
  1437.         }
  1438.  
  1439.         return $iblockItems;
  1440.     }
  1441.  
  1442.     /**
  1443.      * Return default measure.
  1444.      *
  1445.      * @return array|null
  1446.      */
  1447.     protected function getDefaultMeasure()
  1448.     {
  1449.         $defaultMeasure = array();
  1450.  
  1451.         if ($this->useCatalog)
  1452.         {
  1453.             $defaultMeasure = \CCatalogMeasure::getDefaultMeasure(true, true);
  1454.         }
  1455.  
  1456.         return $defaultMeasure;
  1457.     }
  1458.  
  1459.     /**
  1460.      * Return \CIBlockResult iterator for current iblock ID.
  1461.      *
  1462.      * @param int $iblockId
  1463.      * @param array|int $products
  1464.      * @return \CIBlockResult|int
  1465.      */
  1466.     protected function getElementList($iblockId, $products)
  1467.     {
  1468.         $selectFields = $this->selectFields;
  1469.         $filterFields = $this->filterFields;
  1470.  
  1471.         if ($iblockId > 0)
  1472.         {
  1473.             $filterFields['IBLOCK_ID'] = $iblockId;
  1474.         }
  1475.  
  1476.         if (!empty($products))
  1477.         {
  1478.             $filterFields['ID'] = $products;
  1479.         }
  1480.  
  1481.         if ($this->isIblockCatalog || $this->offerIblockExist($iblockId))
  1482.         {
  1483.             $selectFields[] = 'CATALOG_TYPE';
  1484.         }
  1485.  
  1486.         $elementIterator = \CIBlockElement::GetList(
  1487.             $this->sortFields,
  1488.             array_merge($this->globalFilter, $filterFields),
  1489.             false,
  1490.             $this->navParams,
  1491.             $selectFields
  1492.         );
  1493.         $elementIterator->SetUrlTemplates($this->arParams['DETAIL_URL']);
  1494.  
  1495.         return $elementIterator;
  1496.     }
  1497.  
  1498.     /**
  1499.      * Initialization of general query fields.
  1500.      *
  1501.      * @return void
  1502.      */
  1503.     protected function initQueryFields()
  1504.     {
  1505.         $this->selectFields = $this->getSelect();
  1506.         $this->filterFields = $this->getFilter();
  1507.         $this->sortFields = $this->getSort();
  1508.         $this->initPricesQuery();
  1509.     }
  1510.  
  1511.     /**
  1512.      * Return select fields to execute.
  1513.      *
  1514.      * @return array
  1515.      */
  1516.     protected function getSelect()
  1517.     {
  1518.         return array(
  1519.             'ID', 'IBLOCK_ID', 'CODE', 'XML_ID', 'NAME', 'ACTIVE', 'DATE_ACTIVE_FROM', 'DATE_ACTIVE_TO', 'SORT',
  1520.             'PREVIEW_TEXT', 'PREVIEW_TEXT_TYPE', 'DETAIL_TEXT', 'DETAIL_TEXT_TYPE', 'DATE_CREATE', 'CREATED_BY', 'TAGS',
  1521.             'TIMESTAMP_X', 'MODIFIED_BY', 'IBLOCK_SECTION_ID', 'DETAIL_PAGE_URL', 'DETAIL_PICTURE', 'PREVIEW_PICTURE'
  1522.         );
  1523.     }
  1524.  
  1525.     /**
  1526.      * Return filter fields to execute.
  1527.      *
  1528.      * @return array
  1529.      */
  1530.     protected function getFilter()
  1531.     {
  1532.         return array(
  1533.             'IBLOCK_LID' => $this->getSiteId(),
  1534.             'ACTIVE_DATE' => 'Y',
  1535.             'CHECK_PERMISSIONS' => 'Y',
  1536.             'MIN_PERMISSION' => 'R'
  1537.         );
  1538.     }
  1539.  
  1540.     /**
  1541.      * Return sort fields to execute.
  1542.      *
  1543.      * @return array
  1544.      */
  1545.     protected function getSort()
  1546.     {
  1547.         return array();
  1548.     }
  1549.  
  1550.     protected function initPricesQuery()
  1551.     {
  1552.         foreach (array_keys($this->sortFields) as $fieldName)
  1553.         {
  1554.             $fieldName = strtoupper($fieldName);
  1555.             $priceId = 0;
  1556.  
  1557.             if (strncmp($fieldName, 'CATALOG_PRICE_', 14) === 0)
  1558.             {
  1559.                 $priceId = (int)substr($fieldName, 14);
  1560.             }
  1561.             elseif (strncmp($fieldName, 'CATALOG_CURRENCY_', 17) === 0)
  1562.             {
  1563.                 $priceId = (int)substr($fieldName, 17);
  1564.             }
  1565.             elseif (strncmp($fieldName, 'CATALOG_PRICE_SCALE_', 20) === 0)
  1566.             {
  1567.                 $priceId = (int)substr($fieldName, 20);
  1568.             }
  1569.  
  1570.             if ($priceId <= 0)
  1571.                 continue;
  1572.  
  1573.             if (!isset($this->filterFields['CATALOG_SHOP_QUANTITY_'.$priceId]))
  1574.             {
  1575.                 $this->filterFields['CATALOG_SHOP_QUANTITY_'.$priceId] = $this->arParams['SHOW_PRICE_COUNT'];
  1576.             }
  1577.         }
  1578.     }
  1579.  
  1580.     /**
  1581.      * Return parsed conditions array.
  1582.      *
  1583.      * @param $condition
  1584.      * @param $params
  1585.      * @return array
  1586.      */
  1587.     protected function parseCondition($condition, $params)
  1588.     {
  1589.         $result = array();
  1590.  
  1591.         if (!empty($condition) && is_array($condition))
  1592.         {
  1593.             if ($condition['CLASS_ID'] === 'CondGroup')
  1594.             {
  1595.                 if (!empty($condition['CHILDREN']))
  1596.                 {
  1597.                     foreach ($condition['CHILDREN'] as $child)
  1598.                     {
  1599.                         $childResult = $this->parseCondition($child, $params);
  1600.  
  1601.                         // is group
  1602.                         if ($child['CLASS_ID'] === 'CondGroup')
  1603.                         {
  1604.                             $result[] = $childResult;
  1605.                         }
  1606.                         // same property names not overrides each other
  1607.                         elseif (isset($result[key($childResult)]))
  1608.                         {
  1609.                             $fieldName = key($childResult);
  1610.  
  1611.                             if (!is_array($result[$fieldName]) || !isset($result[$fieldName]['LOGIC']))
  1612.                             {
  1613.                                 $result[$fieldName] = array(
  1614.                                     'LOGIC' => $condition['DATA']['All'],
  1615.                                     $result[$fieldName]
  1616.                                 );
  1617.                             }
  1618.  
  1619.                             $result[$fieldName][] = $childResult[$fieldName];
  1620.                         }
  1621.                         else
  1622.                         {
  1623.                             $result += $childResult;
  1624.                         }
  1625.                     }
  1626.  
  1627.                     if (!empty($result))
  1628.                     {
  1629.                         $this->parsePropertyCondition($result, $condition, $params);
  1630.  
  1631.                         if (count($result) > 1)
  1632.                         {
  1633.                             $result['LOGIC'] = $condition['DATA']['All'];
  1634.                         }
  1635.                     }
  1636.                 }
  1637.             }
  1638.             else
  1639.             {
  1640.                 $result += $this->parseConditionLevel($condition, $params);
  1641.             }
  1642.         }
  1643.  
  1644.         return $result;
  1645.     }
  1646.  
  1647.     protected function parseConditionLevel($condition, $params)
  1648.     {
  1649.         $result = array();
  1650.  
  1651.         if (!empty($condition) && is_array($condition))
  1652.         {
  1653.             $name = $this->parseConditionName($condition);
  1654.             if (!empty($name))
  1655.             {
  1656.                 $operator = $this->parseConditionOperator($condition);
  1657.                 $value = $this->parseConditionValue($condition, $name);
  1658.                 $result[$operator.$name] = $value;
  1659.  
  1660.                 if ($name === 'SECTION_ID')
  1661.                 {
  1662.                     $result['INCLUDE_SUBSECTIONS'] = isset($params['INCLUDE_SUBSECTIONS']) && $params['INCLUDE_SUBSECTIONS'] === 'N' ? 'N' : 'Y';
  1663.  
  1664.                     if (isset($params['INCLUDE_SUBSECTIONS']) && $params['INCLUDE_SUBSECTIONS'] === 'A')
  1665.                     {
  1666.                         $result['SECTION_GLOBAL_ACTIVE'] = 'Y';
  1667.                     }
  1668.  
  1669.                     $result = array($result);
  1670.                 }
  1671.             }
  1672.         }
  1673.  
  1674.         return $result;
  1675.     }
  1676.  
  1677.     protected function parseConditionName(array $condition)
  1678.     {
  1679.         $name = '';
  1680.         $conditionNameMap = array(
  1681.             'CondIBXmlID' => 'XML_ID',
  1682. //          'CondIBActive' => 'ACTIVE',
  1683.             'CondIBSection' => 'SECTION_ID',
  1684.             'CondIBDateActiveFrom' => 'DATE_ACTIVE_FROM',
  1685.             'CondIBDateActiveTo' => 'DATE_ACTIVE_TO',
  1686.             'CondIBSort' => 'SORT',
  1687.             'CondIBDateCreate' => 'DATE_CREATE',
  1688.             'CondIBCreatedBy' => 'CREATED_BY',
  1689.             'CondIBTimestampX' => 'TIMESTAMP_X',
  1690.             'CondIBModifiedBy' => 'MODIFIED_BY',
  1691.             'CondIBTags' => 'TAGS',
  1692.             'CondCatQuantity' => 'CATALOG_QUANTITY',
  1693.             'CondCatWeight' => 'CATALOG_WEIGHT'
  1694.         );
  1695.  
  1696.         if (isset($conditionNameMap[$condition['CLASS_ID']]))
  1697.         {
  1698.             $name = $conditionNameMap[$condition['CLASS_ID']];
  1699.         }
  1700.         elseif (strpos($condition['CLASS_ID'], 'CondIBProp') !== false)
  1701.         {
  1702.             $name = $condition['CLASS_ID'];
  1703.         }
  1704.  
  1705.         return $name;
  1706.     }
  1707.  
  1708.     protected function parseConditionOperator($condition)
  1709.     {
  1710.         $operator = '';
  1711.  
  1712.         switch ($condition['DATA']['logic'])
  1713.         {
  1714.             case 'Equal':
  1715.                 $operator = '';
  1716.                 break;
  1717.             case 'Not':
  1718.                 $operator = '!';
  1719.                 break;
  1720.             case 'Contain':
  1721.                 $operator = '%';
  1722.                 break;
  1723.             case 'NotCont':
  1724.                 $operator = '!%';
  1725.                 break;
  1726.             case 'Great':
  1727.                 $operator = '>';
  1728.                 break;
  1729.             case 'Less':
  1730.                 $operator = '<';
  1731.                 break;
  1732.             case 'EqGr':
  1733.                 $operator = '>=';
  1734.                 break;
  1735.             case 'EqLs':
  1736.                 $operator = '<=';
  1737.                 break;
  1738.         }
  1739.  
  1740.         return $operator;
  1741.     }
  1742.  
  1743.     protected function parseConditionValue($condition, $name)
  1744.     {
  1745.         $value = $condition['DATA']['value'];
  1746.  
  1747.         switch ($name)
  1748.         {
  1749.             case 'DATE_ACTIVE_FROM':
  1750.             case 'DATE_ACTIVE_TO':
  1751.             case 'DATE_CREATE':
  1752.             case 'TIMESTAMP_X':
  1753.                 $value = ConvertTimeStamp($value, 'FULL');
  1754.                 break;
  1755.         }
  1756.  
  1757.         return $value;
  1758.     }
  1759.  
  1760.     protected function parsePropertyCondition(array &$result, array $condition, $params)
  1761.     {
  1762.         if (!empty($result))
  1763.         {
  1764.             $subFilter = array();
  1765.  
  1766.             foreach ($result as $name => $value)
  1767.             {
  1768.                 if (($ind = strpos($name, 'CondIBProp')) !== false)
  1769.                 {
  1770.                     list($prefix, $iblock, $propertyId) = explode(':', $name);
  1771.                     $operator = $ind > 0 ? substr($prefix, 0, $ind) : '';
  1772.  
  1773.                     $catalogInfo = \CCatalogSku::GetInfoByIBlock($iblock);
  1774.                     if (!empty($catalogInfo))
  1775.                     {
  1776.                         if (
  1777.                             $catalogInfo['CATALOG_TYPE'] != \CCatalogSku::TYPE_CATALOG
  1778.                             && $catalogInfo['IBLOCK_ID'] == $iblock
  1779.                         )
  1780.                         {
  1781.                             $subFilter[$operator.'PROPERTY_'.$propertyId] = $value;
  1782.                         }
  1783.                         else
  1784.                         {
  1785.                             $result[$operator.'PROPERTY_'.$propertyId] = $value;
  1786.                         }
  1787.                     }
  1788.  
  1789.                     unset($result[$name]);
  1790.                 }
  1791.             }
  1792.  
  1793.             if (!empty($subFilter) && !empty($catalogInfo))
  1794.             {
  1795.                 $offerPropFilter = array(
  1796.                     'IBLOCK_ID' => $catalogInfo['IBLOCK_ID'],
  1797.                     'ACTIVE_DATE' => 'Y',
  1798.                     'ACTIVE' => 'Y'
  1799.                 );
  1800.  
  1801.                 if ($params['HIDE_NOT_AVAILABLE_OFFERS'] === 'Y')
  1802.                 {
  1803.                     $offerPropFilter['HIDE_NOT_AVAILABLE'] = 'Y';
  1804.                 }
  1805.                 elseif ($params['HIDE_NOT_AVAILABLE_OFFERS'] === 'L')
  1806.                 {
  1807.                     $offerPropFilter[] = array(
  1808.                         'LOGIC' => 'OR',
  1809.                         'CATALOG_AVAILABLE' => 'Y',
  1810.                         'CATALOG_SUBSCRIBE' => 'Y'
  1811.                     );
  1812.                 }
  1813.  
  1814.                 if (count($subFilter) > 1)
  1815.                 {
  1816.                     $subFilter['LOGIC'] = $condition['DATA']['All'];
  1817.                     $subFilter = array($subFilter);
  1818.                 }
  1819.  
  1820.                 $result['=ID'] = \CIBlockElement::SubQuery(
  1821.                     'PROPERTY_'.$catalogInfo['SKU_PROPERTY_ID'],
  1822.                     $offerPropFilter + $subFilter
  1823.                 );
  1824.             }
  1825.         }
  1826.     }
  1827.  
  1828.     /**
  1829.      * Process element data to set in $arResult.
  1830.      *
  1831.      * @param array &$element
  1832.      * @return void
  1833.      */
  1834.     protected function processElement(array &$element)
  1835.     {
  1836.         $this->modifyElementCommonData($element);
  1837.         $this->modifyElementPrices($element);
  1838.         $this->setElementPanelButtons($element);
  1839.     }
  1840.  
  1841.     /**
  1842.      * Fill various common fields for element.
  1843.      *
  1844.      * @param array &$element           Element data.
  1845.      * @return void
  1846.      */
  1847.     protected function modifyElementCommonData(array &$element)
  1848.     {
  1849.         $element['ID'] = (int)$element['ID'];
  1850.         $element['IBLOCK_ID'] = (int)$element['IBLOCK_ID'];
  1851.         if ($this->isEnableCompatible())
  1852.         {
  1853.             $element['ACTIVE_FROM'] = (isset($element['DATE_ACTIVE_FROM']) ? $element['DATE_ACTIVE_FROM'] : null);
  1854.             $element['ACTIVE_TO'] = (isset($element['DATE_ACTIVE_TO']) ? $element['DATE_ACTIVE_TO'] : null);
  1855.         }
  1856.  
  1857.         $ipropValues = new Iblock\InheritedProperty\ElementValues($element['IBLOCK_ID'], $element['ID']);
  1858.         $element['IPROPERTY_VALUES'] = $ipropValues->getValues();
  1859.  
  1860.         Iblock\Component\Tools::getFieldImageData(
  1861.             $element,
  1862.             array('PREVIEW_PICTURE', 'DETAIL_PICTURE'),
  1863.             Iblock\Component\Tools::IPROPERTY_ENTITY_ELEMENT,
  1864.             'IPROPERTY_VALUES'
  1865.         );
  1866.  
  1867.         /* it is not the final version */
  1868.         $element['PRODUCT'] = array(
  1869.             'TYPE' => null,
  1870.             'AVAILABLE' => null,
  1871.             'MEASURE' => null,
  1872.             'VAT_ID' => null,
  1873.             'VAT_RATE' => null,
  1874.             'VAT_INCLUDED' => null,
  1875.             'QUANTITY' => null,
  1876.             'QUANTITY_TRACE' => null,
  1877.             'CAN_BUY_ZERO' => null,
  1878.             'SUBSCRIPTION' => null
  1879.         );
  1880.  
  1881.         if (isset($element['CATALOG_TYPE']))
  1882.         {
  1883.             $element['CATALOG_TYPE'] = (int)$element['CATALOG_TYPE']; // this key will be deprecated
  1884.             $element['PRODUCT']['TYPE'] = $element['CATALOG_TYPE'];
  1885.         }
  1886.         if (isset($element['CATALOG_MEASURE']))
  1887.         {
  1888.             $element['CATALOG_MEASURE'] = (int)$element['CATALOG_MEASURE']; // this key will be deprecated
  1889.             $element['PRODUCT']['MEASURE'] = $element['CATALOG_MEASURE'];
  1890.         }
  1891.         /*
  1892.          * this keys will be deprecated
  1893.          * CATALOG_*
  1894.          */
  1895.         if (isset($element['CATALOG_AVAILABLE']))
  1896.             $element['PRODUCT']['AVAILABLE'] = $element['CATALOG_AVAILABLE'];
  1897.         if (isset($element['CATALOG_VAT']))
  1898.             $element['PRODUCT']['VAT_RATE'] = $element['CATALOG_VAT'];
  1899.         if (isset($element['CATALOG_VAT_INCLUDED']))
  1900.             $element['PRODUCT']['VAT_INCLUDED'] = $element['CATALOG_VAT_INCLUDED'];
  1901.         if (isset($element['CATALOG_QUANTITY']))
  1902.             $element['PRODUCT']['QUANTITY'] = $element['CATALOG_QUANTITY'];
  1903.         if (isset($element['CATALOG_QUANTITY_TRACE']))
  1904.             $element['PRODUCT']['QUANTITY_TRACE'] = $element['CATALOG_QUANTITY_TRACE'];
  1905.         if (isset($element['CATALOG_CAN_BUY_ZERO']))
  1906.             $element['PRODUCT']['CAN_BUY_ZERO'] = $element['CATALOG_CAN_BUY_ZERO'];
  1907.         if (isset($element['CATALOG_SUBSCRIPTION']))
  1908.             $element['PRODUCT']['SUBSCRIPTION'] = $element['CATALOG_SUBSCRIPTION'];
  1909.         /* it is not the final version - end*/
  1910.  
  1911.         $element['PROPERTIES'] = array();
  1912.         $element['DISPLAY_PROPERTIES'] = array();
  1913.         $element['PRODUCT_PROPERTIES'] = array();
  1914.         $element['PRODUCT_PROPERTIES_FILL'] = array();
  1915.         $element['OFFERS'] = array();
  1916.         $element['OFFER_ID_SELECTED'] = 0;
  1917.  
  1918.         if (!empty($this->storage['CATALOGS'][$element['IBLOCK_ID']]))
  1919.             $element['CHECK_QUANTITY'] = $this->isNeedCheckQuantity($element['PRODUCT']);
  1920.  
  1921.         if ($this->getAction() === 'bigDataLoad')
  1922.         {
  1923.             $element['RCM_ID'] = $this->recommendationIdToProduct[$element['ID']];
  1924.         }
  1925.     }
  1926.  
  1927.     /**
  1928.      * Add Hermitage button links for element.
  1929.      *
  1930.      * @param array &$element           Element data.
  1931.      * @return void
  1932.      */
  1933.     protected function setElementPanelButtons(&$element)
  1934.     {
  1935.         $buttons = \CIBlock::GetPanelButtons(
  1936.             $element['IBLOCK_ID'],
  1937.             $element['ID'],
  1938.             $element['IBLOCK_SECTION_ID'],
  1939.             array('SECTION_BUTTONS' => false, 'SESSID' => false, 'CATALOG' => true)
  1940.         );
  1941.         $element['EDIT_LINK'] = $buttons['edit']['edit_element']['ACTION_URL'];
  1942.         $element['DELETE_LINK'] = $buttons['edit']['delete_element']['ACTION_URL'];
  1943.     }
  1944.  
  1945.     /**
  1946.      * Process element display properties by iblock parameters.
  1947.      *
  1948.      * @param int $iblock                   Iblock ID.
  1949.      * @param array &$iblockElements        Items.
  1950.      * @return void
  1951.      */
  1952.     protected function modifyDisplayProperties($iblock, &$iblockElements)
  1953.     {
  1954.     }
  1955.  
  1956.     protected function getPropertyList($iblock, $propertyCodes)
  1957.     {
  1958.         $propertyList = array();
  1959.         if (empty($propertyCodes))
  1960.             return $propertyList;
  1961.  
  1962.         $propertyCodes = array_fill_keys($propertyCodes, true);
  1963.  
  1964.         $propertyIterator = Iblock\PropertyTable::getList(array(
  1965.             'select' => array('ID', 'CODE'),
  1966.             'filter' => array('=IBLOCK_ID' => $iblock, '=ACTIVE' => 'Y'),
  1967.             'order' => array('SORT' => 'ASC', 'ID' => 'ASC')
  1968.         ));
  1969.         while ($property = $propertyIterator->fetch())
  1970.         {
  1971.             $code = (string)$property['CODE'];
  1972.  
  1973.             if ($code == '')
  1974.             {
  1975.                 $code = $property['ID'];
  1976.             }
  1977.  
  1978.             if (!isset($propertyCodes[$code]))
  1979.                 continue;
  1980.  
  1981.             $propertyList[] = $code;
  1982.         }
  1983.  
  1984.         return $propertyList;
  1985.     }
  1986.  
  1987.     /**
  1988.      * Clear products data.
  1989.      *
  1990.      * @return void
  1991.      */
  1992.     protected function clearItems()
  1993.     {
  1994.         $this->prices = array();
  1995.         $this->measures = array();
  1996.         $this->ratios = array();
  1997.         $this->quantityRanges = array();
  1998.         $this->oldData = array();
  1999.     }
  2000.  
  2001.     /**
  2002.      * Load measure ratios for items.
  2003.      *
  2004.      * @param array $itemIds        Items id list.
  2005.      *
  2006.      * @return void
  2007.      */
  2008.     protected function loadMeasureRatios(array $itemIds)
  2009.     {
  2010.         if (empty($itemIds))
  2011.             return;
  2012.         Main\Type\Collection::normalizeArrayValuesByInt($itemIds, true);
  2013.         if (empty($itemIds))
  2014.             return;
  2015.         $emptyRatioIds = array_fill_keys($itemIds, true);
  2016.  
  2017.         $iterator = Catalog\MeasureRatioTable::getList(array(
  2018.             'select' => array('ID', 'RATIO', 'IS_DEFAULT', 'PRODUCT_ID'),
  2019.             'filter' => array('@PRODUCT_ID' => $itemIds),
  2020.             'order' => array('PRODUCT_ID' => 'ASC')// not add 'RATIO' => 'ASC' - result will be resorted after load prices
  2021.         ));
  2022.         while ($row = $iterator->fetch())
  2023.         {
  2024.             $ratio = ((float)$row['RATIO'] > (int)$row['RATIO'] ? (float)$row['RATIO'] : (int)$row['RATIO']);
  2025.             if ($ratio > CATALOG_VALUE_EPSILON)
  2026.             {
  2027.                 $row['RATIO'] = $ratio;
  2028.                 $row['ID'] = (int)$row['ID'];
  2029.                 $id = (int)$row['PRODUCT_ID'];
  2030.                 if (!isset($this->ratios[$id]))
  2031.                     $this->ratios[$id] = array();
  2032.                 $this->ratios[$id][$row['ID']] = $row;
  2033.                 unset($emptyRatioIds[$id]);
  2034.                 unset($id);
  2035.             }
  2036.             unset($ratio);
  2037.         }
  2038.         unset($row, $iterator);
  2039.         if (!empty($emptyRatioIds))
  2040.         {
  2041.             $emptyRatio = $this->getEmptyRatio();
  2042.             foreach (array_keys($emptyRatioIds) as $id)
  2043.             {
  2044.                 $this->ratios[$id] = array(
  2045.                     $emptyRatio['ID'] => $emptyRatio
  2046.                 );
  2047.             }
  2048.             unset($id, $emptyRatio);
  2049.         }
  2050.         unset($emptyRatioIds);
  2051.     }
  2052.  
  2053.     /**
  2054.      * Return default empty ratio (unexist in database).
  2055.      *
  2056.      * @return array
  2057.      */
  2058.     protected function getEmptyRatio()
  2059.     {
  2060.         return array(
  2061.             'ID' => 0,
  2062.             'RATIO' => 1,
  2063.             'IS_DEFAULT' => 'Y'
  2064.         );
  2065.     }
  2066.  
  2067.     /**
  2068.      * Init measure for items.
  2069.      *
  2070.      * @param array &$items         Items list.
  2071.      * @return void
  2072.      */
  2073.     protected function initItemsMeasure(array &$items)
  2074.     {
  2075.         if (empty($items))
  2076.             return;
  2077.  
  2078.         foreach (array_keys($items) as $index)
  2079.         {
  2080.             if (!isset($items[$index]['PRODUCT']['MEASURE']))
  2081.                 continue;
  2082.             if ($items[$index]['PRODUCT']['MEASURE'] > 0)
  2083.             {
  2084.                 $items[$index]['ITEM_MEASURE'] = array(
  2085.                     'ID' => $items[$index]['PRODUCT']['MEASURE'],
  2086.                     'TITLE' => '',
  2087.                     '~TITLE' => ''
  2088.                 );
  2089.             }
  2090.             else
  2091.             {
  2092.                 $items[$index]['ITEM_MEASURE'] = array(
  2093.                     'ID' => null,
  2094.                     'TITLE' => $this->storage['DEFAULT_MEASURE']['SYMBOL_RUS'],
  2095.                     '~TITLE' => $this->storage['DEFAULT_MEASURE']['~SYMBOL_RUS']
  2096.                 );
  2097.             }
  2098.         }
  2099.         unset($index);
  2100.     }
  2101.  
  2102.     /**
  2103.      * Return measure ids for items.
  2104.      *
  2105.      * @param array $items          Items data.
  2106.      * @return array
  2107.      */
  2108.     protected function getMeasureIds(array $items)
  2109.     {
  2110.         $result = array();
  2111.  
  2112.         if (!empty($items))
  2113.         {
  2114.             foreach (array_keys($items) as $itemId)
  2115.             {
  2116.                 if (!isset($items[$itemId]['ITEM_MEASURE']))
  2117.                     continue;
  2118.                 $measureId = (int)$items[$itemId]['ITEM_MEASURE']['ID'];
  2119.                 if ($measureId > 0)
  2120.                     $result[$measureId] = $measureId;
  2121.                 unset($measureId);
  2122.             }
  2123.             unset($itemId);
  2124.         }
  2125.  
  2126.         return $result;
  2127.     }
  2128.  
  2129.     /**
  2130.      * Load measures data.
  2131.      *
  2132.      * @param array $measureIds
  2133.      * @return void
  2134.      */
  2135.     protected function loadMeasures(array $measureIds)
  2136.     {
  2137.         if (empty($measureIds))
  2138.             return;
  2139.         Main\Type\Collection::normalizeArrayValuesByInt($measureIds, true);
  2140.         if (empty($measureIds))
  2141.             return;
  2142.  
  2143.         $measureIterator = \CCatalogMeasure::getList(
  2144.             array(),
  2145.             array('@ID' => $measureIds),
  2146.             false,
  2147.             false,
  2148.             array('ID', 'SYMBOL_RUS')
  2149.         );
  2150.         while ($measure = $measureIterator->GetNext())
  2151.         {
  2152.             $measure['ID'] = (int)$measure['ID'];
  2153.             $measure['TITLE'] = $measure['SYMBOL_RUS'];
  2154.             $measure['~TITLE'] = $measure['~SYMBOL_RUS'];
  2155.             unset($measure['SYMBOL_RUS'], $measure['~SYMBOL_RUS']);
  2156.             $this->measures[$measure['ID']] = $measure;
  2157.         }
  2158.         unset($measure, $measureIterator);
  2159.     }
  2160.  
  2161.     /**
  2162.      * Load prices for items.
  2163.      *
  2164.      * @param array $itemIds        Item ids.
  2165.      * @return void
  2166.      */
  2167.     protected function loadPrices(array $itemIds)
  2168.     {
  2169.         if (empty($itemIds))
  2170.             return;
  2171.         Main\Type\Collection::normalizeArrayValuesByInt($itemIds, true);
  2172.         if (empty($itemIds))
  2173.             return;
  2174.         if (empty($this->storage['PRICES_ALLOW']))
  2175.             return;
  2176.  
  2177.         $enableCompatible = $this->isEnableCompatible();
  2178.  
  2179.         $ratioList = array_fill_keys($itemIds, array());
  2180.         $quantityList = array_fill_keys($itemIds, array());
  2181.  
  2182.         $select = array(
  2183.             'ID', 'PRODUCT_ID', 'CATALOG_GROUP_ID', 'PRICE', 'CURRENCY',
  2184.             'QUANTITY_FROM', 'QUANTITY_TO'
  2185.         );
  2186.         if ($enableCompatible)
  2187.             $select[] = 'EXTRA_ID';
  2188.  
  2189.         $pagedItemIds = array_chunk($itemIds, 500);
  2190.         foreach ($pagedItemIds as $pageIds)
  2191.         {
  2192.             if (empty($pageIds))
  2193.                 continue;
  2194.  
  2195.             $iterator = Catalog\PriceTable::getList(array(
  2196.                 'select' => $select,
  2197.                 'filter' => array('@PRODUCT_ID' => $pageIds, '@CATALOG_GROUP_ID' => $this->storage['PRICES_ALLOW']),
  2198.                 'order' => array('PRODUCT_ID' => 'ASC', 'CATALOG_GROUP_ID' => 'ASC')
  2199.             ));
  2200.             while ($row = $iterator->fetch())
  2201.             {
  2202.                 $id = (int)$row['PRODUCT_ID'];
  2203.                 unset($row['PRODUCT_ID']);
  2204.                 if (!isset($this->prices[$id]))
  2205.                 {
  2206.                     $this->prices[$id] = array(
  2207.                         'RATIO' => array(),
  2208.                         'QUANTITY' => array(),
  2209.                         'SIMPLE' => array()
  2210.                     );
  2211.                 }
  2212.  
  2213.                 if ($row['QUANTITY_FROM'] !== null || $row['QUANTITY_TO'] !== null)
  2214.                 {
  2215.                     $hash = $this->getQuantityRangeHash($row);
  2216.                     if (!isset($quantityList[$id][$hash]))
  2217.                     {
  2218.                         $quantityList[$id][$hash] = array(
  2219.                             'HASH' => $hash,
  2220.                             'QUANTITY_FROM' => $row['QUANTITY_FROM'],
  2221.                             'QUANTITY_TO' => $row['QUANTITY_TO'],
  2222.                             'SORT_FROM' => (int)$row['QUANTITY_FROM'],
  2223.                             'SORT_TO' => ($row['QUANTITY_TO'] === null ? INF : (int)$row['QUANTITY_TO'])
  2224.                         );
  2225.                     }
  2226.                     if (!isset($this->prices[$id]['QUANTITY'][$hash]))
  2227.                     {
  2228.                         $this->prices[$id]['QUANTITY'][$hash] = array();
  2229.                     }
  2230.                     $this->prices[$id]['QUANTITY'][$hash][$row['CATALOG_GROUP_ID']] = $row;
  2231.                     unset($hash);
  2232.                 }
  2233.                 elseif ($row['MEASURE_RATIO_ID'] === null && $row['QUANTITY_FROM'] === null && $row['QUANTITY_TO'] === null)
  2234.                 {
  2235.                     $this->prices[$id]['SIMPLE'][$row['CATALOG_GROUP_ID']] = $row;
  2236.                 }
  2237.                 $this->storage['CURRENCY_LIST'][$row['CURRENCY']] = $row['CURRENCY'];
  2238.  
  2239.                 unset($id);
  2240.             }
  2241.             unset($row, $iterator);
  2242.         }
  2243.         unset($pageIds, $pagedItemIds);
  2244.  
  2245.         foreach ($itemIds as $id)
  2246.         {
  2247.             if (isset($this->prices[$id]))
  2248.             {
  2249.                 foreach ($this->prices[$id] as $key => $data)
  2250.                 {
  2251.                     if (empty($data))
  2252.                         unset($this->prices[$id][$key]);
  2253.                 }
  2254.                 unset($key, $data);
  2255.  
  2256.                 if (count($this->prices[$id]) !== 1)
  2257.                 {
  2258.                     unset($this->prices[$id]);
  2259.                 }
  2260.                 else
  2261.                 {
  2262.                     if (!empty($this->prices[$id]['QUANTITY']))
  2263.                     {
  2264.                         $productQuantity = $quantityList[$id];
  2265.                         Main\Type\Collection::sortByColumn(
  2266.                             $productQuantity,
  2267.                             array('SORT_FROM' => SORT_ASC, 'SORT_TO' => SORT_ASC),
  2268.                             '', null, true
  2269.                         );
  2270.                         $this->quantityRanges[$id] = $productQuantity;
  2271.                         unset($productQuantity);
  2272.  
  2273.                         if (count($this->ratios[$id]) > 1)
  2274.                             $this->compactItemRatios($id);
  2275.                     }
  2276.                     if (!empty($this->prices[$id]['SIMPLE']))
  2277.                     {
  2278.                         $range = $this->getFullQuantityRange();
  2279.                         $this->quantityRanges[$id] = array(
  2280.                             $range['HASH'] => $range
  2281.                         );
  2282.                         unset($range);
  2283.                         if (count($this->ratios[$id]) > 1)
  2284.                             $this->compactItemRatios($id);
  2285.                     }
  2286.                 }
  2287.             }
  2288.         }
  2289.         unset($id);
  2290.  
  2291.         unset($quantityList, $ratioList);
  2292.  
  2293.         unset($enableCompatible);
  2294.     }
  2295.  
  2296.     protected function calculateItemPrices(array &$items)
  2297.     {
  2298.         if (empty($items))
  2299.             return;
  2300.  
  2301.         $enableCompatible = $this->isEnableCompatible();
  2302.  
  2303.         if ($enableCompatible)
  2304.             $this->initCompatibleFields($items);
  2305.  
  2306.         foreach (array_keys($items) as $index)
  2307.         {
  2308.             $id = $items[$index]['ID'];
  2309.             if (!isset($this->calculatePrices[$id]))
  2310.                 continue;
  2311.             if (empty($this->prices[$id]))
  2312.                 continue;
  2313.             $productPrices = $this->prices[$id];
  2314.             $result = array(
  2315.                 'ITEM_PRICE_MODE' => null,
  2316.                 'ITEM_PRICES' => array()
  2317.             );
  2318.             if ($this->arParams['FILL_ITEM_ALL_PRICES'])
  2319.                 $result['ITEM_ALL_PRICES'] = array();
  2320.             $priceBlockIndex = 0;
  2321.             if (!empty($productPrices['QUANTITY']))
  2322.             {
  2323.                 $result['ITEM_PRICE_MODE'] = Catalog\ProductTable::PRICE_MODE_QUANTITY;
  2324.                 $ratio = current($this->ratios[$id]);
  2325.                 foreach ($this->quantityRanges[$id] as $range)
  2326.                 {
  2327.                     $priceBlock = $this->calculatePriceBlock(
  2328.                         $items[$index],
  2329.                         $productPrices['QUANTITY'][$range['HASH']],
  2330.                         $ratio['RATIO'],
  2331.                         $this->arParams['USE_PRICE_COUNT'] || $this->checkQuantityRange($range)
  2332.                     );
  2333.                     if (!empty($priceBlock))
  2334.                     {
  2335.                         $minimalPrice = ($this->arParams['FILL_ITEM_ALL_PRICES']
  2336.                             ? $priceBlock['MINIMAL_PRICE']
  2337.                             : $priceBlock
  2338.                         );
  2339.                         if ($minimalPrice['QUANTITY_FROM'] === null)
  2340.                         {
  2341.                             $minimalPrice['MIN_QUANTITY'] = $ratio['RATIO'];
  2342.                         }
  2343.                         else
  2344.                         {
  2345.                             $minimalPrice['MIN_QUANTITY'] = $ratio['RATIO'] * ((int)($minimalPrice['QUANTITY_FROM']/$ratio['RATIO']));
  2346.                             if ($minimalPrice['MIN_QUANTITY'] < $minimalPrice['QUANTITY_FROM'])
  2347.                                 $minimalPrice['MIN_QUANTITY'] += $ratio['RATIO'];
  2348.                         }
  2349.                         $result['ITEM_PRICES'][$priceBlockIndex] = $minimalPrice;
  2350.                         if ($this->arParams['FILL_ITEM_ALL_PRICES'])
  2351.                         {
  2352.                             $priceBlock['ALL_PRICES']['MIN_QUANTITY'] = $minimalPrice['MIN_QUANTITY'];
  2353.                             $result['ITEM_ALL_PRICES'][$priceBlockIndex] = $priceBlock['ALL_PRICES'];
  2354.                         }
  2355.                         unset($minimalPrice);
  2356.                         $priceBlockIndex++;
  2357.                     }
  2358.                     unset($priceBlock);
  2359.                 }
  2360.                 unset($range);
  2361.                 unset($ratio);
  2362.             }
  2363.             if (!empty($productPrices['SIMPLE']))
  2364.             {
  2365.                 $result['ITEM_PRICE_MODE'] = Catalog\ProductTable::PRICE_MODE_SIMPLE;
  2366.                 $ratio = current($this->ratios[$id]);
  2367.                 $priceBlock = $this->calculatePriceBlock(
  2368.                     $items[$index],
  2369.                     $productPrices['SIMPLE'],
  2370.                     $ratio['RATIO'],
  2371.                     true
  2372.                 );
  2373.                 if (!empty($priceBlock))
  2374.                 {
  2375.                     $minimalPrice = ($this->arParams['FILL_ITEM_ALL_PRICES']
  2376.                         ? $priceBlock['MINIMAL_PRICE']
  2377.                         : $priceBlock
  2378.                     );
  2379.                     $minimalPrice['MIN_QUANTITY'] = $ratio['RATIO'];
  2380.                     $result['ITEM_PRICES'][$priceBlockIndex] = $minimalPrice;
  2381.                     if ($this->arParams['FILL_ITEM_ALL_PRICES'])
  2382.                     {
  2383.                         $priceBlock['ALL_PRICES']['MIN_QUANTITY'] = $minimalPrice['MIN_QUANTITY'];
  2384.                         $result['ITEM_ALL_PRICES'][$priceBlockIndex] = $priceBlock['ALL_PRICES'];
  2385.                     }
  2386.                     unset($minimalPrice);
  2387.                     $priceBlockIndex++;
  2388.                 }
  2389.                 unset($priceBlock);
  2390.                 unset($ratio);
  2391.             }
  2392.             $this->prices[$id] = $result;
  2393.  
  2394.             if (isset($items[$index]['ACTIVE']) && $items[$index]['ACTIVE'] === 'N')
  2395.             {
  2396.                 $items[$index]['CAN_BUY'] = false;
  2397.             }
  2398.             else
  2399.             {
  2400.                 $items[$index]['CAN_BUY'] = !empty($result['ITEM_PRICES']) && $items[$index]['PRODUCT']['AVAILABLE'] === 'Y';
  2401.             }
  2402.  
  2403.             unset($priceBlockIndex, $result);
  2404.             unset($productPrices);
  2405.  
  2406.             if ($enableCompatible)
  2407.                 $this->resortOldPrices($id);
  2408.         }
  2409.         unset($index);
  2410.     }
  2411.  
  2412.     protected function transferItems(array &$items)
  2413.     {
  2414.         if (empty($items))
  2415.             return;
  2416.  
  2417.         $enableCompatible = $this->isEnableCompatible();
  2418.         $urls = $this->storage['URLS'];
  2419.  
  2420.         foreach (array_keys($items) as $index)
  2421.         {
  2422.             $itemId = $items[$index]['ID'];
  2423.             // measure
  2424.             if (!empty($items[$index]['ITEM_MEASURE']))
  2425.             {
  2426.                 $id = (int)$items[$index]['ITEM_MEASURE']['ID'];
  2427.                 if (isset($this->measures[$id]))
  2428.                 {
  2429.                     $items[$index]['ITEM_MEASURE']['TITLE'] = $this->measures[$id]['TITLE'];
  2430.                     $items[$index]['ITEM_MEASURE']['~TITLE'] = $this->measures[$id]['~TITLE'];
  2431.                 }
  2432.                 unset($id);
  2433.             }
  2434.             // prices
  2435.             $items[$index]['ITEM_MEASURE_RATIOS'] = $this->ratios[$itemId];
  2436.             $items[$index]['ITEM_MEASURE_RATIO_SELECTED'] = $this->searchItemSelectedRatioId($itemId);
  2437.             $items[$index]['ITEM_QUANTITY_RANGES'] = $this->quantityRanges[$itemId];
  2438.             $items[$index]['ITEM_QUANTITY_RANGE_SELECTED'] = $this->searchItemSelectedQuantityRangeHash($itemId);
  2439.             if (!empty($this->prices[$itemId]))
  2440.             {
  2441.                 $items[$index] = array_merge($items[$index], $this->prices[$itemId]);
  2442.                 if (!empty($items[$index]['ITEM_PRICES']))
  2443.                 {
  2444.                     switch ($items[$index]['ITEM_PRICE_MODE'])
  2445.                     {
  2446.                         case Catalog\ProductTable::PRICE_MODE_SIMPLE:
  2447.                             $items[$index]['ITEM_PRICE_SELECTED'] = 0;
  2448.                             break;
  2449.                         case Catalog\ProductTable::PRICE_MODE_QUANTITY:
  2450.                             foreach (array_keys($items[$index]['ITEM_PRICES']) as $priceIndex)
  2451.                             {
  2452.                                 if ($items[$index]['ITEM_PRICES'][$priceIndex]['QUANTITY_HASH'] == $items[$index]['ITEM_QUANTITY_RANGE_SELECTED'])
  2453.                                 {
  2454.                                     $items[$index]['ITEM_PRICE_SELECTED'] = $priceIndex;
  2455.                                     break;
  2456.                                 }
  2457.                             }
  2458.                             break;
  2459.                         case Catalog\ProductTable::PRICE_MODE_RATIO:
  2460.                             foreach (array_keys($items[$index]['ITEM_PRICES']) as $priceIndex)
  2461.                             {
  2462.                                 if ($items[$index]['ITEM_PRICES'][$priceIndex]['MEASURE_RATIO_ID'] == $items[$index]['ITEM_MEASURE_RATIO_SELECTED'])
  2463.                                 {
  2464.                                     $items[$index]['ITEM_PRICE_SELECTED'] = $priceIndex;
  2465.                                     break;
  2466.                                 }
  2467.                             }
  2468.                             break;
  2469.                     }
  2470.                 }
  2471.             }
  2472.  
  2473.             // compatibility
  2474.             if ($enableCompatible)
  2475.             {
  2476.                 // old links to buy, add to basket, etc
  2477.                 $id = $items[$index]['ID'];
  2478.                 $items[$index]['~BUY_URL'] = str_replace('#ID#', $id, $urls['~BUY_URL_TEMPLATE']);
  2479.                 $items[$index]['BUY_URL'] = str_replace('#ID#', $id, $urls['BUY_URL_TEMPLATE']);
  2480.                 $items[$index]['~ADD_URL'] = str_replace('#ID#', $id, $urls['~ADD_URL_TEMPLATE']);
  2481.                 $items[$index]['ADD_URL'] = str_replace('#ID#', $id, $urls['ADD_URL_TEMPLATE']);
  2482.                 $items[$index]['~SUBSCRIBE_URL'] = str_replace('#ID#', $id, $urls['~SUBSCRIBE_URL_TEMPLATE']);
  2483.                 $items[$index]['SUBSCRIBE_URL'] = str_replace('#ID#', $id, $urls['SUBSCRIBE_URL_TEMPLATE']);
  2484.                 if ($this->arParams['DISPLAY_COMPARE'])
  2485.                 {
  2486.                     $items[$index]['~COMPARE_URL'] = str_replace('#ID#', $id, $urls['~COMPARE_URL_TEMPLATE']);
  2487.                     $items[$index]['COMPARE_URL'] = str_replace('#ID#', $id, $urls['COMPARE_URL_TEMPLATE']);
  2488.                     $items[$index]['~COMPARE_DELETE_URL'] = str_replace('#ID#', $id, $urls['~COMPARE_DELETE_URL_TEMPLATE']);
  2489.                     $items[$index]['COMPARE_DELETE_URL'] = str_replace('#ID#', $id, $urls['COMPARE_DELETE_URL_TEMPLATE']);
  2490.                 }
  2491.                 unset($id);
  2492.  
  2493.                 // old measure
  2494.                 $items[$index]['CATALOG_MEASURE_NAME'] = $items[$index]['ITEM_MEASURE']['TITLE'];
  2495.                 $items[$index]['~CATALOG_MEASURE_NAME'] = $items[$index]['ITEM_MEASURE']['~TITLE'];
  2496.  
  2497.                 // old measure ratio
  2498.                 $items[$index]['CATALOG_MEASURE_RATIO'] = $items[$index]['ITEM_MEASURE_RATIOS'][$items[$index]['ITEM_MEASURE_RATIO_SELECTED']]['RATIO'];
  2499.  
  2500.                 // old fields
  2501.                 if (!empty($this->oldData[$itemId]))
  2502.                     $items[$index] = array_merge($this->oldData[$itemId], $items[$index]);
  2503.             }
  2504.             unset($itemId);
  2505.         }
  2506.         unset($index);
  2507.         unset($urls, $enableCompatible);
  2508.     }
  2509.  
  2510.     /**
  2511.      * Calculate price block (simple price, quantity range, etc).
  2512.      *
  2513.      * @param array $product            Product data.
  2514.      * @param array $priceBlock         Prices.
  2515.      * @param int|float $ratio          Measure ratio value.
  2516.      * @param bool $defaultBlock        Save result to old keys (PRICES, PRICE_MATRIX, MIN_PRICE).
  2517.      * @return array|null
  2518.      */
  2519.     protected function calculatePriceBlock(array $product, array $priceBlock, $ratio, $defaultBlock = false)
  2520.     {
  2521.         if (empty($product) || empty($priceBlock))
  2522.             return null;
  2523.  
  2524.         $enableCompatible = $defaultBlock && $this->isEnableCompatible();
  2525.  
  2526.         if ($enableCompatible && !$this->arParams['USE_PRICE_COUNT'])
  2527.             $this->fillCompatibleRawPriceFields($product['ID'], $priceBlock);
  2528.  
  2529.         $userGroups = $this->getUserGroups();
  2530.  
  2531.         $baseCurrency = Currency\CurrencyManager::getBaseCurrency();
  2532.         /** @var null|array $minimalPrice */
  2533.         $minimalPrice = null;
  2534.         $fullPrices = array();
  2535.  
  2536.         $currencyConvert = $this->arParams['CONVERT_CURRENCY'] === 'Y';
  2537.         $resultCurrency = ($currencyConvert ? $this->storage['CONVERT_CURRENCY']['CURRENCY_ID'] : null);
  2538.  
  2539.         $vatRate = (float)$product['PRODUCT']['VAT_RATE'];
  2540.         $percentVat = $vatRate * 0.01;
  2541.         $percentPriceWithVat = 1 + $percentVat;
  2542.         $vatInclude = $product['PRODUCT']['VAT_INCLUDED'] === 'Y';
  2543.  
  2544.         $oldPrices = array();
  2545.         $oldMinPrice = false;
  2546.         $oldMatrix = false;
  2547.         if ($enableCompatible && $this->arParams['USE_PRICE_COUNT'])
  2548.         {
  2549.             $oldMatrix = $this->getCompatibleFieldValue($product['ID'], 'PRICE_MATRIX');
  2550.             if (empty($oldMatrix))
  2551.             {
  2552.                 $oldMatrix = $this->getEmptyPriceMatrix();
  2553.                 $oldMatrix['AVAILABLE'] = $product['PRODUCT']['AVAILABLE'];
  2554.             }
  2555.         }
  2556.  
  2557.         foreach ($priceBlock as $rawPrice)
  2558.         {
  2559.             $priceType = (int)$rawPrice['CATALOG_GROUP_ID'];
  2560.             $price = (float)$rawPrice['PRICE'];
  2561.             if (!$vatInclude)
  2562.                 $price *= $percentPriceWithVat;
  2563.             $currency = $rawPrice['CURRENCY'];
  2564.  
  2565.             $changeCurrency = $currencyConvert && $currency !== $resultCurrency;
  2566.             if ($changeCurrency)
  2567.             {
  2568.                 $price = \CCurrencyRates::ConvertCurrency($price, $currency, $resultCurrency);
  2569.                 $currency = $resultCurrency;
  2570.             }
  2571.  
  2572.             $discounts = array();
  2573.             if (\CIBlockPriceTools::isEnabledCalculationDiscounts())
  2574.             {
  2575.                 \CCatalogDiscountSave::Disable();
  2576.                 $discounts = \CCatalogDiscount::GetDiscount(
  2577.                     $product['ID'],
  2578.                     $product['IBLOCK_ID'],
  2579.                     array($priceType),
  2580.                     $userGroups,
  2581.                     'N',
  2582.                     $this->getSiteId(),
  2583.                     array()
  2584.                 );
  2585.                 \CCatalogDiscountSave::Enable();
  2586.             }
  2587.             $discountPrice = \CCatalogProduct::CountPriceWithDiscount(
  2588.                 $price,
  2589.                 $currency,
  2590.                 $discounts
  2591.             );
  2592.             unset($discounts);
  2593.             if ($discountPrice !== false)
  2594.             {
  2595.                 $priceWithVat = $price;
  2596.                 $price /= $percentPriceWithVat;
  2597.  
  2598.                 $discountPriceWithVat = $discountPrice;
  2599.                 $discountPrice /= $percentPriceWithVat;
  2600.  
  2601.                 $roundPriceWithVat = Catalog\Product\Price::roundPrice(
  2602.                     $priceType,
  2603.                     $discountPriceWithVat,
  2604.                     $currency
  2605.                 );
  2606.                 $roundPrice = Catalog\Product\Price::roundPrice(
  2607.                     $priceType,
  2608.                     $discountPrice,
  2609.                     $currency
  2610.                 );
  2611.  
  2612.                 $priceRow = array(
  2613.                     'ID' => $rawPrice['ID'],
  2614.                     'PRICE_TYPE_ID' => $rawPrice['CATALOG_GROUP_ID'],
  2615.                     'QUANTITY_FROM' => $rawPrice['QUANTITY_FROM'],
  2616.                     'QUANTITY_TO' => $rawPrice['QUANTITY_TO'],
  2617.                     'QUANTITY_HASH' => $this->getQuantityRangeHash($rawPrice),
  2618.                     'CURRENCY' => $currency
  2619.                 );
  2620.                 if ($this->arParams['PRICE_VAT_INCLUDE'])
  2621.                 {
  2622.                     $priceRow['BASE_PRICE'] = $priceWithVat;
  2623.                     $priceRow['UNROUND_PRICE'] = $discountPriceWithVat;
  2624.                     $priceRow['PRICE'] = $roundPriceWithVat;
  2625.                 }
  2626.                 else
  2627.                 {
  2628.                     $priceRow['BASE_PRICE'] = $price;
  2629.                     $priceRow['UNROUND_PRICE'] = $discountPrice;
  2630.                     $priceRow['PRICE'] = $roundPrice;
  2631.                 }
  2632.                 if ($priceRow['BASE_PRICE'] > $priceRow['UNROUND_PRICE'])
  2633.                 {
  2634.                     $priceRow['DISCOUNT'] = $priceRow['BASE_PRICE'] - $priceRow['PRICE'];
  2635.                     $priceRow['PERCENT'] = roundEx(100*$priceRow['DISCOUNT']/$priceRow['BASE_PRICE'], 0);
  2636.                     if ($priceRow['DISCOUNT'] < 0)
  2637.                     {
  2638.                         $priceRow['BASE_PRICE'] = $priceRow['PRICE'];
  2639.                         $priceRow['DISCOUNT'] = 0;
  2640.                         $priceRow['PERCENT'] = 0;
  2641.                     }
  2642.                 }
  2643.                 else
  2644.                 {
  2645.                     $priceRow['BASE_PRICE'] = $priceRow['PRICE'];
  2646.                     $priceRow['DISCOUNT'] = 0;
  2647.                     $priceRow['PERCENT'] = 0;
  2648.                 }
  2649.                 if ($this->arParams['PRICE_VAT_SHOW_VALUE'])
  2650.                     $priceRow['VAT'] = ($vatRate > 0 ? $roundPriceWithVat - $roundPrice : 0);
  2651.  
  2652.                 $priceRow['PRICE_SCALE'] = \CCurrencyRates::ConvertCurrency(
  2653.                     $priceRow['PRICE'],
  2654.                     $priceRow['CURRENCY'],
  2655.                     $baseCurrency
  2656.                 );
  2657.  
  2658.                 if ($minimalPrice === null || $minimalPrice['PRICE_SCALE'] > $priceRow['PRICE_SCALE'])
  2659.                     $minimalPrice = $priceRow;
  2660.                 if ($this->arParams['FILL_ITEM_ALL_PRICES'])
  2661.                 {
  2662.                     $fullPrices[$priceType] = array(
  2663.                         'ID' => $priceRow['ID'],
  2664.                         'PRICE_TYPE_ID' => $priceRow['PRICE_TYPE_ID'],
  2665.                         'CURRENCY' => $currency,
  2666.                         'BASE_PRICE' => $priceRow['BASE_PRICE'],
  2667.                         'UNROUND_PRICE' => $priceRow['UNROUND_PRICE'],
  2668.                         'PRICE' => $priceRow['PRICE'],
  2669.                         'DISCOUNT' => $priceRow['DISCOUNT'],
  2670.                         'PERCENT' => $priceRow['DISCOUNT']
  2671.                     );
  2672.                     if (isset($priceRow['VAT']))
  2673.                         $fullPrices[$priceType]['VAT'] = $priceRow['VAT'];
  2674.                 }
  2675.  
  2676.                 if ($enableCompatible)
  2677.                 {
  2678.                     if ($this->arParams['USE_PRICE_COUNT'])
  2679.                     {
  2680.                         $rowIndex = $this->getQuantityRangeHash($rawPrice);
  2681.                         $oldMatrix['ROWS'][$rowIndex] = array(
  2682.                             'QUANTITY_FROM' => (float)$rawPrice['QUANTITY_FROM'],
  2683.                             'QUANTITY_TO' => (float)$rawPrice['QUANTITY_TO']
  2684.                         );
  2685.                         if (!isset($oldMatrix['MATRIX'][$priceType]))
  2686.                         {
  2687.                             $oldMatrix['MATRIX'][$priceType] = array();
  2688.                             $oldMatrix['COLS'][$priceType] = $this->storage['PRICE_TYPES'][$priceType];
  2689.                         }
  2690.                         $oldMatrix['MATRIX'][$priceType][$rowIndex] = array(
  2691.                             'ID' => $priceRow['ID'],
  2692.                             'PRICE' => $priceRow['BASE_PRICE'],
  2693.                             'DISCOUNT_PRICE' => $priceRow['PRICE'],
  2694.                             'UNROUND_DISCOUNT_PRICE' => $priceRow['UNROUND_PRICE'],
  2695.                             'CURRENCY' => $currency,
  2696.                             'VAT_RATE' => $percentVat
  2697.                         );
  2698.                         if ($changeCurrency)
  2699.                         {
  2700.                             $oldMatrix['MATRIX'][$priceType][$rowIndex]['ORIG_PRICE'] = \CCurrencyRates::ConvertCurrency(
  2701.                                 $oldMatrix['MATRIX'][$priceType][$rowIndex]['PRICE'],
  2702.                                 $oldMatrix['MATRIX'][$priceType][$rowIndex]['CURRENCY'],
  2703.                                 $oldMatrix['MATRIX'][$priceType][$rowIndex]['ORIG_CURRENCY']
  2704.                             );
  2705.                             $oldMatrix['MATRIX'][$priceType][$rowIndex]['ORIG_DISCOUNT_PRICE'] = \CCurrencyRates::ConvertCurrency(
  2706.                                 $oldMatrix['MATRIX'][$priceType][$rowIndex]['DISCOUNT_PRICE'],
  2707.                                 $oldMatrix['MATRIX'][$priceType][$rowIndex]['CURRENCY'],
  2708.                                 $oldMatrix['MATRIX'][$priceType][$rowIndex]['ORIG_CURRENCY']
  2709.                             );
  2710.                             $oldMatrix['MATRIX'][$priceType][$rowIndex]['ORIG_CURRENCY'] = $rawPrice['CURRENCY'];
  2711.                             $oldMatrix['MATRIX'][$priceType][$rowIndex]['ORIG_VAT_RATE'] = $percentVat; // crazy key, but above all the compatibility
  2712.                         }
  2713.                     }
  2714.                     else
  2715.                     {
  2716.                         $priceCode = $this->storage['PRICES_MAP'][$priceType];
  2717.                         $oldPrices[$priceCode] = array(
  2718.                             'PRICE_ID' => $priceRow['PRICE_TYPE_ID'],
  2719.                             'ID' => $priceRow['ID'],
  2720.                             'CAN_ACCESS' => ($this->storage['PRICES'][$priceCode]['CAN_VIEW'] ? 'Y' : 'N'),
  2721.                             'CAN_BUY' => ($this->storage['PRICES'][$priceCode]['CAN_BUY'] ? 'Y' : 'N'),
  2722.                             'MIN_PRICE' => 'N',
  2723.                             'VALUE_NOVAT' => $price,
  2724.                             'VALUE_VAT' => $priceWithVat,
  2725.                             'VATRATE_VALUE' => $priceWithVat - $price,
  2726.                             'DISCOUNT_VALUE_NOVAT' => $discountPrice,
  2727.                             'DISCOUNT_VALUE_VAT' => $discountPriceWithVat,
  2728.                             'DISCOUNT_VATRATE_VALUE' => $discountPriceWithVat - $discountPrice,
  2729.                             'CURRENCY' => $currency,
  2730.                             'ROUND_VALUE_VAT' => $roundPriceWithVat,
  2731.                             'ROUND_VALUE_NOVAT' => $roundPrice,
  2732.                             'ROUND_VATRATE_VALUE' => $roundPriceWithVat - $roundPrice,
  2733.                             'VALUE' => $priceRow['BASE_PRICE'],
  2734.                             'UNROUND_DISCOUNT_VALUE' => $priceRow['UNROUND_PRICE'],
  2735.                             'DISCOUNT_VALUE' => $priceRow['PRICE'],
  2736.                             'DISCOUNT_DIFF' => $priceRow['DISCOUNT'],
  2737.                             'DISCOUNT_DIFF_PERCENT' => $priceRow['PERCENT']
  2738.                         );
  2739.                         if ($changeCurrency)
  2740.                             $oldPrices[$priceCode]['ORIG_CURRENCY'] = $rawPrice['CURRENCY'];
  2741.                     }
  2742.                 }
  2743.             }
  2744.             unset($priceType);
  2745.         }
  2746.         unset($price);
  2747.  
  2748.         $minimalPriceId = null;
  2749.         if (is_array($minimalPrice))
  2750.         {
  2751.             unset($minimalPrice['PRICE_SCALE']);
  2752.             $minimalPriceId = $minimalPrice['PRICE_TYPE_ID'];
  2753.             $prepareFields = array(
  2754.                 'BASE_PRICE', 'PRICE', 'DISCOUNT'
  2755.             );
  2756.             if ($this->arParams['PRICE_VAT_SHOW_VALUE'])
  2757.                 $prepareFields[] = 'VAT';
  2758.  
  2759.             foreach ($prepareFields as $fieldName)
  2760.             {
  2761.                 $minimalPrice['PRINT_'.$fieldName] = \CCurrencyLang::CurrencyFormat(
  2762.                     $minimalPrice[$fieldName],
  2763.                     $minimalPrice['CURRENCY'],
  2764.                     true
  2765.                 );
  2766.                 $minimalPrice['RATIO_'.$fieldName] = $minimalPrice[$fieldName]*$ratio;
  2767.                 $minimalPrice['PRINT_RATIO_'.$fieldName] = \CCurrencyLang::CurrencyFormat(
  2768.                     $minimalPrice['RATIO_'.$fieldName],
  2769.                     $minimalPrice['CURRENCY'],
  2770.                     true
  2771.                 );
  2772.             }
  2773.             unset($fieldName);
  2774.  
  2775.             if ($this->arParams['FILL_ITEM_ALL_PRICES'])
  2776.             {
  2777.                 foreach (array_keys($fullPrices) as $priceType)
  2778.                 {
  2779.                     foreach ($prepareFields as $fieldName)
  2780.                     {
  2781.                         $fullPrices[$priceType]['PRINT_'.$fieldName] = \CCurrencyLang::CurrencyFormat(
  2782.                             $fullPrices[$priceType][$fieldName],
  2783.                             $fullPrices[$priceType]['CURRENCY'],
  2784.                             true
  2785.                         );
  2786.                         $fullPrices[$priceType]['RATIO_'.$fieldName] = $fullPrices[$priceType][$fieldName]*$ratio;
  2787.                         $fullPrices[$priceType]['PRINT_RATIO_'.$fieldName] = \CCurrencyLang::CurrencyFormat(
  2788.                             $minimalPrice['RATIO_'.$fieldName],
  2789.                             $minimalPrice['CURRENCY'],
  2790.                             true
  2791.                         );
  2792.                     }
  2793.                     unset($fieldName);
  2794.                 }
  2795.                 unset($priceType);
  2796.             }
  2797.  
  2798.             unset($prepareFields);
  2799.         }
  2800.  
  2801.         if ($enableCompatible)
  2802.         {
  2803.             if ($this->arParams['USE_PRICE_COUNT'])
  2804.             {
  2805.                 $oldMatrix['CAN_BUY'] = $this->storage['PRICES_CAN_BUY'];
  2806.                 $this->oldData[$product['ID']]['PRICE_MATRIX'] = $oldMatrix;
  2807.             }
  2808.             else
  2809.             {
  2810.                 $convertFields = array(
  2811.                     'VALUE_NOVAT', 'VALUE_VAT', 'VATRATE_VALUE',
  2812.                     'DISCOUNT_VALUE_NOVAT', 'DISCOUNT_VALUE_VAT', 'DISCOUNT_VATRATE_VALUE'
  2813.                 );
  2814.  
  2815.                 $prepareFields = array(
  2816.                     'VALUE_NOVAT', 'VALUE_VAT', 'VATRATE_VALUE',
  2817.                     'DISCOUNT_VALUE_NOVAT', 'DISCOUNT_VALUE_VAT', 'DISCOUNT_VATRATE_VALUE',
  2818.                     'VALUE', 'DISCOUNT_VALUE', 'DISCOUNT_DIFF'
  2819.                 );
  2820.  
  2821.                 if (!empty($oldPrices))
  2822.                 {
  2823.                     foreach (array_keys($oldPrices) as $index)
  2824.                     {
  2825.                         foreach ($prepareFields as $fieldName)
  2826.                             $oldPrices[$index]['PRINT_'.$fieldName] = \CCurrencyLang::CurrencyFormat(
  2827.                                 $oldPrices[$index][$fieldName],
  2828.                                 $oldPrices[$index]['CURRENCY'],
  2829.                                 true
  2830.                             );
  2831.                         unset($fieldName);
  2832.                         if (isset($oldPrices[$index]['ORIG_CURRENCY']))
  2833.                         {
  2834.                             foreach ($convertFields as $fieldName)
  2835.                                 $oldPrices[$index]['ORIG_' . $fieldName] = \CCurrencyRates::ConvertCurrency(
  2836.                                     $oldPrices[$index][$fieldName],
  2837.                                     $oldPrices[$index]['CURRENCY'],
  2838.                                     $oldPrices[$index]['ORIG_CURRENCY']
  2839.                                 );
  2840.                             unset($fieldName);
  2841.                         }
  2842.                         if ($oldPrices[$index]['PRICE_ID'] === $minimalPriceId)
  2843.                         {
  2844.                             $oldPrices[$index]['MIN_PRICE'] = 'Y';
  2845.                             $oldMinPrice = $oldPrices[$index];
  2846.                         }
  2847.                     }
  2848.                     unset($index);
  2849.                 }
  2850.                 unset($prepareFields);
  2851.  
  2852.                 $this->oldData[$product['ID']]['PRICES'] = $oldPrices;
  2853.                 $this->oldData[$product['ID']]['MIN_PRICE'] = $oldMinPrice;
  2854.             }
  2855.         }
  2856.         unset($oldMatrix, $oldMinPrice, $oldPrices);
  2857.  
  2858.         if (!$this->arParams['FILL_ITEM_ALL_PRICES'])
  2859.             return $minimalPrice;
  2860.  
  2861.         return array(
  2862.             'MINIMAL_PRICE' => $minimalPrice,
  2863.             'ALL_PRICES' => array(
  2864.                 'QUANTITY_FROM' => $minimalPrice['QUANTITY_FROM'],
  2865.                 'QUANTITY_TO' => $minimalPrice['QUANTITY_TO'],
  2866.                 'QUANTITY_HASH' => $minimalPrice['QUANTITY_HASH'],
  2867.                 'MEASURE_RATIO_ID' => $minimalPrice['MEASURE_RATIO_ID'],
  2868.                 'PRICES' => $fullPrices
  2869.             )
  2870.         );
  2871.     }
  2872.  
  2873.     protected function searchItemSelectedRatioId($id)
  2874.     {
  2875.         if (!isset($this->ratios[$id]))
  2876.             return null;
  2877.  
  2878.         $minimal = null;
  2879.         $minimalRatio = null;
  2880.         $result = null;
  2881.         foreach ($this->ratios[$id] as $ratio)
  2882.         {
  2883.             if ($minimalRatio === null || $minimalRatio > $ratio['RATIO'])
  2884.             {
  2885.                 $minimalRatio = $ratio['RATIO'];
  2886.                 $minimal = $ratio['ID'];
  2887.             }
  2888.             if ($ratio['IS_DEFAULT'] === 'Y')
  2889.             {
  2890.                 $result = $ratio['ID'];
  2891.                 break;
  2892.             }
  2893.         }
  2894.         unset($ratio);
  2895.         return ($result === null ? $minimal : $result);
  2896.     }
  2897.  
  2898.     protected function compactItemRatios($id)
  2899.     {
  2900.         $ratioId = $this->searchItemSelectedRatioId($id);
  2901.         if ($ratioId === null)
  2902.             return;
  2903.         $this->ratios[$id] = array(
  2904.             $ratioId => $this->ratios[$id][$ratioId]
  2905.         );
  2906.     }
  2907.  
  2908.     protected function getQuantityRangeHash(array $range)
  2909.     {
  2910.         return ($range['QUANTITY_FROM'] === null ? 'ZERO' : $range['QUANTITY_FROM']).
  2911.             '-'.($range['QUANTITY_TO'] === null ? 'INF' : $range['QUANTITY_TO']);
  2912.     }
  2913.  
  2914.     protected function getFullQuantityRange()
  2915.     {
  2916.         return array(
  2917.             'HASH' => $this->getQuantityRangeHash(array('QUANTITY_FROM' => null, 'QUANTITY_TO' => null)),
  2918.             'QUANTITY_FROM' => null,
  2919.             'QUANTITY_TO' => null,
  2920.             'SORT_FROM' => 0,
  2921.             'SORT_TO' => INF
  2922.         );
  2923.     }
  2924.  
  2925.     protected function searchItemSelectedQuantityRangeHash($id)
  2926.     {
  2927.         if (empty($this->quantityRanges[$id]))
  2928.             return null;
  2929.         foreach ($this->quantityRanges[$id] as $range)
  2930.         {
  2931.             if ($this->checkQuantityRange($range))
  2932.                 return $range['HASH'];
  2933.         }
  2934.         reset($this->quantityRanges[$id]);
  2935.         $firsrRange = current($this->quantityRanges[$id]);
  2936.         return $firsrRange['HASH'];
  2937.     }
  2938.  
  2939.     /**
  2940.      * Load URLs for different actions to storage.
  2941.      *
  2942.      * @return void
  2943.      */
  2944.     protected function initUrlTemplates()
  2945.     {
  2946.         /** @global \CMain $APPLICATION */
  2947.         global $APPLICATION;
  2948.  
  2949.         $actionVar = $this->arParams['ACTION_VARIABLE'];
  2950.         $productIdVar = $this->arParams['PRODUCT_ID_VARIABLE'];
  2951.  
  2952.         if (!empty($this->arParams['CUSTOM_CURRENT_PAGE']))
  2953.         {
  2954.             $pageUrl = $this->arParams['CUSTOM_CURRENT_PAGE'];
  2955.         }
  2956.         else
  2957.         {
  2958.             $pageUrl = $this->request->isAjaxRequest()
  2959.                 ? $this->arParams['CURRENT_BASE_PAGE']
  2960.                 : $APPLICATION->GetCurPageParam();
  2961.         }
  2962.  
  2963.         $currentPath = \CHTTP::urlDeleteParams(
  2964.             $pageUrl,
  2965.             array($productIdVar, $actionVar, ''),
  2966.             array('delete_system_params' => true)
  2967.         );
  2968.         $currentPath .= (stripos($currentPath, '?') === false ? '?' : '&');
  2969.  
  2970.         if ($this->arParams['COMPARE_PATH'] == '')
  2971.         {
  2972.             $comparePath = $currentPath;
  2973.         }
  2974.         else
  2975.         {
  2976.             $comparePath = \CHTTP::urlDeleteParams(
  2977.                 $this->arParams['COMPARE_PATH'],
  2978.                 array($productIdVar, $actionVar, ''),
  2979.                 array('delete_system_params' => true)
  2980.             );
  2981.             $comparePath .= (stripos($comparePath, '?') === false ? '?' : '&');
  2982.         }
  2983.  
  2984.         $this->arParams['COMPARE_PATH'] = $comparePath.$actionVar.'=COMPARE';
  2985.  
  2986.         $urls = array();
  2987.         $urls['~BUY_URL_TEMPLATE'] = $currentPath.$actionVar.'='.self::ACTION_BUY.'&'.$productIdVar.'=#ID#';
  2988.         $urls['~ADD_URL_TEMPLATE'] = $currentPath.$actionVar.'='.self::ACTION_ADD_TO_BASKET.'&'.$productIdVar.'=#ID#';
  2989.         $urls['~SUBSCRIBE_URL_TEMPLATE'] = $currentPath.$actionVar.'='.self::ACTION_SUBSCRIBE.'&'.$productIdVar.'=#ID#';
  2990.         $urls['~COMPARE_URL_TEMPLATE'] = $comparePath.$actionVar.'='.self::ACTION_ADD_TO_COMPARE.'&'.$productIdVar.'=#ID#';
  2991.         $urls['~COMPARE_DELETE_URL_TEMPLATE'] = $comparePath.$actionVar.'='.self::ACTION_DELETE_FROM_COMPARE.'&'.$productIdVar.'=#ID#';
  2992.  
  2993.         $urls['BUY_URL_TEMPLATE'] = Main\Text\HtmlFilter::encode($urls['~BUY_URL_TEMPLATE']);
  2994.         $urls['ADD_URL_TEMPLATE'] = Main\Text\HtmlFilter::encode($urls['~ADD_URL_TEMPLATE']);
  2995.         $urls['SUBSCRIBE_URL_TEMPLATE'] = Main\Text\HtmlFilter::encode($urls['~SUBSCRIBE_URL_TEMPLATE']);
  2996.         $urls['COMPARE_URL_TEMPLATE'] = Main\Text\HtmlFilter::encode($urls['~COMPARE_URL_TEMPLATE']);
  2997.         $urls['COMPARE_DELETE_URL_TEMPLATE'] = Main\Text\HtmlFilter::encode($urls['~COMPARE_DELETE_URL_TEMPLATE']);
  2998.  
  2999.         $this->storage['URLS'] = $urls;
  3000.     }
  3001.  
  3002.     /**
  3003.      * Process element prices.
  3004.      *
  3005.      * @param array &$element       Item data.
  3006.      * @return void
  3007.      */
  3008.     protected function modifyElementPrices(&$element)
  3009.     {
  3010.         $enableCompatible = $this->isEnableCompatible();
  3011.         $id = $element['ID'];
  3012.         $iblockId = $element['IBLOCK_ID'];
  3013.         $catalog = !empty($this->storage['CATALOGS'][$element['IBLOCK_ID']])
  3014.             ? $this->storage['CATALOGS'][$element['IBLOCK_ID']]
  3015.             : array();
  3016.  
  3017.         $element['ITEM_PRICE_MODE'] = null;
  3018.         $element['ITEM_PRICES'] = array();
  3019.         $element['ITEM_QUANTITY_RANGES'] = array();
  3020.         $element['ITEM_MEASURE_RATIOS'] = array();
  3021.         $element['ITEM_MEASURE'] = array();
  3022.         $element['ITEM_MEASURE_RATIO_SELECTED'] = null;
  3023.         $element['ITEM_QUANTITY_RANGE_SELECTED'] = null;
  3024.         $element['ITEM_PRICE_SELECTED'] = null;
  3025.  
  3026.         if (!empty($catalog))
  3027.         {
  3028.             if (!isset($this->productWithOffers[$iblockId]))
  3029.                 $this->productWithOffers[$iblockId] = array();
  3030.             if ($element['PRODUCT']['TYPE'] == Catalog\ProductTable::TYPE_SKU)
  3031.             {
  3032.                 $this->productWithOffers[$iblockId][$id] = $id;
  3033.                 if ($this->storage['SHOW_CATALOG_WITH_OFFERS'] && $enableCompatible)
  3034.                 {
  3035.                     $this->productWithPrices[$id] = $id;
  3036.                     $this->calculatePrices[$id] = $id;
  3037.                 }
  3038.             }
  3039.  
  3040.             if (in_array(
  3041.                 $element['PRODUCT']['TYPE'],
  3042.                 array(
  3043.                     Catalog\ProductTable::TYPE_PRODUCT,
  3044.                     Catalog\ProductTable::TYPE_SET,
  3045.                     Catalog\ProductTable::TYPE_OFFER
  3046.                 )
  3047.             ))
  3048.             {
  3049.                 $this->productWithPrices[$id] = $id;
  3050.                 $this->calculatePrices[$id] = $id;
  3051.             }
  3052.  
  3053.             if (isset($this->productWithPrices[$id]))
  3054.             {
  3055.                 if ($element['PRODUCT']['MEASURE'] > 0)
  3056.                 {
  3057.                     $element['ITEM_MEASURE'] = array(
  3058.                         'ID' => $element['PRODUCT']['MEASURE'],
  3059.                         'TITLE' => '',
  3060.                         '~TITLE' => ''
  3061.                     );
  3062.                 }
  3063.                 else
  3064.                 {
  3065.                     $element['ITEM_MEASURE'] = array(
  3066.                         'ID' => null,
  3067.                         'TITLE' => $this->storage['DEFAULT_MEASURE']['SYMBOL_RUS'],
  3068.                         '~TITLE' => $this->storage['DEFAULT_MEASURE']['~SYMBOL_RUS']
  3069.                     );
  3070.                 }
  3071.                 if ($enableCompatible)
  3072.                 {
  3073.                     $element['CATALOG_MEASURE'] = $element['ITEM_MEASURE']['ID'];
  3074.                     $element['CATALOG_MEASURE_NAME'] = $element['ITEM_MEASURE']['TITLE'];
  3075.                     $element['~CATALOG_MEASURE_NAME'] = $element['ITEM_MEASURE']['~TITLE'];
  3076.                 }
  3077.             }
  3078.         }
  3079.         else
  3080.         {
  3081.             $element['PRICES'] = \CIBlockPriceTools::GetItemPrices(
  3082.                 $element['IBLOCK_ID'],
  3083.                 $this->storage['PRICES'],
  3084.                 $element,
  3085.                 $this->arParams['PRICE_VAT_INCLUDE'],
  3086.                 $this->storage['CONVERT_CURRENCY']
  3087.             );
  3088.             if (!empty($element['PRICES']))
  3089.             {
  3090.                 $element['MIN_PRICE'] = \CIBlockPriceTools::getMinPriceFromList($element['PRICES']);
  3091.             }
  3092.  
  3093.             $element['CAN_BUY'] = \CIBlockPriceTools::CanBuy($element['IBLOCK_ID'], $this->storage['PRICES'], $element);
  3094.         }
  3095.     }
  3096.  
  3097.     /**
  3098.      * Load, calculate and fill data (prices, measures, discounts, deprecated fields) for simple products.
  3099.      *
  3100.      * @return void.
  3101.      */
  3102.     protected function processProducts()
  3103.     {
  3104.         $this->initItemsMeasure($this->elements);
  3105.         $this->loadMeasures($this->getMeasureIds($this->elements));
  3106.  
  3107.         $this->loadMeasureRatios($this->productWithPrices);
  3108.  
  3109.         $this->loadPrices($this->productWithPrices);
  3110.         $this->calculateItemPrices($this->elements);
  3111.  
  3112.         $this->transferItems($this->elements);
  3113.     }
  3114.  
  3115.     /**
  3116.      * Load, calculate and fill data (prices, measures, discounts, deprecated fields) for offers.
  3117.      * Link offers to products.
  3118.      *
  3119.      * @return void
  3120.      */
  3121.     protected function processOffers()
  3122.     {
  3123.         if ($this->useCatalog && !empty($this->iblockProducts))
  3124.         {
  3125.             $offers = array();
  3126.  
  3127.             $paramStack = array();
  3128.             $enableCompatible = $this->isEnableCompatible();
  3129.             if ($enableCompatible)
  3130.             {
  3131.                 $paramStack['USE_PRICE_COUNT'] = $this->arParams['USE_PRICE_COUNT'];
  3132.                 $paramStack['SHOW_PRICE_COUNT'] = $this->arParams['SHOW_PRICE_COUNT'];
  3133.                 $this->arParams['USE_PRICE_COUNT'] = false;
  3134.                 $this->arParams['SHOW_PRICE_COUNT'] = 1;
  3135.             }
  3136.  
  3137.             foreach (array_keys($this->iblockProducts) as $iblock)
  3138.             {
  3139.                 if (!empty($this->productWithOffers[$iblock]))
  3140.                 {
  3141.                     $iblockOffers = $this->getIblockOffers($iblock);
  3142.                     if (!empty($iblockOffers))
  3143.                     {
  3144.                         $offersId = array_keys($iblockOffers);
  3145.                         $this->initItemsMeasure($iblockOffers);
  3146.                         $this->loadMeasures($this->getMeasureIds($iblockOffers));
  3147.  
  3148.                         $this->loadMeasureRatios($offersId);
  3149.  
  3150.                         $this->loadPrices($offersId);
  3151.                         $this->calculateItemPrices($iblockOffers);
  3152.  
  3153.                         $this->transferItems($iblockOffers);
  3154.  
  3155.                         $this->modifyOffers($iblockOffers);
  3156.                         $this->chooseOffer($iblockOffers, $iblock);
  3157.  
  3158.                         $offers = array_merge($offers, $iblockOffers);
  3159.                     }
  3160.                     unset($iblockOffers);
  3161.                 }
  3162.             }
  3163.             if ($enableCompatible)
  3164.             {
  3165.                 $this->arParams['USE_PRICE_COUNT'] = $paramStack['USE_PRICE_COUNT'];
  3166.                 $this->arParams['SHOW_PRICE_COUNT'] = $paramStack['SHOW_PRICE_COUNT'];
  3167.             }
  3168.             unset($enableCompatible, $paramStack);
  3169.         }
  3170.     }
  3171.  
  3172.     /**
  3173.      * Return offers array for current iblock.
  3174.      *
  3175.      * @param $iblockId
  3176.      * @return array
  3177.      */
  3178.     protected function getIblockOffers($iblockId)
  3179.     {
  3180.         $offers = array();
  3181.         $iblockParams = $this->storage['IBLOCK_PARAMS'][$iblockId];
  3182.  
  3183.         $enableCompatible = $this->isEnableCompatible();
  3184.  
  3185.         if (
  3186.             $this->useCatalog
  3187.             && $this->offerIblockExist($iblockId)
  3188.             && !empty($this->productWithOffers[$iblockId])
  3189.         )
  3190.         {
  3191.             $catalog = $this->storage['CATALOGS'][$iblockId];
  3192.  
  3193.             $productProperty = 'PROPERTY_'.$catalog['SKU_PROPERTY_ID'];
  3194.             $productPropertyValue = $productProperty.'_VALUE';
  3195.  
  3196.             $offersFilter = $this->getOffersFilter($catalog['IBLOCK_ID']);
  3197.             $offersFilter[$productProperty] = $this->productWithOffers[$iblockId];
  3198.  
  3199.             $offersOrder = $this->getOffersSort();
  3200.  
  3201.             $offersSelect = array(
  3202.                 'ID' => 1,
  3203.                 'IBLOCK_ID' => 1,
  3204.                 $productProperty => 1,
  3205.                 'CATALOG_TYPE' => 1
  3206.             );
  3207.             if (!empty($iblockParams['OFFERS_FIELD_CODE']))
  3208.             {
  3209.                 foreach ($iblockParams['OFFERS_FIELD_CODE'] as $code)
  3210.                     $offersSelect[$code] = 1;
  3211.                 unset($code);
  3212.             }
  3213.  
  3214.             $checkFields = array();
  3215.             foreach (array_keys($offersOrder) as $code)
  3216.             {
  3217.                 $code = strtoupper($code);
  3218.                 $offersSelect[$code] = 1;
  3219.                 if ($code == 'ID' || $code == 'CATALOG_AVAILABLE')
  3220.                     continue;
  3221.                 $checkFields[] = $code;
  3222.             }
  3223.             unset($code);
  3224.  
  3225.             $offersSelect['PREVIEW_PICTURE'] = 1;
  3226.             $offersSelect['DETAIL_PICTURE'] = 1;
  3227.  
  3228.             $offersId = array();
  3229.             $offersCount = array();
  3230.             $iterator = \CIBlockElement::GetList(
  3231.                 $offersOrder,
  3232.                 $offersFilter,
  3233.                 false,
  3234.                 false,
  3235.                 array_keys($offersSelect)
  3236.             );
  3237.             while($row = $iterator->GetNext())
  3238.             {
  3239.                 $row['ID'] = (int)$row['ID'];
  3240.                 $row['IBLOCK_ID'] = (int)$row['IBLOCK_ID'];
  3241.                 $productId = (int)$row[$productPropertyValue];
  3242.  
  3243.                 if ($productId <= 0)
  3244.                     continue;
  3245.  
  3246.                 if ($enableCompatible && $this->arParams['OFFERS_LIMIT'] > 0)
  3247.                 {
  3248.                     $offersCount[$productId]++;
  3249.                     if($offersCount[$productId] > $this->arParams['OFFERS_LIMIT'])
  3250.                         continue;
  3251.                 }
  3252.  
  3253.                 $row['SORT_HASH'] = 'ID';
  3254.                 if (!empty($checkFields))
  3255.                 {
  3256.                     $checkValues = '';
  3257.                     foreach ($checkFields as $code)
  3258.                         $checkValues .= (isset($row[$code]) ? $row[$code] : '').'|';
  3259.                     unset($code);
  3260.                     if ($checkValues != '')
  3261.                         $row['SORT_HASH'] = md5($checkValues);
  3262.                     unset($checkValues);
  3263.                 }
  3264.                 $row['LINK_ELEMENT_ID'] = $productId;
  3265.                 $row['PROPERTIES'] = array();
  3266.                 $row['DISPLAY_PROPERTIES'] = array();
  3267.  
  3268.                 /* it is not the final version */
  3269.                 $row['PRODUCT'] = array(
  3270.                     'TYPE' => null,
  3271.                     'AVAILABLE' => null,
  3272.                     'MEASURE' => null,
  3273.                     'VAT_ID' => null,
  3274.                     'VAT_RATE' => null,
  3275.                     'VAT_INCLUDED' => null,
  3276.                     'QUANTITY' => null,
  3277.                     'QUANTITY_TRACE' => null,
  3278.                     'CAN_BUY_ZERO' => null,
  3279.                     'SUBSCRIPTION' => null
  3280.                 );
  3281.  
  3282.                 if (isset($row['CATALOG_TYPE']))
  3283.                 {
  3284.                     $row['CATALOG_TYPE'] = (int)$row['CATALOG_TYPE']; // this key will be deprecated
  3285.                     $row['PRODUCT']['TYPE'] = $row['CATALOG_TYPE'];
  3286.                 }
  3287.                 if (isset($row['CATALOG_MEASURE']))
  3288.                 {
  3289.                     $row['CATALOG_MEASURE'] = (int)$row['CATALOG_MEASURE']; // this key will be deprecated
  3290.                     $row['PRODUCT']['MEASURE'] = $row['CATALOG_MEASURE'];
  3291.                 }
  3292.                 /*
  3293.                  * this keys will be deprecated
  3294.                  * CATALOG_*
  3295.                  */
  3296.                 if (isset($row['CATALOG_AVAILABLE']))
  3297.                     $row['PRODUCT']['AVAILABLE'] = $row['CATALOG_AVAILABLE'];
  3298.                 if (isset($row['CATALOG_VAT']))
  3299.                     $row['PRODUCT']['VAT_RATE'] = $row['CATALOG_VAT'];
  3300.                 if (isset($row['CATALOG_VAT_INCLUDED']))
  3301.                     $row['PRODUCT']['VAT_INCLUDED'] = $row['CATALOG_VAT_INCLUDED'];
  3302.                 if (isset($row['CATALOG_QUANTITY']))
  3303.                     $row['PRODUCT']['QUANTITY'] = $row['CATALOG_QUANTITY'];
  3304.                 if (isset($row['CATALOG_QUANTITY_TRACE']))
  3305.                     $row['PRODUCT']['QUANTITY_TRACE'] = $row['CATALOG_QUANTITY_TRACE'];
  3306.                 if (isset($row['CATALOG_CAN_BUY_ZERO']))
  3307.                     $row['PRODUCT']['CAN_BUY_ZERO'] = $row['CATALOG_CAN_BUY_ZERO'];
  3308.                 if (isset($element['CATALOG_SUBSCRIPTION']))
  3309.                     $element['PRODUCT']['SUBSCRIPTION'] = $element['CATALOG_SUBSCRIPTION'];
  3310.                 /* it is not the final version - end*/
  3311.  
  3312.                 if ($row['PRODUCT']['TYPE'] == Catalog\ProductTable::TYPE_OFFER)
  3313.                     $this->calculatePrices[$row['ID']] = $row['ID'];
  3314.  
  3315.                 $row['ITEM_PRICE_MODE'] = null;
  3316.                 $row['ITEM_PRICES'] = array();
  3317.                 $row['ITEM_QUANTITY_RANGES'] = array();
  3318.                 $row['ITEM_MEASURE_RATIOS'] = array();
  3319.                 $row['ITEM_MEASURE'] = array();
  3320.                 $row['ITEM_MEASURE_RATIO_SELECTED'] = null;
  3321.                 $row['ITEM_QUANTITY_RANGE_SELECTED'] = null;
  3322.                 $row['ITEM_PRICE_SELECTED'] = null;
  3323.                 $row['CHECK_QUANTITY'] = $this->isNeedCheckQuantity($row['PRODUCT']);
  3324.  
  3325.                 if ($row['PRODUCT']['MEASURE'] > 0)
  3326.                 {
  3327.                     $row['ITEM_MEASURE'] = array(
  3328.                         'ID' => $row['PRODUCT']['MEASURE'],
  3329.                         'TITLE' => '',
  3330.                         '~TITLE' => ''
  3331.                     );
  3332.                 }
  3333.                 else
  3334.                 {
  3335.                     $row['ITEM_MEASURE'] = array(
  3336.                         'ID' => null,
  3337.                         'TITLE' => $this->storage['DEFAULT_MEASURE']['SYMBOL_RUS'],
  3338.                         '~TITLE' => $this->storage['DEFAULT_MEASURE']['~SYMBOL_RUS']
  3339.                     );
  3340.                 }
  3341.                 if ($enableCompatible)
  3342.                 {
  3343.                     $row['CATALOG_MEASURE'] = $row['ITEM_MEASURE']['ID'];
  3344.                     $row['CATALOG_MEASURE_NAME'] = $row['ITEM_MEASURE']['TITLE'];
  3345.                     $row['~CATALOG_MEASURE_NAME'] = $row['ITEM_MEASURE']['~TITLE'];
  3346.                 }
  3347.  
  3348.                 $row['PROPERTIES'] = array();
  3349.                 $row['DISPLAY_PROPERTIES'] = array();
  3350.  
  3351.                 Iblock\Component\Tools::getFieldImageData(
  3352.                     $row,
  3353.                     array('PREVIEW_PICTURE', 'DETAIL_PICTURE'),
  3354.                     Iblock\Component\Tools::IPROPERTY_ENTITY_ELEMENT,
  3355.                     ''
  3356.                 );
  3357.  
  3358.                 $offersId[$row['ID']] = $row['ID'];
  3359.                 $offers[$row['ID']] = $row;
  3360.             }
  3361.             unset($row, $iterator);
  3362.  
  3363.             if (!empty($offersId))
  3364.             {
  3365.                 $propertyList = $this->getPropertyList(
  3366.                     $catalog['IBLOCK_ID'],
  3367.                     $iblockParams['OFFERS_PROPERTY_CODE']
  3368.                 );
  3369.                 if (!empty($propertyList))
  3370.                 {
  3371.                     \CIBlockElement::GetPropertyValuesArray($offers, $catalog['IBLOCK_ID'], $offersFilter);
  3372.                     foreach ($offers as &$row)
  3373.                     {
  3374.                         if ($this->useDiscountCache)
  3375.                         {
  3376.                             if ($this->storage['USE_SALE_DISCOUNTS'])
  3377.                                 Catalog\Discount\DiscountManager::setProductPropertiesCache($row['ID'], $row["PROPERTIES"]);
  3378.                             else
  3379.                                 \CCatalogDiscount::SetProductPropertiesCache($row['ID'], $row["PROPERTIES"]);
  3380.                         }
  3381.  
  3382.                         foreach ($propertyList as $pid)
  3383.                         {
  3384.                             if (!isset($row["PROPERTIES"][$pid]))
  3385.                                 continue;
  3386.                             $prop = &$row["PROPERTIES"][$pid];
  3387.                             $boolArr = is_array($prop["VALUE"]);
  3388.                             if(
  3389.                                 ($boolArr && !empty($prop["VALUE"])) ||
  3390.                                 (!$boolArr && (string)$prop["VALUE"] !== '')
  3391.                             )
  3392.                             {
  3393.                                 $row["DISPLAY_PROPERTIES"][$pid] = \CIBlockFormatProperties::GetDisplayValue($row, $prop, "catalog_out");
  3394.                             }
  3395.                             unset($boolArr, $prop);
  3396.                         }
  3397.                         unset($pid);
  3398.                     }
  3399.                     unset($row);
  3400.                 }
  3401.  
  3402.                 if ($this->useDiscountCache)
  3403.                 {
  3404.                     if ($this->storage['USE_SALE_DISCOUNTS'])
  3405.                     {
  3406.                         Catalog\Discount\DiscountManager::preloadPriceData($offersId, $this->storage['PRICES_ALLOW']);
  3407.                         Catalog\Discount\DiscountManager::preloadProductDataToExtendOrder($offersId, $this->getUserGroups());
  3408.                     }
  3409.                     else
  3410.                     {
  3411.                         \CCatalogDiscount::SetProductSectionsCache($offersId);
  3412.                         \CCatalogDiscount::SetDiscountProductCache($offersId, array('IBLOCK_ID' => $catalog['IBLOCK_ID'], 'GET_BY_ID' => 'Y'));
  3413.                     }
  3414.                 }
  3415.             }
  3416.             unset($offersId);
  3417.         }
  3418.  
  3419.         return $offers;
  3420.     }
  3421.  
  3422.     protected function getOffersFilter($iblockId)
  3423.     {
  3424.         $offersFilter = array(
  3425.             'IBLOCK_ID' => $iblockId,
  3426.             'ACTIVE' => 'Y',
  3427.             'ACTIVE_DATE' => 'Y',
  3428.             'CHECK_PERMISSIONS' => 'N'
  3429.         );
  3430.  
  3431.         if ($this->arParams['HIDE_NOT_AVAILABLE_OFFERS'] === 'Y')
  3432.         {
  3433.             $offersFilter['CATALOG_AVAILABLE'] = 'Y';
  3434.         }
  3435.         elseif ($this->arParams['HIDE_NOT_AVAILABLE_OFFERS'] === 'L')
  3436.         {
  3437.             $offersFilter['CUSTOM_FILTER'] = array(
  3438.                 'LOGIC' => 'OR',
  3439.                 'CATALOG_AVAILABLE' => 'Y',
  3440.                 'CATALOG_SUBSCRIBE' => 'Y'
  3441.             );
  3442.         }
  3443.  
  3444.         if (!$this->arParams['USE_PRICE_COUNT'])
  3445.         {
  3446.             $offersFilter['SHOW_PRICE_COUNT'] = $this->arParams['SHOW_PRICE_COUNT'];
  3447.         }
  3448.  
  3449.         return $offersFilter;
  3450.     }
  3451.  
  3452.     /**
  3453.      * Return offers sort fields to execute.
  3454.      *
  3455.      * @return array
  3456.      */
  3457.     protected function getOffersSort()
  3458.     {
  3459.         $offersOrder = array(
  3460.             $this->arParams['OFFERS_SORT_FIELD'] => $this->arParams['OFFERS_SORT_ORDER'],
  3461.             $this->arParams['OFFERS_SORT_FIELD2'] => $this->arParams['OFFERS_SORT_ORDER2']
  3462.         );
  3463.         if (!isset($offersOrder['ID']))
  3464.             $offersOrder['ID'] = 'DESC';
  3465.  
  3466.         return $offersOrder;
  3467.     }
  3468.  
  3469.     protected function modifyOffers($offers)
  3470.     {
  3471.         //$urls = $this->storage['URLS'];
  3472.  
  3473.         foreach ($offers as &$offer)
  3474.         {
  3475.             $elementId = $offer['LINK_ELEMENT_ID'];
  3476.  
  3477.             if (!isset($this->elementLinks[$elementId]))
  3478.                 continue;
  3479.  
  3480.             $offer['CAN_BUY'] = $this->elementLinks[$elementId]['ACTIVE'] === 'Y' && $offer['CAN_BUY'];
  3481.  
  3482.             $this->elementLinks[$elementId]['OFFERS'][] = $offer;
  3483.  
  3484.             unset($elementId, $offer);
  3485.         }
  3486.     }
  3487.  
  3488.     abstract protected function chooseOffer($offers, $iblockId);
  3489.  
  3490.     protected function initResultCache()
  3491.     {
  3492.         if (
  3493.             $this->arParams['CONVERT_CURRENCY'] === 'Y'
  3494.             && !empty($this->storage['CURRENCY_LIST'])
  3495.             && defined('BX_COMP_MANAGED_CACHE')
  3496.         )
  3497.         {
  3498.             $this->storage['CURRENCY_LIST'][$this->storage['CONVERT_CURRENCY']['CURRENCY_ID']] = $this->storage['CONVERT_CURRENCY']['CURRENCY_ID'];
  3499.             $taggedCache = Main\Application::getInstance()->getTaggedCache();
  3500.             foreach ($this->storage['CURRENCY_LIST'] as $oneCurrency)
  3501.             {
  3502.                 $taggedCache->registerTag('currency_id_'.$oneCurrency);
  3503.             }
  3504.  
  3505.             unset($oneCurrency);
  3506.             unset($taggedCache);
  3507.         }
  3508.  
  3509.         unset($this->storage['CURRENCY_LIST']);
  3510.  
  3511.         $this->setResultCacheKeys($this->getCacheKeys());
  3512.     }
  3513.  
  3514.     protected function getCacheKeys()
  3515.     {
  3516.         return array();
  3517.     }
  3518.  
  3519.     /**
  3520.      * All iblock/section/element/offer initializations starts here.
  3521.      * If have no errors - result showed in $arResult.
  3522.      */
  3523.     protected function processResultData()
  3524.     {
  3525.         $this->iblockProducts = $this->getProductsSeparatedByIblock();
  3526.         $this->checkIblock();
  3527.  
  3528.         if ($this->hasErrors())
  3529.             return;
  3530.  
  3531.         $this->initCurrencyConvert();
  3532.         $this->initCatalogInfo();
  3533.         $this->initPrices();
  3534.         $this->initUrlTemplates();
  3535.  
  3536.         $this->initElementList();
  3537.         if (!$this->hasErrors())
  3538.         {
  3539.             $this->sortElementList();
  3540.             $this->makeElementLinks();
  3541.             $this->prepareData();
  3542.             $this->filterPureOffers();
  3543.             $this->makeOutputResult();
  3544.         }
  3545.     }
  3546.  
  3547.     /**
  3548.      * Check for correct iblocks.
  3549.      */
  3550.     protected function checkIblock()
  3551.     {
  3552.         if (!empty($this->iblockProducts))
  3553.         {
  3554.             $iblocks = array();
  3555.             $iblockIterator = Iblock\IblockSiteTable::getList(array(
  3556.                 'select' => array('IBLOCK_ID'),
  3557.                 'filter' => array(
  3558.                     '=IBLOCK_ID' => array_keys($this->iblockProducts),
  3559.                     '=SITE_ID' => $this->getSiteId(),
  3560.                     '=IBLOCK.ACTIVE' => 'Y'
  3561.                 )
  3562.             ));
  3563.             while ($iblock = $iblockIterator->fetch())
  3564.             {
  3565.                 $iblocks[$iblock['IBLOCK_ID']] = true;
  3566.             }
  3567.  
  3568.             foreach ($this->iblockProducts as $iblock => $products)
  3569.             {
  3570.                 if (!isset($iblocks[$iblock]))
  3571.                 {
  3572.                     unset($this->iblockProducts[$iblock]);
  3573.                 }
  3574.             }
  3575.  
  3576.             if (empty($this->iblockProducts))
  3577.             {
  3578.                 $this->abortResultCache();
  3579.                 $this->errorCollection->setError(new Error(Loc::getMessage('INVALID_IBLOCK'), self::ERROR_TEXT));
  3580.             }
  3581.         }
  3582.     }
  3583.  
  3584.     protected function prepareData()
  3585.     {
  3586.         $this->clearItems();
  3587.         $this->initCatalogDiscountCache();
  3588.         $this->processProducts();
  3589.         $this->processOffers();
  3590.         $this->makeOutputResult();
  3591.         $this->clearItems();
  3592.     }
  3593.  
  3594.     protected function filterPureOffers()
  3595.     {
  3596.         if (!empty($this->productIds) && is_array($this->productIds))
  3597.         {
  3598.             foreach ($this->productIds as $productId)
  3599.             {
  3600.                 // check if it's element
  3601.                 if ($this->productIdMap[$productId] == $productId)
  3602.                 {
  3603.                     continue;
  3604.                 }
  3605.  
  3606.                 if (isset($this->elementLinks[$this->productIdMap[$productId]]) && !empty($this->elementLinks[$this->productIdMap[$productId]]['OFFERS']))
  3607.                 {
  3608.                     // clear all unwanted offers
  3609.                     foreach ($this->elementLinks[$this->productIdMap[$productId]]['OFFERS'] as $key => $offer)
  3610.                     {
  3611.                         if ($offer['ID'] != $productId)
  3612.                         {
  3613.                             unset($this->elementLinks[$this->productIdMap[$productId]]['OFFERS'][$key]);
  3614.                         }
  3615.                     }
  3616.                 }
  3617.             }
  3618.         }
  3619.     }
  3620.  
  3621.     /**
  3622.      * Set component data from storage to $arResult.
  3623.      */
  3624.     protected function makeOutputResult()
  3625.     {
  3626.         $this->arResult = array_merge($this->arResult, $this->storage['URLS']);
  3627.         $this->arResult['CONVERT_CURRENCY'] = $this->storage['CONVERT_CURRENCY'];
  3628.         $this->arResult['CATALOGS'] = $this->storage['CATALOGS'];
  3629.         $this->arResult['MODULES'] = $this->storage['MODULES'];
  3630.         $this->arResult['PRICES_ALLOW'] = $this->storage['PRICES_ALLOW'];
  3631.  
  3632.         if ($this->isEnableCompatible())
  3633.         {
  3634.             if ($this->arParams['IBLOCK_ID'] > 0)
  3635.             {
  3636.                 $this->arResult['CATALOG'] = false;
  3637.  
  3638.                 if (
  3639.                     !empty($this->storage['CATALOGS'][$this->arParams['IBLOCK_ID']])
  3640.                     && is_array($this->storage['CATALOGS'][$this->arParams['IBLOCK_ID']])
  3641.                 )
  3642.                 {
  3643.                     $this->arResult['CATALOG'] = $this->storage['CATALOGS'][$this->arParams['IBLOCK_ID']];
  3644.                 }
  3645.             }
  3646.         }
  3647.     }
  3648.  
  3649.     /**
  3650.      * Process of buy/add-to-basket/etc actions.
  3651.      */
  3652.     protected function processLinkAction()
  3653.     {
  3654.         global $APPLICATION;
  3655.  
  3656.         if ($this->request->get($this->arParams['ACTION_VARIABLE'].self::ACTION_BUY) !== null)
  3657.         {
  3658.             $action = self::ACTION_BUY;
  3659.         }
  3660.         elseif ($this->request->get($this->arParams['ACTION_VARIABLE'].self::ACTION_ADD_TO_BASKET))
  3661.         {
  3662.             $action = self::ACTION_ADD_TO_BASKET;
  3663.         }
  3664.         else
  3665.         {
  3666.             $action = strtoupper($this->request->get($this->arParams['ACTION_VARIABLE']));
  3667.         }
  3668.  
  3669.         $productId = (int)$this->request->get($this->arParams['PRODUCT_ID_VARIABLE']);
  3670.  
  3671.         if (
  3672.             ($action == self::ACTION_ADD_TO_BASKET || $action == self::ACTION_BUY || $action == self::ACTION_SUBSCRIBE)
  3673.             && $productId > 0
  3674.             && Loader::includeModule('sale')
  3675.             && Loader::includeModule('catalog')
  3676.         )
  3677.         {
  3678.             $addByAjax = $this->request->get('ajax_basket') === 'Y';
  3679.             if ($addByAjax)
  3680.             {
  3681.                 $this->request->set(Main\Text\Encoding::convertEncoding($this->request->toArray(), 'UTF-8', SITE_CHARSET));
  3682.             }
  3683.  
  3684.             list($successfulAdd, $errorMsg) = $this->addProductToBasket($productId, $action);
  3685.  
  3686.             if ($addByAjax)
  3687.             {
  3688.                 if ($successfulAdd)
  3689.                 {
  3690.                     $addResult = array(
  3691.                         'STATUS' => 'OK',
  3692.                         'MESSAGE' => Loc::getMessage('CATALOG_SUCCESSFUL_ADD_TO_BASKET')
  3693.                     );
  3694.                 }
  3695.                 else
  3696.                 {
  3697.                     $addResult = array(
  3698.                         'STATUS' => 'ERROR',
  3699.                         'MESSAGE' => $errorMsg
  3700.                     );
  3701.                 }
  3702.  
  3703.                 $APPLICATION->RestartBuffer();
  3704.                 header('Content-Type: application/json');
  3705.                 echo Main\Web\Json::encode($addResult);
  3706.                 die();
  3707.             }
  3708.             else
  3709.             {
  3710.                 if ($successfulAdd)
  3711.                 {
  3712.                     $pathRedirect = $action == self::ACTION_BUY
  3713.                         ? $this->arParams['BASKET_URL']
  3714.                         : $APPLICATION->GetCurPageParam('', array(
  3715.                             $this->arParams['PRODUCT_ID_VARIABLE'],
  3716.                             $this->arParams['ACTION_VARIABLE'],
  3717.                             $this->arParams['PRODUCT_QUANTITY_VARIABLE'],
  3718.                             $this->arParams['PRODUCT_PROPS_VARIABLE']
  3719.                         ));
  3720.  
  3721.                     LocalRedirect($pathRedirect);
  3722.                 }
  3723.                 else
  3724.                 {
  3725.                     $this->errorCollection->setError(new Error($errorMsg, self::ERROR_TEXT));
  3726.                 }
  3727.             }
  3728.         }
  3729.     }
  3730.  
  3731.     protected function addProductToBasket($productId, $action)
  3732.     {
  3733.         /** @global \CMain $APPLICATION */
  3734.         global $APPLICATION;
  3735.  
  3736.         $successfulAdd = true;
  3737.         $errorMsg = '';
  3738.  
  3739.         $quantity = 0;
  3740.         $productProperties = array();
  3741.         $iblockId = (int)\CIBlockElement::GetIBlockByID($productId);
  3742.  
  3743.         if ($iblockId > 0)
  3744.         {
  3745.             $productCatalogInfo = \CCatalogSku::GetInfoByIBlock($iblockId);
  3746.             if (!empty($productCatalogInfo) && $productCatalogInfo['CATALOG_TYPE'] == \CCatalogSku::TYPE_PRODUCT)
  3747.             {
  3748.                 $productCatalogInfo = false;
  3749.             }
  3750.             if (!empty($productCatalogInfo))
  3751.             {
  3752.                 if ($this->arParams['ADD_PROPERTIES_TO_BASKET'] === 'Y')
  3753.                 {
  3754.                     $productIblockId = ($productCatalogInfo['CATALOG_TYPE'] == \CCatalogSku::TYPE_CATALOG
  3755.                         ? $productCatalogInfo['IBLOCK_ID']
  3756.                         : $productCatalogInfo['PRODUCT_IBLOCK_ID']
  3757.                     );
  3758.                     $iblockParams = $this->storage['IBLOCK_PARAMS'][$productIblockId];
  3759.                     if ($productCatalogInfo['CATALOG_TYPE'] !== \CCatalogSku::TYPE_OFFERS)
  3760.                     {
  3761.                         if (!empty($iblockParams['CART_PROPERTIES']))
  3762.                         {
  3763.                             $productPropsVar = $this->request->get($this->arParams['PRODUCT_PROPS_VARIABLE']);
  3764.                             if (is_array($productPropsVar))
  3765.                             {
  3766.                                 $productProperties = \CIBlockPriceTools::CheckProductProperties(
  3767.                                     $productIblockId,
  3768.                                     $productId,
  3769.                                     $iblockParams['CART_PROPERTIES'],
  3770.                                     $productPropsVar,
  3771.                                     $this->arParams['PARTIAL_PRODUCT_PROPERTIES'] === 'Y'
  3772.                                 );
  3773.                                 if (!is_array($productProperties))
  3774.                                 {
  3775.                                     $errorMsg = Loc::getMessage('CATALOG_PARTIAL_BASKET_PROPERTIES_ERROR');
  3776.                                     $successfulAdd = false;
  3777.                                 }
  3778.                             }
  3779.                             else
  3780.                             {
  3781.                                 $errorMsg = Loc::getMessage('CATALOG_EMPTY_BASKET_PROPERTIES_ERROR');
  3782.                                 $successfulAdd = false;
  3783.                             }
  3784.                         }
  3785.                     }
  3786.                     else
  3787.                     {
  3788.                         $skuAddProps = $this->request->get('basket_props') ?: '';
  3789.                         if (!empty($iblockParams['OFFERS_CART_PROPERTIES']) || !empty($skuAddProps))
  3790.                         {
  3791.                             $productProperties = \CIBlockPriceTools::GetOfferProperties(
  3792.                                 $productId,
  3793.                                 $productIblockId,
  3794.                                 $iblockParams['OFFERS_CART_PROPERTIES'],
  3795.                                 $skuAddProps
  3796.                             );
  3797.                         }
  3798.                     }
  3799.                 }
  3800.             }
  3801.             else
  3802.             {
  3803.                 $errorMsg = Loc::getMessage('CATALOG_PRODUCT_NOT_FOUND');
  3804.                 $successfulAdd = false;
  3805.             }
  3806.  
  3807.             if ($this->arParams['USE_PRODUCT_QUANTITY'])
  3808.             {
  3809.                 $quantity = (float)$this->request->get($this->arParams['PRODUCT_QUANTITY_VARIABLE']);
  3810.             }
  3811.  
  3812.             if ($quantity <= 0)
  3813.             {
  3814.                 $ratioIterator = \CCatalogMeasureRatio::getList(
  3815.                     array(),
  3816.                     array('PRODUCT_ID' => $productId),
  3817.                     false,
  3818.                     false,
  3819.                     array('PRODUCT_ID', 'RATIO')
  3820.                 );
  3821.                 if ($ratio = $ratioIterator->Fetch())
  3822.                 {
  3823.                     $intRatio = (int)$ratio['RATIO'];
  3824.                     $floatRatio = (float)$ratio['RATIO'];
  3825.                     $quantity = $floatRatio > $intRatio ? $floatRatio : $intRatio;
  3826.                 }
  3827.             }
  3828.  
  3829.             if ($quantity <= 0)
  3830.             {
  3831.                 $quantity = 1;
  3832.             }
  3833.         }
  3834.         else
  3835.         {
  3836.             $errorMsg = Loc::getMessage('CATALOG_PRODUCT_NOT_FOUND');
  3837.             $successfulAdd = false;
  3838.         }
  3839.  
  3840.         $rewriteFields = $this->getRewriteFields($action);
  3841.  
  3842.         if ($successfulAdd)
  3843.         {
  3844.             if (!Add2BasketByProductID($productId, $quantity, $rewriteFields, $productProperties))
  3845.             {
  3846.                 if ($ex = $APPLICATION->GetException())
  3847.                 {
  3848.                     $errorMsg = $ex->GetString();
  3849.                 }
  3850.                 else
  3851.                 {
  3852.                     $errorMsg = Loc::getMessage('CATALOG_ERROR2BASKET');
  3853.                 }
  3854.  
  3855.                 $successfulAdd = false;
  3856.             }
  3857.         }
  3858.  
  3859.         return array($successfulAdd, $errorMsg);
  3860.     }
  3861.  
  3862.     protected function getRewriteFields($action)
  3863.     {
  3864.         $rewriteFields = array();
  3865.  
  3866.         if ($action == self::ACTION_SUBSCRIBE)
  3867.         {
  3868.             $notify = unserialize(Main\Config\Option::get('sale', 'subscribe_prod', ''));
  3869.             if (!empty($notify[$this->getSiteId()]) && $notify[$this->getSiteId()]['use'] === 'Y')
  3870.             {
  3871.                 $rewriteFields['SUBSCRIBE'] = 'Y';
  3872.                 $rewriteFields['CAN_BUY'] = 'N';
  3873.             }
  3874.         }
  3875.  
  3876.         return $rewriteFields;
  3877.     }
  3878.  
  3879.     /**
  3880.      * This method executes when "deferredLoad" action chosen.
  3881.      * Override getDeferredProductIds method to return needed product ids array.
  3882.      */
  3883.     protected function deferredLoadAction()
  3884.     {
  3885.         $this->productIds = $this->getDeferredProductIds();
  3886.  
  3887.         // if no products - show empty response
  3888.         if (empty($this->productIds))
  3889.         {
  3890.             static::sendJsonAnswer();
  3891.         }
  3892.  
  3893.         $this->productIdMap = $this->getProductIdMap($this->productIds);
  3894.         $this->loadData();
  3895.     }
  3896.  
  3897.     /**
  3898.      * This method executes when "bigDataLoad" action is chosen.
  3899.      */
  3900.     protected function bigDataLoadAction()
  3901.     {
  3902.         $this->initBigDataLastUsage();
  3903.         $this->productIds = $this->getBigDataProductIds();
  3904.  
  3905.         // if no products - show empty response
  3906.         if (empty($this->productIds))
  3907.         {
  3908.             static::sendJsonAnswer();
  3909.         }
  3910.  
  3911.         $this->productIdMap = $this->getProductIdMap($this->productIds);
  3912.         $this->loadData();
  3913.     }
  3914.  
  3915.     /**
  3916.      * Mark last usage of BigData.
  3917.      */
  3918.     protected function initBigDataLastUsage()
  3919.     {
  3920.         $lastUsage = Main\Config\Option::get('main', 'rcm_component_usage', 0);
  3921.  
  3922.         if ($lastUsage == 0 || (time() - $lastUsage) > 3600)
  3923.         {
  3924.             Main\Config\Option::set('main', 'rcm_component_usage', time());
  3925.         }
  3926.     }
  3927.  
  3928.     /**
  3929.      * This method executes when "initialLoad" action is chosen.
  3930.      */
  3931.     protected function initialLoadAction()
  3932.     {
  3933.         $this->productIds = $this->getProductIds();
  3934.         $this->productIdMap = $this->getProductIdMap($this->productIds);
  3935.         $this->loadData();
  3936.     }
  3937.  
  3938.     /**
  3939.      * Show cached component data or load if outdated.
  3940.      * If extended mode enabled - uses result_modifier.php logic in component (be careful not to duplicate it).
  3941.      */
  3942.     protected function loadData()
  3943.     {
  3944.         if ($this->isCacheDisabled() || $this->startResultCache(false, $this->getAdditionalCacheId(), $this->getComponentCachePath()))
  3945.         {
  3946.             $this->processResultData();
  3947.             if (!$this->hasErrors())
  3948.             {
  3949.                 if ($this->isExtendedMode())
  3950.                 {
  3951.                     $this->initComponentTemplate();
  3952.                     $this->applyTemplateModifications();
  3953.                 }
  3954.  
  3955.                 $this->initResultCache();
  3956.                 $this->includeComponentTemplate();
  3957.                 $this->clearCatalogDiscountCache();
  3958.             }
  3959.         }
  3960.     }
  3961.  
  3962.     /**
  3963.      * Return component cache identifier.
  3964.      *
  3965.      * @return mixed
  3966.      */
  3967.     abstract protected function getAdditionalCacheId();
  3968.  
  3969.     /**
  3970.      * Return component cache path.
  3971.      *
  3972.      * @return mixed
  3973.      */
  3974.     abstract protected function getComponentCachePath();
  3975.  
  3976.     public function getTemplateEmptyPreview()
  3977.     {
  3978.         $emptyPreview = false;
  3979.         $documentRoot = Main\Application::getDocumentRoot();
  3980.         $emptyPreviewPath = $this->getTemplate()->GetFolder().'/images/no_photo.png';
  3981.  
  3982.         $file = new Main\IO\File($documentRoot.$emptyPreviewPath);
  3983.         if ($file->isExists())
  3984.         {
  3985.             $size = getimagesize($documentRoot.$emptyPreviewPath);
  3986.             if (!empty($size))
  3987.             {
  3988.                 $emptyPreview = array(
  3989.                     'ID' => 0,
  3990.                     'SRC' => $emptyPreviewPath,
  3991.                     'WIDTH' => (int)$size[0],
  3992.                     'HEIGHT' => (int)$size[1]
  3993.                 );
  3994.             }
  3995.         }
  3996.  
  3997.         return $emptyPreview;
  3998.     }
  3999.  
  4000.     protected function sliceItemsForSlider(&$items)
  4001.     {
  4002.         $rows = array();
  4003.  
  4004.         while (!empty($items))
  4005.         {
  4006.             $rows[] = array_splice($items, 0, $this->arParams['LINE_ELEMENT_COUNT']);
  4007.         }
  4008.  
  4009.         $items = $rows;
  4010.     }
  4011.  
  4012.     protected function getTemplateCurrencies()
  4013.     {
  4014.         $currencies = array();
  4015.  
  4016.         if ($this->arResult['MODULES']['currency'])
  4017.         {
  4018.             if (isset($this->arResult['CONVERT_CURRENCY']['CURRENCY_ID']))
  4019.             {
  4020.                 $currencyFormat = \CCurrencyLang::GetFormatDescription($this->arResult['CONVERT_CURRENCY']['CURRENCY_ID']);
  4021.                 $currencies = array(
  4022.                     array(
  4023.                         'CURRENCY' => $this->arResult['CONVERT_CURRENCY']['CURRENCY_ID'],
  4024.                         'FORMAT' => array(
  4025.                             'FORMAT_STRING' => $currencyFormat['FORMAT_STRING'],
  4026.                             'DEC_POINT' => $currencyFormat['DEC_POINT'],
  4027.                             'THOUSANDS_SEP' => $currencyFormat['THOUSANDS_SEP'],
  4028.                             'DECIMALS' => $currencyFormat['DECIMALS'],
  4029.                             'THOUSANDS_VARIANT' => $currencyFormat['THOUSANDS_VARIANT'],
  4030.                             'HIDE_ZERO' => $currencyFormat['HIDE_ZERO']
  4031.                         )
  4032.                     )
  4033.                 );
  4034.                 unset($currencyFormat);
  4035.             }
  4036.             else
  4037.             {
  4038.                 $currencyIterator = Currency\CurrencyTable::getList(array(
  4039.                     'select' => array('CURRENCY')
  4040.                 ));
  4041.                 while ($currency = $currencyIterator->fetch())
  4042.                 {
  4043.                     $currencyFormat = \CCurrencyLang::GetFormatDescription($currency['CURRENCY']);
  4044.                     $currencies[] = array(
  4045.                         'CURRENCY' => $currency['CURRENCY'],
  4046.                         'FORMAT' => array(
  4047.                             'FORMAT_STRING' => $currencyFormat['FORMAT_STRING'],
  4048.                             'DEC_POINT' => $currencyFormat['DEC_POINT'],
  4049.                             'THOUSANDS_SEP' => $currencyFormat['THOUSANDS_SEP'],
  4050.                             'DECIMALS' => $currencyFormat['DECIMALS'],
  4051.                             'THOUSANDS_VARIANT' => $currencyFormat['THOUSANDS_VARIANT'],
  4052.                             'HIDE_ZERO' => $currencyFormat['HIDE_ZERO']
  4053.                         )
  4054.                     );
  4055.                 }
  4056.                 unset($currencyFormat, $currency, $currencyIterator);
  4057.             }
  4058.         }
  4059.  
  4060.         return $currencies;
  4061.     }
  4062.  
  4063.     /**
  4064.      * Send answer for AJAX request.
  4065.      *
  4066.      * @param array $result
  4067.      */
  4068.     public static function sendJsonAnswer(array $result = array())
  4069.     {
  4070.         global $APPLICATION;
  4071.  
  4072.         if (!empty($result))
  4073.         {
  4074.             $result['JS'] = Main\Page\Asset::getInstance()->getJs();
  4075.         }
  4076.  
  4077.         $APPLICATION->RestartBuffer();
  4078.         echo Main\Web\Json::encode($result);
  4079.  
  4080.         \CMain::FinalActions();
  4081.         die();
  4082.     }
  4083.  
  4084.     /**
  4085.      * Action preparing to execute in doAction method with postfix "Action".
  4086.      * E.g. action "initialLoad" calls "initialLoadAction".
  4087.      *
  4088.      * @return string
  4089.      */
  4090.     protected function prepareAction()
  4091.     {
  4092.         if (
  4093.             $this->request->get($this->arParams['ACTION_VARIABLE']) !== null
  4094.             && $this->request->get($this->arParams['PRODUCT_ID_VARIABLE']) !== null
  4095.         )
  4096.         {
  4097.             $action = 'processLink';
  4098.         }
  4099.         elseif ($this->request->isAjaxRequest() && $this->request->get('action') === 'deferredLoad')
  4100.         {
  4101.             $action = $this->request->get('bigData') === 'Y' ? 'bigDataLoad' : 'deferredLoad';
  4102.         }
  4103.         else
  4104.         {
  4105.             $action = 'initialLoad';
  4106.         }
  4107.  
  4108.         return $action;
  4109.     }
  4110.  
  4111.     /**
  4112.      * Action executor.
  4113.      */
  4114.     protected function doAction()
  4115.     {
  4116.         $action = $this->getAction();
  4117.  
  4118.         if (is_callable(array($this, $action.'Action')))
  4119.         {
  4120.             call_user_func(array($this, $action.'Action'));
  4121.         }
  4122.     }
  4123.  
  4124.     /**
  4125.      * @return bool
  4126.      */
  4127.     public function executeComponent()
  4128.     {
  4129.         $this->checkModules();
  4130.  
  4131.         if ($this->hasErrors())
  4132.         {
  4133.             return $this->processErrors();
  4134.         }
  4135.  
  4136.         $action = $this->prepareAction();
  4137.         $this->setAction($action);
  4138.         $this->doAction();
  4139.  
  4140.         if ($this->hasErrors())
  4141.         {
  4142.             return $this->processErrors();
  4143.         }
  4144.  
  4145.         return isset($this->arResult['ID']) ? $this->arResult['ID'] : false;
  4146.     }
  4147.  
  4148.     public function applyTemplateModifications()
  4149.     {
  4150.         $this->prepareTemplateParams();
  4151.         $this->checkTemplateTheme();
  4152.         $this->editTemplateData();
  4153.  
  4154.         return $this->arParams;
  4155.     }
  4156.  
  4157.     protected function prepareTemplateParams()
  4158.     {
  4159.         $params =& $this->arParams;
  4160.         $defaultParams = $this->getTemplateDefaultParams();
  4161.         $params = array_merge($defaultParams, $params);
  4162.  
  4163.         $params['SHOW_OLD_PRICE'] = $params['SHOW_OLD_PRICE'] === 'Y' ? 'Y' : 'N';
  4164.         $params['SHOW_CLOSE_POPUP'] = $params['SHOW_CLOSE_POPUP'] === 'Y' ? 'Y' : 'N';
  4165.         $params['SHOW_DISCOUNT_PERCENT'] = $params['SHOW_DISCOUNT_PERCENT'] === 'Y' ? 'Y' : 'N';
  4166.         $params['DISCOUNT_PERCENT_POSITION'] = trim($params['DISCOUNT_PERCENT_POSITION']) ?: 'bottom-right';
  4167.         $params['LABEL_PROP_POSITION'] = trim($params['LABEL_PROP_POSITION']) ?: 'top-left';
  4168.         $params['PRODUCT_SUBSCRIPTION'] = $params['PRODUCT_SUBSCRIPTION'] === 'N' ? 'N' : 'Y';
  4169.         $params['MESS_BTN_BUY'] = trim($params['MESS_BTN_BUY']);
  4170.         $params['MESS_BTN_ADD_TO_BASKET'] = trim($params['MESS_BTN_ADD_TO_BASKET']);
  4171.         $params['MESS_BTN_SUBSCRIBE'] = trim($params['MESS_BTN_SUBSCRIBE']);
  4172.         $params['MESS_BTN_DETAIL'] = trim($params['MESS_BTN_DETAIL']);
  4173.         $params['MESS_NOT_AVAILABLE'] = trim($params['MESS_NOT_AVAILABLE']);
  4174.         $params['MESS_BTN_COMPARE'] = trim($params['MESS_BTN_COMPARE']);
  4175.         $params['SHOW_SLIDER'] = $params['SHOW_SLIDER'] === 'N' ? 'N' : 'Y';
  4176.         $params['SLIDER_INTERVAL'] = (int)$params['SLIDER_INTERVAL'] ?: 5000;
  4177.         $params['SLIDER_PROGRESS'] = $params['SLIDER_PROGRESS'] === 'Y' ? 'Y' : 'N';
  4178.         $params['USE_ENHANCED_ECOMMERCE'] = $params['USE_ENHANCED_ECOMMERCE'] === 'Y' ? 'Y' : 'N';
  4179.         $params['DATA_LAYER_NAME'] = trim($params['DATA_LAYER_NAME']);
  4180.         $params['BRAND_PROPERTY'] = $params['BRAND_PROPERTY'] !== '-' ? trim($params['BRAND_PROPERTY']) : '';
  4181.  
  4182.         if (!isset($params['SHOW_MAX_QUANTITY']) || !in_array($params['SHOW_MAX_QUANTITY'], array('Y', 'M', 'N')))
  4183.         {
  4184.             $params['SHOW_MAX_QUANTITY'] = 'N';
  4185.         }
  4186.  
  4187.         $params['RELATIVE_QUANTITY_FACTOR'] = (int)$params['RELATIVE_QUANTITY_FACTOR'] > 0 ? (int)$params['RELATIVE_QUANTITY_FACTOR'] : 5;
  4188.     }
  4189.  
  4190.     protected function getTemplateDefaultParams()
  4191.     {
  4192.         return array(
  4193.             'TEMPLATE_THEME' => 'blue',
  4194.             'SHOW_MAX_QUANTITY' => 'N',
  4195.             'SHOW_OLD_PRICE' => 'N',
  4196.             'SHOW_CLOSE_POPUP' => 'N',
  4197.             'SHOW_DISCOUNT_PERCENT' => 'N',
  4198.             'DISCOUNT_PERCENT_POSITION' => 'bottom-right',
  4199.             'LABEL_PROP' => array(),
  4200.             'LABEL_PROP_MOBILE' => array(),
  4201.             'LABEL_PROP_POSITION' => 'top-left',
  4202.             'PRODUCT_SUBSCRIPTION' => 'Y',
  4203.             'MESS_BTN_BUY' => '',
  4204.             'MESS_BTN_ADD_TO_BASKET' => '',
  4205.             'MESS_BTN_SUBSCRIBE' => '',
  4206.             'MESS_BTN_DETAIL' => '',
  4207.             'MESS_NOT_AVAILABLE' => '',
  4208.             'MESS_BTN_COMPARE' => '',
  4209.             'SHOW_SLIDER' => 'N',
  4210.             'SLIDER_INTERVAL' => 5000,
  4211.             'SLIDER_PROGRESS' => 'N',
  4212.             'USE_ENHANCED_ECOMMERCE' => 'N',
  4213.             'DATA_LAYER_NAME' => 'dataLayer',
  4214.             'BRAND_PROPERTY' => ''
  4215.         );
  4216.     }
  4217.  
  4218.     protected function checkTemplateTheme()
  4219.     {
  4220.         $theme =& $this->arParams['TEMPLATE_THEME'];
  4221.         $theme = (string)$theme;
  4222.  
  4223.         if ($theme != '')
  4224.         {
  4225.             $theme = preg_replace('/[^a-zA-Z0-9_\-\(\)\!]/', '', $theme);
  4226.             if ($theme === 'site')
  4227.             {
  4228.                 $siteId = $this->getSiteId();
  4229.                 $templateId = Main\Config\Option::get('main', 'wizard_template_id', 'eshop_bootstrap', $siteId);
  4230.                 $templateId = preg_match('/^eshop_adapt/', $templateId) ? 'eshop_adapt' : $templateId;
  4231.                 $theme = Main\Config\Option::get('main', 'wizard_'.$templateId.'_theme_id', 'blue', $siteId);
  4232.             }
  4233.  
  4234.             if ($theme != '')
  4235.             {
  4236.                 $documentRoot = Main\Application::getDocumentRoot();
  4237.                 $templateFolder = $this->getTemplate()->GetFolder();
  4238.  
  4239.                 $file = new Main\IO\File($documentRoot.$templateFolder.'/themes/'.$theme.'/style.css');
  4240.                 if (!$file->isExists())
  4241.                 {
  4242.                     $theme = '';
  4243.                 }
  4244.             }
  4245.         }
  4246.  
  4247.         if ($theme == '')
  4248.         {
  4249.             $theme = 'blue';
  4250.         }
  4251.     }
  4252.  
  4253.     protected function editTemplateData()
  4254.     {
  4255.         //
  4256.     }
  4257.  
  4258.     public static function checkEnlargedData(&$item, $propertyCode)
  4259.     {
  4260.         if (!empty($item) && is_array($item))
  4261.         {
  4262.             $item['ENLARGED'] = 'N';
  4263.             $propertyCode = (string)$propertyCode;
  4264.  
  4265.             if ($propertyCode !== '' && isset($item['PROPERTIES'][$propertyCode]))
  4266.             {
  4267.                 $prop = $item['PROPERTIES'][$propertyCode];
  4268.                 if (!empty($prop['VALUE']))
  4269.                 {
  4270.                     $item['ENLARGED'] = 'Y';
  4271.                 }
  4272.             }
  4273.         }
  4274.     }
  4275.  
  4276.     protected function editTemplateProductSlider(&$item, $iblock, $limit = 0, $addDetailToSlider = true, $default = array())
  4277.     {
  4278.         $propCode = $this->storage['IBLOCK_PARAMS'][$iblock]['ADD_PICT_PROP'];
  4279.  
  4280.         $slider = \CIBlockPriceTools::getSliderForItem($item, $propCode, $addDetailToSlider);
  4281.         if (empty($slider))
  4282.         {
  4283.             $slider = $default;
  4284.         }
  4285.  
  4286.         if ($limit > 0)
  4287.         {
  4288.             $slider = array_slice($slider, 0, $limit);
  4289.         }
  4290.  
  4291.         $item['SHOW_SLIDER'] = true;
  4292.         $item['MORE_PHOTO'] = $slider;
  4293.         $item['MORE_PHOTO_COUNT'] = count($slider);
  4294.     }
  4295.  
  4296.     protected function editTemplateOfferSlider(&$item, $iblock, $limit = 0, $addDetailToSlider = true, $default = array())
  4297.     {
  4298.         $propCode = $this->storage['IBLOCK_PARAMS'][$iblock]['OFFERS_ADD_PICT_PROP'];
  4299.  
  4300.         $slider = \CIBlockPriceTools::getSliderForItem($item, $propCode, $addDetailToSlider);
  4301.         if (empty($slider))
  4302.         {
  4303.             $slider = $default;
  4304.         }
  4305.  
  4306.         if ($limit > 0)
  4307.         {
  4308.             $slider = array_slice($slider, 0, $limit);
  4309.         }
  4310.  
  4311.         $item['MORE_PHOTO'] = $slider;
  4312.         $item['MORE_PHOTO_COUNT'] = count($slider);
  4313.     }
  4314.  
  4315.     protected function editTemplateCatalogInfo(&$item)
  4316.     {
  4317.         if ($this->arResult['MODULES']['catalog'])
  4318.         {
  4319.             $item['CATALOG'] = true;
  4320.             if ($this->isEnableCompatible())
  4321.                 $item['CATALOG_TYPE'] = $item['PRODUCT']['TYPE'];
  4322.         }
  4323.         else
  4324.         {
  4325.             if ($this->isEnableCompatible())
  4326.                 $item['CATALOG_TYPE'] = 0;
  4327.             $item['OFFERS'] = array();
  4328.         }
  4329.     }
  4330.  
  4331.     protected function getTemplatePropCell($code, $offer, &$matrixFields, $skuPropList)
  4332.     {
  4333.         $cell = array(
  4334.             'VALUE' => 0,
  4335.             'SORT' => PHP_INT_MAX,
  4336.             'NA' => true
  4337.         );
  4338.  
  4339.         if (isset($offer['DISPLAY_PROPERTIES'][$code]))
  4340.         {
  4341.             $matrixFields[$code] = true;
  4342.             $cell['NA'] = false;
  4343.  
  4344.             if ($skuPropList[$code]['USER_TYPE'] === 'directory')
  4345.             {
  4346.                 $intValue = $skuPropList[$code]['XML_MAP'][$offer['DISPLAY_PROPERTIES'][$code]['VALUE']];
  4347.                 $cell['VALUE'] = $intValue;
  4348.             }
  4349.             elseif ($skuPropList[$code]['PROPERTY_TYPE'] === 'L')
  4350.             {
  4351.                 $cell['VALUE'] = (int)$offer['DISPLAY_PROPERTIES'][$code]['VALUE_ENUM_ID'];
  4352.             }
  4353.             elseif ($skuPropList[$code]['PROPERTY_TYPE'] === 'E')
  4354.             {
  4355.                 $cell['VALUE'] = (int)$offer['DISPLAY_PROPERTIES'][$code]['VALUE'];
  4356.             }
  4357.  
  4358.             $cell['SORT'] = $skuPropList[$code]['VALUES'][$cell['VALUE']]['SORT'];
  4359.         }
  4360.  
  4361.         return $cell;
  4362.     }
  4363.  
  4364.     /* product tools */
  4365.  
  4366.     /**
  4367.      * Return true, if enable quantity trace and disable make out-of-stock items available for purchase.
  4368.      *
  4369.      * @param array $product        Product data.
  4370.      * @return bool
  4371.      */
  4372.     protected function isNeedCheckQuantity(array $product)
  4373.     {
  4374.         return (
  4375.             $product['QUANTITY_TRACE'] === Catalog\ProductTable::STATUS_YES
  4376.             && $product['CAN_BUY_ZERO'] === Catalog\ProductTable::STATUS_NO
  4377.         );
  4378.     }
  4379.  
  4380.     /* product tools end */
  4381.  
  4382.     /* user tools */
  4383.  
  4384.     /**
  4385.      * Return user groups. Now worked only with current user.
  4386.      *
  4387.      * @return array
  4388.      */
  4389.     protected function getUserGroups()
  4390.     {
  4391.         /** @global \CUser $USER */
  4392.         global $USER;
  4393.         $result = array(2);
  4394.         if (isset($USER) && $USER instanceof \CUser)
  4395.         {
  4396.             $result = $USER->GetUserGroupArray();
  4397.             Main\Type\Collection::normalizeArrayValuesByInt($result, true);
  4398.         }
  4399.         return $result;
  4400.     }
  4401.  
  4402.     /**
  4403.      * Return user groups string for cache id.
  4404.      *
  4405.      * @return string
  4406.      */
  4407.     protected function getUserGroupsCacheId()
  4408.     {
  4409.         return implode(',', $this->getUserGroups());
  4410.     }
  4411.  
  4412.     /* user tools end */
  4413.  
  4414.     /* compatibility tools */
  4415.  
  4416.     /**
  4417.      * Filling deprecated fields of items for compatibility with old templates.
  4418.      * Strict use only for catalog.element, .section, .top, etc in compatible mode.
  4419.      *
  4420.      * @param array $items          Product list.
  4421.      * @return void
  4422.      */
  4423.     protected function initCompatibleFields(array $items)
  4424.     {
  4425.         if (empty($items))
  4426.             return;
  4427.  
  4428.         $initFields = array(
  4429.             'PRICES' => array(),
  4430.             'PRICE_MATRIX' => false,
  4431.             'MIN_PRICE' => false
  4432.         );
  4433.         if (!$this->arParams['USE_PRICE_COUNT'] && !empty($this->storage['PRICES']))
  4434.         {
  4435.             foreach ($this->storage['PRICES'] as $value)
  4436.             {
  4437.                 if (!$value['CAN_VIEW'] && !$value['CAN_BUY'])
  4438.                     continue;
  4439.  
  4440.                 $priceType = $value['ID'];
  4441.                 $initFields['CATALOG_GROUP_ID_'.$priceType] = $priceType;
  4442.                 $initFields['~CATALOG_GROUP_ID_'.$priceType] = $priceType;
  4443.                 $initFields['CATALOG_GROUP_NAME_'.$priceType] = $value['TITLE'];
  4444.                 $initFields['~CATALOG_GROUP_NAME_'.$priceType] = $value['~TITLE'];
  4445.                 $initFields['CATALOG_CAN_ACCESS_'.$priceType] = ($value['CAN_VIEW'] ? 'Y' : 'N');
  4446.                 $initFields['~CATALOG_CAN_ACCESS_'.$priceType] = ($value['CAN_VIEW'] ? 'Y' : 'N');
  4447.                 $initFields['CATALOG_CAN_BUY_'.$priceType] = ($value['CAN_BUY'] ? 'Y' : 'N');
  4448.                 $initFields['~CATALOG_CAN_BUY_'.$priceType] = ($value['CAN_BUY'] ? 'Y' : 'N');
  4449.                 $initFields['CATALOG_PRICE_ID_'.$priceType] = null;
  4450.                 $initFields['~CATALOG_PRICE_ID_'.$priceType] = null;
  4451.                 $initFields['CATALOG_PRICE_'.$priceType] = null;
  4452.                 $initFields['~CATALOG_PRICE_'.$priceType] = null;
  4453.                 $initFields['CATALOG_CURRENCY_'.$priceType] = null;
  4454.                 $initFields['~CATALOG_CURRENCY_'.$priceType] = null;
  4455.                 $initFields['CATALOG_QUANTITY_FROM_'.$priceType] = null;
  4456.                 $initFields['~CATALOG_QUANTITY_FROM_'.$priceType] = null;
  4457.                 $initFields['CATALOG_QUANTITY_TO_'.$priceType] = null;
  4458.                 $initFields['~CATALOG_QUANTITY_TO_'.$priceType] = null;
  4459.                 $initFields['CATALOG_EXTRA_ID_'.$priceType] = null;
  4460.                 $initFields['~CATALOG_EXTRA_ID_'.$priceType] = null;
  4461.                 unset($priceType);
  4462.             }
  4463.             unset($value);
  4464.         }
  4465.  
  4466.         foreach (array_keys($items) as $index)
  4467.             $this->oldData[$items[$index]['ID']] = $initFields;
  4468.         unset($index, $initFields);
  4469.     }
  4470.  
  4471.     /**
  4472.      * Fill deprecated raw price data from database.
  4473.      * Strict use only for catalog.element, .section, .top, etc in compatible mode.
  4474.      *
  4475.      * @param int $id           Item id.
  4476.      * @param array $prices     Price rows from database.
  4477.      * @return void
  4478.      */
  4479.     protected function fillCompatibleRawPriceFields($id, array $prices)
  4480.     {
  4481.         if (!isset($this->oldData[$id]) || empty($prices) || $this->arParams['USE_PRICE_COUNT'])
  4482.             return;
  4483.         foreach ($prices as $rawPrice)
  4484.         {
  4485.             $priceType = $rawPrice['CATALOG_GROUP_ID'];
  4486.             $this->oldData[$id]['CATALOG_PRICE_ID_'.$priceType] = $rawPrice['ID'];
  4487.             $this->oldData[$id]['~CATALOG_PRICE_ID_'.$priceType] = $rawPrice['ID'];
  4488.             $this->oldData[$id]['CATALOG_PRICE_'.$priceType] = $rawPrice['PRICE'];
  4489.             $this->oldData[$id]['~CATALOG_PRICE_'.$priceType] = $rawPrice['PRICE'];
  4490.             $this->oldData[$id]['CATALOG_CURRENCY_'.$priceType] = $rawPrice['CURRENCY'];
  4491.             $this->oldData[$id]['~CATALOG_CURRENCY_'.$priceType] = $rawPrice['CURRENCY'];
  4492.             $this->oldData[$id]['CATALOG_QUANTITY_FROM_'.$priceType] = $rawPrice['QUANTITY_FROM'];
  4493.             $this->oldData[$id]['~CATALOG_QUANTITY_FROM_'.$priceType] = $rawPrice['QUANTITY_FROM'];
  4494.             $this->oldData[$id]['CATALOG_QUANTITY_TO_'.$priceType] = $rawPrice['QUANTITY_TO'];
  4495.             $this->oldData[$id]['~CATALOG_QUANTITY_TO_'.$priceType] = $rawPrice['QUANTITY_TO'];
  4496.             $this->oldData[$id]['CATALOG_EXTRA_ID_'.$priceType] = $rawPrice['EXTRA_ID'];
  4497.             $this->oldData[$id]['~CATALOG_EXTRA_ID_'.$priceType] = $rawPrice['EXTRA_ID'];
  4498.             unset($priceType);
  4499.         }
  4500.         unset($rawPrice);
  4501.     }
  4502.  
  4503.     /**
  4504.      * Return deprecated field value for item.
  4505.      * Strict use only for catalog.element, .section, .top, etc in compatible mode.
  4506.      *
  4507.      * @param int $id               Item id.
  4508.      * @param string $field         Field name.
  4509.      * @return null|mixed
  4510.      */
  4511.     protected function getCompatibleFieldValue($id, $field)
  4512.     {
  4513.         if (!isset($this->oldData[$id]))
  4514.             return null;
  4515.         return (isset($this->oldData[$id][$field]) ? $this->oldData[$id][$field] : null);
  4516.     }
  4517.  
  4518.     /**
  4519.      * Check quantity range for emulate CATALOG_SHOP_QUANTITY_* filter.
  4520.      * Strict use only for catalog.element, .section, .top, etc in compatible mode.
  4521.      *
  4522.      * @param array $row        Price row from database.
  4523.      * @return bool
  4524.      */
  4525.     protected function checkQuantityRange(array $row)
  4526.     {
  4527.         return (
  4528.             ($row['QUANTITY_FROM'] === null || $row['QUANTITY_FROM'] <= $this->arParams['SHOW_PRICE_COUNT'])
  4529.             && ($row['QUANTITY_TO'] === null || $row['QUANTITY_TO'] >= $this->arParams['SHOW_PRICE_COUNT'])
  4530.         );
  4531.     }
  4532.  
  4533.     protected function getEmptyPriceMatrix()
  4534.     {
  4535.         return array(
  4536.             'ROWS' => array(),
  4537.             'COLS' => array(),
  4538.             'MATRIX' => array(),
  4539.             'CAN_BUY' => array(),
  4540.             'AVAILABLE' => 'N',
  4541.             'CURRENCY_LIST' => array()
  4542.         );
  4543.     }
  4544.  
  4545.     /**
  4546.      * Resort old price format for compatibility. Do not use this method.
  4547.      * @internl
  4548.      *
  4549.      * @param int $id       Item id.
  4550.      * @return void
  4551.      */
  4552.     private function resortOldPrices($id)
  4553.     {
  4554.         if (empty($this->oldData[$id]['PRICES']) || count($this->oldData[$id]['PRICES']) < 2)
  4555.             return;
  4556.         foreach (array_keys($this->oldData[$id]['PRICES']) as $priceCode)
  4557.             $this->oldData[$id]['PRICES'][$priceCode]['_SORT'] = $this->storage['PRICES'][$priceCode]['SORT'];
  4558.         unset($priceCode);
  4559.         Main\Type\Collection::sortByColumn(
  4560.             $this->oldData[$id]['PRICES'],
  4561.             array('_SORT' => SORT_ASC, 'PRICE_ID' => SORT_ASC),
  4562.             '', null, true
  4563.         );
  4564.         foreach (array_keys($this->oldData[$id]['PRICES']) as $priceCode)
  4565.             unset($this->oldData[$id]['PRICES'][$priceCode]['_SORT']);
  4566.         unset($priceCode);
  4567.     }
  4568.  
  4569.     /* compatibility tools end */
  4570. }
Advertisement
Add Comment
Please, Sign In to add comment