Beispiel #1
0
        /// <summary>
        /// Resolves the file name pattern for an export profile.
        /// </summary>
        /// <param name="profile">Export profile.</param>
        /// <param name="store">Store.</param>
        /// <param name="fileIndex">One based file index.</param>
        /// <param name="maxFileNameLength">The maximum length of the file name.</param>
        /// <returns>Resolved file name pattern.</returns>
        public static string ResolveFileNamePattern(this ExportProfile profile, Store store, int fileIndex, int maxFileNameLength)
        {
            using var psb = StringBuilderPool.Instance.Get(out var sb);
            sb.Append(profile.FileNamePattern);

            sb.Replace("%Profile.Id%", profile.Id.ToString());
            sb.Replace("%Profile.FolderName%", profile.FolderName);
            sb.Replace("%Store.Id%", store.Id.ToString());
            sb.Replace("%File.Index%", fileIndex.ToString("D4"));

            if (profile.FileNamePattern.Contains("%Profile.SeoName%"))
            {
                sb.Replace("%Profile.SeoName%", SeoHelper.BuildSlug(profile.Name, true, false, false).Replace("-", ""));
            }
            if (profile.FileNamePattern.Contains("%Store.SeoName%"))
            {
                sb.Replace("%Store.SeoName%", profile.PerStore ? SeoHelper.BuildSlug(store.Name, true, false, true) : "allstores");
            }
            if (profile.FileNamePattern.Contains("%Random.Number%"))
            {
                sb.Replace("%Random.Number%", CommonHelper.GenerateRandomInteger().ToString());
            }
            if (profile.FileNamePattern.Contains("%Timestamp%"))
            {
                sb.Replace("%Timestamp%", DateTime.UtcNow.ToString("s", CultureInfo.InvariantCulture));
            }

            var result = sb.ToString()
                         .ToValidFileName(string.Empty)
                         .Truncate(maxFileNameLength);

            return(result);
        }
Beispiel #2
0
        public virtual async Task <ExportProfile> InsertExportProfileAsync(
            string providerSystemName,
            string name,
            string fileExtension,
            ExportFeatures features,
            bool isSystemProfile     = false,
            string profileSystemName = null,
            int cloneFromProfileId   = 0)
        {
            Guard.NotEmpty(providerSystemName, nameof(providerSystemName));

            if (name.IsEmpty())
            {
                name = providerSystemName;
            }

            if (!isSystemProfile)
            {
                var profileCount = await _db.ExportProfiles.CountAsync(x => x.ProviderSystemName == providerSystemName);

                name = $"{T("Common.My").Value} {name} {profileCount + 1}";
            }

            TaskDescriptor task         = null;
            ExportProfile  cloneProfile = null;
            ExportProfile  profile      = null;

            if (cloneFromProfileId != 0)
            {
                cloneProfile = await _db.ExportProfiles
                               .Include(x => x.Task)
                               .Include(x => x.Deployments)
                               .FindByIdAsync(cloneFromProfileId);
            }

            if (cloneProfile == null)
            {
                task = new TaskDescriptor
                {
                    CronExpression = "0 */6 * * *",     // Every six hours.
                    Type           = nameof(DataExportTask),
                    Enabled        = false,
                    StopOnError    = false,
                    IsHidden       = true
                };
            }
            else
            {
                task = cloneProfile.Task.Clone();
            }

            task.Name = name + " Task";

            _db.TaskDescriptors.Add(task);

            // Get the task ID.
            await _db.SaveChangesAsync();

            if (cloneProfile == null)
            {
                profile = new ExportProfile
                {
                    FileNamePattern = _defaultFileNamePattern
                };

                if (isSystemProfile)
                {
                    profile.Enabled          = true;
                    profile.PerStore         = false;
                    profile.CreateZipArchive = false;
                    profile.Cleanup          = false;
                }
                else
                {
                    // What we do here is to preset typical settings for feed creation
                    // but on the other hand they may be untypical for generic data export\exchange.
                    var projection = new ExportProjection
                    {
                        RemoveCriticalCharacters = true,
                        CriticalCharacters       = "¼,½,¾",
                        PriceType         = PriceDisplayType.PreSelectedPrice,
                        NoGroupedProducts = features.HasFlag(ExportFeatures.CanOmitGroupedProducts),
                        OnlyIndividuallyVisibleAssociated = true,
                        DescriptionMerging = ExportDescriptionMerging.Description
                    };

                    var filter = new ExportFilter
                    {
                        IsPublished        = true,
                        ShoppingCartTypeId = (int)ShoppingCartType.ShoppingCart
                    };

                    using var writer1 = new StringWriter();
                    new XmlSerializer(typeof(ExportProjection)).Serialize(writer1, projection);
                    profile.Projection = writer1.ToString();

                    using var writer2 = new StringWriter();
                    new XmlSerializer(typeof(ExportFilter)).Serialize(writer2, projection);
                    profile.Filtering = writer2.ToString();
                }
            }
            else
            {
                profile = cloneProfile.Clone();
            }

            profile.IsSystemProfile    = isSystemProfile;
            profile.Name               = name;
            profile.ProviderSystemName = providerSystemName;
            profile.TaskId             = task.Id;

            var cleanedSystemName = providerSystemName
                                    .Replace("Exports.", string.Empty)
                                    .Replace("Feeds.", string.Empty)
                                    .Replace("/", string.Empty)
                                    .Replace("-", string.Empty);

            var directoryName = SeoHelper.BuildSlug(cleanedSystemName, true, false, false)
                                .ToValidPath()
                                .Truncate(_dataExchangeSettings.MaxFileNameLength);

            // TODO: (mg) (core) find out how to correctly build new app relative paths for ExportProfile.FolderName like x '~/App_Data/Tenants/Default/ExportProfiles/bmecatproductxml-1-2'
            // ExportProfile.FolderName must fulfil PathHelper.IsSafeAppRootPath! See also below.
            var tenantRoot       = DataSettings.Instance.TenantRoot;
            var profileDirectory = tenantRoot.GetDirectory("ExportProfiles");

            // TODO: (mg) (core) TenantRoot is rooted to the tenant dir and DOES NOT CONTAIN "App_Data/Tenants/{TenantName}" anymore.
            // The result will be "ExportProfiles/{UniqueDirName}, which conflicts with how we saved the path in the past ("~/App_Data/..."). You must
            // "upgrade" legacy pathes to new root whenever an entity is loaded and processed.
            profile.FolderName = tenantRoot.PathCombine(profileDirectory.SubPath, tenantRoot.CreateUniqueDirectoryName(profileDirectory.SubPath, directoryName));

            profile.SystemName = profileSystemName.IsEmpty() && isSystemProfile
                ? cleanedSystemName
                : profileSystemName;

            _db.ExportProfiles.Add(profile);

            // Get the export profile ID.
            await _db.SaveChangesAsync();

            task.Alias = profile.Id.ToString();

            if (fileExtension.HasValue() && !isSystemProfile)
            {
                if (cloneProfile == null)
                {
                    if (features.HasFlag(ExportFeatures.CreatesInitialPublicDeployment))
                    {
                        var subFolder = tenantRoot.CreateUniqueDirectoryName(DataExporter.PublicFolder, directoryName);

                        profile.Deployments.Add(new ExportDeployment
                        {
                            ProfileId      = profile.Id,
                            Enabled        = true,
                            DeploymentType = ExportDeploymentType.PublicFolder,
                            Name           = profile.Name,
                            SubFolder      = subFolder
                        });
                    }
                }
                else
                {
                    cloneProfile.Deployments.Each(x => profile.Deployments.Add(x.Clone()));
                }
            }

            // Finally update task and export profile.
            await _db.SaveChangesAsync();

            return(profile);
        }
Beispiel #3
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);
        }