From dcf631675106c5441fcc9ba84c16eb82ac60e411 Mon Sep 17 00:00:00 2001 From: Irma Date: Tue, 10 Oct 2023 12:16:04 +0200 Subject: [PATCH] lf --- .../badClass (copy) (another copy).cs | 2170 +++++++++++++++++ 1 file changed, 2170 insertions(+) create mode 100644 SynonymsAPI/Services/badClass (copy) (another copy).cs diff --git a/SynonymsAPI/Services/badClass (copy) (another copy).cs b/SynonymsAPI/Services/badClass (copy) (another copy).cs new file mode 100644 index 0000000..efc6ad2 --- /dev/null +++ b/SynonymsAPI/Services/badClass (copy) (another copy).cs @@ -0,0 +1,2170 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.AspNetCore.StaticFiles; +using Nop.Core; +using Nop.Core.Data; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Directory; +using Nop.Core.Domain.Media; +using Nop.Core.Domain.Messages; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Tax; +using Nop.Core.Domain.Vendors; +using Nop.Services.Catalog; +using Nop.Services.Directory; +using Nop.Services.ExportImport.Help; +using Nop.Services.Localization; +using Nop.Services.Logging; +using Nop.Services.Media; +using Nop.Services.Messages; +using Nop.Services.Security; +using Nop.Services.Seo; +using Nop.Services.Shipping; +using Nop.Services.Shipping.Date; +using Nop.Services.Tax; +using Nop.Services.Vendors; +using OfficeOpenXml; +using System.Net; +using Microsoft.Extensions.DependencyInjection; +using Nop.Services.Stores; +using Nop.Core.Infrastructure; +using System.Runtime.InteropServices; + +namespace Nop.Services.ExportImport +{ + /// + /// Import manager + /// + public partial class ImportManager : IImportManager + { + #region Constants + + //it's quite fast hash (to cheaply distinguish between objects) + private const string IMAGE_HASH_ALGORITHM = "SHA1"; + + private const string UPLOADS_TEMP_PATH = "~/App_Data/TempUploads"; + + #endregion + + #region Fields + + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly IProductService _productService; + private readonly IProductAttributeService _productAttributeService; + private readonly ICategoryService _categoryService; + private readonly IManufacturerService _manufacturerService; + private readonly IPictureService _pictureService; + private readonly IUrlRecordService _urlRecordService; + private readonly IStoreContext _storeContext; + private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; + private readonly ICountryService _countryService; + private readonly IStateProvinceService _stateProvinceService; + private readonly IEncryptionService _encryptionService; + private readonly IDataProvider _dataProvider; + private readonly MediaSettings _mediaSettings; + private readonly IVendorService _vendorService; + private readonly IProductTemplateService _productTemplateService; + private readonly IShippingService _shippingService; + private readonly IDateRangeService _dateRangeService; + private readonly ITaxCategoryService _taxCategoryService; + private readonly IMeasureService _measureService; + private readonly CatalogSettings _catalogSettings; + private readonly IProductTagService _productTagService; + private readonly IWorkContext _workContext; + private readonly ILocalizationService _localizationService; + private readonly ICustomerActivityService _customerActivityService; + private readonly VendorSettings _vendorSettings; + private readonly ISpecificationAttributeService _specificationAttributeService; + private readonly ILogger _logger; + private readonly IStoreMappingService _storeMappingService; + private readonly INopFileProvider _fileProvider; + + #endregion + + #region Ctor + + public ImportManager(IProductService productService, + ICategoryService categoryService, + IManufacturerService manufacturerService, + IPictureService pictureService, + IUrlRecordService urlRecordService, + IStoreContext storeContext, + INewsLetterSubscriptionService newsLetterSubscriptionService, + ICountryService countryService, + IStateProvinceService stateProvinceService, + IEncryptionService encryptionService, + IDataProvider dataProvider, + MediaSettings mediaSettings, + IVendorService vendorService, + IProductTemplateService productTemplateService, + IShippingService shippingService, + IDateRangeService dateRangeService, + ITaxCategoryService taxCategoryService, + IMeasureService measureService, + IProductAttributeService productAttributeService, + CatalogSettings catalogSettings, + IProductTagService productTagService, + IWorkContext workContext, + ILocalizationService localizationService, + ICustomerActivityService customerActivityService, + VendorSettings vendorSettings, + ISpecificationAttributeService specificationAttributeService, + ILogger logger, + IServiceScopeFactory serviceScopeFactory, + IStoreMappingService storeMappingService, + INopFileProvider fileProvider) + { + this._productService = productService; + this._categoryService = categoryService; + this._manufacturerService = manufacturerService; + this._pictureService = pictureService; + this._urlRecordService = urlRecordService; + this._storeContext = storeContext; + this._newsLetterSubscriptionService = newsLetterSubscriptionService; + this._countryService = countryService; + this._stateProvinceService = stateProvinceService; + this._encryptionService = encryptionService; + this._dataProvider = dataProvider; + this._mediaSettings = mediaSettings; + this._vendorService = vendorService; + this._productTemplateService = productTemplateService; + this._shippingService = shippingService; + this._dateRangeService = dateRangeService; + this._taxCategoryService = taxCategoryService; + this._measureService = measureService; + this._productAttributeService = productAttributeService; + this._catalogSettings = catalogSettings; + this._productTagService = productTagService; + this._workContext = workContext; + this._localizationService = localizationService; + this._customerActivityService = customerActivityService; + this._vendorSettings = vendorSettings; + this._specificationAttributeService = specificationAttributeService; + this._logger = logger; + this._serviceScopeFactory = serviceScopeFactory; + this._storeMappingService = storeMappingService; + this._fileProvider = fileProvider; + } + + #endregion + + #region Utilities + + private static ExportedAttributeType GetTypeOfExportedAttribute(ExcelWorksheet worksheet, PropertyManager productAttributeManager, PropertyManager specificationAttributeManager, int iRow) + { + productAttributeManager.ReadFromXlsx(worksheet, iRow, ExportProductAttribute.ProducAttributeCellOffset); + + if (productAttributeManager.IsCaption) + { + return ExportedAttributeType.ProductAttribute; + } + + specificationAttributeManager.ReadFromXlsx(worksheet, iRow, ExportProductAttribute.ProducAttributeCellOffset); + + if (specificationAttributeManager.IsCaption) + { + return ExportedAttributeType.SpecificationAttribute; + } + + return ExportedAttributeType.NotSpecified; + } + + private static void SetOutLineForSpecificationAttributeRow(object cellValue, ExcelWorksheet worksheet, int endRow) + { + var attributeType = (cellValue ?? string.Empty).ToString(); + + if (attributeType.Equals("AttributeType", StringComparison.InvariantCultureIgnoreCase)) + { + worksheet.Row(endRow).OutlineLevel = 1; + } + else + { + if (SpecificationAttributeType.Option.ToSelectList(useLocalization: false) + .Any(p => p.Text.Equals(attributeType, StringComparison.InvariantCultureIgnoreCase))) + worksheet.Row(endRow).OutlineLevel = 1; + } + } + + protected virtual int GetColumnIndex(string[] properties, string columnName) + { + if (properties == null) + throw new ArgumentNullException(nameof(properties)); + + if (columnName == null) + throw new ArgumentNullException(nameof(columnName)); + + for (var i = 0; i < properties.Length; i++) + if (properties[i].Equals(columnName, StringComparison.InvariantCultureIgnoreCase)) + return i + 1; //excel indexes start from 1 + return 0; + } + + protected virtual string ConvertColumnToString(object columnValue) + { + if (columnValue == null) + return null; + + return Convert.ToString(columnValue); + } + + protected virtual string GetMimeTypeFromFilePath(string filePath) + { + //TODO test ne implementation + new FileExtensionContentTypeProvider().TryGetContentType(filePath, out string mimeType); + //set to jpeg in case mime type cannot be found + if (mimeType == null) + mimeType = MimeTypes.ImageJpeg; + return mimeType; + } + + /// + /// Creates or loads the image + /// + /// The path to the image file + /// The name of the object + /// Image identifier, may be null + /// The image or null if the image has not changed + protected virtual Picture LoadPicture(string picturePath, string name, int? picId = null) + { + if (string.IsNullOrEmpty(picturePath) || !_fileProvider.FileExists(picturePath)) + return null; + + var mimeType = GetMimeTypeFromFilePath(picturePath); + var newPictureBinary = _fileProvider.ReadAllBytes(picturePath); + var pictureAlreadyExists = false; + if (picId != null) + { + //compare with existing product pictures + var existingPicture = _pictureService.GetPictureById(picId.Value); + if (existingPicture != null) + { + var existingBinary = _pictureService.LoadPictureBinary(existingPicture); + //picture binary after validation (like in database) + var validatedPictureBinary = _pictureService.ValidatePicture(newPictureBinary, mimeType); + if (existingBinary.SequenceEqual(validatedPictureBinary) || + existingBinary.SequenceEqual(newPictureBinary)) + { + pictureAlreadyExists = true; + } + } + } + + if (pictureAlreadyExists) return null; + + var newPicture = _pictureService.InsertPicture(newPictureBinary, mimeType, _pictureService.GetPictureSeName(name)); + return newPicture; + } + + private void LogPictureInsertError(string picturePath, Exception ex) + { + var extension = _fileProvider.GetFileExtension(picturePath); + var name = _fileProvider.GetFileNameWithoutExtension(picturePath); + + var point = string.IsNullOrEmpty(extension) ? string.Empty : "."; + var fileName = _fileProvider.FileExists(picturePath) ? $"{name}{point}{extension}" : string.Empty; + _logger.Error($"Insert picture failed (file name: {fileName})", ex); + } + + protected virtual void ImportProductImagesUsingServices(IList productPictureMetadata) + { + foreach (var product in productPictureMetadata) + { + foreach (var picturePath in new[] { product.Picture1Path, product.Picture2Path, product.Picture3Path }) + { + if (string.IsNullOrEmpty(picturePath)) + continue; + + var mimeType = GetMimeTypeFromFilePath(picturePath); + var newPictureBinary = _fileProvider.ReadAllBytes(picturePath); + var pictureAlreadyExists = false; + if (!product.IsNew) + { + //compare with existing product pictures + var existingPictures = _pictureService.GetPicturesByProductId(product.ProductItem.Id); + foreach (var existingPicture in existingPictures) + { + var existingBinary = _pictureService.LoadPictureBinary(existingPicture); + //picture binary after validation (like in database) + var validatedPictureBinary = _pictureService.ValidatePicture(newPictureBinary, mimeType); + if (!existingBinary.SequenceEqual(validatedPictureBinary) && + !existingBinary.SequenceEqual(newPictureBinary)) + continue; + //the same picture content + pictureAlreadyExists = true; + break; + } + } + + if (pictureAlreadyExists) + continue; + + try + { + var newPicture = _pictureService.InsertPicture(newPictureBinary, mimeType, _pictureService.GetPictureSeName(product.ProductItem.Name)); + product.ProductItem.ProductPictures.Add(new ProductPicture + { + //EF has some weird issue if we set "Picture = newPicture" instead of "PictureId = newPicture.Id" + //pictures are duplicated + //maybe because entity size is too large + PictureId = newPicture.Id, + DisplayOrder = 1, + }); + _productService.UpdateProduct(product.ProductItem); + } + catch (Exception ex) + { + LogPictureInsertError(picturePath, ex); + } + } + } + } + + protected virtual void ImportProductImagesUsingHash(IList productPictureMetadata, IList allProductsBySku) + { + //performance optimization, load all pictures hashes + //it will only be used if the images are stored in the SQL Server database (not compact) + var takeCount = _dataProvider.SupportedLengthOfBinaryHash() - 1; + var productsImagesIds = _productService.GetProductsImagesIds(allProductsBySku.Select(p => p.Id).ToArray()); + var allPicturesHashes = _pictureService.GetPicturesHash(productsImagesIds.SelectMany(p => p.Value).ToArray()); + + foreach (var product in productPictureMetadata) + { + foreach (var picturePath in new[] { product.Picture1Path, product.Picture2Path, product.Picture3Path }) + { + if (string.IsNullOrEmpty(picturePath)) + continue; + try + { + var mimeType = GetMimeTypeFromFilePath(picturePath); + var newPictureBinary = _fileProvider.ReadAllBytes(picturePath); + var pictureAlreadyExists = false; + if (!product.IsNew) + { + var newImageHash = _encryptionService.CreateHash(newPictureBinary.Take(takeCount).ToArray(), + IMAGE_HASH_ALGORITHM); + var newValidatedImageHash = _encryptionService.CreateHash(_pictureService.ValidatePicture(newPictureBinary, mimeType) + .Take(takeCount) + .ToArray(), IMAGE_HASH_ALGORITHM); + + var imagesIds = productsImagesIds.ContainsKey(product.ProductItem.Id) + ? productsImagesIds[product.ProductItem.Id] + : new int[0]; + + pictureAlreadyExists = allPicturesHashes.Where(p => imagesIds.Contains(p.Key)) + .Select(p => p.Value).Any(p => p == newImageHash || p == newValidatedImageHash); + } + + if (pictureAlreadyExists) + continue; + + var newPicture = _pictureService.InsertPicture(newPictureBinary, mimeType, _pictureService.GetPictureSeName(product.ProductItem.Name)); + product.ProductItem.ProductPictures.Add(new ProductPicture + { + //EF has some weird issue if we set "Picture = newPicture" instead of "PictureId = newPicture.Id" + //pictures are duplicated + //maybe because entity size is too large + PictureId = newPicture.Id, + DisplayOrder = 1, + }); + _productService.UpdateProduct(product.ProductItem); + } + catch (Exception ex) + { + LogPictureInsertError(picturePath, ex); + } + } + } + } + + protected virtual string UpdateCategoryByXlsx(Category category, PropertyManager manager, Dictionary allCategories, bool isNew, out bool isParentCategoryExists) + { + var seName = string.Empty; + isParentCategoryExists = true; + var isParentCategorySet = false; + + foreach (var property in manager.GetProperties) + { + switch (property.PropertyName) + { + case "Name": + category.Name = property.StringValue.Split(new[] { ">>" }, StringSplitOptions.RemoveEmptyEntries).Last().Trim(); + break; + case "Description": + category.Description = property.StringValue; + break; + case "CategoryTemplateId": + category.CategoryTemplateId = property.IntValue; + break; + case "MetaKeywords": + category.MetaKeywords = property.StringValue; + break; + case "MetaDescription": + category.MetaDescription = property.StringValue; + break; + case "MetaTitle": + category.MetaTitle = property.StringValue; + break; + case "ParentCategoryId": + if (!isParentCategorySet) + { + var parentCategory = allCategories.Values.FirstOrDefault(c => c.Id == property.IntValue); + isParentCategorySet = parentCategory != null; + + isParentCategoryExists = isParentCategorySet || property.IntValue == 0; + + category.ParentCategoryId = parentCategory?.Id ?? property.IntValue; + } + break; + case "ParentCategoryName": + if (_catalogSettings.ExportImportCategoriesUsingCategoryName && !isParentCategorySet) + { + var categoryName = manager.GetProperty("ParentCategoryName").StringValue; + if (!string.IsNullOrEmpty(categoryName)) + { + var parentCategory = allCategories.ContainsKey(categoryName) + //try find category by full name with all parent category names + ? allCategories[categoryName] + //try find category by name + : allCategories.Values.FirstOrDefault(c => c.Name.Equals(categoryName, StringComparison.InvariantCulture)); + + if (parentCategory != null) + { + category.ParentCategoryId = parentCategory.Id; + isParentCategorySet = true; + } + else + { + isParentCategoryExists = false; + } + } + } + break; + case "Picture": + var picture = LoadPicture(manager.GetProperty("Picture").StringValue, category.Name, isNew ? null : (int?)category.PictureId); + if (picture != null) + category.PictureId = picture.Id; + break; + case "PageSize": + category.PageSize = property.IntValue; + break; + case "AllowCustomersToSelectPageSize": + category.AllowCustomersToSelectPageSize = property.BooleanValue; + break; + case "PageSizeOptions": + category.PageSizeOptions = property.StringValue; + break; + case "PriceRanges": + category.PriceRanges = property.StringValue; + break; + case "ShowOnHomePage": + category.ShowOnHomePage = property.BooleanValue; + break; + case "IncludeInTopMenu": + category.IncludeInTopMenu = property.BooleanValue; + break; + case "Published": + category.Published = property.BooleanValue; + break; + case "DisplayOrder": + category.DisplayOrder = property.IntValue; + break; + case "SeName": + seName = property.StringValue; + break; + } + } + + category.UpdatedOnUtc = DateTime.UtcNow; + return seName; + } + + protected virtual Category GetCategoryFromXlsx(PropertyManager manager, ExcelWorksheet worksheet, int iRow, Dictionary allCategories, out bool isNew, out string curentCategoryBreadCrumb) + { + manager.ReadFromXlsx(worksheet, iRow); + + //try get category from database by ID + var category = allCategories.Values.FirstOrDefault(c => c.Id == manager.GetProperty("Id")?.IntValue); + + if (_catalogSettings.ExportImportCategoriesUsingCategoryName && category == null) + { + var categoryName = manager.GetProperty("Name").StringValue; + if (!string.IsNullOrEmpty(categoryName)) + { + category = allCategories.ContainsKey(categoryName) + //try find category by full name with all parent category names + ? allCategories[categoryName] + //try find category by name + : allCategories.Values.FirstOrDefault(c => c.Name.Equals(categoryName, StringComparison.InvariantCulture)); + } + } + + isNew = category == null; + + category = category ?? new Category(); + + curentCategoryBreadCrumb = string.Empty; + + if (isNew) + { + category.CreatedOnUtc = DateTime.UtcNow; + //default values + category.PageSize = _catalogSettings.DefaultCategoryPageSize; + category.PageSizeOptions = _catalogSettings.DefaultCategoryPageSizeOptions; + category.Published = true; + category.IncludeInTopMenu = true; + category.AllowCustomersToSelectPageSize = true; + } + else + { + curentCategoryBreadCrumb = category.GetFormattedBreadCrumb(_categoryService); + } + + return category; + } + + protected virtual void SaveCategory(bool isNew, Category category, Dictionary allCategories, string curentCategoryBreadCrumb, bool setSeName, string seName) + { + if (isNew) + _categoryService.InsertCategory(category); + else + _categoryService.UpdateCategory(category); + + var categoryBreadCrumb = category.GetFormattedBreadCrumb(_categoryService); + if (!allCategories.ContainsKey(categoryBreadCrumb)) + allCategories.Add(categoryBreadCrumb, category); + if (!string.IsNullOrEmpty(curentCategoryBreadCrumb) && allCategories.ContainsKey(curentCategoryBreadCrumb) && + categoryBreadCrumb != curentCategoryBreadCrumb) + allCategories.Remove(curentCategoryBreadCrumb); + + //search engine name + if (setSeName) + _urlRecordService.SaveSlug(category, category.ValidateSeName(seName, category.Name, true), 0); + } + + protected virtual void SetOutLineForProductAttributeRow(object cellValue, ExcelWorksheet worksheet, int endRow) + { + try + { + var aid = Convert.ToInt32(cellValue ?? -1); + + var productAttribute = _productAttributeService.GetProductAttributeById(aid); + + if (productAttribute != null) + worksheet.Row(endRow).OutlineLevel = 1; + } + catch (FormatException) + { + if ((cellValue ?? string.Empty).ToString() == "AttributeId") + worksheet.Row(endRow).OutlineLevel = 1; + } + } + + protected virtual void ImportProductAttribute(PropertyManager productAttributeManager, Product lastLoadedProduct) + { + if (!_catalogSettings.ExportImportProductAttributes || lastLoadedProduct == null || productAttributeManager.IsCaption) + return; + + var productAttributeId = productAttributeManager.GetProperty("AttributeId").IntValue; + var attributeControlTypeId = productAttributeManager.GetProperty("AttributeControlType").IntValue; + + var productAttributeValueId = productAttributeManager.GetProperty("ProductAttributeValueId").IntValue; + var associatedProductId = productAttributeManager.GetProperty("AssociatedProductId").IntValue; + var valueName = productAttributeManager.GetProperty("ValueName").StringValue; + var attributeValueTypeId = productAttributeManager.GetProperty("AttributeValueType").IntValue; + var colorSquaresRgb = productAttributeManager.GetProperty("ColorSquaresRgb").StringValue; + var imageSquaresPictureId = productAttributeManager.GetProperty("ImageSquaresPictureId").IntValue; + var priceAdjustment = productAttributeManager.GetProperty("PriceAdjustment").DecimalValue; + var priceAdjustmentUsePercentage = productAttributeManager.GetProperty("PriceAdjustmentUsePercentage").BooleanValue; + var weightAdjustment = productAttributeManager.GetProperty("WeightAdjustment").DecimalValue; + var cost = productAttributeManager.GetProperty("Cost").DecimalValue; + var customerEntersQty = productAttributeManager.GetProperty("CustomerEntersQty").BooleanValue; + var quantity = productAttributeManager.GetProperty("Quantity").IntValue; + var isPreSelected = productAttributeManager.GetProperty("IsPreSelected").BooleanValue; + var displayOrder = productAttributeManager.GetProperty("DisplayOrder").IntValue; + var pictureId = productAttributeManager.GetProperty("PictureId").IntValue; + var textPrompt = productAttributeManager.GetProperty("AttributeTextPrompt").StringValue; + var isRequired = productAttributeManager.GetProperty("AttributeIsRequired").BooleanValue; + var attributeDisplayOrder = productAttributeManager.GetProperty("AttributeDisplayOrder").IntValue; + + var productAttributeMapping = + lastLoadedProduct.ProductAttributeMappings.FirstOrDefault( + pam => pam.ProductAttributeId == productAttributeId); + + if (productAttributeMapping == null) + { + //insert mapping + productAttributeMapping = new ProductAttributeMapping + { + ProductId = lastLoadedProduct.Id, + ProductAttributeId = productAttributeId, + TextPrompt = textPrompt, + IsRequired = isRequired, + AttributeControlTypeId = attributeControlTypeId, + DisplayOrder = attributeDisplayOrder + }; + _productAttributeService.InsertProductAttributeMapping(productAttributeMapping); + } + else + { + productAttributeMapping.AttributeControlTypeId = attributeControlTypeId; + productAttributeMapping.TextPrompt = textPrompt; + productAttributeMapping.IsRequired = isRequired; + productAttributeMapping.DisplayOrder = attributeDisplayOrder; + _productAttributeService.UpdateProductAttributeMapping(productAttributeMapping); + } + + var pav = _productAttributeService.GetProductAttributeValueById(productAttributeValueId); + + var attributeControlType = (AttributeControlType)attributeControlTypeId; + + if (pav == null) + { + switch (attributeControlType) + { + case AttributeControlType.Datepicker: + case AttributeControlType.FileUpload: + case AttributeControlType.MultilineTextbox: + case AttributeControlType.TextBox: + return; + } + + pav = new ProductAttributeValue + { + ProductAttributeMappingId = productAttributeMapping.Id, + AttributeValueType = (AttributeValueType)attributeValueTypeId, + AssociatedProductId = associatedProductId, + Name = valueName, + PriceAdjustment = priceAdjustment, + PriceAdjustmentUsePercentage = priceAdjustmentUsePercentage, + WeightAdjustment = weightAdjustment, + Cost = cost, + IsPreSelected = isPreSelected, + DisplayOrder = displayOrder, + ColorSquaresRgb = colorSquaresRgb, + ImageSquaresPictureId = imageSquaresPictureId, + CustomerEntersQty = customerEntersQty, + Quantity = quantity, + PictureId = pictureId + }; + + _productAttributeService.InsertProductAttributeValue(pav); + } + else + { + pav.AttributeValueTypeId = attributeValueTypeId; + pav.AssociatedProductId = associatedProductId; + pav.Name = valueName; + pav.ColorSquaresRgb = colorSquaresRgb; + pav.ImageSquaresPictureId = imageSquaresPictureId; + pav.PriceAdjustment = priceAdjustment; + pav.PriceAdjustmentUsePercentage = priceAdjustmentUsePercentage; + pav.WeightAdjustment = weightAdjustment; + pav.Cost = cost; + pav.CustomerEntersQty = customerEntersQty; + pav.Quantity = quantity; + pav.IsPreSelected = isPreSelected; + pav.DisplayOrder = displayOrder; + pav.PictureId = pictureId; + + _productAttributeService.UpdateProductAttributeValue(pav); + } + } + + private void ImportSpecificationAttribute(PropertyManager specificationAttributeManager, Product lastLoadedProduct) + { + if (!_catalogSettings.ExportImportProductSpecificationAttributes || lastLoadedProduct == null || specificationAttributeManager.IsCaption) + return; + + var attributeTypeId = specificationAttributeManager.GetProperty("AttributeType").IntValue; + var allowFiltering = specificationAttributeManager.GetProperty("AllowFiltering").BooleanValue; + var specificationAttributeOptionId = specificationAttributeManager.GetProperty("SpecificationAttributeOptionId").IntValue; + var productId = lastLoadedProduct.Id; + var customValue = specificationAttributeManager.GetProperty("CustomValue").StringValue; + var displayOrder = specificationAttributeManager.GetProperty("DisplayOrder").IntValue; + var showOnProductPage = specificationAttributeManager.GetProperty("ShowOnProductPage").BooleanValue; + + //if specification attribute option isn't set, try to get first of possible specification attribute option for current specification attribute + if (specificationAttributeOptionId == 0) + { + var specificationAttribute = specificationAttributeManager.GetProperty("SpecificationAttribute").IntValue; + specificationAttributeOptionId = _specificationAttributeService.GetSpecificationAttributeOptionsBySpecificationAttribute(specificationAttribute) + .FirstOrDefault()?.Id ?? specificationAttributeOptionId; + } + + var productSpecificationAttribute = specificationAttributeOptionId == 0 + ? null + : _specificationAttributeService.GetProductSpecificationAttributes(productId, specificationAttributeOptionId).FirstOrDefault(); + + var isNew = productSpecificationAttribute == null; + + if (isNew) + { + productSpecificationAttribute = new ProductSpecificationAttribute(); + } + + if (attributeTypeId != (int)SpecificationAttributeType.Option) + { + //we allow filtering only for "Option" attribute type + allowFiltering = false; + } + + //we don't allow CustomValue for "Option" attribute type + if (attributeTypeId == (int)SpecificationAttributeType.Option) + { + customValue = null; + } + + productSpecificationAttribute.AttributeTypeId = attributeTypeId; + productSpecificationAttribute.SpecificationAttributeOptionId = specificationAttributeOptionId; + productSpecificationAttribute.ProductId = productId; + productSpecificationAttribute.CustomValue = customValue; + productSpecificationAttribute.AllowFiltering = allowFiltering; + productSpecificationAttribute.ShowOnProductPage = showOnProductPage; + productSpecificationAttribute.DisplayOrder = displayOrder; + + if (isNew) + { + _specificationAttributeService.InsertProductSpecificationAttribute(productSpecificationAttribute); + } + else + { + _specificationAttributeService.UpdateProductSpecificationAttribute(productSpecificationAttribute); + } + } + + private string DownloadFile(string urlString, IList downloadedFiles) + { + if (string.IsNullOrEmpty(urlString)) + return string.Empty; + + if (!Uri.IsWellFormedUriString(urlString, UriKind.Absolute)) + return urlString; + + if (!_catalogSettings.ExportImportAllowDownloadImages) + return string.Empty; + + //ensure that temp directory is created + var tempDirectory = _fileProvider.MapPath(UPLOADS_TEMP_PATH); + _fileProvider.CreateDirectory(tempDirectory); + + var fileName = _fileProvider.GetFileName(urlString); + if (string.IsNullOrEmpty(fileName)) + return string.Empty; + + var filePath = _fileProvider.Combine(tempDirectory, fileName); + try + { + WebRequest.Create(urlString); + } + catch + { + return string.Empty; + } + + try + { + byte[] fileData; + using (var client = new WebClient()) + { + fileData = client.DownloadData(urlString); + } + using (var fs = new FileStream(filePath, FileMode.OpenOrCreate)) + { + fs.Write(fileData, 0, fileData.Length); + } + + downloadedFiles?.Add(filePath); + return filePath; + } + catch (Exception ex) + { + _logger.Error("Download image failed", ex); + } + + return string.Empty; + } + + private ImportProductMetadata PrepareImportProductData(ExcelWorksheet worksheet) + { + //the columns + var properties = GetPropertiesByExcelCells(worksheet); + + var manager = new PropertyManager(properties); + + var attributProperties = new[] + { + new PropertyByName("AttributeId"), + new PropertyByName("AttributeName"), + new PropertyByName("AttributeTextPrompt"), + new PropertyByName("AttributeIsRequired"), + new PropertyByName("AttributeControlType") + { + DropDownElements = AttributeControlType.TextBox.ToSelectList(useLocalization: false) + }, + new PropertyByName("AttributeDisplayOrder"), + new PropertyByName("ProductAttributeValueId"), + new PropertyByName("ValueName"), + new PropertyByName("AttributeValueType") + { + DropDownElements = AttributeValueType.Simple.ToSelectList(useLocalization: false) + }, + new PropertyByName("AssociatedProductId"), + new PropertyByName("ColorSquaresRgb"), + new PropertyByName("ImageSquaresPictureId"), + new PropertyByName("PriceAdjustment"), + new PropertyByName("PriceAdjustmentUsePercentage"), + new PropertyByName("WeightAdjustment"), + new PropertyByName("Cost"), + new PropertyByName("CustomerEntersQty"), + new PropertyByName("Quantity"), + new PropertyByName("IsPreSelected"), + new PropertyByName("DisplayOrder"), + new PropertyByName("PictureId") + }; + + var productAttributeManager = new PropertyManager(attributProperties); + + var attributeProperties = new[] + { + new PropertyByName("AttributeType", p => p.AttributeTypeId) + { + DropDownElements = SpecificationAttributeType.Option.ToSelectList(useLocalization: false) + }, + new PropertyByName("SpecificationAttribute", + p => p.SpecificationAttributeId) + { + DropDownElements = _specificationAttributeService.GetSpecificationAttributes() + .Select(sa => sa as BaseEntity) + .ToSelectList(p => (p as SpecificationAttribute)?.Name ?? string.Empty) + }, + new PropertyByName("CustomValue", p => p.CustomValue), + new PropertyByName("SpecificationAttributeOptionId", + p => p.SpecificationAttributeOptionId), + new PropertyByName("AllowFiltering", p => p.AllowFiltering), + new PropertyByName("ShowOnProductPage", p => p.ShowOnProductPage), + new PropertyByName("DisplayOrder", p => p.DisplayOrder) + }; + + var specificationAttributeManager = new PropertyManager(attributeProperties); + + var endRow = 2; + var allCategoriesNames = new List(); + var allSku = new List(); + + var tempProperty = manager.GetProperty("Categories"); + var categoryCellNum = tempProperty?.PropertyOrderPosition ?? -1; + + tempProperty = manager.GetProperty("SKU"); + var skuCellNum = tempProperty?.PropertyOrderPosition ?? -1; + + var allManufacturersNames = new List(); + tempProperty = manager.GetProperty("Manufacturers"); + var manufacturerCellNum = tempProperty?.PropertyOrderPosition ?? -1; + + manager.SetSelectList("ProductType", ProductType.SimpleProduct.ToSelectList(useLocalization: false)); + manager.SetSelectList("GiftCardType", GiftCardType.Virtual.ToSelectList(useLocalization: false)); + manager.SetSelectList("DownloadActivationType", + DownloadActivationType.Manually.ToSelectList(useLocalization: false)); + manager.SetSelectList("ManageInventoryMethod", + ManageInventoryMethod.DontManageStock.ToSelectList(useLocalization: false)); + manager.SetSelectList("LowStockActivity", LowStockActivity.Nothing.ToSelectList(useLocalization: false)); + manager.SetSelectList("BackorderMode", BackorderMode.NoBackorders.ToSelectList(useLocalization: false)); + manager.SetSelectList("RecurringCyclePeriod", + RecurringProductCyclePeriod.Days.ToSelectList(useLocalization: false)); + manager.SetSelectList("RentalPricePeriod", RentalPricePeriod.Days.ToSelectList(useLocalization: false)); + + manager.SetSelectList("Vendor", + _vendorService.GetAllVendors(showHidden: true).Select(v => v as BaseEntity) + .ToSelectList(p => (p as Vendor)?.Name ?? string.Empty)); + manager.SetSelectList("ProductTemplate", + _productTemplateService.GetAllProductTemplates().Select(pt => pt as BaseEntity) + .ToSelectList(p => (p as ProductTemplate)?.Name ?? string.Empty)); + manager.SetSelectList("DeliveryDate", + _dateRangeService.GetAllDeliveryDates().Select(dd => dd as BaseEntity) + .ToSelectList(p => (p as DeliveryDate)?.Name ?? string.Empty)); + manager.SetSelectList("ProductAvailabilityRange", + _dateRangeService.GetAllProductAvailabilityRanges().Select(range => range as BaseEntity) + .ToSelectList(p => (p as ProductAvailabilityRange)?.Name ?? string.Empty)); + manager.SetSelectList("TaxCategory", + _taxCategoryService.GetAllTaxCategories().Select(tc => tc as BaseEntity) + .ToSelectList(p => (p as TaxCategory)?.Name ?? string.Empty)); + manager.SetSelectList("BasepriceUnit", + _measureService.GetAllMeasureWeights().Select(mw => mw as BaseEntity) + .ToSelectList(p => (p as MeasureWeight)?.Name ?? string.Empty)); + manager.SetSelectList("BasepriceBaseUnit", + _measureService.GetAllMeasureWeights().Select(mw => mw as BaseEntity) + .ToSelectList(p => (p as MeasureWeight)?.Name ?? string.Empty)); + + var allAttributeIds = new List(); + var allSpecificationAttributeOptionIds = new List(); + + var attributeIdCellNum = 1 + ExportProductAttribute.ProducAttributeCellOffset; + var specificationAttributeOptionIdCellNum = + specificationAttributeManager.GetIndex("SpecificationAttributeOptionId") + + ExportProductAttribute.ProducAttributeCellOffset; + + var productsInFile = new List(); + + //find end of data + while (true) + { + var allColumnsAreEmpty = manager.GetProperties + .Select(property => worksheet.Cells[endRow, property.PropertyOrderPosition]) + .All(cell => string.IsNullOrEmpty(cell?.Value?.ToString())); + + if (allColumnsAreEmpty) + break; + + if (new[] { 1, 2 }.Select(cellNum => worksheet.Cells[endRow, cellNum]) + .All(cell => string.IsNullOrEmpty(cell?.Value?.ToString())) && + worksheet.Row(endRow).OutlineLevel == 0) + { + var cellValue = worksheet.Cells[endRow, attributeIdCellNum].Value; + SetOutLineForProductAttributeRow(cellValue, worksheet, endRow); + SetOutLineForSpecificationAttributeRow(cellValue, worksheet, endRow); + } + + if (worksheet.Row(endRow).OutlineLevel != 0) + { + productAttributeManager.ReadFromXlsx(worksheet, endRow, + ExportProductAttribute.ProducAttributeCellOffset); + if (!productAttributeManager.IsCaption) + { + if (int.TryParse((worksheet.Cells[endRow, attributeIdCellNum].Value ?? string.Empty).ToString(), + out int aid)) + { + allAttributeIds.Add(aid); + } + else + { + specificationAttributeManager.ReadFromXlsx(worksheet, endRow, + ExportProductAttribute.ProducAttributeCellOffset); + + if (!specificationAttributeManager.IsCaption && + int.TryParse( + (worksheet.Cells[endRow, specificationAttributeOptionIdCellNum].Value ?? + string.Empty).ToString(), out int saoid)) + { + allSpecificationAttributeOptionIds.Add(saoid); + } + } + } + + endRow++; + continue; + } + + if (categoryCellNum > 0) + { + var categoryIds = worksheet.Cells[endRow, categoryCellNum].Value?.ToString() ?? string.Empty; + + if (!string.IsNullOrEmpty(categoryIds)) + allCategoriesNames.AddRange(categoryIds + .Split(new[] { ";", ">>" }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()) + .Distinct()); + } + + if (skuCellNum > 0) + { + var sku = worksheet.Cells[endRow, skuCellNum].Value?.ToString() ?? string.Empty; + + if (!string.IsNullOrEmpty(sku)) + allSku.Add(sku); + } + + if (manufacturerCellNum > 0) + { + var manufacturerIds = worksheet.Cells[endRow, manufacturerCellNum].Value?.ToString() ?? + string.Empty; + if (!string.IsNullOrEmpty(manufacturerIds)) + allManufacturersNames.AddRange(manufacturerIds + .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim())); + } + + //counting the number of products + productsInFile.Add(endRow); + + endRow++; + } + + //performance optimization, the check for the existence of the categories in one SQL request + var notExistingCategories = _categoryService.GetNotExistingCategories(allCategoriesNames.ToArray()); + if (notExistingCategories.Any()) + { + throw new ArgumentException(string.Format(_localizationService.GetResource("Admin.Catalog.Products.Import.CategoriesDontExist"), string.Join(", ", notExistingCategories))); + } + + //performance optimization, the check for the existence of the manufacturers in one SQL request + var notExistingManufacturers = _manufacturerService.GetNotExistingManufacturers(allManufacturersNames.ToArray()); + if (notExistingManufacturers.Any()) + { + throw new ArgumentException(string.Format(_localizationService.GetResource("Admin.Catalog.Products.Import.ManufacturersDontExist"), string.Join(", ", notExistingManufacturers))); + } + + //performance optimization, the check for the existence of the product attributes in one SQL request + var notExistingProductAttributes = _productAttributeService.GetNotExistingAttributes(allAttributeIds.ToArray()); + if (notExistingProductAttributes.Any()) + { + throw new ArgumentException(string.Format(_localizationService.GetResource("Admin.Catalog.Products.Import.ProductAttributesDontExist"), string.Join(", ", notExistingProductAttributes))); + } + + //performance optimization, the check for the existence of the specification attribute options in one SQL request + var notExistingSpecificationAttributeOptions = _specificationAttributeService.GetNotExistingSpecificationAttributeOptions(allSpecificationAttributeOptionIds.Where(saoId => saoId != 0).ToArray()); + if (notExistingSpecificationAttributeOptions.Any()) + { + throw new ArgumentException($"The following specification attribute option ID(s) don't exist - {string.Join(", ", notExistingSpecificationAttributeOptions)}"); + } + + return new ImportProductMetadata + { + EndRow = endRow, + Manager = manager, + Properties = properties, + ProductsInFile = productsInFile, + ProductAttributeManager = productAttributeManager, + SpecificationAttributeManager = specificationAttributeManager, + SkuCellNum = skuCellNum, + AllSku = allSku, + AllCategoriesNames = allCategoriesNames, + AllManufacturersNames = allManufacturersNames, + AllAttributeIds = allAttributeIds, + AllSpecificationAttributeOptionIds = allSpecificationAttributeOptionIds + }; + } + + private void ImportProductsFromSplitedXlsx(ExcelWorksheet worksheet, ImportProductMetadata metadata) + { + foreach (var path in SplitProductFile(worksheet, metadata)) + { + using (var scope = _serviceScopeFactory.CreateScope()) + { + // Resolve + var importManager = scope.ServiceProvider.GetRequiredService(); + + using (var sr = new StreamReader(path)) + { + importManager.ImportProductsFromXlsx(sr.BaseStream); + } + } + + try + { + _fileProvider.DeleteFile(path); + } + catch + { + + } + } + } + + private IList SplitProductFile(ExcelWorksheet worksheet, ImportProductMetadata metadata) + { + var fileIndex = 1; + var fileName = Guid.NewGuid().ToString(); + var endCell = metadata.Properties.Max(p => p.PropertyOrderPosition); + + var filePaths = new List(); + + while (true) + { + var curIndex = fileIndex * _catalogSettings.ExportImportProductsCountInOneFile; + + var startRow = metadata.ProductsInFile[(fileIndex - 1) * _catalogSettings.ExportImportProductsCountInOneFile]; + + var endRow = metadata.CountProductsInFile > curIndex + 1 + ? metadata.ProductsInFile[curIndex - 1] + : metadata.EndRow; + + var filePath = $"{_fileProvider.MapPath(UPLOADS_TEMP_PATH)}/{fileName}_part_{fileIndex}.xlsx"; + + CopyDataToNewFile(metadata, worksheet, filePath, startRow, endRow, endCell); + + filePaths.Add(filePath); + fileIndex += 1; + + if (endRow == metadata.EndRow) + break; + } + + return filePaths; + } + + private static void CopyDataToNewFile(ImportProductMetadata metadata, ExcelWorksheet worksheet, string filePath, int startRow, int endRow, int endCell) + { + using (var stream = new FileStream(filePath, FileMode.OpenOrCreate)) + { + // ok, we can run the real code of the sample now + using (var xlPackage = new ExcelPackage(stream)) + { + // uncomment this line if you want the XML written out to the outputDir + //xlPackage.DebugMode = true; + + // get handles to the worksheets + var outWorksheet = xlPackage.Workbook.Worksheets.Add(typeof(Product).Name); + metadata.Manager.WriteCaption(outWorksheet, style => style.Hidden = false); + var outRow = 2; + for (var row = startRow; row <= endRow; row++) + { + outWorksheet.Row(outRow).OutlineLevel = worksheet.Row(row).OutlineLevel; + for (var cell = 1; cell <= endCell; cell++) + { + outWorksheet.Cells[outRow, cell].Value = worksheet.Cells[row, cell].Value; + } + + outRow += 1; + } + + xlPackage.Save(); + } + } + } + + #endregion + + #region Methods + + /// + /// Get property list by excel cells + /// + /// Type of object + /// Excel worksheet + /// Property list + public static IList> GetPropertiesByExcelCells(ExcelWorksheet worksheet) + { + var properties = new List>(); + var poz = 1; + while (true) + { + try + { + var cell = worksheet.Cells[1, poz]; + + if (string.IsNullOrEmpty(cell?.Value?.ToString())) + break; + + poz += 1; + properties.Add(new PropertyByName(cell.Value.ToString())); + } + catch + { + break; + } + } + + return properties; + } + + /// + /// Import products from XLSX file + /// + /// Stream + public virtual void ImportProductsFromXlsx(Stream stream) + { + using (var xlPackage = new ExcelPackage(stream)) + { + // get the first worksheet in the workbook + var worksheet = xlPackage.Workbook.Worksheets.FirstOrDefault(); + if (worksheet == null) + throw new NopException("No worksheet found"); + + var downloadedFiles = new List(); + + var metadata = PrepareImportProductData(worksheet); + + if (_catalogSettings.ExportImportSplitProductsFile && metadata.CountProductsInFile > _catalogSettings.ExportImportProductsCountInOneFile) + { + ImportProductsFromSplitedXlsx(worksheet, metadata); + return; + } + + //performance optimization, load all products by SKU in one SQL request + var allProductsBySku = _productService.GetProductsBySku(metadata.AllSku.ToArray(), _workContext.CurrentVendor?.Id ?? 0); + + //validate maximum number of products per vendor + if (_vendorSettings.MaximumProductNumber > 0 && + _workContext.CurrentVendor != null) + { + var newProductsCount = metadata.CountProductsInFile - allProductsBySku.Count; + if (_productService.GetNumberOfProductsByVendorId(_workContext.CurrentVendor.Id) + newProductsCount > _vendorSettings.MaximumProductNumber) + throw new ArgumentException(string.Format(_localizationService.GetResource("Admin.Catalog.Products.ExceededMaximumNumber"), _vendorSettings.MaximumProductNumber)); + } + + //performance optimization, load all categories IDs for products in one SQL request + var allProductsCategoryIds = _categoryService.GetProductCategoryIds(allProductsBySku.Select(p => p.Id).ToArray()); + + //performance optimization, load all categories in one SQL request + Dictionary allCategories; + try + { + allCategories = _categoryService + .GetAllCategories(showHidden: true, loadCacheableCopy: false) + .ToDictionary(c => new CategoryKey(c, _categoryService, _storeMappingService), c => c); + } + catch (ArgumentException) + { + //categories with the same name are not supported in the same category level + throw new ArgumentException(_localizationService.GetResource("Admin.Catalog.Products.Import.CategoriesWithSameNameNotSupported")); + } + + //performance optimization, load all manufacturers IDs for products in one SQL request + var allProductsManufacturerIds = _manufacturerService.GetProductManufacturerIds(allProductsBySku.Select(p => p.Id).ToArray()); + + //performance optimization, load all manufacturers in one SQL request + var allManufacturers = _manufacturerService.GetAllManufacturers(showHidden: true); + + //product to import images + var productPictureMetadata = new List(); + + Product lastLoadedProduct = null; + var typeOfExportedAttribute = ExportedAttributeType.NotSpecified; + + for (var iRow = 2; iRow < metadata.EndRow; iRow++) + { + //imports product attributes + if (worksheet.Row(iRow).OutlineLevel != 0) + { + if (lastLoadedProduct == null) + continue; + + var newTypeOfExportedAttribute = GetTypeOfExportedAttribute(worksheet, metadata.ProductAttributeManager, metadata.SpecificationAttributeManager, iRow); + + //skip caption row + if (newTypeOfExportedAttribute != ExportedAttributeType.NotSpecified && + newTypeOfExportedAttribute != typeOfExportedAttribute) + { + typeOfExportedAttribute = newTypeOfExportedAttribute; + continue; + } + + switch (typeOfExportedAttribute) + { + case ExportedAttributeType.ProductAttribute: + ImportProductAttribute(metadata.ProductAttributeManager, lastLoadedProduct); + break; + case ExportedAttributeType.SpecificationAttribute: + ImportSpecificationAttribute(metadata.SpecificationAttributeManager, lastLoadedProduct); + break; + case ExportedAttributeType.NotSpecified: + default: + continue; + } + + continue; + } + + metadata.Manager.ReadFromXlsx(worksheet, iRow); + + var product = metadata.SkuCellNum > 0 ? allProductsBySku.FirstOrDefault(p => p.Sku == metadata.Manager.GetProperty("SKU").StringValue) : null; + + var isNew = product == null; + + product = product ?? new Product(); + + //some of previous values + var previousStockQuantity = product.StockQuantity; + var previousWarehouseId = product.WarehouseId; + + if (isNew) + product.CreatedOnUtc = DateTime.UtcNow; + + foreach (var property in metadata.Manager.GetProperties) + { + switch (property.PropertyName) + { + case "ProductType": + product.ProductTypeId = property.IntValue; + break; + case "ParentGroupedProductId": + product.ParentGroupedProductId = property.IntValue; + break; + case "VisibleIndividually": + product.VisibleIndividually = property.BooleanValue; + break; + case "Name": + product.Name = property.StringValue; + break; + case "ShortDescription": + product.ShortDescription = property.StringValue; + break; + case "FullDescription": + product.FullDescription = property.StringValue; + break; + case "Vendor": + //vendor can't change this field + if (_workContext.CurrentVendor == null) + product.VendorId = property.IntValue; + break; + case "ProductTemplate": + product.ProductTemplateId = property.IntValue; + break; + case "ShowOnHomePage": + //vendor can't change this field + if (_workContext.CurrentVendor == null) + product.ShowOnHomePage = property.BooleanValue; + break; + case "MetaKeywords": + product.MetaKeywords = property.StringValue; + break; + case "MetaDescription": + product.MetaDescription = property.StringValue; + break; + case "MetaTitle": + product.MetaTitle = property.StringValue; + break; + case "AllowCustomerReviews": + product.AllowCustomerReviews = property.BooleanValue; + break; + case "Published": + product.Published = property.BooleanValue; + break; + case "SKU": + product.Sku = property.StringValue; + break; + case "ManufacturerPartNumber": + product.ManufacturerPartNumber = property.StringValue; + break; + case "Gtin": + product.Gtin = property.StringValue; + break; + case "IsGiftCard": + product.IsGiftCard = property.BooleanValue; + break; + case "GiftCardType": + product.GiftCardTypeId = property.IntValue; + break; + case "OverriddenGiftCardAmount": + product.OverriddenGiftCardAmount = property.DecimalValue; + break; + case "RequireOtherProducts": + product.RequireOtherProducts = property.BooleanValue; + break; + case "RequiredProductIds": + product.RequiredProductIds = property.StringValue; + break; + case "AutomaticallyAddRequiredProducts": + product.AutomaticallyAddRequiredProducts = property.BooleanValue; + break; + case "IsDownload": + product.IsDownload = property.BooleanValue; + break; + case "DownloadId": + product.DownloadId = property.IntValue; + break; + case "UnlimitedDownloads": + product.UnlimitedDownloads = property.BooleanValue; + break; + case "MaxNumberOfDownloads": + product.MaxNumberOfDownloads = property.IntValue; + break; + case "DownloadActivationType": + product.DownloadActivationTypeId = property.IntValue; + break; + case "HasSampleDownload": + product.HasSampleDownload = property.BooleanValue; + break; + case "SampleDownloadId": + product.SampleDownloadId = property.IntValue; + break; + case "HasUserAgreement": + product.HasUserAgreement = property.BooleanValue; + break; + case "UserAgreementText": + product.UserAgreementText = property.StringValue; + break; + case "IsRecurring": + product.IsRecurring = property.BooleanValue; + break; + case "RecurringCycleLength": + product.RecurringCycleLength = property.IntValue; + break; + case "RecurringCyclePeriod": + product.RecurringCyclePeriodId = property.IntValue; + break; + case "RecurringTotalCycles": + product.RecurringTotalCycles = property.IntValue; + break; + case "IsRental": + product.IsRental = property.BooleanValue; + break; + case "RentalPriceLength": + product.RentalPriceLength = property.IntValue; + break; + case "RentalPricePeriod": + product.RentalPricePeriodId = property.IntValue; + break; + case "IsShipEnabled": + product.IsShipEnabled = property.BooleanValue; + break; + case "IsFreeShipping": + product.IsFreeShipping = property.BooleanValue; + break; + case "ShipSeparately": + product.ShipSeparately = property.BooleanValue; + break; + case "AdditionalShippingCharge": + product.AdditionalShippingCharge = property.DecimalValue; + break; + case "DeliveryDate": + product.DeliveryDateId = property.IntValue; + break; + case "IsTaxExempt": + product.IsTaxExempt = property.BooleanValue; + break; + case "TaxCategory": + product.TaxCategoryId = property.IntValue; + break; + case "IsTelecommunicationsOrBroadcastingOrElectronicServices": + product.IsTelecommunicationsOrBroadcastingOrElectronicServices = property.BooleanValue; + break; + case "ManageInventoryMethod": + product.ManageInventoryMethodId = property.IntValue; + break; + case "ProductAvailabilityRange": + product.ProductAvailabilityRangeId = property.IntValue; + break; + case "UseMultipleWarehouses": + product.UseMultipleWarehouses = property.BooleanValue; + break; + case "WarehouseId": + product.WarehouseId = property.IntValue; + break; + case "StockQuantity": + product.StockQuantity = property.IntValue; + break; + case "DisplayStockAvailability": + product.DisplayStockAvailability = property.BooleanValue; + break; + case "DisplayStockQuantity": + product.DisplayStockQuantity = property.BooleanValue; + break; + case "MinStockQuantity": + product.MinStockQuantity = property.IntValue; + break; + case "LowStockActivity": + product.LowStockActivityId = property.IntValue; + break; + case "NotifyAdminForQuantityBelow": + product.NotifyAdminForQuantityBelow = property.IntValue; + break; + case "BackorderMode": + product.BackorderModeId = property.IntValue; + break; + case "AllowBackInStockSubscriptions": + product.AllowBackInStockSubscriptions = property.BooleanValue; + break; + case "OrderMinimumQuantity": + product.OrderMinimumQuantity = property.IntValue; + break; + case "OrderMaximumQuantity": + product.OrderMaximumQuantity = property.IntValue; + break; + case "AllowedQuantities": + product.AllowedQuantities = property.StringValue; + break; + case "AllowAddingOnlyExistingAttributeCombinations": + product.AllowAddingOnlyExistingAttributeCombinations = property.BooleanValue; + break; + case "NotReturnable": + product.NotReturnable = property.BooleanValue; + break; + case "DisableBuyButton": + product.DisableBuyButton = property.BooleanValue; + break; + case "DisableWishlistButton": + product.DisableWishlistButton = property.BooleanValue; + break; + case "AvailableForPreOrder": + product.AvailableForPreOrder = property.BooleanValue; + break; + case "PreOrderAvailabilityStartDateTimeUtc": + product.PreOrderAvailabilityStartDateTimeUtc = property.DateTimeNullable; + break; + case "CallForPrice": + product.CallForPrice = property.BooleanValue; + break; + case "Price": + product.Price = property.DecimalValue; + break; + case "OldPrice": + product.OldPrice = property.DecimalValue; + break; + case "ProductCost": + product.ProductCost = property.DecimalValue; + break; + case "CustomerEntersPrice": + product.CustomerEntersPrice = property.BooleanValue; + break; + case "MinimumCustomerEnteredPrice": + product.MinimumCustomerEnteredPrice = property.DecimalValue; + break; + case "MaximumCustomerEnteredPrice": + product.MaximumCustomerEnteredPrice = property.DecimalValue; + break; + case "BasepriceEnabled": + product.BasepriceEnabled = property.BooleanValue; + break; + case "BasepriceAmount": + product.BasepriceAmount = property.DecimalValue; + break; + case "BasepriceUnit": + product.BasepriceUnitId = property.IntValue; + break; + case "BasepriceBaseAmount": + product.BasepriceBaseAmount = property.DecimalValue; + break; + case "BasepriceBaseUnit": + product.BasepriceBaseUnitId = property.IntValue; + break; + case "MarkAsNew": + product.MarkAsNew = property.BooleanValue; + break; + case "MarkAsNewStartDateTimeUtc": + product.MarkAsNewStartDateTimeUtc = property.DateTimeNullable; + break; + case "MarkAsNewEndDateTimeUtc": + product.MarkAsNewEndDateTimeUtc = property.DateTimeNullable; + break; + case "Weight": + product.Weight = property.DecimalValue; + break; + case "Length": + product.Length = property.DecimalValue; + break; + case "Width": + product.Width = property.DecimalValue; + break; + case "Height": + product.Height = property.DecimalValue; + break; + } + } + + //set some default default values if not specified + if (isNew && metadata.Properties.All(p => p.PropertyName != "ProductType")) + product.ProductType = ProductType.SimpleProduct; + if (isNew && metadata.Properties.All(p => p.PropertyName != "VisibleIndividually")) + product.VisibleIndividually = true; + if (isNew && metadata.Properties.All(p => p.PropertyName != "Published")) + product.Published = true; + + //sets the current vendor for the new product + if (isNew && _workContext.CurrentVendor != null) + product.VendorId = _workContext.CurrentVendor.Id; + + product.UpdatedOnUtc = DateTime.UtcNow; + + if (isNew) + { + _productService.InsertProduct(product); + } + else + { + _productService.UpdateProduct(product); + } + + //quantity change history + if (isNew || previousWarehouseId == product.WarehouseId) + { + _productService.AddStockQuantityHistoryEntry(product, product.StockQuantity - previousStockQuantity, product.StockQuantity, + product.WarehouseId, _localizationService.GetResource("Admin.StockQuantityHistory.Messages.ImportProduct.Edit")); + } + //warehouse is changed + else + { + //compose a message + var oldWarehouseMessage = string.Empty; + if (previousWarehouseId > 0) + { + var oldWarehouse = _shippingService.GetWarehouseById(previousWarehouseId); + if (oldWarehouse != null) + oldWarehouseMessage = string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.EditWarehouse.Old"), oldWarehouse.Name); + } + + var newWarehouseMessage = string.Empty; + if (product.WarehouseId > 0) + { + var newWarehouse = _shippingService.GetWarehouseById(product.WarehouseId); + if (newWarehouse != null) + newWarehouseMessage = string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.EditWarehouse.New"), newWarehouse.Name); + } + + var message = string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.ImportProduct.EditWarehouse"), oldWarehouseMessage, newWarehouseMessage); + + //record history + _productService.AddStockQuantityHistoryEntry(product, -previousStockQuantity, 0, previousWarehouseId, message); + _productService.AddStockQuantityHistoryEntry(product, product.StockQuantity, product.StockQuantity, product.WarehouseId, message); + } + + var tempProperty = metadata.Manager.GetProperty("SeName"); + if (tempProperty != null) + { + var seName = tempProperty.StringValue; + //search engine name + _urlRecordService.SaveSlug(product, product.ValidateSeName(seName, product.Name, true), 0); + } + + tempProperty = metadata.Manager.GetProperty("Categories"); + + if (tempProperty != null) + { + var categoryNames = tempProperty.StringValue; + + //category mappings + var categories = isNew || !allProductsCategoryIds.ContainsKey(product.Id) ? new int[0] : allProductsCategoryIds[product.Id]; + var importedCategories = categoryNames.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) + .Select(categoryName => new CategoryKey(categoryName)) + .Select(categoryKey => allCategories.ContainsKey(categoryKey) ? allCategories[categoryKey].Id : allCategories.Values.First(c => c.Name == categoryKey.Key).Id).ToList(); + foreach (var categoryId in importedCategories) + { + if (categories.Any(c => c == categoryId)) + continue; + + var productCategory = new ProductCategory + { + ProductId = product.Id, + CategoryId = categoryId, + IsFeaturedProduct = false, + DisplayOrder = 1 + }; + _categoryService.InsertProductCategory(productCategory); + } + + //delete product categories + var deletedProductCategories = categories.Where(categoryId => !importedCategories.Contains(categoryId)) + .Select(categoryId => product.ProductCategories.First(pc => pc.CategoryId == categoryId)); + foreach (var deletedProductCategory in deletedProductCategories) + { + _categoryService.DeleteProductCategory(deletedProductCategory); + } + } + + tempProperty = metadata.Manager.GetProperty("Manufacturers"); + if (tempProperty != null) + { + var manufacturerNames = tempProperty.StringValue; + + //manufacturer mappings + var manufacturers = isNew || !allProductsManufacturerIds.ContainsKey(product.Id) ? new int[0] : allProductsManufacturerIds[product.Id]; + var importedManufacturers = manufacturerNames.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(x => allManufacturers.First(m => m.Name == x.Trim()).Id).ToList(); + foreach (var manufacturerId in importedManufacturers) + { + if (manufacturers.Any(c => c == manufacturerId)) + continue; + + var productManufacturer = new ProductManufacturer + { + ProductId = product.Id, + ManufacturerId = manufacturerId, + IsFeaturedProduct = false, + DisplayOrder = 1 + }; + _manufacturerService.InsertProductManufacturer(productManufacturer); + } + + //delete product manufacturers + var deletedProductsManufacturers = manufacturers.Where(manufacturerId => !importedManufacturers.Contains(manufacturerId)) + .Select(manufacturerId => product.ProductManufacturers.First(pc => pc.ManufacturerId == manufacturerId)); + foreach (var deletedProductManufacturer in deletedProductsManufacturers) + { + _manufacturerService.DeleteProductManufacturer(deletedProductManufacturer); + } + } + + tempProperty = metadata.Manager.GetProperty("ProductTags"); + if (tempProperty != null) + { + var productTags = tempProperty.StringValue.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); + + //product tag mappings + _productTagService.UpdateProductTags(product, productTags); + } + + var picture1 = DownloadFile(metadata.Manager.GetProperty("Picture1")?.StringValue, downloadedFiles); + var picture2 = DownloadFile(metadata.Manager.GetProperty("Picture2")?.StringValue, downloadedFiles); + var picture3 = DownloadFile(metadata.Manager.GetProperty("Picture3")?.StringValue, downloadedFiles); + + productPictureMetadata.Add(new ProductPictureMetadata + { + ProductItem = product, + Picture1Path = picture1, + Picture2Path = picture2, + Picture3Path = picture3, + IsNew = isNew + }); + + lastLoadedProduct = product; + + //update "HasTierPrices" and "HasDiscountsApplied" properties + //_productService.UpdateHasTierPricesProperty(product); + //_productService.UpdateHasDiscountsApplied(product); + } + + if (_mediaSettings.ImportProductImagesUsingHash && _pictureService.StoreInDb && _dataProvider.SupportedLengthOfBinaryHash() > 0) + ImportProductImagesUsingHash(productPictureMetadata, allProductsBySku); + else + ImportProductImagesUsingServices(productPictureMetadata); + + foreach (var downloadedFile in downloadedFiles) + { + if (!_fileProvider.FileExists(downloadedFile)) + continue; + + try + { + _fileProvider.DeleteFile(downloadedFile); + } + catch + { + + } + } + + //activity log + _customerActivityService.InsertActivity("ImportProducts", string.Format(_localizationService.GetResource("ActivityLog.ImportProducts"), metadata.CountProductsInFile)); + } + } + + /// + /// Import newsletter subscribers from TXT file + /// + /// Stream + /// Number of imported subscribers + public virtual int ImportNewsletterSubscribersFromTxt(Stream stream) + { + var count = 0; + using (var reader = new StreamReader(stream)) + { + while (!reader.EndOfStream) + { + var line = reader.ReadLine(); + if (string.IsNullOrWhiteSpace(line)) + continue; + var tmp = line.Split(','); + + string email; + var isActive = true; + var storeId = _storeContext.CurrentStore.Id; + //parse + if (tmp.Length == 1) + { + //"email" only + email = tmp[0].Trim(); + } + else if (tmp.Length == 2) + { + //"email" and "active" fields specified + email = tmp[0].Trim(); + isActive = bool.Parse(tmp[1].Trim()); + } + else if (tmp.Length == 3) + { + //"email" and "active" and "storeId" fields specified + email = tmp[0].Trim(); + isActive = bool.Parse(tmp[1].Trim()); + storeId = int.Parse(tmp[2].Trim()); + } + else + throw new NopException("Wrong file format"); + + //import + var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmailAndStoreId(email, storeId); + if (subscription != null) + { + subscription.Email = email; + subscription.Active = isActive; + _newsLetterSubscriptionService.UpdateNewsLetterSubscription(subscription); + } + else + { + subscription = new NewsLetterSubscription + { + Active = isActive, + CreatedOnUtc = DateTime.UtcNow, + Email = email, + StoreId = storeId, + NewsLetterSubscriptionGuid = Guid.NewGuid() + }; + _newsLetterSubscriptionService.InsertNewsLetterSubscription(subscription); + } + + count++; + } + } + + return count; + } + + /// + /// Import states from TXT file + /// + /// Stream + /// Number of imported states + public virtual int ImportStatesFromTxt(Stream stream) + { + var count = 0; + using (var reader = new StreamReader(stream)) + { + while (!reader.EndOfStream) + { + var line = reader.ReadLine(); + if (string.IsNullOrWhiteSpace(line)) + continue; + var tmp = line.Split(','); + + if (tmp.Length != 5) + throw new NopException("Wrong file format"); + + //parse + var countryTwoLetterIsoCode = tmp[0].Trim(); + var name = tmp[1].Trim(); + var abbreviation = tmp[2].Trim(); + var published = bool.Parse(tmp[3].Trim()); + var displayOrder = int.Parse(tmp[4].Trim()); + + var country = _countryService.GetCountryByTwoLetterIsoCode(countryTwoLetterIsoCode); + if (country == null) + { + //country cannot be loaded. skip + continue; + } + + //import + var states = _stateProvinceService.GetStateProvincesByCountryId(country.Id, showHidden: true); + var state = states.FirstOrDefault(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); + + if (state != null) + { + state.Abbreviation = abbreviation; + state.Published = published; + state.DisplayOrder = displayOrder; + _stateProvinceService.UpdateStateProvince(state); + } + else + { + state = new StateProvince + { + CountryId = country.Id, + Name = name, + Abbreviation = abbreviation, + Published = published, + DisplayOrder = displayOrder, + }; + _stateProvinceService.InsertStateProvince(state); + } + + count++; + } + } + + //activity log + _customerActivityService.InsertActivity("ImportStates", + string.Format(_localizationService.GetResource("ActivityLog.ImportStates"), count)); + + return count; + } + + /// + /// Import manufacturers from XLSX file + /// + /// Stream + public virtual void ImportManufacturersFromXlsx(Stream stream) + { + using (var xlPackage = new ExcelPackage(stream)) + { + // get the first worksheet in the workbook + var worksheet = xlPackage.Workbook.Worksheets.FirstOrDefault(); + if (worksheet == null) + throw new NopException("No worksheet found"); + + //the columns + var properties = GetPropertiesByExcelCells(worksheet); + + var manager = new PropertyManager(properties); + + var iRow = 2; + var setSeName = properties.Any(p => p.PropertyName == "SeName"); + + while (true) + { + var allColumnsAreEmpty = manager.GetProperties + .Select(property => worksheet.Cells[iRow, property.PropertyOrderPosition]) + .All(cell => cell == null || cell.Value == null || string.IsNullOrEmpty(cell.Value.ToString())); + + if (allColumnsAreEmpty) + break; + + manager.ReadFromXlsx(worksheet, iRow); + + var manufacturer = _manufacturerService.GetManufacturerById(manager.GetProperty("Id").IntValue); + + var isNew = manufacturer == null; + + manufacturer = manufacturer ?? new Manufacturer(); + + if (isNew) + { + manufacturer.CreatedOnUtc = DateTime.UtcNow; + + //default values + manufacturer.PageSize = _catalogSettings.DefaultManufacturerPageSize; + manufacturer.PageSizeOptions = _catalogSettings.DefaultManufacturerPageSizeOptions; + manufacturer.Published = true; + manufacturer.AllowCustomersToSelectPageSize = true; + } + + var seName = string.Empty; + + foreach (var property in manager.GetProperties) + { + switch (property.PropertyName) + { + case "Name": + manufacturer.Name = property.StringValue; + break; + case "Description": + manufacturer.Description = property.StringValue; + break; + case "ManufacturerTemplateId": + manufacturer.ManufacturerTemplateId = property.IntValue; + break; + case "MetaKeywords": + manufacturer.MetaKeywords = property.StringValue; + break; + case "MetaDescription": + manufacturer.MetaDescription = property.StringValue; + break; + case "MetaTitle": + manufacturer.MetaTitle = property.StringValue; + break; + case "Picture": + var picture = LoadPicture(manager.GetProperty("Picture").StringValue, manufacturer.Name, isNew ? null : (int?)manufacturer.PictureId); + + if (picture != null) + manufacturer.PictureId = picture.Id; + + break; + case "PageSize": + manufacturer.PageSize = property.IntValue; + break; + case "AllowCustomersToSelectPageSize": + manufacturer.AllowCustomersToSelectPageSize = property.BooleanValue; + break; + case "PageSizeOptions": + manufacturer.PageSizeOptions = property.StringValue; + break; + case "PriceRanges": + manufacturer.PriceRanges = property.StringValue; + break; + case "Published": + manufacturer.Published = property.BooleanValue; + break; + case "DisplayOrder": + manufacturer.DisplayOrder = property.IntValue; + break; + case "SeName": + seName = property.StringValue; + break; + } + } + + manufacturer.UpdatedOnUtc = DateTime.UtcNow; + + if (isNew) + _manufacturerService.InsertManufacturer(manufacturer); + else + _manufacturerService.UpdateManufacturer(manufacturer); + + //search engine name + if (setSeName) + _urlRecordService.SaveSlug(manufacturer, manufacturer.ValidateSeName(seName, manufacturer.Name, true), 0); + + iRow++; + } + + //activity log + _customerActivityService.InsertActivity("ImportManufacturers", + string.Format(_localizationService.GetResource("ActivityLog.ImportManufacturers"), iRow - 2)); + } + } + + /// + /// Import categories from XLSX file + /// + /// Stream + public virtual void ImportCategoriesFromXlsx(Stream stream) + { + using (var xlPackage = new ExcelPackage(stream)) + { + // get the first worksheet in the workbook + var worksheet = xlPackage.Workbook.Worksheets.FirstOrDefault(); + if (worksheet == null) + throw new NopException("No worksheet found"); + + //the columns + var properties = GetPropertiesByExcelCells(worksheet); + + var manager = new PropertyManager(properties); + + var iRow = 2; + var setSeName = properties.Any(p => p.PropertyName == "SeName"); + + //performance optimization, load all categories in one SQL request + var allCategories = _categoryService + .GetAllCategories(showHidden: true, loadCacheableCopy: false) + .GroupBy(c => c.GetFormattedBreadCrumb(_categoryService)) + .ToDictionary(c => c.Key, c => c.First()); + + var saveNextTime = new List(); + + while (true) + { + var allColumnsAreEmpty = manager.GetProperties + .Select(property => worksheet.Cells[iRow, property.PropertyOrderPosition]) + .All(cell => string.IsNullOrEmpty(cell?.Value?.ToString())); + + if (allColumnsAreEmpty) + break; + + //get category by data in xlsx file if it possible, or create new category + var category = GetCategoryFromXlsx(manager, worksheet, iRow, allCategories, out bool isNew, out string curentCategoryBreadCrumb); + + //update category by data in xlsx file + var seName = UpdateCategoryByXlsx(category, manager, allCategories, isNew, out bool isParentCategoryExists); + + if (isParentCategoryExists) + { + //if parent category exists in database then save category into database + SaveCategory(isNew, category, allCategories, curentCategoryBreadCrumb, setSeName, seName); + } + else + { + //if parent category doesn't exists in database then try save category into database next time + saveNextTime.Add(iRow); + } + + iRow++; + } + + var needSave = saveNextTime.Any(); + + while (needSave) + { + var remove = new List(); + + //try to save unsaved categories + foreach (var rowId in saveNextTime) + { + //get category by data in xlsx file if it possible, or create new category + var category = GetCategoryFromXlsx(manager, worksheet, rowId, allCategories, out bool isNew, out string curentCategoryBreadCrumb); + //update category by data in xlsx file + var seName = UpdateCategoryByXlsx(category, manager, allCategories, isNew, out bool isParentCategoryExists); + + if (!isParentCategoryExists) + continue; + + //if parent category exists in database then save category into database + SaveCategory(isNew, category, allCategories, curentCategoryBreadCrumb, setSeName, seName); + remove.Add(rowId); + } + + saveNextTime.RemoveAll(item => remove.Contains(item)); + + needSave = remove.Any() && saveNextTime.Any(); + } + + //activity log + _customerActivityService.InsertActivity("ImportCategories", + string.Format(_localizationService.GetResource("ActivityLog.ImportCategories"), iRow - 2 - saveNextTime.Count)); + + if (!saveNextTime.Any()) + return; + + var caregoriesName = new List(); + + foreach (var rowId in saveNextTime) + { + manager.ReadFromXlsx(worksheet, rowId); + caregoriesName.Add(manager.GetProperty("Name").StringValue); + } + + throw new ArgumentException(string.Format(_localizationService.GetResource("Admin.Catalog.Categories.Import.CategoriesArentImported"), string.Join(", ", caregoriesName))); + } + } + + #endregion + + #region Nested classes + + protected class ProductPictureMetadata + { + public Product ProductItem { get; set; } + + public string Picture1Path { get; set; } + + public string Picture2Path { get; set; } + + public string Picture3Path { get; set; } + + public bool IsNew { get; set; } + } + + public class CategoryKey + { + public CategoryKey(Category category, ICategoryService categoryService, IStoreMappingService storeMappingService) + { + Key = category.GetFormattedBreadCrumb(categoryService); + StoresIds = category.LimitedToStores ? storeMappingService.GetStoresIdsWithAccess(category).ToList() : new List(); + Category = category; + } + + public CategoryKey(string key, List storesIds = null) + { + Key = key.Trim(); + StoresIds = storesIds ?? new List(); + } + + public List StoresIds { get; } + public Category Category { get; } + public string Key { get; } + + public bool Equals(CategoryKey y) + { + if (y == null) + return false; + + if (Category != null && y.Category != null) + return Category.Id == y.Category.Id; + + if ((StoresIds.Any() || y.StoresIds.Any()) + && (StoresIds.All(id => !y.StoresIds.Contains(id)) || y.StoresIds.All(id => !StoresIds.Contains(id)))) + return false; + + return Key.Equals(y.Key); + } + + public override int GetHashCode() + { + if (StoresIds.Any()) + { + var storesIds = StoresIds.Select(id => id.ToString()) + .Aggregate("", (all, current) => all + current); + + return $"{storesIds}_{Key}".GetHashCode(); + } + + return Key.GetHashCode(); + } + + public override bool Equals(object obj) + { + var other = obj as CategoryKey; + return other?.Equals(other) ?? false; + } + } + + #endregion + } +} \ No newline at end of file