Beispiel #1
0
        private static dynamic ToDynamic(ProductSpecificationAttribute productSpecificationAttribute, DataExporterContext ctx)
        {
            if (productSpecificationAttribute == null)
            {
                return(null);
            }

            var option    = productSpecificationAttribute.SpecificationAttributeOption;
            var attribute = option.SpecificationAttribute;

            dynamic result       = new DynamicEntity(productSpecificationAttribute);
            dynamic dynAttribute = new DynamicEntity(attribute);

            dynAttribute.Name       = ctx.GetTranslation(attribute, nameof(attribute.Name), attribute.Name);
            dynAttribute._Localized = GetLocalized(ctx, attribute, x => x.Name);

            dynAttribute.Alias      = ctx.GetTranslation(attribute, nameof(attribute.Alias), attribute.Alias);
            dynAttribute._Localized = GetLocalized(ctx, attribute, x => x.Alias);

            dynamic dynOption = new DynamicEntity(option);

            dynOption.Name       = ctx.GetTranslation(option, nameof(option.Name), option.Name);
            dynOption._Localized = GetLocalized(ctx, option, x => x.Name);

            dynOption.Alias      = ctx.GetTranslation(option, nameof(option.Alias), option.Alias);
            dynOption._Localized = GetLocalized(ctx, option, x => x.Alias);

            dynOption.SpecificationAttribute    = dynAttribute;
            result.SpecificationAttributeOption = dynOption;

            return(result);
        }
Beispiel #2
0
        private static dynamic ToDynamic(Address address, DataExporterContext ctx)
        {
            if (address == null)
            {
                return(null);
            }

            dynamic result = new DynamicEntity(address);

            result.Country = ToDynamic(address.Country, ctx);

            if (address.StateProvinceId.GetValueOrDefault() > 0)
            {
                dynamic sp = new DynamicEntity(address.StateProvince);

                sp.Name       = ctx.GetTranslation(address.StateProvince, nameof(address.StateProvince.Name), address.StateProvince.Name);
                sp._Localized = GetLocalized(ctx, address.StateProvince, x => x.Name);

                result.StateProvince = sp;
            }
            else
            {
                result.StateProvince = null;
            }

            return(result);
        }
Beispiel #3
0
        private static dynamic ToDynamic(QuantityUnit quantityUnit, DataExporterContext ctx)
        {
            if (quantityUnit == null)
            {
                return(null);
            }

            dynamic result = new DynamicEntity(quantityUnit);

            result.Name        = ctx.GetTranslation(quantityUnit, nameof(quantityUnit.Name), quantityUnit.Name);
            result.NamePlural  = ctx.GetTranslation(quantityUnit, nameof(quantityUnit.NamePlural), quantityUnit.NamePlural);
            result.Description = ctx.GetTranslation(quantityUnit, nameof(quantityUnit.Description), quantityUnit.Description);

            result._Localized = GetLocalized(ctx, quantityUnit,
                                             x => x.Name,
                                             x => x.NamePlural,
                                             x => x.Description);

            return(result);
        }
Beispiel #4
0
        private static dynamic ToDynamic(Manufacturer manufacturer, DataExporterContext ctx)
        {
            if (manufacturer == null)
            {
                return(null);
            }

            dynamic result = new DynamicEntity(manufacturer);

            result.Picture = null;
            result.Name    = ctx.GetTranslation(manufacturer, nameof(manufacturer.Name), manufacturer.Name);

            if (!ctx.IsPreview)
            {
                result.SeName            = ctx.GetUrlRecord(manufacturer);
                result.Description       = ctx.GetTranslation(manufacturer, nameof(manufacturer.Description), manufacturer.Description);
                result.BottomDescription = ctx.GetTranslation(manufacturer, nameof(manufacturer.BottomDescription), manufacturer.BottomDescription);
                result.MetaKeywords      = ctx.GetTranslation(manufacturer, nameof(manufacturer.MetaKeywords), manufacturer.MetaKeywords);
                result.MetaDescription   = ctx.GetTranslation(manufacturer, nameof(manufacturer.MetaDescription), manufacturer.MetaDescription);
                result.MetaTitle         = ctx.GetTranslation(manufacturer, nameof(manufacturer.MetaTitle), manufacturer.MetaTitle);

                result._Localized = GetLocalized(ctx, manufacturer,
                                                 x => x.Name,
                                                 x => x.Description,
                                                 x => x.BottomDescription,
                                                 x => x.MetaKeywords,
                                                 x => x.MetaDescription,
                                                 x => x.MetaTitle);
            }

            return(result);
        }
Beispiel #5
0
        private static dynamic ToDynamic(DeliveryTime deliveryTime, DataExporterContext ctx)
        {
            if (deliveryTime == null)
            {
                return(null);
            }

            dynamic result = new DynamicEntity(deliveryTime);

            result.Name       = ctx.GetTranslation(deliveryTime, nameof(deliveryTime.Name), deliveryTime.Name);
            result._Localized = GetLocalized(ctx, deliveryTime, x => x.Name);

            return(result);
        }
Beispiel #6
0
        private static dynamic ToDynamic(Country country, DataExporterContext ctx)
        {
            if (country == null)
            {
                return(null);
            }

            dynamic result = new DynamicEntity(country);

            result.Name       = ctx.GetTranslation(country, nameof(country.Name), country.Name);
            result._Localized = GetLocalized(ctx, country, x => x.Name);

            return(result);
        }
Beispiel #7
0
        private static dynamic ToDynamic(ProductVariantAttribute productAttribute, DataExporterContext ctx)
        {
            if (productAttribute == null)
            {
                return(null);
            }

            var languageId = ctx.LanguageId;
            var attribute  = productAttribute.ProductAttribute;

            dynamic result       = new DynamicEntity(productAttribute);
            dynamic dynAttribute = new DynamicEntity(attribute);

            dynAttribute.Name        = ctx.GetTranslation(attribute, nameof(attribute.Name), attribute.Name);
            dynAttribute.Description = ctx.GetTranslation(attribute, nameof(attribute.Description), attribute.Description);

            dynAttribute.Values = productAttribute.ProductVariantAttributeValues
                                  .OrderBy(x => x.DisplayOrder)
                                  .Select(x =>
            {
                dynamic dyn    = new DynamicEntity(x);
                dyn.Name       = ctx.GetTranslation(x, nameof(x.Name), x.Name);
                dyn._Localized = GetLocalized(ctx, x, y => y.Name);

                return(dyn);
            })
                                  .ToList();

            dynAttribute._Localized = GetLocalized(ctx, attribute,
                                                   x => x.Name,
                                                   x => x.Description);

            result.Attribute = dynAttribute;

            return(result);
        }
Beispiel #8
0
        private static dynamic ToDynamic(Category category, DataExporterContext ctx)
        {
            if (category == null)
            {
                return(null);
            }

            dynamic result = new DynamicEntity(category);

            result.Picture  = null;
            result.Name     = ctx.GetTranslation(category, nameof(category.Name), category.Name);
            result.FullName = ctx.GetTranslation(category, nameof(category.FullName), category.FullName);

            if (!ctx.IsPreview)
            {
                result.SeName            = ctx.GetUrlRecord(category);
                result.Description       = ctx.GetTranslation(category, nameof(category.Description), category.Description);
                result.BottomDescription = ctx.GetTranslation(category, nameof(category.BottomDescription), category.BottomDescription);
                result.MetaKeywords      = ctx.GetTranslation(category, nameof(category.MetaKeywords), category.MetaKeywords);
                result.MetaDescription   = ctx.GetTranslation(category, nameof(category.MetaDescription), category.MetaDescription);
                result.MetaTitle         = ctx.GetTranslation(category, nameof(category.MetaTitle), category.MetaTitle);

                result._CategoryTemplateViewPath = ctx.CategoryTemplates.ContainsKey(category.CategoryTemplateId)
                    ? ctx.CategoryTemplates[category.CategoryTemplateId]
                    : "";

                result._Localized = GetLocalized(ctx, category,
                                                 x => x.Name,
                                                 x => x.FullName,
                                                 x => x.Description,
                                                 x => x.BottomDescription,
                                                 x => x.MetaKeywords,
                                                 x => x.MetaDescription,
                                                 x => x.MetaTitle);
            }

            return(result);
        }
Beispiel #9
0
        /// <summary>
        /// Creates an expando object with the most important general properties of a product such as the name and description.
        /// This method is used for entities where products are to be exported as related data, e.g. order items.
        /// </summary>
        private async Task <dynamic> ToDynamic(Product product, DataExporterContext ctx, string seName = null, Money?price = null)
        {
            if (product == null)
            {
                return(null);
            }

            dynamic result        = new DynamicEntity(product);
            var     localizedName = ctx.GetTranslation(product, nameof(product.Name), product.Name);

            result.AppliedDiscounts               = null;
            result.Downloads                      = null;
            result.TierPrices                     = null;
            result.ProductAttributes              = null;
            result.ProductAttributeCombinations   = null;
            result.ProductPictures                = null;
            result.ProductCategories              = null;
            result.ProductManufacturers           = null;
            result.ProductTags                    = null;
            result.ProductSpecificationAttributes = null;
            result.ProductBundleItems             = null;
            result.Name = localizedName;

            if (!ctx.IsPreview)
            {
                result.SeName           = seName ?? ctx.GetUrlRecord(product);
                result.ShortDescription = ctx.GetTranslation(product, nameof(product.ShortDescription), product.ShortDescription);
                result.FullDescription  = ctx.GetTranslation(product, nameof(product.FullDescription), product.FullDescription);
                result.MetaKeywords     = ctx.GetTranslation(product, nameof(product.MetaKeywords), product.MetaKeywords);
                result.MetaDescription  = ctx.GetTranslation(product, nameof(product.MetaDescription), product.MetaDescription);
                result.MetaTitle        = ctx.GetTranslation(product, nameof(product.MetaTitle), product.MetaTitle);
                result.BundleTitleText  = ctx.GetTranslation(product, nameof(product.BundleTitleText), product.BundleTitleText);

                result._ProductTemplateViewPath = ctx.ProductTemplates.ContainsKey(product.ProductTemplateId)
                    ? ctx.ProductTemplates[product.ProductTemplateId]
                    : string.Empty;

                if (product.BasePriceHasValue && product.BasePriceAmount != 0)
                {
                    if (price == null)
                    {
                        var calculationOptions = _priceCalculationService.CreateDefaultOptions(false, ctx.ContextCustomer, ctx.ContextCurrency, ctx.ProductBatchContext);
                        var productPrice       = await _priceCalculationService.CalculatePriceAsync(new PriceCalculationContext(product, calculationOptions));

                        price = productPrice.FinalPrice;
                    }

                    result._BasePriceInfo = _priceCalculationService.GetBasePriceInfo(product, price.Value, ctx.ContextCurrency, ctx.ContextLanguage, false);
                }
                else
                {
                    result._BasePriceInfo = string.Empty;
                }

                result.DeliveryTime = ctx.DeliveryTimes.TryGetValue(product.DeliveryTimeId ?? 0, out var deliveryTime)
                    ? ToDynamic(deliveryTime, ctx)
                    : null;

                result.QuantityUnit = ctx.QuantityUnits.TryGetValue(product.QuantityUnitId ?? 0, out var quantityUnit)
                    ? ToDynamic(quantityUnit, ctx)
                    : null;

                result.CountryOfOrigin = product.CountryOfOriginId.HasValue
                    ? ToDynamic(product.CountryOfOrigin, ctx)
                    : null;

                result._Localized = GetLocalized(ctx, product,
                                                 x => x.Name,
                                                 x => x.ShortDescription,
                                                 x => x.FullDescription,
                                                 x => x.MetaKeywords,
                                                 x => x.MetaDescription,
                                                 x => x.MetaTitle,
                                                 x => x.BundleTitleText);
            }

            return(result);
        }
Beispiel #10
0
        /// <summary>
        /// Applies the product description to an expando object.
        /// Projection settings controls in detail how the product description is to be exported here.
        /// </summary>
        private static async Task ApplyProductDescription(dynamic dynObject, Product product, DataExporterContext ctx)
        {
            try
            {
                var    languageId  = ctx.LanguageId;
                string description = "";

                // Description merging.
                if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.None)
                {
                    // Export empty description.
                }
                else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ShortDescriptionOrNameIfEmpty)
                {
                    description = dynObject.FullDescription;

                    if (description.IsEmpty())
                    {
                        description = dynObject.ShortDescription;
                    }
                    if (description.IsEmpty())
                    {
                        description = dynObject.Name;
                    }
                }
                else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ShortDescription)
                {
                    description = dynObject.ShortDescription;
                }
                else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.Description)
                {
                    description = dynObject.FullDescription;
                }
                else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.NameAndShortDescription)
                {
                    description = ((string)dynObject.Name).Grow((string)dynObject.ShortDescription, " ");
                }
                else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.NameAndDescription)
                {
                    description = ((string)dynObject.Name).Grow((string)dynObject.FullDescription, " ");
                }
                else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription ||
                         ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ManufacturerAndNameAndDescription)
                {
                    var productManus = await ctx.ProductBatchContext.ProductManufacturers.GetOrLoadAsync(product.Id);

                    if (productManus != null && productManus.Any())
                    {
                        var manufacturer = productManus.First().Manufacturer;
                        description = ctx.GetTranslation(manufacturer, nameof(manufacturer.Name), manufacturer.Name);
                    }

                    description = description.Grow((string)dynObject.Name, " ");
                    description = ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription
                        ? description.Grow((string)dynObject.ShortDescription, " ")
                        : description.Grow((string)dynObject.FullDescription, " ");
                }

                // Append text.
                if (ctx.Projection.AppendDescriptionText.HasValue() && ((string)dynObject.ShortDescription).IsEmpty() && ((string)dynObject.FullDescription).IsEmpty())
                {
                    var appendText = ctx.Projection.AppendDescriptionText.SplitSafe(",").ToArray();
                    if (appendText.Any())
                    {
                        var rnd = CommonHelper.GenerateRandomInteger(0, appendText.Length - 1);
                        description = description.Grow(appendText.ElementAtOrDefault(rnd), " ");
                    }
                }

                // Remove critical characters.
                if (description.HasValue() && ctx.Projection.RemoveCriticalCharacters)
                {
                    foreach (var str in ctx.Projection.CriticalCharacters.SplitSafe(","))
                    {
                        description = description.Replace(str, "");
                    }
                }

                // Convert to plain text.
                if (description.HasValue() && ctx.Projection.DescriptionToPlainText)
                {
                    //Regex reg = new Regex("<[^>]+>", RegexOptions.IgnoreCase);
                    //description = HttpUtility.HtmlDecode(reg.Replace(description, ""));

                    description = HtmlUtils.ConvertHtmlToPlainText(description);
                    description = HtmlUtils.StripTags(HttpUtility.HtmlDecode(description));
                }

                dynObject.FullDescription = description.TrimSafe();
            }
            catch { }
        }
Beispiel #11
0
        /// <summary>
        /// Applies extra data to an expando object.
        /// Export feature flags set by the export provider controls whether and what to be exported here.
        /// </summary>
        private async Task ApplyExportFeatures(
            dynamic dynObject,
            Product product,
            CalculatedPrice price,
            IEnumerable <ProductMediaFile> mediaFiles,
            DataExporterContext ctx,
            DynamicProductContext productContext)
        {
            if (ctx.Supports(ExportFeatures.CanProjectDescription))
            {
                await ApplyProductDescription(dynObject, product, ctx);
            }

            if (ctx.Supports(ExportFeatures.OffersBrandFallback))
            {
                string brand        = null;
                var    productManus = await ctx.ProductBatchContext.ProductManufacturers.GetOrLoadAsync(product.Id);

                if (productManus?.Any() ?? false)
                {
                    var manufacturer = productManus.First().Manufacturer;
                    brand = ctx.GetTranslation(manufacturer, nameof(manufacturer.Name), manufacturer.Name);
                }
                if (brand.IsEmpty())
                {
                    brand = ctx.Projection.Brand;
                }

                dynObject._Brand = brand;
            }

            if (ctx.Supports(ExportFeatures.CanIncludeMainPicture))
            {
                var imageQuery = ctx.Projection.PictureSize > 0 ? new ProcessImageQuery {
                    MaxWidth = ctx.Projection.PictureSize
                } : null;

                if (mediaFiles?.Any() ?? false)
                {
                    var file = _mediaService.ConvertMediaFile(mediaFiles.Select(x => x.MediaFile).First());

                    dynObject._MainPictureUrl         = _mediaService.GetUrl(file, imageQuery, ctx.Store.GetHost());
                    dynObject._MainPictureRelativeUrl = _mediaService.GetUrl(file, imageQuery);
                }
                else if (!_catalogSettings.HideProductDefaultPictures)
                {
                    // Get fallback image URL.
                    dynObject._MainPictureUrl         = _mediaService.GetUrl(null, imageQuery, ctx.Store.GetHost());
                    dynObject._MainPictureRelativeUrl = _mediaService.GetUrl(null, imageQuery);
                }
                else
                {
                    dynObject._MainPictureUrl         = null;
                    dynObject._MainPictureRelativeUrl = null;
                }
            }

            if (ctx.Supports(ExportFeatures.UsesSkuAsMpnFallback) && product.ManufacturerPartNumber.IsEmpty())
            {
                dynObject.ManufacturerPartNumber = product.Sku;
            }

            if (ctx.Supports(ExportFeatures.OffersShippingTimeFallback))
            {
                dynamic deliveryTime = dynObject.DeliveryTime;
                dynObject._ShippingTime = deliveryTime == null ? ctx.Projection.ShippingTime : deliveryTime.Name;
            }

            if (ctx.Supports(ExportFeatures.OffersShippingCostsFallback))
            {
                dynObject._FreeShippingThreshold = ctx.Projection.FreeShippingThreshold;

                dynObject._ShippingCosts = product.IsFreeShipping || (ctx.Projection.FreeShippingThreshold.HasValue && (decimal)dynObject.Price >= ctx.Projection.FreeShippingThreshold.Value)
                    ? decimal.Zero
                    : ctx.Projection.ShippingCosts;
            }

            if (ctx.Supports(ExportFeatures.UsesOldPrice))
            {
                if (product.OldPrice != decimal.Zero && product.OldPrice != (decimal)dynObject.Price && !(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing))
                {
                    if (ctx.Projection.ConvertNetToGrossPrices)
                    {
                        var tax = await _taxCalculator.CalculateProductTaxAsync(product, product.OldPrice, true, ctx.ContextCustomer, ctx.ContextCurrency);

                        dynObject._OldPrice = tax.Price;
                    }
                    else
                    {
                        dynObject._OldPrice = product.OldPrice;
                    }
                }
                else
                {
                    dynObject._OldPrice = null;
                }
            }

            if (ctx.Supports(ExportFeatures.UsesSpecialPrice))
            {
                dynObject._SpecialPrice       = null;   // Special price which is valid now.
                dynObject._FutureSpecialPrice = null;   // Special price which is valid now and in future.
                dynObject._RegularPrice       = null;   // Price as if a special price would not exist.

                if (!(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing))
                {
                    if (price.OfferPrice.HasValue && product.SpecialPriceEndDateTimeUtc.HasValue)
                    {
                        var endDate = DateTime.SpecifyKind(product.SpecialPriceEndDateTimeUtc.Value, DateTimeKind.Utc);
                        if (endDate > DateTime.UtcNow)
                        {
                            dynObject._FutureSpecialPrice = price.OfferPrice.Value.Amount;
                        }
                    }

                    dynObject._SpecialPrice = price.OfferPrice?.Amount ?? null;

                    if (price.OfferPrice.HasValue || dynObject._FutureSpecialPrice != null)
                    {
                        var clonedOptions = ctx.PriceCalculationOptions.Clone();
                        clonedOptions.IgnoreOfferPrice = true;

                        var calculationContext = new PriceCalculationContext(product, clonedOptions);
                        calculationContext.AddSelectedAttributes(productContext?.Combination?.AttributeSelection, product.Id);
                        var priceWithoutOfferPrice = await _priceCalculationService.CalculatePriceAsync(calculationContext);

                        dynObject._RegularPrice = priceWithoutOfferPrice.FinalPrice.Amount;
                    }
                }
            }
        }
Beispiel #12
0
        /// <summary>
        /// Creates an expando object with all product properties.
        /// This method is used when exporting products and when exporting variant combinations as products.
        /// </summary>
        private async Task <dynamic> ToDynamic(Product product, bool isParent, DataExporterContext ctx, DynamicProductContext productContext)
        {
            var combination = productContext.Combination;

            product.MergeWithCombination(combination);

            var productManufacturers = await ctx.ProductBatchContext.ProductManufacturers.GetOrLoadAsync(product.Id);

            var productCategories = await ctx.ProductBatchContext.ProductCategories.GetOrLoadAsync(product.Id);

            var productAttributes = await ctx.ProductBatchContext.Attributes.GetOrLoadAsync(product.Id);

            var productTags = await ctx.ProductBatchContext.ProductTags.GetOrLoadAsync(product.Id);

            var specificationAttributes = await ctx.ProductBatchContext.SpecificationAttributes.GetOrLoadAsync(product.Id);

            var selectedAttributes     = combination?.AttributeSelection;
            var variantAttributeValues = combination?.AttributeSelection?.MaterializeProductVariantAttributeValues(productAttributes);

            // Price calculation.
            var calculationContext = new PriceCalculationContext(product, ctx.PriceCalculationOptions);

            calculationContext.AddSelectedAttributes(combination?.AttributeSelection, product.Id);
            var price = await _priceCalculationService.CalculatePriceAsync(calculationContext);

            dynamic dynObject = ToDynamic(product, ctx, productContext.SeName, price.FinalPrice);

            dynObject._IsParent     = isParent;
            dynObject._CategoryName = null;
            dynObject._CategoryPath = null;
            dynObject._AttributeCombinationValues = null;
            dynObject._AttributeCombinationId     = 0;

            dynObject.Price = price.FinalPrice.Amount;

            if (combination != null)
            {
                dynObject._AttributeCombinationId = combination.Id;
                dynObject._UniqueId = product.Id + "-" + combination.Id;

                if (ctx.Supports(ExportFeatures.UsesAttributeCombination))
                {
                    dynObject._AttributeCombinationValues = variantAttributeValues;
                }

                if (ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName)
                {
                    var valueNames = variantAttributeValues
                                     .Select(x => ctx.GetTranslation(x, nameof(x.Name), x.Name))
                                     .ToList();

                    dynObject.Name = ((string)dynObject.Name).Grow(string.Join(", ", valueNames), " ");
                }
            }
            else
            {
                dynObject._UniqueId = product.Id.ToString();
            }

            if (selectedAttributes?.AttributesMap?.Any() ?? false)
            {
                var query = new ProductVariantQuery();
                await _productUrlHelper.AddAttributesToQueryAsync(query, selectedAttributes, product.Id, 0, productAttributes);

                dynObject._DetailUrl = productContext.AbsoluteProductUrl + _productUrlHelper.ToQueryString(query);
            }
            else
            {
                dynObject._DetailUrl = productContext.AbsoluteProductUrl;
            }

            // Category path.
            {
                var categoryPath = string.Empty;
                var pc           = productCategories.OrderBy(x => x.DisplayOrder).FirstOrDefault();
                if (pc != null)
                {
                    var node = await _categoryService.GetCategoryTreeAsync(pc.CategoryId, true, ctx.Store.Id);

                    if (node != null)
                    {
                        categoryPath = _categoryService.GetCategoryPath(node, ctx.Projection.LanguageId, null, " > ");
                    }
                }

                dynObject._CategoryPath = categoryPath;
            }

            dynObject.CountryOfOrigin = ctx.Countries.TryGetValue(product.CountryOfOriginId ?? 0, out var countryOfOrigin)
                ? ToDynamic(countryOfOrigin, ctx)
                : null;

            dynObject.ProductManufacturers = productManufacturers
                                             .OrderBy(x => x.DisplayOrder)
                                             .Select(x =>
            {
                dynamic dyn              = new DynamicEntity(x);
                dyn.Manufacturer         = ToDynamic(x.Manufacturer, ctx);
                dyn.Manufacturer.Picture = x.Manufacturer != null && x.Manufacturer.MediaFileId.HasValue
                        ? ToDynamic(x.Manufacturer.MediaFile, _mediaSettings.ManufacturerThumbPictureSize, _mediaSettings.ManufacturerThumbPictureSize, ctx)
                        : null;

                return(dyn);
            })
                                             .ToList();

            dynObject.ProductCategories = productCategories
                                          .OrderBy(x => x.DisplayOrder)
                                          .Select(x =>
            {
                dynamic dyn          = new DynamicEntity(x);
                dyn.Category         = ToDynamic(x.Category, ctx);
                dyn.Category.Picture = x.Category != null && x.Category.MediaFileId.HasValue
                        ? ToDynamic(x.Category.MediaFile, _mediaSettings.CategoryThumbPictureSize, _mediaSettings.CategoryThumbPictureSize, ctx)
                        : null;

                if (dynObject._CategoryName == null)
                {
                    dynObject._CategoryName = (string)dyn.Category.Name;
                }

                return(dyn);
            })
                                          .ToList();

            dynObject.ProductAttributes = productAttributes
                                          .OrderBy(x => x.DisplayOrder)
                                          .Select(x => ToDynamic(x, ctx))
                                          .ToList();

            // Do not export combinations if a combination is exported as a product.
            if (productContext.Combinations != null && productContext.Combination == null)
            {
                var pictureSize       = ctx.Projection.PictureSize > 0 ? ctx.Projection.PictureSize : _mediaSettings.ProductDetailsPictureSize;
                var productMediaFiles = (await ctx.ProductBatchContext.ProductMediaFiles.GetOrLoadAsync(product.Id))
                                        .ToDictionarySafe(x => x.MediaFileId, x => x);

                dynObject.ProductAttributeCombinations = productContext.Combinations
                                                         .Select(x =>
                {
                    dynamic dyn       = DataExporter.ToDynamic(x, ctx);
                    var assignedFiles = new List <dynamic>();

                    foreach (var fileId in x.GetAssignedMediaIds().Take(ctx.Projection.NumberOfMediaFiles ?? int.MaxValue))
                    {
                        if (productMediaFiles.TryGetValue(fileId, out var assignedFile) && assignedFile.MediaFile != null)
                        {
                            assignedFiles.Add(ToDynamic(assignedFile.MediaFile, _mediaSettings.ProductThumbPictureSize, pictureSize, ctx));
                        }
                    }

                    dyn.Pictures = assignedFiles;
                    return(dyn);
                })
                                                         .ToList();
            }
            else
            {
                dynObject.ProductAttributeCombinations = Enumerable.Empty <ProductVariantAttributeCombination>();
            }

            if (product.HasTierPrices)
            {
                var tierPrices = await ctx.ProductBatchContext.TierPrices.GetOrLoadAsync(product.Id);

                dynObject.TierPrices = tierPrices
                                       .RemoveDuplicatedQuantities()
                                       .Select(x =>
                {
                    dynamic dyn = new DynamicEntity(x);
                    return(dyn);
                })
                                       .ToList();
            }

            if (product.HasDiscountsApplied)
            {
                var appliedDiscounts = await ctx.ProductBatchContext.AppliedDiscounts.GetOrLoadAsync(product.Id);

                dynObject.AppliedDiscounts = appliedDiscounts
                                             .Select(x => CreateDynamic(x))
                                             .ToList();
            }

            if (product.IsDownload)
            {
                var downloads = await ctx.ProductBatchContext.Downloads.GetOrLoadAsync(product.Id);

                dynObject.Downloads = downloads
                                      .Select(x => CreateDynamic(x))
                                      .ToList();
            }

            dynObject.ProductTags = productTags
                                    .Select(x =>
            {
                var localizedName = ctx.GetTranslation(x, nameof(x.Name), x.Name);
                dynamic dyn       = new DynamicEntity(x);
                dyn.Name          = localizedName;
                dyn.SeName        = SeoHelper.BuildSlug(localizedName, _seoSettings);
                dyn._Localized    = GetLocalized(ctx, x, y => y.Name);

                return(dyn);
            })
                                    .ToList();

            dynObject.ProductSpecificationAttributes = specificationAttributes
                                                       .Select(x => ToDynamic(x, ctx))
                                                       .ToList();

            if (product.ProductType == ProductType.BundledProduct)
            {
                var bundleItems = await ctx.ProductBatchContext.ProductBundleItems.GetOrLoadAsync(product.Id);

                dynObject.ProductBundleItems = bundleItems
                                               .Select(x =>
                {
                    dynamic dyn          = new DynamicEntity(x);
                    dyn.Name             = ctx.GetTranslation(x, nameof(x.Name), x.Name);
                    dyn.ShortDescription = ctx.GetTranslation(x, nameof(x.ShortDescription), x.ShortDescription);
                    dyn._Localized       = GetLocalized(ctx, x, y => y.Name, y => y.ShortDescription);

                    return(dyn);
                })
                                               .ToList();
            }

            var mediaFiles = await ApplyMediaFiles(dynObject, product, ctx, productContext);

            await ApplyExportFeatures(dynObject, product, price, mediaFiles, ctx, productContext);

            return(dynObject);
        }