Index: modules/Extra_Fields/func.php
--- modules/Extra_Fields/func.php   2011-09-27 11:52:35.000000000 +0400
+++ modules/Extra_Fields/func.php   2011-09-27 11:52:27.000000000 +0400
@@ -62,7 +62,7 @@

     if (zerolen($sname))
         return 'empty';
-    if (!preg_match("/^[\d\w_]+$/S", $sname))
+    if (!preg_match("/^[\d\w_:]+$/S", $sname))
         return 'format';
     if (in_array(strtoupper($sname), array('PRODUCTID', 'PRODUCTCODE', 'PRODUCT')))
         return 'name';
Index: modules/Froogle/froogle.php
--- modules/Froogle/froogle.php	2011-10-19 23:18:40.000000000 +0400
+++ modules/Froogle/froogle.php	2011-10-19 23:20:40.000000000 +0400
@@ -49,6 +49,7 @@
 
 define('FROOGLE_TAIL', '...');
 define('FROOGLE_TAIL_LEN', strlen(constant('FROOGLE_TAIL')));
+set_include_path($xcart_dir . XC_DS . 'include' .XC_DS. 'lib' .XC_DS. 'PEAR');
 
 x_session_register('store_froogle_lng');
 
@@ -75,7 +76,73 @@
     return $str;
 }
 
+/**
+ * Get additional attributes from special named extra fields
+ */
+function func_froogle_get_additional_attributes($get_ids = false)
+{
+    global $active_modules, $sql_tbl, $froogle_additional_attributes;
+    static $result = array();
+
+    $md5_args = $get_ids;
+    if (isset($result[$md5_args])) {
+        return $result[$md5_args];
+    }
+
+    $additional_attributes = array();
+
+    if (!empty($active_modules["Extra_Fields"])) {
+        $field = $get_ids ? 'fieldid' : 'DISTINCT service_name';
+        $additional_attributes = func_query_column("
+            SELECT $field 
+            FROM $sql_tbl[extra_fields] WHERE service_name IN ('" . implode("','", $froogle_additional_attributes) . "')
+            ORDER BY orderby; 
+        ");
+    }
+
+    $result[$md5_args] = $additional_attributes;
+    assert('is_array($additional_attributes) /*return Func_froogle_get_additional_attributes*/');
+    return $additional_attributes;
+}
+
+
+/**
+ * Get additional attributes for product
+ */
+function func_froogle_get_product_additional_attributes($additional_attributes, $pid, $delim="\t")
+{
+    global $sql_tbl;
+
+    $product_additional_attributes = '';
+    if (!empty($additional_attributes)) {
+        if (!defined('X_PHP5x_COMPAT'))
+            require_once 'Compat/Function/array_combine.php';
+
+        $additional_attributes_ids = func_froogle_get_additional_attributes('get_ids');
+        $additional_attributes_cond = "'".implode("','",$additional_attributes_ids)."'";
+
+        $product_additional_attributes = func_query_hash("
+        SELECT $sql_tbl[extra_fields].service_name, $sql_tbl[extra_field_values].value
+          FROM $sql_tbl[extra_fields]
+         INNER JOIN $sql_tbl[extra_field_values] USING(fieldid)
+        WHERE
+           $sql_tbl[extra_field_values].fieldid IN ($additional_attributes_cond)
+           AND $sql_tbl[extra_field_values].productid = '$pid'", "service_name", false, true);
+
+        
+        $additional_attributes = array_combine($additional_attributes, array_fill(0,count($additional_attributes),''));    
+        $product_additional_attributes = array_merge($additional_attributes, $product_additional_attributes);
+
+        assert('count($additional_attributes) == count($product_additional_attributes)');
+    }
+
+    return $product_additional_attributes;
+}
+
+/*
 x_load('backoffice','files','taxes');
+*/
+x_load('backoffice','taxes','product');
 
 set_time_limit(0);
 
@@ -115,11 +182,74 @@
         // Full header:
         // title\tdescription\tlink\timage_link\tid\texpiration_date\tlabel\tprice\tprice_type\tcurrency\tpayment_accepted\tpayment_notes\tquantity\tbrand\tupc\tisbn\tmemory\tprocessor_speed\tmodel_number\tsize\tweight\tcondition\tcolor\tactor\tartist\tauthor\tformat\tproduct_type\tlocation
 
+/* OLD CODE
         fputs($fp, "title\tdescription\tlink\timage_link\tid\tprice\tcurrency\tpayment_accepted\tpayment_notes\tquantity\tweight\texpiration_date\tbrand\tcondition\tproduct_type\n");
+*/
+
+        // Define header for basic attributes
+        $file_header = 
+            /* Basic Product Information */
+            "id\t" .
+            "item_group_id\t" .             // for Variant Products only
+            "title\t" .
+            "description\t" .
+            "product_type\t" .
+            "link\t" .
+            "image_link\t" .
+            //"additional_image_link\t" .   // Additional URLs of images of the item
+            "condition\t" .
+            /* Availability & Price */
+            "availability\t" .
+            "price\t" . 
+            //"currency\t" .                // combined with price. retired?
+            /* Tax & Shipping */
+            //"shipping\t" .                // provided in Google Merchant Center
+            //"tax\t" .                     // provided in Google Merchant Center
+            "shipping_weight\t" .
+            /* Additional attributes - recommended */
+            "expiration_date\t" .
+            /* Unique Product Identifiers */
+            "brand"
+        ;
+
+        /*
+        * Add a new value below to use the extra_field with the respective service_name for froogle export 
+        */
+            $froogle_additional_attributes = array(
+                /* Google's category of item */
+                "google_product_category",
+                /* Unique Product Identifiers */
+                "gtin", 
+                "mpn", 
+                "upc", 
+                "ean", 
+                "jan", 
+                "isbn",
+                /* Shared by Apparel and Variants */
+                "color",
+                "size",
+                /* Variant Products */
+                "material",
+                "pattern",
+                /* Apparel Products */
+                "gender",
+                "age_group",
+                /* Custom attributes */
+                "c:custom_attr",
+            );
+
+            // Define header for additional attributes
+            $additional_attributes = func_froogle_get_additional_attributes();
+            foreach ($additional_attributes as $k=>$v) {
+                $file_header .= "\t$v";
+            }
+
+        fputs($fp, $file_header."\n");
 
         $where = '';
         $fields = '';
         $joins = '';
+        $variantid = '"variantid",'; // if (empty($active_modules['Product_Options']))
 
         if ($config['General']['show_outofstock_products'] != 'Y') {
             if (!empty($active_modules['Product_Options'])) {
@@ -129,23 +259,133 @@
             }
         }
 
+/* OLD CODE
         $joins .= " INNER JOIN $sql_tbl[quick_prices] ON $sql_tbl[quick_prices].productid = $sql_tbl[products].productid AND $sql_tbl[quick_prices].membershipid = '0'";
+*/
+        $joins .= "
+            INNER JOIN $sql_tbl[pricing]
+                ON $sql_tbl[pricing].productid = $sql_tbl[products].productid AND $sql_tbl[pricing].membershipid = '0'";
+
+        $group_by = "GROUP BY $sql_tbl[products].productid";
+
         if (!empty($active_modules['Product_Options'])) {
+            $variantid = "$sql_tbl[variants].variantid,";
+/* OLD CODE
             $joins .= " LEFT JOIN $sql_tbl[variants] ON $sql_tbl[variants].productid = $sql_tbl[products].productid AND $sql_tbl[quick_prices].variantid = $sql_tbl[variants].variantid";
             $fields .= ", IFNULL($sql_tbl[variants].productcode, $sql_tbl[products].productcode) as productcode, IFNULL($sql_tbl[variants].avail, $sql_tbl[products].avail) as avail, IFNULL($sql_tbl[variants].weight, $sql_tbl[products].weight) as weight";
+
+*/
+            $joins .= "
+                LEFT JOIN $sql_tbl[variants]
+                    ON $sql_tbl[variants].productid = $sql_tbl[products].productid AND $sql_tbl[pricing].variantid = $sql_tbl[variants].variantid";
+            $fields .= ",
+                IFNULL($sql_tbl[variants].productcode, $sql_tbl[products].productcode) as productcode,
+                IFNULL($sql_tbl[variants].avail, $sql_tbl[products].avail) as avail,
+                IFNULL($sql_tbl[variants].weight, $sql_tbl[products].weight) as weight";
+            // select SKU for variant
+            $group_by .= ",
+                IFNULL($sql_tbl[variants].productcode, $sql_tbl[products].productcode)";
+            // exclude main product entry from the query result
+            $where .= "
+                AND NOT (xcart_variants.variantid = '' AND xcart_quick_prices.variantid <> '')";
         }
 
         if ($froogle_lng) {
+/* OLD CODE
             $joins .= " LEFT JOIN $sql_tbl[products_lng] ON $sql_tbl[products].productid = $sql_tbl[products_lng].productid AND $sql_tbl[products_lng].code = '$froogle_lng'";
             $fields .= ", IF($sql_tbl[products_lng].product != '', $sql_tbl[products_lng].product, $sql_tbl[products].product) as product, IF($sql_tbl[products_lng].descr != '', $sql_tbl[products_lng].descr, $sql_tbl[products].descr) as descr, IF($sql_tbl[products_lng].fulldescr != '', $sql_tbl[products_lng].fulldescr, $sql_tbl[products].fulldescr) as fulldescr";
+*/
+            $joins .= "
+                LEFT JOIN $sql_tbl[products_lng]
+                    ON $sql_tbl[products].productid = $sql_tbl[products_lng].productid AND $sql_tbl[products_lng].code = '$froogle_lng'";
+            $fields .= ",
+                IF($sql_tbl[products_lng].product != '', $sql_tbl[products_lng].product, $sql_tbl[products].product) as product, IF($sql_tbl[products_lng].descr != '', $sql_tbl[products_lng].descr, $sql_tbl[products].descr) as descr, IF($sql_tbl[products_lng].fulldescr != '', $sql_tbl[products_lng].fulldescr, $sql_tbl[products].fulldescr) as fulldescr";
         }
 
         if (!empty($active_modules['Manufacturers'])) {
+/* OLD CODE
             $fields .= ", IF ($sql_tbl[manufacturers_lng].manufacturer != '', $sql_tbl[manufacturers_lng].manufacturer, $sql_tbl[manufacturers].manufacturer) as manufacturer";
             $joins .= " LEFT JOIN $sql_tbl[manufacturers] ON $sql_tbl[products].manufacturerid = $sql_tbl[manufacturers].manufacturerid LEFT JOIN $sql_tbl[manufacturers_lng] ON $sql_tbl[products].manufacturerid = $sql_tbl[manufacturers_lng].manufacturerid AND $sql_tbl[manufacturers_lng].code = '$froogle_lng'";
+*/
+           $fields .= ",
+                IF ($sql_tbl[manufacturers_lng].manufacturer != '', $sql_tbl[manufacturers_lng].manufacturer, $sql_tbl[manufacturers].manufacturer) as manufacturer";
+            $joins .= "
+                LEFT JOIN $sql_tbl[manufacturers]
+                    ON $sql_tbl[products].manufacturerid = $sql_tbl[manufacturers].manufacturerid
+                LEFT JOIN $sql_tbl[manufacturers_lng]
+                    ON $sql_tbl[products].manufacturerid = $sql_tbl[manufacturers_lng].manufacturerid AND $sql_tbl[manufacturers_lng].code = '$froogle_lng'";
         }
 
+/* OLD CODE
         $products = db_query("SELECT $sql_tbl[products].*, $sql_tbl[categories].categoryid, $sql_tbl[pricing].price, $sql_tbl[images_T].image_path $fields FROM ($sql_tbl[categories], $sql_tbl[products_categories], $sql_tbl[pricing], $sql_tbl[products]) LEFT JOIN $sql_tbl[images_T] ON $sql_tbl[products].productid = $sql_tbl[images_T].id $joins WHERE $sql_tbl[products].productid = $sql_tbl[products_categories].productid AND $sql_tbl[products_categories].categoryid = $sql_tbl[categories].categoryid AND $sql_tbl[pricing].priceid = $sql_tbl[quick_prices].priceid AND $sql_tbl[products].forsale = 'Y' AND $sql_tbl[categories].avail = 'Y' $where GROUP BY $sql_tbl[products].productid HAVING (price > '0' OR $sql_tbl[products].product_type = 'C') ORDER BY $sql_tbl[products].product");
+*/
+
+/*
+        // original query formatted
+        $query = "
+            SELECT
+                $sql_tbl[products].*,
+                $sql_tbl[categories].categoryid,
+                $sql_tbl[pricing].price,
+                $sql_tbl[images_T].image_path
+                $fields
+            FROM (
+                $sql_tbl[categories],
+                $sql_tbl[products_categories],
+                $sql_tbl[pricing],
+                $sql_tbl[products]
+            )
+            LEFT JOIN $sql_tbl[images_T]
+                ON $sql_tbl[products].productid = $sql_tbl[images_T].id
+            $joins
+            WHERE
+                $sql_tbl[products].productid = $sql_tbl[products_categories].productid
+                AND $sql_tbl[products_categories].categoryid = $sql_tbl[categories].categoryid
+                AND $sql_tbl[pricing].priceid = $sql_tbl[quick_prices].priceid
+                AND $sql_tbl[products].forsale = 'Y'
+                AND $sql_tbl[categories].avail = 'Y'
+                $where
+            GROUP BY $sql_tbl[products].productid
+            HAVING (
+                price > '0'
+                OR $sql_tbl[products].product_type = 'C'
+            )
+            ORDER BY $sql_tbl[products].product
+        ";
+*/
+        $query = "
+            SELECT
+                $variantid
+                $sql_tbl[products].productcode as main_productcode,
+                $sql_tbl[products].*,
+                $sql_tbl[categories].categoryid,
+                $sql_tbl[pricing].price
+                $fields
+            FROM (
+                $sql_tbl[categories],
+                $sql_tbl[products_categories],
+                $sql_tbl[quick_prices],
+                $sql_tbl[products]
+            )
+            $joins
+            WHERE
+                $sql_tbl[products].productid = $sql_tbl[products_categories].productid
+                AND $sql_tbl[products_categories].categoryid = $sql_tbl[categories].categoryid
+                AND $sql_tbl[quick_prices].productid = $sql_tbl[products].productid
+                AND $sql_tbl[products].forsale = 'Y'
+                AND $sql_tbl[categories].avail = 'Y'
+                $where
+            $group_by
+            HAVING (
+                /* limit to 1 product for testing */
+                /*xcart_products.productid = '16129' AND*/
+                $sql_tbl[pricing].price > '0'
+                OR $sql_tbl[products].product_type = 'C'
+             )
+             ORDER BY $sql_tbl[products].product
+        ";
+
+        $products = db_query($query);
 
         x_load('category');
 
@@ -153,6 +393,81 @@
 
         while ($product = db_fetch_array($products)) {
 
+            // Define product additional attributes, and combine them with variants data
+
+            $product_additional_attributes = func_froogle_get_product_additional_attributes($additional_attributes, $product['productid']);
+
+if (is_array($product_additional_attributes) && !empty($product_additional_attributes)) {            
+
+            if (
+                !empty($active_modules['Product_Options']) &&
+                !empty($product['variantid'])
+            ) {
+                // Get additional data for product variant
+                /* query sample
+                SELECT
+                    IF(xcart_product_options_lng.option_name != '', xcart_product_options_lng.option_name, xcart_class_options.option_name) as option_name,
+                    IF(xcart_class_lng.class != '', xcart_class_lng.class, xcart_classes.class) as class
+                    FROM (xcart_variant_items)
+                    LEFT JOIN xcart_class_options
+                        ON xcart_class_options.optionid = xcart_variant_items.optionid
+                    LEFT JOIN xcart_classes
+                        ON xcart_classes.classid = xcart_class_options.classid
+                    LEFT JOIN xcart_product_options_lng
+                        ON xcart_class_options.optionid = xcart_product_options_lng.optionid AND xcart_product_options_lng.code = 'en'
+                    LEFT JOIN xcart_class_lng
+                        ON xcart_classes.classid = xcart_class_lng.classid AND xcart_class_lng.code = 'en'
+                    WHERE xcart_variant_items.variantid = '377'
+                */
+
+                $joins = "";
+                $fields = "";
+
+                $joins .= "
+                    LEFT JOIN $sql_tbl[product_options_lng]
+                        ON $sql_tbl[class_options].optionid = $sql_tbl[product_options_lng].optionid AND $sql_tbl[product_options_lng].code = '$froogle_lng'
+                    LEFT JOIN $sql_tbl[class_lng]
+                        ON $sql_tbl[classes].classid = $sql_tbl[class_lng].classid AND $sql_tbl[class_lng].code = '$froogle_lng'
+                ";
+                $fields .= "
+                    IF(xcart_product_options_lng.option_name != '', xcart_product_options_lng.option_name, xcart_class_options.option_name) as option_name,
+                    IF(xcart_class_lng.class != '', xcart_class_lng.class, xcart_classes.class) as class
+                ";
+
+                $query = "
+                    SELECT
+                        $fields
+                    FROM ($sql_tbl[variant_items])
+                    LEFT JOIN $sql_tbl[class_options]
+                        ON $sql_tbl[class_options].optionid = $sql_tbl[variant_items].optionid
+                    LEFT JOIN $sql_tbl[classes]
+                        ON $sql_tbl[classes].classid = $sql_tbl[class_options].classid
+                    $joins
+                    WHERE $sql_tbl[variant_items].variantid = '$product[variantid]'
+                ";
+
+                $raw_product_options_data = func_query($query);
+
+                /* structure of $raw_product_options_data array
+                    Array
+                        (
+                            [option_name] => XXL
+                            [class] => Size
+                        )
+                 */                
+                $product_options_data = array();
+                foreach ($raw_product_options_data as $k => $v) {
+                    if (array_key_exists(strtolower($v['class']), $product_additional_attributes)) {
+                        $product_additional_attributes[strtolower($v['class'])] = $v['option_name'];
+                    }
+                }
+                unset($raw_product_options_data);
+            }
+
+            $product_additional_attributes = "\t" . implode("\t", $product_additional_attributes);
+
+} // if (is_array($product_additional_attributes) && !empty($product_additional_attributes))
+
             // Define product category path
 
             $catids = func_get_category_path($product['categoryid']);
@@ -181,6 +496,7 @@
                 $product['descr'] = $product['fulldescr'];
 
             // Define product image
+/* OLD CODE
             $tmp = func_query_first_cell("SELECT image_path FROM $sql_tbl[images_P] WHERE $sql_tbl[images_P].id = '$product[productid]'");
             $tmbn = '';
             $image_path = null;
@@ -202,7 +518,34 @@
                     $tmbn = str_replace($https_location, $froogle_location, $tmbn);
                 }
             }
+*/
+            $image_ids = array();
+
+            $prefered_image_type = 'T';
+            if (
+                !empty($product['variantid'])
+                && !empty($active_modules['Product_Options'])
+            ) {
+                $image_ids['W'] = $product['variantid'];
+                $prefered_image_type = 'W';
+            }
+
+            $image_ids['P'] = $product['productid'];
+            $image_ids['T'] = $product['productid'];
+
+            $image_data = func_get_image_url_by_types($image_ids, $prefered_image_type);
+
+            if (!empty($image_data['image_url'])) {
+                $tmbn = $image_data['image_url'];
+
+                if (strpos($tmbn, $https_location) !== false) {
+                    $tmbn = str_replace($https_location, $froogle_location, $tmbn);
+                }
+            } else {
+                $tmbn = '';
+            }
 
+/* OLD CODE
             $ci = array(
                 'city' => $config['General']['default_city'],
                 'state' => $config['General']['default_state'],
@@ -210,19 +553,40 @@
                 'zipcode' => $config['General']['default_zipcode']
 
             );
+*/
+            $config['General']['apply_default_country'] = 'Y';
+            $customer_info = array(
+                'city' => $config['General']['default_city'],
+                'state' => $config['General']['default_state'],
+                'country' => $config['General']['default_country'],
+                'zipcode' => $config['General']['default_zipcode'],
+                'id' => 0
+            );
 
             if (!empty($active_modules['Product_Options']))
                 $product['price'] += func_get_default_options_markup($product['productid'], $product['price']);
 
+/* OLD CODE
             $tmp = func_tax_price($product['price'], $product['productid'], false, NULL, $ci);
             $product['price'] = $tmp['taxed_price'];
+*/
+            // New code does not include: [*] Improvement: *TAXES* Refactoring for func_tax_price function.
+            $product_taxed_price = func_tax_price($product['price'], $product['productid'], false, NULL, $customer_info);
+            $product['price'] = $product_taxed_price['taxed_price'];
             if ($config['Froogle']['froogle_used_https_links'] == 'Y') {
                 $product_url = $https_location.constant('DIR_CUSTOMER')."/product.php?productid=".$product['productid'];
             } else {
                 $product_url = $http_location.constant('DIR_CUSTOMER').'/'.func_get_resource_url('product', $product['productid'], '', false);
             }
 
+            $appearence = func_get_appearance_data($product);
+            if ($appearence['buy_now_buttons_enabled'] || $product['product_type'] == 'C')
+                $availability = 'in stock';
+            else    
+                $availability = 'out of stock';
+
             // Post string
+/* OLD CODE
             $post = func_froogle_convert($product['product'], 80)."\t".
                 func_froogle_convert($product['descr'], 65536)."\t".
                 $product_url."\t".
@@ -238,6 +602,29 @@
                 func_froogle_convert($product['manufacturer'], 256)."\t".
                 "new\t".
                 (!empty($cats) ? $cats : '');
+*/
+            $post =
+                // unique identifier for all products (including variant products) - productcode, not productid
+                $product['productcode']."\t".
+                // do not include any data into item_group_id attribute if not variant product
+                (($product['productcode'] != $product['main_productcode']) ? $product['main_productcode'] : "")."\t".
+                func_froogle_convert($product['product'], 70)."\t".
+                func_froogle_convert($product['descr'], 9900)."\t".
+                (!empty($cats) ? $cats : '')."\t".
+                $product_url."\t".
+                $tmbn."\t".
+                "new\t".
+                $availability."\t". 
+                // currency is combined with price
+                number_format(round($product['price'], 2), 2, ".", "")." ".
+                (empty($config['Froogle']['froogle_currency']) ? "USD" : $config['Froogle']['froogle_currency'])."\t".
+                $product['weight'] . ' ' . $config['Froogle']['froogle_weight_unit']."\t".
+                date("Y-m-d", XC_TIME+(empty($config['Froogle']['froogle_expiration_date']) ? 0.5 : $config['Froogle']['froogle_expiration_date'])*86400)."\t".
+                func_froogle_convert($product['manufacturer'], 256).
+                $product_additional_attributes
+            ;
+
+            assert('count(explode("\t", $post)) == count(explode("\t", $file_header))');
 
             fputs($fp, $post."\n");
             $cnt++;
@@ -251,7 +638,9 @@
             }
         }
 
+/* OLD CODE
         $user_account = $_user_account;
+*/
         fclose($fp);
         func_chmod_file($froogle_file);
 
