Advertisement
Guest User

Untitled

a guest
Nov 14th, 2014
217
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 183.86 KB | None | 0 0
  1. <?php
  2. /***************************************************************************
  3. *                                                                          *
  4. *   (c) 2004 Vladimir V. Kalynyak, Alexey V. Vinokurov, Ilya M. Shalnev    *
  5. *                                                                          *
  6. * This  is  commercial  software,  only  users  who have purchased a valid *
  7. * license  and  accept  to the terms of the  License Agreement can install *
  8. * and use this program.                                                    *
  9. *                                                                          *
  10. ****************************************************************************
  11. * PLEASE READ THE FULL TEXT  OF THE SOFTWARE  LICENSE   AGREEMENT  IN  THE *
  12. * "copyright.txt" FILE PROVIDED WITH THIS DISTRIBUTION PACKAGE.            *
  13. ****************************************************************************/
  14.  
  15.  
  16. if ( !defined('AREA') ) { die('Access denied'); }
  17.  
  18. // ------------------------- 'Products' object functions ------------------------------------
  19.  
  20. //
  21. // Get full product data by its id
  22. //
  23. function fn_get_product_data($product_id, &$auth, $lang_code = CART_LANGUAGE, $field_list = '', $get_add_pairs = true, $get_main_pair = true, $get_taxes = true, $get_qty_discounts = false, $preview = false, $features = true)
  24. {
  25.     $product_id = intval($product_id);
  26.     if (!empty($product_id)) {
  27.  
  28.         if (empty($field_list)) {
  29.             $descriptions_list = "?:product_descriptions.*";
  30.             $field_list = "?:products.*, $descriptions_list";
  31.         }
  32.         $field_list .= ", MIN(?:product_prices.price) as price";
  33.         $field_list .= ", GROUP_CONCAT(IF(?:products_categories.link_type = 'M', CONCAT(?:products_categories.category_id, 'M'), ?:products_categories.category_id)) as category_ids";
  34.         $field_list .= ", popularity.total as popularity";
  35.  
  36.         $price_usergroup = db_quote(" AND ?:product_prices.usergroup_id IN (?n)", ((AREA == 'A' && !defined('ORDER_MANAGEMENT')) ? USERGROUP_ALL : array_merge(array(USERGROUP_ALL), $auth['usergroup_ids'])));
  37.  
  38.         $_p_statuses = array('A', 'H');
  39.         $_c_statuses = array('A', 'H');
  40.  
  41.         $avail_cond = fn_get_company_condition('?:products.company_id');
  42.         $avail_cond .= (AREA == 'C') ? ' AND (' . fn_find_array_in_set($auth['usergroup_ids'], "?:categories.usergroup_ids", true) . ')' : '';
  43.         $avail_cond .= (AREA == 'C') ? ' AND (' . fn_find_array_in_set($auth['usergroup_ids'], "?:products.usergroup_ids", true) . ')' : '';
  44.         $avail_cond .= (AREA == 'C' && empty($preview)) ? db_quote(' AND ?:categories.status IN (?a) AND ?:products.status IN (?a)', $_c_statuses, $_p_statuses) : '';
  45.  
  46.         $avail_cond .= fn_get_localizations_condition('?:categories.localization');
  47.  
  48.         $condition = $join = '';
  49.  
  50.         if (AREA == 'C' && !$preview) {
  51.             if (fn_check_suppliers_functionality()) {
  52.                 // if MVE or suppliers enabled
  53.                 $field_list .= ', companies.company as company_name';
  54.                 $condition .= " AND (companies.status = 'A' OR ?:products.company_id = 0) ";
  55.                 $join .= " LEFT JOIN ?:companies as companies ON companies.company_id = ?:products.company_id";
  56.             } else {
  57.                 // if suppliers disabled
  58.                 if (PRODUCT_TYPE != 'MULTISHOP') {
  59.                     $condition .= " AND ?:products.company_id = 0 ";
  60.                 }
  61.             }
  62.         }
  63.  
  64.         $join .= " INNER JOIN ?:products_categories ON ?:products_categories.product_id = ?:products.product_id INNER JOIN ?:categories ON ?:categories.category_id = ?:products_categories.category_id $avail_cond";
  65.         $join .= " LEFT JOIN ?:product_popularity as popularity ON popularity.product_id = ?:products.product_id";
  66.  
  67.         fn_set_hook('get_product_data', $product_id, $field_list, $join, $auth, $lang_code, $condition);
  68.  
  69.         $product_data = db_get_row("SELECT $field_list FROM ?:products LEFT JOIN ?:product_prices ON ?:product_prices.product_id = ?:products.product_id AND ?:product_prices.lower_limit = 1 ?p LEFT JOIN ?:product_descriptions ON ?:product_descriptions.product_id = ?:products.product_id AND ?:product_descriptions.lang_code = ?s ?p WHERE ?:products.product_id = ?i ?p GROUP BY ?:products.product_id", $price_usergroup, $lang_code, $join, $product_id, $condition);
  70.  
  71.         if (empty($product_data)) {
  72.             return false;
  73.         }
  74.  
  75.         $product_data['base_price'] = $product_data['price']; // save base price (without discounts, etc...)
  76.  
  77.         $product_data['category_ids'] = fn_convert_categories($product_data['category_ids']);
  78.  
  79.         // Generate meta description automatically
  80.         if (!empty($product_data['full_description']) && empty($product_data['meta_description']) && defined('AUTO_META_DESCRIPTION') && AREA != 'A') {
  81.             $product_data['meta_description'] = fn_generate_meta_description($product_data['full_description']);
  82.         }
  83.  
  84.         // If tracking with options is enabled, check if at least one combination has positive amount
  85.         if (!empty($product_data['tracking']) && $product_data['tracking'] == 'O') {
  86.             $product_data['amount'] = db_get_field("SELECT MAX(amount) FROM ?:product_options_inventory WHERE product_id = ?i", $product_id);
  87.         }
  88.  
  89.         $product_data['product_id'] = $product_id;
  90.  
  91.         // Form old-style categories data FIXME!!!
  92.         foreach ($product_data['category_ids'] as $c => $t) {
  93.             if ($t == 'M') {
  94.                 $product_data['main_category'] = $c;
  95.             } else {
  96.                 $product_data['add_categories'][$c] = $c;
  97.             }
  98.         }
  99.  
  100.         // Get product shipping settings
  101.         if (!empty($product_data['shipping_params'])) {
  102.             $product_data = array_merge(unserialize($product_data['shipping_params']), $product_data);
  103.         }
  104.  
  105.         // Get main image pair
  106.         if ($get_main_pair == true) {
  107.             $product_data['main_pair'] = fn_get_image_pairs($product_id, 'product', 'M', true, true, $lang_code);
  108.         }
  109.  
  110.         // Get additional image pairs
  111.         if ($get_add_pairs == true) {
  112.             $product_data['image_pairs'] = fn_get_image_pairs($product_id, 'product', 'A', true, true, $lang_code);
  113.         }
  114.  
  115.         // Get taxes
  116.         if ($get_taxes == true) {
  117.             $product_data['taxes'] = !empty($product_data['tax_ids']) ? explode(',', $product_data['tax_ids']) : array();
  118.         }
  119.  
  120.         // Get qty discounts
  121.         if ($get_qty_discounts == true) {
  122.  
  123.             // For customer
  124.             if (AREA == 'C') {
  125.                 $_prices = db_get_hash_multi_array("SELECT * FROM ?:product_prices WHERE ?:product_prices.product_id = ?i AND lower_limit > 1 AND ?:product_prices.usergroup_id IN (?n) ORDER BY lower_limit", array('usergroup_id'), $product_id, array_merge(array(USERGROUP_ALL), $auth['usergroup_ids']));
  126.                 // If customer has usergroup and prices defined for this usergroup, get them
  127.                 if (!empty($auth['usergroup_ids'])) {
  128.                     foreach ($auth['usergroup_ids'] as $ug_id) {
  129.                         if (!empty($_prices[$ug_id]) && sizeof($_prices[$ug_id]) > 0) {
  130.                             if (empty($product_data['prices'])) {
  131.                                 $product_data['prices'] = $_prices[$ug_id];
  132.                             } else {
  133.                                 foreach ($_prices[$ug_id] as $comp_data) {
  134.                                     $add_elm = true;
  135.                                     foreach ($product_data['prices'] as $price_id => $price_data) {
  136.                                         if ($price_data['lower_limit'] == $comp_data['lower_limit']) {
  137.                                             $add_elm = false;
  138.                                             if ($price_data['price'] > $comp_data['price']) {
  139.                                                 $product_data['prices'][$price_id] = $comp_data;
  140.                                             }
  141.                                             break;
  142.                                         }
  143.                                     }
  144.                                     if ($add_elm) {
  145.                                         $product_data['prices'][] = $comp_data;
  146.                                     }
  147.                                 }
  148.                             }
  149.                         }
  150.                     }
  151.                     if (!empty($product_data['prices'])) {
  152.                         $tmp = array();
  153.                         foreach ($product_data['prices'] as $price_id => $price_data) {
  154.                             $tmp[$price_id] = $price_data['lower_limit'];
  155.                         }
  156.                         array_multisort($tmp, SORT_ASC, $product_data['prices']);
  157.                     }
  158.                 }
  159.                 // else, get prices for not members
  160.                 if (empty($product_data['prices']) && !empty($_prices[0]) && sizeof($_prices[0]) > 0) {
  161.                     $product_data['prices'] = $_prices[0];
  162.                 }
  163.             // Other - get all
  164.             } else {
  165.                 $product_data['prices'] = db_get_array("SELECT price, lower_limit, usergroup_id FROM ?:product_prices WHERE product_id = ?i ORDER BY usergroup_id, lower_limit", $product_id);
  166.             }
  167.         }
  168.  
  169.         if ($features) {
  170.             // Get product features
  171.             $path = (!empty($product_data['main_category']))? explode('/', db_get_field("SELECT id_path FROM ?:categories WHERE category_id = ?i", $product_data['main_category'])) : "";
  172.  
  173.             $_params = array(
  174.                 'category_ids' => $path,
  175.                 'product_id' => $product_id,
  176.                 'statuses' => AREA == 'C' ? array('A') : array('A', 'H'),
  177.                 'variants' => true,
  178.                 'plain' => false,
  179.                 'display_on' => AREA == 'A' ? '' : 'product',
  180.                 'existent_only' => (AREA != 'A')
  181.             );
  182.             list($product_data['product_features']) = fn_get_product_features($_params, 0, $lang_code);
  183.         }
  184.     } else {
  185.         return false;
  186.     }
  187.    
  188.     $product_data['detailed_params']['info_type'] = 'D';
  189.  
  190.     fn_set_hook('get_product_data_more', $product_data, $auth, $preview);
  191.  
  192.     return (!empty($product_data) ? $product_data : false);
  193. }
  194.  
  195. //
  196. // Get product name by its id
  197. //
  198. function fn_get_product_name($product_id, $lang_code = CART_LANGUAGE, $as_array = false)
  199. {
  200.     if (!empty($product_id)) {
  201.         if (!is_array($product_id) && strpos($product_id, ',') !== false) {
  202.             $product_id = explode(',', $product_id);
  203.         }
  204.         if (is_array($product_id) || $as_array == true) {
  205.             return db_get_hash_single_array("SELECT product_id, product FROM ?:product_descriptions WHERE product_id IN (?n) AND lang_code = ?s", array('product_id', 'product'), $product_id, $lang_code);
  206.         } else {
  207.             return db_get_field("SELECT product FROM ?:product_descriptions WHERE product_id = ?i AND lang_code = ?s", $product_id, $lang_code);
  208.         }
  209.     }
  210.  
  211.     return false;
  212. }
  213.  
  214. /**
  215.  * Get product price.
  216.  *
  217.  * @param int $product_id
  218.  * @param int $amount optional parameter for wholesale prices, etc...
  219.  * @param array $auth
  220.  * @return price
  221.  */
  222.  
  223. function fn_get_product_price($product_id, $amount, &$auth)
  224. {
  225.     $usergroup_condition = db_quote("AND ?:product_prices.usergroup_id IN (?n)", ((AREA == 'C' || defined('ORDER_MANAGEMENT')) ? array_merge(array(USERGROUP_ALL), $auth['usergroup_ids']) : USERGROUP_ALL));
  226.  
  227.     $price = db_get_field("SELECT MIN(?:product_prices.price) as price FROM ?:product_prices WHERE lower_limit <=?i AND ?:product_prices.product_id = ?i ?p ORDER BY lower_limit DESC LIMIT 1", $amount, $product_id, $usergroup_condition);
  228.  
  229.     fn_set_hook('get_product_price', $price, $product_id, $amount, $auth);
  230.  
  231.     return (empty($price))? 0 : floatval($price);
  232. }
  233.  
  234. //
  235. // Translate products descriptions to the selected language
  236. //
  237. function fn_translate_products(&$products, $fields = '',$lang_code = '', $translate_options = false)
  238. {
  239.     if (empty($fields)) {
  240.         $fields = 'product, short_description, full_description';
  241.     }
  242.  
  243.     foreach ($products as $k => $v) {
  244.         if (!empty($v['deleted_product'])) {
  245.             continue;
  246.         }
  247.         $descriptions = db_get_row("SELECT $fields FROM ?:product_descriptions WHERE product_id = ?i AND lang_code = ?s", $v['product_id'], $lang_code);
  248.         foreach ($descriptions as $k1 => $v1) {
  249.             $products[$k][$k1] = $v1;
  250.         }
  251.         if ($translate_options && !empty($v['product_options'])) {
  252.             foreach ($v['product_options'] as $k1 => $v1) {
  253.                 $option_descriptions = db_get_row("SELECT option_name, option_text, description, comment FROM ?:product_options_descriptions WHERE option_id = ?i AND lang_code = ?s", $v1['option_id'], $lang_code);
  254.                 foreach ($option_descriptions as $k2 => $v2) {
  255.                     $products[$k]['product_options'][$k1][$k2] = $v2;
  256.                 }
  257.  
  258.                 if ($v1['option_type'] == 'C') {
  259.                     $products[$k]['product_options'][$k1]['variant_name'] = (empty($v1['position'])) ? fn_get_lang_var('no', $lang_code) : fn_get_lang_var('yes', $lang_code);
  260.                 } elseif ($v1['option_type'] == 'S' || $v1['option_type'] == 'R') {
  261.                     $variant_description = db_get_field("SELECT variant_name FROM ?:product_option_variants_descriptions WHERE variant_id = ?i AND lang_code = ?s", $v1['value'], $lang_code);
  262.                     $products[$k]['product_options'][$k1]['variant_name'] = $variant_description;
  263.                 }
  264.             }
  265.         }
  266.     }
  267. }
  268.  
  269. //
  270. // Build product-prices cache
  271. //
  272. function fn_build_products_cache($product_ids = array(), $category_ids = array())
  273. {
  274.     return false; // Temporarly disabled
  275.  
  276.     $condition = ' 1 ';
  277.     $d_condition = ' 1 ';
  278.     if (!empty($product_ids)) {
  279.         $condition .= db_quote(" AND b.product_id IN (?n)", $product_ids);
  280.     }
  281.     if (!empty($category_ids)) {
  282.         $condition .= db_quote(" AND b.category_id IN (?n)", $category_ids);
  283.     }
  284.  
  285.     db_query("DELETE FROM ?:products_cache as b WHERE $condition");
  286.  
  287.     $_statuses = array('A', 'H');
  288.  
  289.     $total_rows = db_get_field("SELECT COUNT(*) FROM ?:products_categories as b INNER JOIN ?:categories as a ON a.category_id = b.category_id AND a.status IN (?a) WHERE ?p", $_statuses, $condition);
  290.  
  291.     for ($i = 0; $i < $total_rows; $i = $i + 50) {
  292.         $data = db_get_array("SELECT a.category_id, b.position, a.usergroup_ids, b.product_id, c.price, c.usergroup_id as price_usergroup_id FROM ?:categories as a INNER JOIN ?:products_categories as b ON b.category_id = a.category_id INNER JOIN ?:product_prices as c ON c.product_id = b.product_id AND c.lower_limit = 1 WHERE ?p AND a.status IN (?a) LIMIT $i, 50", $_statuses, $condition);
  293.  
  294.         foreach ($data as $k => $v) {
  295.             if (empty($v['usergroup_ids'])) {// category usergroup is empty
  296.                 $v['usergroup_ids'] = $v['price_usergroup_id'];
  297.             }
  298.             $ug_ids = explode(',', $v['usergroup_ids']);
  299.             if (!empty($v['price_usergroup_id']) && !in_array($v['price_usergroup_id'], $ug_ids)) {
  300.                 continue;
  301.             }
  302.  
  303.             unset($v['price_usergroup_id']);
  304.             if (empty($product_ids) && empty($category_ids)) {
  305.                 echo ". ";
  306.                 fn_flush();
  307.             }
  308.             db_query("INSERT INTO ?:products_cache ?e", $v);
  309.         }
  310.     }
  311. }
  312.  
  313. function fn_gather_additional_products_data(&$products, $params)
  314. {
  315.     if (empty($products)) {
  316.         return;
  317.     }
  318.  
  319.     // Set default values to input params
  320.     $default_params = array (
  321.         'get_icon' => false,
  322.         'get_detailed' => false,
  323.         'get_options' => true,
  324.         'get_discounts' => true,
  325.         'get_features' => false,
  326.         'get_extra' => false,
  327.         'get_for_one_product' => (!is_array(reset($products)))? true : false,
  328.     );
  329.  
  330.     $params = array_merge($default_params, $params);
  331.  
  332.     $auth = & $_SESSION['auth'];
  333.     $allow_negative_amount = Registry::get('settings.General.allow_negative_amount');
  334.  
  335.     if ($params['get_for_one_product'] == true) {
  336.         $products = array($products);
  337.     }
  338.  
  339.     $product_ids = array();
  340.     foreach ($products as $v) {
  341.             $product_ids[] = $v['product_id'];
  342.     }
  343.  
  344.     if ($params['get_icon'] == true || $params['get_detailed'] == true) {
  345.         $products_images = fn_get_image_pairs($product_ids, 'product', 'M', $params['get_icon'], $params['get_detailed'], CART_LANGUAGE);
  346.     }
  347.  
  348.     if ($params['get_options'] == true) {
  349.         $product_options = fn_get_product_options($product_ids, CART_LANGUAGE);
  350.     } else {
  351.         $has_product_options = db_get_hash_array("SELECT a.option_id, a.product_id FROM ?:product_options AS a WHERE a.product_id IN (?n) AND a.status = 'A'", 'product_id', $product_ids);
  352.         $has_product_options_links = db_get_hash_array("SELECT c.option_id, c.product_id FROM ?:product_global_option_links AS c LEFT JOIN ?:product_options AS a ON a.option_id = c.option_id WHERE a.status = 'A' AND c.product_id IN (?n)", 'product_id', $product_ids);
  353.     }
  354.  
  355.     fn_set_hook('get_additional_products_data_pre', $product_ids, $params, $products, $auth, $products_images, $product_options, $has_product_options, $has_product_options_links);
  356.        
  357.     // foreach $products
  358.     foreach ($products as &$_product) {
  359.         $product = $_product;
  360.         $product_id = $product['product_id'];
  361.  
  362.         // Get images
  363.         if ($params['get_icon'] == true || $params['get_detailed'] == true) {
  364.             if (empty($product['main_pair']) && !empty($products_images[$product_id])) {
  365.                 $product['main_pair'] = reset($products_images[$product_id]);
  366.             }
  367.         }
  368.  
  369.         if (!isset($product['base_price'])) {
  370.             $product['base_price'] = $product['price']; // save base price (without discounts, etc...)
  371.         }
  372.  
  373.         fn_set_hook('get_additional_product_data_before_options', $product, $auth, $params);
  374.  
  375.         // Convert product categories
  376.         if (!empty($product['category_ids']) && !is_array($product['category_ids'])) {
  377.             $product['category_ids'] = fn_convert_categories($product['category_ids']);
  378.         }
  379.  
  380.         $product['selected_options'] = empty($product['selected_options']) ? array() : $product['selected_options'];
  381.  
  382.         // Get product options
  383.         if ($params['get_options'] == true) {
  384.             if (!isset($product['options_type']) || !isset($product['exceptions_type'])) {
  385.                 $types = db_get_row('SELECT options_type, exceptions_type FROM ?:products WHERE product_id = ?i', $product['product_id']);
  386.                 $product['options_type'] = $types['options_type'];
  387.                 $product['exceptions_type'] = $types['exceptions_type'];
  388.             }
  389.  
  390.             if (empty($product['product_options'])) {
  391.                 if (!empty($product['combination'])) {
  392.                     $selected_options = fn_get_product_options_by_combination($product['combination']);
  393.                 }
  394.  
  395.                 $product['product_options'] = (!empty($selected_options)) ? fn_get_selected_product_options($product['product_id'], $selected_options, CART_LANGUAGE) : $product_options[$product_id];
  396.             }
  397.  
  398.             $product = fn_apply_options_rules($product);
  399.  
  400.             if (!empty($params['get_icon']) || !empty($params['get_detailed'])) {
  401.                 // Get product options images
  402.                 if (!empty($product['combination_hash']) && !empty($product['product_options'])) {
  403.                     $image = fn_get_image_pairs($product['combination_hash'], 'product_option', 'M', $params['get_icon'], $params['get_detailed'], CART_LANGUAGE);
  404.                     if (!empty($image)) {
  405.                         $product['main_pair'] = $image;
  406.                     }
  407.                 }
  408.             }
  409.             $product['has_options'] = !empty($product['product_options']);
  410.             $product = fn_apply_exceptions_rules($product);
  411.         } else {
  412.             $product['has_options'] = (!empty($has_product_options[$product_id]) || !empty($has_product_options_links[$product_id]))? true : false;
  413.         }
  414.  
  415.         fn_set_hook('get_additional_product_data_before_discounts', $product, $auth, $params['get_options'], $params);
  416.  
  417.         // Get product discounts
  418.         if ($params['get_discounts'] == true && !isset($product['exclude_from_calculate'])) {
  419.             fn_promotion_apply('catalog', $product, $auth);
  420.             if (!empty($product['prices']) && is_array($product['prices'])){
  421.                 $product_copy = $product;
  422.                 foreach($product['prices'] as $pr_k => $pr_v){
  423.                     $product_copy['base_price'] = $product_copy['price'] = $pr_v['price'];
  424.                     fn_promotion_apply('catalog', $product_copy, $auth);
  425.                     $product['prices'][$pr_k]['price'] = $product_copy['price'];
  426.                 }
  427.             }
  428.  
  429.             if (empty($product['discount']) && !empty($product['list_price']) && !empty($product['price']) && floatval($product['price']) && $product['list_price'] > $product['price']) {
  430.                 $product['list_discount'] = fn_format_price($product['list_price'] - $product['price']);
  431.                 $product['list_discount_prc'] = sprintf('%d', round($product['list_discount'] * 100 / $product['list_price']));
  432.             }
  433.         }
  434.  
  435.         // FIXME: old product options scheme
  436.         $product['discounts'] = array('A' => 0, 'P' => 0);
  437.         if (!empty($product['promotions'])) {
  438.             foreach ($product['promotions'] as $v) {
  439.                 foreach ($v['bonuses'] as $a) {
  440.                     if ($a['discount_bonus'] == 'to_fixed') {
  441.                         $product['discounts']['A'] += $a['discount'];
  442.                     } elseif ($a['discount_bonus'] == 'by_fixed') {
  443.                         $product['discounts']['A'] += $a['discount_value'];
  444.                     } elseif ($a['discount_bonus'] == 'to_percentage') {
  445.                         $product['discounts']['P'] += 100 - $a['discount_value'];
  446.                     } elseif ($a['discount_bonus'] == 'by_percentage') {
  447.                         $product['discounts']['P'] += $a['discount_value'];
  448.                     }
  449.                 }
  450.             }
  451.         }
  452.  
  453.         // Add product prices with taxes and without taxes
  454.         if (AREA != 'A' && Registry::get('settings.Appearance.show_prices_taxed_clean') == 'Y' && $auth['tax_exempt'] != 'Y') {
  455.             fn_get_taxed_and_clean_prices($product, $auth);
  456.         }
  457.  
  458.         if ($params['get_features'] == true && !isset($product['product_features'])) {
  459.             $product['product_features'] = fn_get_product_features_list($product['product_id']);
  460.         }
  461.  
  462.         if ($params['get_extra'] == true && !empty($product['is_edp']) && $product['is_edp'] == 'Y') {
  463.             $product['agreement'] = array(fn_get_edp_agreements($product['product_id']));
  464.         }
  465.  
  466.         $qty_content = array();
  467.         if (!empty($product['qty_step'])) {
  468.             $per_item = 0;
  469.             if ($allow_negative_amount == 'Y' && !empty($product['max_qty'])) {
  470.                 $amount = $product['max_qty'];
  471.             } else {
  472.                 $amount = isset($product['in_stock']) ? $product['in_stock'] : (isset($product['inventory_amount']) ? $product['inventory_amount'] : $product['amount']);
  473.             }
  474.             //check if the 'inventory' option is set to 'do not track'
  475.             $amount = ($product['tracking'] == 'D') ? (!empty($product['max_qty']) ? $product['max_qty'] : $product['qty_step'] * $product['list_qty_count']) : (($amount < $product['qty_step']) ? $product['qty_step'] : $amount);
  476.  
  477.             for ($i = 1; $per_item <= ($amount - $product['qty_step']); $i++) {
  478.                 $per_item = $product['qty_step'] * $i;
  479.  
  480.                 if (!empty($product['list_qty_count']) && ($i > $product['list_qty_count'])) {
  481.                     break;
  482.                 }
  483.  
  484.                 if ((!empty($product['max_qty']) && $per_item > $product['max_qty']) || (!empty($product['min_qty']) && $per_item < $product['min_qty'])) {
  485.                     continue;
  486.                 }
  487.  
  488.                 $qty_content[$i] = $per_item;
  489.             }
  490.         }
  491.         $product['qty_content'] = $qty_content;
  492.  
  493.         $product['detailed_params'] = empty($product['detailed_params']) ? $params : array_merge($product['detailed_params'], $params);
  494.  
  495.         fn_set_hook('get_additional_product_data', $product, $auth, $params['get_options'], $params);
  496.         $_product = $product;
  497.     }// \foreach $products
  498.  
  499.     fn_set_hook('get_additional_products_data_post', $product_ids, $params, $products, $auth);
  500.  
  501.     if ($params['get_for_one_product'] == true) {
  502.         $products = array_shift($products);
  503.     }
  504. }
  505.  
  506. function fn_gather_additional_product_data(&$product, $get_icon = false, $get_detailed = false, $get_options = true, $get_discounts = true, $get_features = false)
  507. {
  508.     // Get specific settings
  509.     $params = array(
  510.         'get_icon' => $get_icon,
  511.         'get_detailed' => $get_detailed,
  512.         'get_options' => $get_options,
  513.         'get_discounts' => $get_discounts,
  514.         'get_features' => $get_features,
  515.     );
  516.     fn_gather_additional_products_data($product, $params);
  517. }
  518.  
  519. /**
  520.  * Return files attached to object
  521.  *
  522.  * @param int $product_id ID of product
  523.  * @param bool $preview_check get files only with preview
  524.  * @param int $order_id get order ekeys for the files
  525.  * @return array files
  526.  */
  527.  
  528. function fn_get_product_files($product_id, $preview_check = false, $order_id = 0, $lang_code = DESCR_SL)
  529. {
  530.     $fields = array(
  531.         '?:product_files.*',
  532.         '?:product_file_descriptions.file_name',
  533.         '?:product_file_descriptions.license',
  534.         '?:product_file_descriptions.readme'
  535.     );
  536.  
  537.     $join = db_quote(" LEFT JOIN ?:product_file_descriptions ON ?:product_file_descriptions.file_id = ?:product_files.file_id AND ?:product_file_descriptions.lang_code = ?s", $lang_code);
  538.  
  539.     if (!empty($order_id)) {
  540.         $fields[] = '?:product_file_ekeys.active';
  541.         $fields[] = '?:product_file_ekeys.downloads';
  542.         $fields[] = '?:product_file_ekeys.ekey';
  543.  
  544.         $join .= db_quote(" LEFT JOIN ?:product_file_ekeys ON ?:product_file_ekeys.file_id = ?:product_files.file_id AND ?:product_file_ekeys.order_id = ?i", $order_id);
  545.         $join .= (AREA == 'C') ? " AND ?:product_file_ekeys.active = 'Y'" : '';
  546.     }
  547.  
  548.     $condition = db_quote("WHERE ?:product_files.product_id = ?i", $product_id);
  549.  
  550.     if ($preview_check == true) {
  551.         $condition .= " AND preview_path != ''";
  552.     }
  553.  
  554.     if (AREA == 'C') {
  555.         $condition .= " AND ?:product_files.status = 'A'";
  556.     }
  557.  
  558.     fn_set_hook('get_product_files', $product_id, $order_id, $fields, $join, $condition);
  559.  
  560.     $files = db_get_array("SELECT " . implode(', ', $fields) . " FROM ?:product_files ?p ?p ORDER BY position", $join, $condition);
  561.  
  562.     if (!empty($files)) {
  563.         foreach ($files as $k => $file) {
  564.             if (!empty($file['license']) && $file['agreement'] == 'Y') {
  565.                 $files[$k]['agreements'] = array($file);
  566.             }
  567.             if (!empty($file['product_id']) && !empty($file['ekey'])) {
  568.                 $files[$k]['edp_info'] = fn_get_product_edp_info($file['product_id'], $file['ekey']);
  569.             }
  570.         }
  571.     }
  572.  
  573.     return $files;
  574. }
  575.  
  576. /**
  577.  * Return edp ekey info
  578.  *
  579.  * @param int $product_id
  580.  * @param string $ekey - download key
  581.  * @return array download key info
  582.  */
  583. function fn_get_product_edp_info($product_id, $ekey)
  584. {
  585.     $unlimited = db_get_field("SELECT unlimited_download FROM ?:products WHERE product_id = ?i", $product_id);
  586.     $ttl_condition = ($unlimited == 'Y') ? '' :  db_quote(" AND ttl > ?i", TIME);
  587.  
  588.     return db_get_row("SELECT product_id, order_id, file_id FROM ?:product_file_ekeys WHERE product_id = ?i AND active = 'Y' AND ekey = ?s ?p", $product_id, $ekey, $ttl_condition);
  589. }
  590.  
  591. /**
  592.  * Return agreemetns
  593.  *
  594.  * @param int $product_id
  595.  * @param bool $file_name get file name
  596.  * @return array
  597.  */
  598.  
  599. function fn_get_edp_agreements($product_id, $file_name = false)
  600. {
  601.     $join = '';
  602.     $fields = array(
  603.         '?:product_files.file_id',
  604.         '?:product_files.agreement',
  605.         '?:product_file_descriptions.license'
  606.     );
  607.  
  608.     if ($file_name == true) {
  609.         $join .= db_quote(" LEFT JOIN ?:product_file_descriptions ON ?:product_file_descriptions.file_id = ?:product_files.file_id AND product_file_descriptions.lang_code = ?s", CART_LANGUAGE);
  610.         $fields[] = '?:product_file_descriptions.file_name';
  611.     }
  612.  
  613.     return db_get_array("SELECT " . implode(', ', $fields) . " FROM ?:product_files INNER JOIN ?:product_file_descriptions ON ?:product_file_descriptions.file_id = ?:product_files.file_id AND ?:product_file_descriptions.lang_code = ?s WHERE ?:product_files.product_id = ?i AND ?:product_file_descriptions.license != '' AND ?:product_files.agreement = 'Y'", CART_LANGUAGE, $product_id);
  614. }
  615.  
  616. //-------------------------------------- 'Categories' object functions -----------------------------
  617.  
  618. //
  619. // Get subcategories list for current category (first-level categories only)
  620. //
  621. function fn_get_subcategories($category_id = '0', $lang_code = CART_LANGUAGE)
  622. {
  623.     $params = array (
  624.         'category_id' => $category_id,
  625.         'visible' => true
  626.     );
  627.  
  628.     fn_set_hook('get_subcategories', $category_id, $params, $lang_code);
  629.  
  630.     list($categories, ) = fn_get_categories($params, $lang_code);
  631.  
  632.     return $categories;
  633. }
  634.  
  635. //
  636. // Get categories tree (multidimensional) from the current category
  637. //
  638. function fn_get_categories_tree($category_id = '0', $simple = true, $lang_code = CART_LANGUAGE)
  639. {
  640.     $params = array (
  641.         'category_id' => $category_id,
  642.         'simple' => $simple
  643.     );
  644.  
  645.     list($categories, ) = fn_get_categories($params, $lang_code);
  646.  
  647.     return $categories;
  648. }
  649.  
  650. //
  651. // Get categories tree (plain) from the current category
  652. //
  653. function fn_get_plain_categories_tree($category_id = '0', $simple = true, $lang_code = CART_LANGUAGE)
  654. {
  655.     $params = array (
  656.         'category_id' => $category_id,
  657.         'simple' => $simple,
  658.         'visible' => false,
  659.         'plain' => true
  660.     );
  661.  
  662.     list($categories, ) = fn_get_categories($params, $lang_code);
  663.  
  664.     return $categories;
  665. }
  666.  
  667. function fn_cat_sort($a, $b)
  668. {
  669.     if (empty($a["position"]) && empty($b['position'])) {
  670.         return strnatcmp($a["category"], $b["category"]);
  671.     } else {
  672.         return strnatcmp($a["position"], $b["position"]);
  673.     }
  674. }
  675.  
  676. function fn_show_picker($table, $threshold)
  677. {
  678.     return db_get_field("SELECT COUNT(*) FROM ?:$table") > $threshold ? true : false;
  679. }
  680.  
  681. //
  682. // Get categories tree beginnig from category_id
  683. //
  684. // Params
  685. // @category_id - root category
  686. // @visible - get only visible categories
  687. // @current_category_id - current node for visible categories
  688. // @simple - get category path as set of category IDs
  689. // @plain - return continues list of categories
  690. // --------------------------------------
  691. // Examples:
  692. // Gets whole categories tree:
  693. // fn_get_categories()
  694. // --------------------------------------
  695. // Gets subcategories tree of the category:
  696. // fn_get_categories(123)
  697. // --------------------------------------
  698. // Gets all first-level nodes of the category
  699. // fn_get_categories(123, true)
  700. // --------------------------------------
  701. // Gets all visible nodes of the category, start from the root
  702. // fn_get_categories(0, true, 234)
  703.  
  704. function fn_get_categories($params = array(), $lang_code = CART_LANGUAGE)
  705. {
  706.     $default_params = array (
  707.         'category_id' => 0,
  708.         'visible' => false,
  709.         'current_category_id' => 0,
  710.         'simple' => true,
  711.         'plain' => false,
  712.         'sort_order' => 'desc',
  713.         'limit' => 0,
  714.         'sort_by' => 'position',
  715.         'item_ids' => '',
  716.         'group_by_level' => true,
  717.         'get_images' => false,
  718.         'category_delimiter' => '/'
  719.     );
  720.    
  721.     $params = array_merge($default_params, $params);
  722.  
  723.     $sortings = array (
  724.         'timestamp' => '?:categories.timestamp',
  725.         'name' => '?:category_descriptions.category',
  726.         'position' => array(
  727.             '?:categories.position',
  728.             '?:category_descriptions.category'
  729.         )
  730.     );
  731.  
  732.     $directions = array (
  733.         'asc' => 'asc',
  734.         'desc' => 'desc'
  735.     );
  736.  
  737.     $auth = & $_SESSION['auth'];
  738.  
  739.     $fields = array (
  740.         '?:categories.category_id',
  741.         '?:categories.parent_id',
  742.         '?:categories.id_path',
  743.         '?:category_descriptions.category',
  744.         '?:categories.position',
  745.         '?:categories.status'
  746.     );
  747.  
  748.     if ($params['simple'] == false) {
  749.         $fields[] = '?:categories.product_count';
  750.     }
  751.  
  752.     if (empty($params['current_category_id']) && !empty($params['product_category_id'])) {
  753.         $params['current_category_id'] = $params['product_category_id'];
  754.     }
  755.  
  756.     $condition = '';
  757.  
  758.        
  759.     if (AREA == 'C') {
  760.         $_statuses = array('A'); // Show enabled products/categories
  761.         $condition .= fn_get_localizations_condition('?:categories.localization', true);
  762.         $condition .= " AND (" . fn_find_array_in_set($auth['usergroup_ids'], '?:categories.usergroup_ids', true) . ")";
  763.         $condition .= db_quote(" AND ?:categories.status IN (?a)", $_statuses);
  764.     }
  765.  
  766.     if ($params['visible'] == true && empty($params['b_id'])) {
  767.         if (!empty($params['current_category_id'])) {
  768.             $cur_id_path = db_get_field("SELECT id_path FROM ?:categories WHERE category_id = ?i", $params['current_category_id']);
  769.             if (!empty($cur_id_path)) {
  770.                 $parent_categories_ids = explode('/', $cur_id_path);
  771.             }
  772.         }
  773.         $parent_categories_ids[] = $params['category_id'];
  774.         $parents_condition = db_quote(" AND ?:categories.parent_id IN (?n)", $parent_categories_ids);
  775.     }
  776.    
  777.     // if we have company_condtion, skip $parents_condition, it will be processed later by PHP
  778.     if (!empty($parents_condition) && empty($company_condition)) {
  779.         $condition .= $parents_condition;
  780.     }
  781.  
  782.     if (!empty($params['category_id'])) {
  783.         $from_id_path = db_get_field("SELECT id_path FROM ?:categories WHERE category_id = ?i", $params['category_id']);
  784.         $condition .= db_quote(" AND ?:categories.id_path LIKE ?l", "$from_id_path/%");
  785.     }
  786.  
  787.     if (!empty($params['item_ids'])) {
  788.         $condition .= db_quote(' AND ?:categories.category_id IN (?n)', explode(',', $params['item_ids']));
  789.     }
  790.  
  791.     if (!empty($params['except_id']) && (empty($params['item_ids']) || !empty($params['item_ids']) && !in_array($params['except_id'], explode(',', $params['item_ids'])))) {
  792.         $condition .= db_quote(' AND ?:categories.category_id != ?i AND ?:categories.parent_id != ?i', $params['except_id'], $params['except_id']);
  793.     }
  794.  
  795.     if (!empty($params['period']) && $params['period'] != 'A') {
  796.         list($params['time_from'], $params['time_to']) = fn_create_periods($params);
  797.         $condition .= db_quote(" AND (?:categories.timestamp >= ?i AND ?:categories.timestamp <= ?i)", $params['time_from'], $params['time_to']);
  798.     }
  799.  
  800.     $limit = $join = $group_by = '';
  801.  
  802.     if (!empty($params['b_id'])) {
  803.         $join .= " LEFT JOIN ?:block_links ON ?:block_links.object_id = ?:categories.category_id AND ?:block_links.location = 'categories'";
  804.         $condition .= db_quote(' AND ?:block_links.block_id = ?i AND ?:block_links.enable = "Y"', $params['b_id']);
  805.         $params['group_by_level'] = false;
  806.     }
  807.  
  808.     fn_set_hook('get_categories', $params, $join, $condition, $fields, $group_by, $sortings);
  809.  
  810.     if (!empty($params['limit'])) {
  811.         $limit = db_quote(' LIMIT 0, ?i', $params['limit']);
  812.     }
  813.  
  814.     if (empty($params['sort_order']) || empty($directions[$params['sort_order']])) {
  815.         $params['sort_order'] = 'asc';
  816.     }
  817.  
  818.     if (empty($params['sort_by']) || empty($sortings[$params['sort_by']])) {
  819.         $params['sort_by'] = 'position';
  820.     }
  821.  
  822.     // Reverse sorting (for usage in view)
  823.     $params['sort_order'] = ($params['sort_order'] == 'asc') ? 'desc' : 'asc';
  824.  
  825.     $sorting = (is_array($sortings[$params['sort_by']]) ? implode(' ' . $directions[$params['sort_order']] . ', ', $sortings[$params['sort_by']]) : $sortings[$params['sort_by']]) . " " . $directions[$params['sort_order']];
  826.  
  827.     $categories = db_get_hash_array('SELECT ' . implode(',', $fields) . " FROM ?:categories LEFT JOIN ?:category_descriptions ON ?:categories.category_id = ?:category_descriptions.category_id AND ?:category_descriptions.lang_code = ?s $join WHERE 1 ?p $group_by ORDER BY $sorting ?p", 'category_id', $lang_code, $condition, $limit);
  828.    
  829.     fn_set_hook('get_categories_post', $categories);
  830.  
  831.     if (empty($categories)) {
  832.         return array(array());
  833.     }
  834.    
  835.    
  836.     $tmp = array();
  837.     if ($params['simple'] == true || $params['group_by_level'] == true) {
  838.         $child_for = array_keys($categories);
  839.         $where_condition = !empty($params['except_id']) ? db_quote(' AND category_id != ?i', $params['except_id']) : '';
  840.         $has_children = db_get_hash_array("SELECT category_id, parent_id FROM ?:categories WHERE parent_id IN(?n) ?p", 'parent_id', $child_for, $where_condition);
  841.     }
  842.     // Group categories by the level (simple)
  843.     if ($params['simple'] == true) {
  844.         foreach ($categories as $k => $v) {
  845.             $v['level'] = substr_count($v['id_path'], '/');
  846.             if ((!empty($params['current_category_id']) || $v['level'] == 0) && isset($has_children[$k])) {
  847.                 $v['has_children'] = $has_children[$k]['category_id'];
  848.             }
  849.             $tmp[$v['level']][$v['category_id']] = $v;
  850.             if ($params['get_images'] == true) {
  851.                 $tmp[$v['level']][$v['category_id']]['main_pair'] = fn_get_image_pairs($v['category_id'], 'category', 'M', true, true, $lang_code);
  852.             }
  853.         }
  854.     } elseif ($params['group_by_level'] == true) {
  855.         // Group categories by the level (simple) and literalize path
  856.         foreach ($categories as $k => $v) {
  857.             $path = explode('/', $v['id_path']);
  858.             $category_path = array();
  859.             foreach ($path as $__k => $__v) {
  860.                 $category_path[$__v] = @$categories[$__v]['category'];
  861.             }
  862.             $v['category_path'] = implode($params['category_delimiter'], $category_path);
  863.             $v['level'] = substr_count($v['id_path'], "/");
  864.             if ((!empty($params['current_category_id']) || $v['level'] == 0) && isset($has_children[$k])) {
  865.                 $v['has_children'] = $has_children[$k]['category_id'];
  866.             }
  867.             $tmp[$v['level']][$v['category_id']] = $v;
  868.             if ($params['get_images'] == true) {
  869.                 $tmp[$v['level']][$v['category_id']]['main_pair'] = fn_get_image_pairs($v['category_id'], 'category', 'M', true, true, $lang_code);
  870.             }
  871.         }
  872.     } else {
  873.         $tmp = $categories;
  874.         if ($params['get_images'] == true) {
  875.             foreach ($tmp as $k => $v) {
  876.                 if ($params['get_images'] == true) {
  877.                     $tmp[$k]['main_pair'] = fn_get_image_pairs($v['category_id'], 'category', 'M', true, true, $lang_code);
  878.                 }
  879.             }
  880.         }
  881.     }
  882.    
  883.     ksort($tmp, SORT_NUMERIC);
  884.     $tmp = array_reverse($tmp);
  885.  
  886.     foreach ($tmp as $level => $v) {
  887.         foreach ($v as $k => $data) {
  888.             if (isset($data['parent_id']) && isset($tmp[$level + 1][$data['parent_id']])) {
  889.                 $tmp[$level + 1][$data['parent_id']]['subcategories'][] = $tmp[$level][$k];
  890.                 unset($tmp[$level][$k]);
  891.             }
  892.         }
  893.     }
  894.  
  895.     if ($params['group_by_level'] == true) {
  896.         $tmp = array_pop($tmp);
  897.     }
  898.    
  899.     if ($params['plain'] == true) {
  900.         $tmp = fn_multi_level_to_plain($tmp, 'subcategories');
  901.     }
  902.  
  903.     if (!empty($params['item_ids'])) {
  904.         $tmp = fn_sort_by_ids($tmp, explode(',', $params['item_ids']), 'category_id');
  905.     }
  906.  
  907.     if (!empty($params['add_root'])) {
  908.         array_unshift($tmp, array('category_id' => 0, 'category' => $params['add_root']));
  909.     }
  910.    
  911.     return array($tmp, $params);
  912. }
  913.  
  914. function fn_sort(&$array, $key, $function)
  915. {
  916.     usort($array, $function);
  917.     foreach ($array as $k => $v) {
  918.         if (!empty($v[$key])) {
  919.             fn_sort($array[$k][$key], $key, $function);
  920.         }
  921.     }
  922. }
  923.  
  924. //
  925. // Get full category data by its id
  926. //
  927. function fn_get_category_data($category_id = 0, $lang_code = CART_LANGUAGE, $field_list = '', $get_main_pair = true)
  928. {
  929.     if (defined('COMPANY_ID')) {
  930.         $company_data = Registry::get('s_companies.' . COMPANY_ID);
  931.         if (!empty($company_data['categories'])) {
  932.             $allowed_categories = explode(',', $company_data['categories']);
  933.             if (!in_array($category_id, $allowed_categories)) {
  934.                 return false;
  935.             }
  936.         }
  937.     }
  938.    
  939.     $auth = & $_SESSION['auth'];
  940.  
  941.     $conditions = array();
  942.     if (AREA == 'C') {
  943.         $conditions[] = "(" . fn_find_array_in_set($auth['usergroup_ids'], '?:categories.usergroup_ids', true) . ")";
  944.     }
  945.  
  946.     if (!empty($conditions)) {
  947.         $conditions = 'AND '. implode(' AND ', $conditions);
  948.     } else {
  949.         $conditions = '';
  950.     }
  951.  
  952.     if (empty($field_list)) {
  953.         $descriptions_list = "?:category_descriptions.*";
  954.         $field_list = "?:categories.*, $descriptions_list";
  955.     }
  956.  
  957.     $join = '';
  958.  
  959.     fn_set_hook('get_category_data', $category_id, $field_list, $join, $lang_code);
  960.  
  961.     $category_data = db_get_row("SELECT $field_list FROM ?:categories LEFT JOIN ?:category_descriptions ON ?:category_descriptions.category_id = ?:categories.category_id AND ?:category_descriptions.lang_code = ?s ?p WHERE ?:categories.category_id = ?i ?p", $lang_code, $join, $category_id, $conditions);
  962.  
  963.     if (!empty($category_data)) {
  964.         $category_data['category_id'] = $category_id;
  965.  
  966.         // Generate meta description automatically
  967.         if (empty($category_data['meta_description']) && defined('AUTO_META_DESCRIPTION') && AREA != 'A') {
  968.             $category_data['meta_description'] = fn_generate_meta_description($category_data['description']);
  969.         }
  970.  
  971.         if ($get_main_pair == true) {
  972.             $category_data['main_pair'] = fn_get_image_pairs($category_id, 'category', 'M', true, true, $lang_code);
  973.         }
  974.        
  975.         if (!empty($category_data['selected_layouts'])) {
  976.             $category_data['selected_layouts'] = unserialize($category_data['selected_layouts']);
  977.         } else {
  978.             $category_data['selected_layouts'] = array();
  979.         }
  980.     }
  981.  
  982.     fn_set_hook('get_category_data_post', $category_data);
  983.  
  984.     return (!empty($category_data) ? $category_data : false);
  985. }
  986.  
  987. //
  988. // Get category name by its id
  989. //
  990. function fn_get_category_name($category_id = 0, $lang_code = CART_LANGUAGE, $as_array = false)
  991. {
  992.     if (!empty($category_id)) {
  993.         if (!is_array($category_id) && strpos($category_id, ',') !== false) {
  994.             $category_id = explode(',', $category_id);
  995.         }
  996.         if (is_array($category_id) || $as_array == true) {
  997.             return db_get_hash_single_array("SELECT category_id, category FROM ?:category_descriptions WHERE category_id IN (?n) AND lang_code = ?s", array('category_id', 'category'), $category_id, $lang_code);
  998.         } else {
  999.             return db_get_field("SELECT category FROM ?:category_descriptions WHERE category_id = ?i AND lang_code = ?s", $category_id, $lang_code);
  1000.         }
  1001.     }
  1002.  
  1003.     return false;
  1004. }
  1005.  
  1006. //
  1007. // Get category path by its id
  1008. //
  1009. function fn_get_category_path($category_id = 0, $lang_code = CART_LANGUAGE, $path_separator = '/')
  1010. {
  1011.     if (!empty($category_id)) {
  1012.  
  1013.         $id_path = db_get_field("SELECT id_path FROM ?:categories WHERE category_id = ?i", $category_id);
  1014.  
  1015.         $category_path = db_get_hash_single_array("SELECT category_id, category FROM ?:category_descriptions WHERE category_id IN (?n) AND lang_code = ?s", array('category_id', 'category'), explode('/', $id_path), $lang_code);
  1016.  
  1017.         $path = explode('/', $id_path);
  1018.         $_category_path = '';
  1019.         foreach ($path as $v) {
  1020.             $_category_path .= $category_path[$v] . $path_separator;
  1021.         }
  1022.         $_category_path = rtrim($_category_path, $path_separator);
  1023.  
  1024.         return (!empty($_category_path) ? $_category_path : false);
  1025.     }
  1026.  
  1027.     return false;
  1028. }
  1029.  
  1030. //
  1031. // Delete product by its id
  1032. //
  1033. function fn_delete_product($product_id)
  1034. {
  1035.     $auth = & $_SESSION['auth'];
  1036.  
  1037.     if (!empty($product_id)) {
  1038.  
  1039.         $status = true;
  1040.         fn_set_hook('pre_delete_product', $product_id, $status);
  1041.  
  1042.         if ($status == false) {
  1043.             return false;
  1044.         }
  1045.        
  1046.         if (defined('COMPANY_ID')) {
  1047.             $company_id = db_get_field("SELECT company_id FROM ?:products WHERE product_id = ?i", $product_id);
  1048.             if (COMPANY_ID != $company_id) {
  1049.                 fn_set_notification('W', fn_get_lang_var('warning'), fn_get_lang_var('access_denied'));
  1050.                 return false;  
  1051.             }
  1052.         }
  1053.  
  1054.         fn_clean_block_items('products', $product_id);
  1055.         fn_clean_block_links('products', $product_id);
  1056.  
  1057.         // Log product deletion
  1058.         fn_log_event('products', 'delete', array(
  1059.             'product_id' => $product_id,
  1060.         ));
  1061.  
  1062.         $category_ids = db_get_fields("SELECT category_id FROM ?:products_categories WHERE product_id = ?i", $product_id);
  1063.         db_query("DELETE FROM ?:products_categories WHERE product_id = ?i", $product_id);
  1064.         fn_update_product_count($category_ids);
  1065.  
  1066.         db_query("DELETE FROM ?:products WHERE product_id = ?i", $product_id);
  1067.         db_query("DELETE FROM ?:product_descriptions WHERE product_id = ?i", $product_id);
  1068.         db_query("DELETE FROM ?:product_prices WHERE product_id = ?i", $product_id);
  1069.         db_query("DELETE FROM ?:product_features_values WHERE product_id = ?i", $product_id);
  1070.         db_query("DELETE FROM ?:product_options_exceptions WHERE product_id = ?i", $product_id);
  1071.         db_query("DELETE FROM ?:product_popularity WHERE product_id = ?i", $product_id);
  1072.  
  1073.         fn_delete_image_pairs($product_id, 'product');
  1074.  
  1075.         // Delete product options and inventory records for this product
  1076.         fn_poptions_delete_product($product_id);
  1077.  
  1078.         // Delete product files
  1079.         fn_rm(DIR_DOWNLOADS . $product_id);
  1080.  
  1081.         fn_build_products_cache(array($product_id));
  1082.         // Executing delete_product functions from active addons
  1083.  
  1084.         fn_set_hook('delete_product', $product_id);
  1085.  
  1086.         return true;
  1087.     } else {
  1088.         return false;
  1089.     }
  1090. }
  1091.  
  1092. //
  1093. // Update product count for categories
  1094. //
  1095. function fn_update_product_count($category_ids)
  1096. {
  1097.     if (!empty($category_ids)) {
  1098.         foreach($category_ids as $category_id) {
  1099.             $product_count = db_get_field("SELECT COUNT(*) FROM ?:products_categories WHERE category_id = ?i", $category_id);
  1100.             db_query("UPDATE ?:categories SET product_count = ?i WHERE category_id = ?i", $product_count, $category_id);
  1101.         }
  1102.         return true;
  1103.     }
  1104.     return false;
  1105. }
  1106.  
  1107. //
  1108. // Add or update category by its id
  1109. //
  1110. function fn_update_category($category_data, $category_id = 0, $lang_code = CART_LANGUAGE)
  1111. {
  1112.     // category title required
  1113.     if (empty($category_data['category'])) {
  1114.         //return false; // FIXME: management page doesn't have category name
  1115.     }
  1116.  
  1117.     if (isset($category_data['localization'])) {
  1118.         $category_data['localization'] = empty($category_data['localization']) ? '' : fn_implode_localizations($category_data['localization']);
  1119.     }
  1120.     if (isset($category_data['usergroup_ids'])) {
  1121.         $category_data['usergroup_ids'] = empty($category_data['usergroup_ids']) ? '0' : implode(',', $category_data['usergroup_ids']);
  1122.     }
  1123.     $_data = $category_data;
  1124.  
  1125.     if (isset($category_data['timestamp'])) {
  1126.         $_data['timestamp'] = fn_parse_date($category_data['timestamp']);
  1127.     }
  1128.  
  1129.     if (empty($_data['position']) && $_data['position'] != '0'  && isset($_data['parent_id'])) {
  1130.         $_data['position'] = db_get_field("SELECT max(position) FROM ?:categories WHERE parent_id = ?i", $_data['parent_id']);
  1131.         $_data['position'] = $_data['position'] + 10;
  1132.     }
  1133.    
  1134.     if (!empty($_data['selected_layouts'])) {
  1135.         $_data['selected_layouts'] = serialize($_data['selected_layouts']);
  1136.     }
  1137.    
  1138.     if (isset($_data['use_custom_templates']) && $_data['use_custom_templates'] == 'N') {
  1139.         // Clear the layout settings if the category custom templates were disabled
  1140.         $_data['product_columns'] = $_data['selected_layouts'] = $_data['default_layout'] = '';
  1141.     }
  1142.    
  1143.     // create new category
  1144.     if (empty($category_id)) {
  1145.         $create = true;
  1146.         $category_id = db_query("INSERT INTO ?:categories ?e", $_data);
  1147.  
  1148.         if (empty($category_id)) {
  1149.             return false;
  1150.         }
  1151.  
  1152.  
  1153.         // now we need to update 'id_path' field, as we know $category_id
  1154.         /* Generate id_path for category */
  1155.         $parent_id = intval($_data['parent_id']);
  1156.         if ($parent_id == 0) {
  1157.             $id_path = $category_id;
  1158.         } else {
  1159.             $id_path = db_get_row("SELECT id_path FROM ?:categories WHERE category_id = ?i", $parent_id);
  1160.             $id_path = $id_path['id_path'] . '/' . $category_id;
  1161.         }
  1162.  
  1163.         db_query('UPDATE ?:categories SET ?u WHERE category_id = ?i', array('id_path' => $id_path), $category_id);
  1164.  
  1165.  
  1166.         //
  1167.         // Adding same category descriptions for all cart languages
  1168.         //
  1169.         $_data = $category_data;
  1170.         $_data['category_id'] = $category_id;
  1171.  
  1172.         foreach ((array)Registry::get('languages') as $_data['lang_code'] => $v) {
  1173.             db_query("INSERT INTO ?:category_descriptions ?e", $_data);
  1174.         }
  1175.  
  1176.     // update existing category
  1177.     } else {
  1178.  
  1179.         /* regenerate id_path for all child categories of the updated category */
  1180.         if (isset($category_data['parent_id'])) {
  1181.             fn_change_category_parent($category_id, intval($category_data['parent_id']));
  1182.         }
  1183.  
  1184.         db_query("UPDATE ?:categories SET ?u WHERE category_id = ?i", $_data, $category_id);
  1185.         $_data = $category_data;
  1186.         db_query("UPDATE ?:category_descriptions SET ?u WHERE category_id = ?i AND lang_code = ?s", $_data, $category_id, $lang_code);
  1187.     }
  1188.  
  1189.     // Log category add/update
  1190.     fn_log_event('categories', !empty($create) ? 'create' : 'update', array(
  1191.         'category_id' => $category_id
  1192.     ));
  1193.  
  1194.     // Assign usergroup to all subcategories
  1195.     if (!empty($category_data['usergroup_to_subcats']) && $category_data['usergroup_to_subcats'] == 'Y') {
  1196.         $id_path = db_get_field("SELECT id_path FROM ?:categories WHERE category_id = ?i", $category_id);
  1197.         db_query("UPDATE ?:categories SET usergroup_ids = ?s WHERE id_path LIKE ?l", $category_data['usergroup_ids'], "$id_path/%");
  1198.     }
  1199.  
  1200.     if (!empty($category_data['block_id'])) {
  1201.         fn_add_items_to_block($category_data['block_id'], $category_data['add_items'], $category_id, 'categories');
  1202.     }
  1203.  
  1204.     fn_set_hook('update_category', $category_data, $category_id, $lang_code);
  1205.  
  1206.     return $category_id;
  1207.  
  1208. }
  1209.  
  1210. //
  1211. // Change category parent
  1212. //
  1213. function fn_change_category_parent($category_id, $new_parent_id)
  1214. {
  1215.     if (!empty($category_id)) {
  1216.  
  1217.         $new_parent_path = db_get_field("SELECT id_path FROM ?:categories WHERE category_id = ?i", $new_parent_id);
  1218.         $current_path = db_get_field("SELECT id_path FROM ?:categories WHERE category_id = ?i", $category_id);
  1219.  
  1220.         if (!empty($new_parent_path) && !empty($current_path)) {
  1221.             db_query("UPDATE ?:categories SET parent_id = ?i, id_path = ?s WHERE category_id = ?i", $new_parent_id, "$new_parent_path/$category_id", $category_id);
  1222.             db_query("UPDATE ?:categories SET id_path = CONCAT(?s, SUBSTRING(id_path, ?i)) WHERE id_path LIKE ?l", "$new_parent_path/$category_id/", strlen($current_path . '/') + 1, "$current_path/%");
  1223.         } elseif (empty($new_parent_path) && !empty($current_path)) {
  1224.             db_query("UPDATE ?:categories SET parent_id = ?i, id_path = ?i WHERE category_id = ?i", $new_parent_id, $category_id, $category_id);
  1225.             db_query("UPDATE ?:categories SET id_path = CONCAT(?s, SUBSTRING(id_path, ?i)) WHERE id_path LIKE ?l", "$category_id/", strlen($current_path . '/') + 1, "$current_path/%");
  1226.         }
  1227.  
  1228.         return true;
  1229.     }
  1230.  
  1231.     return false;
  1232. }
  1233.  
  1234.  
  1235. //
  1236. // Delete options and it's variants by option_id
  1237. //
  1238. function fn_delete_product_option($option_id, $pid = 0)
  1239. {
  1240.     if (!empty($option_id)) {
  1241.         $condition = fn_get_company_condition();
  1242.         $_otps = db_get_row("SELECT product_id, inventory FROM ?:product_options WHERE option_id = ?i $condition", $option_id);
  1243.         if (empty($_otps)) {
  1244.             return false;
  1245.         }
  1246.         $product_id = $_otps['product_id'];
  1247.         $option_inventory = $_otps['inventory'];
  1248.         $product_link = db_get_fields("SELECT product_id FROM ?:product_global_option_links WHERE option_id = ?i AND product_id = ?i", $option_id, $pid);
  1249.         if (empty($product_id) && !empty($product_link)) {
  1250.             // Linked option
  1251.             $option_description =  db_get_field("SELECT option_name FROM ?:product_options_descriptions WHERE option_id = ?i AND lang_code = ?s", $option_id, CART_LANGUAGE);
  1252.             db_query("DELETE FROM ?:product_global_option_links WHERE product_id = ?i AND option_id = ?i", $pid, $option_id);
  1253.             fn_set_notification('W', fn_get_lang_var('warning'), str_replace('[option_name]', $option_description, fn_get_lang_var('option_unlinked')));
  1254.         } else {
  1255.             // Product option
  1256.             $_vars = db_get_fields("SELECT variant_id FROM ?:product_option_variants WHERE option_id = ?i", $option_id);
  1257.             db_query("DELETE FROM ?:product_options_descriptions WHERE option_id = ?i", $option_id);
  1258.             db_query("DELETE FROM ?:product_options WHERE option_id = ?i", $option_id);
  1259.             fn_delete_product_option_variants($option_id);
  1260.         }
  1261.  
  1262.         if ($option_inventory == "Y" && !empty($product_id)) {
  1263.             $c_ids = db_get_fields("SELECT combination_hash FROM ?:product_options_inventory WHERE product_id = ?i", $product_id);
  1264.             db_query("DELETE FROM ?:product_options_inventory WHERE product_id = ?i", $product_id);
  1265.             foreach ($c_ids as $c_id) {
  1266.                 fn_delete_image_pairs($c_id, 'product_option', '');
  1267.             }
  1268.         }
  1269.  
  1270.         fn_set_hook('delete_product_option', $option_id, $pid);
  1271.  
  1272.         return true;
  1273.     }
  1274.     return false;
  1275. }
  1276.  
  1277.  
  1278. //
  1279. // Delete option variants
  1280. //
  1281. function fn_delete_product_option_variants($option_id = 0, $variant_ids = array())
  1282. {
  1283.     if (!empty($option_id)) {
  1284.         $_vars = db_get_fields("SELECT variant_id FROM ?:product_option_variants WHERE option_id = ?i", $option_id);
  1285.     } elseif (!empty($variant_ids)) {
  1286.         $_vars = db_get_fields("SELECT variant_id FROM ?:product_option_variants WHERE variant_id IN (?n)", $variant_ids);
  1287.     }
  1288.  
  1289.     if (!empty($_vars)) {
  1290.         foreach ($_vars as $v_id) {
  1291.             db_query("DELETE FROM ?:product_option_variants_descriptions WHERE variant_id = ?i", $v_id);
  1292.             fn_delete_image_pairs($v_id, 'variant_image');
  1293.         }
  1294.  
  1295.         db_query("DELETE FROM ?:product_option_variants WHERE variant_id IN (?n)", $_vars);
  1296.     }
  1297.  
  1298.     return true;
  1299. }
  1300.  
  1301. //
  1302. // Get product options
  1303. //
  1304. // @only_selectable - this flag forces to retreive the options with
  1305. // the following types only: select, radio or checkbox
  1306. //
  1307. function fn_get_product_options($product_ids, $lang_code = CART_LANGUAGE, $only_selectable = false, $inventory = false, $only_avail = false)
  1308. {
  1309.     $condition = $_status = '';
  1310.     $extra_variant_fields = '';
  1311.     $option_ids = $variants_ids = $options = array();
  1312.     if (AREA == 'C' || $only_avail == true) {
  1313.         $_status .= " AND status = 'A'";
  1314.     }
  1315.     if ($only_selectable == true) {
  1316.         $condition .= " AND a.option_type IN ('S', 'R', 'C')";
  1317.     }
  1318.     if ($inventory == true) {
  1319.         $condition .= " AND a.inventory = 'Y'";
  1320.     }
  1321.  
  1322.     fn_set_hook('get_product_options', $extra_variant_fields, $product_ids, $lang_code);
  1323.  
  1324.     $condition .= fn_get_company_condition('a.company_id', true, '', true);
  1325.     if (!empty($product_ids)) {
  1326.         $_options = db_get_hash_multi_array("SELECT a.*, b.option_name, b.option_text, b.description, b.inner_hint, b.incorrect_message, b.comment FROM ?:product_options as a LEFT JOIN ?:product_options_descriptions as b ON a.option_id = b.option_id AND b.lang_code = ?s WHERE a.product_id IN (?n) $condition $_status ORDER BY a.position", array('product_id', 'option_id'), $lang_code, $product_ids);
  1327.         $global_options = db_get_hash_multi_array("SELECT c.product_id AS cur_product_id, a.*, b.option_name, b.option_text, b.description, b.inner_hint, b.incorrect_message, b.comment FROM ?:product_options as a LEFT JOIN ?:product_options_descriptions as b ON a.option_id = b.option_id AND b.lang_code = ?s LEFT JOIN ?:product_global_option_links as c ON c.option_id = a.option_id WHERE c.product_id IN (?n) $condition $_status ORDER BY a.position", array('cur_product_id', 'option_id'), $lang_code, $product_ids);
  1328.         foreach ((array)$product_ids as $product_id) {
  1329.             $_opts = (empty($_options[$product_id])? array() : $_options[$product_id]) + (empty($global_options[$product_id])? array() : $global_options[$product_id]);
  1330.             $options[$product_id] = fn_sort_array_by_key($_opts, 'position');
  1331.         }
  1332.     } else {
  1333.         //we need a separate query for global options
  1334.         $options = db_get_hash_multi_array("SELECT a.*, b.option_name, b.option_text, b.description, b.inner_hint, b.incorrect_message, b.comment FROM ?:product_options as a LEFT JOIN ?:product_options_descriptions as b ON a.option_id = b.option_id AND b.lang_code = ?s WHERE a.product_id = 0 $condition $_status ORDER BY a.position", array('product_id', 'option_id'), $lang_code);
  1335.     }
  1336.  
  1337.     foreach ($options as $product_id => $_options) {
  1338.         $option_ids = array_merge($option_ids, array_keys($_options));
  1339.     }
  1340.  
  1341.     if (empty($option_ids)) {
  1342.         return is_array($product_ids)? $options: $options[$product_ids];
  1343.     }
  1344.  
  1345.     $_status = (AREA == 'A')? '' : " AND a.status='A'";
  1346.     $variants = db_get_hash_multi_array("SELECT a.variant_id, a.option_id, a.position, a.modifier, a.modifier_type, a.weight_modifier, a.weight_modifier_type, $extra_variant_fields b.variant_name FROM ?:product_option_variants as a LEFT JOIN ?:product_option_variants_descriptions as b ON a.variant_id = b.variant_id AND b.lang_code = ?s WHERE a.option_id IN (?n) $_status ORDER BY a.position", array('option_id', 'variant_id'), $lang_code, array_unique($option_ids));
  1347.  
  1348.     foreach ($variants as $option_id => $_variants) {
  1349.         $variants_ids = array_merge($variants_ids, array_keys($_variants));
  1350.     }
  1351.  
  1352.     if (empty($variants_ids)) {
  1353.         return is_array($product_ids)? $options: $options[$product_ids];
  1354.     }
  1355.  
  1356.     $image_pairs = fn_get_image_pairs(array_unique($variants_ids), 'variant_image', 'V', true, true, $lang_code);
  1357.  
  1358.     foreach ($variants as $option_id => &$_variants) {
  1359.         foreach ($_variants as $variant_id => &$_variant) {
  1360.             $_variant['image_pair'] = !empty($image_pairs[$variant_id])? reset($image_pairs[$variant_id]) : array();
  1361.         }
  1362.     }
  1363.  
  1364.     foreach ($options as $product_id => &$_options) {
  1365.         foreach ($_options as $option_id => &$_option) {
  1366.             // Add variant names manually, if this option is "checkbox"
  1367.             if ($_option['option_type'] == 'C' && !empty($variants[$option_id])) {
  1368.                 foreach ($variants[$option_id] as $variant_id => $variant) {
  1369.                     $variants[$option_id][$variant_id]['variant_name'] = $variant['position'] == 0 ? fn_get_lang_var('no') : fn_get_lang_var('yes');
  1370.                 }
  1371.             }
  1372.            
  1373.             $_option['variants'] = !empty($variants[$option_id])? $variants[$option_id] : array();
  1374.         }
  1375.     }
  1376.  
  1377.     return is_array($product_ids)? $options: $options[$product_ids];
  1378. }
  1379.  
  1380. /**
  1381.  * Function returns a array of product options with values by combination
  1382.  *
  1383.  * @param string $combination
  1384.  * @return array
  1385.  */
  1386.  
  1387. function fn_get_product_options_by_combination($combination)
  1388. {
  1389.     $options = array();
  1390.  
  1391.     $_comb = explode('_', $combination);
  1392.     if (!empty($_comb) && is_array($_comb)) {
  1393.         $iterations = count($_comb);
  1394.         for ($i = 0; $i < $iterations; $i += 2) {
  1395.             $options[$_comb[$i]] = isset($_comb[$i + 1]) ? $_comb[$i + 1] : '';
  1396.         }
  1397.     }
  1398.  
  1399.     return $options;
  1400. }
  1401.  
  1402. //
  1403. // Delete all product options from the product
  1404. //
  1405. function fn_poptions_delete_product($product_id)
  1406. {
  1407.  
  1408.     $_opts = db_get_fields("SELECT option_id FROM ?:product_options WHERE product_id = ?i", $product_id);
  1409.     if (!fn_is_empty($_opts)) {
  1410.         foreach ($_opts as $k => $v) {
  1411.             $_vars = db_get_fields("SELECT variant_id FROM ?:product_option_variants WHERE option_id = ?i", $v);
  1412.             db_query("DELETE FROM ?:product_options_descriptions WHERE option_id = ?i", $v);
  1413.             if (!fn_is_empty($_vars)) {
  1414.                 foreach ($_vars as $k1 => $v1) {
  1415.                     db_query("DELETE FROM ?:product_option_variants_descriptions WHERE variant_id = ?i", $v1);
  1416.                 }
  1417.                 db_query("DELETE FROM ?:product_option_variants WHERE option_id = ?i", $v);
  1418.             }
  1419.         }
  1420.     }
  1421.     db_query("DELETE FROM ?:product_options WHERE product_id = ?i", $product_id);
  1422.     db_query("DELETE FROM ?:product_options_exceptions WHERE product_id = ?i", $product_id);
  1423.     db_query("DELETE FROM ?:product_options_inventory WHERE product_id = ?i", $product_id);
  1424. }
  1425.  
  1426. //
  1427. // Get product options with select mark
  1428. //
  1429. function fn_get_selected_product_options($product_id, $selected_options, $lang_code = CART_LANGUAGE)
  1430. {
  1431.     $extra_variant_fields = '';
  1432.  
  1433.     fn_set_hook('get_selected_product_options', $extra_variant_fields);
  1434.  
  1435.     $_opts = db_get_array("SELECT a.option_id, a.option_type, a.position, a.inventory, a.product_id, a.regexp, a.required, a.multiupload, a.allowed_extensions, a.max_file_size, b.option_name, b.option_text, b.description, b.inner_hint, b.incorrect_message, b.comment, a.status FROM ?:product_options as a LEFT JOIN ?:product_options_descriptions as b ON a.option_id = b.option_id AND b.lang_code = ?s LEFT JOIN ?:product_global_option_links as c ON c.option_id = a.option_id WHERE (a.product_id = ?i OR c.product_id = ?i) AND a.status = 'A' ORDER BY a.position", $lang_code, $product_id, $product_id);
  1436.     if (is_array($_opts)) {
  1437.         $_status = (AREA == 'A') ? '' : " AND a.status = 'A'";
  1438.         foreach ($_opts as $k => $v) {
  1439.             $_vars = db_get_hash_array("SELECT a.variant_id, a.position, a.modifier, a.modifier_type, a.weight_modifier, a.weight_modifier_type, $extra_variant_fields  b.variant_name FROM ?:product_option_variants as a LEFT JOIN ?:product_option_variants_descriptions as b ON a.variant_id = b.variant_id AND b.lang_code = ?s WHERE a.option_id = ?i $_status ORDER BY a.position", 'variant_id', $lang_code, $v['option_id']);
  1440.  
  1441.             // Add variant names manually, if this option is "checkbox"
  1442.             if ($v['option_type'] == 'C' && !empty($_vars)) {
  1443.                 foreach ($_vars as $variant_id => $variant) {
  1444.                     $_vars[$variant_id]['variant_name'] = $variant['position'] == 0 ? fn_get_lang_var('no') : fn_get_lang_var('yes');
  1445.                 }
  1446.             }
  1447.  
  1448.             $_opts[$k]['value'] = (!empty($selected_options[$v['option_id']])) ? $selected_options[$v['option_id']] : '';
  1449.             $_opts[$k]['variants'] = $_vars;
  1450.         }
  1451.  
  1452.     }
  1453.     return $_opts;
  1454. }
  1455.  
  1456. //
  1457. // Calculate product price/weight with options modifiers
  1458. //
  1459. function fn_apply_options_modifiers($product_options, $base_value, $type, $orig_options = array())
  1460. {
  1461.     $fields = ($type == 'P') ? "modifier, modifier_type" : "weight_modifier as modifier, weight_modifier_type as modifier_type";
  1462.  
  1463.     fn_set_hook('apply_option_modifiers', $fields, $type);
  1464.  
  1465.     $orig_value = $base_value;
  1466.     if (!empty($product_options)) {
  1467.  
  1468.         // Check options type. We need to apply only Selectbox, radiogroup and checkbox modifiers
  1469.         if (empty($orig_options)) {
  1470.             $option_types = db_get_hash_single_array("SELECT option_type as type, option_id FROM ?:product_options WHERE option_id IN (?n)", array('option_id', 'type'), array_keys($product_options));
  1471.         } else {
  1472.             $option_types = array();
  1473.             foreach ($orig_options as $_opt) {
  1474.                 $option_types[$_opt['option_id']] = $_opt['option_type'];
  1475.             }
  1476.         }
  1477.  
  1478.         foreach ($product_options as $option_id => $variant_id) {
  1479.             if (empty($option_types[$option_id]) || strpos('SRC', $option_types[$option_id]) === false) {
  1480.                 continue;
  1481.             }
  1482.             if (empty($orig_options)) {
  1483.                 $_mod = db_get_row("SELECT $fields FROM ?:product_option_variants WHERE variant_id = ?i", $variant_id);
  1484.             } else {
  1485.                 foreach ($orig_options as $_opt) {
  1486.                     if ($_opt['value'] == $variant_id && !empty($variant_id)) {
  1487.                         $_mod = array();
  1488.                         $_mod['modifier'] = $_opt['modifier'];
  1489.                         $_mod['modifier_type'] = $_opt['modifier_type'];
  1490.                     }
  1491.                 }
  1492.             }
  1493.  
  1494.             if (!empty($_mod)) {
  1495.                 if ($_mod['modifier_type'] == 'A') {
  1496.                     // Absolute
  1497.                     if ($_mod['modifier']{0} == '-') {
  1498.                         $base_value = $base_value - floatval(substr($_mod['modifier'],1));
  1499.                     } else {
  1500.                         $base_value = $base_value + floatval($_mod['modifier']);
  1501.                     }
  1502.                 } else {
  1503.                     // Percentage
  1504.                     if ($_mod['modifier']{0} == '-') {
  1505.                         $base_value = $base_value - ((floatval(substr($_mod['modifier'],1)) * $orig_value)/100);
  1506.                     } else {
  1507.                         $base_value = $base_value + ((floatval($_mod['modifier']) * $orig_value)/100);
  1508.                     }
  1509.                 }
  1510.             }
  1511.         }
  1512.     }
  1513.  
  1514.     return $base_value;
  1515. }
  1516.  
  1517. /**
  1518.  * Returns selected product options.
  1519.  * For options wich type is checkbox function gets translation from langvars 'no' and 'yes' and return it as variant_name.
  1520.  *
  1521.  * @param array  $selected_options Options as option_id => selected_variant_id.
  1522.  * @param string $lang_code        2digits language code.
  1523.  *
  1524.  * @return array Array of associative arrays wich contain options data.
  1525.  */
  1526. function fn_get_selected_product_options_info($selected_options, $lang_code = CART_LANGUAGE)
  1527. {
  1528.     if (empty($selected_options) || !is_array($selected_options)) {
  1529.         return array();
  1530.     }
  1531.     $result = array();
  1532.     foreach ($selected_options as $option_id => $variant_id) {
  1533.         $_opts = db_get_row(
  1534.             "SELECT a.option_id, a.option_type, a.inventory, b.option_name, b.option_text, b.description, b.inner_hint, b.incorrect_message " .
  1535.             "FROM ?:product_options as a LEFT JOIN ?:product_options_descriptions as b ON a.option_id = b.option_id AND b.lang_code = ?s " .
  1536.             "WHERE a.option_id = ?i ORDER BY a.position",
  1537.             $lang_code, $option_id
  1538.         );
  1539.  
  1540.         if (empty($_opts)) {
  1541.             continue;
  1542.         }
  1543.         $_vars = array();
  1544.         if (strpos('SRC', $_opts['option_type']) !== false) {
  1545.             $_vars = db_get_row(
  1546.                 "SELECT a.modifier, a.modifier_type, a.position, b.variant_name FROM ?:product_option_variants as a " .
  1547.                 "LEFT JOIN ?:product_option_variants_descriptions as b ON a.variant_id = b.variant_id AND b.lang_code = ?s " .
  1548.                 "WHERE a.variant_id = ?i ORDER BY a.position",
  1549.                 $lang_code, $variant_id
  1550.             );
  1551.         }
  1552.  
  1553.         if ($_opts['option_type'] == 'C') {
  1554.             $_vars['variant_name'] = (empty($_vars['position'])) ? fn_get_lang_var('no', $lang_code) : fn_get_lang_var('yes', $lang_code);
  1555.         } elseif ($_opts['option_type'] == 'I' || $_opts['option_type'] == 'T') {
  1556.             $_vars['variant_name'] = $variant_id;
  1557.         } elseif (!isset($_vars['variant_name'])) {
  1558.             $_vars['variant_name'] = '';
  1559.         }
  1560.  
  1561.         $_vars['value'] = $variant_id;
  1562.  
  1563.         $result[] = fn_array_merge($_opts ,$_vars);
  1564.     }
  1565.  
  1566.     return $result;
  1567. }
  1568.  
  1569. //
  1570. // Get default product options
  1571. //
  1572. function fn_get_default_product_options($product_id, $get_all = false, $product = array())
  1573. {
  1574.     $result = $default = $exceptions = $product_options = array();
  1575.  
  1576.     $exceptions = fn_get_product_exceptions($product_id, true);
  1577.     $exceptions_type = (empty($product['exceptions_type']))? db_get_field('SELECT exceptions_type FROM ?:products WHERE product_id = ?i', $product_id) : $product['exceptions_type'];
  1578.     $track_with_options = (empty($product['tracking']))? db_get_field("SELECT tracking FROM ?:products WHERE product_id = ?i", $product_id) : $product['tracking'];
  1579.  
  1580.     if (!empty($product['product_options'])){
  1581.         //filter out only selectable options
  1582.         foreach ($product['product_options'] as $option_id => $option) {
  1583.             if (in_array($option['option_type'], array('S', 'R', 'C'))) {
  1584.                 $product_options[$option_id] = $option;
  1585.             }
  1586.         }
  1587.     } else {
  1588.         $product_options = fn_get_product_options($product_id, CART_LANGUAGE, true);
  1589.     }
  1590.  
  1591.     if (!empty($product_options)) {
  1592.         foreach ($product_options as $option_id => $option) {
  1593.             if (!empty($option['variants'])) {
  1594.                 $default[$option_id] = key($option['variants']);
  1595.                 foreach ($option['variants'] as $variant_id => $variant) {
  1596.                     $options[$option_id][$variant_id] = true;
  1597.                 }
  1598.             }
  1599.         }
  1600.     } else {
  1601.         return array();
  1602.     }
  1603.    
  1604.     unset($product_options);
  1605.     if (empty($exceptions)) {
  1606.         if ($track_with_options == 'O') {
  1607.             $combination = db_get_field("SELECT combination FROM ?:product_options_inventory WHERE product_id = ?i AND amount > 0 AND combination != '' ORDER BY position LIMIT 1", $product_id);
  1608.             if (!empty($combination)) {
  1609.                 $result = fn_get_product_options_by_combination($combination);
  1610.             }
  1611.         }
  1612.        
  1613.         if (empty($result) && !empty($options)) {
  1614.             foreach ((array)$options as $option_id => $variants) {
  1615.                 $result[$option_id] = key($variants);
  1616.             }
  1617.         }
  1618.        
  1619.         return $result;
  1620.     }
  1621.     $inventory_combinations = array();
  1622.     if ($track_with_options == 'O') {
  1623.         $inventory_combinations = db_get_array("SELECT combination FROM ?:product_options_inventory WHERE product_id = ?i AND amount > 0 AND combination != ''", $product_id);
  1624.         if (!empty($inventory_combinations)) {
  1625.             $_combinations = array();
  1626.             foreach ($inventory_combinations as $_combination) {
  1627.                 $_combinations[] = fn_get_product_options_by_combination($_combination['combination']);
  1628.             }
  1629.             $inventory_combinations = $_combinations;
  1630.             unset($_combinations);
  1631.         }
  1632.     }
  1633.     if ($exceptions_type == 'F') {
  1634.         // Forbidden combinations
  1635.         $_options = array_keys($options);
  1636.         $_variants = array_values($options);
  1637.         if (!empty($_variants)) {
  1638.             foreach ($_variants as $key => $variants) {
  1639.                 $_variants[$key] = array_keys($variants);
  1640.             }
  1641.         }
  1642.        
  1643.         list($result) = fn_get_allowed_options_combination($_options, $_variants, '', 0, $exceptions, $inventory_combinations);
  1644.  
  1645.     } else {
  1646.         // Allowed combinations
  1647.         foreach ($exceptions as $exception) {
  1648.             $result = array();
  1649.             foreach ($exception as $option_id => $variant_id) {
  1650.                 if (isset($options[$option_id][$variant_id]) || $variant_id == -1) {
  1651.                     $result[$option_id] = ($variant_id != -1) ? $variant_id : (isset($options[$option_id]) ? key($options[$option_id]) : '');
  1652.                 } else {
  1653.                     continue 2;
  1654.                 }
  1655.             }
  1656.            
  1657.             $_opt = array_diff_key($options, $result);
  1658.             if (!empty($_opt)) {
  1659.                 foreach ($_opt as $option_id => $variants) {
  1660.                     $result[$option_id] = key($variants);
  1661.                 }
  1662.             }
  1663.            
  1664.             if (empty($inventory_combinations)) {
  1665.                 break;
  1666.             } else {
  1667.                 foreach ($inventory_combinations as $_icombination) {
  1668.                     $_res = array_diff($_icombination, $result);
  1669.                     if (empty($_res)) {
  1670.                         break 2;
  1671.                     }
  1672.                 }
  1673.             }
  1674.         }
  1675.     }
  1676.     return empty($result) ? $default : $result;
  1677. }
  1678.  
  1679. //
  1680. // Generate product variants combinations
  1681. //
  1682. function fn_look_through_variants($product_id, $amount, $options, $variants, $string, $cycle)
  1683. {
  1684.     static $position = 0;
  1685.    
  1686.     // Look through all variants
  1687.     foreach ($variants[$cycle] as $variant_id) {
  1688.         if (count($options)-1 > $cycle) {
  1689.             $string[$cycle][$options[$cycle]] = $variant_id;
  1690.             $cycle ++;
  1691.             $combination = fn_look_through_variants($product_id, $amount, $options, $variants, $string, $cycle);
  1692.             $cycle --;
  1693.             unset($string[$cycle]);
  1694.         } else {
  1695.             $_combination = array();
  1696.             if (!empty($string)) {
  1697.                 foreach ($string as $val) {
  1698.                     foreach ($val as $opt => $var) {
  1699.                         $_combination[$opt] = $var;
  1700.                     }
  1701.                 }
  1702.             }
  1703.             $_combination[$options[$cycle]] = $variant_id;
  1704.             $combination[] = $_combination;
  1705.         }
  1706.     }
  1707.     // if any combinations generated than write them to the database
  1708.     if (!empty($combination)) {
  1709.         foreach ($combination as $k => $v) {
  1710.             $_data = array();
  1711.             $_data['product_id'] = $product_id;
  1712.             $variants = $v;
  1713.             $_data['combination_hash'] = fn_generate_cart_id($product_id, array('product_options' => $variants));
  1714.             $_data['combination'] = fn_get_options_combination($v);
  1715.             $_data['position'] = $position++;
  1716.             $found = db_get_row("SELECT combination_hash, amount, product_code FROM ?:product_options_inventory WHERE product_id = ?i AND combination_hash = ?i AND temp = 'Y'", $product_id, $_data['combination_hash']);
  1717.             $_data['amount'] = empty($found) ?  $amount :  $found['amount'];
  1718.             $_data['product_code'] = empty($found) ?  '' :  $found['product_code'];
  1719.            
  1720.             db_query("REPLACE INTO ?:product_options_inventory ?e", $_data);
  1721.  
  1722.             echo str_repeat('. ', count($combination));
  1723.         }
  1724.     }
  1725.  
  1726.     return $combination;
  1727. }
  1728. //
  1729. // Check and rebuild product options inventory if necessary
  1730. //
  1731. function fn_rebuild_product_options_inventory($product_id, $amount = 50)
  1732. {
  1733.     $_options = db_get_fields("SELECT a.option_id FROM ?:product_options as a LEFT JOIN ?:product_global_option_links as b ON a.option_id = b.option_id WHERE (a.product_id = ?i OR b.product_id = ?i) AND a.option_type IN ('S','R','C') AND a.inventory = 'Y' ORDER BY position", $product_id, $product_id);
  1734.     if (empty($_options)) {
  1735.         return;
  1736.     }
  1737.  
  1738.     db_query("UPDATE ?:product_options_inventory SET temp = 'Y' WHERE product_id = ?i", $product_id);
  1739.     foreach ($_options as $k => $option_id) {
  1740.         $variants[$k] = db_get_fields("SELECT variant_id FROM ?:product_option_variants WHERE option_id = ?i ORDER BY position", $option_id);
  1741.     }
  1742.     $combinations = fn_look_through_variants($product_id, $amount, $_options, $variants, '', 0);
  1743.  
  1744.     // Delete image pairs assigned to old combinations
  1745.     $hashes = db_get_fields("SELECT combination_hash FROM ?:product_options_inventory WHERE product_id = ?i AND temp = 'Y'", $product_id);
  1746.     foreach ($hashes as $v) {
  1747.         fn_delete_image_pairs($v, 'product_option');
  1748.     }
  1749.  
  1750.     // Delete old combinations
  1751.     db_query("DELETE FROM ?:product_options_inventory WHERE product_id = ?i AND temp = 'Y'", $product_id);
  1752. }
  1753.  
  1754. function fn_get_product_features($params = array(), $items_per_page = 0, $lang_code = CART_LANGUAGE)
  1755. {
  1756.     // Init filter
  1757.     $params = fn_init_view('product_features', $params);
  1758.  
  1759.     $default_params = array(
  1760.         'product_id' => 0,
  1761.         'category_ids' => array(),
  1762.         'statuses' => AREA == 'C' ? array('A') : array(),
  1763.         'variants' => false,
  1764.         'plain' => false,
  1765.         'all' => false,
  1766.         'feature_types' => array(),
  1767.         'feature_id' => 0,
  1768.         'display_on' => '',
  1769.         'exclude_group' => false,
  1770.         'exclude_filters' => false,
  1771.         'page' => 1
  1772.     );
  1773.  
  1774.     $params = array_merge($default_params, $params);
  1775.  
  1776.     $base_fields = $fields = array (
  1777.         '?:product_features.feature_id',
  1778.         '?:product_features.feature_type',
  1779.         '?:product_features.parent_id',
  1780.         '?:product_features.display_on_product',
  1781.         '?:product_features.display_on_catalog',
  1782.         '?:product_features_descriptions.description',
  1783.         '?:product_features_descriptions.prefix',
  1784.         '?:product_features_descriptions.suffix',
  1785.         '?:product_features.categories_path',
  1786.         '?:product_features_descriptions.full_description',
  1787.         '?:product_features.status',
  1788.         '?:product_features.comparison',
  1789.         '?:product_features.position'
  1790.     );
  1791.  
  1792.     $condition = $join = $group = '';
  1793.  
  1794.     $join .= db_quote(" LEFT JOIN ?:product_features_descriptions ON ?:product_features_descriptions.feature_id = ?:product_features.feature_id AND ?:product_features_descriptions.lang_code = ?s", $lang_code);
  1795.     $join .= db_quote(" LEFT JOIN ?:product_features AS groups ON ?:product_features.parent_id = groups.feature_id");
  1796.  
  1797.     $fields[] = 'groups.position AS group_position';
  1798.  
  1799.     if (!empty($params['product_id'])) {
  1800.         $join .= db_quote(" LEFT JOIN ?:product_features_values ON ?:product_features_values.feature_id = ?:product_features.feature_id  AND ?:product_features_values.product_id = ?i AND ?:product_features_values.lang_code = ?s", $params['product_id'], $lang_code);
  1801.  
  1802.         if (!empty($params['existent_only'])) {
  1803.             $condition .= db_quote(" AND IF(?:product_features.feature_type = 'G' OR ?:product_features.feature_type = 'C', 1, ?:product_features_values.feature_id)");
  1804.         }
  1805.  
  1806.         $fields[] = '?:product_features_values.value';
  1807.         $fields[] = '?:product_features_values.variant_id';
  1808.         $fields[] = '?:product_features_values.value_int';
  1809.     }
  1810.  
  1811.     if (!empty($params['feature_id'])) {
  1812.         $condition .= db_quote(" AND ?:product_features.feature_id = ?i", $params['feature_id']);
  1813.     }
  1814.  
  1815.     if (!empty($params['exclude_group'])) {
  1816.         $condition .= db_quote(" AND ?:product_features.feature_type != 'G'");
  1817.     }
  1818.  
  1819.     if (isset($params['description']) && fn_string_no_empty($params['description'])) {
  1820.         $condition .= db_quote(" AND ?:product_features_descriptions.description LIKE ?l", "%".trim($params['description'])."%");
  1821.     }
  1822.  
  1823.     if (!empty($params['statuses'])) {
  1824.         $condition .= db_quote(" AND ?:product_features.status IN (?a)", $params['statuses']);
  1825.     }
  1826.  
  1827.     if (isset($params['parent_id']) && $params['parent_id'] !== '') {
  1828.         $condition .= db_quote(" AND ?:product_features.parent_id = ?i", $params['parent_id']);
  1829.     }
  1830.  
  1831.     if (!empty($params['display_on']) && in_array($params['display_on'], array('product', 'catalog'))) {
  1832.         $condition .= " AND ?:product_features.display_on_$params[display_on] = 1";
  1833.     }
  1834.  
  1835.     if (!empty($params['feature_types'])) {
  1836.         $condition .= db_quote(" AND ?:product_features.feature_type IN (?a)", $params['feature_types']);
  1837.     }
  1838.  
  1839.     if (!empty($params['category_ids'])) {
  1840.         $c_ids = is_array($params['category_ids']) ? $params['category_ids'] : fn_explode(',', $params['category_ids']);
  1841.         $find_set = array(
  1842.             " ?:product_features.categories_path = '' "
  1843.         );
  1844.         foreach ($c_ids as $k => $v) {
  1845.             $find_set[] = db_quote(" FIND_IN_SET(?i, ?:product_features.categories_path) ", $v);
  1846.         }
  1847.         $find_in_set = db_quote(" AND (?p)", implode('OR', $find_set));
  1848.         $condition .= $find_in_set;
  1849.     }
  1850.     if (!empty($params['exclude_filters'])) {
  1851.         $condition .= db_quote(" AND ?:product_features.feature_id NOT IN(SELECT ?:product_filters.feature_id FROM ?:product_filters GROUP BY ?:product_filters.feature_id)");
  1852.     }
  1853.     fn_set_hook('get_product_features', $fields, $join, $condition);
  1854.  
  1855.     $limit = '';
  1856.     if (!empty($items_per_page)) {
  1857.         $total = db_get_field("SELECT COUNT(*) FROM ?:product_features $join WHERE 1 $condition $group ORDER BY ?:product_features.position, ?:product_features_descriptions.description");
  1858.         $limit = fn_paginate($params['page'], $total, $items_per_page);
  1859.     }
  1860.  
  1861.     $data = db_get_hash_array("SELECT " . implode(', ', $fields) . " FROM ?:product_features $join WHERE 1 $condition $group ORDER BY group_position, ?:product_features.position, ?:product_features_descriptions.description $limit", 'feature_id');
  1862.  
  1863.     $has_ungroupped = false;
  1864.     if (!empty($data)) {
  1865.         if ($params['variants'] == true) {
  1866.             foreach ($data as $k => $v) {
  1867.                 if (in_array($v['feature_type'], array('S', 'M', 'N', 'E'))) {
  1868.                     list($f_variants, $v_total) = fn_get_product_feature_variants($v['feature_id'], $params['product_id'], $v['feature_type'], true, $lang_code, Registry::get('settings.Appearance.admin_elements_per_page'));
  1869.                     $data[$k]['variants'] = $f_variants;
  1870.                     if ($v_total > PRODUCT_FEATURE_VARIANTS_THRESHOLD) {
  1871.                         $data[$k]['use_variant_picker'] = true;
  1872.                     }
  1873.                 }
  1874.             }
  1875.         }
  1876.  
  1877.        
  1878.         if ($params['plain'] == false) {
  1879.             // Get groups
  1880.             if (!empty($params['exclude_group'])) {
  1881.                 foreach ($data as $k => $v) {
  1882.                     if (empty($v['parent_id'])) {
  1883.                         $has_ungroupped = true;
  1884.                         break;
  1885.                     }
  1886.                 }
  1887.                 $groups = db_get_hash_array("SELECT " . implode(', ', $base_fields) . " FROM ?:product_features LEFT JOIN ?:product_features_descriptions ON ?:product_features_descriptions.feature_id = ?:product_features.feature_id AND ?:product_features_descriptions.lang_code = ?s WHERE ?:product_features.feature_type = 'G' ORDER BY ?:product_features.position, ?:product_features_descriptions.description", 'feature_id', $lang_code);
  1888.                 $data = fn_array_merge($data, $groups);
  1889.             }
  1890.  
  1891.             $delete_keys = array();
  1892.             foreach ($data as $k => $v) {
  1893.                 if (!empty($v['parent_id']) && !empty($data[$v['parent_id']])) {
  1894.                     $data[$v['parent_id']]['subfeatures'][$v['feature_id']] = $v;
  1895.                     $data[$k] = & $data[$v['parent_id']]['subfeatures'][$v['feature_id']];
  1896.                     $delete_keys[] = $k;
  1897.                 }
  1898.  
  1899.                 if (!empty($params['get_descriptions']) && empty($v['parent_id'])) {
  1900.                     $d = fn_get_categories_list($v['categories_path']);
  1901.                     $data[$k]['feature_description'] = fn_get_lang_var('display_on') . ': <span>' . implode(', ', $d) . '</span>';
  1902.                 }
  1903.             }
  1904.  
  1905.             foreach ($delete_keys as $k) {
  1906.                 unset($data[$k]);
  1907.             }
  1908.         }
  1909.     }
  1910.  
  1911.     return array($data, $params, $has_ungroupped);
  1912. }
  1913.  
  1914. function fn_get_product_feature_data($feature_id, $get_variants = false, $get_variant_images = false, $lang_code = CART_LANGUAGE)
  1915. {
  1916.     $feature_data = db_get_row("SELECT ?:product_features.feature_id, ?:product_features.feature_type, ?:product_features.parent_id, ?:product_features.display_on_product, ?:product_features.display_on_catalog, ?:product_features_descriptions.description, ?:product_features_descriptions.prefix, ?:product_features_descriptions.suffix, ?:product_features.categories_path, ?:product_features_descriptions.full_description, ?:product_features.status, ?:product_features.comparison, ?:product_features.feature_type, ?:product_features.position FROM ?:product_features LEFT JOIN ?:product_features_descriptions ON ?:product_features_descriptions.feature_id = ?:product_features.feature_id AND ?:product_features_descriptions.lang_code = ?s WHERE ?:product_features.feature_id = ?i", $lang_code, $feature_id);
  1917.  
  1918.     if ($get_variants == true) {
  1919.         list($variants, $total) = fn_get_product_feature_variants($feature_id, 0, $feature_data['feature_type'], $get_variant_images, $lang_code);
  1920.         $feature_data['variants'] = $variants;
  1921.     }
  1922.  
  1923.     return $feature_data;
  1924. }
  1925.  
  1926. function fn_get_product_features_list($product_id, $display_on = 'C', $lang_code = CART_LANGUAGE)
  1927. {
  1928.     $features_list = array();
  1929.  
  1930.     if ($display_on == 'C') {
  1931.         $condition = " AND f.display_on_catalog = 1";
  1932.     } elseif ($display_on == 'CP') {
  1933.         $condition = " AND (f.display_on_catalog = 1 OR f.display_on_product = 1)";
  1934.     } elseif ($display_on == 'A') {
  1935.         $condition = '';
  1936.     } else {
  1937.         $condition = " AND f.display_on_product = 1";
  1938.     }
  1939.  
  1940.     $_data = db_get_array("SELECT v.feature_id, v.value, v.value_int, v.variant_id, f.feature_type, fd.description, fd.prefix, fd.suffix, vd.variant, f.parent_id FROM ?:product_features_values as v LEFT JOIN ?:product_features as f ON f.feature_id = v.feature_id LEFT JOIN ?:product_features_descriptions as fd ON fd.feature_id = v.feature_id AND fd.lang_code = ?s LEFT JOIN ?:product_feature_variants fv ON fv.variant_id = v.variant_id LEFT JOIN ?:product_feature_variant_descriptions as vd ON vd.variant_id = fv.variant_id AND vd.lang_code = ?s WHERE f.status = 'A' AND IF(f.parent_id, (SELECT status FROM ?:product_features as df WHERE df.feature_id = f.parent_id), 'A') = 'A' AND v.product_id = ?i ?p AND (v.variant_id != 0 OR (f.feature_type != 'C' AND v.value != '') OR (f.feature_type = 'C') OR v.value_int != '') AND v.lang_code = ?s ORDER BY f.position, fd.description, fv.position", $lang_code, $lang_code, $product_id, $condition, $lang_code);
  1941.  
  1942.     if (!empty($_data)) {
  1943.         foreach ($_data as $k => $v) {
  1944.             if ($v['feature_type'] == 'C') {
  1945.                 if ($v['value'] != 'Y' && $display_on != 'A') {
  1946.                     unset($_data[$k]);
  1947.                     continue;
  1948.                 }
  1949.             }
  1950.  
  1951.             if (empty($features_list[$v['feature_id']])) {
  1952.                 $features_list[$v['feature_id']] = $v;
  1953.             }
  1954.  
  1955.             if (!empty($v['variant_id'])) { // feature has several variants
  1956.                 $features_list[$v['feature_id']]['variants'][$v['variant_id']] = array(
  1957.                     'value' => $v['value'],
  1958.                     'value_int' => $v['value_int'],
  1959.                     'variant_id' => $v['variant_id'],
  1960.                     'variant' => $v['variant']
  1961.                 );
  1962.             }
  1963.         }
  1964.  
  1965.         // Sort features by group
  1966.         $groups = array();
  1967.         foreach ($features_list as $f_id => $data) {
  1968.             $groups[$data['parent_id']][$f_id] = $data;
  1969.         }
  1970.  
  1971.         $features_list = !empty($groups[0]) ? $groups[0] : array();
  1972.         unset($groups[0]);
  1973.         if (!empty($groups)) {
  1974.             foreach ($groups as $g) {
  1975.                 $features_list = fn_array_merge($features_list, $g);
  1976.             }
  1977.         }
  1978.     }
  1979.  
  1980.     return $features_list;
  1981. }
  1982.  
  1983. //
  1984. // Get available product fields
  1985. //
  1986. function fn_get_avail_product_features($lang_code = CART_LANGUAGE, $simple = false, $get_hidden = true)
  1987. {
  1988.     $statuses = array('A');
  1989.  
  1990.     if ($get_hidden == false) {
  1991.         $statuses[] = 'D';
  1992.     }
  1993.  
  1994.     if ($simple == true) {
  1995.         $fields = db_get_hash_single_array("SELECT ?:product_features.feature_id, ?:product_features_descriptions.description FROM ?:product_features LEFT JOIN ?:product_features_descriptions ON ?:product_features_descriptions.feature_id = ?:product_features.feature_id AND ?:product_features_descriptions.lang_code = ?s WHERE ?:product_features.status IN (?a) AND ?:product_features.feature_type != 'G' ORDER BY ?:product_features.position", array('feature_id', 'description'), $lang_code, $statuses);
  1996.     } else {
  1997.         $fields = db_get_hash_array("SELECT ?:product_features.*, ?:product_features_descriptions.* FROM ?:product_features LEFT JOIN ?:product_features_descriptions ON ?:product_features_descriptions.feature_id = ?:product_features.feature_id AND ?:product_features_descriptions.lang_code = ?s WHERE ?:product_features.status IN (?a) AND ?:product_features.feature_type != 'G' ORDER BY ?:product_features.position", 'feature_id', $lang_code, $statuses);
  1998.     }
  1999.     return $fields;
  2000. }
  2001.  
  2002. //
  2003. // Get product feature variants
  2004. //
  2005. function fn_get_product_feature_variants($feature_id, $product_id, $feature_type, $get_images = false, $lang_code = CART_LANGUAGE, $items_per_page = 0)
  2006. {
  2007.     $fields = array(
  2008.         '?:product_feature_variant_descriptions.*',
  2009.         '?:product_feature_variants.*',
  2010.     );
  2011.  
  2012.     $condition = $group_by = $sorting = '';
  2013.  
  2014.     $join = db_quote(" LEFT JOIN ?:product_feature_variant_descriptions ON ?:product_feature_variant_descriptions.variant_id = ?:product_feature_variants.variant_id AND ?:product_feature_variant_descriptions.lang_code = ?s", $lang_code);
  2015.     $condition .= db_quote(" AND ?:product_feature_variants.feature_id = ?i", $feature_id);
  2016.     $sorting = db_quote("?:product_feature_variants.position, ?:product_feature_variant_descriptions.variant");
  2017.  
  2018.     if (!empty($product_id)) {
  2019.         $fields[] = '?:product_features_values.variant_id as selected';
  2020.         $fields[] = '?:product_features.feature_type';
  2021.  
  2022.         $join .= db_quote(" LEFT JOIN ?:product_features_values ON ?:product_features_values.variant_id = ?:product_feature_variants.variant_id AND ?:product_features_values.lang_code = ?s AND ?:product_features_values.product_id = ?i", $lang_code, $product_id);
  2023.  
  2024.         $join .= db_quote(" LEFT JOIN ?:product_features ON ?:product_features.feature_id = ?:product_feature_variants.feature_id");
  2025.         $group_by = db_quote(" GROUP BY ?:product_feature_variants.variant_id");
  2026.     }
  2027.  
  2028.     $limit = '';
  2029.     $total = 0;
  2030.     if (!empty($items_per_page)) {
  2031.         $page = !empty($_REQUEST['page']) ? $_REQUEST['page'] : 1;
  2032.         $total = db_get_field("SELECT COUNT(*) FROM ?:product_feature_variants WHERE 1 $condition");
  2033.         $limit = fn_paginate($page, $total, $items_per_page);
  2034.     }
  2035.  
  2036.     fn_set_hook('get_product_feature_variants', $fields, $join, $condition, $group_by, $sorting, $lang_code, $limit);
  2037.  
  2038.     $vars = db_get_hash_array('SELECT ' . implode(', ', $fields) . " FROM ?:product_feature_variants $join WHERE 1 $condition $group_by ORDER BY $sorting $limit", 'variant_id');
  2039.  
  2040.     if ($get_images == true && $feature_type == 'E') {
  2041.         $variant_ids = array();
  2042.         foreach ($vars as $variant) {
  2043.             $variant_ids[] = $variant['variant_id'];
  2044.         }
  2045.         $image_pairs = fn_get_image_pairs($variant_ids, 'feature_variant', 'V', true, true, $lang_code);
  2046.         foreach ($vars as &$variant) {
  2047.             $variant['image_pair'] = array_pop($image_pairs[$variant['variant_id']]);
  2048.         }
  2049.     }
  2050.     return array($vars, $total);
  2051. }
  2052.  
  2053. //
  2054. // Get product feature variant
  2055. //
  2056. function fn_get_product_feature_variant($variant_id, $lang_code = CART_LANGUAGE)
  2057. {
  2058.     $var = db_get_row("SELECT * FROM ?:product_feature_variants LEFT JOIN ?:product_feature_variant_descriptions ON ?:product_feature_variant_descriptions.variant_id = ?:product_feature_variants.variant_id AND ?:product_feature_variant_descriptions.lang_code = ?s WHERE ?:product_feature_variants.variant_id = ?i ORDER BY ?:product_feature_variants.position, ?:product_feature_variant_descriptions.variant", $lang_code, $variant_id);
  2059.     if (empty($var)) {
  2060.         return false;
  2061.     }
  2062.     $var['image_pair'] = fn_get_image_pairs($variant_id, 'feature_variant', 'V', true, true, $lang_code);
  2063.  
  2064.     if (empty($var['meta_description']) && defined('AUTO_META_DESCRIPTION') && AREA != 'A') {
  2065.         $var['meta_description'] = fn_generate_meta_description($var['description']);
  2066.     }
  2067.  
  2068.     return $var;
  2069. }
  2070.  
  2071. function fn_delete_feature($feature_id)
  2072. {
  2073.     $feature_type = db_get_field("SELECT feature_type FROM ?:product_features WHERE feature_id = ?i", $feature_id);
  2074.  
  2075.     fn_set_hook('delete_product_feature', $feature_id, $feature_type);
  2076.  
  2077.     if ($feature_type == 'G') {
  2078.         $fids = db_get_fields("SELECT feature_id FROM ?:product_features WHERE parent_id = ?i", $feature_id);
  2079.         if (!empty($fids)) {
  2080.             foreach ($fids as $fid) {
  2081.                 fn_delete_feature($fid);
  2082.             }
  2083.         }
  2084.     }
  2085.  
  2086.     db_query("DELETE FROM ?:product_features WHERE feature_id = ?i", $feature_id);
  2087.     db_query("DELETE FROM ?:product_features_descriptions WHERE feature_id = ?i", $feature_id);
  2088.     db_query("DELETE FROM ?:product_features_values WHERE feature_id = ?i", $feature_id);
  2089.  
  2090.     $v_ids = db_get_fields("SELECT variant_id FROM ?:product_feature_variants WHERE feature_id = ?i", $feature_id);
  2091.     //Delete variant images
  2092.     foreach ($v_ids as $v_id) {
  2093.         fn_delete_image_pairs($v_id, 'feature_variant');
  2094.     }
  2095.  
  2096.     db_query("DELETE FROM ?:product_feature_variants WHERE feature_id = ?i", $feature_id);
  2097.     db_query("DELETE FROM ?:product_feature_variant_descriptions WHERE variant_id IN (?n)", $v_ids);
  2098.     $filter_ids = db_get_fields("SELECT filter_id FROM ?:product_filters WHERE feature_id = ?i", $feature_id);
  2099.     foreach ($filter_ids as $_filter_id) {
  2100.         fn_delete_product_filter($_filter_id);
  2101.     }
  2102. }
  2103.  
  2104.  
  2105. function fn_get_simple_product_filters($lang_code = CART_LANGUAGE)
  2106. {
  2107.     return db_get_hash_single_array("SELECT ?:product_filters.filter_id, ?:product_filter_descriptions.filter FROM ?:product_filters LEFT JOIN ?:product_filter_descriptions ON ?:product_filter_descriptions.filter_id = ?:product_filters.filter_id AND ?:product_filter_descriptions.lang_code = ?s", array('filter_id', 'filter'), $lang_code);
  2108. }
  2109.  
  2110.  
  2111. function fn_get_product_filters($params = array(), $items_per_page = 0, $lang_code = DESCR_SL)
  2112. {
  2113.     // Init filter
  2114.     $params = fn_init_view('product_filters', $params);
  2115.  
  2116.     // Set default values to input params
  2117.     $params['page'] = empty($params['page']) ? 1 : $params['page']; // default page is 1
  2118.  
  2119.     $condition = $group = '';
  2120.  
  2121.     if (!empty($params['filter_id'])) {
  2122.         $condition .= db_quote(" AND ?:product_filters.filter_id = ?i", $params['filter_id']);
  2123.     }
  2124.  
  2125.     if (isset($params['filter_name']) && fn_string_no_empty($params['filter_name'])) {
  2126.         $condition .= db_quote(" AND ?:product_filter_descriptions.filter LIKE ?l", "%".trim($params['filter_name'])."%");
  2127.     }
  2128.  
  2129.     if (!empty($params['show_on_home_page'])) {
  2130.         $condition .= db_quote(" AND ?:product_filters.show_on_home_page = ?s", $params['show_on_home_page']);
  2131.     }
  2132.  
  2133.     if (!empty($params['status'])) {
  2134.         $condition .= db_quote(" AND ?:product_filters.status = ?s", $params['status']);
  2135.     }
  2136.  
  2137.     if (isset($params['feature_name']) && fn_string_no_empty($params['feature_name'])) {
  2138.         $condition .= db_quote(" AND ?:product_features_descriptions.description LIKE ?l", "%".trim($params['feature_name'])."%");
  2139.     }
  2140.  
  2141.     if (!empty($params['category_ids'])) {
  2142.         $c_ids = is_array($params['category_ids']) ? $params['category_ids'] : fn_explode(',', $params['category_ids']);
  2143.         $find_set = array(
  2144.             " ?:product_filters.categories_path = '' "
  2145.         );
  2146.         foreach ($c_ids as $k => $v) {
  2147.             $find_set[] = db_quote(" FIND_IN_SET(?i, ?:product_filters.categories_path) ", $v);
  2148.         }
  2149.         $find_in_set = db_quote(" AND (?p)", implode('OR', $find_set));
  2150.         $condition .= $find_in_set;
  2151.     }
  2152.  
  2153.     $limit = '';
  2154.     if (!empty($items_per_page)) {
  2155.         $total = db_get_field("SELECT COUNT(*) FROM ?:product_filters LEFT JOIN ?:product_filter_descriptions ON ?:product_filter_descriptions.lang_code = ?s AND ?:product_filter_descriptions.filter_id = ?:product_filters.filter_id LEFT JOIN ?:product_features_descriptions ON ?:product_features_descriptions.feature_id = ?:product_filters.feature_id AND ?:product_features_descriptions.lang_code = ?s LEFT JOIN ?:product_features ON ?:product_features.feature_id = ?:product_filters.feature_id WHERE 1 ?p", $lang_code, $lang_code, $condition);
  2156.  
  2157.         $limit = fn_paginate($params['page'], $total, $items_per_page);
  2158.     }
  2159.  
  2160.     $filters = db_get_hash_array("SELECT ?:product_filters.*, ?:product_filter_descriptions.filter, ?:product_features.feature_type, ?:product_features.parent_id, ?:product_features_descriptions.description as feature, ?:product_features_descriptions.prefix, ?:product_features_descriptions.suffix FROM ?:product_filters LEFT JOIN ?:product_filter_descriptions ON ?:product_filter_descriptions.lang_code = ?s AND ?:product_filter_descriptions.filter_id = ?:product_filters.filter_id LEFT JOIN ?:product_features_descriptions ON ?:product_features_descriptions.feature_id = ?:product_filters.feature_id AND ?:product_features_descriptions.lang_code = ?s LEFT JOIN ?:product_features ON ?:product_features.feature_id = ?:product_filters.feature_id WHERE 1 ?p GROUP BY ?:product_filters.filter_id ORDER BY ?:product_filters.position, ?:product_filter_descriptions.filter $limit", 'filter_id', $lang_code, $lang_code, $condition);
  2161.  
  2162.     if (!empty($filters)) {
  2163.         $fields = fn_get_product_filter_fields();
  2164.  
  2165.         // Get feature group if exist
  2166.         $parent_ids = array();
  2167.         foreach ($filters as $k => $v) {
  2168.             if (!empty($v['parent_id'])) {
  2169.                 $parent_ids[$v['parent_id']] = true;
  2170.             }
  2171.         }
  2172.         $groups = db_get_hash_array("SELECT feature_id, description FROM ?:product_features_descriptions WHERE feature_id IN (?n) AND lang_code = ?s", 'feature_id', array_keys($parent_ids), $lang_code);
  2173.  
  2174.         foreach ($filters as $k => $filter) {
  2175.             // skip supplier filter if suppliers are disabled
  2176.             if ($filter['field_type'] == 'S') {
  2177.                 // remove supplier filter from admin:products.manage because there is special supplier selectbox
  2178.                 if ('products' == CONTROLLER && 'manage' == MODE) {
  2179.                     unset($filters[$k]);
  2180.                     continue;
  2181.                 }
  2182.                 // php notices were displayed
  2183.                 if (empty($fields[$filter['field_type']])) {
  2184.                     continue;
  2185.                 }
  2186.             }
  2187.  
  2188.             if (!empty($filter['parent_id']) && !empty($groups[$filter['parent_id']])) {
  2189.                 $filters[$k]['feature_group'] = $groups[$filter['parent_id']]['description'];
  2190.             }
  2191.  
  2192.             if (!empty($filter['field_type'])) {
  2193.                 $filters[$k]['feature'] = fn_get_lang_var($fields[$filter['field_type']]['description']);
  2194.             }
  2195.             if (empty($filter['feature_id'])) {
  2196.                 $filters[$k]['condition_type'] = $fields[$filter['field_type']]['condition_type'];
  2197.             }
  2198.  
  2199.             if (!empty($params['get_descriptions'])) {
  2200.                 $d = array();
  2201.                 $filters[$k]['filter_description'] = fn_get_lang_var('filter_by') . ': <span>' . $filters[$k]['feature'] . (!empty($filters[$k]['feature_group']) ? ' (' . $filters[$k]['feature_group'] . ' )' : '') . '</span>';
  2202.  
  2203.                 if ($filter['show_on_home_page'] == 'Y') {
  2204.                     $d[] = fn_get_lang_var('home_page');
  2205.                 }
  2206.  
  2207.                 $d = fn_array_merge($d, fn_get_categories_list($filter['categories_path'], $lang_code), false);
  2208.                 $filters[$k]['filter_description'] .= ' | ' . fn_get_lang_var('display_on') . ': <span>' . implode(', ', $d) . '</span>';
  2209.             }
  2210.  
  2211.             if (!empty($params['get_variants'])) {
  2212.                 $filters[$k]['ranges'] = db_get_hash_array("SELECT ?:product_filter_ranges.*, ?:product_filter_ranges_descriptions.range_name FROM ?:product_filter_ranges LEFT JOIN ?:product_filter_ranges_descriptions ON ?:product_filter_ranges_descriptions.range_id = ?:product_filter_ranges.range_id AND ?:product_filter_ranges_descriptions.lang_code = ?s WHERE filter_id = ?i ORDER BY position", 'range_id', $lang_code, $filter['filter_id']);
  2213.                 if (empty($filters[$k]['ranges']) && !empty($filter['feature_id']) && $filter['feature_type'] != 'N') {
  2214.                     list($variants, $total) = fn_get_product_feature_variants($filter['feature_id'], 0, $filter['feature_type']);
  2215.                     $filters[$k]['ranges'] = $variants;
  2216.                 }
  2217.             }
  2218.         }
  2219.     }
  2220.  
  2221.     fn_set_hook('get_product_filters', $filters, $params);
  2222.  
  2223.     return array($filters, $params);
  2224. }
  2225.  
  2226.  
  2227. function fn_get_filters_products_count($params = array())
  2228. {
  2229.     $key = 'pfilters_' . md5(serialize($params));
  2230.     Registry::register_cache($key, array('products', 'product_features', 'product_filters', 'product_features_values', 'categories'), CACHE_LEVEL_USER);
  2231.  
  2232.     if (Registry::is_exist($key) == false) {
  2233.         if (!empty($params['check_location'])) { // FIXME: this is bad style, should be refactored
  2234.             $valid_locations = array(
  2235.                 'index.index',
  2236.                 'products.search',
  2237.                 'categories.view',
  2238.                 'product_features.view',
  2239.                 'products.view',
  2240.                 'subcats.view',
  2241.                 'subcategories'
  2242.                
  2243.             ); // I agree
  2244.  
  2245.             if (!in_array($params['dispatch'], $valid_locations)) {
  2246.                 return array();
  2247.             }
  2248.  
  2249.             if ($params['dispatch'] == 'categories.view') {
  2250.                 $params['simple_link'] = true; // this parameter means that extended filters on this page should be displayed as simple
  2251.                 $params['filter_custom_advanced'] = true; // this parameter means that extended filtering should be stayed on the same page
  2252.             } else {
  2253.                 if ($params['dispatch'] == 'product_features.view') {
  2254.                     $params['simple_link'] = true;
  2255.                     $params['features_hash'] = (!empty($params['features_hash']) ? ($params['features_hash'] . '.') : '') . 'V' . $params['variant_id'];
  2256.                     //$params['exclude_feature_id'] = db_get_field("SELECT feature_id FROM ?:product_features_values WHERE variant_id = ?i", $params['variant_id']);
  2257.                 }
  2258.  
  2259.                 $params['get_for_home'] = 'Y';
  2260.             }
  2261.         }
  2262.  
  2263.         if (!empty($params['skip_if_advanced']) && !empty($params['advanced_filter']) && $params['advanced_filter'] == 'Y') {
  2264.             return array();
  2265.         }
  2266.  
  2267.         // Base fields for the SELECT queries
  2268.         $values_fields = array (
  2269.             '?:product_features_values.feature_id',
  2270.             'COUNT(DISTINCT ?:products.product_id) as products',
  2271.             '?:product_features_values.variant_id as range_id',
  2272.             '?:product_feature_variant_descriptions.variant as range_name',
  2273.             '?:product_features.feature_type',
  2274.             '?:product_filters.filter_id'
  2275.         );
  2276.  
  2277.         $ranges_fields = array (
  2278.             '?:product_features_values.feature_id',
  2279.             'COUNT(DISTINCT ?:products.product_id) as products',
  2280.             '?:product_filter_ranges.range_id',
  2281.             '?:product_filter_ranges_descriptions.range_name',
  2282.             '?:product_filter_ranges.filter_id',
  2283.             '?:product_features.feature_type'
  2284.         );
  2285.  
  2286.         $condition = $where = $join = $filter_vq = $filter_rq = '';
  2287.  
  2288.         $variants_ids = $ranges_ids = $field_filters = $feature_ids = $field_ranges_ids = $field_ranges_counts = array();
  2289.  
  2290.         if (!empty($params['features_hash'])) {
  2291.             list($variants_ids, $ranges_ids, $_field_ranges_ids) = fn_parse_features_hash($params['features_hash']);
  2292.             $field_ranges_ids = array_flip($_field_ranges_ids);
  2293.         }
  2294.  
  2295.         if (!empty($params['category_id'])) {
  2296.             $id_path = db_get_field("SELECT id_path FROM ?:categories WHERE category_id = ?i", $params['category_id']);
  2297.             $category_ids = db_get_fields("SELECT category_id FROM ?:categories WHERE id_path LIKE ?l", $id_path . '/%');
  2298.             $category_ids[] = $params['category_id'];
  2299.            
  2300.             $condition .= db_quote(" AND (categories_path = '' OR FIND_IN_SET(?i, categories_path))", $params['category_id']);
  2301.  
  2302.             $where .= db_quote(" AND ?:products_categories.category_id IN (?n)", $category_ids);
  2303.         } elseif (empty($params['get_for_home']) && empty($params['get_custom'])) {
  2304.             $condition .= " AND categories_path = ''";
  2305.         }
  2306.  
  2307.         if (!empty($params['filter_id'])) {
  2308.             $condition .= db_quote(" AND ?:product_filters.filter_id = ?i", $params['filter_id']);
  2309.         }
  2310.  
  2311.         if (!empty($params['get_for_home'])) {
  2312.             $condition .= db_quote(" AND ?:product_filters.show_on_home_page = ?s", $params['get_for_home']);
  2313.         }
  2314.  
  2315.         if (!empty($params['exclude_feature_id'])) {
  2316.             $condition .= db_quote(" AND ?:product_filters.feature_id NOT IN (?n)", $params['exclude_feature_id']);
  2317.         }
  2318.  
  2319.         $filters = db_get_hash_array("SELECT ?:product_filters.feature_id, ?:product_filters.filter_id, ?:product_filters.field_type, ?:product_filter_descriptions.filter, ?:product_features_descriptions.prefix, ?:product_features_descriptions.suffix FROM ?:product_filters LEFT JOIN ?:product_filter_descriptions ON ?:product_filter_descriptions.filter_id = ?:product_filters.filter_id AND ?:product_filter_descriptions.lang_code = ?s LEFT JOIN ?:product_features_descriptions ON ?:product_features_descriptions.feature_id = ?:product_filters.feature_id AND ?:product_features_descriptions.lang_code = ?s WHERE ?:product_filters.status = 'A' ?p ORDER by position, filter", 'filter_id', CART_LANGUAGE, CART_LANGUAGE, $condition);
  2320.  
  2321.         $fields = fn_get_product_filter_fields();
  2322.  
  2323.         if (empty($filters) && empty($params['advanced_filter'])) {
  2324.             return array(array(), false);
  2325.         } else {
  2326.             foreach ($filters as $k => $v) {
  2327.                 if (!empty($v['feature_id'])) {
  2328.                     // Feature filters
  2329.                     $feature_ids[] = $v['feature_id'];
  2330.                 } else {
  2331.                     // Product field filters
  2332.                     if (!empty($fields[$v['field_type']])) {
  2333.                         $_field = $fields[$v['field_type']];
  2334.                         $field_filters[$v['filter_id']] = array_merge($v, $_field);
  2335.                         $filters[$k]['condition_type'] = $_field['condition_type'];
  2336.                     }
  2337.                 }
  2338.             }
  2339.         }
  2340.         // Variants
  2341.         if (!empty($variants_ids)) {
  2342.             $join .= " LEFT JOIN (SELECT product_id, GROUP_CONCAT(?:product_features_values.variant_id) AS simple_variants FROM ?:product_features_values WHERE lang_code = '" . CART_LANGUAGE . "' GROUP BY product_id) AS pfv_simple ON pfv_simple.product_id = ?:products.product_id";
  2343.  
  2344.             $where_condtions = array();
  2345.             foreach ($variants_ids as $k => $variant_id) {
  2346.                 $where_condtions[] = db_quote(" FIND_IN_SET('?i', simple_variants)", $variant_id);
  2347.             }
  2348.             $where .= ' AND ' . implode(' AND ', $where_condtions);
  2349.         }
  2350.         // Ranges
  2351.         if (!empty($ranges_ids)) {
  2352.             $range_conditions = db_get_array("SELECT `from`, `to`, feature_id FROM ?:product_filter_ranges WHERE range_id IN (?n)", $ranges_ids);
  2353.             foreach ($range_conditions as $k => $condition) {
  2354.                 $join .= db_quote(" LEFT JOIN ?:product_features_values as var_val_$k ON var_val_$k.product_id = ?:products.product_id AND var_val_$k.lang_code = ?s", CART_LANGUAGE);
  2355.                 $where .= db_quote(" AND (var_val_$k.value_int >= ?i AND var_val_$k.value_int <= ?i AND var_val_$k.value = '' AND var_val_$k.feature_id = ?i)", $condition['from'], $condition['to'], $condition['feature_id']);
  2356.             }
  2357.         }
  2358.  
  2359.         if (!empty($params['filter_id']) && empty($params['view_all'])) {
  2360.             $filter_vq .= db_quote(" AND ?:product_filters.filter_id = ?i", $params['filter_id']);
  2361.             $filter_rq .= db_quote(" AND ?:product_filter_ranges.filter_id = ?i", $params['filter_id']);
  2362.         }
  2363.  
  2364.         if (!empty($params['view_all'])) {
  2365.             $values_fields[] = "UPPER(SUBSTRING(?:product_feature_variant_descriptions.variant, 1, 1)) AS `index`";
  2366.         }
  2367.  
  2368.         $_join = $join;
  2369.  
  2370.         // Build condition for the standart fields
  2371.         if (!empty($_field_ranges_ids)) {
  2372.             foreach ($_field_ranges_ids as $rid => $field_type) {
  2373.                 $structure = $fields[$field_type];
  2374.  
  2375.                 if (empty($fields[$field_type])) {
  2376.                     continue;
  2377.                 }
  2378.  
  2379.                 if ($structure['table'] !== 'products' && strpos($join, 'JOIN ?:' . $structure['table']) === false) {
  2380.                     $join .= " LEFT JOIN ?:$structure[table] ON ?:$structure[table].product_id = ?:products.product_id";
  2381.                 }
  2382.  
  2383.                 if ($structure['condition_type'] == 'D') {
  2384.                     $range_condition = db_get_row("SELECT `from`, `to` FROM ?:product_filter_ranges WHERE range_id = ?i", $rid);
  2385.                     if (!empty($range_condition)) {
  2386.                         $where .= db_quote(" AND ?:$structure[table].$structure[db_field] >= ?i AND ?:$structure[table].$structure[db_field] <= ?i", $range_condition['from'], $range_condition['to']);
  2387.                     }
  2388.                 } elseif ($structure['condition_type'] == 'F') {
  2389.                     $where .= db_quote(" AND ?:$structure[table].$structure[db_field] = ?i", $rid);
  2390.                 } elseif ($structure['condition_type'] == 'C') {
  2391.                     $where .= db_quote(" AND ?:$structure[table].$structure[db_field] = ?s", ($rid == 1) ? 'Y' : 'N');
  2392.                 }
  2393.                 if (!empty($structure['join_params'])) {
  2394.                     foreach ($structure['join_params'] as $field => $param) {
  2395.                         $join .= db_quote(" AND ?:$structure[table].$field = ?s ", $param);
  2396.                     }
  2397.                 }
  2398.             }
  2399.         }
  2400.  
  2401.         // Product availability conditions
  2402.         $where .= ' AND (' . fn_find_array_in_set($_SESSION['auth']['usergroup_ids'], '?:categories.usergroup_ids', true) . ')';
  2403.         $where .= ' AND (' . fn_find_array_in_set($_SESSION['auth']['usergroup_ids'], '?:products.usergroup_ids', true) . ')';
  2404.         $where .= db_quote(" AND ?:categories.status IN (?a) AND ?:products.status IN (?a)", array('A', 'H'), array('A'));
  2405.  
  2406.         $_j = " INNER JOIN ?:products_categories ON ?:products_categories.product_id = ?:products.product_id LEFT JOIN ?:categories ON ?:categories.category_id = ?:products_categories.category_id";
  2407.  
  2408.         if (AREA == 'C') {
  2409.             if (fn_check_suppliers_functionality()) {
  2410.                 // if MVE or suppliers enabled
  2411.                 $where .= " AND (companies.status = 'A' OR ?:products.company_id = 0) ";
  2412.                 $_j .= " LEFT JOIN ?:companies as companies ON companies.company_id = ?:products.company_id";
  2413.             } else {
  2414.                 // if suppliers disabled
  2415.                 $where .= " AND ?:products.company_id = 0 ";
  2416.             }
  2417.         }
  2418.  
  2419.         if (Registry::get('settings.General.inventory_tracking') == 'Y' && Registry::get('settings.General.show_out_of_stock_products') == 'N' && AREA == 'C') {
  2420.             $_j .= " LEFT JOIN ?:product_options_inventory as inventory ON inventory.product_id = ?:products.product_id";
  2421.            
  2422.             $where .= " AND IF(?:products.tracking = 'O', inventory.amount > 0, ?:products.amount > 0)";
  2423.         }
  2424.  
  2425.         $_join .= $_j;
  2426.         $join .= $_j;
  2427.  
  2428.         // Localization
  2429.         $where .= fn_get_localizations_condition('?:products.localization', true);
  2430.         $where .= fn_get_localizations_condition('?:categories.localization', true);
  2431.  
  2432.         $variants_counts = db_get_hash_multi_array("SELECT " . implode(', ', $values_fields) . " FROM ?:product_features_values LEFT JOIN ?:products ON ?:products.product_id = ?:product_features_values.product_id LEFT JOIN ?:product_filters ON ?:product_filters.feature_id = ?:product_features_values.feature_id LEFT JOIN ?:product_feature_variants ON ?:product_feature_variants.variant_id = ?:product_features_values.variant_id LEFT JOIN ?:product_feature_variant_descriptions ON ?:product_feature_variant_descriptions.variant_id = ?:product_feature_variants.variant_id AND ?:product_feature_variant_descriptions.lang_code = ?s LEFT JOIN ?:product_features ON ?:product_features.feature_id = ?:product_filters.feature_id ?p WHERE ?:product_features_values.feature_id IN (?n) AND ?:product_features_values.lang_code = ?s AND ?:product_features_values.variant_id ?p ?p AND ?:product_features.feature_type IN ('S', 'M', 'E') GROUP BY ?:product_features_values.variant_id ORDER BY ?:product_feature_variants.position, ?:product_feature_variant_descriptions.variant", array('filter_id', 'range_id'), CART_LANGUAGE, $join, $feature_ids, CART_LANGUAGE, $where, $filter_vq);
  2433.  
  2434.         $ranges_counts = db_get_hash_multi_array("SELECT " . implode(', ', $ranges_fields) . " FROM ?:product_filter_ranges LEFT JOIN ?:product_features_values ON ?:product_features_values.feature_id = ?:product_filter_ranges.feature_id AND ?:product_features_values.value_int >= ?:product_filter_ranges.from AND ?:product_features_values.value_int <= ?:product_filter_ranges.to LEFT JOIN ?:products ON ?:products.product_id = ?:product_features_values.product_id LEFT JOIN ?:product_filter_ranges_descriptions ON ?:product_filter_ranges_descriptions.range_id = ?:product_filter_ranges.range_id AND ?:product_filter_ranges_descriptions.lang_code = ?s LEFT JOIN ?:product_features ON ?:product_features.feature_id = ?:product_filter_ranges.feature_id ?p WHERE ?:product_features_values.feature_id IN (?n) AND ?:product_features_values.lang_code = ?s ?p ?p GROUP BY ?:product_filter_ranges.range_id ORDER BY ?:product_filter_ranges.position, ?:product_filter_ranges_descriptions.range_name", array('filter_id', 'range_id'), CART_LANGUAGE, $join, $feature_ids, CART_LANGUAGE, $where, $filter_rq);
  2435.  
  2436.         if (!empty($field_filters)) {
  2437.             // Field ranges
  2438.  
  2439.             foreach ($field_filters as $filter_id => $field) {
  2440.  
  2441.                 $fields_join = $fields_where = '';
  2442.  
  2443.                 // Dinamic ranges (price, amount etc)
  2444.                 if ($field['condition_type'] == 'D') {
  2445.  
  2446.                     $fields_join = " LEFT JOIN ?:$field[table] ON ?:$field[table].$field[db_field] >= ?:product_filter_ranges.from AND ?:$field[table].$field[db_field] <= ?:product_filter_ranges.to ";
  2447.  
  2448.                     if (strpos($fields_join . $_join, 'JOIN ?:products ') === false) {
  2449.                         $fields_join .= db_quote(" LEFT JOIN ?:products ON ?:products.product_id = ?:product_prices.product_id AND ?:product_prices.lower_limit = 1 AND ?:product_prices.usergroup_id IN (?n)", array_merge(array(USERGROUP_ALL), $_SESSION['auth']['usergroup_ids']));
  2450.                     } elseif (strpos($fields_join . $_join, 'JOIN ?:product_prices ') === false) {
  2451.                         $fields_join .= " LEFT JOIN ?:product_prices ON ?:product_prices.product_id = ?:products.product_id";
  2452.                     }
  2453.  
  2454.                     if ($field['table'] == 'product_prices'){
  2455.                         $fields_join .= db_quote(" LEFT JOIN ?:product_prices as prices_2 ON ?:product_prices.product_id = prices_2.product_id AND ?:product_prices.price > prices_2.price AND prices_2.lower_limit = 1 AND prices_2.usergroup_id IN (?n)", array_merge(array(USERGROUP_ALL), $_SESSION['auth']['usergroup_ids']));
  2456.                         $fields_where .= " AND prices_2.price IS NULL";
  2457.                     }
  2458.  
  2459.                     $field_ranges_counts[$filter_id] = db_get_hash_array("SELECT COUNT(DISTINCT ?:$field[table].product_id) as products, ?:product_filter_ranges.range_id, ?:product_filter_ranges_descriptions.range_name, ?:product_filter_ranges.filter_id FROM ?:product_filter_ranges LEFT JOIN ?:product_filter_ranges_descriptions ON ?:product_filter_ranges_descriptions.range_id = ?:product_filter_ranges.range_id AND ?:product_filter_ranges_descriptions.lang_code = ?s ?p WHERE ?:products.status IN ('A') AND ?:product_filter_ranges.filter_id = ?i ?p GROUP BY ?:product_filter_ranges.range_id HAVING products != 0 ORDER BY ?:product_filter_ranges.position, ?:product_filter_ranges_descriptions.range_name", 'range_id', CART_LANGUAGE, $fields_join . $_join, $filter_id, $where . $fields_where);
  2460.  
  2461.                 // Char values (free shipping etc)
  2462.                 } elseif ($field['condition_type'] == 'C') {
  2463.                     $field_ranges_counts[$filter_id] = db_get_hash_array("SELECT COUNT(DISTINCT ?:$field[table].product_id) as products, ?:$field[table].$field[db_field] as range_name FROM ?:$field[table] ?p WHERE ?:products.status = 'A' ?p GROUP BY ?:$field[table].$field[db_field]", 'range_name', $join, $where);
  2464.                     if (!empty($field_ranges_counts[$filter_id])) {
  2465.                         foreach ($field_ranges_counts[$filter_id] as $range_key => $range) {
  2466.                             $field_ranges_counts[$filter_id][$range_key]['range_name'] = $field['variant_descriptions'][$range['range_name']];
  2467.                             $field_ranges_counts[$filter_id][$range_key]['range_id'] = ($range['range_name'] == 'Y') ? 1 : 0;
  2468.                         }
  2469.                     }
  2470.                 // Fixed values (supplier etc)
  2471.                 } elseif ($field['condition_type'] == 'F') {
  2472.                     $field_ranges_counts[$filter_id] = db_get_hash_array("SELECT COUNT(DISTINCT ?:$field[table].product_id) as products, ?:$field[foreign_table].$field[range_name] as range_name, ?:$field[foreign_table].$field[foreign_index] as range_id FROM ?:$field[table] LEFT JOIN ?:$field[foreign_table] ON ?:$field[foreign_table].$field[foreign_index] = ?:$field[table].$field[db_field] ?p WHERE ?:products.status IN ('A') ?p GROUP BY ?:$field[table].$field[db_field]", 'range_id', $join, $where);
  2473.                 }
  2474.             }
  2475.         }
  2476.  
  2477.         $merged = fn_array_merge($variants_counts, $ranges_counts, $field_ranges_counts);
  2478.  
  2479.         $view_all = array();
  2480.  
  2481.         foreach ($filters as $filter_id => $filter) {
  2482.  
  2483.             if (!empty($merged[$filter_id]) && empty($params['view_all']) || (!empty($params['filter_id']) && $params['filter_id'] != $filter_id)) {
  2484.  
  2485.                 // Check if filter range was selected
  2486.                 if (empty($filters[$filter_id]['feature_id'])) {
  2487.                     $intersect = array_intersect(array_keys($merged[$filter_id]), $field_ranges_ids);
  2488.                 } else {
  2489.                     $intersect = array_intersect(array_keys($merged[$filter_id]), $variants_ids);
  2490.                 }
  2491.                 if (!empty($intersect)) {
  2492.                     foreach ($merged[$filter_id] as $k => $v) {
  2493.                         if (!in_array($v['range_id'], $intersect)) {
  2494.                             // Unset unselected ranges
  2495.                             unset($merged[$filter_id][$k]);
  2496.                         }
  2497.                     }
  2498.                 }
  2499.  
  2500.                 // Calculate number of ranges and compare with constant
  2501.                 $count = count($merged[$filter_id]);
  2502.                 if ($count > FILTERS_RANGES_MORE_COUNT && empty($params['get_all'])) {
  2503.                     $merged[$filter_id] = array_slice($merged[$filter_id], 0, FILTERS_RANGES_MORE_COUNT, true);
  2504.                     $filters[$filter_id]['more_cut'] = true;
  2505.                 }
  2506.                 $filters[$filter_id]['ranges'] = & $merged[$filter_id];
  2507.  
  2508.                 // Add feature type to the filter
  2509.                 $_first = reset($merged[$filter_id]);
  2510.                 if (!empty($_first['feature_type'])) {
  2511.                     $filters[$filter_id]['feature_type'] = $_first['feature_type'];
  2512.                 }
  2513.  
  2514.                 if (!empty($params['simple_link']) && $filters[$filter_id]['feature_type'] == 'E') {
  2515.                     $filters[$filter_id]['simple_link'] = true;
  2516.                 }
  2517.  
  2518.                 if (empty($params['skip_other_variants'])) {
  2519.                     foreach ($filters[$filter_id]['ranges'] as $_k => $r) {
  2520.                         if (!fn_check_selected_filter($r['range_id'], !empty($r['feature_type']) ? $r['feature_type'] : '', $params, $filters[$filter_id]['field_type'])) { // selected variant
  2521.                
  2522.                             $filters[$filter_id]['ranges'] = array( // remove all obsolete ranges
  2523.                                 $_k => $r
  2524.                             );
  2525.                             $filters[$filter_id]['ranges'][$_k]['selected'] = true; // mark selected variant
  2526.  
  2527.                             // Get other variants
  2528.                             $_params = $params;
  2529.                             $_params['filter_id'] = $filter_id;
  2530.                             $_params['req_range_id'] = $r['range_id'];
  2531.                             $_params['features_hash'] =  fn_delete_range_from_url($params['features_hash'], $r, $filters[$filter_id]['field_type']);
  2532.                             $_params['skip_other_variants'] = true;
  2533.                             unset($_params['variant_id'], $_params['check_location']);
  2534.  
  2535.                             list($_f) = fn_get_filters_products_count($_params);
  2536.                             if (!empty($_f)) {
  2537.                                 $_f = reset($_f);
  2538.                                 // delete current range
  2539.                                 foreach ($_f['ranges'] as $_rid => $_rv) {
  2540.                                     if ($_rv['range_id'] == $r['range_id']) {
  2541.                                         unset($_f['ranges'][$_rid]);
  2542.                                         break;
  2543.                                     }
  2544.                                 }
  2545.                                 $filters[$filter_id]['other_variants'] = $_f['ranges'];
  2546.                             }
  2547.                             break;
  2548.                         }
  2549.                     }
  2550.                 } else {
  2551.                     if (!empty($params['variant_id']) && !empty($filters[$filter_id]['ranges'][$params['variant_id']])) {
  2552.                         $filters[$filter_id]['ranges'][$params['variant_id']]['selected'] = true; // mark selected variant
  2553.                     }
  2554.                 }
  2555.  
  2556.                 continue;
  2557.                 // If its "view all" page, return all ranges
  2558.             } elseif (!empty($params['filter_id']) && $params['filter_id'] == $filter_id && !empty($merged[$filter_id])) {
  2559.                 foreach ($merged[$filter_id] as $range) {
  2560.                     if (!empty($range['index'])) { // feature
  2561.                         $view_all[$range['index']][] = $range;
  2562.                     } else { // custom range
  2563.                         $view_all[$filters[$range['filter_id']]['filter']][] = $range;
  2564.                     }
  2565.                 }
  2566.                 ksort($view_all);
  2567.             }
  2568.             // Unset filter if it's empty
  2569.             unset($filters[$filter_id]);
  2570.         }
  2571.  
  2572.         if (!empty($params['advanced_filter'])) {
  2573.             $_params = array(
  2574.                 'feature_types' => array('C', 'T'),
  2575.                 'plain' => true,
  2576.                 'category_ids' => array(empty($params['category_id']) ? 0 : $params['category_id'])
  2577.             );
  2578.             list($features) = fn_get_product_features($_params);
  2579.  
  2580.             if (!empty($features)) {
  2581.                 $filters = array_merge($filters, $features);
  2582.             }
  2583.         }
  2584.  
  2585.         Registry::set($key, array($filters, $view_all));
  2586.     } else {
  2587.         list($filters, $view_all) = Registry::get($key);
  2588.     }
  2589.  
  2590.     return array($filters, $view_all);
  2591. }
  2592.  
  2593. /**
  2594.  * Function check - selected filter or unselected
  2595.  *
  2596.  * @param int $element_id element from filter
  2597.  * @param string $feature_type feature type
  2598.  * @param array $request_params request array
  2599.  * @param string $field_type type of product field (A - amount, P - price, etc)
  2600.  * @return bool true if filter selected or false otherwise
  2601.  */
  2602.  
  2603. function fn_check_selected_filter($element_id, $feature_type = '', $request_params = array(), $field_type = '')
  2604. {
  2605.     $prefix = empty($field_type) ? (in_array($feature_type, array('N', 'O', 'D')) ? 'R' : 'V') : $field_type;
  2606.  
  2607.     if (empty($request_params['features_hash']) && empty($request_params['req_range_id'])) {
  2608.         return true;
  2609.     }
  2610.  
  2611.     if (!empty($request_params['req_range_id']) && $request_params['req_range_id'] == $element_id) {
  2612.         return false;
  2613.     } else {
  2614.         $_tmp = explode('.', $request_params['features_hash']);
  2615.         if (in_array($prefix . $element_id, $_tmp)) {
  2616.             return false;
  2617.         }
  2618.     }
  2619.  
  2620.     return true;
  2621. }
  2622.  
  2623. /**
  2624.  * Delete range from url (example - delete "R2" from "R2.V2.V11" - result "R2.V11")
  2625.  *
  2626.  * @param string $url url from wich will delete
  2627.  * @param array $range deleted element
  2628.  * @param string $field_type type of product field (A - amount, P - price, etc)
  2629.  * @return string
  2630.  */
  2631.  
  2632. function fn_delete_range_from_url($url, $range, $field_type = '')
  2633. {
  2634.     $prefix = empty($field_type) ? (in_array($range['feature_type'], array('N', 'O', 'D')) ? 'R' : 'V') : $field_type;
  2635.  
  2636.     $element = $prefix . $range['range_id'];
  2637.     $pattern = '/(' . $element . '[\.]?)|([\.]?' . $element . ')(?![\d]+)/';
  2638.  
  2639.     return preg_replace($pattern, '', $url);
  2640. }
  2641.  
  2642. /**
  2643.  * Function add range to hash (example - add "V2" to "R23.V11.R5" - result "R23.V11.R5.V2")
  2644.  *
  2645.  * @param string $hash hash to which will be added
  2646.  * @param array $range added element
  2647.  * @param string $prefix element prefix ("R" or "V")
  2648.  * @return string new hash
  2649.  */
  2650.  
  2651. function fn_add_range_to_url_hash($hash, $range, $field_type = '')
  2652. {
  2653.     $prefix = empty($field_type) ? (in_array($range['feature_type'], array('N', 'O', 'D')) ? 'R' : 'V') : $field_type;
  2654.     if (empty($hash)) {
  2655.         return $prefix . $range['range_id'];
  2656.     } else {
  2657.         return $hash . '.' . $prefix . $range['range_id'];
  2658.     }
  2659. }
  2660.  
  2661. function fn_add_filter_ranges_breadcrumbs($request, $url = '')
  2662. {
  2663.     if (empty($request['features_hash'])) {
  2664.         return false;
  2665.     }
  2666.  
  2667.     $parsed_ranges = fn_parse_features_hash($request['features_hash'], false);
  2668.  
  2669.     if (!empty($parsed_ranges[1])) {
  2670.         $features_hash = '';
  2671.         $last_type = array_pop($parsed_ranges[1]);
  2672.         $last_range_id = array_pop($parsed_ranges[2]);
  2673.  
  2674.         if (!empty($parsed_ranges)) {
  2675.             foreach ($parsed_ranges[1] as $k => $v) {
  2676.                 $range = fn_get_filter_range_name($v, $parsed_ranges[2][$k]);
  2677.                 $features_hash = fn_add_range_to_url_hash($features_hash, array('range_id' => $parsed_ranges[2][$k]), $v);
  2678.                 fn_add_breadcrumb(html_entity_decode($range), "$url&features_hash=" . $features_hash . (!empty($request['subcats']) ? '&subcats=Y' : ''));
  2679.             }
  2680.         }
  2681.         $range = fn_get_filter_range_name($last_type, $last_range_id);
  2682.         fn_add_breadcrumb(html_entity_decode($range));
  2683.  
  2684.     }
  2685.  
  2686.     return true;
  2687. }
  2688.  
  2689. function fn_get_filter_range_name($range_type, $range_id)
  2690. {
  2691.     static $fields;
  2692.  
  2693.     if (!isset($fields)) {
  2694.         $fields = fn_get_product_filter_fields();
  2695.     }
  2696.  
  2697.     if ($range_type == 'F') {
  2698.         $range_name = $fields['F']['variant_descriptions'][$range_id == 1 ? 'Y' : 'N'];
  2699.     } else {
  2700.         $range_name = ($range_type == 'V') ? db_get_field("SELECT variant FROM ?:product_feature_variant_descriptions WHERE variant_id = ?i AND lang_code = ?s", $range_id, CART_LANGUAGE) : db_get_field("SELECT range_name FROM ?:product_filter_ranges_descriptions WHERE range_id = ?i AND lang_code = ?s", $range_id, CART_LANGUAGE);
  2701.     }
  2702.  
  2703.     fn_set_hook('get_filter_range_name', $range_name, $range_type, $range_id);
  2704.  
  2705.     return fn_text_placeholders($range_name);
  2706. }
  2707.  
  2708. function fn_delete_product_filter($filter_id)
  2709. {
  2710.     $range_ids = db_get_fields("SELECT range_id FROM ?:product_filter_ranges WHERE filter_id = ?i", $filter_id);
  2711.  
  2712.     fn_set_hook('delete_product_filter', $filter_id, $range_ids);
  2713.  
  2714.     db_query("DELETE FROM ?:product_filters WHERE filter_id = ?i", $filter_id);
  2715.     db_query("DELETE FROM ?:product_filter_descriptions WHERE filter_id = ?i", $filter_id);
  2716.  
  2717.     foreach ($range_ids as $range_id) {
  2718.         db_query("DELETE FROM ?:product_filter_ranges_descriptions WHERE range_id = ?i", $range_id);
  2719.     }
  2720.  
  2721.     db_query("DELETE FROM ?:product_filter_ranges WHERE filter_id = ?i", $filter_id);
  2722.  
  2723.     return true;
  2724. }
  2725.  
  2726. function fn_parse_features_hash($features_hash = '', $values = true)
  2727. {
  2728.     if (empty($features_hash)) {
  2729.         return array();
  2730.     } else {
  2731.         $variants_ids = $ranges_ids = $fields_ids = array();
  2732.         preg_match_all('/([A-Z]+)([\d]+)[,]?/', $features_hash, $vals);
  2733.  
  2734.         if ($values !== true) {
  2735.             return $vals;
  2736.         }
  2737.  
  2738.         if (!empty($vals) && !empty($vals[1]) && !empty($vals[2])) {
  2739.             foreach ($vals[1] as $key => $range_type) {
  2740.                 if ($range_type == 'V') {
  2741.                     // Feature variants
  2742.                     $variants_ids[] = $vals[2][$key];
  2743.                 } elseif ($range_type == 'R') {
  2744.                     // Feature ranges
  2745.                     $ranges_ids[] = $vals[2][$key];
  2746.                 } else {
  2747.                     // Product field ranges
  2748.                     $fields_ids[$vals[2][$key]] = $vals[1][$key];
  2749.                 }
  2750.             }
  2751.         }
  2752.  
  2753.         $variants_ids = array_map('intval', $variants_ids);
  2754.         $ranges_ids = array_map('intval', $ranges_ids);
  2755.  
  2756.         return array($variants_ids, $ranges_ids, $fields_ids);
  2757.     }
  2758. }
  2759.  
  2760. /**
  2761.  * Function generate fields for the product filters
  2762.  * Returns array with following structure:
  2763.  *
  2764.  * code => array (
  2765.  *      'db_field' => db_field,
  2766.  *      'table' => db_table,
  2767.  *      'name' => lang_var_name,
  2768.  *      'condition_type' => condition_type
  2769.  * );
  2770.  *
  2771.  * condition_type - contains "C" - char (example, free_shipping == "Y")
  2772.  *                           "D" - dinamic (1.23 < price < 3.45)
  2773.  *                           "F" - fixed (supplier_id = 3)
  2774.  *
  2775.  */
  2776.  
  2777. function fn_get_product_filter_fields()
  2778. {
  2779.     $filters = array (
  2780.         // price filter
  2781.         'P' => array (
  2782.             'db_field' => 'price',
  2783.             'table' => 'product_prices',
  2784.             'description' => 'price',
  2785.             'condition_type' => 'D',
  2786.             'join_params' => array (
  2787.                 'lower_limit' => 1
  2788.             ),
  2789.             'is_range' => true,
  2790.         ),
  2791.         // amount filter
  2792.         'A' => array (
  2793.             'db_field' => 'amount',
  2794.             'table' => 'products',
  2795.             'description' => 'in_stock',
  2796.             'condition_type' => 'D',
  2797.             'is_range' => true,
  2798.         ),
  2799.         // filter by free shipping
  2800.         'F' => array (
  2801.             'db_field' => 'free_shipping',
  2802.             'table' => 'products',
  2803.             'description' => 'free_shipping',
  2804.             'condition_type' => 'C',
  2805.             'variant_descriptions' => array (
  2806.                 'Y' => fn_get_lang_var('yes'),
  2807.                 'N' => fn_get_lang_var('no')
  2808.             )
  2809.         )
  2810.     );
  2811.  
  2812.     fn_set_hook('get_product_filter_fields', $filters);
  2813.  
  2814.     return $filters;
  2815. }
  2816. //
  2817. //Gets all combinations of options stored in exceptions
  2818. //
  2819. function fn_get_product_exceptions($product_id, $short_list = false)
  2820. {
  2821.     $exceptions = db_get_array("SELECT * FROM ?:product_options_exceptions WHERE product_id = ?i ORDER BY exception_id", $product_id);
  2822.  
  2823.     foreach ($exceptions as $k => $v) {
  2824.         $exceptions[$k]['combination'] = unserialize($v['combination']);
  2825.        
  2826.         if ($short_list) {
  2827.             $exceptions[$k] = $exceptions[$k]['combination'];
  2828.         }
  2829.     }
  2830.    
  2831.     fn_set_hook('get_product_exceptions', $product_id, $exceptions, $short_list);
  2832.    
  2833.     return $exceptions;
  2834. }
  2835.  
  2836.  
  2837. //
  2838. // Returnns true if such combination already exists
  2839. //
  2840. function fn_check_combination($combinations, $product_id)
  2841. {
  2842.     $exceptions = fn_get_product_exceptions($product_id);
  2843.     if (empty($exceptions)) {
  2844.         return false;
  2845.     }
  2846.     foreach ($exceptions as $k => $v) {
  2847.         $temp = array();
  2848.         $temp = $v['combination'];
  2849.         foreach ($combinations as $key => $value) {
  2850.             if ((in_array($value, $temp)) && ($temp[$key] == $value)) {
  2851.                 unset($temp[$key]);
  2852.             }
  2853.         }
  2854.         if (empty($temp)) {
  2855.             return true;
  2856.         }
  2857.     }
  2858.  
  2859.     return false;
  2860. }
  2861.  
  2862. //
  2863. // Updates options exceptions using product_id;
  2864. //
  2865. function fn_update_exceptions($product_id)
  2866. {
  2867.     if ($product_id) {
  2868.         $exceptions = fn_get_product_exceptions($product_id);
  2869.         if (!empty($exceptions)) {
  2870.             db_query("DELETE FROM ?:product_options_exceptions WHERE product_id = ?i", $product_id);
  2871.             foreach ($exceptions as $k => $v) {
  2872.                 $_options_order = db_get_fields("SELECT a.option_id FROM ?:product_options as a LEFT JOIN ?:product_global_option_links as b ON a.option_id = b.option_id WHERE a.product_id = ?i OR b.product_id = ?i ORDER BY position", $product_id, $product_id);
  2873.  
  2874.                 if (empty($_options_order)) {
  2875.                     return false;
  2876.                 }
  2877.                 $combination  = array();
  2878.  
  2879.                 foreach ($_options_order as $option) {
  2880.                     if (!empty($v['combination'][$option])) {
  2881.                         $combination[$option] = $v['combination'][$option];
  2882.                     } else {
  2883.                         $combination[$option] = -1;
  2884.                     }
  2885.                 }
  2886.  
  2887.                 $_data = array(
  2888.                     'product_id' => $product_id,
  2889.                     'exception_id' => $v['exception_id'],
  2890.                     'combination' => serialize($combination),
  2891.                 );
  2892.                 db_query("INSERT INTO ?:product_options_exceptions ?e", $_data);
  2893.  
  2894.             }
  2895.             return true;
  2896.         }
  2897.         return false;
  2898.     }
  2899. }
  2900.  
  2901. //
  2902. // Clone exceptions
  2903. //
  2904. function fn_clone_options_exceptions(&$exceptions, $old_opt_id, $old_var_id, $new_opt_id, $new_var_id)
  2905. {
  2906.  
  2907.     foreach ($exceptions as $key => $value) {
  2908.         foreach ($value['combination'] as $option => $variant) {
  2909.             if ($option == $old_opt_id) {
  2910.                 $exceptions[$key]['combination'][$new_opt_id] = $variant;
  2911.                 unset($exceptions[$key]['combination'][$option]);
  2912.  
  2913.                 if ($variant == $old_var_id) {
  2914.                     $exceptions[$key]['combination'][$new_opt_id] = $new_var_id;
  2915.                 }
  2916.             }
  2917.             if ($variant == $old_var_id) {
  2918.                 $exceptions[$key]['combination'][$option] = $new_var_id;
  2919.             }
  2920.         }
  2921.     }
  2922. }
  2923. //
  2924. // This function clones options to product from a product or global options
  2925. //
  2926. function fn_clone_product_options($from_product_id, $to_product_id, $from_global = false)
  2927. {
  2928.     // Get all product options assigned to the product
  2929.     $id_req = (empty($from_global)) ? db_quote('product_id = ?i', $from_product_id) : db_quote('option_id = ?i', $from_global);
  2930.     $data = db_get_array("SELECT * FROM ?:product_options WHERE $id_req");
  2931.     $linked  = db_get_field("SELECT COUNT(option_id) FROM ?:product_global_option_links WHERE product_id = ?i", $from_product_id);
  2932.     if (!empty($data) || !empty($linked)) {
  2933.         // Get all exceptions for the product
  2934.         if (!empty($from_product_id)) {
  2935.             $exceptions = fn_get_product_exceptions($from_product_id);
  2936.             $inventory = db_get_field("SELECT COUNT(*) FROM ?:product_options_inventory WHERE product_id = ?i", $from_product_id);
  2937.         }
  2938.         // Fill array of options for linked global options options
  2939.         $change_options = array();
  2940.         $change_varaiants = array();
  2941.         // If global option are linked than ids will be the same
  2942.         $change_options = db_get_hash_single_array("SELECT option_id FROM ?:product_global_option_links WHERE product_id = ?i", array('option_id', 'option_id'), $from_product_id);
  2943.         if (!empty($change_options)) {
  2944.             foreach ($change_options as $value) {
  2945.                 $change_varaiants = fn_array_merge(db_get_hash_single_array("SELECT variant_id FROM ?:product_option_variants WHERE option_id = ?i", array('variant_id', 'variant_id'), $value), $change_varaiants, true);
  2946.             }
  2947.         }
  2948.         foreach ($data as $v) {
  2949.             // Clone main data
  2950.             $option_id = $v['option_id'];
  2951.             $v['product_id'] = $to_product_id;
  2952.             $v['company_id'] = defined('COMPANY_ID')? COMPANY_ID : 0;
  2953.             unset($v['option_id']);
  2954.             $new_option_id = db_query("INSERT INTO ?:product_options ?e", $v);
  2955.  
  2956.             // Clone descriptions
  2957.             $_data = db_get_array("SELECT * FROM ?:product_options_descriptions WHERE option_id = ?i", $option_id);
  2958.             foreach ($_data as $_v) {
  2959.                 $_v['option_id'] = $new_option_id;
  2960.                 db_query("INSERT INTO ?:product_options_descriptions ?e", $_v);
  2961.             }
  2962.  
  2963.             $change_options[$option_id] = $new_option_id;
  2964.             // Clone variants if exists
  2965.             if ($v['option_type'] == 'S' || $v['option_type'] == 'R' || $v['option_type'] == 'C') {
  2966.                 $_data = db_get_array("SELECT * FROM ?:product_option_variants WHERE option_id = ?i", $option_id);
  2967.                 foreach ($_data as $_v) {
  2968.                     $variant_id = $_v['variant_id'];
  2969.                     $_v['option_id'] = $new_option_id;
  2970.                     unset($_v['variant_id']);
  2971.                     $new_variant_id = db_query("INSERT INTO ?:product_option_variants ?e", $_v);
  2972.                         // Clone Exceptions
  2973.                     if (!empty($exceptions)) {
  2974.                         fn_clone_options_exceptions($exceptions, $option_id, $variant_id, $new_option_id, $new_variant_id);
  2975.                     }
  2976.                         $change_varaiants[$variant_id] = $new_variant_id;
  2977.                     // Clone descriptions
  2978.                     $__data = db_get_array("SELECT * FROM ?:product_option_variants_descriptions WHERE variant_id = ?i", $variant_id);
  2979.                     foreach ($__data as $__v) {
  2980.                         $__v['variant_id'] = $new_variant_id;
  2981.                         db_query("INSERT INTO ?:product_option_variants_descriptions ?e", $__v);
  2982.                     }
  2983.  
  2984.                     // Clone variant images
  2985.                     fn_clone_image_pairs($new_variant_id, $variant_id, 'variant_image');
  2986.                 }
  2987.                 unset($_data, $__data);
  2988.             }
  2989.         }
  2990.         // Clone Inventory
  2991.         if (!empty($inventory)) {
  2992.             fn_clone_options_inventory($from_product_id, $to_product_id, $change_options, $change_varaiants);
  2993.         }
  2994.         if (!empty($exceptions)) {
  2995.             foreach ($exceptions as $k => $v) {
  2996.                 $_data = array(
  2997.                     'product_id' => $to_product_id,
  2998.                     'combination' => serialize($v['combination']),
  2999.                 );
  3000.                 db_query("INSERT INTO ?:product_options_exceptions ?e", $_data);
  3001.             }
  3002.         }
  3003.     }
  3004. }
  3005.  
  3006. //
  3007. // Clone Inventory
  3008. //
  3009. function fn_clone_options_inventory($from_product_id, $to_product_id, $options, $variants)
  3010. {
  3011.     $inventory = db_get_array("SELECT * FROM ?:product_options_inventory WHERE product_id = ?i", $from_product_id);
  3012.  
  3013.     foreach ($inventory as $key => $value) {
  3014.         $_variants = explode('_', $value['combination']);
  3015.         $inventory[$key]['combination'] = '';
  3016.         foreach ($_variants as $kk => $vv) {
  3017.             if (($kk % 2) == 0 && !empty($_variants[$kk + 1])) {
  3018.                 $_comb[0] = $options[$vv];
  3019.                 $_comb[1] = $variants[$_variants[$kk + 1]];
  3020.  
  3021.                 $new_variants[$kk] = $_comb[1];
  3022.                 $inventory[$key]['combination'] .= implode('_', $_comb) . (!empty($_variants[$kk + 2]) ? '_' : '');
  3023.             }
  3024.         }
  3025.  
  3026.         $_data['product_id'] = $to_product_id;
  3027.         $_data['combination_hash'] = fn_generate_cart_id($to_product_id, array('product_options' => $new_variants));
  3028.         $_data['combination'] = rtrim($inventory[$key]['combination'], "|");
  3029.         $_data['amount'] = $value['amount'];
  3030.         $_data['product_code'] = $value['product_code'];
  3031.         db_query("INSERT INTO ?:product_options_inventory ?e", $_data);
  3032.  
  3033.         // Clone option images
  3034.         fn_clone_image_pairs($_data['combination_hash'], $value['combination_hash'], 'product_option', null, $to_product_id, 'product');
  3035.     }
  3036. }
  3037.  
  3038. // Generate url-safe filename for the object
  3039. function fn_generate_name($str, $object_type = '', $object_id = 0)
  3040. {
  3041.     $d = SEO_DELIMITER;
  3042.  
  3043.     // Replace umlauts with their basic latin representation
  3044.     $chars = array(
  3045.         ' ' => $d,
  3046.         '\'' => '',
  3047.         '"' => '',
  3048.         '\'' => '',
  3049.         '&' => $d.'and'.$d,
  3050.         "\xc3\xa5" => 'aa',
  3051.         "\xc3\xa4" => 'ae',
  3052.         "\xc3\xb6" => 'oe',
  3053.         "\xc3\x85" => 'aa',
  3054.         "\xc3\x84" => 'ae',
  3055.         "\xc3\x96" => 'oe',
  3056.     );
  3057.  
  3058.     $str = html_entity_decode($str, ENT_QUOTES, 'UTF-8'); // convert html special chars back to original chars
  3059.     $str = str_replace(array_keys($chars), $chars, $str);
  3060.    
  3061.     if (!empty($str)) {
  3062.         $str = strtr($str, array("\xc4\x84" => 'A', "\xc4\x85" => 'a', "\xc3\xa1" => 'a', "\xc3\x81" => 'A', "\xc3\xa0" => 'a', "\xc3\x80" => 'A', "\xc3\xa2" => 'a', "\xc3\x82" => 'A', "\xc3\xa3" => 'a', "\xc3\x83" => 'A', "\xc2\xaa" => 'a', "\xc4\x8c" => 'C', "\xc4\x8d" => 'c', "\xc3\xa7" => 'c', "\xc3\x87" => 'C', "\xc3\xa9" => 'e', "\xc3\x89" => 'E', "\xc3\xa8" => 'e', "\xc3\x88" => 'E', "\xc3\xaa" => 'e', "\xc3\x8a" => 'E', "\xc3\xab" => 'e', "\xc3\x8b" =>'E', "\xc4\x98" => 'E', "\xc4\x99" => 'e', "\xc4\x9a" => 'E', "\xc4\x9b" => 'e', "\xc4\x8f" => 'd', "\xc3\xad" => 'i', "\xc3\x8d" => 'I', "\xc3\xac" => 'i', "\xc3\x8c" => 'I', "\xc3\xae" => 'i', "\xc3\x8e" => 'I', "\xc3\xaf" => 'i', "\xc3\x8f" => 'I', "\xc4\xb9" => 'L', "\xc4\xba" => 'l', "\xc4\xbe" => 'l', "\xc5\x87" => 'N', "\xc5\x88" => 'n', "\xc3\xb1" => 'n', "\xc3\x91" => 'N', "\xc3\xb3" => 'o', "\xc3\x93" => 'O', "\xc3\xb2" => 'o', "\xc3\x92" => 'O', "\xc3\xb4" => 'o', "\xc3\x94" => 'O', "\xc3\xb5" => 'o', "\xc3\x95" => 'O', "\xd4\xa5" => 'o', "\xc3\x98" => 'O', "\xc2\xba" => 'o', "\xc3\xb0" => 'o', "\xc5\x94" => 'R', "\xc5\x95" => 'r', "\xc5\x98" => 'R', "\xc5\x99" => 'r', "\xc5\xa0" => 'S', "\xc5\xa1" => 's', "\xc5\xa5" => 't', "\xc3\xba" => 'u', "\xc3\x9a" => 'U', "\xc3\xb9" => 'u', "\xc3\x99" => 'U', "\xc3\xbb" => 'u', "\xc3\x9b" => 'U', "\xc3\xbc" => 'u', "\xc3\x9c" => 'U', "\xc5\xae" => 'U', "\xc5\xaf" => 'u', "\xc3\xbd" => 'y', "\xc3\x9d" => 'Y', "\xc3\xbf" => 'y', "\xc3\xa6" => 'a', "\xc3\x86" => 'A', "\xc3\x9f" => 's', "\xc5\xbd" => 'Z', "\xc5\xbe" => 'z', '?' => '-', ' ' => '-', '/' => '-', '&' => '-', '(' => '-', ')' => '-', '[' => '-', ']' => '-', '%' => '-', '#' => '-', ',' => '-', ':' => '-'));
  3063.  
  3064.         if (!empty($object_type)) {
  3065.             $str .= $d . $object_type . $object_id;
  3066.         }
  3067.  
  3068.         $str = strtolower($str); // only lower letters
  3069.         $str = preg_replace("/($d){2,}/", $d, $str); // replace double (and more) dashes with one dash
  3070.         $str = preg_replace("/[^a-z0-9-\.]/", '', $str); // URL can contain latin letters, numbers, dashes and points only
  3071.  
  3072.         return trim($str, '-'); // remove trailing dash if exist
  3073.     }
  3074.  
  3075.     return false;
  3076. }
  3077.  
  3078. /**
  3079.  * Function construct a string in format option1_variant1_option2_variant2...
  3080.  *
  3081.  * @param array $product_options
  3082.  * @return string
  3083.  */
  3084.  
  3085. function fn_get_options_combination($product_options)
  3086. {
  3087.     if (empty($product_options) && !is_array($product_options)) {
  3088.         return '';
  3089.     }
  3090.  
  3091.     $combination = '';
  3092.     foreach ($product_options as $option => $variant) {
  3093.         $combination .= $option . '_' . $variant . '_';
  3094.     }
  3095.     $combination = trim($combination, '_');
  3096.  
  3097.     return $combination;
  3098. }
  3099.  
  3100.  
  3101. function fn_get_products($params, $items_per_page = 0, $lang_code = CART_LANGUAGE)
  3102. {
  3103.     fn_set_hook('get_products_params', $params, $items_per_page, $lang_code);
  3104.  
  3105.     // Init filter
  3106.     $params = fn_init_view('products', $params);
  3107.  
  3108.     // Set default values to input params
  3109.     $default_params = array (
  3110.         'area' => AREA,
  3111.         'extend' => (AREA == 'C')? array('product_name', 'prices', 'categories') : array('product_name', 'prices'),
  3112.         'custom_extend' => array(),
  3113.         'pname' => '',
  3114.         'pshort' => '',
  3115.         'pfull' => '',
  3116.         'pkeywords' => '',
  3117.         'feature' => array(),
  3118.         'type' => 'simple',
  3119.         'page' => 1,
  3120.         'action' => '',
  3121.         'variants' => array(),
  3122.         'ranges' => array(),
  3123.         'custom_range' => array(),
  3124.         'field_range' => array(),
  3125.         'features_hash' => '',
  3126.         'limit' => 0,
  3127.         'bid' => 0,
  3128.         'match' => '',
  3129.         'sort_by' => '',
  3130.         'search_tracking_flags' => array()
  3131.     );
  3132.     if (empty($params['custom_extend'])) {
  3133.         $params['extend'] = !empty($params['extend']) ? array_merge($default_params['extend'], $params['extend']) : $default_params['extend'];
  3134.     } else {
  3135.         $params['extend'] = $params['custom_extend'];
  3136.     }
  3137.  
  3138.     $params = array_merge($default_params, $params);
  3139.  
  3140.     if ((empty($params['pname']) || $params['pname'] != 'Y') && (empty($params['pshort']) || $params['pshort'] != 'Y') && (empty($params['pfull']) || $params['pfull'] != 'Y') && (empty($params['pkeywords']) || $params['pkeywords'] != 'Y') && (empty($params['feature']) || $params['feature'] != 'Y') && !empty($params['q'])) {
  3141.         $params['pname'] = 'Y';
  3142.     }
  3143.  
  3144.     $auth = & $_SESSION['auth'];
  3145.  
  3146.     // Define fields that should be retrieved
  3147.     if (empty($params['only_short_fields'])) {
  3148.         $fields = array (
  3149.             'products.*',
  3150.         );
  3151.     } else {
  3152.         $fields = array (
  3153.             'products.product_id',
  3154.             'products.product_code',
  3155.             'products.product_type',
  3156.             'products.status',
  3157.             'products.company_id',
  3158.             'products.list_price',
  3159.             'products.amount',
  3160.             'products.weight',
  3161.             'products.tracking',
  3162.             'products.is_edp',
  3163.         );
  3164.     }
  3165.  
  3166.     // Define sort fields
  3167.     $sortings = array (
  3168.         'code' => 'products.product_code',
  3169.         'status' => 'products.status',
  3170.         'product' => 'descr1.product',
  3171.         'position' => 'products_categories.position',
  3172.         'price' => 'prices.price',
  3173.         'list_price' => 'products.list_price',
  3174.         'weight' => 'products.weight',
  3175.         'amount' => 'products.amount',
  3176.         'timestamp' => 'products.timestamp',
  3177.         'popularity' => 'popularity.total',
  3178.         'company' => 'company_name',
  3179.         'null' => 'NULL'
  3180.     );
  3181.  
  3182.     if (!empty($params['get_subscribers'])) {
  3183.         $sortings['num_subscr'] = 'num_subscr';
  3184.         $fields[] = 'COUNT(DISTINCT product_subscriptions.subscription_id) as num_subscr';
  3185.     }
  3186.  
  3187.     $directions = array (
  3188.         'asc' => 'asc',
  3189.         'desc' => 'desc'
  3190.     );
  3191.  
  3192.     if (isset($params['compact']) && $params['compact'] == 'Y') {
  3193.         $union_condition = ' OR ';
  3194.     } else {
  3195.         $union_condition = ' AND ';
  3196.     }
  3197.  
  3198.     $join = $condition = $u_condition = $inventory_condition = '';
  3199.  
  3200.     // Search string condition for SQL query
  3201.     if (isset($params['q']) && fn_string_no_empty($params['q'])) {
  3202.  
  3203.         $params['q'] = trim($params['q']);
  3204.         if ($params['match'] == 'any') {
  3205.             $pieces = fn_explode(' ', $params['q']);
  3206.             $search_type = ' OR ';
  3207.         } elseif ($params['match'] == 'all') {
  3208.             $pieces = fn_explode(' ', $params['q']);
  3209.             $search_type = ' AND ';
  3210.         } else {
  3211.             $pieces = array($params['q']);
  3212.             $search_type = '';
  3213.         }
  3214.  
  3215.         $_condition = array();
  3216.         foreach ($pieces as $piece) {
  3217.             if (strlen($piece) == 0) {
  3218.                 continue;
  3219.             }
  3220.  
  3221.             $tmp = db_quote("(descr1.search_words LIKE ?l)", "%$piece%"); // check search words
  3222.  
  3223.             if ($params['pname'] == 'Y') {
  3224.                 $tmp .= db_quote(" OR descr1.product LIKE ?l", "%$piece%");
  3225.             }
  3226.             if ($params['pshort'] == 'Y') {
  3227.                 $tmp .= db_quote(" OR descr1.short_description LIKE ?l", "%$piece%");
  3228.             }
  3229.             if ($params['pfull'] == 'Y') {
  3230.                 $tmp .= db_quote(" OR descr1.full_description LIKE ?l", "%$piece%");
  3231.             }
  3232.             if ($params['pkeywords'] == 'Y') {
  3233.                 $tmp .= db_quote(" OR (descr1.meta_keywords LIKE ?l OR descr1.meta_description LIKE ?l)", "%$piece%", "%$piece%");
  3234.             }
  3235.             if (!empty($params['feature']) && $params['action'] != 'feature_search') {
  3236.                 $tmp .= db_quote(" OR ?:product_features_values.value LIKE ?l", "%$piece%");
  3237.             }
  3238.  
  3239.             fn_set_hook('additional_fields_in_search', $params, $fields, $sortings, $condition, $join, $sorting, $group_by, $tmp, $piece);
  3240.  
  3241.             $_condition[] = '(' . $tmp . ')';
  3242.         }
  3243.  
  3244.         $_cond = implode($search_type, $_condition);
  3245.  
  3246.         if (!empty($_condition)) {
  3247.             $condition .= ' AND (' . $_cond . ') ';
  3248.         }
  3249.  
  3250.         if (!empty($params['feature']) && $params['action'] != 'feature_search') {
  3251.             $join .= " LEFT JOIN ?:product_features_values ON ?:product_features_values.product_id = products.product_id";
  3252.             $condition .= db_quote(" AND (?:product_features_values.feature_id IN (?n) OR ?:product_features_values.feature_id IS NULL)", array_values($params['feature']));
  3253.         }
  3254.  
  3255.         //if perform search we also get additional fields
  3256.         if ($params['pname'] == 'Y') {
  3257.             $params['extend'][] = 'product_name';
  3258.         }
  3259.  
  3260.         if ($params['pshort'] == 'Y' || $params['pfull'] == 'Y' || $params['pkeywords'] == 'Y') {
  3261.             $params['extend'][] = 'description';
  3262.         }
  3263.  
  3264.         unset($_condition);
  3265.     }
  3266.  
  3267.     //
  3268.     // [Advanced and feature filters]
  3269.     //
  3270.  
  3271.     if (!empty($params['apply_limit']) && $params['apply_limit']) {
  3272.         $pids = array();
  3273.         foreach ($params['pid'] as $pid) {
  3274.             if ($pid != $params['exclude_pid']) {
  3275.                 if (count($pids) == $params['limit']) {
  3276.                     break;
  3277.                 }
  3278.                 else {
  3279.                     $pids[] = $pid;
  3280.                 }
  3281.             }
  3282.         }
  3283.         $params['pid'] = $pids;
  3284.     }
  3285.     if (!empty($params['features_hash']) || (!fn_is_empty($params['variants']))) {
  3286.         $join .= db_quote(" LEFT JOIN ?:product_features_values ON ?:product_features_values.product_id = products.product_id AND ?:product_features_values.lang_code = ?s", CART_LANGUAGE);
  3287.     }
  3288.  
  3289.     if (!empty($params['variants'])) {
  3290.         $params['features_hash'] .= implode('.', $params['variants']);
  3291.     }
  3292.  
  3293.     $advanced_variant_ids = $simple_variant_ids = $ranges_ids = $fields_ids = array();
  3294.  
  3295.     if (!empty($params['features_hash'])) {
  3296.         if (!empty($params['advanced_filter'])) {
  3297.             list($av_ids, $ranges_ids, $fields_ids) = fn_parse_features_hash($params['features_hash']);
  3298.             $advanced_variant_ids = db_get_hash_multi_array("SELECT feature_id, variant_id FROM ?:product_feature_variants WHERE variant_id IN (?n)", array('feature_id', 'variant_id'), $av_ids);
  3299.         } else {
  3300.             list($simple_variant_ids, $ranges_ids, $fields_ids) = fn_parse_features_hash($params['features_hash']);
  3301.         }
  3302.     }
  3303.     if (!empty($params['multiple_variants']) && !empty($params['advanced_filter'])) {
  3304.         $simple_variant_ids = $params['multiple_variants'];
  3305.     }
  3306.  
  3307.     if (!empty($advanced_variant_ids)) {
  3308.         $join .= db_quote(" LEFT JOIN (SELECT product_id, GROUP_CONCAT(?:product_features_values.variant_id) AS advanced_variants FROM ?:product_features_values WHERE lang_code = ?s GROUP BY product_id) AS pfv_advanced ON pfv_advanced.product_id = products.product_id", CART_LANGUAGE);
  3309.  
  3310.         $where_and_conditions = array();
  3311.         foreach ($advanced_variant_ids as $k => $variant_ids) {
  3312.             $where_or_conditions = array();
  3313.             foreach ($variant_ids as $variant_id => $v) {
  3314.                 $where_or_conditions[] = db_quote(" FIND_IN_SET('?i', advanced_variants)", $variant_id);
  3315.             }
  3316.             $where_and_conditions[] = "(".implode(' OR ', $where_or_conditions).")";
  3317.         }
  3318.         $condition .= ' AND '.implode(' AND ', $where_and_conditions);
  3319.     }
  3320.  
  3321.     if (!empty($simple_variant_ids)) {
  3322.         $join .= db_quote(" LEFT JOIN (SELECT product_id, GROUP_CONCAT(?:product_features_values.variant_id) AS simple_variants FROM ?:product_features_values WHERE lang_code = ?s GROUP BY product_id) AS pfv_simple ON pfv_simple.product_id = products.product_id", CART_LANGUAGE);
  3323.  
  3324.         $where_conditions = array();
  3325.         foreach ($simple_variant_ids as $k => $variant_id) {
  3326.             $where_conditions[] = db_quote(" FIND_IN_SET('?i', simple_variants)", $variant_id);
  3327.         }
  3328.         $condition .= ' AND '.implode(' AND ', $where_conditions);
  3329.     }
  3330.  
  3331.     //
  3332.     // Ranges from text inputs
  3333.     //
  3334.  
  3335.     // Feature ranges
  3336.     if (!empty($params['custom_range'])) {
  3337.         foreach ($params['custom_range'] as $k => $v) {
  3338.             $k = intval($k);
  3339.             if (isset($v['from']) && fn_string_no_empty($v['from']) || isset($v['to']) && fn_string_no_empty($v['to'])) {
  3340.                 if (!empty($v['type'])) {
  3341.                     if ($v['type'] == 'D') {
  3342.                         $v['from'] = fn_parse_date($v['from']);
  3343.                         $v['to'] = fn_parse_date($v['to']);
  3344.                     }
  3345.                 }
  3346.                 $join .= db_quote(" LEFT JOIN ?:product_features_values as custom_range_$k ON custom_range_$k.product_id = products.product_id AND custom_range_$k.lang_code = ?s", CART_LANGUAGE);
  3347.                 if (fn_string_no_empty($v['from']) && fn_string_no_empty($v['to'])) {
  3348.                     $condition .= db_quote(" AND (custom_range_$k.value_int >= ?i AND custom_range_$k.value_int <= ?i AND custom_range_$k.value = '' AND custom_range_$k.feature_id = ?i) ", $v['from'], $v['to'], $k);
  3349.                 } else {
  3350.                     $condition .= " AND custom_range_$k.value_int" . (fn_string_no_empty($v['from']) ? db_quote(' >= ?i', $v['from']) : db_quote(" <= ?i AND custom_range_$k.value = '' AND custom_range_$k.feature_id = ?i ", $v['to'], $k));
  3351.                 }
  3352.             }
  3353.         }
  3354.     }
  3355.     // Product field ranges
  3356.     $filter_fields = fn_get_product_filter_fields();
  3357.     if (!empty($params['field_range'])) {
  3358.         foreach ($params['field_range'] as $field_type => $v) {
  3359.             $structure = $filter_fields[$field_type];
  3360.             if (!empty($structure) && (!empty($v['from']) || !empty($v['to']))) {
  3361.                 $params["$structure[db_field]_from"] = trim($v['from']);
  3362.                 $params["$structure[db_field]_to"] = trim($v['to']);
  3363.             }
  3364.         }
  3365.     }
  3366.     // Ranges from database
  3367.     if (!empty($ranges_ids)) {
  3368.         $range_conditions = db_get_array("SELECT `from`, `to`, feature_id FROM ?:product_filter_ranges WHERE range_id IN (?n)", $ranges_ids);
  3369.         foreach ($range_conditions as $k => $range_condition) {
  3370.             $join .= db_quote(" LEFT JOIN ?:product_features_values as var_val_$k ON var_val_$k.product_id = products.product_id AND var_val_$k.lang_code = ?s", CART_LANGUAGE);
  3371.             $condition .= db_quote(" AND (var_val_$k.value_int >= ?i AND var_val_$k.value_int <= ?i AND var_val_$k.value = '' AND var_val_$k.feature_id = ?i) ", $range_condition['from'], $range_condition['to'], $range_condition['feature_id']);
  3372.         }
  3373.     }
  3374.  
  3375.     // Field ranges
  3376.     $fields_ids = empty($params['fields_ids']) ? $fields_ids : $params['fields'];
  3377.     if (!empty($fields_ids)) {
  3378.         foreach ($fields_ids as $rid => $field_type) {
  3379.             if (!empty($filter_fields[$field_type])) {
  3380.                 $structure = $filter_fields[$field_type];
  3381.                 if ($structure['condition_type'] == 'D') {
  3382.                     $range_condition = db_get_row("SELECT `from`, `to`, range_id FROM ?:product_filter_ranges WHERE range_id = ?i", $rid);
  3383.                     if (!empty($range_condition)) {
  3384.                         $params["$structure[db_field]_from"] = $range_condition['from'];
  3385.                         $params["$structure[db_field]_to"] = $range_condition['to'];
  3386.                     }
  3387.                 } elseif ($structure['condition_type'] == 'F') {
  3388.                     $params[$structure['db_field']] = $rid;
  3389.                 } elseif ($structure['condition_type'] == 'C') {
  3390.                     $params[$structure['db_field']] = ($rid == 1) ? 'Y' : 'N';
  3391.                 }
  3392.             }
  3393.         }
  3394.     }
  3395.  
  3396.     // Checkbox features
  3397.     if (!empty($params['ch_filters']) && !fn_is_empty($params['ch_filters'])) {
  3398.         foreach ($params['ch_filters'] as $k => $v) {
  3399.             // Product field filter
  3400.             if (is_string($k) == true && !empty($v) && $structure = $filter_fields[$k]) {
  3401.                 $condition .= db_quote(" AND $structure[table].$structure[db_field] IN (?a)", ($v == 'A' ? array('Y', 'N') : $v));
  3402.             // Feature filter
  3403.             } elseif (!empty($v)) {
  3404.                 $fid = intval($k);
  3405.                 $join .= db_quote(" LEFT JOIN ?:product_features_values as ch_features_$fid ON ch_features_$fid.product_id = products.product_id AND ch_features_$fid.lang_code = ?s", CART_LANGUAGE);
  3406.                 $condition .= db_quote(" AND ch_features_$fid.feature_id = ?i AND ch_features_$fid.value IN (?a)", $fid, ($v == 'A' ? array('Y', 'N') : $v));
  3407.             }
  3408.         }
  3409.     }
  3410.  
  3411.     // Text features
  3412.     if (!empty($params['tx_features'])) {
  3413.         foreach ($params['tx_features'] as $k => $v) {
  3414.             if (fn_string_no_empty($v)) {
  3415.                 $fid = intval($k);
  3416.                 $join .= " LEFT JOIN ?:product_features_values as tx_features_$fid ON tx_features_$fid.product_id = products.product_id";
  3417.                 $condition .= db_quote(" AND tx_features_$fid.value LIKE ?l AND tx_features_$fid.lang_code = ?s", "%" . trim($v) . "%", CART_LANGUAGE);
  3418.             }
  3419.         }
  3420.     }
  3421.  
  3422.     $total = 0;
  3423.     fn_set_hook('get_products_pre', $params, $join, $condition, $u_condition, $inventory_condition, $sortings, $total, $items_per_page, $lang_code);
  3424.  
  3425.     //
  3426.     // [/Advanced filters]
  3427.     //
  3428.  
  3429.     $feature_search_condition = '';
  3430.     if (!empty($params['feature'])) {
  3431.         // Extended search by product fields
  3432.         $_cond = array();
  3433.         $total_hits = 0;
  3434.         foreach ($params['feature'] as $f_id) {
  3435.             if (!empty($f_val)) {
  3436.                 $total_hits++;
  3437.                 $_cond[] = db_quote("(?:product_features_values.feature_id = ?i)", $f_id);
  3438.             }
  3439.         }
  3440.  
  3441.         $params['extend'][] = 'categories';
  3442.         if (!empty($_cond)) {
  3443.             $cache_feature_search = db_get_fields("SELECT product_id, COUNT(product_id) as cnt FROM ?:product_features_values WHERE (" . implode(' OR ', $_cond) . ") GROUP BY product_id HAVING cnt = $total_hits");
  3444.             $feature_search_condition .= db_quote(" AND products_categories.product_id IN (?n)", $cache_feature_search);
  3445.         }
  3446.     }
  3447.  
  3448.     // Category search condition for SQL query
  3449.     if (!empty($params['cid'])) {
  3450.         $cids = is_array($params['cid']) ? $params['cid'] : array($params['cid']);
  3451.  
  3452.         if (!empty($params['subcats']) && $params['subcats'] == 'Y') {
  3453.             $_ids = db_get_fields("SELECT a.category_id FROM ?:categories as a LEFT JOIN ?:categories as b ON b.category_id IN (?n) WHERE a.id_path LIKE CONCAT(b.id_path, '/%')", $cids);
  3454.  
  3455.             $cids = fn_array_merge($cids, $_ids, false);
  3456.         }
  3457.  
  3458.         $params['extend'][] = 'categories';
  3459.         $condition .= db_quote(" AND ?:categories.category_id IN (?n)", $cids);
  3460.     }
  3461.  
  3462.     // If we need to get the products by IDs and no IDs passed, don't search anything
  3463.     if (!empty($params['force_get_by_ids']) && empty($params['pid']) && empty($params['product_id'])) {
  3464.         return array(array(), $params, 0);
  3465.     }
  3466.  
  3467.     // Product ID search condition for SQL query
  3468.     if (!empty($params['pid'])) {
  3469.         $u_condition .= db_quote($union_condition . ' products.product_id IN (?n)', $params['pid']);
  3470.     }
  3471.  
  3472.     // Exclude products from search results
  3473.     if (!empty($params['exclude_pid'])) {
  3474.         $condition .= db_quote(' AND products.product_id NOT IN (?n)', $params['exclude_pid']);
  3475.     }
  3476.  
  3477.     // Search by feature comparison flag
  3478.     if (!empty($params['feature_comparison'])) {
  3479.         $condition .= db_quote(' AND products.feature_comparison = ?s', $params['feature_comparison']);
  3480.     }
  3481.  
  3482.     // Search products by localization
  3483.     $condition .= fn_get_localizations_condition('products.localization', true);
  3484.     $condition .= fn_get_localizations_condition('?:categories.localization', true);
  3485.  
  3486.     $company_condition = '';
  3487.     if ($params['area'] == 'C') {
  3488.         if (fn_check_suppliers_functionality()) {
  3489.             // if MVE or suppliers enabled
  3490.             $company_condition .= " AND (companies.status = 'A' OR products.company_id = 0) ";
  3491.             $params['extend'][] = 'companies';
  3492.         } else {
  3493.             // if suppliers disabled
  3494.             $company_condition .= fn_get_company_condition('products.company_id', true, '0', false, true);
  3495.         }
  3496.     } else {
  3497.         // if admin area
  3498.         $company_condition .= fn_get_company_condition('products.company_id');
  3499.     }
  3500.    
  3501.     $condition .= $company_condition;
  3502.  
  3503.     if (defined('COMPANY_ID') && isset($params['company_id'])) {
  3504.         $params['company_id'] = COMPANY_ID;
  3505.     }
  3506.     if (isset($params['company_id']) && $params['company_id'] != '') {
  3507.         $condition .= db_quote(' AND products.company_id = ?i ', $params['company_id']);
  3508.     }
  3509.  
  3510.     if (isset($params['price_from']) && fn_is_numeric($params['price_from'])) {
  3511.         $condition .= db_quote(' AND prices.price >= ?d', fn_convert_price(trim($params['price_from'])));
  3512.         $params['extend'][] = 'prices2';
  3513.     }
  3514.  
  3515.     if (isset($params['price_to']) && fn_is_numeric($params['price_to'])) {
  3516.         $condition .= db_quote(' AND prices.price <= ?d', fn_convert_price(trim($params['price_to'])));
  3517.         $params['extend'][] = 'prices2';
  3518.     }
  3519.  
  3520.     if (isset($params['weight_from']) && fn_is_numeric($params['weight_from'])) {
  3521.         $condition .= db_quote(' AND products.weight >= ?d', fn_convert_weight(trim($params['weight_from'])));
  3522.     }
  3523.  
  3524.     if (isset($params['weight_to']) && fn_is_numeric($params['weight_to'])) {
  3525.         $condition .= db_quote(' AND products.weight <= ?d', fn_convert_weight(trim($params['weight_to'])));
  3526.     }
  3527.  
  3528.     // search specific inventory status
  3529.     if (!empty($params['search_tracking_flags'])) {
  3530.         $condition .= db_quote(' AND products.tracking IN(?a)', $params['search_tracking_flags']);
  3531.     }
  3532.  
  3533.     if (isset($params['amount_from']) && fn_is_numeric($params['amount_from'])) {
  3534.         $condition .= db_quote(" AND IF(products.tracking = 'O', inventory.amount >= ?i, products.amount >= ?i)", $params['amount_from'], $params['amount_from']);
  3535.         $inventory_condition .= db_quote(' AND inventory.amount >= ?i', $params['amount_from']);
  3536.     }
  3537.  
  3538.     if (isset($params['amount_to']) && fn_is_numeric($params['amount_to'])) {
  3539.         $condition .= db_quote(" AND IF(products.tracking = 'O', inventory.amount <= ?i, products.amount <= ?i)", $params['amount_to'], $params['amount_to']);
  3540.         $inventory_condition .= db_quote(' AND inventory.amount <= ?i', $params['amount_to']);
  3541.     }
  3542.  
  3543.     if (Registry::get('settings.General.inventory_tracking') == 'Y' && Registry::get('settings.General.show_out_of_stock_products') == 'N' && $params['area'] == 'C') { // FIXME? Registry in model
  3544.         $condition .= " AND IF(products.tracking = 'O', inventory.amount > 0, products.amount > 0)";
  3545.     }
  3546.  
  3547.     if (!empty($params['status'])) {
  3548.         $condition .= db_quote(' AND products.status IN (?a)', $params['status']);
  3549.     }
  3550.  
  3551.     if (!empty($params['shipping_freight_from'])) {
  3552.         $condition .= db_quote(' AND products.shipping_freight >= ?d', $params['shipping_freight_from']);
  3553.     }
  3554.  
  3555.     if (!empty($params['shipping_freight_to'])) {
  3556.         $condition .= db_quote(' AND products.shipping_freight <= ?d', $params['shipping_freight_to']);
  3557.     }
  3558.  
  3559.     if (!empty($params['free_shipping'])) {
  3560.         $condition .= db_quote(' AND products.free_shipping = ?s', $params['free_shipping']);
  3561.     }
  3562.  
  3563.     if (!empty($params['downloadable'])) {
  3564.         $condition .= db_quote(' AND products.is_edp = ?s', $params['downloadable']);
  3565.     }
  3566.  
  3567.     if (!empty($params['b_id'])) {
  3568.         $join .= " LEFT JOIN ?:block_links ON ?:block_links.object_id = products.product_id AND ?:block_links.location = 'products'";
  3569.         $condition .= db_quote(' AND ?:block_links.block_id = ?i', $params['b_id']);
  3570.     }
  3571.  
  3572.     if (isset($params['pcode']) && fn_string_no_empty($params['pcode'])) {
  3573.         $pcode = trim($params['pcode']);
  3574.         $fields[] = 'inventory.combination';
  3575.         $u_condition .= db_quote(" $union_condition (inventory.product_code LIKE ?l OR products.product_code LIKE ?l)", "%$pcode%", "%$pcode%");
  3576.         $inventory_condition .= db_quote(" AND inventory.product_code LIKE ?l", "%$pcode%");
  3577.     }
  3578.  
  3579.     if ((isset($params['amount_to']) && fn_is_numeric($params['amount_to'])) || (isset($params['amount_from']) && fn_is_numeric($params['amount_from'])) || !empty($params['pcode']) || (Registry::get('settings.General.inventory_tracking') == 'Y' && Registry::get('settings.General.show_out_of_stock_products') == 'N' && $params['area'] == 'C')) {
  3580.         $join .= " LEFT JOIN ?:product_options_inventory as inventory ON inventory.product_id = products.product_id $inventory_condition";
  3581.     }
  3582.  
  3583.     if (!empty($params['period']) && $params['period'] != 'A') {
  3584.         list($params['time_from'], $params['time_to']) = fn_create_periods($params);
  3585.         $condition .= db_quote(" AND (products.timestamp >= ?i AND products.timestamp <= ?i)", $params['time_from'], $params['time_to']);
  3586.     }
  3587.  
  3588.     if (!empty($params['item_ids'])) {
  3589.         $condition .= db_quote(" AND products.product_id IN (?n)", explode(',', $params['item_ids']));
  3590.     }
  3591.  
  3592.     if (isset($params['popularity_from']) && fn_is_numeric($params['popularity_from'])) {
  3593.         $params['extend'][] = 'popularity';
  3594.         $condition .= db_quote(' AND popularity.total >= ?i', $params['popularity_from']);
  3595.     }
  3596.  
  3597.     if (isset($params['popularity_to']) && fn_is_numeric($params['popularity_to'])) {
  3598.         $params['extend'][] = 'popularity';
  3599.         $condition .= db_quote(' AND popularity.total <= ?i', $params['popularity_to']);
  3600.     }
  3601.  
  3602.  
  3603.     $limit = '';
  3604.     $group_by = 'products.product_id';
  3605.     // Show enabled products
  3606.     $_p_statuses = array('A');
  3607.     $condition .= ($params['area'] == 'C') ? ' AND (' . fn_find_array_in_set($auth['usergroup_ids'], 'products.usergroup_ids', true) . ')' . db_quote(' AND products.status IN (?a)', $_p_statuses) : '';
  3608.  
  3609.     // -- JOINS --
  3610.     if (in_array('product_name', $params['extend'])) {
  3611.         $fields[] = 'descr1.product as product';
  3612.         $join .= db_quote(" LEFT JOIN ?:product_descriptions as descr1 ON descr1.product_id = products.product_id AND descr1.lang_code = ?s ", $lang_code);
  3613.     }
  3614.  
  3615.     // get prices
  3616.     if (in_array('prices', $params['extend'])) {
  3617.         $fields[] = 'MIN(prices.price) as price';
  3618.         $join .= " LEFT JOIN ?:product_prices as prices ON prices.product_id = products.product_id AND prices.lower_limit = 1";
  3619.         $condition .= db_quote(' AND prices.usergroup_id IN (?n)', (($params['area'] == 'A') ? USERGROUP_ALL : array_merge(array(USERGROUP_ALL), $auth['usergroup_ids'])));
  3620.     }
  3621.  
  3622.     // get prices for search by price
  3623.     if (in_array('prices2', $params['extend'])) {
  3624.         $price_usergroup_cond_2 = db_quote(' AND prices_2.usergroup_id IN (?n)', (($params['area'] == 'A') ? USERGROUP_ALL : array_merge(array(USERGROUP_ALL), $auth['usergroup_ids'])));
  3625.         $join .= " LEFT JOIN ?:product_prices as prices_2 ON prices.product_id = prices_2.product_id AND prices_2.lower_limit = 1 AND prices_2.price < prices.price " . $price_usergroup_cond_2;
  3626.         $condition .= ' AND prices_2.price IS NULL';
  3627.     }
  3628.  
  3629.     // get short & full description
  3630.     if (in_array('description', $params['extend'])) {
  3631.         $fields[] = 'descr1.short_description';
  3632.         $fields[] = "IF(descr1.short_description = '', descr1.full_description, '') as full_description";
  3633.     }
  3634.  
  3635.     // get companies
  3636.     $companies_join = db_quote(" LEFT JOIN ?:companies AS companies ON companies.company_id = products.company_id ");
  3637.     if (in_array('companies', $params['extend'])) {
  3638.         $fields[] = 'companies.company as company_name';
  3639.         $join .= $companies_join;
  3640.     }
  3641.  
  3642.     // for compatibility
  3643.     if (in_array('category_ids', $params['extend'])) {
  3644.         $params['extend'][] = 'categories';
  3645.     }
  3646.  
  3647.     // get categories
  3648.     $_c_statuses = array('A' , 'H');// Show enabled categories
  3649.     $category_avail_cond = ($params['area'] == 'C') ? ' AND (' . fn_find_array_in_set($auth['usergroup_ids'], '?:categories.usergroup_ids', true) . ')' : '';
  3650.     $category_avail_cond .= ($params['area'] == 'C') ? db_quote(" AND ?:categories.status IN (?a) ", $_c_statuses) : '';
  3651.     $categories_join = " INNER JOIN ?:products_categories as products_categories ON products_categories.product_id = products.product_id INNER JOIN ?:categories ON ?:categories.category_id = products_categories.category_id $category_avail_cond $feature_search_condition";
  3652.     if (in_array('categories', $params['extend'])) {
  3653.         $fields[] = "GROUP_CONCAT(IF(products_categories.link_type = 'M', CONCAT(products_categories.category_id, 'M'), products_categories.category_id)) as category_ids";
  3654.         $fields[] = 'products_categories.position';
  3655.         $join .= $categories_join;
  3656.     }
  3657.  
  3658.     // get popularity
  3659.     $popularity_join = db_quote(" LEFT JOIN ?:product_popularity as popularity ON popularity.product_id = products.product_id");
  3660.     if (in_array('popularity', $params['extend'])) {
  3661.         $fields[] = 'popularity.total as popularity';
  3662.         $join .= $popularity_join;
  3663.     }
  3664.     //  -- \JOINs --
  3665.  
  3666.     if (!empty($u_condition)) {
  3667.         $condition .= " $union_condition ((" . ($union_condition == ' OR ' ? '0 ' : '1 ') . $u_condition . ')' . $company_condition . ')';
  3668.     }
  3669.  
  3670.     fn_set_hook('get_products', $params, $fields, $sortings, $condition, $join, $sorting, $group_by, $lang_code);
  3671.  
  3672.     // -- SORTINGS --
  3673.     if (empty($params['sort_by']) || empty($sortings[$params['sort_by']])) {
  3674.         $params['sort_by'] = Registry::get('settings.Appearance.default_products_sorting');
  3675.         if (empty($sortings[$params['sort_by']])) {
  3676.             $_products_sortings = fn_get_products_sorting(false);
  3677.             $params['sort_by'] = key($_products_sortings);
  3678.         }
  3679.     }
  3680.  
  3681.     $default_sorting = fn_get_products_sorting(false);
  3682.  
  3683.     if ($params['sort_by'] == 'popularity' && !in_array('popularity', $params['extend'])) {
  3684.         $join .= $popularity_join;
  3685.     }
  3686.  
  3687.     if ($params['sort_by'] == 'position' && !in_array('categories', $params['extend'])) {
  3688.         $join .= $categories_join;
  3689.     }
  3690.  
  3691.     if ($params['sort_by'] == 'company' && !in_array('companies', $params['extend'])) {
  3692.         $join .= $companies_join;
  3693.     }
  3694.  
  3695.     if (empty($params['sort_order']) || empty($directions[$params['sort_order']])) {
  3696.         if (!empty($default_sorting[$params['sort_by']]['default_order'])) {
  3697.             $params['sort_order'] = $default_sorting[$params['sort_by']]['default_order'];
  3698.         } else {
  3699.             $params['sort_order'] = 'asc';
  3700.         }
  3701.     }
  3702.  
  3703.     if (!empty($params['get_subscribers'])) {
  3704.         $join .= " LEFT JOIN ?:product_subscriptions as product_subscriptions ON product_subscriptions.product_id = products.product_id";
  3705.     }
  3706.  
  3707.     $sorting = $sortings[$params['sort_by']] . ' ' . $directions[$params['sort_order']];
  3708.     // -- \SORTINGS --
  3709.  
  3710.     // Reverse sorting (for usage in view)
  3711.     $params['sort_order'] = ($params['sort_order'] == 'asc') ? 'desc' : 'asc';
  3712.  
  3713.     // Used for View cascading
  3714.     if (!empty($params['get_query'])) {
  3715.         return "SELECT products.product_id FROM ?:products as products $join WHERE 1 $condition GROUP BY products.product_id";
  3716.     }
  3717.  
  3718.     // Used for Extended search
  3719.     if (!empty($params['get_conditions'])) {
  3720.         return array($fields, $join, $condition);
  3721.     }
  3722.  
  3723.     if (empty($total) && !empty($params['limit'])) {
  3724.         $limit = db_quote(" LIMIT 0, ?i", $params['limit']);
  3725.     }
  3726.  
  3727.     if (empty($total) && !empty($items_per_page)) {
  3728.         $params['calc_found_rows'] = true;
  3729.         if (!empty($params['limit']) && $total > $params['limit']) {
  3730.             $total = $params['limit'];
  3731.         }
  3732.  
  3733.         $limit = fn_paginate($params['page'], 0, $items_per_page, true);
  3734.     }
  3735.  
  3736.     $products = db_get_array("SELECT SQL_CALC_FOUND_ROWS " . implode(', ', $fields) . " FROM ?:products as products $join WHERE 1 $condition GROUP BY $group_by ORDER BY $sorting $limit");
  3737.  
  3738.     if (!empty($items_per_page)) {
  3739.         $total = !empty($total)? $total : db_get_found_rows();
  3740.  
  3741.         fn_paginate($params['page'], $total, $items_per_page);
  3742.     } else {
  3743.         $total = count($products);
  3744.     }
  3745.  
  3746.     // Post processing
  3747.     if (in_array('categories', $params['extend'])) {
  3748.         foreach ($products as $k => $v) {
  3749.             $products[$k]['category_ids'] = fn_convert_categories($v['category_ids']);
  3750.         }
  3751.     }
  3752.  
  3753.     if (!empty($params['item_ids'])) {
  3754.         $products = fn_sort_by_ids($products, explode(',', $params['item_ids']));
  3755.     }
  3756.     if (!empty($params['pid']) && !empty($params['apply_limit']) && $params['apply_limit']) {
  3757.         $products = fn_sort_by_ids($products, $params['pid']);
  3758.     }
  3759.  
  3760.     fn_set_hook('get_products_post', $products, $params);
  3761.  
  3762.     fn_view_process_results('products', $products, $params, $items_per_page);
  3763.  
  3764.     return array($products, $params, $total);
  3765. }
  3766.  
  3767.  
  3768. function fn_sort_by_ids($items, $ids, $field = 'product_id')
  3769. {
  3770.     $tmp = array();
  3771.  
  3772.     foreach ($items as $k => $item) {
  3773.         foreach ($ids as $key => $item_id) {
  3774.             if ($item_id == $item[$field]) {
  3775.                 $tmp[$key] = $item;
  3776.                 break;
  3777.             }
  3778.         }
  3779.     }
  3780.  
  3781.     ksort($tmp);
  3782.  
  3783.     return $tmp;
  3784. }
  3785.  
  3786. function fn_convert_categories($category_ids)
  3787. {
  3788.     $c_ids = explode(',', $category_ids);
  3789.     $result = array();
  3790.     foreach ($c_ids as $v) {
  3791.         $result[intval($v)] = (strpos($v, 'M') !== false) ? 'M' : 'A';
  3792.     }
  3793.  
  3794.     return $result;
  3795. }
  3796.  
  3797. /**
  3798.  * Update product option
  3799.  *
  3800.  * @param array $option_data option data array
  3801.  * @param int $option_id option ID (empty if we're adding the option)
  3802.  * @param string $lang_code language code to add/update option for
  3803.  * @return int ID of the added/updated option
  3804.  */
  3805. function fn_update_product_option($option_data, $option_id = 0, $lang_code = DESCR_SL)
  3806. {
  3807.     // Add option
  3808.     if (empty($option_id)) {
  3809.  
  3810.         if (empty($option_data['product_id'])) {
  3811.             $option_data['product_id'] = 0;
  3812.         }
  3813.  
  3814.         $option_data['option_id'] = $option_id = db_query('INSERT INTO ?:product_options ?e', $option_data);
  3815.  
  3816.         foreach ((array)Registry::get('languages') as $option_data['lang_code'] => $_v) {
  3817.             db_query("INSERT INTO ?:product_options_descriptions ?e", $option_data);
  3818.         }
  3819.  
  3820.     // Update option
  3821.     } else {
  3822.         db_query("UPDATE ?:product_options SET ?u WHERE option_id = ?i", $option_data, $option_id);
  3823.         db_query("UPDATE ?:product_options_descriptions SET ?u WHERE option_id = ?i AND lang_code = ?s", $option_data, $option_id, $lang_code);
  3824.     }
  3825.  
  3826.  
  3827.     if (!empty($option_data['variants'])) {
  3828.         $var_ids = array();
  3829.  
  3830.         // Generate special variants structure for checkbox (2 variants, 1 hidden)
  3831.         if ($option_data['option_type'] == 'C') {
  3832.             $option_data['variants'] = array_slice($option_data['variants'], 0, 1); // only 1 variant should be here
  3833.             reset($option_data['variants']);
  3834.             $_k = key($option_data['variants']);
  3835.             $option_data['variants'][$_k]['position'] = 1; // checked variant
  3836.             $v_id = db_get_field("SELECT variant_id FROM ?:product_option_variants WHERE option_id = ?i AND position = 0", $option_id);
  3837.             $option_data['variants'][] = array ( // unchecked variant
  3838.                 'position' => 0,
  3839.                 'variant_id' => $v_id
  3840.             );
  3841.         }
  3842.        
  3843.         $variant_images = array();
  3844.         foreach ($option_data['variants'] as $k => $v) {
  3845.             if ((!isset($v['variant_name']) || $v['variant_name'] == '') && $option_data['option_type'] != 'C') {
  3846.                 continue;
  3847.             }
  3848.  
  3849.             // Update product options variants
  3850.             if (isset($v['modifier'])) {
  3851.                 $v['modifier'] = floatval($v['modifier']);
  3852.                 if (floatval($v['modifier']) > 0) {
  3853.                     $v['modifier'] = '+' . $v['modifier'];
  3854.                 }
  3855.             }
  3856.  
  3857.             if (isset($v['weight_modifier'])) {
  3858.                 $v['weight_modifier'] = floatval($v['weight_modifier']);
  3859.                 if (floatval($v['weight_modifier']) > 0) {
  3860.                     $v['weight_modifier'] = '+' . $v['weight_modifier'];
  3861.                 }
  3862.             }
  3863.  
  3864.             $v['option_id'] = $option_id;
  3865.  
  3866.             if (empty($v['variant_id']) || (!empty($v['variant_id']) && !db_get_field("SELECT variant_id FROM ?:product_option_variants WHERE variant_id = ?i", $v['variant_id']))) {
  3867.                 $v['variant_id'] = db_query("INSERT INTO ?:product_option_variants ?e", $v);
  3868.                 foreach ((array)Registry::get('languages') as $v['lang_code'] => $_v) {
  3869.                     db_query("INSERT INTO ?:product_option_variants_descriptions ?e", $v);
  3870.                 }
  3871.             } else {
  3872.                 db_query("UPDATE ?:product_option_variants SET ?u WHERE variant_id = ?i", $v, $v['variant_id']);
  3873.                 db_query("UPDATE ?:product_option_variants_descriptions SET ?u WHERE variant_id = ?i AND lang_code = ?s", $v, $v['variant_id'], $lang_code);
  3874.             }
  3875.  
  3876.             $var_ids[] = $v['variant_id'];
  3877.  
  3878.             if ($option_data['option_type'] == 'C') {
  3879.                 fn_delete_image_pairs($v['variant_id'], 'variant_image'); // force deletion of variant image for "checkbox" option
  3880.             } else {
  3881.                 $variant_images[$k] = $v['variant_id'];
  3882.             }
  3883.         }
  3884.        
  3885.         if ($option_data['option_type'] != 'C' && !empty($variant_images)) {
  3886.             fn_attach_image_pairs('variant_image', 'variant_image', 0, $lang_code, $variant_images);
  3887.         }
  3888.  
  3889.         // Delete obsolete variants
  3890.         $condition = !empty($var_ids) ? db_quote('AND variant_id NOT IN (?n)', $var_ids) : '';
  3891.         $deleted_variants = db_get_fields("SELECT variant_id FROM ?:product_option_variants WHERE option_id = ?i $condition", $option_id, $var_ids);
  3892.         if (!empty($deleted_variants)) {
  3893.             db_query("DELETE FROM ?:product_option_variants WHERE variant_id IN (?n)", $deleted_variants);
  3894.             db_query("DELETE FROM ?:product_option_variants_descriptions WHERE variant_id IN (?n)", $deleted_variants);
  3895.             foreach ($deleted_variants as $v_id) {
  3896.                 fn_delete_image_pairs($v_id, 'variant_image');
  3897.             }
  3898.         }
  3899.     }
  3900.     // Rebuild exceptions
  3901.     if (!empty($option_data['product_id'])) {
  3902.         fn_update_exceptions($option_data['product_id']);
  3903.     }
  3904.     return $option_id;
  3905. }
  3906.  
  3907. function fn_convert_weight($weight)
  3908. {
  3909.     if (Registry::get('config.localization.weight_unit')) {
  3910.         $g = Registry::get('settings.General.weight_symbol_grams');
  3911.         $weight = $weight * Registry::get('config.localization.weight_unit') / $g;
  3912.     }
  3913.     return sprintf('%01.2f', $weight);
  3914. }
  3915.  
  3916. function fn_convert_price($price)
  3917. {
  3918.     $currencies = Registry::get('currencies');
  3919.     return $price * $currencies[CART_PRIMARY_CURRENCY]['coefficient'];
  3920. }
  3921.  
  3922. function fn_get_products_sorting($simple_mode = true)
  3923. {
  3924.     $sorting = array(
  3925.         'position' => array('description' => fn_get_lang_var('default'), 'default_order' => 'asc'),
  3926.         'product' => array('description' => fn_get_lang_var('name'), 'default_order' => 'asc'),
  3927.         'price' => array('description' => fn_get_lang_var('price'), 'default_order' => 'asc'),
  3928.         'popularity' => array('description' => fn_get_lang_var('popularity'), 'default_order' => 'desc')
  3929.     );
  3930.    
  3931.     fn_set_hook('products_sorting', $sorting);
  3932.    
  3933.     if ($simple_mode) {
  3934.         foreach ($sorting as &$sort_item) {
  3935.             $sort_item = $sort_item['description'];
  3936.         }
  3937.     }
  3938.    
  3939.     return $sorting;
  3940. }
  3941.  
  3942. function fn_get_products_views($simple_mode = true, $active = false)
  3943. {
  3944.     //Registry::register_cache('products_views', array(), CACHE_LEVEL_STATIC);
  3945.    
  3946.     $active_layouts = Registry::get('settings.Appearance.default_products_layout_templates');
  3947.     if (!is_array($active_layouts)) {
  3948.         parse_str($active_layouts, $active_layouts);
  3949.     }
  3950.    
  3951.     if (!array_key_exists(Registry::get('settings.Appearance.default_products_layout'), $active_layouts)) {
  3952.         $active_layouts[Registry::get('settings.Appearance.default_products_layout')] = 'Y';
  3953.     }
  3954.    
  3955.     /*if (Registry::is_exist('products_views') == true && AREA != 'A') {
  3956.         $products_views = Registry::get('products_views');
  3957.        
  3958.         foreach ($products_views as &$view) {
  3959.             $view['title'] = fn_get_lang_var($view['title']);
  3960.         }
  3961.        
  3962.         if ($simple_mode) {
  3963.             $products_views = Registry::get('products_views');
  3964.            
  3965.             foreach ($products_views as $key => $value) {
  3966.                 $products_views[$key] = $value['title'];
  3967.             }
  3968.         }
  3969.        
  3970.         if ($active) {
  3971.             $products_views = array_intersect_key($products_views, $active_layouts);
  3972.         }
  3973.        
  3974.         return $products_views;
  3975.     }*/
  3976.  
  3977.     $products_views = array();
  3978.    
  3979.     $skin_name = Registry::get('settings.skin_name_customer');
  3980.    
  3981.     $skin_path = DIR_SKINS . $skin_name;
  3982.     $area = 'customer';
  3983.    
  3984.     fn_set_hook('get_skin_path', $area, $skin_path);
  3985.    
  3986.     // Get all available product_list_templates dirs
  3987.     $templates_path[] = $skin_path . '/customer/blocks/product_list_templates';
  3988.    
  3989.     foreach ((array)Registry::get('addons') as $addon_name => $data) {
  3990.         if ($data['status'] == 'A') {
  3991.             if (is_dir($skin_path . '/customer/addons/' . $addon_name . '/blocks/product_list_templates')) {
  3992.                 $templates_path[] = $skin_path . '/customer/addons/' . $addon_name . '/blocks/product_list_templates';
  3993.             }
  3994.         }
  3995.     }
  3996.    
  3997.     // Scan received directories and fill the "views" array
  3998.     foreach ($templates_path as &$path) {
  3999.         $view_templates = fn_get_dir_contents($path, false, true, 'tpl');
  4000.        
  4001.         if (!empty($view_templates)) {
  4002.             foreach ($view_templates as &$file) {
  4003.                 if ($file != '.' && $file != '..') {
  4004.                     preg_match("/(.*" . basename($skin_name) . "\/customer\/)(.*)/", $path, $matches);
  4005.                    
  4006.                     $_path = $matches[2]. '/' . $file;
  4007.                    
  4008.                     // Check if the template has inner description (like a "block manager")
  4009.                     $tempalte_description = fn_get_file_description($path . '/' . $file, 'template-description', true);
  4010.                    
  4011.                     $_title = substr($file, 0, -4);
  4012.                    
  4013.                     $products_views[$_title] = array(
  4014.                         'template' => $_path,
  4015.                         'title' => empty($tempalte_description) ? $_title : $tempalte_description,
  4016.                         'active' => array_key_exists($_title, $active_layouts)
  4017.                     );
  4018.                 }
  4019.             }
  4020.         }
  4021.     }
  4022.    
  4023.     //Registry::set('products_views',  $products_views);
  4024.    
  4025.     foreach ($products_views as &$view) {
  4026.         $view['title'] = fn_get_lang_var($view['title']);
  4027.     }
  4028.    
  4029.     if ($simple_mode) {
  4030.         foreach ($products_views as $key => $value) {
  4031.             $products_views[$key] = $value['title'];
  4032.         }
  4033.     }
  4034.  
  4035.     if ($active) {
  4036.         $products_views = array_intersect_key($products_views, $active_layouts);
  4037.     }
  4038.    
  4039.     return $products_views;
  4040. }
  4041.  
  4042. function fn_get_products_layout($params)
  4043. {
  4044.     if (!isset($_SESSION['products_layout'])) {
  4045.         $_SESSION['products_layout'] = Registry::get('settings.Appearance.save_selected_layout') == 'Y' ? array() : '';
  4046.     }
  4047.  
  4048.     $active_layouts = fn_get_products_views(false, true);
  4049.     $default_layout = Registry::get('settings.Appearance.default_products_layout');
  4050.  
  4051.     if (!empty($params['category_id'])) {
  4052.         $_layout = db_get_row("SELECT default_layout, selected_layouts FROM ?:categories WHERE category_id = ?i", $params['category_id']);
  4053.         $category_default_layout = $_layout['default_layout'];
  4054.         $category_layouts = unserialize($_layout['selected_layouts']);
  4055.         if (!empty($category_layouts)) {
  4056.             if (!empty($category_default_layout)) {
  4057.                 $default_layout = $category_default_layout;
  4058.             }
  4059.             $active_layouts = $category_layouts;
  4060.         }
  4061.         $ext_id = $params['category_id'];
  4062.     } else {
  4063.         $ext_id = 'search';
  4064.     }
  4065.  
  4066.     if (!empty($params['layout'])) {
  4067.         $layout = $params['layout'];
  4068.     } elseif (Registry::get('settings.Appearance.save_selected_layout') == 'Y' && !empty($_SESSION['products_layout'][$ext_id])) {
  4069.         $layout = $_SESSION['products_layout'][$ext_id];
  4070.     } elseif (Registry::get('settings.Appearance.save_selected_layout') == 'N' && !empty($_SESSION['products_layout'])) {
  4071.         $layout = $_SESSION['products_layout'];
  4072.     }
  4073.  
  4074.     $selected_layout = (!empty($layout) && !empty($active_layouts[$layout])) ? $layout : $default_layout;
  4075.  
  4076.     if (!empty($params['layout']) && $params['layout'] == $selected_layout) {
  4077.         if (Registry::get('settings.Appearance.save_selected_layout') == 'Y') {
  4078.             if (!is_array($_SESSION['products_layout'])) {
  4079.                 $_SESSION['products_layout'] = array();
  4080.             }
  4081.             $_SESSION['products_layout'][$ext_id] = $selected_layout;
  4082.         } else {
  4083.             $_SESSION['products_layout'] = $selected_layout;
  4084.         }
  4085.     }
  4086.  
  4087.     return $selected_layout;
  4088. }
  4089.  
  4090. function fn_get_categories_list($category_ids, $lang_code = CART_LANGUAGE)
  4091. {
  4092.     static $max_categories = 10;
  4093.     $c_names = array();
  4094.     if (!empty($category_ids)) {
  4095.         $c_ids = fn_explode(',', $category_ids);
  4096.         $tr_c_ids = array_slice($c_ids, 0, $max_categories);
  4097.         $c_names = fn_get_category_name($tr_c_ids, $lang_code);
  4098.         if (sizeof($tr_c_ids) < sizeof($c_ids)) {
  4099.             $c_names[] = '...';
  4100.         }
  4101.     } else {
  4102.         $c_names[] = fn_get_lang_var('all_categories');
  4103.     }
  4104.  
  4105.     return $c_names;
  4106. }
  4107.  
  4108. function fn_get_allowed_options_combination($options, $variants, $string, $iteration, $exceptions, $inventory_combinations)
  4109. {
  4110.     static $result = array();
  4111.     $combinations = array();
  4112.     foreach ($variants[$iteration] as $variant_id) {
  4113.         if (count($options) - 1 > $iteration) {
  4114.             $string[$iteration][$options[$iteration]] = $variant_id;
  4115.             list($_c, $is_result) = fn_get_allowed_options_combination($options, $variants, $string, $iteration + 1, $exceptions, $inventory_combinations);
  4116.             if ($is_result) {
  4117.                 return array($_c, $is_result);
  4118.             }
  4119.            
  4120.             $combinations = array_merge($combinations, $_c);
  4121.             unset($string[$iteration]);
  4122.         } else {
  4123.             $_combination = array();
  4124.             if (!empty($string)) {
  4125.                 foreach ($string as $val) {
  4126.                     foreach ($val as $opt => $var) {
  4127.                         $_combination[$opt] = $var;
  4128.                     }
  4129.                 }
  4130.             }
  4131.             $_combination[$options[$iteration]] = $variant_id;
  4132.             $combinations[] = $_combination;
  4133.            
  4134.             foreach ($combinations as $combination) {
  4135.                 $allowed = true;
  4136.                 foreach ($exceptions as $exception) {
  4137.                     $res = array_diff($exception, $combination);
  4138.                    
  4139.                     if (empty($res)) {
  4140.                         $allowed = false;
  4141.                         break;
  4142.                        
  4143.                     } else {
  4144.                         foreach ($res as $option_id => $variant_id) {
  4145.                             if ($variant_id == -1) {
  4146.                                 unset($res[$option_id]);
  4147.                             }
  4148.                         }
  4149.                        
  4150.                         if (empty($res)) {
  4151.                             $allowed = false;
  4152.                             break;
  4153.                         }
  4154.                     }
  4155.                 }
  4156.                
  4157.                 if ($allowed) {
  4158.                     $result = $combination;
  4159.                    
  4160.                     if (empty($inventory_combinations)) {
  4161.                         return array($result, true);
  4162.                     } else {
  4163.                         foreach ($inventory_combinations as $_icombination) {
  4164.                             $_res = array_diff($_icombination, $combination);
  4165.                             if (empty($_res)) {
  4166.                                 return array($result, true);
  4167.                             }
  4168.                         }
  4169.                     }
  4170.                 }
  4171.             }
  4172.            
  4173.             $combinations = array();
  4174.         }
  4175.     }
  4176.  
  4177.     if ($iteration == 0) {
  4178.         return array($result, true);
  4179.     } else {
  4180.         return array($combinations, false);
  4181.     }
  4182. }
  4183.  
  4184. function fn_apply_options_rules($product)
  4185. {
  4186.     /*  Options type:
  4187.             P - simultaneous/parallel
  4188.             S - sequential
  4189.     */
  4190.     // Check for the options and exceptions types
  4191.     if (!isset($product['options_type']) || !isset($product['exceptions_type'])) {
  4192.         $product = array_merge($product, db_get_row('SELECT options_type, exceptions_type FROM ?:products WHERE product_id = ?i', $product['product_id']));
  4193.     }
  4194.    
  4195.     // Get the selected options or get the default options
  4196.     $product['selected_options'] = empty($product['selected_options']) ? array() : $product['selected_options'];
  4197.     $product['options_update'] = ($product['options_type'] == 'S') ? true : false;
  4198.    
  4199.     // Conver the selected options text to the utf8 format
  4200.     if (!empty($product['product_options'])) {
  4201.         foreach ($product['product_options'] as $id => $option) {
  4202.             if (!empty($option['value'])) {
  4203.                 $product['product_options'][$id]['value'] = fn_unicode_to_utf8($option['value']);
  4204.             }
  4205.             if (!empty($product['selected_options'][$option['option_id']])) {
  4206.                 $product['selected_options'][$option['option_id']] = fn_unicode_to_utf8($product['selected_options'][$option['option_id']]);
  4207.             }
  4208.         }
  4209.     }
  4210.    
  4211.     $selected_options = &$product['selected_options'];
  4212.     $changed_option = empty($product['changed_option']) ? true : false;
  4213.    
  4214.     $simultaneous = array();
  4215.     $next = 0;
  4216.    
  4217.     foreach ($product['product_options'] as $_id => $option) {
  4218.         if (!in_array($option['option_type'], array('I', 'T', 'F'))) {
  4219.             $simultaneous[$next] = $option['option_id'];
  4220.             $next = $option['option_id'];
  4221.         }
  4222.        
  4223.         if (!empty($option['value'])) {
  4224.             $selected_options[$option['option_id']] = $option['value'];
  4225.         }
  4226.        
  4227.         if (!$changed_option && $product['changed_option'] == $option['option_id']) {
  4228.             $changed_option = true;
  4229.         }
  4230.        
  4231.         if (!empty($selected_options[$option['option_id']]) && ($selected_options[$option['option_id']] == 'checked' || $selected_options[$option['option_id']] == 'unchecked') && $option['option_type'] == 'C') {
  4232.             foreach ($option['variants'] as $variant) {
  4233.                 if (($variant['position'] == 0 && $selected_options[$option['option_id']] == 'unchecked') || ($variant['position'] == 1 && $selected_options[$option['option_id']] == 'checked')) {
  4234.                     $selected_options[$option['option_id']] = $variant['variant_id'];
  4235.                     if ($changed_option) {
  4236.                         $product['changed_option'] = $option['option_id'];
  4237.                     }
  4238.                 }
  4239.             }
  4240.         }
  4241.        
  4242.         // Check, if the product has any options modifiers
  4243.         if (!empty($product['product_options'][$_id]['variants'])) {
  4244.             foreach ($product['product_options'][$_id]['variants'] as $variant) {
  4245.                 if (!empty($variant['modifier']) && floatval($variant['modifier'])) {
  4246.                     $product['options_update'] = true;
  4247.                 }
  4248.             }
  4249.         }
  4250.     }
  4251.    
  4252.     if (!empty($product['changed_option']) && empty($selected_options[$product['changed_option']]) && $product['options_type'] == 'S') {
  4253.         $product['changed_option'] = array_search($product['changed_option'], $simultaneous);
  4254.         if ($product['changed_option'] == 0) {
  4255.             unset($product['changed_option']);
  4256.             $reset = true;
  4257.             if (!empty($selected_options)) {
  4258.                 foreach ($selected_options as $option_id => $variant_id) {
  4259.                     if (!isset($product['product_options'][$option_id]) || !in_array($product['product_options'][$option_id]['option_type'], array('I', 'T', 'F'))) {
  4260.                         unset($selected_options[$option_id]);
  4261.                     }
  4262.                 }
  4263.             }
  4264.         }
  4265.     }
  4266.    
  4267.     if (empty($selected_options) && $product['options_type'] == 'P') {
  4268.         $selected_options = fn_get_default_product_options($product['product_id'], true, $product);
  4269.     }
  4270.    
  4271.     if (empty($product['changed_option']) && isset($reset)) {
  4272.         $product['changed_option'] = '';
  4273.        
  4274.     } elseif (empty($product['changed_option'])) {
  4275.         end($selected_options);
  4276.         $product['changed_option'] = key($selected_options);
  4277.     }
  4278.    
  4279.     if ($product['options_type'] == 'S') {
  4280.         empty($product['changed_option']) ? $allow = 1 : $allow = 0;
  4281.        
  4282.         foreach ($product['product_options'] as $_id => $option) {
  4283.             $product['product_options'][$_id]['disabled'] = false;
  4284.            
  4285.             if (in_array($option['option_type'], array('I', 'T', 'F'))) {
  4286.                 continue;
  4287.             }
  4288.            
  4289.             $option_id = $option['option_id'];
  4290.            
  4291.             if ($allow >= 1) {
  4292.                 unset($selected_options[$option_id]);
  4293.                 $product['product_options'][$_id]['value'] = '';
  4294.             }
  4295.            
  4296.             if ($allow >= 2) {
  4297.                 $product['product_options'][$_id]['disabled'] = true;
  4298.                 continue;
  4299.             }
  4300.            
  4301.             if (empty($product['changed_option']) || (!empty($product['changed_option']) && $product['changed_option'] == $option_id) || $allow > 0) {
  4302.                 $allow++;
  4303.             }
  4304.         }
  4305.        
  4306.         $product['simultaneous'] = $simultaneous;
  4307.     }
  4308.    
  4309.     // Restore selected values
  4310.     if (!empty($selected_options)) {
  4311.         foreach ($product['product_options'] as $_id => $option) {
  4312.             if (isset($selected_options[$option['option_id']])) {
  4313.                 $product['product_options'][$_id]['value'] = $selected_options[$option['option_id']];
  4314.             }
  4315.         }
  4316.     }
  4317.    
  4318.     // Change price
  4319.     if (empty($product['modifiers_price'])) {
  4320.         $product['base_modifier'] = fn_apply_options_modifiers($selected_options, $product['base_price'], 'P');
  4321.         $old_price = $product['price'];
  4322.         $product['price'] = fn_apply_options_modifiers($selected_options, $product['price'], 'P');
  4323.        
  4324.         if (empty($product['original_price'])) {
  4325.             $product['original_price'] = $old_price;
  4326.         }
  4327.  
  4328.         $product['original_price'] = fn_apply_options_modifiers($selected_options, $product['original_price'], 'P');
  4329.         $product['modifiers_price'] = $product['price'] - $old_price;
  4330.     }
  4331.    
  4332.     if (!empty($product['list_price'])) {
  4333.         $product['list_price'] = fn_apply_options_modifiers($selected_options, $product['list_price'], 'P');
  4334.     }
  4335.    
  4336.     // Generate combination hash to get images. (Also, if the tracking with options, get amount and product code)
  4337.     $combination_hash = fn_generate_cart_id($product['product_id'], array('product_options' => $selected_options), true);
  4338.     $product['combination_hash'] = $combination_hash;
  4339.    
  4340.     // Change product code and amount
  4341.     if (!empty($product['tracking']) && $product['tracking'] == 'O') {
  4342.         $product['hide_stock_info'] = false;
  4343.         if ($product['options_type'] == 'S') {
  4344.             foreach ($product['product_options'] as $option_id => $option) {
  4345.                 if ($option['inventory'] == 'Y' && empty($product['selected_options'][$option_id])) {
  4346.                     $product['hide_stock_info'] = true;
  4347.                    
  4348.                     break;
  4349.                 }
  4350.             }
  4351.         }
  4352.        
  4353.         if (!$product['hide_stock_info']) {
  4354.             $combination = db_get_row("SELECT product_code, amount FROM ?:product_options_inventory WHERE combination_hash = ?i", $combination_hash);
  4355.            
  4356.             if (!empty($combination['product_code'])) {
  4357.                     $product['product_code'] = $combination['product_code'];
  4358.             }
  4359.            
  4360.             if (Registry::get('settings.General.inventory_tracking') == 'Y') {
  4361.                 if (isset($combination['amount'])) {
  4362.                         $product['inventory_amount'] = $combination['amount'];
  4363.                 } else {
  4364.                         $product['inventory_amount'] = $product['amount'] = 0;
  4365.                 }
  4366.             }
  4367.         }
  4368.     }
  4369.    
  4370.     if (!$product['options_update']) {
  4371.         $product['options_update'] = db_get_field('SELECT COUNT(*) FROM ?:product_options_inventory WHERE product_id = ?i', $product['product_id']);
  4372.     }
  4373.    
  4374.     fn_set_hook('apply_options_rules', $product);
  4375.  
  4376.     return $product;
  4377. }
  4378. function fn_apply_exceptions_rules($product)
  4379. {
  4380.     /*  Exceptions type:
  4381.             A - Allowed
  4382.             F - Forbidden
  4383.     */
  4384.     if (empty($product['selected_options']) && $product['options_type'] == 'S') {
  4385.         return $product;
  4386.     }
  4387.    
  4388.     $exceptions = fn_get_product_exceptions($product['product_id'], true);
  4389.    
  4390.     if (empty($exceptions)) {
  4391.         return $product;
  4392.     }
  4393.    
  4394.     $product['options_update'] = true;
  4395.     $options = array();
  4396.     $disabled = array();
  4397.    
  4398.     if (Registry::get('settings.General.exception_style') == 'warning') {
  4399.         $result = fn_is_allowed_options_exceptions($exceptions, $product['selected_options'], $product['options_type'], $product['exceptions_type']);
  4400.        
  4401.         if (!$result) {
  4402.             $product['show_exception_warning'] = 'Y';
  4403.         }
  4404.        
  4405.         return $product;
  4406.     }
  4407.    
  4408.     foreach ($exceptions as $exception_id => $exception) {
  4409.         if ($product['options_type'] == 'S') {
  4410.             // Sequential exceptions type
  4411.             $_selected = array();
  4412.            
  4413.             foreach ($product['selected_options'] as $option_id => $variant_id) {
  4414.                 $disable = true;
  4415.                 $full = array();
  4416.                
  4417.                 $_selected[$option_id] = $variant_id;
  4418.                 $elms = array_diff($exception, $_selected);
  4419.                 $_exception = $exception;
  4420.                
  4421.                 if (!empty($elms)) {
  4422.                     foreach ($elms as $opt_id => $var_id) {
  4423.                         if ($var_id != -2 && $var_id != -1) {
  4424.                             $disable = false;
  4425.                         }
  4426.                         if ($var_id == -1) {
  4427.                             $full[$opt_id] = $var_id;
  4428.                         }
  4429.                         if (($product['exceptions_type'] == 'A' && $var_id == -1 && isset($_selected[$opt_id])) || ($product['exceptions_type'] != 'A' && $var_id == -1)) {
  4430.                             unset($elms[$opt_id]);
  4431.                             if ($product['exceptions_type'] != 'A') {
  4432.                                 unset($_exception[$opt_id]);
  4433.                             }
  4434.                         }
  4435.                     }
  4436.                 }
  4437.                
  4438.                 if ($disable && !empty($elms) && count($elms) != count($full)) {
  4439.                     $vars = array_diff($elms, $full);
  4440.                     $disable = false;
  4441.                     foreach ($vars as $var) {
  4442.                         if ($var != -1) {
  4443.                             $disable = true;
  4444.                         }
  4445.                     }
  4446.                 }
  4447.                
  4448.                 if ($disable && !empty($elms) && count($elms) != count($full)) {
  4449.                     foreach ($elms as $opt_id => $var_id) {
  4450.                         $disabled[$opt_id] = true;
  4451.                     }
  4452.                 } elseif ($disable && !empty($full)) {
  4453.                     foreach ($full as $opt_id => $var_id) {
  4454.                         $options[$opt_id]['any'] = true;
  4455.                     }
  4456.                 } elseif (count($elms) == 1 && reset($elms) == -2) {
  4457.                     $disabled[key($elms)] = true;
  4458.                 } elseif (($product['exceptions_type'] == 'A' && count($elms) + count($_selected) != count($_exception)) || ($product['exceptions_type'] == 'F' && count($elms) != 1)) {
  4459.                     continue;
  4460.                 }
  4461.                
  4462.                 if (!isset($product['simultaneous'][$option_id]) || (isset($product['simultaneous'][$option_id]) && !isset($elms[$product['simultaneous'][$option_id]]))) {
  4463.                     continue;
  4464.                 }
  4465.                
  4466.                 $elms[$product['simultaneous'][$option_id]] = ($elms[$product['simultaneous'][$option_id]] == -1) ? 'any' : $elms[$product['simultaneous'][$option_id]];
  4467.                 if (isset($product['simultaneous'][$option_id]) && !empty($elms) && isset($elms[$product['simultaneous'][$option_id]])) {
  4468.                     $options[$product['simultaneous'][$option_id]][$elms[$product['simultaneous'][$option_id]]] = true;
  4469.                 }
  4470.             }
  4471.         } else {
  4472.             // Parallel exceptions type
  4473.             $disable = true;
  4474.             $full = array();
  4475.            
  4476.             $elms = array_diff($exception, $product['selected_options']);
  4477.            
  4478.             if (!empty($elms)) {
  4479.                 foreach ($elms as $opt_id => $var_id) {
  4480.                     if ($var_id != -2 && $var_id != -1) {
  4481.                         $disable = false;
  4482.                     }
  4483.                    
  4484.                     if ($var_id == -1) {
  4485.                         $full[$opt_id] = $var_id;
  4486.                         unset($elms[$opt_id]);
  4487.                     }
  4488.                 }
  4489.             }
  4490.            
  4491.             if ($disable && !empty($elms)) {
  4492.                 foreach ($elms as $opt_id => $var_id) {
  4493.                     $disabled[$opt_id] = true;
  4494.                 }
  4495.             } elseif ($disable && !empty($full)) {
  4496.                 foreach ($full as $opt_id => $var_id) {
  4497.                     $options[$opt_id]['any'] = true;
  4498.                 }
  4499.             } elseif (count($elms) == 1 && reset($elms) == -2) {
  4500.                 $disabled[key($elms)] = true;
  4501.             } elseif (count($elms) == 1 && !in_array(reset($elms), $product['selected_options'])) {
  4502.                 list($option_id, $variant_id) = array(key($elms), reset($elms));
  4503.                 $options[$option_id][$variant_id] = true;
  4504.             }
  4505.         }
  4506.     }
  4507.    
  4508.     if ($product['exceptions_type'] == 'A' && $product['options_type'] == 'P') {
  4509.         foreach ($product['selected_options'] as $option_id => $variant_id) {
  4510.             $options[$option_id][$variant_id] = true;
  4511.         }
  4512.     }
  4513.    
  4514.     $first_elm = array();
  4515.     $clear_variants = false;
  4516.    
  4517.     foreach ($product['product_options'] as $_id => &$option) {
  4518.         $option_id = $option['option_id'];
  4519.        
  4520.         if (!in_array($option['option_type'], array('I', 'T', 'F')) && empty($first_elm)) {
  4521.             $first_elm = $product['product_options'][$_id];
  4522.         }
  4523.        
  4524.         if (isset($disabled[$option_id])) {
  4525.             $option['disabled'] = true;
  4526.             $option['not_required'] = true;
  4527.         }
  4528.        
  4529.         if (($product['options_type'] == 'S' && $option['option_id'] == $first_elm['option_id']) || (in_array($option['option_type'], array('I', 'T', 'F')))) {
  4530.             continue;
  4531.         }
  4532.        
  4533.         if ($product['options_type'] == 'S' && $option['disabled']) {
  4534.             if ($clear_variants) {
  4535.                 $option['variants'] = array();
  4536.             }
  4537.            
  4538.             continue;
  4539.         }
  4540.        
  4541.         if (!empty($option['variants']) && $option['option_type'] != 'C') { // Exclude "C"heckboxes
  4542.             foreach ($option['variants'] as $variant_id => $variant) {
  4543.                 if ($product['exceptions_type'] == 'A') {
  4544.                     // Allowed combinations
  4545.                     if (empty($options[$option_id][$variant_id]) && !isset($options[$option_id]['any'])) {
  4546.                         unset($option['variants'][$variant_id]);
  4547.                     }
  4548.                 } else {
  4549.                     // Forbidden combinations
  4550.                     if (!empty($options[$option_id][$variant_id]) || isset($options[$option_id]['any'])) {
  4551.                         unset($option['variants'][$variant_id]);
  4552.                     }
  4553.                 }
  4554.             }
  4555.            
  4556.             if (!in_array($option['value'], array_keys($option['variants']))) {
  4557.                 $option['value'] = '';
  4558.             }
  4559.         }
  4560.        
  4561.         if (empty($option['variants'])) {
  4562.             $clear_variants = true;
  4563.         }
  4564.     }
  4565.    
  4566.     fn_set_hook('apply_exceptions', $product, $exceptions);
  4567.    
  4568.     return $product;
  4569. }
  4570.  
  4571. function fn_is_allowed_options_exceptions($exceptions, $options, $o_type = 'P', $e_type = 'F')
  4572. {
  4573.     foreach ($options as $option_id => $variant_id) {
  4574.         if (empty($variant_id)) {
  4575.             unset($options[$option_id]);
  4576.         }
  4577.     }
  4578.    
  4579.     if ($e_type == 'A' && empty($options)) {
  4580.         return true;
  4581.     }
  4582.    
  4583.     $in_exception = false;
  4584.     foreach ($exceptions as $exception) {
  4585.         foreach ($options as $option_id => $variant_id) {
  4586.             if (!isset($exception[$option_id])) {
  4587.                 unset($options[$option_id]);
  4588.             }
  4589.         }
  4590.        
  4591.         if (count($exception) != count($options)) {
  4592.             continue;
  4593.         }
  4594.        
  4595.         $in_exception = true;
  4596.         $diff = array_diff($exception, $options);
  4597.        
  4598.         if (!empty($diff)) {
  4599.             foreach ($diff as $option_id => $variant_id) {
  4600.                 if ($variant_id == -1) {
  4601.                     unset($diff[$option_id]);
  4602.                 }
  4603.             }
  4604.         }
  4605.        
  4606.         if (empty($diff) && $e_type == 'A') {
  4607.             return true;
  4608.         } elseif (empty($diff)) {
  4609.             return false;
  4610.         }
  4611.     }
  4612.    
  4613.     if ($in_exception && $e_type == 'A') {
  4614.         return false;
  4615.     }
  4616.    
  4617.     return true;
  4618. }
  4619. function fn_get_product_details_views($get_default = 'default')
  4620. {
  4621.     $product_details_views = array();
  4622.  
  4623.     if ($get_default == 'category') {
  4624.    
  4625.         $parent_layout = Registry::get('settings.Appearance.default_product_details_layout');
  4626.         $product_details_views['default'] = str_replace('[default]', fn_get_lang_var($parent_layout), fn_get_lang_var('default_product_details_layout'));
  4627.        
  4628.     } elseif ($get_default != 'default') {
  4629.    
  4630.         $parent_layout = db_get_field("SELECT c.product_details_layout FROM ?:products_categories as pc LEFT JOIN ?:categories as c ON pc.category_id = c.category_id WHERE pc.product_id = ?i AND pc.link_type = 'M'", $get_default);
  4631.         if (empty($parent_layout) || $parent_layout == 'default') {
  4632.             $parent_layout = Registry::get('settings.Appearance.default_product_details_layout');
  4633.         }
  4634.         $product_details_views['default'] = str_replace('[default]', fn_get_lang_var($parent_layout), fn_get_lang_var('default_product_details_layout'));
  4635.     }
  4636.    
  4637.     $skin_name = Registry::get('settings.skin_name_customer');
  4638.    
  4639.     // Get all available product_templates dirs
  4640.     $templates_path[] = DIR_SKINS . $skin_name . '/customer/blocks/product_templates';
  4641.    
  4642.     foreach ((array)Registry::get('addons') as $addon_name => $data) {
  4643.         if ($data['status'] == 'A') {
  4644.             if (is_dir(DIR_SKINS . $skin_name . '/customer/addons/' . $addon_name . '/blocks/product_templates')) {
  4645.                 $templates_path[] = DIR_SKINS . $skin_name . '/customer/addons/' . $addon_name . '/blocks/product_templates';
  4646.             }
  4647.         }
  4648.     }
  4649.    
  4650.     // Scan received directories and fill the "views" array
  4651.     foreach ($templates_path as &$path) {
  4652.         $view_templates = fn_get_dir_contents($path, false, true, 'tpl');
  4653.        
  4654.         if (!empty($view_templates)) {
  4655.             foreach ($view_templates as &$file) {
  4656.                 if ($file != '.' && $file != '..') {
  4657.                     preg_match("/(.*$skin_name\/customer\/)(.*)/", $path, $matches);
  4658.                    
  4659.                     $_path = $matches[2]. '/' . $file;
  4660.                    
  4661.                     // Check if the template has inner description (like a "block manager")
  4662.                     $fd = fopen($path . '/' . $file, 'r');
  4663.                     $counter = 1;
  4664.                     $_descr = '';
  4665.                    
  4666.                     while (($s = fgets($fd, 4096)) && ($counter < 3)) {
  4667.                         preg_match('/\{\*\* template-description:(\w+) \*\*\}/i', $s, $matches);
  4668.                         if (!empty($matches[1])) {
  4669.                             $_descr = $matches[1];
  4670.                             break;
  4671.                         }
  4672.                     }
  4673.                    
  4674.                     fclose($fd);
  4675.                    
  4676.                     $_title = empty($_descr) ? substr($file, 0, -4) : $_descr;
  4677.                    
  4678.                     $product_details_views[$_title] = fn_get_lang_var($_title);
  4679.                 }
  4680.             }
  4681.         }
  4682.     }
  4683.     return $product_details_views;
  4684. }
  4685.  
  4686. function fn_get_product_details_layout($product_id)
  4687. {
  4688.     $selected_layout = Registry::get('settings.Appearance.default_product_details_layout');
  4689.    
  4690.     if (!empty($product_id)) {
  4691.    
  4692.         $selected_layout = db_get_field("SELECT details_layout FROM ?:products WHERE product_id = ?i", $product_id);
  4693.  
  4694.         if (empty($selected_layout) || $selected_layout == 'default') {
  4695.             $selected_layout = db_get_field("SELECT c.product_details_layout FROM ?:products_categories as pc LEFT JOIN ?:categories as c ON pc.category_id = c.category_id WHERE pc.product_id = ?i AND pc.link_type = 'M'", $product_id);
  4696.         }
  4697.        
  4698.         if (empty($selected_layout) || $selected_layout == 'default') {
  4699.             $selected_layout = Registry::get('settings.Appearance.default_product_details_layout');
  4700.         }
  4701.     }
  4702.    
  4703.     $skin_name = Registry::get('settings.skin_name_customer');
  4704.  
  4705.     $skin_path = DIR_SKINS . $skin_name;
  4706.     $area = 'customer';
  4707.    
  4708.     fn_set_hook('get_skin_path', $area, $skin_path);
  4709.  
  4710.     // Get all available product_templates dirs
  4711.     $template_path = $skin_path . '/customer/blocks/product_templates/' . $selected_layout  . ".tpl";
  4712.  
  4713.     if (is_file($template_path)) {
  4714.         return $template_path;
  4715.     }
  4716.  
  4717.     foreach ((array)Registry::get('addons') as $addon_name => $data) {
  4718.         if ($data['status'] == 'A') {
  4719.             $template_path = $skin_path . '/customer/addons/' . $addon_name . '/blocks/product_templates/' . $selected_layout  . ".tpl";
  4720.             if (is_file($template_path)) {
  4721.                 return $template_path;
  4722.             }
  4723.         }
  4724.     }
  4725.  
  4726.     return $skin_path . '/customer/blocks/product_templates/' . 'default_template.tpl';
  4727. }
  4728.  
  4729. ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement