public static googleModel.Product ToGoogleModel(this moduleModel.CatalogProduct product, IBlobUrlResolver assetUrlResolver, moduleModel.Property[] properties = null) { var retVal = new googleModel.Product(); retVal.InjectFrom(product); var langCode = product.Catalog.Languages.First().LanguageCode; retVal.Link = @"http://virtocommerce-test.azurewebsites.net/"; retVal.OfferId = product.Id; retVal.Title = product.Name; retVal.Description = product.Reviews.Any() ? product.Reviews.First(x => x.LanguageCode == langCode).Content : product.Name; retVal.Link = @"http://virtocommerce-test.azurewebsites.net/"; retVal.ImageLink = assetUrlResolver.GetAbsoluteUrl(product.Assets.First().Url).TrimStart('/'); retVal.ContentLanguage = langCode; retVal.TargetCountry = "US"; retVal.Channel = "online"; retVal.Availability = "in stock"; retVal.Condition = "new"; retVal.GoogleProductCategory = "Media > Books"; retVal.Gtin = "9780007350896"; retVal.Taxes = new[] { new googleModel.ProductTax { Country = "US", Rate = 10, Region = "CA" } }; retVal.Shipping = new[] { new googleModel.ProductShipping { Country = "US", Price = new googleModel.Price { Currency = "USD", Value = "5"} } }; return retVal; }
public static moduleModel.CatalogProduct ToModuleModel(this googleModel.Product product, IBlobUrlResolver assetUrlResolver) { var retVal = new moduleModel.CatalogProduct(); retVal.InjectFrom(product); return(retVal); }
public static googleModel.ProductsCustomBatchRequestEntry ToBatchEntryModel(this googleModel.Product product, string method = _insertMethod) { var retVal = new googleModel.ProductsCustomBatchRequestEntry(); if (method.Equals(_insertMethod)) { retVal.Product = product; } else { retVal.ProductId = product.Id; } retVal.Method = method; return(retVal); }
public static googleModel.Product ToGoogleModel(this moduleModel.CatalogProduct product, IBlobUrlResolver assetUrlResolver, moduleModel.Property[] properties = null) { var retVal = new googleModel.Product(); retVal.InjectFrom(product); var langCode = product.Catalog.Languages.First().LanguageCode; retVal.Link = @"http://virtocommerce-test.azurewebsites.net/"; retVal.OfferId = product.Id; retVal.Title = product.Name; retVal.Description = product.Reviews.Any() ? product.Reviews.First(x => x.LanguageCode == langCode).Content : product.Name; retVal.Link = @"http://virtocommerce-test.azurewebsites.net/"; retVal.ImageLink = assetUrlResolver.GetAbsoluteUrl(product.Assets.First().Url).TrimStart('/'); retVal.ContentLanguage = langCode; retVal.TargetCountry = "US"; retVal.Channel = "online"; retVal.Availability = "in stock"; retVal.Condition = "new"; retVal.GoogleProductCategory = "Media > Books"; retVal.Gtin = "9780007350896"; retVal.Taxes = new[] { new googleModel.ProductTax { Country = "US", Rate = 10, Region = "CA" } }; retVal.Shipping = new[] { new googleModel.ProductShipping { Country = "US", Price = new googleModel.Price { Currency = "USD", Value = "5" } } }; return(retVal); }
/// <summary> /// Generate a feed /// </summary> /// <param name="stream">Stream</param> /// <param name="store">Store</param> /// <returns>Generated feed</returns> public void GenerateFeed(Stream stream, Store store) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } if (store == null) { throw new ArgumentNullException(nameof(store)); } const string googleBaseNamespace = "http://base.google.com/ns/1.0"; var settings = new XmlWriterSettings { Encoding = Encoding.UTF8 }; //language var languageId = 0; var languages = _languageService.GetAllLanguages(storeId: store.Id); //if we have only one language, let's use it if (languages.Count == 1) { //let's use the first one var language = languages.FirstOrDefault(); languageId = language != null ? language.Id : 0; } //otherwise, use the current one if (languageId == 0) { languageId = _workContext.WorkingLanguage.Id; } //we load all Google products here using one SQL request (performance optimization) var allGoogleProducts = _googleService.GetAll(); //google GoogleWebAuthorizationBroker.Folder = "ShoppingContent.Sample"; var credential = GoogleWebAuthorizationBroker.AuthorizeAsync( new ClientSecrets { ClientId = "584676283202-qphqjj998id40caks269cjag92dsc4rn.apps.googleusercontent.com", ClientSecret = "mv3j9LBNBKuGXjM1xLCi6Tbr" }, new string[] { ShoppingContentService.Scope.Content }, "user", CancellationToken.None).Result; // Create the service. var service = new ShoppingContentService(new BaseClientService.Initializer() { HttpClientInitializer = credential, ApplicationName = "Shopping Content Arc", }); using (var writer = XmlWriter.Create(stream, settings)) { //Generate feed according to the following specs: http://www.google.com/support/merchants/bin/answer.py?answer=188494&expand=GB writer.WriteStartDocument(); writer.WriteStartElement("rss"); writer.WriteAttributeString("version", "2.0"); writer.WriteAttributeString("xmlns", "g", null, googleBaseNamespace); writer.WriteStartElement("channel"); writer.WriteElementString("title", "Google Base feed"); writer.WriteElementString("link", "http://base.google.com/base/"); writer.WriteElementString("description", "Information about products"); var products1 = _productService.SearchProducts(storeId: store.Id, visibleIndividuallyOnly: true) .Where(x => !x.ExcludeGoogleFeed); var query = from p in products1 from pc in p.ProductCategories.Where(pc => !pc.Category.Name.Contains("Copier") && !pc.Category.Name.Contains("Copiers - New")) select p; foreach (var product1 in products1) { Google.Apis.ShoppingContent.v2.Data.Product productG = new Google.Apis.ShoppingContent.v2.Data.Product(); var productsToProcess = new List <Core.Domain.Catalog.Product>(); productG.ContentLanguage = "EN"; productG.TargetCountry = "US"; productG.Channel = "online"; productG.Availability = "in stock"; productG.Condition = "new"; productG.GoogleProductCategory = "Electronics > Print, Copy, Scan & Fax Accessories > Copier Accessories"; productG.Gtin = product1.Gtin; var legacy_Reader = _dbContext.SqlQuery <string>($"SELECT LegacyCode FROM [dbo].LegacyIds where ItemID={product1.Id}").ToList(); if (legacy_Reader.Count > 0) { foreach (var id in legacy_Reader) { productG.Title += $" - {id}"; } } var categoryP = product1.ProductCategories.FirstOrDefault(); if (categoryP != null) { if (categoryP.Category.Name.Contains("Toner")) { productG.ProductType = "Electronics > Print, Copy, Scan & Fax Accessories > Printer Accessories > Toner & Inkjet Cartridges"; } else { productG.ProductType = "Electronics > Print, Copy, Scan & Fax Accessories"; } } switch (product1.ProductType) { case ProductType.SimpleProduct: { //simple product doesn't have child products productsToProcess.Add(product1); } break; case ProductType.GroupedProduct: { //grouped products could have several child products var associatedProducts = _productService.GetAssociatedProducts(product1.Id, store.Id); productsToProcess.AddRange(associatedProducts); } break; default: continue; } foreach (var product in productsToProcess) { writer.WriteStartElement("item"); #region Basic Product Information //id [id]- An identifier of the item writer.WriteElementString("g", "id", googleBaseNamespace, product.Id.ToString()); //title [title] - Title of the item writer.WriteStartElement("title"); var title = product.GetLocalized(x => x.Name, languageId); //title should be not longer than 70 characters if (title.Length > 70) { title = title.Substring(0, 70); } writer.WriteCData(title); writer.WriteEndElement(); // title //description [description] - Description of the item writer.WriteStartElement("description"); string description = product.GetLocalized(x => x.FullDescription, languageId); if (String.IsNullOrEmpty(description)) { description = product.GetLocalized(x => x.ShortDescription, languageId); } if (String.IsNullOrEmpty(description)) { description = product.GetLocalized(x => x.Name, languageId); //description is required } //resolving character encoding issues in your data feed description = StripInvalidChars(description, true); writer.WriteCData(description); writer.WriteEndElement(); // description //google product category [google_product_category] - Google's category of the item //the category of the product according to Google’s product taxonomy. http://www.google.com/support/merchants/bin/answer.py?answer=160081 string googleProductCategory = ""; //var googleProduct = _googleService.GetByProductId(product.Id); var googleProduct = allGoogleProducts.FirstOrDefault(x => x.ProductId == product.Id); if (googleProduct != null) { googleProductCategory = googleProduct.Taxonomy; } if (String.IsNullOrEmpty(googleProductCategory)) { googleProductCategory = _googleShoppingSettings.DefaultGoogleCategory; } if (String.IsNullOrEmpty(googleProductCategory)) { throw new NopException("Default Google category is not set"); } writer.WriteStartElement("g", "google_product_category", googleBaseNamespace); writer.WriteCData(googleProductCategory); writer.WriteFullEndElement(); // g:google_product_category //product type [product_type] - Your category of the item var defaultProductCategory = _categoryService .GetProductCategoriesByProductId(product.Id, store.Id) .FirstOrDefault(); if (defaultProductCategory != null) { //TODO localize categories var category = defaultProductCategory.Category .GetFormattedBreadCrumb(_categoryService, separator: ">", languageId: languageId); if (!String.IsNullOrEmpty(category)) { writer.WriteStartElement("g", "product_type", googleBaseNamespace); writer.WriteCData(category); writer.WriteFullEndElement(); // g:product_type } } //link [link] - URL directly linking to your item's page on your website var productUrl = GetUrlHelper().RouteUrl("Product", new { SeName = product.GetSeName(languageId) }, GetHttpProtocol()); productG.Link = productUrl; writer.WriteElementString("link", productUrl); //image link [image_link] - URL of an image of the item //additional images [additional_image_link] //up to 10 pictures const int maximumPictures = 10; var storeLocation = _securitySettings.ForceSslForAllPages ? (!string.IsNullOrWhiteSpace(store.SecureUrl) ? store.SecureUrl : store.Url.Replace("http://", "https://")): store.Url; var pictures = _pictureService.GetPicturesByProductId(product.Id, maximumPictures); for (int i = 0; i < pictures.Count; i++) { var picture = pictures[i]; var imageUrl = _pictureService.GetPictureUrl(picture, _googleShoppingSettings.ProductPictureSize, storeLocation: storeLocation); if (i == 0) { //default image productG.ImageLink = imageUrl; writer.WriteElementString("g", "image_link", googleBaseNamespace, imageUrl); } else { //additional image productG.AdditionalImageLinks.Add(imageUrl); writer.WriteElementString("g", "additional_image_link", googleBaseNamespace, imageUrl); } } if (!pictures.Any()) { //no picture? submit a default one var imageUrl = _pictureService.GetDefaultPictureUrl(_googleShoppingSettings.ProductPictureSize, storeLocation: storeLocation); productG.ImageLink = imageUrl; writer.WriteElementString("g", "image_link", googleBaseNamespace, imageUrl); } //condition [condition] - Condition or state of the item productG.Condition = "new"; productG.ExpirationDate = DateTime.Now.AddDays(_googleShoppingSettings.ExpirationNumberOfDays).ToString("yyyy-MM-dd"); writer.WriteElementString("g", "condition", googleBaseNamespace, "new"); writer.WriteElementString("g", "expiration_date", googleBaseNamespace, DateTime.Now.AddDays(_googleShoppingSettings.ExpirationNumberOfDays).ToString("yyyy-MM-dd")); #endregion #region Availability & Price //availability [availability] - Availability status of the item string availability = "in stock"; //in stock by default if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock && product.BackorderMode == BackorderMode.NoBackorders && product.GetTotalStockQuantity() <= 0) { availability = "out of stock"; } //uncomment th code below in order to support "preorder" value for "availability" //if (product.AvailableForPreOrder && // (!product.PreOrderAvailabilityStartDateTimeUtc.HasValue || // product.PreOrderAvailabilityStartDateTimeUtc.Value >= DateTime.UtcNow)) //{ // availability = "preorder"; //} productG.Availability = availability; writer.WriteElementString("g", "availability", googleBaseNamespace, availability); //price [price] - Price of the item var currency = GetUsedCurrency(); decimal finalPriceBase; if (_googleShoppingSettings.PricesConsiderPromotions) { var minPossiblePrice = _priceCalculationService.GetFinalPrice(product, _workContext.CurrentCustomer); if (product.HasTierPrices) { //calculate price for the maximum quantity if we have tier prices, and choose minimal minPossiblePrice = Math.Min(minPossiblePrice, _priceCalculationService.GetFinalPrice(product, _workContext.CurrentCustomer, quantity: int.MaxValue)); } finalPriceBase = _taxService.GetProductPrice(product, minPossiblePrice, out decimal _); } else { finalPriceBase = product.Price; } decimal price = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceBase, currency); //round price now so it matches the product details page price = RoundingHelper.RoundPrice(price); Price priceG = new Price { Currency = currency.CurrencyCode, Value = price.ToString(new CultureInfo("en-US", false).NumberFormat) }; productG.Price = priceG; writer.WriteElementString("g", "price", googleBaseNamespace, price.ToString(new CultureInfo("en-US", false).NumberFormat) + " " + currency.CurrencyCode); #endregion #region Unique Product Identifiers /* Unique product identifiers such as UPC, EAN, JAN or ISBN allow us to show your listing on the appropriate product page. If you don't provide the required unique product identifiers, your store may not appear on product pages, and all your items may be removed from Product Search. * We require unique product identifiers for all products - except for custom made goods. For apparel, you must submit the 'brand' attribute. For media (such as books, movies, music and video games), you must submit the 'gtin' attribute. In all cases, we recommend you submit all three attributes. * You need to submit at least two attributes of 'brand', 'gtin' and 'mpn', but we recommend that you submit all three if available. For media (such as books, movies, music and video games), you must submit the 'gtin' attribute, but we recommend that you include 'brand' and 'mpn' if available. */ //GTIN [gtin] - GTIN var gtin = product.Gtin; if (!String.IsNullOrEmpty(gtin)) { productG.Gtin = gtin; writer.WriteStartElement("g", "gtin", googleBaseNamespace); writer.WriteCData(gtin); writer.WriteFullEndElement(); // g:gtin } //brand [brand] - Brand of the item var defaultManufacturer = _manufacturerService.GetProductManufacturersByProductId((product.Id)).FirstOrDefault(); if (defaultManufacturer != null) { productG.Brand = defaultManufacturer.Manufacturer.Name; writer.WriteStartElement("g", "brand", googleBaseNamespace); writer.WriteCData(defaultManufacturer.Manufacturer.Name); writer.WriteFullEndElement(); // g:brand } //mpn [mpn] - Manufacturer Part Number (MPN) of the item var mpn = product.ManufacturerPartNumber; if (!String.IsNullOrEmpty(mpn)) { productG.Mpn = mpn; writer.WriteStartElement("g", "mpn", googleBaseNamespace); writer.WriteCData(mpn); writer.WriteFullEndElement(); // g:mpn } //identifier exists [identifier_exists] - Submit custom goods if (googleProduct != null && googleProduct.CustomGoods) { productG.IdentifierExists = false; writer.WriteElementString("g", "identifier_exists", googleBaseNamespace, "FALSE"); } #endregion #region Apparel Products /* Apparel includes all products that fall under 'Apparel & Accessories' (including all sub-categories) * in Google’s product taxonomy. */ //gender [gender] - Gender of the item if (googleProduct != null && !String.IsNullOrEmpty(googleProduct.Gender)) { productG.Gender = googleProduct.Gender; writer.WriteStartElement("g", "gender", googleBaseNamespace); writer.WriteCData(googleProduct.Gender); writer.WriteFullEndElement(); // g:gender } //age group [age_group] - Target age group of the item if (googleProduct != null && !String.IsNullOrEmpty(googleProduct.AgeGroup)) { productG.AgeGroup = googleProduct.AgeGroup; writer.WriteStartElement("g", "age_group", googleBaseNamespace); writer.WriteCData(googleProduct.AgeGroup); writer.WriteFullEndElement(); // g:age_group } //color [color] - Color of the item if (googleProduct != null && !String.IsNullOrEmpty(googleProduct.Color)) { productG.Color = googleProduct.Color; writer.WriteStartElement("g", "color", googleBaseNamespace); writer.WriteCData(googleProduct.Color); writer.WriteFullEndElement(); // g:color } //size [size] - Size of the item if (googleProduct != null && !String.IsNullOrEmpty(googleProduct.Size)) { productG.Sizes.Add(googleProduct.Size); writer.WriteStartElement("g", "size", googleBaseNamespace); writer.WriteCData(googleProduct.Size); writer.WriteFullEndElement(); // g:size } #endregion #region Tax & Shipping //tax [tax] //The tax attribute is an item-level override for merchant-level tax settings as defined in your Google Merchant Center account. This attribute is only accepted in the US, if your feed targets a country outside of the US, please do not use this attribute. //IMPORTANT NOTE: Set tax in your Google Merchant Center account settings //IMPORTANT NOTE: Set shipping in your Google Merchant Center account settings //shipping weight [shipping_weight] - Weight of the item for shipping //We accept only the following units of weight: lb, oz, g, kg. if (_googleShoppingSettings.PassShippingInfoWeight) { string weightName; var shippingWeight = product.Weight; var weightSystemName = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).SystemKeyword; switch (weightSystemName) { case "ounce": weightName = "oz"; break; case "lb": weightName = "lb"; break; case "grams": weightName = "g"; break; case "kg": weightName = "kg"; break; default: //unknown weight throw new Exception("Not supported weight. Google accepts the following units: lb, oz, g, kg."); } ProductShippingDimension psd = new ProductShippingDimension { Unit = weightName, Value = double.Parse(shippingWeight.ToString()) }; productG.ShippingHeight = psd; writer.WriteElementString("g", "shipping_weight", googleBaseNamespace, string.Format(CultureInfo.InvariantCulture, "{0} {1}", shippingWeight.ToString(new CultureInfo("en-US", false).NumberFormat), weightName)); } //shipping length [shipping_length] - Length of the item for shipping //shipping width [shipping_width] - Width of the item for shipping //shipping height [shipping_height] - Height of the item for shipping //We accept only the following units of length: in, cm if (_googleShoppingSettings.PassShippingInfoDimensions) { string dimensionName; var length = product.Length; var width = product.Width; var height = product.Height; var dimensionSystemName = _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId).SystemKeyword; switch (dimensionSystemName) { case "inches": dimensionName = "in"; break; //TODO support other dimensions (convert to cm) default: //unknown dimension throw new Exception("Not supported dimension. Google accepts the following units: in, cm."); } ProductShippingDimension psd = new ProductShippingDimension { Unit = dimensionName, Value = double.Parse(length.ToString()) }; productG.ShippingLength = psd; psd = new ProductShippingDimension { Unit = dimensionName, Value = double.Parse(width.ToString()) }; productG.ShippingWidth = psd; psd = new ProductShippingDimension { Unit = dimensionName, Value = double.Parse(height.ToString()) }; productG.ShippingHeight = psd; writer.WriteElementString("g", "shipping_length", googleBaseNamespace, string.Format(CultureInfo.InvariantCulture, "{0} {1}", length.ToString(new CultureInfo("en-US", false).NumberFormat), dimensionName)); writer.WriteElementString("g", "shipping_width", googleBaseNamespace, string.Format(CultureInfo.InvariantCulture, "{0} {1}", width.ToString(new CultureInfo("en-US", false).NumberFormat), dimensionName)); writer.WriteElementString("g", "shipping_height", googleBaseNamespace, string.Format(CultureInfo.InvariantCulture, "{0} {1}", height.ToString(new CultureInfo("en-US", false).NumberFormat), dimensionName)); } #endregion writer.WriteEndElement(); // item //used in in description string usedinquery = $@"SELECT ItemsCompatability.ItemID FROM ItemsCompatability INNER JOIN Items ON ItemsCompatability.ItemID = Items.ItemID WHERE (ItemsCompatability.ItemIDPart ={product.Id}) UNION SELECT [Groups-Items].ItemID FROM [Groups-Items] INNER JOIN Items AS Items_1 ON [Groups-Items].ItemID = Items_1.ItemID WHERE ([Groups-Items].GroupID IN (SELECT GroupID FROM [Relations-Groups-Items] WHERE (ItemID ={product.Id}) AND (Direction = 'B'))) UNION SELECT [Relations-Groups-Items].ItemID FROM Items INNER JOIN [Relations-Groups-Items] ON Items.ItemID = [Relations-Groups-Items].ItemID WHERE ([Relations-Groups-Items].Direction = 'A') AND ([Relations-Groups-Items].GroupID IN (SELECT GroupID FROM [Groups-Items] WHERE (ItemID ={product.Id})))"; var usedin_Reader = _dbContext.SqlQuery <int>(usedinquery).ToList(); if (usedin_Reader.Count > 0) { var usedIn = _productService.GetProductsByIds(usedin_Reader.ToArray()); foreach (var item in usedIn) { productG.Description = $"{productG.Description} {item.Name}, "; } productG.Description = productG.Description.TrimEnd(','); productG.Description += "."; } else { productG.Description += "N/A"; } } } writer.WriteEndElement(); // channel writer.WriteEndElement(); // rss writer.WriteEndDocument(); } }