From c12395d8859ac5c81d6df4e2a6b7a0e2bb64a328 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 4 May 2016 21:29:33 +0200 Subject: [PATCH 001/185] Import: added method overloads to test for the existence of any indexed column --- .../Catalog/Importer/ProductImporter.cs | 7 ++- .../Import/ImportDataSegmenter.cs | 34 +++++++++++++- .../DataExchange/Import/ImportRow.cs | 46 +++++++++++++++++-- 3 files changed, 77 insertions(+), 10 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 24ca4e2bf6..9e239dc4b4 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -389,13 +389,14 @@ protected virtual int ProcessSlugs(IImportExecuteContext context, ImportRow("SeName"); + + if (!(seName.HasValue() || row.IsNew || row.NameChanged)) continue; try { UrlRecord urlRecord = null; - var seName = row.GetDataValue("SeName"); seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); if (row.IsNew) @@ -428,9 +429,7 @@ protected virtual int ProcessSlugs(IImportExecuteContext context, ImportRow + /// Determines whether a specific column exists in the underlying data table. + /// + /// The name of the column to find + /// + /// If true and a column with the passed does not exist, + /// this method tests for the existence of any indexed column with the same name. + /// + /// true if the column exists, false otherwise + /// + /// This method takes mapped column names into account. + /// + public bool HasColumn(string name, bool withAnyIndex = false) { - return _table.HasColumn(_columnMap.GetMapping(name).Property); + var result = HasColumn(name, null); + + if (!result && withAnyIndex) + { + // Column does not exist, but withAnyIndex is true: + // Test for existence of any indexed column. + result = GetColumnIndexes(name).Length > 0; + } + + return result; } + /// + /// Determines whether the column name[index] exists in the underlying data table. + /// + /// The name of the column to find + /// The index of the column + /// true if the column exists, false otherwise + /// + /// This method takes mapped column names into account. + /// public bool HasColumn(string name, string index) { return _table.HasColumn(_columnMap.GetMapping(name, index).Property); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs index 128236af05..4dd080293e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -99,16 +99,54 @@ public int Position get { return _position; } } - public bool HasDataValue(string columnName) + /// + /// Determines whether a specific column exists in the underlying data table + /// and contains a non-null, convertible value. + /// + /// The name of the column + /// + /// If true and a column with the passed does not exist, + /// this method seeks for any indexed column with the same name. + /// + /// true if the column exists and contains a value, false otherwise + /// + /// This method takes mapped column names into account. + /// + public bool HasDataValue(string columnName, bool withAnyIndex = false) { - return HasDataValue(columnName, null); + var result = HasDataValue(columnName, null); + + if (!result && withAnyIndex) + { + // Column does not have a value, but withAnyIndex is true: + // Test for values in any indexed column. + var indexes = _segmenter.GetColumnIndexes(columnName); + foreach (var idx in indexes) + { + result = HasDataValue(columnName, idx); + if (result) + break; + } + } + + return result; } + /// + /// Determines whether the column name[index] exists in the underlying data table + /// and contains a non-null, convertible value. + /// + /// The name of the column + /// The index of the column + /// true if the column exists and contains a value, false otherwise + /// + /// This method takes mapped column names into account. + /// public bool HasDataValue(string columnName, string index) { - object value; var mapping = _segmenter.ColumnMap.GetMapping(columnName, index); - + + object value; return (_row.TryGetValue(mapping.Property, out value) && value != null && value != DBNull.Value); } From 75444f138c0a5caddf0b762dec03581aca44d03b Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 5 May 2016 00:28:11 +0200 Subject: [PATCH 002/185] Import: more Importer refactoring --- .../Catalog/Importer/CategoryImporter.cs | 2 +- .../Catalog/Importer/ProductImporter.cs | 183 ++++++++++-------- 2 files changed, 107 insertions(+), 78 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index c81f6bce13..ed03178a9c 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -478,7 +478,7 @@ protected override void Import(IImportExecuteContext context) context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); // process slugs - if (segmenter.HasColumn("SeName") || batch.Any(x => x.IsNew || x.NameChanged)) + if (segmenter.HasColumn("SeName", true) || batch.Any(x => x.IsNew || x.NameChanged)) { try { diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 9e239dc4b4..13c1ca0cf2 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Linq.Expressions; using SmartStore.Core.Async; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; @@ -16,6 +17,7 @@ using SmartStore.Services.Stores; using SmartStore.Utilities; using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Domain.Localization; namespace SmartStore.Services.Catalog.Importer { @@ -40,6 +42,17 @@ public class ProductImporter : EntityImporterBase private readonly SeoSettings _seoSettings; private readonly DataExchangeSettings _dataExchangeSettings; + private static readonly Dictionary>> _localizableProperties = new Dictionary>> + { + { "Name", x => x.Name }, + { "ShortDescription", x => x.ShortDescription }, + { "FullDescription", x => x.FullDescription }, + { "MetaKeywords", x => x.MetaKeywords }, + { "MetaDescription", x => x.MetaDescription }, + { "MetaTitle", x => x.MetaTitle }, + { "BundleTitleText", x => x.BundleTitleText } + }; + public ProductImporter( IRepository productPictureRepository, IRepository productManufacturerRepository, @@ -333,54 +346,52 @@ protected virtual int ProcessProductCategories(IImportExecuteContext context, Im return num; } - protected virtual int ProcessLocalizations(IImportExecuteContext context, ImportRow[] batch) + protected virtual int ProcessLocalizations( + IImportExecuteContext context, + ImportRow[] batch, + string[] localizedProperties) { - foreach (var row in batch) + if (localizedProperties.Length == 0) { - foreach (var lang in context.Languages) - { - var code = lang.UniqueSeoCode; - - var value = row.GetDataValue("Name", code); - if (value.HasValue()) - _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.Name, value, lang.Id); - - value = row.GetDataValue("ShortDescription", code); - if (value.HasValue()) - _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.ShortDescription, value, lang.Id); - - value = row.GetDataValue("FullDescription", code); - if (value.HasValue()) - _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.FullDescription, value, lang.Id); - - value = row.GetDataValue("MetaKeywords", code); - if (value.HasValue()) - _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.MetaKeywords, value, lang.Id); - - value = row.GetDataValue("MetaDescription", code); - if (value.HasValue()) - _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.MetaDescription, value, lang.Id); + return 0; + } - value = row.GetDataValue("MetaTitle", code); - if (value.HasValue()) - _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.MetaTitle, value, lang.Id); + bool shouldSave = false; - value = row.GetDataValue("BundleTitleText", code); - if (value.HasValue()) - _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.BundleTitleText, value, lang.Id); + foreach (var row in batch) + { + foreach (var prop in localizedProperties) + { + var lambda = _localizableProperties[prop]; + foreach (var lang in context.Languages) + { + var code = lang.UniqueSeoCode; + var value = row.GetDataValue(prop /* ColumnName */, code); + if (value.HasValue()) + { + _localizedEntityService.SaveLocalizedValue(row.Entity, lambda, value, lang.Id); + shouldSave = true; + } + } } } - // commit whole batch at once - var num = _productManufacturerRepository.Context.SaveChanges(); + if (shouldSave) + { + // commit whole batch at once + return _productManufacturerRepository.Context.SaveChanges(); + } - return num; + return 0; } - protected virtual int ProcessSlugs(IImportExecuteContext context, ImportRow[] batch) + protected virtual int ProcessSlugs(IImportExecuteContext context, ImportRow[] batch, string[] columnIndexes) { var entityName = typeof(Product).Name; var slugMap = new Dictionary(); + var languageMap = context.Languages.ToDictionarySafe(x => x.UniqueSeoCode); + Language language = null; + UrlRecord urlRecord = null; Func slugLookup = ((s) => { @@ -389,53 +400,56 @@ protected virtual int ProcessSlugs(IImportExecuteContext context, ImportRow("SeName"); - - if (!(seName.HasValue() || row.IsNew || row.NameChanged)) - continue; - try { - UrlRecord urlRecord = null; - seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); + var seName = row.GetDataValue("SeName"); - if (row.IsNew) - { - // dont't bother validating SeName for new entities. - urlRecord = new UrlRecord - { - EntityId = row.Entity.Id, - EntityName = entityName, - Slug = seName, - LanguageId = 0, - IsActive = true, - }; - _urlRecordRepository.Insert(urlRecord); - } - else + if (row.IsNew || row.NameChanged || seName.HasValue()) { - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); - } + seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); - if (urlRecord != null) - { - // a new record was inserted to the store: keep track of it for this batch. - slugMap[seName] = urlRecord; - } - - foreach (var lang in context.Languages) - { - seName = row.GetDataValue("SeName", lang.UniqueSeoCode); - if (seName.HasValue()) + if (row.IsNew) { - seName = row.Entity.ValidateSeName(seName, null, false, _urlRecordService, _seoSettings, lang.Id, slugLookup); - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, lang.Id); - if (urlRecord != null) + // dont't bother validating SeName for new entities. + urlRecord = new UrlRecord { - slugMap[seName] = urlRecord; - } + EntityId = row.Entity.Id, + EntityName = entityName, + Slug = seName, + LanguageId = 0, + IsActive = true, + }; + _urlRecordRepository.Insert(urlRecord); + } + else + { + urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); + } + + if (urlRecord != null) + { + // a new record was inserted to the store: keep track of it for this batch. + slugMap[seName] = urlRecord; } } + + //// TODO: (mc) Think about this VERY thoroughly!!!! + //foreach (var idx in columnIndexes) + //{ + // if (languageMap.TryGetValue(idx, out language)) + // { + // seName = row.GetDataValue("SeName", idx); + // if (seName.HasValue()) + // { + // seName = row.Entity.ValidateSeName(seName, null, false, _urlRecordService, _seoSettings, language.Id, slugLookup); + // urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, language.Id); + // if (urlRecord != null) + // { + // slugMap[seName] = urlRecord; + // } + // } + // } + //} } catch (Exception exception) { @@ -701,6 +715,20 @@ public static string[] DefaultKeyFields } } + private IEnumerable ResolveLocalizedProperties(ImportDataSegmenter segmenter) + { + // Perf: determine whether our localizable properties actually have + // counterparts in the source BEFORE import begins. This way we spare ourself + // to query over and over for values. + foreach (var kvp in _localizableProperties) + { + if (segmenter.GetColumnIndexes(kvp.Key).Length > 0) + { + yield return kvp.Key; + } + } + } + protected override void Import(IImportExecuteContext context) { var srcToDestId = new Dictionary(); @@ -710,9 +738,10 @@ protected override void Import(IImportExecuteContext context) using (var scope = new DbContextScope(ctx: _productRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { var segmenter = context.GetSegmenter(); - Init(context, _dataExchangeSettings); + var localizedProperties = ResolveLocalizedProperties(segmenter).ToArray(); + context.Result.TotalRecords = segmenter.TotalRows; while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) @@ -749,12 +778,12 @@ protected override void Import(IImportExecuteContext context) // IMPORTANT: Unlike with Products AutoCommitEnabled must be TRUE, // as Slugs are going to be validated against existing ones in DB. // =========================================================================== - if (segmenter.HasColumn("SeName") || batch.Any(x => x.IsNew || x.NameChanged)) + if (segmenter.HasColumn("SeName", true) || batch.Any(x => x.IsNew || x.NameChanged)) { try { _productRepository.Context.AutoDetectChangesEnabled = true; - ProcessSlugs(context, batch); + ProcessSlugs(context, batch, segmenter.GetColumnIndexes("Name")); } catch (Exception exception) { @@ -786,7 +815,7 @@ protected override void Import(IImportExecuteContext context) // =========================================================================== try { - ProcessLocalizations(context, batch); + ProcessLocalizations(context, batch, localizedProperties); } catch (Exception exception) { From 7b1bb9c34dcc41362c2112b98c467eacbe019c90 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 5 May 2016 12:53:20 +0200 Subject: [PATCH 003/185] Resolves #953 GMC feed does not generate the sale price if the sale price is set for a future date --- changelog.md | 1 + .../DataExchange/Export/DynamicEntityHelper.cs | 16 +++++++++++++--- .../Description.txt | 2 +- .../Providers/GmcXmlExportProvider.cs | 6 +++--- .../SmartStore.GoogleMerchantCenter/changelog.md | 4 ++++ 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/changelog.md b/changelog.md index 276008244c..d021f5d7d0 100644 --- a/changelog.md +++ b/changelog.md @@ -32,6 +32,7 @@ * Loading shipping by weight grid failed under SQL CE * #949 Import: ProcessSlugs does not process explicitly specified "SeName", if product name did not change in an update scenario * Customer import: Creates customer role duplicates for inserted customers +* GMC feed does not generate the sale price if the sale price is set for a future date ## SmartStore.NET 2.5 diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs index 232e2ee864..e6e4f39a81 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs @@ -841,16 +841,26 @@ private dynamic ToDynamic( if (ctx.Supports(ExportFeatures.UsesSpecialPrice)) { - dynObject._SpecialPrice = null; - dynObject._RegularPrice = null; // price if a special price would not exist + dynObject._SpecialPrice = null; // special price which is valid now + dynObject._FutureSpecialPrice = null; // special price which is valid now and in future + dynObject._RegularPrice = null; // price as if a special price would not exist if (!(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)) { + if (product.SpecialPrice.HasValue && product.SpecialPriceEndDateTimeUtc.HasValue) + { + var endDate = DateTime.SpecifyKind(product.SpecialPriceEndDateTimeUtc.Value, DateTimeKind.Utc); + if (endDate > DateTime.UtcNow) + { + dynObject._FutureSpecialPrice = ConvertPrice(ctx, product, product.SpecialPrice.Value); + } + } + var specialPrice = _priceCalculationService.Value.GetSpecialPrice(product); dynObject._SpecialPrice = ConvertPrice(ctx, product, specialPrice); - if (specialPrice.HasValue) + if (specialPrice.HasValue || dynObject._FutureSpecialPrice != null) { decimal tmpSpecialPrice = product.SpecialPrice.Value; product.SpecialPrice = null; diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt index d30ed161f9..8187db5c15 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt @@ -1,7 +1,7 @@ FriendlyName: Google Merchant Center (GMC) feed SystemName: SmartStore.GoogleMerchantCenter Group: Marketing -Version: 2.5.0 +Version: 2.5.0.1 MinAppVersion: 2.5.0 Author: SmartStore AG DisplayOrder: 1 diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index 3a5cc536f6..4fc2f1f305 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -202,7 +202,7 @@ protected override void Export(IExportExecuteContext context) string category = (gmc == null ? null : gmc.Taxonomy); string productType = product._CategoryPath; string mainImageUrl = product._MainPictureUrl; - var specialPrice = product._SpecialPrice as decimal?; + var futureSpecialPrice = product._FutureSpecialPrice as decimal?; var price = (decimal)product.Price; string brand = product._Brand; string gtin = product.Gtin; @@ -295,12 +295,12 @@ protected override void Export(IExportExecuteContext context) writer.WriteElementString("g", "availability_date", _googleNamespace, availabilityDate); } - if (config.SpecialPrice && specialPrice.HasValue && entity.SpecialPriceStartDateTimeUtc.HasValue && entity.SpecialPriceEndDateTimeUtc.HasValue) + if (config.SpecialPrice && futureSpecialPrice.HasValue && entity.SpecialPriceStartDateTimeUtc.HasValue && entity.SpecialPriceEndDateTimeUtc.HasValue) { var specialPriceDate = "{0}/{1}".FormatInvariant( entity.SpecialPriceStartDateTimeUtc.Value.ToString(dateFormat), entity.SpecialPriceEndDateTimeUtc.Value.ToString(dateFormat)); - writer.WriteElementString("g", "sale_price", _googleNamespace, price.FormatInvariant() + " " + (string)currency.CurrencyCode); + writer.WriteElementString("g", "sale_price", _googleNamespace, futureSpecialPrice.Value.FormatInvariant() + " " + (string)currency.CurrencyCode); writer.WriteElementString("g", "sale_price_effective_date", _googleNamespace, specialPriceDate); price = (product._RegularPrice as decimal?) ?? price; diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md index 65f8637800..c368c87dd7 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md @@ -1,5 +1,9 @@ #Release Notes +##Google Merchant Center (GMC) 2.5.0.1 +###Bugfixes +* GMC feed does not generate the sale price if the sale price is set for a future date + ##Google Merchant Center (GMC) 2.2.0.5 ###New Features * Supports GMC fields: multipack, bundle, adult, energy efficiency class and custom label (0 to 4) From dbd659ad97c66819794407a4dc65e5d6322a7f3a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 5 May 2016 16:33:02 +0200 Subject: [PATCH 004/185] Resolves #952 Import: don't create import profile when uploaded file is invalid --- .../Controllers/ImportController.cs | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index d868118b9c..36cd41e8e5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -54,6 +54,29 @@ public ImportController( #region Utilities + private bool IsValidImportFile(string path, out string error) + { + error = null; + + try + { + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + var unused = LightweightDataTable.FromFile(path, stream, stream.Length, CsvConfiguration.ExcelFriendlyConfiguration, 0, 1); + } + + return true; + } + catch (Exception exception) + { + error = exception.ToAllMessages(); + + FileSystemHelper.Delete(path); + + return false; + } + } + private bool IsDefaultValueDisabled(string column, string property, string[] disabledFieldNames) { if (disabledFieldNames.Contains(property)) @@ -575,9 +598,12 @@ public JsonResult FileUpload(int id) FileSystemHelper.Delete(path); success = postedFile.Stream.ToFile(path); - if (success) - tempFile = postedFile.FileName; + { + success = IsValidImportFile(path, out error); + if (success) + tempFile = postedFile.FileName; + } } else { @@ -597,16 +623,20 @@ public JsonResult FileUpload(int id) { var folder = profile.GetImportFolder(true, true); var fileName = Path.GetFileName(postedFile.FileName); + var path = Path.Combine(folder, fileName); - success = postedFile.Stream.ToFile(Path.Combine(folder, fileName)); - + success = postedFile.Stream.ToFile(path); if (success) { - var fileType = (Path.GetExtension(fileName).IsCaseInsensitiveEqual(".xlsx") ? ImportFileType.XLSX : ImportFileType.CSV); - if (fileType != profile.FileType) + success = IsValidImportFile(path, out error); + if (success) { - profile.FileType = fileType; - _importProfileService.UpdateImportProfile(profile); + var fileType = (Path.GetExtension(fileName).IsCaseInsensitiveEqual(".xlsx") ? ImportFileType.XLSX : ImportFileType.CSV); + if (fileType != profile.FileType) + { + profile.FileType = fileType; + _importProfileService.UpdateImportProfile(profile); + } } } } From 78724747b7ca825eb80c6f4cfa859562bc3a6d1f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 6 May 2016 12:28:48 +0200 Subject: [PATCH 005/185] #875 Implement column mapping support for customer address import (part 1) --- .../Import/ImportProfileService.cs | 148 +++++++++++++----- 1 file changed, 107 insertions(+), 41 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs index 2443bddfb6..c1e54633dc 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs @@ -48,61 +48,107 @@ public ImportProfileService( _dataExchangeSettings = dataExchangeSettings; } - private string GetLocalizedPropertyName(ImportEntityType type, string propertyName) + private string GetLocalizedPropertyName(ImportEntityType type, string property) { - if (propertyName.IsEmpty()) - return null; + if (property.IsEmpty()) + return ""; - var defaultKey = ""; - var keys = new Dictionary - { - { "Id", "Admin.Common.Entity.Fields.Id" }, - { "LimitedToStores", "Admin.Common.Store.LimitedTo" }, - { "DisplayOrder", "Common.DisplayOrder" }, - { "Deleted", "Admin.Common.Deleted" }, - { "CreatedOnUtc", "Common.CreatedOn" }, - { "UpdatedOnUtc", "Common.UpdatedOn" }, - { "HasDiscountsApplied", "Admin.Catalog.Products.Fields.HasDiscountsApplied" }, - { "DefaultViewMode", "Admin.Configuration.Settings.Catalog.DefaultViewMode" }, - { "StoreId", "Admin.Common.Store" } - }; + string key = null; + string prefixKey = null; - if (type == ImportEntityType.Product) - { - defaultKey = "Admin.Catalog.Products.Fields." + propertyName; + if (property.StartsWith("BillingAddress.")) + prefixKey = "Admin.Orders.Fields.BillingAddress"; + else if (property.StartsWith("ShippingAddress.")) + prefixKey = "Admin.Orders.Fields.ShippingAddress"; - keys.Add("ParentGroupedProductId", "Admin.Catalog.Products.Fields.AssociatedToProductName"); - } - else if (type == ImportEntityType.Category) - { - defaultKey = "Admin.Catalog.Categories.Fields." + propertyName; - } - else if (type == ImportEntityType.Customer) - { - defaultKey = "Admin.Customers.Customers.Fields." + propertyName; + #region Get resource key - keys.Add("PasswordFormatId", "Admin.Configuration.Settings.CustomerUser.DefaultPasswordFormat"); - keys.Add("LastIpAddress", "Admin.Customers.Customers.Fields.IPAddress"); - } - else if (type == ImportEntityType.NewsLetterSubscription) + switch (property) { - defaultKey = "Admin.Promotions.NewsLetterSubscriptions.Fields." + propertyName; + case "Id": + key = "Admin.Common.Entity.Fields.Id"; + break; + case "LimitedToStores": + key = "Admin.Common.Store.LimitedTo"; + break; + case "DisplayOrder": + key = "Common.DisplayOrder"; + break; + case "Deleted": + key = "Admin.Common.Deleted"; + break; + case "CreatedOnUtc": + case "BillingAddress.CreatedOnUtc": + case "ShippingAddress.CreatedOnUtc": + key = "Common.CreatedOn"; + break; + case "UpdatedOnUtc": + key = "Common.UpdatedOn"; + break; + case "HasDiscountsApplied": + key = "Admin.Catalog.Products.Fields.HasDiscountsApplied"; + break; + case "DefaultViewMode": + key = "Admin.Configuration.Settings.Catalog.DefaultViewMode"; + break; + case "StoreId": + key = "Admin.Common.Store"; + break; + case "ParentGroupedProductId": + key = "Admin.Catalog.Products.Fields.AssociatedToProductName"; + break; + case "PasswordFormatId": + key = "Admin.Configuration.Settings.CustomerUser.DefaultPasswordFormat"; + break; + case "LastIpAddress": + key = "Admin.Customers.Customers.Fields.IPAddress"; + break; + default: + switch (type) + { + case ImportEntityType.Product: + key = "Admin.Catalog.Products.Fields." + property; + break; + case ImportEntityType.Category: + key = "Admin.Catalog.Categories.Fields." + property; + break; + case ImportEntityType.Customer: + if (property.StartsWith("BillingAddress.") || property.StartsWith("ShippingAddress.")) + key = "Admin.Address.Fields." + property.Substring(property.IndexOf('.') + 1); + else + key = "Admin.Customers.Customers.Fields." + property; + break; + case ImportEntityType.NewsLetterSubscription: + key = "Admin.Promotions.NewsLetterSubscriptions.Fields." + property; + break; + } + break; } - var result = _localizationService.GetResource(keys.ContainsKey(propertyName) ? keys[propertyName] : defaultKey, 0, false, "", true); + #endregion + + if (key.IsEmpty()) + return ""; + + var result = _localizationService.GetResource(key, 0, false, "", true); if (result.IsEmpty()) { - if (defaultKey.EndsWith("Id")) - result = _localizationService.GetResource(defaultKey.Substring(0, defaultKey.Length - 2), 0, false, "", true); - else if (defaultKey.EndsWith("Utc")) - result = _localizationService.GetResource(defaultKey.Substring(0, defaultKey.Length - 3), 0, false, "", true); + if (key.EndsWith("Id")) + result = _localizationService.GetResource(key.Substring(0, key.Length - 2), 0, false, "", true); + else if (key.EndsWith("Utc")) + result = _localizationService.GetResource(key.Substring(0, key.Length - 3), 0, false, "", true); } if (result.IsEmpty()) { - Debug.WriteLine("Missing string resource mapping for {0}.{1}".FormatInvariant(type.ToString(), propertyName)); - return propertyName.SplitPascalCase(); + Debug.WriteLine("Missing string resource mapping for {0} - {1}".FormatInvariant(type.ToString(), property)); + result = property.SplitPascalCase(); + } + + if (prefixKey.HasValue()) + { + result = string.Concat(_localizationService.GetResource(prefixKey, 0, false, "", true), " - ", result); } return result; @@ -289,6 +335,14 @@ public virtual Dictionary GetImportableEntityProperties(ImportEn { ImportEntityType.NewsLetterSubscription, new string[] { } } }; + var addressSet = container.GetEntitySetByName("Addresses", true); + + var addressProperties = addressSet.ElementType.Members + .Where(x => !x.Name.IsCaseInsensitiveEqual("Id") && x.BuiltInTypeKind.HasFlag(BuiltInTypeKind.EdmProperty)) + .Select(x => x.Name) + .ToList(); + + foreach (ImportEntityType type in Enum.GetValues(typeof(ImportEntityType))) { EntitySet entitySet = null; @@ -307,7 +361,8 @@ public virtual Dictionary GetImportableEntityProperties(ImportEn var dic = entitySet.ElementType.Members .Where(x => !x.Name.IsCaseInsensitiveEqual("Id") && x.BuiltInTypeKind.HasFlag(BuiltInTypeKind.EdmProperty)) - .ToDictionary(x => x.Name, x => "", StringComparer.OrdinalIgnoreCase); + .Select(x => x.Name) + .ToDictionary(x => x, x => "", StringComparer.OrdinalIgnoreCase); // lack of abstractness? if ((type == ImportEntityType.Product || type == ImportEntityType.Category) && !dic.ContainsKey("SeName")) @@ -315,6 +370,17 @@ public virtual Dictionary GetImportableEntityProperties(ImportEn dic.Add("SeName", ""); } + // shipping and billing address + if (type == ImportEntityType.Customer) + { + foreach (var property in addressProperties) + { + dic.Add("BillingAddress." + property, ""); + dic.Add("ShippingAddress." + property, ""); + } + } + + // add localized property names foreach (var key in dic.Keys.ToList()) { var localizedValue = GetLocalizedPropertyName(type, key); From bbbebf54783604e85868ebe02d7524921a9989ee Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 6 May 2016 18:43:35 +0200 Subject: [PATCH 006/185] Import: removed entity parameter from ImportRow.SetProperty() method... was unnecessary. --- .../Catalog/Importer/CategoryImporter.cs | 38 ++-- .../Catalog/Importer/ProductImporter.cs | 178 +++++++++--------- .../Customers/Importer/CustomerImporter.cs | 34 ++-- .../DataExchange/Import/ImportRow.cs | 2 +- 4 files changed, 126 insertions(+), 126 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index ed03178a9c..ebc094f629 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -351,31 +351,31 @@ protected virtual int ProcessCategories( row.NameChanged = true; } - row.SetProperty(context.Result, category, (x) => x.Name); - row.SetProperty(context.Result, category, (x) => x.FullName); - row.SetProperty(context.Result, category, (x) => x.Description); - row.SetProperty(context.Result, category, (x) => x.BottomDescription); - row.SetProperty(context.Result, category, (x) => x.MetaKeywords); - row.SetProperty(context.Result, category, (x) => x.MetaDescription); - row.SetProperty(context.Result, category, (x) => x.MetaTitle); - row.SetProperty(context.Result, category, (x) => x.PageSize, 12); - row.SetProperty(context.Result, category, (x) => x.AllowCustomersToSelectPageSize, true); - row.SetProperty(context.Result, category, (x) => x.PageSizeOptions); - row.SetProperty(context.Result, category, (x) => x.PriceRanges); - row.SetProperty(context.Result, category, (x) => x.ShowOnHomePage); - row.SetProperty(context.Result, category, (x) => x.HasDiscountsApplied); - row.SetProperty(context.Result, category, (x) => x.Published, true); - row.SetProperty(context.Result, category, (x) => x.DisplayOrder); - row.SetProperty(context.Result, category, (x) => x.Alias); - row.SetProperty(context.Result, category, (x) => x.DefaultViewMode); + row.SetProperty(context.Result, (x) => x.Name); + row.SetProperty(context.Result, (x) => x.FullName); + row.SetProperty(context.Result, (x) => x.Description); + row.SetProperty(context.Result, (x) => x.BottomDescription); + row.SetProperty(context.Result, (x) => x.MetaKeywords); + row.SetProperty(context.Result, (x) => x.MetaDescription); + row.SetProperty(context.Result, (x) => x.MetaTitle); + row.SetProperty(context.Result, (x) => x.PageSize, 12); + row.SetProperty(context.Result, (x) => x.AllowCustomersToSelectPageSize, true); + row.SetProperty(context.Result, (x) => x.PageSizeOptions); + row.SetProperty(context.Result, (x) => x.PriceRanges); + row.SetProperty(context.Result, (x) => x.ShowOnHomePage); + row.SetProperty(context.Result, (x) => x.HasDiscountsApplied); + row.SetProperty(context.Result, (x) => x.Published, true); + row.SetProperty(context.Result, (x) => x.DisplayOrder); + row.SetProperty(context.Result, (x) => x.Alias); + row.SetProperty(context.Result, (x) => x.DefaultViewMode); // With new entities, "LimitedToStores" is an implicit field, meaning // it has to be set to true by code if it's absent but "StoreIds" exists. - row.SetProperty(context.Result, category, (x) => x.LimitedToStores, !row.GetDataValue>("StoreIds").IsNullOrEmpty()); + row.SetProperty(context.Result, (x) => x.LimitedToStores, !row.GetDataValue>("StoreIds").IsNullOrEmpty()); var tvp = row.GetDataValue("CategoryTemplateViewPath"); category.CategoryTemplateId = (tvp.HasValue() && templateViewPaths.ContainsKey(tvp) ? templateViewPaths[tvp] : defaultTemplateId); - row.SetProperty(context.Result, category, (x) => x.CreatedOnUtc, UtcNow); + row.SetProperty(context.Result, (x) => x.CreatedOnUtc, UtcNow); category.UpdatedOnUtc = UtcNow; if (id != 0 && !srcToDestId.ContainsKey(id)) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 13c1ca0cf2..09f15a8b00 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -557,103 +557,103 @@ protected virtual int ProcessProducts( } } - row.SetProperty(context.Result, product, (x) => x.ProductTypeId, (int)ProductType.SimpleProduct); - row.SetProperty(context.Result, product, (x) => x.VisibleIndividually, true); - row.SetProperty(context.Result, product, (x) => x.Name); - row.SetProperty(context.Result, product, (x) => x.ShortDescription); - row.SetProperty(context.Result, product, (x) => x.FullDescription); - row.SetProperty(context.Result, product, (x) => x.AdminComment); - row.SetProperty(context.Result, product, (x) => x.ShowOnHomePage); - row.SetProperty(context.Result, product, (x) => x.HomePageDisplayOrder); - row.SetProperty(context.Result, product, (x) => x.MetaKeywords); - row.SetProperty(context.Result, product, (x) => x.MetaDescription); - row.SetProperty(context.Result, product, (x) => x.MetaTitle); - row.SetProperty(context.Result, product, (x) => x.AllowCustomerReviews, true); - row.SetProperty(context.Result, product, (x) => x.ApprovedRatingSum); - row.SetProperty(context.Result, product, (x) => x.NotApprovedRatingSum); - row.SetProperty(context.Result, product, (x) => x.ApprovedTotalReviews); - row.SetProperty(context.Result, product, (x) => x.NotApprovedTotalReviews); - row.SetProperty(context.Result, product, (x) => x.Published, true); - row.SetProperty(context.Result, product, (x) => x.Sku); - row.SetProperty(context.Result, product, (x) => x.ManufacturerPartNumber); - row.SetProperty(context.Result, product, (x) => x.Gtin); - row.SetProperty(context.Result, product, (x) => x.IsGiftCard); - row.SetProperty(context.Result, product, (x) => x.GiftCardTypeId); - row.SetProperty(context.Result, product, (x) => x.RequireOtherProducts); - row.SetProperty(context.Result, product, (x) => x.RequiredProductIds); // TODO: global scope - row.SetProperty(context.Result, product, (x) => x.AutomaticallyAddRequiredProducts); - row.SetProperty(context.Result, product, (x) => x.IsDownload); - row.SetProperty(context.Result, product, (x) => x.DownloadId); - row.SetProperty(context.Result, product, (x) => x.UnlimitedDownloads, true); - row.SetProperty(context.Result, product, (x) => x.MaxNumberOfDownloads, 10); - row.SetProperty(context.Result, product, (x) => x.DownloadExpirationDays); - row.SetProperty(context.Result, product, (x) => x.DownloadActivationTypeId, 1); - row.SetProperty(context.Result, product, (x) => x.HasSampleDownload); - row.SetProperty(context.Result, product, (x) => x.SampleDownloadId, (int?)null, ZeroToNull); // TODO: global scope - row.SetProperty(context.Result, product, (x) => x.HasUserAgreement); - row.SetProperty(context.Result, product, (x) => x.UserAgreementText); - row.SetProperty(context.Result, product, (x) => x.IsRecurring); - row.SetProperty(context.Result, product, (x) => x.RecurringCycleLength, 100); - row.SetProperty(context.Result, product, (x) => x.RecurringCyclePeriodId); - row.SetProperty(context.Result, product, (x) => x.RecurringTotalCycles, 10); - row.SetProperty(context.Result, product, (x) => x.IsShipEnabled, true); - row.SetProperty(context.Result, product, (x) => x.IsFreeShipping); - row.SetProperty(context.Result, product, (x) => x.AdditionalShippingCharge); - row.SetProperty(context.Result, product, (x) => x.IsEsd); - row.SetProperty(context.Result, product, (x) => x.IsTaxExempt); - row.SetProperty(context.Result, product, (x) => x.TaxCategoryId, 1); // TODO: global scope - row.SetProperty(context.Result, product, (x) => x.ManageInventoryMethodId); - row.SetProperty(context.Result, product, (x) => x.StockQuantity, 10000); - row.SetProperty(context.Result, product, (x) => x.DisplayStockAvailability); - row.SetProperty(context.Result, product, (x) => x.DisplayStockQuantity); - row.SetProperty(context.Result, product, (x) => x.MinStockQuantity); - row.SetProperty(context.Result, product, (x) => x.LowStockActivityId); - row.SetProperty(context.Result, product, (x) => x.NotifyAdminForQuantityBelow, 1); - row.SetProperty(context.Result, product, (x) => x.BackorderModeId); - row.SetProperty(context.Result, product, (x) => x.AllowBackInStockSubscriptions); - row.SetProperty(context.Result, product, (x) => x.OrderMinimumQuantity, 1); - row.SetProperty(context.Result, product, (x) => x.OrderMaximumQuantity, 10000); - row.SetProperty(context.Result, product, (x) => x.AllowedQuantities); - row.SetProperty(context.Result, product, (x) => x.DisableBuyButton); - row.SetProperty(context.Result, product, (x) => x.DisableWishlistButton); - row.SetProperty(context.Result, product, (x) => x.AvailableForPreOrder); - row.SetProperty(context.Result, product, (x) => x.CallForPrice); - row.SetProperty(context.Result, product, (x) => x.Price); - row.SetProperty(context.Result, product, (x) => x.OldPrice); - row.SetProperty(context.Result, product, (x) => x.ProductCost); - row.SetProperty(context.Result, product, (x) => x.SpecialPrice); - row.SetProperty(context.Result, product, (x) => x.SpecialPriceStartDateTimeUtc); - row.SetProperty(context.Result, product, (x) => x.SpecialPriceEndDateTimeUtc); - row.SetProperty(context.Result, product, (x) => x.CustomerEntersPrice); - row.SetProperty(context.Result, product, (x) => x.MinimumCustomerEnteredPrice); - row.SetProperty(context.Result, product, (x) => x.MaximumCustomerEnteredPrice, 1000); + row.SetProperty(context.Result, (x) => x.ProductTypeId, (int)ProductType.SimpleProduct); + row.SetProperty(context.Result, (x) => x.VisibleIndividually, true); + row.SetProperty(context.Result, (x) => x.Name); + row.SetProperty(context.Result, (x) => x.ShortDescription); + row.SetProperty(context.Result, (x) => x.FullDescription); + row.SetProperty(context.Result, (x) => x.AdminComment); + row.SetProperty(context.Result, (x) => x.ShowOnHomePage); + row.SetProperty(context.Result, (x) => x.HomePageDisplayOrder); + row.SetProperty(context.Result, (x) => x.MetaKeywords); + row.SetProperty(context.Result, (x) => x.MetaDescription); + row.SetProperty(context.Result, (x) => x.MetaTitle); + row.SetProperty(context.Result, (x) => x.AllowCustomerReviews, true); + row.SetProperty(context.Result, (x) => x.ApprovedRatingSum); + row.SetProperty(context.Result, (x) => x.NotApprovedRatingSum); + row.SetProperty(context.Result, (x) => x.ApprovedTotalReviews); + row.SetProperty(context.Result, (x) => x.NotApprovedTotalReviews); + row.SetProperty(context.Result, (x) => x.Published, true); + row.SetProperty(context.Result, (x) => x.Sku); + row.SetProperty(context.Result, (x) => x.ManufacturerPartNumber); + row.SetProperty(context.Result, (x) => x.Gtin); + row.SetProperty(context.Result, (x) => x.IsGiftCard); + row.SetProperty(context.Result, (x) => x.GiftCardTypeId); + row.SetProperty(context.Result, (x) => x.RequireOtherProducts); + row.SetProperty(context.Result, (x) => x.RequiredProductIds); // TODO: global scope + row.SetProperty(context.Result, (x) => x.AutomaticallyAddRequiredProducts); + row.SetProperty(context.Result, (x) => x.IsDownload); + row.SetProperty(context.Result, (x) => x.DownloadId); + row.SetProperty(context.Result, (x) => x.UnlimitedDownloads, true); + row.SetProperty(context.Result, (x) => x.MaxNumberOfDownloads, 10); + row.SetProperty(context.Result, (x) => x.DownloadExpirationDays); + row.SetProperty(context.Result, (x) => x.DownloadActivationTypeId, 1); + row.SetProperty(context.Result, (x) => x.HasSampleDownload); + row.SetProperty(context.Result, (x) => x.SampleDownloadId, (int?)null, ZeroToNull); // TODO: global scope + row.SetProperty(context.Result, (x) => x.HasUserAgreement); + row.SetProperty(context.Result, (x) => x.UserAgreementText); + row.SetProperty(context.Result, (x) => x.IsRecurring); + row.SetProperty(context.Result, (x) => x.RecurringCycleLength, 100); + row.SetProperty(context.Result, (x) => x.RecurringCyclePeriodId); + row.SetProperty(context.Result, (x) => x.RecurringTotalCycles, 10); + row.SetProperty(context.Result, (x) => x.IsShipEnabled, true); + row.SetProperty(context.Result, (x) => x.IsFreeShipping); + row.SetProperty(context.Result, (x) => x.AdditionalShippingCharge); + row.SetProperty(context.Result, (x) => x.IsEsd); + row.SetProperty(context.Result, (x) => x.IsTaxExempt); + row.SetProperty(context.Result, (x) => x.TaxCategoryId, 1); // TODO: global scope + row.SetProperty(context.Result, (x) => x.ManageInventoryMethodId); + row.SetProperty(context.Result, (x) => x.StockQuantity, 10000); + row.SetProperty(context.Result, (x) => x.DisplayStockAvailability); + row.SetProperty(context.Result, (x) => x.DisplayStockQuantity); + row.SetProperty(context.Result, (x) => x.MinStockQuantity); + row.SetProperty(context.Result, (x) => x.LowStockActivityId); + row.SetProperty(context.Result, (x) => x.NotifyAdminForQuantityBelow, 1); + row.SetProperty(context.Result, (x) => x.BackorderModeId); + row.SetProperty(context.Result, (x) => x.AllowBackInStockSubscriptions); + row.SetProperty(context.Result, (x) => x.OrderMinimumQuantity, 1); + row.SetProperty(context.Result, (x) => x.OrderMaximumQuantity, 10000); + row.SetProperty(context.Result, (x) => x.AllowedQuantities); + row.SetProperty(context.Result, (x) => x.DisableBuyButton); + row.SetProperty(context.Result, (x) => x.DisableWishlistButton); + row.SetProperty(context.Result, (x) => x.AvailableForPreOrder); + row.SetProperty(context.Result, (x) => x.CallForPrice); + row.SetProperty(context.Result, (x) => x.Price); + row.SetProperty(context.Result, (x) => x.OldPrice); + row.SetProperty(context.Result, (x) => x.ProductCost); + row.SetProperty(context.Result, (x) => x.SpecialPrice); + row.SetProperty(context.Result, (x) => x.SpecialPriceStartDateTimeUtc); + row.SetProperty(context.Result, (x) => x.SpecialPriceEndDateTimeUtc); + row.SetProperty(context.Result, (x) => x.CustomerEntersPrice); + row.SetProperty(context.Result, (x) => x.MinimumCustomerEnteredPrice); + row.SetProperty(context.Result, (x) => x.MaximumCustomerEnteredPrice, 1000); // HasTierPrices... ignore as long as no tier prices are imported // LowestAttributeCombinationPrice... ignore as long as no combinations are imported - row.SetProperty(context.Result, product, (x) => x.Weight); - row.SetProperty(context.Result, product, (x) => x.Length); - row.SetProperty(context.Result, product, (x) => x.Width); - row.SetProperty(context.Result, product, (x) => x.Height); - row.SetProperty(context.Result, product, (x) => x.DisplayOrder); - row.SetProperty(context.Result, product, (x) => x.DeliveryTimeId); // TODO: global scope - row.SetProperty(context.Result, product, (x) => x.QuantityUnitId); // TODO: global scope - row.SetProperty(context.Result, product, (x) => x.BasePriceEnabled); - row.SetProperty(context.Result, product, (x) => x.BasePriceMeasureUnit); - row.SetProperty(context.Result, product, (x) => x.BasePriceAmount); - row.SetProperty(context.Result, product, (x) => x.BasePriceBaseAmount); - row.SetProperty(context.Result, product, (x) => x.BundleTitleText); - row.SetProperty(context.Result, product, (x) => x.BundlePerItemShipping); - row.SetProperty(context.Result, product, (x) => x.BundlePerItemPricing); - row.SetProperty(context.Result, product, (x) => x.BundlePerItemShoppingCart); - row.SetProperty(context.Result, product, (x) => x.AvailableStartDateTimeUtc); - row.SetProperty(context.Result, product, (x) => x.AvailableEndDateTimeUtc); + row.SetProperty(context.Result, (x) => x.Weight); + row.SetProperty(context.Result, (x) => x.Length); + row.SetProperty(context.Result, (x) => x.Width); + row.SetProperty(context.Result, (x) => x.Height); + row.SetProperty(context.Result, (x) => x.DisplayOrder); + row.SetProperty(context.Result, (x) => x.DeliveryTimeId); // TODO: global scope + row.SetProperty(context.Result, (x) => x.QuantityUnitId); // TODO: global scope + row.SetProperty(context.Result, (x) => x.BasePriceEnabled); + row.SetProperty(context.Result, (x) => x.BasePriceMeasureUnit); + row.SetProperty(context.Result, (x) => x.BasePriceAmount); + row.SetProperty(context.Result, (x) => x.BasePriceBaseAmount); + row.SetProperty(context.Result, (x) => x.BundleTitleText); + row.SetProperty(context.Result, (x) => x.BundlePerItemShipping); + row.SetProperty(context.Result, (x) => x.BundlePerItemPricing); + row.SetProperty(context.Result, (x) => x.BundlePerItemShoppingCart); + row.SetProperty(context.Result, (x) => x.AvailableStartDateTimeUtc); + row.SetProperty(context.Result, (x) => x.AvailableEndDateTimeUtc); // With new entities, "LimitedToStores" is an implicit field, meaning // it has to be set to true by code if it's absent but "StoreIds" exists. - row.SetProperty(context.Result, product, (x) => x.LimitedToStores, !row.GetDataValue>("StoreIds").IsNullOrEmpty()); + row.SetProperty(context.Result, (x) => x.LimitedToStores, !row.GetDataValue>("StoreIds").IsNullOrEmpty()); var tvp = row.GetDataValue("ProductTemplateViewPath"); product.ProductTemplateId = (tvp.HasValue() && templateViewPaths.ContainsKey(tvp) ? templateViewPaths[tvp] : defaultTemplateId); - row.SetProperty(context.Result, product, (x) => x.CreatedOnUtc, UtcNow); + row.SetProperty(context.Result, (x) => x.CreatedOnUtc, UtcNow); product.UpdatedOnUtc = UtcNow; if (id != 0 && !srcToDestId.ContainsKey(id)) diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 8eae3bde32..2cf6b24252 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -398,9 +398,9 @@ private int ProcessCustomers(IImportExecuteContext context, row.Initialize(customer, email ?? id.ToString()); - row.SetProperty(context.Result, customer, (x) => x.CustomerGuid); - row.SetProperty(context.Result, customer, (x) => x.Username); - row.SetProperty(context.Result, customer, (x) => x.Email); + row.SetProperty(context.Result, (x) => x.CustomerGuid); + row.SetProperty(context.Result, (x) => x.Username); + row.SetProperty(context.Result, (x) => x.Email); if (email.HasValue() && currentCustomer.Email.IsCaseInsensitiveEqual(email)) { @@ -408,22 +408,22 @@ private int ProcessCustomers(IImportExecuteContext context, } else { - row.SetProperty(context.Result, customer, (x) => x.Password); - row.SetProperty(context.Result, customer, (x) => x.PasswordFormatId); - row.SetProperty(context.Result, customer, (x) => x.PasswordSalt); + row.SetProperty(context.Result, (x) => x.Password); + row.SetProperty(context.Result, (x) => x.PasswordFormatId); + row.SetProperty(context.Result, (x) => x.PasswordSalt); } - row.SetProperty(context.Result, customer, (x) => x.AdminComment); - row.SetProperty(context.Result, customer, (x) => x.IsTaxExempt); - row.SetProperty(context.Result, customer, (x) => x.Active); - row.SetProperty(context.Result, customer, (x) => x.IsSystemAccount); - row.SetProperty(context.Result, customer, (x) => x.SystemName); - row.SetProperty(context.Result, customer, (x) => x.LastIpAddress); - row.SetProperty(context.Result, customer, (x) => x.LastLoginDateUtc); - row.SetProperty(context.Result, customer, (x) => x.LastActivityDateUtc); - - row.SetProperty(context.Result, customer, (x) => x.CreatedOnUtc, UtcNow); - row.SetProperty(context.Result, customer, (x) => x.LastActivityDateUtc, UtcNow); + row.SetProperty(context.Result, (x) => x.AdminComment); + row.SetProperty(context.Result, (x) => x.IsTaxExempt); + row.SetProperty(context.Result, (x) => x.Active); + row.SetProperty(context.Result, (x) => x.IsSystemAccount); + row.SetProperty(context.Result, (x) => x.SystemName); + row.SetProperty(context.Result, (x) => x.LastIpAddress); + row.SetProperty(context.Result, (x) => x.LastLoginDateUtc); + row.SetProperty(context.Result, (x) => x.LastActivityDateUtc); + + row.SetProperty(context.Result, (x) => x.CreatedOnUtc, UtcNow); + row.SetProperty(context.Result, (x) => x.LastActivityDateUtc, UtcNow); if (affiliateId > 0 && allAffiliateIds.Contains(affiliateId)) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs index 4dd080293e..e44169d087 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -176,7 +176,6 @@ public TProp GetDataValue(string columnName, string index) public bool SetProperty( ImportResult result, - T target, Expression> prop, TProp defaultValue = default(TProp), Func converter = null) @@ -187,6 +186,7 @@ public bool SetProperty( var isPropertySet = false; var pi = prop.ExtractPropertyInfo(); var propName = pi.Name; + var target = _entity; try { From e57792420eb691b8b6f1ee8a8775530e4171a537 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 6 May 2016 18:58:05 +0200 Subject: [PATCH 007/185] Some renaming --- .../Import/ColumnMapping/ColumnMap.cs | 50 +++++++++---------- .../Controllers/ImportController.cs | 4 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs index 1950b34749..0e7be383dc 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs @@ -30,22 +30,22 @@ public IReadOnlyDictionary Mappings get { return _map; } } - public static bool ParseSourceColumn(string sourceColumn, out string columnWithoutIndex, out string index) + public static bool ParseSourceName(string sourceName, out string nameWithoutIndex, out string index) { - columnWithoutIndex = sourceColumn; + nameWithoutIndex = sourceName; index = null; var result = true; - if (sourceColumn.HasValue() && IsIndexed(sourceColumn)) + if (sourceName.HasValue() && IsIndexed(sourceName)) { - var x1 = sourceColumn.IndexOf('['); - var x2 = sourceColumn.IndexOf(']', x1); + var x1 = sourceName.IndexOf('['); + var x2 = sourceName.IndexOf(']', x1); if (x1 != -1 && x2 != -1 && x2 > x1) { - columnWithoutIndex = sourceColumn.Substring(0, x1); - index = sourceColumn.Substring(x1 + 1, x2 - x1 - 1); + nameWithoutIndex = sourceName.Substring(0, x1); + index = sourceName.Substring(x1 + 1, x2 - x1 - 1); } else { @@ -66,24 +66,24 @@ public static bool ParseSourceColumn(string sourceColumn, out string columnWitho // return mappings; //} - public bool AddMapping(string sourceColumn, string entityProperty, string defaultValue = null) + public bool AddMapping(string sourceName, string mappedName, string defaultValue = null) { - return AddMapping(sourceColumn, null, entityProperty, defaultValue); + return AddMapping(sourceName, null, mappedName, defaultValue); } - public bool AddMapping(string sourceColumn, string index, string entityProperty, string defaultValue = null) + public bool AddMapping(string sourceName, string index, string mappedName, string defaultValue = null) { - Guard.ArgumentNotEmpty(() => sourceColumn); - Guard.ArgumentNotEmpty(() => entityProperty); + Guard.ArgumentNotEmpty(() => sourceName); + Guard.ArgumentNotEmpty(() => mappedName); - var isAlreadyMapped = (entityProperty.HasValue() && _map.Any(x => x.Value.Property.IsCaseInsensitiveEqual(entityProperty))); + var isAlreadyMapped = (mappedName.HasValue() && _map.Any(x => x.Value.Property.IsCaseInsensitiveEqual(mappedName))); if (isAlreadyMapped) return false; - _map[CreateSourceName(sourceColumn, index)] = new ColumnMappingValue + _map[CreateSourceName(sourceName, index)] = new ColumnMappingValue { - Property = entityProperty, + Property = mappedName, Default = defaultValue }; @@ -93,29 +93,29 @@ public bool AddMapping(string sourceColumn, string index, string entityProperty, /// /// Gets a mapped column value /// - /// The name of the column to get a mapped value for. + /// The name of the column to get a mapped value for. /// The column index, e.g. a language code (de, en etc.) - /// The mapped column value OR - if the name is unmapped - a value with the passed [] - public ColumnMappingValue GetMapping(string sourceColumn, string index) + /// The mapped column value OR - if the name is unmapped - a value with the passed [] + public ColumnMappingValue GetMapping(string sourceName, string index) { - return GetMapping(CreateSourceName(sourceColumn, index)); + return GetMapping(CreateSourceName(sourceName, index)); } /// /// Gets a mapped column value /// - /// The name of the column to get a mapped value for. - /// The mapped column value OR - if the name is unmapped - the value of the passed - public ColumnMappingValue GetMapping(string sourceColumn) + /// The name of the column to get a mapped value for. + /// The mapped column value OR - if the name is unmapped - the value of the passed + public ColumnMappingValue GetMapping(string sourceName) { ColumnMappingValue result; - if (_map.TryGetValue(sourceColumn, out result)) + if (_map.TryGetValue(sourceName, out result)) { return result; } - return new ColumnMappingValue { Property = sourceColumn }; + return new ColumnMappingValue { Property = sourceName }; } } @@ -124,7 +124,7 @@ public ColumnMappingValue GetMapping(string sourceColumn) public class ColumnMappingValue { /// - /// The property name of the target entity + /// The mapped name /// [JsonProperty] public string Property { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index 36cd41e8e5..1babc264d3 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -83,7 +83,7 @@ private bool IsDefaultValueDisabled(string column, string property, string[] dis return true; string columnWithoutIndex, columnIndex; - if (ColumnMap.ParseSourceColumn(property, out columnWithoutIndex, out columnIndex)) + if (ColumnMap.ParseSourceName(property, out columnWithoutIndex, out columnIndex)) return disabledFieldNames.Contains(columnWithoutIndex); return false; @@ -256,7 +256,7 @@ private void PrepareProfileModel(ImportProfileModel model, ImportProfile profile foreach (var column in dataTable.Columns.Where(x => x.Name.HasValue())) { string columnWithoutIndex, columnIndex; - ColumnMap.ParseSourceColumn(column.Name, out columnWithoutIndex, out columnIndex); + ColumnMap.ParseSourceName(column.Name, out columnWithoutIndex, out columnIndex); model.AvailableSourceColumns.Add(new ColumnMappingItemModel { From 36a15b028db50772f24903f1ec7d77f3fb13a0ff Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 6 May 2016 21:35:15 +0200 Subject: [PATCH 008/185] Import: more refactoring --- .../Catalog/Importer/CategoryImporter.cs | 22 ++--- .../Catalog/Importer/ProductImporter.cs | 26 +++--- .../Common/AddressExtentions.cs | 93 +++++++++++-------- .../Customers/Importer/CustomerImporter.cs | 87 ++++++++++------- .../Import/ImportDataSegmenter.cs | 59 ++++++++---- .../Import/ImportExecuteContext.cs | 6 +- .../DataExchange/Import/ImportRow.cs | 34 +++++-- .../NewsLetterSubscriptionImporter.cs | 4 +- .../Providers/PayPalExpressProvider.cs | 4 +- .../DataExchange/ImportDataSegmenterTests.cs | 2 +- 10 files changed, 207 insertions(+), 130 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index ebc094f629..d01fafaf97 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -65,7 +65,7 @@ public CategoryImporter( _dataExchangeSettings = dataExchangeSettings; } - protected virtual int ProcessSlugs(IImportExecuteContext context, ImportRow[] batch) + protected virtual int ProcessSlugs(IImportExecuteContext context, IEnumerable> batch) { var entityName = typeof(Category).Name; var slugMap = new Dictionary(); @@ -136,7 +136,7 @@ protected virtual int ProcessSlugs(IImportExecuteContext context, ImportRow[] batch) + protected virtual int ProcessLocalizations(IImportExecuteContext context, IEnumerable> batch) { foreach (var row in batch) { @@ -165,8 +165,9 @@ protected virtual int ProcessLocalizations(IImportExecuteContext context, Import return num; } - protected virtual int ProcessParentMappings(IImportExecuteContext context, - ImportRow[] batch, + protected virtual int ProcessParentMappings( + IImportExecuteContext context, + IEnumerable> batch, Dictionary srcToDestId) { foreach (var row in batch) @@ -198,7 +199,7 @@ protected virtual int ProcessParentMappings(IImportExecuteContext context, protected virtual int ProcessPictures( IImportExecuteContext context, - ImportRow[] batch, + IEnumerable> batch, Dictionary srcToDestId) { Picture picture = null; @@ -271,7 +272,7 @@ protected virtual int ProcessPictures( return num; } - protected virtual int ProcessStoreMappings(IImportExecuteContext context, ImportRow[] batch) + protected virtual int ProcessStoreMappings(IImportExecuteContext context, IEnumerable> batch) { _storeMappingRepository.AutoCommitEnabled = false; @@ -290,7 +291,7 @@ protected virtual int ProcessStoreMappings(IImportExecuteContext context, Import protected virtual int ProcessCategories( IImportExecuteContext context, - ImportRow[] batch, + IEnumerable> batch, Dictionary templateViewPaths, Dictionary srcToDestId) { @@ -445,7 +446,7 @@ protected override void Import(IImportExecuteContext context) using (var scope = new DbContextScope(ctx: _categoryRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { - var segmenter = context.GetSegmenter(); + var segmenter = context.CreateSegmenter(); Init(context, _dataExchangeSettings); @@ -453,7 +454,7 @@ protected override void Import(IImportExecuteContext context) while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { - var batch = segmenter.CurrentBatch; + var batch = segmenter.GetCurrentBatch(); // Perf: detach all entities _categoryRepository.Context.DetachAll(false); @@ -544,8 +545,7 @@ protected override void Import(IImportExecuteContext context) while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { - var batch = segmenter.CurrentBatch; - + var batch = segmenter.GetCurrentBatch(); _categoryRepository.Context.DetachAll(false); try diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 09f15a8b00..bdb4fabf19 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -107,7 +107,7 @@ public ProductImporter( protected virtual int ProcessProductMappings( IImportExecuteContext context, - ImportRow[] batch, + IEnumerable> batch, Dictionary srcToDestId) { _productRepository.AutoCommitEnabled = false; @@ -137,7 +137,7 @@ protected virtual int ProcessProductMappings( return num; } - protected virtual void ProcessProductPictures(IImportExecuteContext context, ImportRow[] batch) + protected virtual void ProcessProductPictures(IImportExecuteContext context, IEnumerable> batch) { // true, cause pictures must be saved and assigned an id prior adding a mapping. _productPictureRepository.AutoCommitEnabled = true; @@ -244,7 +244,7 @@ protected virtual void ProcessProductPictures(IImportExecuteContext context, Imp } } - protected virtual int ProcessProductManufacturers(IImportExecuteContext context, ImportRow[] batch) + protected virtual int ProcessProductManufacturers(IImportExecuteContext context, IEnumerable> batch) { _productManufacturerRepository.AutoCommitEnabled = false; @@ -295,7 +295,7 @@ protected virtual int ProcessProductManufacturers(IImportExecuteContext context, return num; } - protected virtual int ProcessProductCategories(IImportExecuteContext context, ImportRow[] batch) + protected virtual int ProcessProductCategories(IImportExecuteContext context, IEnumerable> batch) { _productCategoryRepository.AutoCommitEnabled = false; @@ -347,8 +347,8 @@ protected virtual int ProcessProductCategories(IImportExecuteContext context, Im } protected virtual int ProcessLocalizations( - IImportExecuteContext context, - ImportRow[] batch, + IImportExecuteContext context, + IEnumerable> batch, string[] localizedProperties) { if (localizedProperties.Length == 0) @@ -385,7 +385,7 @@ protected virtual int ProcessLocalizations( return 0; } - protected virtual int ProcessSlugs(IImportExecuteContext context, ImportRow[] batch, string[] columnIndexes) + protected virtual int ProcessSlugs(IImportExecuteContext context, IEnumerable> batch, string[] columnIndexes) { var entityName = typeof(Product).Name; var slugMap = new Dictionary(); @@ -461,7 +461,7 @@ protected virtual int ProcessSlugs(IImportExecuteContext context, ImportRow[] batch) + protected virtual int ProcessStoreMappings(IImportExecuteContext context, IEnumerable> batch) { _storeMappingRepository.AutoCommitEnabled = false; @@ -480,7 +480,7 @@ protected virtual int ProcessStoreMappings(IImportExecuteContext context, Import protected virtual int ProcessProducts( IImportExecuteContext context, - ImportRow[] batch, + IEnumerable> batch, Dictionary templateViewPaths, Dictionary srcToDestId) { @@ -715,7 +715,7 @@ public static string[] DefaultKeyFields } } - private IEnumerable ResolveLocalizedProperties(ImportDataSegmenter segmenter) + private IEnumerable ResolveLocalizedProperties(ImportDataSegmenter segmenter) { // Perf: determine whether our localizable properties actually have // counterparts in the source BEFORE import begins. This way we spare ourself @@ -737,7 +737,7 @@ protected override void Import(IImportExecuteContext context) using (var scope = new DbContextScope(ctx: _productRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { - var segmenter = context.GetSegmenter(); + var segmenter = context.CreateSegmenter(); Init(context, _dataExchangeSettings); var localizedProperties = ResolveLocalizedProperties(segmenter).ToArray(); @@ -746,7 +746,7 @@ protected override void Import(IImportExecuteContext context) while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { - var batch = segmenter.CurrentBatch; + var batch = segmenter.GetCurrentBatch(); // Perf: detach all entities _productRepository.Context.DetachAll(false); @@ -877,7 +877,7 @@ protected override void Import(IImportExecuteContext context) while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { - var batch = segmenter.CurrentBatch; + var batch = segmenter.GetCurrentBatch(); _productRepository.Context.DetachAll(false); diff --git a/src/Libraries/SmartStore.Services/Common/AddressExtentions.cs b/src/Libraries/SmartStore.Services/Common/AddressExtentions.cs index 73afed9723..1055aaa340 100644 --- a/src/Libraries/SmartStore.Services/Common/AddressExtentions.cs +++ b/src/Libraries/SmartStore.Services/Common/AddressExtentions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Collections.Generic; using System.Text; using SmartStore.Core.Domain.Common; @@ -7,50 +8,13 @@ namespace SmartStore.Services.Common { public static class AddressExtentions { - /// - /// Find an address - /// - /// Source - /// First name - /// Last name - /// Phone number - /// Email - /// Fax number - /// Company - /// Address 1 - /// Address 2 - /// City - /// State/province identifier - /// Zip postal code - /// Country identifier - /// Address - public static Address FindAddress(this List
source, - string firstName, string lastName, string phoneNumber, - string email, string faxNumber, string company, string address1, - string address2, string city, int? stateProvinceId, - string zipPostalCode, int? countryId) - { - return source.Find((a) => ((String.IsNullOrEmpty(a.FirstName) && String.IsNullOrEmpty(firstName)) || a.FirstName == firstName) && - ((String.IsNullOrEmpty(a.LastName) && String.IsNullOrEmpty(lastName)) || a.LastName == lastName) && - ((String.IsNullOrEmpty(a.PhoneNumber) && String.IsNullOrEmpty(phoneNumber)) || a.PhoneNumber == phoneNumber) && - ((String.IsNullOrEmpty(a.Email) && String.IsNullOrEmpty(email)) || a.Email == email) && - ((String.IsNullOrEmpty(a.FaxNumber) && String.IsNullOrEmpty(faxNumber)) || a.FaxNumber == faxNumber) && - ((String.IsNullOrEmpty(a.Company) && String.IsNullOrEmpty(company)) || a.Company == company) && - ((String.IsNullOrEmpty(a.Address1) && String.IsNullOrEmpty(address1)) || a.Address1 == address1) && - ((String.IsNullOrEmpty(a.Address2) && String.IsNullOrEmpty(address2)) || a.Address2 == address2) && - ((String.IsNullOrEmpty(a.City) && String.IsNullOrEmpty(city)) || a.City == city) && - ((a.StateProvinceId.IsNullOrDefault() && stateProvinceId.IsNullOrDefault()) || a.StateProvinceId == stateProvinceId) && - ((String.IsNullOrEmpty(a.ZipPostalCode) && String.IsNullOrEmpty(zipPostalCode)) || a.ZipPostalCode == zipPostalCode) && - ((a.CountryId.IsNullOrDefault() && countryId.IsNullOrDefault()) || a.CountryId == countryId)); - } - /// - /// Find first occurrence of address + /// Find first occurrence of an address /// /// Addresses to be searched /// Address to find /// First occurrence of address - public static Address FindAddress(this List
source, Address address) + public static Address FindAddress(this ICollection
source, Address address) { return source.FindAddress( address.FirstName, @@ -68,6 +32,57 @@ public static Address FindAddress(this List
source, Address address) ); } + /// + /// Find an address + /// + /// Source + /// First name + /// Last name + /// Phone number + /// Email + /// Fax number + /// Company + /// Address 1 + /// Address 2 + /// City + /// State/province identifier + /// Zip postal code + /// Country identifier + /// Address + public static Address FindAddress( + this ICollection
source, + string firstName, + string lastName, + string phoneNumber, + string email, + string faxNumber, + string company, + string address1, + string address2, + string city, + int? stateProvinceId, + string zipPostalCode, + int? countryId) + { + Func addressMatcher = (x) => + { + return x.Email.IsCaseInsensitiveEqual(email) + && x.LastName.IsCaseInsensitiveEqual(lastName) + && x.FirstName.IsCaseInsensitiveEqual(firstName) + && x.Address1.IsCaseInsensitiveEqual(address1) + && x.Address2.IsCaseInsensitiveEqual(address2) + && x.Company.IsCaseInsensitiveEqual(company) + && x.ZipPostalCode.IsCaseInsensitiveEqual(zipPostalCode) + && x.City.IsCaseInsensitiveEqual(city) + && x.PhoneNumber.IsCaseInsensitiveEqual(phoneNumber) + && x.FaxNumber.IsCaseInsensitiveEqual(faxNumber) + && x.StateProvinceId == stateProvinceId + && x.CountryId == countryId; + }; + + return source.FirstOrDefault(addressMatcher); + } + /// Returns the full name of the address. public static string GetFullName(this Address address) { diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 2cf6b24252..683fcbc5e0 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Linq.Expressions; using SmartStore.Core.Async; using SmartStore.Core.Data; using SmartStore.Core.Domain.Common; @@ -74,13 +75,12 @@ public CustomerImporter( private int? CountryCodeToId(Dictionary allCountries, string code) { - if (code.HasValue() && allCountries.ContainsKey(code)) + int countryId; + if (code.HasValue() && allCountries.TryGetValue(code, out countryId) && countryId != 0) { - var countryId = allCountries[code]; - - if (countryId != 0) - return countryId; + return countryId; } + return null; } @@ -90,14 +90,13 @@ public CustomerImporter( { var key = Tuple.Create(countryId.Value, abbreviation); - if (allStateProvinces.ContainsKey(key)) + int stateId; + if (allStateProvinces.TryGetValue(key, out stateId) && stateId != 0) { - var stateId = allStateProvinces[key]; - - if (stateId != 0) - return stateId; + return stateId; } } + return null; } @@ -174,9 +173,23 @@ private void ImportAvatar(IImportExecuteContext context, ImportRow row } } + private void ProcessAddresses( + IImportExecuteContext context, + IEnumerable> batch, + Dictionary allCountries, + Dictionary, int> allStateProvinces) + { + foreach (var row in batch) + { + ImportAddress("BillingAddress.", row, context, allCountries, allStateProvinces); + ImportAddress("ShippingAddress.", row, context, allCountries, allStateProvinces); + } + } + private void ImportAddress( string fieldPrefix, ImportRow row, + IImportExecuteContext context, Dictionary allCountries, Dictionary, int> allStateProvinces) { @@ -190,24 +203,26 @@ private void ImportAddress( var importAddress = new Address { - FirstName = row.GetDataValue(fieldPrefix + "FirstName"), LastName = lastName, - Email = row.GetDataValue(fieldPrefix + "Email"), - Company = row.GetDataValue(fieldPrefix + "Company"), - City = row.GetDataValue(fieldPrefix + "City"), - Address1 = row.GetDataValue(fieldPrefix + "Address1"), - Address2 = row.GetDataValue(fieldPrefix + "Address2"), - ZipPostalCode = row.GetDataValue(fieldPrefix + "ZipPostalCode"), - PhoneNumber = row.GetDataValue(fieldPrefix + "PhoneNumber"), - FaxNumber = row.GetDataValue(fieldPrefix + "FaxNumber"), CreatedOnUtc = UtcNow, CountryId = countryId, StateProvinceId = stateId }; - var appliedAddress = row.Entity.Addresses - .ToList() - .FindAddress(importAddress); + var childRow = new ImportRow
(row.Segmenter, row.DataRow, row.Position); + childRow.Initialize(importAddress, row.EntityDisplayName); + + childRow.SetProperty(context.Result, fieldPrefix + "FirstName", x => x.FirstName); + childRow.SetProperty(context.Result, fieldPrefix + "Email", x => x.Email); + childRow.SetProperty(context.Result, fieldPrefix + "Company", x => x.Company); + childRow.SetProperty(context.Result, fieldPrefix + "City", x => x.City); + childRow.SetProperty(context.Result, fieldPrefix + "Address1", x => x.Address1); + childRow.SetProperty(context.Result, fieldPrefix + "Address2", x => x.Address2); + childRow.SetProperty(context.Result, fieldPrefix + "ZipPostalCode", x => x.ZipPostalCode); + childRow.SetProperty(context.Result, fieldPrefix + "PhoneNumber", x => x.PhoneNumber); + childRow.SetProperty(context.Result, fieldPrefix + "FaxNumber", x => x.FaxNumber); + + var appliedAddress = row.Entity.Addresses.FindAddress(importAddress); if (appliedAddress == null) { @@ -230,7 +245,7 @@ private void ImportAddress( } private void ProcessGenericAttributes(IImportExecuteContext context, - ImportRow[] batch, + IEnumerable> batch, Dictionary allCountries, Dictionary, int> allStateProvinces, List allCustomerNumbers) @@ -319,8 +334,9 @@ private void ProcessGenericAttributes(IImportExecuteContext context, } } - private int ProcessCustomers(IImportExecuteContext context, - ImportRow[] batch, + private int ProcessCustomers( + IImportExecuteContext context, + IEnumerable> batch, List allAffiliateIds) { _customerRepository.AutoCommitEnabled = true; @@ -510,7 +526,7 @@ protected override void Import(IImportExecuteContext context) using (var scope = new DbContextScope(ctx: _services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) { - var segmenter = context.GetSegmenter(); + var segmenter = context.CreateSegmenter(); Init(context, _dataExchangeSettings); @@ -518,12 +534,15 @@ protected override void Import(IImportExecuteContext context) while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { - var batch = segmenter.CurrentBatch; + var batch = segmenter.GetCurrentBatch(); _customerRepository.Context.DetachAll(false); context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); + // =========================================================================== + // Process customers + // =========================================================================== try { ProcessCustomers(context, batch, allAffiliateIds); @@ -541,10 +560,12 @@ protected override void Import(IImportExecuteContext context) context.Result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); + // =========================================================================== + // Process generic attributes + // =========================================================================== try { _services.DbContext.AutoDetectChangesEnabled = true; - ProcessGenericAttributes(context, batch, allCountries, allStateProvinces, allCustomerNumbers); } catch (Exception exception) @@ -556,17 +577,15 @@ protected override void Import(IImportExecuteContext context) _services.DbContext.AutoDetectChangesEnabled = false; } + // =========================================================================== + // Process addresses + // =========================================================================== if (segmenter.HasColumn("BillingAddress.LastName") || segmenter.HasColumn("ShippingAddress.LastName")) { try { _services.DbContext.AutoDetectChangesEnabled = true; - - foreach (var row in batch) - { - ImportAddress("BillingAddress.", row, allCountries, allStateProvinces); - ImportAddress("ShippingAddress.", row, allCountries, allStateProvinces); - } + ProcessAddresses(context, batch, allCountries, allStateProvinces); } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs index 03310fe417..394ab69a9d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs @@ -8,12 +8,12 @@ namespace SmartStore.Services.DataExchange.Import { - public class ImportDataSegmenter where T : BaseEntity + public class ImportDataSegmenter { private const int BATCHSIZE = 100; private readonly IDataTable _table; - private ImportRow[] _currentBatch; + private object[] _currentBatch; private readonly IPageable _pageable; private bool _bof; private CultureInfo _culture; @@ -197,28 +197,49 @@ public bool ReadNextBatch() return false; } - public ImportRow[] CurrentBatch + //public IEnumerable> CurrentBatch + //{ + // get + // { + // if (_currentBatch == null) + // { + // int start = _pageable.FirstItemIndex - 1; + // int end = _pageable.LastItemIndex - 1; + + // _currentBatch = new ImportRow[(end - start) + 1]; + + // // Determine values per row + // int i = 0; + // for (int r = start; r <= end; r++) + // { + // _currentBatch[i] = new ImportRow(this, _table.Rows[r], r); + // i++; + // } + // } + + // return _currentBatch.Cast>(); + // } + //} + + public IEnumerable> GetCurrentBatch() where T : BaseEntity { - get + if (_currentBatch == null) { - if (_currentBatch == null) + int start = _pageable.FirstItemIndex - 1; + int end = _pageable.LastItemIndex - 1; + + _currentBatch = new ImportRow[(end - start) + 1]; + + // Determine values per row + int i = 0; + for (int r = start; r <= end; r++) { - int start = _pageable.FirstItemIndex - 1; - int end = _pageable.LastItemIndex - 1; - - _currentBatch = new ImportRow[(end - start) + 1]; - - // Determine values per row - int i = 0; - for (int r = start; r <= end; r++) - { - _currentBatch[i] = new ImportRow(this, _table.Rows[r], r); - i++; - } + _currentBatch[i] = new ImportRow(this, _table.Rows[r], r); + i++; } - - return _currentBatch; } + + return _currentBatch.Cast>(); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index a37b9b0013..29ae2d82d8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -59,7 +59,7 @@ public interface IImportExecuteContext /// /// /// - ImportDataSegmenter GetSegmenter() where TEntity : BaseEntity; + ImportDataSegmenter CreateSegmenter(); /// /// Allows to set a progress message @@ -135,9 +135,9 @@ public bool IsMaxFailures } } - public ImportDataSegmenter GetSegmenter() where TEntity : BaseEntity + public ImportDataSegmenter CreateSegmenter() { - return new ImportDataSegmenter(DataTable, ColumnMap); + return new ImportDataSegmenter(DataTable, ColumnMap); } public void SetProgress(int value, int maximum) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs index e44169d087..02693754aa 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -15,10 +15,10 @@ public class ImportRow where T : BaseEntity private bool _isNew; private ImportRowInfo _rowInfo; - private readonly ImportDataSegmenter _segmenter; + private readonly ImportDataSegmenter _segmenter; private readonly IDataRow _row; - public ImportRow(ImportDataSegmenter parent, IDataRow row, int position) + public ImportRow(ImportDataSegmenter parent, IDataRow row, int position) { _segmenter = parent; _row = row; @@ -73,7 +73,7 @@ public bool IsNew get { return _isNew; } } - public ImportDataSegmenter Segmenter + public ImportDataSegmenter Segmenter { get { return _segmenter; } } @@ -83,6 +83,11 @@ public T Entity get { return _entity; } } + public IDataRow DataRow + { + get { return _row; } + } + public string EntityDisplayName { get { return _entityDisplayName; } @@ -180,7 +185,22 @@ public bool SetProperty( TProp defaultValue = default(TProp), Func converter = null) { - // TBD: (MC) do not check for perf reason? + return SetProperty( + result, + null, // columnName + prop, + defaultValue, + converter); + } + + public bool SetProperty( + ImportResult result, + string columnName, + Expression> prop, + TProp defaultValue = default(TProp), + Func converter = null) + { + // TBD: (MC) do not check or validate for perf reason? //CheckInitialized(); var isPropertySet = false; @@ -188,10 +208,12 @@ public bool SetProperty( var propName = pi.Name; var target = _entity; + columnName = columnName ?? propName; + try { object value; - var mapping = _segmenter.ColumnMap.GetMapping(propName); + var mapping = _segmenter.ColumnMap.GetMapping(columnName); if (mapping.IgnoreProperty) { @@ -227,7 +249,7 @@ public bool SetProperty( // if entity is new and source field value is null, determine default value in this particular order: // 2.) Default value in field mapping table // 3.) passed default value argument - defaultValue = GetDefaultValue(mapping, propName, defaultValue, result); + defaultValue = GetDefaultValue(mapping, columnName, defaultValue, result); // source does not contain field data or is empty... if (defaultValue != null) diff --git a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs index 70ee52c637..0b145335af 100644 --- a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs +++ b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs @@ -47,13 +47,13 @@ public void Execute(IImportExecuteContext context) using (var scope = new DbContextScope(ctx: _services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) { - var segmenter = context.GetSegmenter(); + var segmenter = context.CreateSegmenter(); context.Result.TotalRecords = segmenter.TotalRows; while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { - var batch = segmenter.CurrentBatch; + var batch = segmenter.GetCurrentBatch(); _subscriptionRepository.Context.DetachAll(false); diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs index 63e3efc0f3..ca69a8e17c 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs @@ -404,7 +404,7 @@ public ProcessPaymentRequest SetCheckoutDetails(ProcessPaymentRequest processPay if (billingCountry != null) billingCountryId = billingCountry.Id; - var billingAddress = customer.Addresses.ToList().FindAddress( + var billingAddress = customer.Addresses.FindAddress( billingFirstName, billingLastName, billingPhoneNumber, billingEmail, string.Empty, string.Empty, billingAddress1, billingAddress2, billingCity, billingStateProvinceId, billingZipPostalCode, billingCountryId); @@ -461,7 +461,7 @@ public ProcessPaymentRequest SetCheckoutDetails(ProcessPaymentRequest processPay if (shippingCountry != null) shippingCountryId = shippingCountry.Id; - var shippingAddress = customer.Addresses.ToList().FindAddress( + var shippingAddress = customer.Addresses.FindAddress( shippingFirstName, shippingLastName, shippingPhoneNumber, shippingEmail, string.Empty, string.Empty, shippingAddress1, shippingAddress2, shippingCity, diff --git a/src/Tests/SmartStore.Services.Tests/DataExchange/ImportDataSegmenterTests.cs b/src/Tests/SmartStore.Services.Tests/DataExchange/ImportDataSegmenterTests.cs index d36888d507..aac1e9af4e 100644 --- a/src/Tests/SmartStore.Services.Tests/DataExchange/ImportDataSegmenterTests.cs +++ b/src/Tests/SmartStore.Services.Tests/DataExchange/ImportDataSegmenterTests.cs @@ -24,7 +24,7 @@ public void CanResolveColumnIndexNames() }; var table = new LightweightDataTable(columns, new List()); - var segmenter = new ImportDataSegmenter(table, new ColumnMap()); + var segmenter = new ImportDataSegmenter(table, new ColumnMap()); segmenter.ColumnMap.AddMapping("Attr[Material]", "material"); segmenter.ColumnMap.AddMapping("Name[it]", "name_it"); From f32bd3dd35e40bc75cbff161eb8a496d42de8466 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 6 May 2016 21:59:53 +0200 Subject: [PATCH 009/185] Swap source and mapped name when persisting import column mappings --- ...916117_SwapColumnMappingValues.Designer.cs | 29 ++++ ...201605061916117_SwapColumnMappingValues.cs | 90 +++++++++++++ ...1605061916117_SwapColumnMappingValues.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 7 + .../Import/ColumnMapping/ColumnMap.cs | 8 +- .../ColumnMapping/ColumnMapConverter.cs | 2 +- .../Import/ImportDataSegmenter.cs | 2 +- .../DataExchange/Import/ImportRow.cs | 6 +- .../Controllers/ImportController.cs | 6 +- 9 files changed, 264 insertions(+), 12 deletions(-) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.resx diff --git a/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.Designer.cs new file mode 100644 index 0000000000..1fa094db33 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.1.3-40302")] + public sealed partial class SwapColumnMappingValues : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(SwapColumnMappingValues)); + + string IMigrationMetadata.Id + { + get { return "201605061916117_SwapColumnMappingValues"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs new file mode 100644 index 0000000000..3db5f58fdc --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs @@ -0,0 +1,90 @@ +namespace SmartStore.Data.Migrations +{ + using System.Collections.Generic; + using System.Data.Entity.Migrations; + using System.Linq; + using Core.Domain; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + using Setup; + + public partial class SwapColumnMappingValues : DbMigration, IDataSeeder + { + public override void Up() + { + } + + public override void Down() + { + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + var importProfiles = context.Set().ToList(); + + foreach (var profile in importProfiles.Where(x => x.ColumnMapping.HasValue())) + { + var dic = new Dictionary>(); + var storeMapping = true; + + var json = JObject.Parse(profile.ColumnMapping); + + foreach (var kvp in json) + { + dynamic value = kvp.Value; + + var mappedName = (string)value.MappedName; + var property = (string)value.Property; + var defaultValue = (string)value.Default; + + if (mappedName.HasValue()) + { + // break migration because data is already migrated + storeMapping = false; + break; + } + else if (property.HasValue()) + { + if (!kvp.Key.IsCaseInsensitiveEqual(property) || defaultValue.HasValue()) + { + // swap value + dic.Add(property, new Dictionary + { + { "MappedName", kvp.Key }, + { "Default", defaultValue } + }); + } + else + { + // ignore because persisting not required anymore + } + } + else + { + // explicitly ignored property + dic.Add(property, new Dictionary + { + { "MappedName", property }, + { "Default", "[IGNOREPROPERTY]" } + }); + } + } + + if (storeMapping) + { + if (dic.Any()) + profile.ColumnMapping = JsonConvert.SerializeObject(dic); + else + profile.ColumnMapping = null; + } + } + + context.SaveChanges(); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.resx b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.resx new file mode 100644 index 0000000000..15bcc90554 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index fb9b3fae43..6b9222716c 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -388,6 +388,10 @@ 201605020640016_GtinMpnIndex.cs + + + 201605061916117_SwapColumnMappingValues.cs + @@ -724,6 +728,9 @@ 201605020640016_GtinMpnIndex.cs + + 201605061916117_SwapColumnMappingValues.cs + diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs index 0e7be383dc..31d61bd3cd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs @@ -76,14 +76,14 @@ public bool AddMapping(string sourceName, string index, string mappedName, strin Guard.ArgumentNotEmpty(() => sourceName); Guard.ArgumentNotEmpty(() => mappedName); - var isAlreadyMapped = (mappedName.HasValue() && _map.Any(x => x.Value.Property.IsCaseInsensitiveEqual(mappedName))); + var isAlreadyMapped = (mappedName.HasValue() && _map.Any(x => x.Value.MappedName.IsCaseInsensitiveEqual(mappedName))); if (isAlreadyMapped) return false; _map[CreateSourceName(sourceName, index)] = new ColumnMappingValue { - Property = mappedName, + MappedName = mappedName, Default = defaultValue }; @@ -115,7 +115,7 @@ public ColumnMappingValue GetMapping(string sourceName) return result; } - return new ColumnMappingValue { Property = sourceName }; + return new ColumnMappingValue { MappedName = sourceName }; } } @@ -127,7 +127,7 @@ public class ColumnMappingValue /// The mapped name /// [JsonProperty] - public string Property { get; set; } + public string MappedName { get; set; } /// /// An optional default value diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMapConverter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMapConverter.cs index 78cbd7fa13..8d6d0f8ed7 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMapConverter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMapConverter.cs @@ -32,7 +32,7 @@ public override object ConvertFrom(CultureInfo culture, object value) foreach (var kvp in dict) { - map.AddMapping(kvp.Key, null, kvp.Value.Property, kvp.Value.Default); + map.AddMapping(kvp.Key, null, kvp.Value.MappedName, kvp.Value.Default); } return map; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs index 394ab69a9d..9446847257 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs @@ -125,7 +125,7 @@ public bool HasColumn(string name, bool withAnyIndex = false) /// public bool HasColumn(string name, string index) { - return _table.HasColumn(_columnMap.GetMapping(name, index).Property); + return _table.HasColumn(_columnMap.GetMapping(name, index).MappedName); } /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs index 02693754aa..99982e7a6d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -152,7 +152,7 @@ public bool HasDataValue(string columnName, string index) var mapping = _segmenter.ColumnMap.GetMapping(columnName, index); object value; - return (_row.TryGetValue(mapping.Property, out value) && value != null && value != DBNull.Value); + return (_row.TryGetValue(mapping.MappedName, out value) && value != null && value != DBNull.Value); } public TProp GetDataValue(string columnName) @@ -165,7 +165,7 @@ public TProp GetDataValue(string columnName, string index) object value; var mapping = _segmenter.ColumnMap.GetMapping(columnName, index); - if (_row.TryGetValue(mapping.Property, out value) && value != null && value != DBNull.Value) + if (_row.TryGetValue(mapping.MappedName, out value) && value != null && value != DBNull.Value) { return value.Convert(_segmenter.Culture); } @@ -219,7 +219,7 @@ public bool SetProperty( { // explicitly ignore this property } - else if (_row.TryGetValue(mapping.Property, out value) && (value != null && value != DBNull.Value)) + else if (_row.TryGetValue(mapping.MappedName, out value) && (value != null && value != DBNull.Value)) { // source contains field value. Set it. TProp converted; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index 1babc264d3..d7e46cd009 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -224,8 +224,8 @@ private void PrepareProfileModel(ImportProfileModel model, ImportProfile profile { var mapping = new ColumnMappingItemModel { - Column = x.Key, - Property = x.Value.Property, + Column = x.Value.MappedName, + Property = x.Key, Default = x.Value.Default }; @@ -451,7 +451,7 @@ public ActionResult Edit(ImportProfileModel model, bool continueEditing, FormCol if (defaultValue.HasValue() && GetDisabledDefaultFieldNames(profile).Contains(property)) defaultValue = null; - result = map.AddMapping(column, null, property, defaultValue); + result = map.AddMapping(property, null, column, defaultValue); } if (!result) From 81da79870804fdf44fe0c2a65fc01843b6adfda0 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 6 May 2016 23:58:26 +0200 Subject: [PATCH 010/185] Import: minor refactoring in NewsLetterSubscriptionImporter --- .../NewsLetterSubscriptionImporter.cs | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs index 0b145335af..cb454e8232 100644 --- a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs +++ b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs @@ -42,9 +42,6 @@ public void Execute(IImportExecuteContext context) var utcNow = DateTime.UtcNow; var currentStoreId = _services.StoreContext.CurrentStore.Id; - var toAdd = new List(); - var toUpdate = new List(); - using (var scope = new DbContextScope(ctx: _services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) { var segmenter = context.CreateSegmenter(); @@ -55,7 +52,8 @@ public void Execute(IImportExecuteContext context) { var batch = segmenter.GetCurrentBatch(); - _subscriptionRepository.Context.DetachAll(false); + // Perf: detach all entities + _subscriptionRepository.Context.DetachEntities(false); context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); @@ -64,7 +62,7 @@ public void Execute(IImportExecuteContext context) try { var email = row.GetDataValue("Email"); - var active = row.GetDataValue("Active"); + var active = row.HasDataValue("Active") ? row.GetDataValue("Active") : true; var storeId = row.GetDataValue("StoreId"); if (storeId == 0) @@ -122,32 +120,25 @@ public void Execute(IImportExecuteContext context) StoreId = storeId }; - toAdd.Add(subscription); - ++context.Result.NewRecords; + _subscriptionRepository.Insert(subscription); + context.Result.NewRecords++; } else { subscription.Active = active; - toUpdate.Add(subscription); - ++context.Result.ModifiedRecords; + _subscriptionRepository.Update(subscription); + context.Result.ModifiedRecords++; } - - // insert new subscribers - _subscriptionRepository.AutoCommitEnabled = true; - _subscriptionRepository.InsertRange(toAdd, 500); - toAdd.Clear(); - - // update modified subscribers - _subscriptionRepository.UpdateRange(toUpdate); - toUpdate.Clear(); } catch (Exception exception) { context.Result.AddError(exception.ToAllMessages(), row.GetRowInfo()); } - } - } + } // for + + _subscriptionRepository.Context.SaveChanges(); + } // while } } } From 9ce9ab6b3a1228bb80ed4c7e7f7edcdf4109f3f8 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 7 May 2016 01:48:15 +0200 Subject: [PATCH 011/185] Import: refactoring in CustomerImporter --- .../Catalog/Importer/ProductImporter.cs | 2 +- .../Common/GenericAttributeService.cs | 6 +- .../Customers/Importer/CustomerImporter.cs | 112 +++++++++--------- 3 files changed, 61 insertions(+), 59 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index bdb4fabf19..1ae3b4cc5e 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -379,7 +379,7 @@ protected virtual int ProcessLocalizations( if (shouldSave) { // commit whole batch at once - return _productManufacturerRepository.Context.SaveChanges(); + return _productRepository.Context.SaveChanges(); } return 0; diff --git a/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs b/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs index d57222a18a..8f23424525 100644 --- a/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs @@ -229,12 +229,12 @@ public virtual void SaveAttribute(int entityId, string key, string ke { if (string.IsNullOrWhiteSpace(valueStr)) { - //delete + // delete DeleteAttribute(prop); } else { - //update + // update prop.Value = valueStr; UpdateAttribute(prop); } @@ -243,7 +243,7 @@ public virtual void SaveAttribute(int entityId, string key, string ke { if (!string.IsNullOrWhiteSpace(valueStr)) { - //insert + // insert prop = new GenericAttribute { EntityId = entityId, diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 683fcbc5e0..dee6721500 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -102,23 +102,45 @@ public CustomerImporter( private void SaveAttribute(ImportRow row, string key) { - _genericAttributeService.SaveAttribute(row.Entity.Id, key, _attributeKeyGroup, row.GetDataValue(key)); + + SaveAttribute(row, key, row.GetDataValue(key)); } - private void UpsertRole(ImportRow row, CustomerRole role, bool value) + private void SaveAttribute(ImportRow row, string key) { - if (role != null) + + SaveAttribute(row, key, row.GetDataValue(key)); + } + + private void SaveAttribute(ImportRow row, string key, TPropType value) + { + if (row.IsTransient) + return; + + if (row.IsNew || value!= null) { - var hasRole = row.Entity.CustomerRoles.Any(x => x.SystemName == role.SystemName); + _genericAttributeService.SaveAttribute(row.Entity.Id, key, _attributeKeyGroup, value); + } + } + + private void UpsertRole(ImportRow row, CustomerRole role, bool value) + { + if (role == null) + return; - if (value && !hasRole) - row.Entity.CustomerRoles.Add(role); - else if (!value && hasRole) - row.Entity.CustomerRoles.Remove(role); + var hasRole = row.Entity.CustomerRoles.Any(x => x.SystemName == role.SystemName); + + if (value && !hasRole) + { + row.Entity.CustomerRoles.Add(role); + } + else if (!value && hasRole) + { + row.Entity.CustomerRoles.Remove(role); } } - private void ImportAvatar(IImportExecuteContext context, ImportRow row) + protected virtual void ProcessAvatar(IImportExecuteContext context, ImportRow row) { var urlOrPath = row.GetDataValue("AvatarPictureUrl"); if (urlOrPath.IsEmpty()) @@ -147,8 +169,10 @@ private void ImportAvatar(IImportExecuteContext context, ImportRow row { var currentPictureId = row.Entity.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId); if (currentPictureId != 0 && (picture = _pictureRepository.GetById(currentPictureId)) != null) + { currentPictures.Add(picture); - + } + pictureBinary = _pictureService.ValidatePicture(pictureBinary); pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); @@ -157,8 +181,7 @@ private void ImportAvatar(IImportExecuteContext context, ImportRow row if ((picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false)) != null) { _pictureRepository.Context.SaveChanges(); - - _genericAttributeService.SaveAttribute(row.Entity.Id, SystemCustomerAttributeNames.AvatarPictureId, _attributeKeyGroup, picture.Id.ToString()); + SaveAttribute(row, SystemCustomerAttributeNames.AvatarPictureId, picture.Id); } } else @@ -173,7 +196,7 @@ private void ImportAvatar(IImportExecuteContext context, ImportRow row } } - private void ProcessAddresses( + protected virtual int ProcessAddresses( IImportExecuteContext context, IEnumerable> batch, Dictionary allCountries, @@ -184,6 +207,8 @@ private void ProcessAddresses( ImportAddress("BillingAddress.", row, context, allCountries, allStateProvinces); ImportAddress("ShippingAddress.", row, context, allCountries, allStateProvinces); } + + return _services.DbContext.SaveChanges(); } private void ImportAddress( @@ -240,15 +265,14 @@ private void ImportAddress( } _customerService.UpdateCustomer(row.Entity); - - _services.DbContext.SaveChanges(); } - private void ProcessGenericAttributes(IImportExecuteContext context, + protected virtual int ProcessGenericAttributes( + IImportExecuteContext context, IEnumerable> batch, Dictionary allCountries, Dictionary, int> allStateProvinces, - List allCustomerNumbers) + HashSet allCustomerNumbers) { foreach (var row in batch) { @@ -262,7 +286,7 @@ private void ProcessGenericAttributes(IImportExecuteContext context, SaveAttribute(row, SystemCustomerAttributeNames.Gender); if (_customerSettings.DateOfBirthEnabled) - SaveAttribute(row, SystemCustomerAttributeNames.DateOfBirth); + SaveAttribute(row, SystemCustomerAttributeNames.DateOfBirth); if (_customerSettings.CompanyEnabled) SaveAttribute(row, SystemCustomerAttributeNames.Company); @@ -280,10 +304,10 @@ private void ProcessGenericAttributes(IImportExecuteContext context, SaveAttribute(row, SystemCustomerAttributeNames.City); if (_customerSettings.CountryEnabled) - SaveAttribute(row, SystemCustomerAttributeNames.CountryId); + SaveAttribute(row, SystemCustomerAttributeNames.CountryId); if (_customerSettings.CountryEnabled && _customerSettings.StateProvinceEnabled) - SaveAttribute(row, SystemCustomerAttributeNames.StateProvinceId); + SaveAttribute(row, SystemCustomerAttributeNames.StateProvinceId); if (_customerSettings.PhoneEnabled) SaveAttribute(row, SystemCustomerAttributeNames.Phone); @@ -292,7 +316,7 @@ private void ProcessGenericAttributes(IImportExecuteContext context, SaveAttribute(row, SystemCustomerAttributeNames.Fax); if (_forumSettings.ForumsEnabled) - SaveAttribute(row, SystemCustomerAttributeNames.ForumPostCount); + SaveAttribute(row, SystemCustomerAttributeNames.ForumPostCount); if (_forumSettings.SignaturesEnabled) SaveAttribute(row, SystemCustomerAttributeNames.Signature); @@ -302,12 +326,12 @@ private void ProcessGenericAttributes(IImportExecuteContext context, if (countryId.HasValue) { - _genericAttributeService.SaveAttribute(row.Entity.Id, SystemCustomerAttributeNames.CountryId, _attributeKeyGroup, countryId.Value); + SaveAttribute(row, SystemCustomerAttributeNames.CountryId, countryId.Value); } if (stateId.HasValue) { - _genericAttributeService.SaveAttribute(row.Entity.Id, SystemCustomerAttributeNames.StateProvinceId, _attributeKeyGroup, stateId.Value); + SaveAttribute(row, SystemCustomerAttributeNames.StateProvinceId, stateId.Value); } string customerNumber = null; @@ -317,9 +341,9 @@ private void ProcessGenericAttributes(IImportExecuteContext context, else customerNumber = row.GetDataValue("CustomerNumber"); - if (customerNumber.IsEmpty() || !allCustomerNumbers.Any(x => x.IsCaseInsensitiveEqual(customerNumber))) + if (customerNumber.IsEmpty() || !allCustomerNumbers.Contains(customerNumber)) { - _genericAttributeService.SaveAttribute(row.Entity.Id, SystemCustomerAttributeNames.CustomerNumber, _attributeKeyGroup, customerNumber); + SaveAttribute(row, SystemCustomerAttributeNames.CustomerNumber, customerNumber); if (!customerNumber.IsEmpty()) allCustomerNumbers.Add(customerNumber); @@ -327,14 +351,14 @@ private void ProcessGenericAttributes(IImportExecuteContext context, if (_customerSettings.AllowCustomersToUploadAvatars) { - ImportAvatar(context, row); + ProcessAvatar(context, row); } - - _services.DbContext.SaveChanges(); } + + return _services.DbContext.SaveChanges(); } - private int ProcessCustomers( + protected virtual int ProcessCustomers( IImportExecuteContext context, IEnumerable> batch, List allAffiliateIds) @@ -351,13 +375,6 @@ private int ProcessCustomers( foreach (var row in batch) { - if (row.GetDataValue("IsSystemAccount")) - { - ++context.Result.SkippedRecords; - context.Result.AddInfo("Skipped system account.", row.GetRowInfo(), "IsSystemAccount"); - continue; - } - Customer customer = null; var id = row.GetDataValue("Id"); var email = row.GetDataValue("Email"); @@ -398,7 +415,7 @@ private int ProcessCustomers( { CustomerGuid = new Guid(), AffiliateId = 0, - Active = true + Active = true }; } else @@ -432,11 +449,6 @@ private int ProcessCustomers( row.SetProperty(context.Result, (x) => x.AdminComment); row.SetProperty(context.Result, (x) => x.IsTaxExempt); row.SetProperty(context.Result, (x) => x.Active); - row.SetProperty(context.Result, (x) => x.IsSystemAccount); - row.SetProperty(context.Result, (x) => x.SystemName); - row.SetProperty(context.Result, (x) => x.LastIpAddress); - row.SetProperty(context.Result, (x) => x.LastLoginDateUtc); - row.SetProperty(context.Result, (x) => x.LastActivityDateUtc); row.SetProperty(context.Result, (x) => x.CreatedOnUtc, UtcNow); row.SetProperty(context.Result, (x) => x.LastActivityDateUtc, UtcNow); @@ -520,9 +532,9 @@ protected override void Import(IImportExecuteContext context) var allStateProvinces = _stateProvinceService.GetAllStateProvinces(true) .ToDictionarySafe(x => new Tuple(x.CountryId, x.Abbreviation), x => x.Id); - var allCustomerNumbers = _genericAttributeService.GetAttributes(SystemCustomerAttributeNames.CustomerNumber, _attributeKeyGroup) - .Select(x => x.Value) - .ToList(); + var allCustomerNumbers = new HashSet( + _genericAttributeService.GetAttributes(SystemCustomerAttributeNames.CustomerNumber, _attributeKeyGroup).Select(x => x.Value), + StringComparer.OrdinalIgnoreCase); using (var scope = new DbContextScope(ctx: _services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) { @@ -565,17 +577,12 @@ protected override void Import(IImportExecuteContext context) // =========================================================================== try { - _services.DbContext.AutoDetectChangesEnabled = true; ProcessGenericAttributes(context, batch, allCountries, allStateProvinces, allCustomerNumbers); } catch (Exception exception) { context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessGenericAttributes"); } - finally - { - _services.DbContext.AutoDetectChangesEnabled = false; - } // =========================================================================== // Process addresses @@ -584,17 +591,12 @@ protected override void Import(IImportExecuteContext context) { try { - _services.DbContext.AutoDetectChangesEnabled = true; ProcessAddresses(context, batch, allCountries, allStateProvinces); } catch (Exception exception) { context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessAddresses"); } - finally - { - _services.DbContext.AutoDetectChangesEnabled = false; - } } } } From 3a809aab6ac5b9a5ae27ffcbbdf6f72e644b9bed Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 7 May 2016 01:53:34 +0200 Subject: [PATCH 012/185] Minor change in SwapColumnMappingValues migration --- .../Migrations/201605061916117_SwapColumnMappingValues.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs index 3db5f58fdc..d283753b37 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs @@ -25,9 +25,9 @@ public bool RollbackOnFailure public void Seed(SmartObjectContext context) { - var importProfiles = context.Set().ToList(); + var importProfiles = context.Set().Where(x => x.ColumnMapping.HasValue()).ToList(); - foreach (var profile in importProfiles.Where(x => x.ColumnMapping.HasValue())) + foreach (var profile in importProfiles) { var dic = new Dictionary>(); var storeMapping = true; From 6703cd49478d234c0ca288e66c9f9a07d8374a7f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 7 May 2016 10:29:06 +0200 Subject: [PATCH 013/185] AddMapping needs no validation anymore. One source column mapped to multiple object properties is a valid scenario. --- ...201605061916117_SwapColumnMappingValues.cs | 9 ++++++- .../Catalog/Importer/ProductImporter.cs | 1 - .../Import/ColumnMapping/ColumnMap.cs | 24 +++---------------- .../Controllers/ImportController.cs | 16 ++----------- 4 files changed, 13 insertions(+), 37 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs index d283753b37..5c3e2a8c90 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs @@ -8,7 +8,7 @@ namespace SmartStore.Data.Migrations using Newtonsoft.Json.Linq; using Setup; - public partial class SwapColumnMappingValues : DbMigration, IDataSeeder + public partial class SwapColumnMappingValues : DbMigration, ILocaleResourcesProvider, IDataSeeder { public override void Up() { @@ -25,6 +25,8 @@ public bool RollbackOnFailure public void Seed(SmartObjectContext context) { + context.MigrateLocaleResources(MigrateLocaleResources); + var importProfiles = context.Set().Where(x => x.ColumnMapping.HasValue()).ToList(); foreach (var profile in importProfiles) @@ -86,5 +88,10 @@ public void Seed(SmartObjectContext context) context.SaveChanges(); } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.Delete("Admin.DataExchange.ColumnMapping.Validate.MultipleMappedIgnored"); + } } } diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 1ae3b4cc5e..645c8f10a6 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -390,7 +390,6 @@ protected virtual int ProcessSlugs(IImportExecuteContext context, IEnumerable(); var languageMap = context.Languages.ToDictionarySafe(x => x.UniqueSeoCode); - Language language = null; UrlRecord urlRecord = null; Func slugLookup = ((s) => diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs index 31d61bd3cd..969ea172f4 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Newtonsoft.Json; namespace SmartStore.Services.DataExchange.Import @@ -56,38 +55,21 @@ public static bool ParseSourceName(string sourceName, out string nameWithoutInde return result; } - //public IEnumerable> GetInvalidMappings() - //{ - // var mappings = Mappings.Where(x => - // x.Value.Property.HasValue() && - // Mappings.Count(y => y.Value.Property.IsCaseInsensitiveEqual(x.Value.Property)) > 1 - // ); - - // return mappings; - //} - - public bool AddMapping(string sourceName, string mappedName, string defaultValue = null) + public void AddMapping(string sourceName, string mappedName, string defaultValue = null) { - return AddMapping(sourceName, null, mappedName, defaultValue); + AddMapping(sourceName, null, mappedName, defaultValue); } - public bool AddMapping(string sourceName, string index, string mappedName, string defaultValue = null) + public void AddMapping(string sourceName, string index, string mappedName, string defaultValue = null) { Guard.ArgumentNotEmpty(() => sourceName); Guard.ArgumentNotEmpty(() => mappedName); - var isAlreadyMapped = (mappedName.HasValue() && _map.Any(x => x.Value.MappedName.IsCaseInsensitiveEqual(mappedName))); - - if (isAlreadyMapped) - return false; - _map[CreateSourceName(sourceName, index)] = new ColumnMappingValue { MappedName = mappedName, Default = defaultValue }; - - return true; } /// diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index d7e46cd009..e7b0c74bc1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -419,7 +419,6 @@ public ActionResult Edit(ImportProfileModel model, bool continueEditing, FormCol if (profile == null) return RedirectToAction("List"); - var multipleMapped = new List(); var map = new ColumnMap(); var hasErrors = false; var resetMappings = false; @@ -439,25 +438,18 @@ public ActionResult Edit(ImportProfileModel model, bool continueEditing, FormCol var property = form[key]; var column = form["ColumnMapping.Column." + index]; var defaultValue = form["ColumnMapping.Default." + index]; - var result = true; if (column.IsEmpty()) { // tell mapper to explicitly ignore the property - result = map.AddMapping(property, null, property, "[IGNOREPROPERTY]"); + map.AddMapping(property, null, property, "[IGNOREPROPERTY]"); } else if (!column.IsCaseInsensitiveEqual(property) || defaultValue.HasValue()) { if (defaultValue.HasValue() && GetDisabledDefaultFieldNames(profile).Contains(property)) defaultValue = null; - result = map.AddMapping(property, null, column, defaultValue); - } - - if (!result) - { - // add warning for ignored, multiple mapped properties - multipleMapped.Add("{0} ({1})".FormatInvariant(entityProperties.ContainsKey(property) ? entityProperties[property] : "".NaIfEmpty(), property)); + map.AddMapping(property, null, column, defaultValue); } } } @@ -527,10 +519,6 @@ public ActionResult Edit(ImportProfileModel model, bool continueEditing, FormCol { NotifyWarning(T("Admin.DataExchange.ColumnMapping.Validate.MappingsReset")); } - else if (multipleMapped.Any()) - { - NotifyWarning(T("Admin.DataExchange.ColumnMapping.Validate.MultipleMappedIgnored", "

" + string.Join("
", multipleMapped) + "

")); - } } return (continueEditing ? RedirectToAction("Edit", new { id = profile.Id }) : RedirectToAction("List")); From 6a5f0fe313cd193b1a062984a07ca8fe3cf6878d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 7 May 2016 11:26:31 +0200 Subject: [PATCH 014/185] Changed commit "Minor change in SwapColumnMappingValues migration". It produced an exception "LINQ to Entities does not recognize the method 'Boolean HasValue(System.String)' method...". --- ...201605061916117_SwapColumnMappingValues.cs | 72 ++++++++++--------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs index 5c3e2a8c90..f16431d45b 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs @@ -27,54 +27,62 @@ public void Seed(SmartObjectContext context) { context.MigrateLocaleResources(MigrateLocaleResources); - var importProfiles = context.Set().Where(x => x.ColumnMapping.HasValue()).ToList(); + var importProfiles = context.Set().Where(x => x.ColumnMapping != null).ToList(); foreach (var profile in importProfiles) { var dic = new Dictionary>(); var storeMapping = true; - var json = JObject.Parse(profile.ColumnMapping); - - foreach (var kvp in json) + try { - dynamic value = kvp.Value; - - var mappedName = (string)value.MappedName; - var property = (string)value.Property; - var defaultValue = (string)value.Default; + var json = JObject.Parse(profile.ColumnMapping); - if (mappedName.HasValue()) + foreach (var kvp in json) { - // break migration because data is already migrated - storeMapping = false; - break; - } - else if (property.HasValue()) - { - if (!kvp.Key.IsCaseInsensitiveEqual(property) || defaultValue.HasValue()) + dynamic value = kvp.Value; + + var mappedName = (string)value.MappedName; + var property = (string)value.Property; + var defaultValue = (string)value.Default; + + if (mappedName.HasValue()) { - // swap value - dic.Add(property, new Dictionary + // break migration because data is already migrated + storeMapping = false; + break; + } + else if (property.HasValue()) + { + if (!kvp.Key.IsCaseInsensitiveEqual(property) || defaultValue.HasValue()) { - { "MappedName", kvp.Key }, - { "Default", defaultValue } - }); + // swap value + dic.Add(property, new Dictionary + { + { "MappedName", kvp.Key }, + { "Default", defaultValue } + }); + } + else + { + // ignore because persisting not required anymore + } } else { - // ignore because persisting not required anymore + // explicitly ignored property + dic.Add(property, new Dictionary + { + { "MappedName", property }, + { "Default", "[IGNOREPROPERTY]" } + }); } } - else - { - // explicitly ignored property - dic.Add(property, new Dictionary - { - { "MappedName", property }, - { "Default", "[IGNOREPROPERTY]" } - }); - } + } + catch + { + storeMapping = true; + dic.Clear(); } if (storeMapping) From de324ffbd35d3b275492e1c7e435a366b333cbde Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 7 May 2016 19:25:40 +0200 Subject: [PATCH 015/185] Resolves #955 Import: CustomerImporter > import avatar in a separate batch --- .../Customers/Importer/CustomerImporter.cs | 134 ++++++++++-------- 1 file changed, 75 insertions(+), 59 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index dee6721500..e377c39117 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Linq.Expressions; using SmartStore.Core.Async; using SmartStore.Core.Data; using SmartStore.Core.Domain.Common; @@ -140,62 +139,6 @@ private void UpsertRole(ImportRow row, CustomerRole role, bool value) } } - protected virtual void ProcessAvatar(IImportExecuteContext context, ImportRow row) - { - var urlOrPath = row.GetDataValue("AvatarPictureUrl"); - if (urlOrPath.IsEmpty()) - return; - - Picture picture = null; - var equalPictureId = 0; - var currentPictures = new List(); - var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); - var image = CreateDownloadImage(urlOrPath, seoName, 1); - - if (image == null) - return; - - if (image.Url.HasValue() && !image.Success.HasValue) - { - AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, new FileDownloadManagerItem[] { image })); - } - - if ((image.Success ?? false) && File.Exists(image.Path)) - { - Succeeded(image); - var pictureBinary = File.ReadAllBytes(image.Path); - - if (pictureBinary != null && pictureBinary.Length > 0) - { - var currentPictureId = row.Entity.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId); - if (currentPictureId != 0 && (picture = _pictureRepository.GetById(currentPictureId)) != null) - { - currentPictures.Add(picture); - } - - pictureBinary = _pictureService.ValidatePicture(pictureBinary); - pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); - - if (pictureBinary != null && pictureBinary.Length > 0) - { - if ((picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false)) != null) - { - _pictureRepository.Context.SaveChanges(); - SaveAttribute(row, SystemCustomerAttributeNames.AvatarPictureId, picture.Id); - } - } - else - { - context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "AvatarPictureUrl"); - } - } - } - else - { - context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "AvatarPictureUrl"); - } - } - protected virtual int ProcessAddresses( IImportExecuteContext context, IEnumerable> batch, @@ -348,10 +291,68 @@ protected virtual int ProcessGenericAttributes( if (!customerNumber.IsEmpty()) allCustomerNumbers.Add(customerNumber); } + } + + return _services.DbContext.SaveChanges(); + } + + protected virtual int ProcessAvatars( + IImportExecuteContext context, + IEnumerable> batch) + { + foreach (var row in batch) + { + var urlOrPath = row.GetDataValue("AvatarPictureUrl"); + if (urlOrPath.IsEmpty()) + continue; + + Picture picture = null; + var equalPictureId = 0; + var currentPictures = new List(); + var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); + + var image = CreateDownloadImage(urlOrPath, seoName, 1); + if (image == null) + continue; + + if (image.Url.HasValue() && !image.Success.HasValue) + { + AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, new FileDownloadManagerItem[] { image })); + } - if (_customerSettings.AllowCustomersToUploadAvatars) + if ((image.Success ?? false) && File.Exists(image.Path)) + { + Succeeded(image); + var pictureBinary = File.ReadAllBytes(image.Path); + + if (pictureBinary != null && pictureBinary.Length > 0) + { + var currentPictureId = row.Entity.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId); + if (currentPictureId != 0 && (picture = _pictureRepository.GetById(currentPictureId)) != null) + { + currentPictures.Add(picture); + } + + pictureBinary = _pictureService.ValidatePicture(pictureBinary); + pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); + + if (pictureBinary != null && pictureBinary.Length > 0) + { + if ((picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false)) != null) + { + _pictureRepository.Context.SaveChanges(); + SaveAttribute(row, SystemCustomerAttributeNames.AvatarPictureId, picture.Id); + } + } + else + { + context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "AvatarPictureUrl"); + } + } + } + else { - ProcessAvatar(context, row); + context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "AvatarPictureUrl"); } } @@ -584,6 +585,21 @@ protected override void Import(IImportExecuteContext context) context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessGenericAttributes"); } + // =========================================================================== + // Process avatars + // =========================================================================== + if (_customerSettings.AllowCustomersToUploadAvatars) + { + try + { + ProcessAvatars(context, batch); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessAvatars"); + } + } + // =========================================================================== // Process addresses // =========================================================================== From ca2b4137634f652fb99ab8db83a949da31895d7f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 9 May 2016 16:55:39 +0200 Subject: [PATCH 016/185] Fixed customer address import not functioning. Also resolves #956. --- .../Customers/Importer/CustomerImporter.cs | 63 +++++++++++++------ 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index e377c39117..1e8d37ae20 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -147,8 +147,15 @@ protected virtual int ProcessAddresses( { foreach (var row in batch) { - ImportAddress("BillingAddress.", row, context, allCountries, allStateProvinces); - ImportAddress("ShippingAddress.", row, context, allCountries, allStateProvinces); + if (row.HasDataValue("BillingAddress.LastName")) + { + ImportAddress("BillingAddress.", row, context, allCountries, allStateProvinces); + } + + if (row.HasDataValue("ShippingAddress.LastName")) + { + ImportAddress("ShippingAddress.", row, context, allCountries, allStateProvinces); + } } return _services.DbContext.SaveChanges(); @@ -161,7 +168,7 @@ private void ImportAddress( Dictionary allCountries, Dictionary, int> allStateProvinces) { - // last name is mandatory for an address to be imported + // last name is mandatory for an address to be imported or updated var lastName = row.GetDataValue(fieldPrefix + "LastName"); if (lastName.IsEmpty()) return; @@ -207,7 +214,7 @@ private void ImportAddress( row.Entity.ShippingAddress = appliedAddress; } - _customerService.UpdateCustomer(row.Entity); + _customerRepository.Update(row.Entity); } protected virtual int ProcessGenericAttributes( @@ -374,6 +381,8 @@ protected virtual int ProcessCustomers( var registeredRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered); var forumModeratorRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.ForumModerators); + var customerQuery = _customerRepository.Table.Expand(x => x.Addresses); + foreach (var row in batch) { Customer customer = null; @@ -385,18 +394,31 @@ protected virtual int ProcessCustomers( switch (keyName) { case "Id": - customer = _customerService.GetCustomerById(id); + if (id != 0) + { + customer = customerQuery.FirstOrDefault(x => x.Id == id); + } break; case "CustomerGuid": - var guid = row.GetDataValue("CustomerGuid"); - if (guid.HasValue()) - customer = _customerService.GetCustomerByGuid(new Guid(guid)); + var customerGuid = row.GetDataValue("CustomerGuid"); + if (customerGuid.HasValue()) + { + var guid = new Guid(customerGuid); + customer = customerQuery.FirstOrDefault(x => x.CustomerGuid == guid); + } break; case "Email": - customer = _customerService.GetCustomerByEmail(email); + if (email.HasValue()) + { + customer = customerQuery.FirstOrDefault(x => x.Email == email); + } break; case "Username": - customer = _customerService.GetCustomerByUsername(row.GetDataValue("Username")); + var userName = row.GetDataValue("Username"); + if (userName.HasValue()) + { + customer = customerQuery.FirstOrDefault(x => x.Username == userName); + } break; } @@ -603,16 +625,19 @@ protected override void Import(IImportExecuteContext context) // =========================================================================== // Process addresses // =========================================================================== - if (segmenter.HasColumn("BillingAddress.LastName") || segmenter.HasColumn("ShippingAddress.LastName")) + try { - try - { - ProcessAddresses(context, batch, allCountries, allStateProvinces); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessAddresses"); - } + _services.DbContext.AutoDetectChangesEnabled = true; + + ProcessAddresses(context, batch, allCountries, allStateProvinces); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessAddresses"); + } + finally + { + _services.DbContext.AutoDetectChangesEnabled = false; } } } From 735a2fe79e1258a103ae24231b995ae80db50aa7 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 10 May 2016 01:19:38 +0200 Subject: [PATCH 017/185] Import: minor refactoring in CustomerImporter --- .../Customers/Importer/CustomerImporter.cs | 47 +++++++++++++------ .../DataExchange/Import/ImportRow.cs | 11 +++++ 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 1e8d37ae20..f8bf130bfc 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -101,7 +101,6 @@ public CustomerImporter( private void SaveAttribute(ImportRow row, string key) { - SaveAttribute(row, key, row.GetDataValue(key)); } @@ -169,24 +168,18 @@ private void ImportAddress( Dictionary, int> allStateProvinces) { // last name is mandatory for an address to be imported or updated - var lastName = row.GetDataValue(fieldPrefix + "LastName"); - if (lastName.IsEmpty()) + if (!row.HasDataValue(fieldPrefix + "LastName")) return; - var countryId = CountryCodeToId(allCountries, row.GetDataValue(fieldPrefix + "CountryCode")); - var stateId = StateAbbreviationToId(allStateProvinces, countryId, row.GetDataValue(fieldPrefix + "StateAbbreviation")); - - var importAddress = new Address + var address = new Address { - LastName = lastName, - CreatedOnUtc = UtcNow, - CountryId = countryId, - StateProvinceId = stateId + CreatedOnUtc = UtcNow }; var childRow = new ImportRow
(row.Segmenter, row.DataRow, row.Position); - childRow.Initialize(importAddress, row.EntityDisplayName); + childRow.Initialize(address, row.EntityDisplayName); + childRow.SetProperty(context.Result, fieldPrefix + "LastName", x => x.LastName); childRow.SetProperty(context.Result, fieldPrefix + "FirstName", x => x.FirstName); childRow.SetProperty(context.Result, fieldPrefix + "Email", x => x.Email); childRow.SetProperty(context.Result, fieldPrefix + "Company", x => x.Company); @@ -197,11 +190,36 @@ private void ImportAddress( childRow.SetProperty(context.Result, fieldPrefix + "PhoneNumber", x => x.PhoneNumber); childRow.SetProperty(context.Result, fieldPrefix + "FaxNumber", x => x.FaxNumber); - var appliedAddress = row.Entity.Addresses.FindAddress(importAddress); + childRow.SetProperty(context.Result, fieldPrefix + "CountryId", x => x.CountryId); + if (childRow.Entity.CountryId == null) + { + // try with country code + childRow.SetProperty(context.Result, fieldPrefix + "CountryCode", x => x.CountryId, converter: (val, ci) => CountryCodeToId(allCountries, val.ToString())); + } + + var countryId = childRow.Entity.CountryId; + + if (countryId.HasValue) + { + childRow.SetProperty(context.Result, fieldPrefix + "StateProvinceId", x => x.StateProvinceId); + if (childRow.Entity.StateProvinceId == null) + { + // try with state abbreviation + childRow.SetProperty(context.Result, fieldPrefix + "StateAbbreviation", x => x.StateProvinceId, converter: (val, ci) => StateAbbreviationToId(allStateProvinces, countryId, val.ToString())); + } + } + + if (!childRow.IsDirty) + { + // Not one single property could be set. Get out! + return; + } + + var appliedAddress = row.Entity.Addresses.FindAddress(address); if (appliedAddress == null) { - appliedAddress = importAddress; + appliedAddress = address; row.Entity.Addresses.Add(appliedAddress); } @@ -628,7 +646,6 @@ protected override void Import(IImportExecuteContext context) try { _services.DbContext.AutoDetectChangesEnabled = true; - ProcessAddresses(context, batch, allCountries, allStateProvinces); } catch (Exception exception) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs index 99982e7a6d..0c3a250d64 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -13,6 +13,7 @@ public class ImportRow where T : BaseEntity private string _entityDisplayName; private readonly int _position; private bool _isNew; + private bool _isDirty; private ImportRowInfo _rowInfo; private readonly ImportDataSegmenter _segmenter; @@ -73,6 +74,11 @@ public bool IsNew get { return _isNew; } } + public bool IsDirty + { + get { return _isDirty; } + } + public ImportDataSegmenter Segmenter { get { return _segmenter; } @@ -267,6 +273,11 @@ public bool SetProperty( result.AddWarning("Conversion failed: " + exception.Message, this.GetRowInfo(), propName); } + if (isPropertySet && !_isDirty) + { + _isDirty = true; + } + return isPropertySet; } From c11d804ce8c4ec8e02c2859e02227cb2f07a9f86 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 10 May 2016 01:24:37 +0200 Subject: [PATCH 018/185] Minor fix for last commit --- .../Customers/Importer/CustomerImporter.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index f8bf130bfc..8f9a92b380 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -146,15 +146,8 @@ protected virtual int ProcessAddresses( { foreach (var row in batch) { - if (row.HasDataValue("BillingAddress.LastName")) - { - ImportAddress("BillingAddress.", row, context, allCountries, allStateProvinces); - } - - if (row.HasDataValue("ShippingAddress.LastName")) - { - ImportAddress("ShippingAddress.", row, context, allCountries, allStateProvinces); - } + ImportAddress("BillingAddress.", row, context, allCountries, allStateProvinces); + ImportAddress("ShippingAddress.", row, context, allCountries, allStateProvinces); } return _services.DbContext.SaveChanges(); From f5ae81ef9678b66d1b184e8808b9f2207af615e4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 10 May 2016 15:35:14 +0200 Subject: [PATCH 019/185] Resolves #947 Import: Explicitly ignoring of properties only works if the property is processed through SetProperty --- .../Catalog/Importer/CategoryImporter.cs | 72 +++++++++-------- .../Catalog/Importer/ProductImporter.cs | 77 +++++++++++-------- .../Import/ImportDataSegmenter.cs | 24 +++++- .../DataExchange/Import/ImportRow.cs | 33 ++++++++ .../NewsLetterSubscriptionImporter.cs | 13 +++- 5 files changed, 149 insertions(+), 70 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index d01fafaf97..2ef0cb3480 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -8,6 +8,7 @@ using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Seo; +using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; using SmartStore.Services.DataExchange.Import; using SmartStore.Services.Localization; @@ -15,7 +16,6 @@ using SmartStore.Services.Seo; using SmartStore.Services.Stores; using SmartStore.Utilities; -using SmartStore.Core.Domain.Stores; namespace SmartStore.Services.Catalog.Importer { @@ -69,6 +69,7 @@ protected virtual int ProcessSlugs(IImportExecuteContext context, IEnumerable(); + UrlRecord urlRecord = null; Func slugLookup = ((s) => { @@ -77,43 +78,44 @@ protected virtual int ProcessSlugs(IImportExecuteContext context, IEnumerable("SeName"); - seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); - - if (row.IsNew) + string seName; + if (row.GetDataValue("SeName", out seName, row.IsNew)) { - // dont't bother validating SeName for new entities. - urlRecord = new UrlRecord + if (row.IsNew || row.NameChanged || seName.HasValue()) { - EntityId = row.Entity.Id, - EntityName = entityName, - Slug = seName, - LanguageId = 0, - IsActive = true, - }; - _urlRecordRepository.Insert(urlRecord); - } - else - { - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); - } + seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); - if (urlRecord != null) - { - // a new record was inserted to the store: keep track of it for this batch. - slugMap[seName] = urlRecord; + if (row.IsNew) + { + // dont't bother validating SeName for new entities. + urlRecord = new UrlRecord + { + EntityId = row.Entity.Id, + EntityName = entityName, + Slug = seName, + LanguageId = 0, + IsActive = true, + }; + _urlRecordRepository.Insert(urlRecord); + } + else + { + urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); + } + + if (urlRecord != null) + { + // a new record was inserted to the store: keep track of it for this batch. + slugMap[seName] = urlRecord; + } + } } foreach (var lang in context.Languages) { - seName = row.GetDataValue("SeName", lang.UniqueSeoCode); - if (seName.HasValue()) + if (row.GetDataValue("SeName", lang.UniqueSeoCode, out seName) && seName.HasValue()) { seName = row.Entity.ValidateSeName(seName, null, false, _urlRecordService, _seoSettings, lang.Id, slugLookup); @@ -142,6 +144,7 @@ protected virtual int ProcessLocalizations(IImportExecuteContext context, IEnume { foreach (var lang in context.Languages) { + // TODO: ignored properties var name = row.GetDataValue("Name", lang.UniqueSeoCode); var fullName = row.GetDataValue("FullName", lang.UniqueSeoCode); var description = row.GetDataValue("Description", lang.UniqueSeoCode); @@ -373,8 +376,11 @@ protected virtual int ProcessCategories( // it has to be set to true by code if it's absent but "StoreIds" exists. row.SetProperty(context.Result, (x) => x.LimitedToStores, !row.GetDataValue>("StoreIds").IsNullOrEmpty()); - var tvp = row.GetDataValue("CategoryTemplateViewPath"); - category.CategoryTemplateId = (tvp.HasValue() && templateViewPaths.ContainsKey(tvp) ? templateViewPaths[tvp] : defaultTemplateId); + string tvp; + if (row.GetDataValue("CategoryTemplateViewPath", out tvp, row.IsTransient)) + { + category.CategoryTemplateId = (tvp.HasValue() && templateViewPaths.ContainsKey(tvp) ? templateViewPaths[tvp] : defaultTemplateId); + } row.SetProperty(context.Result, (x) => x.CreatedOnUtc, UtcNow); category.UpdatedOnUtc = UtcNow; @@ -520,7 +526,7 @@ protected override void Import(IImportExecuteContext context) } // process pictures - if (srcToDestId.Any() && segmenter.HasColumn("ImageUrl")) + if (srcToDestId.Any() && segmenter.HasColumn("ImageUrl") && !segmenter.IsIgnored("PictureId")) { try { @@ -539,7 +545,7 @@ protected override void Import(IImportExecuteContext context) } // map parent id of inserted categories - if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentCategoryId")) + if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentCategoryId") && !segmenter.IsIgnored("ParentCategoryId")) { segmenter.Reset(); diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 645c8f10a6..25c2b0a52c 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -9,6 +9,7 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Seo; +using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; using SmartStore.Services.DataExchange.Import; using SmartStore.Services.Localization; @@ -16,8 +17,6 @@ using SmartStore.Services.Seo; using SmartStore.Services.Stores; using SmartStore.Utilities; -using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Domain.Localization; namespace SmartStore.Services.Catalog.Importer { @@ -366,8 +365,9 @@ protected virtual int ProcessLocalizations( foreach (var lang in context.Languages) { var code = lang.UniqueSeoCode; - var value = row.GetDataValue(prop /* ColumnName */, code); - if (value.HasValue()) + string value; + + if (row.GetDataValue(prop /* ColumnName */, code, out value) && value.HasValue()) { _localizedEntityService.SaveLocalizedValue(row.Entity, lambda, value, lang.Id); shouldSave = true; @@ -385,12 +385,15 @@ protected virtual int ProcessLocalizations( return 0; } - protected virtual int ProcessSlugs(IImportExecuteContext context, IEnumerable> batch, string[] columnIndexes) + protected virtual int ProcessSlugs( + IImportExecuteContext context, + IEnumerable> batch, + string[] columnIndexes) { var entityName = typeof(Product).Name; var slugMap = new Dictionary(); - var languageMap = context.Languages.ToDictionarySafe(x => x.UniqueSeoCode); - UrlRecord urlRecord = null; + //var languageMap = context.Languages.ToDictionarySafe(x => x.UniqueSeoCode); + UrlRecord urlRecord = null; Func slugLookup = ((s) => { @@ -401,34 +404,36 @@ protected virtual int ProcessSlugs(IImportExecuteContext context, IEnumerable("SeName"); - - if (row.IsNew || row.NameChanged || seName.HasValue()) + string seName; + if (row.GetDataValue("SeName", out seName, row.IsNew)) { - seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); - - if (row.IsNew) + if (row.IsNew || row.NameChanged || seName.HasValue()) { - // dont't bother validating SeName for new entities. - urlRecord = new UrlRecord + seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); + + if (row.IsNew) { - EntityId = row.Entity.Id, - EntityName = entityName, - Slug = seName, - LanguageId = 0, - IsActive = true, - }; - _urlRecordRepository.Insert(urlRecord); - } - else - { - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); - } + // dont't bother validating SeName for new entities. + urlRecord = new UrlRecord + { + EntityId = row.Entity.Id, + EntityName = entityName, + Slug = seName, + LanguageId = 0, + IsActive = true, + }; + _urlRecordRepository.Insert(urlRecord); + } + else + { + urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); + } - if (urlRecord != null) - { - // a new record was inserted to the store: keep track of it for this batch. - slugMap[seName] = urlRecord; + if (urlRecord != null) + { + // a new record was inserted to the store: keep track of it for this batch. + slugMap[seName] = urlRecord; + } } } @@ -649,8 +654,11 @@ protected virtual int ProcessProducts( // it has to be set to true by code if it's absent but "StoreIds" exists. row.SetProperty(context.Result, (x) => x.LimitedToStores, !row.GetDataValue>("StoreIds").IsNullOrEmpty()); - var tvp = row.GetDataValue("ProductTemplateViewPath"); - product.ProductTemplateId = (tvp.HasValue() && templateViewPaths.ContainsKey(tvp) ? templateViewPaths[tvp] : defaultTemplateId); + string tvp; + if (row.GetDataValue("ProductTemplateViewPath", out tvp, row.IsTransient)) + { + product.ProductTemplateId = (tvp.HasValue() && templateViewPaths.ContainsKey(tvp) ? templateViewPaths[tvp] : defaultTemplateId); + } row.SetProperty(context.Result, (x) => x.CreatedOnUtc, UtcNow); product.UpdatedOnUtc = UtcNow; @@ -782,6 +790,7 @@ protected override void Import(IImportExecuteContext context) try { _productRepository.Context.AutoDetectChangesEnabled = true; + ProcessSlugs(context, batch, segmenter.GetColumnIndexes("Name")); } catch (Exception exception) @@ -870,7 +879,7 @@ protected override void Import(IImportExecuteContext context) // =========================================================================== // 8.) Map parent id of inserted products // =========================================================================== - if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentGroupedProductId")) + if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentGroupedProductId") && !segmenter.IsIgnored("ParentGroupedProductId")) { segmenter.Reset(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs index 9446847257..d4a1fc01d6 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Data; using System.Globalization; -using System.IO; using System.Linq; using SmartStore.Core; @@ -128,6 +127,29 @@ public bool HasColumn(string name, string index) return _table.HasColumn(_columnMap.GetMapping(name, index).MappedName); } + /// + /// Indicates whether to ignore the property that is mapped to columnName + /// + /// The name of the column + /// true ignore, false do not ignore + public bool IsIgnored(string columnName) + { + return IsIgnored(columnName, null); + } + + /// + /// Indicates whether to ignore the property that is mapped to columnName + /// + /// The name of the column + /// The index of the column + /// true ignore, false do not ignore + public bool IsIgnored(string columnName, string index) + { + var mapping = _columnMap.GetMapping(columnName, index); + + return mapping.IgnoreProperty; + } + /// /// Returns an array of exisiting index names for a column /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs index 0c3a250d64..2aa6c65f71 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -185,6 +185,39 @@ public TProp GetDataValue(string columnName, string index) return default(TProp); } + public bool GetDataValue(string columnName, out TProp value, bool force = false) + { + return GetDataValue(columnName, null, out value, force); + } + + public bool GetDataValue(string columnName, string index, out TProp value, bool force = false) + { + object obj; + var mapping = _segmenter.ColumnMap.GetMapping(columnName, index); + + if (!force && mapping.IgnoreProperty) + { + value = default(TProp); + return false; + } + + if (_row.TryGetValue(mapping.MappedName, out obj) && obj != null && obj != DBNull.Value) + { + value = obj.Convert(_segmenter.Culture); + return true; + } + + if (IsNew) + { + // only transient/new entities should fallback to possible defaults. + value = GetDefaultValue(mapping, columnName, default(TProp)); + return true; + } + + value = default(TProp); + return true; + } + public bool SetProperty( ImportResult result, Expression> prop, diff --git a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs index cb454e8232..5018cf8f4e 100644 --- a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs +++ b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using SmartStore.Core.Data; using SmartStore.Core.Domain.DataExchange; @@ -61,12 +60,22 @@ public void Execute(IImportExecuteContext context) { try { + var active = true; var email = row.GetDataValue("Email"); - var active = row.HasDataValue("Active") ? row.GetDataValue("Active") : true; var storeId = row.GetDataValue("StoreId"); if (storeId == 0) + { storeId = currentStoreId; + } + + if (row.HasDataValue("Active") && row.GetDataValue("Active", out active)) + { + } + else + { + active = true; // default + } if (email.IsEmpty()) { From 69702e09bfd0e1679c4d4e72da9aec5568a1ff20 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 10 May 2016 17:48:32 +0200 Subject: [PATCH 020/185] Importer: Coding conventions > renamed bool GetDataValue(string, out TProp) to TryGetDataValue() --- .../Catalog/Importer/CategoryImporter.cs | 6 ++--- .../Catalog/Importer/ProductImporter.cs | 6 ++--- .../Import/ColumnMapping/ColumnMap.cs | 25 +++++++++++++------ .../ColumnMapping/ColumnMapConverter.cs | 2 +- .../DataExchange/Import/ImportRow.cs | 16 ++++++------ .../NewsLetterSubscriptionImporter.cs | 2 +- 6 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 2ef0cb3480..36df38dd7f 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -81,7 +81,7 @@ protected virtual int ProcessSlugs(IImportExecuteContext context, IEnumerable x.LimitedToStores, !row.GetDataValue>("StoreIds").IsNullOrEmpty()); string tvp; - if (row.GetDataValue("CategoryTemplateViewPath", out tvp, row.IsTransient)) + if (row.TryGetDataValue("CategoryTemplateViewPath", out tvp, row.IsTransient)) { category.CategoryTemplateId = (tvp.HasValue() && templateViewPaths.ContainsKey(tvp) ? templateViewPaths[tvp] : defaultTemplateId); } diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 25c2b0a52c..5f9c703827 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -367,7 +367,7 @@ protected virtual int ProcessLocalizations( var code = lang.UniqueSeoCode; string value; - if (row.GetDataValue(prop /* ColumnName */, code, out value) && value.HasValue()) + if (row.TryGetDataValue(prop /* ColumnName */, code, out value) && value.HasValue()) { _localizedEntityService.SaveLocalizedValue(row.Entity, lambda, value, lang.Id); shouldSave = true; @@ -405,7 +405,7 @@ protected virtual int ProcessSlugs( try { string seName; - if (row.GetDataValue("SeName", out seName, row.IsNew)) + if (row.TryGetDataValue("SeName", out seName, row.IsNew)) { if (row.IsNew || row.NameChanged || seName.HasValue()) { @@ -655,7 +655,7 @@ protected virtual int ProcessProducts( row.SetProperty(context.Result, (x) => x.LimitedToStores, !row.GetDataValue>("StoreIds").IsNullOrEmpty()); string tvp; - if (row.GetDataValue("ProductTemplateViewPath", out tvp, row.IsTransient)) + if (row.TryGetDataValue("ProductTemplateViewPath", out tvp, row.IsTransient)) { product.ProductTemplateId = (tvp.HasValue() && templateViewPaths.ContainsKey(tvp) ? templateViewPaths[tvp] : defaultTemplateId); } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs index 969ea172f4..d0d0df1d82 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs @@ -7,7 +7,7 @@ namespace SmartStore.Services.DataExchange.Import public class ColumnMap { // maps source column to property - private readonly Dictionary _map = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _map = new Dictionary(StringComparer.OrdinalIgnoreCase); private static bool IsIndexed(string name) { @@ -24,7 +24,7 @@ private static string CreateSourceName(string name, string index) return name; } - public IReadOnlyDictionary Mappings + public IReadOnlyDictionary Mappings { get { return _map; } } @@ -65,8 +65,11 @@ public void AddMapping(string sourceName, string index, string mappedName, strin Guard.ArgumentNotEmpty(() => sourceName); Guard.ArgumentNotEmpty(() => mappedName); - _map[CreateSourceName(sourceName, index)] = new ColumnMappingValue + var key = CreateSourceName(sourceName, index); + + _map[key] = new ColumnMappingItem { + SoureName = key, MappedName = mappedName, Default = defaultValue }; @@ -78,7 +81,7 @@ public void AddMapping(string sourceName, string index, string mappedName, strin /// The name of the column to get a mapped value for. /// The column index, e.g. a language code (de, en etc.) /// The mapped column value OR - if the name is unmapped - a value with the passed [] - public ColumnMappingValue GetMapping(string sourceName, string index) + public ColumnMappingItem GetMapping(string sourceName, string index) { return GetMapping(CreateSourceName(sourceName, index)); } @@ -88,23 +91,29 @@ public ColumnMappingValue GetMapping(string sourceName, string index) ///
/// The name of the column to get a mapped value for. /// The mapped column value OR - if the name is unmapped - the value of the passed - public ColumnMappingValue GetMapping(string sourceName) + public ColumnMappingItem GetMapping(string sourceName) { - ColumnMappingValue result; + ColumnMappingItem result; if (_map.TryGetValue(sourceName, out result)) { return result; } - return new ColumnMappingValue { MappedName = sourceName }; + return new ColumnMappingItem { SoureName = sourceName, MappedName = sourceName }; } } [JsonObject(MemberSerialization.OptIn)] - public class ColumnMappingValue + public class ColumnMappingItem { + /// + /// The source name + /// + [JsonIgnore] + public string SoureName { get; set; } + /// /// The mapped name /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMapConverter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMapConverter.cs index 8d6d0f8ed7..c46b90b82a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMapConverter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMapConverter.cs @@ -27,7 +27,7 @@ public override object ConvertFrom(CultureInfo culture, object value) { if (value is string) { - var dict = JsonConvert.DeserializeObject>((string)value); + var dict = JsonConvert.DeserializeObject>((string)value); var map = new ColumnMap(); foreach (var kvp in dict) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs index 2aa6c65f71..0b1730083c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -43,7 +43,7 @@ private void CheckInitialized() } } - private TProp GetDefaultValue(ColumnMappingValue mapping, string columnName, TProp defaultValue, ImportResult result = null) + private TProp GetDefaultValue(ColumnMappingItem mapping, TProp defaultValue, ImportResult result = null) { if (mapping != null && mapping.Default.HasValue()) { @@ -56,7 +56,7 @@ private TProp GetDefaultValue(ColumnMappingValue mapping, string columnNa if (result != null) { var msg = "Failed to convert default value '{0}'. Please specify a convertable default value. Column: {1}"; - result.AddWarning(msg.FormatInvariant(mapping.Default, exception.Message), this.GetRowInfo(), columnName); + result.AddWarning(msg.FormatInvariant(mapping.Default, exception.Message), this.GetRowInfo(), mapping.SoureName); } } } @@ -179,18 +179,18 @@ public TProp GetDataValue(string columnName, string index) if (IsNew) { // only transient/new entities should fallback to possible defaults. - return GetDefaultValue(mapping, columnName, default(TProp)); + return GetDefaultValue(mapping, default(TProp)); } return default(TProp); } - public bool GetDataValue(string columnName, out TProp value, bool force = false) + public bool TryGetDataValue(string columnName, out TProp value, bool force = false) { - return GetDataValue(columnName, null, out value, force); + return TryGetDataValue(columnName, null, out value, force); } - public bool GetDataValue(string columnName, string index, out TProp value, bool force = false) + public bool TryGetDataValue(string columnName, string index, out TProp value, bool force = false) { object obj; var mapping = _segmenter.ColumnMap.GetMapping(columnName, index); @@ -210,7 +210,7 @@ public bool GetDataValue(string columnName, string index, out TProp value if (IsNew) { // only transient/new entities should fallback to possible defaults. - value = GetDefaultValue(mapping, columnName, default(TProp)); + value = GetDefaultValue(mapping, default(TProp)); return true; } @@ -288,7 +288,7 @@ public bool SetProperty( // if entity is new and source field value is null, determine default value in this particular order: // 2.) Default value in field mapping table // 3.) passed default value argument - defaultValue = GetDefaultValue(mapping, columnName, defaultValue, result); + defaultValue = GetDefaultValue(mapping, defaultValue, result); // source does not contain field data or is empty... if (defaultValue != null) diff --git a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs index 5018cf8f4e..cb89878d6b 100644 --- a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs +++ b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs @@ -69,7 +69,7 @@ public void Execute(IImportExecuteContext context) storeId = currentStoreId; } - if (row.HasDataValue("Active") && row.GetDataValue("Active", out active)) + if (row.HasDataValue("Active") && row.TryGetDataValue("Active", out active)) { } else From a4b8d81d80791e6fd9c134e361313c8dd7a234a7 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 10 May 2016 19:32:31 +0200 Subject: [PATCH 021/185] Resolves #957 Import: the explicit '[NULL]' column value must also be considered in code parts, where ImportRow.SetProperty() cannot be used --- .../DataExchange/Import/ImportRow.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs index 0b1730083c..ff191e5dec 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -8,6 +8,8 @@ namespace SmartStore.Services.DataExchange.Import { public class ImportRow where T : BaseEntity { + private const string _explicitNull = "[NULL]"; + private bool _initialized = false; private T _entity; private string _entityDisplayName; @@ -173,6 +175,11 @@ public TProp GetDataValue(string columnName, string index) if (_row.TryGetValue(mapping.MappedName, out value) && value != null && value != DBNull.Value) { + if (value.ToString().IsCaseInsensitiveEqual(_explicitNull)) + { + return default(TProp); + } + return value.Convert(_segmenter.Culture); } @@ -192,7 +199,7 @@ public bool TryGetDataValue(string columnName, out TProp value, bool forc public bool TryGetDataValue(string columnName, string index, out TProp value, bool force = false) { - object obj; + object rawValue; var mapping = _segmenter.ColumnMap.GetMapping(columnName, index); if (!force && mapping.IgnoreProperty) @@ -201,9 +208,16 @@ public bool TryGetDataValue(string columnName, string index, out TProp va return false; } - if (_row.TryGetValue(mapping.MappedName, out obj) && obj != null && obj != DBNull.Value) + if (_row.TryGetValue(mapping.MappedName, out rawValue) && rawValue != null && rawValue != DBNull.Value) { - value = obj.Convert(_segmenter.Culture); + if (rawValue.ToString().IsCaseInsensitiveEqual(_explicitNull)) + { + value = default(TProp); + } + else + { + value = rawValue.Convert(_segmenter.Culture); + } return true; } @@ -266,7 +280,7 @@ public bool SetProperty( { converted = converter(value, _segmenter.Culture); } - else if (value.ToString().IsCaseInsensitiveEqual("[NULL]")) + else if (value.ToString().IsCaseInsensitiveEqual(_explicitNull)) { // prop is "explicitly" set to null. Don't fallback to any default! converted = default(TProp); From 5dbb52266c7f63d10bc53fff288bc1697492c9d7 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 10 May 2016 21:19:05 +0200 Subject: [PATCH 022/185] Resolves #913 Use HTML5 Input types (tel, email) --- .../Extensions/HtmlExtensions.cs | 48 ++++++++++++------- .../Models/Common/AddressModel.cs | 16 +++++-- .../Models/Common/ContactUsModel.cs | 8 ++-- .../Models/Customer/CustomerInfoModel.cs | 12 +++-- .../Models/Customer/PaswordRecoveryModel.cs | 8 ++-- .../Models/Customer/RegisterModel.cs | 9 ++-- .../Customer/PasswordRecovery.Mobile.cshtml | 2 +- .../Views/Customer/PasswordRecovery.cshtml | 2 +- .../Views/Home/ContactUs.Mobile.cshtml | 2 +- .../Views/Home/ContactUs.cshtml | 2 +- .../Shared/_CreateOrUpdateAddress.cshtml | 6 +-- 11 files changed, 73 insertions(+), 42 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs index a6adbe2e1a..b56f183331 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs @@ -1,29 +1,29 @@ using System; -using System.Web; -using System.Threading; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Text; +using System.Threading; +using System.Web; using System.Web.Mvc; -using System.Web.Routing; using System.Web.Mvc.Html; +using System.Web.Routing; using System.Web.WebPages; using SmartStore.Core; +using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Infrastructure; using SmartStore.Services.Localization; -using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.UI; -using SmartStore.Web.Framework.Settings; using SmartStore.Utilities; -using SmartStore.Core.Domain.Catalog; +using SmartStore.Web.Framework.Localization; using SmartStore.Web.Framework.Modelling; +using SmartStore.Web.Framework.Settings; +using SmartStore.Web.Framework.UI; namespace SmartStore.Web.Framework { - public enum InputEditorType + public enum InputEditorType { TextBox, Password, Hidden, @@ -447,8 +447,12 @@ public static MvcHtmlString ControlGroupFor( { return html.HiddenFor(expression); } - - var sb = new StringBuilder("
"); + + string inputHtml = ""; + var htmlAttributes = new RouteValueDictionary(); + var dataTypeName = ModelMetadata.FromLambdaExpression(expression, html.ViewData).DataTypeName.EmptyNull(); + + var sb = new StringBuilder("
"); if (editorType != InputEditorType.Checkbox) { @@ -458,14 +462,23 @@ public static MvcHtmlString ControlGroupFor( } sb.AppendLine("
"); - string inputHtml = ""; - object attrs = null; + if (!required && (editorType == InputEditorType.TextBox || editorType == InputEditorType.Password)) { - attrs = new { placeholder = "Optional" /* TODO: Loc */ }; + htmlAttributes.Add("placeholder", EngineContext.Current.Resolve().GetResource("Common.Optional")); } - //var x = ModelMetadata.FromLambdaExpression(expression, html.ViewData).DisplayName; - switch (editorType) + + switch (dataTypeName) + { + case "EmailAddress": + htmlAttributes.Add("type", "email"); + break; + case "PhoneNumber": + htmlAttributes.Add("type", "tel"); + break; + } + + switch (editorType) { case InputEditorType.Checkbox: inputHtml = string.Format("", @@ -473,12 +486,13 @@ public static MvcHtmlString ControlGroupFor( ModelMetadata.FromLambdaExpression(expression, html.ViewData).DisplayName); // TBD: ist das OK so? break; case InputEditorType.Password: - inputHtml = html.PasswordFor(expression, attrs).ToString(); + inputHtml = html.PasswordFor(expression, htmlAttributes).ToString(); break; default: - inputHtml = html.TextBoxFor(expression, attrs).ToString(); + inputHtml = html.TextBoxFor(expression, htmlAttributes).ToString(); break; } + sb.AppendLine(inputHtml); sb.AppendLine(html.ValidationMessageFor(expression).ToString()); if (helpHint.HasValue()) diff --git a/src/Presentation/SmartStore.Web/Models/Common/AddressModel.cs b/src/Presentation/SmartStore.Web/Models/Common/AddressModel.cs index 2f81a1d44c..a393eb0873 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/AddressModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/AddressModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Web.Framework; @@ -7,7 +8,7 @@ namespace SmartStore.Web.Models.Common { - [Validator(typeof(AddressValidator))] + [Validator(typeof(AddressValidator))] public partial class AddressModel : EntityModelBase { public AddressModel() @@ -26,9 +27,12 @@ public AddressModel() [SmartResourceDisplayName("Address.Fields.Email")] [AllowHtml] - public string Email { get; set; } + [DataType(DataType.EmailAddress)] + public string Email { get; set; } + [SmartResourceDisplayName("Address.Fields.EmailMatch")] - public string EmailMatch { get; set; } + [DataType(DataType.EmailAddress)] + public string EmailMatch { get; set; } public bool ValidateEmailAddress { get; set; } [SmartResourceDisplayName("Address.Fields.Company")] @@ -79,13 +83,15 @@ public AddressModel() [SmartResourceDisplayName("Address.Fields.PhoneNumber")] [AllowHtml] - public string PhoneNumber { get; set; } + [DataType(DataType.PhoneNumber)] + public string PhoneNumber { get; set; } public bool PhoneEnabled { get; set; } public bool PhoneRequired { get; set; } [SmartResourceDisplayName("Address.Fields.FaxNumber")] [AllowHtml] - public string FaxNumber { get; set; } + [DataType(DataType.PhoneNumber)] + public string FaxNumber { get; set; } public bool FaxEnabled { get; set; } public bool FaxRequired { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/Common/ContactUsModel.cs b/src/Presentation/SmartStore.Web/Models/Common/ContactUsModel.cs index 5b7f9464c8..f20fd69129 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/ContactUsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/ContactUsModel.cs @@ -1,4 +1,5 @@ -using System.Web.Mvc; +using System.ComponentModel.DataAnnotations; +using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; @@ -6,12 +7,13 @@ namespace SmartStore.Web.Models.Common { - [Validator(typeof(ContactUsValidator))] + [Validator(typeof(ContactUsValidator))] public partial class ContactUsModel : ModelBase { [AllowHtml] [SmartResourceDisplayName("ContactUs.Email")] - public string Email { get; set; } + [DataType(DataType.EmailAddress)] + public string Email { get; set; } [AllowHtml] [SmartResourceDisplayName("ContactUs.Enquiry")] diff --git a/src/Presentation/SmartStore.Web/Models/Customer/CustomerInfoModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/CustomerInfoModel.cs index b75ed065cc..80e9ddafef 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/CustomerInfoModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/CustomerInfoModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Web.Framework; @@ -7,7 +8,7 @@ namespace SmartStore.Web.Models.Customer { - [Validator(typeof(CustomerInfoValidator))] + [Validator(typeof(CustomerInfoValidator))] public partial class CustomerInfoModel : ModelBase { public CustomerInfoModel() @@ -20,7 +21,8 @@ public CustomerInfoModel() [SmartResourceDisplayName("Account.Fields.Email")] [AllowHtml] - public string Email { get; set; } + [DataType(DataType.EmailAddress)] + public string Email { get; set; } [SmartResourceDisplayName("Account.Fields.CustomerNumber")] [AllowHtml] @@ -100,13 +102,15 @@ public CustomerInfoModel() public bool PhoneRequired { get; set; } [SmartResourceDisplayName("Account.Fields.Phone")] [AllowHtml] - public string Phone { get; set; } + [DataType(DataType.PhoneNumber)] + public string Phone { get; set; } public bool FaxEnabled { get; set; } public bool FaxRequired { get; set; } [SmartResourceDisplayName("Account.Fields.Fax")] [AllowHtml] - public string Fax { get; set; } + [DataType(DataType.PhoneNumber)] + public string Fax { get; set; } public bool NewsletterEnabled { get; set; } [SmartResourceDisplayName("Account.Fields.Newsletter")] diff --git a/src/Presentation/SmartStore.Web/Models/Customer/PaswordRecoveryModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/PaswordRecoveryModel.cs index 6bb39f28be..b8581117d4 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/PaswordRecoveryModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/PaswordRecoveryModel.cs @@ -1,4 +1,5 @@ -using System.Web.Mvc; +using System.ComponentModel.DataAnnotations; +using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; @@ -6,12 +7,13 @@ namespace SmartStore.Web.Models.Customer { - [Validator(typeof(PasswordRecoveryValidator))] + [Validator(typeof(PasswordRecoveryValidator))] public partial class PasswordRecoveryModel : ModelBase { [AllowHtml] [SmartResourceDisplayName("Account.PasswordRecovery.Email")] - public string Email { get; set; } + [DataType(DataType.EmailAddress)] + public string Email { get; set; } public string Result { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Models/Customer/RegisterModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/RegisterModel.cs index daedf7b90b..4d1a445e2d 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/RegisterModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/RegisterModel.cs @@ -20,7 +20,8 @@ public RegisterModel() [SmartResourceDisplayName("Account.Fields.Email")] [AllowHtml] - public string Email { get; set; } + [DataType(DataType.EmailAddress)] + public string Email { get; set; } public bool UsernamesEnabled { get; set; } [SmartResourceDisplayName("Account.Fields.Username")] @@ -104,13 +105,15 @@ public RegisterModel() public bool PhoneRequired { get; set; } [SmartResourceDisplayName("Account.Fields.Phone")] [AllowHtml] - public string Phone { get; set; } + [DataType(DataType.PhoneNumber)] + public string Phone { get; set; } public bool FaxEnabled { get; set; } public bool FaxRequired { get; set; } [SmartResourceDisplayName("Account.Fields.Fax")] [AllowHtml] - public string Fax { get; set; } + [DataType(DataType.PhoneNumber)] + public string Fax { get; set; } public bool NewsletterEnabled { get; set; } [SmartResourceDisplayName("Account.Fields.Newsletter")] diff --git a/src/Presentation/SmartStore.Web/Views/Customer/PasswordRecovery.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/PasswordRecovery.Mobile.cshtml index 77782f2fa4..b6681691c6 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/PasswordRecovery.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/PasswordRecovery.Mobile.cshtml @@ -24,7 +24,7 @@
@Html.LabelFor(model => model.Email) - @Html.TextBoxFor(model => model.Email, new { @class = "email" }) + @Html.TextBoxFor(model => model.Email, new { @class = "email", @type = "email" }) @Html.ValidationMessageFor(model => model.Email)
diff --git a/src/Presentation/SmartStore.Web/Views/Customer/PasswordRecovery.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/PasswordRecovery.cshtml index 3123bb9ffd..eaca87e410 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/PasswordRecovery.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/PasswordRecovery.cshtml @@ -29,7 +29,7 @@
@Html.LabelFor(model => model.Email, new { @class = "control-label required" })
- @Html.TextBoxFor(model => model.Email, new { @class = "email" }) + @Html.TextBoxFor(model => model.Email, new { @class = "email", @type = "email" }) @Html.ValidationMessageFor(model => model.Email)
diff --git a/src/Presentation/SmartStore.Web/Views/Home/ContactUs.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Home/ContactUs.Mobile.cshtml index 66308f45d1..37762fffea 100644 --- a/src/Presentation/SmartStore.Web/Views/Home/ContactUs.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Home/ContactUs.Mobile.cshtml @@ -34,7 +34,7 @@
@Html.LabelFor(model => model.Email) - @Html.TextBoxFor(model => model.Email, new { @class = "email" }) + @Html.TextBoxFor(model => model.Email, new { @class = "email", @type = "email" }) @Html.ValidationMessageFor(model => model.Email)
diff --git a/src/Presentation/SmartStore.Web/Views/Home/ContactUs.cshtml b/src/Presentation/SmartStore.Web/Views/Home/ContactUs.cshtml index 02e6e71ddf..d0b173f3f2 100644 --- a/src/Presentation/SmartStore.Web/Views/Home/ContactUs.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Home/ContactUs.cshtml @@ -42,7 +42,7 @@
@Html.LabelFor(model => model.Email, new { @class="control-label required" , @for="Email" } )
- @Html.TextBoxFor(model => model.Email, new { @class = "email", placeholder = T("ContactUs.Email.Hint") }) + @Html.TextBoxFor(model => model.Email, new { @class = "email", @type = "email", placeholder = T("ContactUs.Email.Hint") }) @Html.ValidationMessageFor(model => model.Email)
diff --git a/src/Presentation/SmartStore.Web/Views/Shared/_CreateOrUpdateAddress.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/_CreateOrUpdateAddress.cshtml index 23bde2bdda..ad1a8d84cb 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/_CreateOrUpdateAddress.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/_CreateOrUpdateAddress.cshtml @@ -159,18 +159,18 @@
@Html.LabelFor(model => model.PhoneNumber, new { @class="control-label" + (Model.PhoneRequired ? " required" : "") , @for="PhoneNumber" } )
- @Html.TextBoxFor(model => model.PhoneNumber, (Model.PhoneRequired ? null : new { placeholder = "Optional" } ) ) + @Html.TextBoxFor(model => model.PhoneNumber, new { placeholder = Model.PhoneRequired ? null : T("Common.Optional"), @type = "tel" }) @Html.ValidationMessageFor(model => model.PhoneNumber)
} @if (Model.FaxEnabled) - { + {
@Html.LabelFor(model => model.FaxNumber, new { @class="control-label" + (Model.FaxRequired ? " required" : ""), @for="FaxNumber" } )
- @Html.TextBoxFor(model => model.FaxNumber, (Model.FaxRequired ? null : new { placeholder = "Optional" } ) ) + @Html.TextBoxFor(model => model.FaxNumber, new { placeholder = Model.FaxRequired ? null : T("Common.Optional"), @type = "tel" }) @Html.ValidationMessageFor(model => model.FaxNumber)
From 70ae37347418befe180f106a625fa68e4fe80ee5 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 10 May 2016 21:19:29 +0200 Subject: [PATCH 023/185] Updated changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index d021f5d7d0..9cc6e7eba5 100644 --- a/changelog.md +++ b/changelog.md @@ -17,6 +17,7 @@ * Exports the product detail link including the attribute query string when exporting attribute combinations * #918 Compare products: Display base price information * Export email attachments needs to be stored in database because the temp file may not exist anymore when sending the email +* #913 Use HTML5 Input types (tel, email) ### Bugfixes * Fixed ajax cache issue when saving payment or shipping restrictions. Internet Explorer showed the old data state (before storage). From 2af497ed9560437d24a33d55dd82b5d786d29bcf Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 10 May 2016 21:57:29 +0200 Subject: [PATCH 024/185] Minor refactoring in ImportRow --- .../DataExchange/Import/ImportRow.cs | 46 ++++++------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs index ff191e5dec..ebe37a106f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -8,7 +8,7 @@ namespace SmartStore.Services.DataExchange.Import { public class ImportRow where T : BaseEntity { - private const string _explicitNull = "[NULL]"; + private const string ExplicitNull = "[NULL]"; private bool _initialized = false; private T _entity; @@ -163,33 +163,18 @@ public bool HasDataValue(string columnName, string index) return (_row.TryGetValue(mapping.MappedName, out value) && value != null && value != DBNull.Value); } - public TProp GetDataValue(string columnName) + public TProp GetDataValue(string columnName, bool force = false) { - return GetDataValue(columnName, null); + TProp value; + TryGetDataValue(columnName, null, out value, force); + return value; } - public TProp GetDataValue(string columnName, string index) + public TProp GetDataValue(string columnName, string index, bool force = false) { - object value; - var mapping = _segmenter.ColumnMap.GetMapping(columnName, index); - - if (_row.TryGetValue(mapping.MappedName, out value) && value != null && value != DBNull.Value) - { - if (value.ToString().IsCaseInsensitiveEqual(_explicitNull)) - { - return default(TProp); - } - - return value.Convert(_segmenter.Culture); - } - - if (IsNew) - { - // only transient/new entities should fallback to possible defaults. - return GetDefaultValue(mapping, default(TProp)); - } - - return default(TProp); + TProp value; + TryGetDataValue(columnName, index, out value, force); + return value; } public bool TryGetDataValue(string columnName, out TProp value, bool force = false) @@ -210,14 +195,9 @@ public bool TryGetDataValue(string columnName, string index, out TProp va if (_row.TryGetValue(mapping.MappedName, out rawValue) && rawValue != null && rawValue != DBNull.Value) { - if (rawValue.ToString().IsCaseInsensitiveEqual(_explicitNull)) - { - value = default(TProp); - } - else - { - value = rawValue.Convert(_segmenter.Culture); - } + value = rawValue.ToString().IsCaseInsensitiveEqual(ExplicitNull) + ? default(TProp) + : rawValue.Convert(_segmenter.Culture); return true; } @@ -280,7 +260,7 @@ public bool SetProperty( { converted = converter(value, _segmenter.Culture); } - else if (value.ToString().IsCaseInsensitiveEqual(_explicitNull)) + else if (value.ToString().IsCaseInsensitiveEqual(ExplicitNull)) { // prop is "explicitly" set to null. Don't fallback to any default! converted = default(TProp); From 82602396c87dd9d764aeee47f5c391ea4d4d1586 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 May 2016 02:05:05 +0200 Subject: [PATCH 025/185] ProcessLocalizations should not check value --- .../SmartStore.Services/Catalog/Importer/ProductImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 5f9c703827..1b21b8c7ce 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -367,7 +367,7 @@ protected virtual int ProcessLocalizations( var code = lang.UniqueSeoCode; string value; - if (row.TryGetDataValue(prop /* ColumnName */, code, out value) && value.HasValue()) + if (row.TryGetDataValue(prop /* ColumnName */, code, out value)) { _localizedEntityService.SaveLocalizedValue(row.Entity, lambda, value, lang.Id); shouldSave = true; From f2aa91f0395522e8b85cb3b5f7b5e209c885d8e8 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 May 2016 02:32:57 +0200 Subject: [PATCH 026/185] Made EntityImporterBase generic & added DbContext property to IImportExecuteContext --- .../Catalog/Importer/CategoryImporter.cs | 8 ++++++- .../Catalog/Importer/ProductImporter.cs | 19 +++++------------ .../Customers/Importer/CustomerImporter.cs | 8 ++++++- .../DataExchange/Import/DataImporter.cs | 1 + .../DataExchange/Import/EntityImporterBase.cs | 21 ++++++++++++++++++- .../Import/ImportExecuteContext.cs | 8 +++++++ 6 files changed, 48 insertions(+), 17 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 36df38dd7f..1efdee5fc2 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Linq.Expressions; using SmartStore.Core.Async; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; @@ -19,7 +20,7 @@ namespace SmartStore.Services.Catalog.Importer { - public class CategoryImporter : EntityImporterBase + public class CategoryImporter : EntityImporterBase { private readonly IRepository _categoryRepository; private readonly IRepository _urlRecordRepository; @@ -444,6 +445,11 @@ public static string[] DefaultKeyFields } } + protected override IDictionary>> GetLocalizableProperties() + { + throw new NotImplementedException(); + } + protected override void Import(IImportExecuteContext context) { var srcToDestId = new Dictionary(); diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 1b21b8c7ce..6acd6a5588 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -20,7 +20,7 @@ namespace SmartStore.Services.Catalog.Importer { - public class ProductImporter : EntityImporterBase + public class ProductImporter : EntityImporterBase { private readonly IRepository _productPictureRepository; private readonly IRepository _productManufacturerRepository; @@ -354,7 +354,7 @@ protected virtual int ProcessLocalizations( { return 0; } - + bool shouldSave = false; foreach (var row in batch) @@ -379,7 +379,7 @@ protected virtual int ProcessLocalizations( if (shouldSave) { // commit whole batch at once - return _productRepository.Context.SaveChanges(); + return context.DbContext.SaveChanges(); } return 0; @@ -722,18 +722,9 @@ public static string[] DefaultKeyFields } } - private IEnumerable ResolveLocalizedProperties(ImportDataSegmenter segmenter) + protected override IDictionary>> GetLocalizableProperties() { - // Perf: determine whether our localizable properties actually have - // counterparts in the source BEFORE import begins. This way we spare ourself - // to query over and over for values. - foreach (var kvp in _localizableProperties) - { - if (segmenter.GetColumnIndexes(kvp.Key).Length > 0) - { - yield return kvp.Key; - } - } + return _localizableProperties; } protected override void Import(IImportExecuteContext context) diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 8f9a92b380..fd949b85d9 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Linq.Expressions; using SmartStore.Core.Async; using SmartStore.Core.Data; using SmartStore.Core.Domain.Common; @@ -21,7 +22,7 @@ namespace SmartStore.Services.Customers.Importer { - public class CustomerImporter : EntityImporterBase + public class CustomerImporter : EntityImporterBase { private const string _attributeKeyGroup = "Customer"; @@ -544,6 +545,11 @@ public static string[] DefaultKeyFields } } + protected override IDictionary>> GetLocalizableProperties() + { + throw new NotImplementedException(); + } + protected override void Import(IImportExecuteContext context) { var customer = _services.WorkContext.CurrentCustomer; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs index 2f5e3c3800..6020daa20b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs @@ -253,6 +253,7 @@ private void ImportCoreOuter(DataImporterContext ctx) { ctx.Log = logger; + ctx.ExecuteContext.DbContext = _services.DbContext; ctx.ExecuteContext.Log = logger; ctx.ExecuteContext.Languages = _languageService.GetAllLanguages(true); ctx.ExecuteContext.UpdateOnly = ctx.Request.Profile.UpdateOnly; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index d35778278c..ff02c34e54 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -5,10 +5,12 @@ using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.IO; using SmartStore.Utilities; +using System.Linq.Expressions; +using SmartStore.Core; namespace SmartStore.Services.DataExchange.Import { - public abstract class EntityImporterBase : IEntityImporter + public abstract class EntityImporterBase : IEntityImporter where TEntity : BaseEntity { private const string _imageDownloadFolder = @"Content\DownloadedImages"; @@ -128,5 +130,22 @@ public void Succeeded(FileDownloadManagerItem item) DownloadedItems.Add(item.Url, Path.GetFileName(item.Path)); } } + + protected IEnumerable ResolveLocalizedProperties(ImportDataSegmenter segmenter) + { + // Perf: determine whether our localizable properties actually have + // counterparts in the source BEFORE import begins. This way we spare ourself + // to query over and over for values. + var localizableProperties = GetLocalizableProperties(); + foreach (var kvp in localizableProperties) + { + if (segmenter.GetColumnIndexes(kvp.Key).Length > 0) + { + yield return kvp.Key; + } + } + } + + protected abstract IDictionary>> GetLocalizableProperties(); } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index 29ae2d82d8..675ea09544 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -4,6 +4,7 @@ using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Logging; +using SmartStore.Core.Data; namespace SmartStore.Services.DataExchange.Import { @@ -39,6 +40,11 @@ public interface IImportExecuteContext ///
ILogger Log { get; } + /// + /// The database context + /// + IDbContext DbContext { get; } + /// /// Cancellation token /// @@ -101,6 +107,8 @@ public ImportExecuteContext( public ILogger Log { get; internal set; } + public IDbContext DbContext { get; internal set; } + public CancellationToken CancellationToken { get; private set; } public string ImportFolder { get; internal set; } From 317d990c1b01b9dcffdbe758b78f8c984a9ee9b2 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 May 2016 02:53:01 +0200 Subject: [PATCH 027/185] More refactoring in EntityImporter --- .../Catalog/Importer/CategoryImporter.cs | 67 +++++++++++++------ .../Catalog/Importer/ProductImporter.cs | 2 +- .../DataExchange/Import/DataImporter.cs | 2 +- .../DataExchange/Import/EntityImporterBase.cs | 2 + .../Import/ImportExecuteContext.cs | 6 +- 5 files changed, 52 insertions(+), 27 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 1efdee5fc2..fb99e870f6 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -36,6 +36,17 @@ public class CategoryImporter : EntityImporterBase private readonly SeoSettings _seoSettings; private readonly DataExchangeSettings _dataExchangeSettings; + private static readonly Dictionary>> _localizableProperties = new Dictionary>> + { + { "Name", x => x.Name }, + { "FullName", x => x.FullName }, + { "Description", x => x.Description }, + { "BottomDescription", x => x.BottomDescription }, + { "MetaKeywords", x => x.MetaKeywords }, + { "MetaDescription", x => x.MetaDescription }, + { "MetaTitle", x => x.MetaTitle } + }; + public CategoryImporter( IRepository categoryRepository, IRepository urlRecordRepository, @@ -139,34 +150,44 @@ protected virtual int ProcessSlugs(IImportExecuteContext context, IEnumerable> batch) + protected virtual int ProcessLocalizations( + IImportExecuteContext context, + IEnumerable> batch, + string[] localizedProperties) { + if (localizedProperties.Length == 0) + { + return 0; + } + + bool shouldSave = false; + foreach (var row in batch) { - foreach (var lang in context.Languages) + foreach (var prop in localizedProperties) { - // TODO: ignored properties - var name = row.GetDataValue("Name", lang.UniqueSeoCode); - var fullName = row.GetDataValue("FullName", lang.UniqueSeoCode); - var description = row.GetDataValue("Description", lang.UniqueSeoCode); - var bottomDescription = row.GetDataValue("BottomDescription", lang.UniqueSeoCode); - var metaKeywords = row.GetDataValue("MetaKeywords", lang.UniqueSeoCode); - var metaDescription = row.GetDataValue("MetaDescription", lang.UniqueSeoCode); - var metaTitle = row.GetDataValue("MetaTitle", lang.UniqueSeoCode); - - _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.Name, name, lang.Id); - _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.FullName, fullName, lang.Id); - _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.Description, description, lang.Id); - _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.BottomDescription, bottomDescription, lang.Id); - _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.MetaKeywords, metaKeywords, lang.Id); - _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.MetaDescription, metaDescription, lang.Id); - _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.MetaTitle, metaTitle, lang.Id); + var lambda = _localizableProperties[prop]; + foreach (var lang in context.Languages) + { + var code = lang.UniqueSeoCode; + string value; + + if (row.TryGetDataValue(prop /* ColumnName */, code, out value)) + { + _localizedEntityService.SaveLocalizedValue(row.Entity, lambda, value, lang.Id); + shouldSave = true; + } + } } } - var num = _categoryRepository.Context.SaveChanges(); + if (shouldSave) + { + // commit whole batch at once + return context.Services.DbContext.SaveChanges(); + } - return num; + return 0; } protected virtual int ProcessParentMappings( @@ -447,7 +468,7 @@ public static string[] DefaultKeyFields protected override IDictionary>> GetLocalizableProperties() { - throw new NotImplementedException(); + return _localizableProperties; } protected override void Import(IImportExecuteContext context) @@ -462,6 +483,8 @@ protected override void Import(IImportExecuteContext context) Init(context, _dataExchangeSettings); + var localizedProperties = ResolveLocalizedProperties(segmenter).ToArray(); + context.Result.TotalRecords = segmenter.TotalRows; while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) @@ -524,7 +547,7 @@ protected override void Import(IImportExecuteContext context) // localizations try { - ProcessLocalizations(context, batch); + ProcessLocalizations(context, batch, localizedProperties); } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 6acd6a5588..7b53e04133 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -379,7 +379,7 @@ protected virtual int ProcessLocalizations( if (shouldSave) { // commit whole batch at once - return context.DbContext.SaveChanges(); + return context.Services.DbContext.SaveChanges(); } return 0; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs index 6020daa20b..7da50a9970 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs @@ -253,7 +253,7 @@ private void ImportCoreOuter(DataImporterContext ctx) { ctx.Log = logger; - ctx.ExecuteContext.DbContext = _services.DbContext; + ctx.ExecuteContext.Services = _services; ctx.ExecuteContext.Log = logger; ctx.ExecuteContext.Languages = _languageService.GetAllLanguages(true); ctx.ExecuteContext.UpdateOnly = ctx.Request.Profile.UpdateOnly; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index ff02c34e54..4423364e73 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -7,6 +7,8 @@ using SmartStore.Utilities; using System.Linq.Expressions; using SmartStore.Core; +using Autofac; +using SmartStore.Services.Localization; namespace SmartStore.Services.DataExchange.Import { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index 675ea09544..9029593868 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -41,9 +41,9 @@ public interface IImportExecuteContext ILogger Log { get; } /// - /// The database context + /// Common Services /// - IDbContext DbContext { get; } + ICommonServices Services { get; } /// /// Cancellation token @@ -107,7 +107,7 @@ public ImportExecuteContext( public ILogger Log { get; internal set; } - public IDbContext DbContext { get; internal set; } + public ICommonServices Services { get; internal set; } public CancellationToken CancellationToken { get; private set; } From 2a4cfc996c6a120e45dbe0fd722f48a47f8e1432 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 11 May 2016 14:16:11 +0200 Subject: [PATCH 028/185] Resolves #477 Implement option to specify the number of exported and imported pictures --- changelog.md | 1 + .../Domain/DataExchange/ImportProfile.cs | 5 + .../Mapping/DataExchange/ImportProfileMap.cs | 1 + ...01605111140288_ImportExtraData.Designer.cs | 29 ++++ .../201605111140288_ImportExtraData.cs | 37 +++++ .../201605111140288_ImportExtraData.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 7 + .../Catalog/Importer/ProductImporter.cs | 4 + .../DataExchange/Import/DataImporter.cs | 5 +- .../Import/ImportExecuteContext.cs | 9 +- .../DataExchange/Import/ImportExtraData.cs | 13 ++ .../SmartStore.Services.csproj | 1 + .../Controllers/ImportController.cs | 14 ++ .../Models/DataExchange/ImportProfileModel.cs | 13 ++ .../Views/Import/_Update.cshtml | 13 ++ 15 files changed, 274 insertions(+), 4 deletions(-) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.resx create mode 100644 src/Libraries/SmartStore.Services/DataExchange/Import/ImportExtraData.cs diff --git a/changelog.md b/changelog.md index 9cc6e7eba5..d538dea8d6 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,7 @@ * #870 Implement address import for customers (both billing & shipping address) * #886 Add setting to hide manufacturer images on product detail page and to hide default image for manufacturers * Import localized SEO names for product and categories +* #477 Implement option to specify the number of exported and imported pictures ### Improvements * (Dev) Publishing SmartStore.Web from within Visual Studio now deploys the project correctly. No need to execute ClickToBuild.cmd anymore. Just publish directly to any target, including Azure. diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportProfile.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportProfile.cs index b642e3d50d..1139496b4a 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportProfile.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportProfile.cs @@ -85,6 +85,11 @@ public ImportEntityType EntityType /// public string FileTypeConfiguration { get; set; } + /// + /// XML with extra data + /// + public string ExtraData { get; set; } + /// /// Mapping of import columns /// diff --git a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ImportProfileMap.cs b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ImportProfileMap.cs index eb06973c22..02515fd268 100644 --- a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ImportProfileMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ImportProfileMap.cs @@ -14,6 +14,7 @@ public ImportProfileMap() this.Property(x => x.FolderName).IsRequired().HasMaxLength(100); this.Property(x => x.KeyFieldNames).HasMaxLength(1000); this.Property(x => x.FileTypeConfiguration).IsMaxLength(); + this.Property(x => x.ExtraData).IsMaxLength(); this.Property(x => x.ColumnMapping).IsMaxLength(); this.Property(x => x.ResultInfo).IsMaxLength(); diff --git a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.Designer.cs new file mode 100644 index 0000000000..6cac518be6 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.1.3-40302")] + public sealed partial class ImportExtraData : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ImportExtraData)); + + string IMigrationMetadata.Id + { + get { return "201605111140288_ImportExtraData"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs new file mode 100644 index 0000000000..55bbdd6843 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs @@ -0,0 +1,37 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using Setup; + + public partial class ImportExtraData : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.ImportProfile", "ExtraData", c => c.String()); + } + + public override void Down() + { + DropColumn("dbo.ImportProfile", "ExtraData"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.DataExchange.Import.NumberOfPictures", + "Number of pictures", + "Anzahl der Bilder", + "Specifies the number of images per object to be imported.", + "Legt die Anzahl der zu importierenden Bilder pro Objekt fest."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.resx b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.resx new file mode 100644 index 0000000000..738509bbb4 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index 6b9222716c..bdec0c33b4 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -392,6 +392,10 @@ 201605061916117_SwapColumnMappingValues.cs + + + 201605111140288_ImportExtraData.cs + @@ -731,6 +735,9 @@ 201605061916117_SwapColumnMappingValues.cs + + 201605111140288_ImportExtraData.cs + diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 7b53e04133..7f98b1d17a 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -143,6 +143,7 @@ protected virtual void ProcessProductPictures(IImportExecuteContext context, IEn ProductPicture lastInserted = null; var equalPictureId = 0; + var numberOfPictures = (context.ExtraData.NumberOfPictures ?? int.MaxValue); foreach (var row in batch) { @@ -162,6 +163,9 @@ protected virtual void ProcessProductPictures(IImportExecuteContext context, IEn if (image != null) imageFiles.Add(image); + + if (imageFiles.Count >= numberOfPictures) + break; } // download images diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs index 7da50a9970..9391d96c71 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs @@ -1,8 +1,8 @@ -using SmartStore.Core; -using System; +using System; using System.IO; using System.Text; using System.Threading; +using SmartStore.Core; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.DataExchange; @@ -259,6 +259,7 @@ private void ImportCoreOuter(DataImporterContext ctx) ctx.ExecuteContext.UpdateOnly = ctx.Request.Profile.UpdateOnly; ctx.ExecuteContext.KeyFieldNames = ctx.Request.Profile.KeyFieldNames.SplitSafe(","); ctx.ExecuteContext.ImportFolder = ctx.Request.Profile.GetImportFolder(); + ctx.ExecuteContext.ExtraData = XmlHelper.Deserialize(ctx.Request.Profile.ExtraData); { var mapConverter = new ColumnMapConverter(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index 9029593868..cc69ceec35 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -1,10 +1,8 @@ using System.Collections.Generic; using System.Threading; -using SmartStore.Core; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Logging; -using SmartStore.Core.Data; namespace SmartStore.Services.DataExchange.Import { @@ -55,6 +53,11 @@ public interface IImportExecuteContext ///
ImportResult Result { get; set; } + /// + /// Extra import configuration data + /// + ImportExtraData ExtraData { get; } + /// /// Indicates whether and how to abort the import /// @@ -120,6 +123,8 @@ public ImportExecuteContext( public ImportResult Result { get; set; } + public ImportExtraData ExtraData { get; internal set; } + public DataExchangeAbortion Abort { get diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExtraData.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExtraData.cs new file mode 100644 index 0000000000..ecbe710677 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExtraData.cs @@ -0,0 +1,13 @@ +using System; + +namespace SmartStore.Services.DataExchange.Import +{ + [Serializable] + public class ImportExtraData + { + /// + /// Number of images per object to be imported + /// + public int? NumberOfPictures { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 056e552845..262454bf5c 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -191,6 +191,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index e7b0c74bc1..fdb726fe6c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -158,6 +158,10 @@ private void PrepareProfileModel(ImportProfileModel model, ImportProfile profile csvConfiguration = CsvConfiguration.ExcelFriendlyConfiguration; } + // common configuration + var extraData = XmlHelper.Deserialize(profile.ExtraData); + model.ExtraData.NumberOfPictures = extraData.NumberOfPictures; + // column mapping model.AvailableSourceColumns = new List(); model.AvailableEntityProperties = new List(); @@ -502,6 +506,16 @@ public ActionResult Edit(ImportProfileModel model, bool continueEditing, FormCol var mapConverter = new ColumnMapConverter(); profile.ColumnMapping = mapConverter.ConvertTo(map); } + + if (model.ExtraData != null) + { + var extraData = new ImportExtraData + { + NumberOfPictures = model.ExtraData.NumberOfPictures + }; + + profile.ExtraData = XmlHelper.Serialize(extraData); + } } catch (Exception exception) { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs index a3b8736c9a..41f3c9f23f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs @@ -13,6 +13,11 @@ namespace SmartStore.Admin.Models.DataExchange [Validator(typeof(ImportProfileValidator))] public partial class ImportProfileModel : EntityModelBase { + public ImportProfileModel() + { + ExtraData = new ExtraDataModel(); + } + [SmartResourceDisplayName("Admin.DataExchange.Import.Name")] public string Name { get; set; } @@ -57,9 +62,17 @@ public partial class ImportProfileModel : EntityModelBase public CsvConfigurationModel CsvConfiguration { get; set; } + public ExtraDataModel ExtraData { get; set; } + public List ColumnMappings { get; set; } public List AvailableSourceColumns { get; set; } public List AvailableEntityProperties { get; set; } + + public class ExtraDataModel + { + [SmartResourceDisplayName("Admin.DataExchange.Import.NumberOfPictures")] + public int? NumberOfPictures { get; set; } + } } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml index f887a8150c..0693b5ed32 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml @@ -1,4 +1,5 @@ @using SmartStore.Admin.Models.DataExchange; +@using SmartStore.Core.Domain.DataExchange; @model ImportProfileModel @{ Html.AddScriptParts("~/bundles/fileupload"); @@ -178,6 +179,18 @@ @Html.ValidationMessageFor(x => x.UpdateOnly) + @if (Model.EntityType == ImportEntityType.Product) + { + + + @Html.SmartLabelFor(x => x.ExtraData.NumberOfPictures) + + + @Html.EditorFor(x => x.ExtraData.NumberOfPictures) + @Html.ValidationMessageFor(x => x.ExtraData.NumberOfPictures) + + + } From 398b7735c9215501fa300dd34d707475483608aa Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 May 2016 17:24:23 +0200 Subject: [PATCH 029/185] build.bat: added new lookup folders for vcvarsall.bat --- build.bat | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build.bat b/build.bat index 2cee9dc2b5..b58175809a 100644 --- a/build.bat +++ b/build.bat @@ -5,7 +5,11 @@ FOR %%b in ( "%VS120COMNTOOLS%..\..\VC\vcvarsall.bat" "%ProgramFiles(x86)%\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" - "%ProgramFiles%\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" + "%ProgramFiles%\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" + + "%VS110COMNTOOLS%..\..\VC\vcvarsall.bat" + "%ProgramFiles(x86)%\Microsoft Visual Studio 11.0\VC\vcvarsall.bat" + "%ProgramFiles%\Microsoft Visual Studio 11.0\VC\vcvarsall.bat" ) do ( if exist %%b ( call %%b x86 From b9da0cd69219c4a3ba83087f43f294b63e1913fb Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 11 May 2016 17:31:19 +0200 Subject: [PATCH 030/185] Unit testing --- .../Catalog/PriceCalculationService.cs | 6 ++++-- .../Infrastructure/AutoMapperStartupTask.cs | 12 +++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs index 7a06368334..1aef3aeb63 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs @@ -758,10 +758,12 @@ public virtual decimal GetUnitPrice(OrganizedShoppingCartItem shoppingCartItem, var attributesTotalPrice = decimal.Zero; - var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml).ToList(); + var pvaValuesEnum = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml); - if (pvaValues != null) + if (pvaValuesEnum != null) { + var pvaValues = pvaValuesEnum.ToList(); + foreach (var pvaValue in pvaValues) { attributesTotalPrice += GetProductVariantAttributeValuePriceAdjustment(pvaValue); diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs index a1b5004361..839bbc1f05 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs @@ -137,11 +137,13 @@ public void Execute() .ForMember(dest => dest.FlagFileNames, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.LocaleStringResources, mo => mo.Ignore()); - //email account - Mapper.CreateMap() - .ForMember(dest => dest.IsDefaultEmailAccount, mo => mo.Ignore()) - .ForMember(dest => dest.SendTestEmailTo, mo => mo.Ignore()); - Mapper.CreateMap(); + //email account + Mapper.CreateMap() + .ForMember(dest => dest.IsDefaultEmailAccount, mo => mo.Ignore()) + .ForMember(dest => dest.SendTestEmailTo, mo => mo.Ignore()) + .ForMember(dest => dest.TestEmailShortErrorMessage, mo => mo.Ignore()) + .ForMember(dest => dest.TestEmailFullErrorMessage, mo => mo.Ignore()); + Mapper.CreateMap(); //message template Mapper.CreateMap() .ForMember(dest => dest.TokensTree, mo => mo.Ignore()) From 917906fa6315901985d5d8e23247ab2bc5c070cf Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 May 2016 19:40:54 +0200 Subject: [PATCH 031/185] Fixes #961: "Open Redirection Vulnerability" --- .../Authentication/External/AuthorizeState.cs | 2 +- .../ExternalAuthFacebookController.cs | 6 +- .../Controllers/SmartController.cs | 47 ++++++++++++++-- .../Controllers/PackagingController.cs | 13 ++--- .../Controllers/PluginController.cs | 7 +-- .../Controllers/ScheduleTaskController.cs | 7 +-- .../Controllers/SettingController.cs | 16 ++---- .../Controllers/ThemeController.cs | 9 +-- .../Controllers/CommonController.cs | 55 +++++-------------- .../Controllers/CustomerController.cs | 28 ++++------ .../ExternalAuthenticationController.cs | 4 +- .../Controllers/ShoppingCartController.cs | 9 +-- .../Views/Customer/RegisterResult.cshtml | 22 +++----- 13 files changed, 98 insertions(+), 127 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Authentication/External/AuthorizeState.cs b/src/Libraries/SmartStore.Services/Authentication/External/AuthorizeState.cs index dcc6c9762c..35e853bbfc 100644 --- a/src/Libraries/SmartStore.Services/Authentication/External/AuthorizeState.cs +++ b/src/Libraries/SmartStore.Services/Authentication/External/AuthorizeState.cs @@ -17,7 +17,7 @@ public AuthorizeState(string returnUrl, OpenAuthenticationStatus openAuthenticat _returnUrl = returnUrl; AuthenticationStatus = openAuthenticationStatus; - //in way SEO friendly language URLs will be persisted + // in a way SEO friendly language URLs will be persisted if (AuthenticationStatus == OpenAuthenticationStatus.Authenticated) Result = new RedirectResult(!string.IsNullOrEmpty(_returnUrl) ? _returnUrl : "~/"); } diff --git a/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs b/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs index abc3a709bd..5a99884991 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs +++ b/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs @@ -138,10 +138,10 @@ private ActionResult LoginInternal(string returnUrl, bool verifyResponse) if (result.Result != null) return result.Result; - return HttpContext.Request.IsAuthenticated ? - new RedirectResult(!string.IsNullOrEmpty(returnUrl) ? returnUrl : "~/") : + return HttpContext.Request.IsAuthenticated ? + RedirectToReferrer(returnUrl, "~/") : new RedirectResult(Url.LogOn(returnUrl)); - } + } public ActionResult Login(string returnUrl) { diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs index b5db948b54..c2127adf6b 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs @@ -113,14 +113,53 @@ protected virtual void NotifyAccessDenied(bool durable = true, bool log = true) Services.Notifier.Error(message, durable); } - protected virtual ActionResult RedirectToReferrer() + protected ActionResult RedirectToReferrer() { - if (Request.UrlReferrer != null && Request.UrlReferrer.ToString().HasValue()) + return RedirectToReferrer(null, () => RedirectToRoute("HomePage")); + } + + protected ActionResult RedirectToReferrer(string referrer) + { + return RedirectToReferrer(referrer, () => RedirectToRoute("HomePage")); + } + + protected ActionResult RedirectToReferrer(string referrer, string fallbackUrl) + { + // addressing "Open Redirection Vulnerability" (prevent cross-domain redirects / phishing) + if (fallbackUrl.HasValue() && !Url.IsLocalUrl(fallbackUrl)) { - return Redirect(Request.UrlReferrer.ToString()); + fallbackUrl = null; } - return RedirectToRoute("HomePage"); + return RedirectToReferrer( + referrer, + fallbackUrl.HasValue() ? () => Redirect(fallbackUrl) : (Func)null); + } + + protected virtual ActionResult RedirectToReferrer(string referrer, Func fallbackResult) + { + if (referrer.IsEmpty() && Request.UrlReferrer != null && Request.UrlReferrer.ToString().HasValue()) + { + referrer = Request.UrlReferrer.ToString(); + } + + // addressing "Open Redirection Vulnerability" (prevent cross-domain redirects / phishing) + if (referrer.HasValue() && !Url.IsLocalUrl(referrer)) + { + referrer = null; + } + + if (referrer.HasValue()) + { + return Redirect(referrer); + } + + if (fallbackResult != null) + { + return fallbackResult(); + } + + return HttpNotFound(); } protected virtual ActionResult RedirectToHomePageWithError(string reason, bool durable = true) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PackagingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PackagingController.cs index 9cbc47f5f6..12b1cb92dd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PackagingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PackagingController.cs @@ -45,11 +45,6 @@ public ActionResult UploadPackage(bool isTheme) [HttpPost] public ActionResult UploadPackage(FormCollection form, string returnUrl = "") { - if (returnUrl.IsEmpty()) - { - returnUrl = _services.WebHelper.GetUrlReferrer(); - } - bool isTheme = false; try @@ -69,7 +64,7 @@ public ActionResult UploadPackage(FormCollection form, string returnUrl = "") if (!file.FileExtension.IsCaseInsensitiveEqual(".nupkg")) { NotifyError(T("Admin.Packaging.NotAPackage")); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } var location = CommonHelper.MapPath("~/App_Data"); @@ -102,7 +97,7 @@ public ActionResult UploadPackage(FormCollection form, string returnUrl = "") else { NotifyError(T("Admin.Common.UploadFile")); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } if (!isTheme) @@ -110,13 +105,13 @@ public ActionResult UploadPackage(FormCollection form, string returnUrl = "") _services.WebHelper.RestartAppDomain(); } NotifySuccess(T("Admin.Packaging.InstallSuccess")); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } catch (Exception exc) { NotifyError(exc); Logger.Error(exc); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs index aebeec2e49..971c96eaa9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs @@ -574,12 +574,7 @@ public ActionResult UpdateStringResources(string systemName, string returnUrl = NotifySuccess(T("Admin.Configuration.Plugins.Resources.UpdateSuccess")); } - if (returnUrl.IsEmpty()) - { - return RedirectToAction("List"); - } - - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl, () => RedirectToAction("List")); } public ActionResult UpdateAllStringResources() diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs index 0239e8603f..453ae689c8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs @@ -172,7 +172,7 @@ public ActionResult RunJob(int id, string returnUrl = "") } } - return Redirect(returnUrl.NullEmpty() ?? Request.UrlReferrer.ToString()); + return RedirectToReferrer(returnUrl); } public ActionResult CancelJob(int id /* scheduleTaskId */, string returnUrl = "") @@ -187,8 +187,7 @@ public ActionResult CancelJob(int id /* scheduleTaskId */, string returnUrl = "" NotifyWarning(T("Admin.System.ScheduleTasks.CancellationRequested")); } - returnUrl = returnUrl.NullEmpty() ?? Request.UrlReferrer.ToString(); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } public ActionResult Edit(int id /* taskId */, string returnUrl = null) @@ -223,7 +222,7 @@ public ActionResult Edit(ScheduleTaskModel model, bool continueEditing, string r } var reloadResult = RedirectToAction("Edit", new { id = model.Id, returnUrl = returnUrl }); - var returnResult = Redirect(returnUrl.NullEmpty() ?? Url.Action("List")); + var returnResult = RedirectToReferrer(returnUrl, () => RedirectToAction("List")); var scheduleTask = _scheduleTaskService.GetTaskById(model.Id); if (scheduleTask == null) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs index 4d23358a6c..d26e835c28 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs @@ -139,14 +139,14 @@ public ActionResult StoreScopeConfiguration() if (allStores.Count < 2) return Content(""); - var model = new StoreScopeConfigurationModel() + var model = new StoreScopeConfigurationModel { StoreId = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext) }; foreach (var store in allStores) { - model.AllStores.Add(new SelectListItem() + model.AllStores.Add(new SelectListItem { Text = store.Name, Selected = (store.Id == model.StoreId), @@ -154,7 +154,7 @@ public ActionResult StoreScopeConfiguration() }); } - model.AllStores.Insert(0, new SelectListItem() + model.AllStores.Insert(0, new SelectListItem { Text = _services.Localization.GetResource("Admin.Common.StoresAll"), Selected = (0 == model.StoreId), @@ -172,16 +172,8 @@ public ActionResult ChangeStoreScopeConfiguration(int storeid, string returnUrl _genericAttributeService.SaveAttribute(_services.WorkContext.CurrentCustomer, SystemCustomerAttributeNames.AdminAreaStoreScopeConfiguration, storeid); } - - //url referrer - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = _services.WebHelper.GetUrlReferrer(); - - //home page - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = Url.Action("Index", "Home", new { area = "Admin" }); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl, () => RedirectToAction("Index", "Home", new { area = "Admin" })); } public ActionResult Blog() diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ThemeController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ThemeController.cs index 0e4129951d..9255a18675 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ThemeController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ThemeController.cs @@ -541,7 +541,7 @@ public ActionResult PreviewToolPost(string theme, int storeId, string returnUrl) _services.StoreContext.SetPreviewStore(storeId); } - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } [HttpPost, ActionName("PreviewTool"), FormValueRequired("PreviewMode.Exit")] @@ -556,12 +556,7 @@ public ActionResult ExitPreview() } var returnUrl = (string)TempData["PreviewModeReturnUrl"]; - if (returnUrl.IsEmpty()) - { - returnUrl = Url.Action("Index", "Home", new { area = (string)null }); - } - - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } [HttpPost, ActionName("PreviewTool"), FormValueRequired("PreviewMode.Apply")] diff --git a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs index 54b76c07b9..05cdc3bdc6 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs @@ -368,18 +368,6 @@ public ActionResult SetLanguage(int langid, string returnUrl = "") { _services.WorkContext.WorkingLanguage = language; } - - // url referrer - if (String.IsNullOrEmpty(returnUrl)) - { - returnUrl = _services.WebHelper.GetUrlReferrer(); - } - - // home page - if (String.IsNullOrEmpty(returnUrl)) - { - returnUrl = Url.RouteUrl("HomePage"); - } if (_localizationSettings.SeoFriendlyUrlsForLanguagesEnabled) { @@ -388,10 +376,9 @@ public ActionResult SetLanguage(int langid, string returnUrl = "") returnUrl = helper.GetAbsolutePath(); } - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } - //currency [ChildActionOnly] public ActionResult CurrencySelector() { @@ -402,40 +389,31 @@ public ActionResult CurrencySelector() return PartialView(model); } + public ActionResult CurrencySelected(int customerCurrency, string returnUrl = "") { var currency = _currencyService.Value.GetCurrencyById(customerCurrency); if (currency != null) + { _services.WorkContext.WorkingCurrency = currency; + } - //url referrer - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = _services.WebHelper.GetUrlReferrer(); - //home page - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = Url.RouteUrl("HomePage"); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } - //tax type [ChildActionOnly] public ActionResult TaxTypeSelector() { var model = PrepareTaxTypeSelectorModel(); return PartialView(model); } + public ActionResult TaxTypeSelected(int customerTaxType, string returnUrl = "") { var taxDisplayType = (TaxDisplayType)Enum.ToObject(typeof(TaxDisplayType), customerTaxType); _services.WorkContext.TaxDisplayType = taxDisplayType; - //url referrer - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = _services.WebHelper.GetUrlReferrer(); - //home page - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = Url.RouteUrl("HomePage"); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } //Configuration page (used on mobile devices) @@ -763,12 +741,7 @@ public ActionResult ChangeTheme(string themeName, string returnUrl = null) return Json(new { Success = true }); } - if (returnUrl.IsEmpty()) - { - return RedirectToRoute("HomePage"); - } - - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } [ChildActionOnly] @@ -814,13 +787,13 @@ public ActionResult Favicon() [HttpPost] public ActionResult ChangeDevice(bool dontUseMobileVersion) { - _genericAttributeService.Value.SaveAttribute(_services.WorkContext.CurrentCustomer, - SystemCustomerAttributeNames.DontUseMobileVersion, dontUseMobileVersion, _services.StoreContext.CurrentStore.Id); + _genericAttributeService.Value.SaveAttribute( + _services.WorkContext.CurrentCustomer, + SystemCustomerAttributeNames.DontUseMobileVersion, + dontUseMobileVersion, + _services.StoreContext.CurrentStore.Id); - string returnurl = _services.WebHelper.GetUrlReferrer(); - if (String.IsNullOrEmpty(returnurl)) - returnurl = Url.RouteUrl("HomePage"); - return Redirect(returnurl); + return RedirectToReferrer(); } [ChildActionOnly] diff --git a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs index cd0f271796..9ff54c6482 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs @@ -462,10 +462,7 @@ public ActionResult Login(LoginModel model, string returnUrl, bool captchaValid) //activity log _customerActivityService.InsertActivity("PublicStore.Login", _localizationService.GetResource("ActivityLog.PublicStore.Login"), customer); - if (!String.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl)) - return Redirect(returnUrl); - else - return RedirectToRoute("HomePage"); + return RedirectToReferrer(returnUrl); } else { @@ -579,12 +576,12 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha var registrationResult = _customerRegistrationService.RegisterCustomer(registrationRequest); if (registrationResult.Success) { - //properties + // properties if (_dateTimeSettings.AllowCustomersToSetTimeZone) { _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.TimeZoneId, model.TimeZoneId); } - //VAT number + // VAT number if (_taxSettings.EuVatEnabled) { _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.VatNumber, model.VatNumber); @@ -595,12 +592,12 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.VatNumberStatusId, (int)vatNumberStatus); - //send VAT number admin notification + // send VAT number admin notification if (!String.IsNullOrEmpty(model.VatNumber) && _taxSettings.EuVatEmailAdminWhenNewVatSubmitted) _workflowMessageService.SendNewVatSubmittedStoreOwnerNotification(customer, model.VatNumber, vatAddress, _localizationSettings.DefaultAdminLanguageId); } - //form fields + // form fields if (_customerSettings.GenderEnabled) _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Gender, model.Gender); _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.FirstName, model.FirstName); @@ -636,10 +633,10 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha if (_customerSettings.CustomerNumberMethod == CustomerNumberMethod.AutomaticallySet && String.IsNullOrEmpty(customer.GetAttribute(SystemCustomerAttributeNames.CustomerNumber))) _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CustomerNumber, customer.Id); - //newsletter + // newsletter if (_customerSettings.NewsletterEnabled) { - //save newsletter value + // save newsletter value var newsletter = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(model.Email, _storeContext.CurrentStore.Id); if (newsletter != null) { @@ -718,11 +715,10 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha { case UserRegistrationType.EmailValidation: { - //email validation message + // email validation message _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.AccountActivationToken, Guid.NewGuid().ToString()); _workflowMessageService.SendCustomerEmailValidationMessage(customer, _workContext.WorkingLanguage.Id); - //result return RedirectToRoute("RegisterResult", new { resultId = (int)UserRegistrationType.EmailValidation }); } case UserRegistrationType.AdminApproval: @@ -731,7 +727,7 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha } case UserRegistrationType.Standard: { - //send customer welcome message + // send customer welcome message _workflowMessageService.SendCustomerWelcomeMessage(customer, _workContext.WorkingLanguage.Id); var redirectUrl = Url.RouteUrl("RegisterResult", new { resultId = (int)UserRegistrationType.Standard }); @@ -828,10 +824,8 @@ public ActionResult RegisterResult(int resultId) default: break; } - var model = new RegisterResultModel - { - Result = resultText - }; + + var model = new RegisterResultModel { Result = resultText }; return View(model); } diff --git a/src/Presentation/SmartStore.Web/Controllers/ExternalAuthenticationController.cs b/src/Presentation/SmartStore.Web/Controllers/ExternalAuthenticationController.cs index f95f0ef4d2..3fc35cd8db 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ExternalAuthenticationController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ExternalAuthenticationController.cs @@ -31,10 +31,10 @@ public ExternalAuthenticationController(IOpenAuthenticationService openAuthentic #region Methods - public RedirectResult RemoveParameterAssociation(string returnUrl) + public ActionResult RemoveParameterAssociation(string returnUrl) { ExternalAuthorizerHelper.RemoveParameters(); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } [ChildActionOnly] diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index 18019458f4..a61c57d5ad 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -1749,14 +1749,7 @@ public ActionResult DeleteCartItem(int cartItemId, bool? wishlistItem) public ActionResult ContinueShopping() { string returnUrl = _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.LastContinueShoppingPage, _storeContext.CurrentStore.Id); - if (!String.IsNullOrEmpty(returnUrl)) - { - return Redirect(returnUrl); - } - else - { - return RedirectToRoute("HomePage"); - } + return RedirectToReferrer(returnUrl); } [ValidateInput(false)] diff --git a/src/Presentation/SmartStore.Web/Views/Customer/RegisterResult.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/RegisterResult.cshtml index a3a9ac9314..c4bcbfbfdb 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/RegisterResult.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/RegisterResult.cshtml @@ -4,21 +4,17 @@ @using SmartStore.Core.Infrastructure; @using SmartStore.Core.Infrastructure.DependencyManagement; @{ - Layout = "~/Views/Shared/_ColumnsThree.cshtml"; + Layout = "~/Views/Shared/_ColumnsThree.cshtml"; - //title - Html.AddTitleParts(T("PageTitle.Register").Text); + // title + Html.AddTitleParts(T("PageTitle.Register").Text); - //continue URL - var continueUrl = ""; - if (!String.IsNullOrEmpty(this.Context.Request.QueryString["returnUrl"])) - { - continueUrl = this.Context.Request.QueryString["returnUrl"]; - } - else - { - continueUrl = Url.RouteUrl("HomePage"); - } + // continue URL + var continueUrl = this.Context.Request.QueryString["returnUrl"]; + if (!continueUrl.HasValue() || (continueUrl.HasValue() && !Url.IsLocalUrl(continueUrl))) + { + continueUrl = Url.RouteUrl("HomePage"); + } }
From e91fa05b9dc8b135a583bc283f436688c1e849a7 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 May 2016 20:00:35 +0200 Subject: [PATCH 032/185] Added shortcut extensions methods to ICommonServices for resolution of other services --- .../SmartStore.Services/ICommonServices.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/Libraries/SmartStore.Services/ICommonServices.cs b/src/Libraries/SmartStore.Services/ICommonServices.cs index ee16a33432..f9b3043bf0 100644 --- a/src/Libraries/SmartStore.Services/ICommonServices.cs +++ b/src/Libraries/SmartStore.Services/ICommonServices.cs @@ -86,4 +86,37 @@ IDateTimeHelper DateTimeHelper get; } } + + public static class ICommonServicesExtensions + { + public static TService Resolve(this ICommonServices services) + { + return services.Container.Resolve(); + } + + public static TService Resolve(this ICommonServices services, object serviceKey) + { + return services.Container.ResolveKeyed(serviceKey); + } + + public static TService ResolveNamed(this ICommonServices services, string serviceName) + { + return services.Container.ResolveNamed(serviceName); + } + + public static object Resolve(this ICommonServices services, Type serviceType) + { + return services.Resolve(null, serviceType); + } + + public static object Resolve(this ICommonServices services, object serviceKey, Type serviceType) + { + return services.Container.ResolveKeyed(serviceKey, serviceType); + } + + public static object ResolveNamed(this ICommonServices services, string serviceName, Type serviceType) + { + return services.Container.ResolveNamed(serviceName, serviceType); + } + } } From bb917685463f4674651044f7f28e10251e5e0471 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 11 May 2016 20:46:27 +0200 Subject: [PATCH 033/185] Mobile devices: Fixed "Uncaught Error: Syntax error, unrecognized expression: :nth-child" --- changelog.md | 1 + .../Views/Common/InfoBlock.Mobile.cshtml | 122 ++++++++++-------- 2 files changed, 68 insertions(+), 55 deletions(-) diff --git a/changelog.md b/changelog.md index d538dea8d6..8bd6ef243c 100644 --- a/changelog.md +++ b/changelog.md @@ -35,6 +35,7 @@ * #949 Import: ProcessSlugs does not process explicitly specified "SeName", if product name did not change in an update scenario * Customer import: Creates customer role duplicates for inserted customers * GMC feed does not generate the sale price if the sale price is set for a future date +* Mobile devices: Fixed "Uncaught Error: Syntax error, unrecognized expression: :nth-child" ## SmartStore.NET 2.5 diff --git a/src/Presentation/SmartStore.Web/Views/Common/InfoBlock.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Common/InfoBlock.Mobile.cshtml index 343176764c..4085b4d143 100644 --- a/src/Presentation/SmartStore.Web/Views/Common/InfoBlock.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Common/InfoBlock.Mobile.cshtml @@ -1,62 +1,74 @@ @model InfoBlockModel @using SmartStore.Web.Models.Common; @Html.Widget("mobile_infoblock_before") -
- -
-
-
    - @if (Model.TopicPageUrls.ContainsKey("paymentinfo")) +@if (Model.TopicPageUrls.ContainsKey("contactus") || Model.TopicPageUrls.ContainsKey("aboutus") || Model.BlogEnabled || Model.ForumEnabled) { -
  • @T("Paymentinfo")
  • - } - @if (Model.TopicPageUrls.ContainsKey("shippinginfo")) - { -
  • @T("ShippingReturns")
  • - } -
-
-
- -
-
-
    - @if (Model.TopicPageUrls.ContainsKey("imprint")) - { -
  • @T("Imprint")
  • - } - @if (Model.TopicPageUrls.ContainsKey("disclaimer")) - { -
  • @T("Disclaimer")
  • - } -
-
+
+ +
+} +@if (Model.TopicPageUrls.ContainsKey("paymentinfo") || Model.TopicPageUrls.ContainsKey("shippinginfo")) +{ +
+ +
+} +@if (Model.TopicPageUrls.ContainsKey("privacyinfo") || Model.TopicPageUrls.ContainsKey("conditionsofuse")) +{ +
+ +
+} +@if (Model.TopicPageUrls.ContainsKey("imprint") || Model.TopicPageUrls.ContainsKey("disclaimer")) +{ +
+
    + @if (Model.TopicPageUrls.ContainsKey("imprint")) + { +
  • @T("Imprint")
  • + } + @if (Model.TopicPageUrls.ContainsKey("disclaimer")) + { +
  • @T("Disclaimer")
  • + } +
+
+} @if (Model.AllowPrivateMessages) {
From 78b5cf1836c6439d5e1a6ff3cae19a6f0257d76d Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 May 2016 22:14:00 +0200 Subject: [PATCH 034/185] Import refactoring: added DataExchangeSettings to ImportExecuteContext --- .../Catalog/Importer/CategoryImporter.cs | 9 +-- .../Catalog/Importer/ProductImporter.cs | 8 +- .../Customers/Importer/CustomerImporter.cs | 7 +- .../DataExchange/Import/DataImporter.cs | 9 ++- .../DataExchange/Import/EntityImporterBase.cs | 10 ++- .../Import/ImportExecuteContext.cs | 81 ++++++++++++++++--- 6 files changed, 87 insertions(+), 37 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index fb99e870f6..8a94dd461c 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -34,7 +34,6 @@ public class CategoryImporter : EntityImporterBase private readonly ILocalizedEntityService _localizedEntityService; private readonly FileDownloadManager _fileDownloadManager; private readonly SeoSettings _seoSettings; - private readonly DataExchangeSettings _dataExchangeSettings; private static readonly Dictionary>> _localizableProperties = new Dictionary>> { @@ -59,8 +58,7 @@ public CategoryImporter( IPictureService pictureService, ILocalizedEntityService localizedEntityService, FileDownloadManager fileDownloadManager, - SeoSettings seoSettings, - DataExchangeSettings dataExchangeSettings) + SeoSettings seoSettings) { _categoryRepository = categoryRepository; _urlRecordRepository = urlRecordRepository; @@ -74,7 +72,6 @@ public CategoryImporter( _localizedEntityService = localizedEntityService; _fileDownloadManager = fileDownloadManager; _seoSettings = seoSettings; - _dataExchangeSettings = dataExchangeSettings; } protected virtual int ProcessSlugs(IImportExecuteContext context, IEnumerable> batch) @@ -477,11 +474,11 @@ protected override void Import(IImportExecuteContext context) var templateViewPaths = _categoryTemplateService.GetAllCategoryTemplates().ToDictionarySafe(x => x.ViewPath, x => x.Id); - using (var scope = new DbContextScope(ctx: _categoryRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) + using (var scope = new DbContextScope(ctx: context.Services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { var segmenter = context.CreateSegmenter(); - Init(context, _dataExchangeSettings); + Initialize(context); var localizedProperties = ResolveLocalizedProperties(segmenter).ToArray(); diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 7f98b1d17a..6aab9d005a 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -39,7 +39,6 @@ public class ProductImporter : EntityImporterBase private readonly IStoreMappingService _storeMappingService; private readonly FileDownloadManager _fileDownloadManager; private readonly SeoSettings _seoSettings; - private readonly DataExchangeSettings _dataExchangeSettings; private static readonly Dictionary>> _localizableProperties = new Dictionary>> { @@ -69,8 +68,7 @@ public ProductImporter( IProductTemplateService productTemplateService, IStoreMappingService storeMappingService, FileDownloadManager fileDownloadManager, - SeoSettings seoSettings, - DataExchangeSettings dataExchangeSettings) + SeoSettings seoSettings) { _productPictureRepository = productPictureRepository; _productManufacturerRepository = productManufacturerRepository; @@ -88,9 +86,7 @@ public ProductImporter( _productTemplateService = productTemplateService; _storeMappingService = storeMappingService; _fileDownloadManager = fileDownloadManager; - _seoSettings = seoSettings; - _dataExchangeSettings = dataExchangeSettings; } private int? ZeroToNull(object value, CultureInfo culture) @@ -740,7 +736,7 @@ protected override void Import(IImportExecuteContext context) using (var scope = new DbContextScope(ctx: _productRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { var segmenter = context.CreateSegmenter(); - Init(context, _dataExchangeSettings); + Initialize(context); var localizedProperties = ResolveLocalizedProperties(segmenter).ToArray(); diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index fd949b85d9..528bc5d7d1 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -39,7 +39,6 @@ public class CustomerImporter : EntityImporterBase private readonly CustomerSettings _customerSettings; private readonly DateTimeSettings _dateTimeSettings; private readonly ForumSettings _forumSettings; - private readonly DataExchangeSettings _dataExchangeSettings; public CustomerImporter( IRepository customerRepository, @@ -54,8 +53,7 @@ public CustomerImporter( FileDownloadManager fileDownloadManager, CustomerSettings customerSettings, DateTimeSettings dateTimeSettings, - ForumSettings forumSettings, - DataExchangeSettings dataExchangeSettings) + ForumSettings forumSettings) { _customerRepository = customerRepository; _pictureRepository = pictureRepository; @@ -70,7 +68,6 @@ public CustomerImporter( _customerSettings = customerSettings; _dateTimeSettings = dateTimeSettings; _forumSettings = forumSettings; - _dataExchangeSettings = dataExchangeSettings; } private int? CountryCodeToId(Dictionary allCountries, string code) @@ -580,7 +577,7 @@ protected override void Import(IImportExecuteContext context) { var segmenter = context.CreateSegmenter(); - Init(context, _dataExchangeSettings); + Initialize(context); context.Result.TotalRecords = segmenter.TotalRows; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs index 9391d96c71..1739295b85 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs @@ -27,6 +27,7 @@ public partial class DataImporter : IDataImporter private readonly Lazy _emailAccountService; private readonly Lazy _emailSender; private readonly Lazy _contactDataSettings; + private readonly Lazy _dataExchangeSettings; public DataImporter( ICommonServices services, @@ -35,8 +36,8 @@ public DataImporter( Func importerFactory, Lazy emailAccountService, Lazy emailSender, - Lazy dataExchangeSettings, - Lazy contactDataSettings) + Lazy contactDataSettings, + Lazy dataExchangeSettings) { _services = services; _importProfileService = importProfileService; @@ -45,6 +46,7 @@ public DataImporter( _emailAccountService = emailAccountService; _emailSender = emailSender; _contactDataSettings = contactDataSettings; + _dataExchangeSettings = dataExchangeSettings; T = NullLocalizer.Instance; } @@ -253,6 +255,7 @@ private void ImportCoreOuter(DataImporterContext ctx) { ctx.Log = logger; + ctx.ExecuteContext.DataExchangeSettings = _dataExchangeSettings.Value; ctx.ExecuteContext.Services = _services; ctx.ExecuteContext.Log = logger; ctx.ExecuteContext.Languages = _languageService.GetAllLanguages(true); @@ -269,7 +272,7 @@ private void ImportCoreOuter(DataImporterContext ctx) var files = ctx.Request.Profile.GetImportFiles(); if (files.Count == 0) - throw new SmartException("No files to import found."); + throw new SmartException("No files to import."); if (!HasPermission(ctx)) throw new SmartException("You do not have permission to perform the selected import."); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index 4423364e73..e0e3cba1f5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -53,14 +53,16 @@ public void Execute(IImportExecuteContext context) protected abstract void Import(IImportExecuteContext context); - public void Init(IImportExecuteContext context, DataExchangeSettings dataExchangeSettings) + protected void Initialize(IImportExecuteContext context) { UtcNow = DateTime.UtcNow; DownloadedItems = new Dictionary(); ImageDownloadFolder = Path.Combine(context.ImportFolder, _imageDownloadFolder); - if (dataExchangeSettings.ImageImportFolder.HasValue()) - ImageFolder = Path.Combine(context.ImportFolder, dataExchangeSettings.ImageImportFolder); + var settings = context.DataExchangeSettings; + + if (settings.ImageImportFolder.HasValue()) + ImageFolder = Path.Combine(context.ImportFolder, settings.ImageImportFolder); else ImageFolder = context.ImportFolder; @@ -69,7 +71,7 @@ public void Init(IImportExecuteContext context, DataExchangeSettings dataExchang DownloaderContext = new FileDownloadManagerContext { - Timeout = TimeSpan.FromMinutes(dataExchangeSettings.ImageDownloadTimeout), + Timeout = TimeSpan.FromMinutes(settings.ImageDownloadTimeout), Logger = context.Log, CancellationToken = context.CancellationToken }; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index cc69ceec35..339e662706 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -8,6 +8,8 @@ namespace SmartStore.Services.DataExchange.Import { public interface IImportExecuteContext { + DataExchangeSettings DataExchangeSettings { get; } + /// /// Whether to only update existing records /// @@ -66,7 +68,6 @@ public interface IImportExecuteContext /// /// Creates a segmenter instance /// - /// /// ImportDataSegmenter CreateSegmenter(); @@ -98,32 +99,86 @@ public ImportExecuteContext( Result = new ImportResult(); } - public IDataTable DataTable { get; internal set; } + public DataExchangeSettings DataExchangeSettings + { + get; + internal set; + } - public ColumnMap ColumnMap { get; internal set; } + public IDataTable DataTable + { + get; + internal set; + } + + public ColumnMap ColumnMap + { + get; + internal set; + } - public bool UpdateOnly { get; internal set; } + public bool UpdateOnly + { + get; + internal set; + } - public string[] KeyFieldNames { get; internal set; } + public string[] KeyFieldNames + { + get; + internal set; + } - public IList Languages { get; internal set; } + public IList Languages + { + get; + internal set; + } - public ILogger Log { get; internal set; } + public ILogger Log + { + get; + internal set; + } - public ICommonServices Services { get; internal set; } + public ICommonServices Services + { + get; + internal set; + } - public CancellationToken CancellationToken { get; private set; } + public CancellationToken CancellationToken + { + get; + private set; + } - public string ImportFolder { get; internal set; } + public string ImportFolder + { + get; + internal set; + } /// /// Use this dictionary for any custom data required along the import /// - public Dictionary CustomProperties { get; set; } + public Dictionary CustomProperties + { + get; + set; + } - public ImportResult Result { get; set; } + public ImportResult Result + { + get; + set; + } - public ImportExtraData ExtraData { get; internal set; } + public ImportExtraData ExtraData + { + get; + internal set; + } public DataExchangeAbortion Abort { From 581496a40c0f2cd2473ff375c8961b20cc2bc89c Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 May 2016 22:24:37 +0200 Subject: [PATCH 035/185] Import refactoring: removed IImportExecuteContext interface (the concrete type is sufficient) --- .../Catalog/Importer/CategoryImporter.cs | 14 +- .../Catalog/Importer/ProductImporter.cs | 18 +-- .../Customers/Importer/CustomerImporter.cs | 12 +- .../DataExchange/Import/EntityImporterBase.cs | 6 +- .../DataExchange/Import/IEntityImporter.cs | 2 +- .../Import/ImportExecuteContext.cs | 120 +++++++----------- .../NewsLetterSubscriptionImporter.cs | 2 +- 7 files changed, 72 insertions(+), 102 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 8a94dd461c..84fb723e62 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -74,7 +74,7 @@ public CategoryImporter( _seoSettings = seoSettings; } - protected virtual int ProcessSlugs(IImportExecuteContext context, IEnumerable> batch) + protected virtual int ProcessSlugs(ImportExecuteContext context, IEnumerable> batch) { var entityName = typeof(Category).Name; var slugMap = new Dictionary(); @@ -148,7 +148,7 @@ protected virtual int ProcessSlugs(IImportExecuteContext context, IEnumerable> batch, string[] localizedProperties) { @@ -188,7 +188,7 @@ protected virtual int ProcessLocalizations( } protected virtual int ProcessParentMappings( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, Dictionary srcToDestId) { @@ -220,7 +220,7 @@ protected virtual int ProcessParentMappings( } protected virtual int ProcessPictures( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, Dictionary srcToDestId) { @@ -294,7 +294,7 @@ protected virtual int ProcessPictures( return num; } - protected virtual int ProcessStoreMappings(IImportExecuteContext context, IEnumerable> batch) + protected virtual int ProcessStoreMappings(ImportExecuteContext context, IEnumerable> batch) { _storeMappingRepository.AutoCommitEnabled = false; @@ -312,7 +312,7 @@ protected virtual int ProcessStoreMappings(IImportExecuteContext context, IEnume } protected virtual int ProcessCategories( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, Dictionary templateViewPaths, Dictionary srcToDestId) @@ -468,7 +468,7 @@ protected override IDictionary>> GetLo return _localizableProperties; } - protected override void Import(IImportExecuteContext context) + protected override void Import(ImportExecuteContext context) { var srcToDestId = new Dictionary(); diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 6aab9d005a..b3b8a5d3ec 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -101,7 +101,7 @@ public ProductImporter( } protected virtual int ProcessProductMappings( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, Dictionary srcToDestId) { @@ -132,7 +132,7 @@ protected virtual int ProcessProductMappings( return num; } - protected virtual void ProcessProductPictures(IImportExecuteContext context, IEnumerable> batch) + protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnumerable> batch) { // true, cause pictures must be saved and assigned an id prior adding a mapping. _productPictureRepository.AutoCommitEnabled = true; @@ -243,7 +243,7 @@ protected virtual void ProcessProductPictures(IImportExecuteContext context, IEn } } - protected virtual int ProcessProductManufacturers(IImportExecuteContext context, IEnumerable> batch) + protected virtual int ProcessProductManufacturers(ImportExecuteContext context, IEnumerable> batch) { _productManufacturerRepository.AutoCommitEnabled = false; @@ -294,7 +294,7 @@ protected virtual int ProcessProductManufacturers(IImportExecuteContext context, return num; } - protected virtual int ProcessProductCategories(IImportExecuteContext context, IEnumerable> batch) + protected virtual int ProcessProductCategories(ImportExecuteContext context, IEnumerable> batch) { _productCategoryRepository.AutoCommitEnabled = false; @@ -346,7 +346,7 @@ protected virtual int ProcessProductCategories(IImportExecuteContext context, IE } protected virtual int ProcessLocalizations( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, string[] localizedProperties) { @@ -386,7 +386,7 @@ protected virtual int ProcessLocalizations( } protected virtual int ProcessSlugs( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, string[] columnIndexes) { @@ -465,7 +465,7 @@ protected virtual int ProcessSlugs( return _urlRecordRepository.Context.SaveChanges(); } - protected virtual int ProcessStoreMappings(IImportExecuteContext context, IEnumerable> batch) + protected virtual int ProcessStoreMappings(ImportExecuteContext context, IEnumerable> batch) { _storeMappingRepository.AutoCommitEnabled = false; @@ -483,7 +483,7 @@ protected virtual int ProcessStoreMappings(IImportExecuteContext context, IEnume } protected virtual int ProcessProducts( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, Dictionary templateViewPaths, Dictionary srcToDestId) @@ -727,7 +727,7 @@ protected override IDictionary>> GetLoc return _localizableProperties; } - protected override void Import(IImportExecuteContext context) + protected override void Import(ImportExecuteContext context) { var srcToDestId = new Dictionary(); diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 528bc5d7d1..ed37970740 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -137,7 +137,7 @@ private void UpsertRole(ImportRow row, CustomerRole role, bool value) } protected virtual int ProcessAddresses( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, Dictionary allCountries, Dictionary, int> allStateProvinces) @@ -154,7 +154,7 @@ protected virtual int ProcessAddresses( private void ImportAddress( string fieldPrefix, ImportRow row, - IImportExecuteContext context, + ImportExecuteContext context, Dictionary allCountries, Dictionary, int> allStateProvinces) { @@ -227,7 +227,7 @@ private void ImportAddress( } protected virtual int ProcessGenericAttributes( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, Dictionary allCountries, Dictionary, int> allStateProvinces, @@ -313,7 +313,7 @@ protected virtual int ProcessGenericAttributes( } protected virtual int ProcessAvatars( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch) { foreach (var row in batch) @@ -376,7 +376,7 @@ protected virtual int ProcessAvatars( } protected virtual int ProcessCustomers( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, List allAffiliateIds) { @@ -547,7 +547,7 @@ protected override IDictionary>> GetLo throw new NotImplementedException(); } - protected override void Import(IImportExecuteContext context) + protected override void Import(ImportExecuteContext context) { var customer = _services.WorkContext.CurrentCustomer; var allowManagingCustomerRoles = _services.Permissions.Authorize(StandardPermissionProvider.ManageCustomerRoles, customer); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index e0e3cba1f5..44af3a266a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -46,14 +46,14 @@ public FileDownloadManagerContext DownloaderContext private set; } - public void Execute(IImportExecuteContext context) + public void Execute(ImportExecuteContext context) { Import(context); } - protected abstract void Import(IImportExecuteContext context); + protected abstract void Import(ImportExecuteContext context); - protected void Initialize(IImportExecuteContext context) + protected void Initialize(ImportExecuteContext context) { UtcNow = DateTime.UtcNow; DownloadedItems = new Dictionary(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/IEntityImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/IEntityImporter.cs index a33f2a3ccd..bc64b63531 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/IEntityImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/IEntityImporter.cs @@ -2,6 +2,6 @@ { public partial interface IEntityImporter { - void Execute(IImportExecuteContext context); + void Execute(ImportExecuteContext context); } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index 339e662706..8786b5e311 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -6,81 +6,7 @@ namespace SmartStore.Services.DataExchange.Import { - public interface IImportExecuteContext - { - DataExchangeSettings DataExchangeSettings { get; } - - /// - /// Whether to only update existing records - /// - bool UpdateOnly { get; } - - /// - /// Name of key fields to identify existing records for updating - /// - string[] KeyFieldNames { get; } - - /// - /// The import folder - /// - string ImportFolder { get; } - - /// - /// Use this dictionary for any custom data required along the export - /// - Dictionary CustomProperties { get; set; } - - /// - /// All languages - /// - IList Languages { get; } - - /// - /// To log information into the import log file - /// - ILogger Log { get; } - - /// - /// Common Services - /// - ICommonServices Services { get; } - - /// - /// Cancellation token - /// - CancellationToken CancellationToken { get; } - - /// - /// Result of the import - /// - ImportResult Result { get; set; } - - /// - /// Extra import configuration data - /// - ImportExtraData ExtraData { get; } - - /// - /// Indicates whether and how to abort the import - /// - DataExchangeAbortion Abort { get; set; } - - /// - /// Creates a segmenter instance - /// - /// - ImportDataSegmenter CreateSegmenter(); - - /// - /// Allows to set a progress message - /// - /// Progress value - /// /// Progress maximum - void SetProgress(int value, int maximum); - } - - - public class ImportExecuteContext : IImportExecuteContext + public class ImportExecuteContext { private DataExchangeAbortion _abortion; private ProgressValueSetter _progressValueSetter; @@ -99,60 +25,90 @@ public ImportExecuteContext( Result = new ImportResult(); } + /// + /// Import settings + /// public DataExchangeSettings DataExchangeSettings { get; internal set; } + /// + /// The data source (CSV, Excel etc.) + /// public IDataTable DataTable { get; internal set; } + /// + /// Mapping information between database and data source + /// public ColumnMap ColumnMap { get; internal set; } + /// + /// Whether to only update existing records + /// public bool UpdateOnly { get; internal set; } + /// + /// Name of key fields to identify existing records for updating + /// public string[] KeyFieldNames { get; internal set; } + /// + /// All active languages + /// public IList Languages { get; internal set; } + /// + /// To log information into the import log file + /// public ILogger Log { get; internal set; } + /// + /// Common Services + /// public ICommonServices Services { get; internal set; } + /// + /// Cancellation token + /// public CancellationToken CancellationToken { get; private set; } + /// + /// The import folder + /// public string ImportFolder { get; @@ -168,18 +124,27 @@ public Dictionary CustomProperties set; } + /// + /// Result of the import + /// public ImportResult Result { get; set; } + /// + /// Extra import configuration data + /// public ImportExtraData ExtraData { get; internal set; } + /// + /// Indicates whether and how to abort the import + /// public DataExchangeAbortion Abort { get @@ -208,6 +173,11 @@ public ImportDataSegmenter CreateSegmenter() return new ImportDataSegmenter(DataTable, ColumnMap); } + /// + /// Allows to set a progress message + /// + /// Progress value + /// /// Progress maximum public void SetProgress(int value, int maximum) { try diff --git a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs index cb89878d6b..263cb3dfc6 100644 --- a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs +++ b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs @@ -36,7 +36,7 @@ public static string[] DefaultKeyFields } } - public void Execute(IImportExecuteContext context) + public void Execute(ImportExecuteContext context) { var utcNow = DateTime.UtcNow; var currentStoreId = _services.StoreContext.CurrentStore.Id; From 18651fdef1eb529eae88858a7d3ac4074a873ee6 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 May 2016 22:40:26 +0200 Subject: [PATCH 036/185] Import refactoring: CreateDataSegmenter() > DataSegmenter --- .../Catalog/Importer/CategoryImporter.cs | 4 +-- .../Catalog/Importer/ProductImporter.cs | 4 +-- .../Customers/Importer/CustomerImporter.cs | 4 +-- .../DataExchange/Import/EntityImporterBase.cs | 2 ++ .../Import/ImportDataSegmenter.cs | 24 --------------- .../Import/ImportExecuteContext.cs | 30 ++++++++++++++++--- .../NewsLetterSubscriptionImporter.cs | 2 +- 7 files changed, 32 insertions(+), 38 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 84fb723e62..fae01e82ec 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -476,14 +476,12 @@ protected override void Import(ImportExecuteContext context) using (var scope = new DbContextScope(ctx: context.Services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { - var segmenter = context.CreateSegmenter(); + var segmenter = context.DataSegmenter; Initialize(context); var localizedProperties = ResolveLocalizedProperties(segmenter).ToArray(); - context.Result.TotalRecords = segmenter.TotalRows; - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { var batch = segmenter.GetCurrentBatch(); diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index b3b8a5d3ec..4197839911 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -735,13 +735,11 @@ protected override void Import(ImportExecuteContext context) using (var scope = new DbContextScope(ctx: _productRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { - var segmenter = context.CreateSegmenter(); + var segmenter = context.DataSegmenter; Initialize(context); var localizedProperties = ResolveLocalizedProperties(segmenter).ToArray(); - context.Result.TotalRecords = segmenter.TotalRows; - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { var batch = segmenter.GetCurrentBatch(); diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index ed37970740..3eec752b0b 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -575,12 +575,10 @@ protected override void Import(ImportExecuteContext context) using (var scope = new DbContextScope(ctx: _services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) { - var segmenter = context.CreateSegmenter(); + var segmenter = context.DataSegmenter; Initialize(context); - context.Result.TotalRecords = segmenter.TotalRows; - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { var batch = segmenter.GetCurrentBatch(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index 44af3a266a..7d2f08871e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -75,6 +75,8 @@ protected void Initialize(ImportExecuteContext context) Logger = context.Log, CancellationToken = context.CancellationToken }; + + context.Result.TotalRecords = context.DataSegmenter.TotalRows; } public FileDownloadManagerItem CreateDownloadImage(string urlOrPath, string seoName, int displayOrder) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs index d4a1fc01d6..41129e24f8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs @@ -219,30 +219,6 @@ public bool ReadNextBatch() return false; } - //public IEnumerable> CurrentBatch - //{ - // get - // { - // if (_currentBatch == null) - // { - // int start = _pageable.FirstItemIndex - 1; - // int end = _pageable.LastItemIndex - 1; - - // _currentBatch = new ImportRow[(end - start) + 1]; - - // // Determine values per row - // int i = 0; - // for (int r = start; r <= end; r++) - // { - // _currentBatch[i] = new ImportRow(this, _table.Rows[r], r); - // i++; - // } - // } - - // return _currentBatch.Cast>(); - // } - //} - public IEnumerable> GetCurrentBatch() where T : BaseEntity { if (_currentBatch == null) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index 8786b5e311..67e3c090a1 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -12,6 +12,9 @@ public class ImportExecuteContext private ProgressValueSetter _progressValueSetter; private string _progressInfo; + private IDataTable _dataTable; + private ImportDataSegmenter _segmenter; + public ImportExecuteContext( CancellationToken cancellation, ProgressValueSetter progressValueSetter, @@ -39,8 +42,15 @@ public DataExchangeSettings DataExchangeSettings /// public IDataTable DataTable { - get; - internal set; + get + { + return _dataTable; + } + internal set + { + _dataTable = value; + _segmenter = null; + } } /// @@ -168,9 +178,21 @@ public bool IsMaxFailures } } - public ImportDataSegmenter CreateSegmenter() + public ImportDataSegmenter DataSegmenter { - return new ImportDataSegmenter(DataTable, ColumnMap); + get + { + if (_segmenter == null) + { + if (this.DataTable == null || this.ColumnMap == null) + { + throw new SmartException("A DataTable and a ColumnMap must be specified before accessing the DataSegmenter property."); + } + _segmenter = new ImportDataSegmenter(DataTable, ColumnMap); + } + + return _segmenter; + } } /// diff --git a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs index 263cb3dfc6..979051d1d6 100644 --- a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs +++ b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs @@ -43,7 +43,7 @@ public void Execute(ImportExecuteContext context) using (var scope = new DbContextScope(ctx: _services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) { - var segmenter = context.CreateSegmenter(); + var segmenter = context.DataSegmenter; context.Result.TotalRecords = segmenter.TotalRows; From 224b3c97514c1787d7ee017954e98a1009039afc Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 May 2016 00:52:53 +0200 Subject: [PATCH 037/185] Import: correct import handling of (localized) SeName --- .../Catalog/Importer/CategoryImporter.cs | 63 +++++++-------- .../Catalog/Importer/ProductImporter.cs | 81 +++++++++---------- .../Customers/Importer/CustomerImporter.cs | 2 +- .../Import/ColumnMapping/ColumnMap.cs | 12 ++- .../DataExchange/Import/DataImporter.cs | 3 +- .../DataExchange/Import/EntityImporterBase.cs | 1 + .../DataExchange/Import/ImportRow.cs | 4 +- .../Localization/ILocalizedEntityService.cs | 6 +- 8 files changed, 89 insertions(+), 83 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index fae01e82ec..a5010b2a72 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -74,9 +74,11 @@ public CategoryImporter( _seoSettings = seoSettings; } - protected virtual int ProcessSlugs(ImportExecuteContext context, IEnumerable> batch) + protected virtual int ProcessSlugs( + ImportExecuteContext context, + IEnumerable> batch) { - var entityName = typeof(Category).Name; + var entityName = typeof(Product).Name; var slugMap = new Dictionary(); UrlRecord urlRecord = null; @@ -89,47 +91,44 @@ protected virtual int ProcessSlugs(ImportExecuteContext context, IEnumerable("Name", lang.UniqueSeoCode); + seName = row.Entity.ValidateSeName(seName, localizedName, false, _urlRecordService, _seoSettings, lang.Id, slugLookup); urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, lang.Id); - if (urlRecord != null) { slugMap[seName] = urlRecord; diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 4197839911..954dbcb902 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -17,6 +17,7 @@ using SmartStore.Services.Seo; using SmartStore.Services.Stores; using SmartStore.Utilities; +using SmartStore.Core.Domain.Localization; namespace SmartStore.Services.Catalog.Importer { @@ -387,12 +388,10 @@ protected virtual int ProcessLocalizations( protected virtual int ProcessSlugs( ImportExecuteContext context, - IEnumerable> batch, - string[] columnIndexes) + IEnumerable> batch) { var entityName = typeof(Product).Name; var slugMap = new Dictionary(); - //var languageMap = context.Languages.ToDictionarySafe(x => x.UniqueSeoCode); UrlRecord urlRecord = null; Func slugLookup = ((s) => @@ -404,56 +403,50 @@ protected virtual int ProcessSlugs( { try { - string seName; - if (row.TryGetDataValue("SeName", out seName, row.IsNew)) + string seName = null; + if (row.IsNew || row.NameChanged || row.TryGetDataValue("SeName", out seName)) { - if (row.IsNew || row.NameChanged || seName.HasValue()) - { - seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); + seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); - if (row.IsNew) - { - // dont't bother validating SeName for new entities. - urlRecord = new UrlRecord - { - EntityId = row.Entity.Id, - EntityName = entityName, - Slug = seName, - LanguageId = 0, - IsActive = true, - }; - _urlRecordRepository.Insert(urlRecord); - } - else + if (row.IsNew) + { + // dont't bother validating SeName for new entities. + urlRecord = new UrlRecord { - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); - } + EntityId = row.Entity.Id, + EntityName = entityName, + Slug = seName, + LanguageId = 0, + IsActive = true, + }; + _urlRecordRepository.Insert(urlRecord); + } + else + { + urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); + } + + if (urlRecord != null) + { + // a new record was inserted to the store: keep track of it for this batch. + slugMap[seName] = urlRecord; + } + } + // process localized SeNames + foreach (var lang in context.Languages) + { + if (row.IsNew || row.NameChanged || row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName)) + { + var localizedName = row.GetDataValue("Name", lang.UniqueSeoCode); + seName = row.Entity.ValidateSeName(seName, localizedName, false, _urlRecordService, _seoSettings, lang.Id, slugLookup); + urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, lang.Id); if (urlRecord != null) { - // a new record was inserted to the store: keep track of it for this batch. slugMap[seName] = urlRecord; } } } - - //// TODO: (mc) Think about this VERY thoroughly!!!! - //foreach (var idx in columnIndexes) - //{ - // if (languageMap.TryGetValue(idx, out language)) - // { - // seName = row.GetDataValue("SeName", idx); - // if (seName.HasValue()) - // { - // seName = row.Entity.ValidateSeName(seName, null, false, _urlRecordService, _seoSettings, language.Id, slugLookup); - // urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, language.Id); - // if (urlRecord != null) - // { - // slugMap[seName] = urlRecord; - // } - // } - // } - //} } catch (Exception exception) { @@ -780,7 +773,7 @@ protected override void Import(ImportExecuteContext context) { _productRepository.Context.AutoDetectChangesEnabled = true; - ProcessSlugs(context, batch, segmenter.GetColumnIndexes("Name")); + ProcessSlugs(context, batch); } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 3eec752b0b..367d8dd854 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -544,7 +544,7 @@ public static string[] DefaultKeyFields protected override IDictionary>> GetLocalizableProperties() { - throw new NotImplementedException(); + throw new NotSupportedException(); } protected override void Import(ImportExecuteContext context) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs index d0d0df1d82..14a0ac77e9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs @@ -108,6 +108,8 @@ public ColumnMappingItem GetMapping(string sourceName) [JsonObject(MemberSerialization.OptIn)] public class ColumnMappingItem { + private bool? _ignored; + /// /// The source name /// @@ -131,7 +133,15 @@ public class ColumnMappingItem /// public bool IgnoreProperty { - get { return Default != null && Default == "[IGNOREPROPERTY]"; } + get + { + if (_ignored == null) + { + _ignored = Default != null && Default == "[IGNOREPROPERTY]"; + } + + return _ignored.Value; + } } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs index 1739295b85..aeb4b81897 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs @@ -2,6 +2,7 @@ using System.IO; using System.Text; using System.Threading; +using System.Linq; using SmartStore.Core; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; @@ -200,7 +201,7 @@ private void ImportCoreInner(DataImporterContext ctx, string filePath) CsvConfiguration csvConfiguration = null; var extension = Path.GetExtension(filePath); - if (extension.IsCaseInsensitiveEqual(".csv")) + if ((new string[] { ".csv", ".txt", ".tab" }).Contains(extension, StringComparer.OrdinalIgnoreCase)) { var converter = new CsvConfigurationConverter(); csvConfiguration = converter.ConvertFrom(ctx.Request.Profile.FileTypeConfiguration); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index 7d2f08871e..6e037a3cdc 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -9,6 +9,7 @@ using SmartStore.Core; using Autofac; using SmartStore.Services.Localization; +using SmartStore.Core.Domain.Localization; namespace SmartStore.Services.DataExchange.Import { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs index ebe37a106f..1f213ee9e4 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -184,7 +184,6 @@ public bool TryGetDataValue(string columnName, out TProp value, bool forc public bool TryGetDataValue(string columnName, string index, out TProp value, bool force = false) { - object rawValue; var mapping = _segmenter.ColumnMap.GetMapping(columnName, index); if (!force && mapping.IgnoreProperty) @@ -193,6 +192,7 @@ public bool TryGetDataValue(string columnName, string index, out TProp va return false; } + object rawValue; if (_row.TryGetValue(mapping.MappedName, out rawValue) && rawValue != null && rawValue != DBNull.Value) { value = rawValue.ToString().IsCaseInsensitiveEqual(ExplicitNull) @@ -209,7 +209,7 @@ public bool TryGetDataValue(string columnName, string index, out TProp va } value = default(TProp); - return true; + return false; } public bool SetProperty( diff --git a/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs b/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs index 871afe9887..070d1fbfe5 100644 --- a/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs +++ b/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs @@ -62,12 +62,14 @@ public partial interface ILocalizedEntityService /// Key selector /// Locale value /// Language ID - void SaveLocalizedValue(T entity, + void SaveLocalizedValue( + T entity, Expression> keySelector, string localeValue, int languageId) where T : BaseEntity, ILocalizedEntity; - void SaveLocalizedValue(T entity, + void SaveLocalizedValue( + T entity, Expression> keySelector, TPropType localeValue, int languageId) where T : BaseEntity, ILocalizedEntity; From 87c7edb87da834170bd10034216fea59966ab1c9 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 May 2016 01:17:24 +0200 Subject: [PATCH 038/185] Further changes to fix for "Open Redirection Vulnerability" --- .../Controllers/SmartController.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs index c2127adf6b..29fdb90d52 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs @@ -138,13 +138,29 @@ protected ActionResult RedirectToReferrer(string referrer, string fallbackUrl) protected virtual ActionResult RedirectToReferrer(string referrer, Func fallbackResult) { + bool skipLocalCheck = false; + if (referrer.IsEmpty() && Request.UrlReferrer != null && Request.UrlReferrer.ToString().HasValue()) { referrer = Request.UrlReferrer.ToString(); + if (referrer.HasValue()) + { + var domain1 = (new Uri(referrer)).GetLeftPart(UriPartial.Authority); + var domain2 = this.Request.Url.GetLeftPart(UriPartial.Authority); + if (domain1.IsCaseInsensitiveEqual(domain2)) + { + // always allow fully qualified urls from local host + skipLocalCheck = true; + } + else + { + referrer = null; + } + } } // addressing "Open Redirection Vulnerability" (prevent cross-domain redirects / phishing) - if (referrer.HasValue() && !Url.IsLocalUrl(referrer)) + if (referrer.HasValue() && !skipLocalCheck && !Url.IsLocalUrl(referrer)) { referrer = null; } From 9cf5dea8fafb15c28ddc1508b86525848cb8b17c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 12 May 2016 10:23:34 +0200 Subject: [PATCH 039/185] Minor fix --- .../SmartStore.Services/Catalog/Importer/CategoryImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index a5010b2a72..4ab3ecc39e 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -78,7 +78,7 @@ protected virtual int ProcessSlugs( ImportExecuteContext context, IEnumerable> batch) { - var entityName = typeof(Product).Name; + var entityName = typeof(Category).Name; var slugMap = new Dictionary(); UrlRecord urlRecord = null; From 62dd2ef939e7fef23e93e227ba249833cb957bb3 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 12 May 2016 11:29:25 +0200 Subject: [PATCH 040/185] DevTools should generate an exception when calling installation page --- .../SmartStore.DevTools/DependencyRegistrar.cs | 11 ++--------- src/Plugins/SmartStore.DevTools/ProfilerHttpModule.cs | 6 ++++++ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Plugins/SmartStore.DevTools/DependencyRegistrar.cs b/src/Plugins/SmartStore.DevTools/DependencyRegistrar.cs index 91f0a57419..4e4cb6d96d 100644 --- a/src/Plugins/SmartStore.DevTools/DependencyRegistrar.cs +++ b/src/Plugins/SmartStore.DevTools/DependencyRegistrar.cs @@ -1,12 +1,5 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Web.Mvc; -using System.Reflection; using Autofac; -using Autofac.Core; using Autofac.Integration.Mvc; -using SmartStore.Core.Plugins; using SmartStore.Core.Data; using SmartStore.Core.Infrastructure; using SmartStore.Core.Infrastructure.DependencyManagement; @@ -16,13 +9,13 @@ namespace SmartStore.DevTools { - public class DependencyRegistrar : IDependencyRegistrar + public class DependencyRegistrar : IDependencyRegistrar { public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, bool isActiveModule) { builder.RegisterType().As().InstancePerRequest(); - if (isActiveModule) + if (isActiveModule && DataSettings.DatabaseIsInstalled()) { // intercept ALL public store controller actions builder.RegisterType().AsActionFilterFor(); diff --git a/src/Plugins/SmartStore.DevTools/ProfilerHttpModule.cs b/src/Plugins/SmartStore.DevTools/ProfilerHttpModule.cs index e1c5693558..c2e585d0eb 100644 --- a/src/Plugins/SmartStore.DevTools/ProfilerHttpModule.cs +++ b/src/Plugins/SmartStore.DevTools/ProfilerHttpModule.cs @@ -1,5 +1,6 @@ using System; using System.Web; +using SmartStore.Core.Data; using SmartStore.Core.Infrastructure; using StackExchange.Profiling; @@ -43,6 +44,11 @@ private static bool ShouldProfile(HttpApplication app) if (app.Context == null || app.Context.Request == null) return false; + if (!DataSettings.DatabaseIsInstalled()) + { + return false; + } + var url = app.Context.Request.AppRelativeCurrentExecutionFilePath; if (url.StartsWith("~/admin", StringComparison.InvariantCultureIgnoreCase) || url.StartsWith("~/mini-profiler", StringComparison.InvariantCultureIgnoreCase) || url.StartsWith("~/bundles", StringComparison.InvariantCultureIgnoreCase)) { From 9b3fdbb0edde030a1a132126e0ac46bad6f876eb Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 12 May 2016 12:22:36 +0200 Subject: [PATCH 041/185] Installation: Fixes "No constructor was found that had all the dependencies satisfied." --- .../Infrastructure/DependencyManagement/ContainerManager.cs | 2 +- .../SmartStore.Web.Framework/DependencyRegistrar.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs index 7b1bbc9292..f39bbeedeb 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs @@ -95,7 +95,7 @@ public object ResolveUnregistered(Type type, ILifetimeScope scope = null) } } - throw new SmartException("No contructor was found that had all the dependencies satisfied."); + throw new SmartException("No constructor for {0} was found that had all the dependencies satisfied.".FormatInvariant(type.Name.NaIfEmpty())); } private bool TryResolveAll(Type[] types, out object[] instances, ILifetimeScope scope = null) diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index 046a1e8d4a..73a2c9bd9a 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -1094,11 +1094,12 @@ public DataExchangeModule(ITypeFinder typeFinder) protected override void Load(ContainerBuilder builder) { + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + if (!DataSettings.DatabaseIsInstalled()) return; - builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); From 788e08d0c7f9b73867d112a4d1552f1795518fcf Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 12 May 2016 13:26:40 +0200 Subject: [PATCH 042/185] Minor fix --- .../SmartStore.Web/Themes/Alpha/Content/product.less | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less index 3aa0ecedd3..39a374f693 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less @@ -401,7 +401,9 @@ } .attributes .image-has-value { - margin-left: 28px; + margin-left: 8px; + vertical-align: top; + display: inline-block; } .mb-bundle-pictures .fa-plus { @@ -409,8 +411,7 @@ } .mb-bundle-pictures a, -.mb-bundle-pictures i, -.attributes .attribute-value-image-checkbox { +.mb-bundle-pictures i { float: left; } From 6abe5db27b2406c855bac68b277568715d59d05e Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 12 May 2016 13:44:41 +0200 Subject: [PATCH 043/185] Renamed AutoCompleteSearchThumbPictureSize as it was only used for variant value thumbs --- src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs | 4 ++-- .../Administration/Infrastructure/AutoMapperStartupTask.cs | 2 +- src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs b/src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs index cee3019aa8..b3072c0912 100644 --- a/src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs @@ -18,7 +18,7 @@ public MediaSettings() CartThumbPictureSize = 80; CartThumbBundleItemPictureSize = 32; MiniCartThumbPictureSize = 32; - AutoCompleteSearchThumbPictureSize = 20; + VariantValueThumbPictureSize = 20; MaximumImageSize = 1280; DefaultPictureZoomEnabled = true; PictureZoomType = "window"; @@ -38,7 +38,7 @@ public MediaSettings() public int CartThumbPictureSize { get; set; } public int CartThumbBundleItemPictureSize { get; set; } public int MiniCartThumbPictureSize { get; set; } - public int AutoCompleteSearchThumbPictureSize { get; set; } + public int VariantValueThumbPictureSize { get; set; } public bool DefaultPictureZoomEnabled { get; set; } public string PictureZoomType { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs index 839bbc1f05..2f7c0f3339 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs @@ -668,7 +668,7 @@ public void Execute() //.ForMember(dest => dest.DefaultPictureZoomEnabled, mo => mo.Ignore()) .ForMember(dest => dest.DefaultImageQuality, mo => mo.Ignore()) .ForMember(dest => dest.MultipleThumbDirectories, mo => mo.Ignore()) - .ForMember(dest => dest.AutoCompleteSearchThumbPictureSize, mo => mo.Ignore()); + .ForMember(dest => dest.VariantValueThumbPictureSize, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.AvailableCustomerNumberMethods, mo => mo.Ignore()) .ForMember(dest => dest.AvailableCustomerNumberVisibilities, mo => mo.Ignore()) diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index 68e7442173..2c0f3ef2be 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -571,7 +571,7 @@ public ProductDetailsModel PrepareProductDetailModel( { var linkagePicture = _pictureService.GetPicturesByProductId(pvaValue.LinkedProductId, 1).FirstOrDefault(); if (linkagePicture != null) - pvaValueModel.ImageUrl = _pictureService.GetPictureUrl(linkagePicture, _mediaSettings.AutoCompleteSearchThumbPictureSize, false); + pvaValueModel.ImageUrl = _pictureService.GetPictureUrl(linkagePicture, _mediaSettings.VariantValueThumbPictureSize, false); } pvaModel.Values.Add(pvaValueModel); From 37d67f7595c9b25981cc85b1cbbfc29825f3a755 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 12 May 2016 18:56:26 +0200 Subject: [PATCH 044/185] Refactored last commit --- .../201605111140288_ImportExtraData.cs | 6 ++ .../Themes/Alpha/Content/product.less | 19 ++-- .../Views/Product/_Attributes.cshtml | 90 ++++++++----------- 3 files changed, 53 insertions(+), 62 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs index 55bbdd6843..17d0564ce6 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs @@ -23,6 +23,12 @@ public bool RollbackOnFailure public void Seed(SmartObjectContext context) { context.MigrateLocaleResources(MigrateLocaleResources); + + context.MigrateSettings(x => + { + x.Add("MediaSettings.VariantValueThumbPictureSize", "20"); + x.Delete("MediaSettings.AutoCompleteSearchThumbPictureSize"); + }); } public void MigrateLocaleResources(LocaleResourcesBuilder builder) diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less index 39a374f693..0a0fde971a 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less @@ -395,25 +395,28 @@ border-top: none !important; } +.attributes .image-has-value { + display: table-cell; + padding-top: 10px; +} + .attribute-value-image { margin-right: 5px; vertical-align: middle; } - -.attributes .image-has-value { - margin-left: 8px; - vertical-align: top; - display: inline-block; -} - .mb-bundle-pictures .fa-plus { padding: 4px 10px 0 10px; } .mb-bundle-pictures a, -.mb-bundle-pictures i { +.mb-bundle-pictures i, +.attributes .attribute-value-image-checkbox { float: left; } +.attributes .attribute-value-image-checkbox { + margin-right: 10px; + padding-top: 10px; +} .mb-bundle-pictures img { max-width: 24px; diff --git a/src/Presentation/SmartStore.Web/Views/Product/_Attributes.cshtml b/src/Presentation/SmartStore.Web/Views/Product/_Attributes.cshtml index bbe0a418d2..853aa09e75 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/_Attributes.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/_Attributes.cshtml @@ -37,70 +37,52 @@ { case AttributeControlType.DropdownList: { - - } - break; - case AttributeControlType.RadioList: - { - foreach (var pvaValue in attribute.Values) - { - if (pvaValue.ImageUrl.HasValue()) + - @(pvaValue.Name)@(pvaValue.GetAttributeValueInfo()) - - } - + } break; + case AttributeControlType.RadioList: case AttributeControlType.Checkboxes: { foreach (var pvaValue in attribute.Values) { - if (pvaValue.ImageUrl.HasValue()) - { - if (!String.IsNullOrEmpty(pvaValue.SeName)) - { - - - - } - else + var inputType = attribute.AttributeControlType == AttributeControlType.RadioList ? "radio" : "checkbox"; + +
+ @if (pvaValue.ImageUrl.HasValue()) { - + if (!String.IsNullOrEmpty(pvaValue.SeName)) + { + + + + } + else + { + + } } - } - - + + +
} } break; From 1f49bc0dc4783754d36ba7d37a283e9815d66a88 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 12 May 2016 20:34:50 +0200 Subject: [PATCH 045/185] Download nor sample download were removable when editing product --- changelog.md | 1 + .../Administration/Controllers/ProductController.cs | 2 +- .../Administration/Models/Catalog/ProductModel.cs | 4 ++-- .../Administration/Views/Product/_CreateOrUpdate.Info.cshtml | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/changelog.md b/changelog.md index 8bd6ef243c..a6a61a03fc 100644 --- a/changelog.md +++ b/changelog.md @@ -36,6 +36,7 @@ * Customer import: Creates customer role duplicates for inserted customers * GMC feed does not generate the sale price if the sale price is set for a future date * Mobile devices: Fixed "Uncaught Error: Syntax error, unrecognized expression: :nth-child" +* Download nor sample download were removable when editing product ## SmartStore.NET 2.5 diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 46fc08645f..8ea467ea2c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -215,7 +215,7 @@ protected void UpdateProductGeneralInfo(Product product, ProductModel model) p.GiftCardTypeId = m.GiftCardTypeId; p.IsDownload = m.IsDownload; - p.DownloadId = m.DownloadId; + p.DownloadId = m.DownloadId ?? 0; p.UnlimitedDownloads = m.UnlimitedDownloads; p.MaxNumberOfDownloads = m.MaxNumberOfDownloads; p.DownloadExpirationDays = m.DownloadExpirationDays; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs index d7f5f66e1f..d24020eaea 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs @@ -140,7 +140,7 @@ public ProductModel() [SmartResourceDisplayName("Admin.Catalog.Products.Fields.Download")] [UIHint("Download")] - public int DownloadId { get; set; } + public int? DownloadId { get; set; } [SmartResourceDisplayName("Admin.Catalog.Products.Fields.UnlimitedDownloads")] public bool UnlimitedDownloads { get; set; } @@ -159,7 +159,7 @@ public ProductModel() [SmartResourceDisplayName("Admin.Catalog.Products.Fields.SampleDownload")] [UIHint("Download")] - public int SampleDownloadId { get; set; } + public int? SampleDownloadId { get; set; } [SmartResourceDisplayName("Admin.Catalog.Products.Fields.HasUserAgreement")] public bool HasUserAgreement { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml index f180d223f8..8d295edfb2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml @@ -618,7 +618,7 @@ @Html.EditorFor(model => model.SampleDownloadId) - @*@Html.ValidationMessageFor(model => model.SampleDownloadId)*@ + @Html.ValidationMessageFor(model => model.SampleDownloadId) From 52d2416371c231887e923031c48b65b42c5ada41 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 12 May 2016 21:06:20 +0200 Subject: [PATCH 046/185] Copied product must not share sample download of source product. Could produce "The DELETE statement conflicted with the REFERENCE constraint 'FK_dbo.Product_dbo.Download_SampleDownloadId'". --- changelog.md | 1 + .../SmartStore.Services/Catalog/CopyProductService.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index a6a61a03fc..8e188f25e4 100644 --- a/changelog.md +++ b/changelog.md @@ -37,6 +37,7 @@ * GMC feed does not generate the sale price if the sale price is set for a future date * Mobile devices: Fixed "Uncaught Error: Syntax error, unrecognized expression: :nth-child" * Download nor sample download were removable when editing product +* Copied product must not share sample download of source product. Could produce "The DELETE statement conflicted with the REFERENCE constraint 'FK_dbo.Product_dbo.Download_SampleDownloadId'". ## SmartStore.NET 2.5 diff --git a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs index 2dcb1cb673..f786bf9c78 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs @@ -84,8 +84,8 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli var utcNow = DateTime.UtcNow; // product download & sample download - int downloadId = product.DownloadId; - int? sampleDownloadId = product.SampleDownloadId; + int downloadId = 0; + int? sampleDownloadId = null; if (product.IsDownload) { From e7837c8b35e6c424f9df5961f4189b477317a98b Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 May 2016 19:16:03 +0200 Subject: [PATCH 047/185] Import: ProcessSlugs() does not always fetch SeName from source --- .../SmartStore.Services/Catalog/Importer/CategoryImporter.cs | 4 ++-- .../SmartStore.Services/Catalog/Importer/ProductImporter.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 4ab3ecc39e..2122938194 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -92,7 +92,7 @@ protected virtual int ProcessSlugs( try { string seName = null; - if (row.IsNew || row.NameChanged || row.TryGetDataValue("SeName", out seName)) + if (row.TryGetDataValue("SeName", out seName) || row.IsNew || row.NameChanged) { seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); @@ -124,7 +124,7 @@ protected virtual int ProcessSlugs( // process localized SeNames foreach (var lang in context.Languages) { - if (row.IsNew || row.NameChanged || row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName)) + if (row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName) || row.IsNew || row.NameChanged) { var localizedName = row.GetDataValue("Name", lang.UniqueSeoCode); seName = row.Entity.ValidateSeName(seName, localizedName, false, _urlRecordService, _seoSettings, lang.Id, slugLookup); diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 954dbcb902..c38cac828d 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -404,7 +404,7 @@ protected virtual int ProcessSlugs( try { string seName = null; - if (row.IsNew || row.NameChanged || row.TryGetDataValue("SeName", out seName)) + if (row.TryGetDataValue("SeName", out seName) || row.IsNew || row.NameChanged) { seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); @@ -436,7 +436,7 @@ protected virtual int ProcessSlugs( // process localized SeNames foreach (var lang in context.Languages) { - if (row.IsNew || row.NameChanged || row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName)) + if (row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName) || row.IsNew || row.NameChanged) { var localizedName = row.GetDataValue("Name", lang.UniqueSeoCode); seName = row.Entity.ValidateSeName(seName, localizedName, false, _urlRecordService, _seoSettings, lang.Id, slugLookup); From f0b0d80fdf6d04c3244f3db1c7549e9295ad4a37 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 May 2016 20:39:44 +0200 Subject: [PATCH 048/185] Importer refactoring: moved ProcessSlugs() and ProcessStoreMappings() to base class --- .../Catalog/Importer/CategoryImporter.cs | 108 +---------------- .../Catalog/Importer/ProductImporter.cs | 114 +----------------- .../DataExchange/Import/EntityImporterBase.cs | 111 ++++++++++++++++- 3 files changed, 113 insertions(+), 220 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 2122938194..de4d5a073f 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -23,17 +23,12 @@ namespace SmartStore.Services.Catalog.Importer public class CategoryImporter : EntityImporterBase { private readonly IRepository _categoryRepository; - private readonly IRepository _urlRecordRepository; private readonly IRepository _pictureRepository; private readonly ICommonServices _services; - private readonly IUrlRecordService _urlRecordService; - private readonly IRepository _storeMappingRepository; private readonly ICategoryTemplateService _categoryTemplateService; - private readonly IStoreMappingService _storeMappingService; private readonly IPictureService _pictureService; private readonly ILocalizedEntityService _localizedEntityService; private readonly FileDownloadManager _fileDownloadManager; - private readonly SeoSettings _seoSettings; private static readonly Dictionary>> _localizableProperties = new Dictionary>> { @@ -48,102 +43,20 @@ public class CategoryImporter : EntityImporterBase public CategoryImporter( IRepository categoryRepository, - IRepository urlRecordRepository, IRepository pictureRepository, - IRepository storeMappingRepository, ICommonServices services, - IUrlRecordService urlRecordService, ICategoryTemplateService categoryTemplateService, - IStoreMappingService storeMappingService, IPictureService pictureService, ILocalizedEntityService localizedEntityService, - FileDownloadManager fileDownloadManager, - SeoSettings seoSettings) + FileDownloadManager fileDownloadManager) { _categoryRepository = categoryRepository; - _urlRecordRepository = urlRecordRepository; _pictureRepository = pictureRepository; - _storeMappingRepository = storeMappingRepository; _services = services; - _urlRecordService = urlRecordService; _categoryTemplateService = categoryTemplateService; - _storeMappingService = storeMappingService; _pictureService = pictureService; _localizedEntityService = localizedEntityService; _fileDownloadManager = fileDownloadManager; - _seoSettings = seoSettings; - } - - protected virtual int ProcessSlugs( - ImportExecuteContext context, - IEnumerable> batch) - { - var entityName = typeof(Category).Name; - var slugMap = new Dictionary(); - UrlRecord urlRecord = null; - - Func slugLookup = ((s) => - { - return (slugMap.ContainsKey(s) ? slugMap[s] : null); - }); - - foreach (var row in batch) - { - try - { - string seName = null; - if (row.TryGetDataValue("SeName", out seName) || row.IsNew || row.NameChanged) - { - seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); - - if (row.IsNew) - { - // dont't bother validating SeName for new entities. - urlRecord = new UrlRecord - { - EntityId = row.Entity.Id, - EntityName = entityName, - Slug = seName, - LanguageId = 0, - IsActive = true, - }; - _urlRecordRepository.Insert(urlRecord); - } - else - { - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); - } - - if (urlRecord != null) - { - // a new record was inserted to the store: keep track of it for this batch. - slugMap[seName] = urlRecord; - } - } - - // process localized SeNames - foreach (var lang in context.Languages) - { - if (row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName) || row.IsNew || row.NameChanged) - { - var localizedName = row.GetDataValue("Name", lang.UniqueSeoCode); - seName = row.Entity.ValidateSeName(seName, localizedName, false, _urlRecordService, _seoSettings, lang.Id, slugLookup); - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, lang.Id); - if (urlRecord != null) - { - slugMap[seName] = urlRecord; - } - } - } - } - catch (Exception exception) - { - context.Result.AddWarning(exception.Message, row.GetRowInfo(), "SeName"); - } - } - - // commit whole batch at once - return _urlRecordRepository.Context.SaveChanges(); } protected virtual int ProcessLocalizations( @@ -293,23 +206,6 @@ protected virtual int ProcessPictures( return num; } - protected virtual int ProcessStoreMappings(ImportExecuteContext context, IEnumerable> batch) - { - _storeMappingRepository.AutoCommitEnabled = false; - - foreach (var row in batch) - { - var storeIds = row.GetDataValue>("StoreIds"); - if (!storeIds.IsNullOrEmpty()) - { - _storeMappingService.SaveStoreMappings(row.Entity, storeIds.ToArray()); - } - } - - // commit whole batch at once - return _services.DbContext.SaveChanges(); - } - protected virtual int ProcessCategories( ImportExecuteContext context, IEnumerable> batch, @@ -513,7 +409,7 @@ protected override void Import(ImportExecuteContext context) try { _categoryRepository.Context.AutoDetectChangesEnabled = true; - ProcessSlugs(context, batch); + ProcessSlugs(context, batch, typeof(Category).Name); } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index c38cac828d..bbb266ebfa 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -8,16 +8,11 @@ using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Domain.Seo; -using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; using SmartStore.Services.DataExchange.Import; using SmartStore.Services.Localization; using SmartStore.Services.Media; -using SmartStore.Services.Seo; -using SmartStore.Services.Stores; using SmartStore.Utilities; -using SmartStore.Core.Domain.Localization; namespace SmartStore.Services.Catalog.Importer { @@ -26,20 +21,15 @@ public class ProductImporter : EntityImporterBase private readonly IRepository _productPictureRepository; private readonly IRepository _productManufacturerRepository; private readonly IRepository _productCategoryRepository; - private readonly IRepository _urlRecordRepository; private readonly IRepository _productRepository; - private readonly IRepository _storeMappingRepository; private readonly ICommonServices _services; private readonly ILocalizedEntityService _localizedEntityService; private readonly IPictureService _pictureService; private readonly IManufacturerService _manufacturerService; private readonly ICategoryService _categoryService; private readonly IProductService _productService; - private readonly IUrlRecordService _urlRecordService; private readonly IProductTemplateService _productTemplateService; - private readonly IStoreMappingService _storeMappingService; private readonly FileDownloadManager _fileDownloadManager; - private readonly SeoSettings _seoSettings; private static readonly Dictionary>> _localizableProperties = new Dictionary>> { @@ -56,38 +46,28 @@ public ProductImporter( IRepository productPictureRepository, IRepository productManufacturerRepository, IRepository productCategoryRepository, - IRepository urlRecordRepository, IRepository productRepository, - IRepository storeMappingRepository, ICommonServices services, ILocalizedEntityService localizedEntityService, IPictureService pictureService, IManufacturerService manufacturerService, ICategoryService categoryService, IProductService productService, - IUrlRecordService urlRecordService, IProductTemplateService productTemplateService, - IStoreMappingService storeMappingService, - FileDownloadManager fileDownloadManager, - SeoSettings seoSettings) + FileDownloadManager fileDownloadManager) { _productPictureRepository = productPictureRepository; _productManufacturerRepository = productManufacturerRepository; _productCategoryRepository = productCategoryRepository; - _urlRecordRepository = urlRecordRepository; _productRepository = productRepository; - _storeMappingRepository = storeMappingRepository; _services = services; _localizedEntityService = localizedEntityService; _pictureService = pictureService; _manufacturerService = manufacturerService; _categoryService = categoryService; _productService = productService; - _urlRecordService = urlRecordService; _productTemplateService = productTemplateService; - _storeMappingService = storeMappingService; _fileDownloadManager = fileDownloadManager; - _seoSettings = seoSettings; } private int? ZeroToNull(object value, CultureInfo culture) @@ -386,95 +366,6 @@ protected virtual int ProcessLocalizations( return 0; } - protected virtual int ProcessSlugs( - ImportExecuteContext context, - IEnumerable> batch) - { - var entityName = typeof(Product).Name; - var slugMap = new Dictionary(); - UrlRecord urlRecord = null; - - Func slugLookup = ((s) => - { - return (slugMap.ContainsKey(s) ? slugMap[s] : null); - }); - - foreach (var row in batch) - { - try - { - string seName = null; - if (row.TryGetDataValue("SeName", out seName) || row.IsNew || row.NameChanged) - { - seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); - - if (row.IsNew) - { - // dont't bother validating SeName for new entities. - urlRecord = new UrlRecord - { - EntityId = row.Entity.Id, - EntityName = entityName, - Slug = seName, - LanguageId = 0, - IsActive = true, - }; - _urlRecordRepository.Insert(urlRecord); - } - else - { - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); - } - - if (urlRecord != null) - { - // a new record was inserted to the store: keep track of it for this batch. - slugMap[seName] = urlRecord; - } - } - - // process localized SeNames - foreach (var lang in context.Languages) - { - if (row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName) || row.IsNew || row.NameChanged) - { - var localizedName = row.GetDataValue("Name", lang.UniqueSeoCode); - seName = row.Entity.ValidateSeName(seName, localizedName, false, _urlRecordService, _seoSettings, lang.Id, slugLookup); - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, lang.Id); - if (urlRecord != null) - { - slugMap[seName] = urlRecord; - } - } - } - } - catch (Exception exception) - { - context.Result.AddWarning(exception.Message, row.GetRowInfo(), "SeName"); - } - } - - // commit whole batch at once - return _urlRecordRepository.Context.SaveChanges(); - } - - protected virtual int ProcessStoreMappings(ImportExecuteContext context, IEnumerable> batch) - { - _storeMappingRepository.AutoCommitEnabled = false; - - foreach (var row in batch) - { - var storeIds = row.GetDataValue>("StoreIds"); - if (!storeIds.IsNullOrEmpty()) - { - _storeMappingService.SaveStoreMappings(row.Entity, storeIds.ToArray()); - } - } - - // commit whole batch at once - return _services.DbContext.SaveChanges(); - } - protected virtual int ProcessProducts( ImportExecuteContext context, IEnumerable> batch, @@ -772,8 +663,7 @@ protected override void Import(ImportExecuteContext context) try { _productRepository.Context.AutoDetectChangesEnabled = true; - - ProcessSlugs(context, batch); + ProcessSlugs(context, batch, typeof(Product).Name); } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index 6e037a3cdc..0108649f6c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -10,10 +10,15 @@ using Autofac; using SmartStore.Services.Localization; using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Seo; +using SmartStore.Services.Seo; +using SmartStore.Core.Data; +using SmartStore.Services.Stores; +using SmartStore.Core.Domain.Stores; namespace SmartStore.Services.DataExchange.Import { - public abstract class EntityImporterBase : IEntityImporter where TEntity : BaseEntity + public abstract class EntityImporterBase : IEntityImporter where T : BaseEntity { private const string _imageDownloadFolder = @"Content\DownloadedImages"; @@ -153,6 +158,108 @@ protected IEnumerable ResolveLocalizedProperties(ImportDataSegmenter seg } } - protected abstract IDictionary>> GetLocalizableProperties(); + protected abstract IDictionary>> GetLocalizableProperties(); + + protected virtual int ProcessStoreMappings( + ImportExecuteContext context, + IEnumerable> batch) where TEntity : BaseEntity, IStoreMappingSupported + { + var storeMappingService = context.Services.Resolve(); + var storeMappingRepository = context.Services.Resolve>(); + + storeMappingRepository.AutoCommitEnabled = false; + + foreach (var row in batch) + { + var storeIds = row.GetDataValue>("StoreIds"); + if (!storeIds.IsNullOrEmpty()) + { + storeMappingService.SaveStoreMappings(row.Entity, storeIds.ToArray()); + } + } + + // commit whole batch at once + return context.Services.DbContext.SaveChanges(); + } + + protected virtual int ProcessSlugs( + ImportExecuteContext context, + IEnumerable> batch, + string entityName) where TEntity : BaseEntity, ISlugSupported + { + var slugMap = new Dictionary(); + UrlRecord urlRecord = null; + + var urlRecordService = context.Services.Resolve(); + var urlRecordRepository = context.Services.Resolve>(); + var seoSettings = context.Services.Resolve(); + + Func slugLookup = ((s) => + { + return (slugMap.ContainsKey(s) ? slugMap[s] : null); + }); + + foreach (var row in batch) + { + try + { + string seName = null; + string localizedName = null; + + if (row.TryGetDataValue("SeName", out seName) || row.IsNew || row.NameChanged) + { + seName = row.Entity.ValidateSeName(seName, row.EntityDisplayName, true, urlRecordService, seoSettings, extraSlugLookup: slugLookup); + + if (row.IsNew) + { + // dont't bother validating SeName for new entities. + urlRecord = new UrlRecord + { + EntityId = row.Entity.Id, + EntityName = entityName, + Slug = seName, + LanguageId = 0, + IsActive = true, + }; + urlRecordRepository.Insert(urlRecord); + } + else + { + urlRecord = urlRecordService.SaveSlug(row.Entity, seName, 0); + } + + if (urlRecord != null) + { + // a new record was inserted to the store: keep track of it for this batch. + slugMap[seName] = urlRecord; + } + } + + // process localized SeNames + foreach (var lang in context.Languages) + { + var hasSeName = row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName); + var hasLocalizedName = row.TryGetDataValue("Name", lang.UniqueSeoCode, out localizedName); + + if (hasSeName || hasLocalizedName) + { + seName = row.Entity.ValidateSeName(seName, localizedName, false, urlRecordService, seoSettings, lang.Id, slugLookup); + urlRecord = urlRecordService.SaveSlug(row.Entity, seName, lang.Id); + if (urlRecord != null) + { + slugMap[seName] = urlRecord; + } + } + } + } + catch (Exception exception) + { + context.Result.AddWarning(exception.Message, row.GetRowInfo(), "SeName"); + } + } + + // commit whole batch at once + return context.Services.DbContext.SaveChanges(); + } } } From 1fc4648c0cccce2a54c60e2d45788954fc3bd68f Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 May 2016 21:19:32 +0200 Subject: [PATCH 049/185] Importer refactoring: moved ProcessLocalizations() to base class (DRY) --- .../Catalog/Importer/CategoryImporter.cs | 9 +--- .../Catalog/Importer/ProductImporter.cs | 50 +---------------- .../Customers/Importer/CustomerImporter.cs | 5 -- .../DataExchange/Import/EntityImporterBase.cs | 53 ++++++++++++++++--- 4 files changed, 48 insertions(+), 69 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index de4d5a073f..1d67735d05 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -358,11 +358,6 @@ public static string[] DefaultKeyFields } } - protected override IDictionary>> GetLocalizableProperties() - { - return _localizableProperties; - } - protected override void Import(ImportExecuteContext context) { var srcToDestId = new Dictionary(); @@ -375,8 +370,6 @@ protected override void Import(ImportExecuteContext context) Initialize(context); - var localizedProperties = ResolveLocalizedProperties(segmenter).ToArray(); - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { var batch = segmenter.GetCurrentBatch(); @@ -437,7 +430,7 @@ protected override void Import(ImportExecuteContext context) // localizations try { - ProcessLocalizations(context, batch, localizedProperties); + ProcessLocalizations(context, batch, _localizableProperties); } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index bbb266ebfa..06d527bc5f 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -326,46 +326,6 @@ protected virtual int ProcessProductCategories(ImportExecuteContext context, IEn return num; } - protected virtual int ProcessLocalizations( - ImportExecuteContext context, - IEnumerable> batch, - string[] localizedProperties) - { - if (localizedProperties.Length == 0) - { - return 0; - } - - bool shouldSave = false; - - foreach (var row in batch) - { - foreach (var prop in localizedProperties) - { - var lambda = _localizableProperties[prop]; - foreach (var lang in context.Languages) - { - var code = lang.UniqueSeoCode; - string value; - - if (row.TryGetDataValue(prop /* ColumnName */, code, out value)) - { - _localizedEntityService.SaveLocalizedValue(row.Entity, lambda, value, lang.Id); - shouldSave = true; - } - } - } - } - - if (shouldSave) - { - // commit whole batch at once - return context.Services.DbContext.SaveChanges(); - } - - return 0; - } - protected virtual int ProcessProducts( ImportExecuteContext context, IEnumerable> batch, @@ -606,11 +566,6 @@ public static string[] DefaultKeyFields } } - protected override IDictionary>> GetLocalizableProperties() - { - return _localizableProperties; - } - protected override void Import(ImportExecuteContext context) { var srcToDestId = new Dictionary(); @@ -620,9 +575,8 @@ protected override void Import(ImportExecuteContext context) using (var scope = new DbContextScope(ctx: _productRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { var segmenter = context.DataSegmenter; - Initialize(context); - var localizedProperties = ResolveLocalizedProperties(segmenter).ToArray(); + Initialize(context); while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { @@ -695,7 +649,7 @@ protected override void Import(ImportExecuteContext context) // =========================================================================== try { - ProcessLocalizations(context, batch, localizedProperties); + ProcessLocalizations(context, batch, _localizableProperties); } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 367d8dd854..25d6e70ad8 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -542,11 +542,6 @@ public static string[] DefaultKeyFields } } - protected override IDictionary>> GetLocalizableProperties() - { - throw new NotSupportedException(); - } - protected override void Import(ImportExecuteContext context) { var customer = _services.WorkContext.CurrentCustomer; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index 0108649f6c..347a72354c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Net.Mime; +using System.Linq; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.IO; using SmartStore.Utilities; @@ -143,22 +144,58 @@ public void Succeeded(FileDownloadManagerItem item) } } - protected IEnumerable ResolveLocalizedProperties(ImportDataSegmenter segmenter) + protected virtual int ProcessLocalizations( + ImportExecuteContext context, + IEnumerable> batch, + IDictionary>> localizableProperties) where TEntity : BaseEntity, ILocalizedEntity { + Guard.ArgumentNotNull(() => context); + Guard.ArgumentNotNull(() => batch); + Guard.ArgumentNotNull(() => localizableProperties); + // Perf: determine whether our localizable properties actually have - // counterparts in the source BEFORE import begins. This way we spare ourself + // counterparts in the source BEFORE import batch begins. This way we spare ourself // to query over and over for values. - var localizableProperties = GetLocalizableProperties(); - foreach (var kvp in localizableProperties) + var localizedProps = (from kvp in localizableProperties + where context.DataSegmenter.GetColumnIndexes(kvp.Key).Length > 0 + select kvp.Key).ToArray(); + + if (localizedProps.Length == 0) { - if (segmenter.GetColumnIndexes(kvp.Key).Length > 0) + return 0; + } + + var localizedEntityService = context.Services.Resolve(); + + bool shouldSave = false; + + foreach (var row in batch) + { + foreach (var prop in localizedProps) { - yield return kvp.Key; + var lambda = localizableProperties[prop]; + foreach (var lang in context.Languages) + { + var code = lang.UniqueSeoCode; + string value; + + if (row.TryGetDataValue(prop /* ColumnName */, code, out value)) + { + localizedEntityService.SaveLocalizedValue(row.Entity, lambda, value, lang.Id); + shouldSave = true; + } + } } } - } - protected abstract IDictionary>> GetLocalizableProperties(); + if (shouldSave) + { + // commit whole batch at once + return context.Services.DbContext.SaveChanges(); + } + + return 0; + } protected virtual int ProcessStoreMappings( ImportExecuteContext context, From f4d6733435cd4973f22657bfdf6e3035b327dc88 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 May 2016 22:49:48 +0200 Subject: [PATCH 050/185] Importer: removed generic type parameter from EntityImporterBase --- .../SmartStore.Services/Catalog/Importer/CategoryImporter.cs | 2 +- .../SmartStore.Services/Catalog/Importer/ProductImporter.cs | 2 +- .../SmartStore.Services/Customers/Importer/CustomerImporter.cs | 2 +- .../DataExchange/Import/EntityImporterBase.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 1d67735d05..98cef22f35 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -20,7 +20,7 @@ namespace SmartStore.Services.Catalog.Importer { - public class CategoryImporter : EntityImporterBase + public class CategoryImporter : EntityImporterBase { private readonly IRepository _categoryRepository; private readonly IRepository _pictureRepository; diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 06d527bc5f..756ca28d01 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -16,7 +16,7 @@ namespace SmartStore.Services.Catalog.Importer { - public class ProductImporter : EntityImporterBase + public class ProductImporter : EntityImporterBase { private readonly IRepository _productPictureRepository; private readonly IRepository _productManufacturerRepository; diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 25d6e70ad8..da0d917059 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -22,7 +22,7 @@ namespace SmartStore.Services.Customers.Importer { - public class CustomerImporter : EntityImporterBase + public class CustomerImporter : EntityImporterBase { private const string _attributeKeyGroup = "Customer"; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index 347a72354c..eff63d8431 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -19,7 +19,7 @@ namespace SmartStore.Services.DataExchange.Import { - public abstract class EntityImporterBase : IEntityImporter where T : BaseEntity + public abstract class EntityImporterBase : IEntityImporter { private const string _imageDownloadFolder = @"Content\DownloadedImages"; From 042592d62df1cafcc57da729df5a99fa08fddeea Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 May 2016 23:03:39 +0200 Subject: [PATCH 051/185] Importer: reordered members in some files --- .../Catalog/Importer/CategoryImporter.cs | 340 ++++---- .../Catalog/Importer/ProductImporter.cs | 733 +++++++++--------- .../Customers/Importer/CustomerImporter.cs | 711 ++++++++--------- 3 files changed, 893 insertions(+), 891 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 98cef22f35..3f7315905b 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -59,76 +59,125 @@ public CategoryImporter( _fileDownloadManager = fileDownloadManager; } - protected virtual int ProcessLocalizations( - ImportExecuteContext context, - IEnumerable> batch, - string[] localizedProperties) + protected override void Import(ImportExecuteContext context) { - if (localizedProperties.Length == 0) - { - return 0; - } + var srcToDestId = new Dictionary(); - bool shouldSave = false; + var templateViewPaths = _categoryTemplateService.GetAllCategoryTemplates().ToDictionarySafe(x => x.ViewPath, x => x.Id); - foreach (var row in batch) + using (var scope = new DbContextScope(ctx: context.Services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { - foreach (var prop in localizedProperties) + var segmenter = context.DataSegmenter; + + Initialize(context); + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { - var lambda = _localizableProperties[prop]; - foreach (var lang in context.Languages) + var batch = segmenter.GetCurrentBatch(); + + // Perf: detach all entities + _categoryRepository.Context.DetachAll(false); + + context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); + + try { - var code = lang.UniqueSeoCode; - string value; + ProcessCategories(context, batch, templateViewPaths, srcToDestId); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessCategories"); + } - if (row.TryGetDataValue(prop /* ColumnName */, code, out value)) + // reduce batch to saved (valid) products. + // No need to perform import operations on errored products. + batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); + + // update result object + context.Result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); + context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); + + // process slugs + if (segmenter.HasColumn("SeName", true) || batch.Any(x => x.IsNew || x.NameChanged)) + { + try { - _localizedEntityService.SaveLocalizedValue(row.Entity, lambda, value, lang.Id); - shouldSave = true; + _categoryRepository.Context.AutoDetectChangesEnabled = true; + ProcessSlugs(context, batch, typeof(Category).Name); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessSlugs"); + } + finally + { + _categoryRepository.Context.AutoDetectChangesEnabled = false; } } - } - } - if (shouldSave) - { - // commit whole batch at once - return context.Services.DbContext.SaveChanges(); - } + // process store mappings + if (segmenter.HasColumn("StoreIds")) + { + try + { + ProcessStoreMappings(context, batch); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessStoreMappings"); + } + } - return 0; - } + // localizations + try + { + ProcessLocalizations(context, batch, _localizableProperties); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessLocalizedProperties"); + } - protected virtual int ProcessParentMappings( - ImportExecuteContext context, - IEnumerable> batch, - Dictionary srcToDestId) - { - foreach (var row in batch) - { - var id = row.GetDataValue("Id"); - var rawParentId = row.GetDataValue("ParentCategoryId"); - var parentId = rawParentId.ToInt(-1); + // process pictures + if (srcToDestId.Any() && segmenter.HasColumn("ImageUrl") && !segmenter.IsIgnored("PictureId")) + { + try + { + _categoryRepository.Context.AutoDetectChangesEnabled = true; + ProcessPictures(context, batch, srcToDestId); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessPictures"); + } + finally + { + _categoryRepository.Context.AutoDetectChangesEnabled = false; + } + } + } - if (id != 0 && parentId != -1 && srcToDestId.ContainsKey(id) && srcToDestId.ContainsKey(parentId)) + // map parent id of inserted categories + if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentCategoryId") && !segmenter.IsIgnored("ParentCategoryId")) { - // only touch hierarchical data if child and parent were inserted - if (srcToDestId[id].Inserted && srcToDestId[parentId].Inserted && srcToDestId[id].DestinationId != 0) + segmenter.Reset(); + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { - var category = _categoryRepository.GetById(srcToDestId[id].DestinationId); - if (category != null) - { - category.ParentCategoryId = srcToDestId[parentId].DestinationId; + var batch = segmenter.GetCurrentBatch(); + _categoryRepository.Context.DetachAll(false); - _categoryRepository.Update(category); + try + { + ProcessParentMappings(context, batch, srcToDestId); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessParentMappings"); } } } } - - var num = _categoryRepository.Context.SaveChanges(); - - return num; } protected virtual int ProcessPictures( @@ -206,6 +255,78 @@ protected virtual int ProcessPictures( return num; } + protected virtual int ProcessLocalizations( + ImportExecuteContext context, + IEnumerable> batch, + string[] localizedProperties) + { + if (localizedProperties.Length == 0) + { + return 0; + } + + bool shouldSave = false; + + foreach (var row in batch) + { + foreach (var prop in localizedProperties) + { + var lambda = _localizableProperties[prop]; + foreach (var lang in context.Languages) + { + var code = lang.UniqueSeoCode; + string value; + + if (row.TryGetDataValue(prop /* ColumnName */, code, out value)) + { + _localizedEntityService.SaveLocalizedValue(row.Entity, lambda, value, lang.Id); + shouldSave = true; + } + } + } + } + + if (shouldSave) + { + // commit whole batch at once + return context.Services.DbContext.SaveChanges(); + } + + return 0; + } + + protected virtual int ProcessParentMappings( + ImportExecuteContext context, + IEnumerable> batch, + Dictionary srcToDestId) + { + foreach (var row in batch) + { + var id = row.GetDataValue("Id"); + var rawParentId = row.GetDataValue("ParentCategoryId"); + var parentId = rawParentId.ToInt(-1); + + if (id != 0 && parentId != -1 && srcToDestId.ContainsKey(id) && srcToDestId.ContainsKey(parentId)) + { + // only touch hierarchical data if child and parent were inserted + if (srcToDestId[id].Inserted && srcToDestId[parentId].Inserted && srcToDestId[id].DestinationId != 0) + { + var category = _categoryRepository.GetById(srcToDestId[id].DestinationId); + if (category != null) + { + category.ParentCategoryId = srcToDestId[parentId].DestinationId; + + _categoryRepository.Update(category); + } + } + } + } + + var num = _categoryRepository.Context.SaveChanges(); + + return num; + } + protected virtual int ProcessCategories( ImportExecuteContext context, IEnumerable> batch, @@ -358,127 +479,6 @@ public static string[] DefaultKeyFields } } - protected override void Import(ImportExecuteContext context) - { - var srcToDestId = new Dictionary(); - - var templateViewPaths = _categoryTemplateService.GetAllCategoryTemplates().ToDictionarySafe(x => x.ViewPath, x => x.Id); - - using (var scope = new DbContextScope(ctx: context.Services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) - { - var segmenter = context.DataSegmenter; - - Initialize(context); - - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) - { - var batch = segmenter.GetCurrentBatch(); - - // Perf: detach all entities - _categoryRepository.Context.DetachAll(false); - - context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); - - try - { - ProcessCategories(context, batch, templateViewPaths, srcToDestId); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessCategories"); - } - - // reduce batch to saved (valid) products. - // No need to perform import operations on errored products. - batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); - - // update result object - context.Result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); - context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); - - // process slugs - if (segmenter.HasColumn("SeName", true) || batch.Any(x => x.IsNew || x.NameChanged)) - { - try - { - _categoryRepository.Context.AutoDetectChangesEnabled = true; - ProcessSlugs(context, batch, typeof(Category).Name); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessSlugs"); - } - finally - { - _categoryRepository.Context.AutoDetectChangesEnabled = false; - } - } - - // process store mappings - if (segmenter.HasColumn("StoreIds")) - { - try - { - ProcessStoreMappings(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessStoreMappings"); - } - } - - // localizations - try - { - ProcessLocalizations(context, batch, _localizableProperties); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessLocalizedProperties"); - } - - // process pictures - if (srcToDestId.Any() && segmenter.HasColumn("ImageUrl") && !segmenter.IsIgnored("PictureId")) - { - try - { - _categoryRepository.Context.AutoDetectChangesEnabled = true; - ProcessPictures(context, batch, srcToDestId); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessPictures"); - } - finally - { - _categoryRepository.Context.AutoDetectChangesEnabled = false; - } - } - } - - // map parent id of inserted categories - if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentCategoryId") && !segmenter.IsIgnored("ParentCategoryId")) - { - segmenter.Reset(); - - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) - { - var batch = segmenter.GetCurrentBatch(); - _categoryRepository.Context.DetachAll(false); - - try - { - ProcessParentMappings(context, batch, srcToDestId); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessParentMappings"); - } - } - } - } - } - public class ImportCategoryMapping { public int DestinationId { get; set; } diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 756ca28d01..6228f5b443 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -70,260 +70,166 @@ public ProductImporter( _fileDownloadManager = fileDownloadManager; } - private int? ZeroToNull(object value, CultureInfo culture) - { - int result; - if (CommonHelper.TryConvert(value, culture, out result) && result > 0) - { - return result; - } - - return (int?)null; - } - - protected virtual int ProcessProductMappings( - ImportExecuteContext context, - IEnumerable> batch, - Dictionary srcToDestId) - { - _productRepository.AutoCommitEnabled = false; - - foreach (var row in batch) - { - var id = row.GetDataValue("Id"); - var parentGroupedProductId = row.GetDataValue("ParentGroupedProductId"); - - if (id != 0 && parentGroupedProductId != 0 && srcToDestId.ContainsKey(id) && srcToDestId.ContainsKey(parentGroupedProductId)) - { - // only touch relationship if child and parent were inserted - if (srcToDestId[id].Inserted && srcToDestId[parentGroupedProductId].Inserted && srcToDestId[id].DestinationId != 0) - { - var product = _productRepository.GetById(srcToDestId[id].DestinationId); - if (product != null) - { - product.ParentGroupedProductId = srcToDestId[parentGroupedProductId].DestinationId; - _productRepository.Update(product); - } - } - } - } - - var num = _productRepository.Context.SaveChanges(); - - return num; - } - - protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnumerable> batch) + protected override void Import(ImportExecuteContext context) { - // true, cause pictures must be saved and assigned an id prior adding a mapping. - _productPictureRepository.AutoCommitEnabled = true; + var srcToDestId = new Dictionary(); - ProductPicture lastInserted = null; - var equalPictureId = 0; - var numberOfPictures = (context.ExtraData.NumberOfPictures ?? int.MaxValue); + var templateViewPaths = _productTemplateService.GetAllProductTemplates().ToDictionarySafe(x => x.ViewPath, x => x.Id); - foreach (var row in batch) + using (var scope = new DbContextScope(ctx: _productRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { - var imageUrls = row.GetDataValue>("ImageUrls"); - if (imageUrls.IsNullOrEmpty()) - continue; + var segmenter = context.DataSegmenter; - var imageNumber = 0; - var displayOrder = -1; - var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); - var imageFiles = new List(); + Initialize(context); - // collect required image file infos - foreach (var urlOrPath in imageUrls) + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { - var image = CreateDownloadImage(urlOrPath, seoName, ++imageNumber); - - if (image != null) - imageFiles.Add(image); - - if (imageFiles.Count >= numberOfPictures) - break; - } + var batch = segmenter.GetCurrentBatch(); - // download images - if (imageFiles.Any(x => x.Url.HasValue())) - { - // async downloading in batch processing is inefficient cause only the image processing benefits from async, - // not the record processing itself. a per record processing may speed up the import. + // Perf: detach all entities + _productRepository.Context.DetachAll(false); - AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, imageFiles.Where(x => x.Url.HasValue() && !x.Success.HasValue))); - } + context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); - // import images - foreach (var image in imageFiles.OrderBy(x => x.DisplayOrder)) - { + // =========================================================================== + // 1.) Import products + // =========================================================================== try { - if ((image.Success ?? false) && File.Exists(image.Path)) - { - Succeeded(image); - var pictureBinary = File.ReadAllBytes(image.Path); - - if (pictureBinary != null && pictureBinary.Length > 0) - { - var currentProductPictures = _productPictureRepository.TableUntracked.Expand(x => x.Picture) - .Where(x => x.ProductId == row.Entity.Id) - .ToList(); - - var currentPictures = currentProductPictures - .Select(x => x.Picture) - .ToList(); + ProcessProducts(context, batch, templateViewPaths, srcToDestId); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProducts"); + } - if (displayOrder == -1) - { - displayOrder = (currentProductPictures.Any() ? currentProductPictures.Select(x => x.DisplayOrder).Max() : 0); - } + // reduce batch to saved (valid) products. + // No need to perform import operations on errored products. + batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); - pictureBinary = _pictureService.ValidatePicture(pictureBinary); - pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); + // update result object + context.Result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); + context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); - if (pictureBinary != null && pictureBinary.Length > 0) - { - // no equal picture found in sequence - var newPicture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false); - if (newPicture != null) - { - var mapping = new ProductPicture - { - ProductId = row.Entity.Id, - PictureId = newPicture.Id, - DisplayOrder = ++displayOrder - }; + // =========================================================================== + // 2.) Import SEO Slugs + // IMPORTANT: Unlike with Products AutoCommitEnabled must be TRUE, + // as Slugs are going to be validated against existing ones in DB. + // =========================================================================== + if (segmenter.HasColumn("SeName", true) || batch.Any(x => x.IsNew || x.NameChanged)) + { + try + { + _productRepository.Context.AutoDetectChangesEnabled = true; + ProcessSlugs(context, batch, typeof(Product).Name); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessSlugs"); + } + finally + { + _productRepository.Context.AutoDetectChangesEnabled = false; + } + } - _productPictureRepository.Insert(mapping); - lastInserted = mapping; - } - } - else - { - context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); - } - } + // =========================================================================== + // 3.) Import StoreMappings + // =========================================================================== + if (segmenter.HasColumn("StoreIds")) + { + try + { + ProcessStoreMappings(context, batch); } - else if (image.Url.HasValue()) + catch (Exception exception) { - context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessStoreMappings"); } } + + // =========================================================================== + // 4.) Import Localizations + // =========================================================================== + try + { + ProcessLocalizations(context, batch, _localizableProperties); + } catch (Exception exception) { - context.Result.AddWarning(exception.ToAllMessages(), row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessLocalizations"); } - } - } - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - { - _services.EventPublisher.EntityInserted(lastInserted); - } - } - - protected virtual int ProcessProductManufacturers(ImportExecuteContext context, IEnumerable> batch) - { - _productManufacturerRepository.AutoCommitEnabled = false; - ProductManufacturer lastInserted = null; + // =========================================================================== + // 5.) Import product category mappings + // =========================================================================== + if (segmenter.HasColumn("CategoryIds")) + { + try + { + ProcessProductCategories(context, batch); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductCategories"); + } + } - foreach (var row in batch) - { - var manufacturerIds = row.GetDataValue>("ManufacturerIds"); - if (!manufacturerIds.IsNullOrEmpty()) - { - try + // =========================================================================== + // 6.) Import product manufacturer mappings + // =========================================================================== + if (segmenter.HasColumn("ManufacturerIds")) { - foreach (var id in manufacturerIds) + try { - if (_productManufacturerRepository.TableUntracked.Where(x => x.ProductId == row.Entity.Id && x.ManufacturerId == id).FirstOrDefault() == null) - { - // ensure that manufacturer exists - var manufacturer = _manufacturerService.GetManufacturerById(id); - if (manufacturer != null) - { - var productManufacturer = new ProductManufacturer - { - ProductId = row.Entity.Id, - ManufacturerId = manufacturer.Id, - IsFeaturedProduct = false, - DisplayOrder = 1 - }; - _productManufacturerRepository.Insert(productManufacturer); - lastInserted = productManufacturer; - } - } + ProcessProductManufacturers(context, batch); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductManufacturers"); } } - catch (Exception exception) + + // =========================================================================== + // 7.) Import product picture mappings + // =========================================================================== + if (segmenter.HasColumn("ImageUrls")) { - context.Result.AddWarning(exception.Message, row.GetRowInfo(), "ManufacturerIds"); + try + { + ProcessProductPictures(context, batch); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductPictures"); + } } } - } - - // commit whole batch at once - var num = _productManufacturerRepository.Context.SaveChanges(); - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - _services.EventPublisher.EntityInserted(lastInserted); - return num; - } + // =========================================================================== + // 8.) Map parent id of inserted products + // =========================================================================== + if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentGroupedProductId") && !segmenter.IsIgnored("ParentGroupedProductId")) + { + segmenter.Reset(); - protected virtual int ProcessProductCategories(ImportExecuteContext context, IEnumerable> batch) - { - _productCategoryRepository.AutoCommitEnabled = false; + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + { + var batch = segmenter.GetCurrentBatch(); - ProductCategory lastInserted = null; + _productRepository.Context.DetachAll(false); - foreach (var row in batch) - { - var categoryIds = row.GetDataValue>("CategoryIds"); - if (!categoryIds.IsNullOrEmpty()) - { - try - { - foreach (var id in categoryIds) + try { - if (_productCategoryRepository.TableUntracked.Where(x => x.ProductId == row.Entity.Id && x.CategoryId == id).FirstOrDefault() == null) - { - // ensure that category exists - var category = _categoryService.GetCategoryById(id); - if (category != null) - { - var productCategory = new ProductCategory - { - ProductId = row.Entity.Id, - CategoryId = category.Id, - IsFeaturedProduct = false, - DisplayOrder = 1 - }; - _productCategoryRepository.Insert(productCategory); - lastInserted = productCategory; - } - } + ProcessProductMappings(context, batch, srcToDestId); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessParentMappings"); } - } - catch (Exception exception) - { - context.Result.AddWarning(exception.Message, row.GetRowInfo(), "CategoryIds"); } } } - - // commit whole batch at once - var num = _productCategoryRepository.Context.SaveChanges(); - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - _services.EventPublisher.EntityInserted(lastInserted); - - return num; } protected virtual int ProcessProducts( @@ -541,190 +447,285 @@ protected virtual int ProcessProducts( { _services.EventPublisher.EntityInserted(lastInserted); } - - if (lastUpdated != null) - { - _services.EventPublisher.EntityUpdated(lastUpdated); - } - - return num; - } - - public static string[] SupportedKeyFields - { - get - { - return new string[] { "Id", "Sku", "Gtin", "ManufacturerPartNumber", "Name" }; - } - } - - public static string[] DefaultKeyFields - { - get - { - return new string[] { "Sku", "Gtin", "ManufacturerPartNumber" }; - } + + if (lastUpdated != null) + { + _services.EventPublisher.EntityUpdated(lastUpdated); + } + + return num; + } + + protected virtual int ProcessProductMappings( + ImportExecuteContext context, + IEnumerable> batch, + Dictionary srcToDestId) + { + _productRepository.AutoCommitEnabled = false; + + foreach (var row in batch) + { + var id = row.GetDataValue("Id"); + var parentGroupedProductId = row.GetDataValue("ParentGroupedProductId"); + + if (id != 0 && parentGroupedProductId != 0 && srcToDestId.ContainsKey(id) && srcToDestId.ContainsKey(parentGroupedProductId)) + { + // only touch relationship if child and parent were inserted + if (srcToDestId[id].Inserted && srcToDestId[parentGroupedProductId].Inserted && srcToDestId[id].DestinationId != 0) + { + var product = _productRepository.GetById(srcToDestId[id].DestinationId); + if (product != null) + { + product.ParentGroupedProductId = srcToDestId[parentGroupedProductId].DestinationId; + _productRepository.Update(product); + } + } + } + } + + var num = _productRepository.Context.SaveChanges(); + + return num; + } + + protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnumerable> batch) + { + // true, cause pictures must be saved and assigned an id prior adding a mapping. + _productPictureRepository.AutoCommitEnabled = true; + + ProductPicture lastInserted = null; + var equalPictureId = 0; + var numberOfPictures = (context.ExtraData.NumberOfPictures ?? int.MaxValue); + + foreach (var row in batch) + { + var imageUrls = row.GetDataValue>("ImageUrls"); + if (imageUrls.IsNullOrEmpty()) + continue; + + var imageNumber = 0; + var displayOrder = -1; + var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); + var imageFiles = new List(); + + // collect required image file infos + foreach (var urlOrPath in imageUrls) + { + var image = CreateDownloadImage(urlOrPath, seoName, ++imageNumber); + + if (image != null) + imageFiles.Add(image); + + if (imageFiles.Count >= numberOfPictures) + break; + } + + // download images + if (imageFiles.Any(x => x.Url.HasValue())) + { + // async downloading in batch processing is inefficient cause only the image processing benefits from async, + // not the record processing itself. a per record processing may speed up the import. + + AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, imageFiles.Where(x => x.Url.HasValue() && !x.Success.HasValue))); + } + + // import images + foreach (var image in imageFiles.OrderBy(x => x.DisplayOrder)) + { + try + { + if ((image.Success ?? false) && File.Exists(image.Path)) + { + Succeeded(image); + var pictureBinary = File.ReadAllBytes(image.Path); + + if (pictureBinary != null && pictureBinary.Length > 0) + { + var currentProductPictures = _productPictureRepository.TableUntracked.Expand(x => x.Picture) + .Where(x => x.ProductId == row.Entity.Id) + .ToList(); + + var currentPictures = currentProductPictures + .Select(x => x.Picture) + .ToList(); + + if (displayOrder == -1) + { + displayOrder = (currentProductPictures.Any() ? currentProductPictures.Select(x => x.DisplayOrder).Max() : 0); + } + + pictureBinary = _pictureService.ValidatePicture(pictureBinary); + pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); + + if (pictureBinary != null && pictureBinary.Length > 0) + { + // no equal picture found in sequence + var newPicture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false); + if (newPicture != null) + { + var mapping = new ProductPicture + { + ProductId = row.Entity.Id, + PictureId = newPicture.Id, + DisplayOrder = ++displayOrder + }; + + _productPictureRepository.Insert(mapping); + lastInserted = mapping; + } + } + else + { + context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + } + } + } + else if (image.Url.HasValue()) + { + context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + } + } + catch (Exception exception) + { + context.Result.AddWarning(exception.ToAllMessages(), row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + } + } + } + + // Perf: notify only about LAST insertion and update + if (lastInserted != null) + { + _services.EventPublisher.EntityInserted(lastInserted); + } } - protected override void Import(ImportExecuteContext context) + protected virtual int ProcessProductManufacturers(ImportExecuteContext context, IEnumerable> batch) { - var srcToDestId = new Dictionary(); + _productManufacturerRepository.AutoCommitEnabled = false; - var templateViewPaths = _productTemplateService.GetAllProductTemplates().ToDictionarySafe(x => x.ViewPath, x => x.Id); + ProductManufacturer lastInserted = null; - using (var scope = new DbContextScope(ctx: _productRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) + foreach (var row in batch) { - var segmenter = context.DataSegmenter; - - Initialize(context); - - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + var manufacturerIds = row.GetDataValue>("ManufacturerIds"); + if (!manufacturerIds.IsNullOrEmpty()) { - var batch = segmenter.GetCurrentBatch(); - - // Perf: detach all entities - _productRepository.Context.DetachAll(false); - - context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); - - // =========================================================================== - // 1.) Import products - // =========================================================================== try { - ProcessProducts(context, batch, templateViewPaths, srcToDestId); + foreach (var id in manufacturerIds) + { + if (_productManufacturerRepository.TableUntracked.Where(x => x.ProductId == row.Entity.Id && x.ManufacturerId == id).FirstOrDefault() == null) + { + // ensure that manufacturer exists + var manufacturer = _manufacturerService.GetManufacturerById(id); + if (manufacturer != null) + { + var productManufacturer = new ProductManufacturer + { + ProductId = row.Entity.Id, + ManufacturerId = manufacturer.Id, + IsFeaturedProduct = false, + DisplayOrder = 1 + }; + _productManufacturerRepository.Insert(productManufacturer); + lastInserted = productManufacturer; + } + } + } } catch (Exception exception) { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProducts"); + context.Result.AddWarning(exception.Message, row.GetRowInfo(), "ManufacturerIds"); } + } + } - // reduce batch to saved (valid) products. - // No need to perform import operations on errored products. - batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); + // commit whole batch at once + var num = _productManufacturerRepository.Context.SaveChanges(); - // update result object - context.Result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); - context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); + // Perf: notify only about LAST insertion and update + if (lastInserted != null) + _services.EventPublisher.EntityInserted(lastInserted); - // =========================================================================== - // 2.) Import SEO Slugs - // IMPORTANT: Unlike with Products AutoCommitEnabled must be TRUE, - // as Slugs are going to be validated against existing ones in DB. - // =========================================================================== - if (segmenter.HasColumn("SeName", true) || batch.Any(x => x.IsNew || x.NameChanged)) - { - try - { - _productRepository.Context.AutoDetectChangesEnabled = true; - ProcessSlugs(context, batch, typeof(Product).Name); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessSlugs"); - } - finally - { - _productRepository.Context.AutoDetectChangesEnabled = false; - } - } + return num; + } - // =========================================================================== - // 3.) Import StoreMappings - // =========================================================================== - if (segmenter.HasColumn("StoreIds")) - { - try - { - ProcessStoreMappings(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessStoreMappings"); - } - } + protected virtual int ProcessProductCategories(ImportExecuteContext context, IEnumerable> batch) + { + _productCategoryRepository.AutoCommitEnabled = false; - // =========================================================================== - // 4.) Import Localizations - // =========================================================================== + ProductCategory lastInserted = null; + + foreach (var row in batch) + { + var categoryIds = row.GetDataValue>("CategoryIds"); + if (!categoryIds.IsNullOrEmpty()) + { try { - ProcessLocalizations(context, batch, _localizableProperties); + foreach (var id in categoryIds) + { + if (_productCategoryRepository.TableUntracked.Where(x => x.ProductId == row.Entity.Id && x.CategoryId == id).FirstOrDefault() == null) + { + // ensure that category exists + var category = _categoryService.GetCategoryById(id); + if (category != null) + { + var productCategory = new ProductCategory + { + ProductId = row.Entity.Id, + CategoryId = category.Id, + IsFeaturedProduct = false, + DisplayOrder = 1 + }; + _productCategoryRepository.Insert(productCategory); + lastInserted = productCategory; + } + } + } } catch (Exception exception) { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessLocalizations"); + context.Result.AddWarning(exception.Message, row.GetRowInfo(), "CategoryIds"); } + } + } - // =========================================================================== - // 5.) Import product category mappings - // =========================================================================== - if (segmenter.HasColumn("CategoryIds")) - { - try - { - ProcessProductCategories(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductCategories"); - } - } + // commit whole batch at once + var num = _productCategoryRepository.Context.SaveChanges(); - // =========================================================================== - // 6.) Import product manufacturer mappings - // =========================================================================== - if (segmenter.HasColumn("ManufacturerIds")) - { - try - { - ProcessProductManufacturers(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductManufacturers"); - } - } + // Perf: notify only about LAST insertion and update + if (lastInserted != null) + _services.EventPublisher.EntityInserted(lastInserted); - // =========================================================================== - // 7.) Import product picture mappings - // =========================================================================== - if (segmenter.HasColumn("ImageUrls")) - { - try - { - ProcessProductPictures(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductPictures"); - } - } - } + return num; + } - // =========================================================================== - // 8.) Map parent id of inserted products - // =========================================================================== - if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentGroupedProductId") && !segmenter.IsIgnored("ParentGroupedProductId")) - { - segmenter.Reset(); - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) - { - var batch = segmenter.GetCurrentBatch(); + private int? ZeroToNull(object value, CultureInfo culture) + { + int result; + if (CommonHelper.TryConvert(value, culture, out result) && result > 0) + { + return result; + } - _productRepository.Context.DetachAll(false); + return (int?)null; + } - try - { - ProcessProductMappings(context, batch, srcToDestId); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessParentMappings"); - } - } - } + public static string[] SupportedKeyFields + { + get + { + return new string[] { "Id", "Sku", "Gtin", "ManufacturerPartNumber", "Name" }; + } + } + + public static string[] DefaultKeyFields + { + get + { + return new string[] { "Sku", "Gtin", "ManufacturerPartNumber" }; } } diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index da0d917059..452f281fb8 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -70,70 +70,263 @@ public CustomerImporter( _forumSettings = forumSettings; } - private int? CountryCodeToId(Dictionary allCountries, string code) + + protected override void Import(ImportExecuteContext context) { - int countryId; - if (code.HasValue() && allCountries.TryGetValue(code, out countryId) && countryId != 0) + var customer = _services.WorkContext.CurrentCustomer; + var allowManagingCustomerRoles = _services.Permissions.Authorize(StandardPermissionProvider.ManageCustomerRoles, customer); + + var allAffiliateIds = _affiliateService.GetAllAffiliates(true) + .Select(x => x.Id) + .ToList(); + + var allCountries = new Dictionary(); + foreach (var country in _countryService.GetAllCountries(true)) { - return countryId; + if (!allCountries.ContainsKey(country.TwoLetterIsoCode)) + allCountries.Add(country.TwoLetterIsoCode, country.Id); + + if (!allCountries.ContainsKey(country.ThreeLetterIsoCode)) + allCountries.Add(country.ThreeLetterIsoCode, country.Id); } - return null; - } + var allStateProvinces = _stateProvinceService.GetAllStateProvinces(true) + .ToDictionarySafe(x => new Tuple(x.CountryId, x.Abbreviation), x => x.Id); - private int? StateAbbreviationToId(Dictionary, int> allStateProvinces, int? countryId, string abbreviation) - { - if (countryId.HasValue && abbreviation.HasValue()) + var allCustomerNumbers = new HashSet( + _genericAttributeService.GetAttributes(SystemCustomerAttributeNames.CustomerNumber, _attributeKeyGroup).Select(x => x.Value), + StringComparer.OrdinalIgnoreCase); + + using (var scope = new DbContextScope(ctx: _services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) { - var key = Tuple.Create(countryId.Value, abbreviation); + var segmenter = context.DataSegmenter; - int stateId; - if (allStateProvinces.TryGetValue(key, out stateId) && stateId != 0) + Initialize(context); + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { - return stateId; + var batch = segmenter.GetCurrentBatch(); + + _customerRepository.Context.DetachAll(false); + + context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); + + // =========================================================================== + // Process customers + // =========================================================================== + try + { + ProcessCustomers(context, batch, allAffiliateIds); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessCustomers"); + } + + // reduce batch to saved (valid) records. + // No need to perform import operations on errored records. + batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); + + // update result object + context.Result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); + context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); + + // =========================================================================== + // Process generic attributes + // =========================================================================== + try + { + ProcessGenericAttributes(context, batch, allCountries, allStateProvinces, allCustomerNumbers); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessGenericAttributes"); + } + + // =========================================================================== + // Process avatars + // =========================================================================== + if (_customerSettings.AllowCustomersToUploadAvatars) + { + try + { + ProcessAvatars(context, batch); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessAvatars"); + } + } + + // =========================================================================== + // Process addresses + // =========================================================================== + try + { + _services.DbContext.AutoDetectChangesEnabled = true; + ProcessAddresses(context, batch, allCountries, allStateProvinces); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessAddresses"); + } + finally + { + _services.DbContext.AutoDetectChangesEnabled = false; + } } } - - return null; } - private void SaveAttribute(ImportRow row, string key) + protected virtual int ProcessCustomers( + ImportExecuteContext context, + IEnumerable> batch, + List allAffiliateIds) { - SaveAttribute(row, key, row.GetDataValue(key)); - } + _customerRepository.AutoCommitEnabled = true; - private void SaveAttribute(ImportRow row, string key) - { + Customer lastInserted = null; + Customer lastUpdated = null; + var currentCustomer = _services.WorkContext.CurrentCustomer; - SaveAttribute(row, key, row.GetDataValue(key)); - } + var guestRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Guests); + var registeredRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered); + var forumModeratorRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.ForumModerators); - private void SaveAttribute(ImportRow row, string key, TPropType value) - { - if (row.IsTransient) - return; + var customerQuery = _customerRepository.Table.Expand(x => x.Addresses); - if (row.IsNew || value!= null) + foreach (var row in batch) { - _genericAttributeService.SaveAttribute(row.Entity.Id, key, _attributeKeyGroup, value); - } - } + Customer customer = null; + var id = row.GetDataValue("Id"); + var email = row.GetDataValue("Email"); - private void UpsertRole(ImportRow row, CustomerRole role, bool value) - { - if (role == null) - return; + foreach (var keyName in context.KeyFieldNames) + { + switch (keyName) + { + case "Id": + if (id != 0) + { + customer = customerQuery.FirstOrDefault(x => x.Id == id); + } + break; + case "CustomerGuid": + var customerGuid = row.GetDataValue("CustomerGuid"); + if (customerGuid.HasValue()) + { + var guid = new Guid(customerGuid); + customer = customerQuery.FirstOrDefault(x => x.CustomerGuid == guid); + } + break; + case "Email": + if (email.HasValue()) + { + customer = customerQuery.FirstOrDefault(x => x.Email == email); + } + break; + case "Username": + var userName = row.GetDataValue("Username"); + if (userName.HasValue()) + { + customer = customerQuery.FirstOrDefault(x => x.Username == userName); + } + break; + } - var hasRole = row.Entity.CustomerRoles.Any(x => x.SystemName == role.SystemName); + if (customer != null) + break; + } - if (value && !hasRole) + if (customer == null) + { + if (context.UpdateOnly) + { + ++context.Result.SkippedRecords; + continue; + } + + customer = new Customer + { + CustomerGuid = new Guid(), + AffiliateId = 0, + Active = true + }; + } + else + { + _customerRepository.Context.LoadCollection(customer, (Customer x) => x.CustomerRoles); + } + + var isGuest = row.GetDataValue("IsGuest"); + var isRegistered = row.GetDataValue("IsRegistered"); + var isAdmin = row.GetDataValue("IsAdministrator"); + var isForumModerator = row.GetDataValue("IsForumModerator"); + var affiliateId = row.GetDataValue("AffiliateId"); + + row.Initialize(customer, email ?? id.ToString()); + + row.SetProperty(context.Result, (x) => x.CustomerGuid); + row.SetProperty(context.Result, (x) => x.Username); + row.SetProperty(context.Result, (x) => x.Email); + + if (email.HasValue() && currentCustomer.Email.IsCaseInsensitiveEqual(email)) + { + context.Result.AddInfo("Security. Ignored password of current customer (who started this import).", row.GetRowInfo(), "Password"); + } + else + { + row.SetProperty(context.Result, (x) => x.Password); + row.SetProperty(context.Result, (x) => x.PasswordFormatId); + row.SetProperty(context.Result, (x) => x.PasswordSalt); + } + + row.SetProperty(context.Result, (x) => x.AdminComment); + row.SetProperty(context.Result, (x) => x.IsTaxExempt); + row.SetProperty(context.Result, (x) => x.Active); + + row.SetProperty(context.Result, (x) => x.CreatedOnUtc, UtcNow); + row.SetProperty(context.Result, (x) => x.LastActivityDateUtc, UtcNow); + + if (affiliateId > 0 && allAffiliateIds.Contains(affiliateId)) + { + customer.AffiliateId = affiliateId; + } + + if (isAdmin) + { + context.Result.AddInfo("Security. Ignored administrator role.", row.GetRowInfo(), "IsAdministrator"); + } + + UpsertRole(row, guestRole, isGuest); + UpsertRole(row, registeredRole, isRegistered); + UpsertRole(row, forumModeratorRole, isForumModerator); + + if (row.IsTransient) + { + _customerRepository.Insert(customer); + lastInserted = customer; + } + else + { + _customerRepository.Update(customer); + lastUpdated = customer; + } + } + + var num = _customerRepository.Context.SaveChanges(); + + if (lastInserted != null) { - row.Entity.CustomerRoles.Add(role); + _services.EventPublisher.EntityInserted(lastInserted); } - else if (!value && hasRole) + + if (lastUpdated != null) { - row.Entity.CustomerRoles.Remove(role); + _services.EventPublisher.EntityUpdated(lastUpdated); } + + return num; } protected virtual int ProcessAddresses( @@ -272,258 +465,107 @@ protected virtual int ProcessGenericAttributes( SaveAttribute(row, SystemCustomerAttributeNames.Phone); if (_customerSettings.FaxEnabled) - SaveAttribute(row, SystemCustomerAttributeNames.Fax); - - if (_forumSettings.ForumsEnabled) - SaveAttribute(row, SystemCustomerAttributeNames.ForumPostCount); - - if (_forumSettings.SignaturesEnabled) - SaveAttribute(row, SystemCustomerAttributeNames.Signature); - - var countryId = CountryCodeToId(allCountries, row.GetDataValue("CountryCode")); - var stateId = StateAbbreviationToId(allStateProvinces, countryId, row.GetDataValue("StateAbbreviation")); - - if (countryId.HasValue) - { - SaveAttribute(row, SystemCustomerAttributeNames.CountryId, countryId.Value); - } - - if (stateId.HasValue) - { - SaveAttribute(row, SystemCustomerAttributeNames.StateProvinceId, stateId.Value); - } - - string customerNumber = null; - - if (_customerSettings.CustomerNumberMethod == CustomerNumberMethod.AutomaticallySet) - customerNumber = row.Entity.Id.ToString(); - else - customerNumber = row.GetDataValue("CustomerNumber"); - - if (customerNumber.IsEmpty() || !allCustomerNumbers.Contains(customerNumber)) - { - SaveAttribute(row, SystemCustomerAttributeNames.CustomerNumber, customerNumber); - - if (!customerNumber.IsEmpty()) - allCustomerNumbers.Add(customerNumber); - } - } - - return _services.DbContext.SaveChanges(); - } - - protected virtual int ProcessAvatars( - ImportExecuteContext context, - IEnumerable> batch) - { - foreach (var row in batch) - { - var urlOrPath = row.GetDataValue("AvatarPictureUrl"); - if (urlOrPath.IsEmpty()) - continue; - - Picture picture = null; - var equalPictureId = 0; - var currentPictures = new List(); - var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); - - var image = CreateDownloadImage(urlOrPath, seoName, 1); - if (image == null) - continue; - - if (image.Url.HasValue() && !image.Success.HasValue) - { - AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, new FileDownloadManagerItem[] { image })); - } - - if ((image.Success ?? false) && File.Exists(image.Path)) - { - Succeeded(image); - var pictureBinary = File.ReadAllBytes(image.Path); - - if (pictureBinary != null && pictureBinary.Length > 0) - { - var currentPictureId = row.Entity.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId); - if (currentPictureId != 0 && (picture = _pictureRepository.GetById(currentPictureId)) != null) - { - currentPictures.Add(picture); - } - - pictureBinary = _pictureService.ValidatePicture(pictureBinary); - pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); - - if (pictureBinary != null && pictureBinary.Length > 0) - { - if ((picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false)) != null) - { - _pictureRepository.Context.SaveChanges(); - SaveAttribute(row, SystemCustomerAttributeNames.AvatarPictureId, picture.Id); - } - } - else - { - context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "AvatarPictureUrl"); - } - } - } - else - { - context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "AvatarPictureUrl"); - } - } - - return _services.DbContext.SaveChanges(); - } - - protected virtual int ProcessCustomers( - ImportExecuteContext context, - IEnumerable> batch, - List allAffiliateIds) - { - _customerRepository.AutoCommitEnabled = true; - - Customer lastInserted = null; - Customer lastUpdated = null; - var currentCustomer = _services.WorkContext.CurrentCustomer; - - var guestRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Guests); - var registeredRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered); - var forumModeratorRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.ForumModerators); - - var customerQuery = _customerRepository.Table.Expand(x => x.Addresses); - - foreach (var row in batch) - { - Customer customer = null; - var id = row.GetDataValue("Id"); - var email = row.GetDataValue("Email"); - - foreach (var keyName in context.KeyFieldNames) - { - switch (keyName) - { - case "Id": - if (id != 0) - { - customer = customerQuery.FirstOrDefault(x => x.Id == id); - } - break; - case "CustomerGuid": - var customerGuid = row.GetDataValue("CustomerGuid"); - if (customerGuid.HasValue()) - { - var guid = new Guid(customerGuid); - customer = customerQuery.FirstOrDefault(x => x.CustomerGuid == guid); - } - break; - case "Email": - if (email.HasValue()) - { - customer = customerQuery.FirstOrDefault(x => x.Email == email); - } - break; - case "Username": - var userName = row.GetDataValue("Username"); - if (userName.HasValue()) - { - customer = customerQuery.FirstOrDefault(x => x.Username == userName); - } - break; - } - - if (customer != null) - break; - } - - if (customer == null) - { - if (context.UpdateOnly) - { - ++context.Result.SkippedRecords; - continue; - } - - customer = new Customer - { - CustomerGuid = new Guid(), - AffiliateId = 0, - Active = true - }; - } - else - { - _customerRepository.Context.LoadCollection(customer, (Customer x) => x.CustomerRoles); - } + SaveAttribute(row, SystemCustomerAttributeNames.Fax); - var isGuest = row.GetDataValue("IsGuest"); - var isRegistered = row.GetDataValue("IsRegistered"); - var isAdmin = row.GetDataValue("IsAdministrator"); - var isForumModerator = row.GetDataValue("IsForumModerator"); - var affiliateId = row.GetDataValue("AffiliateId"); + if (_forumSettings.ForumsEnabled) + SaveAttribute(row, SystemCustomerAttributeNames.ForumPostCount); - row.Initialize(customer, email ?? id.ToString()); + if (_forumSettings.SignaturesEnabled) + SaveAttribute(row, SystemCustomerAttributeNames.Signature); - row.SetProperty(context.Result, (x) => x.CustomerGuid); - row.SetProperty(context.Result, (x) => x.Username); - row.SetProperty(context.Result, (x) => x.Email); + var countryId = CountryCodeToId(allCountries, row.GetDataValue("CountryCode")); + var stateId = StateAbbreviationToId(allStateProvinces, countryId, row.GetDataValue("StateAbbreviation")); - if (email.HasValue() && currentCustomer.Email.IsCaseInsensitiveEqual(email)) + if (countryId.HasValue) { - context.Result.AddInfo("Security. Ignored password of current customer (who started this import).", row.GetRowInfo(), "Password"); + SaveAttribute(row, SystemCustomerAttributeNames.CountryId, countryId.Value); } - else + + if (stateId.HasValue) { - row.SetProperty(context.Result, (x) => x.Password); - row.SetProperty(context.Result, (x) => x.PasswordFormatId); - row.SetProperty(context.Result, (x) => x.PasswordSalt); + SaveAttribute(row, SystemCustomerAttributeNames.StateProvinceId, stateId.Value); } - row.SetProperty(context.Result, (x) => x.AdminComment); - row.SetProperty(context.Result, (x) => x.IsTaxExempt); - row.SetProperty(context.Result, (x) => x.Active); + string customerNumber = null; - row.SetProperty(context.Result, (x) => x.CreatedOnUtc, UtcNow); - row.SetProperty(context.Result, (x) => x.LastActivityDateUtc, UtcNow); + if (_customerSettings.CustomerNumberMethod == CustomerNumberMethod.AutomaticallySet) + customerNumber = row.Entity.Id.ToString(); + else + customerNumber = row.GetDataValue("CustomerNumber"); - if (affiliateId > 0 && allAffiliateIds.Contains(affiliateId)) + if (customerNumber.IsEmpty() || !allCustomerNumbers.Contains(customerNumber)) { - customer.AffiliateId = affiliateId; - } + SaveAttribute(row, SystemCustomerAttributeNames.CustomerNumber, customerNumber); - if (isAdmin) - { - context.Result.AddInfo("Security. Ignored administrator role.", row.GetRowInfo(), "IsAdministrator"); + if (!customerNumber.IsEmpty()) + allCustomerNumbers.Add(customerNumber); } + } - UpsertRole(row, guestRole, isGuest); - UpsertRole(row, registeredRole, isRegistered); - UpsertRole(row, forumModeratorRole, isForumModerator); + return _services.DbContext.SaveChanges(); + } - if (row.IsTransient) + protected virtual int ProcessAvatars( + ImportExecuteContext context, + IEnumerable> batch) + { + foreach (var row in batch) + { + var urlOrPath = row.GetDataValue("AvatarPictureUrl"); + if (urlOrPath.IsEmpty()) + continue; + + Picture picture = null; + var equalPictureId = 0; + var currentPictures = new List(); + var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); + + var image = CreateDownloadImage(urlOrPath, seoName, 1); + if (image == null) + continue; + + if (image.Url.HasValue() && !image.Success.HasValue) { - _customerRepository.Insert(customer); - lastInserted = customer; + AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, new FileDownloadManagerItem[] { image })); } - else + + if ((image.Success ?? false) && File.Exists(image.Path)) { - _customerRepository.Update(customer); - lastUpdated = customer; - } - } + Succeeded(image); + var pictureBinary = File.ReadAllBytes(image.Path); - var num = _customerRepository.Context.SaveChanges(); + if (pictureBinary != null && pictureBinary.Length > 0) + { + var currentPictureId = row.Entity.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId); + if (currentPictureId != 0 && (picture = _pictureRepository.GetById(currentPictureId)) != null) + { + currentPictures.Add(picture); + } - if (lastInserted != null) - { - _services.EventPublisher.EntityInserted(lastInserted); - } + pictureBinary = _pictureService.ValidatePicture(pictureBinary); + pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); - if (lastUpdated != null) - { - _services.EventPublisher.EntityUpdated(lastUpdated); + if (pictureBinary != null && pictureBinary.Length > 0) + { + if ((picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false)) != null) + { + _pictureRepository.Context.SaveChanges(); + SaveAttribute(row, SystemCustomerAttributeNames.AvatarPictureId, picture.Id); + } + } + else + { + context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "AvatarPictureUrl"); + } + } + } + else + { + context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "AvatarPictureUrl"); + } } - return num; + return _services.DbContext.SaveChanges(); } public static string[] SupportedKeyFields @@ -542,110 +584,69 @@ public static string[] DefaultKeyFields } } - protected override void Import(ImportExecuteContext context) + private int? CountryCodeToId(Dictionary allCountries, string code) { - var customer = _services.WorkContext.CurrentCustomer; - var allowManagingCustomerRoles = _services.Permissions.Authorize(StandardPermissionProvider.ManageCustomerRoles, customer); - - var allAffiliateIds = _affiliateService.GetAllAffiliates(true) - .Select(x => x.Id) - .ToList(); - - var allCountries = new Dictionary(); - foreach (var country in _countryService.GetAllCountries(true)) + int countryId; + if (code.HasValue() && allCountries.TryGetValue(code, out countryId) && countryId != 0) { - if (!allCountries.ContainsKey(country.TwoLetterIsoCode)) - allCountries.Add(country.TwoLetterIsoCode, country.Id); - - if (!allCountries.ContainsKey(country.ThreeLetterIsoCode)) - allCountries.Add(country.ThreeLetterIsoCode, country.Id); + return countryId; } - var allStateProvinces = _stateProvinceService.GetAllStateProvinces(true) - .ToDictionarySafe(x => new Tuple(x.CountryId, x.Abbreviation), x => x.Id); - - var allCustomerNumbers = new HashSet( - _genericAttributeService.GetAttributes(SystemCustomerAttributeNames.CustomerNumber, _attributeKeyGroup).Select(x => x.Value), - StringComparer.OrdinalIgnoreCase); + return null; + } - using (var scope = new DbContextScope(ctx: _services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) + private int? StateAbbreviationToId(Dictionary, int> allStateProvinces, int? countryId, string abbreviation) + { + if (countryId.HasValue && abbreviation.HasValue()) { - var segmenter = context.DataSegmenter; - - Initialize(context); + var key = Tuple.Create(countryId.Value, abbreviation); - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + int stateId; + if (allStateProvinces.TryGetValue(key, out stateId) && stateId != 0) { - var batch = segmenter.GetCurrentBatch(); + return stateId; + } + } - _customerRepository.Context.DetachAll(false); + return null; + } - context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); + private void SaveAttribute(ImportRow row, string key) + { + SaveAttribute(row, key, row.GetDataValue(key)); + } - // =========================================================================== - // Process customers - // =========================================================================== - try - { - ProcessCustomers(context, batch, allAffiliateIds); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessCustomers"); - } + private void SaveAttribute(ImportRow row, string key) + { - // reduce batch to saved (valid) records. - // No need to perform import operations on errored records. - batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); + SaveAttribute(row, key, row.GetDataValue(key)); + } - // update result object - context.Result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); - context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); + private void SaveAttribute(ImportRow row, string key, TPropType value) + { + if (row.IsTransient) + return; - // =========================================================================== - // Process generic attributes - // =========================================================================== - try - { - ProcessGenericAttributes(context, batch, allCountries, allStateProvinces, allCustomerNumbers); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessGenericAttributes"); - } + if (row.IsNew || value != null) + { + _genericAttributeService.SaveAttribute(row.Entity.Id, key, _attributeKeyGroup, value); + } + } - // =========================================================================== - // Process avatars - // =========================================================================== - if (_customerSettings.AllowCustomersToUploadAvatars) - { - try - { - ProcessAvatars(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessAvatars"); - } - } + private void UpsertRole(ImportRow row, CustomerRole role, bool value) + { + if (role == null) + return; - // =========================================================================== - // Process addresses - // =========================================================================== - try - { - _services.DbContext.AutoDetectChangesEnabled = true; - ProcessAddresses(context, batch, allCountries, allStateProvinces); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessAddresses"); - } - finally - { - _services.DbContext.AutoDetectChangesEnabled = false; - } - } + var hasRole = row.Entity.CustomerRoles.Any(x => x.SystemName == role.SystemName); + + if (value && !hasRole) + { + row.Entity.CustomerRoles.Add(role); + } + else if (!value && hasRole) + { + row.Entity.CustomerRoles.Remove(role); } } } From 81a440a64830a63e1cc218ce4fd3a9ef20d0432c Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 May 2016 23:51:17 +0200 Subject: [PATCH 052/185] Updated changelog --- changelog.md | 6 +++++- src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 8e188f25e4..c995ef0207 100644 --- a/changelog.md +++ b/changelog.md @@ -3,9 +3,12 @@ ## SmartStore.NET 2.5.1 ### Highlights -* #637 Integrate PayPal PLUS Provider +* #637 Integrated PayPal PLUS payment Provider +* Major improvements in Importer: better field mapping, higher performance, bug fixes etc. ### New Features +* #637 Integrated PayPal PLUS payment Provider +* #961 Fix "Open Redirection Vulnerability" * #571 Option to display another checkbox on confirm page to let the customer accept that his email address can be handed over to a third party * #870 Implement address import for customers (both billing & shipping address) * #886 Add setting to hide manufacturer images on product detail page and to hide default image for manufacturers @@ -13,6 +16,7 @@ * #477 Implement option to specify the number of exported and imported pictures ### Improvements +* Major improvements in Importer: better field mapping, higher performance, bug fixes etc. * (Dev) Publishing SmartStore.Web from within Visual Studio now deploys the project correctly. No need to execute ClickToBuild.cmd anymore. Just publish directly to any target, including Azure. * Localization & SEO: language switcher now takes language specific SEO slugs into account when building links * Exports the product detail link including the attribute query string when exporting attribute combinations diff --git a/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs b/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs index 67337bbb66..29c29d13ef 100644 --- a/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs +++ b/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs @@ -243,7 +243,7 @@ public static string ValidateSeName(this T entity, } } - // validate and alter sename if it could be interpreted as SEO code + // validate and alter SeName if it could be interpreted as SEO code if (LocalizationHelper.IsValidCultureCode(seName)) { if (seName.Length == 2) From 94038d8a2d816b3cb8d39f3a4031ba11f422791e Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 13 May 2016 10:35:26 +0200 Subject: [PATCH 053/185] Resolves #963 Import: ColumnMapping > lock SeName default value field --- .../Administration/Controllers/ImportController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index fdb726fe6c..8de7f54e5c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -94,9 +94,9 @@ private string[] GetDisabledDefaultFieldNames(ImportProfile profile) switch (profile.EntityType) { case ImportEntityType.Product: - return new string[] { "Name", "Sku", "ManufacturerPartNumber", "Gtin" }; + return new string[] { "Name", "Sku", "ManufacturerPartNumber", "Gtin", "SeName" }; case ImportEntityType.Category: - return new string[] { "Name" }; + return new string[] { "Name", "SeName" }; case ImportEntityType.Customer: return new string[] { "CustomerGuid", "Email" }; case ImportEntityType.NewsLetterSubscription: From 8e207d6107436f5a6350a423d4f3657b9f56ea7d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 13 May 2016 13:40:41 +0200 Subject: [PATCH 054/185] Resolves #921 Specification attribute options with single quotation marks are causing a Javascript error --- changelog.md | 1 + .../Administration/Controllers/ProductController.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index c995ef0207..b5e154c8f0 100644 --- a/changelog.md +++ b/changelog.md @@ -42,6 +42,7 @@ * Mobile devices: Fixed "Uncaught Error: Syntax error, unrecognized expression: :nth-child" * Download nor sample download were removable when editing product * Copied product must not share sample download of source product. Could produce "The DELETE statement conflicted with the REFERENCE constraint 'FK_dbo.Product_dbo.Download_SampleDownloadId'". +* #921 Specification attribute options with single quotation marks are causing a Javascript error ## SmartStore.NET 2.5 diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 8ea467ea2c..752a19c41c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Data; using System.Linq; +using System.Web; using System.Web.Mvc; using Autofac; using Newtonsoft.Json; @@ -2313,7 +2314,7 @@ public ActionResult ProductSpecAttrList(GridCommand command, int productId) }); } - attr.SpecificationAttributeOptionsJsonString = JsonConvert.SerializeObject(attr.SpecificationAttributeOptions); + attr.SpecificationAttributeOptionsJsonString = HttpUtility.HtmlEncode(JsonConvert.SerializeObject(attr.SpecificationAttributeOptions)); } model.Data = productrSpecsModel; From 811246b73bf5622ce9a9f4058c60726edd363ac8 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 13 May 2016 19:34:46 +0200 Subject: [PATCH 055/185] Added paging to frontend order list --- changelog.md | 1 + .../Domain/Orders/OrderSettings.cs | 9 ++- .../201605111140288_ImportExtraData.cs | 13 ++++ .../Models/Settings/OrderSettingsModel.cs | 4 + .../Settings/OrderSettingsValidator.cs | 7 +- .../Administration/Views/Setting/Order.cshtml | 9 +++ .../Controllers/CustomerController.cs | 48 ++++++------ .../Models/Customer/CustomerOrderListModel.cs | 13 ++-- .../Views/Customer/Orders.Mobile.cshtml | 8 +- .../Views/Customer/Orders.cshtml | 75 +++++++++---------- 10 files changed, 116 insertions(+), 71 deletions(-) diff --git a/changelog.md b/changelog.md index b5e154c8f0..7327aee605 100644 --- a/changelog.md +++ b/changelog.md @@ -23,6 +23,7 @@ * #918 Compare products: Display base price information * Export email attachments needs to be stored in database because the temp file may not exist anymore when sending the email * #913 Use HTML5 Input types (tel, email) +* Added paging to frontend order list ### Bugfixes * Fixed ajax cache issue when saving payment or shipping restrictions. Internet Explorer showed the old data state (before storage). diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/OrderSettings.cs b/src/Libraries/SmartStore.Core/Domain/Orders/OrderSettings.cs index 996271de79..05e9a49fd4 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/OrderSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/OrderSettings.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using SmartStore.Core.Configuration; +using SmartStore.Core.Configuration; using SmartStore.Core.Domain.Localization; namespace SmartStore.Core.Domain.Orders @@ -16,6 +15,7 @@ public OrderSettings() ReturnRequestReasons = "Received Wrong Product,Wrong Product Ordered,There Was A Problem With The Product"; NumberOfDaysReturnRequestAvailable = 365; MinimumOrderPlacementInterval = 30; + OrderListPageSize = 10; } /// @@ -87,5 +87,10 @@ public OrderSettings() /// Gets or sets a value indicating whether to display all orders of all stores to a customer /// public bool DisplayOrdersOfAllStores { get; set; } + + /// + /// Page size of the order list + /// + public int OrderListPageSize { get; set; } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs index 17d0564ce6..0d7b6fcc06 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs @@ -38,6 +38,19 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Anzahl der Bilder", "Specifies the number of images per object to be imported.", "Legt die Anzahl der zu importierenden Bilder pro Objekt fest."); + + builder.Update("Admin.Configuration.Settings.Catalog.DefaultPageSizeOptions") + .Value("en", "Number of displayed products per page"); + + builder.AddOrUpdate("Admin.Validation.ValueGreaterZero", + "The value must be greater than 0.", + "Der Wert muss größer 0 sein."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Order.OrderListPageSize", + "Number of displayed orders per page", + "Anzahl der Aufträge pro Seite", + "Specifies the number of displayed orders per page.", + "Legt die Anzahl der dargestellten Aufträge pro Seite fest."); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/OrderSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/OrderSettingsModel.cs index c54a264eb5..c0e73f70fb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/OrderSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/OrderSettingsModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Admin.Validators.Settings; @@ -66,6 +67,9 @@ public OrderSettingsModel() [SmartResourceDisplayName("Admin.Configuration.Settings.Order.DisplayOrdersOfAllStores")] public bool DisplayOrdersOfAllStores { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Order.OrderListPageSize")] + public int OrderListPageSize { get; set; } + public IList Locales { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/Settings/OrderSettingsValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/Settings/OrderSettingsValidator.cs index eab92160eb..547872cac6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Validators/Settings/OrderSettingsValidator.cs +++ b/src/Presentation/SmartStore.Web/Administration/Validators/Settings/OrderSettingsValidator.cs @@ -11,8 +11,13 @@ public OrderSettingsValidator(ILocalizationService localizationService) { RuleFor(x => x.GiftCards_Activated_OrderStatusId).NotEqual((int)OrderStatus.Pending) .WithMessage(localizationService.GetResource("Admin.Configuration.Settings.RewardPoints.PointsForPurchases_Awarded.Pending")); + RuleFor(x => x.GiftCards_Deactivated_OrderStatusId).NotEqual((int)OrderStatus.Pending) .WithMessage(localizationService.GetResource("Admin.Configuration.Settings.RewardPoints.PointsForPurchases_Canceled.Pending")); - } + + RuleFor(x => x.OrderListPageSize) + .GreaterThan(0) + .WithMessage(localizationService.GetResource("Admin.Validation.ValueGreaterZero")); + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml index 37cb664577..c10c4c8f52 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml @@ -105,6 +105,15 @@ @Html.ValidationMessageFor(model => model.GiftCards_Deactivated_OrderStatusId) + + + @Html.SmartLabelFor(model => model.OrderListPageSize) + + + @Html.EditorFor(model => model.OrderListPageSize) + @Html.ValidationMessageFor(model => model.OrderListPageSize) + + @if (Model.OrderIdent.HasValue) { diff --git a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs index 9ff54c6482..cd3eb7dd5d 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs @@ -352,35 +352,40 @@ protected void PrepareCustomerInfoModel(CustomerInfoModel model, Customer custom } [NonAction] - protected CustomerOrderListModel PrepareCustomerOrderListModel(Customer customer) + protected CustomerOrderListModel PrepareCustomerOrderListModel(Customer customer, int pageIndex) { if (customer == null) throw new ArgumentNullException("customer"); - var model = new CustomerOrderListModel(); + var storeScope = (_orderSettings.DisplayOrdersOfAllStores ? 0 : _storeContext.CurrentStore.Id); + + var model = new CustomerOrderListModel(); model.NavigationModel = GetCustomerNavigationModel(customer); model.NavigationModel.SelectedTab = CustomerNavigationEnum.Orders; - var storeScope = (_orderSettings.DisplayOrdersOfAllStores ? 0 : _storeContext.CurrentStore.Id); + var orders = _orderService.SearchOrders(storeScope, customer.Id, null, null, null, null, null, null, null, null, pageIndex, _orderSettings.OrderListPageSize); - var orders = _orderService.SearchOrders(storeScope, customer.Id, null, null, null, null, null, null, null, null, 0, int.MaxValue); + var orderModels = orders + .Select(x => + { + var orderModel = new CustomerOrderListModel.OrderDetailsModel + { + Id = x.Id, + OrderNumber = x.GetOrderNumber(), + CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc), + OrderStatus = x.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), + IsReturnRequestAllowed = _orderProcessingService.IsReturnRequestAllowed(x) + }; - foreach (var order in orders) - { - var orderModel = new CustomerOrderListModel.OrderDetailsModel - { - Id = order.Id, - OrderNumber = order.GetOrderNumber(), - CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc), - OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), - IsReturnRequestAllowed = _orderProcessingService.IsReturnRequestAllowed(order) - }; + var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(x.OrderTotal, x.CurrencyRate); + orderModel.OrderTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, x.CustomerCurrencyCode, false, _workContext.WorkingLanguage); - var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); - orderModel.OrderTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage); + return orderModel; + }) + .ToList(); + + model.Orders = new PagedList(orderModels, orders.PageIndex, orders.PageSize, orders.TotalCount); - model.Orders.Add(orderModel); - } var recurringPayments = _orderService.SearchRecurringPayments(_storeContext.CurrentStore.Id, customer.Id, 0, null); @@ -1256,13 +1261,12 @@ public ActionResult AddressEdit(CustomerAddressEditModel model, int id) #region Orders [RequireHttpsByConfigAttribute(SslRequirement.Yes)] - public ActionResult Orders() + public ActionResult Orders(int? page) { if (!IsCurrentUserRegistered()) return new HttpUnauthorizedResult(); - var customer = _workContext.CurrentCustomer; - var model = PrepareCustomerOrderListModel(customer); + var model = PrepareCustomerOrderListModel(_workContext.CurrentCustomer, Math.Max((page ?? 0) - 1, 0)); return View(model); } @@ -1295,7 +1299,7 @@ public ActionResult CancelRecurringPayment(FormCollection form) { var errors = _orderProcessingService.CancelRecurringPayment(recurringPayment); - var model = PrepareCustomerOrderListModel(customer); + var model = PrepareCustomerOrderListModel(customer, 0); model.CancelRecurringPaymentErrors = errors; return View(model); diff --git a/src/Presentation/SmartStore.Web/Models/Customer/CustomerOrderListModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/CustomerOrderListModel.cs index d81be762d6..eebb536d65 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/CustomerOrderListModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/CustomerOrderListModel.cs @@ -1,19 +1,19 @@ using System; using System.Collections.Generic; +using SmartStore.Core; using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Customer { - public partial class CustomerOrderListModel : ModelBase + public partial class CustomerOrderListModel : ModelBase { public CustomerOrderListModel() { - Orders = new List(); - RecurringOrders = new List(); - CancelRecurringPaymentErrors = new List(); + RecurringOrders = new List(); + CancelRecurringPaymentErrors = new List(); } - public IList Orders { get; set; } + public PagedList Orders { get; set; } public IList RecurringOrders { get; set; } public IList CancelRecurringPaymentErrors { get; set; } @@ -21,6 +21,7 @@ public CustomerOrderListModel() #region Nested classes + public partial class OrderDetailsModel : EntityModelBase { public string OrderNumber { get; set; } @@ -29,6 +30,7 @@ public partial class OrderDetailsModel : EntityModelBase public string OrderStatus { get; set; } public DateTime CreatedOn { get; set; } } + public partial class RecurringOrderModel : EntityModelBase { public string StartDate { get; set; } @@ -39,6 +41,7 @@ public partial class RecurringOrderModel : EntityModelBase public int InitialOrderId { get; set; } public bool CanCancel { get; set; } } + #endregion } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Views/Customer/Orders.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/Orders.Mobile.cshtml index 554885a8c1..a0e67e4c08 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/Orders.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/Orders.Mobile.cshtml @@ -1,11 +1,10 @@ -@model CustomerOrderListModel +@using SmartStore.Web.Models.Customer; +@model CustomerOrderListModel @{ Layout = "~/Views/Shared/_Root.cshtml"; - //title Html.AddTitleParts(T("PageTitle.Account").Text); } -@using SmartStore.Web.Models.Customer;
diff --git a/src/Presentation/SmartStore.Web/Views/Customer/Orders.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/Orders.cshtml index bc7a8f8606..94938bb554 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/Orders.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/Orders.cshtml @@ -1,11 +1,10 @@ -@model CustomerOrderListModel +@using SmartStore.Web.Models.Customer; +@model CustomerOrderListModel @{ Layout = "~/Views/Shared/_ColumnsTwo.cshtml"; - //title Html.AddTitleParts(T("PageTitle.Account").Text); } -@using SmartStore.Web.Models.Customer; @section left { @Html.Partial("MyAccountNavigation", Model.NavigationModel, new ViewDataDictionary()) @@ -19,7 +18,6 @@
@if (Model.RecurringOrders.Count > 0) { - using (Html.BeginForm()) {
@@ -106,46 +104,47 @@
} } + + @if (Model.Orders.Count == 0) + { +
+ @T("Account.CustomerOrders.NoOrders") +
+ } +
- @if (Model.Orders.Count > 0) - { - foreach (var order in Model.Orders) - { -
- @T("Account.CustomerOrders.OrderNumber"): @order.OrderNumber -
-
-
@T("Account.CustomerOrders.OrderStatus"):
-
@order.OrderStatus
-
@T("Account.CustomerOrders.OrderDate"):
-
@order.CreatedOn.ToString()
-
@T("Account.CustomerOrders.OrderTotal"):
-
@order.OrderTotal
-
-
- +
+ }
+ + @Html.SmartStore().Pager(Model.Orders).Name("orders-paginator").Action("Orders", "Customer")
From cd037cd38ea01722cd8b6d52d8f07d321c6d16bd Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 13 May 2016 20:37:00 +0200 Subject: [PATCH 056/185] Export refactoring: removed ExportExecuteContext interface (the concrete type is sufficient) --- .../DataExchange/Events/RowExportingEvent.cs | 29 ++- .../Export/ExportExecuteContext.cs | 193 +++++++----------- .../DataExchange/Export/ExportProviderBase.cs | 6 +- .../DataExchange/Export/IExportProvider.cs | 4 +- .../Providers/GmcXmlExportProvider.cs | 2 +- .../Views/Import/_ColumnMappings.cshtml | 4 +- 6 files changed, 107 insertions(+), 131 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs b/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs index 7459b9ee3f..0b96526e00 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs @@ -4,13 +4,32 @@ namespace SmartStore.Services.DataExchange.Export.Events { // TODO: Another event message must be implemented, say 'ColumnsBuildingEvent' // The consumer of this event (most likely a plugin) could push a list of specific column headers - // into global the export definition. + // into the global export definition. public class RowExportingEvent { - public dynamic Row { get; internal set; } - public ExportEntityType EntityType { get; internal set; } - public DataExportRequest ExportRequest { get; internal set; } - public IExportExecuteContext ExecuteContext { get; internal set; } + public dynamic Row + { + get; + internal set; + } + + public ExportEntityType EntityType + { + get; + internal set; + } + + public DataExportRequest ExportRequest + { + get; + internal set; + } + + public ExportExecuteContext ExecuteContext + { + get; + internal set; + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs index 514c223624..c6d52c746a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs @@ -7,131 +7,177 @@ namespace SmartStore.Services.DataExchange.Export { - public interface IExportExecuteContext + public class ExportExecuteContext { + private DataExportResult _result; + private CancellationToken _cancellation; + private DataExchangeAbortion _providerAbort; + + internal ExportExecuteContext(DataExportResult result, CancellationToken cancellation, string folder) + { + _result = result; + _cancellation = cancellation; + Folder = folder; + ExtraDataStreams = new List(); + CustomProperties = new Dictionary(); + } + /// /// Provides the data to be exported /// - IExportDataSegmenterConsumer Segmenter { get; } + public IExportDataSegmenterConsumer Segmenter { get; set; } /// /// The store context to be used for the export /// - dynamic Store { get; } + public dynamic Store { get; internal set; } /// /// The customer context to be used for the export /// - dynamic Customer { get; } + public dynamic Customer { get; internal set; } /// /// The currency context to be used for the export /// - dynamic Currency { get; } + public dynamic Currency { get; internal set; } /// /// The language context to be used for the export /// - dynamic Language { get; } + public dynamic Language { get; internal set; } /// /// Projection data /// - ExportProjection Projection { get; } + public ExportProjection Projection { get; internal set; } /// /// To log information into the export log file /// - ILogger Log { get; } + public ILogger Log { get; internal set; } /// /// Indicates whether and how to abort the export /// - DataExchangeAbortion Abort { get; set; } + public DataExchangeAbortion Abort + { + get + { + if (_cancellation.IsCancellationRequested || IsMaxFailures) + return DataExchangeAbortion.Hard; + + return _providerAbort; + } + set + { + _providerAbort = value; + } + } + public bool IsMaxFailures + { + get { return RecordsFailed > 11; } + } /// /// Identifier of current data stream. Can be null. /// - string DataStreamId { get; set; } + public string DataStreamId { get; set; } /// /// Stream used to write data to /// - Stream DataStream { get; } + public Stream DataStream { get; internal set; } /// /// List with extra data streams required by provider /// - List ExtraDataStreams { get; set; } - + public List ExtraDataStreams { get; set; } /// /// The maximum allowed file name length /// - int MaxFileNameLength { get; } + public int MaxFileNameLength { get; internal set; } /// /// The name of the current export file /// - string FileName { get; } + public string FileName { get; internal set; } /// /// The path of the export content folder /// - string Folder { get; } - + public string Folder { get; private set; } /// /// Whether the profile has a public deployment into "Exchange" folder /// - bool HasPublicDeployment { get; } + public bool HasPublicDeployment { get; internal set; } /// /// The local path to the public export folder "Exchange". null if the profile has no public deployment. /// - string PublicFolderPath { get; } + public string PublicFolderPath { get; internal set; } /// /// The public URL of the export file (accessible through the internet). null if the profile has no public deployment. /// - string PublicFileUrl { get; } - + public string PublicFileUrl { get; internal set; } /// /// Provider specific configuration data /// - object ConfigurationData { get; } + public object ConfigurationData { get; internal set; } /// /// Use this dictionary for any custom data required along the export /// - Dictionary CustomProperties { get; set; } + public Dictionary CustomProperties { get; set; } /// /// Number of successful processed records /// - int RecordsSucceeded { get; set; } + public int RecordsSucceeded { get; set; } /// /// Number of failed records /// - int RecordsFailed { get; set; } + public int RecordsFailed { get; set; } /// /// Processes an exception that occurred while exporting a record /// - /// Exception - void RecordException(Exception exc, int entityId); + /// Exception + public void RecordException(Exception exception, int entityId) + { + ++RecordsFailed; + + Log.Error("Error while processing record with id {0}: {1}".FormatInvariant(entityId, exception.ToAllMessages()), exception); + + if (IsMaxFailures) + _result.LastError = exception.ToString(); + } + + public ProgressValueSetter ProgressValueSetter { get; internal set; } /// /// Allows to set a progress message /// /// Output message - void SetProgress(string message); + public void SetProgress(string message) + { + if (ProgressValueSetter != null && message.HasValue()) + { + try + { + ProgressValueSetter.Invoke(0, 0, message); + } + catch { } + } + } } - public class ExportExtraStreams { /// @@ -149,93 +195,4 @@ public class ExportExtraStreams /// public string FileName { get; set; } } - - - public class ExportExecuteContext : IExportExecuteContext - { - private DataExportResult _result; - private CancellationToken _cancellation; - private DataExchangeAbortion _providerAbort; - - internal ExportExecuteContext(DataExportResult result, CancellationToken cancellation, string folder) - { - _result = result; - _cancellation = cancellation; - Folder = folder; - ExtraDataStreams = new List(); - CustomProperties = new Dictionary(); - } - - public IExportDataSegmenterConsumer Segmenter { get; set; } - - public dynamic Store { get; internal set; } - public dynamic Customer { get; internal set; } - public dynamic Currency { get; internal set; } - public dynamic Language { get; internal set; } - public ExportProjection Projection { get; internal set; } - - public ILogger Log { get; internal set; } - public ProgressValueSetter ProgressValueSetter { get; internal set; } - - public DataExchangeAbortion Abort - { - get - { - if (_cancellation.IsCancellationRequested || IsMaxFailures) - return DataExchangeAbortion.Hard; - - return _providerAbort; - } - set - { - _providerAbort = value; - } - } - - public bool IsMaxFailures - { - get { return RecordsFailed > 11; } - } - - public string DataStreamId { get; set; } - public Stream DataStream { get; internal set; } - public List ExtraDataStreams { get; set; } - - public int MaxFileNameLength { get; internal set; } - public string FileName { get; internal set; } - public string Folder { get; private set; } - - public bool HasPublicDeployment { get; internal set; } - public string PublicFolderPath { get; internal set; } - public string PublicFileUrl { get; internal set; } - - public object ConfigurationData { get; internal set; } - - public Dictionary CustomProperties { get; set; } - - public int RecordsSucceeded { get; set; } - public int RecordsFailed { get; set; } - - public void RecordException(Exception exc, int entityId) - { - ++RecordsFailed; - - Log.Error("Error while processing record with id {0}: {1}".FormatInvariant(entityId, exc.ToAllMessages()), exc); - - if (IsMaxFailures) - _result.LastError = exc.ToString(); - } - - public void SetProgress(string message) - { - if (ProgressValueSetter != null && message.HasValue()) - { - try - { - ProgressValueSetter.Invoke(0, 0, message); - } - catch { } - } - } - } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs index 2a2d9618f1..46c2bd0fc3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs @@ -32,9 +32,9 @@ public virtual ExportConfigurationInfo ConfigurationInfo /// Export data to a file /// /// Export execution context - protected abstract void Export(IExportExecuteContext context); + protected abstract void Export(ExportExecuteContext context); - public void Execute(IExportExecuteContext context) + public void Execute(ExportExecuteContext context) { Export(context); } @@ -43,7 +43,7 @@ public void Execute(IExportExecuteContext context) /// Called once per store when export execution ended /// /// Export execution context - public virtual void OnExecuted(IExportExecuteContext context) + public virtual void OnExecuted(ExportExecuteContext context) { } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProvider.cs index e5248b20ba..60be2e11a9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProvider.cs @@ -24,12 +24,12 @@ public partial interface IExportProvider : IProvider, IUserEditable /// Export data to a file /// /// Export execution context - void Execute(IExportExecuteContext context); + void Execute(ExportExecuteContext context); /// /// Called once per store when export execution ended /// /// Export execution context - void OnExecuted(IExportExecuteContext context); + void OnExecuted(ExportExecuteContext context); } } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index 4fc2f1f305..3daf2af928 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -153,7 +153,7 @@ public override string FileExtension get { return "XML"; } } - protected override void Export(IExportExecuteContext context) + protected override void Export(ExportExecuteContext context) { dynamic currency = context.Currency; string measureWeightSystemKey = ""; diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml index a2c7b2f9be..8af26579a7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml @@ -128,7 +128,7 @@ { var index = Model.ColumnMappings.IndexOf(mapping) + 1; - +
@@ -147,7 +147,7 @@
- +
From 8b626c4da3b7647efcbd470c89301e39c61cd0e2 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 13 May 2016 20:39:25 +0200 Subject: [PATCH 057/185] Fixed a broken unit test --- .../Public/Infrastructure/RoutesTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tests/SmartStore.Web.MVC.Tests/Public/Infrastructure/RoutesTests.cs b/src/Tests/SmartStore.Web.MVC.Tests/Public/Infrastructure/RoutesTests.cs index b275d3afec..380578d32f 100644 --- a/src/Tests/SmartStore.Web.MVC.Tests/Public/Infrastructure/RoutesTests.cs +++ b/src/Tests/SmartStore.Web.MVC.Tests/Public/Infrastructure/RoutesTests.cs @@ -96,7 +96,7 @@ public void Customer_routes() "~/customer/myaccount/".ShouldMapTo(c => c.MyAccount()); "~/customer/info/".ShouldMapTo(c => c.Info()); "~/customer/addresses/".ShouldMapTo(c => c.Addresses()); - "~/customer/orders/".ShouldMapTo(c => c.Orders()); + "~/customer/orders/".ShouldMapTo(c => c.Orders(null)); "~/customer/returnrequests/".ShouldMapTo(c => c.ReturnRequests()); "~/customer/downloadableproducts/".ShouldMapTo(c => c.DownloadableProducts()); "~/customer/backinstocksubscriptions/".ShouldMapTo(c => c.BackInStockSubscriptions(null)); From ca6ea78f03f97abeaf4d9bacc65a97ce9d65e230 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 13 May 2016 20:52:43 +0200 Subject: [PATCH 058/185] Export refactoring: renamed ExportDataStreams to ExportDataUnit --- .../DataExchange/Export/DataExporter.cs | 9 ++++----- .../DataExchange/Export/ExportExecuteContext.cs | 8 ++++---- .../DataExchange/Export/ExportProviderBase.cs | 10 +++++----- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 09d444655a..bee1a48494 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -1116,17 +1116,16 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) if (ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard) { // always call OnExecuted - if (ctx.ExecuteContext.ExtraDataStreams.Count == 0) - ctx.ExecuteContext.ExtraDataStreams.Add(new ExportExtraStreams()); + if (ctx.ExecuteContext.ExtraDataUnits.Count == 0) + ctx.ExecuteContext.ExtraDataUnits.Add(new ExportDataUnit()); - ctx.ExecuteContext.ExtraDataStreams.ForEach(x => + ctx.ExecuteContext.ExtraDataUnits.ForEach(x => { var path = (x.FileName.HasValue() ? Path.Combine(ctx.ExecuteContext.Folder, x.FileName) : null); - CallProvider(ctx, x.Id, "OnExecuted", path); }); - ctx.ExecuteContext.ExtraDataStreams.Clear(); + ctx.ExecuteContext.ExtraDataUnits.Clear(); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs index c6d52c746a..a0de0334ea 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs @@ -18,7 +18,7 @@ internal ExportExecuteContext(DataExportResult result, CancellationToken cancell _result = result; _cancellation = cancellation; Folder = folder; - ExtraDataStreams = new List(); + ExtraDataUnits = new List(); CustomProperties = new Dictionary(); } @@ -91,9 +91,9 @@ public bool IsMaxFailures public Stream DataStream { get; internal set; } /// - /// List with extra data streams required by provider + /// List with extra data units/streams required by provider /// - public List ExtraDataStreams { get; set; } + public List ExtraDataUnits { get; private set; } /// /// The maximum allowed file name length @@ -178,7 +178,7 @@ public void SetProgress(string message) } } - public class ExportExtraStreams + public class ExportDataUnit { /// /// Your Id to identify this stream within a list of streams diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs index 46c2bd0fc3..c77638f60b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs @@ -28,17 +28,17 @@ public virtual ExportConfigurationInfo ConfigurationInfo get { return null; } } + public void Execute(ExportExecuteContext context) + { + Export(context); + } + /// /// Export data to a file /// /// Export execution context protected abstract void Export(ExportExecuteContext context); - public void Execute(ExportExecuteContext context) - { - Export(context); - } - /// /// Called once per store when export execution ended /// From 8186b1e799edea300bee953c51d370a8595692fb Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 13 May 2016 20:56:38 +0200 Subject: [PATCH 059/185] Export refactoring: renamed ExportExecuteContext.Segmenter to DataSegmenter --- .../DataExchange/Export/DataExporter.cs | 16 ++++++++-------- .../DataExchange/Export/ExportExecuteContext.cs | 2 +- .../Providers/GmcXmlExportProvider.cs | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index bee1a48494..46c9a7d969 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -285,7 +285,7 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in switch (ctx.Request.Provider.Value.EntityType) { case ExportEntityType.Product: - ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( skip => GetProducts(ctx, skip), entities => @@ -310,7 +310,7 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in break; case ExportEntityType.Order: - ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( skip => GetOrders(ctx, skip), entities => @@ -329,7 +329,7 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in break; case ExportEntityType.Manufacturer: - ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( skip => GetManufacturers(ctx, skip), entities => @@ -345,7 +345,7 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in break; case ExportEntityType.Category: - ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( skip => GetCategories(ctx, skip), entities => @@ -361,7 +361,7 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in break; case ExportEntityType.Customer: - ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( skip => GetCustomers(ctx, skip), entities => @@ -376,7 +376,7 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in break; case ExportEntityType.NewsLetterSubscription: - ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( skip => GetNewsLetterSubscriptions(ctx, skip), null, @@ -386,11 +386,11 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in break; default: - ctx.ExecuteContext.Segmenter = null; + ctx.ExecuteContext.DataSegmenter = null; break; } - return ctx.ExecuteContext.Segmenter as IExportDataSegmenterProvider; + return ctx.ExecuteContext.DataSegmenter as IExportDataSegmenterProvider; } private bool CallProvider(DataExporterContext ctx, string streamId, string method, string path) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs index a0de0334ea..6c25d397bd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs @@ -25,7 +25,7 @@ internal ExportExecuteContext(DataExportResult result, CancellationToken cancell /// /// Provides the data to be exported /// - public IExportDataSegmenterConsumer Segmenter { get; set; } + public IExportDataSegmenterConsumer DataSegmenter { get; set; } /// /// The store context to be used for the export diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index 3daf2af928..bc9047d888 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -177,9 +177,9 @@ protected override void Export(ExportExecuteContext context) writer.WriteElementString("link", "http://base.google.com/base/"); writer.WriteElementString("description", "Information about products"); - while (context.Abort == DataExchangeAbortion.None && context.Segmenter.ReadNextSegment()) + while (context.Abort == DataExchangeAbortion.None && context.DataSegmenter.ReadNextSegment()) { - var segment = context.Segmenter.CurrentSegment; + var segment = context.DataSegmenter.CurrentSegment; int[] productIds = segment.Select(x => (int)((dynamic)x).Id).ToArray(); var googleProducts = _googleFeedService.GetGoogleProductRecords(productIds); From 97a6f80d2ee0f50e57dfe2d461fc01bef50c9683 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 14 May 2016 14:56:17 +0200 Subject: [PATCH 060/185] Resolves #964 Import: make column mapping UI perfectly fit into 1024px screen width --- .../Administration/Content/admin.less | 30 +++++++++++++++++-- .../Models/DataExchange/ImportProfileModel.cs | 2 ++ .../Views/Import/_ColumnMappings.cshtml | 22 +++++++------- .../Views/Import/_Update.cshtml | 28 ++++++++++++----- 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index d15f091e3e..b6bdce0dbf 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -1584,15 +1584,41 @@ table.payment-method-features td { -------------------------------------------------------------- */ .column-mapping { .column-one, .column-two { - width: 38%; + width: 42%; } - .select-property, .select-column, .input-default { + .select-column, .select-property, .input-default { width: 98%; } + th, td, .mapping-delete, .mapping-add, .mapping-apply, .mapping-cancel { + white-space: nowrap; + padding-left: 8px; + padding-right: 8px; + } + + .select2-container .select2-choice span { + margin-right: 12px; + white-space: nowrap; + } + + .mapping-edit { + white-space: nowrap; + + .select-column, .select-property { + width: 84%; + } + .select2-container { + float: left; + display: inline-block; + min-width: 220px; + margin-right: 4px; + } + } + .mapping-item { .item-inner { + white-space: nowrap; cursor: pointer; padding: 7px 0; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs index 41f3c9f23f..5baf801477 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs @@ -68,6 +68,8 @@ public ImportProfileModel() public List AvailableSourceColumns { get; set; } public List AvailableEntityProperties { get; set; } + public int MaxMappingLabelTextLength { get { return 42; } } + public class ExtraDataModel { [SmartResourceDisplayName("Admin.DataExchange.Import.NumberOfPictures")] diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml index 8af26579a7..3aa2e9b9ef 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml @@ -34,7 +34,7 @@
- @foreach (var property in Model.AvailableEntityProperties) { @@ -47,7 +47,7 @@
- @foreach (var column in Model.AvailableSourceColumns) { @@ -102,10 +102,10 @@ @@ -119,8 +119,8 @@ @T("Admin.DataExchange.ColumnMapping.EntityProperty") @T("Admin.DataExchange.ColumnMapping.ImportField") - @T("Admin.DataExchange.ColumnMapping.DefaultValue") -   + @T("Admin.DataExchange.ColumnMapping.DefaultValue") +   @@ -128,16 +128,16 @@ { var index = Model.ColumnMappings.IndexOf(mapping) + 1; - +
- @if (mapping.PropertyDescription.Length > 50) + @if (mapping.PropertyDescription.Length > Model.MaxMappingLabelTextLength) { - @(mapping.PropertyDescription.Substring(0, 50))… + @(mapping.PropertyDescription.Substring(0, Model.MaxMappingLabelTextLength))… } else { @@ -147,7 +147,7 @@
- +
@@ -170,7 +170,7 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml index 0693b5ed32..c87b1629c2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml @@ -212,6 +212,16 @@ var mappedProperties = []; jQuery(document).ready(function () { + var isNarrowScreen = ($(window).width() <= 1280), + maxMappingLableLength = @(Model.MaxMappingLabelTextLength), + maxEditTextLength = 40; + + // hide some mapping elements for narrow screen resolution + if (isNarrowScreen) { + maxMappingLableLength -= 10; + maxEditTextLength -= 10; + $('#AddMappingTemplate, #ImportColumnMappings').find('.mapping-button-label, .right-label').hide(); + } // upload import file $('#ImportFileUploader').fileupload({ @@ -382,8 +392,8 @@ isLocalized = (val.indexOf(']', val.length - 1) !== -1), text = context.find('select').find('option:selected').text(); - if (text.length > 50) { - leftLabel.text(text.substring(0, 50) + '…'); + if (text.length > maxMappingLableLength) { + leftLabel.text(text.substring(0, maxMappingLableLength) + '…'); leftLabel.attr('title', $('
').html(text).text()); } else { @@ -468,11 +478,13 @@ html += ''; } - html += '' + item.id + ''; + if (!isNarrowScreen) { + html += '' + item.id + ''; + } html += ' 40) { - html += ' title="' + $('
').text(item.text).html() + '">' + item.text.substring(0, 40) + '…'; + if (item.text.length > maxEditTextLength) { + html += ' title="' + $('
').text(item.text).html() + '">' + item.text.substring(0, maxEditTextLength) + '…'; } else { html += '>' + item.text; @@ -498,10 +510,10 @@ html += '
'; - if (query && item.text !== item.id) { + if (query && item.text !== item.id && !isNarrowScreen) { html += ' 40) { - html += ' title="' + $('
').text(item.text).html() + '">' + item.text.substring(0, 40) + '…'; + if (item.text.length > maxEditTextLength) { + html += ' title="' + $('
').text(item.text).html() + '">' + item.text.substring(0, maxEditTextLength) + '…'; } else { html += '>' + item.text; From 66ec39c12f0260dedfe04eec90ff0b892a2399e7 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 15 May 2016 11:29:11 +0200 Subject: [PATCH 061/185] Resolves #965: Export abortion by user deletes the current (uncompleted) export file --- .../SmartStore.Services/DataExchange/Export/DataExporter.cs | 5 +++++ .../Administration/Views/Import/_ColumnMappings.cshtml | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 46c9a7d969..7ca9ea7936 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -443,6 +443,11 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho ctx.ExecuteContext.DataStream.Dispose(); ctx.ExecuteContext.DataStream = null; } + + if (ctx.ExecuteContext.Abort == DataExchangeAbortion.Hard && ctx.IsFileBasedExport && path.HasValue()) + { + FileSystemHelper.Delete(path); + } } return (ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml index 3aa2e9b9ef..be30b9c8e5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml @@ -101,10 +101,10 @@ - - @@ -169,7 +169,7 @@ @Html.Attr("disabled", "disabled", mapping.IsDefaultDisabled || !mapping.Column.HasValue()) /> - From d5d1d2aef605fb359027c5805bdaa7a2d903a8e1 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 15 May 2016 11:57:26 +0200 Subject: [PATCH 062/185] Localization of task executor exception log message --- .../201605111140288_ImportExtraData.cs | 8 +++++ .../SmartStore.Services/Tasks/TaskExecutor.cs | 30 ++++++++++++------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs index 0d7b6fcc06..8b06547f56 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs @@ -51,6 +51,14 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Anzahl der Aufträge pro Seite", "Specifies the number of displayed orders per page.", "Legt die Anzahl der dargestellten Aufträge pro Seite fest."); + + builder.AddOrUpdate("Admin.System.ScheduleTasks.RunningError", + "Error while running scheduled task \"{0}\"", + "Fehler beim Ausführen der Aufgabe \"{0}\""); + + builder.AddOrUpdate("Admin.System.ScheduleTasks.Cancellation", + "The scheduled task \"{0}\" has been canceled", + "Die geplante Aufgabe \"{0}\" wurde abgebrochen"); } } } diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs index fc7ce73e42..8fd93c2956 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs @@ -1,21 +1,22 @@ using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Threading; using Autofac; +using SmartStore.Core; using SmartStore.Core.Async; using SmartStore.Core.Data; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Tasks; +using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Customers; -using SmartStore.Core; -using SmartStore.Core.Domain.Customers; -using System.Diagnostics; -using System.Collections.Generic; namespace SmartStore.Services.Tasks { - public class TaskExecutor : ITaskExecutor + public class TaskExecutor : ITaskExecutor { private readonly IScheduleTaskService _scheduledTaskService; private readonly IDbContext _dbContext; @@ -42,11 +43,13 @@ public TaskExecutor( this._taskResolver = taskResolver; Logger = NullLogger.Instance; - } + T = NullLocalizer.Instance; + } public ILogger Logger { get; set; } + public Localizer T { get; set; } - public void Execute( + public void Execute( ScheduleTask task, IDictionary taskParameters = null, bool throwOnError = false) @@ -126,12 +129,17 @@ public void Execute( instance.Execute(ctx); } - catch (Exception ex) + catch (Exception exception) { faulted = true; - canceled = ex is OperationCanceledException; - Logger.Error(string.Format("Error while running scheduled task '{0}'. {1}", task.Name, ex.Message), ex); - lastError = ex.Message.Truncate(995, "..."); + canceled = exception is OperationCanceledException; + lastError = exception.Message.Truncate(995, "..."); + + if (canceled) + Logger.Warning(T("Admin.System.ScheduleTasks.Cancellation", task.Name), exception); + else + Logger.Error(string.Concat(T("Admin.System.ScheduleTasks.RunningError", task.Name), ": ", exception.Message), exception); + if (throwOnError) { throw; From b73c29eeef21330a8e9eecb7efea165e1fdbbe79 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 17 May 2016 17:57:06 +0200 Subject: [PATCH 063/185] Export all in preview displayed wrong message --- .../SmartStore.Web/Administration/Views/Export/Preview.cshtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml index 548863d36e..0c520cc1e8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml @@ -41,10 +41,10 @@ $('#ExportAll, #ExportSelected').click(function (e) { e.preventDefault(); var form = $('#ProfileExportForm'), - exportAll = $(this).attr('name') === 'ExportAll'; + exportAll = $(this).attr('id') === 'ExportAll'; if (!exportAll && selectedIds.length === 0) { - EventBroker.publish("message", { title: '@T("Admin.Common.NoEntriesSelected")', type: 'warning' }); + displayNotification('@T("Admin.Common.NoEntriesSelected")', 'warning'); return false; } From 49b01a0a8c558dfdf7d69bc26dfb5fb39ab0699c Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 17 May 2016 20:09:42 +0200 Subject: [PATCH 064/185] Coding style --- .../DataExchange/Export/DataExporter.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 7ca9ea7936..bf39638dd6 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -182,9 +182,7 @@ private void SetProgress(DataExporterContext ctx, int loadedRecords) totalRecords = ctx.Request.Profile.Limit; ctx.RecordCount = Math.Min(ctx.RecordCount + loadedRecords, totalRecords); - var msg = ctx.ProgressInfo.FormatInvariant(ctx.RecordCount, totalRecords); - ctx.Request.ProgressValueSetter.Invoke(ctx.RecordCount, totalRecords, msg); } } @@ -275,11 +273,8 @@ private void DetachAllEntitiesAndClear(DataExporterContext ctx) private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, int pageIndex = 0) { var offset = Math.Max(ctx.Request.Profile.Offset, 0) + (pageIndex * PageSize); - var limit = (ctx.IsPreview ? PageSize : Math.Max(ctx.Request.Profile.Limit, 0)); - var recordsPerSegment = (ctx.IsPreview ? 0 : Math.Max(ctx.Request.Profile.BatchSize, 0)); - var totalCount = Math.Max(ctx.Request.Profile.Offset, 0) + ctx.RecordsPerStore.First(x => x.Key == ctx.Store.Id).Value; switch (ctx.Request.Provider.Value.EntityType) @@ -416,7 +411,9 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho if (ctx.IsFileBasedExport && path.HasValue() && ctx.ExecuteContext.DataStream.Length > 0) { if (!ctx.ExecuteContext.DataStream.CanSeek) + { ctx.Log.Warning("Data stream seems to be closed!"); + } ctx.ExecuteContext.DataStream.Seek(0, SeekOrigin.Begin); @@ -424,7 +421,6 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) { ctx.Log.Information("Creating file {0}.".FormatInvariant(path)); - ctx.ExecuteContext.DataStream.CopyTo(fileStream); } } @@ -1052,11 +1048,8 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) } ctx.ExecuteContext.Store = ToDynamic(ctx, ctx.Store); - ctx.ExecuteContext.MaxFileNameLength = dataExchangeSettings.MaxFileNameLength; - ctx.ExecuteContext.HasPublicDeployment = ctx.Request.Profile.Deployments.Any(x => x.IsPublic && x.DeploymentType == ExportDeploymentType.FileSystem); - ctx.ExecuteContext.PublicFolderPath = (ctx.ExecuteContext.HasPublicDeployment ? Path.Combine(HttpRuntime.AppDomainAppPath, PublicFolder) : null); var fileExtension = (ctx.Request.Provider.Value.FileExtension.HasValue() ? ctx.Request.Provider.Value.FileExtension.ToLower().EnsureStartsWith(".") : ""); @@ -1226,7 +1219,6 @@ private void ExportCoreOuter(DataExporterContext ctx) if (ctx.Request.Profile.Deployments.Any(x => x.Enabled)) { SetProgress(ctx, T("Common.Deployment")); - Deploy(ctx, zipPath); } } From 033cd1929c2d55d8949c98ebc5029d402fb3b875 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 18 May 2016 01:25:31 +0200 Subject: [PATCH 065/185] Enhanced help link generator method --- .../SmartStore.Core/SmartStoreVersion.cs | 18 ++++++++++++++---- .../Theming/WebViewPage.cs | 4 ++-- .../Views/ScheduleTask/Edit.cshtml | 1 - .../Administration/Views/Shared/Navbar.cshtml | 5 +++-- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Libraries/SmartStore.Core/SmartStoreVersion.cs b/src/Libraries/SmartStore.Core/SmartStoreVersion.cs index df3c302220..38750e3069 100644 --- a/src/Libraries/SmartStore.Core/SmartStoreVersion.cs +++ b/src/Libraries/SmartStore.Core/SmartStoreVersion.cs @@ -23,9 +23,10 @@ public static class SmartStoreVersion new Version("2.5") }; - private const string HELP_BASEURL = "http://docs.smartstore.com/display/SMNET25/"; + //private const string HELP_BASEURL = "http://docs.smartstore.com/display/SMNET25/"; + private const string HELP_BASEURL = "http://docs.smartstore.com/display/"; - static SmartStoreVersion() + static SmartStoreVersion() { s_breakingChangesHistory.Reverse(); @@ -67,9 +68,18 @@ public static Version Version } } - public static string GenerateHelpUrl(string path) + public static string GenerateHelpUrl(string languageCode, string path) { - return HELP_BASEURL + path.Trim().TrimStart('/', '\\'); + return String.Concat( + HELP_BASEURL, + GetUserGuideSpaceKey(languageCode), + "/", + path.EmptyNull().Trim().TrimStart('/', '\\')); + } + + public static string GetUserGuideSpaceKey(string languageCode) + { + return "SMNET25"; } /// diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPage.cs b/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPage.cs index b4a4268f49..b6fade194d 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPage.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPage.cs @@ -322,9 +322,9 @@ public string GetThemeVariable(string varname, string defaultValue = "") return defaultValue; } - public static string GenerateHelpUrl(string path) + public string GenerateHelpUrl(string path) { - return SmartStoreVersion.GenerateHelpUrl(path); + return SmartStoreVersion.GenerateHelpUrl(WorkContext.WorkingLanguage.UniqueSeoCode, path); } private void EnsureThemeContextInitialized() diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml index 281220e2df..a1018ad9ea 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml @@ -3,7 +3,6 @@ @using SmartStore.Core @{ ViewBag.Title = T("Admin.System.ScheduleTasks.EditTask") + " - " + Model.Name; - //var cronHelpLink = WorkContext.WorkingLanguage.UniqueSeoCode.ToLower() == "de" ? "https://de.wikipedia.org/wiki/Cron" : "https://en.wikipedia.org/wiki/Cron"; var cronHelpLink = GenerateHelpUrl("Managing+Scheduled+Tasks#ManagingScheduledTasks-Cron"); var returnUrl = ((string)ViewBag.ReturnUrl).NullEmpty(); } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Navbar.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Navbar.cshtml index 5188aa5b37..eae6d73f32 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Navbar.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Navbar.cshtml @@ -1,6 +1,7 @@ @using SmartStore.Core.Domain.Stores; @using SmartStore.Services.Customers; @using SmartStore.Services.Stores; +@using SmartStore.Core; @{ var currentCustomer = this.WorkContext.CurrentCustomer; @@ -11,7 +12,7 @@ string communityUrl = (currentLanguageCode == "de" ? "http://community.smartstore.com" : "http://community.smartstore.com/index.php?/&k=880ea6a14ea49e853634fbdc5015a024&setlanguage=1&cal_id=&langid=1"); - string docsUrl = "http://docs.smartstore.com/display/SMNET25/SmartStore.NET+Documentation+Home"; + string docsUrl = GenerateHelpUrl(""); var checkUpdateResult = ViewBag.CheckUpdateResult as CheckUpdateResult; } @@ -33,7 +34,7 @@
- + From 969ed98cea65a4e9c038b1f2413caea609765f1a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 18 May 2016 12:04:19 +0200 Subject: [PATCH 066/185] #964 Import: make column mapping UI perfectly fit into 1024px screen width (part 2) --- .../Administration/Content/admin.less | 19 +++++++++-- .../Views/Import/_Update.cshtml | 33 +++++++------------ 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index b6bdce0dbf..dfa4145f39 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -1582,6 +1582,23 @@ table.payment-method-features td { /* Import column mapping -------------------------------------------------------------- */ +@media (max-width: 1280px) { + #ImportColumnMappings .right-label, + .column-mapping .mapping-button-label, + .mapping-list-item .right-label { + display: none; + } +} + +.column-mapping .right-label, +.mapping-list-item .right-label { + float: right; + font-size: 0.9em; +} +.mapping-list-item { + .ellipsis(); +} + .column-mapping { .column-one, .column-two { width: 42%; @@ -1630,8 +1647,6 @@ table.payment-method-features td { display: inline-block; } .right-label { - float: right; - font-size: 0.9em; margin-right: 20px; } } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml index c87b1629c2..195b0e8dfe 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml @@ -212,16 +212,7 @@ var mappedProperties = []; jQuery(document).ready(function () { - var isNarrowScreen = ($(window).width() <= 1280), - maxMappingLableLength = @(Model.MaxMappingLabelTextLength), - maxEditTextLength = 40; - - // hide some mapping elements for narrow screen resolution - if (isNarrowScreen) { - maxMappingLableLength -= 10; - maxEditTextLength -= 10; - $('#AddMappingTemplate, #ImportColumnMappings').find('.mapping-button-label, .right-label').hide(); - } + var maxMappingLableLength = @(Model.MaxMappingLabelTextLength); // upload import file $('#ImportFileUploader').fileupload({ @@ -448,7 +439,7 @@ html = ''; if (option.length === 0) { - html += '
'; + html += '
'; html += ''; html += ''; html += ''; @@ -469,7 +460,7 @@ var mappingIndex = $.inArray(item.id, mappedProperties); html += '
'; - html += (mappingIndex === -1 ? '
' : '
'); + html += '
'; html += ''; html += '' + item.id + ''; - } + html += '' + item.id + ''; html += ' maxEditTextLength) { - html += ' title="' + $('
').text(item.text).html() + '">' + item.text.substring(0, maxEditTextLength) + '…'; + if (item.text.length > maxMappingLableLength) { + html += ' title="' + $('
').text(item.text).html() + '">' + item.text.substring(0, maxMappingLableLength) + '…'; } else { html += '>' + item.text; @@ -508,12 +497,12 @@ html += '
'; } - html += '
'; + html += '
'; - if (query && item.text !== item.id && !isNarrowScreen) { - html += ' maxEditTextLength) { - html += ' title="' + $('
').text(item.text).html() + '">' + item.text.substring(0, maxEditTextLength) + '…'; + if (query && item.text !== item.id) { + html += ' maxMappingLableLength) { + html += ' title="' + $('
').text(item.text).html() + '">' + item.text.substring(0, maxMappingLableLength) + '…'; } else { html += '>' + item.text; From 0f839d0541182c451021e9a1d295b506158b601a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 18 May 2016 12:41:00 +0200 Subject: [PATCH 067/185] Resolves #968 Import: column mapping table > clicking outside mapping table should close select2 --- .../Views/Import/_Update.cshtml | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml index 195b0e8dfe..3d5a866db1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml @@ -269,7 +269,8 @@ }); // mapping row handling - $('#ImportColumnMappings').on('change', '.select-property, .select-column', function () { + $('#ImportColumnMappings').on('change', '.select-property, .select-column', function (e) { + e.stopPropagation(); var row = $(this).closest('tr'); if (row.hasClass('column-mapping-new')) { var properties = row.find('.select-property'), @@ -281,8 +282,9 @@ row.find('.mapping-add').prop('disabled', !(hasProperty && hasColumn)); } - }).on('click', '.item-inner', function () { + }).on('click', '.item-inner', function (e) { // start editing mapping item + e.stopPropagation(); var item = $(this).closest('.mapping-item'); closeAllMappingEdits(); @@ -303,9 +305,9 @@ select = initSelectBox(select, isProperty); select.select2('focus'); - - }).on('click', '.mapping-apply', function () { + }).on('click', '.mapping-apply', function (e) { // apply edited values + e.stopPropagation(); var item = $(this).closest('.mapping-item'), val = item.find('select').select2('val'); @@ -330,8 +332,9 @@ row.find('.input-default').prop('disabled', !hasColumn || isDefaultDisabled); closeAllMappingEdits(); - }).on('click', '.mapping-add', function () { + }).on('click', '.mapping-add', function (e) { // add new mapping row + e.stopPropagation(); var row = $(this).closest('tr'), table = row.closest('table'), index = parseInt(table.attr('data-maxindex')) + 1; @@ -357,12 +360,22 @@ row.find('.mapping-delete').show(); appendNewMappingRow(); - }).on('click', '.mapping-cancel', function () { + }).on('click', '.mapping-cancel', function (e) { + e.stopPropagation(); closeAllMappingEdits(); - }).on('click', '.mapping-delete', function () { + }).on('click', '.mapping-delete', function (e) { + e.stopPropagation(); $(this).closest('tr').remove(); }); + // close mapping edits + $(document).on('click', function (e) { + var table = $(e.target).closest('table'); + + if (table.length === 0 || !table.hasClass('column-mapping')) { + closeAllMappingEdits(); + } + }); appendNewMappingRow(); From f4bd48c87aaed8355267ee069b3309a6e6706e21 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 18 May 2016 20:39:48 +0200 Subject: [PATCH 068/185] Trace logger should not create new files if the original is locked --- .../SmartStore.Core/Logging/TraceLogger.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs b/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs index 3b8f7a4657..593a39ddb0 100644 --- a/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs +++ b/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs @@ -30,6 +30,8 @@ public TraceLogger(string fileName) console.Filter = new EventTypeFilter(SourceLevels.All); console.Name = "console"; + _traceSource.Listeners.Add(console); + var textListener = new TextWriterTraceListener(fileName); textListener.Filter = new EventTypeFilter(SourceLevels.All); textListener.TraceOutputOptions = TraceOptions.DateTime; @@ -41,17 +43,19 @@ public TraceLogger(string fileName) _streamWriter = new StreamWriter(fileName, append, Encoding.UTF8); textListener.Writer = _streamWriter; + + _traceSource.Listeners.Add(textListener); + } + catch (IOException) + { + // file is locked by another process } - catch (IOException) { } - _traceSource.Listeners.Add(console); - _traceSource.Listeners.Add(textListener); - - // Allow the trace source to send messages to - // listeners for all event types. Currently only + // Allow the trace source to send messages to + // listeners for all event types. Currently only // error messages or higher go to the listeners. - // Messages must get past the source switch to - // get to the listeners, regardless of the settings + // Messages must get past the source switch to + // get to the listeners, regardless of the settings // for the listeners. _traceSource.Switch.Level = SourceLevels.All; } From 6b1e5c9322b3a6b144dc399c54a6420b4dcb9eca Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 19 May 2016 11:48:18 +0200 Subject: [PATCH 069/185] System warnings: Output more information when sitemap is not reachable --- .../201605111140288_ImportExtraData.cs | 4 ++++ .../Controllers/CommonController.cs | 24 +++++++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs index 8b06547f56..b77d237359 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs @@ -59,6 +59,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.System.ScheduleTasks.Cancellation", "The scheduled task \"{0}\" has been canceled", "Die geplante Aufgabe \"{0}\" wurde abgebrochen"); + + builder.AddOrUpdate("Admin.Common.HttpStatus", + "HTTP status {0} ({1}).", + "HTTP-Status {0} ({1})."); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs index a8d2257e4a..ecae61fc64 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs @@ -519,35 +519,45 @@ public ActionResult Warnings() } // sitemap reachability - var sitemapReachable = false; + HttpStatusCode? sitemapStatusCode = null; + string sitemapUrl = null; try { - var sitemapUrl = Url.RouteUrl("SitemapSEO", (object)null, _securitySettings.Value.ForceSslForAllPages ? "https" : "http"); + sitemapUrl = Url.RouteUrl("SitemapSEO", (object)null, _securitySettings.Value.ForceSslForAllPages ? "https" : "http"); var request = (HttpWebRequest)WebRequest.Create(sitemapUrl); request.Method = "HEAD"; request.Timeout = 15000; using (var response = (HttpWebResponse)request.GetResponse()) { - sitemapReachable = (response.StatusCode == HttpStatusCode.OK); + sitemapStatusCode = response.StatusCode; } } - catch (WebException) { } + catch (WebException exception) + { + Logger.Warning(sitemapUrl.IsEmpty() ? "SitemapSEO" : sitemapUrl, exception); + } - if (sitemapReachable) + if (sitemapStatusCode.HasValue && sitemapStatusCode.Value == HttpStatusCode.OK) { model.Add(new SystemWarningModel { Level = SystemWarningLevel.Pass, - Text = _localizationService.GetResource("Admin.System.Warnings.SitemapReachable.OK") + Text = T("Admin.System.Warnings.SitemapReachable.OK") }); } else { + var warningText = T("Admin.System.Warnings.SitemapReachable.Wrong"); + if (sitemapStatusCode.HasValue) + { + warningText = string.Concat(warningText, " ", T("Admin.Common.HttpStatus", (int)sitemapStatusCode.Value, sitemapStatusCode.Value.ToString())); + } + model.Add(new SystemWarningModel { Level = SystemWarningLevel.Warning, - Text = _localizationService.GetResource("Admin.System.Warnings.SitemapReachable.Wrong") + Text = warningText }); } From a18abd6593a3cd90cd0da0104a5153ac2d4d303a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 19 May 2016 12:48:20 +0200 Subject: [PATCH 070/185] Improvements to last commit --- .../201605111140288_ImportExtraData.cs | 4 ++ .../Controllers/CommonController.cs | 45 ++++++++++--------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs index b77d237359..191ace97ba 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs @@ -63,6 +63,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Common.HttpStatus", "HTTP status {0} ({1}).", "HTTP-Status {0} ({1})."); + + builder.AddOrUpdate("Admin.System.Warnings.SitemapReachable.MethodNotAllowed", + "The reachability of the sitemap could not be validated.", + "Die Erreichbarkeit der Sitemap konnte nicht überprüft werden."); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs index ecae61fc64..318391856a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs @@ -519,7 +519,6 @@ public ActionResult Warnings() } // sitemap reachability - HttpStatusCode? sitemapStatusCode = null; string sitemapUrl = null; try { @@ -530,35 +529,37 @@ public ActionResult Warnings() using (var response = (HttpWebResponse)request.GetResponse()) { - sitemapStatusCode = response.StatusCode; + var status = response.StatusCode; + var warningModel = new SystemWarningModel(); + warningModel.Level = (status == HttpStatusCode.OK ? SystemWarningLevel.Pass : SystemWarningLevel.Warning); + + switch (status) + { + case HttpStatusCode.OK: + warningModel.Text = T("Admin.System.Warnings.SitemapReachable.OK"); + break; + default: + if (status == HttpStatusCode.MethodNotAllowed) + warningModel.Text = T("Admin.System.Warnings.SitemapReachable.MethodNotAllowed"); + else + warningModel.Text = T("Admin.System.Warnings.SitemapReachable.Wrong"); + + warningModel.Text = string.Concat(warningModel.Text, " ", T("Admin.Common.HttpStatus", (int)status, status.ToString())); + break; + } + + model.Add(warningModel); } } catch (WebException exception) { - Logger.Warning(sitemapUrl.IsEmpty() ? "SitemapSEO" : sitemapUrl, exception); - } - - if (sitemapStatusCode.HasValue && sitemapStatusCode.Value == HttpStatusCode.OK) - { - model.Add(new SystemWarningModel - { - Level = SystemWarningLevel.Pass, - Text = T("Admin.System.Warnings.SitemapReachable.OK") - }); - } - else - { - var warningText = T("Admin.System.Warnings.SitemapReachable.Wrong"); - if (sitemapStatusCode.HasValue) - { - warningText = string.Concat(warningText, " ", T("Admin.Common.HttpStatus", (int)sitemapStatusCode.Value, sitemapStatusCode.Value.ToString())); - } - model.Add(new SystemWarningModel { Level = SystemWarningLevel.Warning, - Text = warningText + Text = T("Admin.System.Warnings.SitemapReachable.Wrong") }); + + Logger.Warning(sitemapUrl.IsEmpty() ? "SitemapSEO" : sitemapUrl, exception); } //primary exchange rate currency From a08b83dd7f5f91e3a7d274a0f61f20711ea557b0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 19 May 2016 17:06:38 +0200 Subject: [PATCH 071/185] Resolves #859 Make checkout attributes suitable for multi-stores --- changelog.md | 1 + .../Domain/Orders/CheckoutAttribute.cs | 27 ++- ...16_CheckoutAttributeMultiStore.Designer.cs | 29 +++ ...05191216116_CheckoutAttributeMultiStore.cs | 17 ++ ...191216116_CheckoutAttributeMultiStore.resx | 126 ++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 7 + .../Orders/CheckoutAttributeService.cs | 70 ++++-- .../Orders/ICheckoutAttributeService.cs | 20 +- .../Orders/ShoppingCartService.cs | 2 +- .../CheckoutAttributeController.cs | 96 +++++--- .../Infrastructure/AutoMapperStartupTask.cs | 14 +- .../Orders/CheckoutAttributeListModel.cs | 13 ++ .../Models/Orders/CheckoutAttributeModel.cs | 12 +- .../Administration/SmartStore.Admin.csproj | 1 + .../Views/CheckoutAttribute/List.cshtml | 24 +- .../CheckoutAttribute/_CreateOrUpdate.cshtml | 216 +++++++++++------- .../Controllers/ShoppingCartController.cs | 6 +- ...heckoutAttributeParserAndFormatterTests.cs | 15 +- 18 files changed, 506 insertions(+), 190 deletions(-) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.resx create mode 100644 src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeListModel.cs diff --git a/changelog.md b/changelog.md index 7327aee605..aa1f1df6c0 100644 --- a/changelog.md +++ b/changelog.md @@ -14,6 +14,7 @@ * #886 Add setting to hide manufacturer images on product detail page and to hide default image for manufacturers * Import localized SEO names for product and categories * #477 Implement option to specify the number of exported and imported pictures +* #859 Make checkout attributes suitable for multi-stores ### Improvements * Major improvements in Importer: better field mapping, higher performance, bug fixes etc. diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutAttribute.cs b/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutAttribute.cs index a317879baa..63cbf58ea8 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutAttribute.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutAttribute.cs @@ -1,14 +1,15 @@ using System.Collections.Generic; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Stores; namespace SmartStore.Core.Domain.Orders { - /// - /// Represents a checkout attribute - /// - public partial class CheckoutAttribute : BaseEntity, ILocalizedEntity - { + /// + /// Represents a checkout attribute + /// + public partial class CheckoutAttribute : BaseEntity, ILocalizedEntity, IStoreMappingSupported + { private ICollection _checkoutAttributeValues; public CheckoutAttribute() @@ -60,11 +61,16 @@ public CheckoutAttribute() /// Gets or sets whether the checkout attribute is active ///
public bool IsActive { get; set; } - - /// - /// Gets the attribute control type - /// - public AttributeControlType AttributeControlType + + /// + /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores + /// + public bool LimitedToStores { get; set; } + + /// + /// Gets the attribute control type + /// + public AttributeControlType AttributeControlType { get { @@ -75,6 +81,7 @@ public AttributeControlType AttributeControlType this.AttributeControlTypeId = (int)value; } } + /// /// Gets the checkout attribute values /// diff --git a/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.Designer.cs new file mode 100644 index 0000000000..d4625babee --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.1.3-40302")] + public sealed partial class CheckoutAttributeMultiStore : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(CheckoutAttributeMultiStore)); + + string IMigrationMetadata.Id + { + get { return "201605191216116_CheckoutAttributeMultiStore"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.cs b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.cs new file mode 100644 index 0000000000..1b6ba5a31f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.cs @@ -0,0 +1,17 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + + public partial class CheckoutAttributeMultiStore : DbMigration + { + public override void Up() + { + AddColumn("dbo.CheckoutAttribute", "LimitedToStores", c => c.Boolean(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.CheckoutAttribute", "LimitedToStores"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.resx b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.resx new file mode 100644 index 0000000000..d743254b0e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index bdec0c33b4..79a179374f 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -396,6 +396,10 @@ 201605111140288_ImportExtraData.cs + + + 201605191216116_CheckoutAttributeMultiStore.cs + @@ -738,6 +742,9 @@ 201605111140288_ImportExtraData.cs + + 201605191216116_CheckoutAttributeMultiStore.cs + diff --git a/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs index d9a340c451..798636316a 100644 --- a/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs @@ -4,18 +4,19 @@ using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; namespace SmartStore.Services.Orders { - /// - /// Checkout attribute service - /// - public partial class CheckoutAttributeService : ICheckoutAttributeService + /// + /// Checkout attribute service + /// + public partial class CheckoutAttributeService : ICheckoutAttributeService { #region Constants - private const string CHECKOUTATTRIBUTES_ALL_KEY = "SmartStore.checkoutattribute.all-{0}"; + private const string CHECKOUTATTRIBUTES_ALL_KEY = "SmartStore.checkoutattribute.all-{0}-{1}"; private const string CHECKOUTATTRIBUTEVALUES_ALL_KEY = "SmartStore.checkoutattributevalue.all-{0}"; private const string CHECKOUTATTRIBUTES_PATTERN_KEY = "SmartStore.checkoutattribute."; private const string CHECKOUTATTRIBUTEVALUES_PATTERN_KEY = "SmartStore.checkoutattributevalue."; @@ -28,7 +29,8 @@ public partial class CheckoutAttributeService : ICheckoutAttributeService private readonly IRepository _checkoutAttributeRepository; private readonly IRepository _checkoutAttributeValueRepository; - private readonly IEventPublisher _eventPublisher; + private readonly IRepository _storeMappingRepository; + private readonly IEventPublisher _eventPublisher; private readonly ICacheManager _cacheManager; #endregion @@ -45,25 +47,31 @@ public partial class CheckoutAttributeService : ICheckoutAttributeService public CheckoutAttributeService(ICacheManager cacheManager, IRepository checkoutAttributeRepository, IRepository checkoutAttributeValueRepository, - IEventPublisher eventPublisher) + IRepository storeMappingRepository, + IEventPublisher eventPublisher) { _cacheManager = cacheManager; _checkoutAttributeRepository = checkoutAttributeRepository; _checkoutAttributeValueRepository = checkoutAttributeValueRepository; + _storeMappingRepository = storeMappingRepository; _eventPublisher = eventPublisher; - } - #endregion + this.QuerySettings = DbQuerySettings.Default; + } - #region Methods + #endregion - #region Checkout attributes + public DbQuerySettings QuerySettings { get; set; } - /// - /// Deletes a checkout attribute - /// - /// Checkout attribute - public virtual void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute) + #region Methods + + #region Checkout attributes + + /// + /// Deletes a checkout attribute + /// + /// Checkout attribute + public virtual void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute) { if (checkoutAttribute == null) throw new ArgumentNullException("checkoutAttribute"); @@ -77,13 +85,15 @@ public virtual void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute) _eventPublisher.EntityDeleted(checkoutAttribute); } - /// - /// Gets all checkout attributes - /// - /// Checkout attribute collection - public virtual IList GetAllCheckoutAttributes(bool showHidden = false) + /// + /// Gets all checkout attributes + /// + /// Whether to filter result by store identifier + /// A value indicating whether to show hidden records + /// Checkout attribute collection + public virtual IList GetAllCheckoutAttributes(int storeId = 0, bool showHidden = false) { - string key = CHECKOUTATTRIBUTES_ALL_KEY.FormatInvariant(showHidden); + string key = CHECKOUTATTRIBUTES_ALL_KEY.FormatInvariant(storeId, showHidden); return _cacheManager.Get(key, () => { @@ -92,6 +102,22 @@ public virtual IList GetAllCheckoutAttributes(bool showHidden if (!showHidden) query = query.Where(x => x.IsActive); + if (storeId > 0 && !QuerySettings.IgnoreMultiStore) + { + query = + from x in query + join sm in _storeMappingRepository.Table on new { c1 = x.Id, c2 = "CheckoutAttribute" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into x_sm + from sm in x_sm.DefaultIfEmpty() + where !x.LimitedToStores || storeId == sm.StoreId + select x; + + query = + from x in query + group x by x.Id into grp + orderby grp.Key + select grp.FirstOrDefault(); + } + query = query.OrderBy(x => x.DisplayOrder); var checkoutAttributes = query.ToList(); diff --git a/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs b/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs index 2ceb9c3396..57ee71c45f 100644 --- a/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs @@ -3,10 +3,10 @@ namespace SmartStore.Services.Orders { - /// - /// Checkout attribute service - /// - public partial interface ICheckoutAttributeService + /// + /// Checkout attribute service + /// + public partial interface ICheckoutAttributeService { #region Checkout attributes @@ -16,11 +16,13 @@ public partial interface ICheckoutAttributeService /// Checkout attribute void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute); - /// - /// Gets all checkout attributes - /// - /// Checkout attribute collection - IList GetAllCheckoutAttributes(bool showHidden = false); + /// + /// Gets all checkout attributes + /// + /// Whether to filter result by store identifier + /// A value indicating whether to show hidden records + /// Checkout attribute collection + IList GetAllCheckoutAttributes(int storeId = 0, bool showHidden = false); /// /// Gets a checkout attribute diff --git a/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs b/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs index 259b323910..27f0331be4 100644 --- a/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs +++ b/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs @@ -879,7 +879,7 @@ public virtual IList GetShoppingCartWarnings(IList s.ToModel()) + .ToList(); + + if (!excludeProperties) + { + model.SelectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(checkoutAttribute); + } + } #endregion @@ -114,7 +141,14 @@ public ActionResult List() if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - return View(); + var model = new CheckoutAttributeListModel + { + GridPageSize = _adminAreaSettings.GridPageSize + }; + + model.AvailableStores = _services.StoreService.GetAllStores().ToSelectListItems(); + + return View(model); } [HttpPost, GridAction(EnableCustomBinding = true)] @@ -124,7 +158,7 @@ public ActionResult List(GridCommand command) if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) { - var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(true); + var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(0, true); model.Data = checkoutAttributes.Select(x => { @@ -173,10 +207,13 @@ public ActionResult Create(CheckoutAttributeModel model, bool continueEditing) { var checkoutAttribute = model.ToEntity(); _checkoutAttributeService.InsertCheckoutAttribute(checkoutAttribute); + UpdateAttributeLocales(checkoutAttribute, model); - //activity log - _customerActivityService.InsertActivity("AddNewCheckoutAttribute", _services.Localization.GetResource("ActivityLog.AddNewCheckoutAttribute"), checkoutAttribute.Name); + _storeMappingService.SaveStoreMappings(checkoutAttribute, model.SelectedStoreIds); + + //activity log + _customerActivityService.InsertActivity("AddNewCheckoutAttribute", _services.Localization.GetResource("ActivityLog.AddNewCheckoutAttribute"), checkoutAttribute.Name); NotifySuccess(_services.Localization.GetResource("Admin.Catalog.Attributes.CheckoutAttributes.Added")); return continueEditing ? RedirectToAction("Edit", new { id = checkoutAttribute.Id }) : RedirectToAction("List"); @@ -195,7 +232,6 @@ public ActionResult Edit(int id) var checkoutAttribute = _checkoutAttributeService.GetCheckoutAttributeById(id); if (checkoutAttribute == null) - //No checkout attribute found with the specified id return RedirectToAction("List"); var model = checkoutAttribute.ToModel(); @@ -218,7 +254,6 @@ public ActionResult Edit(CheckoutAttributeModel model, bool continueEditing) var checkoutAttribute = _checkoutAttributeService.GetCheckoutAttributeById(model.Id); if (checkoutAttribute == null) - //No checkout attribute found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) @@ -228,8 +263,10 @@ public ActionResult Edit(CheckoutAttributeModel model, bool continueEditing) UpdateAttributeLocales(checkoutAttribute, model); - //activity log - _customerActivityService.InsertActivity("EditCheckoutAttribute", _services.Localization.GetResource("ActivityLog.EditCheckoutAttribute"), checkoutAttribute.Name); + _storeMappingService.SaveStoreMappings(checkoutAttribute, model.SelectedStoreIds); + + //activity log + _customerActivityService.InsertActivity("EditCheckoutAttribute", _services.Localization.GetResource("ActivityLog.EditCheckoutAttribute"), checkoutAttribute.Name); NotifySuccess(_services.Localization.GetResource("Admin.Catalog.Attributes.CheckoutAttributes.Updated")); return continueEditing ? RedirectToAction("Edit", checkoutAttribute.Id) : RedirectToAction("List"); @@ -311,7 +348,6 @@ public ActionResult ValueCreatePopup(string btnId, string formId, CheckoutAttrib var checkoutAttribute = _checkoutAttributeService.GetCheckoutAttributeById(model.CheckoutAttributeId); if (checkoutAttribute == null) - //No checkout attribute found with the specified id return RedirectToAction("List"); model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; @@ -342,7 +378,6 @@ public ActionResult ValueEditPopup(int id) var cav = _checkoutAttributeService.GetCheckoutAttributeValueById(id); if (cav == null) - //No checkout attribute value found with the specified id return RedirectToAction("List"); var model = cav.ToModel(); @@ -366,7 +401,6 @@ public ActionResult ValueEditPopup(string btnId, string formId, CheckoutAttribut var cav = _checkoutAttributeService.GetCheckoutAttributeValueById(model.Id); if (cav == null) - //No checkout attribute value found with the specified id return RedirectToAction("List"); model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs index 2f7c0f3339..bee55cf932 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs @@ -520,12 +520,14 @@ public void Execute() Mapper.CreateMap() .ForMember(dest => dest.SpecificationAttribute, mo => mo.Ignore()) .ForMember(dest => dest.ProductSpecificationAttributes, mo => mo.Ignore()); - //checkout attributes - Mapper.CreateMap() - .ForMember(dest => dest.AvailableTaxCategories, mo => mo.Ignore()) - .ForMember(dest => dest.AttributeControlTypeName, mo => mo.Ignore()) - .ForMember(dest => dest.Locales, mo => mo.Ignore()); - Mapper.CreateMap() + //checkout attributes + Mapper.CreateMap() + .ForMember(dest => dest.AvailableTaxCategories, mo => mo.Ignore()) + .ForMember(dest => dest.AttributeControlTypeName, mo => mo.Ignore()) + .ForMember(dest => dest.Locales, mo => mo.Ignore()) + .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()); + Mapper.CreateMap() .ForMember(dest => dest.AttributeControlType, mo => mo.Ignore()) .ForMember(dest => dest.CheckoutAttributeValues, mo => mo.Ignore()); Mapper.CreateMap() diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeListModel.cs new file mode 100644 index 0000000000..6084bd6210 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeListModel.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.Orders +{ + public class CheckoutAttributeListModel : ModelBase + { + public int GridPageSize { get; set; } + + public IList AvailableStores { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeModel.cs index 5e26016af9..6f6a404473 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeModel.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using FluentValidation.Attributes; +using SmartStore.Admin.Models.Stores; using SmartStore.Admin.Validators.Orders; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; @@ -51,9 +52,16 @@ public CheckoutAttributeModel() public int DisplayOrder { get; set; } public IList Locales { get; set; } - } - public class CheckoutAttributeLocalizedModel : ILocalizedModelLocal + [SmartResourceDisplayName("Admin.Common.Store.LimitedTo")] + public bool LimitedToStores { get; set; } + + [SmartResourceDisplayName("Admin.Common.Store.AvailableFor")] + public List AvailableStores { get; set; } + public int[] SelectedStoreIds { get; set; } + } + + public class CheckoutAttributeLocalizedModel : ILocalizedModelLocal { public int LanguageId { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index 7d95256bb5..2dad6d6fe4 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -265,6 +265,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml index d80b3f8bfb..33ab6112a0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml @@ -1,5 +1,5 @@ @using Telerik.Web.Mvc.UI - +@model CheckoutAttributeListModel @{ ViewBag.Title = T("Admin.Catalog.Attributes.CheckoutAttributes").Text; } @@ -26,30 +26,22 @@ .Columns(columns => { columns.Bound(x => x.Name) - .Width(300) - .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) + .Width(500) .ClientTemplate("<#= Name #>"); - columns.Bound(x => x.AttributeControlTypeName) - .Width(150); + columns.Bound(x => x.TextPrompt); + columns.Bound(x => x.AttributeControlTypeName); columns.Bound(x => x.IsActive) - .Width(100) - .Template(item => @Html.SymbolForBool(item.IsActive)) .ClientTemplate(@Html.SymbolForBool("IsActive")) .Centered(); columns.Bound(x => x.IsRequired) - .Width(100) - .Template(item => @Html.SymbolForBool(item.IsRequired)) .ClientTemplate(@Html.SymbolForBool("IsRequired")) .Centered(); columns.Bound(x => x.DisplayOrder) - .Width(100) .Centered(); - columns.Bound(x => x.Id) - .Width(50) - .Centered() - .Template(x => Html.ActionLink(T("Admin.Common.Edit").Text, "Edit", new { id = x.Id })) - .ClientTemplate("\">" + T("Admin.Common.Edit").Text + "") - .Title(T("Admin.Common.Edit").Text); + columns.Bound(x => x.LimitedToStores) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Hidden(Model.AvailableStores.Count <= 1) + .Centered(); }) .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "CheckoutAttribute")) .ClientEvents(events => events.OnRowDataBound("checkoutAttributesGrid_onRowDataBound")) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/_CreateOrUpdate.cshtml index 33499f2b73..be84f43826 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/_CreateOrUpdate.cshtml @@ -1,17 +1,17 @@ -@model CheckoutAttributeModel - -@using SmartStore.Core.Domain.Catalog; +@using SmartStore.Core.Domain.Catalog; @using Telerik.Web.Mvc.UI; +@model CheckoutAttributeModel @Html.ValidationSummary(false) @Html.HiddenFor(model => model.Id) @Html.SmartStore().TabStrip().Name("checkoutattribute-edit").Items(x => { - x.Add().Text(T("Admin.Catalog.Attributes.CheckoutAttributes.Info").Text).Content(TabInfo()).Selected(true); - x.Add().Text(T("Admin.Catalog.Attributes.CheckoutAttributes.Values").Text).Content(TabValues()); - - //generate an event - EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "checkoutattribute-edit", this.Html, this.Model)); + x.Add().Text(T("Admin.Catalog.Attributes.CheckoutAttributes.Info").Text).Content(TabInfo()).Selected(true); + x.Add().Text(T("Admin.Catalog.Attributes.CheckoutAttributes.Values").Text).Content(TabValues()); + x.Add().Text(T("Admin.Common.Stores").Text).Content(TabStores()); + + //generate an event + EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "checkoutattribute-edit", this.Html, this.Model)); }) @helper TabInfo() @@ -151,85 +151,131 @@ @helper TabValues() { - if (Model.Id > 0) - { - /*TODO display the following warning if values are not supported by selected attribute control type - 'Values are not required for this attribute control type' - */ - - - + +
- @(Html.Telerik().Grid() - .Name("checkoutattributevalues-grid") - .DataKeys(x => - { - x.Add(y => y.Id).RouteKey("valueId"); - x.Add(y => y.CheckoutAttributeId).RouteKey("checkoutAttributeId"); - }) - .Columns(columns => - { - columns.Bound(x => x.Name); - columns.Bound(x => x.PriceAdjustment) - .Format("{0:0.00}") - .RightAlign() - .Width(200); - columns.Bound(x => x.WeightAdjustment) - .Format("{0:0.00}") - .Centered() - .Width(200); - columns.Bound(x => x.IsPreSelected) - .Width(140) - .Template(item => @Html.SymbolForBool(item.IsPreSelected)) - .ClientTemplate(@Html.SymbolForBool("IsPreSelected")) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Width(140) - .Centered(); - columns.Bound(x => x.Id) - .Width(120) - .Centered() - .ClientTemplate("?btnId=btnRefresh&formId=checkoutattribute-form', 800, 300, true); return false;\" class='t-button' />") - .Title(T("Admin.Common.Edit").Text); - columns.Command(commands => - { - commands.Delete().Localize(T); - }) + if (Model.Id > 0) + { + /*TODO display the following warning if values are not supported by selected attribute control type + 'Values are not required for this attribute control type' + */ + + + - - - + + + - -
+ @(Html.Telerik().Grid() + .Name("checkoutattributevalues-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("valueId"); + x.Add(y => y.CheckoutAttributeId).RouteKey("checkoutAttributeId"); + }) + .Columns(columns => + { + columns.Bound(x => x.Name); + columns.Bound(x => x.PriceAdjustment) + .Format("{0:0.00}") + .RightAlign() + .Width(200); + columns.Bound(x => x.WeightAdjustment) + .Format("{0:0.00}") + .Centered() + .Width(200); + columns.Bound(x => x.IsPreSelected) + .Width(140) + .Template(item => @Html.SymbolForBool(item.IsPreSelected)) + .ClientTemplate(@Html.SymbolForBool("IsPreSelected")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Width(140) + .Centered(); + columns.Bound(x => x.Id) .Width(120) - .Title(T("Admin.Common.Delete").Text); + .Centered() + .ClientTemplate("?btnId=btnRefresh&formId=checkoutattribute-form', 800, 300, true); return false;\" class='t-button' />") + .Title(T("Admin.Common.Edit").Text); + columns.Command(commands => + { + commands.Delete().Localize(T); }) - .DataBinding(dataBinding => - dataBinding.Ajax().Select("ValueList", "CheckoutAttribute", new { checkoutAttributeId = Model.Id }) - .Delete("ValueDelete", "CheckoutAttribute")) - .EnableCustomBinding(true)) -
- + .Width(120) + .Title(T("Admin.Common.Delete").Text); + }) + .DataBinding(dataBinding => + dataBinding.Ajax().Select("ValueList", "CheckoutAttribute", new { checkoutAttributeId = Model.Id }) + .Delete("ValueDelete", "CheckoutAttribute")) + .EnableCustomBinding(true)) +
+ - + - -
- } - else - { - @T("Admin.Catalog.Attributes.CheckoutAttributes.Values.SaveBeforeEdit") - } - } \ No newline at end of file + //return false to don't reload a page + return false; + }); + }); + +
+ } + else + { + @T("Admin.Catalog.Attributes.CheckoutAttributes.Values.SaveBeforeEdit") + } +} + +@helper TabStores() +{ + + + + + + + + + + + +
+ @Html.SmartLabelFor(model => model.LimitedToStores) + + @Html.EditorFor(model => model.LimitedToStores) + @Html.ValidationMessageFor(model => model.LimitedToStores) +
+ @Html.SmartLabelFor(model => model.AvailableStores) + + @if (Model.AvailableStores != null && Model.AvailableStores.Count > 0) + { + foreach (var store in Model.AvailableStores) + { + + } + } + else + { +
@T("Admin.Configuration.Stores.NoStoresDefined")
+ } +
+} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index a61c57d5ad..e6ce38af48 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -700,7 +700,7 @@ protected void PrepareShoppingCartModel(ShoppingCartModel model, #region Checkout attributes - var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(); + var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(_storeContext.CurrentStore.Id); if (!cart.RequiresShipping()) { //remove attributes which require shippable products @@ -985,7 +985,7 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() //1. "terms of services" are enabled (OBSOLETE now) //2. we have at least one checkout attribute //3. min order sub-total is OK - var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(); + var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(_storeContext.CurrentStore.Id); if (!cart.RequiresShipping()) { //remove attributes which require shippable products @@ -1076,7 +1076,7 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() protected void ParseAndSaveCheckoutAttributes(List cart, FormCollection form) { string selectedAttributes = ""; - var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(); + var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(_storeContext.CurrentStore.Id); if (!cart.RequiresShipping()) { diff --git a/src/Tests/SmartStore.Services.Tests/Orders/CheckoutAttributeParserAndFormatterTests.cs b/src/Tests/SmartStore.Services.Tests/Orders/CheckoutAttributeParserAndFormatterTests.cs index 11a8780744..c744c9874a 100644 --- a/src/Tests/SmartStore.Services.Tests/Orders/CheckoutAttributeParserAndFormatterTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Orders/CheckoutAttributeParserAndFormatterTests.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; using System.Linq; +using NUnit.Framework; +using Rhino.Mocks; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; @@ -7,24 +9,24 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Events; using SmartStore.Services.Catalog; using SmartStore.Services.Directory; -using SmartStore.Core.Events; using SmartStore.Services.Media; using SmartStore.Services.Orders; using SmartStore.Services.Tax; using SmartStore.Tests; -using NUnit.Framework; -using Rhino.Mocks; namespace SmartStore.Services.Tests.Orders { - [TestFixture] + [TestFixture] public class CheckoutAttributeParserAndFormatterTests : ServiceTest { IRepository _checkoutAttributeRepo; IRepository _checkoutAttributeValueRepo; - IEventPublisher _eventPublisher; + IRepository _storeMappingRepo; + IEventPublisher _eventPublisher; ICheckoutAttributeService _checkoutAttributeService; ICheckoutAttributeParser _checkoutAttributeParser; @@ -129,6 +131,8 @@ public class CheckoutAttributeParserAndFormatterTests : ServiceTest _checkoutAttributeValueRepo.Expect(x => x.GetById(cav2_1.Id)).Return(cav2_1); _checkoutAttributeValueRepo.Expect(x => x.GetById(cav2_2.Id)).Return(cav2_2); + _storeMappingRepo = MockRepository.GenerateMock>(); + var cacheManager = new NullCache(); _eventPublisher = MockRepository.GenerateMock(); @@ -137,6 +141,7 @@ public class CheckoutAttributeParserAndFormatterTests : ServiceTest _checkoutAttributeService = new CheckoutAttributeService(cacheManager, _checkoutAttributeRepo, _checkoutAttributeValueRepo, + _storeMappingRepo, _eventPublisher); _checkoutAttributeParser = new CheckoutAttributeParser(_checkoutAttributeService); From 6acca50f6e2c0398f9b28ca03fcef7e43f8f51fa Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 18 May 2016 21:35:11 +0200 Subject: [PATCH 072/185] Moved some misplaced project files --- .../{ => Export}/Internal/CategoryExportContext.cs | 0 .../{ => Export}/Internal/CustomerExportContext.cs | 0 .../{ => Export}/Internal/DataExporterContext.cs | 0 .../{ => Export}/Internal/DynamicEntity.cs | 0 .../Internal/ManufacturerExportContext.cs | 0 .../{ => Export}/Internal/OrderExportContext.cs | 0 .../{ => Export}/Internal/ProductExportContext.cs | 0 .../SmartStore.Services/SmartStore.Services.csproj | 14 +++++++------- 8 files changed, 7 insertions(+), 7 deletions(-) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Internal/CategoryExportContext.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Internal/CustomerExportContext.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Internal/DataExporterContext.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Internal/DynamicEntity.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Internal/ManufacturerExportContext.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Internal/OrderExportContext.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Internal/ProductExportContext.cs (100%) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/CategoryExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/CategoryExportContext.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/CategoryExportContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Internal/CategoryExportContext.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/CustomerExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/CustomerExportContext.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/CustomerExportContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Internal/CustomerExportContext.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DynamicEntity.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DynamicEntity.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/ManufacturerExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ManufacturerExportContext.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/ManufacturerExportContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ManufacturerExportContext.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/OrderExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/OrderExportContext.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/OrderExportContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Internal/OrderExportContext.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/ProductExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ProductExportContext.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/ProductExportContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ProductExportContext.cs diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 262454bf5c..529b51a6f4 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -205,19 +205,19 @@ - + - - - + + + - + - - + + From a80ca88c54a0bf0d1fedd8f92eefddfc98c97c43 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 19 May 2016 21:43:59 +0200 Subject: [PATCH 073/185] Added paging to backend checkout attribute list --- changelog.md | 1 + .../Orders/CheckoutAttributeService.cs | 64 +++++++++++-------- .../Orders/ICheckoutAttributeService.cs | 9 +++ .../CheckoutAttributeController.cs | 8 ++- .../Views/CheckoutAttribute/List.cshtml | 5 +- 5 files changed, 57 insertions(+), 30 deletions(-) diff --git a/changelog.md b/changelog.md index aa1f1df6c0..9502bd2eb2 100644 --- a/changelog.md +++ b/changelog.md @@ -25,6 +25,7 @@ * Export email attachments needs to be stored in database because the temp file may not exist anymore when sending the email * #913 Use HTML5 Input types (tel, email) * Added paging to frontend order list +* Added paging to backend checkout attribute list ### Bugfixes * Fixed ajax cache issue when saving payment or shipping restrictions. Internet Explorer showed the old data state (before storage). diff --git a/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs index 798636316a..7e266a98b1 100644 --- a/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs @@ -85,6 +85,40 @@ public virtual void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute) _eventPublisher.EntityDeleted(checkoutAttribute); } + /// + /// Gets checkout attributes + /// + /// Whether to filter result by store identifier + /// A value indicating whether to show hidden records + /// Checkout attributes query + public virtual IQueryable GetCheckoutAttributes(int storeId = 0, bool showHidden = false) + { + var query = _checkoutAttributeRepository.Table; + + if (!showHidden) + query = query.Where(x => x.IsActive); + + if (storeId > 0 && !QuerySettings.IgnoreMultiStore) + { + query = + from x in query + join sm in _storeMappingRepository.Table on new { c1 = x.Id, c2 = "CheckoutAttribute" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into x_sm + from sm in x_sm.DefaultIfEmpty() + where !x.LimitedToStores || storeId == sm.StoreId + select x; + + query = + from x in query + group x by x.Id into grp + orderby grp.Key + select grp.FirstOrDefault(); + } + + query = query.OrderBy(x => x.DisplayOrder); + + return query; + } + /// /// Gets all checkout attributes /// @@ -97,32 +131,10 @@ public virtual IList GetAllCheckoutAttributes(int storeId = 0 return _cacheManager.Get(key, () => { - var query = _checkoutAttributeRepository.Table; - - if (!showHidden) - query = query.Where(x => x.IsActive); - - if (storeId > 0 && !QuerySettings.IgnoreMultiStore) - { - query = - from x in query - join sm in _storeMappingRepository.Table on new { c1 = x.Id, c2 = "CheckoutAttribute" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into x_sm - from sm in x_sm.DefaultIfEmpty() - where !x.LimitedToStores || storeId == sm.StoreId - select x; - - query = - from x in query - group x by x.Id into grp - orderby grp.Key - select grp.FirstOrDefault(); - } - - query = query.OrderBy(x => x.DisplayOrder); - - var checkoutAttributes = query.ToList(); - return checkoutAttributes; - }); + var query = GetCheckoutAttributes(storeId, showHidden); + + return query.ToList(); + }); } /// diff --git a/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs b/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs index 57ee71c45f..9a520ca28b 100644 --- a/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using SmartStore.Core.Domain.Orders; namespace SmartStore.Services.Orders @@ -16,6 +17,14 @@ public partial interface ICheckoutAttributeService /// Checkout attribute void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute); + /// + /// Gets checkout attributes + /// + /// Whether to filter result by store identifier + /// A value indicating whether to show hidden records + /// Checkout attributes query + IQueryable GetCheckoutAttributes(int storeId = 0, bool showHidden = false); + /// /// Gets all checkout attributes /// diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CheckoutAttributeController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CheckoutAttributeController.cs index da0b4aaf19..51db07dad9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CheckoutAttributeController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CheckoutAttributeController.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Orders; +using SmartStore.Core; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Orders; @@ -158,16 +159,17 @@ public ActionResult List(GridCommand command) if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) { - var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(0, true); + var query = _checkoutAttributeService.GetCheckoutAttributes(0, true); + var pagedList = new PagedList(query, command.Page - 1, command.PageSize); - model.Data = checkoutAttributes.Select(x => + model.Data = pagedList.Select(x => { var caModel = x.ToModel(); caModel.AttributeControlTypeName = x.AttributeControlType.GetLocalizedEnum(_services.Localization, _services.WorkContext); return caModel; }); - model.Total = checkoutAttributes.Count(); + model.Total = pagedList.TotalCount; } else { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml index 33ab6112a0..b3707ed7fe 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml @@ -8,7 +8,9 @@ @T("Admin.Catalog.Attributes.CheckoutAttributes")
\ No newline at end of file From 82020c6c5f835e107d9ec1e3515c792f10bf89b0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 20 May 2016 18:23:04 +0200 Subject: [PATCH 079/185] Removed confusing export deployment ZIP option. It is now using the profile ZIP option. --- .../Domain/DataExchange/ExportDeployment.cs | 6 - .../201605201230154_ExportRevision.resx | 126 ------------------ ...01605201342001_ExportRevision.Designer.cs} | 2 +- ...n.cs => 201605201342001_ExportRevision.cs} | 11 +- .../201605201342001_ExportRevision.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 10 +- .../Deployment/FileSystemFilePublisher.cs | 8 +- .../DataExchange/Export/DataExporter.cs | 26 ++-- .../DataExchange/Export/ExportExtensions.cs | 2 +- .../Administration/Content/admin.less | 3 + .../Controllers/ExportController.cs | 13 +- .../DataExchange/ExportDeploymentModel.cs | 6 +- .../Views/Export/InfoDeployment.cshtml | 8 +- .../Export/_CreateOrUpdate.Deployment.cshtml | 23 ++-- 14 files changed, 190 insertions(+), 180 deletions(-) delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.resx rename src/Libraries/SmartStore.Data/Migrations/{201605201230154_ExportRevision.Designer.cs => 201605201342001_ExportRevision.Designer.cs} (92%) rename src/Libraries/SmartStore.Data/Migrations/{201605201230154_ExportRevision.cs => 201605201342001_ExportRevision.cs} (82%) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.resx diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs index 0cb68384a3..2b89d0925a 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs @@ -20,11 +20,6 @@ public class ExportDeployment : BaseEntity, ICloneable ///
public bool Enabled { get; set; } - /// - /// Whether to create a zip archive with the content of the export - /// - public bool CreateZip { get; set; } - /// /// The deployment type identifier /// @@ -117,7 +112,6 @@ public ExportDeployment Clone() { Name = this.Name, Enabled = this.Enabled, - CreateZip = this.CreateZip, DeploymentTypeId = this.DeploymentTypeId, Username = this.Username, Password = this.Password, diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.resx b/src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.resx deleted file mode 100644 index c1a2a076b4..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.resx +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - H4sIAAAAAAAEAOy923LcuLIo+D4R8w8OP50zsY/ddq+OWLuje05IsmQrti9akmzvvV4UFAuSuMwiq3nRpSfmy+ZhPun8wgHAGwgk7iBZpa4Xu0QkEkAikUgkMhP/6//7/3/7n4/r9MU9Ksokz35/+ebVTy9foCzOV0l2+/vLurr5H39/+T//7//z//jteLV+fPGtg/uZwOGaWfn7y7uq2vz6+nUZ36F1VL5aJ3GRl/lN9SrO16+jVf767U8//fvrN29eI4ziJcb14sVv53VWJWtE/8B/HuVZjDZVHaWf8hVKy/Y7LrmgWF98jtao3EQx+v3lxToqqosqL9Crd1EVvXxxkCYR7sYFSm9evoiyLK+iCnfy168luqiKPLu92OAPUXr5tEEY7iZKS9R2/tcB3HQcP70l43g9VOxQxXVZ5WtLhG9+bgnzmq/uRN6XPeEw6Y4xiasnMmpKvt9fXuabJH75gm/p16O0IFAj0h5R+mKwJHtF65XNf//2ggP6t54p3r765dVPr376txdHdVrVBfo9Q3VVROm/vTirr9Mk/g/0dJn/QNnvWZ2mbE9xX3HZ6AP+dFbkG1RUT+fopu3/6erli9fjeq/5in01pk4zuNOs+vntyxefcePRdYp6RmAIQUf1HmWoiCq0OouqChUZwYEoKYXWubYunsoKrcnvrk3Mf3gdvXzxKXr8iLLb6u73l/jnyxcnySNadV/afnzNErzscKWqqJGuqdMsTusVOs0uEtxktOkaPMzzFEUZMEwNvvIsKsuHvFjhggrFePi+KDuEk9PiMqnS6Sl+mK+eJm/kE6oizNGEbOUsjb1DZVwkm0bgzNDePHP1MVnjZbG6zOmCLn05+RxlK1QclN+T1S2qfLE1WP6ZZ9PToWnqexFt8AZbYSEm9N2k/sVd/jCaN7+RH2LmRkUA+VIkeUGlslq+GwiPy+g28Fz89nrYfdV7cvR4hDeb27x4ctmZo8dXDIb95ixvS7Mt/+2nn4wm2ZK93iXlJo2evhCWt2FUY/65QFVFx2LNO1gk3CS3dUGhX7V49hzkzEFvp+Ggb1Fah9gpLJultNJT14lnP+ZxlCZ/olXXpgP3tjga5hUQ7tlY3lYzHXZTC6hYUXZbR7eWLALgIVOHMH3eF3m9mV9A9+0v1fRc6/tzdJ/cUgaSzOTLF+copQDlXbJp7CniyroawE+KfH2ep9CC7qGuLvK6iMn4ci3oZVRQ9dpNpvTd8hQlLZ69BJG3pdkI30y0XNqZaamu3ImnaB/X/qNGFyg/wjhUrQc4uJ2k0e3pGg/2JEmRhty/mI1Wc8StUt/zWOBDN11L5Z3/OdFVBVfKTCq6m8k4RyWVcaVcgHKQchkqA+xl40iMSqE7oeuunXGogyhoHM69hNXLOl/tqqP1MkeXrvWFjjCnJVldZ2l9m2SCENFVvczrGBI+4bQqf6EA6lZaEeIkFM5QsU5KsjjPUUyN+tYC4QLFNbHXveJx7QWBvK1Al0m2h3+Ti6y3v/wyRduDNXTylqWL94jyNirIsoK3dZ6Hr7gqwxJWQwprWAPutYhZVC4Gw7Z6+YpFtF+9zqt3ohV0UiB0gVl1Q9vzU54vo8fjR7TeeN96YUStIk7wcFOgrnoQV8m99+XTadlItYb5/XAFlY+mQomXDBMLJv7EYSjH3LSLHK9/e4FEqpX0370QkrcV6iyxpCrS+kRMfmEezOhA7sy/ZB/w+jijKr0ftoM0zR/e16is8LnkW155I/SwiQj3RHjdvcOM+bWKO0zkz8tkra17nK0ca/ramtyObUTSgMe0UYGo0o1KIQ1OKfZx7YOsfMDiTNqppvyqEaPjbjFFokjnyr1leIPMS5I3KPbyXCGjMJV2U5Z/rtfXqPhyQyRY6TeAKYy6zfLxW2LQ2oeWoE2fMLmoQUeh9HFQV+xiHHdWAgbKBhmst5xgEXtJCxZRMJnx4jAqUdsBQt1O0e186LSrsyGT9Ro12AKmmn2IbXWc4mWCCGJ+2O8S8rY6Gr2vk77V5rfttWeJ29XdQIa4gjzGs5xO3oqBV3rYhk7yYh1Vvht2h+0iSqvJu36wWifZUb5eMx7D00UylMFsTAc3N0ma4OXiS+0wFqd3KEUB4ig6w9VBHOc14MI9he0qDB99jMrqdHOwWuEjmiqcwdRhRCPxCkQk5ZcMPE9aO5uU1cf8NslcD6i4PuUiLKqlKJwVgpakkruJTvRfMWCDGiCWCrs/AGKrtx4maYqnuZ97VTd5WKCvYxB5hzk4217zmp6q2y3M1aDRiP3mYQQtWwoIqdh+N1Y9aqlBGIaQE1t/M6Xq8fEjUWii9KCu7ohKE1Mg1SlHVQOcBqMKwpyY1bKdIKwG1OuzvKzgsfXF4EDEUqHXAIhTF5tYT3kfabm8k+NiuJccjG036Zkf7iEtAjs3LhH6xRXbdukc4TMGZpE/qIkW7NoIBOwiDCF0VQJm3+WHqFid5UlWlR8SjITcuIP9FuAkvZfDAWNQANuOpLvqNNprBGBA/HEwcgHIA9qKwIu7nNY/wofYU6yUwX3noUDyS4EE2sshvcw6PUEd4pDW6zx71SLYn+nlbZ0kRVkFMt3q1ddZGtId+sO0gllsE2XTh28fkQNaIcbWaK/QKoS/3SdZLB5dNS0yAbCTDatdmm/maujt5A39M9kQXSlKNb78gW6V7/IMNZcfk7d1Ej3O1JLfeVt+kmnWELgVdhttDzNsgFyRsGnz5dZ7NbtIlZ3jIMUujgCkHR1D+RndO3JZ783vkgLFRD171eLYb8/ytjQb5kRhUdQ3pGwNH0EcTcpgzoMP+UdEiHhazhE0dXlXIGTa4M8BGsSCFhVJzDXm6EdTX/8LL7XL/FvkbeLdhtCpuRx2zvEpGM8Bxt1x7SdU3eUSE9IY5mqo3Ii3ZGwQ00MDpyttFevzLbsRSIY12iuA3REEEDsPQnkdC8f0cIkaaRFwU7ffhxRiSb0PTZXpZMZMUuHk0ulthml9dEdWwiRSiREsc8gjXpu0EGFu63us7HoomCNM++Utb0tmZAjrAjiVtnp9XaD7JNKIiTA3wtugBzmdcx33c2HxK3d9L/+t/oY0gBtXi2u/5hVrviWV76I38cYwDQbXea0cbDaY9fwXX1C/jq+b1SRGq/5KJrTHgOzySepa4LSsD9P8tnf4sl7SpHb5isGxHa64bWcu0eP0Xmxk8MSiHM7pt8MIshRD66sBcGAnqFxgJRDIm42arnjwEEGw3wvkbYWK5wuVitey2UB5lF0Mmy2ze6cm6na2Ft8R65bpqIznVXikl9Ht9Emkl4n/M8yTbWrq0De2bXmyw4wscMquiW4EmX0KtqI47Ya88VO5ZQYJH+2xQiGkQiG4XY8h/DL6FPmqjqtzfBpHDy7nuKiKcI9ejfBsh+LXdmlbNkh1Kw3hZlFSz6OKudVzI8oHlG5u6vS/UHmJOSUNguxz7oJLHi7XTD8cK8dy61UPyUTJQQBifBwIZR3cyWJpaZHhw/mxwmIsq3PFrWfJiKQ1xABQs2p+saAd/X2lz/68oJWGpDn/KLQCt0kzNdMEviGk7LekTDD0abZK7pNVHaXpk68esswF2MVdjjXhGfXEE9y/OdubNSyw41q03qQBIvrC5mPp8IS7h9wfaMJErtJjfheXRHeoYKf9Rnu6qNfBjvqBMHboqBLFDdq7j+GQ9h5OB/G25Zq++FFPv+iirL6JYqKCFHgfrbReumGafV8lqgUeppHT8n1yUx1FhfdlT4cnhLZCYqSSAn2p7jDFm+0kwFNgFOeg/GjimoMItZroxiQQEetGB6sV1wfvMZ2W7/KHLM0j/3vyFo/vzH3N0maBdwi9x/ipc4v/ciPgdMxA1KI5ftwkzRtK76InHqcZChooTlGEYPsPUXkRYa0JhZrVMTbLABXcG5JB5OC2QIhVHF07M0I2i9XktDwnmauLAA7RPaKjpzhFTad8ZRyL8QwVSe69+nqcdO+niD3Xyil1KT/OSJUA+TBCJrjF8jQhKy9KO4yNM2Bvv0Zxsia2qbMC/2ofSf77yxcXJNU63j8duh8s38lpeVx6k5N5PtCXcbCKQ+4ls3u8NDG6xufQ++RW5fGPf9RRa+vwEtnNcY1iPLiPElw3SRmsnu5hYE+dN6wkCzjyj/lDM+o2JYm382BeJTdP1CBwkhddHw8RPn35IT6M4h/0fVDyDrh3Gh9yGiQYTxta4hNIf+j11ijooR/PUrKu12EmqcEYPYbDSAmAVi2yBM2grOK1QDp4WD8d1lU1GBc81haB/p6Ud2lSVmGQtos/RZh5sVwf2W+cLz+xdk7RJbG3fWmEJPgO9CVdTdtAezA5otewE7VxscF4otRyIOY4e78Gcnnt4KHA4mr9HBwxdeas46xCRRmEv1qxNcKMJmaKVrDN2iY+fFwmqFmT3gIf76GorA4qLDqv6wod5evrJGuv9QIyIe4zlnk01RtxoU0Tf435O0pu76ZbiuNzTHD035PVhNg/TEubfqfxlSc9Ij9hEu7GIlxwRZg0iVvkJi4OMLlHxROpbGk96dRArIKJF606TbpE7e4T5Njd4/uEorIuEOmSQp0M8ipl3+bBmnWH9BWxPVryY4zajLR1tkoRvdHSWKDCmNeb9s5QQfI1hTJ8jJASaoTGySaaskYsT33ZbIn9Fgk6yXSlVy30ZT7YygfHGDmU4AyjALV1EWSlgcrD52oMKPjzjMplDkpjIEf/pGZW5QnKBLCrlg+k3lUyUJkTkhTelvjdyaQxeGl8rDqzmMJNjAeRDUCAc+w4e4mo7DsLqOg/BCYbAwjrOI6zhOJRDqGFUfSeg5B1nAdz7HN/CR7OrVDpc+fbX3oCvWnTl/bHFmX/4SqK8agryManqeU43ibcQSFQR3CiPGWKpeKUhXGUpt+iIsFKHXSOVHZfUU8xQQa1ZLNkUtVxqnjUVgO3GK3tEJ3HxerpSh4cA4pMyJZLuXAEZJ3IjLsbVnSWBxW7O4aQdpgDs+0ya8MButsXQ5whFAq8IEJ4+TR3qqJTMoxWqe1/7f2a5W1pvHwnetO8m5kQnhpfS3JWivFwA/ikdh0TMQY3anVN2Z7KrW9+lwgtbL2ryqO83uQZm9jN2TIlYJos40k3M9Q/kq4yXx4dMBHi2fqB6I7xl7nmKCYevNkKqrP8ACdsS0pg280JMDFMYZHQj0EwXhhbJFpULefL4yz7NuEawDggQPlQQGi/dDX9Y/LOEUYdiv1WLG9LsxVPFHBD4l8Ctawz4M8XK3GYV5hPZw3rSZNohteZ2mUUKqBnHzAT6JU6EtEXygGwtbRZ3lSRSKqL5E/k1/Yo9Ke8zPGOheKKx+3skdPi+TJ2HpssfRY9kJ5H2a3SZysMDwSOjQvvv7DFkUPbdgse7n5/m+/TbyKsaH1L0MOnSZ5vCH356HBk0V9AAucbYytsI6bhZHX9TVUHxCSp48rE5HQ8gF+Gkq4H1rr7J7RKoldt/b3irhBfDYkOkywqBpf19i+ThaTzM1zTJC7KLWySWHyUnyQpytSng58DBT9+Rg++cvu0vCyirExCREhNk+LS5eZWFCEwhPbmlpM3buJkhNM/58hevJimHpnqWDGfbuS0lTqvBsk1mmzRWK7gsD4Xur4qUiNZX6kxdsEgt2sMvv0qVq4bSq4A648n+3mdIqMHyQMlq9ygyW+t2peYAmXp74NSw6Ab3n/ABwjm+WZLi82Appfw02caZRqNngjz9A/9zNZwNxt6K1Dollt7heU8HT9iQYV1v2jGM/Lg/zDdDZL0dKy8b3Lbe0ZeuB7vm7CI9ruNvC3NRmAYV2B9RZ/mxQf0+C1K6/lbbxXTjznZc6aOqXDVgm3PWYPPtv9Ra8C1XznytgKdtkYe9r7IgiVuiEMGAMl9pRZOOam9qJk5w2SSYhZk3XQ974aSFbq8q9fXGfPqtyuyNnHo9tzgPMerF/lVSMcUDY8YRgZxta7YTUIRJSSvpo8YUtS1vTfhwpqmDYWSGFa0oVMBrUFMW0YDMO96ELOQbqpD6h0c6r0aIm9rIJp3/p6O6MEQUf3e+xG1EqsdjXuJw66hkS6NLFxGlmrXrYEc9lnJAbw1OUz7dSpvK5CGH8p37LQ8wVpPPeQ1XVAfk7/z13OoQdjwACyPG+5/SNaeCDjBPu8S6Kzrb8gdno089pcNLLa9fJhcPrDk/kvIiDG3Gobojyup4/RHf0jWobzCBPLDJ9mASf+DyJJAQmQvPbY5JGRWJ32GE/ZRDdsV1bCPQ9jBOIS9a/4zsg+rHOPGm7voFgeVC4oCCBTitAFnp/E/d8B49zqESvgEOYHAhG8kaJAU4401LED+vCYSqx12iGCsSVxe9QeC0HmkJIcEw+xTpgNT8YnDaNXoTEmgwmJJFyUqL8mpHqqz4FSh3ctNW4Hn/eZDGA/d7ZFg4RLkzbDU7VLpGUkNP7kIkkndrgTPQB+H6oIMdMExgfALLvb2Au+vIJpU3AtLpgWWHC+LfJatz0ntMrr1P5ZhJPul5by0QtlxdXt1sMyukn0UzP7qw5uK7Kn+PKtAvudleVtzPPM7y3u7C70lHPSdG9tXY9TYLB+J0bgpW74Jo/GH285HLA7KMrnN8Crq7krmeBxukZdQTkv67KH/3UsYc+Rw0vzPdRpAa9XIvHCPSFJb55e6+nJDkVK91/6WxvthDFVecs2bGaZVZYY14/oTOFlMl//dYbCuJlbTPOmqtjUp1E2rugxbm3jdWEscD8IjrJRFtNf95G1pzjETBXYaeVeEDOvc7hhSaG9us7nZ7SO+p7HwR7D92pO3FUhxatEEu9Agb7fhT8xD7JNpYadlm5HA24uE2ZeyqshTgm0rEyPZazQuD7sYbuLOCgvPcy7jAe2upnVsR6i+1DAYKo+YhkxZPdBDa1xJJa+WBsr6pk/5qJGEsPAF3Ez2u4jBLjJPnvKFrpImc8P11ZTajCih1SWKds/t8rYkdPP3V9uhRaTxgiU5ey7+qKMClee315MPqrHhrv6FV8IaTZhArnmtfYaG/OKYJzsSUtEQQq39mGQ/mAx1iySysdW2tkfPMtQ7zZQ1px1peHjQMQF4h2C/zyjWbUuj93XSt9r8to0jKPsHHr8W3sl9AFSTbSRdW1NlQScnddS8GDj5WAyynodp6PgRj6kMr69C29Szya9uLPr6N0k9NO8ex178ydsKpB3AyVltw7rcE/KGueC19O9wz6LBDFTzou8YFHrWl4UQlBYJWMjrWcvXh+U9VJkobU/xzQvyH1C6uanTDJWl/wleQBlMrLwgHixtV+gTNO1UtfvmS5MF3PTOdwV+j8p2gOHcGkYdVKn3AoGvuKqCQq+pIVPhddW8GBDv1eVRvnbM0U9qv2JQbAePtZ3Rxa6HMms0jaHH6a+lCKWdErVJub3DCDI6M7FXA+DA1lC5wMQgkDfLOuYp7vl1n5tYY4uJsts6uvW/sw20Bu0Dc4sQy9Gy2RMquuZuNVjihGUecG8ScDQSwjuJxMFmU+T3aNXiOwLcbG3DKPMqPNLAqTOCJpjY57RxG5l0j+1kqXSPpVviADXeYEeF4O46hrA9KzH7M+w14KQF8Lf+SlXBSQsgBq0ii9KDuroje1oTsHaOYsy3LqenLgPQKxXivcqgEEItBX1VhuM1k2x+UnsomeVmdDeJMhwnbJPt1duMLX8hvEy5b56mDuIYH1PnaRD/eZ+sUDHls2RayxgoOFWC5GqoOUhSowrCFmBWy+vEdZIX9fosL11MBLRu+apHsReh8rYu800ShzJLh3C3nf8wc3p2sFoV1AI6sX/ILmRlU8qXfkmBwkQsFSQHAGKrPVIUlG01XWQBoU4O5YpuMkD+8qztjJdAozj2Ek3eFqXS1kg0MlshPJUu6ut/oVglHf82TYDS52YhlH7d/5bgE5inASMqK9ITb5+vFk+oKe7wNXLXzhD1PDaERjxKd4RxMSxrORinPUHXwRYI7B39qepaA+C/BXhK/73gl7dFCfS+yOvNxHnsTPMmb42HPyjVKV97yuQwmwORoEHOIc9xi/hr5YQe1rBcml+xQJw0Z8pgac4C+EvzthNeIp3i2Mt1hZB59tL4Wa7xwPePapEB3yNZiQv+/kgqT5zERTu31pKiSaHT/LcXEvK2KIG0Sc4CXTiQtryjIYI6BHu/6J2kKSZWawj1NlbgJbhRoDMg7wVmizpYR8JgO4ueyG1yUGSfUHWXr6a8SZJwzFFdFCiLn45wzRkabRo7j6pBAVZ7j//deS1cRo/thhrC8PYt0udODChWLurrCovE9DSL00uCNpSrvaqx48d5GrskjeG5iYk701wjHDU6z0hbqTPPCNvGZh0ZbshiKds3NhKOeBdJiIoQpScITU1TectTE1je8tTUbvGr7j8DSrjJmbRb69O2QsXKZE1gxbvOVmhlmxDWupmHqFid5UlWld9RgTDb+fv0Ht2h+EdeDzHecx6lhcZnSaHaqR6hfNgPbm6SNAnwWmR/PthMTgPqVE0ONRj9UYGw/DrCvDXWnZxZCmMiGKafSNLlWdRwgTYTvkJa/kAr2ZRMOsKj+/u3szR0/LhJCmpI+JRnQ5rvmdr8LxRNT092fTXJXd+h6yTA+9Y9qoOY7psf8nQ1A3+IDc/EmEzDh1H2Y5YDMNfmLCKGbfP0aM7maLjKkCdjjiZPr6MZlIt2N6UKYB/COvW6r/GBoEj+pJKGpqiIYvJzUA1mb3qWJSNr/ByVTDLgCSX8hhjS5yW42OhMo72or3sdfd4hn9VFfBeVaE4D/lmUuIYOdhaQ5pg+/by0zZHzORY4m7q5LZndahzogevtvZ5kT8LniFy9MY/1Gl1bfIhIFqLWivM5r/oXzXyJRkJbNtXlXYL7F+HPNFrpQ5Stvtw7nKyk96jjKyDwPpWu0SsecLhThcoFNwwQyNbhT+mU2LQA+SOOSyRdc/ZC7AxPX8voFn1ISvL6AthBCPCqvSEeOiuHEu6qFaC2T3m+T27oKVE7CAjw6muJVt+T6k4YjB5aGJRBFdvB0VokEFTB3zROVOg/VyR0li936hmWGpI82n2xpGdDEdwzpty2Z+dohdAarVgJedyo90BHWSgtU2iBhcHoa1g/XYv7Lo9E7kpFso9LxAcvx8UuvdoYSWMBkpd2HIBE6PFQfmk5QUHo8IRNg6YEBebeG0beVn/H6mk+bqSarw3aS+nT7rbmO+xQQ7PJdh+FtaKGttUXWpkyhYJg1PGx5HJa6bC24OrlBmHbL3N5Wx29fFfoaAcNgax9EmCiW8uJBEpHTXNtd6ihUXS7j8K6VEPbChRO0ZpDaTcaEKzTeQkcfyGzFyzytjqjHCMVoByAZtIphBfexH4Qp2XXWfoObBTA1NUhPMrrzUwW83NMjA3JXz2LSbBvbZ7sORcoI+fYOUbWNDXPsD7hwxZNwTVxO+R5uY47qG1yaWPuRAYo+y3ZyOwkbOCmo4ElqXI8V7I64ogkoNJdWQbvtSUzPfQKb9knSDU5jGMieYeeBNHzzxZ9NYfXfLOkojnSp/YP7hua3B14jtHMMpKp3Zc720ajI05NsnFrU9NuER/aeX1nu7d1AuS4PS07ZMHU+I94gWTDu0WWRyAirZvH2sw5RBOoV2erFGE1K5reS6IR8Ec0CV8o/pYqSgdlmcfE5XnVKSvwrUdALUmm+em0Kn/7rsVFI3hdA1xEGiukiodKBtxn4kMlQqGia2chHirpLy499UqCYq9XytsKog028+Qtkew3YxrnmQ/OHlt5ptVIArOLfXC5ARf/TosNuFj3SmkN4NuvQQXfBYrjbwgeAsdhlEbZ8NaX+13QxKbbuQxoE0kGpSMb5O0CubWp4ASxoQQOf+MU2CHIZDQBr5s6Nx4HWdh51PQ+RHv5J28riA5yWUTxD0zxmbzIaQRw2NMd5Rnk6pv+DqXJPSqeHKvPrvsYO9fxi17ie2fr8id3BGUhrgYZIHZwBCB1AhxDeeWRYlGGEEp7o7t+RVI6BfGkc3lIL4y9XLsYQq4D2XqFV4vjeQWzY3aO/qiR0xsRrYFghGa/DhTrIES2smCLINRxKczl0zmKyjw7yYuGm+Y3g7T8i6jZO8gFgV8H1JFZZlPLP883nbdHFd3ctJEXU1+srNZJpo8b/ttPQV4bGcm2MEnrtih+z/VEzdBEcpaGIIBzJwjmedjMqXJ2hDchP38KHtN+Z1Ot/wA721lUtPqN5Q1hc5/nUJGd4hAelsEskGHcQpbJ34SXJCowDxG3iMkslmGUjl0QxQNzS04XYyl1xcKzZwwpGHDSkMMGjTEWGoL2EimQvt/OFlnV/a7QCnDNK4PRdznIpa+Qv819ExRQ7XdBeVsax2rTB4ptLbboscKf1pvpM5wQB+g/6qQI8GY5saER+JbhQ+E9LS+jx+NHxFDDFRVGdITZ4DYvnoJtxEd5VhV5GkLXCPfewWlJPb2QL8Eme51AEEI0NA62OsOwV4BIHMS1aR3BNm1c0ctcDbcSUKZTfHvBLm9LoNjEj2NNtFNQlfxg9S/MN6z1JLhy3tznzdDQaYkx4WWP4gBeqh4C1VxyzS+zeJXTWtg5Xi3EdUH4us3l5B3WLkG4l1rytniSPedEFvxYJYZLkIeuxMqsKdOsDmDcNKwYdJ2FW2D7laVg4qc4Rc3W7LkaCKIzVCS5d/4J6klD8Xm6Ll5UeNql7i8LnS0C5U48zZIqidJtlmRsF42k2NW4hlx0jQC18moMbWtBk+7/c4tlMfmZpTx3EsuU6fGnj/mtg0TGtW6JfxGDZS+N5W0xZNqmS5xwua23QzBxZAaXMgNzJcAPq1cBJsglFWzQ2wi2IegiAipX9jbMhbZAxhDihMDvRYq8rSYrNO7RQ16oElS/mcZQozEPTfRa73FGgC11LGM+9tsK91ug2vSe335E9yj1f+szL6pw0UCWrZ9g8Nly+Wz6ZK/e68xRn9B5w9yir8X0bhuYyVFRoGKOtmb0t9BwWkGczrLY138kzypyhohK1WtJf3PiIjclRqa8KJWWcMoKfembfQfDQeQ3b5G/ElDt5b+8LZZO3pmNQh2A6Az6m7U2SbylphrlShT4F1yXcihhlSpAvdbsWUGzvPQbr+uCHePZr1bFag3hLUt4KNRKHXI/eCuQ9fW/UKx0zP9lMi+o+TVW4hAVBXBaas3bh0/Ng10BEfYZK31xTiRDWTYG5ehYrFyN4QdBqgATJKkK1takxKZN0feehZb2fQDS9ZyB9LMxdU/lOkj/vm45/NwLf3lb7bHT2/0vzIWY4+2c3EiseGulZ48r4J0VoVA0rQoQfoFC8R1a1Sm6jMofDmxPqpWvWCR7ppe3pTFo/jKNQfMAs4vKvmParG5jzLPjxw3hSHXc6JswIYnNLYC0lb9vkXEYVH03X7LjovBXcj5jje+8drH+fIxoLGdROdY9zlaurdZxjPnEtV2WbNMx2Gn5IVnhhe07QfjPW7IqzhCW40JaUrO6enNwoEGf5w+ttO6HnWQR8Vg4yjPiGkCshZ8oOtrWeM3BHXAQqM37pGjlqsjFKVb4c6enPS6IBwRG96pHst/R5G01pPfV4hosy3iDd0cHol35juMU67NpQDWS7Zvksr7l0asxKHtdD0EAF/YgmJdq+bXwWYX5q77+fgE+5wV4kda387caLNorym5rvDXbzYD5G1WEMZLYJ4SVOGDl2Sse035RTb2o8IjeF3m9mZ+5ccvzNzp6qG8+27PDLYLx6ru8Q2v0LSoSgsrFOkLql69GaPbrTt4WJVQAzp0l6tBvNbwNc16bkvspbhfdjcY9N//tuX1yPrT1F1I6OE2l45VpKMsZOZ5rPMLCrOAPealMGRfI4vIxv83PkpisgO3JhfChWqeH+YpRgaZzhmucx7qEw59R9ZAXPyaf3bMiwYLpiS7ho9au5Z+viuI8fozv8KkAkZeqnFErkvJIG4ET9ZARXilrMRl7dMBi6h5tDfusQ+LM6EfGgUuGNIJSj2UM6peIqO+W9V76LilQTCKzXnVI9juqvC3t/do0BsRmYjTv8v4ySbJU88fw/u66nXzMCQJ/spqYYU/yYo0nnbYwbXu+WXp4oUUWWHm3aPqN5bzMbcdIZdtxtsIzG1zDsjvefIpo4jnPU06LZS+a5W09Azv1hEftwyj+cZrhFuIfnmEDR1EVpfntKwnGPYsGnWDwxj5Eztpg0dcLuPxLWA90/NfBCjq6tkLIJKeyxs7EXKcaUONxnIXIfPopyuqbiNoUiku0xgqF281RK0sgdHtBIm9rmf3pW4IecFfVIXiTtOyqs5ofoNsEpAF4mUe15+M9H8/Gx61wD8DGHKY9F++5eDYupooSedmgVYKcmXiMaM/D8rb6U8WbQKeTt8vcNJnv+EVellgHT/25jEe157Nt5TPzfbRJTvcJVXe5i3dpW798NUK0ZwzFhLKEatJC6ffVSbY3kh3nHWJMZP49cOLBf9SoRqtjzE/pQVVF8Z1j3tk2tKN8BSLc86S8LYZg3vH6uCd4EshV14ipiBGImxEB1FY0qpXCQF4lJwkQzaDZ0/F+yQf9mHh7eGnfE+Vw+5SsEWlzhpblIf2JJGgDXOhXDfhgUJRDCbZEBaitNZRBZdH3US3dEJivhiNha3jZRUf9DCKp9/JZIemKJC+8H6cj7DR/zDRpVetNEiSMOZ9/cJf5LEM7R5v0Kcj4jNqZZUxHR5M3cRjHk7ehz08USA8hTqTTu5CG9HS5wMLtski8nw/AaJxyMjabXxzntf9TJbgTK3J5F6VpPwsBkpv0OzOc4CSwjsCnrjfXKkwHxJJcN6KrMTA4kBGMSscZA3qpNuNuues2LJ69cqNZpqrjxS+TOHy2hlfdqcqwcX0AxPwjPMsLPkeGra9iiWcyDIGsz/ZlqUmVPlHLTXTNRZlai3mReO/QTYRXNd5V6VrAteyQWlzrrzdRcuvi5tbLqw7HXlbJ29JIi6kCc7U65kQNB9I5l3S39vJON3cTaxaRhztCvww5VPvV6LwaJ7KI4lNlo9o1SQPR9An/n9cZM1S+kJDHu8AxLF6nRekZ8Y3DVcRQ+61X7Z+1tY1l5Wf0UH5EZKl7Ouj3IhPGuJec8rZginkn+l/oKBdGnoS1fU0Y4vIJRSVm2uZRbK9I2BGm/XpRrBe1pjHRq10LPxp2Tkg3dUzsdI5vLXO/w8s3K912GGGl9Mj2i2W/WJ7TYjldb/Kiwq3eJE75uUb194tj2xbHSZ5izlmobcwRpM0wEcthMIVJoPQj2fh15DL6gfwwNNkEvmT+50y8SE4SlK7IXzMka+q44ijPbpLbuhh7C05lezh+xMKGdc+bMBlTWq+zPinCxK2doxLL09PsRuUjEqap9oUKjJy8UREuG9P4+Qzg6ni0xVyNwYfLYzmUcH2sAPV7COQpi92zYRDu7LI/vWJQ7TdVxbIOkhKj4Q91xtc302Q+MsrGMdH+TNPGPVYLae2U5pO8d8oPky7EU8tnI5paXZ+m3jNoY2QDFzZz3dkni90sV+ZeMY9EWL5DmzR/cozP4FHsJZq8rXZX8hVpyyzqQPp1Y5D956BkO2cC61kuxOnBwCkmUGJNvQ9MmIa0OWCDJIGtqs1lEWXlOqHvWoWYCgjnOOKIihwYzOEY29iYAgWja+98m0P75C0ZX2YHYgTansGzrgEHF+aSmKzH5B59YrJTeviGuXiYqVIv3ciCpPht96oHHk5NMhjhzCQF9HO5ffSzPo7q79UKeVtbbH2cKIcHEdvkV0uq6eW2YUR3kMTb+X2CCbtoEPlp2W6K3eL1dOoJZJr1M1KJPIR5Z44TIP6TbItzmEI77mkMsLPYRGe0Un65uSmRp/c7dQrzQ3EYVfHdRfKnp5X/DC/y5gmR7XGZIw9L0hejbfTHYCFq+FR4UMR3Idx+SK3a/owp1cUG5QgO6vLSx/gQLq3iFsz8PtKxpOZ3OZRElQxgfq/XrCqpyTFxWp6k0W3Zz6pvfJespWBaKF4dWEqnT2QvY5breFo/ofU1Kjqhk+bXL1/QZ5N+f/mTwAIjWKIeYfUVkbz7qK/0Rl2pOQC3sIAbaDMhqklSHMu9J2h0gSJvacEJukiI6DyjQWFm0/QJdyTZ4O6StP5kgKPKb0ym4KAs8zihJOz2N/ocQSOo8dZMl+dV9woj1//jbPWiWbbKWsMiH2wwUAW8L9ARYWLiJf37y/9LGL5pg71AZBrshsA18mY8JtzIl+wdInvYiwOqc5E4qjKOVqLYxBRdjb+0i4YEdWENqsTskWSVeEBMshjPW2ozFA6J4TmTdLJvji/B+wPKyBHRZg5N+sG+2Sn2p2+WI6aOdr+9ZpjVgIexfrXq+mbIwGAVKfey0NasCze1e3yrHMdcTKuct53gWKzLt7sQ8Ex0KeVadTWIc/kaNoyraQ1g3vGr2cqWbIiVp6l+RY+gQFLkZJ+2GP4I4U4tVbDrM6xOcA52Y0Hinh9k5QMqriifqJiCgZPxWQNiy20sYoDfIAbeDl4DOj4TtwFzYdIygV+M1y7weZo4czXZP6+w7oV1sLhCqyNikqG5cmRcoq8KceS4lg1XGrQHbQO0UKck2VAMn/oQtVXiA+pVh17aaQgapAsLaEUWsAVzSiy/apUjmGHtKufIpP22ymKLuLV1apmRg4PYsAWxYUAeqznr/fTqlWjYcWIhSR9mYB4JTXeJbcaiRzfN49USloXGuAFGUkrJ8OwE9mdGpgJpbdL+qOJiDNY/3DVcxsg4QASFWGt4ZsyctwDMAGOZMa3L2A+TlFz7dg1ouzmGD04FDr05KcTV5UCN9kXw4b04XX/5Cip6tLAuZBGaURgUtk+B0o1iBomlmy+j/ZB5GHEReXWY5rfkHkNv4BEgIb7sgGwYUkS8U8YeafdnYEHpnOyE0Yf0/ihf0yvznnFUXMIDyziwhbNlQgE9wIcyBt8OPpSNYCZWlM2PSfNdneVMkM0jP2cJfRn0qv1fbokEwUGD5AjSyigJtwHZJmHky3OleghzWCmV82RkrGyqbA1jtk+ImTIN/2LvFIzJvfIrtrH9jDkewgKMOZ4nI8YcHude5qzbPj6rlZU8IHiiaWGsjjI8XnPJGM5wIuvEHCcQCV13Qaq9S8omT/zBBk8KSfvYjkZ1/6KqBDFVB2/DVMo2oDOyGeN6kaZd6TaE6apMR5a+BXOxH4Am3Y9z9EedFKhx8dV2GqplQ5mg+5Vp/wC6AnD6SXQSa0akm0HGGZHIpB9d/aX1uCvMN8k9Kp5oZgCNhjUCVuhwDsrbGDXEa2w/J9s+Vb2ZTwsD6WzEVUy9pTnrsM5WKTqt0Pqgqorkuq5QEzV1NZToGM4Eh4IPpdUdGNSoK/K9hxnzth4+bEY431qwYQEjy05fa3sWSDsUw7O0rJ7RQvDifK69HTxg68ayBF/Ds2jOy0sfvMUB2TPyfCzswrwhVQt5VxbhvR009LTt9+aG/sirYQKhgoLbXOw/0mYsjulbIyilo5iPS6XzZdKFrs7WcKmhTOThJ+bRHd7KZWNYgEF3UIiSt2huImp0LbQWcwgYYkwWzoYrQfxLWM9VHZmBr1R03gUresvUo2GMeEIjlqQVFVLQlee0TQL8p2pra+SidkTzCUjtfJp0ha23VZxtuKNDdWbi5x3e3VXjWIiBd3CXb9u/2KA4uUmaNCS9Gc2UgdW1FawMV3Rgak0PdpC9zUY0H6ObzfEusDw8ki/0NbsrCUfK2M8BFxhZqEBjFWjo0B0ohMdoWS6/VDyGO8PC8eANk97BGLZ0I1EyuJtsV5N3sS1H2S3jtQYv/eVXnP/YF9+xTPjGff01eJZehd22fBndKrLDiLCBPTZYzHIVDBcHTP7S4PwWFUmUVf20HOXr6ySjgFb+LKZ4FIRToHCgqXGHlnaQse3ofHLBdk5NerZNbjWq8Rke6AxQbAXH7/D5zmJY27E0dvCkZzCqf9QRzRD7NUu8VgWLZyuWxqhDwPoYDXzJzQDq6HZwPDSnJj1j620b77vuAA5iPwBDPycBvz1S/RmJ8m4oekudKYJlGFxpjhPGuGssv4T1zXa+LRbB1hjb+MHR1N5XMm61ZE0lMotVQvEEWCrq/siXjW7Vbu3qMRrwcivJiD8sVhWPYunFZXU0MNX/XaxoW6XJL6yuPwud/OoiIm85vMsfsjSPVlreGoMH5i4OOWQ27Po5OW/BfZmRu2BKG90GjGouxmGXCSrwqEmOVDa3uowJYHCIw3pIGx6ToLfMBE/5bOk9Wj2UGVhUPVUmHWDrbQGD6gwRAuQEbLmL5gVp72dlwt01IZyj+wQ9mNrBxtCKvbcBdNiBuRZ2iRWVI5hv24bnaOdY8gNKNzd1mpGcr2OmMuIgaXUt0zI1nflX3rqcoeEls2VsrR3Y3HyunWcLxm8qLsb+n9FDSWNMtclcBUiIqTsgGyYWEe9UMldp92fgSumcmLS9eDJX0vsu/2fPOCou4YFlHOiQzBVED/ChjMG3gw9lI5iJFWXzY9J8V2f5NPxmD7vB4MET0S/0gNvxY4WKLEoP6uqOkLdxseQelZPSxqg2RCpVRRvymXVgp7LXWw1phvVuNcc2tpHFBMBJXtRrmoNay+AiKMTNPZQN6wKorfg0iDVY3okZOEtO3N1ho8t8k8SGfDSGlTISBbPmJA75QqwE92IuXoIJvDvMdEX/fV/k9UbNSQyglI2sOYhFCrAP07et2zNl/Z+L8YD5MGl6qLUNQqzhGgMh0wx5CvHVYJYx35byHdD1eQXeaD6M+W4L1C+GX/RaEjPg8CoYg1zGfSBfbwkLSsYwqw4nzo9J87TCYqz4pViZP0kHAUOsSOFs2BBEbP4SXSD9TdWLGRhJRV2T5sc1F+Yo7XlgDBaQi3bT5gH3fTau28kTQ5ez/msZ3aIPCe5N8dQnwpcynrKW6tUDtoLL2xBwg4pnDLaPS42GMgPTGs2hST8Wf/YAHEkj+az4qVnGc3Fv0xrAuqDM3lK+HQ1iKaYdzZtJJ2iFZTd3et+l5lEOTrq9216g83h3hwUlPZ9rhxfnYpeYTec+J0BOwHC76DIn7f2sbLeDrnLvk5vqKCpWV2e4y3dRiVbfk+pu4CAZu2jqQWzZVbHhSl0zMrEIcX+4wArDXs3Ae4bTYMSJIIbFGXOkRPQspOMXsJaKKV21RnWDAHvKVsHyMtRoKDPytHIOTfrR1dkuHv7KLjE7Rh5VnY2bx63ujiJqPpilmBqcT5POjCouq7Z+zitkckYa4KQqKwGxVlkZvLvDmpKez6WsinOx/Wekc/SAl89ZjhGU3frR2t5VlSA2BOBtGFLZ3E5Z6U1GMgO3mszfTljwoYGYKQLamsZiDzz/2DTktmDEZm2Sit8lG+p+ribSGAxMBd5CWKX9HmPdne0F7vgM6xWeh+3fXLp+N2fmjlV0fDGCVjGdrS0ObgDKci3h6u1hQXAIM3IiOEcm7fcIlnUvIN3YGHuscNABnQ14zOY+K+HMbsqezKU7y2hsylObLfBcOUdVXWTn6I8amURGwOCwOsBA2mnOYBM7pjOrxjCLtqyap53Qk4dOG8o9WYXgQXshBaCVZpLTZo+iomLerFbezSjqwFrKGNxOU5E3Jb8/lD8pHW6zMOjZLGqIdipMejHUWlBF5kai3TakNSZnwt3cP7TDWIJfd3IXEUahc6uQVZicU3fR10I3iCXYdAc9L87yNP2WV3gUbYA1+XCQlQ+q53rldcB0RBy4VRoiRVMQtw6d3zqGNRjKDDxrMHdGbNvXWk5Jv0Pxj7zmcxYLn+VKuyECUIkH61qp9KatQ9qDMMat43bb4c3A+rbzbaRk8JUXtKbEdYFJcXsWPVEr42mWELy6ex1FLdi2Mq5gZ15RNWZ+sRHkZGbUmVnMJQYzYNQPpt7WcGF3hSewjSmPyBCY8KbT3blh8wC36pbG8kLZdnQLsL9uvk26xNddbDWQab3Hc/8xv71ifhOekS4ARR2I5xkQGz5XtQLZFLnObx1nG4xnBmY2mDuTXnBVt4J9tXY2CHgiht1Nw5pqBDPz5k6a04y4UMd9llznym1b8arBQoy2swxG84lc1NdlXCTNg+FmWdbAKtKUMSy0deoYuKmFUq8pOzMDo+mJvxNsh8d7H1XoEyqJU/7VSZGvtXynqAMnhGfB7dLAyxuan+0MejOHCVVPfJNesPW2hfkuc1vWG2pMynhMM4uzndiX+ZlOJLtJH4Zay50pbm6SFH9BVzqfGgESPE10QFZnCQHz7LmvpF2Y4yQgI6zR2XRhp8GDOOUyQZNBKU6lEDh8Lk3trycl6C2TqS9/VFCPY5bTqWqebPQ4Um85l48qL8jzWck6Kp6OH+O7KLtF53ipHdUFbiJ+kvt+6GqCTiCkkpXnh7YVkHXbvk8jCo37NAMbGs+CSV8UaLaDQekfdpw5qhKeJcfoF+ZFsDNzMyFIcAvuG9VfjO0Oo/jHaYb7Ev+wM7DoKkIsKKljw5TaZnfK8mw6mhl423Q+d8JyIxuMzs9TU29mnt5F50/DsSzI0DvoCvqPGtVodbyOkvSgqqL4jt67nySK45W8CsTEILQNCyuas33afGkO1g9lBubVT5+RITNZ8MTFDOGqGUiszkcsq6BhWEc2HTcBMOmoz1snZ3UjmZdHwfky6QJbbxs4lVlsLIvZiT2WMPMJW7ZVgJ0VK2aruFk9osVELzCnJn1hqi3G3qfrTV5UuG83dPuI79CqTtFlVP6Q8rW8CsTQI2gbRlY0AyWzYHs+jZVB36EZGFBPfJNOtPWS7JbUXIz5jh+tmU9eBX4+1JH5FM0sw3z6Ds3AfHri7xzz4YbSvHFV7thEzRNiBTnjDbD2vAe0A+mhKgZffuvWDWU2npXPmuER/2a+09IxrlM94ToVroGKbtWso6L6cv0vFFekCD1iRoipKS3KsryiWH79WqKjtCD8Uf7+sipqkZkJ6gtUsa9qlS9fNN8ZvmofMRN4lKsePR5FFbrNiwSBWPryJy0u/IuEN0Jo2iItio95HKXJn2jVziDcKR5K37WPUXZbR7cwtrbMrHPooipoDGdJmU/aPQ5Oi/wMFeukLJPuvWUIMQ+jRTp+9lxEOL4b1/UwT1OwV/i7UeUmbFWGooseNhySajhaJK0vBUiT3v1E1xFyppWsmqbMYMW0SWU+oeouB6d8DKFHiKUIwqviHotWsGcjAGNiU3GVVSqatyBalIdpfkseEYRwdWV6bmoMtiArdZZzDYrukRoIx/AulY4+KtFpLDfPkriqCxBHW2RKEBWmEYQxeUg2qaRAa8nsA2B61ChN7lHxdJmswZ6y5aYDHxLkKMbOph2yRduHGZ8kaSURYpo6po0qmWoMY8BbDfynKKtvIjr1qr6zYFrUOpxWyNoOXGxQnNwkMdWFevopegxX0MtJsNoXeoMEik0FvGNj5s2YEu8yAnWvodQU0beoSKJsCK0/ytfXSRbJiKOvpW34H3VEv3zNElDOsOWuo7DoumkTJritkfKdockSLDrfJsTQSeH2jg6UwP39ne70kKACH+NgVaMv1KL5jB5KmezuyrRIjh+xjM2i9KCu7siZrVlEcmVaBa9trH8LGcLMPFBtgkZ6gmPfotYjimQYzHpB36aX9oKWahHRDAgQjjadhKHOwbyRAW+C8FOPGuzACxwwdvgRFUPsKoRm9JMtBOZdKxM05I0HKZrmpQ0NGjG3O0wvMAe8wcFHplMOSa0NkcgoNk6/rR0tkzwVHuYot622b+MMZXD/+ExyuuMGn/MGPHaIKYps0Up3HFnyJS1toVQTMm6SpBKxbMMEt95OMIRug6YCNqLeHBU17qnRNbketPYosFcmvREiOKWSf+z0ptVg2FgpWGcZx6XpqNZFzID0GkKQdKuRuecAV+Lo9kc7k6lcq2CCWTRovhYKNH2hfuNBGcJKllIk8DB6ve4OH+WpXnkNGw5HAAZWqRw2SrQ+61orFHWillhZer90k058iqjElfalLddbsWDnQ9CoJXMgtTheX6L1JpWsAgjO1HD1pELMwxgfQxU4ORADqU7AVgp73xhCP/AiL0tcMVWg5GH0A2+2E7kVdwRgcBgGPFTgUzHoaWSOXoNUf+Zi3LvAM9bIx07Lk+tNlNyCi6gr06+aZmNRLpgxiNHp9COq8GlRt9RhSIM+RyVevd9RcnsHknEEYIruXYK5oZT0lIfRIh05rUAYOSchnTB+ymKVLB6KDc7949th+KzP3+UbIVUMl7u159Ax18Lq68Or4fKRqaO4Rxwq8LfYbO4cRb3e1aEfsvRiU7goN22i83JgmxguYHk/hTGxTAnJXv/qqQhDa8YHVpLSj7+11lEPxj4x6fgb5Cvuhlgkn6aGfJDqihAZgStwBRE1+AFCcmP1J2aepkrWGwMohsLCgZRprt5V1BihmJqLhszVzeU/PHQWRN1zBlI2/N55QEMEFhVABpCSDiQY38tfYRmIZWGMFVHGLUCkikEt+ej0lSHaCS4GCvoZtAAtq2HE/mRl3ROuejcKgJIgoGJoEDxIL86DQkUuEOfEFOoyhyhow4PIR8BBQvRgXGUUpOARzUQEzptFTooxoH4c46n1JssYHUAcNdc5UKgP62U6KpIHgJIPRgSGCMP4ZykIA+ACqCIlsg9BDpM0ZR4WU1GFAzUYzrhGAPpwCGciUuvMNWQJUFBJgNWPiq+iotPgemZALgGxQgsMQa/OYU2pBYpA8oEIsBBpGBc6BU1EVBNrhaTBo3xNPbQHTz6YHgKcehw8uDfDgEgB+khJ7aI1jzzwrnpPPEB5hiEVii9YAVSleUdBlToNY4W0agm6AFTqTLB6KkG5H5Tj4ZI+BKISl9RBxBpEULe2eRUXCTAK+cmBguuLcUtUiWIe1bTs0rmCXB1sNmmCVpc520+RKEp4+ahU1SBiMd7FClopsUJbl3QKglBu4E8TuvXQNuPrKoWkWY9z2iXXNwy7RitIBlYwGCFULwDhQLQA7eBxhhLwV2M3b6l4H8NpxfAIXCHajWT6GBlEorEreyjaSF3I2dd/5SQzqq4dvAkWBYFVrvN6yhs1Ll/vo3CA8LPSdkKvm0irWBBgXNOI4pYk5lqYVoyKrVuR0YWAU5JuVqL1utUQ/iGlmQirHZZQRUExQ21Qinlq7YZvWM9kAqj5qPQM5kiuebiL9SBSHShAOPlgIHCIRFwckYI8IMZZjqOjlseBTVKGktfRMoC0qoLFzMmobQQgqRJ7ILrq1ygIbjdQ/Vr1I+SsGwIcL2ZASE1F7ZDV9RXElcbC6cmsaXNagqsCAK9kYXvAtaUDGsUFpD028MpTHQypugF16AB0+2XGFMHXiTqq03bxKLG5creawjMuM2VHjOdUwlQeZoTOo5jGvsqtCCyY/tzPQHvbEFhcchnV9D8UVRShuKZ2F2MUWgqYYlJQWh2QrJ8E4y7MZ+NRdUm/f5vU9qKKfiefck5mVaJUHRnHnTtNxwiFF1VYTAtNzKgLwOxwcfxTTZHDCnFfFg5rwYnOy3J917pSfTWu6zx4paI6BbWViikPMyH9aSTtlaTQYi7UeKzJpERnMUddSLH9RKl7IJ80LZd4KJ2G24Kl7DcV8GZ650Ki+uoiWm9SNKQQkVOHg9QPaVzBm0IcOkj3G5KkeNOnT4cyfj8PoI8EUj4guAJEHzZhi4JCEoQzxFQMLSu2eBHIZCiKbdyaLLNu1efoPkEPBjoPB6hdAWN4b6c6GOuMJPqA0s1NnWbEU3ZUoKWZvKbhcKUIwlJV3oxibcqacSB3l41J6fAqAslHJ8BC9GLyQykIJaKa2OGVNNg5kQ5pqmB6CHDqcfDg3lwEIgXoIyW1j8u9NshQAqnwMgQrBPC3nz2IUJWGTOl/b1ZRPmSj+hBFNYnWFFQ2a3Jin/0+O5uSugCUfFwiMEQ3NmecgkgAsjkoQvPM6UnCgWmGMYaWEqXLgKejCoduDrJcsenvJDRhYTQjYEAl1Ij0ZGCRADQYpfMLyRxtNkElZzQwRvPYjCUMTzS4YGIIFPUSGmxSRpXYYOBM1jozlhCig0En4xCYwA60oRkFDcLqQDj5YCBwiDZd0kkFXUBUE4fSNW2q5CkHoeu+Sooa02Au2Qll6Lwa0qrLneXhCvJhKeupnOX5NKAGnvNwGwrP+cko2eZTNSRjA205voZfJiRg0wBAPZiZXZcgPehI6cWDaNbPACldhfpzKo9pHhIorEcikEnnFVYja0LMYyvq8vBeneEe30UlWn1Pqjsmr65IGl0V+eA0NSGyMTmEFVTTIZaxU6hTPZQe+WpIcSynIVxBP1Cwnop+FpJJ3QZASukchaLkV3ZCjck5rmU53lHlKQk7bmhyuUeyXGtEPwOikVMDpFTitSm3dRKPwTQlCYC03Ep1VAkvH5KqGkQpSVZxBdGULUysx0Jta1eovpLdYB2XjSVe17lyTJ5FLdFSEnIQ8nGNAWUJsMDYVRWeKRcmm5b+akh2LyfCGFA/hhG8iiR6FQ1GCbn7yqjsfHTukpFpLQo8oO4MzMF7nad5XBMbFUZvEGikOQipEhBQBVgqjF9KUEodEOnEQnvovJ6FpLAGd0UGjGR5+zQzO/EPUGjCy1XgKhEirQXLJuHhDKV8kiOfKzxc6INqXcqBLQapWp2e9JtrjQoNK+wTUliLcSmsFZ4Um8nPJU/Tb3lF0y3T61L2yVTAuUUBrnA1kdfyd2NR4JbkaZWkfHXZEcB3a66Ap3KAHcK0rkK+G6IAiSx7nEe1n5i2By1y4I2hAHrL+OGdq9MsIa9qK45QqgoqhUNRD1ZmhGeDlPqMCv20p3nwWaQr8UkjPTGldc0HLkNhQmLTU79hiwDVtZPqknJ2eJzpSnioSaS5Clw+aEUtMAnt6HkqBTFVeCEdU3irKij5VPoRCGc2MJVW5ESpuXQhHU0MaaGjgXbsc45ZeFtM73oEQ2u8IcBKUhcL7oEXnasFjHxiyo1fULs6KfK1inQqcIW2Jq8Fh11w774p/ZnlqOcl3WVuQTgG2HhsQ53ARGMQT0yy/o29K4URRQRSCFgeFhTXzLN/KmEt4JrYYNK/7qeN4ZFAqjYeqAK8l6UmzrYShDO4NF+Ql/TwuThZR8XT8WN8F2W36BxP0/BWH3DI11ZSnMl1deGHHnJdal89XpCaw2uFYUlJ/zCm4RjacJCjSiGoNkY4Nbkkby0qdQttHflIdVXB5O3SlyMVJNU2NHWee0n7CqOdror9YBUmvKBEncegB77deHWSwNuJAlo+QHkliIayVycVFFQ0MHEwLdPy1fiRSSXtxrBGAxtV0dDNmFpjpACtuIc1J+K20VOfpkzHVrJlDZYEU7Ig2w4Y+i6fMgfijp6+vBq/JS1SVQEtH6a8EkRH/rFOBf0UiKFb/vE72gGC32wop4CWD1BeCY5tM6acAvFslBteNL06654ildENgNUNTqwip9noAVYt2QDMkBRUzoaCar+9brCQm6koyVDRl/32mszEOmo//PYag8RoU9VR+ilfobTsCrrXaYea7ZcXF5soJpcs/+Pi5YvHdZqVv7+8q6rNr69flxR1+WqdxEVe5jfVqzhfv45W+eu3P/3076/fvHm9bnC8jkdH5N+43vYtYeUZH/e5UvKU2gqdJEVZvYuq6Doq8XwcrdYC2AVWwKsv1/9CcUWv6B65Le+3ntRdg20OhiYySJxDAk0Mwh04+d2eU0hTVNd/Rfr0Cgx8Gmh4godFJp+OEDEzLqmHa17EURoVZ+2DqZ3kXeGR52m9zoa/ed6T1754Kiu0Jr/HWNjv5thOszitVwhrlAmuHW24ngmlFpjLs6gsH/BRHhdUiDzIyCGHAMzxd5XHSIev5pgukyrliNl+MsdxmK+exiiaL+YYPqEq+g/09NCYSFhM4xI7jO8Q86I3j3RUaIcXoBnz2RzXx2SNWWt1mXdHdBajUGiO9xxlK1QclN+TFZXPLFq+zBxrU+OfecYNnf1ui+17EW1aXwQI6ajYFvfFXf4AzJRQaIv3MCc3xPyC5sss1nKR5AWWptxa7r9aruXL6BZYzvSriOm315x453eQ18IWwm3o/IZktl1Fj8o3nCx2rR6TYDAz2btUtafZwcS9y3bXepeUmzR6ah0xWEzjkq2ZbfyBOBH5TXSLxGGSpTW3dYKp688YRfvJQlEiROAH0n/cGtb4mOPOJ3+iVdt9X3HA43MRCgY4puGcpg88juGrhWLRpkzicbHfLbARgiCshLU5NUYYuTIHrBKE9riAdTMq2B6u7zNaefG6JFWXCYtLq26rTOx6fFSnzVs3EFv3heZ4v2bJHzW6QDk5oI+xckXmOE/S6PZ0jftDzOri0IFiC9W+Sjl9nnxY/shxVl+nSXnHa8XM52es4DRS5qIqqKt0Se1tAfYxDqPrVqZFM82aD7sHdb0Xl9O4xB4jsGtwRTZmH+IbdZbWtwlncRiX2GC8zOtYWFfM561ZBWeoWCdlmQyJ5XxWAI/Ngfv1KLZ1twtr5hyeHmRxDV+3hoN0qSTNuUflwWXAOerq28o1JwVCXTgip3KMSiwMStHj8SNabzjjHPPZCle7fTcO+BzCUZk5Vur8zWHrvtlI2WZlNa6BY0HLlky9gpeS3HmaekprjMFFQoPVdkEfCSXj26sNiEn6omW0cGIh/5J9wGLwjCaNHnWQK7NYr2maP7ynYeiX+be84peuWDzHuUFuRcNsjhkcfa1i3pTGltjYeFYgPvb7fKe5BeVNFynqK3XgCFlD2SOrPI0EIi3yGLpvc0qez/X6GhVfbr41yY9GqMZFz/jMLkRC+zIiGyntyI5qFNMxZbMMINYcSsJPnIbG+IiLf3+5+W8y3b6duf/uod93d8sz0bprlsfCfrdQWjd9fM+oS8NnGwX4YLMp8nvRzjB8txhngfBetvqSCdvcuMTCTLtZSTCOS2bnUp45D9P8tn20wYEvlbUn4smmuUviajaeKrbAwhcID4Hks+Z7xX5ffJaU78WYCGt1/YkkddOoIKaHz/N6fTWjFxmH/W6BLaoEs0X3zRxL+9jOfyF8fKgi7qpEKLTG+zmXo+3Ltou7meeHfBldiWpSnm/al3D+UGjhyRWV7Wg4Ly7m++LzyDwA5DB1ytrT7iWicBmXLLc7dW8l8eNkv2/dESWMKdxDTZ5bP35fJxINuSmx0BtL8noRf2AevloYbpqQq5HNpvm0hNd2V+ckL9aRqBIIpfaYL6K0grE2JRYmv9U6yTpJNLb2jUqsLkXhi4lRgUUPu6QEPCFHBXNfSrxDKRLiBvqP9pcbfbwldL/RFy51SfkxwmcD+ETLFS15DiVd+ZjfJhloxBVL7TB3mYykyAWArdmrhpQdPnuVJBeJwVYlrTnNTkVDuUTWZz7bzbyIavg67x6FxfEmynhfge6jDR4sTgrBm5X5bHUNUyH87T7JYsCpmSu06KMQdXFkGXHRst0bfl/rvlpjegtiemuD6Z/JhhhaolR0aeSKLLSCuzxDzeUApxSwBRbrJ3qEsDGf55DyS+n1dA34urq3K8lFrZfVnEZWirLNVq7RW+GyfQwOuDAeimxxwu4yfJmFXe0h/4iqChWnJeBRLJZaYL4rEFLhBsqtrgRRkcQgZr7MQm7XNJL5Mv8WcWrnuGTX3Iyf2XV8x+ifUHWXe7ptjnG5RGtpEGyrjJLGGjvGGYfnztPbjCT2uiNJG/g7w3HR9nAmq9t5MiaLyoUv1fWnMqiGU6IDbMPX1wW6b5J3cBvmqGTXxPlCzN3diPrxdYfF8bYZrjoNN4f1wNyOxBVUVWxNmCWgRQ5FFjhbl4u27pFotYMhLGRBXukbkQLZ+HzfckRpvjwfD8x94pKQUYQ7d5Q/67JLBrivd7+en/k2noxEchffFdncNxV4ZDR6nEbcgy4uEhjzVr4lZXKdotNsldwnqzpKU07ugwCzhgPc0RxrknUvllrY2uo0lSIWCpe81+uYCK2xvibexgHFS4czdHXkyicMsd9gNLxFdacuCo86+IDaFQ9hr2Q1PmYX9RrWsJhiJ/VKgh6GsO89dTmD6QNCOI1B3ogUyMFGeBBzV1fjkuWVk4sfNddB8sFifURZfRPFJENFgXe0CroAkcGYt/K+4kPPmy82/gLD0/BjV4Hhu0V/2jqQ0sCX2fie/lEnBfpS3aGi18E4L1QIwrqFQd2A8Y/KLdZvjeUWXvgxUTQOVisOG7+WtdA2s9sl9eZnd/huYXRp6/Azy3638NbK0mZ5MnnHR35bQLnN+nvswpsk+GEIe2ocP26SghrD3kVPJUwZHsa+FeoMQjFAa0sOZaHdROVFhJUtBLMMUGwhTUc1hUt9odSq18TB7+C2QEjUTcVSO2/CvqLokwoU26zL/gk8fmEyBTbyq6109BSn6CPKbqs7XoJBEK4tnKEiyYV5lME4tEIVDIpGkMQQhJV/3F2yOc4ifP4ThOKoyAanPBkDX2blI5KQlRylXe3mykbwGZFALedheVoelwJt6ScbY2KfrJNnM67ISicjhufsHq9YXLm5dOSxS4FszJh5/OMfdURNN7wdc1RkfeFB6x/cR0kaXSepgF4O5dYSPAgYwmIekkyBXSy1OA3kD83YW9dJ4fIBKLc6JSU3T9TecZIXXf8OET6bCiclOaDFhUUU/6AphknmeyHEji+0PG/Ln+QRDt7mr/fI26SmEDy1ybpew/MOQ9i2ED3qWuAhLOmGVm3VhN+agGKrVUf2ncP66bCuKsGtQCi1xvw9Ke/SpKwU6HkQC8o0siZFmN3P8DlMtIzBEBaXBfg0RKsmMR9tNCqxsT8KqKxxfElXAJrhq7U19Ijc0EJ20KbAYg/aoDiJUqB34xI3jP113GWyBi7rlJBuLbYXdtr2eDgLDmvNi8dZhYoSYjQIwGrXI4JnhAVB7KMEtDoBG7anArQ6iV0mqFmHnGTkiqz2c1QOT90f5evrJKPnW2AcWmCrsWCZ2LzudbDZpAl/VgABzPF/R8ntHf8IQvvNgjrAOc/+ZPc9WfFI2k8W9ALG88F6PP0eoRYvCjCHtlSCRQq0pGdfUDeqQFFn25QDRDHW5B4VT2QSBfsZV2aOtdMjv2aJcJPNl9lo/iVqtzPAUCGWOmD+hKKyLhDpmwT7CMKhhYO16GYkFDrgJT+UuFkAC/x1tkoRvbwUrX1CoS3eM1SQkHPYSCQBcWyD0EDdRA/hPIq8MTVh+ascCQu2NW48/cbt5cfTYXFw5JFXncaTh/zr6e/e9hi8e+DKrCzsmFNiTCLBSYMrsu+pDDFUbo8dEj982fNxe2xvIMujvN7kmRgzBJVbub2DWN2wdbNAr/6p8i/j1zGEpcdBc8eGtQXI44At3BqpF+gJLo/3t3bv8S3iLydiGb7aKJ9hA4YO8wof1qVYgWIbM2cSCaZN+snGXNfMtcy7Dyrfu8mpcTWOsbJbMbHUAnNCnZAElMNnm17eoovkT96y2n91dAssL/MLfC6NKxi/Dta+/1+g6xCh0NLcfB5lQjjeqGBxF9eJDV+74wC4jWaW8Mak3TDc3ER1Wn1L0MMnQUsUCrdG4Wqlp2e0SIPEJVpEVnMaZatt7jDJIv7tGa7I5sZkjcTnQ4avFmIH5eT9tUzQ5UYFNh4unxF3599+svK6KaKsTASntVHBHAtv2XCqMOtkhMs9uGruVRMwy29IJW7nwqs7naQ1RTRRuCHMewxCD0ufEss0rNU1LTN/uGFjBnJep0iWFdAA3MoNALa4jQoszs5N/iBJugWx1EYnb10wYdRAsY0RrqzwSYFqs+yLZaITrhzOpbUzaUgEBOHUQvREGKRLSCNrhYNyaambAPBspwBzmyXwVW4QwMKw+1gVERFQnF13+Lw9Ipm5U/WUxQwmFyGsrL6ttkdcNy8+oEfgxVKuyHpbbx6KBff1rugZqwqtuGpuKsk1ZRDNc0DnrnyqcGy//tle/cIYhUJ7DwvYt8JFiYF1le25ZfQXHhPmF0jSChW9kx23h4qlFqbHZIUu7+r1dSbkteWKzHG2iR7G2PqPCxkDn7URb1uEes+DDUsGlvEc9hAiX4tymh1g6AAssuXvZEivbbqBCPn12QIHfFTdkSLtS20scGcFam6KxFjAUdG28Xmgy/wxNpc7fS2G7ddbZBeqblepp+UJFrn1EK/P85VQ/PxVbTbPRRCGZRG6M60ay/YzLtt/IIp1VLZnYCcGDse5niy7BK/6H0NCez2xVJB5Fslg9t5Falx7H6Dd8wHa+77sj83jcjcFjQbQ3iQxdVxmrDcBVDUYtbvSZopv+9U3eCSNVBFuDjWwlgK1MTEIMUJ8ma0zYudDIfFHHBU/Y6VRNVmezxooMLu8cmCFbpoVBffBjP8XiIJ6Dpw4BQ8G474dOtzsGje08pcm5Q+ws2M87ts4WHnpmV52Xr5FRRJlYP6KIPOlwO8+j1ZIJ9pBPDP1+ufQnSfXb4h8Rc8tY8iu5ho4KMvkNkOr3rrCx9sB5VZH7p3JdHFaQu9nD1+XOZsNutJ/rjlfBK7IQk5NkBWSnti+1NWXG4qCahxQVjoRZGt2P5Z1/PY5FpPDjqauvrRuMpeJfbtcBOVSo4374sVG/3lr+FuiKE2i0oXT43bRoNZWkhoRoHJz7CTtD/4k5Ehmv9swcJfWn+fg4bvDdnWUZ1WRp1CaFhnM8z/whl1zARbb7KssREaH5baq7RLc1M9sEunduPMHE+ESdJPKcb4PEhEMge0aN9MgjIs/6qhA5fntNRChwRZaHvMPVv+qy0p8z0IotDi008O1DLFYOo8b5XwqI10O0MY4KrC5e89+SN8wFAqnDHdYKr6rfzTHRxb2r8nYyz151WlkXNfe+zrhjSqjEgt/gbJ/9eZrwdkZ+DL7fgooPfFBuR34Mhv5mVWoyRrIy06mwCowBcjv4JLc4fgRt18KWgrz2UY27hNFiOWWkobJHe4jano0DrJGUXf7D8ZgILJD8LEq7Nw91DxcfJ/NDclCrIyXfukfc9phcWBkedVp+PhjlN3W5EElXkNivlsYYURnV2tHVxr9CAREWm02dcrtrc2XZVwstz1/bOOY27wQDT7s2xdZ4Gxfom3rHolXdDCExRFxeO5W3ogUaHkn3vDurc/bMX6hHYGolUUWpQd1dYcbbf2hzlFMaemzS6gwO+wcduim2U06LUOmfVgmU1kLkebtJ7vDAqHK6YrQ5CbhzRVQuT321uihawQAM2/rC5nYy/wH4pYh+90S20GMdfZShnNUaqUZ3ycrVMhyT0HlW7PaT/KiXp/lpedVd4/GYR0r6k6zaC/zTRLzKPqPSy1+8Y0P60d8zw5WK7wpc3sh83kfi2K7NChbBFgbFI/r4pBUnmZ10BZ5FP3HxVYHIQFkLR8VWBxRmuzO3Omk+2ihlHfSc6yF918t7gMSfBLmbgKaTzaH27IiDYuH2+G7PTbZTELl9thpFjQQb1Oyl1l2MivyF1aucmpWEfW+yOsNKKf6kl1wJlAIlnbv4SVL93kJIUWWJqg+jQr2AsuQZ/YhxBOpbVQEBFDbKB5XcSipPI1M3D4J9ry5eyrr6UKrpnXD91kwzXTarxVJvWmWCW0MiocaFVjiEx0wmM/LXYuGOXm1+cdbG4KQlE8otbn4atJaS1ADxZbzclFFVS3g5Yrs+wujFUstTIhNLnEYsVBojbfJUS61T8qA7DnuqC4KlMVPwOOJIIRNC029cyyUecxsiX2fL6PHdj+CzAtyKBtPPzACk/lsy9f1dZVXUXqaxSnuGMTePIRjC8ePuhZ6CPsWLkn9/vkH1VhgSM8WlWODIW1bbEWCYmw8hGMLirHwEI4t4Lri2oMhHOUTlvMJ0TGj9AQhkGQG4CHaBolpAB6ibZDMBuAWltSmCqeZDl8t+QPmOhdOg7Ohc0W2vSPLGOpf890c2zm6qbMVWkGx13yZDdaHqFid5UlWld9RgfDc8g44EhCLfe4OxT/yeghukJ7z1JAeLYpByxIQ+91b5s4FlVs489zcJGkCJHMcFTho5BuJRr6xdl8i5wIsdPHBF4uDI8wikIqhhrToP64FOEb3X+0wiSro8NUSEzBmtxF+isofaKWmpgzGrs9H9/dvxR43X+0wHT9ukoI6vXzKMz4BBgjgiv+/UARQmS934+B3SYHi6h26Tnh/ORmQjQmqr3YQ0x3kQ54C5igZlE9LEAfJoZxaOoyyH+JRCwRwxi8uVhDADf/pkRw1KXPC2r5mJsXclzthP72OePMoX2i/L1CdpPXXhHeIMYTFSquxzlgkf9JlSqMzohhK6KiC829NZFI1pH+L56gUsjHoYG2k44akwVHQE4bwaQEakRzKyhOi1/IUA1KA2TiZF/FdVCKpFRYEsDlXJbCf+KjA3uLXnLxge19XZo+VHLjwkt7UVbNHK610xpVsLnzCZELehWsZ9hh1jtZRkgnZZiUg5m18iEgkXXtY/5xXfTLJcTsKMAu5F8doU13eJbjHEf5M/YQ/RNnqy71wCFCDbs0VVHfI/1ri89qHpKz835MBULo8KmOGZpoLq7DP5lK5Cl6IWG5Pzkt+IeZ6n9zQM1tA5gJQujCXGZppmKtrm8fCfreQ2iVafU+qO5DJhEI7vMCro8znvwDjhuFVD/6cLdC4VQEZbgEeg5ND2XM/dO/Hl1nszICF2N4yfFp2PaCJJyMgkwkAYD92fBjeQOczqNxG24qTDUkDIOqxXJEDTiBKii+z0MVRRk4aorrNfLfFBnRwVGBhlURlKTxe0H+04aaB7FThhNK8CQDPWKr2EiOAy5Nj4Lui7oSuT6RBibvSUDS/PnkWMLtEuPwNJPMqTdgA3kaLpQ6YwbtmsdSGkrL+uvZV3k/XPoKX3A5X2d0ZqNlewUFLQFzbAMkgAbFQGbRXs75XslPkj+5SLQFZCLgim42qqypVewAAG+/cGGVDIisxTZpQbNF3LD+/A1nk2e8Wbpf0tWOyy3AOl8x3a/l6RKJsIQnbFGzX9owVEc8Teo/GdXuG6065PftvpbTP40CA3FqsUkfHvLt8Ar332eJnrCmyVuEwdiMRo4vZyAjLNJwa1r+8GQInldpvtlgOcUkmpNwaF9nZnaDTPPt9/rPczq0gcnPVpFPyWTUdFpeHvaRVt1uSXxZR/AMPDLoo5csssBLXR0hPGRVY3mYi+NqVL7O6m6TPwoBohcK/wOrxN1awmDxW0Zwmi75N4O69++5gAIHXprX9eGcyI5+jqi4y8pAD8k30M0LlpLYo60/ERoEC60IzUFhFKpzd6xxFZZ6d5EUzW7ytnSu0wUunHdGzO2/iEAqd8cq9R5SA9vMGp5AUS204Nbq5aQ6+HLMO3y2sPqt1koFuheMSG0ozyxcOV5SA7KY70WKbfk7/PMLCN8TGP8bmtPnrUGy35D6LilZ1EaNr2RJb+xyEcVxio+wMNIaupKHyxQ7tW/62YTcqzMKoIA9fCIm4YYgldtu9vAPCwbwEnoDOQeIZ4JhG5JF/OfttZOeFsM0vw1GLQHSdolZUwLjlUDb9vowejx+RQIZRgdWl5RFeO7d58STkIBwXOYi+LX0nT07bUI/k7mi6HEE6BHiFDcYZQnTN+vKa0LqgcUAAc8rH/YtmflJhMStWXBckGLQNnQh1GwdhdbuRM8Q0zbrjmxfP5WL5PnDAmuvCclsANptNrj/FKfqIslsh0pwtsMR3hookF9xwuCLLuy1am09vwhZYmeMCP2QTTnMKFb13miVVEqXgAufLnvE6pxOACz7mt35LnEHksLqVtadZ2EyT4PlDLF7K6gRHyNtbsXeZOQnmYAxKs7d4MSmMYSJjMI1ybp914sTxuGjO08RxRgwVXH/6j1vDQt5yzU2ezSjHcFMf0T1KBade5ruVNb6oQJ+tcYk5RvIiH4hwVGCxcW/g5142Ls+9hL0dwCMRHvvtP9ocaW5QUaBCwDUq2E1Lu5RLCnJzmsXcxQLz2Wa/pC8Xf4hKXmVnC7ZGRNGk6WyekQAJ3Fl0rnnc1Tgm2uuYNsV4LbF0qaUe6rWgYG9y7Zpqd1bQKJZW/vtx/BiXA7vrEGz3Jf9Jka9l3M2X2XCmDOe4xGptB3l7KsCLceU5ioB7vcjypq01Oxw+NSm2hJAtvtgJdx+hLEXPQDxjidHnIPU8BnZoXA6A8roT2SjgJwKcngYIZTiDDHlyM95SnmTxHVrVKbqMyh+eXmQMJhcPMmX1abjG/3B/gLlceCGcfrIRMHl2/LghjCqmXObKLIS/kI7XNhWvreFCsX1vvmTHRcEL/lGBxazhTey8FsUx+93CKhBRL9SiEvCNS+wwHmcrEF/33bJ/NX15GO4hU2bZR3FGmM82W/CHZLXiX0wevlp5Dd4SVj9DRQxcvHOF9nhB64pQaGF/yB++oUJctez3rZH0B3Ea4pH2Ho2TXVhadxoB37TN4xi+2mISNwz2u/0Z+zxPpZnpuzKbhXi6SoWbw+bb1rDh1yIIG/ZoHNhQUfevxYYXac3lOG2+LOLoJ3moQf1Aw1JJ91CGiiQO5I7MY3NJwqdFse2c/R/oqXkPc4Rp+GqFSUBiUx9IJWmdRtLWZLUQH1/eoTX6FhUJUer9mHiEyoGDNfWnYV/aKHdKaj7NeYj8CzFc66jtZWcgP10MDHC9bbUsCHealneZF2UKHuDZ7xbYiFOheGfLfDbH9UF8avyD9Tvj+W1+lsTkcQPgOp8tWjIM4kO1Tg/zlbA/st+tb2u75BafUfWQFz/Ai1sBxsr5HS+4J7pauqczxTA+GMa6lePH+A7rd4g+WqBuTAa6NaKt7ZSvv3k3Npe4DmnVbRVyqvdg3d6BFfNGOiSNJIv9Y44LhZeNRkW25/2TvFhHVZXwD0uIpfPFR0kXaH2dJuUdv3swn5cUrLvgqPMuJ0+EHGcrPKPcvHBFWyPCKJu0nwIoaS2Yq64mrb7tR9nARprd0OwPo/jHaYb7Ff8I56IlQerAUcaYpmGuYHkqAuZVCOx7v2tuEp+irL6J6DmhuERrvA/52vEgjA6saoZmW1W6bwl5u4qPARu+zqE2LHUCaGP8w3ATj83lQKBFseeireOiVpaHYSIOmZPLpwbDnoW2joXOEZmqVTt1vhHJLC6neGQ1gmn4p9eI3kg0pTdO2N5KsFm9zb1z/HRU5GV5gdI0CEfx2Fw2Ni2K58tVS21LTTaB5j1Zz02JReWyJanrTzT1bKOyR3ilQHZRgNJnXoTCrWGPf9SoRiv6gNlBVUXxnX8uDBClA7sY4pmGbZjGeURckZ0xIbpFxAwtsohQaMPivLZkqymdJKI/W/fNYn8UHrWxfc7GX3v8lKyR6EI9fN3GhRdsufktsvk24yQvEj7D5vDVLjBMDAezxSAy3fDVJpyMDyKzqy32ovtmcYmGNukT35H+ozUesUujAgsb4xFnWTyyqX0Yc4ZJ+mH+sDxy8891hH5Z8vrrAq/pS5orjfPv6D/b4QK6xny2uEqhu3YMvnDOl1n1cEWsrFGaPgmdZEq2RsizQ/WT8iwmBzGvrj7RxZz4XK71Q7ntwVqURKMCOx8m0YXJavfKCz7AhX6xuLIuUZEJAxq+2ih+ZSnmzBm+2saMXZT8fA2frcb3Dt1EdVphqbbC3JhEaSkMFgLZmnV7FK03UXLreQvaYXG6AZBV3Vaz7XPeZXc0mXEbGRfoknSMzOV+VIdhW1kb65vN/tlEpfMMABTv4rIJFxI0jfI3lcecu1IpwzhYq95AhhWg2AX3WzXutz64f1bj/lmOeyFR9xk9lB9RVaEinBMTjNNB8Jkimkj+ga2LuadUcPMq/XYJOGY8bO+GR98nFJV1gZq89L6bPoPKactX1t/WDX+KhKTnxKwveLgnVna5nbuAbuf/XYKbLL0FMo/NnSEVKPY8+cx58nS9yQvyXtJN4hs7O0LlwI2a+tvKiid5iqdUxMN+t7t7hPKTs99tffghfOMSW9NYgHRKPxIuSL75YnFJFP3gr4joF9tomS8Zf9Jhv5tjwxLnJEHpivzFnce4IntuOMqzm+S2LoCbcgmIxYw+VkUkXlYzn22COwmCLuSFi+kcFdlcv5V1Wp1mN8JN3vDdgu+aBG24DyRFm6C+CqVbI6gvnrI4TFDTgMglpklVe6Kbk2AhTRd5XcRIyKzBfF4qPIpGPD9WIrpRge1IxZTY7HfbGJtT4U2K4bMtrouqkASQdiW2GA/zPIXwNd9tNMssBs/Jo4KtEQvHj0Rpeoc2aR7g4SYem8vdqhbFNFKi1RuBmLIba9Ofv1IYSk1qjDP/5HUl5rPF2aSfE0ghFEt3+w7XN/PIh6raXBZRVq4TmrUUopkMxq8VfRu2KmRzJBYdI/kyq4ua5nwjXNV0n20vSODbJPerJFoTvE8alyx9kUN4O7lHn4TcEaMCq7UoOC5037Zs1wpidRihct6v9lYHSl4sBiostEUpMSq0NdOJvXQLKsB/3id4dNK4BaDc5sa3FYUtK3AXv1zhAhYT75OrYvbxxAr6OPPZao6IXBXMFOx3+xlvjBuikQIqX8qy8OXmpkTcVtN9s7zXB27zrbwfoiq+u0j+5HiY+WwxA3g50ZRzY7r3X5fePo/y9Ya+aaDSIqRADor4QRHfCfexYqkF5hRFGZ8xtP84/5Z9UJZ5nFAzohg+h4pWDW1SD1+xaZ4xPRXRcpqaQnQcB8+CA/rCSmRbRXNXjRkE0BaM9noBOcSyhMx9r7w7fBkVtwg6jht1mMVl2dnfXoP8YM4yF3cJ/bOJJrzCsrUqEvKK+xFZ7I1XvsJYaVBbMFGO6jSAQjY5YAr0bXnyDddAAK4x6LIn5zTUm5tpOoa9YmS1KtmgCC1mFWxgulcj9bQVsXpOf48wwMQDnfOb6BbPYhN9mKREee0f9TSYbb6KbMpt5nqM05OmHLKQ086h9mNM645hdXuVkPl8cVp+rtP095c3UVry6qZu9N7M8y4pqcZ4dbDZpAnxd21TBWk2FXU9no066C4NkQE7qRrwnKsedQBuUnbTc99oiTW3PBGHdNYl+7BiiKGWjB3O4JwdRmTusG81L/Sd9OOEFs3cjNA12/5/Gd2qjycQuCSJ1ABjchAREfuePpwIaty5ILONMS2mSRgfSWU1ZHqE5REURr+NOuRzOWqeo4eoWJ3lSVaVHxLcD7z/fC3R6ntS3bV+l6oMYdrKYk4woYoBX2gb8pyBMa4AfKLv8DaqnzoyhBM43SHc5uwi1AlxeOGQevIRjy2kxOFxbyMD6cevZ6HO1kq8uKIkQwUP0htz2y/932X3oc2ERK6B03KoR+571hElSLmJYmqqWaGTpCgrwmnXUYkakJcvukuS7tqttaz9kR6lzSvSHcCnKEtuUFld5j9Q9vvLtz+9efvyBX0IlkT/pTcvXzyu06z8NabTGGVZXtGh//7yrqo2v75+XdIWy1frJC7yMr+pXsX5+nW0yl9jXD+/fvPmNVqtX/PVW7RGWH769w5LWa5GOXqYS+POQTjfJPGYp377DyQwQ8ck5+jmhYyffnvNV/wN4EnS9u8vE0JSupzpc2Y0a2Rzc0qgEO3lyxeE7cgVYM96r5Xo2SvNppnsPiriu6j4b+vo8b+z+KpCfHNJ6G0Wp/UKnWYXCUYbbTqk1+SiybJrp2XnnoMLKhTT96Xd0Q2+PgHGeZlUaRiKNfHDARB9QlXUBqeUwRCOsukFwhmOdkI4sjt3nKMMC6qD8nuyIlubB6YGwz/zLMwYG3Tfi2jTPl4k6Zs5rou7/GE0B+6jPMyJEuS5LvuMZIyYs8RBh0POzfYUZ2811VI/eoQtdTsr+yGp//LFp+jxI8puq7vfX/7tp5+skY7D0Uyn1HgWsDZUCSECz3cG3jrMQPs4omYlmGkHXdR18Hmkj0Qlf6JelX4mMzpEjzCN8Irpr6d4s3n8/eX/Qyv9+uL0P68EelwR35K8zlb/9oKupV9fvHnx/1p3h30eOHiH/ubSIfo42PBuLc/7YXr2M+mZpyDrezpVJ98G66Tzijdfri0fPZNVqpW7b1zmoqXRUZ2Sx2k0ct0a/dcs+aNGFyhv3vxTIbfVC0/S6PZ0jbveeeQq0f/yky3+8yr10RADqvnMA33uSCZWcZo13YSrnaMSsGDt7Lrz2I76mr38/Mlh++noOYni1SEPqICdliRJ8lla3yaZx8HvtLzM61jO9h7HIt6/8JlwqpFFzNPCNma4X36xRt2fR/0QG8+1/KLyWc2z97ycFAh1Vn6fjeYyejx+ROuNlz0KI2k3rCbCD9yvTIRIlxvMx6LbLAbKPx543JaUj5DL0/SZMPziW3BoydonZApgbA2iHhL76pfsQ04C2269+PwgTfOH9zUqK7x/f8srL2RuWitkE4oKchWIaL6CBg9Ja1MlZF7t6H2crQJhcj4jWMmAg6x84K/Od1YSkAHZS4Gm1pZIgM/1+hoVX27I2ih9mHriI13v6dVe4TwPBmJfQrZjoqGmFyOdbkauMWZHObPz0sFmU+T3fhvBOAGqXL6ZmX9o7jAnZNZs+sz4s0mu2zRUUytaQlHeJGSx285Dn3xEaSKzZrk2fW9YpDKfh1B4T/JiHVU2F0RyXBdRWoXu58FqnWRH+XrN3Jv7+beUQc5kBzc3SZpgLvcjnf+J7B2i4csjFHL57SCh2xNf9ybORIc+ZZf9WOhjVFZm+8wbe+RBdwjS04/5bZKFUqkxPsphWPYaoDTqIxC7Zac5CAhkCoTJCCFPTLvuiBjM+2O8LYOhpTu7K1NXUqMjgCmTBkMG7sFOmMhlb5SFcexrQ6UdtOyuotcawegRdeTNYgejEVfdpydHjJOYF0Hb9fQmJLK3QZD9M9mc5WUVpdAdr5tF6y7PUHNMDoLvJHoMiM1j/zM/1EB5GnZWek7iuUDtjGW7tXobLMsgVx8PefNKymk5gcPD5V2BkDn+n23x4yWCiiTmcDtZW5t0gpf5t8hLe17Q8WEJY+04f8lfZe07+esG9vAPxSOntxmm3tEdCTWbhENYxeSZMMh0muKCe9P1dYHukwhiUN/j9y54gx2m+S3RCZ8Jiy5+/WwWfWRmzDAJ3TLXm1o7pZd/Y3dt0eI6Yo1uLlL4c16FRjlkzPDcabbzIloVhqfarLcuIs+3swG9duc4Jp5BaXd2Vsy2oyEt+N7OFLgLNFiDxjIQrH4YvyVlgqGxgE/uk1X76qs7b0yiFF/c0acmwq60EwwdGmfwC66OcdrXo/2mOpwnVIcj1OliL6VHmkfn14sVbfQQRAEhDxxmtxf1OpD2EQRfh+wSa5kpN1jP/oVC2dt7DuJtiZm5+FEHXyJRVt9EMQmTKvAOU8EmZfN7XYcuvK8S4Dw5YYOn5fvkpjqKCq/TZofDf2c/R3/USYG+VHeoOBvlMnTNoEDxDUqC9rbcXljVZHKqJCZKw8FqxTXp1f3T8l3+kKV55GcMaHH4Tc3XLG2Wb4fOa2SfuiubLzcCPidfyRbJ8eMmaV6FfBc9yTAamQxbhNTZgCL05+4PUXkRkeT8IWZ1jMnhEpSr73MLigdGXNAObguEWK3PZVwjRJfoMZSP1DmK66LwvAHqkRw9xSlqxIafvGPxnaEiyT2XaY+Rbv4UrdfCOqU3Z/0LLj6yLFQEEhayNJ9ZlHbYGot8fwpHcbKOUpLqC/8qac6uN3/H51kSwoq3SYeuB3GwOy2PSy8SMtle/JgEqzrEaJnd4yWGkTUXUp7HqiqPf/yjjlrTgIckb05TFN/BfZTguknK4PQwh4N9dNq9kizYeD/mD81YW682v2nA2n9y80RP4Cd50fXvEOEDlQ/awyj+QdMqkcx8nv6h5HBH8J02NMSHi/5Y6qVS0O0Lz0yyrtchJqbBFz2GwkcHjlYtqgSFV0UxmxPgw/rpsK6qXBacbbpkCPT3pLxLk7LyR9iu5RRhvsSCeWQtcbK/Yn2bokpiLyvOCEHwreNLupq2gfaYcURu46Zq42KD8USp80CMLk6YNvpLlMtkHeL6g8XdXqoEwtwZqY6zChWlNy+20muEFU3MQK2Em7VNfF64TFCzdr1kPt48UVkdVFWRXNcVOsrX10lGTzyTMivuf/eEQNk+IeAziu8oub2bbvmOjynB0X9PVhNi/zAtbfpdKbTM6RGHFTih7hrCeJlMHKizLbGK4MiTe1Q8kVm1N72Ma/sYXjq99GuW8DegBv0Y1/bpx2FUonZ79TYU9Lg+oaisC0R6p1SV7XN/9U0crFnPkdDbRN8M+TFuysEieVhnqxQ1WWUBs5jvVUCD/gwVpxVahzDTjBASMoTEd3GXN5YfLLUncMTsdvdn4nmh9UdwySzW0cjfLv61JNMa48F53sj3z1UJ2IIrDl1T7hJkhxObtHdc5VFeb/KMDSdwOskLWEL50naTRC+e6S7ox6gDHrJ1W9jVzQOD9tm8tUiJz5ItYiNuCewfc5hXWAML7l1FnigJvVA6tgvjYrV3ZBo5Koa5szlLqGuKQ3KkrqKPgk/83C6SP5HPCEZuXeVlfoGPj3HFY3Z7RqXB8WV8ixAq8w7V4c+j7FZjs3fgkIDeiGFtUlvo87UTNotQBppttn3cRHVafUvQwye3iHBz//dGdD0TZagdzWGSRUN6XEy1a/rBbefCE0jwaMSS/epHOUm7num0rJ9dXA4/owefFXxaXhZRVia8n5HBVtivgysGiddbFuqV5d0lfTCZbSzJM1tSYPCHiVLUVfSK25tOJ9uGKM9OnWhP++vnkySyG5mD6b6v6TtzPGnP6xQFfvjwYoMmN1C1CTrgKG/bXadz5wuB7ByVFdbbqe7J5oiXYjUzfHVIz6Qe5U5TxSCOngg7ND55oZF3FJYclDyxt0q+F4WPH6siIm+2TqlXshdwz0SkaQ1iv9hrgUd5mhcf0CP4dogv8nbzah6WCXy/N/HG2K795kKK3EY9Ex5aWJtqL/hcO8FV9+pKIEfmeMrbZfll1xwhy4bGrQkilpMUs3nv8uVlUfqQrNDlXb2+zpgciy6I2vjx5a1bz8z65C6SewZp+OWZSOhhfK7ysanrJRx70noGAnRoqHrhh+u0xEK1uU+wWUC27PXMbmcX3u8l92ImqcH6mn5PNZQnWMzVQ7Du/KLXlgPZIPU9F4bgQpai9n0Y1/7LcOMzZMNJXFWCeyowdN/7aoT11dj7V2h7t3v+FXv3g7/WAZCG+t0kMSVkf8J4JlvUwpoSTNtGJjjkH1Fi8zugEkHYnPs9Yw4a96zu6tzfO31KnUxF0GeyAOAhhuK9rXlBchFGeSYssjOz0AqVy+j2GVPe8gBlS7xvUZFEGRj//UyIOkGeQzjJ4Dy5Ez2PaFPG9LtHyZtgdw6SN0pH4Bwj/2yDZQ/KMrnN8ArsDucTZNvZx4Bz9kv/pz8XPuEMt8r/uQ7z5l6wNGH0TPOlrr7cUJR0kFM8QcPywzPZQ6dwjVKYNcP4Lm2RXxS01NsgkOmuWnnV7pnwoq+A6/7v6XL1KaIenYMEvGJn2Su4gW/Ludehjtgk7wX+xOTh9E4K2yRr9tmzmNNHVhV5SjD53clIl+kMfPI2eMTJc1vCkwRgBzRbeF24+YrpxlX4eUy0ZIjOUhCklawwpBxfjmPNnpJO8+LijzoqUHl+ex26n83RdfUvPE1savLwafDoIXyGhhx93SbZZoKwtH7LEftOG/LfaT8m2Y9AT0jZH/LMw1W65wKeh1zthvO+TvqG6iz5o0YJRXmTIN2rJ7YX0GX/0MHXwsvRG0Dj9zJtiy9kUDjRQ1GTlyuUxz0YCe6E7PgR960M5Q20DyZfJpi8z977TETS0p4VyrBJM4cdeYiriUP1qHYIa7TPWEImnzZmaSxIymcUy/ec3jSmcWRhUJHccWEwBXEH3M70jn+ll5+340Vg4Eb9r+mXbSyuiR5ZZFF6UFd3REQ23kznKMYEeyYivNuU3bdzTxF+vGbCYr21fjJbp8wBLyDa1qwQGPsXwluX+Q8UZoVQdAdxjMoyHFL85z0+NRdemWOMF91JXtTrM/LUzPNYYZf5Jontl1dbzS/4cenFbfS0otkZ+uxgtcJ7Z/istLsWhEDXB+WOZ7JA6IDsWbSttuMLhMyjv4m5TaSqVoscPFA+N5LY63HPb74vg3+Myor0wtMK32KRTLkjNprcyfMktJsS6DkJn/dFXm8cJVBbN3hUh//TGYEPX5/bXcdrLYcQKGTtQUrVXqxM4oe3m+KJLstnIqN2Qjw8N3YLaMUz5tyGeM+DaelY4BAdN8sGwae+zV/uQmyx80ObGbc9Gzvk6+Lq+90wtolfnXsjIPC5JKQ1L3BbNd8Tx2GFwNUm3g2IqsnhGzipc8edR3VRoCx+gt7NckTcIDzHQsbw8vXvzqvyMnpsdyX/E/a3SBJv6C7M8KG9wmshPc3iFHd1Ml++UWPHj/M0dkka6xOZzzTCUaPzjLSVDfOMsG1s1pHhhiwWq31jIzGGBX9C9oYoPUFoaprKW56awPKWp6Z2iz9MUnbKJ5Mzok+KY/NWqOiYrAmsqddY21lN/E7COXqIitVZjje48jsqEGYtP9eRozsU/8jrwb069KlRaCBYUGynBEh8lGydTG5ukjTxToTXa/2bIGOkPjTkGEPesygQliNHeP7HeorTtGMspHaYiSBdCqaYCuPzTHVY/kArGem8e3p0f/82GLLjx01S0JPTpzwb8jwExPtfKAozdpYv3yVYuFXv0PXwwLpbHEmP5iCm+8GHPF0FmisReUBGYJAfRtmPYEcpDm+wJcbiPT0KjbJ9dic02tPrKNCG1EpoqhS0/n9h1kSNlb0i+bN52Zn4zUfxOKndJOiDsZusgXNUMjH6ntJoQ/KohCeOiDhgr/G5s9eJwnf9rCZ1SxTanHoWJaE8ebuTYnOcCUPTFiU5q+BFuKkbk+Ektq6ps6Vu74UGe1w4R+soyZhMlg75kD5EJFKqPdx+zqs+/5+Xe3cco011eZfgnkb4M/UX/RBlqy/3Nkqu9YOEX0t8aPiQYD54Nk8xLP4iIa1q33pbzc/Ty30VGrPO++SGniKeIet0Q7OfvKGm1/x9LdHqe1LdObIQV927K6PX6oLbceZk1mfCoJ2exMyy0wNGMjw+N5Edqf1voSa2H56WXVdplr3IM/dDhwyf+TYBTyLneLAbEnQcTAXsMYYLRrlAGVHYQ/WwQReue59QWTKZxr0zXHUzQpU+T5vzDMKvX9jPRPr14wnqILOguna2bKx8iABzktySBplPfY/ZNzT5teUco5llJFNfs3bnpWa/nppk49ampt1kd4Hh7wC7xDfeAdynZYcqiGL0EbN4NqQL8nrLnEj979Pm5qZvejo+ly4V7UckwjMMj9pt+1hHeS4ZZRbcoCkVdexgKr2oi17eXb1su/LImk2fl5VncXfmhqheXrEUw2GURtmQ3sdJfSsnd9cJeQgLe8vA2j+uPK1tVzbGE/P3he6SDb2bfR4rb0FJfllE8Y8kuw14uUid6aZVSugNIQp1hdk9lBEI3Ry7ULcAnpEVox+SU+BIUzPA/ZRoN57cemChe1R1kZGM8ujZJEgJEIcVaOIW139C2J3OUVTm2UleNLwSRklvOQ7R86/Bed8Fqalvg1UAE5dgzvOdnejmhhx8wqA7WK2TTOJixic0s05rMxIUIcLNtsWNxWKDzKkP0VFUPKtN0l9ankVFqzh4WZ4au5DbNStb1+dq9X+3d229ceNK+q8M5nGxONnM4gCLRc4CtuNMDCQTj+1ksPsiKN10W4ha6pXUSXyA/e9LUTdeileRuiUvM3GzWKwqfuK1WEV38vjr1dnH/5mvPcK9wsBfJyrqIPj+gu6GmT1XN8wJL2g2Ms5BV+eWmV6BjekSczuRrWxN337EPnjelA/x9+vviNLUhQ1mcoW785AXzyMdV6bLX2VtqfEZJcNHBhG+8i3lQBKUc5h/RRZhI3K5jD4/swT5/ZgtTk1256J+jNb6pm/r1obXzv7rETl4d/Ne5D0eq/VG4HD1vEsRm2jcqQdqNreoSHK5f4LZGqK+DCDcRl3smWYxmGzSh94N2YqRJVUSp46XP2ztxb+OIBbHP73LDxv50iiNgPWsQQ8KDNYdzNj0uewSIVn/uhFYNu8j27weykXsS4dFrHZl7BKM8jqriS3GUuMu3s5ogzV5h76idGwwu7yoTB1PjDjW2Y68Pic4mUT6/80h0n/A8dHsyfUB+UrpiJGKigIVvvjNcuxplBeyqK+wst24Q90mWeXbuBTCpnCbek/eSSQEMR0YYCNjEK2S16cms69doCQU1hud0THJp1j03BbEgb0bsjcCzPG3om+K/OgOQ7b2yJwg7mLQdcfF8g2UUcRjRqDyDsUjL0naPfzlcxPdxhOz/jnk0j3Z+6h2GxkFnANP+4l/7eFEyPZYytwbZveE9ucUPcTll430tnYn+neHnegF/h7UO4+/u+QYy7Pr76caYoDzHL8lt2ZPzi4EpiazRY2P/2Pa/w9Pu3eD1v/AU8HdOYva6qOymmNsnT5k10UxbhBvRXJNjS7TyGGzWGeCIUferrL0DMi/rrPR0mAW/mV56WiZM0lx6WF/SQSiYePtq7wp3yb7PRoV/Ar/eahHjVtU7Kj1hIOzXsdJclIzWtu7/NsnVNDDW5F/+9r9Ihn6m9Bu9cjh6zw33VRy3kbBcduahkcQf4Zuq3GXpy4uG0ztUQuvG7xyS0Osmj4WPwE1IaDu0/PBO1MvDl4OQcTNI3HV/ZvstubA6QNrWIUmkZtvUGDG3nkyMeHGnW1YH2cZg+3hCR3Rp7hIalYbQRrRyaY7jdJdWg5wJjxBhPDXWtZcw4GFcN4ISIJMWMD12/hJsEytr8MBLrVzG3TdOBbDb+n0rd62D+/yQ36b7Ooo3ctwC39bHdPLfE/NX+PuV5sbyO6d+R+o+pYXX3z3zW2RHOPimXw6XaY1l7dEEJeR75oIy+vvWMvsgEhY77HywcwsxDR3vm+5/ygjocO1jipRIMvb3r05ZKrAdrh4l9cMLIxi7m/yJi+OuMuoyOqe2Ds/OgE+z/PnNCmf5vDh93rxBUzQPuPlv87rGPnX2R535tgp0G4N9j4mr1w3MgAt/RQi2Hr6Mt59uckw/92XLToHeXiZPu/759m9kKa49H8fZ+fHmCy1iwd0xGP2Zo6cggwHn5I6p4mYcG0048CP3rqHuz/7eLt93A57P7t4u118h1Ji1aanN9LD/XT90stq4bcxXEIPw0VelvcoTX/2oJ8eNB8c6SzbWzE7rZM8EZ4yfJjxqx5FgHfL+GTGffbnGZ3RnuQ5uaiqePe0oYfZlG72Gwym8jjPbqxWfED1kRfds26BxDTzqctVwJvExZukqTXGx40OcI91+ZxkcfHsdNiv/SJdnki+x1tA2NHTirHLl7iR7++2SPJiZHiw+pmHd5/jmqnBoby9X3DuXdSHPISgd+iUPttJa8E2hMRXQmLssRwvdzvfLE3e8LiM0PW9qJ9bUZ9H/fd4WHgokpFRRjATL+9WmyXMzjHfKlt73HSPsn195Benad9pHj0kaUk3Mk+AWfbYafbv9teY7TZTvzZgeZu6ZHiX9zYvZP7mZjdvJe4FS2UNF51lqY1s4cK48bq5L1PzzwRU+zV6jM9phQc3gkBcK8B3dxUfT3Fy2MrdEfRZOLpOwpOeGzOjmW76W+3w8SbbZyI/2sGyy8YML9yaSbB5QyoGhx8bt2TxyzgfHvaK9dJUH4evtZPIZzjKegmcbtgZe+D1m0de/27Jy3gg+QN9K9+h+sPdoMcDrJzXwCiSxajRY9RvZSoI12wtotZXg32HOXL9NH4Q8LsfBNxRfJvtN39Opu9RXJ4L1GbN2sj3oZtvXQLQhQ1vd1fjIrS7aai7xhZDr/HnkpUbGmZ/wmhSGN0cT3jzj9t8TDbzRiwIht7kaZ1QOAhrbPqapQ8vWR98xj9I+pKcxojwEH9BY+o3nuAfsnEreYziNwlK9/Vf/l9BdZ1+lWePyeFcxNDVu9MW7fp7VcTeUnhf5en5mHVO6T443qHynFY32aNwCeSWqa2JdoSlq+Md2S8C+fohXhXdP2e7H8Cv38TagyWiy+eGy2Dxf/3lpvxItlH/+csD7nuXO5D8XOwQ+ObbWryGl1o8ehhweEyletsw1pwvRXlNVQWqjp7myLvI71UYXX9z1xWoOn7FSZoYHYQWMCKx082o+EANj/uq8DWiNwwv81x2nWI0juM+Cvwc4vp7vdh9jU5pvqUsIO3q3eklzaPSl2q+5baHVWhzwvQ/w1LULfNHB5bxK2uja1KXp+Amt6QOfA0iCtiHFKiq00MRZ+UxIQESx1sV4jjK7Qt/Ec2Bge0TAsMLnmYn6Zuxxb2US78R9kaBgl1F93EfVH8JyVf0nnqZ7nixb+UaYDkB/Txt8XXa4vKGp/6+63+1mnv/wI0d9a2H+SL/mmCrhHwKcFO2Q18L0TG3vB7Okebb2oO4wXjxtWTGf9ZDqa8znw4bzWmSt8Mfz0c1Hx4fSzTK04xc+I9hcBlXu6f75J+jlgi3+CMk4bIW4QRRR1YlwcjtlgBuDrx4OX2B2Y29/E1RnJ0tFubmD8FQ0a4Dm2CedORRlwmX5xeJMzBYj24XqKOftcGGLbd3IgsvIRN6nezE4aoHOWet02vjIbp5M3dVf1tuqV9ZRsad3jTo1N9Ai5aTHc9gZH6/QRXLbu5rhonP1YCoyxvm0LcdC9NebZty6lWmLbfPZWxP0uLbSTDUDNGTr5OSTH5d7AqXrux4GH+gbVtOfck0ZmdJquq4r5KW3xJOQ9WQvXnr/vLdtjPbptbal7T41se2bc0QPdlyb//3EDvdWMr7RkWOG3NbK01rSqkAnfxOMjSVQ86ZY9fCthPn6DXwAmbP5a95b/M0/ZTXQSdnTV9gksR85EeGFb3Iym8uMdvouiE6ofYk7lJib9X+rX4PSSWGT3V0LWoYmqQJNIrFgDvhpkJ8ak9DR/CmZghwXKb54UcBh6++rG12m5cO76mHmgFXR3foa4K+vUXp6fGcZo4b0VV0LKOw8+Kmqz5KlL/isrV4iINDWtCt9+ZcQVf9zR1NP3kbbmof/uFmx8kNoUHmf6PyAVsx9cDqj9ySkwzrF2WZ7xLSs901ComB3niA3aGSOKtFXeIjDvzX2f6Xevk6ZEbqJLpH6ePfhh/fn9MqOaXJDovwj19f/sp/Mh+yJmnqLxfk3qs+zSh38V40B1ZjL5UBkJyVByRgZfsXoUn8GaOiecF/lWdlVcTY3OI3n2S75BSnvD04QsPhoda0Z8mXvEYnlNVX8iq9Tdql01mJ7ffNcD2gs8erFxSoDLCW/JOEtSSyrQhotNgiytjSbUCM0WkV+BJuueg9dxndA58K1c18baaXxcJJoKe8w1TJxxIGAaRgkgmAaX6nK2nf7BJ3AWB9iIsD4jeJAzCkQFB1/A8IUmuAzA1QzUnpVODM03Qdk3MtKQsy8sPqp2Cixjpm3f5ANRKFtu2pgChpZBQk6H4OM0ea9qIHtLSKGM2CmHw2vHDeKXgLg7cyuwq1TjqJfqXGcmB6lC+aBF2wr5FcrJ4iCOY4E0yAPgNfK0nDJs5VC8GkbkEG9Lqkn38IFNqgYDb4yb30psJdFVeIeGxnOxTBXoqLwhgtL4sttmT1mGLUscDSbFDqXPQ8gujf/va3l0LPDZw6x0uaU//b2gEAepUuvOsVoB3/DS8ODPaf6ISQYISbDRi961P/FES3tO5qgGdKE00yvGM3JErgcaZTeAJUKd3YJU2q3e9mQpZmgWwxQGwWVzZ9PAOs5M8bpkbVZZLWbzXhpx1OmNJMX1bj3nrRYNQWa/z50UCiBWVVBCux0MmrFRoUpS/bzOTVaWQzec2Gq84tcB0XHJ20jAzDj6u/6OhVMWlr/suO9klMQjJpR+3/5XcebTlz6dD9Ns3NByMwKwhXFOYGBLJQGCRx+pi02NIuBk7gkzmhM6Fe/FHgBFloOXAafGrnWTF1Lzs9jku6hXP3ZpdZ6PQ/rn1MgZ8ky7p/5tGkf496ccImr5OhtOIb3Kl2dZl+HH6cZHAR3oFDsgTGVq/yBOBSv3uXtKl+Gz07zHTXpDbDxYZhZtXlc8BMER9hNpi10+uqxjJoySaUbWYks1meLXEg6xGmGcbmX4jPj68Jl+Iu8FLGnZgUXd0/7tD/npMC1Y9j5UfaSxq7KIFBcZjyzYxhtFY249jchwgR7vnkKyqeH+o46dK+pYmYTmUKrPaAFqOhL1xIVQ2DDat5jZJtbkxcnrN9iup4BhdVVSSfzxVqgpdGQ4lukqMogQ6mS6c8gZJqphZSIA45NcpsHBSjcl1NZBhqLwe6LVZXczjq+sGsb13mhnOmOxcEM68A086WP1GibnQh+OjPQSTBKZd1tMUJDSFrQwdbvEY251qLwdVqprUZQTX9YGV37zPzUFUnq3+Myd1TMeHVH90sw44tWPsVIKONERyW4VTAoEKuxNgODTvoSEUCy4NAzbr//YxA1s3SFRaFvdXMcAsA2/Qznf3wtoyF+f0J7ZLHZEeK+qOO9YANlh+SS0a5EQBK1FsDFGHRP5zqPyMTvehneXo8mAEh1NNfha4GUnaEYZ4CukNo7DthlbIm7cMMFjq4mmsbGjg/5Ng8GmyzD9gqDebGPJV9QR8rcCmriEFm0BebKt3IaoFSyWKFMGMkNQBcZs5Fkj4FOvNHgJhph8+JMjhpzbRA+xQXSZxV/dh6lR8/JxkhnN1BRCEbBC0l+ZbcSlSKmkixJI8TFf5WszdfPFCnn3fHYnTurboBPP88xyRo/scskWOUIaKxwBZscniUG2jR0KPFXhr+1jsmmkBys6Pfmoe8DnL6I0mBEOjyqY8hF4vGSY8dPcByMWeNvBKf4vTcg1StYRhcTItcoq6JmC1hSAw74SkMlBttLfDMM5gb1rMvJyd8jjHTwnB1q7/oPj6eUvQ6/5alecznZ6XOYDoC5vyl/3HhaIDVmx8JrPFnw8JDggqsXx3vWZ6FmXIrdsn0QjARZBbrpWdkoX7dRBqXQR+TxmjpFgCr5W8p5wHRhNtGO/zMvVHsch6vIyBeJy0jw/Dj6gPi9aqYtDV7QLw+ZKtVrr1FRerUTbATpimbJKQVpJbNNDd/RNj15spbIuimWlSNBl5dYT7wXX+vUJHF6cW5eqo5Nk5MXPLGRY94Kg0YudSEqx8BlerZAHI2LL7Ji/ORxDD2DTz5QULfJsOJ+nX1uBh0WQ8IHvJTspsaBaRREQbtz9vAQaPMeoAQkf/+XuTnkxQFFInQee3Pk0xEpEFRhEDQkRkmIHiMGhrkWsIQAsht32Mh8TLDoGPel9OOOIR8AYsPmdhjui4khCZfvVj266TrFyLWbCD6UOyD5PlRrV1ImwyT9pe1p/dp1DBpiDX4zL2/ik3ztKCZcplrjprZV7hdjM2PZXxAbxMsTfEcwcFBFxpLlZYclIcl2Ew0VUYtk3ZnD6cKYg34VuzHiB8AYuZjylz4IhLOO/GRu9YlI6qXUpQg4CXrZNgZFFkTYJbv1jEPbCZ067ADztxuHb8nj9VVXOyj23Oxe4pLtP8rqZ4kOrh3o8b/sJOCYTb8GG4kmSrucq+LESbArpgdIsxaB1bItUsDjTWQ5KA8Eyx6rBDgCW3W656u4rKw9pH+FBa6GloS1CZbIznjjOnReZdNf+QVWv46u5ZSlKD5dd0YGhRZ/jr7Dn3DaL/NMYOyG5xWcT4JCM6IA5av/uwS0moVJ5kQzrxOgprl+HLgMtkw5IoVplvm8zu8f0pOdTKqRc9knZBsQL/+x3UDqNdj+dNYJyo5MYLldu21wMgRDhzYgjABFW061hOIjI8l+grzXtLWYpz83tFrpqif1/S/ckafcbVSnYuszoiIAvgaB1oPUyJzSxumZANrYFqfVax++wc8048qVthc39hiBTzO+nOubXMiyFVcVFSqv5BpKTUw4SXiliR84XbSRwq6mbS5gHSRAoRWMUstAWZTzlVO6Jp9uhKwtfw7+CUAa8IbeSdczX0xf/WEdl/yMx/yTPhZPoIJlMxQJpZO87YZVEstWsigZhp7hgGkREOj4Y6vOuO2b3cusMaH2/iZHD3eZEnN19MJpOp0mm2Y27/xhes+VhT0MWqT6onF4KO7yFBr5Kufgx0cgEopRQt7Q+IGEL+gtLkw4evOhs8aBl9xT7zLDxH177oj5ScNHB1z4sCXTQJIqlWZNKHOLVQ2C4M7WimT5jgRFwG1Vew850PVlPtNWzjNvtX0j59wMSB56GwDMquBCnnme3/+XO6KpEk1OXH4D7pt8Tk1W7p6WIg6rQIkWL+vcYXeo7J24IzeFPlxOpSwjXOnYWzR6vHBKWTSIt0ZSwHIQ/4THguBx9AV861qHx+TFP+CoolCM/QNsoyGX9d+PzuoYrS7mdnv42KXcoENay20IwMhmjnCZi86t7tJJwlfKJopEJx6fWxWI3Xd+S7Tqryog5Mnx7h4vv6+e4qzA7rDX8TVucBN7J4V8GoJWGh1P5qPMkQE9kas+SUQJiC9wuCh0cOkIUUHLAMa5I+fmJgBE4zlZwPDZbz7cpNhWXZfgm5yg8w+EuEZkaQ0q1/syjRbxY5Zhrvl+3wsD3QTeoCMwdzcjiB/ntEZ7a+PcZJeVFW8eyKX7W8SxVLbPjVUEMiBknMJzECK1WecgvUyOvZJZlx9U2JHjfA7dfA0hogJDs8UTDLAUcLLMBYIWXJTBUeXUXO0fEvAFjWMSVUZ17E/xIBmC4PZxjSq5mzwuzme8qLCsj3iATa63z2h/TlFD3H5Rf7wkSZi9n5MgfkukpGB4ciVhHnHKNU5DF5YnUwabCVMskMt44w5XOaHCiMDl3Zle1BhdVodVHBDad74wII6jO/XYLl+WPEBgejCMIsn6773AjZKL8NN4OOcK/PbPE0/5RXGe3vlUnfUwJRc3feHX/j3qgbjQx7x9bTnYm1dON1YV2Zxi8u3zxyACIUGr/BGnW91GkwAM7XlrdqcA1/1DxdZ+U1xlEqR8L3a/TzJKDYKY77OsiTmWhC2BhFnzSF7lR/JTGk4gFFVph676Kb5NLL97xsasaSmtmpuYhjBiXBdMwEHzEZsiSRP45JVnuCJ8dPJNt9lTpofLIcjqsrUwxHdNHNpQ/++oeFIamqr5iaGUf1vMbkS14tChqnhx2muAu2R5Gk4gs2zDPx0ss3obEsuGu/Q1wR9e4vS0+M5zerIK6Z7PUn9yfd8MjmAy2+AaENDmFmPWLU9Jw6ZAp2rQ0sl7fPZEOXXewEyxgLBxFReBrKchrVZxzJzMG9n1FrRULUCJyxHOK3P4coeQ9O6WV3jOtVz/VHhGqjo3AvzPXqTFGX1Oq7iz3Ep3uPUte5R1T/CIblpm5+pzmx/ry+pjvE/ft1/znFfx5/ToYow4HCM4+9XcYUOJGyEyJ4uBRuhCTRN4X/VB4lAM30J1ERfqGH/Lt/FafJPtO96HGgIoIGaBMh0jcfZ4UyeiIlt9kVgU32piXrovirIWWyZn4sd2BpIJlVSoNRIcYuKY1KWGOPdIbcggUgCtS5SaVpm3+4IrbLFUIsshU7PPE0h3cjPoD6kxIBrd2EB8u4KZS105Ya26pchUnP1FCqL9USGzSraUzekbaF/yic00JdA/PtCnQK13xo4EPYloPhdoW4AbOOcvkfVUw59OjwBOBxyNLo2Kzw842HsK55Ooe+GKwdbZEk0DQ4nTEJbQxHUzFCq+4q6hZT4CXUl4PfTFWrYDxl5Bf5DEdTAUKqDmXzCVc+2xlPtbbKrzgXU330JaKKu0KwHFK1wBIr+iFqi6H1McG3cP3Wk6aRAR3iAA6lUvcYQ6kRAafIVFc8PyRHSni0GG2UozKxNxxCWGZymUdicJrNtvA8H+CZJK3gi01YxEk2oZSap4uMSKFSw7KiMcdlWfB9n58eYQFphHJZKJQdNaSyLRgh96yyFmeb3J7RLHpMd2QJR0T5lNpDRq6wB1zG2C1z9Q/v2SJwcleTgXKms4SSdsVw2Epn26UMM7dfoQkVvkXKzdj7FRRJnQ6zRq/z4OcliSb+YVFLIpaynkffPc0x++Zgl0LTDFkMysBRu1jE3iWbqbf5v/x3xFeUCmUlijUtezzZwr6lZWnILjLQ1dAuD/uWbuCjoi8AFQV+qO69JUHFbJOBanioDz2qGYk0jg9OK0MZQBDVRl2q5X3/H03kWpxfn6qk+UWuGJul5gpockkJdQyMdCbAl2cBQZVC7pLiMjDYxhFZ2qkcXKhoyO+EjxLJGlPxbChP+vxf5+SRrpC1UtNRSaFpq4zcLjbS/Q/zbIsMtBZsxV7qnYMlUmwqWUiMFnLdXkAImg6SAKQ2lULSsbs2sGyXDC1Um7U6jfQuVtRZupCmTNtIUaxoBs1MKzYFUUMMgocGZkmQLPBTJzpGMNrts4jtpK/JOYym0JmWyYgHGZMphMzIkWvX4JBSAijwJrCZPpTsGEhMjiOdBIg14MCSS2TYuW0HJCI3EMFs1ScOYA70voYRxICG2FMdADjMB9MfbdNBj8YibLgWPuWkC86aaGOKq5hoKTZMNkfYWC9JMqpGJJkCUV3gZwtIoViMsoXZDwoaIBLYgLIFq5ciR6vpwiEco9t5QBvbbUKwbH5l3peLYyBSD4yJDoYVlKt0IUGUwFFPDJf7HQt4IVQY1QhXr1lEoQ3izpRrcRRJwXSVQ6baImAci+9fP4FUpVw5uFVkS7R1TDt4CtL/Dd0q5wT3DEEBNnJb6IvhCsSs1Eb0/9YA16Iulipgem0ijHgkNSynBmzMZscUp8QM6nlJ4KIHJdKfGA6XhbZhCApFEdTtm3HJ3TClvWKBQnXaaNnuHarK9/BqTJ4DXFiyNzshFXpaYeSpvVSQBjSxQ6YzcrH2kd95cOWhglkR7QgsGRgGOakE6+MwWJDUXRN28tlH9kRoTBEk8QmOKwSMzhkL7xR5PcXKAxq2hCP5Cu1LdyNSsgFSDEk8Bjkc8kcG55ztUVajQDM8yQtmZKESrNUFc4nH0L5QcnqA+5cph9RkSswZfJxjcJay2SKJolqLStMzFpBGa5cqhNjkS3az/nO0Ukz5dCs75NIH2sJuPbQEccPMk8KE2T2XUstyqXLm8TVOr8i59Go89NbmJz2BUk5rf/TKOU3JXLwmd3hfrDpV4iU58tg1czVpt5R5tAoXKc64lQvqGu6NphZ+ESKI6444uTqc0QfuHvKVPLKSQL0cECjMZWnK9BJ2LksE9t+F1dzTQGYOyd7E0824193JlIuWb3+sKMSxEmwgkMmdVlspg6u1fhILzbV8qm2R7AgMPRXlTTKnMT9G0KflLMRnWAFIF5ABqG4l0Yhi0LTRIPYNQO69Hg+s7VUfhxj5U4F9tULnAOD98LILcx56tqfCvb7iY+Mu/YNU3NQ391kBvF5g6mFGgNxWDRZTPJOzNIczx9DhWRvet2UXLmFWUqyp72UA01b1WUHKCxnaQq/KFgndTPsTFAVUOpmwryg0gVVil4DJNiKcx5ffIEoT4BOmXKI3K0EsTN9Wa5yRRwxFWjiYZKaRQg30K09eTPHKxV5Fdnkfd+hy1i/pEOZ6YV5YrCT/4IIqqn3EouHAbF4CZ7DlKEAPKRxHzyoqRBFBWot5STEY/lol6voB5QMIApoAeADUmUL7rsVe93YOqlOZJ/KvL7adJPdnzL3cVOdPJFWUJvffShKr3G8v+qEExeMqJ9asGcMWgVFxylsIwCGoI+SAoJ/bSj0sww2WS1tGRe84KI3Ck4UxghKERSndBQwbWcq0F2pCfAPfil2Ege8Zrb4buqaZyaSwShVge889NSU3pU1KHZTLzkjHqXzQCq2WYUrEEZp9PNutf+AUmWw98fdlU54q8q98dxurVhyOAiGpA8i9G/f4RoKLfBRr/Pc6f1TeftewxsL2aqqN8xSxvUk2uFP/Omigle0MN1gTNIhaGNY987jep5qnPF2ae7hLGCjt8pZDIgcYeoSykYWxQw1fyP6TOZBbg6T24flRXCIkTIMYAw0QVNMB5so3Y6ADSqZalUygDRCtotFBEIXDFlbva0pf/ER2uQGoNo+paFcUgC7Sy8rgJal6SmAkwa13cAw8Gbk2iX81JqwRb1rl2gBezWBlkg6bol7LD6kBqCZE2xFpGEtSDNoTHlQyvmh4PAmkwNExkAibciGLXA9L53/lAgUxIZWWAEuee54Kt0E1IISCv41ktCBVSRmB5EDPpPxKQPNiHMo9JJIFp9MbRVAxmJnWwGZqbYQQZh7syRbAcSaQf8CbNgY3iJkZvFzODGHBlAw8peEsCCPlGq7pxWwgruU1sqoV+AaLnqNLFSU4d7phV8I9lTlqp0iDGUDgpyam1Skm0AdRYhkkUAatMDwuMWUxwjmAQ7Iu2qE24riAm1k/iJrWDfaJrMycbAc3JpgwLuYGgYG3EFKogbGs1sQNMp8emibXCm0i5nDSuq1VeqbXZunGRZiRRXSJps8YmVfOZxCgmfJnIOCrmcLQb94WQ4UA5+WgY9h4guo+PpxQNERHlinOUiqULF5exWbZIYi5OqHIfkjFiHddFlSWUeo8gdyd8IdQkqS6PIjlGfcXcJRL5n63Cq9rFzVS6TIlEcpHdXab46J6k5vCjP89A06c76gp6iI9wl9N9JJ7fk0g0NfAfnfwJzlwmUgVvVfpbmlUMgSaTgLaEm1VgWnvT9eFplXYCqEIYRYijS6pSv/pRl8TA1evLkQVTmInoO2gMR+p1VDmig+pK9KVpNCIzoXsHkcGQvGJtsaLfnm1ZKru1oRkt6Dy9Sb5Emq3qi6XoPIo+8XdL4usaPBsA6eSCu74ZYKJKk1pw0GhXRVVjE0cRYlgKpx4U5Doa3OvkfnxwBcXubbQfHxRSm+GiDJHtyTSt3Q3t0lCP7tdFmKMP2y23AU/iV3Eh5PhQz9OmZ5BfsZUVifxvZcOr2gV6j27x/uwpLtH+r6R6oloQFddV8aYOU5cPZU+qSsPUuxuC+W4H/nIzwBU8KQLWlH7xRjkCPJnmI93vxvZha/kdF+YyT59iQDMgUiQBBkQ6PcJQD8x8YK8ikMxAuRRS0odYGCmSNxAmJtkY/JhF+13oK/lFxzym6fJEyL8JjsKv0nwmjfsuTISn1x10Goxo4CvXkiX0JDhYU5hflSk7XDdCXcgN7ZaPJ1zTno9JPqIZ8EDKMEMdkFSl/ZJVuVJGHHAbdLaU1n9/h44LwOd+0TxZUpFrdwRenijJst60X78mk40HA6m+DDlxiI9jdlMoNoxSWv/7xqnNAOcKioBMRsDIYVpXgRdZ8qUGOLp8SmpeoseIYWYkl/mGzTEU3WRJlcSpYi2pquB7HQlnUmqnHk12pPHG6NbKYlN6u0jrBlJXyQlehJult3KIcjUkdYqEBE+i5VTkiqkcTkDVTOnqrFIyPjIenk2imrpAuhCz1jRq69QNriavnh+1hHRg+gtkmDrYPTKUf2K4iFMmknBxvKPTjkVvivyosoeKPIRB4Axr7cJGmTBttCkecgtDUMRrN0OfHi5S7N9EIv8bNyHFXVNTmr3OZWRPgSjH8NgOUur72t0JS0iH147xsiR0LvFKcxKtKznGxfP1991TnB3QHTbtkEIN2JZoK6mMwuZ0aw0C52tjdyh0mrlmWwJmkRtpBPKHsfYs9brUluSNU06G2johhj5N3jzCyDQJnj8zKfbtuir+t+/zmghMkBa9SeCBVEGt8F1wduFXJoS7bx5AmOR3G2WWiM2wpjQKSytXDMr6RvRRZXOTmUZmkGDoYNowBQldyataC0EMk8csYlPnijZSUCvmEyChbzOtKBL1MhzAfG2EhToNm4szuI05FNQhzQEmWmu+Q2X+NFdzDDngop61zBgArXdFAB5ivjuKjSKHnVsaCzrRVFQfGF3lWVkVcVLPKnh32i9OulDMD3kkJqgC9ne+eOvXRA5xm9llgyRxV7N+0CXj8mB2OnGHgSUpcrVSVjlCZjUJlYLMFChM1jLRaiM5hkcdkLTtvntMJkuRNs6wwzM1tbl6OrXwxs/gZlCbyjNn2vtMajpgVzKOY3g8AZn5mo2LIuXeOMMOkeLV5urp1MIbR6KfQe12FymkDDSe1ST1FQ+0/bc1wUwqVVPckevTL3rsJqbAyuhsTUPV5fqux3xOcNOa1o3rxMg1777RRtbHneEI/Z9++Vf91YuGSW143Muo6MtevWiSorY/4D+rvIgP6H2+R2lJfn314u6Max9R89drVCaHgcUrzDNDJE/0wLSjucke64DvJJ0mJ1FH0hX3edureB9X8UVRJXVcS1y8w98SSfdLvEzq86DPaH+TfThXp3OFVUbHzylz6Pzqhbr9Vy8EmV81gfZKHypgMROsAvqQXZ6TdN/L/SZOS267KWNxha3/O8K/N32JP80KHZ57Tn/kmSGj1nx4k4qyPf7kHtDxlGJm5YfsPv6KXGT7WKJ36BDvnm/rNF7EaUbGRN8RrNlfvU7iQxEfy5bHUB//iTG8P37/r/8Hx09YI7S1CAA= - - - dbo - - \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.Designer.cs similarity index 92% rename from src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.Designer.cs rename to src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.Designer.cs index a9a2bb71ff..256bd186e0 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.Designer.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.Designer.cs @@ -13,7 +13,7 @@ public sealed partial class ExportRevision : IMigrationMetadata string IMigrationMetadata.Id { - get { return "201605201230154_ExportRevision"; } + get { return "201605201342001_ExportRevision"; } } string IMigrationMetadata.Source diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs similarity index 82% rename from src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.cs rename to src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs index ee0d9c7eaa..d55e05d285 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs @@ -12,10 +12,12 @@ public override void Up() { AddColumn("dbo.ExportDeployment", "SubFolder", c => c.String(maxLength: 400)); AlterColumn("dbo.ExportProfile", "FolderName", c => c.String(nullable: false, maxLength: 400)); + DropColumn("dbo.ExportDeployment", "CreateZip"); } public override void Down() { + AddColumn("dbo.ExportDeployment", "CreateZip", c => c.Boolean(nullable: false)); AlterColumn("dbo.ExportProfile", "FolderName", c => c.String(nullable: false, maxLength: 100)); DropColumn("dbo.ExportDeployment", "SubFolder"); } @@ -77,13 +79,18 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.SubFolder", "Name of subfolder", - "Name des Unterordner", + "Name des Unterordners", "Specifies the name of a subfolder where to deploy the data.", "Legt den Namen eines Unterordners fest, in den die Daten bereitgestellt werden sollen."); + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.ZipUsageNote", + "If there are a large number of export files, it is recommended to use the option Create ZIP archive. This saves time and avoids problems, such as a full email mailbox.", + "Bei einer großen Anzahl an Exportdateien wird empfohlen die Option ZIP-Archiv erstellen zu benutzen. Das spart Zeit und vermeidet Probleme, wie z.B. ein volles E-Mail Postfach."); + builder.Delete( "Admin.DataExchange.Export.FolderAndFileName.Validate", - "Admin.DataExchange.Export.Deployment.IsPublic"); + "Admin.DataExchange.Export.Deployment.IsPublic", + "Admin.DataExchange.Export.Deployment.CreateZip"); } } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.resx b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.resx new file mode 100644 index 0000000000..fa683624cb --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index efee428a5b..fa7a0999ef 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -400,9 +400,9 @@ 201605191216116_CheckoutAttributeMultiStore.cs - - - 201605201230154_ExportRevision.cs + + + 201605201342001_ExportRevision.cs @@ -749,8 +749,8 @@ 201605191216116_CheckoutAttributeMultiStore.cs - - 201605201230154_ExportRevision.cs + + 201605201342001_ExportRevision.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs index d4f89e79d8..11a1433afe 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs @@ -39,13 +39,9 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de System.IO.Directory.CreateDirectory(destinationFolder); } - if (deployment.CreateZip) + if (deployment.Profile.CreateZipArchive) { - var name = (new DirectoryInfo(deployment.Profile.FolderName)).Name; - if (name.IsEmpty()) - name = "ExportData"; - - var path = Path.Combine(destinationFolder, name.ToValidFileName() + ".zip"); + var path = Path.Combine(destinationFolder, Path.GetFileName(context.ZipPath)); if (FileSystemHelper.Copy(context.ZipPath, path)) context.Log.Information("Copied ZIP archive " + path); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 572f46cbfb..a581b96ba4 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -451,7 +451,7 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho private void Deploy(DataExporterContext ctx, string zipPath) { - var allFiles = System.IO.Directory.GetFiles(ctx.FolderContent, "*.*", SearchOption.AllDirectories); + string[] allFiles = null; var context = new ExportDeploymentContext { @@ -462,15 +462,25 @@ private void Deploy(DataExporterContext ctx, string zipPath) foreach (var deployment in ctx.Request.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) { - if (deployment.CreateZip) - context.DeploymentFiles = new string[] { zipPath }; - else - context.DeploymentFiles = allFiles; - try { IFilePublisher publisher = null; + if (!ctx.Request.Profile.CreateZipArchive || deployment.DeploymentType == ExportDeploymentType.FileSystem) + { + if (allFiles == null) + allFiles = System.IO.Directory.EnumerateFiles(ctx.FolderContent, "*", SearchOption.AllDirectories).ToArray(); + + context.DeploymentFiles = allFiles; + } + else + { + if (File.Exists(zipPath)) + context.DeploymentFiles = new string[] { zipPath }; + else + context.DeploymentFiles = new string[0]; + } + if (deployment.DeploymentType == ExportDeploymentType.Email) { publisher = new EmailFilePublisher(_emailAccountService.Value, _queuedEmailService.Value); @@ -1220,9 +1230,9 @@ private void ExportCoreOuter(DataExporterContext ctx) { if (ctx.IsFileBasedExport) { - if (ctx.Request.Profile.CreateZipArchive || ctx.Request.Profile.Deployments.Any(x => x.Enabled && x.CreateZip)) + if (ctx.Request.Profile.CreateZipArchive) { - ZipFile.CreateFromDirectory(ctx.FolderContent, zipPath, CompressionLevel.Fastest, true); + ZipFile.CreateFromDirectory(ctx.FolderContent, zipPath, CompressionLevel.Fastest, false); } if (ctx.Request.Profile.Deployments.Any(x => x.Enabled)) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs index 4dec9a2bbe..29a45613ad 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs @@ -91,7 +91,7 @@ public static List GetExportFiles(this ExportProfile profile) if (System.IO.Directory.Exists(exportFolder)) { - return System.IO.Directory.EnumerateFiles(exportFolder, "*", SearchOption.TopDirectoryOnly) + return System.IO.Directory.EnumerateFiles(exportFolder, "*", SearchOption.AllDirectories) .OrderBy(x => x) .ToList(); } diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index dfa4145f39..6da44acf8d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -911,6 +911,9 @@ td.adminSeparator hr { .disabled { .muted(); } + .centered { + text-align: center; + } .progress-info { min-width: 260px; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 7fa0b7ff35..5de25bbd28 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -318,7 +318,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile model.Deployments = profile.Deployments .Select(x => { - var deploymentModel = PrepareDeploymentModel(x, null, false); + var deploymentModel = PrepareDeploymentModel(profile, x, null, false); deploymentModel.ProfileDetails = PrepareProfileDetailsModel(profile, true); @@ -417,7 +417,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile } } - private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment, Provider provider, bool forEdit) + private ExportDeploymentModel PrepareDeploymentModel(ExportProfile profile, ExportDeployment deployment, Provider provider, bool forEdit) { var model = new ExportDeploymentModel { @@ -425,7 +425,6 @@ private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment ProfileId = deployment.ProfileId, Name = deployment.Name, Enabled = deployment.Enabled, - CreateZip = deployment.CreateZip, DeploymentType = deployment.DeploymentType, DeploymentTypeName = deployment.DeploymentType.GetLocalizedEnum(_services.Localization, _services.WorkContext), Username = deployment.Username, @@ -445,6 +444,7 @@ private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment { var allEmailAccounts = _emailAccountService.GetAllEmailAccounts(); + model.CreateZip = profile.CreateZipArchive; model.AvailableDeploymentTypes = ExportDeploymentType.FileSystem.ToSelectList(false).ToList(); model.AvailableHttpTransmissionTypes = ExportHttpTransmissionType.SimplePost.ToSelectList(false).ToList(); @@ -469,7 +469,6 @@ private void ModelToEntity(ExportDeploymentModel model, ExportDeployment deploym deployment.Name = model.Name; deployment.Enabled = model.Enabled; deployment.DeploymentType = model.DeploymentType; - deployment.CreateZip = model.CreateZip; deployment.Username = model.Username; deployment.Password = model.Password; deployment.Url = model.Url; @@ -1196,7 +1195,7 @@ public ActionResult CreateDeployment(int id) //var fileSystemName = ExportDeploymentType.FileSystem.GetLocalizedEnum(_services.Localization, _services.WorkContext); - var model = PrepareDeploymentModel(new ExportDeployment + var model = PrepareDeploymentModel(profile, new ExportDeployment { ProfileId = id, Enabled = true, @@ -1251,7 +1250,7 @@ public ActionResult EditDeployment(int id) if (provider.Metadata.IsHidden) return RedirectToAction("List"); - var model = PrepareDeploymentModel(deployment, provider, true); + var model = PrepareDeploymentModel(deployment.Profile, deployment, provider, true); return View(model); } @@ -1282,7 +1281,7 @@ public ActionResult EditDeployment(ExportDeploymentModel model, bool continueEdi return SmartRedirect(continueEditing, deployment.ProfileId, deployment.Id); } - model = PrepareDeploymentModel(deployment, _exportService.LoadProvider(deployment.Profile.ProviderSystemName), true); + model = PrepareDeploymentModel(deployment.Profile, deployment, _exportService.LoadProvider(deployment.Profile.ProviderSystemName), true); return View(model); } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs index b9b5c4f40b..7d06e197a2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs @@ -13,6 +13,7 @@ namespace SmartStore.Admin.Models.DataExchange public class ExportDeploymentModel : EntityModelBase { public int ProfileId { get; set; } + public bool CreateZip { get; set; } [SmartResourceDisplayName("Common.Image")] public string ThumbnailUrl { get; set; } @@ -44,15 +45,14 @@ public string DeploymentTypeIconClass return "fa-globe"; case ExportDeploymentType.Ftp: return "fa-files-o"; + case ExportDeploymentType.PublicFolder: + return "fa-unlock"; default: return "fa-question"; } } } - [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.CreateZip")] - public bool CreateZip { get; set; } - [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.Username")] public string Username { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml index d48d8eeaa0..f111f30257 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml @@ -13,10 +13,6 @@ }
 @Model.DeploymentTypeName - @if (Model.DeploymentType == ExportDeploymentType.FileSystem && Model.IsPublic) - { - (@T("Common.Public")) - }
@if (Model.DeploymentType == ExportDeploymentType.FileSystem) @@ -51,4 +47,8 @@ {
@Model.Url
} + else if (Model.DeploymentType == ExportDeploymentType.PublicFolder) + { +
TODO
+ }
\ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml index 30b6318cdc..05765e96e4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml @@ -7,6 +7,13 @@ @Html.HiddenFor(model => model.Id) @Html.HiddenFor(model => model.ProfileId) +@if (!Model.CreateZip) +{ +
+ @Html.Raw(T("Admin.DataExchange.Export.Deployment.ZipUsageNote")) +
+} + - - - - - + + @foreach (var deployment in Model.Deployments) { + var result = deployment.LastResult; - + - @@ -151,10 +158,15 @@ \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml index 529ff0124f..e1b19be258 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml @@ -20,7 +20,7 @@ @if (Model.Any()) { -
@@ -36,15 +43,6 @@ @Html.ValidationMessageFor(x => x.Enabled)
- @Html.SmartLabelFor(x => x.CreateZip) - - @Html.EditorFor(x => x.CreateZip) - @Html.ValidationMessageFor(x => x.CreateZip) -
@Html.SmartLabelFor(x => x.FileSystemPath) @@ -157,10 +155,13 @@ // deployment type changed $('#@(Html.FieldIdFor(x => x.DeploymentType))').change(function () { - var table = $('#DeploymentTable'); + var table = $('#DeploymentTable'), + val = $(this).val(); table.find('tr:not(.deployment-all)').hide(); - table.find('.deployment-' + $(this).val()).show(); + table.find('.deployment-' + val).show(); + + $('#ZipUsageNote').toggle(val === '@((int)ExportDeploymentType.Email)' || val === '@((int)ExportDeploymentType.Http)' || val === '@((int)ExportDeploymentType.Ftp)'); }).trigger('change'); }); From 836f47550887c006f1fe596a391626ff689af6be Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 20 May 2016 20:32:22 +0200 Subject: [PATCH 080/185] Improved export publisher code --- .../Deployment/EmailFilePublisher.cs | 2 +- .../Deployment/FileSystemFilePublisher.cs | 14 ++--------- .../Deployment/FtpFilePublisher.cs | 4 ++-- .../Deployment/HttpFilePublisher.cs | 4 ++-- .../DataExchange/Deployment/IFilePublisher.cs | 24 ++++++++++++++++--- .../Deployment/PublicFolderPublisher.cs | 18 +++++++++++--- .../DataExchange/Export/DataExporter.cs | 20 ++-------------- 7 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs index 8a9beeeea0..298d476c41 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs @@ -42,7 +42,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de EmailAccountId = deployment.EmailAccountId }; - foreach (var path in context.DeploymentFiles) + foreach (var path in context.GetDeploymentFiles()) { var name = Path.GetFileName(path); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs index 11a1433afe..c8bea99259 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs @@ -39,19 +39,9 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de System.IO.Directory.CreateDirectory(destinationFolder); } - if (deployment.Profile.CreateZipArchive) - { - var path = Path.Combine(destinationFolder, Path.GetFileName(context.ZipPath)); - - if (FileSystemHelper.Copy(context.ZipPath, path)) - context.Log.Information("Copied ZIP archive " + path); - } - else - { - FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); + FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); - context.Log.Information("Copied export data files to " + destinationFolder); - } + context.Log.Information("Copied export data files to " + destinationFolder); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FtpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/FtpFilePublisher.cs index 2352d7250f..99262471d5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FtpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/FtpFilePublisher.cs @@ -16,7 +16,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de var url = deployment.Url; var buffLength = 32768; byte[] buff = new byte[buffLength]; - var deploymentFiles = context.DeploymentFiles.ToList(); + var deploymentFiles = context.GetDeploymentFiles().ToList(); var lastIndex = (deploymentFiles.Count - 1); if (!url.StartsWith("ftp://", StringComparison.InvariantCultureIgnoreCase)) @@ -26,7 +26,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de { var fileUrl = url.EnsureEndsWith("/") + Path.GetFileName(path); - var request = (FtpWebRequest)System.Net.WebRequest.Create(fileUrl); + var request = (FtpWebRequest)WebRequest.Create(fileUrl); request.Method = WebRequestMethods.Ftp.UploadFile; request.KeepAlive = (deploymentFiles.IndexOf(path) != lastIndex); request.UseBinary = true; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/HttpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/HttpFilePublisher.cs index 48303bd200..6afb1260be 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/HttpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/HttpFilePublisher.cs @@ -30,7 +30,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de using (var client = new HttpClient(handler)) using (var formData = new MultipartFormDataContent()) { - foreach (var path in context.DeploymentFiles) + foreach (var path in context.GetDeploymentFiles()) { byte[] fileData = File.ReadAllBytes(path); formData.Add(new ByteArrayContent(fileData), "file {0}".FormatInvariant(++count), Path.GetFileName(path)); @@ -60,7 +60,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de if (deployment.Username.HasValue()) webClient.Credentials = new NetworkCredential(deployment.Username, deployment.Password); - foreach (var path in context.DeploymentFiles) + foreach (var path in context.GetDeploymentFiles()) { webClient.UploadFile(url, path); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs index 25ffddcaa7..68f54ce5f6 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs @@ -1,4 +1,7 @@ -using SmartStore.Core.Domain; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using SmartStore.Core.Domain; using SmartStore.Core.Logging; namespace SmartStore.Services.DataExchange.Export.Deployment @@ -13,10 +16,25 @@ public class ExportDeploymentContext { public ILogger Log { get; set; } - public string[] DeploymentFiles { get; set; } - public string FolderContent { get; set; } public string ZipPath { get; set; } + + public bool CreateZipArchive { get; set; } + + public IEnumerable GetDeploymentFiles() + { + if (!CreateZipArchive) + { + return System.IO.Directory.EnumerateFiles(FolderContent, "*", SearchOption.AllDirectories).ToArray(); + } + + if (File.Exists(ZipPath)) + { + return new string[] { ZipPath }; + } + + return new string[0]; + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/PublicFolderPublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/PublicFolderPublisher.cs index 890509ab85..5fdeee0ffd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/PublicFolderPublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/PublicFolderPublisher.cs @@ -13,11 +13,23 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de if (destinationFolder.HasValue()) { - // TODO: zip + if (context.CreateZipArchive) + { + if (File.Exists(context.ZipPath)) + { + var destinationFile = Path.Combine(destinationFolder, Path.GetFileName(context.ZipPath)); - FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); + File.Copy(context.ZipPath, destinationFile, true); - context.Log.Information("Copied export data files to " + destinationFolder); + context.Log.Information("Copied zipped export data to " + destinationFile); + } + } + else + { + FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); + + context.Log.Information("Copied export data files to " + destinationFolder); + } } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index a581b96ba4..a0b9cc6d6f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -451,13 +451,12 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho private void Deploy(DataExporterContext ctx, string zipPath) { - string[] allFiles = null; - var context = new ExportDeploymentContext { Log = ctx.Log, FolderContent = ctx.FolderContent, - ZipPath = zipPath + ZipPath = zipPath, + CreateZipArchive = ctx.Request.Profile.CreateZipArchive }; foreach (var deployment in ctx.Request.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) @@ -466,21 +465,6 @@ private void Deploy(DataExporterContext ctx, string zipPath) { IFilePublisher publisher = null; - if (!ctx.Request.Profile.CreateZipArchive || deployment.DeploymentType == ExportDeploymentType.FileSystem) - { - if (allFiles == null) - allFiles = System.IO.Directory.EnumerateFiles(ctx.FolderContent, "*", SearchOption.AllDirectories).ToArray(); - - context.DeploymentFiles = allFiles; - } - else - { - if (File.Exists(zipPath)) - context.DeploymentFiles = new string[] { zipPath }; - else - context.DeploymentFiles = new string[0]; - } - if (deployment.DeploymentType == ExportDeploymentType.Email) { publisher = new EmailFilePublisher(_emailAccountService.Value, _queuedEmailService.Value); From 12133ed5acd9f0b2ab0e04b536af58be423391e6 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 20 May 2016 20:36:03 +0200 Subject: [PATCH 081/185] Moving deployment publisher into export folder --- .../DataExchange/Excel/ExcelDataReader.cs | 2 -- .../{ => Export}/Deployment/EmailFilePublisher.cs | 0 .../Deployment/FileSystemFilePublisher.cs | 0 .../{ => Export}/Deployment/FtpFilePublisher.cs | 0 .../{ => Export}/Deployment/HttpFilePublisher.cs | 0 .../{ => Export}/Deployment/IFilePublisher.cs | 0 .../Deployment/PublicFolderPublisher.cs | 0 .../{ => Export}/Events/RowExportingEvent.cs | 0 .../SmartStore.Services/SmartStore.Services.csproj | 14 +++++++------- 9 files changed, 7 insertions(+), 9 deletions(-) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Deployment/EmailFilePublisher.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Deployment/FileSystemFilePublisher.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Deployment/FtpFilePublisher.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Deployment/HttpFilePublisher.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Deployment/IFilePublisher.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Deployment/PublicFolderPublisher.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Events/RowExportingEvent.cs (100%) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Excel/ExcelDataReader.cs b/src/Libraries/SmartStore.Services/DataExchange/Excel/ExcelDataReader.cs index d5eb02d5a9..d46879193c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Excel/ExcelDataReader.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Excel/ExcelDataReader.cs @@ -6,8 +6,6 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using OfficeOpenXml; namespace SmartStore.Services.DataExchange.Excel diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FtpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Deployment/FtpFilePublisher.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/HttpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Deployment/HttpFilePublisher.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/PublicFolderPublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Deployment/PublicFolderPublisher.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Events/RowExportingEvent.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Events/RowExportingEvent.cs diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index f14da22d07..5bd7ee173b 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -174,20 +174,20 @@ - + - - - - - + + + + + - + From e9c7baf209f7ca19f1fdb57e5d03138dfcc19bd8 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 20 May 2016 20:59:39 +0200 Subject: [PATCH 082/185] Clearing up export folder only when all deployments succeeded --- .../Utilities/FileSystemHelper.cs | 8 ++++- .../201605201342001_ExportRevision.cs | 8 +++++ .../DataExchange/Export/DataExporter.cs | 18 ++++++++--- .../Export/Deployment/EmailFilePublisher.cs | 4 ++- .../Deployment/FileSystemFilePublisher.cs | 6 ++-- .../Export/Deployment/FtpFilePublisher.cs | 7 +++- .../Export/Deployment/HttpFilePublisher.cs | 6 +++- .../Export/Deployment/IFilePublisher.cs | 2 +- .../Deployment/PublicFolderPublisher.cs | 32 +++++++++++-------- 9 files changed, 66 insertions(+), 25 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs b/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs index 95672ae9a5..cf778b71ff 100644 --- a/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs +++ b/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs @@ -114,8 +114,10 @@ public static bool Copy(string sourcePath, string destinationPath, bool overwrit /// Source directory /// Target directory /// Whether to override existing files - public static void CopyDirectory(DirectoryInfo source, DirectoryInfo target, bool overwrite = true) + public static bool CopyDirectory(DirectoryInfo source, DirectoryInfo target, bool overwrite = true) { + var result = true; + foreach (FileInfo fi in source.GetFiles()) { try @@ -124,6 +126,7 @@ public static void CopyDirectory(DirectoryInfo source, DirectoryInfo target, boo } catch (Exception exc) { + result = false; exc.Dump(); } } @@ -137,9 +140,12 @@ public static void CopyDirectory(DirectoryInfo source, DirectoryInfo target, boo } catch (Exception exc) { + result = false; exc.Dump(); } } + + return result; } /// diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs index d55e05d285..23e687029d 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs @@ -87,6 +87,14 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "If there are a large number of export files, it is recommended to use the option Create ZIP archive. This saves time and avoids problems, such as a full email mailbox.", "Bei einer großen Anzahl an Exportdateien wird empfohlen die Option ZIP-Archiv erstellen zu benutzen. Das spart Zeit und vermeidet Probleme, wie z.B. ein volles E-Mail Postfach."); + builder.AddOrUpdate("Admin.DataExchange.Export.Cleanup", + "Clean up after successful deployment", + "Nach erfolgreicher Veröffentlichung aufräumen", + "Specifies whether to delete unneeded files after all deployments succeeded.", + "Legt fest, ob nicht mehr benötigte Dateien gelöscht werden sollen, nachdem alle Veröffentlichungen erfolgreich waren."); + + + builder.Delete( "Admin.DataExchange.Export.FolderAndFileName.Validate", "Admin.DataExchange.Export.Deployment.IsPublic", diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index a0b9cc6d6f..665babf9c1 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -449,8 +449,9 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho return (ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard); } - private void Deploy(DataExporterContext ctx, string zipPath) + private bool Deploy(DataExporterContext ctx, string zipPath) { + var result = true; var context = new ExportDeploymentContext { Log = ctx.Log, @@ -488,15 +489,20 @@ private void Deploy(DataExporterContext ctx, string zipPath) if (publisher != null) { - publisher.Publish(context, deployment); + if (!publisher.Publish(context, deployment)) + result = false; } } catch (Exception exception) { + result = false; + ctx.Log.Error("Deployment \"{0}\" of type {1} failed: {2}".FormatInvariant( deployment.Name, deployment.DeploymentType.ToString(), exception.Message), exception); } } + + return result; } private void SendCompletionEmail(DataExporterContext ctx, string zipPath) @@ -1136,6 +1142,7 @@ private void ExportCoreOuter(DataExporterContext ctx) if (ctx.Request.Profile == null || !ctx.Request.Profile.Enabled) return; + var allDeploymentsSucceeded = false; var logPath = ctx.Request.Profile.GetExportLogPath(); var zipPath = ctx.Request.Profile.GetExportZipPath(); @@ -1222,7 +1229,8 @@ private void ExportCoreOuter(DataExporterContext ctx) if (ctx.Request.Profile.Deployments.Any(x => x.Enabled)) { SetProgress(ctx, T("Common.Deployment")); - Deploy(ctx, zipPath); + + allDeploymentsSucceeded = Deploy(ctx, zipPath); } } @@ -1259,9 +1267,11 @@ private void ExportCoreOuter(DataExporterContext ctx) try { - if (ctx.IsFileBasedExport && ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard && ctx.Request.Profile.Cleanup) + if (ctx.IsFileBasedExport && ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard && ctx.Request.Profile.Cleanup && allDeploymentsSucceeded) { FileSystemHelper.ClearDirectory(ctx.FolderContent, false); + + logger.Information("Cleared up export folder"); } } catch (Exception exception) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs index 298d476c41..814d34498a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs @@ -23,7 +23,7 @@ public EmailFilePublisher( _queuedEmailService = queuedEmailService; } - public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) { var emailAccount = _emailAccountService.GetEmailAccountById(deployment.EmailAccountId); var smtpContext = new SmtpContext(emailAccount); @@ -60,6 +60,8 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de } context.Log.Information("{0} email(s) created and queued for deployment.".FormatInvariant(count)); + + return true; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs index c8bea99259..774cf7dee1 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs @@ -7,13 +7,13 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class FileSystemFilePublisher : IFilePublisher { - public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) { string destinationFolder = null; if (deployment.FileSystemPath.IsEmpty()) { - return; + return false; } else if (Path.IsPathRooted(deployment.FileSystemPath)) { @@ -42,6 +42,8 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); context.Log.Information("Copied export data files to " + destinationFolder); + + return true; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs index 99262471d5..29a6d086b5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs @@ -9,8 +9,9 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class FtpFilePublisher : IFilePublisher { - public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) { + var result = true; var bytesRead = 0; var succeededFiles = 0; var url = deployment.Url; @@ -61,6 +62,8 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de } else { + result = false; + var msg = "The FTP transfer might fail. {0} ({1}), {2}. File {3}".FormatInvariant( response.StatusCode.ToString(), statusCode, response.StatusDescription.NaIfEmpty(), path); @@ -70,6 +73,8 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de } context.Log.Information("{0} file(s) successfully uploaded via FTP.".FormatInvariant(succeededFiles)); + + return result; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs index 6afb1260be..9952c275ab 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs @@ -10,8 +10,9 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class HttpFilePublisher : IFilePublisher { - public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) { + var result = true; var succeeded = 0; var url = deployment.Url; @@ -44,6 +45,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de } else if (response.Content != null) { + result = false; var content = response.Content.ReadAsStringAsync().Result; var msg = "Multipart form data upload failed. {0} ({1}). Response: {2}".FormatInvariant( @@ -70,6 +72,8 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de } context.Log.Information("{0} file(s) successfully uploaded via HTTP ({1}).".FormatInvariant(succeeded, deployment.HttpTransmissionType.ToString())); + + return result; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs index 68f54ce5f6..a6875e85ec 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs @@ -8,7 +8,7 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public interface IFilePublisher { - void Publish(ExportDeploymentContext context, ExportDeployment deployment); + bool Publish(ExportDeploymentContext context, ExportDeployment deployment); } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs index 5fdeee0ffd..179d9dbf66 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs @@ -7,30 +7,34 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class PublicFolderPublisher : IFilePublisher { - public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) { var destinationFolder = deployment.GetPublicFolder(true); + var result = false; - if (destinationFolder.HasValue()) + if (destinationFolder.IsEmpty()) + return false; + + if (context.CreateZipArchive) { - if (context.CreateZipArchive) + if (File.Exists(context.ZipPath)) { - if (File.Exists(context.ZipPath)) - { - var destinationFile = Path.Combine(destinationFolder, Path.GetFileName(context.ZipPath)); + var destinationFile = Path.Combine(destinationFolder, Path.GetFileName(context.ZipPath)); - File.Copy(context.ZipPath, destinationFile, true); + File.Copy(context.ZipPath, destinationFile, true); + result = true; - context.Log.Information("Copied zipped export data to " + destinationFile); - } + context.Log.Information("Copied zipped export data to " + destinationFile); } - else - { - FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); + } + else + { + result = FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); - context.Log.Information("Copied export data files to " + destinationFolder); - } + context.Log.Information("Copied export data files to " + destinationFolder); } + + return result; } } } From fb9ed21563ab471bbbec23176979b9c316575caf Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 21 May 2016 11:57:20 +0200 Subject: [PATCH 083/185] Display result info about last execution of an export deployment --- .../Domain/DataExchange/ExportDeployment.cs | 5 + .../DataExchange/ExportDeploymentMap.cs | 1 + .../201605201342001_ExportRevision.resx | 126 ------------------ ...01605201911421_ExportRevision.Designer.cs} | 2 +- ...n.cs => 201605201911421_ExportRevision.cs} | 12 ++ .../201605201911421_ExportRevision.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 10 +- .../DataExchange/Export/DataExporter.cs | 80 ++++++----- .../Export/Deployment/DataDeploymentResult.cs | 23 ++++ .../Export/Deployment/EmailFilePublisher.cs | 4 +- .../Deployment/FileSystemFilePublisher.cs | 11 +- .../Export/Deployment/FtpFilePublisher.cs | 12 +- .../Export/Deployment/HttpFilePublisher.cs | 12 +- .../Export/Deployment/IFilePublisher.cs | 7 +- .../Deployment/PublicFolderPublisher.cs | 18 ++- .../Export/ExportProfileService.cs | 10 +- .../Export/IExportProfileService.cs | 8 +- .../SmartStore.Services.csproj | 1 + .../Administration/Content/admin.less | 3 - .../Controllers/ExportController.cs | 12 ++ .../DataExchange/ExportDeploymentModel.cs | 18 ++- .../Views/Export/_Tab.Deployment.cshtml | 26 +++- 22 files changed, 321 insertions(+), 206 deletions(-) delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.resx rename src/Libraries/SmartStore.Data/Migrations/{201605201342001_ExportRevision.Designer.cs => 201605201911421_ExportRevision.Designer.cs} (92%) rename src/Libraries/SmartStore.Data/Migrations/{201605201342001_ExportRevision.cs => 201605201911421_ExportRevision.cs} (88%) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.resx create mode 100644 src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/DataDeploymentResult.cs diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs index 2b89d0925a..b9e323c670 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs @@ -20,6 +20,11 @@ public class ExportDeployment : BaseEntity, ICloneable /// public bool Enabled { get; set; } + /// + /// XML with information about the last deployment result + /// + public string ResultInfo { get; set; } + /// /// The deployment type identifier /// diff --git a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportDeploymentMap.cs b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportDeploymentMap.cs index dc38acf13d..2c49aa7d7a 100644 --- a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportDeploymentMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportDeploymentMap.cs @@ -11,6 +11,7 @@ public ExportDeploymentMap() this.HasKey(x => x.Id); this.Property(x => x.Name).IsRequired().HasMaxLength(100); + this.Property(x => x.ResultInfo).IsMaxLength(); this.Property(x => x.Username).HasMaxLength(400); this.Property(x => x.Password).HasMaxLength(400); this.Property(x => x.Url).HasMaxLength(4000); diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.resx b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.resx deleted file mode 100644 index fa683624cb..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.resx +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 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 - - - dbo - - \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.Designer.cs similarity index 92% rename from src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.Designer.cs rename to src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.Designer.cs index 256bd186e0..e3a2d0014f 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.Designer.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.Designer.cs @@ -13,7 +13,7 @@ public sealed partial class ExportRevision : IMigrationMetadata string IMigrationMetadata.Id { - get { return "201605201342001_ExportRevision"; } + get { return "201605201911421_ExportRevision"; } } string IMigrationMetadata.Source diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs similarity index 88% rename from src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs rename to src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs index 23e687029d..4aa3f18e8d 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs @@ -10,6 +10,7 @@ public partial class ExportRevision : DbMigration, ILocaleResourcesProvider, IDa { public override void Up() { + AddColumn("dbo.ExportDeployment", "ResultInfo", c => c.String()); AddColumn("dbo.ExportDeployment", "SubFolder", c => c.String(maxLength: 400)); AlterColumn("dbo.ExportProfile", "FolderName", c => c.String(nullable: false, maxLength: 400)); DropColumn("dbo.ExportDeployment", "CreateZip"); @@ -20,6 +21,7 @@ public override void Down() AddColumn("dbo.ExportDeployment", "CreateZip", c => c.Boolean(nullable: false)); AlterColumn("dbo.ExportProfile", "FolderName", c => c.String(nullable: false, maxLength: 100)); DropColumn("dbo.ExportDeployment", "SubFolder"); + DropColumn("dbo.ExportDeployment", "ResultInfo"); } public bool RollbackOnFailure @@ -93,6 +95,16 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Specifies whether to delete unneeded files after all deployments succeeded.", "Legt fest, ob nicht mehr benötigte Dateien gelöscht werden sollen, nachdem alle Veröffentlichungen erfolgreich waren."); + builder.AddOrUpdate("Admin.Common.FtpStatus", + "FTP status {0} ({1}).", + "FTP-Status {0} ({1})."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.CopyFileFailed", + "At least one file could not be copied.", + "Mindestens eine Datei konnte nicht kopiert werden."); + + builder.AddOrUpdate("Admin.Common.LastRun", "Last run", "Letzte Ausführung"); + builder.AddOrUpdate("Admin.Common.SuccessfulOn", "Successful on", "Erfolgreich am"); builder.Delete( diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.resx b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.resx new file mode 100644 index 0000000000..a653f94065 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index fa7a0999ef..715e87aaf6 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -400,9 +400,9 @@ 201605191216116_CheckoutAttributeMultiStore.cs - - - 201605201342001_ExportRevision.cs + + + 201605201911421_ExportRevision.cs @@ -749,8 +749,8 @@ 201605191216116_CheckoutAttributeMultiStore.cs - - 201605201342001_ExportRevision.cs + + 201605201911421_ExportRevision.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 665babf9c1..eef4bd4712 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -451,58 +451,78 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho private bool Deploy(DataExporterContext ctx, string zipPath) { - var result = true; + var allSucceeded = true; + var deployments = ctx.Request.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled); + + if (deployments.Count() == 0) + return false; + var context = new ExportDeploymentContext { + T = T, Log = ctx.Log, FolderContent = ctx.FolderContent, ZipPath = zipPath, CreateZipArchive = ctx.Request.Profile.CreateZipArchive - }; + }; - foreach (var deployment in ctx.Request.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) + foreach (var deployment in deployments) { - try + IFilePublisher publisher = null; + + context.Result = new DataDeploymentResult { - IFilePublisher publisher = null; + LastExecutionUtc = DateTime.UtcNow + }; - if (deployment.DeploymentType == ExportDeploymentType.Email) - { - publisher = new EmailFilePublisher(_emailAccountService.Value, _queuedEmailService.Value); - } - else if (deployment.DeploymentType == ExportDeploymentType.FileSystem) - { - publisher = new FileSystemFilePublisher(); - } - else if (deployment.DeploymentType == ExportDeploymentType.Ftp) - { - publisher = new FtpFilePublisher(); - } - else if (deployment.DeploymentType == ExportDeploymentType.Http) - { - publisher = new HttpFilePublisher(); - } - else if (deployment.DeploymentType == ExportDeploymentType.PublicFolder) + try + { + switch (deployment.DeploymentType) { - publisher = new PublicFolderPublisher(); + case ExportDeploymentType.Email: + publisher = new EmailFilePublisher(_emailAccountService.Value, _queuedEmailService.Value); + break; + case ExportDeploymentType.FileSystem: + publisher = new FileSystemFilePublisher(); + break; + case ExportDeploymentType.Ftp: + publisher = new FtpFilePublisher(); + break; + case ExportDeploymentType.Http: + publisher = new HttpFilePublisher(); + break; + case ExportDeploymentType.PublicFolder: + publisher = new PublicFolderPublisher(); + break; } if (publisher != null) { - if (!publisher.Publish(context, deployment)) - result = false; + publisher.Publish(context, deployment); + + if (!context.Result.Succeeded) + allSucceeded = false; } } catch (Exception exception) { - result = false; + allSucceeded = false; + + if (context.Result != null) + { + context.Result.LastError = exception.ToAllMessages(); + } ctx.Log.Error("Deployment \"{0}\" of type {1} failed: {2}".FormatInvariant( deployment.Name, deployment.DeploymentType.ToString(), exception.Message), exception); } + + deployment.ResultInfo = XmlHelper.Serialize(context.Result); + + _exportProfileService.Value.UpdateExportDeployment(deployment); } - return result; + return allSucceeded; } private void SendCompletionEmail(DataExporterContext ctx, string zipPath) @@ -1255,7 +1275,7 @@ private void ExportCoreOuter(DataExporterContext ctx) { if (!ctx.IsPreview && ctx.Request.Profile.Id != 0) { - ctx.Request.Profile.ResultInfo = XmlHelper.Serialize(ctx.Result); + ctx.Request.Profile.ResultInfo = XmlHelper.Serialize(ctx.Result); _exportProfileService.Value.UpdateExportProfile(ctx.Request.Profile); } @@ -1269,9 +1289,9 @@ private void ExportCoreOuter(DataExporterContext ctx) { if (ctx.IsFileBasedExport && ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard && ctx.Request.Profile.Cleanup && allDeploymentsSucceeded) { - FileSystemHelper.ClearDirectory(ctx.FolderContent, false); + logger.Information("Cleaning up export folder"); - logger.Information("Cleared up export folder"); + FileSystemHelper.ClearDirectory(ctx.FolderContent, false); } } catch (Exception exception) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/DataDeploymentResult.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/DataDeploymentResult.cs new file mode 100644 index 0000000000..7fff7a46e5 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/DataDeploymentResult.cs @@ -0,0 +1,23 @@ +using System; + +namespace SmartStore.Services.DataExchange.Export.Deployment +{ + [Serializable] + public class DataDeploymentResult + { + /// + /// Whether the deployment succeeded + /// + public bool Succeeded + { + get { return LastError.IsEmpty(); } + } + + public string LastError { get; set; } + + /// + /// Last execution date + /// + public DateTime LastExecutionUtc { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs index 814d34498a..298d476c41 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs @@ -23,7 +23,7 @@ public EmailFilePublisher( _queuedEmailService = queuedEmailService; } - public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) { var emailAccount = _emailAccountService.GetEmailAccountById(deployment.EmailAccountId); var smtpContext = new SmtpContext(emailAccount); @@ -60,8 +60,6 @@ public virtual bool Publish(ExportDeploymentContext context, ExportDeployment de } context.Log.Information("{0} email(s) created and queued for deployment.".FormatInvariant(count)); - - return true; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs index 774cf7dee1..8827ab4aa3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs @@ -7,13 +7,13 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class FileSystemFilePublisher : IFilePublisher { - public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) { string destinationFolder = null; if (deployment.FileSystemPath.IsEmpty()) { - return false; + return; } else if (Path.IsPathRooted(deployment.FileSystemPath)) { @@ -39,11 +39,12 @@ public virtual bool Publish(ExportDeploymentContext context, ExportDeployment de System.IO.Directory.CreateDirectory(destinationFolder); } - FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); + if (!FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder))) + { + context.Result.LastError = context.T("Admin.DataExchange.Export.Deployment.CopyFileFailed"); + } context.Log.Information("Copied export data files to " + destinationFolder); - - return true; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs index 29a6d086b5..7a0954a1c7 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs @@ -9,9 +9,8 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class FtpFilePublisher : IFilePublisher { - public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) { - var result = true; var bytesRead = 0; var succeededFiles = 0; var url = deployment.Url; @@ -62,19 +61,14 @@ public virtual bool Publish(ExportDeploymentContext context, ExportDeployment de } else { - result = false; + context.Result.LastError = context.T("Admin.Common.FtpStatus", statusCode, response.StatusCode.ToString()); - var msg = "The FTP transfer might fail. {0} ({1}), {2}. File {3}".FormatInvariant( - response.StatusCode.ToString(), statusCode, response.StatusDescription.NaIfEmpty(), path); - - context.Log.Error(msg); + context.Log.Error("The FTP transfer failed. FTP status {0} ({1}). File {3}".FormatInvariant(statusCode, response.StatusCode.ToString(), path)); } } } context.Log.Information("{0} file(s) successfully uploaded via FTP.".FormatInvariant(succeededFiles)); - - return result; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs index 9952c275ab..bce4f36bbd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs @@ -10,9 +10,8 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class HttpFilePublisher : IFilePublisher { - public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) { - var result = true; var succeeded = 0; var url = deployment.Url; @@ -45,11 +44,12 @@ public virtual bool Publish(ExportDeploymentContext context, ExportDeployment de } else if (response.Content != null) { - result = false; + context.Result.LastError = context.T("Admin.Common.HttpStatus", (int)response.StatusCode, response.StatusCode.ToString()); + var content = response.Content.ReadAsStringAsync().Result; - var msg = "Multipart form data upload failed. {0} ({1}). Response: {2}".FormatInvariant( - response.StatusCode.ToString(), (int)response.StatusCode, content.NaIfEmpty().Truncate(2000, "...")); + var msg = "Multipart form data upload failed. HTTP status {0} ({1}). Response: {2}".FormatInvariant( + (int)response.StatusCode, response.StatusCode.ToString(), content.NaIfEmpty().Truncate(2000, "...")); context.Log.Error(msg); } @@ -72,8 +72,6 @@ public virtual bool Publish(ExportDeploymentContext context, ExportDeployment de } context.Log.Information("{0} file(s) successfully uploaded via HTTP ({1}).".FormatInvariant(succeeded, deployment.HttpTransmissionType.ToString())); - - return result; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs index a6875e85ec..9bbc852ac1 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs @@ -2,26 +2,29 @@ using System.IO; using System.Linq; using SmartStore.Core.Domain; +using SmartStore.Core.Localization; using SmartStore.Core.Logging; namespace SmartStore.Services.DataExchange.Export.Deployment { public interface IFilePublisher { - bool Publish(ExportDeploymentContext context, ExportDeployment deployment); + void Publish(ExportDeploymentContext context, ExportDeployment deployment); } public class ExportDeploymentContext { + public Localizer T { get; set; } public ILogger Log { get; set; } public string FolderContent { get; set; } public string ZipPath { get; set; } - public bool CreateZipArchive { get; set; } + public DataDeploymentResult Result { get; set; } + public IEnumerable GetDeploymentFiles() { if (!CreateZipArchive) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs index 179d9dbf66..24b874be5f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs @@ -7,13 +7,17 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class PublicFolderPublisher : IFilePublisher { - public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) { var destinationFolder = deployment.GetPublicFolder(true); - var result = false; if (destinationFolder.IsEmpty()) - return false; + return; + + if (!System.IO.Directory.Exists(destinationFolder)) + { + System.IO.Directory.CreateDirectory(destinationFolder); + } if (context.CreateZipArchive) { @@ -22,19 +26,19 @@ public virtual bool Publish(ExportDeploymentContext context, ExportDeployment de var destinationFile = Path.Combine(destinationFolder, Path.GetFileName(context.ZipPath)); File.Copy(context.ZipPath, destinationFile, true); - result = true; context.Log.Information("Copied zipped export data to " + destinationFile); } } else { - result = FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); + if (!FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder))) + { + context.Result.LastError = context.T("Admin.DataExchange.Export.Deployment.CopyFileFailed"); + } context.Log.Information("Copied export data files to " + destinationFolder); } - - return result; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs index 80933dd1a3..f3e8c982b5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs @@ -346,14 +346,20 @@ public virtual ExportDeployment GetExportDeploymentById(int id) return deployment; } + public virtual void UpdateExportDeployment(ExportDeployment deployment) + { + if (deployment == null) + throw new ArgumentNullException("deployment"); + } + public virtual void DeleteExportDeployment(ExportDeployment deployment) { if (deployment == null) throw new ArgumentNullException("deployment"); - _exportDeploymentRepository.Delete(deployment); + _exportDeploymentRepository.Update(deployment); - _eventPublisher.EntityDeleted(deployment); + _eventPublisher.EntityUpdated(deployment); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProfileService.cs index 7a61a36def..b0b0c8a6bd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProfileService.cs @@ -108,7 +108,13 @@ ExportProfile InsertExportProfile( ExportDeployment GetExportDeploymentById(int id); /// - /// Deleted a export deployment + /// Update an export deployment + /// + /// Export deployment + void UpdateExportDeployment(ExportDeployment deployment); + + /// + /// Deleted an export deployment /// /// Export deployment void DeleteExportDeployment(ExportDeployment deployment); diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 5bd7ee173b..1edfe58738 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -174,6 +174,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index 6da44acf8d..dfa4145f39 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -911,9 +911,6 @@ td.adminSeparator hr { .disabled { .muted(); } - .centered { - text-align: center; - } .progress-info { min-width: 260px; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 5de25bbd28..9d652d6ccf 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -24,6 +24,7 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Customers; using SmartStore.Services.DataExchange.Export; +using SmartStore.Services.DataExchange.Export.Deployment; using SmartStore.Services.Directory; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; @@ -322,6 +323,17 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile deploymentModel.ProfileDetails = PrepareProfileDetailsModel(profile, true); + if (x.ResultInfo.HasValue()) + { + var resultInfo = XmlHelper.Deserialize(x.ResultInfo); + + deploymentModel.LastResult = new ExportDeploymentModel.LastResultInfo + { + Execution = _dateTimeHelper.ConvertToUserTime(resultInfo.LastExecutionUtc, DateTimeKind.Utc), + ExecutionPretty = resultInfo.LastExecutionUtc.RelativeFormat(true, "f"), + Error = resultInfo.LastError + }; + } return deploymentModel; }) .ToList(); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs index 7d06e197a2..6c3c643cdc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs @@ -1,9 +1,11 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Admin.Validators.DataExchange; using SmartStore.Core.Domain.DataExchange; +using SmartStore.Services.DataExchange.Export.Deployment; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; @@ -91,5 +93,19 @@ public string DeploymentTypeIconClass public bool UseSsl { get; set; } public ExportProfileDetailsModel ProfileDetails { get; set; } + + public LastResultInfo LastResult { get; set; } + + public class LastResultInfo + { + public DateTime Execution { get; set; } + public string ExecutionPretty { get; set; } + public string Error { get; set; } + + public bool Succeeded + { + get { return Error.IsEmpty(); } + } + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml index dd67e6dd24..6ad62bfa39 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml @@ -111,23 +111,41 @@
@T("Admin.DataExchange.Export.Deployment.Name")@T("Common.Enabled") @T("Admin.DataExchange.Export.Deployment.DeploymentType")@T("Common.Enabled")@T("Admin.Common.LastRun") @T("Admin.Common.Actions")
@deployment.Name + + @Html.Partial("InfoDeployment", deployment) + @Html.SymbolForBool(deployment.Enabled) - @Html.Partial("InfoDeployment", deployment) + + @if (result == null) + { +
@T("Common.Never")
+ } + else if (result.Succeeded) + { +
@(T("Admin.Common.SuccessfulOn")): @result.Execution.ToString("g")
+
@result.ExecutionPretty
+ } + else + { +
@result.Execution.ToString("g")
+
@T("Common.Error"): @result.Error
+ }
From 81b653776d9c84d1e6fe4c88b456081bfef273e4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 21 May 2016 12:04:20 +0200 Subject: [PATCH 084/185] Forgot a method --- .../DataExchange/Export/ExportProfileService.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs index f3e8c982b5..d991b2cd42 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs @@ -350,6 +350,10 @@ public virtual void UpdateExportDeployment(ExportDeployment deployment) { if (deployment == null) throw new ArgumentNullException("deployment"); + + _exportDeploymentRepository.Update(deployment); + + _eventPublisher.EntityUpdated(deployment); } public virtual void DeleteExportDeployment(ExportDeployment deployment) @@ -357,9 +361,9 @@ public virtual void DeleteExportDeployment(ExportDeployment deployment) if (deployment == null) throw new ArgumentNullException("deployment"); - _exportDeploymentRepository.Update(deployment); + _exportDeploymentRepository.Delete(deployment); - _eventPublisher.EntityUpdated(deployment); + _eventPublisher.EntityDeleted(deployment); } } } From 76669070cd3b3af1891faf3d88ec77035d40a0a4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 23 May 2016 11:21:12 +0200 Subject: [PATCH 085/185] Improved export deployment --- .../Utilities/FileSystemHelper.cs | 22 +++++ .../201605201911421_ExportRevision.cs | 32 ++++++- .../DataExchange/Export/DataExporter.cs | 3 - .../Deployment/FileSystemFilePublisher.cs | 11 +-- .../Export/ExportExecuteContext.cs | 5 - .../DataExchange/Export/ExportExtensions.cs | 58 +++++++++++ .../Export/ExportProfileService.cs | 8 +- .../Administration/Content/admin.less | 7 ++ .../Controllers/ExportController.cs | 4 +- .../DataExchange/ExportDeploymentModel.cs | 26 +---- .../Administration/SmartStore.Admin.csproj | 1 - .../Views/Export/InfoDeployment.cshtml | 54 ----------- .../Export/_CreateOrUpdate.Deployment.cshtml | 51 ++++++++-- .../Views/Export/_ProfileList.cshtml | 8 +- .../Views/Export/_Tab.Deployment.cshtml | 96 +++++++++++++------ 15 files changed, 235 insertions(+), 151 deletions(-) delete mode 100644 src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml diff --git a/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs b/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs index cf778b71ff..3fbb4f83f8 100644 --- a/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs +++ b/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs @@ -31,6 +31,28 @@ public static string TempDir(string subDirectory = null) return path; } + /// + /// Ensures that path is a valid root path + /// + /// Relative path + /// Valid root path + public static string ValidateRootPath(string path) + { + if (path.HasValue()) + { + path = path.Replace('\\', '/'); + + if (!path.StartsWith("~/")) + { + if (path.StartsWith("~")) + path = path.Substring(1); + + path = (path.StartsWith("/") ? "~" : "~/") + path; + } + } + return path; + } + /// /// Safe way to cleanup the temp directory. Should be called via scheduled task. /// diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs index 4aa3f18e8d..1af4e3b589 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs @@ -82,8 +82,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.SubFolder", "Name of subfolder", "Name des Unterordners", - "Specifies the name of a subfolder where to deploy the data.", - "Legt den Namen eines Unterordners fest, in den die Daten bereitgestellt werden sollen."); + "Specifies the name of a subfolder where to publish the data.", + "Legt den Namen eines Unterordners fest, in den die Daten veröffentlicht werden sollen."); builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.ZipUsageNote", "If there are a large number of export files, it is recommended to use the option Create ZIP archive. This saves time and avoids problems, such as a full email mailbox.", @@ -106,6 +106,34 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Common.LastRun", "Last run", "Letzte Ausführung"); builder.AddOrUpdate("Admin.Common.SuccessfulOn", "Successful on", "Erfolgreich am"); + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.Name", + "Name of profile", + "Name des Profils", + "Specifies the name of the publishing profile.", + "Legt den Namen des Veröffentlichungsprofils fest."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.ProfilesTitle", + "Publishing profiles", + "Veröffentlichungsprofile"); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.NoProfiles", + "There are no publishing profiles.", + "Es liegen keine Veröffentlichungsprofile vor."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.Note", + "Click New profile to add one or multiple publishing profiles to specify how to further proceed with the export files.", + "Legen Sie über Neues Profil ein oder mehrere Veröffentlichungsprofile an, um festzulegen wie mit den Exportdateien weiter zu verfahren ist."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.PublishingTarget", + "Publishing target", + "Veröffentlichungsziel"); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.DeploymentType", + "Publishing type", + "Art der Veröffentlichung", + "Specifies the type of publishing.", + "Legt die Art Veröffentlichung fest."); + builder.Delete( "Admin.DataExchange.Export.FolderAndFileName.Validate", diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index eef4bd4712..79cba8794d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -1109,9 +1109,6 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) ctx.ExecuteContext.FileName = resolvedPattern + fileExtension; path = Path.Combine(ctx.ExecuteContext.Folder, ctx.ExecuteContext.FileName); - - if (ctx.ExecuteContext.HasPublicDeployment) - ctx.ExecuteContext.PublicFileUrl = ctx.Store.Url.EnsureEndsWith("/") + PublicFolder.EnsureEndsWith("/") + ctx.ExecuteContext.FileName; } if (CallProvider(ctx, null, "Execute", path)) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs index 8827ab4aa3..d769ac2a6b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs @@ -21,16 +21,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de } else { - destinationFolder = deployment.FileSystemPath; - - if (!destinationFolder.StartsWith("~/")) - { - if (destinationFolder.StartsWith("~")) - destinationFolder = destinationFolder.Substring(1); - - destinationFolder = (destinationFolder.StartsWith("/") ? "~" : "~/") + destinationFolder; - } - + destinationFolder = FileSystemHelper.ValidateRootPath(deployment.FileSystemPath); destinationFolder = CommonHelper.MapPath(destinationFolder); } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs index 6c25d397bd..d5f2860ff8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs @@ -120,11 +120,6 @@ public bool IsMaxFailures /// public string PublicFolderPath { get; internal set; } - /// - /// The public URL of the export file (accessible through the internet). null if the profile has no public deployment. - /// - public string PublicFileUrl { get; internal set; } - /// /// Provider specific configuration data /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs index 29a45613ad..3ac52e5b6b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Web; +using SmartStore.Core; using SmartStore.Core.Domain; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Stores; @@ -157,5 +158,62 @@ public static string GetPublicFolder(this ExportDeployment deployment, bool crea return path; } + + /// + /// Get url of the public folder and take filtering and projection into consideration + /// + /// Export deployment + /// Common services + /// Absolute URL of the public folder + public static string GetPublicFolderUrl(this ExportDeployment deployment, ICommonServices services) + { + if (deployment != null && deployment.DeploymentType == ExportDeploymentType.PublicFolder) + { + var filter = XmlHelper.Deserialize(deployment.Profile.Filtering); + var storeId = filter.StoreId; + + if (storeId == 0) + { + var projection = XmlHelper.Deserialize(deployment.Profile.Projection); + storeId = (projection.StoreId ?? 0); + } + + var store = (storeId == 0 ? services.StoreContext.CurrentStore : services.StoreService.GetStoreById(storeId)); + + var url = string.Concat( + store.Url.EnsureEndsWith("/"), + DataExporter.PublicFolder.EnsureEndsWith("/"), + deployment.SubFolder.HasValue() ? deployment.SubFolder.EnsureEndsWith("/") : "" + ); + + return url; + } + + return null; + } + + /// + /// Get icon class for a deployment type + /// + /// Deployment type + /// Icon class + public static string GetIconClass(this ExportDeploymentType type) + { + switch (type) + { + case ExportDeploymentType.FileSystem: + return "fa-folder-open-o"; + case ExportDeploymentType.Email: + return "fa-envelope-o"; + case ExportDeploymentType.Http: + return "fa-globe"; + case ExportDeploymentType.Ftp: + return "fa-files-o"; + case ExportDeploymentType.PublicFolder: + return "fa-unlock"; + default: + return "fa-question"; + } + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs index d991b2cd42..2b9b6b5757 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs @@ -220,13 +220,7 @@ public virtual void UpdateExportProfile(ExportProfile profile) if (profile == null) throw new ArgumentNullException("profile"); - if (!profile.FolderName.StartsWith("~/")) - { - if (profile.FolderName.StartsWith("~")) - profile.FolderName = profile.FolderName.Substring(1); - - profile.FolderName = (profile.FolderName.StartsWith("/") ? "~" : "~/") + profile.FolderName; - } + profile.FolderName = FileSystemHelper.ValidateRootPath(profile.FolderName); _exportProfileRepository.Update(profile); diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index dfa4145f39..c31aff72bc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -1580,6 +1580,13 @@ table.payment-method-features td { } +/* Data exchange +-------------------------------------------------------------- */ +.profile-list { + margin-bottom: 10px; + border-bottom: 1px solid #ddd; +} + /* Import column mapping -------------------------------------------------------------- */ @media (max-width: 1280px) { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 9d652d6ccf..d109ac91cb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -321,8 +321,6 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile { var deploymentModel = PrepareDeploymentModel(profile, x, null, false); - deploymentModel.ProfileDetails = PrepareProfileDetailsModel(profile, true); - if (x.ResultInfo.HasValue()) { var resultInfo = XmlHelper.Deserialize(x.ResultInfo); @@ -452,6 +450,8 @@ private ExportDeploymentModel PrepareDeploymentModel(ExportProfile profile, Expo UseSsl = deployment.UseSsl }; + model.PublicFolderUrl = deployment.GetPublicFolderUrl(Services); + if (forEdit) { var allEmailAccounts = _emailAccountService.GetAllEmailAccounts(); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs index 6c3c643cdc..5392139e71 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs @@ -5,7 +5,6 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.DataExchange; using SmartStore.Core.Domain.DataExchange; -using SmartStore.Services.DataExchange.Export.Deployment; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; @@ -16,6 +15,7 @@ public class ExportDeploymentModel : EntityModelBase { public int ProfileId { get; set; } public bool CreateZip { get; set; } + public string PublicFolderUrl { get; set; } [SmartResourceDisplayName("Common.Image")] public string ThumbnailUrl { get; set; } @@ -33,28 +33,6 @@ public class ExportDeploymentModel : EntityModelBase [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.DeploymentType")] public string DeploymentTypeName { get; set; } - public string DeploymentTypeIconClass - { - get - { - switch (DeploymentType) - { - case ExportDeploymentType.FileSystem: - return "fa-folder-open-o"; - case ExportDeploymentType.Email: - return "fa-envelope-o"; - case ExportDeploymentType.Http: - return "fa-globe"; - case ExportDeploymentType.Ftp: - return "fa-files-o"; - case ExportDeploymentType.PublicFolder: - return "fa-unlock"; - default: - return "fa-question"; - } - } - } - [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.Username")] public string Username { get; set; } @@ -92,8 +70,6 @@ public string DeploymentTypeIconClass [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.UseSsl")] public bool UseSsl { get; set; } - public ExportProfileDetailsModel ProfileDetails { get; set; } - public LastResultInfo LastResult { get; set; } public class LastResultInfo diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index 2dad6d6fe4..15852be9a4 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -508,7 +508,6 @@ - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml deleted file mode 100644 index f111f30257..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml +++ /dev/null @@ -1,54 +0,0 @@ -@using SmartStore.Core.Domain.DataExchange -@using SmartStore.Admin.Models.DataExchange -@model ExportDeploymentModel -@{ - Layout = null; -} -@helper FileDownloadLink(string path, string fileName) -{ - -} -
-  @Model.DeploymentTypeName -
-
- @if (Model.DeploymentType == ExportDeploymentType.FileSystem) - { - foreach (var path in Model.ProfileDetails.ExportFiles) - { - @FileDownloadLink(path, Path.GetFileName(path)) - } - - if (Model.ProfileDetails.PublicFiles.Count > 0) - { - foreach (var grp in Model.ProfileDetails.PublicFiles.OrderBy(x => x.StoreId).GroupBy(x => x.StoreId)) - { -

-
@(grp.First().StoreName):
- foreach (var file in grp) - { - - } - } - } - } - else if (Model.DeploymentType == ExportDeploymentType.Email) - { -
@Model.EmailAddresses
- } - else if (Model.DeploymentType == ExportDeploymentType.Http) - { -
@Model.Url
- } - else if (Model.DeploymentType == ExportDeploymentType.Ftp) - { -
@Model.Url
- } - else if (Model.DeploymentType == ExportDeploymentType.PublicFolder) - { -
TODO
- } -
\ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml index 05765e96e4..b4ad8eece7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml @@ -1,6 +1,7 @@ -@model ExportDeploymentModel -@using SmartStore.Core.Domain.DataExchange; -@using SmartStore.Admin.Models.DataExchange; +@using SmartStore.Core.Domain.DataExchange; +@using SmartStore.Services.DataExchange.Export +@using SmartStore.Admin.Models.DataExchange +@model ExportDeploymentModel @Html.ValidationSummary(false) @@ -10,6 +11,7 @@ @if (!Model.CreateZip) {
+ @Html.Raw(T("Admin.DataExchange.Export.Deployment.ZipUsageNote"))
} @@ -20,9 +22,14 @@ @Html.SmartLabelFor(x => x.DeploymentType)
- @Html.DropDownListFor(x => x.DeploymentType, Model.AvailableDeploymentTypes) + @Html.ValidationMessageFor(x => x.DeploymentType) -
+
@@ -85,14 +85,14 @@
} - else { + else + {
@T("Admin.DataExchange.Export.NoProfiles")
} - @if (!isSystemProfiles) { - +   @T("Admin.DataExchange.AddNewProfile") } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml index 6ad62bfa39..ea84580fbe 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml @@ -1,5 +1,7 @@ -@model ExportProfileModel +@using SmartStore.Core.Domain.DataExchange +@using SmartStore.Services.DataExchange.Export @using SmartStore.Admin.Models.DataExchange +@model ExportProfileModel @@ -55,6 +57,7 @@ @Html.ValidationMessageFor(x => x.CreateZipArchive) @if (Model.Details.ZipPath.HasValue()) { + @*TODO: move*@
@Html.IconForFileExtension(Path.GetExtension(Model.Details.ZipPath), false) @Path.GetFileName(Model.Details.ZipPath) @@ -62,6 +65,24 @@ }
+ + + + + + + + } - - - - - - - -
+ @Html.SmartLabelFor(x => x.EmailAccountId) + + @Html.DropDownListFor(x => x.EmailAccountId, Model.AvailableEmailAccounts, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.EmailAccountId) +
+ @Html.SmartLabelFor(x => x.CompletedEmailAddresses) + + @Html.HiddenFor(model => model.CompletedEmailAddresses) + @Html.ValidationMessageFor(x => x.CompletedEmailAddresses) +
@Html.SmartLabelFor(x => x.Cleanup) @@ -72,47 +93,31 @@
- @Html.SmartLabelFor(x => x.EmailAccountId) - - @Html.DropDownListFor(x => x.EmailAccountId, Model.AvailableEmailAccounts, Model.UnspecifiedString) - @Html.ValidationMessageFor(x => x.EmailAccountId) -
- @Html.SmartLabelFor(x => x.CompletedEmailAddresses) - - @Html.HiddenFor(model => model.CompletedEmailAddresses) - @Html.ValidationMessageFor(x => x.CompletedEmailAddresses) -

 

@if (Model.Provider.IsFileBasedExport) { +
+
@T("Admin.DataExchange.Export.Deployment.ProfilesTitle")
+
+
+ @Html.Raw(T("Admin.DataExchange.Export.Deployment.Note"))
- -  @T("Common.AddNew") - - if (Model.Deployments.Count > 0) {
- +
- + + @@ -123,14 +128,37 @@ var result = deployment.LastResult; +
@T("Admin.DataExchange.Export.Deployment.Name")@T("Admin.DataExchange.Export.Deployment.DeploymentType")@T("Admin.DataExchange.Export.Deployment.PublishingTarget") @T("Common.Enabled")@T("Admin.DataExchange.Export.ExportFiles") @T("Admin.Common.LastRun") @T("Admin.Common.Actions")
- @deployment.Name + +
+  @deployment.DeploymentTypeName +
- @Html.Partial("InfoDeployment", deployment) + @if (deployment.DeploymentType == ExportDeploymentType.FileSystem) + { + @deployment.FileSystemPath.NaIfEmpty() + } + else if (deployment.DeploymentType == ExportDeploymentType.Email) + { + @deployment.EmailAddresses.NaIfEmpty() + } + else if (deployment.DeploymentType == ExportDeploymentType.Http || deployment.DeploymentType == ExportDeploymentType.Ftp) + { + @deployment.Url.NaIfEmpty() + } + else if (deployment.DeploymentType == ExportDeploymentType.PublicFolder) + { + @deployment.PublicFolderUrl.NaIfEmpty() + } @Html.SymbolForBool(deployment.Enabled) + TODO + @if (result == null) { @@ -144,7 +172,7 @@ else {
@result.Execution.ToString("g")
-
@T("Common.Error"): @result.Error
+
@T("Common.Error"): @result.Error
}
@@ -158,6 +186,14 @@
} + else + { +
@T("Admin.DataExchange.Export.NoProfiles")
+ } + + +   @T("Admin.DataExchange.AddNewProfile") + } else { @@ -209,7 +245,7 @@ else @Model.FileNamePatternDescriptions.SafeGet(7) - + } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml index d7a7cddc7d..bd410950b6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml @@ -51,7 +51,7 @@ @Html.IconForFileExtension(profile.Provider.FileExtension, true) - @Html.Partial("ProfileFileCount", profile.Details.ExportFileCount) + @Html.Partial("ProfileFileCount", profile.ExportFileCount)
diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml index ea84580fbe..f47ba97ff5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml @@ -55,14 +55,6 @@ @Html.EditorFor(x => x.CreateZipArchive) @Html.ValidationMessageFor(x => x.CreateZipArchive) - @if (Model.Details.ZipPath.HasValue()) - { - @*TODO: move*@ -
- @Html.IconForFileExtension(Path.GetExtension(Model.Details.ZipPath), false) - @Path.GetFileName(Model.Details.ZipPath) -
- } diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js index 1765045a54..ff2f5be51f 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js @@ -68,6 +68,38 @@ return spinner; } + window.copyTextToClipboard = function (text) { + var result = false; + + if (window.clipboardData && window.clipboardData.setData) { + result = clipboardData.setData('Text', text); + } + else if (document.queryCommandSupported && document.queryCommandSupported('copy')) { + var textarea = document.createElement('textarea'), + focusElement = document.activeElement; + + textarea.textContent = text; + textarea.style.position = 'fixed'; + document.body.appendChild(textarea); + textarea.focus(); + textarea.setSelectionRange(0, textarea.value.length); + + try { + result = document.execCommand('copy'); + } + catch (e) { + } + finally { + document.body.removeChild(textarea); + if (focusElement) { + focusElement.focus(); + } + } + } + return result; + } + + // on document ready $(function () { From 3df4795cd410b30fbfebde287ccc54f1610c6682 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 24 May 2016 14:50:54 +0200 Subject: [PATCH 089/185] Updated changelog --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 9502bd2eb2..8cf0b81d05 100644 --- a/changelog.md +++ b/changelog.md @@ -31,7 +31,7 @@ * Fixed ajax cache issue when saving payment or shipping restrictions. Internet Explorer showed the old data state (before storage). * "The provider failed at the Execute method: Member 'CurrentValues' cannot be called for the entity of type 'Product'" when exporting product attribute combinations * Bundles without selected attributes could throw an exception on product detail page -* GMC feed did not export the product type (category path) +* GMC feed did not export the product type and Billiger did not export shop_cat (category path) * The error message of a payment provider when capturing a payment was not displayed * Adding new shipping method threw an exception * Attribute Values: Assigning IsPreselected to more than one value causes an error From 3880d011dc4c555d2d98bb625e1327e22cd66799 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 24 May 2016 15:28:37 +0200 Subject: [PATCH 090/185] Updated a string resource --- .../201605201911421_ExportRevision.cs | 4 ++++ .../Product/_BundleItemsAddToCart.cshtml | 20 +++++-------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs index b3b3c3e659..782060d577 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs @@ -146,6 +146,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Common.CopyToClipboard.Succeeded", "Copied!", "Kopiert!"); builder.AddOrUpdate("Common.CopyToClipboard.Failded", "Failed to copy.", "Kopieren ist fehlgeschlagen."); + builder.AddOrUpdate("Products.NoBundledItems", + "No bundle items available", + "Keine Bundle-Bestandteile vorhanden"); + builder.Delete( "Admin.DataExchange.Export.FolderAndFileName.Validate", diff --git a/src/Presentation/SmartStore.Web/Views/Product/_BundleItemsAddToCart.cshtml b/src/Presentation/SmartStore.Web/Views/Product/_BundleItemsAddToCart.cshtml index 004d08faa1..2163c9526a 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/_BundleItemsAddToCart.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/_BundleItemsAddToCart.cshtml @@ -10,16 +10,6 @@
@if (!Model.AddToCart.DisableBuyButton) { - @*@Html.LabelFor(model => Model.AddToCart.EnteredQuantity, new { @class = "qty-label" }): - if (Model.AddToCart.AllowedQuantities.Count > 0) - { - @Html.DropDownListFor(model => Model.AddToCart.EnteredQuantity, Model.AddToCart.AllowedQuantities, new { @class = "qty-dropdown" }) - } - else - { - @Html.TextBoxFor(model => Model.AddToCart.EnteredQuantity, new { @class = "qty-input", style = "width:25px" }) - }*@ - @{ - int i = 0; - int bundleItemAmount = Model.BundledItems.Count; + var i = 0; + var bundleItemCount = Model.BundledItems.Where(x => x.BundleItem.Visible).Count(); } @foreach (var item in Model.BundledItems.Where(x => x.BundleItem.Visible)) @@ -77,10 +67,10 @@ if (item.BundleItem.Quantity > 1) { נ@item.BundleItem.Quantity - } + } - if (bundleItemAmount != (i + 1)) - { + if (bundleItemCount != (i + 1)) + { } From 4efec983406df2898fcf8daeb5c9da0fc1d692ac Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 24 May 2016 20:31:01 +0200 Subject: [PATCH 091/185] Display public zip url in export file dialog --- .../Controllers/ExportController.cs | 135 ++++++++++-------- .../Models/DataExchange/ExportProfileModel.cs | 33 ++--- .../Views/Export/ProfileFileDetails.cshtml | 70 ++++----- 3 files changed, 122 insertions(+), 116 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 5fb9aa55f9..63fade3ccf 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Net.Mime; using System.Text; -using System.Web; using System.Web.Mvc; using SmartStore.Admin.Extensions; using SmartStore.Admin.Models.DataExchange; @@ -20,7 +19,6 @@ using SmartStore.Core.Domain.Stores; using SmartStore.Core.IO; using SmartStore.Core.Plugins; -using SmartStore.Services; using SmartStore.Services.Catalog; using SmartStore.Services.Customers; using SmartStore.Services.DataExchange.Export; @@ -44,7 +42,6 @@ namespace SmartStore.Admin.Controllers [AdminAuthorize] public class ExportController : AdminControllerBase { - private readonly ICommonServices _services; private readonly IExportProfileService _exportService; private readonly PluginMediator _pluginMediator; private readonly ICategoryService _categoryService; @@ -62,7 +59,6 @@ public class ExportController : AdminControllerBase private readonly Lazy _customerSettings; public ExportController( - ICommonServices services, IExportProfileService exportService, PluginMediator pluginMediator, ICategoryService categoryService, @@ -79,7 +75,6 @@ public ExportController( IDataExporter dataExporter, Lazy customerSettings) { - _services = services; _exportService = exportService; _pluginMediator = pluginMediator; _categoryService = categoryService; @@ -114,51 +109,77 @@ private string GetThumbnailUrl(Provider provider) return url; } + private void AddFileInfo(List list, string path, string publicFolderUrl = null, Store store = null) + { + if (System.IO.File.Exists(path) && !list.Any(x => x.FilePath == path)) + { + var fileInfo = new ExportProfileDetailsModel.FileInfo(); + fileInfo.FilePath = path; + fileInfo.FileName = Path.GetFileName(path); + fileInfo.FileExtension = Path.GetExtension(path); + fileInfo.DisplayOrder = (fileInfo.FileExtension.IsCaseInsensitiveEqual(".zip") ? 0 : 1); + + if (store != null) + { + fileInfo.StoreId = store.Id; + fileInfo.StoreName = store.Name; + } + + if (publicFolderUrl.HasValue()) + { + fileInfo.FileUrl = publicFolderUrl + fileInfo.FileName; + } + + list.Add(fileInfo); + } + } + private ExportProfileDetailsModel PrepareProfileDetailsModel(ExportProfile profile, Provider provider) { var model = new ExportProfileDetailsModel { Id = profile.Id, - DownloadString = T("Common.Download"), + ExportFiles = new List(), PublicFiles = new List() }; try { - var zipPath = profile.GetExportZipPath(); var publicDeployment = profile.Deployments.FirstOrDefault(x => x.DeploymentType == ExportDeploymentType.PublicFolder); + var zipPath = profile.GetExportZipPath(); + + // add export files + AddFileInfo(model.ExportFiles, zipPath); - if (System.IO.File.Exists(zipPath)) + foreach (var path in profile.GetExportFiles(provider)) { - model.ZipPath = new ExportProfileDetailsModel.FileInfo { FilePath = zipPath }; + AddFileInfo(model.ExportFiles, path); } - model.ExportFiles = profile.GetExportFiles(provider) - .Select(x => new ExportProfileDetailsModel.FileInfo { FilePath = x }) - .ToList(); - + // add public files if (publicDeployment != null) { - var allStores = _services.StoreService.GetAllStores(); - var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); + var currentStore = Services.StoreContext.CurrentStore; + var allStores = Services.StoreService.GetAllStores(); var publicFolder = publicDeployment.GetPublicFolder(); + var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); + + AddFileInfo( + model.PublicFiles, + Path.Combine(publicFolder, Path.GetFileName(zipPath)), + publicDeployment.GetPublicFolderUrl(Services, currentStore)); if (resultInfo != null && resultInfo.Files != null) { - foreach (var fileInfo in resultInfo.Files) + foreach (var file in resultInfo.Files) { - if (System.IO.File.Exists(Path.Combine(publicFolder, fileInfo.FileName)) && !model.PublicFiles.Any(x => x.FileName == fileInfo.FileName)) - { - var store = allStores.FirstOrDefault(x => x.Id == fileInfo.StoreId) ?? _services.StoreContext.CurrentStore; - var publicFolderUrl = publicDeployment.GetPublicFolderUrl(Services, store); - - model.PublicFiles.Add(new ExportProfileDetailsModel.FileInfo - { - StoreId = store.Id, - StoreName = store.Name, - FileUrl = publicFolderUrl + fileInfo.FileName - }); - } + var store = allStores.FirstOrDefault(x => x.Id == file.StoreId) ?? currentStore; + + AddFileInfo( + model.PublicFiles, + Path.Combine(publicFolder, file.FileName), + publicDeployment.GetPublicFolderUrl(Services, store), + store); } } } @@ -208,7 +229,7 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile model.Provider.FriendlyName = _pluginMediator.GetLocalizedFriendlyName(provider.Metadata); model.Provider.Description = _pluginMediator.GetLocalizedDescription(provider.Metadata); model.Provider.EntityType = provider.Value.EntityType; - model.Provider.EntityTypeName = provider.Value.EntityType.GetLocalizedEnum(_services.Localization, _services.WorkContext); + model.Provider.EntityTypeName = provider.Value.EntityType.GetLocalizedEnum(Services.Localization, Services.WorkContext); model.Provider.FileExtension = provider.Value.FileExtension; } } @@ -218,10 +239,10 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile var filter = XmlHelper.Deserialize(profile.Filtering); var projection = XmlHelper.Deserialize(profile.Projection); - var language = _services.WorkContext.WorkingLanguage; - var store = _services.StoreContext.CurrentStore; + var language = Services.WorkContext.WorkingLanguage; + var store = Services.StoreContext.CurrentStore; - var allStores = _services.StoreService.GetAllStores(); + var allStores = Services.StoreService.GetAllStores(); var allLanguages = _languageService.GetAllLanguages(true); var allCurrencies = _currencyService.GetAllCurrencies(true); var allEmailAccounts = _emailAccountService.GetAllEmailAccounts(); @@ -444,7 +465,7 @@ private ExportDeploymentModel PrepareDeploymentModel(ExportProfile profile, Expo Name = deployment.Name, Enabled = deployment.Enabled, DeploymentType = deployment.DeploymentType, - DeploymentTypeName = deployment.DeploymentType.GetLocalizedEnum(_services.Localization, _services.WorkContext), + DeploymentTypeName = deployment.DeploymentType.GetLocalizedEnum(Services.Localization, Services.WorkContext), Username = deployment.Username, Password = deployment.Password, Url = deployment.Url, @@ -527,7 +548,7 @@ public ActionResult Index() public ActionResult List() { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); var providers = _exportService.LoadAllExportProviders(0, false).ToList(); @@ -543,7 +564,7 @@ public ActionResult List() PrepareProfileModel(profileModel, profile, provider); - profileModel.TaskModel = profile.ScheduleTask.ToScheduleTaskModel(_services.Localization, _dateTimeHelper, Url); + profileModel.TaskModel = profile.ScheduleTask.ToScheduleTaskModel(Services.Localization, _dateTimeHelper, Url); model.Add(profileModel); } @@ -554,7 +575,7 @@ public ActionResult List() public ActionResult ProfileListDetails(int profileId) { - if (_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) { var profile = _exportService.GetExportProfileById(profileId); if (profile != null) @@ -574,7 +595,7 @@ public ActionResult ProfileListDetails(int profileId) public ActionResult ProfileFileDetails(int profileId) { - if (_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) { var profile = _exportService.GetExportProfileById(profileId); if (profile != null) @@ -594,7 +615,7 @@ public ActionResult ProfileFileDetails(int profileId) public ActionResult Create() { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return Content(T("Admin.AccessDenied.Description")); var count = 0; @@ -641,7 +662,7 @@ public ActionResult Create() [HttpPost] public ActionResult Create(ExportProfileModel model) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); if (model.ProviderSystemName.HasValue()) @@ -662,7 +683,7 @@ public ActionResult Create(ExportProfileModel model) public ActionResult Edit(int id) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); var profile = _exportService.GetExportProfileById(id); @@ -685,7 +706,7 @@ public ActionResult Edit(int id) [FormValueRequired("save", "save-continue")] public ActionResult Edit(ExportProfileModel model, bool continueEditing) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); var profile = _exportService.GetExportProfileById(model.Id); @@ -818,7 +839,7 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); var profile = _exportService.GetExportProfileById(id); @@ -847,7 +868,7 @@ public ActionResult DeleteConfirmed(int id) public ActionResult Preview(int id) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); var profile = _exportService.GetExportProfileById(id); @@ -886,7 +907,7 @@ public ActionResult Preview(int id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult PreviewList(GridCommand command, int id, int totalRecords) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) { NotifyAccessDenied(); @@ -929,7 +950,7 @@ public ActionResult PreviewList(GridCommand command, int id, int totalRecords) { Id = product.Id, ProductTypeId = product.ProductTypeId, - ProductTypeName = product.GetProductTypeLabel(_services.Localization), + ProductTypeName = product.GetProductTypeLabel(Services.Localization), ProductTypeLabelHint = product.ProductTypeLabelHint, Name = item.Name, Sku = item.Sku, @@ -1010,7 +1031,7 @@ public ActionResult PreviewList(GridCommand command, int id, int totalRecords) var subscription = item.Entity as NewsLetterSubscription; if (allStores == null) - allStores = _services.StoreService.GetAllStores(); + allStores = Services.StoreService.GetAllStores(); var store = allStores.FirstOrDefault(x => x.Id == subscription.StoreId); @@ -1070,7 +1091,7 @@ public ActionResult Execute(int id, string selectedIds) return RedirectToAction("List"); var taskParams = new Dictionary(); - taskParams.Add(TaskExecutor.CurrentCustomerIdParamName, _services.WorkContext.CurrentCustomer.Id.ToString()); + taskParams.Add(TaskExecutor.CurrentCustomerIdParamName, Services.WorkContext.CurrentCustomer.Id.ToString()); if (selectedIds.HasValue()) taskParams.Add("SelectedIds", selectedIds); @@ -1118,7 +1139,7 @@ public ActionResult InfoProfile(string systemName, string returnUrl) public ActionResult DownloadLogFile(int id) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); var profile = _exportService.GetExportProfileById(id); @@ -1148,7 +1169,7 @@ public ActionResult DownloadExportFile(int id, string name) { string message = null; - if (_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) { var profile = _exportService.GetExportProfileById(id); if (profile != null) @@ -1193,10 +1214,10 @@ public ActionResult ResolveFileNamePatternExample(int id, string pattern) { var profile = _exportService.GetExportProfileById(id); - _services.DbContext.DetachEntity(profile); + Services.DbContext.DetachEntity(profile); profile.FileNamePattern = pattern.EmptyNull(); - var resolvedPattern = profile.ResolveFileNamePattern(_services.StoreContext.CurrentStore, 1, _dataExchangeSettings.MaxFileNameLength); + var resolvedPattern = profile.ResolveFileNamePattern(Services.StoreContext.CurrentStore, 1, _dataExchangeSettings.MaxFileNameLength); return this.Content(resolvedPattern); } @@ -1205,7 +1226,7 @@ public ActionResult ResolveFileNamePatternExample(int id, string pattern) public ActionResult CreateDeployment(int id) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); var profile = _exportService.GetExportProfileById(id); @@ -1216,7 +1237,7 @@ public ActionResult CreateDeployment(int id) if (provider.Metadata.IsHidden) return RedirectToAction("List"); - //var fileSystemName = ExportDeploymentType.FileSystem.GetLocalizedEnum(_services.Localization, _services.WorkContext); + //var fileSystemName = ExportDeploymentType.FileSystem.GetLocalizedEnum(Services.Localization, Services.WorkContext); var model = PrepareDeploymentModel(profile, new ExportDeployment { @@ -1233,7 +1254,7 @@ public ActionResult CreateDeployment(int id) [FormValueRequired("save", "save-continue")] public ActionResult CreateDeployment(ExportDeploymentModel model, bool continueEditing, ExportDeploymentType deploymentType) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); var profile = _exportService.GetExportProfileById(model.ProfileId); @@ -1262,7 +1283,7 @@ public ActionResult CreateDeployment(ExportDeploymentModel model, bool continueE public ActionResult EditDeployment(int id) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); var deployment = _exportService.GetExportDeploymentById(id); @@ -1282,7 +1303,7 @@ public ActionResult EditDeployment(int id) [FormValueRequired("save", "save-continue")] public ActionResult EditDeployment(ExportDeploymentModel model, bool continueEditing) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); var deployment = _exportService.GetExportDeploymentById(model.Id); @@ -1311,7 +1332,7 @@ public ActionResult EditDeployment(ExportDeploymentModel model, bool continueEdi public ActionResult DeleteDeployment(int id) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); var deployment = _exportService.GetExportDeploymentById(id); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 0c685de70b..e4cce87ebf 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -151,51 +151,44 @@ public class ProviderSelectItem public partial class ExportProfileDetailsModel : EntityModelBase { - public string DownloadString { get; set; } - public int ExportFileCount { get { - return (ExportFiles.Count + (ZipPath != null ? 1 : 0)); + return (ExportFiles.Count + (ZipFile != null ? 1 : 0)); } } - public FileInfo ZipPath { get; set; } + public FileInfo ZipFile { get; set; } public List ExportFiles { get; set; } - public List PublicFiles { get; set; } + + public FileInfo PublicZipFile { get; set; } + public List PublicFiles { get; set; } public class FileInfo { public int StoreId { get; set; } public string StoreName { get; set; } + public int DisplayOrder { get; set; } + public string FilePath { get; set; } public string FileUrl { get; set; } - public string FileName - { - get - { - return (FilePath.HasValue() ? Path.GetFileName(FilePath) : ""); - } - } + public string FileName { get; set; } + public string FileExtension { get; set; } public string FileRootPath { get { var rootPath = ""; + var appPath = HttpRuntime.AppDomainAppPath; - if (FilePath.HasValue()) - { - var appPath = HttpRuntime.AppDomainAppPath; - - if (FilePath.StartsWith(appPath)) - rootPath = FilePath.Replace(appPath, "~/"); + if (FilePath.StartsWith(appPath)) + rootPath = FilePath.Replace(appPath, "~/"); - rootPath = rootPath.Replace('\\', '/'); - } + rootPath = rootPath.Replace('\\', '/'); return rootPath; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml index ac95de36a7..9b0a1e5374 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml @@ -1,59 +1,51 @@ @using SmartStore.Admin.Models.DataExchange; @using SmartStore.Web.Framework; @model ExportProfileDetailsModel -@helper FileDownloadLink(ExportProfileDetailsModel.FileInfo info) -{ - +@{ + ViewBag.DownloadString = T("Common.Download").Text; + ViewBag.AllStoresString = T("Admin.Common.StoresAll").Text; } -@if (!Model.ExportFiles.Any() && !Model.PublicFiles.Any() && Model.ZipPath == null) -{ -
@T("Common.NoFilesAvailable")
-} -else +@if (Model.ExportFiles.Any() || Model.PublicFiles.Any()) {
- @if (Model.ZipPath != null) + @foreach (var grp in Model.ExportFiles.OrderBy(x => x.DisplayOrder).GroupBy(x => x.DisplayOrder)) { -
@FileDownloadLink(Model.ZipPath)
- if (Model.ExportFiles.Any()) + foreach (var file in grp) { -

 

+
+ @Html.IconForFileExtension(file.FileExtension, false) + @file.FileRootPath +
} +

 

} - @foreach (var file in Model.ExportFiles) - { - @FileDownloadLink(file) - } - - @if (Model.PublicFiles.Count > 0) + @foreach (var grp in Model.PublicFiles.OrderBy(x => x.StoreId).GroupBy(x => x.StoreId)) { -

 

+ var storeName = grp.First().StoreName; +
+
+ @T("Admin.Common.PublicFiles") + (@(storeName.HasValue() ? storeName : ViewBag.AllStoresString)) +
- foreach (var grp in Model.PublicFiles.OrderBy(x => x.StoreId).GroupBy(x => x.StoreId)) - { -
-
- @T("Admin.Common.PublicFiles") (@(grp.First().StoreName)) + @foreach (var file in grp) + { +
+ + @file.FileUrl
- - @foreach (var file in grp) - { -
- - @file.FileUrl -
- } -
- } + } +
}
} +else +{ +
@T("Common.NoFilesAvailable")
+} \ No newline at end of file From 964d2b5955e58333960f27ba4aa9cfb37b176baa Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 26 May 2016 00:35:04 +0200 Subject: [PATCH 097/185] Event log grid: sort by updated date, THEN by created date --- src/Libraries/SmartStore.Services/Logging/DefaultLogger.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Logging/DefaultLogger.cs b/src/Libraries/SmartStore.Services/Logging/DefaultLogger.cs index fb152d8a1f..8387de3969 100644 --- a/src/Libraries/SmartStore.Services/Logging/DefaultLogger.cs +++ b/src/Libraries/SmartStore.Services/Logging/DefaultLogger.cs @@ -119,9 +119,9 @@ public virtual IPagedList GetAllLogs(DateTime? fromUtc, DateTime? toUtc, st var query = _logRepository.Table; if (fromUtc.HasValue) - query = query.Where(l => fromUtc.Value <= l.CreatedOnUtc); + query = query.Where(l => fromUtc.Value <= l.CreatedOnUtc || fromUtc.Value <= l.UpdatedOnUtc); if (toUtc.HasValue) - query = query.Where(l => toUtc.Value >= l.CreatedOnUtc); + query = query.Where(l => toUtc.Value >= l.CreatedOnUtc || toUtc.Value >= l.UpdatedOnUtc); if (logLevel.HasValue) { int logLevelId = (int)logLevel.Value; @@ -129,7 +129,8 @@ public virtual IPagedList GetAllLogs(DateTime? fromUtc, DateTime? toUtc, st } if (!String.IsNullOrEmpty(message)) query = query.Where(l => l.ShortMessage.Contains(message) || l.FullMessage.Contains(message)); - query = query.OrderByDescending(l => l.CreatedOnUtc); + + query = query.OrderByDescending(l => l.UpdatedOnUtc).ThenByDescending(l => l.CreatedOnUtc); if (minFrequency > 0) query = query.Where(l => l.Frequency >= minFrequency); From 6ff7024591434c0bab0ecc351e7fd4cd4e491abc Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 26 May 2016 00:39:34 +0200 Subject: [PATCH 098/185] Logging: log exception message before stack trace --- src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs b/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs index e0baba6444..fb8a10be35 100644 --- a/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs +++ b/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs @@ -47,7 +47,6 @@ public static void ErrorsAll(this ILogger logger, Exception exception, Customer while (exception != null) { FilteredLog(logger, LogLevel.Error, exception.Message, exception.StackTrace, exception, customer); - exception = exception.InnerException; } } @@ -62,7 +61,7 @@ private static void FilteredLog(ILogger logger, LogLevel level, string message, { if (exception != null && fullMessage.IsEmpty()) { - fullMessage = exception.StackTrace; + fullMessage = "{0}\n{1}\n{2}".FormatCurrent(exception.Message, new String('-', 20), exception.StackTrace); } logger.InsertLog(level, message, fullMessage, customer); From b5bb45d49ebc1104ffe2c8c2e1c6a8494da9210b Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 26 May 2016 00:54:21 +0200 Subject: [PATCH 099/185] Added WebHelper.GetPublicIPAddress() helper method --- src/Libraries/SmartStore.Core/WebHelper.cs | 77 +++++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/src/Libraries/SmartStore.Core/WebHelper.cs b/src/Libraries/SmartStore.Core/WebHelper.cs index b5e9d594bc..e085f3d3a5 100644 --- a/src/Libraries/SmartStore.Core/WebHelper.cs +++ b/src/Libraries/SmartStore.Core/WebHelper.cs @@ -11,10 +11,11 @@ using SmartStore.Core.Domain.Stores; using SmartStore.Core.Infrastructure; using SmartStore.Utilities; +using System.Net; +using System.Text; namespace SmartStore.Core { - public partial class WebHelper : IWebHelper { private static bool? s_optimizedCompilationsEnabled; @@ -598,5 +599,77 @@ public static string GetAbsoluteUrl(string url, HttpRequestBase request) url = string.Format("{0}://{1}{2}", request.Url.Scheme, request.Url.Authority, url); return url; } - } + + public static string GetPublicIPAddress() + { + string result = string.Empty; + + try + { + using (var client = new WebClient()) + { + client.Headers["User-Agent"] = "Mozilla/4.0 (Compatible; Windows NT 5.1; MSIE 6.0) (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)"; + try + { + byte[] arr = client.DownloadData("http://checkip.amazonaws.com/"); + string response = Encoding.UTF8.GetString(arr); + result = response.Trim(); + } + catch { } + } + } + catch { } + + var checkers = new string[] + { + "https://ipinfo.io/ip", + "https://api.ipify.org", + "https://icanhazip.com", + "https://wtfismyip.com/text", + "http://bot.whatismyipaddress.com/" + }; + + if (string.IsNullOrEmpty(result)) + { + foreach (var checker in checkers) + { + try + { + using (var client = new WebClient()) + { + result = client.DownloadString(checker).Replace("\n", ""); + if (!string.IsNullOrEmpty(result)) + { + break; + } + } + } + catch { } + } + } + + if (string.IsNullOrEmpty(result)) + { + try + { + var url = "http://checkip.dyndns.org"; + var req = WebRequest.Create(url); + using (var resp = req.GetResponse()) + { + using (var sr = new StreamReader(resp.GetResponseStream())) + { + var response = sr.ReadToEnd().Trim(); + var a = response.Split(':'); + var a2 = a[1].Substring(1); + var a3 = a2.Split('<'); + result = a3[0]; + } + } + } + catch { } + } + + return result; + } + } } From 63e6eaf2ed8a6092832607c8b14aa0430d4e84b5 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 26 May 2016 12:02:40 +0200 Subject: [PATCH 100/185] Improved export file counting code --- .../Controllers/ExportController.cs | 277 ++++++++---------- .../DataExchange/ExportDeploymentModel.cs | 2 +- .../Models/DataExchange/ExportProfileModel.cs | 20 +- .../Views/Export/ProfileFileCount.cshtml | 7 +- .../Views/Export/ProfileFileDetails.cshtml | 2 +- 5 files changed, 132 insertions(+), 176 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index bb341b6cfa..2b397e47ca 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -109,6 +109,41 @@ private string GetThumbnailUrl(Provider provider) return url; } + private void ModelToEntity(ExportDeploymentModel model, ExportDeployment deployment) + { + deployment.ProfileId = model.ProfileId; + deployment.Name = model.Name; + deployment.Enabled = model.Enabled; + deployment.DeploymentType = model.DeploymentType; + deployment.Username = model.Username; + deployment.Password = model.Password; + deployment.Url = model.Url; + deployment.HttpTransmissionType = model.HttpTransmissionType; + deployment.FileSystemPath = model.FileSystemPath; + deployment.SubFolder = model.SubFolder; + deployment.EmailAddresses = model.EmailAddresses; + deployment.EmailSubject = model.EmailSubject; + deployment.EmailAccountId = model.EmailAccountId; + deployment.PassiveMode = model.PassiveMode; + deployment.UseSsl = model.UseSsl; + } + + private ActionResult SmartRedirect(bool continueEditing, int profileId, int deploymentId) + { + if (!continueEditing) + { + TempData["SelectedTab.export-profile-edit"] = new SelectedTabInfo + { + TabId = "export-profile-edit-6", + Path = Url.Action("Edit", new { id = profileId }) + }; + } + + return (continueEditing ? + RedirectToAction("EditDeployment", new { id = deploymentId }) : + RedirectToAction("Edit", new { id = profileId })); + } + private void AddFileInfo(List list, string path, string publicFolderUrl = null, Store store = null) { if (System.IO.File.Exists(path) && !list.Any(x => x.FilePath == path)) @@ -134,57 +169,7 @@ private void AddFileInfo(List list, string path } } - private ExportFileCountModel CreateFileCountModel(ExportProfile profile, Provider provider) - { - var model = new ExportFileCountModel(); - model.FileCount = profile.GetExportFileCount(provider); - model.HasItems = (model.FileCount > 0); - - if (model.FileCount == 0) - { - model.HasItems = ( - profile.Cleanup && - profile.ResultInfo.HasValue() && - profile.Deployments.Any(x => x.DeploymentType == ExportDeploymentType.PublicFolder)); - } - - return model; - } - - private ExportFileCountModel CreateFileCountModel(ExportProfile profile, ExportDeployment deployment) - { - if (deployment.DeploymentType != ExportDeploymentType.FileSystem && deployment.DeploymentType != ExportDeploymentType.PublicFolder) - return null; - - var model = new ExportFileCountModel(); - - if (profile.CreateZipArchive) - { - var zipPath = profile.GetExportZipPath(); - var path = Path.Combine(deployment.GetDeploymentFolder(), Path.GetFileName(zipPath)); - - model.FileCount = (System.IO.File.Exists(path) ? 1 : 0); - model.HasItems = (model.FileCount > 0); - - return model; - } - - var currentStore = Services.StoreContext.CurrentStore; - var allStores = Services.StoreService.GetAllStores(); - var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); - - if (resultInfo != null && resultInfo.Files != null) - { - var deploymentFolder = deployment.GetDeploymentFolder(); - - model.FileCount = resultInfo.Files.Count(x => System.IO.File.Exists(Path.Combine(deploymentFolder, x.FileName))); - model.HasItems = (model.FileCount > 0); - } - - return model; - } - - private ExportFileDetailsModel CreateProfileDetailsModel(ExportProfile profile, Provider provider, ExportDeployment deployment = null) + private ExportFileDetailsModel CreateFileDetailsModel(ExportProfile profile, Provider provider, ExportDeployment deployment) { var model = new ExportFileDetailsModel { @@ -196,9 +181,7 @@ private ExportFileDetailsModel CreateProfileDetailsModel(ExportProfile profile, try { // add export files - var currentStore = Services.StoreContext.CurrentStore; var zipPath = profile.GetExportZipPath(); - var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); if (deployment == null) { @@ -217,11 +200,15 @@ private ExportFileDetailsModel CreateProfileDetailsModel(ExportProfile profile, { AddFileInfo(model.ExportFiles, Path.Combine(deploymentFolder, Path.GetFileName(zipPath))); } - else if (resultInfo.Files != null) + else { - foreach (var file in resultInfo.Files) + var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); + if (resultInfo.Files != null) { - AddFileInfo(model.ExportFiles, Path.Combine(deploymentFolder, file.FileName)); + foreach (var file in resultInfo.Files) + { + AddFileInfo(model.ExportFiles, Path.Combine(deploymentFolder, file.FileName)); + } } } } @@ -236,6 +223,7 @@ private ExportFileDetailsModel CreateProfileDetailsModel(ExportProfile profile, if (publicDeployment != null) { + var currentStore = Services.StoreContext.CurrentStore; var deploymentFolder = publicDeployment.GetDeploymentFolder(); // note public folder not cleaned up during export, so only display files that has been created during last export. @@ -247,19 +235,23 @@ private ExportFileDetailsModel CreateProfileDetailsModel(ExportProfile profile, Path.Combine(deploymentFolder, Path.GetFileName(zipPath)), publicDeployment.GetPublicFolderUrl(Services, currentStore)); } - else if (resultInfo.Files != null) + else { - var allStores = Services.StoreService.GetAllStores(); - - foreach (var file in resultInfo.Files) + var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); + if (resultInfo.Files != null) { - var store = (file.StoreId == 0 ? null : allStores.FirstOrDefault(x => x.Id == file.StoreId)); + var allStores = Services.StoreService.GetAllStores(); + + foreach (var file in resultInfo.Files) + { + var store = (file.StoreId == 0 ? null : allStores.FirstOrDefault(x => x.Id == file.StoreId)); - AddFileInfo( - model.PublicFiles, - Path.Combine(deploymentFolder, file.FileName), - publicDeployment.GetPublicFolderUrl(Services, store ?? currentStore), - store); + AddFileInfo( + model.PublicFiles, + Path.Combine(deploymentFolder, file.FileName), + publicDeployment.GetPublicFolderUrl(Services, store ?? currentStore), + store); + } } } } @@ -272,6 +264,60 @@ private ExportFileDetailsModel CreateProfileDetailsModel(ExportProfile profile, return model; } + private ExportDeploymentModel CreateDeploymentModel(ExportProfile profile, ExportDeployment deployment, Provider provider, bool forEdit) + { + var model = new ExportDeploymentModel + { + Id = deployment.Id, + ProfileId = deployment.ProfileId, + Name = deployment.Name, + Enabled = deployment.Enabled, + DeploymentType = deployment.DeploymentType, + DeploymentTypeName = deployment.DeploymentType.GetLocalizedEnum(Services.Localization, Services.WorkContext), + Username = deployment.Username, + Password = deployment.Password, + Url = deployment.Url, + HttpTransmissionType = deployment.HttpTransmissionType, + FileSystemPath = deployment.FileSystemPath, + SubFolder = deployment.SubFolder, + EmailAddresses = deployment.EmailAddresses, + EmailSubject = deployment.EmailSubject, + EmailAccountId = deployment.EmailAccountId, + PassiveMode = deployment.PassiveMode, + UseSsl = deployment.UseSsl + }; + + model.PublicFolderUrl = deployment.GetPublicFolderUrl(Services); + + if (forEdit) + { + var allEmailAccounts = _emailAccountService.GetAllEmailAccounts(); + + model.CreateZip = profile.CreateZipArchive; + model.AvailableDeploymentTypes = ExportDeploymentType.FileSystem.ToSelectList(false).ToList(); + model.AvailableHttpTransmissionTypes = ExportHttpTransmissionType.SimplePost.ToSelectList(false).ToList(); + + model.SerializedEmailAddresses = string.Join(",", deployment.EmailAddresses.SplitSafe(",").Select(x => x.EncodeJsString())); + + model.AvailableEmailAccounts = allEmailAccounts + .Select(x => new SelectListItem { Text = x.FriendlyName, Value = x.Id.ToString() }) + .ToList(); + + if (provider != null) + { + model.ThumbnailUrl = GetThumbnailUrl(provider); + } + } + else + { + var fileDetailsModel = CreateFileDetailsModel(profile, null, deployment); + + model.FileCount = fileDetailsModel.FileCount; + } + + return model; + } + private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile, Provider provider) { model.Id = profile.Id; @@ -290,8 +336,6 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile model.HasActiveProvider = (provider != null); model.FileNamePatternDescriptions = T("Admin.DataExchange.Export.FileNamePatternDescriptions").Text.SplitSafe(";"); - model.FileCount = CreateFileCountModel(profile, provider); - model.Provider = new ExportProfileModel.ProviderModel(); model.Provider.ThumbnailUrl = GetThumbnailUrl(provider); @@ -533,93 +577,6 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile } } - private ExportDeploymentModel CreateDeploymentModel(ExportProfile profile, ExportDeployment deployment, Provider provider, bool forEdit) - { - var model = new ExportDeploymentModel - { - Id = deployment.Id, - ProfileId = deployment.ProfileId, - Name = deployment.Name, - Enabled = deployment.Enabled, - DeploymentType = deployment.DeploymentType, - DeploymentTypeName = deployment.DeploymentType.GetLocalizedEnum(Services.Localization, Services.WorkContext), - Username = deployment.Username, - Password = deployment.Password, - Url = deployment.Url, - HttpTransmissionType = deployment.HttpTransmissionType, - FileSystemPath = deployment.FileSystemPath, - SubFolder = deployment.SubFolder, - EmailAddresses = deployment.EmailAddresses, - EmailSubject = deployment.EmailSubject, - EmailAccountId = deployment.EmailAccountId, - PassiveMode = deployment.PassiveMode, - UseSsl = deployment.UseSsl - }; - - model.PublicFolderUrl = deployment.GetPublicFolderUrl(Services); - - if (forEdit) - { - var allEmailAccounts = _emailAccountService.GetAllEmailAccounts(); - - model.CreateZip = profile.CreateZipArchive; - model.AvailableDeploymentTypes = ExportDeploymentType.FileSystem.ToSelectList(false).ToList(); - model.AvailableHttpTransmissionTypes = ExportHttpTransmissionType.SimplePost.ToSelectList(false).ToList(); - - model.SerializedEmailAddresses = string.Join(",", deployment.EmailAddresses.SplitSafe(",").Select(x => x.EncodeJsString())); - - model.AvailableEmailAccounts = allEmailAccounts - .Select(x => new SelectListItem { Text = x.FriendlyName, Value = x.Id.ToString() }) - .ToList(); - - if (provider != null) - { - model.ThumbnailUrl = GetThumbnailUrl(provider); - } - } - else - { - model.FileCount = CreateFileCountModel(profile, deployment); - } - - return model; - } - - private void ModelToEntity(ExportDeploymentModel model, ExportDeployment deployment) - { - deployment.ProfileId = model.ProfileId; - deployment.Name = model.Name; - deployment.Enabled = model.Enabled; - deployment.DeploymentType = model.DeploymentType; - deployment.Username = model.Username; - deployment.Password = model.Password; - deployment.Url = model.Url; - deployment.HttpTransmissionType = model.HttpTransmissionType; - deployment.FileSystemPath = model.FileSystemPath; - deployment.SubFolder = model.SubFolder; - deployment.EmailAddresses = model.EmailAddresses; - deployment.EmailSubject = model.EmailSubject; - deployment.EmailAccountId = model.EmailAccountId; - deployment.PassiveMode = model.PassiveMode; - deployment.UseSsl = model.UseSsl; - } - - private ActionResult SmartRedirect(bool continueEditing, int profileId, int deploymentId) - { - if (!continueEditing) - { - TempData["SelectedTab.export-profile-edit"] = new SelectedTabInfo - { - TabId = "export-profile-edit-6", - Path = Url.Action("Edit", new { id = profileId }) - }; - } - - return (continueEditing ? - RedirectToAction("EditDeployment", new { id = deploymentId }) : - RedirectToAction("Edit", new { id = profileId })); - } - #endregion public ActionResult Index() @@ -642,9 +599,11 @@ public ActionResult List() if (provider != null) { var profileModel = new ExportProfileModel(); + var fileDetailsModel = CreateFileDetailsModel(profile, provider, null); PrepareProfileModel(profileModel, profile, provider); + profileModel.FileCount = fileDetailsModel.FileCount; profileModel.TaskModel = profile.ScheduleTask.ToScheduleTaskModel(Services.Localization, _dateTimeHelper, Url); model.Add(profileModel); @@ -664,8 +623,8 @@ public ActionResult ProfileListDetails(int profileId) var provider = _exportService.LoadProvider(profile.ProviderSystemName); if (provider != null && !provider.Metadata.IsHidden) { - var model = CreateFileCountModel(profile, provider); - return Json(this.RenderPartialViewToString("ProfileFileCount", model), JsonRequestBehavior.AllowGet); + var model = CreateFileDetailsModel(profile, provider, null); + return Json(this.RenderPartialViewToString("ProfileFileCount", model.FileCount), JsonRequestBehavior.AllowGet); } } } @@ -685,7 +644,7 @@ public ActionResult ProfileFileDetails(int profileId, int deploymentId) var provider = _exportService.LoadProvider(profile.ProviderSystemName); if (provider != null && !provider.Metadata.IsHidden) { - var model = CreateProfileDetailsModel(profile, provider); + var model = CreateFileDetailsModel(profile, provider, null); return PartialView(model); } } @@ -695,7 +654,7 @@ public ActionResult ProfileFileDetails(int profileId, int deploymentId) var deployment = _exportService.GetExportDeploymentById(deploymentId); if (deployment != null) { - var model = CreateProfileDetailsModel(deployment.Profile, null, deployment); + var model = CreateFileDetailsModel(deployment.Profile, null, deployment); return PartialView(model); } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs index 22e3651971..f5cb8f82c6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs @@ -72,7 +72,7 @@ public class ExportDeploymentModel : EntityModelBase public LastResultInfo LastResult { get; set; } - public ExportFileCountModel FileCount { get; set; } + public int FileCount { get; set; } public class LastResultInfo { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index da5be8ca5b..5c2e77b6ef 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -91,7 +91,7 @@ public partial class ExportProfileModel : EntityModelBase public ScheduleTaskModel TaskModel { get; set; } - public ExportFileCountModel FileCount { get; set; } + public int FileCount { get; set; } public class ProviderModel { @@ -151,18 +151,23 @@ public class ProviderSelectItem public partial class ExportFileDetailsModel : EntityModelBase { - public int ExportFileCount + public int FileCount { get { - return ExportFiles.Count; + var result = ExportFiles.Count; + + if (result == 0) + result = PublicFiles.Count; + + return result; } } public List ExportFiles { get; set; } public List PublicFiles { get; set; } - public bool IsDeployment { get; set; } + public bool IsForDeployment { get; set; } public class FileInfo { @@ -194,11 +199,4 @@ public string FileRootPath } } } - - - public partial class ExportFileCountModel : ModelBase - { - public bool HasItems { get; set; } - public int FileCount { get; set; } - } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileCount.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileCount.cshtml index 826865311c..1465f01567 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileCount.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileCount.cshtml @@ -1,8 +1,7 @@ -@using SmartStore.Admin.Models.DataExchange; -@model ExportFileCountModel -@if (Model.HasItems) +@model int +@if (Model > 0) { - @Model.FileCount + @Model } else { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml index 2293b24dc6..e1aaa83f01 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml @@ -14,7 +14,7 @@ {
@Html.IconForFileExtension(file.FileExtension, false) - @file.FileRootPath
} From 4c28efdce192f5227377b1a1adcdbe86129f05f8 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 26 May 2016 14:40:01 +0200 Subject: [PATCH 101/185] Testing export deployment file dialog --- .../Export/Deployment/HttpFilePublisher.cs | 12 ++++++------ .../Export/Deployment/IFilePublisher.cs | 3 +-- .../Controllers/ExportController.cs | 16 +++++----------- .../Views/Export/_Tab.Deployment.cshtml | 9 +-------- 4 files changed, 13 insertions(+), 27 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs index bce4f36bbd..4f0d9df8d8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs @@ -12,7 +12,7 @@ public class HttpFilePublisher : IFilePublisher { public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) { - var succeeded = 0; + var succeededFiles = 0; var url = deployment.Url; if (!url.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !url.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) @@ -20,7 +20,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de if (deployment.HttpTransmissionType == ExportHttpTransmissionType.MultipartFormDataPost) { - var count = 0; + var countFiles = 0; ICredentials credentials = null; if (deployment.Username.HasValue()) @@ -33,14 +33,14 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de foreach (var path in context.GetDeploymentFiles()) { byte[] fileData = File.ReadAllBytes(path); - formData.Add(new ByteArrayContent(fileData), "file {0}".FormatInvariant(++count), Path.GetFileName(path)); + formData.Add(new ByteArrayContent(fileData), "file {0}".FormatInvariant(++countFiles), Path.GetFileName(path)); } var response = client.PostAsync(url, formData).Result; if (response.IsSuccessStatusCode) { - succeeded = count; + succeededFiles = countFiles; } else if (response.Content != null) { @@ -66,12 +66,12 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de { webClient.UploadFile(url, path); - ++succeeded; + ++succeededFiles; } } } - context.Log.Information("{0} file(s) successfully uploaded via HTTP ({1}).".FormatInvariant(succeeded, deployment.HttpTransmissionType.ToString())); + context.Log.Information("{0} file(s) successfully uploaded via HTTP ({1}).".FormatInvariant(succeededFiles, deployment.HttpTransmissionType.ToString())); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs index 9bbc852ac1..1ae3301ec4 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.IO; -using System.Linq; using SmartStore.Core.Domain; using SmartStore.Core.Localization; using SmartStore.Core.Logging; @@ -29,7 +28,7 @@ public IEnumerable GetDeploymentFiles() { if (!CreateZipArchive) { - return System.IO.Directory.EnumerateFiles(FolderContent, "*", SearchOption.AllDirectories).ToArray(); + return System.IO.Directory.EnumerateFiles(FolderContent, "*", SearchOption.AllDirectories); } if (File.Exists(ZipPath)) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 2b397e47ca..ef87eab725 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -174,6 +174,7 @@ private ExportFileDetailsModel CreateFileDetailsModel(ExportProfile profile, Pro var model = new ExportFileDetailsModel { Id = (deployment == null ? profile.Id : deployment.Id), + IsForDeployment = (deployment != null), ExportFiles = new List(), PublicFiles = new List() }; @@ -195,20 +196,13 @@ private ExportFileDetailsModel CreateFileDetailsModel(ExportProfile profile, Pro else if (deployment.DeploymentType == ExportDeploymentType.FileSystem) { var deploymentFolder = deployment.GetDeploymentFolder(); + var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); - if (profile.CreateZipArchive) + if (resultInfo.Files != null) { - AddFileInfo(model.ExportFiles, Path.Combine(deploymentFolder, Path.GetFileName(zipPath))); - } - else - { - var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); - if (resultInfo.Files != null) + foreach (var file in resultInfo.Files) { - foreach (var file in resultInfo.Files) - { - AddFileInfo(model.ExportFiles, Path.Combine(deploymentFolder, file.FileName)); - } + AddFileInfo(model.ExportFiles, Path.Combine(deploymentFolder, file.FileName)); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml index 2601416119..f657afb843 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml @@ -149,14 +149,7 @@ @Html.SymbolForBool(deployment.Enabled) - @if (deployment.FileCount == null) - { - @Html.SymbolForBool(false) - } - else - { - @Html.Partial("ProfileFileCount", deployment.FileCount) - } + @Html.Partial("ProfileFileCount", deployment.FileCount) @if (result == null) From 40f5f275ac8948384f868e2ad5e5645bc7364fa0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 27 May 2016 10:34:23 +0200 Subject: [PATCH 102/185] A HTTP GET should not change business data --- .../Controllers/ExportController.cs | 1 + .../Controllers/ImportController.cs | 32 ++++++++----------- .../Administration/Views/Export/Edit.cshtml | 12 +++---- .../Administration/Views/Export/List.cshtml | 14 +++----- .../Views/Export/Preview.cshtml | 4 +-- .../Views/Export/_ProfileList.cshtml | 4 +-- .../Views/Export/_Tab.Deployment.cshtml | 11 +++++-- .../Administration/Views/Import/Edit.cshtml | 6 ++-- .../Administration/Views/Import/List.cshtml | 16 +++------- .../Views/Import/_Update.cshtml | 16 ++++------ .../Scripts/smartstore.doAjax.js | 32 +++++++++++++++++++ 11 files changed, 82 insertions(+), 66 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index ef87eab725..95b26a0da0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -1385,6 +1385,7 @@ public ActionResult EditDeployment(ExportDeploymentModel model, bool continueEdi return View(model); } + [HttpPost] public ActionResult DeleteDeployment(int id) { if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index 8de7f54e5c..00a216f5d4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -11,7 +11,6 @@ using SmartStore.Core.Domain; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.IO; -using SmartStore.Services; using SmartStore.Services.Catalog.Importer; using SmartStore.Services.Customers.Importer; using SmartStore.Services.DataExchange.Csv; @@ -32,20 +31,17 @@ namespace SmartStore.Admin.Controllers [AdminAuthorize] public class ImportController : AdminControllerBase { - private readonly ICommonServices _services; private readonly IImportProfileService _importProfileService; private readonly IDateTimeHelper _dateTimeHelper; private readonly ITaskScheduler _taskScheduler; private readonly ILanguageService _languageService; public ImportController( - ICommonServices services, IImportProfileService importService, IDateTimeHelper dateTimeHelper, ITaskScheduler taskScheduler, ILanguageService languageService) { - _services = services; _importProfileService = importService; _dateTimeHelper = dateTimeHelper; _taskScheduler = taskScheduler; @@ -132,7 +128,7 @@ private void PrepareProfileModel(ImportProfileModel model, ImportProfile profile model.IsTaskRunning = profile.ScheduleTask.IsRunning; model.IsTaskEnabled = profile.ScheduleTask.Enabled; model.LogFileExists = System.IO.File.Exists(profile.GetImportLogPath()); - model.EntityTypeName = profile.EntityType.GetLocalizedEnum(_services.Localization, _services.WorkContext); + model.EntityTypeName = profile.EntityType.GetLocalizedEnum(Services.Localization, Services.WorkContext); model.ExistingFileNames = profile.GetImportFiles() .Select(x => Path.GetFileName(x)) @@ -323,7 +319,7 @@ public ActionResult Index() public ActionResult List() { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) return AccessDeniedView(); var model = new ImportProfileListModel @@ -340,7 +336,7 @@ public ActionResult List() PrepareProfileModel(profileModel, profile, false); - profileModel.TaskModel = profile.ScheduleTask.ToScheduleTaskModel(_services.Localization, _dateTimeHelper, Url); + profileModel.TaskModel = profile.ScheduleTask.ToScheduleTaskModel(Services.Localization, _dateTimeHelper, Url); model.Profiles.Add(profileModel); } @@ -350,7 +346,7 @@ public ActionResult List() public ActionResult ProfileListDetails(int profileId) { - if (_services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) { var profile = _importProfileService.GetImportProfileById(profileId); if (profile != null) @@ -371,7 +367,7 @@ public ActionResult ProfileListDetails(int profileId) [HttpPost] public ActionResult Create(ImportProfileModel model) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) return AccessDeniedView(); var importFile = Path.Combine(FileSystemHelper.TempDir(), model.TempFileName.EmptyNull()); @@ -399,7 +395,7 @@ public ActionResult Create(ImportProfileModel model) public ActionResult Edit(int id) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) return AccessDeniedView(); var profile = _importProfileService.GetImportProfileById(id); @@ -416,7 +412,7 @@ public ActionResult Edit(int id) [FormValueRequired("save", "save-continue")] public ActionResult Edit(ImportProfileModel model, bool continueEditing, FormCollection form) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) return AccessDeniedView(); var profile = _importProfileService.GetImportProfileById(model.Id); @@ -541,7 +537,7 @@ public ActionResult Edit(ImportProfileModel model, bool continueEditing, FormCol [HttpPost] public ActionResult ResetColumnMappings(int id) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) return AccessDeniedView(); var profile = _importProfileService.GetImportProfileById(id); @@ -559,7 +555,7 @@ public ActionResult ResetColumnMappings(int id) [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) return AccessDeniedView(); var profile = _importProfileService.GetImportProfileById(id); @@ -589,7 +585,7 @@ public JsonResult FileUpload(int id) string error = null; string tempFile = ""; - if (_services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) { var postedFile = Request.ToPostedFileResult(); if (postedFile != null) @@ -670,7 +666,7 @@ public ActionResult Execute(int id) return RedirectToAction("List"); var taskParams = new Dictionary(); - taskParams.Add(TaskExecutor.CurrentCustomerIdParamName, _services.WorkContext.CurrentCustomer.Id.ToString()); + taskParams.Add(TaskExecutor.CurrentCustomerIdParamName, Services.WorkContext.CurrentCustomer.Id.ToString()); _taskScheduler.RunSingleTask(profile.SchedulingTaskId, taskParams); @@ -685,7 +681,7 @@ public ActionResult Execute(int id) public ActionResult DownloadLogFile(int id) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) return AccessDeniedView(); var profile = _importProfileService.GetImportProfileById(id); @@ -715,7 +711,7 @@ public ActionResult DownloadImportFile(int id, string name) { string message = null; - if (_services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) { var profile = _importProfileService.GetImportProfileById(id); if (profile != null) @@ -759,7 +755,7 @@ public ActionResult DownloadImportFile(int id, string name) [HttpPost] public ActionResult DeleteImportFile(int id, string name) { - if (_services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) { var profile = _importProfileService.GetImportProfileById(id); if (profile != null) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 11098cf0a3..e3cd585606 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -6,8 +6,6 @@ ViewBag.Title = title; } -
- @using (Html.BeginForm()) {
@@ -23,9 +21,9 @@ { if (Model.Id != 0) { - + } @@ -380,9 +378,9 @@ // execute export $('#ProfileExportButton').click(function (e) { e.preventDefault(); - if (confirm('@T("Admin.Common.AskToProceed")')) { - $('#ProfileExportForm').submit(); - } + $(this).doPostData({ + ask: '@T("Admin.Common.AskToProceed")' + }); return false; }); }); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 21206a34b4..05bd582199 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -18,10 +18,6 @@ @Html.Partial("_ProfileList", Model.Where(x => x.IsSystemProfile), new ViewDataDictionary { { "IsSystemProfile", true } })
-
- -
-
} -
- -
-
+ if (!Model.Deployments.Any()) + { +
+ + @T("Admin.DataExchange.Export.NoProfiles") +
+ } + if (Model.Deployments.Count > 0) {
@@ -178,10 +186,6 @@
} - else - { -
@T("Admin.DataExchange.Export.NoProfiles")
- }   @T("Admin.DataExchange.AddNewProfile") From a947a44dc4fc9b8ea12e97c882e028bf069d0215 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 27 May 2016 18:28:15 +0200 Subject: [PATCH 104/185] #971 Product is added to cart automatically if it has a non-required file upload attribute --- changelog.md | 1 + .../Scripts/public.product-detail.js | 14 ++++++++++++-- .../Scripts/public.product-detail.mobile.js | 18 ++++++++++++++---- .../ProductTemplate.Simple.Mobile.cshtml | 2 +- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/changelog.md b/changelog.md index 8cf0b81d05..3f9ee7a98a 100644 --- a/changelog.md +++ b/changelog.md @@ -46,6 +46,7 @@ * Download nor sample download were removable when editing product * Copied product must not share sample download of source product. Could produce "The DELETE statement conflicted with the REFERENCE constraint 'FK_dbo.Product_dbo.Download_SampleDownloadId'". * #921 Specification attribute options with single quotation marks are causing a Javascript error +* #971 Product is added to cart automatically if it has a non-required file upload attribute ## SmartStore.NET 2.5 diff --git a/src/Presentation/SmartStore.Web/Scripts/public.product-detail.js b/src/Presentation/SmartStore.Web/Scripts/public.product-detail.js index d36f75d8f4..90e9d734aa 100644 --- a/src/Presentation/SmartStore.Web/Scripts/public.product-detail.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.product-detail.js @@ -25,15 +25,25 @@ // update product data and gallery $(el).find(':input').change(function () { + var inputType = $(this).attr('type'); + if (inputType && (inputType === 'file' || inputType === 'submit')) + return this; + var context = $(this).closest('.update-container'); - if (context[0]) { // associated or bundled item + if (context[0]) { } else { context = el; } - context.doAjax({ + var url = context.attr('data-url'); + if (!url) { + return this; + } + + $({}).doAjax({ + url: url, data: context.find(':input').serialize(), callbackSuccess: function (response) { self.updateDetailData(response, context); diff --git a/src/Presentation/SmartStore.Web/Scripts/public.product-detail.mobile.js b/src/Presentation/SmartStore.Web/Scripts/public.product-detail.mobile.js index e15764dc15..3f4d6c1aa3 100644 --- a/src/Presentation/SmartStore.Web/Scripts/public.product-detail.mobile.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.product-detail.mobile.js @@ -16,17 +16,27 @@ var opts = this.options; - // update product data and gallery - $(el).find(':input').change(function () { + // update product data. grouped product not supported because associated product has no mobile view. + $(el).find(':input').change(function () { + var inputType = $(this).attr('type'); + if (inputType && (inputType === 'file' || inputType === 'submit')) + return this; + var context = $(this).closest('.update-container'); - if (context[0]) { // associated or bundled item + if (context[0]) { } else { context = el; } - context.doAjax({ + var url = context.attr('data-url'); + if (!url) { + return this; + } + + $({}).doAjax({ + url: url, data: context.find(':input').serialize(), callbackSuccess: function (response) { self.updateDetailData(response, context); diff --git a/src/Presentation/SmartStore.Web/Views/Product/ProductTemplate.Simple.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Product/ProductTemplate.Simple.Mobile.cshtml index e3d04fe6c3..adc9dd77e4 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/ProductTemplate.Simple.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/ProductTemplate.Simple.Mobile.cshtml @@ -60,7 +60,7 @@ } } -
+
@Html.Widget("mobile_productdetails_top") @*we add enctype = "multipart/form-data" because "File upload" attribute control type requries it*@ From 00eebbe26b32a5c1cc9160d1cd98b4a38d2d109c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 27 May 2016 20:49:05 +0200 Subject: [PATCH 105/185] Localized a hard coded string --- .../Migrations/201605201911421_ExportRevision.cs | 3 +++ .../Administration/Controllers/DownloadController.cs | 2 +- .../Administration/Controllers/MediaController.cs | 2 +- .../SmartStore.Web/Controllers/ShoppingCartController.cs | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs index 782060d577..e4b309d30a 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs @@ -150,6 +150,9 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "No bundle items available", "Keine Bundle-Bestandteile vorhanden"); + builder.AddOrUpdate("Common.NoFileUploaded", + "There was no file uploaded.", + "Es wurde keine Datei hochgeladen."); builder.Delete( "Admin.DataExchange.Export.FolderAndFileName.Validate", diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/DownloadController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/DownloadController.cs index 7a6d174ad1..c215d9b7e9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/DownloadController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/DownloadController.cs @@ -73,7 +73,7 @@ public ActionResult AsyncUpload(bool minimalMode = false, string fieldName = nul var postedFile = Request.ToPostedFileResult(); if (postedFile == null) { - throw new ArgumentException("No file uploaded"); + throw new ArgumentException(T("Common.NoFileUploaded")); } var download = new Download diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/MediaController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/MediaController.cs index aa64078b5f..b55656e0cc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/MediaController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/MediaController.cs @@ -53,7 +53,7 @@ private UploadFileResult UploadImageInternal() var postedFile = Request.ToPostedFileResult(); if (postedFile == null) { - return new UploadFileResult { Message = "No file uploaded" }; + return new UploadFileResult { Message = T("Common.NoFileUploaded") }; } if (postedFile.FileName.IsEmpty()) diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index e6ce38af48..c6874c7c2c 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -1480,7 +1480,7 @@ public ActionResult UploadFileProductAttribute(int productId, int productAttribu var postedFile = Request.ToPostedFileResult(); if (postedFile == null) { - throw new ArgumentException("No file uploaded"); + throw new ArgumentException(T("Common.NoFileUploaded")); } int fileMaxSize = _catalogSettings.FileUploadMaximumSizeBytes; From b5f967e4c568aa44ed132c99e36900fbb1b2975c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 28 May 2016 10:50:44 +0200 Subject: [PATCH 106/185] Resolves #973 Bundle item upload is nowhere linked --- changelog.md | 1 + .../SmartStore.Services/Orders/OrderProcessingService.cs | 2 +- .../SmartStore.Web/Controllers/ShoppingCartController.cs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 3f9ee7a98a..5ab9d81ad2 100644 --- a/changelog.md +++ b/changelog.md @@ -47,6 +47,7 @@ * Copied product must not share sample download of source product. Could produce "The DELETE statement conflicted with the REFERENCE constraint 'FK_dbo.Product_dbo.Download_SampleDownloadId'". * #921 Specification attribute options with single quotation marks are causing a Javascript error * #971 Product is added to cart automatically if it has a non-required file upload attribute +* #973 Bundle item upload is nowhere linked ## SmartStore.NET 2.5 diff --git a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs index 7c633a920f..d92e6212a0 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs @@ -1139,7 +1139,7 @@ public virtual PlaceOrderResult PlaceOrder( var bundleItemSubTotal = _taxService.GetProductPrice(childItem.Item.Product, _priceCalculationService.GetSubTotal(childItem, true), out taxRate); var attributesInfo = _productAttributeFormatter.FormatAttributes(childItem.Item.Product, childItem.Item.AttributesXml, order.Customer, - renderPrices: false, allowHyperlinks: false); + renderPrices: false, allowHyperlinks: true); childItem.BundleItemData.ToOrderData(listBundleData, bundleItemSubTotal, childItem.Item.AttributesXml, attributesInfo); } diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index c6874c7c2c..f194738cb8 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -259,7 +259,7 @@ private ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartItemModel(Org model.BundlePerItemShoppingCart = item.BundleItem.BundleProduct.BundlePerItemShoppingCart; model.AttributeInfo = _productAttributeFormatter.FormatAttributes(product, item.AttributesXml, _workContext.CurrentCustomer, - renderPrices: false, renderGiftCardAttributes: true, allowHyperlinks: false); + renderPrices: false, renderGiftCardAttributes: true, allowHyperlinks: true); string bundleItemName = item.BundleItem.GetLocalized(x => x.Name); if (bundleItemName.HasValue()) From 7f72a74ac30266c1ed27c0a1dcec93d0bea22e2b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 28 May 2016 12:18:22 +0200 Subject: [PATCH 107/185] CK HTML editor: Enabled native context menu in source mode --- .../Views/Shared/EditorTemplates/RichEditor.cshtml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml index ca75eb1c47..1ba69f79da 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml @@ -39,7 +39,13 @@ removeDialogTabs: 'link:upload;image:upload', fullPage: (fullPage === true) ? true : false, entities: false, - removePlugins: 'divarea' + removePlugins: 'divarea', + on: { + instanceReady: function (evt) { + // Fix native context menu + evt.editor.container.getPrivate().events.contextmenu.listeners.splice(0, 1); + } + } }); }); From b197f1571343714aa9099b5ba8366a23565bb12e Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 28 May 2016 00:07:28 +0200 Subject: [PATCH 108/185] Improved TaskScheduler polling --- .../Tasks/InitializeSchedulerFilter.cs | 3 - .../Tasks/TaskScheduler.cs | 93 +++++++++++-------- 2 files changed, 54 insertions(+), 42 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs b/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs index e0d61ec085..3329fbf3a7 100644 --- a/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs +++ b/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs @@ -43,9 +43,6 @@ public void OnAuthorization(AuthorizationContext filterContext) try { - logger = EngineContext.Current.Resolve(); - taskScheduler = EngineContext.Current.Resolve(); - var taskService = EngineContext.Current.Resolve(); var storeService = EngineContext.Current.Resolve(); var eventPublisher = EngineContext.Current.Resolve(); diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs index 2ba9eb56e6..88d9999b18 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs @@ -21,6 +21,7 @@ public class DefaultTaskScheduler : DisposableObject, ITaskScheduler, IRegistere private bool _intervalFixed; private int _sweepInterval; private string _baseUrl; + private bool _mustVerifyBaseUrl; private System.Timers.Timer _timer; private bool _shuttingDown; private int _errCount; @@ -48,7 +49,8 @@ public string BaseUrl { CheckUrl(value); _baseUrl = value.TrimEnd('/', '\\'); - } + _mustVerifyBaseUrl = true; + } } public void Start() @@ -173,67 +175,80 @@ protected internal virtual void CallEndpoint(string url) req.Method = "POST"; req.ContentType = "text/plain"; req.ContentLength = 0; + req.ServicePoint.Expect100Continue = false; + req.Timeout = 10000; // 10 sec. string authToken = CreateAuthToken(); req.Headers.Add("X-AUTH-TOKEN", authToken); - req.GetResponseAsync().ContinueWith(t => - { - if (t.IsFaulted) + HttpWebResponse response = null; + + try + { + response = (HttpWebResponse)req.GetResponse(); + _errCount = 0; + _mustVerifyBaseUrl = false; + + //using (var logger = new TraceLogger()) + //{ + // logger.Warning("TaskScheduler Sweep called successfully: {0}".FormatCurrent(response.GetResponseStream().AsString())); + //} + } + catch (Exception ex) + { + HandleException(ex, url); + _errCount++; + if (_errCount >= 10) { - HandleException(t.Exception, url); - _errCount++; - if (_errCount >= 10) + // 10 failed attempts in succession. Stop the timer! + this.Stop(); + using (var logger = new TraceLogger()) { - // 10 failed attempts in succession. Stop the timer! - this.Stop(); - using (var logger = new TraceLogger()) - { - logger.Information("Stopping TaskScheduler sweep timer. Too many failed requests in succession."); - } + logger.Information("Stopping TaskScheduler sweep timer. Too many failed requests in succession."); } } - else - { - _errCount = 0; - var response = t.Result; - - //using (var logger = new TraceLogger()) - //{ - // logger.Debug("TaskScheduler Sweep called successfully: {0}".FormatCurrent(response.GetResponseStream().AsString())); - //} - + } + finally + { + if (response != null) response.Dispose(); - } - }); - } + } + } - private void HandleException(AggregateException exception, string url) + private void HandleException(Exception exception, string url) { using (var logger = new TraceLogger()) { string msg = "Error while calling TaskScheduler endpoint '{0}'.".FormatInvariant(url); - var wex = exception.InnerExceptions.OfType().FirstOrDefault(); - if (wex == null) + var wex = exception as WebException; + + if (_mustVerifyBaseUrl) { - logger.Error(msg, exception); + if (wex == null || wex.Response == null) + { + // a network related error occurred. Fallback to localhost! + _baseUrl = "http://localhost:52089/taskscheduler"; + _mustVerifyBaseUrl = false; + + logger.Information("A netwotk related error occurred while calling TaskScheduler endpoint'{0}'. Will try with 'localhost' on next sweep.".FormatInvariant(url)); + } + } + + if (wex != null && wex.Response != null) + { + var response = wex.Response as HttpWebResponse; + msg += " HTTP {0}, {1}".FormatCurrent((int)response.StatusCode, response.StatusDescription); + logger.Error(msg); } else { - using (var response = wex.Response as HttpWebResponse) - { - if (response != null) - { - msg += " HTTP {0}, {1}".FormatCurrent((int)response.StatusCode, response.StatusDescription); - } - logger.Error(msg); - } + logger.Error(msg, exception); } } } - private void CheckUrl(string url) + private void CheckUrl(string url) { if (!url.IsWebUrl()) { From bf189bef7a3ef9612008f1986d00cb646e86523a Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Mon, 30 May 2016 21:00:18 +0200 Subject: [PATCH 109/185] Fixed PAngV calculation ajax call --- .../Views/Product/_CreateOrUpdate.Price.cshtml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Price.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Price.cshtml index ce16846d32..bebc6fa0c7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Price.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Price.cshtml @@ -16,7 +16,6 @@ fieldBasePriceBaseAmount = $("#@Html.FieldIdFor(model => model.BasePriceBaseAmount)"); fieldBasePriceMeasureUnit.on("change", getCurrentBasePrice); - fieldBasePriceAmount.on("blur", getCurrentBasePrice); fieldBasePriceBaseAmount.on("blur", getCurrentBasePrice); @@ -25,7 +24,9 @@ toggleBasePrice(); function getCurrentBasePrice() { - if (parseFloat(fieldBasePriceAmount.val()) > 0 && parseInt(fieldBasePriceBaseAmount.val()) > 0) { + var basePriceAmount = Globalize.parseFloat(fieldBasePriceAmount.val()); + var basePriceBaseAmount = Globalize.parseInt(fieldBasePriceBaseAmount.val()); + if (basePriceAmount > 0 && basePriceBaseAmount > 0) { $.ajax({ cache:false, type: "POST", @@ -40,7 +41,7 @@ $("#BasePriceAmountInfo").html(data.BasePrice); }, error: function (xhr, ajaxOptions, thrownError){ - alert(thrownError); + //alert(thrownError); } }); } From e0bdc37f58643fe14f220187f307f900bbddecb1 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 31 May 2016 01:58:34 +0200 Subject: [PATCH 110/185] Added helper to create HttpWebRequest instances for safe local calls (unfinished) --- .../Utilities/FileDownloadManager.cs | 2 +- src/Libraries/SmartStore.Core/WebHelper.cs | 61 +++++++++++- .../Tasks/TaskScheduler.cs | 97 ++++++++----------- .../Controllers/CommonController.cs | 2 +- .../Controllers/ThemeController.cs | 3 +- .../Controllers/TaskSchedulerController.cs | 7 +- 6 files changed, 109 insertions(+), 63 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs b/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs index 09f40c69d9..7759a8bc93 100644 --- a/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs +++ b/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs @@ -37,7 +37,7 @@ public FileDownloadResponse DownloadFile(string url, bool sendAuthCookie = false url = WebHelper.GetAbsoluteUrl(url, _httpRequest); - var req = (HttpWebRequest)WebRequest.Create(url); + var req = WebRequest.CreateHttp(url); req.UserAgent = "SmartStore.NET"; if (timeout.HasValue) diff --git a/src/Libraries/SmartStore.Core/WebHelper.cs b/src/Libraries/SmartStore.Core/WebHelper.cs index e085f3d3a5..2f622643d9 100644 --- a/src/Libraries/SmartStore.Core/WebHelper.cs +++ b/src/Libraries/SmartStore.Core/WebHelper.cs @@ -18,11 +18,13 @@ namespace SmartStore.Core { public partial class WebHelper : IWebHelper { + private static object s_lock = new object(); private static bool? s_optimizedCompilationsEnabled; private static AspNetHostingPermissionLevel? s_trustLevel; private static readonly Regex s_staticExts = new Regex(@"(.*?)\.(css|js|png|jpg|jpeg|gif|bmp|html|htm|xml|pdf|doc|xls|rar|zip|ico|eot|svg|ttf|woff|otf|axd|ashx|less)", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex s_htmlPathPattern = new Regex(@"(?<=(?:href|src)=(?:""|'))(?!https?://)(?[^(?:""|')]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline); private static readonly Regex s_cssPathPattern = new Regex(@"url\('(?.+)'\)", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline); + private static string s_safeLocalHostName = null; private readonly HttpContextBase _httpContext; private bool? _isCurrentConnectionSecured; @@ -631,11 +633,11 @@ public static string GetPublicIPAddress() if (string.IsNullOrEmpty(result)) { - foreach (var checker in checkers) + using (var client = new WebClient()) { - try + foreach (var checker in checkers) { - using (var client = new WebClient()) + try { result = client.DownloadString(checker).Replace("\n", ""); if (!string.IsNullOrEmpty(result)) @@ -643,8 +645,8 @@ public static string GetPublicIPAddress() break; } } + catch { } } - catch { } } } @@ -671,5 +673,56 @@ public static string GetPublicIPAddress() return result; } + + public static HttpWebRequest CreateHttpRequestForSafeLocalCall(Uri requestUri) + { + Guard.ArgumentNotNull(() => requestUri); + + EnsureSafeLocalHostNameDetermined(requestUri); + + var uri = requestUri; + + if (!requestUri.Host.Equals(s_safeLocalHostName, StringComparison.OrdinalIgnoreCase)) + { + var url = String.Format("{0}://{1}{2}", + requestUri.Scheme, + requestUri.Port == 80 ? s_safeLocalHostName : s_safeLocalHostName + ":" + requestUri.Port, + requestUri.PathAndQuery); + uri = new Uri(url); + } + + var request = WebRequest.CreateHttp(uri); + request.ServerCertificateValidationCallback += (sender, cert, chain, errors) => true; + request.ServicePoint.Expect100Continue = false; + request.UserAgent = "SmartStore.NET {0}".FormatInvariant(SmartStoreVersion.CurrentFullVersion); + + return request; + } + + private static void EnsureSafeLocalHostNameDetermined(Uri requestUri) + { + if (s_safeLocalHostName == null) + { + lock (s_lock) + { + if (s_safeLocalHostName == null) + { + //var hosts = new string[] + //{ + // requestUri.Host, + // "localhost", + // Dns.GetHostName(), // TODO: add local IPs also + // "127.0.0.1" + //}; + + //foreach (var host in hosts) + //{ + // // ... + //} + s_safeLocalHostName = "localhost"; // TBD: what about ssl and differing ports? + } + } + } + } } } diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs index 88d9999b18..4ca2101c29 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs @@ -13,6 +13,7 @@ using SmartStore.Collections; using SmartStore.Core.Infrastructure; using SmartStore.Core.Caching; +using SmartStore.Core; namespace SmartStore.Services.Tasks { @@ -21,7 +22,6 @@ public class DefaultTaskScheduler : DisposableObject, ITaskScheduler, IRegistere private bool _intervalFixed; private int _sweepInterval; private string _baseUrl; - private bool _mustVerifyBaseUrl; private System.Timers.Timer _timer; private bool _shuttingDown; private int _errCount; @@ -49,7 +49,6 @@ public string BaseUrl { CheckUrl(value); _baseUrl = value.TrimEnd('/', '\\'); - _mustVerifyBaseUrl = true; } } @@ -137,7 +136,7 @@ public void RunSingleTask(int scheduleTaskId, IDictionary taskPa query = qs.ToString(); } - CallEndpoint("{0}/Execute/{1}{2}".FormatInvariant(_baseUrl, scheduleTaskId, query)); + CallEndpoint(new Uri("{0}/Execute/{1}{2}".FormatInvariant(_baseUrl, scheduleTaskId, query))); } private void Elapsed(object sender, System.Timers.ElapsedEventArgs e) @@ -155,7 +154,7 @@ private void Elapsed(object sender, System.Timers.ElapsedEventArgs e) _intervalFixed = true; } - CallEndpoint(_baseUrl + "/Sweep"); + CallEndpoint(new Uri(_baseUrl + "/Sweep")); } } finally @@ -164,86 +163,76 @@ private void Elapsed(object sender, System.Timers.ElapsedEventArgs e) } } - protected internal virtual void CallEndpoint(string url) + protected internal virtual void CallEndpoint(Uri uri) { if (_shuttingDown) return; - var req = (HttpWebRequest)WebRequest.Create(url); - req.ServerCertificateValidationCallback += (sender, cert, chain, errors) => true; - req.UserAgent = "SmartStore.NET"; + var req = WebHelper.CreateHttpRequestForSafeLocalCall(uri); req.Method = "POST"; req.ContentType = "text/plain"; req.ContentLength = 0; - req.ServicePoint.Expect100Continue = false; req.Timeout = 10000; // 10 sec. string authToken = CreateAuthToken(); req.Headers.Add("X-AUTH-TOKEN", authToken); - HttpWebResponse response = null; - - try - { - response = (HttpWebResponse)req.GetResponse(); - _errCount = 0; - _mustVerifyBaseUrl = false; - - //using (var logger = new TraceLogger()) - //{ - // logger.Warning("TaskScheduler Sweep called successfully: {0}".FormatCurrent(response.GetResponseStream().AsString())); - //} - } - catch (Exception ex) + req.GetResponseAsync().ContinueWith(t => { - HandleException(ex, url); - _errCount++; - if (_errCount >= 10) + if (t.IsFaulted) { - // 10 failed attempts in succession. Stop the timer! - this.Stop(); - using (var logger = new TraceLogger()) + HandleException(t.Exception, uri); + _errCount++; + if (_errCount >= 10) { - logger.Information("Stopping TaskScheduler sweep timer. Too many failed requests in succession."); + // 10 failed attempts in succession. Stop the timer! + this.Stop(); + using (var logger = new TraceLogger()) + { + logger.Information("Stopping TaskScheduler sweep timer. Too many failed requests in succession."); + } } } - } - finally - { - if (response != null) + else + { + _errCount = 0; + var response = t.Result; + + //using (var logger = new TraceLogger()) + //{ + // logger.Debug("TaskScheduler Sweep called successfully: {0}".FormatCurrent(response.GetResponseStream().AsString())); + //} + response.Dispose(); - } + } + }); } - private void HandleException(Exception exception, string url) + private void HandleException(AggregateException exception, Uri uri) { using (var logger = new TraceLogger()) { - string msg = "Error while calling TaskScheduler endpoint '{0}'.".FormatInvariant(url); - - var wex = exception as WebException; + string msg = "Error while calling TaskScheduler endpoint '{0}'.".FormatInvariant(uri.OriginalString); + var wex = exception.InnerExceptions.OfType().FirstOrDefault(); - if (_mustVerifyBaseUrl) + if (wex == null) { - if (wex == null || wex.Response == null) - { - // a network related error occurred. Fallback to localhost! - _baseUrl = "http://localhost:52089/taskscheduler"; - _mustVerifyBaseUrl = false; - - logger.Information("A netwotk related error occurred while calling TaskScheduler endpoint'{0}'. Will try with 'localhost' on next sweep.".FormatInvariant(url)); - } + logger.Error(msg, exception.InnerException); } - - if (wex != null && wex.Response != null) + else if (wex.Response == null) { - var response = wex.Response as HttpWebResponse; - msg += " HTTP {0}, {1}".FormatCurrent((int)response.StatusCode, response.StatusDescription); - logger.Error(msg); + logger.Error(msg, wex); } else { - logger.Error(msg, exception); + using (var response = wex.Response as HttpWebResponse) + { + if (response != null) + { + msg += " HTTP {0}, {1}".FormatCurrent((int)response.StatusCode, response.StatusDescription); + } + logger.Error(msg); + } } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs index 318391856a..1da4e0fed7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs @@ -523,7 +523,7 @@ public ActionResult Warnings() try { sitemapUrl = Url.RouteUrl("SitemapSEO", (object)null, _securitySettings.Value.ForceSslForAllPages ? "https" : "http"); - var request = (HttpWebRequest)WebRequest.Create(sitemapUrl); + var request = WebHelper.CreateHttpRequestForSafeLocalCall(new Uri(sitemapUrl)); request.Method = "HEAD"; request.Timeout = 15000; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ThemeController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ThemeController.cs index 9255a18675..c47332e250 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ThemeController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ThemeController.cs @@ -305,8 +305,7 @@ private string ValidateLess(ThemeManifest manifest, int storeId) storeId, manifest.ThemeName); - HttpWebRequest request = WebRequest.CreateHttp(url); - request.UserAgent = "SmartStore.NET {0}".FormatInvariant(SmartStoreVersion.CurrentFullVersion); + var request = WebHelper.CreateHttpRequestForSafeLocalCall(new Uri(url)); WebResponse response = null; try diff --git a/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs b/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs index 1dd684ee5b..68c94b3fe7 100644 --- a/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs @@ -83,5 +83,10 @@ public ActionResult Execute(int id /* taskId */) return Content("Task '{0}' executed".FormatCurrent(task.Name)); } - } + public ContentResult Noop() + { + return Content("noop"); + } + + } } \ No newline at end of file From ae9142a234376f5d8e7936aa7daefeac555e176c Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 31 May 2016 12:37:52 +0200 Subject: [PATCH 111/185] Strip html out of short description for bundle items --- .../SmartStore.Web/Views/Product/_BundleItems.cshtml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Views/Product/_BundleItems.cshtml b/src/Presentation/SmartStore.Web/Views/Product/_BundleItems.cshtml index a61c43934e..6c588c5d96 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/_BundleItems.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/_BundleItems.cshtml @@ -1,6 +1,7 @@ @model ProductDetailsModel @using SmartStore.Web.Models.Catalog; @using SmartStore.Web; +@using System.Text.RegularExpressions; @foreach (var item in Model.BundledItems.Where(x => x.BundleItem.Visible)) { @@ -34,9 +35,10 @@ @if (!String.IsNullOrWhiteSpace(item.ShortDescription)) { -
- @Html.Raw(item.ShortDescription.Truncate(170, "...")) - @if (item.ShortDescription.Length > 170) { ... } + var strippedShortDesc = Regex.Replace(item.ShortDescription, @"<[^>]*>", String.Empty); + +
+ @Html.Raw(strippedShortDesc.Truncate(170, "..."))
} From 93958b1fe90c1a11babfcc4440a7586d7878f473 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 31 May 2016 12:43:18 +0200 Subject: [PATCH 112/185] Resolves #977 Show PAngV base/delivery amount also --- changelog.md | 1 + .../201605201911421_ExportRevision.cs | 7 ++++++- .../Catalog/ProductExtensions.cs | 21 ++++++++++++------- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index 5ab9d81ad2..2d597b0d26 100644 --- a/changelog.md +++ b/changelog.md @@ -26,6 +26,7 @@ * #913 Use HTML5 Input types (tel, email) * Added paging to frontend order list * Added paging to backend checkout attribute list +* #977 Show PAngV base/delivery amount also ### Bugfixes * Fixed ajax cache issue when saving payment or shipping restrictions. Internet Explorer showed the old data state (before storage). diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs index e4b309d30a..d89896b820 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs @@ -154,12 +154,17 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "There was no file uploaded.", "Es wurde keine Datei hochgeladen."); + builder.AddOrUpdate("Common.Content", + "Content", + "Inhalt"); + builder.Delete( "Admin.DataExchange.Export.FolderAndFileName.Validate", "Admin.DataExchange.Export.Deployment.IsPublic", "Admin.DataExchange.Export.Deployment.CreateZip", "Admin.Common.TemporaryFiles", - "Admin.Common.NoTempFilesFound"); + "Admin.Common.NoTempFilesFound", + "Products.BasePriceInfo"); } } } diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs index 85b55cfb4f..a76113c777 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs @@ -327,22 +327,29 @@ public static string GetBasePriceInfo(this Product product, ILocalizationService var taxrate = decimal.Zero; var currentPrice = priceCalculationService.GetFinalPrice(product, workContext.CurrentCustomer, true); - decimal price = taxService.GetProductPrice(product, decimal.Add(currentPrice, priceAdjustment), out taxrate); + var price = taxService.GetProductPrice(product, decimal.Add(currentPrice, priceAdjustment), out taxrate); price = currencyService.ConvertFromPrimaryStoreCurrency(price, currency); - decimal basePriceValue = Convert.ToDecimal((price / product.BasePriceAmount) * product.BasePriceBaseAmount); + var value = Convert.ToDecimal((price / product.BasePriceAmount) * product.BasePriceBaseAmount); + var valueFormatted = priceFormatter.FormatPrice(value, true, currency); + var amountFormatted = Math.Round(product.BasePriceAmount.Value, 2).ToString("G29"); - string basePrice = priceFormatter.FormatPrice(basePriceValue, true, currency); - string unit = "{0} {1}".FormatWith(product.BasePriceBaseAmount, product.BasePriceMeasureUnit); + var result = "{0} {1} ({2} / {3} {1})".FormatInvariant( + amountFormatted, + product.BasePriceMeasureUnit, + valueFormatted, + product.BasePriceBaseAmount + ); if (languageIndependent) { - return "{0} / {1}".FormatWith(basePrice, unit); + return result; } - return localizationService.GetResource("Products.BasePriceInfo").FormatWith(basePrice, unit); - } + return string.Concat(localizationService.GetResource("Common.Content"), ": ", result); + } + return ""; } From fa9ed8e63d5bc50b6295b1630c48cff6c00a1f70 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 31 May 2016 13:47:02 +0200 Subject: [PATCH 113/185] Fixed: Base price in product list ignored PriceDisplayType (catalog settings) and possibly displayed the wrong base price info. f2a904a related --- changelog.md | 3 +- .../Catalog/PriceCalculationService.cs | 6 +++ .../Catalog/ProductExtensions.cs | 37 ++++++++++++++++--- .../Controllers/CatalogHelper.cs | 22 ++++++----- 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/changelog.md b/changelog.md index 2d597b0d26..97798e8f54 100644 --- a/changelog.md +++ b/changelog.md @@ -48,7 +48,8 @@ * Copied product must not share sample download of source product. Could produce "The DELETE statement conflicted with the REFERENCE constraint 'FK_dbo.Product_dbo.Download_SampleDownloadId'". * #921 Specification attribute options with single quotation marks are causing a Javascript error * #971 Product is added to cart automatically if it has a non-required file upload attribute -* #973 Bundle item upload is nowhere linked +* #973 Bundle item upload is nowhere linked +* Base price in product list ignored PriceDisplayType (catalog settings) and possibly displayed the wrong base price info ## SmartStore.NET 2.5 diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs index 1aef3aeb63..ca45f495d7 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs @@ -251,6 +251,12 @@ protected virtual decimal GetPreselectedPrice(Product product, PriceCalculationC if (selectedCombination != null && selectedCombination.IsActive && selectedCombination.Price.HasValue) { product.MergedDataValues = new Dictionary { { "Price", selectedCombination.Price.Value } }; + + if (selectedCombination.BasePriceAmount.HasValue) + product.MergedDataValues.Add("BasePriceAmount", selectedCombination.BasePriceAmount.Value); + + if (selectedCombination.BasePriceBaseAmount.HasValue) + product.MergedDataValues.Add("BasePriceBaseAmount", selectedCombination.BasePriceBaseAmount.Value); } } diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs index a76113c777..5675097c9e 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs @@ -14,7 +14,7 @@ namespace SmartStore.Services.Catalog { - public static class ProductExtensions + public static class ProductExtensions { public static ProductVariantAttributeCombination MergeWithCombination(this Product product, string selectedAttributes) { @@ -299,7 +299,7 @@ public static int[] ParseRequiredProductIds(this Product product) } /// - /// Gets the base price + /// Gets the base price info /// /// Product /// Localization service @@ -310,7 +310,7 @@ public static int[] ParseRequiredProductIds(this Product product) /// Target currency /// Price adjustment /// Whether the result string should be language independent - /// The base price + /// The base price info public static string GetBasePriceInfo(this Product product, ILocalizationService localizationService, IPriceFormatter priceFormatter, ICurrencyService currencyService, ITaxService taxService, IPriceCalculationService priceCalculationService, Currency currency, decimal priceAdjustment = decimal.Zero, bool languageIndependent = false) @@ -331,8 +331,33 @@ public static string GetBasePriceInfo(this Product product, ILocalizationService price = currencyService.ConvertFromPrimaryStoreCurrency(price, currency); - var value = Convert.ToDecimal((price / product.BasePriceAmount) * product.BasePriceBaseAmount); - var valueFormatted = priceFormatter.FormatPrice(value, true, currency); + return product.GetBasePriceInfo(price, localizationService, priceFormatter, currency, languageIndependent); + } + + return ""; + } + + /// + /// Gets the base price info + /// + /// Product + /// The calculated product price + /// Localization service + /// Price formatter + /// Target currency + /// Whether the result string should be language independent + /// The base price info + public static string GetBasePriceInfo(this Product product, + decimal productPrice, + ILocalizationService localizationService, + IPriceFormatter priceFormatter, + Currency currency, + bool languageIndependent = false) + { + if (product.BasePriceHasValue && product.BasePriceAmount != Decimal.Zero) + { + var value = Convert.ToDecimal((productPrice / product.BasePriceAmount) * product.BasePriceBaseAmount); + var valueFormatted = priceFormatter.FormatPrice(value, true, currency); var amountFormatted = Math.Round(product.BasePriceAmount.Value, 2).ToString("G29"); var result = "{0} {1} ({2} / {3} {1})".FormatInvariant( @@ -351,7 +376,7 @@ public static string GetBasePriceInfo(this Product product, ILocalizationService } return ""; - } + } public static string GetProductTypeLabel(this Product product, ILocalizationService localizationService) { diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index 2c0f3ef2be..55793edc65 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -1108,6 +1108,7 @@ public IEnumerable PrepareProductOverviewModels( foreach (var product in products) { var contextProduct = product; + var finalPrice = decimal.Zero; var model = new ProductOverviewModel { @@ -1188,7 +1189,7 @@ public IEnumerable PrepareProductOverviewModels( decimal taxRate = decimal.Zero; decimal oldPriceBase = _taxService.GetProductPrice(contextProduct, contextProduct.OldPrice, out taxRate); decimal finalPriceBase = _taxService.GetProductPrice(contextProduct, displayPrice.Value, out taxRate); - decimal finalPrice = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceBase, workingCurrency); + finalPrice = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceBase, workingCurrency); priceModel.OldPrice = null; @@ -1256,7 +1257,7 @@ public IEnumerable PrepareProductOverviewModels( decimal finalPriceBase = _taxService.GetProductPrice(product, displayPrice, out taxRate); decimal oldPrice = _currencyService.ConvertFromPrimaryStoreCurrency(oldPriceBase, workingCurrency); - decimal finalPrice = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceBase, workingCurrency); + finalPrice = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceBase, workingCurrency); priceModel.HasDiscount = (finalPriceBase != oldPriceBase && oldPriceBase != decimal.Zero); @@ -1396,16 +1397,19 @@ from a in colorAttribute.ProductVariantAttributeValues.Take(50) model.Manufacturers = PrepareManufacturersOverviewModel(_manufacturerService.GetProductManufacturersByProductId(product.Id), cachedManufacturerModels, false); } - if (_catalogSettings.ShowBasePriceInProductLists || isCompareList) + if (finalPrice != decimal.Zero && (_catalogSettings.ShowBasePriceInProductLists || isCompareList)) { - model.BasePriceInfo = contextProduct.GetBasePriceInfo(_localizationService, _priceFormatter, _currencyService, _taxService, _priceCalculationService, workingCurrency); + model.BasePriceInfo = contextProduct.GetBasePriceInfo(finalPrice, _localizationService, _priceFormatter, workingCurrency); } - var addShippingPrice = _currencyService.ConvertCurrency(contextProduct.AdditionalShippingCharge, currentStore.PrimaryStoreCurrency, workingCurrency); - - if (addShippingPrice > 0 && displayPrices) + if (displayPrices) { - model.TransportSurcharge = res["Common.AdditionalShippingSurcharge"].Text.FormatWith(_priceFormatter.FormatPrice(addShippingPrice, true, false)); + var addShippingPrice = _currencyService.ConvertCurrency(contextProduct.AdditionalShippingCharge, currentStore.PrimaryStoreCurrency, workingCurrency); + + if (addShippingPrice > 0) + { + model.TransportSurcharge = res["Common.AdditionalShippingSurcharge"].Text.FormatCurrent(_priceFormatter.FormatPrice(addShippingPrice, true, false)); + } } if (contextProduct.Weight > 0) @@ -1416,7 +1420,7 @@ from a in colorAttribute.ProductVariantAttributeValues.Take(50) // IsNew if (_catalogSettings.LabelAsNewForMaxDays.HasValue) { - model.IsNew = (DateTime.UtcNow - product.CreatedOnUtc).Days <= _catalogSettings.LabelAsNewForMaxDays.Value; + model.IsNew = ((DateTime.UtcNow - product.CreatedOnUtc).Days <= _catalogSettings.LabelAsNewForMaxDays.Value); } models.Add(model); From c073be3258e6668ee55968f5adca3e63503a147d Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 31 May 2016 18:26:17 +0200 Subject: [PATCH 114/185] DRY: use StringExtensions.RemoveHtml() --- .../SmartStore.Web/Views/Product/_BundleItems.cshtml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Views/Product/_BundleItems.cshtml b/src/Presentation/SmartStore.Web/Views/Product/_BundleItems.cshtml index 6c588c5d96..be033b6957 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/_BundleItems.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/_BundleItems.cshtml @@ -1,7 +1,6 @@ @model ProductDetailsModel @using SmartStore.Web.Models.Catalog; @using SmartStore.Web; -@using System.Text.RegularExpressions; @foreach (var item in Model.BundledItems.Where(x => x.BundleItem.Visible)) { @@ -35,8 +34,8 @@ @if (!String.IsNullOrWhiteSpace(item.ShortDescription)) { - var strippedShortDesc = Regex.Replace(item.ShortDescription, @"<[^>]*>", String.Empty); - + var strippedShortDesc = item.ShortDescription.RemoveHtml(); +
@Html.Raw(strippedShortDesc.Truncate(170, "..."))
From 45db3df1fc1d47428b06f01ed51f51860c8d61f3 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 31 May 2016 20:53:31 +0200 Subject: [PATCH 115/185] Base price info template should be localizable. Closes #977. --- .../201605201911421_ExportRevision.cs | 13 ++++-- .../Catalog/ProductExtensions.cs | 46 +++++++++++-------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs index d89896b820..4618512963 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs @@ -154,17 +154,20 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "There was no file uploaded.", "Es wurde keine Datei hochgeladen."); - builder.AddOrUpdate("Common.Content", - "Content", - "Inhalt"); + builder.AddOrUpdate("Products.BasePriceInfo", + "Content: {0} {1} ({2} / {3} {1})", + "Inhalt: {0} {1} ({2} / {3} {1})"); + + builder.AddOrUpdate("Products.BasePriceInfo.LanguageInsensitive", + "{0} {1} ({2} / {3} {1})", + "{0} {1} ({2} / {3} {1})"); builder.Delete( "Admin.DataExchange.Export.FolderAndFileName.Validate", "Admin.DataExchange.Export.Deployment.IsPublic", "Admin.DataExchange.Export.Deployment.CreateZip", "Admin.Common.TemporaryFiles", - "Admin.Common.NoTempFilesFound", - "Products.BasePriceInfo"); + "Admin.Common.NoTempFilesFound"); } } } diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs index 5675097c9e..051fc841f2 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs @@ -309,17 +309,23 @@ public static int[] ParseRequiredProductIds(this Product product) /// Price calculation service /// Target currency /// Price adjustment - /// Whether the result string should be language independent + /// Whether the result string should be language insensitive /// The base price info - public static string GetBasePriceInfo(this Product product, ILocalizationService localizationService, IPriceFormatter priceFormatter, - ICurrencyService currencyService, ITaxService taxService, IPriceCalculationService priceCalculationService, - Currency currency, decimal priceAdjustment = decimal.Zero, bool languageIndependent = false) + public static string GetBasePriceInfo(this Product product, + ILocalizationService localizationService, + IPriceFormatter priceFormatter, + ICurrencyService currencyService, + ITaxService taxService, + IPriceCalculationService priceCalculationService, + Currency currency, + decimal priceAdjustment = decimal.Zero, + bool languageInsensitive = false) { - if (product == null) - throw new ArgumentNullException("product"); - - if (localizationService == null && !languageIndependent) - throw new ArgumentNullException("localizationService"); + Guard.ArgumentNotNull(() => product); + Guard.ArgumentNotNull(() => currencyService); + Guard.ArgumentNotNull(() => taxService); + Guard.ArgumentNotNull(() => priceCalculationService); + Guard.ArgumentNotNull(() => currency); if (product.BasePriceHasValue && product.BasePriceAmount != Decimal.Zero) { @@ -331,7 +337,7 @@ public static string GetBasePriceInfo(this Product product, ILocalizationService price = currencyService.ConvertFromPrimaryStoreCurrency(price, currency); - return product.GetBasePriceInfo(price, localizationService, priceFormatter, currency, languageIndependent); + return product.GetBasePriceInfo(price, localizationService, priceFormatter, currency, languageInsensitive); } return ""; @@ -345,34 +351,36 @@ public static string GetBasePriceInfo(this Product product, ILocalizationService /// Localization service /// Price formatter /// Target currency - /// Whether the result string should be language independent + /// Whether the result string should be language insensitive /// The base price info public static string GetBasePriceInfo(this Product product, decimal productPrice, ILocalizationService localizationService, IPriceFormatter priceFormatter, Currency currency, - bool languageIndependent = false) + bool languageInsensitive = false) { + Guard.ArgumentNotNull(() => product); + Guard.ArgumentNotNull(() => localizationService); + Guard.ArgumentNotNull(() => priceFormatter); + Guard.ArgumentNotNull(() => currency); + if (product.BasePriceHasValue && product.BasePriceAmount != Decimal.Zero) { var value = Convert.ToDecimal((productPrice / product.BasePriceAmount) * product.BasePriceBaseAmount); var valueFormatted = priceFormatter.FormatPrice(value, true, currency); var amountFormatted = Math.Round(product.BasePriceAmount.Value, 2).ToString("G29"); - var result = "{0} {1} ({2} / {3} {1})".FormatInvariant( + var infoTemplate = localizationService.GetResource(languageInsensitive ? "Products.BasePriceInfo.LanguageInsensitive" : "Products.BasePriceInfo"); + + var result = infoTemplate.FormatInvariant( amountFormatted, product.BasePriceMeasureUnit, valueFormatted, product.BasePriceBaseAmount ); - if (languageIndependent) - { - return result; - } - - return string.Concat(localizationService.GetResource("Common.Content"), ": ", result); + return result; } return ""; From 06ba5bb79196550f0e115cadb21aaa22b7f7a5a0 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 1 Jun 2016 00:37:51 +0200 Subject: [PATCH 116/185] Enhanced safe local http call helper --- .../Utilities/FileDownloadManager.cs | 16 +++- src/Libraries/SmartStore.Core/WebHelper.cs | 76 +++++++++++++------ 2 files changed, 67 insertions(+), 25 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs b/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs index 7759a8bc93..d3fcef7874 100644 --- a/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs +++ b/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs @@ -31,14 +31,24 @@ public FileDownloadManager(HttpRequestBase httpRequest) /// The URL to download the file from (either a fully qualified URL or an app relative/absolute path) /// Specifies whether the FormsAuthenticationCookie should be sent /// Timeout in milliseconds - public FileDownloadResponse DownloadFile(string url, bool sendAuthCookie = false, int? timeout = null) + /// Specifiers whether the file is located on the local server + public FileDownloadResponse DownloadFile(string url, bool sendAuthCookie = false, int? timeout = null, bool isLocal = false) { Guard.ArgumentNotEmpty(() => url); url = WebHelper.GetAbsoluteUrl(url, _httpRequest); - var req = WebRequest.CreateHttp(url); - req.UserAgent = "SmartStore.NET"; + HttpWebRequest req; + + if (isLocal) + { + req = WebHelper.CreateHttpRequestForSafeLocalCall(new Uri(url)); + } + else + { + req = WebRequest.CreateHttp(url); + req.UserAgent = "SmartStore.NET"; + } if (timeout.HasValue) { diff --git a/src/Libraries/SmartStore.Core/WebHelper.cs b/src/Libraries/SmartStore.Core/WebHelper.cs index 2f622643d9..d170abc60c 100644 --- a/src/Libraries/SmartStore.Core/WebHelper.cs +++ b/src/Libraries/SmartStore.Core/WebHelper.cs @@ -2,6 +2,7 @@ using System.Configuration; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Web; @@ -13,6 +14,9 @@ using SmartStore.Utilities; using System.Net; using System.Text; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Net.Sockets; namespace SmartStore.Core { @@ -24,7 +28,7 @@ public partial class WebHelper : IWebHelper private static readonly Regex s_staticExts = new Regex(@"(.*?)\.(css|js|png|jpg|jpeg|gif|bmp|html|htm|xml|pdf|doc|xls|rar|zip|ico|eot|svg|ttf|woff|otf|axd|ashx|less)", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex s_htmlPathPattern = new Regex(@"(?<=(?:href|src)=(?:""|'))(?!https?://)(?[^(?:""|')]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline); private static readonly Regex s_cssPathPattern = new Regex(@"url\('(?.+)'\)", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline); - private static string s_safeLocalHostName = null; + private static ConcurrentDictionary s_safeLocalHostNames = new ConcurrentDictionary(); private readonly HttpContextBase _httpContext; private bool? _isCurrentConnectionSecured; @@ -678,15 +682,15 @@ public static HttpWebRequest CreateHttpRequestForSafeLocalCall(Uri requestUri) { Guard.ArgumentNotNull(() => requestUri); - EnsureSafeLocalHostNameDetermined(requestUri); + var safeHostName = GetSafeLocalHostName(requestUri); var uri = requestUri; - if (!requestUri.Host.Equals(s_safeLocalHostName, StringComparison.OrdinalIgnoreCase)) + if (!requestUri.Host.Equals(safeHostName, StringComparison.OrdinalIgnoreCase)) { var url = String.Format("{0}://{1}{2}", requestUri.Scheme, - requestUri.Port == 80 ? s_safeLocalHostName : s_safeLocalHostName + ":" + requestUri.Port, + requestUri.IsDefaultPort ? safeHostName : safeHostName + ":" + requestUri.Port, requestUri.PathAndQuery); uri = new Uri(url); } @@ -699,30 +703,58 @@ public static HttpWebRequest CreateHttpRequestForSafeLocalCall(Uri requestUri) return request; } - private static void EnsureSafeLocalHostNameDetermined(Uri requestUri) + private static string GetSafeLocalHostName(Uri requestUri) { - if (s_safeLocalHostName == null) + return s_safeLocalHostNames.GetOrAdd(requestUri.Port, (port) => { - lock (s_lock) + var hostName = Dns.GetHostName(); + var hosts = new List { requestUri.Host, "localhost", hostName, "127.0.0.1" }; + + // now add all local IP addresses + var ipAddresses = Dns.GetHostAddresses(hostName).Where(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.ToString()); + hosts.AddRange(ipAddresses); + + var firstHost = true; + + foreach (var host in hosts) { - if (s_safeLocalHostName == null) + var url = String.Format("{0}://{1}/taskscheduler/noop", + requestUri.Scheme, + requestUri.IsDefaultPort ? host : host + ":" + requestUri.Port); + var uri = new Uri(url); + + var request = WebRequest.CreateHttp(uri); + request.ServerCertificateValidationCallback += (sender, cert, chain, errors) => true; + request.ServicePoint.Expect100Continue = false; + request.UserAgent = "SmartStore.NET"; + request.Timeout = firstHost ? 5000 : 500; + + firstHost = false; + + HttpWebResponse response = null; + + try { - //var hosts = new string[] - //{ - // requestUri.Host, - // "localhost", - // Dns.GetHostName(), // TODO: add local IPs also - // "127.0.0.1" - //}; - - //foreach (var host in hosts) - //{ - // // ... - //} - s_safeLocalHostName = "localhost"; // TBD: what about ssl and differing ports? + response = (HttpWebResponse)request.GetResponse(); + if (response.StatusCode == HttpStatusCode.OK) + { + return host; + } + } + catch + { + // try the next host + } + finally + { + if (response != null) + response.Dispose(); } } - } + + // None of the hosts are callable. WTF? + return requestUri.Host; + }); } } } From 1bb75a8f037ec9dceba198f7edf6afd5ccb43161 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 1 Jun 2016 19:51:36 +0200 Subject: [PATCH 117/185] TaskScheduler could fail polling when primary store url is an external IP address --- changelog.md | 2 + src/Libraries/SmartStore.Core/WebHelper.cs | 95 +++++++++++-------- .../Tasks/TaskScheduler.cs | 6 +- .../Seo/CanonicalHostNameAttribute.cs | 2 +- .../Controllers/CommonController.cs | 2 +- 5 files changed, 65 insertions(+), 42 deletions(-) diff --git a/changelog.md b/changelog.md index 97798e8f54..50fde28956 100644 --- a/changelog.md +++ b/changelog.md @@ -27,8 +27,10 @@ * Added paging to frontend order list * Added paging to backend checkout attribute list * #977 Show PAngV base/delivery amount also +* Updated LiveEngage LiveChat plugin ### Bugfixes +* TaskScheduler could fail polling when primary store url is an external IP address * Fixed ajax cache issue when saving payment or shipping restrictions. Internet Explorer showed the old data state (before storage). * "The provider failed at the Execute method: Member 'CurrentValues' cannot be called for the entity of type 'Product'" when exporting product attribute combinations * Bundles without selected attributes could throw an exception on product detail page diff --git a/src/Libraries/SmartStore.Core/WebHelper.cs b/src/Libraries/SmartStore.Core/WebHelper.cs index d170abc60c..fee592e852 100644 --- a/src/Libraries/SmartStore.Core/WebHelper.cs +++ b/src/Libraries/SmartStore.Core/WebHelper.cs @@ -198,12 +198,12 @@ private string GetStoreHost(bool useSsl, out bool appPathPossiblyAppended) if (_currentStore != null) { - var securityMode = _currentStore.GetSecurityMode(useSsl); + var securityMode = _currentStore.GetSecurityMode(); if (httpHost.IsEmpty()) { - //HTTP_HOST variable is not available. - //It's possible only when HttpContext is not available (for example, running in a schedule task) + // HTTP_HOST variable is not available. + // It's possible only when HttpContext is not available (for example, running in a schedule task) result = _currentStore.Url.EnsureEndsWith("/"); appPathPossiblyAppended = true; @@ -707,48 +707,33 @@ private static string GetSafeLocalHostName(Uri requestUri) { return s_safeLocalHostNames.GetOrAdd(requestUri.Port, (port) => { + // first try original host + if (TestHost(requestUri, requestUri.Host, 5000)) + { + return requestUri.Host; + } + + // try loopback var hostName = Dns.GetHostName(); - var hosts = new List { requestUri.Host, "localhost", hostName, "127.0.0.1" }; + var hosts = new List { "localhost", hostName, "127.0.0.1" }; + foreach (var host in hosts) + { + if (TestHost(requestUri, host, 500)) + { + return host; + } + } - // now add all local IP addresses + // try local IP addresses + hosts.Clear(); var ipAddresses = Dns.GetHostAddresses(hostName).Where(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.ToString()); hosts.AddRange(ipAddresses); - var firstHost = true; - foreach (var host in hosts) { - var url = String.Format("{0}://{1}/taskscheduler/noop", - requestUri.Scheme, - requestUri.IsDefaultPort ? host : host + ":" + requestUri.Port); - var uri = new Uri(url); - - var request = WebRequest.CreateHttp(uri); - request.ServerCertificateValidationCallback += (sender, cert, chain, errors) => true; - request.ServicePoint.Expect100Continue = false; - request.UserAgent = "SmartStore.NET"; - request.Timeout = firstHost ? 5000 : 500; - - firstHost = false; - - HttpWebResponse response = null; - - try - { - response = (HttpWebResponse)request.GetResponse(); - if (response.StatusCode == HttpStatusCode.OK) - { - return host; - } - } - catch - { - // try the next host - } - finally + if (TestHost(requestUri, host, 500)) { - if (response != null) - response.Dispose(); + return host; } } @@ -756,5 +741,41 @@ private static string GetSafeLocalHostName(Uri requestUri) return requestUri.Host; }); } + + private static bool TestHost(Uri originalUri, string host, int timeout) + { + var url = String.Format("{0}://{1}/taskscheduler/noop", + originalUri.Scheme, + originalUri.IsDefaultPort ? host : host + ":" + originalUri.Port); + var uri = new Uri(url); + + var request = WebRequest.CreateHttp(uri); + request.ServerCertificateValidationCallback += (sender, cert, chain, errors) => true; + request.ServicePoint.Expect100Continue = false; + request.UserAgent = "SmartStore.NET"; + request.Timeout = timeout; + + HttpWebResponse response = null; + + try + { + response = (HttpWebResponse)request.GetResponse(); + if (response.StatusCode == HttpStatusCode.OK) + { + return true; + } + } + catch + { + // try the next host + } + finally + { + if (response != null) + response.Dispose(); + } + + return false; + } } } diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs index 4ca2101c29..0e055029a3 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs @@ -167,9 +167,9 @@ protected internal virtual void CallEndpoint(Uri uri) { if (_shuttingDown) return; - - var req = WebHelper.CreateHttpRequestForSafeLocalCall(uri); - req.Method = "POST"; + + var req = WebHelper.CreateHttpRequestForSafeLocalCall(uri); + req.Method = "POST"; req.ContentType = "text/plain"; req.ContentLength = 0; req.Timeout = 10000; // 10 sec. diff --git a/src/Presentation/SmartStore.Web.Framework/Seo/CanonicalHostNameAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Seo/CanonicalHostNameAttribute.cs index 2b3d2f2bd5..f683d550e1 100644 --- a/src/Presentation/SmartStore.Web.Framework/Seo/CanonicalHostNameAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Seo/CanonicalHostNameAttribute.cs @@ -57,7 +57,7 @@ public virtual void OnAuthorization(AuthorizationContext filterContext) if (securityMode == HttpSecurityMode.SharedSsl) { // Don't attempt to redirect when shared SSL is being used and current request is secured. - // Redirecting from https://ssl.bla.com to https://www.ssl.bla.com will most probably fail. + // Redirecting from http://ssl.bla.com to https://www.ssl.bla.com will most probably fail. return; } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs index 1da4e0fed7..4bebf35ee1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs @@ -522,7 +522,7 @@ public ActionResult Warnings() string sitemapUrl = null; try { - sitemapUrl = Url.RouteUrl("SitemapSEO", (object)null, _securitySettings.Value.ForceSslForAllPages ? "https" : "http"); + sitemapUrl = WebHelper.GetAbsoluteUrl(Url.RouteUrl("SitemapSEO"), this.Request); var request = WebHelper.CreateHttpRequestForSafeLocalCall(new Uri(sitemapUrl)); request.Method = "HEAD"; request.Timeout = 15000; From 2eb3c1ce1bde9bb6b3fafec6026fb58d27a8bdd3 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 2 Jun 2016 15:18:15 +0200 Subject: [PATCH 118/185] Minor fix in string extension --- .../SmartStore.Core/Extensions/StringExtensions.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs index 5a172fc17f..9131705215 100644 --- a/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs @@ -490,16 +490,19 @@ public static string[] SplitSafe(this string value, string separator) /// true: success, false: failure [DebuggerStepThrough] [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] - public static bool SplitToPair(this string value, out string strLeft, out string strRight, string delimiter) { + public static bool SplitToPair(this string value, out string strLeft, out string strRight, string delimiter) + { int idx = -1; - if (value.IsEmpty() || delimiter.IsEmpty() || (idx = value.IndexOf(delimiter)) == -1) + if (string.IsNullOrEmpty(value) || string.IsNullOrEmpty(delimiter) || (idx = value.IndexOf(delimiter)) == -1) { strLeft = value; strRight = ""; return false; } + strLeft = value.Substring(0, idx); strRight = value.Substring(idx + delimiter.Length); + return true; } From 253103745bf32f8b0615d6cde073760c8ed543bf Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 3 Jun 2016 13:04:47 +0200 Subject: [PATCH 119/185] Private messages: Fixes "No route in the route table matches the supplied values" --- changelog.md | 1 + .../Controllers/PrivateMessagesController.cs | 69 +++++++++---------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/changelog.md b/changelog.md index 50fde28956..826a9778de 100644 --- a/changelog.md +++ b/changelog.md @@ -52,6 +52,7 @@ * #971 Product is added to cart automatically if it has a non-required file upload attribute * #973 Bundle item upload is nowhere linked * Base price in product list ignored PriceDisplayType (catalog settings) and possibly displayed the wrong base price info +* Private messages: Fixes "No route in the route table matches the supplied values" ## SmartStore.NET 2.5 diff --git a/src/Presentation/SmartStore.Web/Controllers/PrivateMessagesController.cs b/src/Presentation/SmartStore.Web/Controllers/PrivateMessagesController.cs index 81ed0af2d8..8171bf6d4f 100644 --- a/src/Presentation/SmartStore.Web/Controllers/PrivateMessagesController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/PrivateMessagesController.cs @@ -4,20 +4,19 @@ using SmartStore.Core; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Logging; using SmartStore.Services.Customers; using SmartStore.Services.Forums; using SmartStore.Services.Helpers; -using SmartStore.Services.Localization; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Security; using SmartStore.Web.Models.Common; using SmartStore.Web.Models.PrivateMessages; -using SmartStore.Core.Logging; -using SmartStore.Web.Framework.Filters; namespace SmartStore.Web.Controllers { - [RequireHttpsByConfigAttribute(SslRequirement.Yes)] + [RequireHttpsByConfigAttribute(SslRequirement.Yes)] public partial class PrivateMessagesController : PublicControllerBase { #region Fields @@ -25,7 +24,6 @@ public partial class PrivateMessagesController : PublicControllerBase private readonly IForumService _forumService; private readonly ICustomerService _customerService; private readonly ICustomerActivityService _customerActivityService; - private readonly ILocalizationService _localizationService; private readonly IWorkContext _workContext; private readonly IStoreContext _storeContext; private readonly IDateTimeHelper _dateTimeHelper; @@ -37,16 +35,17 @@ public partial class PrivateMessagesController : PublicControllerBase #region Constructors public PrivateMessagesController(IForumService forumService, - ICustomerService customerService, ICustomerActivityService customerActivityService, - ILocalizationService localizationService, - IWorkContext workContext, IStoreContext storeContext, + ICustomerService customerService, + ICustomerActivityService customerActivityService, + IWorkContext workContext, + IStoreContext storeContext, IDateTimeHelper dateTimeHelper, - ForumSettings forumSettings, CustomerSettings customerSettings) + ForumSettings forumSettings, + CustomerSettings customerSettings) { this._forumService = forumService; this._customerService = customerService; this._customerActivityService = customerActivityService; - this._localizationService = localizationService; this._workContext = workContext; this._storeContext = storeContext; this._dateTimeHelper = dateTimeHelper; @@ -131,7 +130,7 @@ public ActionResult Inbox(int page, string tab) foreach (var pm in list) { - inbox.Add(new PrivateMessageModel() + inbox.Add(new PrivateMessageModel { Id = pm.Id, FromCustomerId = pm.FromCustomer.Id, @@ -175,7 +174,7 @@ public ActionResult SentItems(int page, string tab) foreach (var pm in list) { - sentItems.Add(new PrivateMessageModel() + sentItems.Add(new PrivateMessageModel { Id = pm.Id, FromCustomerId = pm.FromCustomer.Id, @@ -227,8 +226,8 @@ public ActionResult DeleteInboxPM(FormCollection formCollection) } } } - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } [HttpPost, FormValueRequired("mark-unread"), ActionName("InboxUpdate")] public ActionResult MarkUnread(FormCollection formCollection) @@ -256,7 +255,7 @@ public ActionResult MarkUnread(FormCollection formCollection) } } } - return RedirectToRoute("PrivateMessages"); + return RedirectToAction("Index"); } //updates sent items (deletes PrivateMessages) @@ -306,8 +305,8 @@ public ActionResult Send(int id /* toCustomerId */, int? replyToMessageId) if (customerTo == null || customerTo.IsGuest()) { - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } var model = new SendPrivateMessageModel(); model.ToCustomerId = customerTo.Id; @@ -319,8 +318,8 @@ public ActionResult Send(int id /* toCustomerId */, int? replyToMessageId) var replyToPM = _forumService.GetPrivateMessageById(replyToMessageId.Value); if (replyToPM == null) { - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } if (replyToPM.ToCustomerId == _workContext.CurrentCustomer.Id || replyToPM.FromCustomerId == _workContext.CurrentCustomer.Id) { @@ -329,10 +328,10 @@ public ActionResult Send(int id /* toCustomerId */, int? replyToMessageId) } else { - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } } - return View(model); + return View(model); } [HttpPost] @@ -358,8 +357,8 @@ public ActionResult Send(SendPrivateMessageModel model) } else { - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } } else { @@ -368,8 +367,8 @@ public ActionResult Send(SendPrivateMessageModel model) if (toCustomer == null || toCustomer.IsGuest()) { - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } model.ToCustomerId = toCustomer.Id; model.CustomerToName = toCustomer.FormatUserName(); model.AllowViewingToProfile = _customerSettings.AllowViewingProfiles && !toCustomer.IsGuest(); @@ -408,9 +407,9 @@ public ActionResult Send(SendPrivateMessageModel model) _forumService.InsertPrivateMessage(privateMessage); //activity log - _customerActivityService.InsertActivity("PublicStore.SendPM", _localizationService.GetResource("ActivityLog.PublicStore.SendPM"), toCustomer.Email); + _customerActivityService.InsertActivity("PublicStore.SendPM", T("ActivityLog.PublicStore.SendPM", toCustomer.Email)); - return RedirectToRoute("PrivateMessages", new { tab = "sent" }); + return RedirectToAction("Index", new { tab = "sent" }); } catch (Exception ex) { @@ -438,8 +437,8 @@ public ActionResult View(int id /* privateMessageId */) { if (pm.ToCustomerId != _workContext.CurrentCustomer.Id && pm.FromCustomerId != _workContext.CurrentCustomer.Id) { - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } if (!pm.IsRead && pm.ToCustomerId == _workContext.CurrentCustomer.Id) { @@ -449,10 +448,10 @@ public ActionResult View(int id /* privateMessageId */) } else { - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } - var model = new PrivateMessageModel() + var model = new PrivateMessageModel { Id = pm.Id, FromCustomerId = pm.FromCustomer.Id, @@ -497,8 +496,8 @@ public ActionResult Delete(int id /* privateMessageId */) _forumService.UpdatePrivateMessage(pm); } } - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } #endregion } From e2d903b5e04e294cd8013d9d6fe11ba6510856b0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 3 Jun 2016 14:04:20 +0200 Subject: [PATCH 120/185] Localized some hard coded strings --- .../201605201911421_ExportRevision.cs | 10 ++++++ .../Controllers/CustomerController.cs | 35 ++++++++----------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs index 4618512963..485fa9fdec 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs @@ -162,6 +162,16 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "{0} {1} ({2} / {3} {1})", "{0} {1} ({2} / {3} {1})"); + builder.AddOrUpdate("PrivateMessages.Disabled", + "Private messages are disabled.", + "Private Nachrichten sind deaktiviert."); + + builder.AddOrUpdate("Common.MethodNotSupportedForGuests", + "This function is not available for guests.", + "Diese Funktion steht für Gäste nicht zur Verfügung."); + + + builder.Delete( "Admin.DataExchange.Export.FolderAndFileName.Validate", "Admin.DataExchange.Export.Deployment.IsPublic", diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs index ee08e3f976..44b6c10a0f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs @@ -974,19 +974,22 @@ public ActionResult ChangePassword(CustomerModel model) var customer = _customerService.GetCustomerById(model.Id); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) { - var changePassRequest = new ChangePasswordRequest(model.Email, - false, _customerSettings.DefaultPasswordFormat, model.Password); + var changePassRequest = new ChangePasswordRequest(model.Email, false, _customerSettings.DefaultPasswordFormat, model.Password); var changePassResult = _customerRegistrationService.ChangePassword(changePassRequest); - if (changePassResult.Success) - NotifySuccess(_localizationService.GetResource("Admin.Customers.Customers.PasswordChanged")); - else - foreach (var error in changePassResult.Errors) + + if (changePassResult.Success) + { + NotifySuccess(_localizationService.GetResource("Admin.Customers.Customers.PasswordChanged")); + } + else + { + foreach (var error in changePassResult.Errors) NotifyError(error); + } } return RedirectToAction("Edit", customer.Id); @@ -1001,7 +1004,6 @@ public ActionResult MarkVatNumberAsValid(CustomerModel model) var customer = _customerService.GetCustomerById(model.Id); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); _genericAttributeService.SaveAttribute(customer, @@ -1020,7 +1022,6 @@ public ActionResult MarkVatNumberAsInvalid(CustomerModel model) var customer = _customerService.GetCustomerById(model.Id); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); _genericAttributeService.SaveAttribute(customer, @@ -1078,7 +1079,6 @@ public ActionResult Impersonate(int id) var customer = _customerService.GetCustomerById(id); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); // ensure that a non-admin user cannot impersonate as an administrator @@ -1150,19 +1150,18 @@ public ActionResult SendPm(CustomerModel model) var customer = _customerService.GetCustomerById(model.Id); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); try { if (!_forumSettings.AllowPrivateMessages) - throw new SmartException("Private messages are disabled"); + throw new SmartException(T("PrivateMessages.Disabled")); if (customer.IsGuest()) - throw new SmartException("Customer should be registered"); + throw new SmartException(T("Common.MethodNotSupportedForGuests")); if (String.IsNullOrWhiteSpace(model.SendPm.Subject)) - throw new SmartException("PM subject is empty"); + throw new SmartException(T("Admin.Customers.Customers.SendPM.Subject.Hint")); if (String.IsNullOrWhiteSpace(model.SendPm.Message)) - throw new SmartException("PM message is empty"); + throw new SmartException(T("Admin.Customers.Customers.SendPM.Message.Hint")); var privateMessage = new PrivateMessage @@ -1314,7 +1313,6 @@ public ActionResult AddressCreate(int customerId) var customer = _customerService.GetCustomerById(customerId); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); var model = new CustomerAddressModel(); @@ -1359,7 +1357,6 @@ public ActionResult AddressCreate(CustomerAddressModel model) var customer = _customerService.GetCustomerById(model.CustomerId); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) @@ -1408,12 +1405,10 @@ public ActionResult AddressEdit(int addressId, int customerId) var customer = _customerService.GetCustomerById(customerId); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); var address = _addressService.GetAddressById(addressId); if (address == null) - //No address found with the specified id return RedirectToAction("Edit", new { id = customer.Id }); var model = new CustomerAddressModel(); @@ -1466,12 +1461,10 @@ public ActionResult AddressEdit(CustomerAddressModel model) var customer = _customerService.GetCustomerById(model.CustomerId); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); var address = _addressService.GetAddressById(model.Address.Id); if (address == null) - //No address found with the specified id return RedirectToAction("Edit", new { id = customer.Id }); if (ModelState.IsValid) From fb2d4cc63f926092ee641a7fb6fd4142656b38cd Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Fri, 3 Jun 2016 15:27:07 +0200 Subject: [PATCH 121/185] DRY Fix: Merged GetOverrideKeys & UpdateSettings with their corresponding methods for updating localized settings --- .../Settings/StoreDependingSettingHelper.cs | 136 ++++++------------ 1 file changed, 44 insertions(+), 92 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs b/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs index 0c73aa1f3a..80a2d035e8 100644 --- a/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs +++ b/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs @@ -19,6 +19,7 @@ public StoreDependingSettingHelper(ViewDataDictionary viewData) } public static string ViewDataKey { get { return "StoreDependingSettingData"; } } + public StoreDependingSettingData Data { get @@ -38,16 +39,19 @@ private bool IsOverrideChecked(string settingKey, FormCollection form) } return false; } + public bool IsOverrideChecked(object settings, string name, FormCollection form) { var key = settings.GetType().Name + "." + name; return IsOverrideChecked(key, form); } + public void AddOverrideKey(object settings, string name) { var key = settings.GetType().Name + "." + name; Data.OverrideSettingKeys.Add(key); } + public void CreateViewDataObject(int activeStoreScopeConfiguration, string rootSettingClass = null) { _viewData[ViewDataKey] = new StoreDependingSettingData() @@ -57,112 +61,60 @@ public void CreateViewDataObject(int activeStoreScopeConfiguration, string rootS }; } - public void GetOverrideKeys(object settings, object model, int storeId, ISettingService settingService, bool isRootModel = true) - { - if (storeId <= 0) - return; // single store mode -> there are no overrides + public void GetOverrideKeys(object settings, object model, int storeId, ISettingService settingService, bool isRootModel = true, ILocalizedModelLocal localized = null, int? index = null) + { + if (storeId <= 0) + return; // single store mode -> there are no overrides - var data = Data; - if (data == null) - data = new StoreDependingSettingData(); + var data = Data; + if (data == null) + data = new StoreDependingSettingData(); - var settingName = settings.GetType().Name; - var properties = settings.GetType().GetProperties(); + var settingName = settings.GetType().Name; + var properties = settings.GetType().GetProperties(); + var localizedEntityService = EngineContext.Current.Resolve(); var modelType = model.GetType(); - foreach (var prop in properties) - { - var name = prop.Name; - var modelProperty = modelType.GetProperty(name); - - if (modelProperty == null) - continue; // setting is not configurable or missing or whatever... however we don't need the override info - - var key = settingName + "." + name; - var setting = settingService.GetSettingByKey(key, storeId: storeId); - - if (setting != null) - data.OverrideSettingKeys.Add(key); - } - - if (isRootModel) - { - data.ActiveStoreScopeConfiguration = storeId; - data.RootSettingClass = settingName; - - _viewData[ViewDataKey] = data; - } - } - - // DRY? - public void GetOverrideKeysLocalized(object settings, object model, int storeId, ISettingService settingService, bool isRootModel = true, ILocalizedModelLocal localized = null, int? index = null) - { - if (storeId <= 0) - return; // single store mode -> there are no overrides - - var data = Data; - if (data == null) - data = new StoreDependingSettingData(); - - var settingName = settings.GetType().Name; - var properties = localized.GetType().GetProperties(); - var localizedEntityService = EngineContext.Current.Resolve(); - - var modelType = model.GetType(); - - foreach (var prop in properties) - { - var name = prop.Name; + foreach (var prop in properties) + { + var name = prop.Name; var modelProperty = modelType.GetProperty(name); - if (modelProperty == null) - continue; // setting is not configurable or missing or whatever... however we don't need the override info - - var key = "Locales[" + index.ToString() + "]." + name; + if (modelProperty == null) + continue; // setting is not configurable or missing or whatever... however we don't need the override info - var resultStr = localizedEntityService.GetLocalizedValue(localized.LanguageId, 0, settingName, name); + var key = String.Empty; + var setting = String.Empty; - if (!String.IsNullOrEmpty(resultStr)) - data.OverrideSettingKeys.Add(key); - } + if (localized == null) + { + key = settingName + "." + name; + setting = settingService.GetSettingByKey(key, storeId: storeId); + } + else + { + key = "Locales[" + index.ToString() + "]." + name; + setting = localizedEntityService.GetLocalizedValue(localized.LanguageId, 0, settingName, name); + } - if (isRootModel) - { - data.ActiveStoreScopeConfiguration = storeId; - data.RootSettingClass = settingName; + if (!String.IsNullOrEmpty(setting)) + data.OverrideSettingKeys.Add(key); + } - _viewData[ViewDataKey] = data; - } - } + if (isRootModel) + { + data.ActiveStoreScopeConfiguration = storeId; + data.RootSettingClass = settingName; - public void UpdateSettings(object settings, FormCollection form, int storeId, ISettingService settingService) - { - var settingName = settings.GetType().Name; - var properties = FastProperty.GetProperties(settings.GetType()).Values; - - foreach (var prop in properties) - { - var name = prop.Name; - var key = settingName + "." + name; - - if (storeId == 0 || IsOverrideChecked(key, form)) - { - dynamic value = prop.GetValue(settings); - settingService.SetSetting(key, value == null ? "" : value, storeId, false); - } - else if (storeId > 0) - { - settingService.DeleteSetting(key, storeId); - } - } - } + _viewData[ViewDataKey] = data; + } + } - // DRY? - public void UpdateLocalizedSettings(object settings, FormCollection form, int storeId, ISettingService settingService, ILocalizedModelLocal localized) + public void UpdateSettings(object settings, FormCollection form, int storeId, ISettingService settingService, ILocalizedModelLocal localized = null) { var settingName = settings.GetType().Name; - var properties = FastProperty.GetProperties(localized.GetType()).Values; + var properties = FastProperty.GetProperties(localized == null ? settings.GetType() : localized.GetType()).Values; foreach (var prop in properties) { @@ -171,7 +123,7 @@ public void UpdateLocalizedSettings(object settings, FormCollection form, int st if (storeId == 0 || IsOverrideChecked(key, form)) { - dynamic value = prop.GetValue(settings); + dynamic value = prop.GetValue(localized == null ? settings : localized); settingService.SetSetting(key, value == null ? "" : value, storeId, false); } else if (storeId > 0) From c86f744de409ce8034921b7e69be05c136272a3e Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 6 Jun 2016 12:33:58 +0200 Subject: [PATCH 122/185] Minor improvements --- .../DataExchange/Export/DataExporter.cs | 9 +++++++-- .../DataExchange/Export/DynamicEntityHelper.cs | 17 +++++++++++++---- .../DataExchange/Export/ExportXmlHelper.cs | 1 + .../Export/Internal/DataExporterContext.cs | 6 ++++-- .../Views/Category/_CreateOrUpdate.cshtml | 4 +++- .../Product/_CreateOrUpdate.Discounts.cshtml | 6 ++++-- 6 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 4b818df859..b910cf23df 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -61,7 +61,8 @@ public partial class DataExporter : IDataExporter private readonly Lazy _productAttributeParser; private readonly Lazy _productAttributeService; private readonly Lazy _productTemplateService; - private readonly Lazy _productService; + private readonly Lazy _categoryTemplateService; + private readonly Lazy _productService; private readonly Lazy _orderService; private readonly Lazy _manufacturerService; private readonly ICustomerService _customerService; @@ -101,6 +102,7 @@ public DataExporter( Lazy productAttributeParser, Lazy productAttributeService, Lazy productTemplateService, + Lazy categoryTemplateService, Lazy productService, Lazy orderService, Lazy manufacturerService, @@ -138,6 +140,7 @@ public DataExporter( _productAttributeParser = productAttributeParser; _productAttributeService = productAttributeService; _productTemplateService = productTemplateService; + _categoryTemplateService = categoryTemplateService; _productService = productService; _orderService = orderService; _manufacturerService = manufacturerService; @@ -1199,7 +1202,8 @@ private void ExportCoreOuter(DataExporterContext ctx) { ctx.DeliveryTimes = _deliveryTimeService.Value.GetAllDeliveryTimes().ToDictionary(x => x.Id); ctx.QuantityUnits = _quantityUnitService.Value.GetAllQuantityUnits().ToDictionary(x => x.Id); - ctx.ProductTemplates = _productTemplateService.Value.GetAllProductTemplates().ToDictionary(x => x.Id); + ctx.ProductTemplates = _productTemplateService.Value.GetAllProductTemplates().ToDictionary(x => x.Id, x => x.ViewPath); + ctx.CategoryTemplates = _categoryTemplateService.Value.GetAllCategoryTemplates().ToDictionary(x => x.Id, x => x.ViewPath); if (ctx.Request.Provider.Value.EntityType == ExportEntityType.Product) { @@ -1293,6 +1297,7 @@ private void ExportCoreOuter(DataExporterContext ctx) { ctx.NewsletterSubscriptions.Clear(); ctx.ProductTemplates.Clear(); + ctx.CategoryTemplates.Clear(); ctx.Countries.Clear(); ctx.Languages.Clear(); ctx.QuantityUnits.Clear(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs index e6e4f39a81..f9e4bd158a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs @@ -487,6 +487,11 @@ private dynamic ToDynamic(DataExporterContext ctx, Category category) result.Picture = null; + if (ctx.CategoryTemplates.ContainsKey(category.CategoryTemplateId)) + result._CategoryTemplateViewPath = ctx.CategoryTemplates[category.CategoryTemplateId]; + else + result._CategoryTemplateViewPath = ""; + result._Localized = GetLocalized(ctx, category, x => x.Name, x => x.FullName, @@ -547,7 +552,6 @@ private dynamic ToDynamic( product.MergeWithCombination(combination); var languageId = (ctx.Projection.LanguageId ?? 0); - var productTemplate = ctx.ProductTemplates.FirstOrDefault(x => x.Key == product.ProductTemplateId); var pictureSize = _mediaSettings.Value.ProductDetailsPictureSize; var numberOfPictures = (ctx.Projection.NumberOfPictures ?? int.MaxValue); int[] pictureIds = (combination == null ? new int[0] : combination.GetAssignedPictureIds()); @@ -586,7 +590,10 @@ private dynamic ToDynamic( dynObject._BasePriceInfo = product.GetBasePriceInfo(_services.Localization, _priceFormatter.Value, _currencyService.Value, _taxService.Value, _priceCalculationService.Value, ctx.ContextCurrency, decimal.Zero, true); - dynObject._ProductTemplateViewPath = (productTemplate.Value == null ? "" : productTemplate.Value.ViewPath); + if (ctx.ProductTemplates.ContainsKey(product.ProductTemplateId)) + dynObject._ProductTemplateViewPath = ctx.ProductTemplates[product.ProductTemplateId]; + else + dynObject._ProductTemplateViewPath = ""; var detailUrl = ctx.Store.Url.EnsureEndsWith("/") + (string)dynObject.SeName; @@ -1081,9 +1088,11 @@ private List Convert(DataExporterContext ctx, Order order) .Select(e => { dynamic dyn = ToDynamic(ctx, e); - var productTemplate = ctx.ProductTemplates.FirstOrDefault(x => x.Key == e.Product.ProductTemplateId); - dyn.Product._ProductTemplateViewPath = (productTemplate.Value == null ? "" : productTemplate.Value.ViewPath); + if (ctx.ProductTemplates.ContainsKey(e.Product.ProductTemplateId)) + dyn.Product._ProductTemplateViewPath = ctx.ProductTemplates[e.Product.ProductTemplateId]; + else + dyn.Product._ProductTemplateViewPath = ""; dyn.Product._BasePriceInfo = e.Product.GetBasePriceInfo(_services.Localization, _priceFormatter.Value, _currencyService.Value, _taxService.Value, _priceCalculationService.Value, ctx.ContextCurrency, decimal.Zero, true); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs index 6f84b5f064..605a5dafd3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs @@ -335,6 +335,7 @@ public void WriteCategory(dynamic category, string node) _writer.Write("Description", (string)category.Description); _writer.Write("BottomDescription", (string)category.BottomDescription); _writer.Write("CategoryTemplateId", entity.CategoryTemplateId.ToString()); + _writer.Write("CategoryTemplateViewPath", (string)category._CategoryTemplateViewPath); _writer.Write("MetaKeywords", (string)category.MetaKeywords); _writer.Write("MetaDescription", (string)category.MetaDescription); _writer.Write("MetaTitle", (string)category.MetaTitle); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs index f909b93678..5c6c1315dd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs @@ -34,7 +34,8 @@ public DataExporterContext( Stores = new Dictionary(); Languages = new Dictionary(); Countries = new Dictionary(); - ProductTemplates = new Dictionary(); + ProductTemplates = new Dictionary(); + CategoryTemplates = new Dictionary(); NewsletterSubscriptions = new HashSet(); RecordsPerStore = new Dictionary(); @@ -109,7 +110,8 @@ public bool IsFileBasedExport public Dictionary Stores { get; set; } public Dictionary Languages { get; set; } public Dictionary Countries { get; set; } - public Dictionary ProductTemplates { get; set; } + public Dictionary ProductTemplates { get; set; } + public Dictionary CategoryTemplates { get; set; } public HashSet NewsletterSubscriptions { get; set; } // data loaded once per page diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml index 507546d242..f78aa0983e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml @@ -456,7 +456,9 @@ } else { - @T("Admin.Catalog.Categories.Discounts.NoDiscounts") +
+ @T("Admin.Catalog.Categories.Discounts.NoDiscounts") +
} } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Discounts.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Discounts.cshtml index 23e3aa1efb..694263f00a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Discounts.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Discounts.cshtml @@ -6,13 +6,15 @@ { } } else { - @T("Admin.Catalog.Products.Discounts.NoDiscounts") +
+ @T("Admin.Catalog.Products.Discounts.NoDiscounts") +
} From fa2388e55be5a601b238ec90b3d766ecc83ceb59 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 6 Jun 2016 14:22:13 +0200 Subject: [PATCH 123/185] Flag to exclude data from XML export --- .../DataExchange/Export/ExportXmlHelper.cs | 75 ++++++++++++------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs index 605a5dafd3..68c8b78d55 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs @@ -49,6 +49,8 @@ public static XmlWriterSettings DefaultSettings } } + public ExportXmlExclude Exclude { get; set; } + public XmlWriter Writer { get { return _writer; } @@ -330,37 +332,41 @@ public void WriteCategory(dynamic category, string node) } _writer.Write("Id", entity.Id.ToString()); - _writer.Write("Name", (string)category.Name); - _writer.Write("FullName", (string)category.FullName); - _writer.Write("Description", (string)category.Description); - _writer.Write("BottomDescription", (string)category.BottomDescription); - _writer.Write("CategoryTemplateId", entity.CategoryTemplateId.ToString()); - _writer.Write("CategoryTemplateViewPath", (string)category._CategoryTemplateViewPath); - _writer.Write("MetaKeywords", (string)category.MetaKeywords); - _writer.Write("MetaDescription", (string)category.MetaDescription); - _writer.Write("MetaTitle", (string)category.MetaTitle); - _writer.Write("SeName", (string)category.SeName); - _writer.Write("ParentCategoryId", entity.ParentCategoryId.ToString()); - _writer.Write("PictureId", entity.PictureId.HasValue ? entity.PictureId.Value.ToString() : ""); - _writer.Write("PageSize", entity.PageSize.ToString()); - _writer.Write("AllowCustomersToSelectPageSize", entity.AllowCustomersToSelectPageSize.ToString()); - _writer.Write("PageSizeOptions", entity.PageSizeOptions); - _writer.Write("PriceRanges", entity.PriceRanges); - _writer.Write("ShowOnHomePage", entity.ShowOnHomePage.ToString()); - _writer.Write("HasDiscountsApplied", entity.HasDiscountsApplied.ToString()); - _writer.Write("Published", entity.Published.ToString()); - _writer.Write("Deleted", entity.Deleted.ToString()); - _writer.Write("DisplayOrder", entity.DisplayOrder.ToString()); - _writer.Write("CreatedOnUtc", entity.CreatedOnUtc.ToString(_culture)); - _writer.Write("UpdatedOnUtc", entity.UpdatedOnUtc.ToString(_culture)); - _writer.Write("SubjectToAcl", entity.SubjectToAcl.ToString()); - _writer.Write("LimitedToStores", entity.LimitedToStores.ToString()); - _writer.Write("Alias", (string)category.Alias); - _writer.Write("DefaultViewMode", entity.DefaultViewMode); - WritePicture(category.Picture, "Picture"); + if (!Exclude.HasFlag(ExportXmlExclude.Category)) + { + _writer.Write("Name", (string)category.Name); + _writer.Write("FullName", (string)category.FullName); + _writer.Write("Description", (string)category.Description); + _writer.Write("BottomDescription", (string)category.BottomDescription); + _writer.Write("CategoryTemplateId", entity.CategoryTemplateId.ToString()); + _writer.Write("CategoryTemplateViewPath", (string)category._CategoryTemplateViewPath); + _writer.Write("MetaKeywords", (string)category.MetaKeywords); + _writer.Write("MetaDescription", (string)category.MetaDescription); + _writer.Write("MetaTitle", (string)category.MetaTitle); + _writer.Write("SeName", (string)category.SeName); + _writer.Write("ParentCategoryId", entity.ParentCategoryId.ToString()); + _writer.Write("PictureId", entity.PictureId.HasValue ? entity.PictureId.Value.ToString() : ""); + _writer.Write("PageSize", entity.PageSize.ToString()); + _writer.Write("AllowCustomersToSelectPageSize", entity.AllowCustomersToSelectPageSize.ToString()); + _writer.Write("PageSizeOptions", entity.PageSizeOptions); + _writer.Write("PriceRanges", entity.PriceRanges); + _writer.Write("ShowOnHomePage", entity.ShowOnHomePage.ToString()); + _writer.Write("HasDiscountsApplied", entity.HasDiscountsApplied.ToString()); + _writer.Write("Published", entity.Published.ToString()); + _writer.Write("Deleted", entity.Deleted.ToString()); + _writer.Write("DisplayOrder", entity.DisplayOrder.ToString()); + _writer.Write("CreatedOnUtc", entity.CreatedOnUtc.ToString(_culture)); + _writer.Write("UpdatedOnUtc", entity.UpdatedOnUtc.ToString(_culture)); + _writer.Write("SubjectToAcl", entity.SubjectToAcl.ToString()); + _writer.Write("LimitedToStores", entity.LimitedToStores.ToString()); + _writer.Write("Alias", (string)category.Alias); + _writer.Write("DefaultViewMode", entity.DefaultViewMode); - WriteLocalized(category); + WritePicture(category.Picture, "Picture"); + + WriteLocalized(category); + } if (node.HasValue()) { @@ -898,4 +904,15 @@ public void WriteCustomer(dynamic customer, string node) } } } + + + /// + /// Allows to exclude XML nodes from export + /// + [Flags] + public enum ExportXmlExclude + { + None = 0, + Category = 1 + } } From 9952273f3813dd184ae455f4bc3097e3d55a501a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 7 Jun 2016 12:21:50 +0200 Subject: [PATCH 124/185] Confirm order: Do not display empty cart or empty order summary together with buy button. Do not display duplicate warnings. --- .../Controllers/CheckoutController.cs | 93 +++++++++++-------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs b/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs index 7856167fb5..5047764d52 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs @@ -796,18 +796,22 @@ public ActionResult Confirm() [ValidateInput(false)] public ActionResult ConfirmOrder(FormCollection form) { - //validation - var cart = _workContext.CurrentCustomer.GetCartItems(ShoppingCartType.ShoppingCart, _storeContext.CurrentStore.Id); + //validation + var storeId = _storeContext.CurrentStore.Id; + var customer = _workContext.CurrentCustomer; + var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, storeId); if (cart.Count == 0) return RedirectToRoute("ShoppingCart"); - if ((_workContext.CurrentCustomer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed)) + if ((customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed)) return new HttpUnauthorizedResult(); - //model var model = new CheckoutConfirmModel(); - try + PlaceOrderResult placeOrderResult = null; + PostProcessPaymentRequest postProcessPaymentRequest = null; + + try { var processPaymentRequest = _httpContext.Session["OrderPaymentInfo"] as ProcessPaymentRequest; if (processPaymentRequest == null) @@ -820,55 +824,68 @@ public ActionResult ConfirmOrder(FormCollection form) } //prevent 2 orders being placed within an X seconds time frame - if (!IsMinimumOrderPlacementIntervalValid(_workContext.CurrentCustomer)) - throw new Exception(_localizationService.GetResource("Checkout.MinOrderPlacementInterval")); + if (!IsMinimumOrderPlacementIntervalValid(customer)) + throw new Exception(T("Checkout.MinOrderPlacementInterval")); //place order - processPaymentRequest.StoreId = _storeContext.CurrentStore.Id; - processPaymentRequest.CustomerId = _workContext.CurrentCustomer.Id; - processPaymentRequest.PaymentMethodSystemName = _workContext.CurrentCustomer.GetAttribute( - SystemCustomerAttributeNames.SelectedPaymentMethod, _genericAttributeService, _storeContext.CurrentStore.Id); + processPaymentRequest.StoreId = storeId; + processPaymentRequest.CustomerId = customer.Id; + processPaymentRequest.PaymentMethodSystemName = customer.GetAttribute(SystemCustomerAttributeNames.SelectedPaymentMethod, _genericAttributeService, storeId); var placeOrderExtraData = new Dictionary(); placeOrderExtraData["CustomerComment"] = form["customercommenthidden"]; placeOrderExtraData["SubscribeToNewsLetter"] = form["SubscribeToNewsLetterHidden"]; placeOrderExtraData["AcceptThirdPartyEmailHandOver"] = form["AcceptThirdPartyEmailHandOverHidden"]; - var placeOrderResult = _orderProcessingService.PlaceOrder(processPaymentRequest, placeOrderExtraData); - - if (placeOrderResult.Success) - { - var postProcessPaymentRequest = new PostProcessPaymentRequest - { - Order = placeOrderResult.PlacedOrder - }; - - _paymentService.PostProcessPayment(postProcessPaymentRequest); - - _httpContext.Session["PaymentData"] = null; - _httpContext.Session["OrderPaymentInfo"] = null; - _httpContext.RemoveCheckoutState(); - - if (postProcessPaymentRequest.RedirectUrl.HasValue()) - { - return Redirect(postProcessPaymentRequest.RedirectUrl); - } + placeOrderResult = _orderProcessingService.PlaceOrder(processPaymentRequest, placeOrderExtraData); - return RedirectToAction("Completed"); - } - else + if (!placeOrderResult.Success) { model.Warnings.AddRange(placeOrderResult.Errors.Select(x => HtmlUtils.ConvertPlainTextToHtml(x))); } } - catch (Exception exc) + catch (Exception exception) { - Logger.Warning(exc.Message, exc); - model.Warnings.Add(exc.Message); + Logger.Warning(exception.Message, exception); + + if (!model.Warnings.Any(x => x == exception.Message)) + { + model.Warnings.Add(exception.Message); + } } - return View(model); - } + if (placeOrderResult == null || !placeOrderResult.Success || model.Warnings.Any()) + { + return View(model); + } + + try + { + postProcessPaymentRequest = new PostProcessPaymentRequest + { + Order = placeOrderResult.PlacedOrder + }; + + _paymentService.PostProcessPayment(postProcessPaymentRequest); + } + catch (Exception exception) + { + NotifyError(exception); + } + finally + { + _httpContext.Session["PaymentData"] = null; + _httpContext.Session["OrderPaymentInfo"] = null; + _httpContext.RemoveCheckoutState(); + } + + if (postProcessPaymentRequest != null && postProcessPaymentRequest.RedirectUrl.HasValue()) + { + return Redirect(postProcessPaymentRequest.RedirectUrl); + } + + return RedirectToAction("Completed"); + } public ActionResult Completed() From 21d76ad0f4250bf7cd7e56839d6365a9c2dc8d20 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 7 Jun 2016 13:22:33 +0200 Subject: [PATCH 125/185] Re-post payment should friendly output exceptions --- .../Controllers/OrderController.cs | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Controllers/OrderController.cs index bf9e11078e..448a1983d3 100644 --- a/src/Presentation/SmartStore.Web/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/OrderController.cs @@ -680,21 +680,29 @@ public ActionResult RePostPayment(int id) if (IsUnauthorizedOrder(order)) return new HttpUnauthorizedResult(); - if (_paymentService.CanRePostProcessPayment(order)) + try { - var postProcessPaymentRequest = new PostProcessPaymentRequest + if (_paymentService.CanRePostProcessPayment(order)) { - Order = order, - IsRePostProcessPayment = true - }; + var postProcessPaymentRequest = new PostProcessPaymentRequest + { + Order = order, + IsRePostProcessPayment = true + }; - _paymentService.PostProcessPayment(postProcessPaymentRequest); + _paymentService.PostProcessPayment(postProcessPaymentRequest); - if (postProcessPaymentRequest.RedirectUrl.HasValue()) - { - return Redirect(postProcessPaymentRequest.RedirectUrl); + if (postProcessPaymentRequest.RedirectUrl.HasValue()) + { + return Redirect(postProcessPaymentRequest.RedirectUrl); + } } } + catch (Exception exception) + { + NotifyError(exception); + } + return RedirectToAction("Details", "Order", new { id = order.Id }); } From 9b6b5b3dba4961d17ca2dd1ff30acd2cbe4a1c26 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 7 Jun 2016 14:32:48 +0200 Subject: [PATCH 126/185] Obsolete because of client-side validation --- src/Presentation/SmartStore.Web/Views/Customer/Info.cshtml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Views/Customer/Info.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/Info.cshtml index 22eca6cfcf..fbf29a96b4 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/Info.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/Info.cshtml @@ -52,11 +52,6 @@ { @Html.AntiForgeryToken() - if (!ViewData.ModelState.IsValid) - { - @Html.ValidationSummary(true) - } -
@T("Account.YourPersonalDetails") From e1e35e7f396560a9630b2bd24cd9ff0ecc6f7723 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 8 Jun 2016 13:41:19 +0200 Subject: [PATCH 127/185] SEO fix: Google doesn't like review pages without reviews --- src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml b/src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml index 0d8f9ba9a8..c0fa631da9 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml @@ -6,6 +6,11 @@ //title Html.AddTitleParts(Model.ProductName); Html.AddTitleParts(T("PageTitle.ProductReviews").Text); + + if (Model.Items.Count == 0) + { + Html.AddCustomHeadParts(""); + } }

@T("Reviews.ProductReviewsFor")

From 16cc815d629d43c5b86649e083aae4ea96e579be Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 9 Jun 2016 12:10:17 +0200 Subject: [PATCH 128/185] Addendum to the last commit --- src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml b/src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml index c0fa631da9..f819e40646 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml @@ -9,7 +9,7 @@ if (Model.Items.Count == 0) { - Html.AddCustomHeadParts(""); + Html.AddCustomHeadParts(""); } }
From 414ecfcc075c7147aff02d584c94ce1f750c0fbb Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 9 Jun 2016 12:50:53 +0200 Subject: [PATCH 129/185] PayPal Plus: minor correction --- src/Libraries/SmartStore.Core/Domain/Orders/Order.cs | 2 +- src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs | 2 +- src/Plugins/SmartStore.PayPal/Services/PayPalService.cs | 9 +++++---- .../Administration/Views/Order/Edit.cshtml | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs b/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs index d59871a751..9db5f14733 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs @@ -451,7 +451,7 @@ protected virtual SortedDictionary ParseTaxRates(string taxRat public int? RewardPointsRemaining { get; set; } /// - /// Gets or sets a value indicating whether a new payment notification (IPN) arrived + /// Gets or sets a value indicating whether a new payment notification arrived (IPN, webhook, callback etc.) /// [DataMember] public bool HasNewPaymentNotification { get; set; } diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs index cfb377fabb..6a799e16a1 100644 --- a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -12,7 +12,7 @@ namespace SmartStore.PayPal.Services { public interface IPayPalService { - void AddOrderNote(PayPalSettingsBase settings, Order order, string anyString); + void AddOrderNote(PayPalSettingsBase settings, Order order, string anyString, bool isIpn = false); void LogError(Exception exception, string shortMessage = null, string fullMessage = null, bool notify = false, IList errors = null, bool isWarning = false); diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 0b96289c19..f5819da195 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -184,7 +184,7 @@ public static Dictionary GetSecurityProtocols() return dic; } - public void AddOrderNote(PayPalSettingsBase settings, Order order, string anyString) + public void AddOrderNote(PayPalSettingsBase settings, Order order, string anyString, bool isIpn = false) { try { @@ -201,6 +201,9 @@ public void AddOrderNote(PayPalSettingsBase settings, Order order, string anyStr sb.AppendFormat("{0}", note); + if (isIpn) + order.HasNewPaymentNotification = true; + _orderService.AddOrderNote(order, sb.ToString()); } catch { } @@ -1034,9 +1037,7 @@ public HttpStatusCode ProcessWebhook( break; } - order.HasNewPaymentNotification = true; - - AddOrderNote(settings, order, (string)ToInfoString(json)); + AddOrderNote(settings, order, (string)ToInfoString(json), true); return HttpStatusCode.OK; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml index 6749179a34..75fefead92 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml @@ -1775,7 +1775,7 @@ @Html.SmartLabelFor(model => model.AddOrderNoteMessage) - @Html.TextAreaFor(model => model.AddOrderNoteMessage, new { style = "Width: 350px; Height: 150px;" }) + @Html.TextAreaFor(model => model.AddOrderNoteMessage, new { @class = "control-xlarge", style = "height: 120px;" }) @Html.ValidationMessageFor(model => model.AddOrderNoteMessage) From 698a89b496b2b57fe1a022f805b1725de6c85a42 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 10 Jun 2016 14:51:58 +0200 Subject: [PATCH 130/185] Minor change --- .../Administration/Views/Export/_ProfileList.cshtml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml index ffa740bd96..7ae123c0a9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml @@ -87,7 +87,10 @@ } else { -
@T("Admin.DataExchange.Export.NoProfiles")
+
+ + @T("Admin.DataExchange.Export.NoProfiles") +
} @if (!isSystemProfiles) From fe245b30927ebd529a50b88612e56b19a51fc913 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 11 Jun 2016 10:32:34 +0200 Subject: [PATCH 131/185] Updated changelog --- changelog.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 826a9778de..c714683f3e 100644 --- a/changelog.md +++ b/changelog.md @@ -3,7 +3,8 @@ ## SmartStore.NET 2.5.1 ### Highlights -* #637 Integrated PayPal PLUS payment Provider +* #637 Integrated PayPal PLUS payment provider +* #932 Implement paydirekt payment plugin * Major improvements in Importer: better field mapping, higher performance, bug fixes etc. ### New Features From 2768f18454303b6fb5b211e7f413dab816290ee6 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 13 Jun 2016 10:33:35 +0200 Subject: [PATCH 132/185] Updated changelog --- changelog.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index c714683f3e..156e86078c 100644 --- a/changelog.md +++ b/changelog.md @@ -3,9 +3,10 @@ ## SmartStore.NET 2.5.1 ### Highlights +* Major improvements in Importer: better field mapping, higher performance, bug fixes etc. * #637 Integrated PayPal PLUS payment provider * #932 Implement paydirekt payment plugin -* Major improvements in Importer: better field mapping, higher performance, bug fixes etc. +* New Viveum payment plugin ### New Features * #637 Integrated PayPal PLUS payment Provider @@ -54,6 +55,7 @@ * #973 Bundle item upload is nowhere linked * Base price in product list ignored PriceDisplayType (catalog settings) and possibly displayed the wrong base price info * Private messages: Fixes "No route in the route table matches the supplied values" +* Payone: Hash string incorrect for frontend API payments where the order has more than 9 products ## SmartStore.NET 2.5 From 3e7abd62d82e1d113016f8a9d4463f1cb92b2241 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 13 Jun 2016 21:02:38 +0200 Subject: [PATCH 133/185] Export mail notification: Download link not working if SSL is enabled --- changelog.md | 1 + .../DataExchange/Export/DataExporter.cs | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/changelog.md b/changelog.md index 156e86078c..3a070fd2f2 100644 --- a/changelog.md +++ b/changelog.md @@ -56,6 +56,7 @@ * Base price in product list ignored PriceDisplayType (catalog settings) and possibly displayed the wrong base price info * Private messages: Fixes "No route in the route table matches the supplied values" * Payone: Hash string incorrect for frontend API payments where the order has more than 9 products +* Export mail notification: Download link not working if SSL is enabled ## SmartStore.NET 2.5 diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index b910cf23df..173b61b475 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -535,7 +535,7 @@ private void SendCompletionEmail(DataExporterContext ctx, string zipPath) if (emailAccount == null) emailAccount = _emailAccountService.Value.GetDefaultEmailAccount(); - var downloadUrl = "{0}Admin/Export/DownloadExportFile/{1}?name=".FormatInvariant(_services.WebHelper.GetStoreLocation(false), ctx.Request.Profile.Id); + var downloadUrl = "{0}Admin/Export/DownloadExportFile/{1}?name=".FormatInvariant(_services.WebHelper.GetStoreLocation(ctx.Store.SslEnabled), ctx.Request.Profile.Id); var languageId = ctx.Projection.LanguageId ?? 0; var smtpContext = new SmtpContext(emailAccount); @@ -553,7 +553,7 @@ private void SendCompletionEmail(DataExporterContext ctx, string zipPath) if (ctx.IsFileBasedExport && File.Exists(zipPath)) { var fileName = Path.GetFileName(zipPath); - body.AppendFormat("

{1}

", downloadUrl + HttpUtility.UrlDecode(fileName), fileName); + body.AppendFormat("

{2}

", downloadUrl, HttpUtility.UrlEncode(fileName), fileName); } if (ctx.IsFileBasedExport && ctx.Result.Files.Any()) @@ -561,7 +561,7 @@ private void SendCompletionEmail(DataExporterContext ctx, string zipPath) body.Append("

"); foreach (var file in ctx.Result.Files) { - body.AppendFormat("

", downloadUrl + HttpUtility.UrlDecode(file.FileName), file.FileName); + body.AppendFormat("", downloadUrl, HttpUtility.UrlEncode(file.FileName), file.FileName); } body.Append("

"); } @@ -571,9 +571,6 @@ private void SendCompletionEmail(DataExporterContext ctx, string zipPath) if (ctx.Request.Profile.CompletedEmailAddresses.HasValue()) message.To.AddRange(ctx.Request.Profile.CompletedEmailAddresses.SplitSafe(",").Where(x => x.IsEmail()).Select(x => new EmailAddress(x))); - if (message.To.Count == 0 && _contactDataSettings.Value.WebmasterEmailAddress.HasValue()) - message.To.Add(new EmailAddress(_contactDataSettings.Value.WebmasterEmailAddress)); - if (message.To.Count == 0 && _contactDataSettings.Value.CompanyEmailAddress.HasValue()) message.To.Add(new EmailAddress(_contactDataSettings.Value.CompanyEmailAddress)); From 7a06b158770eb869523faeca93289493d9037802 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 15 Jun 2016 12:01:18 +0200 Subject: [PATCH 134/185] Export: _IsChild indicates that current row is a child row (e.g. an attribute combination exported as a product) --- .../DataExchange/Export/DynamicEntityHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs index f9e4bd158a..96362b5999 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs @@ -606,8 +606,8 @@ private dynamic ToDynamic( } } + dynObject._IsChild = (combination != null); dynObject._DetailUrl = detailUrl; - dynObject._CategoryName = null; if (ctx.Categories.Count > 0) From 373c19dbac856baaed66f23951975297167d30fb Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 16 Jun 2016 10:50:56 +0200 Subject: [PATCH 135/185] Product details: Select attribute and gift card values by query string parameters --- changelog.md | 1 + .../Controllers/CatalogHelper.cs | 86 +++++++++++++++++++ .../Controllers/ProductController.cs | 2 + 3 files changed, 89 insertions(+) diff --git a/changelog.md b/changelog.md index 3a070fd2f2..4206394556 100644 --- a/changelog.md +++ b/changelog.md @@ -17,6 +17,7 @@ * Import localized SEO names for product and categories * #477 Implement option to specify the number of exported and imported pictures * #859 Make checkout attributes suitable for multi-stores +* Product details: Select attribute and gift card values by query string parameters ### Improvements * Major improvements in Importer: better field mapping, higher performance, bug fixes etc. diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index 55793edc65..8e2a4f8ca7 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -285,6 +285,92 @@ public ProductDetailsModel PrepareProductDetailsPageModel( return model; } + public void SelectProductAttributeValues(ProductDetailsModel model, NameValueCollection queryData) + { + Guard.ArgumentNotNull(() => model); + + if (queryData == null || queryData.Count <= 0) + return; + + try + { + var items = queryData.AllKeys + .Where(x => x.EmptyNull().StartsWith("product_attribute_")) + .SelectMany(queryData.GetValues, (k, v) => new { key = k, value = v }); + + foreach (var item in items) + { + var ids = item.key.Replace("product_attribute_", "").SplitSafe("_"); + if (ids.Count() > 3) + { + var productId = ids[0].ToInt(); + var bundleItemId = ids[1].ToInt(); + var productAttributeId = ids[2].ToInt(); + //var attributeId = ids[3].ToInt(); + + var attribute = model.ProductVariantAttributes.FirstOrDefault(x => + x.ProductId == productId && + x.BundleItemId == bundleItemId && + x.ProductAttributeId == productAttributeId); + + if (attribute != null) + { + switch (attribute.AttributeControlType) + { + case AttributeControlType.DropdownList: + case AttributeControlType.RadioList: + case AttributeControlType.Checkboxes: + case AttributeControlType.ColorSquares: + var attributeValue = attribute.Values.FirstOrDefault(x => x.Name.IsCaseInsensitiveEqual(item.value)); + if (attributeValue != null) + { + attribute.Values.Each(x => x.IsPreSelected = false); + attributeValue.IsPreSelected = true; + } + break; + case AttributeControlType.TextBox: + case AttributeControlType.MultilineTextbox: + attribute.TextValue = item.value; + break; + case AttributeControlType.Datepicker: + if (item.value.Length == 8) + { + attribute.SelectedYear = item.value.Substring(0, 4).ToInt(); + attribute.SelectedMonth = item.value.Substring(4, 2).ToInt(); + attribute.SelectedDay = item.value.Substring(6, 2).ToInt(); + } + break; + } + } + } + } + + items = queryData.AllKeys + .Where(x => x.EmptyNull().StartsWith("giftcard_")) + .SelectMany(queryData.GetValues, (k, v) => new { key = k, value = v }); + + foreach (var item in items) + { + var key = item.key.EmptyNull().ToLower(); + + if (key.EndsWith("recipientname")) + model.GiftCard.RecipientName = item.value; + else if (key.EndsWith("recipientemail")) + model.GiftCard.RecipientEmail = item.value; + else if (key.EndsWith("sendername")) + model.GiftCard.SenderName = item.value; + else if (key.EndsWith("senderemail")) + model.GiftCard.SenderEmail = item.value; + else if (key.EndsWith("message")) + model.GiftCard.Message = item.value; + } + } + catch (Exception exception) + { + Logger.Warning(exception.ToAllMessages(), exception); + } + } + public void PrepareProductReviewsModel(ProductReviewsModel model, Product product) { if (product == null) diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index 60bd17bca2..fa13176e2a 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -177,6 +177,8 @@ public ActionResult ProductDetails(int productId, string attributes) var model = _helper.PrepareProductDetailsPageModel(product, selectedAttributes: selectedAttributes); + _helper.SelectProductAttributeValues(model, Request.QueryString); + //save as recently viewed _recentlyViewedProductsService.AddProductToRecentlyViewedList(product.Id); From 9b922b529533573f03536dca5b91c0d84ac8150c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 16 Jun 2016 15:12:34 +0200 Subject: [PATCH 136/185] Display friendly error message if the rendering of an export provider configuration failed --- .../Administration/Views/Export/Edit.cshtml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index e3cd585606..f47ae86481 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -296,7 +296,18 @@ viewData.TemplateInfo.HtmlFieldPrefix = "CustomProperties[ProviderConfigData]"; viewData.ModelState.Merge(ViewData.ModelState); @Html.Hidden("CustomProperties[ProviderConfigData].__Type__", Model.Provider.ConfigDataType.AssemblyQualifiedName) - @Html.Partial(Model.Provider.ConfigPartialViewName, Model.Provider.ConfigData, viewData) + + try + { + @Html.Partial(Model.Provider.ConfigPartialViewName, Model.Provider.ConfigData, viewData) + } + catch (Exception exception) + { +
+ @T("Admin.Configuration.Plugins.UnknownError") +
+
@exception.ToString()
+ } } else { From 965ac71fb4ee9021289f6d52d48a2b9dd8834292 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 17 Jun 2016 17:19:43 +0200 Subject: [PATCH 137/185] Smarter import of plugin resource files (de-DE > de > de-* > en-US > en > en-* > *) --- .../Localization/ILocalizationService.cs | 9 +- .../Localization/LanguageService.cs | 7 +- .../Localization/LocalizationService.cs | 250 +++++++++++++----- .../Controllers/ProductController.cs | 2 +- 4 files changed, 191 insertions(+), 77 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Localization/ILocalizationService.cs b/src/Libraries/SmartStore.Services/Localization/ILocalizationService.cs index 918ad1be88..c2a57e6ad5 100644 --- a/src/Libraries/SmartStore.Services/Localization/ILocalizationService.cs +++ b/src/Libraries/SmartStore.Services/Localization/ILocalizationService.cs @@ -131,11 +131,14 @@ void ImportResourcesFromXml(Language language, ///
/// codehint: sm-add /// Descriptor of the plugin - /// Load them into list rather than into database + /// Load them into the passed list rather than into database /// Specifies whether user touched resources should also be updated /// Import only files for particular languages - void ImportPluginResourcesFromXml(PluginDescriptor pluginDescriptor, - List forceToList = null, bool updateTouchedResources = true, IList filterLanguages = null); + void ImportPluginResourcesFromXml( + PluginDescriptor pluginDescriptor, + IList targetList = null, + bool updateTouchedResources = true, + IList filterLanguages = null); /// /// Flattens all nested LocaleResource child nodes into a new document diff --git a/src/Libraries/SmartStore.Services/Localization/LanguageService.cs b/src/Libraries/SmartStore.Services/Localization/LanguageService.cs index bc8e7d0999..db75218b54 100644 --- a/src/Libraries/SmartStore.Services/Localization/LanguageService.cs +++ b/src/Libraries/SmartStore.Services/Localization/LanguageService.cs @@ -110,18 +110,19 @@ public virtual IList GetAllLanguages(bool showHidden = false, int stor { var query = _languageRepository.Table; if (!showHidden) - query = query.Where(l => l.Published); - query = query.OrderBy(l => l.DisplayOrder); + query = query.Where(x => x.Published); + query = query.OrderBy(x => x.DisplayOrder); return query.ToList(); }); - //store mapping + // store mapping if (storeId > 0) { languages = languages .Where(l => _storeMappingService.Authorize(l, storeId)) .ToList(); } + return languages; } diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs b/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs index 8610072e92..9f3a0bf29f 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs @@ -16,6 +16,8 @@ using SmartStore.Core.Events; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; +using SmartStore.Core.Localization; +using System.Globalization; namespace SmartStore.Services.Localization { @@ -172,6 +174,7 @@ orderby lsr.ResourceName if (localeStringResource == null && logIfNotFound) _logger.Warning(string.Format("Resource string ({0}) not found. Language ID = {1}", resourceName, languageId)); + return localeStringResource; } @@ -415,6 +418,182 @@ public virtual string ExportResourcesToXml(Language language) return stringWriter.ToString(); } + public virtual void ImportPluginResourcesFromXml( + PluginDescriptor pluginDescriptor, + IList targetList = null, + bool updateTouchedResources = true, + IList filterLanguages = null) + { + var directory = new DirectoryInfo(Path.Combine(pluginDescriptor.OriginalAssemblyFile.Directory.FullName, "Localization")); + + if (!directory.Exists) + return; + + if (targetList == null && updateTouchedResources) + { + DeleteLocaleStringResources(pluginDescriptor.ResourceRootKey); + } + + var unprocessedLanguages = new List(); + + var defaultLanguageId = _languageService.GetDefaultLanguageId(); + var languages = filterLanguages ?? _languageService.GetAllLanguages(true); + + string code = null; + foreach (var language in languages) + { + code = ImportPluginResourcesForLanguage( + language, + null, + directory, + pluginDescriptor.ResourceRootKey, + targetList, + updateTouchedResources, + false); + + if (code == null) + { + unprocessedLanguages.Add(language); + } + } + + if (filterLanguages == null && unprocessedLanguages.Count > 0) + { + // There were unprocessed languages (no corresponding resource file could be found). + // In order for GetResource() to be able to gracefully fallback to the default language's resources, + // we need to import resources for the current default language.... + var processedLanguages = languages.Except(unprocessedLanguages).ToList(); + if (!processedLanguages.Any(x => x.Id == defaultLanguageId)) + { + // ...but only if no resource file could be mapped to the default language before, + // namely because in this case the following operation would be redundant. + var defaultLanguage = _languageService.GetLanguageById(_languageService.GetDefaultLanguageId()); + if (defaultLanguage != null) + { + ImportPluginResourcesForLanguage( + defaultLanguage, + "en-us", + directory, + pluginDescriptor.ResourceRootKey, + targetList, + updateTouchedResources, + true); + } + } + } + } + + /// + /// Resolves a resource file for the specified language and processes the import + /// + /// Language + /// The culture code of the processed resource file + private string ImportPluginResourcesForLanguage( + Language language, + string fileCode, + DirectoryInfo directory, + string resourceRootKey, + IList targetList, + bool updateTouchedResources, + bool canFallBackToAnyResourceFile) + { + var fileNamePattern = "resources.{0}.xml"; + + var codeCandidates = GetResourceFileCodeCandidates( + fileCode ?? language.LanguageCulture, + directory, + canFallBackToAnyResourceFile); + + string path = null; + string code = null; + + foreach (var candidate in codeCandidates) + { + var pathCandidate = Path.Combine(directory.FullName, fileNamePattern.FormatInvariant(candidate)); + if (File.Exists(pathCandidate)) + { + code = candidate; + path = pathCandidate; + break; + } + } + + if (code != null) + { + var doc = new XmlDocument(); + + doc.Load(path); + doc = FlattenResourceFile(doc); + + if (targetList == null) + { + ImportResourcesFromXml(language, doc, resourceRootKey, true, updateTouchedResources: updateTouchedResources); + } + else + { + var nodes = doc.SelectNodes(@"//Language/LocaleResource"); + foreach (XmlNode node in nodes) + { + var valueNode = node.SelectSingleNode("Value"); + var res = new LocaleStringResource() + { + ResourceName = node.Attributes["Name"].InnerText.Trim(), + ResourceValue = (valueNode == null ? "" : valueNode.InnerText), + LanguageId = language.Id, + IsFromPlugin = true + }; + + if (res.ResourceName.HasValue()) + { + targetList.Add(res); + } + } + } + } + + return code; + } + + private IEnumerable GetResourceFileCodeCandidates(string code, DirectoryInfo directory, bool canFallBackToAnyResourceFile) + { + // exact match (de-DE) + yield return code; + + // neutral culture (de) + var ci = CultureInfo.GetCultureInfo(code); + if (ci.Parent != null && !ci.IsNeutralCulture) + { + code = ci.Parent.Name; + yield return code; + } + + var rgFileName = new Regex("^resources.(.+?).xml$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + // any other region with same language (de-*) + foreach (var fi in directory.EnumerateFiles("resources.{0}-*.xml".FormatInvariant(code), SearchOption.TopDirectoryOnly)) + { + code = rgFileName.Match(fi.Name).Groups[1].Value; + if (LocalizationHelper.IsValidCultureCode(code)) + { + yield return code; + yield break; + } + } + + if (canFallBackToAnyResourceFile) + { + foreach (var fi in directory.EnumerateFiles("resources.*.xml", SearchOption.TopDirectoryOnly)) + { + code = rgFileName.Match(fi.Name).Groups[1].Value; + if (LocalizationHelper.IsValidCultureCode(code)) + { + yield return code; + yield break; + } + } + } + } + /// /// Import language resources from XML file /// @@ -490,80 +669,11 @@ public virtual void ImportResourcesFromXml( _lsrRepository.UpdateRange(toUpdate); toUpdate.Clear(); - //clear cache + // clear cache _cacheManager.RemoveByPattern(LOCALESTRINGRESOURCES_PATTERN_KEY); } } - /// - /// Import plugin resources from xml files in plugin's localization directory. - /// - /// Descriptor of the plugin - /// Load them into list rather than into database - /// Specifies whether user touched resources should also be updated - /// Import only files for particular languages - public virtual void ImportPluginResourcesFromXml(PluginDescriptor pluginDescriptor, - List forceToList = null, bool updateTouchedResources = true, IList filterLanguages = null) - { - string pluginDir = pluginDescriptor.OriginalAssemblyFile.Directory.FullName; - string localizationDir = Path.Combine(pluginDir, "Localization"); - - if (!System.IO.Directory.Exists(localizationDir)) - return; - - if (forceToList == null && updateTouchedResources) - DeleteLocaleStringResources(pluginDescriptor.ResourceRootKey); - - var languages = _languageService.GetAllLanguages(true); - var doc = new XmlDocument(); - - foreach (var filePath in System.IO.Directory.EnumerateFiles(localizationDir, "*.xml")) - { - Match match = Regex.Match(Path.GetFileName(filePath), Regex.Escape("resources.") + "(.*?)" + Regex.Escape(".xml")); - string languageCode = match.Groups[1].Value; - - Language language = languages.Where(l => l.LanguageCulture.IsCaseInsensitiveEqual(languageCode)).FirstOrDefault(); - if (language != null) - { - language = _languageService.GetLanguageById(language.Id); - } - - if (languageCode.HasValue() && language != null) - { - if (filterLanguages != null && !filterLanguages.Any(x => x.Id == language.Id)) - { - continue; - } - - doc.Load(filePath); - doc = FlattenResourceFile(doc); - - if (forceToList == null) - { - ImportResourcesFromXml(language, doc, pluginDescriptor.ResourceRootKey, true, updateTouchedResources: updateTouchedResources); - } - else - { - var nodes = doc.SelectNodes(@"//Language/LocaleResource"); - foreach (XmlNode node in nodes) - { - var valueNode = node.SelectSingleNode("Value"); - var res = new LocaleStringResource() - { - ResourceName = node.Attributes["Name"].InnerText.Trim(), - ResourceValue = (valueNode == null ? "" : valueNode.InnerText), - LanguageId = language.Id, - IsFromPlugin = true - }; - - if (res.ResourceName.HasValue()) - forceToList.Add(res); - } - } - } - } - } - public virtual XmlDocument FlattenResourceFile(XmlDocument source) { Guard.ArgumentNotNull(() => source); diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index fa13176e2a..b3f13e7653 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -168,7 +168,7 @@ public ActionResult ProductDetails(int productId, string attributes) } } - //prepare the model + // prepare the model var selectedAttributes = new NameValueCollection(); selectedAttributes.ConvertAttributeQueryData( From f4f0da67e171871679ccf7f4c27bd1247e42c5f5 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 17 Jun 2016 19:32:08 +0200 Subject: [PATCH 138/185] Updated changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 4206394556..058b7fcddb 100644 --- a/changelog.md +++ b/changelog.md @@ -23,6 +23,7 @@ * Major improvements in Importer: better field mapping, higher performance, bug fixes etc. * (Dev) Publishing SmartStore.Web from within Visual Studio now deploys the project correctly. No need to execute ClickToBuild.cmd anymore. Just publish directly to any target, including Azure. * Localization & SEO: language switcher now takes language specific SEO slugs into account when building links +* Smarter import of plugin resource files with graceful fallbacks (de-DE > de > de-* > en-US > en > en-* > *) * Exports the product detail link including the attribute query string when exporting attribute combinations * #918 Compare products: Display base price information * Export email attachments needs to be stored in database because the temp file may not exist anymore when sending the email From c6baefa0eac717f66b814d18ca9f6c9caa58ba49 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 17 Jun 2016 23:26:43 +0200 Subject: [PATCH 139/185] (Perf) Faster language resource file import --- changelog.md | 1 + src/Libraries/SmartStore.Data/EfRepository.cs | 2 +- .../Localization/ILocalizationService.cs | 7 ++- .../Localization/LocalizationService.cs | 56 ++++++++++++------- 4 files changed, 41 insertions(+), 25 deletions(-) diff --git a/changelog.md b/changelog.md index 058b7fcddb..ddeda6310a 100644 --- a/changelog.md +++ b/changelog.md @@ -24,6 +24,7 @@ * (Dev) Publishing SmartStore.Web from within Visual Studio now deploys the project correctly. No need to execute ClickToBuild.cmd anymore. Just publish directly to any target, including Azure. * Localization & SEO: language switcher now takes language specific SEO slugs into account when building links * Smarter import of plugin resource files with graceful fallbacks (de-DE > de > de-* > en-US > en > en-* > *) +* (Perf) Faster language resource file import * Exports the product detail link including the attribute query string when exporting attribute combinations * #918 Compare products: Display base price information * Export email attachments needs to be stored in database because the temp file may not exist anymore when sending the email diff --git a/src/Libraries/SmartStore.Data/EfRepository.cs b/src/Libraries/SmartStore.Data/EfRepository.cs index 860551f723..5afd5e5d41 100644 --- a/src/Libraries/SmartStore.Data/EfRepository.cs +++ b/src/Libraries/SmartStore.Data/EfRepository.cs @@ -161,7 +161,7 @@ public virtual void UpdateRange(IEnumerable entities) private void SetEntityStateToModifiedIfApplicable(T entity) { var entry = InternalContext.Entry(entity); - if (entry.State == System.Data.Entity.EntityState.Detached || (this.AutoCommitEnabledInternal && !InternalContext.Configuration.AutoDetectChangesEnabled)) + if (entry.State < System.Data.Entity.EntityState.Added || (this.AutoCommitEnabledInternal && !InternalContext.Configuration.AutoDetectChangesEnabled)) { entry.State = System.Data.Entity.EntityState.Modified; } diff --git a/src/Libraries/SmartStore.Services/Localization/ILocalizationService.cs b/src/Libraries/SmartStore.Services/Localization/ILocalizationService.cs index c2a57e6ad5..6195301c03 100644 --- a/src/Libraries/SmartStore.Services/Localization/ILocalizationService.cs +++ b/src/Libraries/SmartStore.Services/Localization/ILocalizationService.cs @@ -117,9 +117,10 @@ LocaleStringResource GetLocaleStringResourceByName(string resourceName, int lang /// Language /// XML document /// Prefix for resource key name - /// Specifies whether resource should be inserted or updated (or both) - /// Specifies whether user touched resources should also be updated - void ImportResourcesFromXml(Language language, + /// Specifies whether resource should be inserted or updated (or both) + /// Specifies whether user touched resources should also be updated + /// The number of processed (added or updated) resource entries + int ImportResourcesFromXml(Language language, XmlDocument xmlDocument, string rootKey = null, bool sourceIsPlugin = false, diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs b/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs index 9f3a0bf29f..c6ac869c27 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs @@ -273,6 +273,7 @@ orderby l.ResourceName string cacheKey = string.Format(LOCALESTRINGRESOURCES_ALL_KEY, languageId); var dict = _cacheManager.Get(cacheKey, () => { + // TODO: make result cacheable in distributed cache (IDictionary) var result = new ConcurrentDictionary>(8, 2000, StringComparer.CurrentCultureIgnoreCase); if (forceAll || _localizationSettings.LoadAllLocaleRecordsOnStartup) { @@ -594,13 +595,7 @@ private IEnumerable GetResourceFileCodeCandidates(string code, Directory } } - /// - /// Import language resources from XML file - /// - /// Language - /// XML document - /// Prefix for resource key name - public virtual void ImportResourcesFromXml( + public virtual int ImportResourcesFromXml( Language language, XmlDocument xmlDocument, string rootKey = null, @@ -608,12 +603,16 @@ public virtual void ImportResourcesFromXml( ImportModeFlags mode = ImportModeFlags.Insert | ImportModeFlags.Update, bool updateTouchedResources = false) { - using (var scope = new DbContextScope(autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) + using (var scope = new DbContextScope(autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false, forceNoTracking: true, hooksEnabled: false)) { var toAdd = new List(); var toUpdate = new List(); var nodes = xmlDocument.SelectNodes(@"//Language/LocaleResource"); + var resources = language.LocaleStringResources.ToDictionarySafe(x => x.ResourceName, StringComparer.OrdinalIgnoreCase); + + LocaleStringResource resource; + foreach (var xel in nodes.Cast()) { string name = xel.GetAttribute("Name").TrimSafe(); @@ -631,18 +630,23 @@ public virtual void ImportResourcesFromXml( name = "{0}.{1}".FormatWith(rootKey, name); } + resource = null; + // do not use "Insert"/"Update" methods because they clear cache // let's bulk insert - var resource = language.LocaleStringResources.Where(x => x.ResourceName.Equals(name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - if (resource != null) + //var resource = language.LocaleStringResources.Where(x => x.ResourceName.Equals(name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); + if (resources.TryGetValue(name, out resource)) { if (mode.HasFlag(ImportModeFlags.Update)) { if (updateTouchedResources || !resource.IsTouched.GetValueOrDefault()) { - resource.ResourceValue = value; - resource.IsTouched = null; - toUpdate.Add(resource); + if (value != resource.ResourceValue) + { + resource.ResourceValue = value; + resource.IsTouched = null; + toUpdate.Add(resource); + } } } } @@ -651,7 +655,7 @@ public virtual void ImportResourcesFromXml( if (mode.HasFlag(ImportModeFlags.Insert)) { toAdd.Add( - new LocaleStringResource() + new LocaleStringResource { LanguageId = language.Id, ResourceName = name, @@ -662,15 +666,25 @@ public virtual void ImportResourcesFromXml( } } - _lsrRepository.AutoCommitEnabled = true; - _lsrRepository.InsertRange(toAdd, 500); - toAdd.Clear(); + //_lsrRepository.AutoCommitEnabled = true; - _lsrRepository.UpdateRange(toUpdate); - toUpdate.Clear(); + if (toAdd.Any() || toUpdate.Any()) + { + _lsrRepository.InsertRange(toAdd); + toAdd.Clear(); + + _lsrRepository.UpdateRange(toUpdate); + toUpdate.Clear(); + + int num = _lsrRepository.Context.SaveChanges(); + + // clear cache + _cacheManager.RemoveByPattern(LOCALESTRINGRESOURCES_PATTERN_KEY); + + return num; + } - // clear cache - _cacheManager.RemoveByPattern(LOCALESTRINGRESOURCES_PATTERN_KEY); + return 0; } } From 1a810e2c1ca95cdad0e62b6235963a813d26bf41 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 18 Jun 2016 00:04:56 +0200 Subject: [PATCH 140/185] Minor enhancements in data layer --- .../SmartStore.Data/ObjectContextBase.cs | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/Libraries/SmartStore.Data/ObjectContextBase.cs b/src/Libraries/SmartStore.Data/ObjectContextBase.cs index 579994d654..26b4e52171 100644 --- a/src/Libraries/SmartStore.Data/ObjectContextBase.cs +++ b/src/Libraries/SmartStore.Data/ObjectContextBase.cs @@ -16,6 +16,7 @@ using SmartStore.Core.Data; using SmartStore.Core.Data.Hooks; using SmartStore.Core.Events; +using System.Data.Entity.Core.Objects; namespace SmartStore.Data { @@ -138,7 +139,7 @@ public bool HooksEnabled public virtual string CreateDatabaseScript() { - return ((IObjectContextAdapter)this).ObjectContext.CreateDatabaseScript(); + return ObjectContext.CreateDatabaseScript(); } public new DbSet Set() where TEntity : BaseEntity @@ -219,7 +220,7 @@ private IEnumerable ToParameters(params object[] parameters) // database call var reader = cmd.ExecuteReader(); - var result = ((IObjectContextAdapter)(this)).ObjectContext.Translate(reader).ToList(); + var result = ObjectContext.Translate(reader).ToList(); if (!ForceNoTracking) { for (int i = 0; i < result.Count; i++) @@ -263,8 +264,8 @@ public int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, in if (timeout.HasValue) { //store previous timeout - previousTimeout = ((IObjectContextAdapter)this).ObjectContext.CommandTimeout; - ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = timeout; + previousTimeout = ObjectContext.CommandTimeout; + ObjectContext.CommandTimeout = timeout; } var transactionalBehavior = doNotEnsureTransaction @@ -275,7 +276,7 @@ public int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, in if (timeout.HasValue) { //Set previous timeout back - ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = previousTimeout; + ObjectContext.CommandTimeout = previousTimeout; } return result; @@ -498,27 +499,29 @@ protected internal bool IsSqlServer2012OrHigher() public TEntity Attach(TEntity entity) where TEntity : BaseEntity { - var dbSet = Set(); - var alreadyAttached = dbSet.Local.FirstOrDefault(x => x.Id == entity.Id); - - if (alreadyAttached == null) + ObjectStateEntry entry; + if (ObjectContext.ObjectStateManager.TryGetObjectStateEntry(entity, out entry)) { - dbSet.Attach(entity); - return entity; + if (entry.State != System.Data.Entity.EntityState.Detached) + { + return (TEntity)entry.Entity; + } } - return alreadyAttached; + Set().Attach(entity); + return entity; } public bool IsAttached(TEntity entity) where TEntity : BaseEntity { - if (entity != null) + ObjectStateEntry entry; + if (ObjectContext.ObjectStateManager.TryGetObjectStateEntry(entity, out entry)) { - return Set().Local.Any(x => x.Id == entity.Id); + return (entry.State != System.Data.Entity.EntityState.Detached); } - + return false; - } + } public void DetachEntity(TEntity entity) where TEntity : BaseEntity { @@ -550,7 +553,6 @@ public int DetachEntities(bool unchangedEntitiesOnly = true) where TEnt public void ChangeState(TEntity entity, System.Data.Entity.EntityState newState) where TEntity : BaseEntity { - Console.WriteLine("ChangeState ORIGINAL"); this.Entry(entity).State = newState; } @@ -559,6 +561,11 @@ public void ReloadEntity(TEntity entity) where TEntity : BaseEntity this.Entry(entity).Reload(); } + internal ObjectContext ObjectContext + { + get { return ((IObjectContextAdapter)this).ObjectContext; } + } + private string FormatValidationExceptionMessage(IEnumerable results) { var sb = new StringBuilder(); From 9069036320a6d38aa16a2c20d6ceae2eacf7e6cf Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 18 Jun 2016 17:06:37 +0200 Subject: [PATCH 141/185] Reviewed "Product details: Select attribute and gift card values by query string parameters" --- .../Catalog/ProductAttributeParser.cs | 15 +- .../NameValueCollectionExtensions.cs | 48 ++++-- ...teOrUpdateAttributeCombinationPopup.cshtml | 9 +- .../Controllers/CatalogHelper.cs | 143 +++++++----------- .../Controllers/ProductController.cs | 13 +- 5 files changed, 108 insertions(+), 120 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs index 23ee794f0b..9c87960614 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs @@ -374,15 +374,20 @@ public virtual ProductVariantAttributeCombination FindProductVariantAttributeCom public virtual List> DeserializeQueryData(string jsonData) { - if (jsonData.HasValue()) + try { - if (jsonData.StartsWith("[")) + if (jsonData.HasValue()) { - return JsonConvert.DeserializeObject>>(jsonData); - } + if (jsonData.StartsWith("[")) + { + return JsonConvert.DeserializeObject>>(jsonData); + } - return new List> { JsonConvert.DeserializeObject>(jsonData) }; + return new List> { JsonConvert.DeserializeObject>(jsonData) }; + } } + catch { } + return new List>(); } diff --git a/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs b/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs index a14c25e424..216894b2d2 100644 --- a/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs +++ b/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs @@ -13,6 +13,7 @@ namespace SmartStore { public static class NameValueCollectionExtensions { + // TODO: find place to make it public static private static string AttributeFormatedName(int productAttributeId, int attributeId, int productId = 0, int bundleItemId = 0) { if (productId == 0) @@ -25,34 +26,57 @@ public static void AddProductAttribute(this NameValueCollection collection, int { if (productAttributeId != 0 && attributeId != 0 && valueId != 0) { - string name = AttributeFormatedName(productAttributeId, attributeId, productId, bundleItemId); + var name = AttributeFormatedName(productAttributeId, attributeId, productId, bundleItemId); collection.Add(name, valueId.ToString()); } } /// - /// Converts attribute query data + /// Get selected attributes from query string /// - /// Name value collection + /// Name value collection with selected attributes + /// Query string parameters /// Attribute query data items with following structure: /// Product.Id, ProductAttribute.Id, Product_ProductAttribute_Mapping.Id, ProductVariantAttributeValue.Id, [BundleItem.Id] /// Product identifier to filter - public static void ConvertAttributeQueryData(this NameValueCollection collection, List> queryData, int productId = 0) + public static void GetSelectedAttributes(this NameValueCollection collection, NameValueCollection queryString, List> attributes, int productId = 0) { - if (collection == null || queryData == null || queryData.Count <= 0) - return; + Guard.NotNull(() => collection); - var items = queryData.Where(i => i.Count > 3); + // ambiguous parameters: let other query string parameters win over the json formatted attributes parameter + if (queryString != null && queryString.Count > 0) + { + var items = queryString.AllKeys + .Where(x => x.EmptyNull().StartsWith("product_attribute_")) + .SelectMany(queryString.GetValues, (k, v) => new { key = k.EmptyNull(), value = v.TrimSafe() }); - if (productId != 0) - items = items.Where(i => i[0] == productId); + foreach (var item in items) + { + var ids = item.key.Replace("product_attribute_", "").SplitSafe("_"); + if (ids.Count() > 3) + { + if (productId == 0 || (productId != 0 && productId == ids[0].ToInt())) + { + collection.Add(item.key, item.value); + } + } + } + } - foreach (var item in items) + if (attributes != null && attributes.Count > 0) { - var name = AttributeFormatedName(item[1], item[2], item[0], item.Count > 4 ? item[4] : 0); + var items = attributes.Where(i => i.Count > 3); + + if (productId != 0) + items = items.Where(i => i[0] == productId); - collection.Add(name, item[3].ToString()); + foreach (var item in items) + { + var name = AttributeFormatedName(item[1], item[2], item[0], item.Count > 4 ? item[4] : 0); + + collection.Add(name, item[3].ToString()); + } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateAttributeCombinationPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateAttributeCombinationPopup.cshtml index 92de51ade3..9e07dff329 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateAttributeCombinationPopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateAttributeCombinationPopup.cshtml @@ -29,12 +29,13 @@ @if (!ViewBag.IsEdit) { -
+
@if (Model.Warnings.Count > 0) { -
- @foreach (var warning in Model.Warnings) { -

 @warning

+
+ @foreach (var warning in Model.Warnings) + { +

 @warning

}
} diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index 8e2a4f8ca7..ebc506887c 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Diagnostics; using System.Linq; -using System.Linq.Expressions; using System.Web; using System.Web.Mvc; using SmartStore.Collections; @@ -25,14 +25,12 @@ using SmartStore.Services.Security; using SmartStore.Services.Seo; using SmartStore.Services.Tax; +using SmartStore.Services.Topics; using SmartStore.Web.Framework.UI; using SmartStore.Web.Framework.UI.Captcha; using SmartStore.Web.Infrastructure.Cache; using SmartStore.Web.Models.Catalog; using SmartStore.Web.Models.Media; -using SmartStore.Core.Data; -using System.Collections.Specialized; -using SmartStore.Services.Topics; namespace SmartStore.Web.Controllers { @@ -152,7 +150,8 @@ public ProductDetailsModel PrepareProductDetailsPageModel( bool isAssociatedProduct = false, ProductBundleItemData productBundleItem = null, IList productBundleItems = null, - NameValueCollection selectedAttributes = null) + NameValueCollection selectedAttributes = null, + NameValueCollection queryData = null) { if (product == null) throw new ArgumentNullException("product"); @@ -187,6 +186,30 @@ public ProductDetailsModel PrepareProductDetailsPageModel( IsCurrentCustomerRegistered = _services.WorkContext.CurrentCustomer.IsRegistered() }; + // get gift card values from query string + if (queryData != null && queryData.Count > 0) + { + var giftCardItems = queryData.AllKeys + .Where(x => x.EmptyNull().StartsWith("giftcard_")) + .SelectMany(queryData.GetValues, (k, v) => new { key = k, value = v.TrimSafe() }); + + foreach (var item in giftCardItems) + { + var key = item.key.EmptyNull().ToLower(); + + if (key.EndsWith("recipientname")) + model.GiftCard.RecipientName = item.value; + else if (key.EndsWith("recipientemail")) + model.GiftCard.RecipientEmail = item.value; + else if (key.EndsWith("sendername")) + model.GiftCard.SenderName = item.value; + else if (key.EndsWith("senderemail")) + model.GiftCard.SenderEmail = item.value; + else if (key.EndsWith("message")) + model.GiftCard.Message = item.value; + } + } + // Back in stock subscriptions if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock && product.BackorderMode == BackorderMode.NoBackorders && @@ -285,92 +308,6 @@ public ProductDetailsModel PrepareProductDetailsPageModel( return model; } - public void SelectProductAttributeValues(ProductDetailsModel model, NameValueCollection queryData) - { - Guard.ArgumentNotNull(() => model); - - if (queryData == null || queryData.Count <= 0) - return; - - try - { - var items = queryData.AllKeys - .Where(x => x.EmptyNull().StartsWith("product_attribute_")) - .SelectMany(queryData.GetValues, (k, v) => new { key = k, value = v }); - - foreach (var item in items) - { - var ids = item.key.Replace("product_attribute_", "").SplitSafe("_"); - if (ids.Count() > 3) - { - var productId = ids[0].ToInt(); - var bundleItemId = ids[1].ToInt(); - var productAttributeId = ids[2].ToInt(); - //var attributeId = ids[3].ToInt(); - - var attribute = model.ProductVariantAttributes.FirstOrDefault(x => - x.ProductId == productId && - x.BundleItemId == bundleItemId && - x.ProductAttributeId == productAttributeId); - - if (attribute != null) - { - switch (attribute.AttributeControlType) - { - case AttributeControlType.DropdownList: - case AttributeControlType.RadioList: - case AttributeControlType.Checkboxes: - case AttributeControlType.ColorSquares: - var attributeValue = attribute.Values.FirstOrDefault(x => x.Name.IsCaseInsensitiveEqual(item.value)); - if (attributeValue != null) - { - attribute.Values.Each(x => x.IsPreSelected = false); - attributeValue.IsPreSelected = true; - } - break; - case AttributeControlType.TextBox: - case AttributeControlType.MultilineTextbox: - attribute.TextValue = item.value; - break; - case AttributeControlType.Datepicker: - if (item.value.Length == 8) - { - attribute.SelectedYear = item.value.Substring(0, 4).ToInt(); - attribute.SelectedMonth = item.value.Substring(4, 2).ToInt(); - attribute.SelectedDay = item.value.Substring(6, 2).ToInt(); - } - break; - } - } - } - } - - items = queryData.AllKeys - .Where(x => x.EmptyNull().StartsWith("giftcard_")) - .SelectMany(queryData.GetValues, (k, v) => new { key = k, value = v }); - - foreach (var item in items) - { - var key = item.key.EmptyNull().ToLower(); - - if (key.EndsWith("recipientname")) - model.GiftCard.RecipientName = item.value; - else if (key.EndsWith("recipientemail")) - model.GiftCard.RecipientEmail = item.value; - else if (key.EndsWith("sendername")) - model.GiftCard.SenderName = item.value; - else if (key.EndsWith("senderemail")) - model.GiftCard.SenderEmail = item.value; - else if (key.EndsWith("message")) - model.GiftCard.Message = item.value; - } - } - catch (Exception exception) - { - Logger.Warning(exception.ToAllMessages(), exception); - } - } - public void PrepareProductReviewsModel(ProductReviewsModel model, Product product) { if (product == null) @@ -591,9 +528,31 @@ public ProductDetailsModel PrepareProductDetailModel( pvaModel.BeginYear = match.Groups[1].Value.ToInt(); pvaModel.EndYear = match.Groups[2].Value.ToInt(); } + + if (hasSelectedAttributes) + { + var attributeKey = "product_attribute_{0}_{1}_{2}_{3}".FormatInvariant(product.Id, bundleItemId, attribute.ProductAttributeId, attribute.Id); + var day = selectedAttributes[attributeKey + "_day"].ToInt(); + var month = selectedAttributes[attributeKey + "_month"].ToInt(); + var year = selectedAttributes[attributeKey + "_year"].ToInt(); + if (day > 0 && month > 0 && year > 0) + { + pvaModel.SelectedDay = day; + pvaModel.SelectedMonth = month; + pvaModel.SelectedYear = year; + } + } + } + else if (attribute.AttributeControlType == AttributeControlType.TextBox || attribute.AttributeControlType == AttributeControlType.MultilineTextbox) + { + if (hasSelectedAttributes) + { + var attributeKey = "product_attribute_{0}_{1}_{2}_{3}".FormatInvariant(product.Id, bundleItemId, attribute.ProductAttributeId, attribute.Id); + pvaModel.TextValue = selectedAttributes[attributeKey]; + } } - int preSelectedValueId = 0; + var preSelectedValueId = 0; var pvaValues = (attribute.ShouldHaveValues() ? _productAttributeService.GetProductVariantAttributeValues(attribute.Id) : new List()); foreach (var pvaValue in pvaValues) diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index b3f13e7653..79497418a9 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -8,7 +8,6 @@ using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Localization; using SmartStore.Services; using SmartStore.Services.Catalog; using SmartStore.Services.Common; @@ -31,7 +30,7 @@ namespace SmartStore.Web.Controllers { - public partial class ProductController : PublicControllerBase + public partial class ProductController : PublicControllerBase { #region Fields @@ -168,16 +167,16 @@ public ActionResult ProductDetails(int productId, string attributes) } } - // prepare the model var selectedAttributes = new NameValueCollection(); - selectedAttributes.ConvertAttributeQueryData( + // get selected attributes from query string + selectedAttributes.GetSelectedAttributes( + Request.QueryString, _productAttributeParser.DeserializeQueryData(attributes), product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing ? 0 : product.Id); - var model = _helper.PrepareProductDetailsPageModel(product, selectedAttributes: selectedAttributes); - - _helper.SelectProductAttributeValues(model, Request.QueryString); + // prepare the view model + var model = _helper.PrepareProductDetailsPageModel(product, selectedAttributes: selectedAttributes, queryData: Request.QueryString); //save as recently viewed _recentlyViewedProductsService.AddProductToRecentlyViewedList(product.Id); From 3b10ce6a44de3fa805074267b4940a68adfd2416 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 18 Jun 2016 17:51:18 +0200 Subject: [PATCH 142/185] Updated licensing component --- .../SmartStore.Licensing.dll | Bin 280576 -> 282624 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/lib/SmartStore.Licensing/SmartStore.Licensing.dll b/lib/SmartStore.Licensing/SmartStore.Licensing.dll index 51e391ce236bd865fee64fdd1a7c9ad3e42ef34e..8acdf39c24bdd3254678ba27247be601f4ecfe75 100644 GIT binary patch literal 282624 zcmd3P37izg*?w>LOwUZu%(A_+JF^F}1IXI!%q+_;;0hjq;DINKxQimYAPA_mGT_0w zD{4HVpklmYzzdJWJMlJZj2bmYBNFcjF&d9}UBEX|^%>uHPS>nX0Gi zse0>r>#jXy&gF`xC`t&Q?b{XQ5&Y7>$^59W zQe$)vHX4EpftigIv;rd)9R z!lRwo-cwqK$18M2*$+}h1=@N%%vqRMlcbY{Sru{vVEO4+c?#~p>7S-dK3`F)`2W(c zv4w<%9j^aD6h3THVN+Jr`5U+aa+e#D0<5<)#?+!_)$VFBUG)Q4dAz{7OI`T zW`d$rw7@<=)vRVkF)R`tK&maGWP!uKSwz0*hOTlbHhIuh>s*9vpNB8K#Ynfn|1QcA z@C>h}s@ddfk*j78;M>rr4?$pId>Zj-$LBzNdhj_NpG)w$0iXNvc>$mQ@O`$x-uVZ7 zsoDxepxPulOWOIKKzj)7(G>k80smCMsC%kC7oH{j0|8qE|E>goN5DyfrpP9E_gMclq*&2`eW!GHUG_0i|d+_${+o?W;71Sr_0L9F#?Lm?ZJ+0ns zy^XxE-oe*$RF+e8Yd}@>71xVOh%}x=;Uhg!Cw?{{oXRG%CDmpq9WW!^gVwPovm=F^ zL^4!+7y`1I@a4(GR9lSeH1fkN?gBT|X^TAC5I@=u75y5tG;TXu zTSEB-aQdgMtOc5{RJB{laDp`oK3lYvGaZx+E_#H6lEFofbWk$5=sgZf1{b~8LCN5v z_cU`g@;}6L4GJ`VanO~Jc~~jpR4h?8=trE3Bk{F`GEk+=lD4G!G4hnrE@X}s0k-f zO}LzD99ob_O=nIxpJEv`1BRjys-q-zZhzpr5&jX1$jTx4 zlF$l=9&#XUDX|7%XjDS4O3b5s+S6+dDDKsHgo2DDUF7sDai?vtLAa3;qrS#!B?GX^ zrpgu#ZLa}l`seK*U_Oib3i3;O&gpm1h|MA*ZrbI|{8~J*@T9dxDH2aPT57~&2Y`9(>k{&{J?7zYX?yifnYGB$|j!NTa898;!uoblbb*$H+aa zXK&n*>k2C55Q6T&y(q$E5>pl;e^AdLPj0qmcOu|;bwwX#NFq8NN?w5~;IR=(LdkW& zjq)ec5z}o{dPtGgC1y`#dzEfaVnal?_auX6?}eXq+f7_&JbgjJsbg$F@aP}VzJyL@ z1F*`b6#9pRGEImvc>j><*cCOMik*beEeN1H0A$cDNZ{!(sJ}oJR1B&YNCnqL5`rX0 zU1TXpYG1wdv=5AWgq&fY7+5=Eok8|f9nxor^{W)`PF7Sj8a5Ss)mZ;M6) z>HSg4FG-P7JsE5aNP2%HJPg4n3p45JgYmf(pZWNlfzLs3+Z~@@!TeWzFp}kQBlQhr z4*Gl#?w#H6fC4>%N|^QjK2B;nrwJ!ljdL11X=Dw8tHlLcCs5QaF&-4CDo|(%*>g}X zbbgL_5rV7Ph2J*>>S}OnvMb0Bg;OsD!(EM=HL;a|B zj1=hRav5_%4leY z@KWkm(0t9}fryvtG6`*y5ao5bvMB~#GD>-u3VdgE*{Q3;kvOPCkW%=F8~e<>$_*Z?g=*!69qW3r`8C>*U2PK1xlD^{JkPI%m!9mI3qGjqv zqEI*cQ!QjMA-_>4`opc%_5#XoI`xNl<3oL079Z*lsj!d7rw^Yb$iwj&kIye*{}P{G zm`R5mh+i@XeXf9?&ST*LEu~zCu#BDi3n!>Ru|gtHnw5dzY7mjS&ISRaqKFPr!kU2H z#xG&nCAx}5!mmRjgxyPlaL4CnSf|t?$SBb>NybLO=pmRoF$u2`@NNXV(;MN&bEE<} zxSgSNn1iTYohC8TBB+6)my=jKFf=yN8%nHfeWp~--p1tV6Kk9{!3M|*w}n)DvBpM& z&k76ugLjP$WndOj=G<0@WRr*Two=)Y;PEZQ4XjU1iEbW>BO4Xufr56WfBOgCO2J#yQ^1rSaCxIml98!D!2Tdsv z2!=)A9I2jOE4`p90C+eyb*2P<&A|3`4=mHK8 zcm|$4G4s9zPGaEXH;(v$1eP<Em4n&e-F#f&@?}5ylPA-sePf0|TFade%Idn#l}QOvq*=1u6!VpYHja4q5Tu zVc@%$U*TlHEC$vtI&@A`TnO?UBat7xe~gm@A2HH!$RC>Pg%{`|1bh1PSN%c8b29@& z=N)^J1kPnZ-F4LA61a|myR6s$E&&Xg5XSCDEMF;sItC&y&pki_-(cX%8?7}GxRikn zXFWe!0*^BAodXHWm9%+`GvcUk6$E1>0{v2^|L>g zz%vZYdvoUT66j{&*!GF46UanH?q2xi9O>mH2HrkKeLw=}ERf*83_WkI1Qszc<;8DT zN}!&BbGENML;}w4BXZ+ z>{405moczM{oDJ=XxbTA`tiB1$Yyj218Wxl?8YWjgo?2ZBw2LG9(PJk0|P_TO(#mA zm4VM^e?CnDpD=J=-y2ThwK1^ogLB6_vNkd@^zmlz}a`?s2%xGz_p1%YK(V`E%)G8Uxqd`k!w!Rf#-0l#zcP8yY6P%wZt$+gqNJ zKraJ#&K|O=PEatcK&aD?etm}YaTf#I_Wy8iSrp%4;0Fi%b(Zuog@JW9oH#)O`!Uck z>)U^o0J>`g`p(76|KjBHsf-+PTEyw~E@$Acp`7Am>t2jpdGT$I+TWdl_7@g7wR0^4 z$4%d`R>qTM;2#4EhB<-!TyWIfi4HQIk>B37yVFiE%7t+D_+eMdP>y2YkSlr{Byb`F zS9NY`m%u3uynFiW(XuRh7&z@0`G+0ldOjmRDKtB|bS49H?tSRTGLXv{xG}%wk4~ms z%t+UV16NBgCooX+;zv#cwHbK+s<);%$vKdb)@e5!EJZnufnR@i!ZR|EVGLAT(a93n zmjSc&tGEQd&A@@TY#ixG{{u$GrsiKDz1+$`{@DJF5}3`v=Vw1ZSL(_<1IM3o$=yzJ zFe5<5-u=&oD`m|Y%fRy2iz{Rxn6Sae!5Ejg!?q71|Lo%Az7}%12*=fW3GjMxu%0egCUT5UBm;M=(fxOMYK9klvS${YK zw_R29q>T4t2C5r-elCzb8t#4fZ z#4bX49KpaX3txWENgsyzkp9IF_HYIa>lk?MmDP91K;C0u`vYHoAc2D!xb@n_w@FD( zXW*v$&p1{3xQc-*ubb@X@m(1>V|)EL>Ekd4ULN^kL;`P#FpgOK3kj@e;6MMd;eA)&`^x$WX;$Ofi4F2dh5ca(#QP_+#NdD3G^5S1{VM1htkLQ7?}9C zH%^wocNzHYSwARBU=;&joxjgV5}3)rGoK8*Mgj{M7{1{GrzLGBKpb#T6lH{(ww8-2 zGS4+31OF2~wDg#U?4md2W>~h*G100n&Pr%&Yca*{|C~b9`u{*bnf^bDpFfG8KhsaT z{|jOJ5joOI+NDcI862hF z|9c8N)Xy=d`v;4W*2=X|IFq)}Sb5J2>jR;1Wyl3c4vcz~>M}W+gX9QN#!PZuf9p!g z851VgwjfuC9_kK{smf-Yox9UZ@>a*|3w$BKk&Sm8SMlJs4Zi3_Yt>St27{??vrHD^kA_@lOLivrX7y2eBzo7|rCm_EjWDV5l zy82{d4OA868mNKFD^_@{8b-xxQ_(z!wT;l@xf(4L=@P{ys$ZmQ-TFwTdsI9<<~KQ6 zkita^>7pQasFJKhFe&?*IxdQd>QlQ~+aex(;ESNY;5+oDPF5ZuvPxv^HZ=~Md)cJS_Cz$VrsI+P*JgPb?Kqq)Z>g~^ zEfhmLj-eeVxgBfBVUf=)o1>6RWfWHKK^gs5^4FV?_SwRg=|W#Xf&0_f?oXc_0SJFy zj;~&^@KRSKrLKr+>!B<()QzJcJN*mP_oxGhy5+d9V-h1!H1kpwbOhlVN*lS(nY~Ha zqM`B1Uyv$33(f|_3GA;9#s*+?o@e%^ItWDj$p&o!8>^wvXux$GZ7Ul!+jJ~z>BZUP zd^En$-Zeg-n?W7dY|Jf;0b;bOH7yqUaeZ5A;P0?&HU`&5wPIss%_zW8x37n5i_(a3 z59ZVgVl}1TiI2d}p^Qo;EldOq$KfhCWG{YMwu)55jAYiL@GDYbGn{UkxWNqDn5-Ge z*riKS(F|=g8%1bOBNa*;xkF<5b*Y#clN8<5(@ke>Fm!F z+4#7)Nik7OC@^REiZipZO;xDhp*&N!rTYE>w9u-KGB~j=l7ea{g4K(Fx25{Y+t!FK z;&FVnAk8AaYNcr`9cNUOE8O_zgNYdl=3a&x?#BYGp<>Lw5Wm!4p$?D-svqQoe#r~{ zvKK1b6XCC(%64TXj)NL{eje>7qMZ;^=WqK281wsEiy5bobPms6LiiYKDdQBf&f(h# z)5vG&+&99Zm~{TOPll0B8_-w?<;efC*NinE_C#qfgQu?S=%bFhb&e-S+xEbv2<=lm za4A0fRDw}=BYaG!0qLvBp7@%qR}E`>P^U&=6i8`Ig{ni<%C=eq$=BD=ccJD~`ax}$ z_E2+8F1{^IpuHTFn@43tj;6xZ;p&QQwdh0|BWvgzT>8N|N@B;~LlAOS!Y@O1RSG*;rjc{u*(naWnihQk^d7#g%YbrShTFhB=ML z6i3Ynp%RUJvaU_>Kn>uxl`+#Z02x9j*`IaY=g8K7A7 ztCo?|J4@esJG*Rf#Kwt@W%#$MIDR{0;6cSPhf+pr|P(woS>mrkD$>56t0-$SKbjpHz(Ofr@(t^zR?!5}4R zMq;#ZfjXRRob%+SNO!b*(L(&UXyL*|*+XBLdu*gP+T|h(7xfxuC?CV=@3TP4Rn(+V z#q$|Gl3lcH?wMWDUM8~th48Awt4`}A>=m_i)3s=L+%tC!)wAb+@!M-D##mRc(J)XC z>3UYvcdp+I7e?yY!uc$HFUv!L_8ROfBfZT=$1w|mZyHh7-?uhEp8kJKz8<3Y8g<@) zch$3J9(-&Ok(MNlb@v)gRUTghICvP+yH?W=sx$g`RT+!lU68*6l!v1d+{>|9wLd`C zV!RlN7+b4U>vh-+v^&wo=`F}#(e4t;P)oGCMvR+2LN7Or%4@%kc@@h$#~t^>S-tU^ zR47;D36?ZVUNb{21C5Aw;?0_((A2<4jkeCFpN0V(`3+^?tA4yo?~e5LtR|~Dy11Gg zbv?WJ%oQuTA}D1&)>O6NRqGt!G4a!^XYV;@-p_g?UF0gATu;r}MNd6<2ErVio4xt$ z>Cfw30bHc!$p>cj3a*UJ)U9(7xqTiKO!kj|d!BN;+wo$Kf`|0BijE`)(UcVqBD~K1 zS-9Se8SQcsmM^|Zt&xh9%hi4xRy{j#;v|yJZETBs7pI1s!wZ(zQ~zP8Cy{P$X4Fa- zErhq#H3%wavOjaUy_G`Ga7{=xm<{$fD9@X>n$0ku4|DMX0VrlOtr;4thms`pl^?=C zmo6#jH3FqJkVtb=h-RNTVEo^y)O!=wk1;4u?H)CEKe;y%&F*{P9V<`-y=1=OnA4x7 zf`mCW!JLrlNT+tm+WVb23(Q`#V~(Ev)}Kdy5A|*?&YHGmH;pbXh4|=Ggw5C@ndA2#H;v8*0|u7@ZoaR&y7#GP9s7g&z}^E()XG{w0Wo zUv5p9_4x>_7s2{2f-Xd}doF+Gc`7+Ki+dAhA=;IY)nn*Q(|!oK=4EG36Vi%;jIPEl zs}So4J$o3nQQYT+Q)XSN#jL~3YJ@qWDc-e<+0xa!Ih8e= z((BBoI+iJ0KffzAG@YtRr((I;88bGokTucK-O#vr79yLI{o}MBa#evZb0|hwucm6u zn$4+pvu4-=GS`{0b*bIx$80fo>ov3WUA<;|@8rxSJt3vk( zW%Je>8>zPSa?)FOPp6Dro;~ei8uQkrYR%gE`DSh7f}YJVM@)l0>&#jT9;<*U14y|2 z=R%7mx>Ip;d;xw^L(FihI@e}KPzaH`MjFf^)*Dpvh=6=BiA~G|eHVxo$(oG`mu;nAPo{nwG5oxs-J=>a;l|Z4S9~ ziG7J^6Y=cuIZ~6v>*sYfBD>=zE}4BfVLdg=)&o>C+@)t5?l_PYJ)%GHR#D^U^`<(j zJKdg9=XTsnecxv87Mo=cWvvl0jVbJJhOD)05j`>nJ-~;(j=B0G4 zj(uX0pc_jAV}`VZbdZfu6yJ$;>oIZY9+e8G+v_ldYyTi;3g=P`>w)jn&$pr#WvM$K z9|onOHi+FyTn<6vFs3H3Ww}~sy^AbR`v^~CtA>6vR8bbwIO^wWLy)`hS|_)0h=#^a zn>Y={OHl-?Fvbn@mD_5OM{zCFF#6-i9;gMuDBNMrgGELZNG}>mo8~dMK}wSdODVl@Ty?Uz*^D~GM}5O);6QY z^k*S~BVRk>G)X?5$~09OT4p|lnG5Ne`AEr_5H=XT#M&q3ZdgPv3jk#_oPwhDklPRr zM$AYjg-$&Usv4?E-5k`Dwu+W0Gm~jlj(F}wGZeXuMCro~xDc&ZLTB^z#Wy3 z0ItFGi0`vukN#FNTjUi9uWX`q&ymU(P%J3jLGS4BJ&&pIfC(_ae}N?lZiG+b7b{T; z29L_k_+b3XJ3XhspY!<=m}B<;9s_@)@aa4eUpVK#8@|Pj2$HUooPp8`BtL>-U4hoe zf;$)FF_jm$*F8=YAXrIqnu=gNPMGMEQ-qvQ&|atCLrggBK~D4rhKKcE2mI_NzK=mlh)sAp_|h^RZiga0)% zW!=&6e#Py;>7Vb`<6;XLNcWqLgkQdgL)}2PK1xA~!|2WN=Yb zM1hh)qPWR)HDwG7})#1c?>t!DI}2n)fKS_q>ke?r~HA@ZzqlYrf7Ea685+)JWxwR1DJ^LpXrwin5` zP{4DEL2X6A#W2M0@g!Zs{In%9T-Z_b5%+@lwAWVm3gUpQ~yl59gV`Q zTZ?eT8!mcA^Se*~) zdhcfFe?!?9ZlmEA7JI@tX^;+!JxZH`jH9Eh(W9y!T^ETi?XoJMOxANJ8gBnYM5Twq za<`7c+Y8|#T2p1ZA%cwypOJJ}42iIKp4MWaE;SNcr^lRNdEq_4JvRlizHBo6~3JAX5(*r0Ax_!6i`=UC~ z+fuiZ*&?rqsIn=JJljRNkQyGPaj>@nh9FWb;_|)6kubNwyf=QSjgxr}epli{`hx6e zlKe~NXJFQw(vv>NZznx6gC>CLCNBjVkfM{WRst=lf*X+^EMVuKz=9J36dmAF>n7KY zv=ZFhW^0vbKNL=G)AcyP6R}+N;0X}xNaR#Wk%W|(mW3R1I`3>Zuf2!7@W?Iy|rWJ71Mm)5VAFA;?Cjx0h z(3*H?rHaZ`xM9y9zQ@3geA91`Z}!8!Tgi7`6OXts(!pWVcPra>jEgz!wvpI)+T|Af z7Vxyot@x$24$T%Wnsm{mONC5Zx&>!GLly?{)ZkI~ZzmIeze~TQ!-gXtG}OYu->=%2 z_19&1zlz!p-KC~Bn=kjDS$h{Mems)il*Z7H7wo;dvzx+*)YMqFBL{CKFHTq}JzrRB z$ob~43DEWi=)9{%X`wj}rA2IL@>^V_$Mw3<&3b)!4U~AJS1?pdC}){C;v>JAU%$2o z33B*r)}W?vAfAW4pkm9p(2Ij`{WMhY5{>&1=$f76|MTph-VZx71(9ek6`c>sigEZ( zcw1cFic289D0`@N-qXSeL5$(pfN(IbgGLb;8w7@th``t&Fbog{#s)$Q*=K5Q~ucL+HdZNVNqagkG)Xz6k9mP_H)C z@*8Qp!-q{FGq=>`bKF_*QQl?|nZSCU1hXlU^6{RhzZmDFfeqlJ51?tlntQlZP#$Gt z-)}2PK1xZg5aCxab52C4)pU^gw@tm!n7^ zH!q;6@GcY}8v9ZLZE`Qq3u71ZKT6p`{R)-aV0{`|86Mk2pM(!(GGgZY{?It?o&R+( z{|Fy!;_{mzJ@}=u@9*#-bI|7vxH~hx6R31cV_%ZCb3bs=1yqsKTB zN-D9cPay`IzBmXpS=aA9gGdYW(a>OyV&S3&>OKWA$eSh950d_+FoYa+6u43U>?5c#!-gRy}z;h%IKt;5v=L;q%$`dF;mwPLVsL;Dyl*x5(#2K{pk8YXn3 zsjfT_`GfOIDzr|aR;35yphhriz#5>=ugzuQU!^4gop};x#&Tk0g(x$Ui$w z`X?HNE&|{+qt4F(Xc!tRxqQ5MJRFVAw;2MFpx>a&GCH4Q2wgHkAF{9e843xA$Jm`W zGKBU|z92P-YZ=l7bQoHY(YcBtG)wZuimLMjhN1$xhJDRq2u+53P2sRm6cE)A&=(BR zi&&Z!7tpZ`H8PYC&`5?L9ehDQP*_M8g!m8SvSy(H6Eu%we4n8q0=kT$R~Uj8B40y7 zM0}i~z2Tg%;Y^NW$@e@|l8DiHBfFX8eX?4p)U5lE@3bUSk;pY`1K*;OL3t98kEzlj ziI;f3@A``tPoS7l)K)cxGea$m(gv;|W|DubAHZ)i7ZHIcV?|_?Zsf9}Otsc3^a}UG z3k@o_YRy{}Rrt_bWR=KAQ2zZ?t6LO0qo62lYH}}C>w#1Q;;lq2U#YD`AQdYq}kb50_i&o-q{0<*=U^C0O`?6F(0Qal!a2ZdW&(4H5W9RgJh6jD!(;70h}VH zYlflM^O*>oI*CS{0}7X=@R%RK!AGiG4#G z!vVv33SYEEV4-E}Hrl$8$R(pT+O9!+nMMvmBh!q+XIqVEUYF<9kn)4O>%RcrQ-iPEK8iz!Cb7}~y$Z#`1)+aK;YIbUOjt*7B9xz%XS#J7g&ONr#` z^b8!95DB}T)-&vb+J+N!%zhRa&cY_F9vI$@wU}`fn^rLyxrW!S>w6W5PXF#!jE9x* z4AdGnKdyw)++luJseN0~dSF(SbCeT57w2cuaMtnm9PMhU*qWnVF;%PY;w`Thh1}u9 zUf2abyzX?yYc^u-qFF%irxl!8Y?`)Sugh*?OMTP0^?F10k8Ejdn!8>fnnk|D+b-F6=@;#9 zN~Ays#m=OFL)Z!09FH_*QE%gF)N}FxWfaA3=e%{jdW626qX*nP%}B>%Xke62(@_c& zDWA60PDQDy#TOubKRW)9I`C66?ggVJ&QN2=t0})BD$ZP|N#AD|5)IWmdJ1gkyi7mN_)*_HqPFWv$YFsIn*IYT8y@#%)6FKO8jfYp5<9 z1^2z+&d26d)*gHY&0r0q+v^e1lDKFa2|X$s2BLTs4r(8&QYeqZdWx@dzEMFbb_9EETcR+ZI=qNXU8KTIk#XL{nl7%J?4E zPklQLxc~a|f#R7Q)IHVyIxf4GUI(Bi1`5RLhUh3zceF6wyMZ8#z;3 zwpE~GCv9mY6s?EjtW%5;jyb2qCphLkmH7U`?2M(Ue{54w=OB~XhXHt~fPH?9<3p^2 zASHD!c<)!93F#*FKQosx2$r{HylDBog^@9s3)WUP;%zFhI+mcok!we-+DELWZ5`0Q{TzUii=>?z*+(_9|1f;f+{&_1PAu!o&^41eokWR8rmKj6 zhB}wRq4WFr!U<{)ExX-m0sS-K6_+aLXGaaze+0SqQB(!PIu;S_v6O;I_a6zU6Dfr{ z>=T{{xvn2$MoGxyz<`0=a`&5%sEidoF8Gn14q1_$2Z>PdgZ0NfJ+cB{ZqN-72l)A zk0Bi%g`qlK{59Dzy$Gqr_sNJ(6vhw8h^`yP-;nVvGG_WQ#*}p*GtSjy3H1-qk7|v> zIz&IJBDWnpw7cnGL@6dTA0h*5(ql~-z#u_&JuidkP$7D3h_{9O6elC0^tkTwyOlI{vA z$Kw-iGPDEQLRh~rPq-{J`^vIuHN=UC>TH+k|1gW%si?4X@ zK)tljIq)grpD{jGFHNX%+A5CM_&tgL9DX<=WHG9ZPCg>}l;>^Kq#Cm(@=h0(3;J~0 z@7|$YCS42%r)UhATgi?z&FEYQla{acr0kJI=eYnq$g7E5QOghUB=(U6rPM9ElM}fk zw0wAWr{kcQ<)-7dZ|pKT(hPjonj#zYl`AL zR16t`#nrTR9_I+v)gcPTkE{dn>P2n$bV@v8Rx0snz=u{uNlRjM%QwYn209;~W%ytr zdjLys2K_-IqeUI~y~q3qkNJ-t^PfCs=slPFrN{gq9`j#3=D)gTEqgXjrH)N&+0~F^ zOr=T}Z4K=N8w(XA$ya2v|3Mb)=L}H0wPw0$4%66DneMFzRC%n6WIY+De26UAkMfTj zZo}k9;XfqIv`CHBL$Sf0stS25&Yv1Et4y;;{H1{w!D>o(oav#(ad`?6@{*MGpO7*U z-E^d*M_QB7P4_vLj;!!%T?t>Q>LRU&V$QzQtnRVDO;d41z_O!!j8uvjM``97F;hYU z96c6^XzW-aWNGYZ^K9s+YC@%InyTdpXf?&GF>6H>X1J7yv7SVv(q7Rrf{E2MGriQz zNGh~2=UP*GGM1v9ES!8UWv3_OuxeVKAGBQL$&pIfN?Ee2H{LoCFP(|6Gb2l7yfMUE z%9eVOEp)iO-i*k6F=VPJJA_}(;s$SaG>X7+zT9ju8)evWH#;Ujj&$rGdP0a-DqP%& zNO%&{0-Kwd)}dz7<5mfq>sBt-k{|=$GsHON^4C$c1n?kDpKx+G?3QF&Bya-xc<$T2X zVe6kr0ypuf>`H08x1NIDPu;Y^X}*rqNww{+`~f;;hDWE&EH#LZGb%%caoz|$ts@@B z@$<+#37qkcbSGE+X$$!IHR&Avv9Mp(+$)=Jpi^68i?B0wA5F#^MFW}Z4O z;*PsftC|o$Vaf((QOy?VNRY2@Q|3!uH~A+lABKy1V@I6dCi5nsp`%$7apJm?C)Z?x zlWwF=dOOb8A+czCWr?lyBzBYx9qnz06b*3$mkGk9F6pV1psJE5)}MtoAxXjFEi1Yz z<2e%*Dh>L&`Hvi$rmJ^5R_&mma8svD@@kLd&8o;3Tz2$mMNt+ht_PgWq8-r9C6CGx zWl>Y|CDco*El&4bX`RU#cS+r%su4Y7J%U1V+Rz9!V?9Xa2>+y$GK2G=l)le>HQl47 zQ3}TY2zxcKttJ|4+PV=&3DDU(vA2a`YE&^NqH)qXH;rlzA)dVr6$#hwbGzt~jRSCn zn%YivtAMXOu1jxC@Pa57oZ7cw4+X;QJWc7cG&SOHJ3-Sahf-Vx+^e@S7s5KAU4fs1 zx^=JW3LeE+D(p3pBG#XcgkB8crx?Z;Mz9fEhaswFmd5M|nQZK0iemJS1-c5`Vq)%Njo>WB8;12(LKs;e(j4?K$nI&kqz~UhF+m^F zmN4|8{Skg$RR~^0*$uqInTOp36w|%0y0bJjVZaj;s5wy8o1xG=*Oc)E@z^Y*|&5lE1 zq=rM6b}wCm)O75bP7O(?BDs+mm57GK%?SO?MoM^VIFlNZ!*c_;X9$ea|AslFpqnvt zQKpG492)hRNvxxtj=?=Ptt zw>H6oF!xjK8pMTd8Q#`!sbQU9C_UgF;kFfI-Bc7vJdy>xTHZ5Fux+xmB_s5T*go@6 zQ86*Asfk$+qTpK8h;=6wAnieMMTH_%am5e&aL6Zu(|AcgUn^&)i;+#Sl+oTJbcYuHbVEZlp&m_Rn%IsL?|@KTx2b45PLPU zM5r`cYpaF-IOPkE7qC&l{ZdKirJ#?;z|X8w-Z{F78PI0Bbro{Iprvya8D=}0jrNoF z2KOl9qS=qcXjAqamytQ5MmDDLTdYOxIEN8>Zi&_mx9Awbqybwg%$&KtR4H2!7G~5t zmf;#Wc-JFHU|Nqjxbs^uY5A0A_2H2V5}oG(^+1~wxfNQz%CiFT*ahXK2p@x$B7E(z zA_1?yQhcDi6yaf~rAX~%6PiT@FREF)Qw?YAIv10D5A~}ZNXpU36JLKL#^2nDVEz%Y zLI@=v#Y}`(vWj)cS#d|S2K+AOqlSnBeAHj@A`sRBFb@qmT^;(VD!#hbJy;oBgn$Ms zgBhKd!Npew%MzVu1ND@_vPABb@ll6|d;X>Jx#}Lne zIaMVa877uh$B2ejHYYK?o~zk2ULV6f?_=3>X#z5w$DN5CryrPoSUn`VGZvq`7WwQS zj(ca5t+13P9(#)cEi|d8Ldk)CNVs9SV~1gSSjOs1JFu{^!zf)3^FYs?bUeX=iAhHc zlMb)k4IR76NdfvkPo_voB4wVjT!h6M-|T8*yyu&NJ&fvEvFk8LhO(b!|^ic zWCP0|gHJY&5tEI0X|i$J4wDT`*UBavp0yABNmG&kn1t;x*+?S$XlBqNry3Y#>@d}+ zbo_YMF?Q7dbw2%1bB6Duq>e#-Pa`k-F;Iv{Hr?*%qRFed80)Ej$Fz?I9itNYWzp>Y zzmR7#cxYCb6U~0J962DG{Z=_LA(~zAZW(LWXm&+8QXS1sD?{>X1}8jgwpI_&<^&Ic zwM_pSnBRrkG-xiP;&oy5dV1~_OVw(6DTXN@h;czg84e8NTIf$P?k;{4<3X(B@Q~ZS z1eS=a7s}Quqz-P#b{zlm^Rzb*^Uou6?rP)ErlfQ+NMYTJ_-MRO{jO(CRfVs|(QeE9 z422(P<-v7(M68z3SwSb<#=!b~;7ZX|%5)u`|;Jc|L^ zR#P=Y!aFXhldpGMM7bQJbX>|uQaDejFPlf{wJ-w4K2UKeLdAH%dJ>`*v7QYHi@N|z z#_VDE!OGZk(j!r-B-_hysP<{PB z*XbPV*XZ=;Nit_4=?Rpekg~CZb-G9Huh;1w&;QdporB)FP8VMOJ)J)He_5w<=6;P% zXFsk^zeBTY&@>83jsGmVjh$;O0H_05{A%`aeB-G?E6g>2K4u5hLel<;0ZOs4Ew5aI zaZV4~qg-D|iS-4VX61L0D-88wg#phj8H(FFJ-!G}LqSi=w$R22FjJ%3z;&KK*ot$691G>@?IT-nHtjL=zS;Yj*L zSf!)$oFuKL>(=i0O4aCQjnxU0fy1y{?!)*k!mf#^H4?HJ`b12Qu}z!jb1`@yHjVdS z!Ng)fl%D>H@`*vy=$3zcosT}^;abde40~5f9o`N~Rnq+aK%U<(pv=K@bwjYSGK$=X z@YyfC{L>6uei-2M!8McO91)gm;#e-(jb$ldfk3PR(Aq)!y?WtPQ_qferuq%yl;vDt znuFOEg*pQd)b^6lctpz{O|e$dszoz)+@hQ}v@%M!!>|k>7Er>bi8CDT>OvKw+bj;o z+4m#2@+a|0ac2o9RgH_uBhBgdcH; zP4~`_@n)SUmo!(igs5g>tpY`bwThHjtH3^+GUZ@YRQ$t1A)3P~I3lHJ_(gvXlcGF| z@BPVuDn)n?a2R}!%y;tuqYE0u(|L`9oC#nAJ~H^wBz#0}U(Y=65lp}K@J#C-J3o;d z?VrK{^Ul~F{hApY_2oy%zMRZ=P?v<*8{38{7oiTI@tIm7HjqaRqnkbE7^_jmtr#ngO6mP412l2? zHQj$2c{*!3zPHt4UAR&0!z3kSw<2oQ+JIoZC-}EUBI4;;C}kYd57{V9v5%IF4bVZG z*1Y780Y5!<7*@Y%?@v}lS{CLxP%5$;vQgyPHqvbo7remBJxyJ(`)dgWE=Dd$A^0gP|G^6Lvw^Ti-TT5Xo>?$V=WF~ zb57QXT&v*>dlv(>3X7WKsY>%T;$DD?5#O|p3RM{3M=H1eO(C%{@8(N4?%0;bcV9Ce z=9Lm^qf=1k$5Z_*$wU`d=={AISxd|4IH=o6xuiv}qjf?(%Gpv%wSiZX_+l6>vho5z zYYbO1`0i3jPv-E#TS23(!Z2^q;#H&k1UznxKFBB(bg>mOO6N?IXeK++nYP4qix*@M zg6~z4s2LUoc?$_9kLK^Q07Gvn{t#qoVS-aD&|GD~V4}%geZB!FXLkWiYtRwdmp8#~ z#%_id&9&o@K}e*Ji+m_HIE8;qZebRkKNF0bO&p0N&1**5Ux-i}8cZ(^^XWxrFPHq% zdJ-hYoJFiZiqW^&BBA{%XUyG*wJXdVl!fps#}{JqM*A1_a&!?EqYJ#U$5-5qt%u8z z1qKB?uE1CTx(D4o4=dWxFpRDFh!{LHB0KHxA|H~`yzECX{D7AjT~`T-ZbWa%kJ80l zB&jSaitYgOyqS&)br!#9tqtp5$|A@cDvVZ3$3POE^C3x|q8oZNHka3wFd;8_!~Fs*UuRX8$~2}-18*_KA=(JKPmG1|NSJ6J3F_FRu^JV9`gjT#qZCo?SO;)b z6&-u~+fl5qClricD1RK8OMSwDgY^l+b5neM!tUNaA(6BET|#+}fF-@JS;HHw-q!o- zZM#&cV^JwYT~8<%ARYS-mW~n`nKV@sor$>y0(i3>DqvKyEdFwa}zR!>Z*C^&y?MaM{&aHE6{7wCf?+5|%#)2O}ir&D1~9#JL~c1o|!dw%cKs4>O0cof5X5 zMHy4~=yBK5{G3cPv=4EtgAu0cSRR0-pSmgsc#?oq2(a?(QILibOkJh>%5zKcAkpRu z%95*v?%(+yamG3ofjFJP&hsRbDQ_cb<@d;wrN|SxUfVIuv$iSEd0^nFFBH|onaq+m zv=DKeG!=20YhPDj%)Da`(z- z**Hq*tdCGc&Ke*3@Ez9p!or7~4N^XhLE2}P>a(Luid5Nn@5V&N4!U==iNxLVp? z(GrWkx6xI7SfoNO@#Y0$qx92Q^`sr;`q##)=lU8n>^qEAfq5JB{a@3d=`2M9WHRx; zry63TPUzSE37cGB>#q$BQe}s9J zm^V24M>{>U_D7Hd&#a{t^sCCrh!1z3oT2`tFfd0WBKx^-ghrh79L${+a1_l0l^^TY zqOy4(E!rDnA8u|@iy}ui>6W1h<|92#sT91k znc>9iHgQjDN4zH5GPaO7juI5lEjj{2hyOmv|bPLj}Cl=T8TOo9W(=^H65ddC8T zNeppOtQ6n_W+g85+j{}V8aQoY6ef$}DRk-sd&ADri1NYW%@8r-uF(hfp^%2~f?Arc z7UrTl7ISD_4L7`V`_iewQXYx?snrK2gGDZ`Fu9aIfayv(Xktc16Bd&2Az53R^nt1H zr>!`J(y=d$x{%76fH-sA+KRK_kcx-$QIQbrXAew+H#1&iPseYrQXkmQW5?`)&Zh9x z5`T(iV1HORvALu!-8g8oa*vnL1a+=~Tr4SsOu1@Saem_X4lI~ZQ5RrFJXJ#^`&4gv zMP+eunxmzB0FCqHK$3zNFzdXKXDu_M56plkuPc{=K5!6m>2N(yqL6v}EW<1c$B=-F zTy`WLIK=U4hG@S{-iE>S$XG#EC|X(@r3rs#khRfN+c@PEw0axq)$ph8&f6?Et%`>s z1RNnem-J~wGdichmrzI4iZk8QE0IU^!N8oBC6vEFQ_^Q0%s;~C3w#(r^3+r3BKH6M-L4_==aYpGZ2sXLsCWgB{(;;53)(D zui?BCuLGskC@)bZW1>~W#3@_7X@z(y1+@`>gD#E*@=!!PmV%aoej#knM4;ApsWm-@ zI>4u8=}GbSH(F$n^1Ln$_y(KD4&}P%lp3E6g4UAbJ@;(T*OcG45x*>Vy4N&-{8&z9 z)VB;;&prYo7FyK49vZO>hc2X5P5V7)8G0yH?_UY4+IJ4V#XTTN!*G<&>b`}{$~TG? zJlabqJ3pKw_P}#E*IHT$)$KQ^3d%t{$#OKc(ekpPi)pl`fl(AMydx4suIGnf*z6Wf z0NSaeWO^IhYL(5BCH_L8utcjX>{&>5^VUdAtSLn%Vw{Ir^gQyK602Im zrd5aO{T-tjFZQ%=3*jyvo>i!lZZs~jXT#OAI#sC0RfpP80S~zF-~{T1#*PI#^H4A} z>w3xqVJRg5f5VPr)_K1!2cgMsVRyu67H0g>MhfL4X{)ijMYPV}85F6+!!$Vc0rf*^ zE+sQ+Mr1k*V^j@!^Ct*_66KspMZKJeJx2sA|GJZtCo$=Xa!9j}A;FMnnSEQwIth?} z8|@EBOv_vmtRVz>H-aa1e@(RmYPUr@fiyiaL6%CI&=9-GtS5`og)80Z-L0>>m(_&qLW?v{2 z%=oFZ8C6JNG)WlienStn#xO;r;XMkgMQD}60U_2dG~gt!)^SKvzC&0-7P7{OZ;dtc ztxtN2M_m6zdo=Qu5qh}>c}5TM&>t{I9TM+W9IOmEMu{B-WkA|3wo1`@V4kD#;En$D z+yJTqD$#I!w_sjb=h62meyaYo@^*)XBi4usaYrdBS|1C{ca+c`-B z{lshxK6OXK&>S}H&Sz;3Ya;JXr}ihdiqZbWi4>aSlHMc#2T*f_opc~&I?Z{V=D#w9 z^25`Tyn(IhydnvGMf)-ucSB-_qM7(pv&Xw5G^|76ucxE1@9eN?|rW%y#Yf{o_qTl^5E%ZaF~V4pGI9eclx9;&jaF~ z7@|E5j7=5D{jbl#n?Uf+o2MXOX_cxM`DXL^NzWXd7J-81;HuI23naoD)=uZ(9G39i z(D5~Ma2fBQb8ywj9Vfc5Zulmf5xRW62R9x8pv+i+o=XSp7OJy$MFt8lCAExuJYC&5zDO0Bq4>f!j7`1vpYnc=scTV->w z+DLgvvufe13+=HqX4~;xUZbzicl_da9TgtUN&fryaC=28?n$Hn@q4(v zGS57RD$DQT3JLx@_i#lN|D}7llIomi`aipeE92eSJzSYD|NcE(8TS8w_i&|P|MERt znFIgoJzTz%5TuuPdgp5x+LIh9((s4Q$wzq27Aq{c|3;mJq4SF~=pEC#y%<0?BQBwcU+oOzkXYEnu%fH_qW!V4!wMQw~zuX>W z4tUz5b5}xMS>bkrRTA3gm*XF7O?%(g;1xF7Yp#vPc+e*HZ@$FfNBnUMOo~4&y}67;t=+wYgc54d;QmTEP8T)g~N+kfvY*i2`-PRF`j z3y=QjE`JX5i_%#za^HYsUH5oNu0@b=mp?B^@+=s+6A3%jVqt({axH>{vtZbQ@SFuB zcaR1<3+4)0b{0(B2g2HcJ1%!Oh%Q3rI~zm>%n*;a{|=D%flTivVCOk-^#cAEVjeEP zkF{j*`&i!?`tD;PEbopTJnVc69`!h>d=CPiP4$>3;uF`ys_|`y>`i5t81^`q zTvqa8JVx~S(=achb#^QOBCn17BDwHai3OizEYm)_GL*iK7W-Gym9PB%v9}P{L99|500c_S%H2dJok#uq&XqPpm`Sku4cKH zK#H|{V)Rc}@T&C)W?q%(>24v&y|Mj?7!&Vo?FeU3JZBDeIya&|IACAVxi@i}+II@( zYy+4#hYDuRHhnHQ#ifvSH9?G9HO?KtO8&c#s4U+3RB7D~XV>KF(8?rIg_A@Wq)$c8 z#IdHhayLN9SiU;OFUF^0)iLWtDw*6UeI?39r5RX0h4PBgbj+wWEUe?eS)L!pC0q-u zV0fHYEn7frLD+N zl>ge|Ff%MJPp6`Izyp7%6NM<=Be+?ttA^2q{89v?i}pHhEKWzpYHXXz5)BW3FB}WL zHN5ez;)R9tPyt(%r^V_f?vEdUoLcquE1*;!m`jepFa49-cF-5-{S4en57$_z=tc|H z8SPWaK>iI@i^BhMfc!Uf%=Ox|`%#wKA#54n|Wj1C6eXL3S|y7A(~$AA{^*oE01g+HhGMWCvsKU1g#a$PUI~!HoTV z46=jqR50VmJ_gyrcq2F+$N3m!2jkG-IIi+B$PUKV;5e{iR+d*}2jl4AI7a&zWCvqo zFat|gWpR)ljJ{yTO+E(M!T4S<;{+dr>|l%zW*qEekR6O`g7XTBtt=g62jgeK4Cu}> z2HC;5A()ZzF~|XgY01ZHCQSv>z2hq zb}+sb9LG8zgY01ZIGBN_6UyQsI~ex{GcaB(V~`z;GlLoPeGIabRKY2K*~cI|7#~?cxCj>KI^f6@n_+~I;hL1sZ#DOc#LAm=A zAA{^*92m?v%Eur(7_SEBRnf;FI~dOd$AQCIWg3p`V4NMyxWUIDI~YF=mTGq&gY00O z7#znlJ_gyrI3$>HosU6wFuonkc+AHjI~X;=j4OQ%vV(DTaLPaPF~| zj7NeQZ}=Ew2jkITsea{SkR6P>gBh3k7-R zf*A{a46=jq^Wb#6=VOo^j2nXE*yLl79gL@f?~- z!DtC){MN@HI~b{8#sNMC*})ha%$V$BkR6P>gBiE`7-R?I@?gfTJ_gyr_$s(Oa8stN z%*hVMqrq`B`WR#f0^)`j2{FumiZWD2jjS4#dKbX<$V~`z;y@DB8AA{^*ycC>Qm-rZD2jksfhUH_B9gOY4jO9KC*}=fU zi6GtnsE|op;9LHWh2HC;b9302KJ_gyr$OOkR!p9&x7&isS(e7iA z9gM#RGdB7dWC!EAV5#2nF~|nWCvqSuv7&fgY01ZIhb*_k3n`Y9u8*I`50sen*%Fis9;bov-%2V+z)qrt}@I~YF+ zW{mYQ$PUK*V8+ot2HC;5H8`(k`50se)SvV(C(Fyl}kgY01J z63jT+#~?cxj|DRh^fAZ|1|ByE>N%hDF~||i_)9LG66 z2HC;5bdWgct?EVvM*tOc8D@wchL~QX;2T?qGtb*&=9PA6@+-HWN{_V5b{yw;bxe_r1O-XP;tlYG&*LO>B9S3$@X`|xmmjW z7Pe1^gE%}3dD@Y)^wgqee@5|)gVlsc&;a6{2Ky6&M^W^#mf}2$onB+G|3GjrzD%Q~ zh*=5W0M_h}2|kW|$6K=BcJF>d1@Ya_iwAonIid2qBF^1dx@&+51-)yBv+cK{9Oa_` zrMs`V{iEA=Ax!UG0_QD+!QKt=-Vd69XSL5j7W6O2ksq!=#l>(s z!JTRwu6&6?yZ|zk-d<{K8yZPZIuEir$Ln{8`6w!JbP_mEj6Xa}b?Gd8Uc~1Lfb^-8 zzZ2L~deVIScAk%~fVY?MdbVfP4X(Ji4OdhV@#-1gL3GW@XsoR& zDz08eWBiLh4r6p$pva4)l^rAHz3E2P|3_|nkTFK*KOnrvNh|rXgok(FVX)B9$*O#a zTl{HUqynMc&7rwYTImnyTEfE`_7Ed3d?B%}k^hjokz3}*m5`E$GDfaTT``$BN1@JY zC8Oj_^gkZuy?}cq>W=hRt#Z8*;R=lIbpCh#UgQzZacjEy;ac1~Cg{gJcTC9rdR!N| z*GqCO?j4hML6Ub&$Q`7?u8X+wd9I7#eq6?Lr@KbAyjIpC=E1Jpo8E*pB2 z**!fSrtdkeTIm*{VN*yC$!701 z`yJps{5GK3T?2l}uLETl z8+~A4m}OafU{uni823}tU3%`DX&lNhT);5i1EOVR*r-$p7p3<<`DPlO%{LRAMQ8QR zG}`hm5K`pin+d71`DQ}WEO&u+Sl$If0)xUo`<`Vp#|c{kx@`Ri9+;L&X+E%k8P|Zp zhHQC>ejp|(Ko*~X$3U#_>~qXLXqoPxR?^hWRv*ooD~7SEX}sxf8ySZ*;)ZvyMihYE z7VHjlJakryVP`{$HUilbfyA(F&keod@(TvdSUTxZe~meiC|Je6?ox}os(^*TM6uAg0*c&wIerd&4-TL(O4t0+a>Z& zLaW81TcYB2RTj=s)<5?k&JtIGf@b(Vu>7|zt;frwZ?s_cQenA z{*mqU|Kx{mYc@Z0owE6%gCRCEnc(Ytrni(UjY&KS}3w675{Z*CJS*E@gimUsT-%&#A|7h!%G<%eBB zf-*VPXDhsnz^V&y(3~-Nricf{iR)Cxjq3lquVgO z3RXEN`+xHF>;&XBTFm(0_4UlEHaO_PLDI~u2BQ@mnXhNMt_jmbwDi1l^7U-ifq&!c z*(^ECyZ&GH^~?(YCtuGz5#LMxt*_@6{Pg<&(AP5?X2Ds{pM5>ERQysQ**G${w+Hr> zXcDa{{UP9>g#8V1mFSP)P2~i7mIB&QSU_-#Oi654K{21x6utS+il!p1z>xb z>2zkYRAr;9BQu?|Os6p$T?z9o>Lut98(lt`>BQwC6pLMizOTZo{10CTZ^7@tWY+Ih z!1g;fAWgVm>pSph4YHR0!JoBFG`{Ko)t3KXj2%J-l8i^;EQWsA4$p1*w`7T#Eq|Kd zowW8)H}Mm2tQ1W_8_l*RqM$}033v%E>_8&t)&U!%*G5|IpKPP2n_0H(CWFrOX{Q zj31AsroqpX)X~OS;bt3}jA3(xaSI$Ft%%ddtn^~I#vxtUP(%u2gZ5AAA`$#!hjf)O z4|~qA-%QY2s0tpVo5Fa86Y@!zV{L5*piMwUcV{KW(jPus%&2T$kbGGZNFkOk(3&hP z+nBCIpL0|OSi|O+&W;({aKz2(h$gU>3p3K@UZmyh$~5mCcUn>(Gl?TQZ<1j$)i832 zI~^RXVAW>vgi36t2PG zQN^-ogHII{Tl|(_1RRGBM}^Ft8Sl1{2j*d~xMU7nLQ=gmtt;XdxX0tlFPV-2&klz% zawq2EC>uNpZ#Wr&KZXacyaTssWFJZTfP!Rb7T^U3tbuZ709t3w?LMoS)o7hhwis?Y zS`U33_yIGSM&=VB*G~Z)qYnd(w%TY^c?&F9nSVte;1=-L7C+)HF2;|zHS~}83yqBX zp0fyCaI)JV+=YkbO6)k?h-%m(Bp2*xBpU2!cxk7i$^ZvzDc|=eU_7iEf(^j$@7Kzb z&O;jL;AEfaS_)@6Cwna#qw=?wHq=!S(zh_C*Pli61H52P6ZVN|UbWManE51cNI9IO zaGS>sZCYEqG}N(;Q8pMQN-JD7Hq;qLIdD=2{~7k2X$a^m3Rb=-6cQe|VJ~PKwg3U8 zCcmL#?g6!kQe&45;$aC13XMTlxKDSRw|qT%t(}1e!kmX0WF{aJ=F*5*fM@Z9sNUHo z%!e9cqj_dgc$f@OW65?>_j%gc$OL5yR}*Q#xlk zvu!Jv&!c^Vb1GAN8?>KxGPoFT9}$^7Uc(Y<}C;) z^D0BHT3if6aqd!sn>qHa!HZkXtqO1zh-a(0ARy zKcnz=B%*ITcs^juSXub(b`CDgY|YMQ_)jFbR|SNz%2_z1opoTa89m^vH1l@stYS;%H-Io?gw;gATEQt#IEq?OA$**J#k{`Glhatj93AN?RV|!xAa1 znMV)fDPf^m6Fagdnn)j8^D6BGxsju-qn&RAn#d(bW}*V;EH1E_0YqR5Il)Gb_ACda zN(S$ya2O%b|Cr>g?R12v>7F!&0x#YzB)b!3$e=8oD9ggYGgU;stfg(x|ZBc zq;@_KEI?}(FTg=isN(eFkk-PVq_z#JFiXvBv&)LxnUxj@qt6Kt!b<}dK;GaiNKrsIm>Hy!9_*T zz7^pEg|mt)jF^R>mH_&^Yhw{22WEon8aQ|WorYbUxqO5yL%~EFYA2^mS8PvWv6B-r z?MB1l@HWcUAmxDLU8S7ala?DcC^_4pK!*!b4jy2#3^{XzY2)IkG&@n4#W*{_FqH$_ z*^-s0MF}Z~3yu|@7=plp{JaeR_1?KjP@M6t0wJObC~LGPrQp`2oaM3rJ8lZ!XsHS( zT!Ff6jIJ^1N9Ynv6DT)XKprSg2&VIum<`%@ji+^dX(WzPC%Hn~4dB2KX@Jq}Jcin^ zDI*EadPt^Cp;QoYI7$^#F0(;ve1y{BI*@#ZB+@=*YzIU-bXo)D!$mqaL^^Vjj(Xso z^5oqo7`BK$Uu{47tAM)&3{P->5NJyu>e2#YfNC=NT z+;^%y?25rZ56cUc3fUbcW?x$YJ-FrXXz(N45Z{Tm7^$szdgGY{3?)Q=j^AVaG5`m~ z#QqVSz8IN+L0?$}WdZiju!mu-8t4Jj_vYX1tE02*VR+BBhXH23he3#GTt*dh*jFoQ zhGpY#cn7DZ++P4Z3k3KG{x=oe|E8^@qIW-9t+yd1HaPdj=i1x_C*Lyk zI0nLOeFwf}^v;LW5aQ@hqfsV?0Bs`rWk8Gov71V%&p;$+q<|fyq%#^YSPwZL4%srJ zhj44|A>7&q=TN~-f!rVK$l$v(c_Tl5l5DgfT4~SrI0rNUM&x>9|KpBG8D{ZS#};lO z+QgiRvxH|InzKI81b|xxhLvKiH!uXJUCvXhiZl~V^U8}cmuVTqSWKp65ODojrbY5! zs@Gg@#U)mpE$kdzO$4h6qQ%q~COUQoUzuEsGtMoYJHg@;eZx(D-8oPE5iO!ts|{SIE&$5wCz*Ia2a+ z*Q*dBWiM8Tn!P0R!yaa67Le=<;;Ke^a0-q<9jMsymU#JI#ni{CoT$zVi z{naVV%b_zq?*v4sq3uZ4~tHNL)-y%R&}l?sI{k>6g-)5quCcZ^x!H zeJcz`wllqRa7|G=h(wc-&!B5ZtcI$dXoLhL&uz0Gtvqaq7BGr`1{nA=MtDUmMuOu~ zvS=rsGw_49q6oZW2A?d%(|hPO{4t(t{mhi|VzcXvsX|jtsqI311bs9Yvs{qXs6wYB zl^M*8=%d&^rZU{%sljwa$*IB`ro?t3d4vjG%`%ZJNEMoo)o48InDGH9mIhMjV{ZC? z7)NOA!7Y%G7*a_KML^?`EItm~l$Ix7X-w{&CAqOR@z^;G$)WiCC+$LBUUOr4z7}l_ zq@|+7QsMHf7p$$gx@+E6V;Zv&kEdgzmM8xKBoPF%)K;_PrnV+iTY2)+NN$nJm_bOe zmJ%=`Q{ZYOqb^k_xQC|Uwio71?xqR_0}lmOpiE8C6AQgXRcI>~8-rNvT*RgV>!^U7 zA3SHERXL#tge)SSBv7ab%v)6`81rZWo$JHo#uSX_A~clBqlC0{%#=wj+<2%BUOW_B zr8I!LfKQpo2~Mq-v7WAB+41Ry|9Y;>hwlaaRO%y8wJLNxi?%_uE!PBe2Bm@2pi$W) z#8JsK1Mx}S#Kd#aF%R0<*Ip$D3+D=B8Wht4)!2NCqF4bww( zRI`VcEFi0N_U3bgY}j0L(*1Jf{5|8Dk?+CWAJ#JCKFAmnAmS^bp#f#N7+8RXu;PC_s2Wf*cWtz@C*7+LH~v}hJEiaGfv8Gyl1100W+tK zVeC6s+St&3e1l3Ezd^z`$~m|IlT9s0dnJMl8fM{XFy-)=%{`@o>8}#Y*?7tWeaxnV zz8+FG0gxH7QD>H}A;wXMR>xYvDQcMp=mpom34KtcOn(S7D&s=_a7;{C9;A}#_u&s6 zn;6Zv1j8GTAANzodCDZ_+O)U%EN+pD? z&yp5LXY;fQo|gFdawfYpE0>($oiLONqKj}%$7xFi@yPW=I-V%!3pJHgft zvZ23vP^_weV6y`0kYHXp&XQCn7N{8l8>Na78#90ZHlVWzeh4~ZKn-e_L|-g4&TVOw z!e|=@;tA%=7mPIU1?5OVCqdG?^4B#ZR+}xxldV0*4b;^jm~qqQEV&YYV#!$=&K8(^ zM)b^)Pp4w{0Mw+%DO!m6lsH=e7cgfv(hjZGIcV&Ryci|}h5*HIep!ij#TTTF)^Hvuj2D?d))w1f zh#%Ym4O>O+=UWtAAi=>5Hz1@k$aq(wS~yaTx@B;rx*C9i@`SO77ff%<6UN|0{~H;# zB{7Hk6Zn&}S#F{!NoN7a2D004&g(Mx5foH4!SZ0dXdL3Os>z)oXZ5fz792&%c|d01 zegOUGTc$86X*gTM@h{hh=iVH7L3tTVXCsDAkQ+l7V8*(jv9n?HEk0ZVpMaIjjQT?Z zyCPNz`2kylao|!M;j7>c9n_KN*IG7~(19h+ObygCP@F(-`r^n@(BL`G2$96P>2cIo z6=KH;`ckl{fmA%5;Y{Zw#~@Q;?F1TiJ4&p+10p;c#obc)qhI_C z4th4kVMcAK z%!E`n*5tj*sL4rVYh*oJ2-9-Z6SUCeq`d!AJ;7$^ol(m5zpE$6sy2w#aF*m~Y$n%Y zdV(9MfdW~Ylb)bi2mXzopjq<&`+9<`@PE=1cC)vu88Tz)lV~7B3Rmj5`cCq?ErsZ7{HA;ShW^q zzd@&J%XC;?vxejCx^0ErzNq;7F1 z!#UF64U+C8vh$B8!vWxmW4sFELv#$|KZ1Wu{6h9Mgs96HcHv>hFT{xlRv#oX3G%ZXZV3s^lV@oili2W^M9%i4Flx`%O0qoA108n}It~Vbe4c?u z<9CV5>Y>fZ9iqgKLpPlj?tBdK^>Y(8nT^<$k{#8~4vq?OLLvqfo?xWb;K=%6@SPp+ zL=Y3U7~NLRB9L+?A=Nx*Cz41xnUQryW-MturQdSI0g7w%VW*MlX|zch$taO>bhI-` zr2NZ^I2|?9%45A1iIzYkDn&)`1b1GbVZoi{XAcss+HloYHQYYwP_%*bO8Fc_*O0F* z;hW$P$#BjNAB_mZW&vCm1<^Z>H#I^hAtVWKvN$({4gt^tqU#}n$kBABe)7 zLHE~!7tj!8U=5Sc_~RH0d@c?2XGmT%defO-fQPP@bw|#|F;G&J%($@$$}>>Wl64dm z#f_VDEN6+aqq6oVHf6QK#1{{;RhX$Uzs1dAJi!{eHYCh44Wh-h>SL!)D<$voOAgl%A!* zTpGhC5ym~e6sm@?6lBF`kpe%hqj2#kS5$hM%P4C@dSJ z9-u}zu-M469#`aA(ycKrF!TCME6w}_2>zMM(FWRvZJoL1n_2-5EvYIhj3=@XLw&Z& zto13)>%&n3urj<&|%WSRM;SHNMr{0XT56Bm@Wh+oFQeL!h9AFYvD%$L5CKHnI zHCslk)dH!&hTIHP=X?Y(E{X37+Ja#AR@yD}5VMCK$_!XSf$n(Uh9F3JB%lzft*V%8 zJFhrl1ipUIFO(#*0LlYwqyfeP3?EdC8@7Y$(w@Z>=y#wDu{HxiTu{>P#9IutNg^V# z*-pZVKHwIK1MDTs=}AlH5=|ZRPdJJfg7#2ZAr8DCyi$4G1P1xUrv8GLlrYNB9v0wj z9BqKH0AS6G<3sEsu{4obNLb5e<$xUEePsb&&Ugci1sF^VAqnxY5}n!(T($m9akEsb%it{oeqUGrKLqh zvXXRp0a6ku5F3-J2JD=LF%$#Es0w2&7isX^g?i#2A}cv_qXz1!F)}gTfaY%G86S>~ z;bFQtAPp>_Q63WbY#Gf^4bxZFH$lAjdSVWfn4%M9s6^A zBXh~uJC|{ZJN)M|o8{8KKDp$ZoJ)NbbIJGX=%3pXkxM??T;e=)iTgX3xU;#$UCkwK zSuSxCa*3OhOI*y6KaWe@T;iJL5|=-hIGbGJ+;fS$mrLB~T;eY061Oy$xN*6}&B`Ti z$mu_iXYE|#n&c9v$t6yiOPp&iahbWqoy;ZfVlHt9a*4Z|%eoztOTKTp>}OMR$#*H2 zeDP=gJV&c@*(aLkl5c1b1$)S}P$wcX1hm1w$rF<;5Ai~&9#6}9 zWFAs>1<$;~ek?PocQN{e%K{4t`8lVBwbQJPAWqOmvn;Hgx#xj$$A8WPS|o=&x?J0K z&X{L_GYGn#^oM93PJ4dNh&B-ONmD=#WwbHAPNmoN##g#3m;qfnPWdNIXQUI(e_Y+w zPOM)JDr+!h63APsBOEhGbS5;%IGxD?XfI&6*2iHiz+tluFc#o2+sc<#Ipe6i;J~%4x9jfCPoQ313uMcJn5gLPeVWh zpccNI0$MUS=r7^U%?SwJx)IozQrZDXf$oN+bZ}x&3K*dzz-Lej7@;`%a3}=?C|I$( zL5}j`jC67;(aB^Ak?3UV2s4FfNhhoF5*`;6UB!9!PxO0LcXYBE5ch(MgQJ@}EJ#tXj|_*vuqIerxW)9(V}Lz^N24gj6-S3d;})`AzvdlOoLM>$Xw zj^i@c$54L`1&J@#zcxZ$I21fzL0y4nA~cg)CFu>Gj#%$!O#75W!w5=D0$wh)25F7w zehvi(hM=7(Eg1#mqaF&xCa@iWYoTBPOd|ns41pOtaJOMc9c9s4N-|?v0}e@Q0nd zA_2vj5PvZpj`+`Wt7Ocy9pQuqze@OFm>KR&#}mvHQ3igYYzka?Et-KF_If&6KFf2v^a}v_YiK8HBbNy=VL5GM`kdT=7usebD>$*y*tghuLxhL+}HhS zR77*;O%1@WCVtWQ^}~-aJsH29_&J~;^WWDTVCM1n5FctowVRj^i4$rYK|lJCmF z50L|jjcZ;2pW$HRq8GqBIM}#m1@K}HHf{z2cnk*{cLNUA^+tWhy`ZlEYy+Ti%^QHV z0L1>u1#v<`;+4b@jO_x(nX=?{ET`FI9W-05B2jCO2NFnlz={O}M3M-?KuX_C_NMFiM|(#((VPykn+2gwhPA_J6|`fSFOkg9l%zXBM-+T zswzfU)CUt*56=PkrQlZ=@8|IRj$c89k7oG+-$|9W!ah8C%UMgHIvQgb7)bo^QB~pY zZwRThQ=6C)uNarIHY38+)X+pK#jF@JWYD4p^Q4WkB{d?5cE4p(C~jpb)rd6O1C~jD zqMMctFk+1Mpk>nY*^(L&NqfjL>56Pgjp(I4Y>`wq*s^vb8f%YOrfX)I&h+(6vrHFc zna=cWvK)C6Y7(98h4$`a`#Pawd?(Bpb{cc;n@BWF_}&Z#MyL{x;SatX>HKjV58A&n zc^ZjZQ7_GtOTr;Y^V1ND$5um#KT(Che z9NBTfOAGZm0AO{;urg85Jxb*kyx9$VjJ2pQbrAn()bCi1 zy40<>Oe&ewrI?rIq9I1J$`Il+i)d*S?^jU=%`9VNZBz!$J~lBDsnBtpR#e8947^}$ zY(Rq_;!R*}h`ub~P&}V`{duHw4|34-F-EYmC2sl)h=GZ|MwSt1wGv9VYY>Tsmoh|Z zYz!fx;Vgw&+ue=v|E}$K91Cf_Tjue{uE%Y5O%N5>C~^cgS>+YSU!pnd4ORgbh+6G%b^)Sv055o zECB+FEm3O=Pk~v!VQlnjBoiiy$GS%`q@wl_9^$x2a|7Cs3eO>{MHRd)sFo7DM-^1D zmB_JAXeckpNu23QxT{E^zYtiGyskIuI&7?qmj&BVp@vX^+QSfRp|j%48e_t`55|I7 z_hr2MEJ|oiK^v?vYL;BW>dZ1WH1^nF%<5;O3nO$o4c$Uu!^vupM+rm3o9-S-8mUxp z@v9)-cHjel2`|`>>A{wE;$;H(W+q;t=O7%>RJ`{X! z_cJanVb&-ki1~_!%J``BY7!{YEmKN&sbm2@h9tZn6vw7Hks1TFDVEwb1XhDEE)C#(togS>1j^#5C&@Xv#n z^K=m1-VLM<(PvQn<8(Uv^Bp7 zTjw9o1)v2`4fH(q9cu&R?<3J|Pi9`_H#?QbBAR6&cTmXVs!Y?}~fZ7#W z6;T)+6A5<>6V94!-~^aRw(G_yd!7rQRe?!x!x;4uZ9#!1lI*52>N<}ykzlusQ6MxZ zmx<=PZH!vRqY6`9kAVmiupwKSMmBUfj}N4HCD;uUKwz20>pG%b6ZK`B5nVmNO;p|; zLw!0rTbQW2yGA(4QDdkQcIGgZlan0vDB;p%j4$C5A&8y^`;WM^$$(2Nfc^lNh#NA2 zSXqQi670ovIG}%$KAQk#JTVGrN$&tWv4sRA_7??W4Nzc@iKQh#ftND~FIh&@+4dN< znRGCz&D0R4Q()O<6~2d!`dvkQD4{ea1EiA~Pb-e~C$tKWGftoY4&-1X!t$XAb>mx3NjviOR*eP{(YgM^EXh0 zY^-4CfijU)UkCSsn4Lq6MNs?(ES#hrfP)6$c}kh*K^gW>}jfrStZ(sC_WLq5z+@TUlX_qXKu#pPGfi%2`Df58Q`Ipq)RPL8 z3nrXi~b7Y)C#xQ3ZW=NI~Z_hMLhOXl@0Eb zkDymM4y_1}L^ysF2Z(T$7Fz*WcL8#koeC7YPzV<69wVb%I}TAu@dPh)16IYrreFFu+4W_4;%vVEC zZ#IkzY|2)V2xNgnaV2Ilm5z;dD=`>MqY@yFqB@oso;=X{z2L_ zRLiYolgy|&+6wZ`F@6s^t&(}_6Y#W=!+5cN5JEaD7nr72aK9^fu~lZwL2(Q(>C^~w z)%e2{@l2S(UW1q=TI_lHmzWddp8B`;66((YBby^P!y@e|z_e%S&pC?iV;M^@jHhw` zlt4CB3GehTqqjxcjCjk}EcIjDu|~XDQAU5ug8Es$W~ntxgD)5g zXWfZmJ+r7sDgs%_{$=TFmVwFA9X>jiEz|T8zfgDV*r7wm4*1)#LyR`vSUc-34XeKh z=Mc@V?(dIL5|%nD#($bvGq}|=1q1QSQ4&qk^5px*M3FHx?jQm+%3_KEMsSo^5)~6; zoKreCU_f|(vJ6y~`N%SEmR-r(`7EQ#@WV8N##gg;NJQ<0b}*FEOtBe%kM)<2PfUho z>)6C(8RIMy5~De|vg0kG3@U}q0mq#3oK6tC#cFB$z_&!k4$;SrYkzzJG$0KYE*M@vGA!)QR+u)X#+akl(>lN}=5$mgI zOJ=v^anbYuBGLG~#r&N3)A(RhWFI*zf5Vu#YlhJPP`lCK4%FL|)LZ^a01wR)8V`!) zsTN@%&(lB}G9eWo14_bE;ig>|8woa5+BsSOv-dI5M(HSvw9Ro~BGv!cf3RX{7Q~7~ zng#zAgCPl$x&+yaKr_Fciieib{MPpg7MyZi$ecNO24A1b={8 ziL8B=1Ls{bC5aHnkhugZoXHW1X5^jCv`U1ShUsy$Xl6_JZ{9uhPs02)okSNU0q(p2 z6>6b^WVky|A2ONnj)%|&)@~Q#2UCk6cri4L_BpbfYARnLssY$3l+O#tm<+mN6i~1+ z;jP9Z#|PCF%v9Eiv3SM(f=6g5mLO}6ejH$O|G|GONc)w#)dKbZL_nNCt#zBqy_XQ3 zDP=rr&>sKP7SNU8tPqd2mStOI7yNNQ*RuRhY)wiNOmw3v~)_+YrPqKc{+=7+FMhMp|L8O8o?e(V$cwHV-eI43H^RVYsUyruY zRPNdTK{le(IwLQ5B97v`|Fu>~A83lart+sZ4FM1On6)quYoU!i4m+VNE}ES+{{N^G z3s`iT`uU$nSLI}t>59{Y*vU$kcosc&vLcZu(>Jmf=~3>P|Gn{3E41&hz3_9FFU>pZ zw$>n!)P^q=l{FnFY+%MuCXatFq?L+rk|HGsN3q*fF~e4|fJYpKRAS5{pF&~wgozX9 zohtTKvbptD@p<*NVRNb$`>GLV%j4{XIC~anOZW1$?}dG;k=WP447;=+1UTKf$%}JC z2HNE&>=QZcFxp>853SUq`LrL{WOD;SXch^T=u_`)2GU>~CGEzm*C zwr9QtvkfnScHlrq?6$O9PsB!R#WrOZwM-tx5(ir&q_bvW@@GK17cUM@L-gaZL}BVY z+up*YWQKG~F{{Q+{|Sw9mdXR2m9h|f0SG&jpJ`eYfpS^=7lhQf#Z0Yo!>R5o-V4c< zkgLfey5c6Zf~mV;ffHRm9dk(kB!OO}z`s5Z{P>pV} zFC2aFV7;2Z(C1F*Z%!yjpV%@hXdK)*!Vw^JCIQ$8FarP{!UE9K=gU@Tpz^{3B}pc2 z1~@{h)%Aw9je=)pzWQ&=h4(Ss?)T>T@mv9SPwEKf?cY#$t2A|E0#*obZ3gpJM+r{1^aV&`kV`?k2ymK%nh7Kqiqz8P-*{ zy-+3xgGo~S9ML6o-U-L^lF$~pZvhZyNrr)m|Aq|@dc9AuiKt}?-yv<}bh|iOCC?LS zqhOmso4`{zIGOH;W&o>kKlB_Pr~tBR7Q&Mvn64=cpw8BkK0~m^NPyRy0$K@}-MGS9 zUc%Dy98lK~$9XJK9KnF}y<-!`=-%6Vwe+Gq0NkkQOlL z=WrMca2Rx9IE)1t%&031oe3Vt0<1o*1DZk%Wuva_Pt>h-q<)Z`+Z{OPC&CHnk1|5Y zxsjc@(x`{02EWqyea4SICmrwukBiK$iH_n|14UWmc}ED-drrS#!1Lf2S{Z*ZykO-B z62f1G28IUlD5J>lJZpETI|qXZ1{%3*cvH2+B8#`~)zzWDDV*0N%sF zM!6xcaXE(;rlKFCXr`Pla}u3vd{84|qBj089ctXiYrnA5$Tb2A0ugVlD%;?4kcn zVIQ&SMGu@)k)c2p>G**AA!ei(d*LO}njzh9il#8y0>uViFAH!OG#WUJ1vu=c0mcFx zcFO=`0S>!ufUy8zbfzhUC&@BQGBR^91bFq?i3lYceldyIL^`%b=3qtN#&7(Yw7S zz+sRHau^G67-Ui$#sVA$_9=(40EdCu$zd$OVc>Ui7z=RNTLX**I1J1Ro{j}L3`_|Q zV*w7s!JfldfWvU2=P(xFFdV2kj0HFhr)dsj0l@xw?`&$CUkHBLwIowwjVxxI#xc6)Tx`28&8k6A3ImZR%{qwlb z&S^a01BHD{K{@iavH;2heuDzxFc#o2P$C@00vrZfgu_^X!$6vF7z=P1zS|te0vv|D zki%Gj!?1=qj0FIrzWp? z<85UD-hQOwFc#o2*q-As766QB#?2#qB(GX4MPZovrhr-DSI`cAlY(vxe^+s6zA>xf z3NCCkX8+q}^~Y=TRj$(QLjA)>Mu&!;ZoMzQoHlGp;?*6gPbXGS2$eP%ssHY`XxP=_ z;u^#Av`?!1ed^sY=^>*}ei}Y@{pp(_?!U#)#^+yEu;=$O{w-UUk5H739Jo(6{X|gF z&OL^HsQe^u{<5dTzYKbkci-y^A?L02zugAyDSv06&7j+_)h9X+`{kotCUyS3bk(|p zYwCN{Yx-tWp^;vVk9^rV#O|*F&tHG^`MNpt=KIu!+MOLG^#(p_J*n5Pfi3M)9Uk;A zF{E_2{^2tqmiwMKsiP!$$iC_$d$&mMcC)BwMsP>h!x_WNRov?L*Yg>tE{%_xc6j33 z;Y}O2-2S0JXwt+&vwO{4`tp>%Y=QCH7Z-Vap;)2Cp&@P^st#VZ>q2nfYK0nWGD>>S zF0cGGdFz0Q>&xW#4NY#8bZ^7Vqw6wOq>R7t-2Tk^hu^wP&mS`~es)lw{h!Z{`xReA z-D}>d7Kz~lkCflk#(Q%c-(HI+47>Ycv3ox2NUQL6O`m#-x3_wfJjX8LP@-G)f<=Ec zxL0PVebWO|zidd2@GKFZ-uA`R!+pEVU3ccd#jyA?tJW$jI{fHeaX_1;WxfpQFLHQW ztkJxlRST63J=|H;KWgECv;AK z`^b@9Tzk&j^|*Gw4xPr{y;Qw>_ZG#rc^2yXvrp=Kx8ye)#yhtEQl)y+-5XoAnS7%{ zb@!E?Lw$dA=)CM|=-8XrH`iNHv|f3aW}2iAF3$t1Owaq{%ILhAeUoceX)-iwVu@+# z6P|oIQ^{WxbkkbvH23LK?`!H4Zx1DGI@!C-4zHDUOLcIHwF`@|syF!X+zyZ8Z-op_ z9QkaF@}$1!uq%h0j{VgvCSc*J*UFREo;8X{-B)tdw7$vb-wb|e(`Ja*{AZ6cmbBTf zJXq{S@a!dj$3$sWq1Ow2d3a6N>wfF^_m`LX9(eHi4W}V}nl(ApW%`jGPp5}WIPhuY zeYZm2yN@rjW?#!&F&$cMY9CnhYzxWL9fA8gby?jj?o@dzH|f9*YCnlz)j~f*NA;Rl zcf_Q#TVikeG@tG2ywu@tnAO);L)8hh7S}rIn*4Us`4PJ=JgeFucyzyx-#ax;nP<25 zq_W?nYgZ+H>4h#Y3A;XVjI4ih{lb;s&MqT~>*N_f{rAcxfp3aeJsqD?F{td?t($k1 zo%vePd~fCAZQNJv3_j@g`Gj>rzfGN1`@N{Vw0Pw_{pWgm?%ed(QmaWX z>&|U7=0dscao)d9O{{zBT;)s6hR<(x{dL6j2YWm#u3CTb$tk)0;MTQ|T6>>-Fm&v0 zuj`f5?unLO?=|Szk1nm=9J=)~Wt4r%x|c8KOV4+7bJWKt=N<1=ywhWO$7Q1z_9=7a z@j}P^`qzoimlW9j)$iz*YTE+Vr5=|qo_lHT;cZVoziygY<)M4Q?uQ4JbXob~#`6); z&#RqUKWp`|9pmN~sntERZ|C=YHdcSS^-M-WsY8_(%-e7;J~IERlycS+!&@GzlypQi zb>z^pTRuH1-8!sV+i^*+s&990andhjLD=aY`FdN8Dn9l`+M)Umuu76;`G@9s1B})u_iUKA&thW~*O`jKsTj_KaBAG;;fu zm7?l5tI6NJ^C}nl*tYn}Nk>{&Ns+!e*X!xms3Q^MedLjQi&nGI`rEd&S4Q^<@NG7+ z%&vt`hfMqK_3Y-0f`1)66Y{jY%i7MheF9=n)senf71D6$+nuGZICeQ})3!ipA^$BY zvGa>uoAK~|iP|S_-R=GI@QJcFHAOOqEWA0f!Y}K1k4Ec0{;WRXP2OOyHWLH3202@8 zJ}dDEA2}lL>{BZ*P481?Y~AuWajzU%t9a>~cungKsgFRVN{ zV{C(3>x+BzEOVt(s`_?J*Q*oT_LeAiIQBQ6UI%j`kV+&o;)5=wAiuTy6 zy;r@&Q}@avPB+@~X;p*nqN`he*bE-Lufm#LN$tZ=beos(QvbO0v&JPpm79Ea`@ZFy zB8wiLs@pgA(51{4C!Dv|p64^Q?}K59-FtRh=ijP)fM)iTs>9#RY2eV)J@`nuCTnEt zR$VRnK<7ViZCcEjr!PNluD)jLhdqhc^6dE$w#(B!@Va9oC-v>{-rouwJDtCZ;`iwM zOXiPHmiD+%@N3;!wo8A!dS9SQTJ5Wkd*$nOb;!A275g>6RNOJo$iQjU5)0SykL|PN z?)!CLkLs&Qx8JLBsrH7;+kdOei>iG*b9nyIsQ#7n#B7sw-dOVHv4LZ^NmLzul|DzN zOkI1blUK-&DNeml1wDLQY}1rSQ?AuHSh!ZZb7lJN_^xjfk}~UnYk|CFtK|7TXIKaS zmevmkjgPSH-0tg3?Z}OF4sBfdJD>9yug7Cff2zGE*0#iPo!^Uy7R4I(?e=O?l~cZ( zdkyv5`e1#?wK;dsPC1zW{^V`}iUJwazDFM^{kB$LyV~N9Z)}~8c4*&c!v6ksd#{e3 zF5mUq>r2Y?k1x;5t&a}>_$n&A&EOA}qt1R-&1s(AF3PP_+Rl$a_mnQjVWo-F1$8y^v0i ziZ)QDIyp4j7_VN?%DVXZQ049RqW5*0t#}zwP4}sM%|@MHJ)fqV*Sy5~S!+`~8tzDY z679U@Q(v!*<;v%|Rc7@A*H=TAtZZKE`06XEwWAz$Why-jpHOkp;L%dd@Qce{O!ea;J%S0bF&ueLh>@XkAJ z%&yka6=!q}_3hlk)usOTmVH|{DSkR{wCA3|;y&At)^hKbS)l5N`|>Ko&fI;`V{G#- z&i+^5*ZvvMGp6eC{Lzy>guZQeyFgLDDGw40oD1w8+bL|(`tfgl`X24k{q*#kZD(A+ zSaW+--+mXO>Kr-RJMgoc@6nVkHH*(}TI>9_cJ>#Z7Cf)|ou^!tq-80Csy_%SV=wOi z<@8DUnY7A7n!5HIx2t#NJ>8W~gHxApAE}bgTd>h3-fi5>gxPuG?QPD_e)lQgonEEX(w)TW_dh+T*H>+0rD%TDymU2UU zu<-Ylp7$bbErn{56bcldpoUyE+Ne;9K7*Aacmb6XpybebPfufw6KQ)~XZ zyQs*ga$`5#NpB&o;`#ah+CjUImu*s^)#4qkMaPEjX+7p-P~ywogOAkl-{;={?xV*6*tEuo;{#ma3kvC@|#Pq4k_oqwNC6>#UAUDhrh3?x~XonBE^b~ z{#^XQEpa73=QD%SJu-g=efR!Sa;K=ZgZ$>bjC0|&+?>6toC$edu5)rLb@Nd(B6s}W z9G)3-pu(E)BL`1bkZvwnty1ccZbg5ty_qTT@{>BH+lF2J`MLJ!h#}#pi~YLv&2iGj zzeR2f)>ZoLxV-RT$LbZ7exapX7m)Ybw{L^P1J#tCDe7a>oBr}Y|7vjN+GU=tYR)aS z;(2nVY0c-qe^I2siokZAeA@rDdB~V1y=xu{Y+5MEZteXyUcqs<>up}vyJZvACrgY1oK`5w(u@oG`O#{J{5Lx0c+O(WP+cjXaX$hr0~F^L^U9QYGj2 zy8iv?-|+`GMOzI$D?b{X@o|cmDzIPog01UV?|Q6i^W(D3IkVl7U- z2*0dcGrnNI2Im(#Y#V*0TKn~Z0}elN*j&88C|Sj!-s`{4(QPa@vBQSF-<$R6ziV!9 z?}WO~M?02I9^T*kbuZu38=v`AU;pyOq%yka^H+AO(99~&7nI5Jw(w9x@U-{ zRa~?A`scny)BajnWZj|W{Q^CQH|jpubGnT>qskl4f~&qqU2h#bu=(?vwPUtCZqs>Q z_fn(gUQQ|9Lz^~y>+!FLo^75~?#YAK1FU{ebX?Q+TV(ZT_1gG-eXwTh#}S=QP1p>E z<0f{YFN|b+PXr49r_OC3*Wvo1&rls#r@dztQa8_p9ZPH2*!_xJnWm42!lj&^O)?#;#vO;;b?jMjh0 zNZs;ROmx@}-}MQz_FlNU z@;#Rfk>~j@Gs{&h8hih~e9e*JO$UWFULrd4D&LXvI_vpYB30qu+xiBcRLkELxN=r< z;ys{LVcBAGGSddd=LY z)610BH#l^Bx?9(>Nu!VS@9(uNJ$6g+brViR)Y(c{9B7dN-Sto>uCLTV&4* zK95@-Z&YP+^4jk|o2{NwO?zbKr-uFs<35M?N0Nn2&mSvRV9fJ|Pb<5%nm2f9v?jFo zFZ;e@zOa&HT^`Qb9foAf@5cRXeWuD==gsO=n|t=%%LC?cufN;pj~9M*uhhR?LWN$% zI_%W#Y+mX8{++iF`Fz;3buqhA{SKYKvUTn6@%osS;-1<$7dxL#uJH0T;@hjoe_x3}7b*OFHn zof;&)@&3@!6MO2rFK!g%_tU51h^RI-{o)>9t1&S>?W;pU@B8=5PW8W6KXpX@L4cea zFjv&{^}PK4W3O}^g7ry^W^BIX&{@lLC_0*%!Up31P ze^%>vHYou{pvba%Sc)o&zpR{K%q_(($nr- zp0nqh!-L3)Q<_wIc=vwG_MIamSGBG2K4kPjN#oaP^LI(NTB(z#2$>EPn)*ZtPSaNT z&VsTow^Gi$V?nzvmmLQcL8;o3C+{_@Ji1=m`u-h4P;PC#CH^yFe#Y$SS+3!> zsRd8#54xWGEAo5ULPMMulq>E1ZEsNL^B2FzUYxq?A&0IILaNvkuQ`^eRr?|%S zToGOR=Ul)$Ubnf`>&Bqn=V~awRK0$EDnJvP$whHXtZO_@OjP_GcV}14Y1-yai>^++ z@%CVcWY}AhuUvUu`%2`8PW?|TnRiX!B=tq+n`g2ni=6r`996&4 z%n`l$0R4I&K3@nPuK1!jGFOdU6&e=3#9ntbV@>R{^!RIKOeD4p$MQv;|qS!n6xz>5#sbf<%#!W0PSygvOzHVbq^xAM|IudM;zOpX7 zUTOtx=T-aaKd5|6-M-JkDnj{tD=qj-2#&B1-q45ls>=`cau$?z+0mlPE*1o$MYo$Q zB$zqnmwp_ou3I4HWXo1VSGIKBH2wG9BD>39`Z@8N5-|s_&Rub-MMvo}`!C7X?du%d zH9X1o`FH0!D=ytxIt<1_QSCFE3ktu&)9Q=OPwyz!U*Xa?sduT%U4*rX*x_+@K z;?+_YZXeP!ebuk#8%H4NyjC-(xA3WYqV9<%b5`#P4c*ggrXuEjpC<r(#BS zY5ulCKm(V+;xR=LdA8Wwb>rTDEqLg#_!KmI+fb;$TfXOQUO z)vZMoDP_ajB&9cavA@IGOV?tq{A}B?a>YtPe~ETJL<+yW+N884E{hsQp80&;J4y1b z^nm;?--hp5UiMi23vDM4MUtA$Kb|~WKC!7ndZ_4Uk8^!SJ9}0x(|vrnk7s1q`U6N% zaN4~yZIZVlwApd)$z`9c;?7qcadOJ=2_y4Kr1tmDjA~hLOnTaykv>a*PJeegXrOwY zBrfkDfSNq_yEA*`#ptn}nw;y6_wY({U#==q^nma3s(*jeJRd3BU3#Vi;DvTcuea-b z*)MeUttGw3d6g>%C}Qi7$xgc_2^w zaWAee5zTGfKeBZ-Y251Axd1!2@Q7B;h^^eO^JKhj%Qxr7nbD~T?esb2dSp@mjcYF! zTWA;mq5YvH%K>d0>bs;|y-A)8-#i)LWORDk?8H&ebwggg41XL{39rqD#qFptclP7D z<3EKA!F#o{1v-{|88H~aVR3(to;$wOhkRp3&CS$Kw~DUzyYkeenAHd0v@C__hx3ap zJk_g858EM=Hbva8SnpY@F^4L*P4wFOc}&W5+b^;?>(s@%wy*K~m;IulXZmejb-;I6 zo3=r>kn_xriNlUxIEPTk0nbS;W$#T`n2DF}JCb&GmG&I+Wn=Oey!y+Nwp^U-dNWV2 zhRSw#y7+Eu+Tn+GG9n%wO5Hs&D(>ThN6S1`91Qq)spgLw`vI?Tao(2XsCMqrwzf;~ zx=tP<^{;pBSG7^;Y2J+k9Q?YszR}p#GjNpp>7ZJh+O)BqsvYs=V0FYU^h;03bL;cT z2gf=L#Ot!TwSOeED%lRf8(+d#&6?^_YUY&+1DbjR&|;RPyFH$ts+O4c=JkvfF5lMG zyD{7OTRFg11l+z`vX*UBm61(9ZAvNtNTU2?tkdN-<-5hMwN1Qr1&}M3E}S3QzERl+ zt31DyKe_iaKo6!DDea%$t6orLRrht5WeZE)t6$zxGUnLbWm`ubkbjO=H#iwm>4sJL zb%#W4hL5h@ZMUvre2Ej^N_p8WD49Nb5=yu!QVdu!D*a&#>9`Ix-sD?8XK97Y-)bl} z-rmvTU6tD{ea~-bGcrAG(t=-_!bMizxxTjHo4QQ^_*V7%)TgiJSC6}U>eKn=JMudQ zyWU^h7!lv!Pj>oQb;;sOom$`iOSP@-V3&@YlQ&d*zs|pn-jl0;iRPihAmM70`P{gPFld7@?S4X6$b-MRD zr0?2D`|IA{d-feL<$nD@s$vwVr3S1O2{fI7Q>a~>5jc)jaHEVFOvSjSh`@s`dr&hlDtXbcuwLX^Z)S&c} zK?~XzOdVN!+FX5!PGj1h3nf|OPubykq+IJfYPe``E-ojmumeud+JNw7}VLvZp7=}*$b^lAleLF?7&)qzJ`yL=Yc2+3zeN&R+^Vvdq zj!jMKP%V!-6JW22U6;>}PnDHZe}2|^Y`w7dox1E-r_CSbmsIr3OvEc4W<4L?eE8MV zwT7-5altLq*T4DuQ%Q&@;J82fl>ehPJBn@DD?MJmz|MKfDkEJV_&qG={pnZF1AX?r z|Jr7FdfKIlduN~3|Mp&1VdkBWwRVry)M(kyw)v(-)!#4dgy`9-_>JB1?7Cp*ea|9J z2pw?mod3P-fJ5;1eyY<}05}yYAsW~tHKNScgi?4_AG*`5P>;X*H{PUmIrB|5qog;0 z@~`)Fy}uVOveEBivH6wHHS2CYJ>TGFH^tLe?Jm{({G12fch!iixN%Owy?&=Qc6i{q zwnA*TyunQ`#pQj5%-4#w$P+SUHbRoyvD55lEktO1m6X!88jnXPAaz;%;q{vl%IkGo zlG=ATLYXP@C0|2tW}NgXblpMLs`{k(Ik!A-{~p+($hqTB9@Q<}TGTmx;fM5j14bVi zw#R4TjL3=SukDWOJL~H6g0C76Dm6WIey?HaY3@H9mP{*rQggiQhB?h2Ojxw_Km{j& zUH1&CmlzxAH!$|(mw7)4GIo?WU|@P1rPIM#;{)1Osk&+1%!K6??%Pa0)wY-l@$tQu zWxCZGxbXC$*jd-Amw){#_4g{jdl%cUZsVA6Z-KUz@^PN=ACaz7$%S^m`;IyN{?S{! z#TM%Odb?lagbe@dL-a14hDl2*D$fM`eS`ch`d_R*e0Jg0Cwv>d8v{`2!zT_0-nH6; z;Ku!zT+f~ISm_m3+Vjr1L&*R=sdnw$Ykm0N9UnS18k(MV;l}W?H7Bn4c<{*b2k$3V z0pvzd>K@g{h%nvS0&dQMu>!7chl+Tf-1Dtmn!?PUfv^p?(LjV^bx@Ohr|`_ zDf$q$wfn4z#iG(~PX+9l-K;|+-=5lksau!v1v=OBT}9c~&YznR^l(G;!BjU*Dc_?p zT@~x6-ETEA{OFTpMDIPe>s6=uv-^GdcBszq>T?{bC#9#=P~B*A%<*V71P@>IFIhA0 zuW}XNX7s_UYJ1U$rz`(*wGZDr2(Kq~K3^Qv6%P7&E$LXG&)CV0roWH_Txsa?mao39 zl_FR;rdG<08hy%XU5+_EF9pEysbAA&UHi;`9B5U(l+)@A9~d@RhHh{i=Tm zzkuLT&BvRkf**UgJaK#f^HFa8c}n+Ht9B0f8&TV*i08MM{|%wV%UZnMJEBdeX;&Bg zE>m^QrmkDoHCsO(5oO+vv8s3YWLhF>n6u@z96>%z1-UN6s$yI5}iF1&a3 z@XD}Wc>hg=2;hqVTr%Lh(9kHvEg0$_W=6eihHoC+sIc^Nz%%7VFi!)gw*z{ z82-Lq>uOa3CI#kMf7Tk3-{$`YXlhurP5v{BCUqZI zLu%)Bd2HPMdwz=nf1*9|W&5k@iIp?IrQeAj;y1Fc=geoWYwEny&L}nt@u{u%CpKT} zrL;@E)xY8X`paS(RMKuK_p@F3N3l10NA7O9U)8$OqV%+ex8~TbpV+tgy*7vUc8CbJ zc6S~OXr(dv-ImO2pm%9nqS?|F!-oXCK4tT$y`n)`@wtPk%kzHrMVgE`??0MWNX6{DR>{~VIxT%x=DA^@Y^@-KYeA@d}H5zD$( z3X&|mQM$+dA5@ZL;WVcPO-`LIQlj$SHvqhk%p2$PLZdx-H|}>rq2mYV1D5CD_?;mEZq3x~1qVBf0ap{uow!2`2A(WxJW9Y%|Kt@FwWsvU9q3kXkWRy`rx?|`8 z2D<|obQBbRYlA*|p5Hm&=e+Orediz9dw;IyT)Wn~*S+qwkJe^2(7n)TcNG1AfsvE(iIW9up@n@o$4B35!fJrCHJb2UQ@vydiOJSyzQ|~={-jvVCl-abTaq z0^yNS>lkDHWQl6|6&BrYw~AcJSx<&92Om^~?+k{*mfT#-wJY@V2who&TY*p^FOBc@fl@@eL(s8cFwphuZn%z z;+#$Vi+QZmb5dL=4EQWQT}+}N(CCU2ziqzj`9pr;NmoYRP*&gGGVg`z$HPKT+s`*= z!)N91`Z?+F@8IXv^jsgdd7P$J;l>`#mnZi%&1*S3X?6|9q7I$CiQ^C+Mx?6CR&tU~ z?%?|HoxtwYxoMRPzEj8K+Be-*VHst~0X$C_KOT^?Zw09M!-z?DP=2smZ$al?Sb}2oDpMQ*P=%f@HhGH#&uGW%pUb67rG>51)xW=ZcY&Q{`7;#tCu)q zmP5zO6p^b;IlO8^Z^*}a;}?2dwkz>Gp2r@9GFKQ~tdcEJJKUeB!WlR3kz};LG&T>7 zyiCqNaOHN-4;WXi-st`k{(T&zyEEcF{Bi0Yzh*l8OL?rfH5dL(@T|LHl7VGhiid{c zcJB7Gspm)S%b^j%`&m>m`xwrtb=w(edLJqkrX{vKzI=c{sjF_L$<1l>c#@Di5Ivgc z039{jPvVrmJTct=TBLHLKXiOtA-cM0;F(0g4rnZ&UsJl8_vpRd@V7|f7v*=W1q5xC zpz)#DHlFODAeSc&4RNpFi(~smPM@gXu0y|<6}Q`CzYzxdZ|C1#&*WO!HY|F;Ld%D{ z+>A9TZQTe1rSFb4%m2V;vx(oE_m7=ib*SdaQ|MlNK7QTtW}OcQMKq+;UyUm$QYRJ- zdA)#@zzT-J`e?4y0e&x1KV7GK?*PzDRFmSv*x& zj!@oNuW$zkJ(q%%QaAK{l0PWv@s;%5D(TO0?N?BLtcleh_*^?GiXY+(Mpt_pbRx0jmQd4!Id%fi1 z?2k9&MP60x=Th|6|E2?dT<32e>6<*b5#Blrnb>x;V}YOw1G<;7H8~$I;~zb%WD>27Q|=9`3tT<5jP`V9%OnofEhHJ_PN6?t;+% zX3xjPec?Hak8Bh9cwQX(wvf~Zi!P-utG<8oV>wLD(4Bf=)#oELmeUf>pWj-;Kd<@3U8ob(X_+@WT_vQO?k8%x} zbNE&WK=;?GHy`S_T-^=JC-{mBD^`gG&}FN=K3$~fa@N4`75~i?(jhyG#8i*ED^Ihd&iVLt1>xWkcrVKCiObjYv*|-TGMJ z%P=HISLc>FoiEJwmT(E0`Ytg$*6k>k`xZ9pnNe5m;GA)Fc*A}^+YS?G^*+8l(ucEJ zSE(z0*wR~OgdMj7dUa|D@mvBgcEX>pxHj-cNUz`I$}#s>lYtKTso3|=e5nUM&-bM& z2|`DH%aHI<=Pj3&ELTBGS1({wAZho3-lr8Jw_Jmk*G81EJD^`t^4X5Li-^JSrx5Rs zouO}K870e~unFY(o3AaFiTEmuv$WHlpC5hiV6@7Bj@M)s#uzhxay(gl4r$Atk)d_@ zmaB}n=waJvEJ?lWD@i5ow^_VB8*;l3KIV*x>g*zKzMi$kr1*1oM153wt(vp1@O;bD zE3@}?zxhIsW9+vOQIcHOpEvyHrPp>kD97EY0W3GD|Q*nWL zW_76vhLVRTt=_fNy_Y8)I3>AaOR4NanJ8yy zEZs>sD|R%za?&BfVzc1!y8*s&d}=i!7fK`!7tYao@z!;@tnU$#L*|QCzF*SLshJ8n zo#gBd1Ysu0}KO!gnPFEL=O~fSEiRkbAWP2bR z{%4BnOOit1Uw)(A7o*|dHm|l1L-235=!Cg+t?o+tnVRGX@}&P(nd4LQ?bkcyk)#w0 ze2TJpR(>A)q^8R3*Km=S?2WpZU%JzB@YDPc=Y5ySDQ$_*UE7kpuh@0taIw|#ho1s$ zm+twxCSniU>1BHujc0XDL#6)n-FMc%Pg2@CJ@*`b0=qg5E*pEn=vZFFX}-@G(tF*2 zt4SeGOjb?k>cu@EKf4sJS-Y?1)8@q03JVRCdAa6jVw#)l;a6R)@4C;KU}v$(!pF>6 zk>u<_aI?ibd8m@T9GyK)32r`QFB^ii`X++2k;yg_4=aLfAl}EzX^xVrjJNxIDIq~A z8A)S1Cv#t8Ukh*d^ge?D+w=EQIJx!Ruq$$Cy~j) zWLx-I!jJy!b!BrClac>oklC4=#jNk^YUu14N=tju^QfBD*B_ab5tovbl2n*BQaeyp zS;LC#W;HgounLDe&xK~>7G&Qp2)&k^PRVagsjkSZOmA&zg#R9;t*MdC=6w0gnS|f; zL`Pm!-l+r*{Gd63>Xgx2+4WeHZFny$hkZ7Vc6gYuD@@qg%}1696ZAF2`};Y&`Vg%C zAJ4z(bq}kh>3!Ycqn?~eRu`+gp3%{l!e+Mj3{|uG+h4aa+n8yE?^%pTbYDKRH2HLsWw zc_}YDGp8{pG%E|%W9kPl27XurM`wp^yBr8MUPhXzjm2u~>g(&{a5!y4yv4q4CU_Hq z8MF;>v*X%WI4{-J6%`e@<@1E+@JK0nE)y4$l2mZ+Zc;O?0lDgsQ!T zuZ5?Dr;WR}jiVUK z(42HiQb8d#GmnxVl6fyRF$VT~VzTka^fa42#ukJhFZ`x2UXL`iz!MaMJxv6KW`)-rU<;PEbKY zR9dd1Fzp4Srkd5&9SNs9B`H6wFm1eYpsKZj71_aTYs87T6=l-1k|#`XteE)P@M-+R`~J=k-v_>j zc2_<9(HQomxpgeIH}2GrS5>c>J%i01Z+pM7dW+9zM`hnFzTG=;IkO-k?mFD>%zk=7 zdO=2XYD#wJ@P*7%AqCkvjSbzc-K`yMot?KM3&TPqQfo5`65|pI($uB5n!9-uT>S~g z2OV9#oWy+1{k-7<=;@lk@y8jcNQfC58U}1RuJ$H?&8vR+PLzML|*+Ik_JlA@Zhy1=PJX#T&> z2eN;(PtRNH+gLp*W2~lu!ZcQOS6@vpqd%+g4YQ?!IXDy>mJ^i`ml2m%_`PwsuJ#cl zwx7}3*1+scEBq9hQbWC@@l9_Eu1fnkylta-%?mqS4aXU zxkOf7%XYr(g2j3(1&nQYG&ia)^Y#Y!CMvQ@1k7DNm^%iTyY9ZU!Z$sSDwuu!u@M=G z8L=rzX@w)T!*!K4jM#2gYg-GeGcD~|ZN~ts>pQFRQ&$(WBQ!st% z8t>v6z@MJjH`>;yjm1W`f2&)z5r=C&8}98CB$kKTXP}M7N40)`-FqwSg(BQeC zB$sgf9Xg%g@uZ1yDV2IAx13W#Glica{HVk`$+5LnLm$6aPDQ^NX=#Xf|MJI^q^Q@= zI^T{>vA&(V7*`cqQjm1Mh*qC;@mgX@byXAnW<oFv$e4*?Owt?8#5<7$;5s$kH`^sk>CTzg7)qW2oP45-vtiQ1{|Uh zI79+CL`!gp((1dtj10YeJcaob#bkwL#ii9x@T&{(^GR7Ns)>mUsY$!|E##L}SCiQw z#l2QWLP^qCl3!gN?)%XrA5M_K-v>EK2&ixg&76M{DLDAeF)H;k%x}EqWAxbjiOK%D z;i;%or@CIIRo1_64h=0YqyEGPru4qo3~-Q-!9g-|o>w(6x(6rfnwa?^l)Rkqf!@oR zl$f~damlALhi+#_qz(++D2R#AOipQiG*sJP*I(B=a4#_}E;b>vH9s>nEFv?jci>uV zep;sEF}(Fw4#0=yF=IP=LdDblC*R-`U z!7dRM)Zo#V)mpEtWv{h*12@LSZ`I%ZIdiD1FzX4Urk>H&9h#65n;)7VmYX$R+1u9I zzzpqR*48~@4CH3LYVGf3430Bv|J9#QF(%mT*IkW~586sYGfx*?uA`*(X4a%66c$r& zC1h7tPJ#LVY5yk^ucsCy{KR`(9Gu;qZSY>Usyn@nobfJz_hbVdeO!ZVEbzXj1P@P3 zdF5RI4{ZP*8aXQ-2K*~21ZAR!!IO+Ed02!qK?7LdDhv$Vh;3l^yNS4VRSNT z9yc)BSS{@K!M?}COlEG@^GYy~jFj)~k9rEzV0o%xc_I-YtQx8s2Dp<8aHjy^PAtHk zLV!D=0C%zh?py=7li!$8Q9)_VZf$6-ZLR}{**aq{czFT-0sh16+h%WSLcq%gknpw+ zK8DUt-pw-Opty zx2&?1!a6nnl@c;?s`iqGhH}OV_IB3hwl+#8Hr6xeKP~{ye=t!;PGAw28jqB|jDkei z5dgr{y8D1{XeFhYcj)Ek^Fk{pD;QI(me)ODNrm0-CX&J;LY{;%+G8V2OG}sED=)vC zoLD*amHFxY_mTREx~?hOwW_d);(JAH@wK(nQ`2+i!VmmAXYWK`ZQ_mF(UIpfuNF`* z+-!MxJu|od_O(0L%R_FaB;UMors(|Ds~0o;wg-v?ZQgal15XSL62%MckoWVq_cY`Z z-@jXoD6PH^;JyXGeKUaj&H(pq!9S=2JTe1#5D{8OLtmD$03#B9(1 z&S-tY>}_RzV)e9io+?aZfgM!whf_-y;j7A|p~8a|#lZVhYka zhtGjSEC7cX3J#G14zU^>VmdfPI6nn#b+uEYY=f^H&Rm2CzwQ1y4gPPLgs?fkr|;YY zPL?+I9-D29w>WM!-eQd3w}+d%bpr;YiSg3MV)ga)^a1bA+<)x;ZT6N91ia*NYbOb5 zBR_W~UKtr7c_~#FzvXgVbCeVmtxeqELO40N_^ks2$qfcl77U~)7)Tc|kjh{n)xbc? zfPw4?4`+mSg`SEHjZR`FMMd5zqO#~Tn)y9Cy|f(cKf(u=v=2Ye|Hx2(PkSANN7WD> z^+I@5_zT~(bvJdl{TqA(;ZY=nN9hnA-GcBa1;V3h2#+cuJgSHAs7@FF0Koa5cpkTX z6W}*zFU4RthrJGV((2BJjy^`-1UF^n{RAspyrYwt0K$Wc0`kho>|JcE?Eucp0i2fu zIKL9$yqtuNobW;g6)9PBMO#ZXYZY@dV{20rDGL*0wErjFg5mxL26)Pd%LwrSJUU5T zRz{^!@6+i2GT)A$^JVu8Rpx)`uVgYBnEkyd{sD8*!^|1)YhY#N0z7J_e?MjR>xa!NG3!crQDGuOHrpTUp-A)Wg-v9q%ixz8E%K z4K~~XHrxR=+yyqA2R7UsHe3WY+z&Qf7B+kxY`6q$xV@yQshqKlv7NQCrHztbA>HT$gA>Jt~c|Oon!=SJE4))ixMp(7656fr9OYSbC!gv{t z!>V|}7_MZP7o+un;c|%QMk$jhp8uXli921K8H?^WYEMie`;P1(C+vJ;bW%ZP$Ajdw zzps~SpqHZw_%|;rV{g&_;=IFh;bFP(uv~j#x$yt_yayd8-#JVsM#FhOdgR7lm_PaW zY9%!lKDkK1i$SCkf3#kA0N^10!_<`C{=T-ksfU$6(l;=`=xbwibu_1Z`7%_?V$}3h zbv0z=GMWe59}P1*asePT06^#njYIx#s15@DLIz) z5Nhii7{R!j9tOKgM%a4)k*(qu+YhcVQ8DFSueuClV6bqN_8MdRLqziY~g(0s{n z4ikxSCj$c`q1gcwM)zM0j>8esaTrJO-$~g24Kx~^SxPUzR6e`kccG2&?E)M0cHZ8HHEB*MZFR)B1z!r|OtVp2=(bc%6nk=7yGps@ z=>(gzVU2}k-Q?u-hh7eqAeI@bH^rIMtW$5!8K1_SB2{mtG&n@niDLpjP*DS1O$F5yvy&N0zO1g`qbomT9xfP96&kY)rl%Wp$8D zK9Fy9?|@X|tFcl#J@)#LF1N=ahi8{)G{46y0?Tw7Hwnsy+}L=lghtz}(O_44G|W2$ z#Ah^NpCc9fvw!E50OCLIXKXN|sfvL1c0!&!ai_ zuGZz(9wm`d1zP4-rWYk`+8h}8tk;FJ+a(YeOh$YN`2T3q(q-Ip0RZH47{~3G-*PoqrJ~14we>c4#j&g}o zkolk>C#`oNGdeFhA2Jz{<#)%CN}nP-h+-yY3+Vc6)*Mzp%n52of9fQNn@n<2Q zLK{Y-zqGu6-WL(pH9lFzjP6aUZH|d;45Mcjc0Np?LO_G?eAMZvyol6(dTMq-Mto*U z_HgHg%!u%U?2Oum?#7PRj;i6#+o8E(p)m!u8I;(#1ZO{W>7B-I;PCtj=6fAo@xEfd z#@^lt@93GWz~Pnw|8dNOf4@)g7J{ce>Cj%@ATAe+qh{_t{QL$u10yi!LR$g@54kBQ zh)akkK~#S+h+Lq=B^+U$T;0+tkECe+}PUEfI{O?gr`6*dH%OS|{_eBOAXqw>G|S==(fU$x5q; z`TRbb{bKafSYl#f)^lc0L)+`N_7-N!G!Vl5KnM?J<$eJ8(*yA5QGaEBEx?}vfIo2n ze_8?lgaQ2N1^AN>@CW$MbJ+lYYExp<(t!RL`%>|kGx-?e1Iq2X;JrRZB=Z0hC%bLj zzT2Iwef9>332SR>&t0Xjzet7K-rIRM+0^`)h3{sb1ACmr&5v!`>t(k?SXfU_ckVLq z7epUNdwyQcCAPTv5}NY*Qt}#HHU?Z~Qc^1yScwQJESFp;HBV=?gt(Bl>UK08xVkh=ioEjjxHXi6m2< zAkvTk`8!`48XFSDh)PN*4D;H5Z{UH zAACzIG3>`r5Jz0pJV*!085n!vcWQ4gd$)|D34Ytgo%TZH+C= z(4n5%x|*JW+}wAq{hd97)%xR$-Nhnt+_2%OWK?KJjJZ zGNMXO7$pfgRbDkkiC_7Vp~O@9p*fK`aap-v8#|jCAN3>-F)FKSn7vuKpBjfc|HzMY zH+FWy_TMR5RSNisPW{yWzNrQBKefQ;0-l@AXCb^R$hm$VrdyEO@h~~BFf%o0pqCPz zogR~xncoTS;1sxnoLUGNJ0M^LI>Q|33^SlJ2ta2Lz#VJ_cR&DlU<~fS%U8^a7akII z6%!Q(f9)kM0s$jdTdNt}7uf+7->j1GJL+RuKP`m(yMi zKCY{Q{8!?A`V9WNQ)G$w655SHR@-AXt2ydU80CCeW>{kMy);T*dde#>aIhR-zGZwu z`3}ydywgz;1v$yBkRXBf^dJ1!B}jk7LA-au&DYu0VbhNP{rm@eH}Z#MZ2|9ne_<4rHf$Vd%jFhKW}dhExP=mv!prmT1wuf z*0hjwDHLi*&gIhD+KFkg?|}dHFwJn@X{+`1GuGj7O}ZG&{`FWa9uR<$0S*WBKgM$* z7OShT3-#R-vk~3a~=+So&(Ic4)!+}_IvB>c_3o_0|VpY zU*-huBa>UO;Y(SGQrshehi{*4%LqI{rK;I8Duc@C^nfVu_{*26)U)|^50tD~ts5Zv zJ-ma*(;e*JueOwNPp#p38qIRa+)mZ#GaC9Z|LS2#%?cMJet0%LEgiD{X5%{!M`Z2} zMOkRiIbfP=NJ%u}eLM+(U%A=;p9jfWqVlV@{ zE>3EGD6(oX_`)+V*KZ*PGf(i-Vc( zC2K!x$<>5%?IseVUsk9iyz`jeW{UCy*J1pQMWJQHb~4#)=y3@ePkFUE4~>ff;JeCiMvKAA-tc~Mvc+wm9$F8YwhW)onekIL zOKx^*SM|<0v>XcoPTU+ce_@#Igm@?Kj+OfQq8ZmGDsF~*7hp-DzsARb4@2vNLF$5D7NbE4({VgJhL}G+03G*Bz63cATOiyhlk39un!w5s8D$R|B$9w`b3H6U^smn0tuT6&meAA$8KZPPCcZ z^^gPC<=OTh{LiE3OeO%rCIEy(<&@;qoN&1Q-84vdr-WqQ&P`3vOO3fw5Djp!v-5O8 zYDC!iu=JRM?%8}eB|mwf_e?=dbZT-+Ws535xcmPhzl*ot>Eq+;NSyFkJV4+$?1 zPhlQe5m^WirPck-{Va^loQ2&8_6~Lg7wNzAxj^qp0ljAg^qvFId*)ys1CHppA-KB< z*27*(NrhKdiciPER>s(1skTB@=(}%UVq-mWX85l^^Igo>fM1!c{CBK@oKMW&&t1d6 z`A=y7#{Y=UgZSs4@juX?1wen+0R347^k*en4~+*BXg`RM z004{v02l=TFc$z|69B+O0Dx5h0J8u94gmnX2OFFX0I(JSU;zNY8UTP*001ii5KaB? zA4LA}`}tH52qoQ3&ZWfX_M-f6<&0ir>POZ9nxCG(v&m0>*T%<)3Vg^6->+M~2H`y& zS7(!5hwZJb2u?QVkvs<0OB?%B|58DenkUQ)0vNZ2a`H|~rKC5i$jf=V|I~Xx+s|dO z93QWmf~>g%;)k6~{9M*?EAwy*shP_v35$q=ydw?d9YFX2-}$b+3*;RgAnynTc}Faq zPi9zLEaFFh=YxA6vwGk{w84c)dBx~_n3t7#Cn+oyUwB|$Z91b^o!38-;NX$p%e$tQxn2Rab;AK^d5|8^75c{zso z-;GLA(hwkx4EFZS;u+v~AIcU0-l0BdXH-4^!m4j(^)Vv>5F&h3dS8+Pc(D9IMNi%9 z2}X52bCC570C58B?_c`~g030-E6DkY_d7~xl#sCO@XU;?g5H7D_`Kv(g_IO9f2m;p z(o-s%I{r!D1Ln^InZLPny~KQkg~WvC2;c?8#SlL-lMh0CR-o|D{oWuUtu7}a0Qmr3 zw`C$~DspPlNf_{O`(R~aN^W5VrTA9amAmPebK>7&|V4t!ktV;`%S|Z)94#o zw+ZeqYCi{n$0j?2Mj!eTD#Pu1(xE+>N;`2tz^U#Yy}W-cQUQ&3rPJsVABt->9{V_~ z*CtTLqp}sy+`jzZ_^O}J9>pm>kGZI764=X#hh}WcQr()ot()fgvwhxrl#30!)y52 z2;U6rBYY=cQS^qxF=$e+uN|76UcbadaSzCW{6iPnBL3I)$_A^gY?!VHOm{vG=P11T z62gCLB=z+X9#mGwV2aIOhaf!Uu8IT1*E=T@q5;JJt^oXh&-!{b!ZVXjBoe}NOhA8# z|J?!i{kz!d`mW_Cgp8<5ypi5TcQcv%Re1J$<)7cL0sJ?0QPcH(FkK(%U*vPBRB=|g zcoXDz7Lmon5&p3{OQT7KU(gbQ_>YGXBGLwUX&F%@XA&uG-bZTCW2~M;r}x#!0K|{3hXWFn zfBa$&}OSo<}XGHHRb;$?qv+I{QD8`MjIV+y% zT}1cg{fBAHr8H&wVs#uzPao+y5&wm;*ye?|a@ghIXy+vf@?Sj2e?37CGL!$u`+MVE zeVomJ|H6YDWFN?RsCy(jF8pQHKt&RJ{KuPw;l9wavayL_Hm9$v zF}9$j@N#N%MpW#D^P#Qbk)a8V*PEkSBOXRnUaKhwQAkrp>Oj-gfxP_MJ0O_+GS)k5 z4}*B+54kz>9zJ+XXrHg+E^B{yVIIHT#O;Lj<}G%F;G=@_8Pr5+x zQC5;x4)UnP+V554!yli7Hht^wd-FJJDE#@UfvQJuBVqdGv;Iy{aPr*cP=K?kLziO1 zQvV0O=b!!mzvFv;>wV8@N{E?nllBrkY%S*L6qK45`l9ZQ| zTWqK&V{0is&stW}e1o~E{Sw}#ii(yZt1JX05jmk5vHxE0Slcno>;i$vr>=HZM`%Z5VIdpz-JBUcFQj{?1IA9rhKHq6y6>fD z0U;Cz>4A{`;_RI4#F!XLN?gb7y!_I3ni%|u+1!1WL(cKS{Q&aQu z>6y7vsS*F8hb_*I0Qhr*0s?@a`GUXV1#=*HJwX6q1p)wd5CHgr0Kgdp0FEF4Z~}rC z{J*AFo&v%<8v+Op2c0dv5&qNCM|iMx1LA)IK+ZL*=OQm!1^7r#$X8mK+dzC-l@J znD!jvHC9i?GX@BLp&X(D${}K*90KJ-(*CYz{wKW;ltZw;d{*d&<(hn-O^MqV=;LVY z`s92*X9m4if<7AEe8G_XQ1O_vM<2g81L@vU<})h)0pAMxq<(f&6gnj3vwOPO&|d~jf!iVsD3lRJmO*@#sfwI2-Mp+*p(7$3Z8dz>EtbUgw$_EgE{_DTCxjo$3IDsmS z!+n5sW`&yAqk4A|Nyd8fyJjWfnh5V%yjL&B-{1@ZPv87wqP@W+5^5_$Tg72Y+r#GBhS zwfmnIqW0%AK$rqtkg8&{4GR*D_Vr9oI$jxxC|`YcSg%RC`tFm-Ny)8O6tvsqE86Qg z9G@HGr@qVwKnU%k4*&@3)a&tW)6=g3&t7Lo%|&>3t{4tC{x+K8eXJlNU|rByB41iO1%t#59m%^ zpgSeZ_rEFyI`Dum07SsQJ0W3w?`B;-4#I&{9uNosy=f6iqp5r-esp-#$F!pWheiPo z`7Zt7(-nJWC2EUA%&re6t-KuH##JE(bf8J>?0W26^?Sa{N0UD#*M8WE=I_ujIcb?0 zX=OOfW-G;+gC*N?`V$25R{NApE^ zzYXS(;(_b0f5!ukfBRkv_Ve%a7rFI&{%Acq1AgQGT$n#PFVaAm>~;r1#BY7`v!8s= z2nfLbSM#4U@!@R#<7-KNR%YD26h7cTzDC(n$AItjatooBgML;e9ta4t&$LpAi4K zzi9BB?2>ar49Is3eTkHrN8*Y0YU8U$_&>|5ERNe%L;Q0Mhx1~XO;2xqviC#pBz0_b z*EG_9jFqJ(#YH}Z`hZHf?-MimU&#NA3ZncFXC$v6>GTy!0?65Y;d+CAo3&@N`fjY_ zANqHH4^N}ndY~W+8@w;#15K3y{(1rYwE_6+yIz4$>Q_Eb)!xY0_$=KYtJU708GF)PJWzLbM$cqAW;={wF>R7|`)=8F2csLhd4;8T_YMq{~>=v<@+M-KkK(u1p7S~4QrBTq$wK8F<-776^$ zouWv{_s~kpN8vp#@bA>3nS8ee(ErH)o6hhB_p2daI0jOork)H+<|)K){Mujk7~(&s z1|}!F`YIDsGm6iKpN`DP&ZjjF-->#0FYRu4)qTqOb8Yzv*P1e$N{a%x_8i*AD^23z zhZ%x_2e~i8H(32RSdaAG>#X*?+XCTnCxplPxUvfC;NxB~jb^K{`cN?9{z1cWtE_?b zU}N6hsMDKCE}bu@qWEw7so8N9|1FH8(oRPo{EGOI zU-6$;8)x!OC%b&=N8>2+@1w=p8QGL1#Q%)U_(Q~ZBK-q8|Fik;?{_i^DN(m`(EdVs z!>m8wILr5LbaXb^7D#aOuyJpJVT1>?0S+MlrxWDsj(0fbR0HNg7wBOe5d=;EZ$R%w zn$h#c2Lvqrx9u6dKh(n!<>mqXD<$W)Tuf3#PO9fsV-;h%ryua||9<}8hynS~v8T6F z!_I}q6+BExPNk*hXBDPeF_AtjuLjlw_WNi3Yqh>32!K&N++hzd3s*OSn@xbDH{MTm zr@e(Ukznazg?9iv@GCz6f(a;pz{7Dz<&73l{@@L8M+C|r?4kUD2g)DZq5MG=${%!~ z{6PWAAI#yntC>rfo0&s`LCwa*7|i1z_P_g={U2o3_4Tv5+rG39Fgx?n`i%GWx3C)8 zm@R3Av-J|c?Li&Ve?kHT%3HFayoK_AW)G&S5x&<4{`;7#n;jT5FC$w)RPPdl2!AjK zhJtASmmKzRvm;p99Jg~MY_c!{_i$Ys0MpF=qj+YWT_B0;0Ez7AW6Zt4VEsD7H5;`g zmZ|W`&J$HolhM=AuovT&l(5Fmm0M~guuz6)@dBgu(t;AkC&<<)-#^3uem@2L4!zt6 zI?N!y4e9?GeJ_=|@D7dEF%c?(^217y9Xz&2z!z6S2f}cZ;L$L75I}N3;AtaCoS2@@ zxzp(3ap7yidhFF+l>dDAf(-=VGDF0N>HLik(}w&9(mz%Ko}VZ9?AZC~B;I5{e);9| zjvXzB>G>YzPfy>y!RqxN{LtyiFu`AZ7?ul%<6m-BE9cYvWcveWHzK~r!;DCLq#v@d zK%_yiOWE3c`@RjDli>bQyHxGmdGj4twa~bvo^gBoYJa{PZ7D`l&o{2jxHzQo$UmfH|~?IaOYc__B*-G@Abw5hoyw$}`FuJxZSj z$gHfIB=PWWQN7s0W+qk_l{b86nFrwfd;vT|+pnU&kZxZI{5yh=&J^WWntQca}fWEC~ z;1i>V)p2cB|6D>tdT`J`8Dm5nQ-{*J!Ngw}D`5lA@u?K$E`y6$$;S*4o_A+(#uyS|yMD>tR?xOf4j-G^6|QJ0(5yQyuI5H*lipC_QgC&i2O{>WtjTM)C?Gw^CJ2BLUk>tqwo~<+y=#OsB*8hDCnPUIw9*XN5@Bh#pGak{vdGl@RCDi}X z&rHpn{~;(3pwS9oy`+qpjc>ri{k5O403KWRWj_LgS6_H%LAZ?ayHprUcS;$?r4PSJ)ztY$>GKSNgoL1jwYs1=WDBKZLMtW zpk@dqK4AYdb&$P3te_;YY8^ixpOm6FMQ2tI7C@ZQ{{tK(DXrq4i}AA<__Mph@YuF<%dM!QQdoq~90 zq8x_rfIFD=2j$Ox*1tWiVZ38DK(SEgaOW)F{h#W)Kn|V{a_|U{gAaflJPqXFnIH$x z137pO$Uz$asPCGI|1^)H`@O-R;1Edgv^hvHvfu}POdFdWtpCTj4!ZwAzP64o`~q@I z_|#_e`-ty;PeSu08wjpaQ(Pe`oe0l81VH*9%{K_u`^@l#fjock|09DnjGU?Np$2Ai zQ%hqzdfy;ttREO)*7tS@@S4zh_^p2*fd>OX{`0f`DIulyzpH=B0r?N?_tf-X`I3y; z_adO284u;mP~RHh=4nT;wJp{>V27(Sr5Oi=sL9ja%1j|D~uo4smD?oxkky}|A&Hto~F5sg8 zB!5_@BPJxIBq@^e88#TM^Iv*yTWZf^CI~!UgTRA1Q}3KVG4tNb*RJj+W?fSu2%`Sd ze|!i2|2LoA(9zh@2IbJ5P!1gi<2aG4~_u=0Q4@x|F=&dd_S8Hg!gfL{&*k9AH5Gmbj5o)J4w#)A9FlG z04=2E?!5{ipumj%_gpUn9ezLvfO}N6h5~U^4-4)Q-oN1Ig69IH;JE-LcrIY=-}5=c z!>}GDch`MK{@_+0v+n&!ZA}fMAIcxPTA1fKuv|zFbN4hG`HOpf@ZQVCh}>B{4}^15 zwGaSL4RAP6-Vhs~+WN4#=-$KX@I?Cco3zlhn8ez%>BwLH{2tkT9xw@;4tDx*FYQbpLjNnCJgYFHBnyP9(iLa;&&UT=yd-b z;%;kTx~Tnh8U&xNCr!Ly0TBPmfbk&a)@|GXXMYrNIB9YI_(_x0W{bOHY<9(f6*<48 z(h>Q8)!b&*E7{SODY?4?r9F!=9hx1i1f>!t<{o z|7RUA>;GgyAB^zs0+=w;cT!+NNbf48Ioo#9C6ncOzOn+FluWD4Ko4T+@@TS6&9Ket zXdtOh&azH7m`p|dAJRWuh5C2J_%j=apuP|BA4uOY`mLM_1#r6ap#BfFyS|oF2P1DR zMD=fm-|6L`4-wx%XM?;`OTdmgIh~tym@t#yM||MKw}`jEcZ>}}eZmJc!3a;2SV(x^ACjBe|P?eqQij@ z3k5_zu2gqO;G>lrW1=ot%!9!BQoBE3^? zW9M*cd~)8Y!h(!}UU&{88lJ2tbph7KJXmIPIwN(lizdhT!OHV zFdsL(@8m2X&aJ066AxfC1&(2{b_?|31p{t-0h@V}I9?-eS!vycDjVeWH0-b@iu!s2 zdiDmgRt_80EbR1fP#ru^i%-V<_-uX*2x6oc->gR>*{nzUpO@?8Al9!jWJNTCv5*r zbI(Nb!udqbc0aOy&~YOVFaSi7j2zE01JFC7`iGzVIPhhBpqJ)@`Zx5R1LO7g{?M3D z-;-XBM|Zva{QTvko~Vhz$jFD~5!rD6TR{E|@o?gO*za;~=s@u>iht#xjpARQGhY)t z^{@7#`(5;g%|`h4Y{}&0>x!ihpd7Xyjobe*j^g3kp~*>*gUIfI37$@Eda`!8G=_Y7 zGD$Fd`v$vm&JOu8f#JC)7vn$<-pea2iPg^+TwGKD^M`W<06{G8q`fd~-ea%4i4yUt2TR--{s;KOF5&EqEX~(-SeV>MylzR{FG&cT+ z_q`5I4Yr1ssUadl~s!Y34R``-STdR#R;HquoS@;0&t z&VO0?tRC{W9+bUx4D4I#rTlXbs9DzY zze9mkEfh%gLxEHSD4+|WK#($@89`$6Pe9w1C_nf~To0pdv5`ymkOuf_(Hx}~$ zOHD!G8(?|T3-4lWhu`LG7O@QIGZ?I&FDW-ea!FuMD=f$ z_7=iY2Fn)7;1ZB`3HF3!w;VKkca0zEa7<%Zg}296`uD{hUY!p;du|QfASCTyoVt?@8Mt^6hz_z z07MK31^pY+JE8SZ#42y((=eBHz$`P7)D&I3lwVDcAJ!uv5X!|!WZ(m-)bA-^-c%{z z-y*Vs|GG^benq2&0{z?l5$qfFK@pWU&iyvA9O(m@qqFrwM!B=~Le+rwbXHiXmcf2* zD=*1&z4ATg$x2A?g5L4|6U6Uoh&w&xa8@{1Ub_hPe-phlnIM)L$~7`(SBE?`U-xhpVc-*|B{q7F&NY-sL>`s1F*)f538P_+n_E zo~*Jo*fEw~l0l-QdZrt}MB;m_flM*K{VSc;1=b|Z9bb}D90NJU!uGFffet*qkVx!? za+zo_|M$eKpFOCIT#NABK>ln#d(Yl~&F^12MWxDyU(t<#_W_Qz#x6wf0}R80p?tZ- zPRN)4kPZb z48n`T&?`GF0C=O1AI!qZ~r zKFD9hCEmXWxK9k=J}Gkk`)$(4`A_)oE`a+e0q&y?xQ`d$K8W`%Mo0e6LH=v0GaU)z z)Yklf_`;7}LAoo~zXao>Bk5lbHd0fYYhlvWK+m*W50BZpnmxMw ztY-o|Pzvb2pFsC52i>;B`^JOrdk=Kq`qGyVYAfqPtHZ+TBOZkAjA*ZCQgDLe zJ81y;km;Ci8m1da{ciE;{mglfA7K1w8-8#pI(2W%<@i)96ugnoPep!bNq9CK_vUhb z@0^v8Lvr^beh~42mO8rIxm*2JFI(aY{tp1Jpm!L8-a&9CXV5!TLGMrjy~7mr4%2_D z4}so+_&%$(-ak?J4h8=L2%u%DeLQ~igF@__7n7_gGQ zhoMUqjnwqbjdhJoyJ-*5=&Y~P6@WK{_rwQ~;oGns{;K!&M!io>-*j#tWfiP{&whXh zYW^F(;b*=N;DIjz53B<`Fbwd((7*G2Zq#CZMc+aAK9mz55B;~WsFSGw(R{!DaSGwT z^!uT}#H~^3329g3Q=x{l~Ep zNat~!i)S7F9)8l|2-?ABXa`GWe2JH58GJk%0H8m}@f|35-0qF{L3%$R_)h-4WfkE5 z_W<{21MdH|VE7U)=n!u}hxlA1UcliF)48z$f+kCDa07;uzGtex0Q66tkEt3rIv&f) zOp@vUyvZt>_TZOwinDS+2eAPiWEtdtlCwir^cDynTmx5{aY_> zxorm3gTh}T;RXHG;PItfFa7WXv^#!)08a73(Wf9tfF9fxc(xlBay;9H;31P{+#jSW zqWG!E*jDzn?(R_WMP>zz<%Q-fRAq2M7C>{yJ&d#j2Ai=h$?Aoskic)QPKB zvqRbZicm=WU(kQ%nj;`=<3k?;V}7`CH2c@%Yo9#6FUbJ>yA}T}O>B(^GIu6N#NqSK z*|j@v>sII;wCVjM0ssuT>}`LywXmt?MRV{}S$>av zSB?i8eieL(TFKof?{0{myn9vg_W7&kmWq#!kwJC0BBUkvpNpFl4(07hIe#?2EH(VZ zrJ|DXU0c&aPbVEngi^}o#N&m>_9y&Z?;~+{T*@}+A++@i{#(5dT#qAqxc>map^apEDW3g}wIC6w>0&YbVPRE$ z-N(kdhm9riX>TeUuN6HiszpQK3;2z4y~phd5v_WUkOMA+9B>`vfJ-0;oc5F6;~v2M zaNak6?skRZp)aT70+Q1LqfQ@3xm(7EYZ4tljpt(0|0aDnmd#J~eUk+~*3-Q`r*vE4 zVUGp?-+kir$=-H0GpBYpZl{mG4()*MX#YC?TYVdHxJF$efC@QWQ?$v{A%{!-v7vp@ z5D?58V^s9?Vd0&|I#FYBZAs1Tn|H2#EM@ZF&-FF+Qy;hC-$)OV@NZM!;y%a%K@xfy zjgbGjld|Tz?0$os<;G&%Z%r-9TaR5$hzgBJ1ky8D3-ITU;`MoA$;L9Of3*PqD9eX; zARlgx|484>f~j|b-|fJr``y4m)qciePj$_8O6#Zlu2x*=F~wQaW&O%^wyNv>RxP%j zXXCX-YtiKClht(xsr)j*p6+|(x9$1grUMW^8{xLs@8-!>*47xNt*@uAq3$!I75_EF zl=L!N^`5tqzV&~i_gt|xpgO1`pdvK%VMJtOSR=)EpD)EDzWdg6B0M@LWp{FX6ab%C z?sAm(N&otCHST|z-qD$)toYbd1&4CN^Y>qf+ml&ZvQy1=rj|P0e{|nZ7{6MF;sMgP zgW$2ps=14mw|FYqZd7qyFk|^lZ{8FoEd&hck0=5EM!zpde~ZT#^fqAh@9no>h_Byw zy)3^KY#7{w+oOZ7io8EI5Emr+V_i(O`wSaA)~@e!T2C}Ivim^KPIxs}Qki$3@KBdz z^}gq?GBT{ecSHRzdm^4A;wS#=vx1u2jZ8f#9QA(M?~r%L`hKfu%Ll&Ug2(j#!n?l1 zg6}(>7!{S6l9;eNBPuRNi0CK~^$&BI-Uq_pu`Y-JQSC693B6aQ{$Yjo2h~3=b*1;i zo`w3y7FTaq`TJ4_+?VIOW|ToU10!wXKk)zJ`_g&9ybs^?%=|YDHPSZI?V^u%Ab+Dw z55^wqe-z(*iKXyncXCj9<+cw$ZUj~o-2U{s?B?gUJA;4RfBo`pS@lX0_{DqWK*dG^bV;=<+Mob zTgb$_(7O>SeiX@`Q#_34vVJG+XTrD6_?^pI2EFgLF+K=Zhv_V~$c^65A`32ebMPuv z_A#FSOp1Tghz-Z18@wYpKXIYH{p7c#FlfKC%!uLJGYLouWdEg%hh?;7t{nfjrf7I8R) z4;(jx#iDrGci7e1A{(xd^lbdFz>3ZX1|hsplJ!H7;Dkc)Cu!Pc-1a%n(UbaNHxU1K zZDoYMs<#)@|5X@?e(-W-&)Yce*Of-}Cw|Y#3%YzB=<+iB zV^b>Qe=vUu<7e>QN&O&}`;O}?w)pP66XKEh!`W!FcZFj&^}1U7t&S@tKJ@0%Z1%dK z#ai4i_&M?4yMX^s{Pwjm6U*~4o`d#ATfMW#bcXL^3HfuXchYnEeH&lsmSoaPubV2@ zsh-JSEa(3$LORf%|3iAHSKJFaml=Q{wQSXkB(#Hf`~X4fmmkSb_vXJ^KzJ`>6hJ}m z=YAmmKPJ50v)`G&^Z|(fPx;|%@S)94EoO@d4@>;-fY<*s|NZW~Ga?J(|Kz@t^MCxB z?_AsH9bP&a^-$U`#Q!3G(*|QdKgWtUIRQ_u8z(^C^7O|};ali?cLE-Y@}r?fetuK& zgE0IcQ}^U4;{R{Rs4g9v1Ux?kkNhEcbZ^4M(iXk6Rkp%F!vX%34~9QR`ycq=x|-bF zWf$+9C4Hb#m5_&hGl+lQIQmzoOwj$6e$e;*v_t}Wzq6rRvqNewKo5B(D8SgM!niI{r%Qm@<3`;-1bnim9(5>z~l~pV$A- zOHKz$B{Q+Uc5mhDyB6DjX$w0m^SYDo8u#t_j(`J~9SQJxK6>WhB)uo)7IYpI;}19v zAEetqyn^r?!*tl}AswD>K>N1?joa{z1EF|@&p)3<{a%H~vVJe*!dUECN(0Z*{m{>l z%Ud|@X4<3IdW&)I1_ zP=4QG!aHq-$;UVlwd41Q!~jfQ8WUQ+0OQ+her52l0CdarpMT-7*q>~bj#9m63+{jF zhljkD_rt0GjSHb;vl53pK;h*G+W)xW*C}q>FR$+ySVQgu=l^0)$bAfWk-KtsNQq<+nEWoyjBajzv_oRZ2`QU?gKjiITaZh7dE|?E=N0r z>OW^-5VHa05YLR(eh}`z*@)-N_Q3_O@A~jgk4O)tcZ?Wv&ko&mpocvosBi^+A2aq! zq}>m#;yxO)XHH2XDgS=9nQ@`I60<{n+@RZTV*wfILX*5?z5AN6<( zzLu`4_G>pfFS7UTW!BHkYO@Z$}{wL*s_geXlV@g-zfHR-z`!1JrYTJ_Ublx}MyeGaR#eY+=9q7Es zuFcKuB%1Q4a%G;wNz7*{%OQ{SPfjBKU&*#BYp;Jo{0|u(O0I|R(R>JYIgzl-33vZT zzq>BL_sm^Bx5J8gZmXuN@~5hMtXi?s*?-yGi9S{vZ5#zFXRhAtx_MkDk4ZKita=Qy z{Tu#|qibN=O;caTK(2o^)y>|;=|9oGYRJETAMYb4+wBhVAu>~{;CFX}>L2)*p6>Y| zE9(^WvPC<--hAuGkzZgy)H&YZL0w^_dGYnM&sFWb0i z>SFJSinDArR86~4|0CR#_J^Dg!Gi&wO#f@VR<|Bzjx+vSKEy)^yjH>h?+y&`8eo9e zo)2;Lick@PSaAtis1Jmf()@E$?i4+^Q35;!BAkmjlzI zqbuu(-wXPi^hS`Z-qYNOA2iApF;VHV{(3sByqdjD}d5TtNDklvA}zxrO_?0*k&;Beydp_ecR4R6q2c8*J(d{Y|% z0Pg#JY7oFd`8QtSQm=4j9xoE<&K-3>Kk9$yDDimneFSX&CKW$F!P{N}nZC)Ln(eRd zJbw25yEyduJ@JjkJLUNeugijit_VXx2W!kJ#9J>?fzqfHpy?{Q0f@PlUs zx5|QpvxUTWg&_*nds?^qX|%iB;lItULJsj7-mu%r=M#6wmR`DW>2j1XDmzWMH>33I)oq8i2L_+Mai#3cuA|43Z>#ufp`ZCb z=>4?v8_^zCG=v~D+QY_Z4?Fy<7t)^J2zw_zypFZ&t2*F(^YeFs0EQy<1M$yq)23QY zX8xQ>BbA2@8`#}Zd!%vqqECU>n**P}1KbVzZ#~NBJlqREz^ApJ%0Ju+xph6T;9*VG zv%)XmK9+obRrlb>^B2z_eiQ}PJg9nB|K@H@K6u~w-06F7-~T?{+W*cDPT3t@_`m4k z{2RaF|E+&)ZqB~8jP&uu4){QLXaGR|nbvos;MrOMkH3ZH>kBztnZIEeZ*Wh@KPyB2 zxs#F07VGJJP<@cTf2s5`@Nk*=<+&G+gh!U{xxDw{VZ~*B>V66vHvO)(aMqG#)16e* z-KWo(zHsiGrCXeT_gL!YqU5k%dDUj?%^Q5&o!9-gVe?G-9;jYq39TQ@9|G|9s=Agw z+}^)vm~^yI>A*3vW9cc37%-;G;IWz``;8c)W^Qigtf{0vW|W2bpngt-55j%`K!EfC zbJ8+04!2yFRMs|Dmo-8FhN*{N%yoQ@`KjMRKO{XXDuKR-dpQ4WpqKHe00O{}hizyI z?#iF*doppWSln^>wCL^uV-D~_L?Ug?<=x67knjKwX@%F*`J8S^YNSMx`Fc|i!ypmw z8R$=|Bl=8R$bmpC^xdkh*=(I|&fYRTH;g-3vDtm~Sbpv5VHYi0_25^!Pq(%5wsA7t zFxA?@VfrjbU1ei+PfM2dm`+L>8eN7MDXJKC(e1Bd&{@~awDbSH{>`E56fdMFRDm9F zgYH8d|4k@=GW{&qPlbUb=?1wVk!d;DvU2T*S1|2(U#%7@=u{+{o* z3JZ!JRNbg4dYB*kM}B2x{Z06RHQfi}vGVran*T`;;b%Paf5m?WUZz#=!RDXV*VSgW zz2Q1M_N13_dWuGzw>_g zp6vkxIKn?!a*kT#_yhiDU|;&4dE-pA$E!jNsl^2S4H^DJ=I_AZqjvB$Tm9a^e^XU4 zRn~ID2cxag#o$_H!Ts_CX-)CnZ})2N+^Q+M-(yAri0HTp_g^6tHx62#&!65m*x33Pl_<76tC3DuePjXe%Ug|s*>5e+m9do2R^gb-dJK3Qh#2UA5va7qZx4?T2 zCJgx7kZD+d+4D#N&i}OwMyePXjfMW}REHrNiU7QQGo*Zn`ah)qOZrfx59ZeopSuR& zKNi|ub=628>i-`96M^F&3w|LfDiMHxdLI~l#PVbPTVN278cuq#^u8qDNDt$;1B{-H zl?~r)=0fM`b~E@hm&}?wdCP+JYrUr_DUD8fF6z{agd-t)x z{V}`(yuTsvo*A^>7@x;;!iV+7aDRG6MrS-fOXmZ|sP4-AoO7|i*8uO^7v+Db-^k)2 z>i-cQFatvu(0_FBoL&#j|1%JDx7HF&&VJ;OKKE;am-V|4Hk#s8XGe17}!3W2;6s$dCy+ld z)yFjPhn>8#->LrRo2Y%Q1MwZA)JinN6z1=X7|vgYf^SHMf}eo!KkKO;W~k5je{H1q zv&DeVp9j0(BHd|4by!b5$bF_0pTh(bM)}t|o%l`1j^_#oo5gdr8w0c*!GCH#Ght58 z^+HzLIP_TESmHl%BjtLYCR?SVB{S~(6+VmI(yH$%8@JQ@l#KxZ%6kR981NzSeOnr{ z+2c2Z@Cmznmx=C-9uR{E7%$uZWwP(x8t0+sTJ^o8e;U=FgMv_GwG+PoPpZfADkd%Z zY>)e$^j!%Loz3-=m}z{f^#7uYKcL)G_0>%oA|Kp^Is5R(Eb(T^KMF~{r-vinYnucKyHo_ z?%ftC!@J4)ozc7KIRu|}?MqJ#2#AW2;REo&zz%302QR?Fat*{?yqq`NJ36}0wUg@` z-tJB3pZRDH2RiR2=#U;9_5Vl9`v0@ZA-sJ&WBfOKYzrVk81A?IMY*<+@s_YC*Ru6FQuHuRt2=IiC)j0?dG0nbbXJgx|M z$nW=Oyzu|(_Y1)rT?pP-LGZ>If;YBwAFK&(qVuo~FU*{OnHQZb={(E&Qd88FSJl`^ z`2QRxA4WU`Kt%4q7YslCY4g`@+uzl_c~bo2<@c&vIj437CuU@p7PjI&AP0&2Eq)i_ zedzp;jueUR;f37CdS_uA&zyhad>n|d%L}!^0oUg5LH)frIA9R}UmXB<`rgSOe0LV( z2i_uN`2WupnR@wFSIv+D9Qc6$^(Os~Z-?aiA7}^c``Hg94uW^oFOC!Q|MCNA^ZP)% zV-Do4yV4kR$ZfWq++6B+IV0hTvB=8ucgzRBF*%g~X#Cu}bDF_RX>r zbq?9+?|=_R_2Bat*&H+oHq#h_2#|kCF~SD{IQ*P;`Ytj)7vceb1GNDYfPU68CA<&m zKl}|3yv79fcl=FjdYEeT9HepQ$AjyTKcM^$`Tr!k?=q0y!0&e;?3=iG2J1QhD8UXor zc%Y8h4r0mY9XJn81q0sgy%Ahy2Jgb?U%}ppzIV=O;`{LDctaeW!H0toDmxEwAhtf9 zXWm842nML8UAw8^{PP#sjp51gKLNPl1%hRo(8uBGcTl#_&>f&{s-dnq!WaS&ov0q- z*KGy92TV_qiQZ58MZ6G< z_m|;?2%`c*&c~%Df$uF$3`-7A+!~U&H{l=naQjaM2S5O#E;}eGJ~1Ia5eBh8=QF7H zfc|5s_D}Vm_WH1(19-RBv-0;BgaZ$>c72#E-kA+Mh@Ko>g`TjxGB#!F8jwBK=pKsZ z#>Rb=!4L09dbsI7;hQcIzUj&Jng@+d53gbU(=&o8{*mkdSN&c8pZMOip5MMMME^_L zBEArq96-l7-wJ%07!z9)K5VAhX8<7eLG-2%V2CS7XT2!#ZiSz z6}l%aw!hL0`b&C7I_p4!oQ--UI6&bf|hH;e*FR9)>=$~^i{|nZC1jzcmfPXv~ zG{QVX!FZ>T^p5B}>}APf!R|D>78_m*0nrx-gcjfh(Q_KpdG-n~0C4UpOB@9P$U7!% z#r(Sh5PJN9X8$%`KI^FYdP)4+p8X5Wnf#PN~#0@9h?E@Sm3A zxFbE(0cdx&>7i2n=a8j{mkhYTP{`l3_=7O~=2sRg9)OPt0CYTP{J_ni{vP35xFPPE zAv?w}en_?%91h_DMpOJN%m5+mMA!`C{|90E6(T-=$LlgZ6LZ@t;(Lw#lP{)z_f-kv zeYE>ODILqBacJw9^xsz#U%s-ZKtTRZDyOoth~G3Fke_M2I@~Ay_bY^dKWQngV+t>n9=o8&7-yZF*P_4$JRDl*9M-$rMPj{4c3K?*C0S z9qms6+Mlaz(~0lxwNA(-emD6+WoTfzHL=`n@!*DKxg3+Q{Ua>^7aPJ;3nRS9s6}%A zTh2RwgMAiK0qMUE+_GJOmj>sXg*k;;ysZ_bc znTo}@{YHFS%p<*j;`hz!%+7ka@`RX0^Jk;|JM}040%&)41Ra`7V~02LG422J(;2^0 zt#-cYuml=#uPd>-WBZgf2ITV^lvfB3jm?*D*z-uZP4qwmie zp9^`n&UtAflDc@BR(eeU45bDGas&hfPs@u_8+wa)!(7Xc#(oC?1lK>|ALd~?ZGIPR_y@}O9$$1Uu}pXR zmiq?|z$5@dED0Z0`JI*ZI!bGW=Qvq>FaXu z{`S>lE#D7>fJp$zMVh(s_=K0bb5MqtlKEMX{=>nYxq!QAdsbXv{GB7X%z1FhdCDNS zAQsZUN5gm-q>miAQm&6|hUJw2?xOmE{0#`-rX&>c$?v(}58y#wnV%v4N8yma^FPky zh$VXamb04lw+2p(HhW!Yxe41@uM<~D`#<+Q!+)gpH>dSxw_9(*N2&R-v*>%!%;9ot zL4aIxs^4rn|Hdy82-?1fE|dKvfP+K`eqMi{_44&cK$u7UZy3DE^1G5IA&bUlHCeff zO@rPSW(zaCZYy42lwEt0&V%la3?GL0GP3blq0H|?LhC>E5&2ns-`Snyja&Jzv>!O@ z8UM2x4N|H8R;vK=e`_00;X)@i5f~J`(;zmnV|pqdpV=*YO#P^(uSvHSH3udgzEtfjTS z(NN2Q;|-L?aGmsZj5Kr{M@%)JINofGx$Oj9cJEQ_Q3lrjfJY-fTrcB4n<-QLlR^Fe zo4L89=W!769q?dTcV+RgP^f`%bxQ;UQhaupLLHFspn7;tdKq*d7;qUsZ(9G)SJ2(f z8+^_=^}6$H)Vro;bUaD>!MELhn2|`gY*KIxL?OKlJ0A-u2Fqy9VF6K9|vd zEEx#5p9H`mPweohKDemL&0i~-# zQ|8YWuzlCRpG5J_?*@o}`x_|h8(Zjh)6y`~^kgAlLU`c2QPCgI{Wm>+P}p#HkC^(| zDN>=3@&C-cIF0fFVMZDBe?C_>-p{|$Sj^zvNgovL@QV}axiEwDe=Z{Yg`f3~PCszs zA>L{FOz|J&jk0%#M@5BDKKM!IhqSOsDqY$lLcG}gS%LBe>65+gb}I$$lWcq)T$eg~ zJJ?Tyy~%V`M7=#=2%rH5IN~GV-<2VU#Nn_A|L*5HalZR(YwyxAsE1Jgzs7DHCgiu4 z@|l6AW~%zK{D%5lTmGC#{SSZAdm7$V+enXNs;c0|qWL5K-xxsgV8-#Pnu0$hHO04o z)YsfCy;UQw$*a22SaiFxs+#ca*dE}An#ASil0Qn$_xq4TyqG1+|7G=$?;_H(o(uW+ z*!NQDxdxF)=>s#qeU9|);g7jnEKUGAct1V|;{QcQip2|xB@%UvW%VBx${VtTCWOu9 zN>LCYg9Dnc2>{QO|0*Hip?cRP`$s$I{QD|kXZbjsi=%>AEuHs^{rP?kQx<)+M11>6 z^;90|fh=Fk=z%=NdeePJ_X7fgzwQU>k0T&pk)F?Q<8L^oTNe7V=e=X_P*>aVP;~!O z{FhGg-;qqA&=dezF7V=1FPnn=0QOFkaJ_f+T_o`%J(CXD04V=$b5~7`m! z$PZR}s}223_`rc!|9P$&L*J8Lc>3$iI~Sg^v&i3v;ycT&-U_=0p&m+jzqD2@qscQorUJk#YY7yv>uZz1^2EonlEQ9a!=L&@rhjqA5-#gv+_N`;`M=d(Z;Gw5K z%0NJ6OwVoLddTVh+4RWYn(8Hnl@Oo=}*z5U(Gk=R0v#oB7 z&a`h4Zd=Xa(D^o+^8L(oM&BBOPe*CIt#$nTqT`LR?c>GK6#qt}o=f%2Be47206S2s z@6q|c57!}`_ig%Ek3kQKL%2wO2xa>Qd5|B#c)L~{GrakFP8Lid-^ecr*=?tBz&tv9)oxMM)7?!)jJL^ z5Q+MJ-TRX8!8?AJO0~|4-;y3?8on5>CGno4*m6E&_Hd@Y(}sVX3BL={hXvgAW+Cc9 zgom9qLjLUP(&W*>{bosr9@ajUsch>^`zwG>-vmSr+5?t{HAi9r0BY^7LPPiZ_AGins%#pn= zGx}G{btC?q4?nc7ug?7&zF(yI>!dWifIo2oYJDkjnP)+G;2Yq>(fz++I4gtBbJD+< zsrAcAihp&U{14v)`9VD5ak;}xO2&|11o{pt6rS+!z zZznT9$vMy=gqT0=j}yOT_M`g$Kno_GCqBcK3HiQk%`eP+W%1eU4+%iT`Zhc?`GFnzExi|w`!)j}h4KE6_;m~9E;PSqWp$57!cXgM zju(RPE{y!ZXspmWZsW)H>z$F&et&48;Y0PmhbNeNp5gC@yV8EoL_%btDi!2E+OwXX z1O6!i{F5aaxG_*sO7Y?Fz2pbCwQe^@uJ;^|dI0(Ra>?J9MfrdJbQZyX9Yweg`VLv` zzZ>@hjpOh=wB`Q~yYl(bJzr<>@dCE{QNJU&tDm0%f9ZQR) z7Jnsv^RG)7ely{leFw<#&E;=WHqvt+0AvyFv&G1FRV1|kRl$#9yV%eFl*D2w^>jN= z@sH3`9{-RY_8%u<{|7&~fK15!wfZCPJj-H{A9C8!nHse!gmwjHDF6rG;{dabhNQBSf=no+N zsoqO^V6-1d|CY|jw)#%~I1cAnV!^Ba@8Z;#l#F0=P!N7Kn&zM6DxZHo_<*qr1G5)# zA^(E!h2~H9nbr#i?@jR^jYHuF5EAetWxWXx0O6*XxJ!N#$`5pKJtS)1=+Nem7v!G- zLBHd@DgPh0g313^9p3cXVOl>=Q|P}D{@onaZ^FMC$N%b-`y1%ucXD&)5kG|LW4`Jl zo{aAf`@8iVu~cdlX%(;s1uxJ&e#ddpqoS9HhjyRyYZSz2|I-h)Az@Sp17^Ikser|z z{EtKD*-ps&KF57%N%!Hi<;;C(N&KgpFYMe9_l_aMfPV%-eo|jEt{)QOa?7FJ{a9;x zOn-aZHbMU(s<6K@^&DlU-w6N!$eVOFgW-_}439dZ-)#+rkhuT=%mx6UI{*Mi002PV zq!RKbFClMI33-!3$eYwa-Xs9>Cbf_^LA|zSYa|Sh0Ed;udl@+tFMfJhUs8NM6aX+8 zpA7-6q?OOCDLTFna@tXG)lfJIKXl;V@F4%K-V5ZRp$7{@WVc!H2ehL8KkwWd#IuNZ ziU0W+i0@f5?Nx1l(H+)Fb2xiqf3|5qg8_!zUK3clQn*h_4lb+Jt29>&6yz-+za!#*Y&phajrz7+K=I%pog1T`#Hla1f$6Ib@Vqj@D#kkD z#gbe32A!&sDm7NtCV>XuCXUbo(_W`>?v{_#kH<2f3W z|A*V+ms0_NlTiJG?z@8Dnfp#^7guI?8QBiygL%!&eLrush}SlDS4|9!e9B^xo`1aY zi&E~u?rcGuzxRYW zk^fM??;-Lz2jqWx7)uH7gRvUMQT$Vs zcc5XfkOcz7=bN6#IcRvjM8j)qM>_A`WB!!?F85=x2rnQXGx2;B%g>MWzm8)5bpQ3j zn9l!14)a{sT+WBlvVc4m{NWGceG&ihg_w!|y^;RXc~{fSj5l!@zE3Cr_Iw`?7{o#i ze#e3@YBgbM&O(vs>Wj5qmyVbC(RyoRzJ&j0^iTKvN%`=y~s55ao&hyJfC=nh^;e>WrjoyBvvakT^h0O{>m7WutX|J!Xihf91|8hiEz{*^=d zua7p5C%}Hcjqg7T>8vfbH|4j>pl?QcF!o{|Z{d`)qv(5}_;=MNh7V|h-#-H#rV7`6 zm*3>|ytevIn;zKZHa#$G_wj9dU=%OR03k3}xiZY*MUx2jwtB^(0*2qxO#GH6FQk8I zfWLP`I8^#>*!e%-KHI%~UQ7w`LkBK}{gcI!48p(k>f5r4;=L7xoLs_xz8ycO_Z+2x z2dN%-`8`)i`QQYEd)txz((@X~qYz)NCYRAaqx|REN(A`e`)z1Z$`qdh;nx-hz*O%} zJtvT&{c&@XFB2dB2E#An|L_=nQ1LR#|AkEjMU{1Te^iy!G~Q$(9Xc<=yZ40S2jP3C z$m&DccK{Fk_TXksNo7&}gSxHXTfY}s|C9Qe(z7wgBEung)g-G2h(AhhpOZ*wy^nto z!46L>*^YjA=O5S)I1r^hcqgi+^@!^tx8)aoDS*1tuKlvNCt?wbwn~4t@KA*w+t5))OlV+DlsXuaO z;A$0476@W^J{r%dUy_M%<|Nu5g#YL+6bkDg?@xNiXZIn&L%64m>6_Xk0fzq*@t-E+ z1rZ*o+$c9UELxF0@z+U7vmkGJlncWbgfmyb_oaRp-3J;l0HpQq1iCBrhl0GN(&C4X ze#HMJ{kuFegu(ns|IP*NEYi2@)ENyQ(0y-#-${OVwoxor2eiYe-$QzMu(sopW`#6 z`{4gYxJx8u_pJB|tMGc@htrQG_rqyB4;Oh+z3|V~RR5Ie|Cya(^#3$K@J%fAc5>(~ zU;o#sN-O4KKM?e8a%FA zut)oo@jFz6-yi8CHhf~@1KKa04l?*Q%6DY^?zKP!Zbty{7=M_61W4i8a26W{fFkQe z4!5ITU~bugZ9c?@oZ2GSzisokD|s*A3F``9`8YHFUaE}(-Xxz}QsReF{i}D5y#57) zm?{v214?^kWKsXa3jW{N0M6I|Sr9;C5g!;Epk$Y?ygx+#=k&fd>U zQ;6>87C&C2`CTL;ean#6dWZ_#A%?k^AFamoluW?=&<_}c`rrr4BN_C*e$f7R8r+I+ zLwTpfg7|*>1~YnBwyDrRFbfyb{CD3i_jr4Gc{m`QkqN zJHA^3g7AtEgoizzC))p}Iv|8~oy#@qs%zN6bc~*9kFG|Y#g#>sNf7vlo$Vihv)027 z_zvuV8({~W^s4?=Sv>?`wKOP+t1`|BPC@5td=q261} zVzIgFW;s}InL6EdJQqV2o56eFzFUF(hw9*@LnhIAPhwBW$F zeR10p0GC(C`5?venvc%?h3eho{hgh?=6VPL|FmFF(vrnDGW=tTKAX+@edbaJ5JG2~ zGkV}-Iax!wT=nr3?>M#MAHLob2+;2Q;ei0CCfHOE~XV-)D3>0w#DnbyUZA^Nn8z2xd82JFz1L;0kQ5oQF*B?bK_rxa{6!9?IQkwXxx;9?<3oFmyKKm&3;QK=qQu=3oEvzj*=yfcJPI zRNskcS^k$F7WKpCHF0vu4+r*#<*+}L;QbhHd&=-14z2MHj(e4+ z%w9e??kC~6_iMwyY2kvLh70ll3dT>6&`~@$#MJJ$J#_w^Gh)s^@;5|*BY+#roR6j_ znf~yg^bC{(7`?#Cjqf0D&>ivyVjOSzINpr&1kiUtfkQ3GOCl}4Yvl)lbcFwU#qkXi z@;icEoCtJzI~^m$Lnw&I^v>Nr{>5j;=dGSh{B92u4$412Zrew(*Z{<20Pqq+_d(Ld zzwyNrywJ}B;9(-2@9NqAuk(H4{m(qe{ap-Es%QKUAb>ZG#wGLo1PBO4?fjq1TK%8N z52yK7Z7lhpTzl~FVdafawbi#93vNcIwfet2s;rA`zV!h7h`7K2;Jr%ot4pejD{kL? zcrG|CBxrk7X>4jlWO!=y-JkgpdHH){uiwtUmY7z2>tnbN)XWP0Apf6M zzn62y*zfeYxA!{-oujdG&mZ*_DUzasqUV*R<&DKRe>9fC|D?6vaVjxWsjwS^qN5wMwR#&1@ z<@jc~ANsCVKlDR!dm3^AW9lxHhij*fP>T7&El`tJ~Y@^aN#>h5CVZ#7e{2d-+O zXwt#(pY_1UV1W&=z_wHD4GpK+*-zH(KGDiXcajqp*dGf_=i^2+K>Lj8+D)T#7dDYg_mG^U8*MjkPyx?o^~S<>gh@SKrT% zPpT<lgV z?5Js^YhtFpeJJ(+|ETu!BmN`dTdiKve!utta872vUu)_calz60;&HMOeGg{|ANu|- z1hJxwKdQkw1*!0=uQfy7%7NPKWDEt;8s?3;T0VEb;lo@Z&rt%^JtAYu8Fu>7f5 z{!lD`8kRo@%fA=Pe-g`|QhL2GHl?_ryd*WLOhwIIK?(7!p`CJH{2Uve@9>W(bW;-(eU^;#Db>>9|a8KQCZWf*KwGT96`~s~#r@Bp%`33&0XZaL{NR3s%H@~mCQ(qAj7F1Ck z5E&F$^KeIaWPMmDzK`_tBh+ZW9Hjm7Jvk*LaBosl)J|dQj^yyTNXUUV+RExX06-QcPcqJ+k2LuxjxBR%UmG<{onV*+zz5k}~C!g=M*7+{P0kO$yrr{bq%S$t%(W44oBa}v!|{hCfoxP*2jbm4DGGmFku%MB#wYVqLPZP zeK&+holW&-K>oM_^2gsHe_RFmgy}rEctQp9&B<&oYpJfh`M9Pq zzw-KRd-OlA3j6p_yx;t}774Eea1P+VGgCvhMjTGv0YBVh@rfx%QqocZ2;Y&KCX4s* z^QO=EAn@}yqz|!Wj-8i__e^(ZEcjZlY13S1x-RAo-w!%lP0t!&bcr_@lY7 zH8cty!||>b3D4L1;`;XY`~MgJi`!)hg|XLT3k#$3ZzdKe;`46#_`wJ9u~7pD`$ zB9l`B;`U6U^8k#v9|_awe4F4qV+POL%f}1#{5C&u86FyXD96%he=vS;bLMzC+byxT zah>E2HGF&z_#j&Oojd&AZW;jlFOC27KAb+wR(+hNminWT+RvZz8>7p1etr`sdj9cg zQ$)mnlMjXH2yhu|Nd8drTRF{R?__T3E^_9kpijg35>7#12G(mp<{NZ*O;;xOc&Qz8Fc zj_ab#MA=Mlo%L)Rz`aympl4#`>SNY@iM5-D>#WsYwkxL3a-XZ!LDkKrqq0_K@7bE2 za9yb4y091<6i)G=<=igtJ0(5`{t5iI6yCu7&)?iZR~rG3j)swr8tGsB#fNTvzCr%H zi2S`t&!v^mhT~mbk?#9rbl-o!(@<2?P|#FYRrRp4x)y{8Y?oJ$>3NQP{s}+LKNI|h znAqc~nEtLdf5_@nsR@{VvfK}&yc*N5!1PyR`qr4f8>a7w>FZ(ovoU>dOn)Y(uYl=y z!1TQ^eGN=s77uZD(f2=k8t{F;=ym{~nfU$wg2L-Ge_uZMzOwU9UX99bm!ZxT6w)EC}DfTj(@24R6sE2I|Os8Aea*r7SRx! z2TP#1=xTSh;5d1|o$g3?jF9daAl;c{Z)h^b&VH(C_lcA3bSGJ3e{9+~s0j5j-a-Z37vnp2 z?KaRzOWi21=wr>pnrn>=-|4E*O(R2A-(dWoCr-?`y*r}tW8L?vGLTm)9^8K@eN_AO zea(yN{It5z9TD&EW!~Hqa=0)m^=5MH_M0g=-C^)!4Ffn27{J-X0B$-6@C#r7=celBG-JjD4lBQ% z9xCyl%ts!Mp!y#gzz%2t({e-ph7Y>8p8ySD2Hy&N=dc2*2l5rLL0J0A`q&rYkp?lq#nai<|K0Nl7)ukO#P1 zQ;`<^yz<8N#;WSz$UXSqf8uu;o0k_?zPJ2VIr&8w+$?SWEY1i2c^(>qEhFuAVn4IZ z(axTU>yPex=sS`_5ynPldKO)k5&ncC zoX7ft|1_(f&V%&^;6F{!g+HLmSbcp{h7V8u^1tu`dP7w6V1S!|gdgdA?VeXP_16&( zT>SC}xw&Qb=V!Gy| z4H7B7C3)w0i@e+?5g(4Z|KSHB&kr1a$`5{X_nPvXzJjWm@@V}Iu>WkQ_id?YAol~e z^_=WE$<4uI_BeGt4OK;bCqrE&df%uisNzallZPS?mW8QX1aJg+Rjjd-x~yqVTZiZ3klBT>1KNK zVGx7xP*q*0vyK8TjPAHFoN-|&;lfaZ;p=P|z8b>tbrcL=d%*D39tn;K5}c_>a3bMCl4a6yTo)MV)^7-iscU!v3rt=}#Tfp9d+=>ud5vH3jw3 zN?HC}d9Ad)Kk^>jck=uf2~YzPpnuPQuNNjD0V+iTq<{pd0}>$Qzs6f>e{&}y|DEsc zJx$={0LK$-57zlmJV(P9cCfPihixN2ALmX`>EhX2*GPkFq;1O4@1ceOSYJhJ_Uu-@ zkJkKX_Uxgioiz2k=s4NVwep=mYT++XP-&lED(ijp|=Sb^M`R_v;%6Db{ zKL=Ceb_r8=r853M4O33P%C`hUeZ((CuBr=PTcW-3{=*0)f>! z9`6GHkfd+vgE8RH*HP}%mWn>Wkof9SPrd(v#8i-R8p7e_LjW#jmRs zKM?*c8H6a5Kd(2;Wca~7@cq+x_hpHM^gjvD_;?wctqwp-H2^R)_S?&56TX=M!Hhma zE!Lmv{e+KXQ~JC4ou`TzxgOQ#3a-z=yE01oypGXIwU{C?=& z9uUGtfiObvD?^>>ms(jf_{X0148E%wfbfY_{}tIwqyWHaceZBmFhBaeH9tW8ujy8h z%RIH5{XGWL|3mZ7)ouOmmT;#3`EeBEhjt~N8Q($wzpWoi_@6wCssH`?o4o&>2KXb7 z@H|xC--fPj`+7L_2V{IO{6L16#}Cl{NSiin0pou|c;0oW_q6r*+zhfH2dm*W0C*_k z|6sy)7y9N32N>Bb%3ky7*pr^%{}UcAi~L_sjFkJoq`&a(a%_T_PyO!`Pto8l8+?xV zUSn|I>27rlBYuq48%8f%udlrSP4hARiuttm!wC=PGndO9fcHcASHc6N0`L0?^iO&} zgohnRcxXQOerSI_p!qH=W9t7G*YWviZ{8vOWx3=zqZ8 zcf906Ru<%5uZ3)U7ft=`&!>TZG7b9kDf&Nz2hCk0=ey4)fb@v`rD%_Bw7IS)l==A# zm?V~v-t*(Ja=l-|JAWqsKkbQPaa(_E+YF{ZK46jr|G|gt`eWsMrbE)m4up5S9RN50 z@IZw3=@Z7_ecs}GAE?I`(EcCw1@Zt{6F8j9phFP;qvL%(pYZNWvHz+6|62hBZ?PZN z68~!x2(c+TJO&T!TN$LU8ehcl0UD;v|2WW(>2Dj^Hqt+hUb|#LEWAzmgvGy2Unz*NE;4{ZGQbdqEAJ^qohd-%ImT9V^E> z>nu5La4~MGR^Thhr7fNoJ@E7H_wzVgJF27{Y9hRAC!B8s6#iWOE>3I7TNa128+@4478_LyMUHAN4zl&-A+*!*5uWT?NA17Mp6Md&GAjz9a4T95cwHT%>-H-Nb&L z5h_9&(KGpy^Zi+M>;Zi-*e+|yp!K!kY-jE z@u5cn4&8>2d8PmY@UV{e3_)+12ZB=`+I_@#ju<89JNHBTZ{=r^i1^N=4=mmQ!F%nF zt8|Q}iN&A;M<)OPMe`lCi_w3l@A>6u7K`}b^gi^z$lnL?9rqzX0Q_M#@ujH&SRmcX z>l)=R}FP~Xo{}N_DwDrRWqdrdSU3rapeg)6luYcyn7~F3{;qjI`gl}rP zU0GXxyRxEI+}LpMVU0}hG$%J#8v!A%|62(E_TuL!K2)?kz!jd7j>|kEGAefO59u9m zif`>?^{sCd--7N$^)ImmhzKbdvLXrPgBah#7%yONo1fQ}9~>?Sf4htk|D)kqd*~xa z*fu7-uO;VtYy88SfdCQdVYd|?j$XR3{YC&}*2&b!jP$I!^iVW2?Kay3;Z8?{JJxn+ zklCR@hH$4l!W|EHt4NcIA@O1hK(+E zGZX|9=UXXm{#ASJw2j&`=4oy~{U3J7Hn2mU>ZQYT@1k#_0DT8}d}lS+*3(uV-|1t* zN|;0p3i==I`k$4p{m*K5m5Y8%V;ysgiBP@-H z{e#9^5CF`BVc`A7>c8@#atHv{70L72f6j*z%8FADqHO7Ij}XUr*a{+Cm4HrR)9er~BA0SI?BT z_5;RW%|$%N_aC7PInPe$|JjrNMH?R0pYmZ1K99e0roFm}xsGGUE?tzzcN(H%)AN^M zTAF>Pm>XC}K;Q!Y4_VnVKa-z%}fj{~5K`u!>2<^6u*_rC(* zxy9@0Gl~aCLhpw7?{7PCxip^d&WxL2080E9`>TBZN)W(E-<ETlR zYn$%p2f2%J=zsdd>jX|^B^pY*QJkE2l%_YnU8Q`^zZuW>g$<;06_kxiXZ^Y1_7WG z8YDU(0H9qy7wz&s5~YLGG~@a*uU#zqhRkumg*Y zj86OhFa5Cw$UPQ9?y(T~fK4X7?D+;h_BQ`o3$N{yA*|9(0z|GEC>soBw6 zeU7S%67}Z? zO<&OmcBoxphpJ0{Z>{}68#f2?%bno^`LiPE8-+st>^9`jqJQoO!Va|(cBp}{Lyd$T zYB1f07kQiUJ>;gpslQbt+s~x$^i^_cq*S^Pga^_$qwk&k{aTuiVk~Z^dfx*GB0hus zdDh{S?6^IFCGq9EFBS=`J?(8>l-1^_s# zS76MgdLhOh7#CcBx5xVmmUqg*FsV)u zF~-awgOTO`I_JJDpU?mI`2K(2$K&7Q;kcjgbKTdyU+cNfDc-jzegA?TD_8Abk>fqc zecWs_GrKN{R=6Jaf5r2k1F`;zp08bBJnz19%@a@|w59g2b!D9?UKDucpg|#_WdDi) zSk`3k`_sUX(7?{OM}<*)|7r%z{db+%*c{`bWFI-8{f$Y%WdEFzrH>B`{TUqq`1i{> zME<-|e#mEk@b9iZxcT@ld>r3Ab)#(A>q~DQynlD=N$i5d`PGu}0-S{XGpb9jA4*Ge zaQ%sg3jL^mL;bzm7j=$UQO}e3`WBrjK0NbvooW5It0mUARfG9wUATMhbaEt(|Jo10 zbnhsyLHxag?b?&STPhzcP(F;A;bzv`aO50UZ?nj;13jnLHZZbpQ{SMqcV(XzmZo0i zoVEG}bqxDEc>7fFh={xzgztm=L+SDE=SMCjfAOI+W{2W-xL(2i>NDk+OoYEO>3isq z-v1~6AGytQ@@T#9(W6-Z3ja`KAH3^;ZvWOkQ@4k^@GdX^Lfp6P&!;}(9Ym~X zPD^x%$}iaQy6kCD!^7n7hT@~l#`gdRa7F0wRQk6;>FZUCBmDoR`{8ixx=3ya_n&_) zyV$w2?%$E@TWLLHj*YYP>WNNX3n}~&`iFDs>%9Es7oO4LRYJ2vp|3ao(K@wVmE#L4 zj)nWvpAimtj^*Ts&JNaI7w=~ug1xJIbKTza0M>`l``^F+r}aRwyA)V$p6?Z=Y2y-i>hV zaE1AwA;Q6Qd5HzUo!Z;k;q_0Y{tvAWr}ob$e|dG+?P_^;&J6wi$AHGdm0poB?_KML z05@0;bb8G!!~;eH=r-B=y`LN&N%rsld%xJfUs_;=<^Q_BpL;j+^1s=CVJrFjSxevl zk>%*_y@;#%$v&9uf2n`iV^L1djRoJeC41ri@c!!y``&-g>+4yspU~}pmp0bzeMhw} zEv^4PKJ82Vv%U>~515VS@6OTs z`jG4SdjFT=A$K3I;vr+bN3F@XGzs)G`D8Ae8*RjA0;lUo<`(|D#zYt4d}SoGfZo>! z0OjG_Ikv;d{%c1bDUUvT0;uBZyzr1tn#r-qa3vVx2@FU9m1DTlq=Jwx` z+yCAE3x~fCwEzCey-M%%Llp1{X-5})cYf9OJqvfNOb(KKBLTL1PnGEXCU0$8&$4iW#8MXT)7S&l`VZMqWv>lFYhqF@!3Aas+opx zuvLb2t!@JIplZ$Qn3lJIKfuka^!W~$yKp?)J1Z7%T)b}ax@DN)I)w?YA2C622@?dj zFhTG)CI}EN1J+(x*Rn+TT@mw*o4IdtewqeF-G|NHdDR3ARd*t8P!-zyUmqmt7W zFG@;YKYx!ZA4<_)Vt@H%DSKk$m%-j`Zdk1`J-mX3_bj`4G8cW)NgQA z1o-Xl0)MDx2DQppD_^Zr)w&TL!+UzT_cI$D1mp2mFmZ}*O0x`yfg+lhbNJ#(XA<8RlBZeNd6_Kx>%UEI9(!KLFR@DH;95p(~y z_KpQ7l8>D~_v#hLga7b;<40=GbsF1m$na?c=Jp*r-^F{FU(S7c{%HSn&3{bgBN;%A znHc1W@E_zKoZ3GQoo3de@4pfJAGMM_>>aBZR4m`p$g^?X`flYrcWYb4+QY*Lmp4Bo zwJ)`YXU|zZn7;ooXX`38+FI2vN8bm{7jt{=%^GUYr6K(5i)SBSKaYuxzW;Q=lY6Jm z#$J9_7FYZzE>8D!ny;!E}iyI{+a+#|=27w6`mS#~fxDL!TG)(!g#wx23~ zQ`Q>}a6Vc##ru$Z745-)%POJ7BY%-FmjrKEOzTDM?CRor?%{gt>g^r0|KR?vq^Kwp zoQe0pZF}4ez8}ucmAb)QqQ~PXsxNDxzDx`6Y(f5yY5jk_y>NGh@y|S5uVwh`Jv6>$ zvM2JuGn}@X94m*4){mkALOuLVNG^hby1fd-R&5`}a#9nV08u4#R&a@8^xp z!2+S-ORnBR_zSl-eJ_sD*MI8ch3zVtH`2@V-x_}W*gMDYd-4ZI<$VxVf6{u_-(FyR zIP*Y?i5O**j(BiXUlg>}$gwynQJ#=!A}7e{KPF>x0vEVqx*LX;l6)FxbI5{_k9cD zzW;`KDBX_YdP;&l6kY`M!OhyL3I)gCxS=&#TwE zz2gVOd*J?(>FZUh|GVEG0d9Nr=s6;2@Wdcr?;$>)?OVUZzxf~ip?)3z2JVjQeC-dF z-v8+|{u4So0M(-`*Q~Q;l~(QzO^jXJHE7VXm91ADhmVW@J^kXJHh zVjtctx)S$k{Nw2UduShGx|`;|pPadM_Qe(01AS#LgzG(gmEPz3H;PyMl#sD;*ZM{0 zk1o#Jv~BC!<;!<&-@k43xl`;9tBl6GHsW@azJ4E*v~pYare$jrHo&|A@y)aj{JlHt z^SdP_9fOoT_;lPK*aJtRdYXRmJ&0vl+#Gt$xzJG@f9e<pn6T6I$Iics&3`l($T7qMavdu4wl~DpV~X~=hbZ(g6BWa z5bNDLTU%RI`Y(Ghn~7AO9ijH^tGlPKU-*gJ!}P#ayysNJdtOks^5w^sXaBu&Iqsh) zC!ZJpQhfK(xidFQe!744M)Bq?8#fl_9!y!XEcNIQ@r7Uf;jXJs{lA~1us#3=aVudE zm$^PMW$}jPs2^XwesBoy173)}zf*bc=!WI~VSX_19tH!?p8dRVeVspF@4!FldRs;e z>GjPJR}X)e9%FjAqq;mg0TlqMvl+8q)IutTz*!b!_~a}%YWx* zIy^-GAcy>&lfB>E1DTntpdW(ifUWd?;C22PI9jb3Y0~qT&+u(g~ zKC4A!?fP(tmt(rSI^2VhKiD1huwWL`KO<1V-NJBZhA$TMVfgO@nC7={-B;_o(cI7Z zqhrUA4>wNI`rlW7e_8)a>6`C`LlZ22qWQm%TO%XW5f7I9Tcv-m{99o?G|hL9+#VkO z&I}Vm7%uI;OJ6U17t5t6{%;Rl&!_qBF4HnIseN1hQ~P#jW~P1N1VQ6NOGm*$0Oo^c zCFtp?z3kBT%X|-A&$n$#ORX^6OY5B@Q6Wt5uhjTaAQniEY`W_@mOq`@j|MZ1fBxB| zjKU?vQ-8+$iSExx{^6WY{(U#0KSblbeS@5xF+CE4<)5dwKI+ni#z#ZEbbslWdM@=O ze@IakzDbK~88Y#AtoilzWe>iaO^gXS2^C|dtitpb!=``Q}W^5iU_cu9&{^F!>=RP6-Z-?L? zuMHfU(e*t3y{v5V@ayn~+CA!5H2B}bAqvKSP~MlO!#r{!%tH^xty;AyadYy2!~?53 z*1x-3P{3g0O4Vriv(o=I9#}8$88bpNtC2syTHm3-4+*ocZU6(v@|M+1t?KWj_Wys_ zLsnPzkR9q&8VK|6O0e&GaH;6TjjJVh*KXG5KmMbAhj3sEH>`bGc>VmjleZRJy!GGy zV*YzQi{8FNeSjOFUw?(tHOfA8|N3PYvHmA}tri+y8x8vp$d`$lwk zx}&>O1KpiL=h9v>LcZ?M_G3KfQ!2bz^LM|Y<>x;xyT*}jPeKqJW>zA>8jmT2DB z|4#q@XU}PzPVK+BwsoyVl`rKPDUvfb6qLHau&UPmwoWfI(+c?+fo-4#y-1u z=iY<&4{km9eZ!ku9}140Tzu@og}7L<|JT2N4ePnPXCa^?x<7H-mo1AubSyh+&F=FF zr_$mVZ;FaKyzoRu@}AQxqR;Nm*|2O^(ShvPLrKdLir;+n%0T-sqB7dELnosGbaKA& z2bTN$b%eulM0u&VkB9fbh#9VLC<-`y-{H>m{P9QQ zJ!Ic#489KThxL%dp$A5`fd%AayKa_b4?+IlN5Y}ecNhS3Iya^Qy8rv=dpJ8PCx;lz zyS)y;0fzImK{c^>selHLCCj+@F{yZ2P#(5DpGw$lp=S-@3n}8Qt`Fu=B2V z>@v>6YVwe~&i|br;XYoiF4`AbcA~oHiS`)pn0d5~GYp{4(0a&9TfVG^B!9;=KA2P* z5()=6O=!I2gCJ&(EazCU4;n7X?5J`2&7?Kws}k`WwS^e{f_E zQ#_dCqeOnP_nBI&?Bk!;UuTw<(s*a?&9bt0WZxdW=f@X%`@anJ-5nYaE!qzU0Td7Y zXoJ0%Kh5`3dDkB09o)NyICS9lQ0Go_{CWrV<9Hx;82<0#ysf>n-HvW{9rtubeb}W7 zhD)78oY23CY*-W1e^}2qKN3jwpDT(tAM41-yvlg~p=&YTNB5)oSNG$S|4SHn!M$t$ zC2ep$A=S)UhwaFOd)E~f6yLbU?fgtE2>ylqV;-XMp2@c}Gpj}I?DPcvC9?k*Wp!%n zQZ@cb_8)`Lzwe$Wwzi(PwBlQ;|I8CgOPz^gEeS{1CK-d4v zfQ}tEnl?Tdh4-8M9alj_^iTe-;SOwcA>MZyAH(w33LD@L?rZ-DE(1CT zxux^|&Rf$ZtOm?s&>uPpdxz{EYhA`C>*M1z{!9EC;~!)nau=_E)^>xzlMm?rZ0Y)H z;QC~l3oiI1E*R!+lP~D;j;Eo%r+9xf9@O!wK3+un8o0jh2>(O=9^X>^rs0QYopTB}oa1 zg=cMyZM6Sse9IpNejpY2zJmtg_2|}a$`vaAKHE!Ec^5i+cC*y*@DaE^o7ycqyV2>y_U%{kK5gn@I=D*pfsU3{t6H%= ztl?nWD%I+gt76^P4$I}Lx?;JEA(qS3z;c<->wj0$eCPko`d>_lVY$o#ESFh=J$&O80JE+ zULUT4@$a(t7~L+rMg5%z81Ke%mdbtz@J90;RK9iV`fFR<9=rB4hxQxd<<9jX$d2RT z6%`dZdra}r{oU29U87FTO0ajSFwDNXO%G;q$LfpZoOod3)D@a#i*E~x*H9?7yQMfvv@<9$ch#3f;>7YiVd+tBz9uJ8TX zYBb*Y4htZ+<*rz|e0}0FQ!s89?w7+e>K{4S`1K5Mr~XbS?(a~%?=SbmJ|b{f&wfFJ z-6DcqDgIkG=MRVo@kQS;0LvRXV|`0Jk7pg2N%jAXS?L)64-XIPR=c(YPY*p55gH!F z_5b|o-PcinDAK{QVx4wYCgok>@GJ*^SkA-inS6XQPpS2-Z!TQMa3|SA6|H-A_v+n~ z*KQ}4-YPh8IX3mh&2!i8p1cc(DgR&o{r<=GaT`lY`WgEfOJj4xNGq}QW!XocH6X~cf>Q@-|Ow# zr7&-FSw-z3OkD2HR$L>-Y0KIgA4_2XSIw;MYq-CiZ=G8HW9HeJ*UHVn_#Y~~3XQHg z+db$S*p%$Qj`i%4@AzU=_+O1bOrrV$6GG0I5V{$G@xdqc-;RTU$n9g-oI^$pTN-gI zu8Mg!{9VJ~)umckNa*HPk8&c(-}4(-gOfkt?RXz(K6uWWkdOi^^Bx~X?5F8){zkB_<%3`l2r2n<19{XnsE2p}HTS_R$r;y!=}6-*)xF`%(T@jKP~9^76_5(JS(Q6=|*ezk2#-DFWQx z3m^1T6SI#L-=+Y=z3d;9>_eaa`SByd<3%Do-gM$Ws($>)?L8jvC3_y{kL@kYU3&~0 z73?~{!Z*Ra-OEiGH)m`mlQGl!Pntb9XwI}ry$n2N1ofFPFVwWxsL8%_O?&imZeb)k znb_HMb*$Reqg`WrGv`3~zwGGgS>CLibvrYyZsl6F-5pz)n|HLe8|v-Rut&{?9ZZ_E zXx+xKrbiXC1$RFDuhzT%-}paz^-pO{D*xW10h}65<2_U!zAaPz!3wWKUMYK#!wJKO z`}_4k|JSoxL{Q%$z5V@cLWc~%4+DpIxel*rIkDex5C53~pVog?t&8xtfe3#)5W}NE zy?v0}wK?GO#|jTl)^<1H*&ddPH0z`;blNQQU{bqd=cK zClJG3^XF6h#;&mqVq;6Ms=gK-dO2&g%#ADe=!FRpOm`q2s{MM52ZuT(VZbbWJxaSW zS6qK~J{EMKyL;~9Pk+3PDt&(Z+4H}bmEQg9>9uEt@9z}E#lie5-;dfy7!WMJ_4d*G zrN1wDQT()YZS;wv)vH#2|HJBC(WiHu+_gCC`;A!#3R8dBnY(rWlDzZh5b#tze_HQ# z>1}E0ZN!H^dhWofdvP1i{NNSt?cSsJ%<**%X8X*aHeld%!|++tM~(E2oHS7^Tu!}SLyLOw-`MYK~bwv2bWmkJ*{BPxwzt(I`2@QwCBNTU$nBL#N?D=AS zeS`msNVxxfwYq$4XgCZ6oud9KsZH%=7yx>XND7Z!^Govq6|W@DF3aKZ+_jDLUehq6ieLzx?4Gb?ClzuA5I zc=a3R-D`*o0v=-e&mGf$H8B0x8`FQoF#U&kXw}MN`p+BFf88m){oZdh3P-UyPkfO{6Ec|?Nq0HxAKUOTiMv4isAaDU(bKd zU5xteQ1;VXr$3xH@#C$lg~u)-+(lmgk^DHkAVKbEAADNx>w^ZuH8c>;-@Ufx)w9HZ z*KbZvTZjqHn|<4#C~~!5?T+ z3BB)ZAB1=q2S!l)z%kt4$G}=t$8Zj{cd)+gkF`|4Pw!Ss;&#ydAJ_M@X}rfD^`l>) ztJc)fs#5EAhF!hPJev%y?r`T_;lo=$#=d{|^4QPUKK!}jm$hqeTzdHa-qW-3B@2$` zw;am-fg$c#9mVBAv!j`rdAJ{!&>kqn_wZV^=cv4+_#Xb?(|F<0ye&hAAN_rOz7O`# zhBLtQgFp29boIw`Xy20kYnR$xx-^4BL+TH&hlgM4|C*ZX_2DG6e~6RL>hS^mi#l?= z#6sWk-rtPz*=a(e!kB0hS+R)1( z7%vI=YQ6u*Zt#E5bMUxe-x=bYnO@z?jhHlRYRsFNk|K6*ww_jay!Qwom|^BcQkem@bfk9?<%eB+ji)KS#y`>7RGRg+oes1 zwvAd<^(=4S*}>YeqI1&*KD8X%I&^No0tNuLt{p%27y<2XC6-=8c*lafH!pm)?_LqT zF?L<-m-Vjyy2W!HZ$(hxpTLZSOJ6je}34IX;UJCJ?HiuFh6v%K@aztVbd%5 z7#qX>vDXV#wL(y5zU8<#$|w!O@IcPnq(wrTaAuJsL@)oEScwzsj5 zm8DhpIt{#Oee~S$>Ier&<-y~*^u5gMW@c8WY6WWlXXms+^A6AdDA{{#g85U*>ey3v z@14E<`|=AnFFg7w=0nuOf1jT&`|H;Y_x^bFOYFVEmka)hTX^MB>G{-!o30-^_0MZT z@3SGj-mgnv;Rf`5Sypxx-@_*S^3UskQ2*0-ct_9_+>ex3Wo5t6=YjE0Tn~?THbwu= z5A)k;nD2Jx{vSqzz_i}y(T`vJA(MR={AiE9jjw^$|17sp@=A!-=d=HvrrSSW8n639 zemP&ae_U$2$vqXVeOeDQd4fLvGiioC{^N-8A6oz9l#!oL@!xvC?6>rYiyq&uR(yI_ z6SNPaHW=B_d^hQbp9y}l9(r0YbZI}ros4qXO#Uq4xAyyg(7kK(Yv)}w{~huY;hhbbOJFn#|A(I2GoPOGW9 zKTtG)O^Fvxjl^(XdZ+Oy-_hVTu}6Q0*87n?pcCu?DgGPT18(~+C$DYoRd!S!SfIZ| z@ednK&vPdK$9}M9#)L;++1pR``(xDah0Ag>Po0b{IS{otCF$GPe^IPL6Cck0;jExiAC?$Qb9QdY??=auWnhH6Sxxp8kOE z0|C#`oaspOSv0?8bi>Y$`a4H39zxIeQ#{~Gxc;>8*@1NZnP2sfDBc@A|K17u^`NG2JEs{S0U@96XDOMmeGgVNH`gYJd@ zez`R7EBl9zZ*LUGEnBlI*L$|%*f96ra?I4}-}Ob69= zap+pn-mXXchV@N-jrtflHEh+jQRn_0?K^wGJyew{a0m-`XjL#iMDbv&-?_4R70j86 zV9pe~JTWeD!N!G~G2QhOrn~N9y6Y6eBiuuHghGTzxPkBpe;_={G8VIUiN*miXAu2$2-+Li1fXNOu*a|r%WB-%egDDDFW zc$n2~?(1dJ#G`&)L;L!*UA)_NwDPqws`dKrlUIMQeEa!wX}_NC z(uMfPuxk|`Ku`9eE#Us*<)ei+E1|w1`_Sa#A>J?G&7bUhD`Pz4q}`I1w7&IanC{;` z74|^s)2&C(JlewfR$LwPcfq=S5Y-n8mg(yQZ~CNnE%$#Y-}iZcD&O_>pTnQF2+hZL z^P6`c1PDVoq)ar2mUKjoLE|6C5dfFgyOY1u2`CRf_3!uLdq=oW=NGN#Evoo|=0{dx zeeBHGW>_HJ$s~O~-2cw%e%yilSr5hMPV@OMZ4m%x%J#B$5=zQA8@==TJK$Tn%*94+#(d= zp<1VSjNASXjb}~A)1>*W;fU`@@o+QdJ}IU64m98AwIwqL4gosALWbLeBT)XwZoAr* z)`tfl%`ZiOpXn&@OfUid5fk8%m=NDZ{!OhhzkveK0tG}Uwf~;|TZZtDy`#t;{2AH< z7!ZobfYAAR_3b)tU0XM&c~!ee=WazP?&~1_S*x1H-b2j2r}Q1&Yi^Lg-@x$^22+FP z2Yxfo&D^{l%!SZCVEcHdkR>4@p)2QwhGz$p|DSm%{%O9WBVPa7XztMTC)}fR{{XAO zA}tX9_3L;5x3=o@J0bX-tGnJh<(T`QJOlkb@&}&=hsWh!d;US|U&$VV`~^&03WGQ- zXDyH6Uh?Pfk8ti`Z?FKmO;4LasNX(un+KhDkv+tpmb!g#f4u$_A0PtN1^NFcdk=}> zE%Ikh{{CO1db^A2fa5>mevo|#t%q;c+Sz&cs@nOq9=>r^EEwo|-oY9D|1h|FY+T2x zZbu7iOB(;29%_L5v2+g4_s*dEGX(MRU?6SYu2Z{q4a#+QY*@cmkD8vX>U4AJc)YM^ z1wMDS_kww2er8UlE6jZ<{$Y=U`S~+&KMv#^MEii|LtaCV72`SkFh9B#?*slyzeoL# zfMzRb{B!V`2lMcTzs3MytU7<$J#TXon7u zVFd?=E(mZJnp8N%Bg@gQBNo8zYk$FT1v*5*U-$RhVm_Gs<7T)%j;HZbEC}TO|H_S_ z;gbeN&qM`}&p#v-0TA9(`)1xjzFQDH7dg&7Hl@xRf0??PM; z`TMQndW-Ghd-=2&+@o(^{@z6QTm<~X@Muonr|;pr0DZm0ZnTe(K6d7t(%1Ol=b?f( zIBEG2)1QW0>3u(s_n+dw())hml^!33=EqSXzD9)@O6wtTzm7z<*W=yi*;KgIXy z()pB=vvU;;chmgO$XTPy_ZT~ET%|xmkKkbwB4^AWJz>UFP3x-rJ7RmUut+%c3|TfO zgva~mhLb;#C`|wL!0V5AAmJup_zS`N5E^HaUs-Ln;%JF^k) zkmftD9@f``ef@nL!}~$=p^mV3B73IBXUobc-k(V=H~_`;=jZoZ#rsQoqQ|@Gv8kZf zWW4`%FdR(dqm%!K_glF3_jZ*+(y*bZfPb^?+t{|Tv$bp2iGDE>Qy{M_5~AUJAzWgG zXaxJphW%&Gm^gKO2&EXaND2%5->~OEFE{)@6MVQH*mcUZQDEEcQrtEI59|*TLK8v` z+Pu}dTqVF;X(y`V2W;lz-*Wt`hy<(9im-*x(f ztyp$o>B6!n%fTkoO^n4tmhmjVV_C%V9?N$uYnf8YCM=y<`m>zQGK}RSmMJWEm}Z+A zi?b|mvb@LgDa%rpWt`He9OYbxr7g=YEInBEWf{P79Lq44N#*FewzAAAccPrJxKZv6 zWKA=Yb}T(u_GLMZ<$RXOX1mPrcCoz9@}AiX{8Fhr$@(liu=Hf<&vF9GIV=;(?<{XD zj+Li#xW@7c%Xcg_7;)oXv20g?(m1koX6egvc!lvu6T&irWgN@3EWhV8M=G2|np==& z;%DA|&GG|ljVn^ltk1F;%eE{#vh2@t7|XGc)y1TWLm{Ift;E`jlelczT9N+a z@>E)gJ)lNXa+zl#ZiAwwhLS4^x(en*rYPfxoIaBPMS*;Tsjxv-79s=GNR|@~Ipuv$ zX(n2-&H}|+BUxUwVx0>pT2>JDtP2MDpiD=Uf$=r7-c0$jfK$$6Dv-rYClr-1nKq>T3gjs!7e!|b=uIA{=p5@-D!RyY zK+$EUONy@YU25D&;IB~Lz?~3cAShZE%A2fP$GRf<6W^titSgdt@uc-9c2+wpKd|m! z)}2?>yfNi+QJNak(;N(HB(KOyOiNjJRn{=1KEhcPES}RqOtc&20ulRslL0DTj0V;i8H7(5{zk zBCOhzbYR(!B=&Q zy*&9Lw!23t8S}`Icq%&a_v1mcA?}v5aN8h2>F}w^+VnS)~i@ z)rO@v%W*6hu-wG*5X+6$l=2#HzhG(ZM8C9P>A`Xo%lRzVv&?3BiRE7`&AQTFO<20J zJk*kY3F7U!ER$ItV0n(^6PCu^C}jhd-B=D`Ih$n?%e^d5v3$f*bEdtbtx4A5?anOy zSzhJeXYlq4mb+LMv;3Lm2bNYYl!F7yek`Z5jAOZ-Wg*MEEZ?%M-ktVp&(fFWB$lx( zx3E0Q@)pZiEUUQEUO(`;wc%}Vmg871V7ZCqA(q!zzF=wIgZ6E~(u3tFmh)MzXPM3N z63gd&l)re}%#HSK!qSyx5X-qNlUW{Md5+~1md5V1R|A&aSPo%1n`IKqy(~|$e8f`o zpuOs_?99@i+`9Q! zDZ6Q3Que@hMH`aQ)+C>^{DCF%
%)hw08mVl{RWpOEBIJULHB-;=6!JvVW>w$li?)&htp9|L*aLKo2goT zNV=A**0h(q%?rp2V#{I|FTccLUw0eI z!QDo$-I94*KbHPnj^liueRC4I)tid^h2PXb$S9T>{L6jbwwOlRDbwk!QX!4S8J6#+ z)94P-%PF5DXoasK` z$>JC9F0@y)HT_c9ezYN;Z07(2ypvt%mo1$q8n(lG@(og6a+(FLFSH1r(~0g_6Nhla z`gn7q==bjQ``eyzNIAn}7Uk@kfZu00QhTGY(`fv%(VFhLYggKLc1M!tPASO2-DV4p z;_9#$X+C%y!Y?g49L08aj};oef}SPVZsBwmzh`&2gl#LAeh#1BWO^c0N*E(Il6ooOZu1Z4DNVn; zrPMLom1muvcWIvU!m*7Pnlw;7M^id;fQTTj9EbCy4F8TF++q^Ix?#sIIP%%OK+FXy0N z%lXgS&26q=+uf!Vvid!Gg6&z3VwuFU@E$#v7x&7LronwmGmd5QeIqlytY{~Kei@8= zvV!H&U+Dh)#j?&rDu;a^(l3)9w!uhX?5}xBO0n+|<#w9oGnN&8qhFe_?9Os9%Nf7X zIi&q|7Dvqa?GohW-`?PPz2@x(kLkLc9@D;K9^ZymKj+vd^h-9&M=Y!VLHP{%gYvO| zO4=}%J6PU$N?PkbN&2vi_>~_(3KNAn3Bca78@`&nx)9RB~gK*7EEUp4QaUP>te;GR&CLd)^l^y5;lm0GM7B>{p(JPCGis)*qh{rnd)m9M}P3d_0 znN<}|Ov&O&s}c;Kj!|UQx#mODcaTg4~%WsY)(4; zjjSc==_GEoZYyeuW{S+(*n#X7b#5c2m2grtsSQGF3Qs2e8Pyg(I`LW87O9Hp8Ci=f z`hNV3ti^4e#FjQm&^=UisZBEInZ6%CBMeBjpz|y?rMqM!Qgz~IR7aF39o@4!;*8SK zGpZx9T79{`y5g`-0)C=oT~VZ{PTQKG3rzY`sVD9$qT|&Q6I%1p`FQokYMuCBP^WnW)fCKOWteX5xk-y7SG&ZJqe&%|(gbmpjoyT+m5OZP!?~5H}RX zwQB)-$fV!-mg1=*IzmhFPA9(FR-&2xm#b|hTIs~cYb8>cV))H!Ez(p|QCjmTos?@JqD+2hMuw-NO|Q!-L6`Ah?3Te12x4Ux9u-Desp+lvZrpK_Tf zI|%E~G(&b0k988ho>8Kkc&2DR(;G#5nS?uEt-R~m04Xh)9?1q?L^YX;WDBntkQ-C9 zi1mt)&LWj5R%9cki%4TCmXExmrHd$FS}0t-iAod&dB=iYDq7*4BwU512OWK(*u}I> z(Jk*4(p9`6?OAT zmF{ARm#&-YlO#OF4Mm%Lwnt@4ZL%5fhm7C+#PKbrPCylJFB@irV`ogJKlHzLYu#~=qJwTI^o*qBvMxBL+6qxF7>G`28d@&3-Put2jP(TTPQ~P z{Uis95lnQR&p}IcGA-=;UJe!siq7=aw80`p(NBF%Kxv8|^{oKf!Bi|>^sNl4*O!h^ zEUNWO5<|oiMfLkF1*P^Qoqk`2h;2HF7OdN&$fMtKq|9Z~uYIUE%%orYP;rGRSK#~|O;uXypkPJ#uv<12`B2CdH=*EaW zO#0Q16&bpY%a^etSEbYjdT3)sfui;UeL!dQ{iN@}Br#50QM7AdGU&caY3$!0mL!iA zIrt9(y;L*~x(VW)qGaeM2-AUl2SqV-6NQDMSI|uq){2@8>aR@_jTE^K8U(Uc6a?L5 z;izZ@bd!afPJBgEgpbl49W+v#A_ge>YtVSm2t{TAOT{;0f}$n?%Rw_0`3FRasUkvA zY(NZX36p*Yr-^uwscL&q6Hon#=v#@>rU|D(tP=}{L}|goXEB*+ALwIs7~NIki8<`z$7tCOkj!@^8*`zf)(uzOa@(0Db0s95VOTQ zMcsyN)Mkr-kph4E_Yf+AKU1;@6&X5-HbF@uOyn{}i=dzepedtxKd~Zcrxqqk6x|Bi ztIZJsqrc=CE=K4iVu$Bw;bMZK?BRK!nM`^sFG8g0#I=p<^4L@P^#DV@#8RdR_~qNo{DilPo9*Fv|0DO$LW zyr(S{MY>MR7&%fd6PA)4|sDD8_nDo1wEUxIpPdZsVR7B4qS-ez4PkOC* zqxPdazgEOgp`#~@uu&hh6cOsYn$Girz7$FeQtcqs>=tQ?3dRmI*dul*x;=I@C`-|su~8yj99Gn9 zTnwm0QNXw;u~%GBlnUKmaa$)Y>-LF$#j?@(WRU(xPjjk4hIsSU zei?$yV{msx)cASOnJP*hzYt`p=*;*WZNIQ*(!Z+x!buT5&Hchh5k1WV!cQk|#U2m| zim2_KDGoE~_bgKs=_KBbUv7{o&M0a%ArW*#-;dv(Eb&+o9WM)Gwsh9|dv;Ki=)_;~ zL5$?;I(nB5irf$)y$zi$3YcO=;DliY*%-YgU95v-92J&|iY6w5>gg#( zy-86bUo=zXG${sTuM_7|Ae@+z#gs|w3<`t`Q=(WhX_LW^VuI4;PTFozDDH=TI=Y=G z5~x1#7b_l5YAcQlQ$;l=+kqM}>F-jpXr+jrZLx4vMAv>oIO)XKegdQ7l&AjOOT-8e zYG2_wIm4htOi>iZbb(Xq?bZ_USm}05J}FCtZ8+tUC@iLA8JrS+iqb%5M6jX@Q}PVX zi7OyFg7_xS;5_YwdLgl%?Kg!67eq<~F}=yl#bxny9+L>3cFy36h?%dq1P)KTY;aX< z1Hr;rSO?!VxF$>^b&3dnWN=*+DY_l}%;1*riz1!=b=($ceh>+tkfq|bSgk06>4Hu; zdNOp+m>$TCSsxAV2*+sbCLYMN+2suH2p2^UXGg&r!Fr*t6QPx$YozE|Xane4DY6eE zvQ;!DtS!iqNpBh75k5?Go&)3^Q8R|}j20<#>Kfh^o=mZ#*<4q7UlcGUi-5U}4etxF zh*IibyaY0c;xVz!+!XlPV zhpzp;;U8kjXZqdnPm#u?-=)7q&Bc^5T9kyRi@!vXqKBZrMaB|cXBiPAo{5yDI=Mxp z!#=!-DN)Rf_}lP>*b%4e5}Qh^sHx}-=$&Y#=UNJOWL8ZuXrEvTk^$&@U7qPrT^k}m7?{UV}cL~Yq>y*dI? zT1yv2B_JC)K+zje9l2Ujvjyp*uFO&t0IDa86fId0BkIdN8}vMLp=%&>6+H$ulqHI4 zF7z>KBv)_JQ+h5;7fofZqOgU7jhe|Nn|0leg=39c%GHW)AZ2S=q{uWTU9^!m6glFt zww3o4O@OYgtdOSXk^-`owu(wX?PP$WH=y=%jG|_XzA>_s^}f~jb6XT*WG_c3nz<+) z_L&Kawt*aEs-i24e2hBE3EODDMDc7;j{%GVOc)2m#P_ zlSPV_fShH?4qcZEa*=^Mb$SfyE+;6exj0?8%9)Bh7cVvHA#GvaOYi&`=z2;QMXN!c za)P3w#p{f`WbPh4*HZ?AdP@sMX-jr$zH*PE3()nE z1x%=cmTocflg;+(``Io{7yab`MI%51WW1t;rTdHq%G-+eKsQhZ@6-3Y4f2<(6;+6H z6oce8MNV<)Ajb?n5v08N&;Oo`%Y{3)X;^5sGO2-eH57)_NKKj`GMJY59KGm0WW)1~KOUAG4` zLk26l4GNKAiYlx~7c=EHMNXhuGDFc6&}?~I(GpOoe4{8A6ei=3=tp=Anj`Ix>SVYw zUCfn^it2&FWuPKIP=pLt6a|_mQx&DIY#`>#X8C$9XP}!e;}yMIdDkdXUieYhwMzKi zXrYWKBtq*cAUo}lZnKP6^bV9JbD0uF&DE8Szm>mRq8+PW8*Y;i&**s; zLAPDLRP++`y>vUP>l&>|7dvFIq5*408|;*+iejMKB{LLdt*K+YTV7D~5W4;H$~iq3 z%j6hwKpsA?lUs6o<4ig8f=)A&yBX)mJ&Lv^r;A*9L(vt`57OeIp3-n_k~k!5Dyp}( z0mw~}-`aH0V?|MGV?-XN%Juy+pgSxr72O9Nkr|3CQqsjy`B0Hdil=eDw7jD47o5`1 zxKR2iN=q4Rd`wn&r|S-=5kJWi16@}$HC^16w-wog?#O3~#(?fhLqk1fRO%Ard(u)- z26XqOqoVtu2hvB8#m02;vkX?`0{TT38R@wMZ$ukf8XD^qzcEJqDxWGkjFgY03#?)2 zYM+9BlYxq?H>HcmGM*_>_-ryT{#{zYu7XldfbLIettbWbmrPTX1^Qc_QS=b>OxA>b z2<;~}8yG#8mKAie-Hf)dtjB~SY+h;nN{&&w20dxl+6vqzcQXFQDkl2V)UEgX~Qsf5uASWoA3Hm6fDB6~`&RA%N6WYU?xs zRA0+glmcp?6*0w%l5N?>4Yeyu_XfHq8XDF77Bu^=foQ75C<^!v?On~@M%T>*HPbv5 zZTqgkxViSYuCBWRT}v&Zo=%3_(?u&SK~cT!CC06_3H5cIA9QWBRHj4`wY{=&TkWvY zWk6@E6)3t7YNuH?(D$?W9<5)^UXjc9=Z)>O_(r-e5ITD;OHn+igZ7LmRviBRhOvY8 zPU&ty*GaQztnX(Eqm|B@wIatI=^$H013+Cg3_$UHi2*rjX-s&_cbqcns#&zqbq}F) z*6bBo?nGNyb5!I8>aL|K8Ub?EZYW9s_0aA!C5wWc>kQnqhfIm$_RgP;-8I9O`VrnB zrH7WHsM#*Gnzb}V0U%E;OVN^DzZ-jLQ(Ei$i%|3!hugWNb^)w zVNW@e0PU$Fr#vEwRu6ZhYyvM?1gf_E-uB(|| z&19rz>!6coI$F4z8&jePOFv~aMoaCe>vp8qH5sS9Q*A zk>lPNF-7z1qU$C=_l*{>C+{AQkwUeHU?LM@3wPsz3 z62*vp9ZW*BET(7?0o_dPouWOUSz66*`hF##*;+kCZ$P1%ts=_|w0E@$irheRv=l`% zL36b%McY8(+6_flGF}@-XgCpJz#}fsCi4{)%V$U#^_gC0xSK?OCWHNbO=2`p7dkT< zsTpaqNDE+kBrEQpViK#R>N-(v|2%^w+BQY*`xk;T6wTTnC6;QrioV+)11e(DXPx7; zr;2DsI!?3fPRDyBU-XSKS*AHM>0^2Enu|{2>i+&(yymIM=)fS*07V@Rv=z&>Kt*E@ z*nuYKglmX5S)oOJCfMF+OH|4=2bO{o6dgaX9F(f)p98}T618oL8fT6MeSJTZKHsxv z8ObKAKhx`sjV8&T$zcCZll5ALo(qqkZP0R6o&z(NiVa$UqD7g@L1&os@!eGIiXs}> zOVu7KqLICg+GCw~e0QVf=Sp`_AK%@iMKF;?K?AW#i(y(NWY!^*O~ff(XUKz z6jj6U(PqukT|c@zldYmzOg@UfW167oDpQOiqaP@jG({bl4l5eVbVJb^rk9G2Gg)}( zNB@VZm7>OnC{IsC1DVDsTErBk=pa+7qFda9`L5z5olQ$PAZrbdbuF}Wx@$P}pP zSEevU)s9jws};F3Whk1(bVkv4Opg^^Wis^Ak8YGtxztnCfyqhHSf&6)YnWy#I?j}! z=pUv%iW(PCo+XL~GCfqZhzSE@{QVtdvQ~7F$&rcXZ_k?GomXV@<7Ja?wKPQ&f4ph3 zRlBd~$d6macFnRE<(VvQg1*ggmF6-J5OS~Eol3u8d`iY`L8M{`mnkHvsI z6}3AyQcKtT6pcJK9u%l3@z_$aR~w_~$79Pu!HS+AYpdRZ%S%h2K# zeFI&FmZE55(MWB-mZs=z(Rk1vCf$zZfR>@_xTHOx<*JnLi~4H^v;sw~j}HQ!QTx%H zO{R8R5tZzjT3BzoOM2;@rESxRXJxar9ZE+tI9XbJpHKHQSsc{%Fg=i7$Df*HYcCbe zIsR13*4`=Fc|1yFYZiTJzX#H&I0;l!QHSCNpn8hNGBr}PrnoJ1_DuTo%hsH968nqa z;COC|9u&U=`7!A=SB^G95nW%77OWFnZsuyKN=Nlru9l{BbbYy+Q~yubXKea|Hb5s) z`9ww2Lt3Ds?k9{v!A$z`^0Y8Tbi6!mhax({VQr62`Yf<^Wgs0vzt=~!cTD>A9ntFf ze>!@yIHHaCOjS*fYKDVIr@!U-nkCbta??*FiG0nP>46MCfjMEVo}%j~+JfAeVuf8v z15mJ{J|#A$`PywoktGdH3$zgdbc6?TUr9^TLd|6`pNlAevb|}M=E?Lxb~)(;3Q)B6 zWI4kkZGuW!da{8i(n=IHJoR1{X>SzuXBsm^&vW4^cjWR=QRb;$rpL8bLrF(%h9RIp zCf&mH|KaUT;N7Usv+pu1CL7yaaX zU|LBUtT62EpIi@?CslUfCpQ6`x?D~{d4~K)+C>Ue?qq8tDwFSF z-mjGqQ+(d9-L5cfM1?u-X7KsE_FBZ0oIS6-9kH9RM;`G>R-#FR37U7Gpz~MDlDn$M!z{;W4)87jTVJ zdk2aq=bDFj-#f$l^WW57&C5Qo!suW8+5D5*YgKkL#5<|ogBU!s@QaH$V!BuHxm=U#$dougch89Y61s zc7v$s_WbHX@NzR^*I^I*>S{>+Uc_#|PX6lp`5N5QQ!GFD8M);7HNYPyKK0 zBK&T|c3?Zrkv46{0@a-_wC;Wy1P~O-2UCb_NnYN=r-Z^ zt8Cq~yMaBXGV8Ouu+8{WDyxBRGybB=ZUWsFeCwYn_5KaeZNVcdd+6Ei*n9Ad%3gjJ zp7X%BschbJ3l^M@(<(dvxmCbKmHo+cyRZxJD^%8gZa1*2RrXh))8W^u>^q>-;d@l} z^XJ~P;Jx^5DtrC8_W`>TF{P$2#1A0016%U^`PhZ{%IzrEs8%k-Hz;gs_4C_7r&F2j z`JKS@h$%C|`|yCmu)jF)kp=I=C6)d7fOEk`_#G;H;XnYt7(bvg?XSOzy&qq@Lr(8H zY}2np3qF8fc7_!ed>Fq?Wgq!9v%riWR9WNK(gFv*vls3K-S#nE0KZycQ=fZr z7Z$*;RoP81?gq9;Wq%F2Aby+5{snYF{7#iU4!RKjEtMSrT?oHdWp98kj6a|{69AUpllPiF;Hwdg&yvgvxfkG_^2=7gV9;SUfvCCugcsnuUeSH zZ&z9I8`D*G7d%J{V^i-2w$ezVGopxcJurn1_r+c5&aLuET(h3`V) z-%{CEKv%){sq9YBRqzK?_I=P*@yAs5An2<2ew951>5=%0Dmx76k@#ViE&9zajKWW< z?4sZ71~zrIeD3#ZJioCltl^g;b{|#+R>yh7l(CM+uRu&G7aHG**p=9qe-m0r<2R4# z7<{k7rsR@m@Y_`;mpp^tt+M88yRZiSJ(XSg+HPR?tIYlK_3(z`M^sjXmfXajLJaQl zy|x}>aSW5+v+!TRpMT)L66@X4(!yII{@6bMt7kWt&+%L-G7fLMn9b*S$OOFq;@shr z@J5eWt(+@(;UU6t|8u#OUwtR%Vch?C$eiUn*^FyvWg#wGegzQTrjd{_m6Su;fH?Mf zzzOVc05#YNgmWb@p6>X$&wBn{%RfeVs)TZg@F74gb{Y`JF8PEU{(XecTqeuy%Vj)` z@b5k;%Q9-Qqe%XJ@)p}B+cy9;*tk4b{%4=jc&(kQj>gMs-2QBD;~}$_w^!s`-3tis z1gXl%j-mXmH!Z3!9D54n3G6u1D&ew>tF@_w$lh>l8!7v_c?^}hJ{y0Za{J@k%%1zX zJpZyOpY>rt4Yrk)WghTsSqaf#+gh@X{EaUh+Xo2YoQ(4Kt~BTyPw+++UY2FlVs`-I z$g>7p)R99j1(e^}BqKdUGN2Yaj^uZ1AsXzpk!*j_r(}B%pk@pqEfn(Z9)7nR&Q(6= z3LkG*imX^FEpvRlI@|L14;fGWZ2!u*HxJ$iB$vSNp3%8N#(kd4=H0``b24sucKClk zEye#I%k#LO|9%~fdmAr@cbETbvCidl+}pX%NU<3&$#Z%8V}+k9SO0T4IotCe+7rh; zyn8wPp_b!0dG|i6cpkTZ_ne$dJML%PW<2NP^0>{oe72w8C)T)!|NrIjJfEvajOXgz z?aw~nar<#UXUk{D{e5i4{r{o)KimI!$RFx?Jgy>-dpKJ=ZgVc}9~%Dqr1-~8S4r_) z`5e!WQakV7qtE4MJkRgm-_Op^xkBFE&%5X3zoQ+`&$xW{Ii8)$+4g7KjE9`demwka z`S&ZCxwSg%An%daRC0(GbBxKK1sTU~MIp+XUqNNhuE@XdN^JbR{wL(K%i+J5ioBCm z_BdMXu?OemE%0p@9J>@y1Mj|+OYG_~S=nce@43#Fk0X2i!?K5Oy5AY)b-ot8lLW`G zM`fGw`nm!_U}ul&|@DgKj+TDd(SdEN`lmT9r+ z7i8@wKrJ>85LZLSwQrYY`_M;aRL`8W{_L~Br*AEu%t#f{E;O|&FHWPP&gc1vTO3EZ^S^}aX7t1&d=p2 zq_U)leg1GA@P@-JWPjC?yR<0(sN8}Goq)Tsmo$40PtPlYb~kn%)Z=dWW}S?;VqZV} zSK#5F58u6HhgN=b5i$8MhyQg6G5PS}Cp8Z2slyK}nTAsO_<0WO)x(c2nLnXD@}y?| z#LSUh@XJr|4rccO2QF*`eKa#HaZhXn(o% zYnmI5JhSvx&7LEFia(0~&5_IT8T?yELd$0G2W3QUer9t2kp#%H{~7#0kK{qCIr>S! z`A6mZf^ta)@zqCnXbwSHk;`^y<@ZBPPhNDiv21$Me3V~yo%TH`Y1yHPKRNnU_@0lP zs~uYJ(dz-__e1T_#*Utk$u;}nG8umg4_juA{sQDjF8q=vsk!XvQ{YpHCBMyThgN=z z)ef!v_NpCP`K?tuwDQ}kc4+0dRPE5pZ<*SmmESHUw;XI*#!0|OE?f)sOCFWma3A6I zM=!@0L4Cb<=_2g!kDk&j!v5)~R{I40gQM@AzGvc*qyMZ;YMwu8n3h}GrGPgac1{zM z2ajGht-((~Tmz;#W}Sp@uO9p6^l)MZ0 z8~^;_gMc?2J~S=Y!ujw^F}-7tLQmOsY!@~?`4`9Ja{KDBOP41#cN}vqzZbPMxfLas z&*0xZ_MxTsqShtXY)Nx5U5`mdkkk@*QY&>S$j%_LuN&oMXqnzTBYw?XiDcevfA9@$W1TYBwJL&_qzH zJAMzy#^bl(9j)W|SK&KnvYea@9e-$fa`Lj{av$f9Ke_x_R8#jt3G83~03f{Q1mRPt zOb#GCh>)0+j;~vB7|ACQ-m1Ow_}$uDwbwo!U9oHO%HywVcTHaVbb3WnbM^7!io5XZ zj|(fV!}lB?fP5>+cj14B@E;L=58=NddlhIrx4vH@qJdeUZfx>GLeitr*ybHhLt>3JXXNTR`K{QW&8~*~>A3$ex0G-PNs0JTX@s_vb zk^C!f6<0ik{q0*Dr=G&(kOvSxhW#T7kT>Vcjm>D^eCv9^iIY%Anq?QWi*
Rx-nNx50{K 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 z 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 Date: Sat, 18 Jun 2016 21:49:21 +0200 Subject: [PATCH 143/185] Revert "Minor enhancements in data layer" This reverts commit 1a810e2c1ca95cdad0e62b6235963a813d26bf41. --- .../SmartStore.Data/ObjectContextBase.cs | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/Libraries/SmartStore.Data/ObjectContextBase.cs b/src/Libraries/SmartStore.Data/ObjectContextBase.cs index 26b4e52171..579994d654 100644 --- a/src/Libraries/SmartStore.Data/ObjectContextBase.cs +++ b/src/Libraries/SmartStore.Data/ObjectContextBase.cs @@ -16,7 +16,6 @@ using SmartStore.Core.Data; using SmartStore.Core.Data.Hooks; using SmartStore.Core.Events; -using System.Data.Entity.Core.Objects; namespace SmartStore.Data { @@ -139,7 +138,7 @@ public bool HooksEnabled public virtual string CreateDatabaseScript() { - return ObjectContext.CreateDatabaseScript(); + return ((IObjectContextAdapter)this).ObjectContext.CreateDatabaseScript(); } public new DbSet Set() where TEntity : BaseEntity @@ -220,7 +219,7 @@ private IEnumerable ToParameters(params object[] parameters) // database call var reader = cmd.ExecuteReader(); - var result = ObjectContext.Translate(reader).ToList(); + var result = ((IObjectContextAdapter)(this)).ObjectContext.Translate(reader).ToList(); if (!ForceNoTracking) { for (int i = 0; i < result.Count; i++) @@ -264,8 +263,8 @@ public int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, in if (timeout.HasValue) { //store previous timeout - previousTimeout = ObjectContext.CommandTimeout; - ObjectContext.CommandTimeout = timeout; + previousTimeout = ((IObjectContextAdapter)this).ObjectContext.CommandTimeout; + ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = timeout; } var transactionalBehavior = doNotEnsureTransaction @@ -276,7 +275,7 @@ public int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, in if (timeout.HasValue) { //Set previous timeout back - ObjectContext.CommandTimeout = previousTimeout; + ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = previousTimeout; } return result; @@ -499,29 +498,27 @@ protected internal bool IsSqlServer2012OrHigher() public TEntity Attach(TEntity entity) where TEntity : BaseEntity { - ObjectStateEntry entry; - if (ObjectContext.ObjectStateManager.TryGetObjectStateEntry(entity, out entry)) + var dbSet = Set(); + var alreadyAttached = dbSet.Local.FirstOrDefault(x => x.Id == entity.Id); + + if (alreadyAttached == null) { - if (entry.State != System.Data.Entity.EntityState.Detached) - { - return (TEntity)entry.Entity; - } + dbSet.Attach(entity); + return entity; } - Set().Attach(entity); - return entity; + return alreadyAttached; } public bool IsAttached(TEntity entity) where TEntity : BaseEntity { - ObjectStateEntry entry; - if (ObjectContext.ObjectStateManager.TryGetObjectStateEntry(entity, out entry)) + if (entity != null) { - return (entry.State != System.Data.Entity.EntityState.Detached); + return Set().Local.Any(x => x.Id == entity.Id); } - + return false; - } + } public void DetachEntity(TEntity entity) where TEntity : BaseEntity { @@ -553,6 +550,7 @@ public int DetachEntities(bool unchangedEntitiesOnly = true) where TEnt public void ChangeState(TEntity entity, System.Data.Entity.EntityState newState) where TEntity : BaseEntity { + Console.WriteLine("ChangeState ORIGINAL"); this.Entry(entity).State = newState; } @@ -561,11 +559,6 @@ public void ReloadEntity(TEntity entity) where TEntity : BaseEntity this.Entry(entity).Reload(); } - internal ObjectContext ObjectContext - { - get { return ((IObjectContextAdapter)this).ObjectContext; } - } - private string FormatValidationExceptionMessage(IEnumerable results) { var sb = new StringBuilder(); From 7158f24d8d752e43317e61bda0ef063ef7e9fe5f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 20 Jun 2016 11:06:23 +0200 Subject: [PATCH 144/185] Export: Some improvements --- changelog.md | 1 + .../Domain/DataExchange/ExportEnums.cs | 7 ++- .../Catalog/IProductAttributeParser.cs | 4 +- .../Catalog/ProductAttributeParser.cs | 9 ++-- .../DataExchange/Csv/CsvWriter.cs | 4 +- .../DataExchange/Export/DataExporter.cs | 8 +++- .../Export/DynamicEntityHelper.cs | 46 +++++++++++-------- .../Views/Export/_Tab.Deployment.cshtml | 2 +- 8 files changed, 49 insertions(+), 32 deletions(-) diff --git a/changelog.md b/changelog.md index ddeda6310a..480d9476d6 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,7 @@ * #637 Integrated PayPal PLUS payment provider * #932 Implement paydirekt payment plugin * New Viveum payment plugin +* #979 Implement BeezUP product feed ### New Features * #637 Integrated PayPal PLUS payment Provider diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs index a5b5a48763..6ea68ec73c 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs @@ -126,7 +126,12 @@ public enum ExportFeatures /// /// Whether to not automatically send a completion email /// - CanOmitCompletionMail = 1 << 12 + CanOmitCompletionMail = 1 << 12, + + /// + /// Whether to provide additional data of attribute combinations + /// + UsesAttributeCombination = 1 << 13 } /// diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs index 64a412d339..a02648a747 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs @@ -35,11 +35,11 @@ public partial interface IProductAttributeParser /// /// Get list of localized product variant attribute values /// - /// XML formatted attributes + /// Map of combined attributes /// Product variant attributes /// Language identifier /// List of localized product variant attribute values - IList ParseProductVariantAttributeValues(string attributesXml, IEnumerable attributes, int languageId = 0); + IList ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes, int languageId = 0); /// /// Gets selected product variant attribute value diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs index 9c87960614..0afe47049b 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs @@ -194,21 +194,20 @@ where id.HasValue() return values; } - public IList ParseProductVariantAttributeValues(string attributesXml, IEnumerable attributes, int languageId = 0) + public virtual IList ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes, int languageId = 0) { var values = new List(); - if (attributesXml.IsEmpty()) + if (attributeCombination == null || !attributeCombination.Any()) return values; var allValueIds = new List(); - var combinedAttributes = DeserializeProductVariantAttributes(attributesXml); foreach (var pva in attributes.Where(x => x.ShouldHaveValues()).OrderBy(x => x.DisplayOrder)) { - if (combinedAttributes.ContainsKey(pva.Id)) + if (attributeCombination.ContainsKey(pva.Id)) { - var pvaValuesStr = combinedAttributes[pva.Id]; + var pvaValuesStr = attributeCombination[pva.Id]; var ids = pvaValuesStr.Where(x => x.HasValue()).Select(x => x.ToInt()); allValueIds.AddRange(ids); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvWriter.cs b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvWriter.cs index 83eee20228..8bb9942052 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvWriter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvWriter.cs @@ -51,7 +51,7 @@ public CsvConfiguration Configuration /// to complete writing of the current row. /// /// The fields to write. - public virtual void WriteFields(string[] fields) + public virtual void WriteFields(IEnumerable fields) { Guard.ArgumentNotNull(() => fields); fields.Each(x => WriteField(x)); @@ -69,7 +69,7 @@ public virtual void WriteFields(string[] fields) /// /// The fields to write. /// True to quote the fields, otherwise false. - public virtual void WriteFields(string[] fields, bool shouldQuote) + public virtual void WriteFields(IEnumerable fields, bool shouldQuote) { Guard.ArgumentNotNull(() => fields); fields.Each(x => WriteField(x, shouldQuote)); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 173b61b475..41cb53a69f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -1065,8 +1065,12 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) logHead.AppendLine("Entity:\t\t\t" + ctx.Request.Provider.Value.EntityType.ToString()); - var storeInfo = (ctx.Request.Profile.PerStore ? "{0} (Id {1})".FormatInvariant(ctx.Store.Name, ctx.Store.Id) : "All stores"); - logHead.AppendLine("Store:\t\t\t" + storeInfo); + try + { + var uri = new Uri(store.Url); + logHead.AppendLine("Store:\t\t\t{0} (Id {1})".FormatInvariant(uri.DnsSafeHost.NaIfEmpty(), ctx.Store.Id)); + } + catch { } var customer = _services.WorkContext.CurrentCustomer; logHead.Append("Executed by:\t\t" + (customer.Email.HasValue() ? customer.Email : customer.SystemName)); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs index 96362b5999..e925553e9f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs @@ -578,14 +578,14 @@ private dynamic ToDynamic( #region gerneral data - dynObject.Price = CalculatePrice(ctx, product, combination != null); - - if (combination != null && ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) - { - var values = _productAttributeParser.Value.ParseProductVariantAttributeValues(combination.AttributesXml, productAttributes, ctx.Projection.LanguageId ?? 0); + dynObject._CategoryName = null; + dynObject._CategoryPath = null; + dynObject._AttributeCombination = null; + dynObject._AttributeCombinationValues = null; + dynObject._AttributeCombinationId = (combination == null ? 0 : combination.Id); + dynObject._DetailUrl = ctx.Store.Url.EnsureEndsWith("/") + (string)dynObject.SeName; - dynObject.Name = ((string)dynObject.Name).Grow(string.Join(", ", values), " "); - } + dynObject.Price = CalculatePrice(ctx, product, combination != null); dynObject._BasePriceInfo = product.GetBasePriceInfo(_services.Localization, _priceFormatter.Value, _currencyService.Value, _taxService.Value, _priceCalculationService.Value, ctx.ContextCurrency, decimal.Zero, true); @@ -595,21 +595,34 @@ private dynamic ToDynamic( else dynObject._ProductTemplateViewPath = ""; - var detailUrl = ctx.Store.Url.EnsureEndsWith("/") + (string)dynObject.SeName; - if (combination != null) { + if (ctx.Supports(ExportFeatures.UsesAttributeCombination) || + ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) + { + var variantAttributes = _productAttributeParser.Value.DeserializeProductVariantAttributes(combination.AttributesXml); + var variantAttributeValues = _productAttributeParser.Value.ParseProductVariantAttributeValues(variantAttributes, productAttributes, languageId); + + if (ctx.Supports(ExportFeatures.UsesAttributeCombination)) + { + dynObject._AttributeCombination = variantAttributes; + dynObject._AttributeCombinationValues = variantAttributeValues; + } + + if (ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) + { + dynObject.Name = ((string)dynObject.Name).Grow(string.Join(", ", variantAttributeValues), " "); + } + } + var attributeQueryString = _productAttributeParser.Value.SerializeQueryData(combination.AttributesXml, product.Id); if (attributeQueryString.HasValue()) { - detailUrl = string.Concat(detailUrl, detailUrl.Contains("?") ? "&" : "?", "attributes=", attributeQueryString); + var url = (string)dynObject._DetailUrl; + dynObject._DetailUrl = string.Concat(url, url.Contains("?") ? "&" : "?", "attributes=", attributeQueryString); } } - dynObject._IsChild = (combination != null); - dynObject._DetailUrl = detailUrl; - dynObject._CategoryName = null; - if (ctx.Categories.Count > 0) { dynObject._CategoryPath = _categoryService.Value.GetCategoryPath( @@ -621,13 +634,8 @@ private dynamic ToDynamic( productCategories.OrderBy(x => x.DisplayOrder).FirstOrDefault() ); } - else - { - dynObject._CategoryPath = null; - } ToDeliveryTime(ctx, dynObject, product.DeliveryTimeId); - ToQuantityUnit(ctx, dynObject, product.QuantityUnitId); dynObject.ProductPictures = productPictures diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml index 1db883b2d3..d77bc3cf54 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml @@ -104,7 +104,7 @@ {
- @T("Admin.DataExchange.Export.NoProfiles") + @T("Admin.DataExchange.Export.Deployment.NoProfiles")
} From 59186e1f3520cb43887e048cb7b4068a8d1fc254 Mon Sep 17 00:00:00 2001 From: Michael-Herzog Date: Tue, 21 Jun 2016 11:20:14 +0200 Subject: [PATCH 145/185] Update README.md Updated link to new website --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d3402dc825..a7a83aee3a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ A comprehensive set of tools for CRM & CMS, sales, marketing, payment & shipping The state-of-the-art architecture of SmartStore.NET - with `ASP.NET 4.5` + `MVC 5`, `Entity Framework 6` and Domain Driven Design approach - makes it easy to extend, extremely flexible and essentially fun to work with ;-) -* **Website:** [http://www.smartstore.com/net](http://www.smartstore.com/net) +* **Website:** [http://www.smartstore.com/en/net](http://www.smartstore.com/en/net) * **Forum:** [http://community.smartstore.com](http://community.smartstore.com) * **Marketplace:** [http://community.smartstore.com/marketplace](http://community.smartstore.com/marketplace) * **Documentation:** [SmartStore.NET Documentation in English](http://docs.smartstore.com/display/SMNET/SmartStore.NET+Documentation+Home) From d604fd1b244e07593bfa81dc2f837fc69b6d8682 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 21 Jun 2016 11:35:24 +0200 Subject: [PATCH 146/185] Duplicate entry in changelog --- changelog.md | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog.md b/changelog.md index 480d9476d6..db0febbc6f 100644 --- a/changelog.md +++ b/changelog.md @@ -10,7 +10,6 @@ * #979 Implement BeezUP product feed ### New Features -* #637 Integrated PayPal PLUS payment Provider * #961 Fix "Open Redirection Vulnerability" * #571 Option to display another checkbox on confirm page to let the customer accept that his email address can be handed over to a third party * #870 Implement address import for customers (both billing & shipping address) From 30713d24ba65b31babf271cb688b09e933dedc3c Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 21 Jun 2016 15:13:31 +0200 Subject: [PATCH 147/185] Enabled import context to accept strings as progress messages --- .../Import/ImportExecuteContext.cs | 14 ++++++++++++++ .../Administration/Content/admin.less | 6 ++++++ .../Views/Export/InfoProfile.cshtml | 18 +++++++++--------- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index 67e3c090a1..70170c0fd3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -209,5 +209,19 @@ public void SetProgress(int value, int maximum) } catch { } } + + /// + /// Allows to set a message + /// + /// Message to display + public void SetProgress(string message) + { + try + { + if (_progressValueSetter != null && message.HasValue()) + _progressValueSetter.Invoke(0, 0, message); + } + catch { } + } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index c31aff72bc..5483f5971f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -1580,6 +1580,8 @@ table.payment-method-features td { } + + /* Data exchange -------------------------------------------------------------- */ .profile-list { @@ -1587,6 +1589,10 @@ table.payment-method-features td { border-bottom: 1px solid #ddd; } +.info-profile td { + padding-top: 20px; +} + /* Import column mapping -------------------------------------------------------------- */ @media (max-width: 1280px) { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml index 54799e977b..912809feb9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml @@ -16,15 +16,15 @@ } @foreach (var profile in Model.Profiles) { - - - @Html.SmartLabelFor(m => m.Profiles) - - -
- @profile.Name - - + + + @Html.SmartLabelFor(m => m.Profiles) + + + + @profile.Name + + if (profile.ScheduleTaskId.HasValue) { From e10d4f8c6cebc36bac0626f10a7a54dd7590d74c Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 21 Jun 2016 15:33:32 +0200 Subject: [PATCH 148/185] Removed promotion feeds node from admin menu --- src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs | 6 +----- .../SmartStore.Web/Administration/sitemap.config | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs index b6c3684093..2dbc226f57 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs @@ -7,17 +7,13 @@ public class AdminMenu : AdminMenuProvider { protected override void BuildMenuCore(TreeNode pluginsNode) { - var root = pluginsNode.SelectNode(x => x.Value.Id == "promotion-feeds"); - if (root == null) - return; - var menuItem = new MenuItem().ToBuilder() .Text("Google Merchant Center") .ResKey("Plugins.FriendlyName.SmartStore.GoogleMerchantCenter") .Action("ConfigurePlugin", "Plugin", new { systemName = GoogleMerchantCenterFeedPlugin.SystemName, area = "Admin" }) .ToItem(); - root.Append(menuItem); + pluginsNode.Prepend(menuItem); } } diff --git a/src/Presentation/SmartStore.Web/Administration/sitemap.config b/src/Presentation/SmartStore.Web/Administration/sitemap.config index 179ac3a5c4..a1501d7653 100644 --- a/src/Presentation/SmartStore.Web/Administration/sitemap.config +++ b/src/Presentation/SmartStore.Web/Administration/sitemap.config @@ -147,7 +147,6 @@ - From 3b58ca04740eeaeb10239063d2e80f9fafeb2db4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 21 Jun 2016 20:32:20 +0200 Subject: [PATCH 149/185] Cart and mini cart should not have different item order --- .../Customers/CustomerExtentions.cs | 11 ++++++----- .../Controllers/ShoppingCartController.cs | 3 +-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs b/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs index 719f144ed5..a3525e9cac 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs @@ -287,14 +287,15 @@ public static int CountProductsInCart(this Customer customer, ShoppingCartType c return count; } - public static List GetCartItems(this Customer customer, ShoppingCartType cartType, int? storeId = null, bool orderById = false) + + public static List GetCartItems(this Customer customer, ShoppingCartType cartType, int? storeId = null) { var rawItems = customer.ShoppingCartItems.Filter(cartType, storeId); - if (orderById) - rawItems = rawItems.OrderByDescending(x => x.Id); - - var organizedItems = rawItems.ToList().Organize(); + var organizedItems = rawItems + .OrderByDescending(x => x.Id) + .ToList() + .Organize(); return organizedItems.ToList(); } diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index f194738cb8..e4ff2cb6a7 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -996,7 +996,6 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() //products. sort descending (recently added products) foreach (var sci in cart - .OrderByDescending(x => x.Item.Id) .Take(_shoppingCartSettings.MiniShoppingCartProductNumber) .ToList()) { @@ -2633,7 +2632,7 @@ public ActionResult FlyoutWishlist() { Customer customer = _workContext.CurrentCustomer; - var cart = customer.GetCartItems(ShoppingCartType.Wishlist, _storeContext.CurrentStore.Id, true); + var cart = customer.GetCartItems(ShoppingCartType.Wishlist, _storeContext.CurrentStore.Id); var model = new WishlistModel(); PrepareWishlistModel(model, cart, true); From ee9a7c0ba9da185fc6fa98032102f5483e697a01 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 21 Jun 2016 21:41:02 +0200 Subject: [PATCH 150/185] Minor perf improvement --- .../Discounts/DiscountService.cs | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs b/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs index 9b75b86e8b..b973e84153 100644 --- a/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs +++ b/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs @@ -12,6 +12,7 @@ using SmartStore.Services.Customers; using SmartStore.Services.Common; using SmartStore.Services.Configuration; +using SmartStore.Services.Orders; namespace SmartStore.Services.Discounts { @@ -317,18 +318,18 @@ public virtual bool IsDiscountValid(Discount discount, Customer customer, string } //check date range - DateTime now = DateTime.UtcNow; - int storeId = _storeContext.CurrentStore.Id; + var now = DateTime.UtcNow; + var store = _storeContext.CurrentStore; if (discount.StartDateUtc.HasValue) { - DateTime startDate = DateTime.SpecifyKind(discount.StartDateUtc.Value, DateTimeKind.Utc); + var startDate = DateTime.SpecifyKind(discount.StartDateUtc.Value, DateTimeKind.Utc); if (startDate.CompareTo(now) > 0) return false; } if (discount.EndDateUtc.HasValue) { - DateTime endDate = DateTime.SpecifyKind(discount.EndDateUtc.Value, DateTimeKind.Utc); + var endDate = DateTime.SpecifyKind(discount.EndDateUtc.Value, DateTimeKind.Utc); if (endDate.CompareTo(now) < 0) return false; } @@ -336,33 +337,36 @@ public virtual bool IsDiscountValid(Discount discount, Customer customer, string if (!CheckDiscountLimitations(discount, customer)) return false; - // discount requirements - var requirements = discount.DiscountRequirements; + // better not to apply discounts if there are gift cards in the cart cause the customer could "earn" money through that. + if (discount.DiscountType == DiscountType.AssignedToOrderTotal || discount.DiscountType == DiscountType.AssignedToOrderSubTotal) + { + var cart = customer.ShoppingCartItems + .Filter(ShoppingCartType.ShoppingCart, store.Id) + .ToList(); + + if (cart.Any(x => x.Product.IsGiftCard)) + return false; + } + + // discount requirements + var requirements = discount.DiscountRequirements; foreach (var req in requirements) { - var requirementRule = LoadDiscountRequirementRuleBySystemName(req.DiscountRequirementRuleSystemName, storeId); + var requirementRule = LoadDiscountRequirementRuleBySystemName(req.DiscountRequirementRuleSystemName, store.Id); if (requirementRule == null) continue; - var request = new CheckDiscountRequirementRequest() + var request = new CheckDiscountRequirementRequest { DiscountRequirement = req, Customer = customer, - Store = _storeContext.CurrentStore + Store = store }; + if (!requirementRule.Value.CheckRequirement(request)) return false; } - // better not to apply discounts if there are gift cards in the cart cause the customer could "earn" money through that. - if (discount.DiscountType == DiscountType.AssignedToOrderTotal || discount.DiscountType == DiscountType.AssignedToOrderSubTotal) - { - var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, storeId); - - if (cart.Any(x => x.Item.Product.IsGiftCard)) - return false; - } - return true; } From 2a8f648f10e106ac5a0e367827fb260b965ec1f9 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 22 Jun 2016 11:38:15 +0200 Subject: [PATCH 151/185] Discount rule has spent amount including sub total option can cause wrong discount calculation if the cart contains a product several times --- changelog.md | 1 + .../Discounts/DiscountService.cs | 3 +- .../SmartStore.DiscountRules/Description.txt | 2 +- .../Providers/HadSpentAmountRule.cs | 54 +++++++++++++------ 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/changelog.md b/changelog.md index db0febbc6f..097390ed9d 100644 --- a/changelog.md +++ b/changelog.md @@ -60,6 +60,7 @@ * Private messages: Fixes "No route in the route table matches the supplied values" * Payone: Hash string incorrect for frontend API payments where the order has more than 9 products * Export mail notification: Download link not working if SSL is enabled +* Discount rule has spent amount including sub total option can cause wrong discount calculation if the cart contains a product several times ## SmartStore.NET 2.5 diff --git a/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs b/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs index b973e84153..ffa0fa2615 100644 --- a/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs +++ b/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs @@ -363,7 +363,8 @@ public virtual bool IsDiscountValid(Discount discount, Customer customer, string Store = store }; - if (!requirementRule.Value.CheckRequirement(request)) + // TODO: cache result... CheckRequirement is very often called + if (!requirementRule.Value.CheckRequirement(request)) return false; } diff --git a/src/Plugins/SmartStore.DiscountRules/Description.txt b/src/Plugins/SmartStore.DiscountRules/Description.txt index 2a17b4fcd9..a2336d8b8e 100644 --- a/src/Plugins/SmartStore.DiscountRules/Description.txt +++ b/src/Plugins/SmartStore.DiscountRules/Description.txt @@ -2,7 +2,7 @@ Description: Contains common discount requirement rule providers like "Billing country is", "Customer role is", "Had spent amount" etc. Group: Marketing SystemName: SmartStore.DiscountRules -Version: 2.5.0 +Version: 2.5.0.1 MinAppVersion: 2.5.0 DisplayOrder: 0 FileName: SmartStore.DiscountRules.dll diff --git a/src/Plugins/SmartStore.DiscountRules/Providers/HadSpentAmountRule.cs b/src/Plugins/SmartStore.DiscountRules/Providers/HadSpentAmountRule.cs index b67d2a7efd..69d7f2614b 100644 --- a/src/Plugins/SmartStore.DiscountRules/Providers/HadSpentAmountRule.cs +++ b/src/Plugins/SmartStore.DiscountRules/Providers/HadSpentAmountRule.cs @@ -1,15 +1,16 @@ using System; +using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -using SmartStore.Services.Discounts; +using SmartStore.DiscountRules.Settings; +using SmartStore.Services.Catalog; using SmartStore.Services.Customers; +using SmartStore.Services.Discounts; using SmartStore.Services.Orders; -using SmartStore.Services.Catalog; using SmartStore.Services.Tax; -using SmartStore.Core.Localization; -using SmartStore.DiscountRules.Settings; -using Newtonsoft.Json; namespace SmartStore.DiscountRules { @@ -21,15 +22,18 @@ public partial class HadSpentAmountRule : DiscountRequirementRuleBase private readonly IOrderService _orderService; private readonly IPriceCalculationService _priceCalculationService; private readonly ITaxService _taxService; + private readonly ILogger _logger; public HadSpentAmountRule( IOrderService orderService, IPriceCalculationService priceCalculationService, - ITaxService taxService) + ITaxService taxService, + ILogger logger) { - this._orderService = orderService; - this._priceCalculationService = priceCalculationService; - this._taxService = taxService; + _orderService = orderService; + _priceCalculationService = priceCalculationService; + _taxService = taxService; + _logger = logger; } public override bool CheckRequirement(CheckDiscountRequirementRequest request) @@ -88,14 +92,34 @@ private bool CheckTotalHistoryRequirement(CheckDiscountRequirementRequest reques private bool CheckCurrentSubTotalRequirement(CheckDiscountRequirementRequest request) { - var cartItems = request.Customer.GetCartItems(ShoppingCartType.ShoppingCart, request.Store.Id); + var spentAmount = decimal.Zero; - decimal spentAmount = decimal.Zero; - decimal taxRate = decimal.Zero; - foreach (var sci in cartItems) + try { - // includeDiscounts == true produces a stack overflow! - spentAmount += sci.Item.Quantity * _taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetUnitPrice(sci, false), out taxRate); + var taxRate = decimal.Zero; + var cartItems = request.Customer.GetCartItems(ShoppingCartType.ShoppingCart, request.Store.Id); + + foreach (var cartItem in cartItems) + { + var product = cartItem.Item.Product; + Dictionary mergedValuesClone = null; + + // we must reapply merged values because CheckCurrentSubTotalRequirement uses price calculation and is called by it itself. + // this can cause wrong discount calculation if the cart contains a product several times. + if (product.MergedDataValues != null) + mergedValuesClone = new Dictionary(product.MergedDataValues); + + // includeDiscounts == true produces a stack overflow! + spentAmount += cartItem.Item.Quantity * _taxService.GetProductPrice(product, _priceCalculationService.GetUnitPrice(cartItem, false), out taxRate); + + if (mergedValuesClone != null) + product.MergedDataValues = new Dictionary(mergedValuesClone); + } + } + catch (Exception exception) + { + _logger.Error(exception); + return false; } return spentAmount >= request.DiscountRequirement.SpentAmount; From c3753153b07b91e2cf0fbf0122f1eaf0a4509959 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 22 Jun 2016 18:11:46 +0200 Subject: [PATCH 152/185] CronExpression.GetFriendlyDescription() should not throw exception when expression is empty --- .../SmartStore.Services/Tasks/CronExpression.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Tasks/CronExpression.cs b/src/Libraries/SmartStore.Services/Tasks/CronExpression.cs index 25243b0ad5..c35b4b7128 100644 --- a/src/Libraries/SmartStore.Services/Tasks/CronExpression.cs +++ b/src/Libraries/SmartStore.Services/Tasks/CronExpression.cs @@ -59,14 +59,16 @@ public static string GetFriendlyDescription(string expression) Use24HourTimeFormat = Thread.CurrentThread.CurrentUICulture.DateTimeFormat.AMDesignator.IsEmpty() }; - try - { - return ExpressionDescriptor.GetDescription(expression, options); - } - catch + if (expression.HasValue()) { - return null; + try + { + return ExpressionDescriptor.GetDescription(expression, options); + } + catch { } } + + return "?"; } } From ecc787a1ad4ec3e02b369ac1b8651852c5c39831 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 22 Jun 2016 18:33:35 +0200 Subject: [PATCH 153/185] Updated changelog --- changelog.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/changelog.md b/changelog.md index 097390ed9d..8efe9bbc11 100644 --- a/changelog.md +++ b/changelog.md @@ -1,13 +1,14 @@ # Release Notes -## SmartStore.NET 2.5.1 +## SmartStore.NET 2.6 ### Highlights -* Major improvements in Importer: better field mapping, higher performance, bug fixes etc. -* #637 Integrated PayPal PLUS payment provider -* #932 Implement paydirekt payment plugin -* New Viveum payment plugin -* #979 Implement BeezUP product feed +* Major improvements in Importer & Exporter: better field mapping, higher performance, bug fixes etc. +* 'PayPal PLUS' payment plugin +* 'paydirekt' payment plugin +* 'Viveum' payment plugin +* 'BeezUP' export provider +* (Dev) Publishing SmartStore.Web from within Visual Studio now deploys the project correctly. No need to execute ClickToBuild.cmd anymore. Just publish directly to any target, including Azure. ### New Features * #961 Fix "Open Redirection Vulnerability" From c2ef0419896c0cc383dc327dca8afe94266f3cf5 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 22 Jun 2016 18:58:07 +0200 Subject: [PATCH 154/185] Updated ua-parser-regexes.yaml --- .../App_Data/ua-parser.regexes.yaml | 53 +++++++++++++++---- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/src/Presentation/SmartStore.Web/App_Data/ua-parser.regexes.yaml b/src/Presentation/SmartStore.Web/App_Data/ua-parser.regexes.yaml index 6ef74a0d89..c160ade711 100644 --- a/src/Presentation/SmartStore.Web/App_Data/ua-parser.regexes.yaml +++ b/src/Presentation/SmartStore.Web/App_Data/ua-parser.regexes.yaml @@ -17,6 +17,10 @@ user_agent_parsers: - regex: 'Google.*/\+/web/snippet' family_replacement: 'GooglePlusBot' + # Twitter + - regex: '(Twitterbot)/(\d+)\.(\d+)' + family_replacement: 'TwitterBot' + # Bots Pattern '/name-0.0' - regex: '/((?:Ant-)?Nutch|[A-z]+[Bb]ot|[A-z]+[Ss]pider|Axtaris|fetchurl|Isara|ShopSalad|Tailsweep)[ \-](\d+)(?:\.(\d+)(?:\.(\d+))?)?' # Bots Pattern 'name/0.0' @@ -106,7 +110,7 @@ user_agent_parsers: # Opera will stop at 9.80 and hide the real version in the Version string. # see: http://dev.opera.com/articles/view/opera-ua-string-changes/ - regex: '(Opera Tablet).*Version/(\d+)\.(\d+)(?:\.(\d+))?' - - regex: '(Opera Mini)(?:/att)?/(\d+)?(?:\.(\d+))?(?:\.(\d+))?' + - regex: '(Opera Mini)(?:/att)?/?(\d+)?(?:\.(\d+))?(?:\.(\d+))?' - regex: '(Opera)/.+Opera Mobi.+Version/(\d+)\.(\d+)' family_replacement: 'Opera Mobile' - regex: '(Opera)/(\d+)\.(\d+).+Opera Mobi' @@ -205,6 +209,10 @@ user_agent_parsers: - regex: 'Windows Phone .*(Edge)/(\d+)\.(\d+)' family_replacement: 'Edge Mobile' + # Samsung Internet (based on Chrome, but lacking some features) + - regex: '(SamsungBrowser)/(\d+)\.(\d+)' + family_replacement: 'Samsung Internet' + # Chrome Mobile - regex: '(CrMo)/(\d+)\.(\d+)\.(\d+)\.(\d+)' family_replacement: 'Chrome Mobile' @@ -272,12 +280,12 @@ user_agent_parsers: - regex: '(AdobeAIR|FireWeb|Jasmine|ANTGalio|Midori|Fresco|Lobo|PaleMoon|Maxthon|Lynx|OmniWeb|Dillo|Camino|Demeter|Fluid|Fennec|Epiphany|Shiira|Sunrise|Spotify|Flock|Netscape|Lunascape|WebPilot|NetFront|Netfront|Konqueror|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|Opera Mini|iCab|NetNewsWire|ThunderBrowse|Iris|UP\.Browser|Bunjalloo|Google Earth|Raven for Mac|Openwave)/(\d+)\.(\d+)\.(\d+)' # Outlook 2007 - - regex: 'MSOffice 12' + - regex: 'Microsoft Office Outlook 12\.\d+\.\d+|MSOffice 12' family_replacement: 'Outlook' v1_replacement: '2007' # Outlook 2010 - - regex: 'MSOffice 14' + - regex: 'Microsoft Outlook 14\.\d+\.\d+|MSOffice 14' family_replacement: 'Outlook' v1_replacement: '2010' @@ -286,6 +294,15 @@ user_agent_parsers: family_replacement: 'Outlook' v1_replacement: '2013' + # Outlook 2016 + - regex: 'Microsoft Outlook (?:Mail )?16\.\d+\.\d+' + family_replacement: 'Outlook' + v1_replacement: '2016' + + # Windows Live Mail + - regex: 'Outlook-Express\/7\.0.*' + family_replacement: 'Windows Live Mail' + # Apple Air Mail - regex: '(Airmail) (\d+)\.(\d+)(?:\.(\d+))?' @@ -299,6 +316,10 @@ user_agent_parsers: # Edge/major_version.minor_version - regex: '(Edge)/(\d+)\.(\d+)' + # Brave Browser https://brave.com/ + - regex: '(brave)/(\d+)\.(\d+)\.(\d+) Chrome' + family_replacement: 'Brave' + # Chrome/Chromium/major_version.minor_version.beta_version - regex: '(Chromium|Chrome)/(\d+)\.(\d+)\.(\d+)' @@ -388,15 +409,15 @@ user_agent_parsers: - regex: '(iPod|iPhone|iPad).+Version/(\d+)\.(\d+)(?:\.(\d+))?.* Safari' family_replacement: 'Mobile Safari' - regex: '(iPod|iPhone|iPad).+Version/(\d+)\.(\d+)(?:\.(\d+))?' - family_replacement: 'Mobile Safari UIWebView' + family_replacement: 'Mobile Safari UI/WKWebView' - regex: '(iPod|iPhone|iPad);.*CPU.*OS (\d+)_(\d+)(?:_(\d+))?.*Mobile.* Safari' family_replacement: 'Mobile Safari' - regex: '(iPod|iPhone|iPad);.*CPU.*OS (\d+)_(\d+)(?:_(\d+))?.*Mobile' - family_replacement: 'Mobile Safari UIWebView' + family_replacement: 'Mobile Safari UI/WKWebView' - regex: '(iPod|iPhone|iPad).* Safari' family_replacement: 'Mobile Safari' - regex: '(iPod|iPhone|iPad)' - family_replacement: 'Mobile Safari UIWebView' + family_replacement: 'Mobile Safari UI/WKWebView' - regex: '(AvantGo) (\d+).(\d+)' @@ -498,7 +519,7 @@ user_agent_parsers: # apple mail - not directly detectable, have it after Safari stuff - regex: '(AppleWebKit)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'AppleMail' + family_replacement: 'Apple Mail' # AFTER THE EDGE CASES ABOVE! # AFTER IE11 @@ -514,6 +535,9 @@ user_agent_parsers: - regex: '(Java)[/ ]{0,1}\d+\.(\d+)\.(\d+)[_-]*([a-zA-Z0-9]+)*' + # Roku Digital-Video-Players https://www.roku.com/ + - regex: '^(Roku)/DVP-(\d+)\.(\d+)' + os_parsers: ########## # HbbTV vendors @@ -981,6 +1005,9 @@ os_parsers: - regex: 'SunOS' os_replacement: 'Solaris' + # Roku Digital-Video-Players https://www.roku.com/ + - regex: '^(Roku)/DVP-(\d+)\.(\d+)' + device_parsers: ######### @@ -2049,7 +2076,7 @@ device_parsers: device_replacement: 'Huawei Ideos$1' brand_replacement: 'Huawei' model_replacement: 'Ideos$1' - - regex: '; *(Orange Daytona|Pulse|Pulse Mini|Vodafone 858|C8500|C8600|C8650|C8660) Build' + - regex: '; *(Orange Daytona|Pulse|Pulse Mini|Vodafone 858|C8500|C8600|C8650|C8660|Nexus 6P) Build' device_replacement: 'Huawei $1' brand_replacement: 'Huawei' model_replacement: '$1' @@ -2571,7 +2598,7 @@ device_parsers: # LG # @ref: http://www.lg.com/uk/mobile ######### - - regex: '; *(E[34][0-9]{2}|LS[6-8][0-9]{2}|VS[6-9][0-9]+[^;/]+|Nexus [45]|GT540f?|Optimus (?:2X|G|4X HD)|OptimusX4HD) *(?:Build|;)' + - regex: '; *(E[34][0-9]{2}|LS[6-8][0-9]{2}|VS[6-9][0-9]+[^;/]+|Nexus 4|Nexus 5X?|GT540f?|Optimus (?:2X|G|4X HD)|OptimusX4HD) *(?:Build|;)' device_replacement: '$1' brand_replacement: 'LG' model_replacement: '$1' @@ -3841,7 +3868,7 @@ device_parsers: # XiaoMi # @ref: http://www.xiaomi.com/event/buyphone ######### - - regex: '; *((MI|HM|MI-ONE|Redmi)[ -](NOTE |Note )?[^;/]*) Build/' + - regex: '; *((MI|HM|MI-ONE|Redmi)[ -](NOTE |Note )?[^;/]*) (Build|MIUI)/' device_replacement: 'XiaoMi $1' brand_replacement: 'XiaoMi' model_replacement: '$1' @@ -4700,6 +4727,10 @@ device_parsers: - regex: '(WebTV)/\d+.\d+' brand_replacement: 'Generic_Inettv' model_replacement: '$1' + # Roku Digital-Video-Players https://www.roku.com/ + - regex: '^(Roku)/DVP-\d+\.\d+' + brand_replacement: 'Generic_Inettv' + model_replacement: '$1' ########## # Generic Tablet @@ -4764,4 +4795,4 @@ device_parsers: regex_flag: 'i' device_replacement: 'Generic Feature Phone' brand_replacement: 'Generic' - model_replacement: 'Feature Phone' + model_replacement: 'Feature Phone' \ No newline at end of file From 97918a561971ee21cbf3b888f50458f108efaec0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 22 Jun 2016 22:24:04 +0200 Subject: [PATCH 155/185] Resolves #986 File uploads possible through /content/filemanager/index.html --- changelog.md | 1 + .../Content/filemanager/LICENSE.txt | 0 .../Content/filemanager/conf.json | 33 + .../css/images/animated-overlay.gif | Bin .../css/images/ui-bg_flat_0_aaaaaa_40x100.png | Bin .../images/ui-bg_flat_75_ffffff_40x100.png | Bin .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin .../images/ui-bg_glass_65_ffffff_1x400.png | Bin .../images/ui-bg_glass_75_dadada_1x400.png | Bin .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin .../css/images/ui-icons_222222_256x240.png | Bin .../css/images/ui-icons_2e83ff_256x240.png | Bin .../css/images/ui-icons_454545_256x240.png | Bin .../css/images/ui-icons_888888_256x240.png | Bin .../css/images/ui-icons_cd0a0a_256x240.png | Bin .../css/jquery-ui-1.10.4.custom.css | 0 .../css/jquery-ui-1.10.4.custom.min.css | 0 .../Content/filemanager/css/main.css | 0 .../Content/filemanager/css/main.min.css | 0 .../images/action-folder-paste.png | Bin .../images/action-folder-rename.png | Bin .../Content/filemanager/images/arrow_down.png | Bin .../Content/filemanager/images/arrow_up.png | Bin .../Content/filemanager/images/blank.gif | Bin .../Content/filemanager/images/copy.png | Bin .../Content/filemanager/images/cut.png | Bin .../Content/filemanager/images/dir-minus.png | Bin .../Content/filemanager/images/dir-plus.png | Bin .../Content/filemanager/images/file-add.png | Bin .../filemanager/images/file-delete.png | Bin .../filemanager/images/file-download.png | Bin .../filemanager/images/file-duplicate.png | Bin .../filemanager/images/file-upload.png | Bin .../filetypes/big/file_extension_3gp.png | Bin .../filetypes/big/file_extension_7z.png | Bin .../filetypes/big/file_extension_ace.png | Bin .../filetypes/big/file_extension_ai.png | Bin .../filetypes/big/file_extension_aif.png | Bin .../filetypes/big/file_extension_aiff.png | Bin .../filetypes/big/file_extension_amr.png | Bin .../filetypes/big/file_extension_asf.png | Bin .../filetypes/big/file_extension_asx.png | Bin .../filetypes/big/file_extension_bat.png | Bin .../filetypes/big/file_extension_bin.png | Bin .../filetypes/big/file_extension_bmp.png | Bin .../filetypes/big/file_extension_bup.png | Bin .../filetypes/big/file_extension_cab.png | Bin .../filetypes/big/file_extension_cbr.png | Bin .../filetypes/big/file_extension_cda.png | Bin .../filetypes/big/file_extension_cdl.png | Bin .../filetypes/big/file_extension_cdr.png | Bin .../filetypes/big/file_extension_chm.png | Bin .../filetypes/big/file_extension_dat.png | Bin .../filetypes/big/file_extension_divx.png | Bin .../filetypes/big/file_extension_dll.png | Bin .../filetypes/big/file_extension_dmg.png | Bin .../filetypes/big/file_extension_doc.png | Bin .../filetypes/big/file_extension_dss.png | Bin .../filetypes/big/file_extension_dvf.png | Bin .../filetypes/big/file_extension_dwg.png | Bin .../filetypes/big/file_extension_eml.png | Bin .../filetypes/big/file_extension_eps.png | Bin .../filetypes/big/file_extension_exe.png | Bin .../filetypes/big/file_extension_fla.png | Bin .../filetypes/big/file_extension_flv.png | Bin .../filetypes/big/file_extension_gif.png | Bin .../filetypes/big/file_extension_gz.png | Bin .../filetypes/big/file_extension_hqx.png | Bin .../filetypes/big/file_extension_htm.png | Bin .../filetypes/big/file_extension_html.png | Bin .../filetypes/big/file_extension_ifo.png | Bin .../filetypes/big/file_extension_indd.png | Bin .../filetypes/big/file_extension_iso.png | Bin .../filetypes/big/file_extension_jar.png | Bin .../filetypes/big/file_extension_jpeg.png | Bin .../filetypes/big/file_extension_jpg.png | Bin .../filetypes/big/file_extension_lnk.png | Bin .../filetypes/big/file_extension_log.png | Bin .../filetypes/big/file_extension_m4a.png | Bin .../filetypes/big/file_extension_m4b.png | Bin .../filetypes/big/file_extension_m4p.png | Bin .../filetypes/big/file_extension_m4v.png | Bin .../filetypes/big/file_extension_mcd.png | Bin .../filetypes/big/file_extension_mdb.png | Bin .../filetypes/big/file_extension_mid.png | Bin .../filetypes/big/file_extension_mov.png | Bin .../filetypes/big/file_extension_mp2.png | Bin .../filetypes/big/file_extension_mp4.png | Bin .../filetypes/big/file_extension_mpeg.png | Bin .../filetypes/big/file_extension_mpg.png | Bin .../filetypes/big/file_extension_msi.png | Bin .../filetypes/big/file_extension_mswmm.png | Bin .../filetypes/big/file_extension_ogg.png | Bin .../filetypes/big/file_extension_pdf.png | Bin .../filetypes/big/file_extension_png.png | Bin .../filetypes/big/file_extension_pps.png | Bin .../filetypes/big/file_extension_ps.png | Bin .../filetypes/big/file_extension_psd.png | Bin .../filetypes/big/file_extension_pst.png | Bin .../filetypes/big/file_extension_ptb.png | Bin .../filetypes/big/file_extension_pub.png | Bin .../filetypes/big/file_extension_qbb.png | Bin .../filetypes/big/file_extension_qbw.png | Bin .../filetypes/big/file_extension_qxd.png | Bin .../filetypes/big/file_extension_ram.png | Bin .../filetypes/big/file_extension_rar.png | Bin .../filetypes/big/file_extension_rm.png | Bin .../filetypes/big/file_extension_rmvb.png | Bin .../filetypes/big/file_extension_rtf.png | Bin .../filetypes/big/file_extension_sea.png | Bin .../filetypes/big/file_extension_ses.png | Bin .../filetypes/big/file_extension_sit.png | Bin .../filetypes/big/file_extension_sitx.png | Bin .../filetypes/big/file_extension_ss.png | Bin .../filetypes/big/file_extension_swf.png | Bin .../filetypes/big/file_extension_tgz.png | Bin .../filetypes/big/file_extension_thm.png | Bin .../filetypes/big/file_extension_tif.png | Bin .../filetypes/big/file_extension_tmp.png | Bin .../filetypes/big/file_extension_torrent.png | Bin .../filetypes/big/file_extension_ttf.png | Bin .../filetypes/big/file_extension_txt.png | Bin .../filetypes/big/file_extension_vcd.png | Bin .../filetypes/big/file_extension_vob.png | Bin .../filetypes/big/file_extension_wav.png | Bin .../filetypes/big/file_extension_wma.png | Bin .../filetypes/big/file_extension_wmv.png | Bin .../filetypes/big/file_extension_wps.png | Bin .../filetypes/big/file_extension_xls.png | Bin .../filetypes/big/file_extension_xpi.png | Bin .../filetypes/big/file_extension_zip.png | Bin .../images/filetypes/big/unknown.png | Bin .../images/filetypes/file_extension_3gp.png | Bin .../images/filetypes/file_extension_7z.png | Bin .../images/filetypes/file_extension_ace.png | Bin .../images/filetypes/file_extension_ai.png | Bin .../images/filetypes/file_extension_aif.png | Bin .../images/filetypes/file_extension_aiff.png | Bin .../images/filetypes/file_extension_amr.png | Bin .../images/filetypes/file_extension_asf.png | Bin .../images/filetypes/file_extension_asx.png | Bin .../images/filetypes/file_extension_bat.png | Bin .../images/filetypes/file_extension_bin.png | Bin .../images/filetypes/file_extension_bmp.png | Bin .../images/filetypes/file_extension_bup.png | Bin .../images/filetypes/file_extension_cab.png | Bin .../images/filetypes/file_extension_cbr.png | Bin .../images/filetypes/file_extension_cda.png | Bin .../images/filetypes/file_extension_cdl.png | Bin .../images/filetypes/file_extension_cdr.png | Bin .../images/filetypes/file_extension_chm.png | Bin .../images/filetypes/file_extension_dat.png | Bin .../images/filetypes/file_extension_divx.png | Bin .../images/filetypes/file_extension_dll.png | Bin .../images/filetypes/file_extension_dmg.png | Bin .../images/filetypes/file_extension_doc.png | Bin .../images/filetypes/file_extension_dss.png | Bin .../images/filetypes/file_extension_dvf.png | Bin .../images/filetypes/file_extension_dwg.png | Bin .../images/filetypes/file_extension_eml.png | Bin .../images/filetypes/file_extension_eps.png | Bin .../images/filetypes/file_extension_exe.png | Bin .../images/filetypes/file_extension_fla.png | Bin .../images/filetypes/file_extension_flv.png | Bin .../images/filetypes/file_extension_gif.png | Bin .../images/filetypes/file_extension_gz.png | Bin .../images/filetypes/file_extension_hqx.png | Bin .../images/filetypes/file_extension_htm.png | Bin .../images/filetypes/file_extension_html.png | Bin .../images/filetypes/file_extension_ifo.png | Bin .../images/filetypes/file_extension_indd.png | Bin .../images/filetypes/file_extension_iso.png | Bin .../images/filetypes/file_extension_jar.png | Bin .../images/filetypes/file_extension_jpeg.png | Bin .../images/filetypes/file_extension_jpg.png | Bin .../images/filetypes/file_extension_lnk.png | Bin .../images/filetypes/file_extension_log.png | Bin .../images/filetypes/file_extension_m4a.png | Bin .../images/filetypes/file_extension_m4b.png | Bin .../images/filetypes/file_extension_m4p.png | Bin .../images/filetypes/file_extension_m4v.png | Bin .../images/filetypes/file_extension_mcd.png | Bin .../images/filetypes/file_extension_mdb.png | Bin .../images/filetypes/file_extension_mid.png | Bin .../images/filetypes/file_extension_mov.png | Bin .../images/filetypes/file_extension_mp2.png | Bin .../images/filetypes/file_extension_mp4.png | Bin .../images/filetypes/file_extension_mpeg.png | Bin .../images/filetypes/file_extension_mpg.png | Bin .../images/filetypes/file_extension_msi.png | Bin .../images/filetypes/file_extension_mswmm.png | Bin .../images/filetypes/file_extension_ogg.png | Bin .../images/filetypes/file_extension_pdf.png | Bin .../images/filetypes/file_extension_png.png | Bin .../images/filetypes/file_extension_pps.png | Bin .../images/filetypes/file_extension_ps.png | Bin .../images/filetypes/file_extension_psd.png | Bin .../images/filetypes/file_extension_pst.png | Bin .../images/filetypes/file_extension_ptb.png | Bin .../images/filetypes/file_extension_pub.png | Bin .../images/filetypes/file_extension_qbb.png | Bin .../images/filetypes/file_extension_qbw.png | Bin .../images/filetypes/file_extension_qxd.png | Bin .../images/filetypes/file_extension_ram.png | Bin .../images/filetypes/file_extension_rar.png | Bin .../images/filetypes/file_extension_rm.png | Bin .../images/filetypes/file_extension_rmvb.png | Bin .../images/filetypes/file_extension_rtf.png | Bin .../images/filetypes/file_extension_sea.png | Bin .../images/filetypes/file_extension_ses.png | Bin .../images/filetypes/file_extension_sit.png | Bin .../images/filetypes/file_extension_sitx.png | Bin .../images/filetypes/file_extension_ss.png | Bin .../images/filetypes/file_extension_swf.png | Bin .../images/filetypes/file_extension_tgz.png | Bin .../images/filetypes/file_extension_thm.png | Bin .../images/filetypes/file_extension_tif.png | Bin .../images/filetypes/file_extension_tmp.png | Bin .../filetypes/file_extension_torrent.png | Bin .../images/filetypes/file_extension_ttf.png | Bin .../images/filetypes/file_extension_txt.png | Bin .../images/filetypes/file_extension_vcd.png | Bin .../images/filetypes/file_extension_vob.png | Bin .../images/filetypes/file_extension_wav.png | Bin .../images/filetypes/file_extension_wma.png | Bin .../images/filetypes/file_extension_wmv.png | Bin .../images/filetypes/file_extension_wps.png | Bin .../images/filetypes/file_extension_xls.png | Bin .../images/filetypes/file_extension_xpi.png | Bin .../images/filetypes/file_extension_zip.png | Bin .../filemanager/images/filetypes/unknown.png | Bin .../Content/filemanager/images/find.png | Bin .../Content/filemanager/images/folder-add.png | Bin .../filemanager/images/folder-delete.png | Bin .../filemanager/images/folder-download.png | Bin .../filemanager/images/folder-green.png | Bin .../filemanager/images/folder-opened.png | Bin .../Content/filemanager/images/folder.png | Bin .../filemanager/images/loading-dir.gif | Bin .../Content/filemanager/images/loading.gif | Bin .../Content/filemanager/images/paste.png | Bin .../Content/filemanager/images/preview.png | Bin .../Content/filemanager/images/rename.png | Bin .../Content/filemanager/images/search.png | Bin .../Content/filemanager/images/select.png | Bin .../Content/filemanager/images/upload-big.png | Bin .../Content/filemanager/images/view-list.png | Bin .../Content/filemanager/images/view-tile.png | Bin .../Content/filemanager/index.html | 0 .../Content/filemanager/js/custom.js | 0 .../Content/filemanager/js/directory.js | 0 .../Content/filemanager/js/file.js | 0 .../filemanager/js/fileman-modified.zip | Bin .../Content/filemanager/js/filetypes.js | 0 .../filemanager/js/jquery-1.10.2.min.js | 0 .../filemanager/js/jquery-dateFormat.min.js | 0 .../js/jquery-ui-1.10.4.custom.min.js | 0 .../Content/filemanager/js/main.js | 0 .../Content/filemanager/js/main.min.js | 0 .../Content/filemanager/js/mini-main.js | 0 .../Content/filemanager/js/tiny_mce_popup.js | 0 .../Content/filemanager/js/utils.js | 0 .../Content/filemanager/lang/bg.json | 0 .../Content/filemanager/lang/de.json | 0 .../Content/filemanager/lang/en.json | 0 .../Content/filemanager/lang/fr.json | 0 .../Controllers/RoxyFileManagerController.cs | 828 ++++++++++++++++++ .../Administration/SmartStore.Admin.csproj | 269 ++++++ .../Shared/EditorTemplates/RichEditor.cshtml | 2 +- .../Content/filemanager/asp_net/main.ashx | 719 --------------- .../Content/filemanager/conf.json | 34 - .../SmartStore.Web/SmartStore.Web.csproj | 268 ------ 274 files changed, 1132 insertions(+), 1022 deletions(-) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/LICENSE.txt (100%) create mode 100644 src/Presentation/SmartStore.Web/Administration/Content/filemanager/conf.json rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/animated-overlay.gif (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-bg_flat_0_aaaaaa_40x100.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-bg_flat_75_ffffff_40x100.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-bg_glass_55_fbf9ee_1x400.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-bg_glass_65_ffffff_1x400.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-bg_glass_75_dadada_1x400.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-bg_glass_75_e6e6e6_1x400.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-bg_glass_95_fef1ec_1x400.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-icons_222222_256x240.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-icons_2e83ff_256x240.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-icons_454545_256x240.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-icons_888888_256x240.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-icons_cd0a0a_256x240.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/jquery-ui-1.10.4.custom.css (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/jquery-ui-1.10.4.custom.min.css (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/main.css (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/main.min.css (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/action-folder-paste.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/action-folder-rename.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/arrow_down.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/arrow_up.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/blank.gif (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/copy.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/cut.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/dir-minus.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/dir-plus.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/file-add.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/file-delete.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/file-download.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/file-duplicate.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/file-upload.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_3gp.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_7z.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ace.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ai.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_aif.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_aiff.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_amr.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_asf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_asx.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_bat.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_bin.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_bmp.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_bup.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_cab.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_cbr.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_cda.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_cdl.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_cdr.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_chm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_dat.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_divx.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_dll.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_dmg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_doc.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_dss.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_dvf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_dwg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_eml.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_eps.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_exe.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_fla.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_flv.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_gif.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_gz.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_hqx.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_htm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_html.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ifo.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_indd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_iso.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_jar.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_jpeg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_jpg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_lnk.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_log.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_m4a.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_m4b.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_m4p.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_m4v.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mcd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mdb.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mid.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mov.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mp2.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mp4.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mpeg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mpg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_msi.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mswmm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ogg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_pdf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_png.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_pps.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ps.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_psd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_pst.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ptb.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_pub.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_qbb.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_qbw.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_qxd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ram.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_rar.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_rm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_rmvb.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_rtf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_sea.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ses.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_sit.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_sitx.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ss.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_swf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_tgz.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_thm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_tif.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_tmp.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_torrent.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ttf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_txt.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_vcd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_vob.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_wav.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_wma.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_wmv.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_wps.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_xls.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_xpi.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_zip.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/unknown.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_3gp.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_7z.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ace.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ai.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_aif.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_aiff.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_amr.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_asf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_asx.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_bat.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_bin.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_bmp.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_bup.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_cab.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_cbr.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_cda.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_cdl.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_cdr.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_chm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_dat.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_divx.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_dll.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_dmg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_doc.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_dss.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_dvf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_dwg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_eml.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_eps.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_exe.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_fla.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_flv.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_gif.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_gz.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_hqx.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_htm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_html.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ifo.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_indd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_iso.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_jar.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_jpeg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_jpg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_lnk.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_log.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_m4a.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_m4b.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_m4p.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_m4v.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mcd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mdb.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mid.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mov.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mp2.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mp4.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mpeg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mpg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_msi.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mswmm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ogg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_pdf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_png.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_pps.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ps.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_psd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_pst.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ptb.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_pub.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_qbb.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_qbw.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_qxd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ram.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_rar.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_rm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_rmvb.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_rtf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_sea.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ses.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_sit.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_sitx.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ss.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_swf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_tgz.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_thm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_tif.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_tmp.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_torrent.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ttf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_txt.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_vcd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_vob.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_wav.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_wma.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_wmv.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_wps.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_xls.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_xpi.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_zip.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/unknown.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/find.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/folder-add.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/folder-delete.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/folder-download.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/folder-green.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/folder-opened.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/folder.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/loading-dir.gif (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/loading.gif (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/paste.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/preview.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/rename.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/search.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/select.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/upload-big.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/view-list.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/view-tile.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/index.html (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/custom.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/directory.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/file.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/fileman-modified.zip (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/filetypes.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/jquery-1.10.2.min.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/jquery-dateFormat.min.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/jquery-ui-1.10.4.custom.min.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/main.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/main.min.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/mini-main.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/tiny_mce_popup.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/utils.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/lang/bg.json (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/lang/de.json (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/lang/en.json (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/lang/fr.json (100%) create mode 100644 src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs delete mode 100644 src/Presentation/SmartStore.Web/Content/filemanager/asp_net/main.ashx delete mode 100644 src/Presentation/SmartStore.Web/Content/filemanager/conf.json diff --git a/changelog.md b/changelog.md index 8efe9bbc11..47f492ff48 100644 --- a/changelog.md +++ b/changelog.md @@ -62,6 +62,7 @@ * Payone: Hash string incorrect for frontend API payments where the order has more than 9 products * Export mail notification: Download link not working if SSL is enabled * Discount rule has spent amount including sub total option can cause wrong discount calculation if the cart contains a product several times +* #986 File uploads possible through /content/filemanager/index.html ## SmartStore.NET 2.5 diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/LICENSE.txt b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/LICENSE.txt similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/LICENSE.txt rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/LICENSE.txt diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/conf.json b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/conf.json new file mode 100644 index 0000000000..c08220bdfe --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/conf.json @@ -0,0 +1,33 @@ +{ + "FILES_ROOT": "~/Media/Uploaded", + "SESSION_PATH_KEY": "", + "THUMBS_VIEW_WIDTH": "140", + "THUMBS_VIEW_HEIGHT": "120", + "PREVIEW_THUMB_WIDTH": "300", + "PREVIEW_THUMB_HEIGHT": "200", + "MAX_IMAGE_WIDTH": "0", + "MAX_IMAGE_HEIGHT": "0", + "INTEGRATION": "ckeditor", + "DIRLIST": "../../../Admin/RoxyFileManager/ProcessRequest?a=DIRLIST", + "CREATEDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=CREATEDIR", + "DELETEDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=DELETEDIR", + "MOVEDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=MOVEDIR", + "COPYDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=COPYDIR", + "RENAMEDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=RENAMEDIR", + "FILESLIST": "../../../Admin/RoxyFileManager/ProcessRequest?a=FILESLIST", + "UPLOAD": "../../../Admin/RoxyFileManager/ProcessRequest?a=UPLOAD", + "DOWNLOAD": "../../../Admin/RoxyFileManager/ProcessRequest?a=DOWNLOAD", + "DOWNLOADDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=DOWNLOADDIR", + "DELETEFILE": "../../../Admin/RoxyFileManager/ProcessRequest?a=DELETEFILE", + "MOVEFILE": "../../../Admin/RoxyFileManager/ProcessRequest?a=MOVEFILE", + "COPYFILE": "../../../Admin/RoxyFileManager/ProcessRequest?a=COPYFILE", + "RENAMEFILE": "../../../Admin/RoxyFileManager/ProcessRequest?a=RENAMEFILE", + "GENERATETHUMB": "../../../Admin/RoxyFileManager/ProcessRequest?a=GENERATETHUMB", + "DEFAULTVIEW": "list", + "FORBIDDEN_UPLOADS": "zip js jsp jsb mhtml mht xhtml xht php phtml php3 php4 php5 phps shtml jhtml pl sh py cgi exe scr dll msi vbs bat com pif cmd vxd cpl htpasswd htaccess", + "ALLOWED_UPLOADS": "", + "FILEPERMISSIONS": "0644", + "DIRPERMISSIONS": "0755", + "LANG": "auto", + "DATEFORMAT": "dd.MM.yyyy HH:mm" +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/animated-overlay.gif b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/animated-overlay.gif similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/animated-overlay.gif rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/animated-overlay.gif diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_flat_0_aaaaaa_40x100.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_flat_0_aaaaaa_40x100.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_flat_0_aaaaaa_40x100.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_flat_0_aaaaaa_40x100.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_flat_75_ffffff_40x100.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_flat_75_ffffff_40x100.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_flat_75_ffffff_40x100.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_flat_75_ffffff_40x100.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_55_fbf9ee_1x400.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_55_fbf9ee_1x400.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_55_fbf9ee_1x400.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_55_fbf9ee_1x400.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_65_ffffff_1x400.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_65_ffffff_1x400.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_65_ffffff_1x400.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_65_ffffff_1x400.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_75_dadada_1x400.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_75_dadada_1x400.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_75_dadada_1x400.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_75_dadada_1x400.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_75_e6e6e6_1x400.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_75_e6e6e6_1x400.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_75_e6e6e6_1x400.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_75_e6e6e6_1x400.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_95_fef1ec_1x400.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_95_fef1ec_1x400.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_95_fef1ec_1x400.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_95_fef1ec_1x400.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_222222_256x240.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_222222_256x240.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_222222_256x240.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_222222_256x240.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_2e83ff_256x240.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_2e83ff_256x240.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_2e83ff_256x240.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_2e83ff_256x240.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_454545_256x240.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_454545_256x240.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_454545_256x240.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_454545_256x240.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_888888_256x240.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_888888_256x240.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_888888_256x240.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_888888_256x240.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_cd0a0a_256x240.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_cd0a0a_256x240.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_cd0a0a_256x240.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_cd0a0a_256x240.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/jquery-ui-1.10.4.custom.css b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/jquery-ui-1.10.4.custom.css similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/jquery-ui-1.10.4.custom.css rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/jquery-ui-1.10.4.custom.css diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/jquery-ui-1.10.4.custom.min.css b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/jquery-ui-1.10.4.custom.min.css similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/jquery-ui-1.10.4.custom.min.css rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/jquery-ui-1.10.4.custom.min.css diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/main.css b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/main.css similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/main.css rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/main.css diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/main.min.css b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/main.min.css similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/main.min.css rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/main.min.css diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/action-folder-paste.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-paste.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/action-folder-paste.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-paste.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/action-folder-rename.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-rename.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/action-folder-rename.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-rename.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/arrow_down.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_down.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/arrow_down.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_down.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/arrow_up.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_up.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/arrow_up.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_up.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/blank.gif b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/blank.gif similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/blank.gif rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/blank.gif diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/copy.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/copy.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/copy.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/copy.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/cut.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/cut.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/cut.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/cut.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/dir-minus.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-minus.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/dir-minus.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-minus.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/dir-plus.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-plus.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/dir-plus.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-plus.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/file-add.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-add.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/file-add.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-add.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/file-delete.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-delete.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/file-delete.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-delete.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/file-download.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-download.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/file-download.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-download.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/file-duplicate.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-duplicate.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/file-duplicate.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-duplicate.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/file-upload.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-upload.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/file-upload.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-upload.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_3gp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_3gp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_3gp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_3gp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_7z.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_7z.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_7z.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_7z.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ace.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ace.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ace.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ace.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ai.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ai.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ai.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ai.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_aif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_aif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_aiff.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aiff.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_aiff.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aiff.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_amr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_amr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_amr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_amr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_asf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_asf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_asx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_asx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bat.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bat.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bat.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bat.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bin.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bin.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bin.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bin.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bmp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bmp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bmp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bmp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bup.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bup.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bup.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bup.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cab.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cab.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cab.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cab.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cbr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cbr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cbr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cbr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cda.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cda.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cda.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cda.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cdl.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdl.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cdl.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdl.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cdr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cdr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_chm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_chm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_chm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_chm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dat.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dat.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dat.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dat.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_divx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_divx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_divx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_divx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dll.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dll.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dll.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dll.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dmg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dmg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dmg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dmg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_doc.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_doc.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_doc.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_doc.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dss.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dss.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dss.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dss.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dvf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dvf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dvf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dvf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dwg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dwg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dwg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dwg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_eml.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eml.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_eml.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eml.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_eps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_eps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_exe.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_exe.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_exe.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_exe.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_fla.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_fla.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_fla.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_fla.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_flv.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_flv.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_flv.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_flv.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_gif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_gif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_gz.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gz.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_gz.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gz.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_hqx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_hqx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_hqx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_hqx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_htm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_htm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_htm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_htm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_html.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_html.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_html.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_html.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ifo.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ifo.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ifo.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ifo.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_indd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_indd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_indd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_indd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_iso.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_iso.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_iso.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_iso.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jar.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jar.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jar.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jar.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jpeg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpeg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jpeg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpeg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jpg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jpg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_lnk.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_lnk.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_lnk.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_lnk.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_log.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_log.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_log.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_log.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4a.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4a.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4a.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4a.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4b.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4b.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4b.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4b.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4p.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4p.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4p.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4p.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4v.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4v.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4v.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4v.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mcd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mcd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mcd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mcd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mdb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mdb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mdb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mdb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mid.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mid.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mid.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mid.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mov.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mov.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mov.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mov.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mp2.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp2.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mp2.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp2.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mp4.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp4.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mp4.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp4.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mpeg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpeg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mpeg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpeg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mpg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mpg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_msi.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_msi.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_msi.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_msi.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mswmm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mswmm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mswmm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mswmm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ogg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ogg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ogg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ogg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pdf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pdf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pdf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pdf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_png.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_png.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_png.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_png.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_psd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_psd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_psd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_psd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pst.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pst.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pst.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pst.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ptb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ptb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ptb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ptb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pub.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pub.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pub.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pub.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qbb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qbb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qbw.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbw.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qbw.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbw.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qxd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qxd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qxd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qxd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ram.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ram.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ram.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ram.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rar.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rar.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rar.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rar.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rmvb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rmvb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rmvb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rmvb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rtf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rtf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rtf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rtf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sea.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sea.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sea.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sea.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ses.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ses.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ses.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ses.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sit.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sit.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sit.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sit.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sitx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sitx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sitx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sitx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ss.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ss.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ss.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ss.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_swf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_swf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_swf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_swf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tgz.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tgz.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tgz.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tgz.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_thm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_thm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_thm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_thm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tmp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tmp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tmp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tmp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_torrent.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_torrent.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_torrent.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_torrent.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ttf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ttf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ttf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ttf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_txt.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_txt.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_txt.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_txt.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_vcd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vcd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_vcd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vcd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_vob.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vob.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_vob.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vob.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wav.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wav.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wav.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wav.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wma.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wma.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wma.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wma.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wmv.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wmv.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wmv.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wmv.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_xls.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xls.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_xls.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xls.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_xpi.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xpi.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_xpi.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xpi.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_zip.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_zip.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_zip.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_zip.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/unknown.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/unknown.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/unknown.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/unknown.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_3gp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_3gp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_3gp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_3gp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_7z.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_7z.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_7z.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_7z.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ace.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ace.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ace.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ace.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ai.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ai.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ai.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ai.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_aif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_aif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_aiff.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aiff.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_aiff.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aiff.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_amr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_amr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_amr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_amr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_asf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_asf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_asx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_asx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bat.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bat.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bat.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bat.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bin.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bin.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bin.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bin.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bmp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bmp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bmp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bmp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bup.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bup.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bup.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bup.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cab.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cab.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cab.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cab.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cbr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cbr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cbr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cbr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cda.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cda.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cda.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cda.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cdl.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdl.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cdl.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdl.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cdr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cdr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_chm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_chm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_chm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_chm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dat.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dat.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dat.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dat.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_divx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_divx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_divx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_divx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dll.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dll.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dll.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dll.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dmg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dmg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dmg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dmg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_doc.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_doc.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_doc.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_doc.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dss.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dss.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dss.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dss.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dvf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dvf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dvf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dvf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dwg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dwg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dwg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dwg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_eml.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eml.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_eml.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eml.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_eps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_eps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_exe.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_exe.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_exe.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_exe.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_fla.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_fla.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_fla.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_fla.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_flv.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_flv.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_flv.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_flv.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_gif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_gif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_gz.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gz.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_gz.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gz.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_hqx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_hqx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_hqx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_hqx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_htm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_htm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_htm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_htm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_html.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_html.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_html.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_html.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ifo.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ifo.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ifo.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ifo.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_indd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_indd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_indd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_indd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_iso.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_iso.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_iso.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_iso.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jar.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jar.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jar.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jar.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jpeg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpeg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jpeg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpeg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jpg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jpg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_lnk.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_lnk.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_lnk.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_lnk.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_log.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_log.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_log.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_log.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4a.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4a.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4a.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4a.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4b.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4b.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4b.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4b.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4p.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4p.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4p.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4p.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4v.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4v.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4v.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4v.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mcd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mcd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mcd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mcd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mdb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mdb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mdb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mdb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mid.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mid.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mid.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mid.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mov.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mov.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mov.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mov.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mp2.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp2.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mp2.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp2.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mp4.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp4.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mp4.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp4.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mpeg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpeg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mpeg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpeg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mpg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mpg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_msi.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_msi.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_msi.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_msi.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mswmm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mswmm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mswmm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mswmm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ogg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ogg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ogg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ogg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pdf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pdf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pdf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pdf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_png.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_png.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_png.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_png.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_psd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_psd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_psd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_psd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pst.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pst.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pst.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pst.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ptb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ptb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ptb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ptb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pub.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pub.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pub.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pub.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qbb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qbb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qbw.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbw.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qbw.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbw.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qxd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qxd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qxd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qxd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ram.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ram.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ram.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ram.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rar.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rar.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rar.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rar.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rmvb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rmvb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rmvb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rmvb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rtf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rtf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rtf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rtf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sea.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sea.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sea.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sea.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ses.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ses.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ses.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ses.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sit.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sit.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sit.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sit.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sitx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sitx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sitx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sitx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ss.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ss.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ss.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ss.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_swf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_swf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_swf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_swf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tgz.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tgz.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tgz.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tgz.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_thm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_thm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_thm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_thm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tmp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tmp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tmp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tmp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_torrent.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_torrent.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_torrent.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_torrent.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ttf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ttf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ttf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ttf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_txt.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_txt.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_txt.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_txt.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_vcd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vcd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_vcd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vcd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_vob.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vob.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_vob.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vob.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wav.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wav.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wav.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wav.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wma.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wma.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wma.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wma.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wmv.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wmv.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wmv.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wmv.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_xls.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xls.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_xls.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xls.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_xpi.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xpi.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_xpi.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xpi.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_zip.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_zip.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_zip.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_zip.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/unknown.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/unknown.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/unknown.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/unknown.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/find.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/find.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/find.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/find.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder-add.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-add.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder-add.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-add.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder-delete.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-delete.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder-delete.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-delete.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder-download.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-download.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder-download.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-download.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder-green.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-green.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder-green.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-green.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder-opened.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-opened.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder-opened.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-opened.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/loading-dir.gif b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/loading-dir.gif similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/loading-dir.gif rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/loading-dir.gif diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/loading.gif b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/loading.gif similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/loading.gif rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/loading.gif diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/paste.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/paste.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/paste.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/paste.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/preview.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/preview.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/preview.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/preview.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/rename.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/rename.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/rename.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/rename.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/search.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/search.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/search.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/search.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/select.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/select.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/select.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/select.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/upload-big.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/upload-big.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/upload-big.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/upload-big.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/view-list.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-list.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/view-list.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-list.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/view-tile.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-tile.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/view-tile.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-tile.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/index.html b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/index.html similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/index.html rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/index.html diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/custom.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/custom.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/custom.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/custom.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/directory.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/directory.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/directory.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/directory.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/file.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/file.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/fileman-modified.zip b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/fileman-modified.zip similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/fileman-modified.zip rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/fileman-modified.zip diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/filetypes.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/filetypes.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/filetypes.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/filetypes.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-1.10.2.min.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-1.10.2.min.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-1.10.2.min.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-1.10.2.min.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-dateFormat.min.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-dateFormat.min.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-dateFormat.min.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-dateFormat.min.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-ui-1.10.4.custom.min.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-ui-1.10.4.custom.min.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-ui-1.10.4.custom.min.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-ui-1.10.4.custom.min.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/main.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/main.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/main.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/main.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/main.min.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/main.min.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/main.min.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/main.min.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/mini-main.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/mini-main.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/mini-main.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/mini-main.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/tiny_mce_popup.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/tiny_mce_popup.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/tiny_mce_popup.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/tiny_mce_popup.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/utils.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/utils.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/utils.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/utils.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/lang/bg.json b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/bg.json similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/lang/bg.json rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/bg.json diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/lang/de.json b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/de.json similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/lang/de.json rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/de.json diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/lang/en.json b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/en.json similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/lang/en.json rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/en.json diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/lang/fr.json b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/fr.json similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/lang/fr.json rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/fr.json diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs new file mode 100644 index 0000000000..e0c2ca36c2 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs @@ -0,0 +1,828 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.IO.Compression; +using System.Text.RegularExpressions; +using System.Web; +using SmartStore.Services.Security; +using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; + +namespace SmartStore.Admin.Controllers +{ + [AdminAuthorize] + public class RoxyFileManagerController : AdminControllerBase + { + private const string CONFIG_FILE = "~/Administration/Content/filemanager/conf.json"; + + private Dictionary _lang = null; + private Dictionary _settings = null; + + private readonly IPermissionService _permissionService; + private readonly HttpContextBase _context; + private readonly HttpResponseBase _response; + + public RoxyFileManagerController( + IPermissionService permissionService, + HttpContextBase context) + { + _permissionService = permissionService; + _context = context; + _response = _context.Response; + } + + #region Utilities + + private string FixPath(string path) + { + if (path == null) + { + path = ""; + } + + if (!path.StartsWith("~")) + { + if (!path.StartsWith("/")) + path = "/" + path; + path = "~" + path; + } + + //var filesRoot = GetSetting("FILES_ROOT"); + //if (!path.ToLowerInvariant().Contains(filesRoot.ToLowerInvariant())) + //{ + // path = filesRoot; + //} + + return _context.Server.MapPath(path); + } + + private string GetLangFile() + { + //return "../lang/en.json"; + + var filename = "../lang/" + GetSetting("LANG") + ".json"; + + if (!System.IO.File.Exists(_context.Server.MapPath(filename))) + filename = "../lang/en.json"; + + return filename; + } + + protected string LangRes(string name) + { + string ret = name; + if (_lang == null) + _lang = ParseJSON(GetLangFile()); + if (_lang.ContainsKey(name)) + ret = _lang[name]; + + return ret; + } + + protected string GetFileType(string ext) + { + string ret = "file"; + ext = ext.ToLower(); + if (ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif") + ret = "image"; + else if (ext == ".swf" || ext == ".flv") + ret = "flash"; + return ret; + } + + protected bool CanHandleFile(string filename) + { + bool ret = false; + FileInfo file = new FileInfo(filename); + string ext = file.Extension.Replace(".", "").ToLower(); + string setting = GetSetting("FORBIDDEN_UPLOADS").Trim().ToLower(); + if (setting != "") + { + ArrayList tmp = new ArrayList(); + tmp.AddRange(Regex.Split(setting, "\\s+")); + if (!tmp.Contains(ext)) + ret = true; + } + setting = GetSetting("ALLOWED_UPLOADS").Trim().ToLower(); + if (setting != "") + { + ArrayList tmp = new ArrayList(); + tmp.AddRange(Regex.Split(setting, "\\s+")); + if (!tmp.Contains(ext)) + ret = false; + } + + return ret; + } + + protected Dictionary ParseJSON(string file) + { + Dictionary ret = new Dictionary(); + string json = ""; + try + { + json = System.IO.File.ReadAllText(_context.Server.MapPath(file), System.Text.Encoding.UTF8); + } + catch { } + + json = json.Trim(); + if (json != "") + { + if (json.StartsWith("{")) + json = json.Substring(1, json.Length - 2); + json = json.Trim(); + json = json.Substring(1, json.Length - 2); + string[] lines = Regex.Split(json, "\"\\s*,\\s*\""); + foreach (string line in lines) + { + string[] tmp = Regex.Split(line, "\"\\s*:\\s*\""); + try + { + if (tmp[0] != "" && !ret.ContainsKey(tmp[0])) + { + ret.Add(tmp[0], tmp[1]); + } + } + catch { } + } + } + return ret; + } + + protected string GetFilesRoot() + { + string ret = GetSetting("FILES_ROOT"); + if (GetSetting("SESSION_PATH_KEY") != "" && _context.Session[GetSetting("SESSION_PATH_KEY")] != null) + ret = (string)_context.Session[GetSetting("SESSION_PATH_KEY")]; + + if (ret == "") + ret = _context.Server.MapPath("../Uploads"); + else + ret = FixPath(ret); + return ret; + } + + protected void LoadConf() + { + if (_settings == null) + _settings = ParseJSON(CONFIG_FILE); + } + + protected string GetSetting(string name) + { + string ret = ""; + LoadConf(); + if (_settings.ContainsKey(name)) + ret = _settings[name]; + + return ret; + } + + protected void CheckPath(string path) + { + if (FixPath(path).IndexOf(GetFilesRoot()) != 0) + { + throw new Exception("Access to " + path + " is denied"); + } + } + + protected void VerifyAction(string action) + { + string setting = GetSetting(action); + if (setting.IndexOf("?") > -1) + setting = setting.Substring(0, setting.IndexOf("?")); + if (!setting.StartsWith("/")) + setting = "/" + setting; + setting = ".." + setting; + + if (_context.Server.MapPath(setting) != _context.Server.MapPath(_context.Request.Url.LocalPath)) + throw new Exception(LangRes("E_ActionDisabled")); + } + + protected string GetResultStr(string type, string msg) + { + return "{\"res\":\"" + type + "\",\"msg\":\"" + msg.Replace("\"", "\\\"") + "\"}"; + } + + protected string GetSuccessRes(string msg) + { + return GetResultStr("ok", msg); + } + + protected string GetSuccessRes() + { + return GetSuccessRes(""); + } + + protected string GetErrorRes(string msg) + { + return GetResultStr("error", msg); + } + + private void _copyDir(string path, string dest) + { + if (!Directory.Exists(dest)) + Directory.CreateDirectory(dest); + foreach (string f in Directory.GetFiles(path)) + { + FileInfo file = new FileInfo(f); + if (!System.IO.File.Exists(Path.Combine(dest, file.Name))) + { + System.IO.File.Copy(f, Path.Combine(dest, file.Name)); + } + } + foreach (string d in Directory.GetDirectories(path)) + { + DirectoryInfo dir = new DirectoryInfo(d); + _copyDir(d, Path.Combine(dest, dir.Name)); + } + } + + protected void CopyDir(string path, string newPath) + { + CheckPath(path); + CheckPath(newPath); + DirectoryInfo dir = new DirectoryInfo(FixPath(path)); + DirectoryInfo newDir = new DirectoryInfo(FixPath(newPath + "/" + dir.Name)); + + if (!dir.Exists) + { + throw new Exception(LangRes("E_CopyDirInvalidPath")); + } + else if (newDir.Exists) + { + throw new Exception(LangRes("E_DirAlreadyExists")); + } + else + { + _copyDir(dir.FullName, newDir.FullName); + } + _response.Write(GetSuccessRes()); + } + + protected string MakeUniqueFilename(string dir, string filename) + { + string ret = filename; + int i = 0; + while (System.IO.File.Exists(Path.Combine(dir, ret))) + { + i++; + ret = Path.GetFileNameWithoutExtension(filename) + " - Copy " + i.ToString() + Path.GetExtension(filename); + } + return ret; + } + + protected void CopyFile(string path, string newPath) + { + CheckPath(path); + FileInfo file = new FileInfo(FixPath(path)); + newPath = FixPath(newPath); + if (!file.Exists) + throw new Exception(LangRes("E_CopyFileInvalisPath")); + else + { + string newName = MakeUniqueFilename(newPath, file.Name); + try + { + System.IO.File.Copy(file.FullName, Path.Combine(newPath, newName)); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_CopyFile")); + } + } + } + + protected void CreateDir(string path, string name) + { + CheckPath(path); + path = FixPath(path); + if (!Directory.Exists(path)) + throw new Exception(LangRes("E_CreateDirInvalidPath")); + else + { + try + { + path = Path.Combine(path, name); + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_CreateDirFailed")); + } + } + } + + protected void DeleteDir(string path) + { + CheckPath(path); + path = FixPath(path); + if (!Directory.Exists(path)) + throw new Exception(LangRes("E_DeleteDirInvalidPath")); + else if (path == GetFilesRoot()) + throw new Exception(LangRes("E_CannotDeleteRoot")); + else if (Directory.GetDirectories(path).Length > 0 || Directory.GetFiles(path).Length > 0) + throw new Exception(LangRes("E_DeleteNonEmpty")); + else + { + try + { + Directory.Delete(path); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_CannotDeleteDir")); + } + } + } + + protected void DeleteFile(string path) + { + CheckPath(path); + path = FixPath(path); + if (!System.IO.File.Exists(path)) + throw new Exception(LangRes("E_DeleteFileInvalidPath")); + else + { + try + { + System.IO.File.Delete(path); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_DeletеFile")); + } + } + } + + private List GetFiles(string path, string type) + { + List ret = new List(); + if (type == "#") + type = ""; + string[] files = Directory.GetFiles(path); + foreach (string f in files) + { + if ((GetFileType(new FileInfo(f).Extension) == type) || (type == "")) + ret.Add(f); + } + return ret; + } + + private ArrayList ListDirs(string path) + { + string[] dirs = Directory.GetDirectories(path); + ArrayList ret = new ArrayList(); + foreach (string dir in dirs) + { + ret.Add(dir); + ret.AddRange(ListDirs(dir)); + } + return ret; + } + + protected void ListDirTree(string type) + { + DirectoryInfo d = new DirectoryInfo(GetFilesRoot()); + if (!d.Exists) + throw new Exception("Invalid files root directory. Check your configuration."); + + ArrayList dirs = ListDirs(d.FullName); + dirs.Insert(0, d.FullName); + + string localPath = _context.Server.MapPath("~/"); + _response.Write("["); + for (int i = 0; i < dirs.Count; i++) + { + string dir = (string)dirs[i]; + _response.Write("{\"p\":\"/" + dir.Replace(localPath, "").Replace("\\", "/") + "\",\"f\":\"" + GetFiles(dir, type).Count.ToString() + "\",\"d\":\"" + Directory.GetDirectories(dir).Length.ToString() + "\"}"); + if (i < dirs.Count - 1) + _response.Write(","); + } + _response.Write("]"); + } + + protected double LinuxTimestamp(DateTime d) + { + DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0).ToLocalTime(); + TimeSpan timeSpan = (d.ToLocalTime() - epoch); + + return timeSpan.TotalSeconds; + + } + + protected void ListFiles(string path, string type) + { + CheckPath(path); + string fullPath = FixPath(path); + List files = GetFiles(fullPath, type); + _response.Write("["); + for (int i = 0; i < files.Count; i++) + { + FileInfo f = new FileInfo(files[i]); + int w = 0, h = 0; + if (GetFileType(f.Extension) == "image") + { + try + { + FileStream fs = new FileStream(f.FullName, FileMode.Open); + Image img = Image.FromStream(fs); + w = img.Width; + h = img.Height; + fs.Close(); + fs.Dispose(); + img.Dispose(); + } + catch (Exception ex) + { + throw ex; + } + } + _response.Write("{"); + _response.Write("\"p\":\"" + path + "/" + f.Name + "\""); + _response.Write(",\"t\":\"" + Math.Ceiling(LinuxTimestamp(f.LastWriteTime)).ToString() + "\""); + _response.Write(",\"s\":\"" + f.Length.ToString() + "\""); + _response.Write(",\"w\":\"" + w.ToString() + "\""); + _response.Write(",\"h\":\"" + h.ToString() + "\""); + _response.Write("}"); + if (i < files.Count - 1) + _response.Write(","); + } + _response.Write("]"); + } + + public void DownloadDir(string path) + { + path = FixPath(path); + if (!Directory.Exists(path)) + throw new Exception(LangRes("E_CreateArchive")); + string dirName = new FileInfo(path).Name; + string tmpZip = _context.Server.MapPath("../tmp/" + dirName + ".zip"); + if (System.IO.File.Exists(tmpZip)) + System.IO.File.Delete(tmpZip); + ZipFile.CreateFromDirectory(path, tmpZip, CompressionLevel.Fastest, true); + _response.Clear(); + _response.Headers.Add("Content-Disposition", "attachment; filename=\"" + dirName + ".zip\""); + _response.ContentType = "application/force-download"; + _response.TransmitFile(tmpZip); + _response.Flush(); + System.IO.File.Delete(tmpZip); + _response.End(); + } + + protected void DownloadFile(string path) + { + CheckPath(path); + FileInfo file = new FileInfo(FixPath(path)); + if (file.Exists) + { + _response.Clear(); + _response.Headers.Add("Content-Disposition", "attachment; filename=\"" + file.Name + "\""); + _response.ContentType = "application/force-download"; + _response.TransmitFile(file.FullName); + _response.Flush(); + _response.End(); + } + } + + protected void MoveDir(string path, string newPath) + { + CheckPath(path); + CheckPath(newPath); + DirectoryInfo source = new DirectoryInfo(FixPath(path)); + DirectoryInfo dest = new DirectoryInfo(FixPath(Path.Combine(newPath, source.Name))); + if (dest.FullName.IndexOf(source.FullName) == 0) + throw new Exception(LangRes("E_CannotMoveDirToChild")); + else if (!source.Exists) + throw new Exception(LangRes("E_MoveDirInvalisPath")); + else if (dest.Exists) + throw new Exception(LangRes("E_DirAlreadyExists")); + else + { + try + { + source.MoveTo(dest.FullName); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_MoveDir") + " \"" + path + "\""); + } + } + } + + protected void MoveFile(string path, string newPath) + { + CheckPath(path); + CheckPath(newPath); + FileInfo source = new FileInfo(FixPath(path)); + FileInfo dest = new FileInfo(FixPath(newPath)); + if (!source.Exists) + throw new Exception(LangRes("E_MoveFileInvalisPath")); + else if (dest.Exists) + throw new Exception(LangRes("E_MoveFileAlreadyExists")); + else + { + try + { + source.MoveTo(dest.FullName); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_MoveFile") + " \"" + path + "\""); + } + } + } + + protected void RenameDir(string path, string name) + { + CheckPath(path); + DirectoryInfo source = new DirectoryInfo(FixPath(path)); + DirectoryInfo dest = new DirectoryInfo(Path.Combine(source.Parent.FullName, name)); + if (source.FullName == GetFilesRoot()) + throw new Exception(LangRes("E_CannotRenameRoot")); + else if (!source.Exists) + throw new Exception(LangRes("E_RenameDirInvalidPath")); + else if (dest.Exists) + throw new Exception(LangRes("E_DirAlreadyExists")); + else + { + try + { + source.MoveTo(dest.FullName); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_RenameDir") + " \"" + path + "\""); + } + } + } + + protected void RenameFile(string path, string name) + { + CheckPath(path); + FileInfo source = new FileInfo(FixPath(path)); + FileInfo dest = new FileInfo(Path.Combine(source.Directory.FullName, name)); + if (!source.Exists) + throw new Exception(LangRes("E_RenameFileInvalidPath")); + else if (!CanHandleFile(name)) + throw new Exception(LangRes("E_FileExtensionForbidden")); + else + { + try + { + source.MoveTo(dest.FullName); + _response.Write(GetSuccessRes()); + } + catch (Exception ex) + { + throw new Exception(ex.Message + "; " + LangRes("E_RenameFile") + " \"" + path + "\""); + } + } + } + + public bool ThumbnailCallback() + { + return false; + } + + protected void ShowThumbnail(string path, int width, int height) + { + CheckPath(path); + FileStream fs = new FileStream(FixPath(path), FileMode.Open); + Bitmap img = new Bitmap(Bitmap.FromStream(fs)); + fs.Close(); + fs.Dispose(); + int cropWidth = img.Width, cropHeight = img.Height; + int cropX = 0, cropY = 0; + + double imgRatio = (double)img.Width / (double)img.Height; + + if (height == 0) + height = Convert.ToInt32(Math.Floor((double)width / imgRatio)); + + if (width > img.Width) + width = img.Width; + if (height > img.Height) + height = img.Height; + + double cropRatio = (double)width / (double)height; + cropWidth = Convert.ToInt32(Math.Floor((double)img.Height * cropRatio)); + cropHeight = Convert.ToInt32(Math.Floor((double)cropWidth / cropRatio)); + if (cropWidth > img.Width) + { + cropWidth = img.Width; + cropHeight = Convert.ToInt32(Math.Floor((double)cropWidth / cropRatio)); + } + if (cropHeight > img.Height) + { + cropHeight = img.Height; + cropWidth = Convert.ToInt32(Math.Floor((double)cropHeight * cropRatio)); + } + if (cropWidth < img.Width) + { + cropX = Convert.ToInt32(Math.Floor((double)(img.Width - cropWidth) / 2)); + } + if (cropHeight < img.Height) + { + cropY = Convert.ToInt32(Math.Floor((double)(img.Height - cropHeight) / 2)); + } + + Rectangle area = new Rectangle(cropX, cropY, cropWidth, cropHeight); + Bitmap cropImg = img.Clone(area, System.Drawing.Imaging.PixelFormat.DontCare); + img.Dispose(); + Image.GetThumbnailImageAbort imgCallback = new Image.GetThumbnailImageAbort(ThumbnailCallback); + + _response.AddHeader("Content-Type", "image/png"); + cropImg.GetThumbnailImage(width, height, imgCallback, IntPtr.Zero).Save(_response.OutputStream, ImageFormat.Png); + _response.OutputStream.Close(); + cropImg.Dispose(); + } + + private ImageFormat GetImageFormat(string filename) + { + ImageFormat ret = ImageFormat.Jpeg; + switch (new FileInfo(filename).Extension.ToLower()) + { + case ".png": + ret = ImageFormat.Png; + break; + case ".gif": + ret = ImageFormat.Gif; + break; + } + return ret; + } + + protected void ImageResize(string path, string dest, int width, int height) + { + FileStream fs = new FileStream(path, FileMode.Open); + Image img = Image.FromStream(fs); + fs.Close(); + fs.Dispose(); + float ratio = (float)img.Width / (float)img.Height; + if ((img.Width <= width && img.Height <= height) || (width == 0 && height == 0)) + return; + + int newWidth = width; + int newHeight = Convert.ToInt16(Math.Floor((float)newWidth / ratio)); + if ((height > 0 && newHeight > height) || (width == 0)) + { + newHeight = height; + newWidth = Convert.ToInt16(Math.Floor((float)newHeight * ratio)); + } + Bitmap newImg = new Bitmap(newWidth, newHeight); + Graphics g = Graphics.FromImage((Image)newImg); + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + g.DrawImage(img, 0, 0, newWidth, newHeight); + img.Dispose(); + g.Dispose(); + if (dest != "") + { + newImg.Save(dest, GetImageFormat(dest)); + } + newImg.Dispose(); + } + + protected void Upload(string path) + { + CheckPath(path); + path = FixPath(path); + string res = GetSuccessRes(); + try + { + for (int i = 0; i < Request.Files.Count; i++) + { + if (CanHandleFile(Request.Files[i].FileName)) + { + string filename = MakeUniqueFilename(path, Request.Files[i].FileName); + string dest = Path.Combine(path, filename); + Request.Files[i].SaveAs(dest); + if (GetFileType(new FileInfo(filename).Extension) == "image") + { + int w = 0; + int h = 0; + int.TryParse(GetSetting("MAX_IMAGE_WIDTH"), out w); + int.TryParse(GetSetting("MAX_IMAGE_HEIGHT"), out h); + ImageResize(dest, dest, w, h); + } + } + else + res = GetSuccessRes(LangRes("E_UploadNotAll")); + } + } + catch (Exception ex) + { + res = GetErrorRes(ex.Message); + } + _response.Write(""); + } + + public bool IsReusable + { + get + { + return false; + } + } + + #endregion + + public void ProcessRequest() + { + if (!_permissionService.Authorize(StandardPermissionProvider.UploadPictures)) + { + _response.Write(GetErrorRes("You do not have the required permission")); + return; + } + + string action = "DIRLIST"; + try + { + if (_context.Request["a"] != null) + action = (string)_context.Request["a"]; + + //VerifyAction(action); + switch (action.ToUpper()) + { + case "DIRLIST": + ListDirTree(_context.Request["type"]); + break; + case "FILESLIST": + ListFiles(_context.Request["d"], _context.Request["type"]); + break; + case "COPYDIR": + CopyDir(_context.Request["d"], _context.Request["n"]); + break; + case "COPYFILE": + CopyFile(_context.Request["f"], _context.Request["n"]); + break; + case "CREATEDIR": + CreateDir(_context.Request["d"], _context.Request["n"]); + break; + case "DELETEDIR": + DeleteDir(_context.Request["d"]); + break; + case "DELETEFILE": + DeleteFile(_context.Request["f"]); + break; + case "DOWNLOAD": + DownloadFile(_context.Request["f"]); + break; + case "DOWNLOADDIR": + DownloadDir(_context.Request["d"]); + break; + case "MOVEDIR": + MoveDir(_context.Request["d"], _context.Request["n"]); + break; + case "MOVEFILE": + MoveFile(_context.Request["f"], _context.Request["n"]); + break; + case "RENAMEDIR": + RenameDir(_context.Request["d"], _context.Request["n"]); + break; + case "RENAMEFILE": + RenameFile(_context.Request["f"], _context.Request["n"]); + break; + case "GENERATETHUMB": + int w = 140, h = 0; + int.TryParse(_context.Request["width"].Replace("px", ""), out w); + int.TryParse(_context.Request["height"].Replace("px", ""), out h); + ShowThumbnail(_context.Request["f"], w, h); + break; + case "UPLOAD": + Upload(_context.Request["d"]); + break; + default: + _response.Write(GetErrorRes("This action is not implemented.")); + break; + } + } + catch (Exception ex) + { + if (action == "UPLOAD") + { + _response.Write(""); + } + else + { + _response.Write(GetErrorRes(ex.Message)); + } + } + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index 15852be9a4..603f623aa1 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -102,6 +102,7 @@ + False @@ -213,6 +214,7 @@ + @@ -429,6 +431,267 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -461,6 +724,12 @@ + + + + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml index 1ba69f79da..04db8e0e26 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml @@ -29,7 +29,7 @@ "); - } - else{ - _r.Write(GetErrorRes(ex.Message)); - } - } - - } - private string FixPath(string path) - { - if (!path.StartsWith("~")){ - if (!path.StartsWith("/")) - path = "/" + path; - path = "~" + path; - } - return _context.Server.MapPath(path); - } - private string GetLangFile(){ - return "../lang/en.json"; - } - protected string LangRes(string name) - { - string ret = name; - if (_lang == null) - _lang = ParseJSON(GetLangFile()); - if (_lang.ContainsKey(name)) - ret = _lang[name]; - - return ret; - } - protected string GetFileType(string ext){ - string ret = "file"; - ext = ext.ToLower(); - if(ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif") - ret = "image"; - else if(ext == ".swf" || ext == ".flv") - ret = "flash"; - return ret; - } - protected bool CanHandleFile(string filename) - { - bool ret = false; - FileInfo file = new FileInfo(filename); - string ext = file.Extension.Replace(".", "").ToLower(); - string setting = GetSetting("FORBIDDEN_UPLOADS").Trim().ToLower(); - if (setting != "") - { - ArrayList tmp = new ArrayList(); - tmp.AddRange(Regex.Split(setting, "\\s+")); - if (!tmp.Contains(ext)) - ret = true; - } - setting = GetSetting("ALLOWED_UPLOADS").Trim().ToLower(); - if (setting != "") - { - ArrayList tmp = new ArrayList(); - tmp.AddRange(Regex.Split(setting, "\\s+")); - if (!tmp.Contains(ext)) - ret = false; - } - - return ret; - } - protected Dictionary ParseJSON(string file){ - Dictionary ret = new Dictionary(); - string json = ""; - try{ - json = File.ReadAllText(_context.Server.MapPath(file), System.Text.Encoding.UTF8); - } - catch {} - - json = json.Trim(); - if(json != ""){ - if (json.StartsWith("{")) - json = json.Substring(1, json.Length - 2); - json = json.Trim(); - json = json.Substring(1, json.Length - 2); - string[] lines = Regex.Split(json, "\"\\s*,\\s*\""); - foreach(string line in lines){ - string[] tmp = Regex.Split(line, "\"\\s*:\\s*\""); - try{ - if (tmp[0] != "" && !ret.ContainsKey(tmp[0])) - { - ret.Add(tmp[0], tmp[1]); - } - } - catch {} - } - } - return ret; - } - protected string GetFilesRoot(){ - string ret = GetSetting("FILES_ROOT"); - if (GetSetting("SESSION_PATH_KEY") != "" && _context.Session[GetSetting("SESSION_PATH_KEY")] != null) - ret = (string)_context.Session[GetSetting("SESSION_PATH_KEY")]; - - if(ret == "") - ret = _context.Server.MapPath("../Uploads"); - else - ret = FixPath(ret); - return ret; - } - protected void LoadConf(){ - if(_settings == null) - _settings = ParseJSON(confFile); - } - protected string GetSetting(string name){ - string ret = ""; - LoadConf(); - if(_settings.ContainsKey(name)) - ret = _settings[name]; - - return ret; - } - protected void CheckPath(string path) - { - if (FixPath(path).IndexOf(GetFilesRoot()) != 0) - { - throw new Exception("Access to " + path + " is denied"); - } - } - protected void VerifyAction(string action) - { - string setting = GetSetting(action); - if (setting.IndexOf("?") > -1) - setting = setting.Substring(0, setting.IndexOf("?")); - if (!setting.StartsWith("/")) - setting = "/" + setting; - setting = ".." + setting; - - if (_context.Server.MapPath(setting) != _context.Server.MapPath(_context.Request.Url.LocalPath)) - throw new Exception(LangRes("E_ActionDisabled")); - } - protected string GetResultStr(string type, string msg) - { - return "{\"res\":\"" + type + "\",\"msg\":\"" + msg.Replace("\"","\\\"") + "\"}"; - } - protected string GetSuccessRes(string msg) - { - return GetResultStr("ok", msg); - } - protected string GetSuccessRes() - { - return GetSuccessRes(""); - } - protected string GetErrorRes(string msg) - { - return GetResultStr("error", msg); - } - private void _copyDir(string path, string dest){ - if(!Directory.Exists(dest)) - Directory.CreateDirectory(dest); - foreach(string f in Directory.GetFiles(path)){ - FileInfo file = new FileInfo(f); - if (!File.Exists(Path.Combine(dest, file.Name))){ - File.Copy(f, Path.Combine(dest, file.Name)); - } - } - foreach (string d in Directory.GetDirectories(path)) - { - DirectoryInfo dir = new DirectoryInfo(d); - _copyDir(d, Path.Combine(dest, dir.Name)); - } - } - protected void CopyDir(string path, string newPath) - { - CheckPath(path); - CheckPath(newPath); - DirectoryInfo dir = new DirectoryInfo(FixPath(path)); - DirectoryInfo newDir = new DirectoryInfo(FixPath(newPath + "/" + dir.Name)); - - if (!dir.Exists) - { - throw new Exception(LangRes("E_CopyDirInvalidPath")); - } - else if (newDir.Exists) - { - throw new Exception(LangRes("E_DirAlreadyExists")); - } - else{ - _copyDir(dir.FullName, newDir.FullName); - } - _r.Write(GetSuccessRes()); - } - protected string MakeUniqueFilename(string dir, string filename){ - string ret = filename; - int i = 0; - while (File.Exists(Path.Combine(dir, ret))) - { - i++; - ret = Path.GetFileNameWithoutExtension(filename) + " - Copy " + i.ToString() + Path.GetExtension(filename); - } - return ret; - } - protected void CopyFile(string path, string newPath) - { - CheckPath(path); - FileInfo file = new FileInfo(FixPath(path)); - newPath = FixPath(newPath); - if (!file.Exists) - throw new Exception(LangRes("E_CopyFileInvalisPath")); - else{ - string newName = MakeUniqueFilename(newPath, file.Name); - try{ - File.Copy(file.FullName, Path.Combine(newPath, newName)); - _r.Write(GetSuccessRes()); - } - catch { - throw new Exception(LangRes("E_CopyFile")); - } - } - } - protected void CreateDir(string path, string name) - { - CheckPath(path); - path = FixPath(path); - if(!Directory.Exists(path)) - throw new Exception(LangRes("E_CreateDirInvalidPath")); - else{ - try - { - path = Path.Combine(path, name); - if(!Directory.Exists(path)) - Directory.CreateDirectory(path); - _r.Write(GetSuccessRes()); - } - catch - { - throw new Exception(LangRes("E_CreateDirFailed")); - } - } - } - protected void DeleteDir(string path) - { - CheckPath(path); - path = FixPath(path); - if (!Directory.Exists(path)) - throw new Exception(LangRes("E_DeleteDirInvalidPath")); - else if (path == GetFilesRoot()) - throw new Exception(LangRes("E_CannotDeleteRoot")); - else if(Directory.GetDirectories(path).Length > 0 || Directory.GetFiles(path).Length > 0) - throw new Exception(LangRes("E_DeleteNonEmpty")); - else - { - try - { - Directory.Delete(path); - _r.Write(GetSuccessRes()); - } - catch - { - throw new Exception(LangRes("E_CannotDeleteDir")); - } - } - } - protected void DeleteFile(string path) - { - CheckPath(path); - path = FixPath(path); - if (!File.Exists(path)) - throw new Exception(LangRes("E_DeleteFileInvalidPath")); - else - { - try - { - File.Delete(path); - _r.Write(GetSuccessRes()); - } - catch - { - throw new Exception(LangRes("E_DeletеFile")); - } - } - } - private List GetFiles(string path, string type){ - List ret = new List(); - if(type == "#") - type = ""; - string[] files = Directory.GetFiles(path); - foreach(string f in files){ - if ((GetFileType(new FileInfo(f).Extension) == type) || (type == "")) - ret.Add(f); - } - return ret; - } - private ArrayList ListDirs(string path){ - string[] dirs = Directory.GetDirectories(path); - ArrayList ret = new ArrayList(); - foreach(string dir in dirs){ - ret.Add(dir); - ret.AddRange(ListDirs(dir)); - } - return ret; - } - protected void ListDirTree(string type) - { - DirectoryInfo d = new DirectoryInfo(GetFilesRoot()); - if(!d.Exists) - throw new Exception("Invalid files root directory. Check your configuration."); - - ArrayList dirs = ListDirs(d.FullName); - dirs.Insert(0, d.FullName); - - string localPath = _context.Server.MapPath("~/"); - _r.Write("["); - for(int i = 0; i files = GetFiles(fullPath, type); - _r.Write("["); - for(int i = 0; i < files.Count; i++){ - FileInfo f = new FileInfo(files[i]); - int w = 0, h = 0; - if (GetFileType(f.Extension) == "image"){ - try{ - FileStream fs = new FileStream(f.FullName, FileMode.Open); - Image img = Image.FromStream(fs); - w = img.Width; - h = img.Height; - fs.Close(); - fs.Dispose(); - img.Dispose(); - } - catch(Exception ex) - { - throw ex; - } - } - _r.Write("{"); - _r.Write("\"p\":\""+path + "/" + f.Name+"\""); - _r.Write(",\"t\":\"" + Math.Ceiling(LinuxTimestamp(f.LastWriteTime)).ToString() + "\""); - _r.Write(",\"s\":\""+f.Length.ToString()+"\""); - _r.Write(",\"w\":\""+w.ToString()+"\""); - _r.Write(",\"h\":\""+h.ToString()+"\""); - _r.Write("}"); - if (i < files.Count - 1) - _r.Write(","); - } - _r.Write("]"); - } - public void DownloadDir(string path) - { - path = FixPath(path); - if(!Directory.Exists(path)) - throw new Exception(LangRes("E_CreateArchive")); - string dirName = new FileInfo(path).Name; - string tmpZip = _context.Server.MapPath("../tmp/" + dirName + ".zip"); - if(File.Exists(tmpZip)) - File.Delete(tmpZip); - ZipFile.CreateFromDirectory(path, tmpZip,CompressionLevel.Fastest, true); - _r.Clear(); - _r.Headers.Add("Content-Disposition", "attachment; filename=\"" + dirName + ".zip\""); - _r.ContentType = "application/force-download"; - _r.TransmitFile(tmpZip); - _r.Flush(); - File.Delete(tmpZip); - _r.End(); - } - protected void DownloadFile(string path) - { - CheckPath(path); - FileInfo file = new FileInfo(FixPath(path)); - if(file.Exists){ - _r.Clear(); - _r.Headers.Add("Content-Disposition", "attachment; filename=\"" + file.Name + "\""); - _r.ContentType = "application/force-download"; - _r.TransmitFile(file.FullName); - _r.Flush(); - _r.End(); - } - } - protected void MoveDir(string path, string newPath) - { - CheckPath(path); - CheckPath(newPath); - DirectoryInfo source = new DirectoryInfo(FixPath(path)); - DirectoryInfo dest = new DirectoryInfo(FixPath(Path.Combine(newPath, source.Name))); - if(dest.FullName.IndexOf(source.FullName) == 0) - throw new Exception(LangRes("E_CannotMoveDirToChild")); - else if (!source.Exists) - throw new Exception(LangRes("E_MoveDirInvalisPath")); - else if (dest.Exists) - throw new Exception(LangRes("E_DirAlreadyExists")); - else{ - try{ - source.MoveTo(dest.FullName); - _r.Write(GetSuccessRes()); - } - catch { - throw new Exception(LangRes("E_MoveDir") + " \"" + path + "\""); - } - } - - } - protected void MoveFile(string path, string newPath) - { - CheckPath(path); - CheckPath(newPath); - FileInfo source = new FileInfo(FixPath(path)); - FileInfo dest = new FileInfo(FixPath(newPath)); - if (!source.Exists) - throw new Exception(LangRes("E_MoveFileInvalisPath")); - else if (dest.Exists) - throw new Exception(LangRes("E_MoveFileAlreadyExists")); - else - { - try - { - source.MoveTo(dest.FullName); - _r.Write(GetSuccessRes()); - } - catch - { - throw new Exception(LangRes("E_MoveFile") + " \"" + path + "\""); - } - } - } - protected void RenameDir(string path, string name) - { - CheckPath(path); - DirectoryInfo source = new DirectoryInfo(FixPath(path)); - DirectoryInfo dest = new DirectoryInfo(Path.Combine(source.Parent.FullName, name)); - if(source.FullName == GetFilesRoot()) - throw new Exception(LangRes("E_CannotRenameRoot")); - else if (!source.Exists) - throw new Exception(LangRes("E_RenameDirInvalidPath")); - else if (dest.Exists) - throw new Exception(LangRes("E_DirAlreadyExists")); - else - { - try - { - source.MoveTo(dest.FullName); - _r.Write(GetSuccessRes()); - } - catch - { - throw new Exception(LangRes("E_RenameDir") + " \"" + path + "\""); - } - } - } - protected void RenameFile(string path, string name) - { - CheckPath(path); - FileInfo source = new FileInfo(FixPath(path)); - FileInfo dest = new FileInfo(Path.Combine(source.Directory.FullName, name)); - if (!source.Exists) - throw new Exception(LangRes("E_RenameFileInvalidPath")); - else if (!CanHandleFile(name)) - throw new Exception(LangRes("E_FileExtensionForbidden")); - else - { - try - { - source.MoveTo(dest.FullName); - _r.Write(GetSuccessRes()); - } - catch (Exception ex) - { - throw new Exception(ex.Message + "; " + LangRes("E_RenameFile") + " \"" + path + "\""); - } - } - } - public bool ThumbnailCallback() - { - return false; - } - - protected void ShowThumbnail(string path, int width, int height) - { - CheckPath(path); - FileStream fs = new FileStream(FixPath(path), FileMode.Open); - Bitmap img = new Bitmap(Bitmap.FromStream(fs)); - fs.Close(); - fs.Dispose(); - int cropWidth = img.Width, cropHeight = img.Height; - int cropX = 0, cropY = 0; - - double imgRatio = (double)img.Width / (double)img.Height; - - if(height == 0) - height = Convert.ToInt32(Math.Floor((double)width / imgRatio)); - - if (width > img.Width) - width = img.Width; - if (height > img.Height) - height = img.Height; - - double cropRatio = (double)width / (double)height; - cropWidth = Convert.ToInt32(Math.Floor((double)img.Height * cropRatio)); - cropHeight = Convert.ToInt32(Math.Floor((double)cropWidth / cropRatio)); - if (cropWidth > img.Width) - { - cropWidth = img.Width; - cropHeight = Convert.ToInt32(Math.Floor((double)cropWidth / cropRatio)); - } - if (cropHeight > img.Height) - { - cropHeight = img.Height; - cropWidth = Convert.ToInt32(Math.Floor((double)cropHeight * cropRatio)); - } - if(cropWidth < img.Width){ - cropX = Convert.ToInt32(Math.Floor((double)(img.Width - cropWidth) / 2)); - } - if(cropHeight < img.Height){ - cropY = Convert.ToInt32(Math.Floor((double)(img.Height - cropHeight) / 2)); - } - - Rectangle area = new Rectangle(cropX, cropY, cropWidth, cropHeight); - Bitmap cropImg = img.Clone(area, System.Drawing.Imaging.PixelFormat.DontCare); - img.Dispose(); - Image.GetThumbnailImageAbort imgCallback = new Image.GetThumbnailImageAbort(ThumbnailCallback); - - _r.AddHeader("Content-Type", "image/png"); - cropImg.GetThumbnailImage(width, height, imgCallback, IntPtr.Zero).Save(_r.OutputStream, ImageFormat.Png); - _r.OutputStream.Close(); - cropImg.Dispose(); - } - private ImageFormat GetImageFormat(string filename){ - ImageFormat ret = ImageFormat.Jpeg; - switch(new FileInfo(filename).Extension.ToLower()){ - case ".png": ret = ImageFormat.Png; break; - case ".gif": ret = ImageFormat.Gif; break; - } - return ret; - } - protected void ImageResize(string path, string dest, int width, int height) - { - FileStream fs = new FileStream(path, FileMode.Open); - Image img = Image.FromStream(fs); - fs.Close(); - fs.Dispose(); - float ratio = (float)img.Width / (float)img.Height; - if ((img.Width <= width && img.Height <= height) || (width == 0 && height == 0)) - return; - - int newWidth = width; - int newHeight = Convert.ToInt16(Math.Floor((float)newWidth / ratio)); - if ((height > 0 && newHeight > height) || (width == 0)) - { - newHeight = height; - newWidth = Convert.ToInt16(Math.Floor((float)newHeight * ratio)); - } - Bitmap newImg = new Bitmap(newWidth, newHeight); - Graphics g = Graphics.FromImage((Image)newImg); - g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; - g.DrawImage(img, 0, 0, newWidth, newHeight); - img.Dispose(); - g.Dispose(); - if(dest != ""){ - newImg.Save(dest, GetImageFormat(dest)); - } - newImg.Dispose(); - } - - protected void Upload(string path) - { - CheckPath(path); - path = FixPath(path); - string res = GetSuccessRes(); - try{ - for(int i = 0; i < HttpContext.Current.Request.Files.Count; i++){ - if (CanHandleFile(HttpContext.Current.Request.Files[i].FileName)) - { - string filename = MakeUniqueFilename(path, HttpContext.Current.Request.Files[i].FileName); - string dest = Path.Combine(path, filename); - HttpContext.Current.Request.Files[i].SaveAs(dest); - if(GetFileType(new FileInfo(filename).Extension) == "image"){ - int w = 0; - int h = 0; - int.TryParse(GetSetting("MAX_IMAGE_WIDTH"), out w); - int.TryParse(GetSetting("MAX_IMAGE_HEIGHT"), out h); - ImageResize(dest, dest, w, h); - } - } - else - res = GetSuccessRes(LangRes("E_UploadNotAll")); - } - } - catch(Exception ex){ - res = GetErrorRes(ex.Message); - } - _r.Write(""); - } - - public bool IsReusable { - get { - return false; - } - } - -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/conf.json b/src/Presentation/SmartStore.Web/Content/filemanager/conf.json deleted file mode 100644 index 3df29bf505..0000000000 --- a/src/Presentation/SmartStore.Web/Content/filemanager/conf.json +++ /dev/null @@ -1,34 +0,0 @@ -{ -"FILES_ROOT": "~/Media/Uploaded", -"SESSION_PATH_KEY": "", -"THUMBS_VIEW_WIDTH": "140", -"THUMBS_VIEW_HEIGHT": "120", -"PREVIEW_THUMB_WIDTH":"300", -"PREVIEW_THUMB_HEIGHT":"200", -"MAX_IMAGE_WIDTH": "0", -"MAX_IMAGE_HEIGHT": "0", -"INTEGRATION": "ckeditor", -"DIRLIST": "asp_net/main.ashx?a=DIRLIST", -"CREATEDIR": "asp_net/main.ashx?a=CREATEDIR", -"DELETEDIR": "asp_net/main.ashx?a=DELETEDIR", -"MOVEDIR": "asp_net/main.ashx?a=MOVEDIR", -"COPYDIR": "asp_net/main.ashx?a=COPYDIR", -"RENAMEDIR": "asp_net/main.ashx?a=RENAMEDIR", -"FILESLIST": "asp_net/main.ashx?a=FILESLIST", -"UPLOAD": "asp_net/main.ashx?a=UPLOAD", -"DOWNLOAD": "asp_net/main.ashx?a=DOWNLOAD", -"DOWNLOADDIR": "asp_net/main.ashx?a=DOWNLOADDIR", -"DOWNLOADDIR": "asp_net/main.ashx?a=DOWNLOADDIR", -"DELETEFILE": "asp_net/main.ashx?a=DELETEFILE", -"MOVEFILE": "asp_net/main.ashx?a=MOVEFILE", -"COPYFILE": "asp_net/main.ashx?a=COPYFILE", -"RENAMEFILE": "asp_net/main.ashx?a=RENAMEFILE", -"GENERATETHUMB": "asp_net/main.ashx?a=GENERATETHUMB", -"DEFAULTVIEW": "list", -"FORBIDDEN_UPLOADS": "zip js jsp jsb mhtml mht xhtml xht php phtml php3 php4 php5 phps shtml jhtml pl sh py cgi exe scr dll msi vbs bat com pif cmd vxd cpl htpasswd htaccess", -"ALLOWED_UPLOADS": "", -"FILEPERMISSIONS": "0644", -"DIRPERMISSIONS": "0755", -"LANG": "auto", -"DATEFORMAT": "dd.MM.yyyy HH:mm" -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index 2bd35ba917..8340a62bfb 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -645,267 +645,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -919,13 +658,6 @@ - - - - - - - From 8e1c3f47188692020652696177667d4149bf59dc Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 23 Jun 2016 09:39:37 +0200 Subject: [PATCH 156/185] Delete old static files of file manager by migration --- .../Migrations/201605201911421_ExportRevision.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs index 485fa9fdec..486bff7969 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs @@ -5,6 +5,7 @@ namespace SmartStore.Data.Migrations using Core.Domain; using Core.Domain.DataExchange; using Setup; + using Utilities; public partial class ExportRevision : DbMigration, ILocaleResourcesProvider, IDataSeeder { @@ -58,6 +59,9 @@ public void Seed(SmartObjectContext context) context.ColumnDelete("ExportDeployment", "IsPublic"); } + + var oldFileManagerPath = CommonHelper.MapPath("~/Content/filemanager"); + FileSystemHelper.ClearDirectory(oldFileManagerPath, true); } public void MigrateLocaleResources(LocaleResourcesBuilder builder) From e79c397d0e39d03a22de7418744a880da833b9e8 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 23 Jun 2016 14:05:05 +0200 Subject: [PATCH 157/185] Resolves #950 make contact form comply with current German law --- changelog.md | 1 + .../Domain/Customers/CustomerSettings.cs | 6 +++++ .../201605201911421_ExportRevision.cs | 17 ++++++++++++ .../Settings/CustomerUserSettingsModel.cs | 3 +++ .../Views/Setting/CustomerUser.cshtml | 9 +++++++ .../Controllers/HomeController.cs | 10 +++++-- .../Models/Common/ContactUsModel.cs | 5 ++++ .../Validators/Common/ContactUsValidator.cs | 5 ++++ .../Views/Home/ContactUs.cshtml | 27 +++++++++++++++---- 9 files changed, 76 insertions(+), 7 deletions(-) diff --git a/changelog.md b/changelog.md index 47f492ff48..0dac88a9fd 100644 --- a/changelog.md +++ b/changelog.md @@ -19,6 +19,7 @@ * #477 Implement option to specify the number of exported and imported pictures * #859 Make checkout attributes suitable for multi-stores * Product details: Select attribute and gift card values by query string parameters +* #950 make contact form comply with current German law ### Improvements * Major improvements in Importer: better field mapping, higher performance, bug fixes etc. diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs index d0dbf678e5..e6d7f56f56 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs @@ -24,6 +24,7 @@ public CustomerSettings() NewsletterEnabled = true; OnlineCustomerMinutes = 20; StoreLastVisitedPage = true; + DisplayPrivacyAgreementOnContactUs = false; } /// @@ -151,6 +152,11 @@ public CustomerSettings() /// public bool StoreLastVisitedPage { get; set; } + /// + /// Gets or sets a value indicating whether to display a checkbox to the customer where he can agree to privacy terms + /// + public bool DisplayPrivacyAgreementOnContactUs { get; set; } + #region Form fields /// diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs index 486bff7969..d168932260 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs @@ -174,6 +174,23 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "This function is not available for guests.", "Diese Funktion steht für Gäste nicht zur Verfügung."); + builder.AddOrUpdate("ContactUs.PrivacyAgreement", + "Privacy consent", + "Einwilligungserklärung Datenschutz"); + + builder.AddOrUpdate("ContactUs.PrivacyAgreement.MustBeAccepted", + "Please agree to the storage of your data.", + "Bitte stimmen Sie der Speicherung Ihrer Daten zu."); + + builder.AddOrUpdate("ContactUs.PrivacyAgreement.DetailText", + "Yes I've read the privacy terms and agree that my data given by me can be stored electronically. My data will thereby only be used to process my inquiry.", + "Ja, ich habe die Datenschutzerklärung zur Kenntnis genommen und bin damit einverstanden, dass die von mir angegebenen Daten elektronisch erhoben und gespeichert werden. Meine Daten werden dabei nur zur Bearbeitung meiner Anfrage genutzt."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.DisplayPrivacyAgreementOnContactUs", + "Get privacy consent for contact requests", + "Einwilligungserklärung im Kontaktformular fordern", + "Specifies whether a checkbox will be displayed on the contact page which requests the user to agree on storage of his data.", + "Bestimmt ob im Kontaktformular eine Checkbox angezeigt wird, die den Benutzer auffordert der Speicherung seiner Daten zuzustimmen."); builder.Delete( diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs index bfbb495345..f395584d6c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs @@ -89,6 +89,9 @@ public partial class CustomerSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.StoreLastVisitedPage")] public bool StoreLastVisitedPage { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.DisplayPrivacyAgreementOnContactUs")] + public bool DisplayPrivacyAgreementOnContactUs { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.GenderEnabled")] public bool GenderEnabled { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml index 1d868a640d..ccb0eb2876 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml @@ -259,6 +259,15 @@ @Html.ValidationMessageFor(model => model.CustomerSettings.StoreLastVisitedPage) + + + @Html.SmartLabelFor(model => model.CustomerSettings.DisplayPrivacyAgreementOnContactUs) + + + @Html.SettingEditorFor(model => model.CustomerSettings.DisplayPrivacyAgreementOnContactUs) + @Html.ValidationMessageFor(model => model.CustomerSettings.DisplayPrivacyAgreementOnContactUs) + + } @helper TabCustomerFormFields() diff --git a/src/Presentation/SmartStore.Web/Controllers/HomeController.cs b/src/Presentation/SmartStore.Web/Controllers/HomeController.cs index e16d5e80b7..58e6eb4ace 100644 --- a/src/Presentation/SmartStore.Web/Controllers/HomeController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/HomeController.cs @@ -6,6 +6,7 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Cms; using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Messages; using SmartStore.Core.Infrastructure; using SmartStore.Core.Localization; @@ -41,6 +42,7 @@ public partial class HomeController : PublicControllerBase private readonly Lazy _sitemapGenerator; private readonly Lazy _captchaSettings; private readonly Lazy _commonSettings; + private readonly Lazy _customerSettings; #endregion @@ -56,7 +58,8 @@ public HomeController( Lazy emailAccountService, Lazy sitemapGenerator, Lazy captchaSettings, - Lazy commonSettings) + Lazy commonSettings, + Lazy customerSettings) { this._services = services; this._categoryService = categoryService; @@ -68,6 +71,7 @@ public HomeController( this._sitemapGenerator = sitemapGenerator; this._captchaSettings = captchaSettings; this._commonSettings = commonSettings; + this._customerSettings = customerSettings; } #endregion @@ -119,7 +123,9 @@ public ActionResult ContactUs() { Email = _services.WorkContext.CurrentCustomer.Email, FullName = _services.WorkContext.CurrentCustomer.GetFullName(), - DisplayCaptcha = _captchaSettings.Value.Enabled && _captchaSettings.Value.ShowOnContactUsPage + DisplayCaptcha = _captchaSettings.Value.Enabled && _captchaSettings.Value.ShowOnContactUsPage, + DisplayPrivacyAgreement = _customerSettings.Value.DisplayPrivacyAgreementOnContactUs + }; return View(model); diff --git a/src/Presentation/SmartStore.Web/Models/Common/ContactUsModel.cs b/src/Presentation/SmartStore.Web/Models/Common/ContactUsModel.cs index f20fd69129..db9ab838bc 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/ContactUsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/ContactUsModel.cs @@ -10,6 +10,11 @@ namespace SmartStore.Web.Models.Common [Validator(typeof(ContactUsValidator))] public partial class ContactUsModel : ModelBase { + [SmartResourceDisplayName("ContactUs.PrivacyAgreement")] + public bool PrivacyAgreement { get; set; } + + public bool DisplayPrivacyAgreement { get; set; } + [AllowHtml] [SmartResourceDisplayName("ContactUs.Email")] [DataType(DataType.EmailAddress)] diff --git a/src/Presentation/SmartStore.Web/Validators/Common/ContactUsValidator.cs b/src/Presentation/SmartStore.Web/Validators/Common/ContactUsValidator.cs index 7e9b037037..9e42f7ed02 100644 --- a/src/Presentation/SmartStore.Web/Validators/Common/ContactUsValidator.cs +++ b/src/Presentation/SmartStore.Web/Validators/Common/ContactUsValidator.cs @@ -8,6 +8,11 @@ public class ContactUsValidator : AbstractValidator { public ContactUsValidator(ILocalizationService localizationService) { + RuleFor(x => x.PrivacyAgreement) + .Must(x => x == true) + .WithMessage(localizationService.GetResource("ContactUs.PrivacyAgreement.MustBeAccepted")) + .When(x => x.DisplayPrivacyAgreement == true); + RuleFor(x => x.Email).NotEmpty().WithMessage(localizationService.GetResource("ContactUs.Email.Required")); RuleFor(x => x.Email).EmailAddress().WithMessage(localizationService.GetResource("Common.WrongEmail")); RuleFor(x => x.FullName).NotEmpty().WithMessage(localizationService.GetResource("ContactUs.FullName.Required")); diff --git a/src/Presentation/SmartStore.Web/Views/Home/ContactUs.cshtml b/src/Presentation/SmartStore.Web/Views/Home/ContactUs.cshtml index d0b173f3f2..7105b3d794 100644 --- a/src/Presentation/SmartStore.Web/Views/Home/ContactUs.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Home/ContactUs.cshtml @@ -24,11 +24,6 @@ {
- @if (!ViewData.ModelState.IsValid) - { - @Html.ValidationSummary(true) - } - @T("PageTitle.ContactUs")
@@ -55,6 +50,28 @@
+ @if(Model.DisplayPrivacyAgreement) + { + @Html.HiddenFor(model => model.DisplayPrivacyAgreement) + +
+ @Html.LabelFor(model => model.PrivacyAgreement, new { @class = "control-label required", @for = "PrivacyAgreement" }) +
+
+ +
+
+ @Html.ValidationMessageFor(model => model.PrivacyAgreement) +
+
+
+ } + @if (Model.DisplayCaptcha) {
From da1e3d1cd857bbb41b615b85e6d90d55820edfb3 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 23 Jun 2016 14:53:38 +0200 Subject: [PATCH 158/185] Increased version number to 2.6.0 --- SmartStoreNET.Tasks.Targets | 2 +- src/AssemblyVersionInfo.cs | 6 +- .../SmartStore.AmazonPay/Description.txt | 2 +- .../SmartStore.Clickatell/Description.txt | 2 +- .../SmartStore.DevTools/Description.txt | 2 +- .../SmartStore.DiscountRules/Description.txt | 2 +- .../SmartStore.FacebookAuth/Description.txt | 2 +- .../Description.txt | 2 +- .../Description.txt | 2 +- .../SmartStore.OfflinePayment/Description.txt | 2 +- src/Plugins/SmartStore.PayPal/Description.txt | 2 +- .../SmartStore.Shipping/Description.txt | 2 +- .../Description.txt | 2 +- src/Plugins/SmartStore.Tax/Description.txt | 2 +- src/Plugins/SmartStore.WebApi/Description.txt | 2 +- .../Localization/App/de/all.smres.xml | 339 ++++++++++++++++-- .../App_Data/Localization/App/de/head.txt | 2 +- .../Localization/App/en/all.smres.xml | 333 +++++++++++++++-- .../App_Data/Localization/App/en/head.txt | 2 +- 19 files changed, 616 insertions(+), 94 deletions(-) diff --git a/SmartStoreNET.Tasks.Targets b/SmartStoreNET.Tasks.Targets index 4fb755fcf5..f184eaa7eb 100644 --- a/SmartStoreNET.Tasks.Targets +++ b/SmartStoreNET.Tasks.Targets @@ -44,7 +44,7 @@ x86 $(BUILD_NUMBER) - 2.5.0 + 2.6.0 $(StageFolder) .$(Version) diff --git a/src/AssemblyVersionInfo.cs b/src/AssemblyVersionInfo.cs index 67335b18c1..f74f0edf78 100644 --- a/src/AssemblyVersionInfo.cs +++ b/src/AssemblyVersionInfo.cs @@ -9,7 +9,7 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("2.5.0.0")] +[assembly: AssemblyVersion("2.6.0.0")] -[assembly: AssemblyFileVersion("2.5.0.0")] -[assembly: AssemblyInformationalVersion("2.5.0.0")] +[assembly: AssemblyFileVersion("2.6.0.0")] +[assembly: AssemblyInformationalVersion("2.6.0.0")] diff --git a/src/Plugins/SmartStore.AmazonPay/Description.txt b/src/Plugins/SmartStore.AmazonPay/Description.txt index 3e203915e6..8237c4a7ad 100644 --- a/src/Plugins/SmartStore.AmazonPay/Description.txt +++ b/src/Plugins/SmartStore.AmazonPay/Description.txt @@ -1,6 +1,6 @@ FriendlyName: Pay with Amazon SystemName: SmartStore.AmazonPay -Version: 2.5.0 +Version: 2.6.0 Group: Payment MinAppVersion: 2.5.0 Author: SmartStore AG diff --git a/src/Plugins/SmartStore.Clickatell/Description.txt b/src/Plugins/SmartStore.Clickatell/Description.txt index d5e561a2fc..177b509843 100644 --- a/src/Plugins/SmartStore.Clickatell/Description.txt +++ b/src/Plugins/SmartStore.Clickatell/Description.txt @@ -1,7 +1,7 @@ FriendlyName: Clickatell SMS Provider SystemName: SmartStore.Clickatell Group: Mobile -Version: 2.5.0 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.Clickatell.dll diff --git a/src/Plugins/SmartStore.DevTools/Description.txt b/src/Plugins/SmartStore.DevTools/Description.txt index fff9df57de..b169d8b16a 100644 --- a/src/Plugins/SmartStore.DevTools/Description.txt +++ b/src/Plugins/SmartStore.DevTools/Description.txt @@ -1,7 +1,7 @@ FriendlyName: SmartStore.NET Developer Tools (MiniProfiler and other goodies) SystemName: SmartStore.DevTools Group: Developer -Version: 2.5.0 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.DevTools.dll diff --git a/src/Plugins/SmartStore.DiscountRules/Description.txt b/src/Plugins/SmartStore.DiscountRules/Description.txt index a2336d8b8e..f4bb29d728 100644 --- a/src/Plugins/SmartStore.DiscountRules/Description.txt +++ b/src/Plugins/SmartStore.DiscountRules/Description.txt @@ -2,7 +2,7 @@ Description: Contains common discount requirement rule providers like "Billing country is", "Customer role is", "Had spent amount" etc. Group: Marketing SystemName: SmartStore.DiscountRules -Version: 2.5.0.1 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 0 FileName: SmartStore.DiscountRules.dll diff --git a/src/Plugins/SmartStore.FacebookAuth/Description.txt b/src/Plugins/SmartStore.FacebookAuth/Description.txt index 6e028c02d4..b5411fc4ac 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Description.txt +++ b/src/Plugins/SmartStore.FacebookAuth/Description.txt @@ -1,7 +1,7 @@ FriendlyName: Facebook SystemName: SmartStore.FacebookAuth Group: Security -Version: 2.5.0 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 5 FileName: SmartStore.FacebookAuth.dll diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Description.txt b/src/Plugins/SmartStore.GoogleAnalytics/Description.txt index 908e329118..59bf21b018 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Description.txt +++ b/src/Plugins/SmartStore.GoogleAnalytics/Description.txt @@ -1,7 +1,7 @@ FriendlyName: Google Analytics SystemName: SmartStore.GoogleAnalytics Group: Analytics -Version: 2.5.0 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.GoogleAnalytics.dll diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt index 8187db5c15..5bc3e9ad59 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt @@ -1,7 +1,7 @@ FriendlyName: Google Merchant Center (GMC) feed SystemName: SmartStore.GoogleMerchantCenter Group: Marketing -Version: 2.5.0.1 +Version: 2.6.0 MinAppVersion: 2.5.0 Author: SmartStore AG DisplayOrder: 1 diff --git a/src/Plugins/SmartStore.OfflinePayment/Description.txt b/src/Plugins/SmartStore.OfflinePayment/Description.txt index 0b09221978..db6f0241f9 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Description.txt +++ b/src/Plugins/SmartStore.OfflinePayment/Description.txt @@ -2,7 +2,7 @@ Description: Contains common offline payment methods like Direct Debit, Invoice, Prepayment etc. Group: Payment SystemName: SmartStore.OfflinePayment -Version: 2.5.0 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 0 FileName: SmartStore.OfflinePayment.dll diff --git a/src/Plugins/SmartStore.PayPal/Description.txt b/src/Plugins/SmartStore.PayPal/Description.txt index c78ff1bd32..75a43e5adb 100644 --- a/src/Plugins/SmartStore.PayPal/Description.txt +++ b/src/Plugins/SmartStore.PayPal/Description.txt @@ -2,7 +2,7 @@ Description: Provides the PayPal payment methods PayPal Standard, PayPal Direct, PayPal Express and PayPal PLUS. SystemName: SmartStore.PayPal Group: Payment -Version: 2.5.0.2 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.PayPal.dll diff --git a/src/Plugins/SmartStore.Shipping/Description.txt b/src/Plugins/SmartStore.Shipping/Description.txt index 5d85eddbb8..f241173e84 100644 --- a/src/Plugins/SmartStore.Shipping/Description.txt +++ b/src/Plugins/SmartStore.Shipping/Description.txt @@ -2,7 +2,7 @@ Description: Provides shipping methods for fixed rate shipping and computation based on weight. SystemName: SmartStore.Shipping Group: Shipping -Version: 2.5.0 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.Shipping.dll diff --git a/src/Plugins/SmartStore.ShippingByWeight/Description.txt b/src/Plugins/SmartStore.ShippingByWeight/Description.txt index 76876783c9..218a277c2b 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Description.txt +++ b/src/Plugins/SmartStore.ShippingByWeight/Description.txt @@ -1,7 +1,7 @@ FriendlyName: Shipping by weight SystemName: SmartStore.ShippingByWeight Group: Shipping -Version: 2.5.0.1 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.ShippingByWeight.dll diff --git a/src/Plugins/SmartStore.Tax/Description.txt b/src/Plugins/SmartStore.Tax/Description.txt index 79d79ec7a8..ec211f9efe 100644 --- a/src/Plugins/SmartStore.Tax/Description.txt +++ b/src/Plugins/SmartStore.Tax/Description.txt @@ -2,7 +2,7 @@ Description: Contains default tax providers like FixedRate, ByRegion etc. Group: Tax SystemName: SmartStore.Tax -Version: 2.5.0 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 0 FileName: SmartStore.Tax.dll diff --git a/src/Plugins/SmartStore.WebApi/Description.txt b/src/Plugins/SmartStore.WebApi/Description.txt index eacdc805f8..646ac2fd0d 100644 --- a/src/Plugins/SmartStore.WebApi/Description.txt +++ b/src/Plugins/SmartStore.WebApi/Description.txt @@ -1,6 +1,6 @@ FriendlyName: SmartStore.NET Web Api SystemName: SmartStore.WebApi -Version: 2.5.0.1 +Version: 2.6.0 Group: Api MinAppVersion: 2.5.0 Author: SmartStore AG diff --git a/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/all.smres.xml b/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/all.smres.xml index e7e5d5e8fa..8e379a8055 100644 --- a/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/all.smres.xml +++ b/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/all.smres.xml @@ -3136,6 +3136,12 @@ Bearbeiten + + Die E-Mail wurde erfolgreich versendet. + + + Bitte geben Sie eine E-Mail-Adresse ein. + Objekt @@ -3187,6 +3193,12 @@ Als XML exportieren + + Die Datei ist in Benutzung und kann daher nicht geöffnet werden. + + + Datei nicht gefunden + {0} Dateien wurden gelöscht @@ -3196,6 +3208,9 @@ {0} Verzeichnisse wurden gelöscht + + FTP-Status {0} ({1}). + Allgemein @@ -3220,6 +3235,12 @@ Verbergen + + HTTP-Status {0} ({1}). + + + Ignorieren + Importdatei @@ -3232,6 +3253,9 @@ Info + + Letzte Ausführung + Lizenzieren @@ -3256,9 +3280,6 @@ Es wurden keine Einträge ausgewählt. - - Es sind keine temporären Dateien vorhanden. - von @@ -3328,6 +3349,9 @@ Anzeigen + + Werte für Überspringen und Begrenzen müssen größer oder gleich 0 sein. + Ausgelassen @@ -3361,12 +3385,12 @@ Alle Shops + + Erfolgreich am + Der Vorgang wurde erfolgreich ausgeführt. - - Temporäre Dateien - Zeilen insgesamt @@ -4045,6 +4069,9 @@ Die E-Mail wurde erfolgreich versandt. + + Test der E-Mail-Funktion. + Das E-Mail-Konto wurde erfolgreich bearbeitet. @@ -4474,6 +4501,9 @@ Regionale Einstellungen + + Sie müssen zunächst speichern, bevor Sie Einschränkungen festlegen können. + Einstellungen @@ -4666,6 +4696,12 @@ Legt fest, ob das Standardbild bei Warengruppen ausgeblendet werden soll. Das Standardbild wird angezeigt, wenn der Warengruppe kein Bild zugeordnet ist. + + Standardbild bei Herstellern ausblenden + + + Legt fest, ob das Standardbild bei Herstellern ausgeblendet werden soll. Das Standardbild wird angezeigt, wenn dem Hersteller kein Bild zugeordnet ist. + Standardbild bei Produkten ausblenden @@ -4972,6 +5008,12 @@ Bestimmt ob Hersteller auf der Homepage als Bilder oder textuelle Links angezeigt werden. + + Bilder von Herstellern anzeigen + + + Legt fest, ob Herstellerbilder auf der Produktdetailseite angezeigt werden sollen. + Zeige Hersteller auf der Homepage @@ -5242,6 +5284,18 @@ Bestimmt ob Kundennummern hinterlegt werden können. + + Kundennummern + + + Legt fest, ob Kundennummern vergeben werden und ob diese automatisch vergeben werden sollen. + + + Darstellung der Kundennummer + + + Legt die Darstellung und Handhabung der Kundennummer gegenüber dem Kunden fest. + Kundeneinstellungen @@ -5278,6 +5332,12 @@ Bestimmt ob Kunden ihre Kundennummer in Ihrem Account-Bereich einsehen können. + + Einwilligungserklärung im Kontaktformular fordern + + + Bestimmt ob im Kontaktformular eine Checkbox angezeigt wird, die den Benutzer auffordert der Speicherung seiner Daten zuzustimmen. + Automatische Registrierung aktiviert @@ -6382,6 +6442,12 @@ Legen Sie einen Startwert für Bestellnummern fest. Dies ist nützlich, wenn Sie Ihre Aufträge ab einem Bestimmten Wert starten lassen möchten. Der Wert muss größer sein, als die aktuell höchste Bestellnummer und gilt für zukünftige Bestellungen. + + Anzahl der Aufträge pro Seite + + + Legt die Anzahl der dargestellten Aufträge pro Seite fest. + Auftragseinstellungen @@ -6712,6 +6778,21 @@ Legt fest ob das Produktgewicht im Warenkorb angezeigt wird. + + Zustimmung zur E-Mail Weitergabe an Dritte + + + Legt fest, ob Kunden bei einer Bestellung der Weitergabe ihrer E-Mail Adresse an Dritte zustimmen können und ob die Checkbox dafür standardmäßig aktiviert ist. + + + Text für E-Mail Weitergabe + + + Mit der Übermittlung und Speicherung meiner E-Mail-Adresse durch dritte Parteien bin ich einverstanden. + + + Legt den Text für die Zustimmung zur Weitergabe der E-Mail Adresse an Dritte fest. Wählen Sie bitte einen konkreten Grund für die Weitergabe, z.B. 'Mit der Übermittlung und Speicherung meiner E-Mail-Adresse zur Abwicklung des Käuferschutzes durch Trusted Shops bin ich einverstanden.' + Wunschzettel @@ -8629,6 +8710,9 @@ Dashboard + + Neues Profil + Standardwert @@ -8644,9 +8728,6 @@ Die gespeicherten Feldzuordnungen sind aufgrund der Änderung des Trennzeichens ungültig und wurden zurückgesetzt. - - Die folgenden Objekteigenschaft wurden mehrfach zugeodnet und deshalb ignoriert: {0} - Trennzeichen @@ -8708,10 +8789,10 @@ Ein System-Exportprofil kann nicht gelöscht werden. - Zum Schluss aufräumen + Nach erfolgreicher Veröffentlichung aufräumen - Legt fest, ob nicht mehr benötigte Dateien nach der Bereitstellung gelöscht werden sollen. + Legt fest, ob nicht mehr benötigte Dateien gelöscht werden sollen, nachdem alle Veröffentlichungen erfolgreich waren. Einstellungen übernehmen von @@ -8726,10 +8807,10 @@ Export von Profil "{0}" ist abgeschlossen - E-Mail-Addressen an + E-Mail Adressen an - Legt die E-Mail Addressen fest, an die die Benachrichtigung geschickt werden soll. + Legt die E-Mail Adressen fest, an die die Benachrichtigung geschickt werden soll. Die folgenden spezifischen Angaben werden durch den Provider beim Export berücksichtigt. @@ -8743,17 +8824,17 @@ Legt fest, ob die Exportdateien in einem ZIP-Archiv zusammengefasst werden sollen. Das Archiv verbleibt im temporären Ordner des Exportprofils ohne weitere Vearbeitung. - - ZIP-Archiv erstellen + + Mindestens eine Datei konnte nicht kopiert werden. Legt fest, ob die Exportdateien in einem ZIP-Archiv zusammengefasst und nur das Archiv bereitgestellt werden soll. - Art der Bereitstellung + Art der Veröffentlichung - Legt die Art Bereitstellung fest. + Legt die Art Veröffentlichung fest. E-Mail Konto @@ -8762,10 +8843,10 @@ Legt das E-Mail Konto fest, über welches die Daten verschickt werden sollen. - E-Mail-Addressen an + E-Mail Adressen an - Legt die E-Mail Addressen fest, an die die Daten verschickt werden soll. + Legt die E-Mail Adressen fest, an die die Daten verschickt werden soll. E-Mail Betreff @@ -8785,20 +8866,20 @@ Legt fest, aus welcher Art die Exportdateien per HTTP übertragen werden sollen. - - In öffentlichen Ordner kopieren - Legt fest, ob die exportierten Daten in einen übers Internet zugänglichen Ordner kopiert werden sollen. - Name + Name des Profils - Legt den Namen der Bereitstellung fest. + Legt den Namen des Veröffentlichungsprofils fest. + + + Es liegen keine Veröffentlichungsprofile vor. - Legen Sie über <b>Hinzufügen</b> ein oder mehrere Veröffentlichungsprofile an, um festzulegen wie mit den Exportdateien weiter zu verfahren ist. + Legen Sie über <b>Neues Profil</b> ein oder mehrere Veröffentlichungsprofile an, um festzulegen wie mit den Exportdateien weiter zu verfahren ist. Passiver Modus @@ -8812,6 +8893,18 @@ Legt das Passwort fest. + + Veröffentlichungsprofile + + + Veröffentlichungsziel + + + Name des Unterordners + + + Legt den Namen eines Unterordners fest, in den die Daten veröffentlicht werden sollen. + URL\Host @@ -8830,6 +8923,9 @@ Legt fest, ob einen SSL (Secure Sockets Layer) Verbindung genutzt werden soll. + + Bei einer großen Anzahl an Exportdateien wird empfohlen die Option <b>ZIP-Archiv erstellen</b> zu benutzen. Das spart Zeit und vermeidet Probleme, wie z.B. ein volles E-Mail Postfach. + E-Mail Benachrichtigung @@ -8860,6 +8956,9 @@ Legt das Muster fest, nach dem Dateinamen erzeugt werden. + + Bitte ein gültiges Muster für Dateinamen eingeben. Beispiel: %Store.Id%-%Profile.Id%-%File.Index%-%Profile.SeoName% + ID des Exportprofils;Ordername des Exportprofils;SEO Name des Exportprofils;Shop ID;SEO Name des Shops;Mit 1 beginnender Dateiindex;Zufallszahl;UTC Zeitstempel @@ -8875,6 +8974,12 @@ Nach der Verfügbarkeitsmenge filtern. + + Rechnungsländer + + + Nach Rechnungsländern filtern. + Warengruppen @@ -8905,6 +9010,18 @@ Nach empfohlenen Produkten filtern. Wird nur bei der Filterung nach Warengruppen und Hersteller angewendet. + + Hat x Bestellungen + + + Nach der Anzahl der getätigten Bestellungen filtern. + + + Hat Betrag x ausgegeben + + + Nach dem insgesamt ausgegebenen Betrag filtern. + Produkt-ID bis @@ -8917,12 +9034,42 @@ Nach der Produkt-ID filtern. + + Nur aktive Kunden + + + Nach aktiven bzw. inaktiven Kunden filtern. + + + Nur aktive Abonnenten + + + Nach aktiven bzw. inaktiven Newsletter Abonnenten filtern. + Veröffentlicht Nach Veröffentlichung filtern. + + Nur MwSt. befreite Kunden + + + Nach MwSt. befreiten Kunden filtern. + + + Zuletzt aktiv von + + + Nach dem Datum der letzten Shop Aktivität filtern. + + + Zuletzt aktiv bis + + + Nach dem Datum der letzten Shop-Aktivität filtern. + Hersteller @@ -8968,6 +9115,12 @@ Nach Produkttyp filtern. + + Versandländer + + + Nach Versandländern filtern. + Versandstatus @@ -8992,14 +9145,14 @@ Nach fehlender Herstellerzuordnung filtern. - - Bitte einen gültigen Ordner- und Dateinamen eingeben. Beispiel für Dateinamen: %File.Index%-%Profile.Id%-gmc-%Store.Name% - - Ordnername + Ordnerpfad - Legt den Namen des Ordners fest, in den die Daten exportiert werden. + Legt den relativen Pfad des Ordners fest, in den die Daten exportiert werden. + + + Bitte einen gültigen, relativen Ordnerpfad für die zu exportierenden Daten eingeben. Systemprofil @@ -9040,6 +9193,12 @@ Diese Option wird in der Vorschau nicht berücksichtigt. + + Mit den folgenden Einstellungen lassen sich die zu exportierenden Daten aufteilen. Dazu zählt<ul><li>Das Überspringen der ersten n Datensätze</li><li>Die maximale Anzahl zu exportierender Datensätze</li><li>Die Anzahl der Datensätze pro Exportdatei</li><li>Daten von jedem Shop in eine separate Datei exportieren</li></ul>Standardmäßig werden alle Daten eines Shops in eine Datei exportiert. + + + Einstellungen zur Aufteilung müssen größer oder gleich 0 sein. + Per Shop @@ -9196,6 +9355,15 @@ Der Systemname des Exportprofils. + + Die folgende Liste enthält Systemprofile, die von Plugins wie bspw. dem <a href='http://community.smartstore.com/index.php?/files/file/85-smartstorenet-common-export-providers/' target='_blank'>Datenexporte Plugin</a> bereitgestellt werden. Sie können Systemprofile nach Belieben anpassen, aber keine Neuen erstellen. Für diese Profile stehen außerdem zusätzliche Aktions-Buttons zur Verfügung. Sie finden diese über den entsprechenden Listen, wie z.B. der Produkt- oder Auftragsliste. + + + Systemprofile + + + Benutzerprofile + Importdatei hinzufügen... @@ -9241,11 +9409,14 @@ Es wurden keine Importprofile gefunden. - - Laden Sie bitte eine Importdatei hoch, legen Sie einen aussagekräftigen Namen für das Importprofil fest und speichern Sie. + + Anzahl der Bilder + + + Legt die Anzahl der zu importierenden Bilder pro Objekt fest. - - Wählen Sie bitte ein Objekt aus, das Sie importieren möchten. + + Wählen Sie bitte das zu importierende Objekt und laden Sie eine Importdatei hoch. {0} von {1} Datensätzen verarbeitet @@ -9500,7 +9671,7 @@ Zahlung wurde erfasst - Fehler bei Zahlungserfassung für Auftrag #{0}. Fehler: {1} + Es ist ein Fehler bei der Zahlungsbuchung zu Auftrag {0} aufgetreten. Auftrag wurde gelöscht @@ -9607,6 +9778,12 @@ Auftragsdetails bearbeiten + + Akzeptiert Weitergabe der E-Mail + + + Gibt an, ob der Kunde bei der Bestellung einer Weitergabe seiner E-Mail Adresse an Dritte zugestimmt hat oder nicht. + Partner @@ -11560,6 +11737,9 @@ Abbruch erzwungen durch Herunterfahren der Anwendung + + Die geplante Aufgabe "{0}" wurde abgebrochen + Abbruchanforderung wurde übermittelt. @@ -11617,6 +11797,9 @@ Datum der nächsten geplanten Ausführung + + Fehler beim Ausführen der Aufgabe "{0}" + Jetzt ausführen @@ -11866,6 +12049,9 @@ Es wird empfohlen, nur eine Versandkosten-Offline-Berechnungsmethode zu benutzen. + + Die Erreichbarkeit der Sitemap konnte nicht überprüft werden. + Die Sitemap für den Shop ist erreichbar. @@ -12169,6 +12355,9 @@ Bitte geben Sie Benutzername und Passwort ein + + Der Wert muss größer 0 sein. + API ist nicht erreichbar. @@ -12577,6 +12766,15 @@ Fortsetzen + + In die Zwischenablage kopieren + + + Kopieren ist fehlgeschlagen. + + + Kopiert! + Anzahl @@ -12769,6 +12967,9 @@ Lade nächsten Schritt… + + Diese Funktion steht für Gäste nicht zur Verfügung. + Verschiedenes @@ -12778,6 +12979,9 @@ Mehr Info + + Mein + Navigation @@ -12793,6 +12997,12 @@ Nein, abbrechen + + Es sind keine Dateien vorhanden. + + + Es wurde keine Datei hochgeladen. + Der Vorgang wurde aus Sicherheitsgründen nicht ausgeführt. @@ -12865,6 +13075,9 @@ Veröffentlicht + + Veröffentlichung + Frage @@ -13036,6 +13249,15 @@ Namen eingeben + + Einwilligungserklärung Datenschutz + + + Ja, ich habe die <a href="{0}">Datenschutzerklärung</a> zur Kenntnis genommen und bin damit einverstanden, dass die von mir angegebenen Daten elektronisch erhoben und gespeichert werden. Meine Daten werden dabei nur zur Bearbeitung meiner Anfrage genutzt. + + + Bitte stimmen Sie der Speicherung Ihrer Daten zu. + Ihre Anfrage wurde erfolgreich übermittelt. @@ -13252,6 +13474,27 @@ Benutzernamen anzeigen + + Automatisch vergeben + + + Deaktiviert + + + Aktiviert + + + Anzeigen + + + Immer editierbar + + + Editierbar falls leer + + + Nicht anzeigen + Lesbar @@ -13277,7 +13520,10 @@ FTP - HTTP + HTTP POST + + + Öffentlicher Ordner Detailbeschreibung @@ -13295,7 +13541,7 @@ Produktname + Kurzbeschreibung - Nicht spezifiziert + Keine Kurzbeschreibung @@ -13447,6 +13693,15 @@ Nicht anzeigen + + Aktiviert anzeigen + + + Deaktiviert anzeigen + + + Nicht anzeigen + Abgebrochen @@ -14807,6 +15062,9 @@ Datenschutzerklärung + + Private Nachrichten sind deaktiviert. + Posteingang @@ -14937,7 +15195,10 @@ Nicht am Lager - Grundpreis: {0} pro {1} + Inhalt: {0} {1} ({2} / {3} {1}) + + + {0} {1} ({2} / {3} {1}) Home @@ -15138,7 +15399,7 @@ Dieses Produkt ist ausverkauft. - Dieses Produkt ist ausverkauft. + Keine Bundle-Bestandteile vorhanden Das Produkt {0} wurde nicht gefunden. diff --git a/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/head.txt b/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/head.txt index fb2d850500..f822a9cb76 100644 --- a/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/head.txt +++ b/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/head.txt @@ -1 +1 @@ -201601262000441_ImportFramework1 \ No newline at end of file +201605201911421_ExportRevision \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/all.smres.xml b/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/all.smres.xml index 6b493fa664..734f05e1de 100644 --- a/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/all.smres.xml +++ b/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/all.smres.xml @@ -3232,6 +3232,12 @@ Edit + + The email has been successfully sent. + + + Please enter an email address. + Object @@ -3283,6 +3289,12 @@ Export to XML + + The file is in use and cannot be opened. + + + File not found + {0} files were deleted @@ -3292,6 +3304,9 @@ {0} folders were deleted + + FTP status {0} ({1}). + General @@ -3316,6 +3331,12 @@ Hide + + HTTP status {0} ({1}). + + + Ignore + Import file @@ -3328,6 +3349,9 @@ Info + + Last run + License @@ -3352,9 +3376,6 @@ No entries have been selected. - - There are no temporary files. - of @@ -3424,6 +3445,9 @@ Show + + Values for skip and limit must be greater than or equal to 0. + Skipped @@ -3457,12 +3481,12 @@ All stores + + Successful on + The task was successfully processed. - - Temporary files - Total rows @@ -4171,6 +4195,9 @@ Email has been successfully sent. + + Testing email functionality. + The email account has been updated successfully. @@ -4627,6 +4654,9 @@ Regional Settings + + You need to save before you can specify restrictions. + Settings @@ -4751,7 +4781,7 @@ Check the box to allow customers to use the 'Compare Products' option in your store - Amount of displayed products per page + Number of displayed products per page Determines the amount of displayed products per page. @@ -4822,6 +4852,12 @@ Specifies whether to hide the default image for categories. The default image is shown when no image is assigned to a category. + + Hide default picture for manufacturers + + + Specifies whether to hide the default image for manufacturers. The default image is shown when no image is assigned to a manufacturer. + Hide default picture for products @@ -5128,6 +5164,12 @@ Specifies whether manufacturers will be displayed as images or textual links on the homepage. + + Show manufacturer pictures + + + Specifies whether to show manufacturer pictures on product detail page. + Show manufacturers on homepage @@ -5398,6 +5440,18 @@ Specifies whether customer numbers can be saved. + + Customer numbers + + + Specifies whether to assign customer numbers and whether they should be created automatically. + + + Customer number presentation + + + Specifies the presentation and handling of the customer number to the customer. + Customer settings @@ -5434,6 +5488,12 @@ Specifies whether customer numbers will be displayed to customers in their account area. + + Get privacy consent for contact requests + + + Specifies whether a checkbox will be displayed on the contact page which requests the user to agree on storage of his data. + Auto register enabled @@ -6550,6 +6610,12 @@ Set the order ID counter. This is useful if you want your orders to start at a certain number. This only affects orders created going forward. The value must be greater than the current maximum order ID. + + Number of displayed orders per page + + + Specifies the number of displayed orders per page. + Order settings @@ -6880,6 +6946,21 @@ Determines whether the product weight is shown in shopping cart + + Consent for email transfer to third parties + + + Specifies whether customers can agree to a transferring of their email address to third parties when ordering, and whether the checkbox is enabled by default during checkout. + + + Text for email transfer consent + + + I agree to the transfer and storage of my email address by third parties. + + + Specifies the text to be displayed to the customer. Please choose a specific reason, e.g. 'I agree to the transfer and storage of my email address for TrustedShops buyer protection.' + Wishlist @@ -8887,6 +8968,9 @@ Store Statistics + + New profile + Default Value @@ -8902,9 +8986,6 @@ The stored field assignments are invalid due to the change of the delimiter and were reset. - - The following object properties were multiple assigned and thus ignored: {0} - Delimiter @@ -8966,10 +9047,10 @@ Cannot delete a system export profile. - Clean up at the end + Clean up after successful deployment - Specifies whether to delete unneeded files after deployment. + Specifies whether to delete unneeded files after all deployments succeeded. Apply settings from @@ -9001,17 +9082,17 @@ Specifies whether to combine the export files in temporary a ZIP archive. The archive remains in the temporary folder of the export profile without further processing. - - Create ZIP archive + + At least one file could not be copied. Specifies whether to combine the export files in a ZIP archive and only to deploy the archive. - Type of deployment + Publishing type - Specifies the deployment type. + Specifies the type of publishing. Email account @@ -9043,20 +9124,20 @@ Specifies how to transmit the export files via HTTP. - - Copy to public folder - Specifies whether to copy the exported data into a folder that is accessible through the internet. - Name + Name of profile - Specifies the name of the deployment. + Specifies the name of the publishing profile. + + + There are no publishing profiles. - Click <b>Insert</b> to add one or multiple publishing profiles to specify how to further proceed with the export files. + Click <b>New profile</b> to add one or multiple publishing profiles to specify how to further proceed with the export files. Passive mode @@ -9070,6 +9151,18 @@ Specifies the password. + + Publishing profiles + + + Publishing target + + + Name of subfolder + + + Specifies the name of a subfolder where to publish the data. + URL\Host @@ -9088,6 +9181,9 @@ Specifies whether to use a SSL (Secure Sockets Layer) connection. + + If there are a large number of export files, it is recommended to use the option <b>Create ZIP archive</b>. This saves time and avoids problems, such as a full email mailbox. + Email notification @@ -9118,6 +9214,9 @@ Specifies the pattern for creating file names. + + Please enter a valid pattern for file names. Example for file names: %Store.Id%-%Profile.Id%-%File.Index%-%Profile.SeoName% + ID of export profil;Folder name of export profil;SEO name of export profil;Store ID;SEO name of store;One based file index;Random number;UTC timestamp @@ -9133,6 +9232,12 @@ Filter by availability quantity. + + Billing countries + + + Filter by billing countries. + Categories @@ -9163,6 +9268,18 @@ Filter by featured products. Is only applied when the filtering by categories and manufacturers. + + Has placed x orders + + + Filter by number of placed orders. + + + Has spent amount x + + + Filter by spent amount. + Product Id to @@ -9175,12 +9292,42 @@ Filter by product identifier. + + Only active customers + + + Filter by active or inactive customers. + + + Only active subscribers + + + Filter by active or inactive newsletter subscribers. + Published Filter by publishing. + + Only tax exempt customers + + + Filter by tax exempt customers. + + + Last activity from + + + Filter by date of last store activity. + + + Last active until + + + Filter by date of last store activity. + Manufacturer @@ -9226,6 +9373,12 @@ Filter by product type. + + Shipping countries + + + Filter by shipping countries. + Shipping status @@ -9250,14 +9403,14 @@ Filter by missing manufacturer mapping. - - Please enter a valid folder and file name. Example for file names: %File.Index%-%Profile.Id%-gmc-%Store.Name% - - Folder name + Folder path - Specifies the name of the folder where to export the data. + Specifies the relative path of the folder where to export the data. + + + Please enter a valid, relative folder path for the export data. System profile @@ -9298,6 +9451,12 @@ This option is not taken into account in the preview. + + With the following settings you can partition the data to be exported. This includes<ul><li>Skipping the first n records</li><li>The maximum number of records to be exported</li><li>The number of records per export file</li><li>Export data for each shop in a separate file</li></ul>By default, all data of a store will be exported into one file. + + + Partitioning setting values must be greater than or equal to 0. + Per store @@ -9454,6 +9613,15 @@ The system name of the export profile. + + The following list contains system profiles, which are provided by plugins such as the <a href='http://community.smartstore.com/index.php?/files/file/85-smartstorenet-common-export-providers/' target='_blank'>Data Export Plugin</a>. You can customize system profiles as desired, but cannot create new ones. These profiles also add additional action buttons. You will find these above data lists, such as the product or order list. + + + System profiles + + + User profiles + Add import file... @@ -9499,11 +9667,14 @@ There were no import profiles found. - - Please upload an import file, enter a meaningful name for the import profile and save. + + Number of pictures + + + Specifies the number of images per object to be imported. - - Please select an object that you want to import. + + Please select the import object and upload an import file. {0} of {1} records processed @@ -9773,7 +9944,7 @@ Order has been captured - Error capturing order #{0}. Error: {1} + Unable to capture payment for order {0}. Order has been deleted @@ -9886,6 +10057,12 @@ Affiliate + + Accepts transfer of email + + + Indicates whether the customer has agreed to a transfer of his email address to third parties. + Affiliate @@ -11893,6 +12070,9 @@ Abnormally aborted due to application shutdown + + The scheduled task "{0}" has been canceled + Cancellation request has been submitted. @@ -11950,6 +12130,9 @@ Date of the next scheduled execution + + Error while running scheduled task "{0}" + Run now @@ -12223,6 +12406,9 @@ Only one offline shipping rate computation method is recommended to use + + The reachability of the sitemap could not be validated. + The sitemap for the store is reachable. @@ -12544,6 +12730,9 @@ Please enter username and password + + The value must be greater than 0. + API not available. @@ -12952,6 +13141,15 @@ Continue + + Copy to clipboard + + + Failed to copy. + + + Copied! + Count @@ -13144,6 +13342,9 @@ Loading next step... + + This function is not available for guests. + Miscellaneous @@ -13153,6 +13354,9 @@ More info + + My + Navigation @@ -13168,6 +13372,12 @@ No, cancel + + There are no files available. + + + There was no file uploaded. + The operation was not carried out for security reasons. @@ -13240,6 +13450,9 @@ Published + + Publishing + Question @@ -13411,6 +13624,15 @@ Enter your name + + Privacy consent + + + Yes I've read the <a href="{0}">privacy terms</a> and agree that my data given by me can be stored electronically. My data will thereby only be used to process my inquiry. + + + Please agree to the storage of your data. + Your enquiry has been successfully sent to the store owner. @@ -13624,6 +13846,27 @@ Show usernames + + Automatically assigned + + + Disabled + + + Enabled + + + Display + + + Always editable + + + Editable if empty + + + Do not display + Clear @@ -13649,7 +13892,10 @@ FTP - HTTP + HTTP POST + + + Public folder Description @@ -13667,7 +13913,7 @@ Product name + short description - Not specified + None Short description @@ -13819,6 +14065,15 @@ Do not show + + Show activated + + + Show deactivated + + + Do not show + Cancelled @@ -15156,6 +15411,9 @@ Privacy Notice + + Private messages are disabled. + Inbox @@ -15286,7 +15544,10 @@ Out of stock - base price: {0} per {1} + Content: {0} {1} ({2} / {3} {1}) + + + {0} {1} ({2} / {3} {1}) Top @@ -15487,7 +15748,7 @@ This product is sold out. - This product is sold out. + No bundle items available The product {0} was not found. diff --git a/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/head.txt b/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/head.txt index fb2d850500..f822a9cb76 100644 --- a/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/head.txt +++ b/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/head.txt @@ -1 +1 @@ -201601262000441_ImportFramework1 \ No newline at end of file +201605201911421_ExportRevision \ No newline at end of file From f15dcc25220a96d4f326978e048c15b781124f1d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 30 Jun 2016 10:55:41 +0200 Subject: [PATCH 159/185] PayPal PLUS throws an exception if an initially empty configuration page is saved --- src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs | 3 ++- src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj | 2 +- .../SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs index d7752e61f5..f81f6c2d7f 100644 --- a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs +++ b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs @@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations; using System.Net; using System.Web.Mvc; -using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; @@ -164,6 +163,7 @@ public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) { SecurityProtocol = settings.SecurityProtocol; UseSandbox = settings.UseSandbox; + TransactMode = (int)Settings.TransactMode.AuthorizeAndCapture; AdditionalFee = settings.AdditionalFee; AdditionalFeePercentage = settings.AdditionalFeePercentage; @@ -179,6 +179,7 @@ public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) { settings.SecurityProtocol = SecurityProtocol; settings.UseSandbox = UseSandbox; + settings.TransactMode = Settings.TransactMode.AuthorizeAndCapture; settings.AdditionalFee = AdditionalFee; settings.AdditionalFeePercentage = AdditionalFeePercentage; diff --git a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj index 2690f97cb2..e82c4a4097 100644 --- a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj +++ b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj @@ -70,7 +70,7 @@ ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - True + False diff --git a/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs b/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs index 54fa987d14..b7355b4299 100644 --- a/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs +++ b/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs @@ -25,7 +25,7 @@ public PayPalPlusConfigValidator(ILocalizationService localize, Func x.ThirdPartyPaymentMethods) - .Must(x => x.Count <= 5) + .Must(x => x == null || x.Count <= 5) .WithMessage(localize.GetResource("Plugins.Payments.PayPalPlus.ValidateThirdPartyPaymentMethods")); } } From ab3c8b192840817ba25d92a1574888f8e6a119f7 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 4 Jul 2016 12:10:31 +0200 Subject: [PATCH 160/185] Export all product categories rather than all of current store (causes shop connector to not import all categories) --- .../SmartStore.Services/DataExchange/Export/DataExporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 41cb53a69f..26d20b72a8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -293,7 +293,7 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in x => _productAttributeService.Value.GetProductVariantAttributesByProductIds(x, null), x => _productAttributeService.Value.GetProductVariantAttributeCombinations(x), x => _productService.Value.GetTierPricesByProductIds(x, (ctx.Projection.CurrencyId ?? 0) != 0 ? ctx.ContextCustomer : null, ctx.Store.Id), - x => _categoryService.Value.GetProductCategoriesByProductIds(x), + x => _categoryService.Value.GetProductCategoriesByProductIds(x, null, true), x => _manufacturerService.Value.GetProductManufacturersByProductIds(x), x => _productService.Value.GetProductPicturesByProductIds(x), x => _productService.Value.GetProductTagsByProductIds(x), From dfb6e116f640cfc5e71d5a1631183c7e74046276 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 4 Jul 2016 20:38:03 +0200 Subject: [PATCH 161/185] SKU, EAN, MPN of last attribute combination was exported for all combinations --- .../Export/DynamicEntityHelper.cs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs index e925553e9f..70ef30f356 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs @@ -1013,19 +1013,17 @@ private List Convert(DataExporterContext ctx, Product product) //var productValues = new Dictionary(); var dbContext = _dbContext as DbContext; - product = _dbContext.Attach(product); - - var entry = dbContext.Entry(product); - - // the returned object is not the entity and is not being tracked by the context. - // it also does not have any relationships set to other objects. - // CurrentValues only includes database (thus primitive) values. - var productClone = entry.CurrentValues.ToObject() as Product; - - _dbContext.DetachEntity(product); - foreach (var combination in combinations.Where(x => x.IsActive)) { + product = _dbContext.Attach(product); + var entry = dbContext.Entry(product); + + // the returned object is not the entity and is not being tracked by the context. + // it also does not have any relationships set to other objects. + // CurrentValues only includes database (thus primitive) values. + var productClone = entry.CurrentValues.ToObject() as Product; + _dbContext.DetachEntity(product); + var dynObject = ToDynamic(ctx, productClone, combinations, combination); result.Add(dynObject); } From 07a38ef3d9a18119a867b1b0295581b746eb718c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 4 Jul 2016 20:43:48 +0200 Subject: [PATCH 162/185] GMC: Id should be unique when exporting attribute combinations as products --- .../DataExchange/Export/DynamicEntityHelper.cs | 5 +++++ src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt | 2 +- .../Providers/GmcXmlExportProvider.cs | 3 ++- src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md | 4 ++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs index 70ef30f356..c8687ac97e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs @@ -585,6 +585,11 @@ private dynamic ToDynamic( dynObject._AttributeCombinationId = (combination == null ? 0 : combination.Id); dynObject._DetailUrl = ctx.Store.Url.EnsureEndsWith("/") + (string)dynObject.SeName; + if (combination == null) + dynObject._UniqueId = product.Id.ToString(); + else + dynObject._UniqueId = string.Concat(product.Id, "-", combination.Id); + dynObject.Price = CalculatePrice(ctx, product, combination != null); dynObject._BasePriceInfo = product.GetBasePriceInfo(_services.Localization, _priceFormatter.Value, _currencyService.Value, _taxService.Value, diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt index 5bc3e9ad59..6f343b6b69 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt @@ -1,7 +1,7 @@ FriendlyName: Google Merchant Center (GMC) feed SystemName: SmartStore.GoogleMerchantCenter Group: Marketing -Version: 2.6.0 +Version: 2.6.0.1 MinAppVersion: 2.5.0 Author: SmartStore AG DisplayOrder: 1 diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index bc9047d888..9be5e6e3ca 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -204,6 +204,7 @@ protected override void Export(ExportExecuteContext context) string mainImageUrl = product._MainPictureUrl; var futureSpecialPrice = product._FutureSpecialPrice as decimal?; var price = (decimal)product.Price; + var uniqueId = (string)product._UniqueId; string brand = product._Brand; string gtin = product.Gtin; string mpn = product.ManufacturerPartNumber; @@ -244,7 +245,7 @@ protected override void Export(ExportExecuteContext context) } } - writer.WriteElementString("g", "id", _googleNamespace, entity.Id.ToString()); + writer.WriteElementString("g", "id", _googleNamespace, uniqueId); writer.WriteStartElement("title"); writer.WriteCData(((string)product.Name).Truncate(70).RemoveInvalidXmlChars()); diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md index c368c87dd7..c51035cfc6 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md @@ -1,5 +1,9 @@ #Release Notes +##Google Merchant Center (GMC) 2.6.0.1 + ###Bugfixes + * Id should be unique when exporting attribute combinations as products + ##Google Merchant Center (GMC) 2.5.0.1 ###Bugfixes * GMC feed does not generate the sale price if the sale price is set for a future date From 7526f3f8cee44564e3912cdd86d4bb8499456618 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 4 Jul 2016 20:56:04 +0200 Subject: [PATCH 163/185] GMC: No special price exported when the special price period was not specified --- .../Providers/GmcXmlExportProvider.cs | 19 +++++++++++++------ .../changelog.md | 1 + 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index 9be5e6e3ca..3edd31e361 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -202,7 +202,6 @@ protected override void Export(ExportExecuteContext context) string category = (gmc == null ? null : gmc.Taxonomy); string productType = product._CategoryPath; string mainImageUrl = product._MainPictureUrl; - var futureSpecialPrice = product._FutureSpecialPrice as decimal?; var price = (decimal)product.Price; var uniqueId = (string)product._UniqueId; string brand = product._Brand; @@ -211,6 +210,10 @@ protected override void Export(ExportExecuteContext context) string condition = "new"; string availability = "in stock"; + var specialPrice = product._FutureSpecialPrice as decimal?; + if (!specialPrice.HasValue) + specialPrice = product._SpecialPrice; + if (category.IsEmpty()) category = config.DefaultGoogleCategory; @@ -296,13 +299,17 @@ protected override void Export(ExportExecuteContext context) writer.WriteElementString("g", "availability_date", _googleNamespace, availabilityDate); } - if (config.SpecialPrice && futureSpecialPrice.HasValue && entity.SpecialPriceStartDateTimeUtc.HasValue && entity.SpecialPriceEndDateTimeUtc.HasValue) + if (config.SpecialPrice && specialPrice.HasValue) { - var specialPriceDate = "{0}/{1}".FormatInvariant( - entity.SpecialPriceStartDateTimeUtc.Value.ToString(dateFormat), entity.SpecialPriceEndDateTimeUtc.Value.ToString(dateFormat)); + writer.WriteElementString("g", "sale_price", _googleNamespace, specialPrice.Value.FormatInvariant() + " " + (string)currency.CurrencyCode); + + if (entity.SpecialPriceStartDateTimeUtc.HasValue && entity.SpecialPriceEndDateTimeUtc.HasValue) + { + var specialPriceDate = "{0}/{1}".FormatInvariant( + entity.SpecialPriceStartDateTimeUtc.Value.ToString(dateFormat), entity.SpecialPriceEndDateTimeUtc.Value.ToString(dateFormat)); - writer.WriteElementString("g", "sale_price", _googleNamespace, futureSpecialPrice.Value.FormatInvariant() + " " + (string)currency.CurrencyCode); - writer.WriteElementString("g", "sale_price_effective_date", _googleNamespace, specialPriceDate); + writer.WriteElementString("g", "sale_price_effective_date", _googleNamespace, specialPriceDate); + } price = (product._RegularPrice as decimal?) ?? price; } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md index c51035cfc6..2c1f7000cf 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md @@ -3,6 +3,7 @@ ##Google Merchant Center (GMC) 2.6.0.1 ###Bugfixes * Id should be unique when exporting attribute combinations as products + * No special price exported when the special price period was not specified ##Google Merchant Center (GMC) 2.5.0.1 ###Bugfixes From 1a6ff6fb6b6394a9eb2cf9d28d5def49e63d7031 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 7 Jul 2016 15:25:51 +0200 Subject: [PATCH 164/185] GMC: Attribute price adjustments were ignored when exporting attribute combinations as products --- .../Catalog/IProductAttributeParser.cs | 19 +++--- .../Catalog/ProductAttributeParser.cs | 39 ++++++------ .../Export/DynamicEntityHelper.cs | 60 ++++++++++++------- 3 files changed, 63 insertions(+), 55 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs index a02648a747..f8565562e5 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs @@ -33,21 +33,20 @@ public partial interface IProductAttributeParser IEnumerable ParseProductVariantAttributeValues(string attributesXml); /// - /// Get list of localized product variant attribute values + /// Get list of product variant attribute values /// /// Map of combined attributes /// Product variant attributes - /// Language identifier - /// List of localized product variant attribute values - IList ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes, int languageId = 0); + /// List of product variant attribute values + IList ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes); - /// - /// Gets selected product variant attribute value - /// + /// + /// Gets selected product variant attribute value + /// /// XML formatted attributes - /// Product variant attribute identifier - /// Product variant attribute value - IList ParseValues(string attributesXml, int productVariantAttributeId); + /// Product variant attribute identifier + /// Product variant attribute value + IList ParseValues(string attributesXml, int productVariantAttributeId); /// /// Adds an attribute diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs index 0afe47049b..cf4531f322 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs @@ -13,14 +13,13 @@ using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; -using SmartStore.Services.Localization; namespace SmartStore.Services.Catalog { - /// - /// Product attribute parser - /// - public partial class ProductAttributeParser : IProductAttributeParser + /// + /// Product attribute parser + /// + public partial class ProductAttributeParser : IProductAttributeParser { // 0 = ProductId, 1 = AttributeXml Hash private const string ATTRIBUTECOMBINATION_BY_ID_HASH = "SmartStore.parsedattributecombination.id-{0}-{1}"; @@ -194,12 +193,12 @@ where id.HasValue() return values; } - public virtual IList ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes, int languageId = 0) + public virtual IList ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes) { - var values = new List(); + var result = new List(); if (attributeCombination == null || !attributeCombination.Any()) - return values; + return result; var allValueIds = new List(); @@ -219,28 +218,24 @@ public virtual IList ParseProductVariantAttributeValues(Multimap x.Id == id); - if (attributeValue != null) + if (attributeValue != null && !result.Any(x => x.Id == attributeValue.Id)) { - var value = attributeValue.GetLocalized(x => x.Name, languageId, true, false); - - if (!values.Any(x => x.IsCaseInsensitiveEqual(value))) - values.Add(value); + result.Add(attributeValue); break; } } } - return values; + return result; } - - /// - /// Gets selected product variant attribute value - /// - /// Attributes - /// Product variant attribute identifier - /// Product variant attribute value - public virtual IList ParseValues(string attributesXml, int productVariantAttributeId) + /// + /// Gets selected product variant attribute value + /// + /// Attributes + /// Product variant attribute identifier + /// Product variant attribute value + public virtual IList ParseValues(string attributesXml, int productVariantAttributeId) { var selectedProductVariantAttributeValues = new List(); try diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs index c8687ac97e..288d05ab59 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs @@ -136,15 +136,30 @@ private void PrepareProductDescription(DataExporterContext ctx, dynamic dynObjec return price; } - private decimal CalculatePrice(DataExporterContext ctx, Product product, bool forAttributeCombination) + private decimal CalculatePrice( + DataExporterContext ctx, + Product product, + ProductVariantAttributeCombination combination, + IList attributeValues) { - decimal price = product.Price; + var price = product.Price; + var priceCalculationContext = ctx.ProductExportContext as PriceCalculationContext; - // price type - if (ctx.Projection.PriceType.HasValue && !forAttributeCombination) + if (combination != null) { - var priceCalculationContext = ctx.ProductExportContext as PriceCalculationContext; + // price for attribute combination + var attributesTotalPriceBase = decimal.Zero; + + if (attributeValues != null) + { + attributeValues.Each(x => attributesTotalPriceBase += _priceCalculationService.Value.GetProductVariantAttributeValuePriceAdjustment(x)); + } + price = _priceCalculationService.Value.GetFinalPrice(product, null, ctx.ContextCustomer, attributesTotalPriceBase, true, 1, null, priceCalculationContext); + } + else if (ctx.Projection.PriceType.HasValue) + { + // price for product if (ctx.Projection.PriceType.Value == PriceDisplayType.LowestPrice) { bool displayFromMessage; @@ -163,7 +178,6 @@ private decimal CalculatePrice(DataExporterContext ctx, Product product, bool fo return ConvertPrice(ctx, product, price) ?? price; } - private List GetLocalized(DataExporterContext ctx, T entity, params Expression>[] keySelectors) where T : BaseEntity, ILocalizedEntity { @@ -567,10 +581,11 @@ private dynamic ToDynamic( var productTags = ctx.ProductExportContext.ProductTags.Load(product.Id); var specificationAttributes = ctx.ProductExportContext.ProductSpecificationAttributes.Load(product.Id); + var variantAttributes = (combination != null ? _productAttributeParser.Value.DeserializeProductVariantAttributes(combination.AttributesXml) : null); + var variantAttributeValues = (combination != null ? _productAttributeParser.Value.ParseProductVariantAttributeValues(variantAttributes, productAttributes) : null); + if (pictureIds.Length > 0) - { productPictures = productPictures.Where(x => pictureIds.Contains(x.PictureId)); - } productPictures = productPictures.Take(numberOfPictures); @@ -590,7 +605,7 @@ private dynamic ToDynamic( else dynObject._UniqueId = string.Concat(product.Id, "-", combination.Id); - dynObject.Price = CalculatePrice(ctx, product, combination != null); + dynObject.Price = CalculatePrice(ctx, product, combination, variantAttributeValues); dynObject._BasePriceInfo = product.GetBasePriceInfo(_services.Localization, _priceFormatter.Value, _currencyService.Value, _taxService.Value, _priceCalculationService.Value, ctx.ContextCurrency, decimal.Zero, true); @@ -602,22 +617,19 @@ private dynamic ToDynamic( if (combination != null) { - if (ctx.Supports(ExportFeatures.UsesAttributeCombination) || - ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) + if (ctx.Supports(ExportFeatures.UsesAttributeCombination)) { - var variantAttributes = _productAttributeParser.Value.DeserializeProductVariantAttributes(combination.AttributesXml); - var variantAttributeValues = _productAttributeParser.Value.ParseProductVariantAttributeValues(variantAttributes, productAttributes, languageId); + dynObject._AttributeCombination = variantAttributes; + dynObject._AttributeCombinationValues = variantAttributeValues; + } - if (ctx.Supports(ExportFeatures.UsesAttributeCombination)) - { - dynObject._AttributeCombination = variantAttributes; - dynObject._AttributeCombinationValues = variantAttributeValues; - } + if (ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) + { + var valueNames = variantAttributeValues + .Select(x => x.GetLocalized(y => y.Name, languageId, true, false)) + .ToList(); - if (ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) - { - dynObject.Name = ((string)dynObject.Name).Grow(string.Join(", ", variantAttributeValues), " "); - } + dynObject.Name = ((string)dynObject.Name).Grow(string.Join(", ", valueNames), " "); } var attributeQueryString = _productAttributeParser.Value.SerializeQueryData(combination.AttributesXml, product.Id); @@ -884,7 +896,9 @@ private dynamic ToDynamic( { decimal tmpSpecialPrice = product.SpecialPrice.Value; product.SpecialPrice = null; - dynObject._RegularPrice = CalculatePrice(ctx, product, combination != null); + + dynObject._RegularPrice = CalculatePrice(ctx, product, combination, variantAttributeValues); + product.SpecialPrice = tmpSpecialPrice; } } From 783bf8729ba587ade2c6113af774871bf88e46e5 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 11 Jul 2016 12:16:16 +0200 Subject: [PATCH 165/185] GMC: Associated products that are not individually visible are not exported anymore. GMC rejects them because the frontend redirects to the grouped product. --- .../SmartStore.Services/DataExchange/Export/DataExporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 26d20b72a8..e6b182c270 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -686,7 +686,7 @@ private List GetProducts(DataExporterContext ctx, int skip) OrderBy = ProductSortingEnum.Position, PageSize = int.MaxValue, StoreId = (ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId), - VisibleIndividuallyOnly = false, + VisibleIndividuallyOnly = true, ParentGroupedProductId = product.Id }; From d9455aa4935943c69cc187b8bd18051cb2284f38 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 11 Jul 2016 18:41:11 +0200 Subject: [PATCH 166/185] Fixes "Cannot insert the value NULL into column 'IsPublic', table 'dbo.ExportDeployment'; column does not allow nulls. INSERT fails" --- ...71_FixExportDeploymentIsPublic.Designer.cs | 29 ++++ ...07111548571_FixExportDeploymentIsPublic.cs | 21 +++ ...111548571_FixExportDeploymentIsPublic.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 7 + 4 files changed, 183 insertions(+) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.resx diff --git a/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.Designer.cs new file mode 100644 index 0000000000..16b788408f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.1.3-40302")] + public sealed partial class FixExportDeploymentIsPublic : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(FixExportDeploymentIsPublic)); + + string IMigrationMetadata.Id + { + get { return "201607111548571_FixExportDeploymentIsPublic"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.cs b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.cs new file mode 100644 index 0000000000..f48207c9fc --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.cs @@ -0,0 +1,21 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using System.Web.Hosting; + using Core.Data; + + public partial class FixExportDeploymentIsPublic : DbMigration + { + public override void Up() + { + if (HostingEnvironment.IsHosted && DataSettings.Current.IsSqlServer) + { + Sql("IF EXISTS(SELECT TOP 1 1 FROM sys.objects o INNER JOIN sys.columns c ON o.object_id = c.object_id WHERE o.name = 'ExportDeployment' AND c.name = 'IsPublic') ALTER TABLE [dbo].[ExportDeployment] DROP COLUMN [IsPublic];"); + } + } + + public override void Down() + { + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.resx b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.resx new file mode 100644 index 0000000000..55e598ca59 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + H4sIAAAAAAAEAOy923LcuLIo+D4R8w8OP50zsY/ddq8+sXZH95yQZMlWbF+0JNnee70oKBYkcZlFVvOiS0/Ml83DfNL8wgDgDQQSd5CsUteLXSISCSCRSCQSmYn/7//5f3/7X4/r9MU9Ksokz35/+ebVTy9foCzOV0l2+/vLurr5H39/+b/+z//9f/vteLV+fPGtg/uZwOGaWfn7y7uq2vz6+nUZ36F1VL5aJ3GRl/lN9SrO16+jVf767U8//fvrN29eI4ziJcb14sVv53VWJWtE/8B/HuVZjDZVHaWf8hVKy/Y7LrmgWF98jtao3EQx+v3lxToqqosqL9Crd1EVvXxxkCYR7sYFSm9evoiyLK+iCnfy168luqiKPLu92OAPUXr5tEEY7iZKS9R2/tcB3HQcP70l43g9VOxQxXVZ5WtLhG9+bgnzmq/uRN6XPeEw6Y4xiasnMmpKvt9fXuabJH75gm/p16O0IFAj0h5R+mKwJHtF65XNf//2ggP6t54p3r76n69+evXTv704qtOqLtDvGaqrIkr/7cVZfZ0m8X+gp8v8B8p+z+o0ZXuK+4rLRh/wp7Mi36CiejpHN23/T1cvX7we13vNV+yrMXWawZ1m1c9vX774jBuPrlPUMwJDCDqq9yhDRVSh1VlUVajICA5ESSm0zrV18VRWaE1+d21i/sPr6OWLT9HjR5TdVne/v8Q/X744SR7RqvvS9uNrluBlhytVRY10TZ1mcVqv0Gl2keAmo03X4GGepyjKgGFq8JVnUVk+5MUKF1QoxsP3RdkhnJwWl0mVTk/xw3z1NHkjn1AVYY4mZCtnaewdKuMi2TQCZ4b25pmrj8kaL4vVZU4XdOnLyecoW6HioPyerG5R5YutwfLPPJueDk1T34togzfYCgsxoe8m9S/u8ofRvPmN/BAzNyoCyJciyQsqldXy3UB4XEa3gefit9fD7qvek6PHI7zZ3ObFk8vOHD2+YjDsN2d5W5pt+W8//WQ0yZbs9S4pN2n09IWwvA2jGvPPBaoqOhZr3sEi4Sa5rQsK/arFs+cgZw56Ow0HfYvSOsROYdkspZWeuk48+zGPozT5E626Nh24t8XRMK+AcM/G8raa6bCbWkDFirLbOrq1ZBEAD5k6hOnzvsjrzfwCum9/qabnWt+fo/vkljKQZCZfvjhHKQUo75JNY08RV9bVAH5S5OvzPIUWdA91dZHXRUzGl2tBL6OCqtduMqXvlqcoafHsJYi8Lc1G+Gai5dLOTEt15U48Rfu49h81ukD5Ecahaj3Awe0kjW5P13iwJ0mKNOT+xWy0miNulfqexwIfuulaKu/8z4muKrhSZlLR3UzGOSqpjCvlApSDlMtQGWAvG0diVArdCV137YxDHURB43DuJaxe1vlqVx2tlzm6dK0vdIQ5LcnqOkvr2yQThIiu6mVex5DwCadV+QsFULfSihAnoXCGinVSksV5jmJq1LcWCBcorom97hWPay8I5G0FukyyPfybXGS9/eWXKdoerKGTtyxdvEeUt1FBlhW8rfM8fMVVGZawGlJYwxpwr0XMonIxGLbVy1csov3qdV69E62gkwKhC8yqG9qen/J8GT0eP6L1xvvWCyNqFXGCh5sCddWDuEruvS+fTstGqjXM74crqHw0FUq8ZJhYMPEnDkM55qZd5Hj92wskUq2k/+6FkLytUGeJJVWR1idi8gvzYEYHcmf+JfuA18cZVen9sB2kaf7wvkZlhc8l3/LKG6GHTUS4J8Lr7h1mzK9V3GEif14ma23d42zlWNPX1uR2bCOSBjymjQpElW5UCmlwSrGPax9k5QMWZ9JONeVXjRgdd4spEkU6V+4twxtkXpK8QbGX5woZham0m7L8c72+RsWXGyLBSr8BTGHUbZaP3xKD1j60BG36hMlFDToKpY+DumIX47izEjBQNshgveUEi9hLWrCIgsmMF4dRidoOEOp2im7nQ6ddnQ2ZrNeowRYw1exDbKvjFC8TRBDzw36XkLfV0eh9nfStNr9trz1L3K7uBjLEFeQxnuV08lYMvNLDNnSSF+uo8t2wO2wXUVpN3vWD1TrJjvL1mvEYni6SoQxmYzq4uUnSBC8XX2qHsTi9QykKEEfRGa4O4jivARfuKWxXYfjoY1RWp5uD1Qof0VThDKYOIxqJVyAiKb9k4HnS2tmkrD7mt0nmekDF9SkXYVEtReGsELQkldxNdKL/igEb1ACxVNj9ARBbvfUwSVM8zf3cq7rJwwJ9HYPIO8zB2faa1/RU3W5hrgaNRuw3DyNo2VJASMX2u7HqUUsNwjCEnNj6mylVj48fiUITpQd1dUdUmpgCqU45qhrgNBhVEObErJbtBGE1oF6f5WUFj60vBgcilgq9BkCcutjEesr7SMvlnRwXw73kYGy7Sc/8cA9pEdi5cYnQL67YtkvnCJ8xMIv8QU20YNdGIGAXYQihqxIw+y4/RMXqLE+yqvyQYCTkxh3stwAn6b0cDhiDAth2JN1Vp9FeIwAD4o+DkQtAHtBWBF7c5bT+ET7EnmKlDO47DwWSXwok0F4O6WXW6QnqEIe0XufZqxbB/kwvb+skKcoqkOlWr77O0pDu0B+mFcximyibPnz7iBzQCjG2RnuFViH87T7JYvHoqmmRCYCdbFjt0nwzV0NvJ2/on8mG6EpRqvHlD3SrfJdnqLn8mLytk+hxppb8ztvyk0yzhsCtsNtoe5hhA+SKhE2bL7feq9lFquwcByl2cQQg7egYys/o3pHLem9+lxQoJurZqxbHfnuWt6XZMCcKi6K+IWVr+AjiaFIGcx58yD8iQsTTco6gqcu7AiHTBn8O0CAWtKhIYq4xRz+a+vpfeKld5t8ibxPvNoROzeWwc45PwXgOMO6Oaz+h6i6XmJDGMFdD5Ua8JWODmB4aOF1pq1ifb9mNQDKs0V4B7I4ggNh5EMrrWDimh0vUSIuAm7r9PqQQS+p9aKpMJzNmkgonl05vM0zrozuyEiaRSoxgmUMe8dqkhQhzW99jZddDwRxh2i9veVsyI0NYF8CptNXr6wLdJ5FGTIS5Ed4GPcjpnOu4nwuLX7nre/lv9TekAdy4Wlz7Na9Y8y2pfBe9iTeGaTC4zmvlYLPBrOe/+IL6dXzdrCYxWvVXMqE9BmSXT1LXAqdlfZjmt73Dl/WSJrXLVwyO7XDFbTtziR6n92IjgycW5XBOvx1GkKUYWl8NgAM7QeUCK4FA3mzUdMWDhwiC/V4gbytUPF+oVLyWzQbKo+xi2GyZ3Ts1UbeztfiOWLdMR2U8r8IjvYxup08ivUz8n2GebFNTh76xbcuTHWZkgVN2TXQjyOxTsBXFaTfkjZ/KLTNI+GiPFQohFQrB7XoM4ZfRp8hXdVyd49M4enA5x0VVhHv0aoRnOxS/tkvbskGqW2kIN4uSeh5VzK2eG1E+oHRzU6f/hcpLzClpEGSfcxdc8nC5ZvrhWDmWW696SCZKDgIQ4+NAKOvgThZLS4sMH86PFRZjWZ0rbj1LRiStIQaAmlXziwXt6O8rffbnBa00JM35R6EVuE2aqZkm8A0hZb8lZYKhT7NVcp+s6ihNn3z1kGUuwC7ucqwJz6gnnuD+zdnerGGBHdei9SYNENEXNh9LhyfcPeT+QBMmcpUe87u4JLpDBTvtN9rTRb0OdtQPhLFDR5UobtDefQyHtPdwOoi3Ldf0xY96+kUXZfVNFBMVpMD7aKX10g3T7PsqUS3wMI2clu+Tm+ooKrwvezo8IbQVEiOVFOhLdYcp3mwnAZ4CozgH5UcT1xxEqNVENyaBiFg3OlituD54j+m0fJc/ZGke+d+Tt3h8Z+5rljYLvEPoPcZPnVv8lxsBp2MGohbN8eMmad5Qehc98TjNUNBAcYoiBNt/iMqLCGtNKNSsjrFZBqjg3pAMIge3BUKs4ujamRGyWawmp+U5yVxdBHCI7hEdPcUpajrlK+NYjGeoSHLv1dfjpHs/Rey5Vk6pS/lxRqoEyIcRMsEtlqcJWXlR2mFsnAF7+zWKkzWxTZ0V+Ff7SPLfX764IKnW8f7p0P1g+U5Oy+PSm5zM84G+jINVHHIvmd3jpYnRNT6H3ie3Ko9//KOOWluHl8hujmsU48F9lOC6Scpg9XQPA3vqvGElWcCRf8wfmlG3KUm8nQfzKrl5ogaBk7zo+niI8OnLD/FhFP+g74OSd8C90/iQ0yDBeNrQEp9A+kOvt0ZBD/14lpJ1vQ4zSQ3G6DEcRkoAtGqRJWgGZRWvBdLBw/rpsK6qwbjgsbYI9PekvEuTsgqDtF38KcLMi+X6yH7jfPmJtXOKLom97UsjJMF3oC/patoG2oPJEb2GnaiNiw3GE6WWAzHH2fs1kMtrBw8FFlfr5+CIqTNnHWcVKsog/NWKrRFmNDFTtIJt1jbx4eMyQc2a9Bb4eA9FZXVQYdF5XVfoKF9fJ1l7rReQCXGfscyjqd6IC22a+GvM31FyezfdUhyfY4Kj/56sJsT+YVra9DuNrzzpEfkJk3A3FuGCK8KkSdwiN3FxgMk9Kp5IZUvrSacGYhVMvGjVadIlanefIMfuHt8nFJV1gUiXFOpkkFcp+zYP1qw7pK+I7dGSH2PUZqSts1WK6I2WxgIVxrzetHeGCpKvKZThY4SUUCM0TjbRlDVieerLZkvst0jQSaYrvWqhL/PBVj44xsihBGcYBaitiyArDVQePldjQMGfZ1Quc1AaAzn6JzWzKk9QJoBdtXwg9a6SgcqckKTwtsTvTiaNwUvjY9WZxRRuYjyIbAACnGPH2UtEZd9ZQEX/ITDZGEBYx3GcJRSPcggtjKL3HISs4zyYY5/7S/BwboVKnzvf/tIT6E2bvrQ/tij7D1dRjEddQTY+TS3H8TbhDgqBOoIT5SlTLBWnLIyjNP0WFQlW6qBzpLL7inqKCTKoJZslk6qOU8Wjthq4xWhth+g8LlZPV/LgGFBkQrZcyoUjIOtEZtzdsKKzPKjY3TGEtMMcmG2XWRsO0N2+GOIMoVDgBRHCy6e5UxWdkmG0Sm3/a+/XLG9L4+U70Zvm3cyE8NT4WpKzUoyHG8AnteuYiDG4UatryvZUbn3zu0RoYetdVR7l9SbP2MRuzpYpAdNkGU+6maH+kXSV+fLogIkQz9YPRHeMv8w1RzHx4M1WUJ3lBzhhW1IC225OgIlhCouEfgyC8cLYItGiajlfHmfZtwnXAMYBAcqHAkL7pavpH5N3jjDqUOy3Ynlbmq14ooAbEv8SqGWdAX++WInDvMJ8OmtYT5pEM7zO1C6jUAE9+4CZQK/UkYi+UA6AraXN8qaKRFJdJH8iv7ZHoT/lZY53LBRXPG5nj5wWz5ex89hk6bPogfQ8ym6VPltheCBwbFx4/4UtjhzatlvwcPf723yffhNhRetbgh4+TfJ8Q+jLR4cji/4CEjjfGFthGzENJ6vrb6o6ICZJHVcmJqfjAfwylHQ9sNbdP6FVEr1q6+8Vd4X4akh0mGRRMbist3+ZLCSdn+GaJnFRbmGTxOKj/CRJUaY+HfwcKPjxM3rwldun5WURZWUSIkJqmhSXLje3ogiBIbQ3t5y8cRMnI5z+OUf24sU09chUx4r5dCOnrdR5NUiu0WSLxnIFh/W50PVVkRrJ+kqNsQsGuV1j8O1XsXLdUHIFWH882c/rFBk9SB4oWeUGTX5r1b7EFChLfx+UGgbd8P4DPkAwzzdbWmwGNL2Enz7TKNNo9ESYp3/oZ7aGu9nQW4FCt9zaKyzn6fgRCyqs+0UznpEH/4fpbpCkp2PlfZPb3jPywvV434RFtN9t5G1pNgLDuALrK/o0Lz6gx29RWs/fequYfszJnjN1TIWrFmx7zhp8tv2PWgOu/cqRtxXotDXysPdFFixxQxwyAEjuK7VwykntRc3MGSaTFLMg66breTeUrNDlXb2+zphXv12RtYlDt+cG5zlevcivQjqmaHjEMDKIq3XFbhKKKCF5NX3EkKKu7b0JF9Y0bSiUxLCiDZ0KaA1i2jIagHnXg5iFdFMdUu/gUO/VEHlbA9G88/d0RA+GiOr33o+olVjtaNxLHHYNjXRpZOEyslS7bg3ksM9KDuCtyWHar1N5W4E0/FC+Y6flCdZ66iGv6YL6mPydv55DDcKGB2B53HD/Q7L2RMAJ9nmXQGddf0Pu8Gzksb9sYLHt5cPk8oEl919CRoy51TBEf1xJHac/+kOyDuUVJpAfPskGTPofRJYEEiJ76bHNISGzOukznLCPatiuqIZ9HMIOxiHsXfOfkX1Y5Rg33txFtzioXFAUQKAQpw04O43/uQPGu9chVMInyAkEJnwjQYOkGG+sYQHy5zWRWO2wQwRjTeLyqj8QhM4jJTkkGGafMh2Yik8cRqtGZ0oCFRZLuihReUlO9VCdBacK7V5u2go87zcfwnjobo8EC5cgb4albpdKz0hq+MlFkEzqdiV4Bvo4VBdkoAuOCYRfcLG3F3h/BdGk4l5YMi2w5HhZ5LNsfU5ql9Gt/7EMI9kvLeelFcqOq9urg2V2leyjYPZXH95UZE/151kF8j0vy9ua45nfWd7bXegt4aDv3Ni+GqPGZvlIjMZN2fJNGI0/3HY+YnFQlslthldRd1cyx+Nwi7yEclrSZw/9717CmCOHk+Z/rtMAWqtG5oV7RJLaOr/U1ZcbipTqvfa3NN4PY6jykmvezDCtKjOsGdefwMliuvzvDoN1NbGa5klXta1JoW5a1WXY2sTrxlrieBAeYaUsor3uJ29Lc46ZKLDTyLsiZFjndseQQntzm83Nbh/xPY2FP4Lt1568rUCKU4sm2IUGebsNf2IeYp9MCzst24wE3l4kzL6UVUWeEmxbmRjJXqNxedjFcBN3Vlh4nnMZD2h3Na1jO0L1pYbBUHnENGTK6oEeWuNKKnm1NFDWN33KR40khIUv4Gay30UMdpF58pQvdJU0mRuur6bUZkQJrS5RtHtul7cloZu/v9oOLSKNFyzJ2XPxRx0VqDy/vZ58UI0Nd/UvvBLWaMIEcs1r7TM05BfHPNmRkIqGEGrtxyT7wWSoWySRja22tT16lqHeaaasOe1Iw8ODjgnAOwT7fUaxblsava+TvtXmt20cQdk/8Pi18E7uA6CabCPp2poqCzo5qaPmxcDJx2KQ9TxMQ8ePeExleH0V2qaeTX51Y9HXv0nqoXn3OPbiT95WIO0ATs5qG9blnpA3zAWvpX+HexYNZqCaF33HoNCzviyEoLRIwEJez1q+PizvocpEaXuKb16Q/4DSzU2dZqgs/U/wAspgYuUF8WBpu0KfoGmnqt03X5os4KZ3vivwe1S2Awzn1jDqoEq9Fwh8xVUVFHpNDZkKr6vmxYB4ry6P8rVjjn5S+xWDYjt4rO2MLnY9lFmjaQw9Tn8tRSjtlKhNyu0dRpDRmYm9GgAHtobKBSYGgbxZ1jFPcc+v+9zEGltMlN3W0a3/nW2gNWgfmFuEWI6WzZ5Q0TV3q8ESJyzzgHuTgKOREN5JJA42myK/R6sW3xHgZmsbRplX4ZEGTp0RNMHEPqeN28ike2wnS6V7LN0SB6jxBjsqBHfXMYTtWYnZn2GvASctgL/1V6oKTloAMWgVWZQe1NUd2dOagLVzFGO+dTk9dRmAXqkQ71UGhRBqKeirMhyvmWTzk9pDySw3o7tJlOE4YZtsr95mbPkL4WXKffM0dRDH+Jg6T4P4z/tkhYopnyXTWsZAwakSJFdDzUGSGlUQtgCzWl4nrpO8qNdneeliIqB1y1c9ir0Ilbd1mW+SOJRZOoS77fyHmdOzg9WqoBbQif1DdiErm1K+9EsKFCZiqSA5ABBb7ZGioGyr6SILCHVyKFd0kwHyl2dtZ7wEGsWxl2jytiiVtkaikdkK4al0UV//C8Uq6fi3aQKUPjcLofTr/rcEn8A8DRhRWZGeePt8tXhCTXGHr5G7doao57EhNOJRuiOMi2FZy8E47Qm6DrZAYO/oT1XXGgD/LcBT+u8Fv7wtSqD3RV5vJs5jZ5o3eWs8/EGpTvnaUyaH2RyIBA1yDnmOW8RfKyf0sIbl0vyKBeKkOVMGS3MWwF+at53wEukUx16uK4TMs5fGz3KNB75/VIsM+B7JSlzw90dSeeIkLtq5tZYUTQqd5r+9kJC3RQmkTXIW6MKBtOUdDRHUIdj7Re8kTTGxWkOot7ECL8GNAp0BeS8wW9TBOhIG21n0RG6TgyL7hKq7fDXlTZKEY47qokBZ/HSEa87QaNPYeVQNCrDae/zvzmvhMnpsN9QQhrdvkT53YkCxclFfV1gkpqdZnF4StKFc7VWNHT/O09glaQzPTUzcmeYa4ajReUbaSp15Rtg2NuvIcEMWS9m+sZFwxLtIQlSEKD1BaGqayluemsDylqemdotfdf8ZUMJNzqTdWp+2FSpWJmsCK951tkIr24Sw1s08RMXqLE+yqvyOCoTZzt+n9+gOxT/yeojxnvMoLTQ+SwrVTvUI5cN+cHOTpEmA1yL788FmchpQp2pyqMHojwqE5dcR5q2x7uTMUhgTwTD9RJIuz6KGC7SZ8BXS8gdayaZk0hEe3d+/naWh48dNUlBDwqc8G9J8z9Tmf6Foenqy66tJ7voOXScB3rfuUR3EdN/8kKerGfhDbHgmxmQaPoyyH7McgLk2ZxExbJunR3M2R8NVhjwZczR5eh3NoFy0uylVAPsQ1qnXfY0PBEXyJ5U0NEVFFJOfg2owe9OzLBlZ4+eoZJIBTyjhN8SQPi/BxUZnGu1Ffd3r6PMO+awu4ruoRHMa8M+ixDV0sLOANMf06eelbY6cz7HA2dTNbcnsVuNAD1xv7/UkexI+R+TqjXms1+ja4kNEshC1VpzPedW/aOZLNBLasqku7xLcvwh/ptFKH6Js9eXe4WQlvUcdXwGB96l0jV7xgMOdKlQuuGGAQLYOf0qnxKYFyB9xXCLpmrMXYmd4+lpGt+hDUpLXF8AOQoBX7Q3x0Fk5lHBXrQC1fcrzfXJDT4naQUCAV19LtPqeVHfCYPTQwqAMqtgOjtYigaAK/qZxokL/uSKhs3y5U8+w1JDk0e6LJT0biuCeMeW2PTtHK4TWaMVKyONGvQc6ykJpmUILLAxGX8P66Vrcd3kkclcqkn1cIj54OS526dXGSBoLkLy04wAkQo+H8kvLCQpChydsGjQlKDD33jDytvo7Vk/zcSPVfG3QXkqfdrc132GHGppNtvsorBU1tK2+0MqUKRQEo46PJZfTSoe1BVcvNwjbfpnL2+ro5btCRztoCGTtkwAT3VpOJFA6appru0MNjaLbfRTWpRraVqBwitYcSrvRgGCdzkvg+AuZvWCRt9UZ5RipAOUANJNOIbzwJvaDOC27ztJ3YKMApq4O4VFeb2aymJ9jYmxI/upZTIJ9a/Nkz7lAGTnHzjGypql5hvUJH7ZoCq6J2yHPy3XcQW2TSxtzJzJA2W/JRmYnYQM3HQ0sSZXjuZLVEUckAZXuyjJ4ry2Z6aFXeMs+QarJYRwTyTv0JIief7boqzm85pslFc2RPrV/cN/Q5O7Ac4xmlpFM7b7c2TYaHXFqko1bm5p2i/jQzus7272tEyDH7WnZIQumxn/ECyQb3i2yPAIRad081mbOIZpAvTpbpQirWdH0XhKNgD+iSfhC8bdUUTooyzwmLs+rTlmBbz0CakkyzU+nVfnbdy0uGsHrGuAi0lghVTxUMuA+Ex8qEQoVXTsL8VBJf3HpqVcSFHu9Ut5WEG2wmSdviWS/GdM4z3xw9tjKM61GEphd7IPLDbj4d1pswMW6V0prAN9+DSr4LlAcf0PwEDgOozTKhre+3O+CJjbdzmVAm0gyKB3ZIG8XyK1NBSeIDSVw+BunwA5BJqMJeN3UufE4yMLOo6b3IdrLP3lbQXSQyyKKf2CKz+RFTiOAw57uKM8gV9/0dyhN7lHx5Fh9dt3H2LmOX/QS3ztblz+5IygLcTXIALGDIwCpE+AYyiuPFIsyhFDaG931K5LSKYgnnctDemHs5drFEHIdyNYrvFoczyuYHbNz9EeNnN6IaA0EIzT7daBYByGylQVbBKGOS2Eun85RVObZSV403DS/GaTlX0TN3kEuCPw6oI7MMpta/nm+6bw9qujmpo28mPpiZbVOMn3c8N9+CvLayEi2hUlat0Xxe64naoYmkrM0BAGcO0Ewz8NmTpWzI7wJ+flT8Jj2O5tq/QfY2c6iotVvLG8Im/s8h4rsFIfwsAxmgQzjFrJM/ia8JFGBeYi4RUxmsQyjdOyCKB6YW3K6GEupKxaePWNIwYCThhw2aIyx0BC0l0iB9P12tsiq7neFVoBrXhmMvstBLn2F/G3um6CAar8LytvSOFabPlBsa7FFjxX+tN5Mn+GEOED/USdFgDfLiQ2NwLcMHwrvaXkZPR4/IoYarqgwoiPMBrd58RRsIz7Ks6rI0xC6Rrj3Dk5L6umFfAk22esEghCioXGw1RmGvQJE4iCuTesItmnjil7mariVgDKd4tsLdnlbAsUmfhxrop2CquQHq39hvmGtJ8GV8+Y+b4aGTkuMCS97FAfwUvUQqOaSa36Zxauc1sLO8WohrgvC120uJ++wdgnCvdSSt8WT7DknsuDHKjFcgjx0JVZmTZlmdQDjpmHFoOss3ALbrywFEz/FKWq2Zs/VQBCdoSLJvfNPUE8ais/TdfGiwtMudX9Z6GwRKHfiaZZUSZRusyRju2gkxa7GNeSiawSolVdjaFsLmnT/n1ssi8nPLOW5k1imTI8/fcxvHSQyrnVL/IsYLHtpLG+LIdM2XeKEy229HYKJIzO4lBmYKwF+WL0KMEEuqWCD3kawDUEXEVC5srdhLrQFMoYQJwR+L1LkbTVZoXGPHvJClaD6zTSGGo15aKLXeo8zAmypYxnzsd9WuN8C1ab3/PYjukep/1ufeVGFiwaybP0Eg8+Wy2fTJ3v1XmeO+oTOG+YWfS2md9vATI6KAhVztDWjv4WG0wridJbFvv4jeVaRM0RUql5L+psTF7kpMTLlRam0hFNW6Evf7DsYDiK/eYv8lYBqL//lbbF08s5sFOoARGfQ36y1SeItNdUoV6LAv+C6lEMJq1QB6rVmzwqa5aXfeF0X7BjPfrUqVmsIb1nCQ6FW6pD7wVuBrK//hWKlY/4vk3lBza+xEoeoKIDTUmvePnxqHuwKiLDPWOmLcyIZyrIxKEfHYuVqDD8IUgWYIElVsLYmJTZtir73LLS07wOQrucMpJ+NqXsq10H693XL4ede+Mvbao+d3u5/YS7EHG/n5EZixVsrPXtcAe+sCIWiaVWA8AsUiu/Qqk7RZVT+cGB7Uq18xSLZM728LY1B85dpDJoHmF1U9h3TZnUbY54dP24IR6rjRt+ECUlsbgGkrfx9i4zDoOq7+ZIdF4W/kvMZa3zntYv152NEYzmLyrHucbZybbWOY8wnru2yZJuOwU7LD8kKL2zfCcJ/3pJVcYawHBfSkprV1ZuDAw36PH9opXU/7CSLiMfCUZ4R1wBiLfxE0dG2xmsO7oCDQG3eJ0UrV0UuTrHCnzs97XFBPCAwulc9kv2OJm+rIb2vFtdgWcYbvDs6EO3KdxynWJ9NA6qRbN8kl/Utj16NQdnreggCuLAHwbxUy6+FzyrMX/X19wvwOS/Ai7S+nb/VYNFeUXZb463ZbgbM36gijJHEPiGsxAErz17xmPaLaupFhUf0vsjrzfzMjVuev9HRQ33z2Z4dbhGMV9/lHVqjb1GREFQu1hFSv3w1QrNfd/K2KKECcO4sUYd+q+FtmPPalNxPcbvobjTuuflvz+2T86Gtv5DSwWkqHa9MQ1nOyPFc4xEWZgV/yEtlyrhAFpeP+W1+lsRkBWxPLoQP1To9zFeMCjSdM1zjPNYlHP6Mqoe8+DH57J4VCRZMT3QJH7V2Lf98VRTn8WN8h08FiLxU5YxakZRH2gicqIeM8EpZi8nYowMWU/doa9hnHRJnRj8yDlwypBGUeixjUL9ERH23rPfSd0mBYhKZ9apDst9R5W1p79emMSA2E6N5l/eXSZKlmj+G93fX7eRjThD4k9XEDHuSF2s86bSFadvzzdLDCy2ywMq7RdNvLOdlbjtGKtuOsxWe2eAalt3x5lNEE895nnJaLHvRLG/rGdipJzxqH0bxj9MMtxD/8AwbOIqqKM1vX0kw7lk06ASDN/YhctYGi75ewOVfwnqg478OVtDRtRVCJjmVNXYm5jrVgBqP4yxE5tNPUVbfRNSmUFyiNVYo3G6OWlkCodsLEnlby+xP3xL0gLuqDsGbpGVXndX8AN0mIA3AyzyqPR/v+Xg2Pm6FewA25jDtuXjPxbNxMVWUyMsGrRLkzMRjRHselrfVnyreBDqdvF3mpsl8xy/yssQ6eOrPZTyqPZ9tK5+Z76NNcrpPqLrLXbxL2/rlqxGiPWMoJpQlVJMWSr+vTrK9kew47xBjIvPvgRMP/qNGNVodY35KD6oqiu8c8862oR3lKxDhniflbTEE847Xxz3Bk0CuukZMRYxA3IwIoLaiUa0UBvIqOUmAaAbNno73Sz7ox8Tbw0v7niiH26dkjUibM7QsD+lPJEEb4EK/asAHg6IcSrAlKkBtraEMKou+j2rphsB8NRwJW8PLLjrqZxBJvZfPCklXJHnh/TgdYaf5Y6ZJq1pvkiBhzPn8g7vMZxnaOdqkT0HGZ9TOLGM6Opq8icM4nrwNfX6iQHoIcSKd3oU0pKfLBRZul0Xi/XwARuOUk7HZ/OI4r/2fKsGdWJHLuyhN+1kIkNyk35nhBCeBdQQ+db25VmE6IJbkuhFdjYHBgYxgVDrOGNBLtRl3y123YfHslRvNMlUdL36ZxOGzNbzqTlWGjesDIOYf4Vle8DkybH0VSzyTYQhkfbYvS02q9IlabqJrLsrUWsyLxHuHbiK8qvGuStcCrmWH1OJaf72JklsXN7deXnU49rJK3pZGWkwVmKvVMSdqOJDOuaS7tZd3urmbWLOIPNwR+mXIodqvRufVOJFFFJ8qG9WuSRqIpk/4/7zOmKHyhYQ83gWOYfE6LUrPiG8criKG2m+9av+srW0sKz+jh/IjIkvd00G/F5kwxr3klLcFU8w70f9CR7kw8iSs7WvCEJdPKCox0zaPYntFwo4w7deLYr2oNY2JXu1a+NGwc0K6qWNip3N8a5n7HV6+Wem2wwgrpUe2Xyz7xfKcFsvpepMXFW71JnHKzzWqv18c27Y4TvIUc85CbWOOIG2GiVgOgylMAqUfycavI5fRD+SHockm8CXzP2fiRXKSoHRF/pohWVPHFUd5dpPc1sXYW3Aq28PxIxY2rHvehMmY0nqd9UkRJm7tHJVYnp5mNyofkTBNtS9UYOTkjYpw2ZjGz2cAV8ejLeZqDD5cHsuhhOtjBajfQyBPWeyeDYNwZ5f96RWDar+pKpZ1kJQYDX+oM76+mSbzkVE2jon2Z5o27rFaSGunNJ/kvVN+mHQhnlo+G9HU6vo09Z5BGyMbuLCZ684+WexmuTL3inkkwvId2qT5k2N8Bo9iL9HkbbW7kq9IW2ZRB9KvZ1RqBqYMcb4wcJsJlHpT7yUTpiFtltggaWKranNZRFm5TujLVyGmAsI5jkmiQgkGczjoNlaoQOHq2lvh5lg/eUvG192BGIG2Z/Dwa8DBhblGJusxuUefmPyVHt5jLj5oquRMN7IwKn5jvuqBh3OVDEY4VUkB/ZxyH/3sk6P6e8VD3tYW2ycnyvJBxDb51ZJqerltGPMdJDV3fp9gwi4aZn5atptit3g93X4CGW/9zFgiD2HemeOMiP8k2+IcxtKOexoT7SxW0xlV/i83NyXy9I+nbmN+KA6jKr67SP70vAc4w4u8eWRke5zqyNOT9E1pG/0xWBDbP5PNQRHfhXAMIrWGx6f8dbFBOYLDvrz0MT7IS6u4BTPQj3QsqYFeDiVRJQMY6Os1q0pqslCclidpdFv2s+obASZrKZgWilcHltLpE9nLmOU6ntZPaH2Nik7opPn1yxf0YaXfX/4ksMAIlqhHWH1FJDM/6iu9UVdqDsAtLOAo2kyIapIUx3LvCRpdschbWnCCLhIiOs9o2JjZNH3CHUk2uLsk8T8Z4KjyG5MpOCjLPE4oCbv9jT5Y0AhqvDXT5XnVvdPI9f84W71olq2y1rDIBxsMVAHvC3REmJh4Sf/+8v8Qhm/aYC8QmQa7IXCNvBmPCTfyJXuHyB724oDqXCTSqoyjlSg2MUVX4y/toiFhX1iDKjF7JFklHhCTLMbzltoMhUNieM4kneyb40vw/oAyckS0mUOTfrCveor96ZvliKmj3W+vGWY14GGsX626vhkyMFhFyr0stDXrwk3tHt8qxzEX0yrnbSc4Fuvy7S4EPCRdSrlWXQ3iXL6GDeNqWgOYd/yutrIlG2Llaapf0SMokBQ52acthj9CuFNLFez6DKsTnIPdWJC45wdZ+YCKK8onKqZg4GR81oDYchuLGOA3iIG3g9eAjs/EbcBcmLRM4BfjtQt8nibuXk1+0Cuse2EdLK7Q6oiYZGg2HRmX6KtCHDmuZcOVBu1B2wAt1ClJNhTDpz5EbZX4gHrVoZd2GoIG6cICWpEFbMGcEsuvWuUIZli7yjkyab+tstgibm2dWmbk4CA2bEFsGJDHas56P716JRp2nFhI0ocZmEdC011im7Ho0U3zeLWEZaExboCRlFIyPDuB/ZmRqUBam7Q/qrgYg/VPew2XMTIOEEEh1hoeIjPnLQAzwFhmTOsy9sMkJde+XQPabo7hg1OBQ29OCnF1OVCjfTN8eFFO11++gooeLawLWYRmFAaF7VOgdKOYQWLp5stoP2SeTlxEXh2m+S25x9AbeARIiC87IBuGFBHvlLFH2v0ZWFA6Jzth9CG9P8rX9Mq8ZxwVl/DAMg5s4WyZUEAP8KGMwbeDD2UjmIkVZfNj0nxXZzkTZPMM0FlC3w69av+XWyJBcNAgOYK0MkrCbUC2SRj58lypHsIcVkrlPBkZK5sqW8OY7SNjpkzDv+k7BWNy7wCLbWw/Y46HsABjjufJiDGH57uXOeu2z9NqZSUPCJ5oWhirowyP11wyhjOcyDoxxwlEQtddkGrvkrLJJH+wwZNCEkO2o1Hdv6gqQUzVwdswlbIN6IxsxrhepGlXug1huirTkaVvwVzsB6BJ9+Mc/VEnBWpcfLWdhmrZUCbofmXaP4CuAJx+Ep3EmhHpZpBxRiQy6UdXf2k97grzTXKPiieaO0CjYY2AFTqcg/I2Rg3xGtvPybZPVW/m08JAOhtxFVNvac46rLNVik4rtD6oqiK5rivURE1dDSU6hjPBoeBDaXUHBjXqinzvYca8rYcPmxHOtxZsWMDIstPX2p4F0g7F8Cwtq2e0ELw4n2tvBw/YurEswdfwLJrz8tIHb3FA9ow8Hwu7MG9I1ULelUV4bwcNPW37vbmhP/JqmECooOA2F/uPtBmLY/rWCErpKObjUul8mXShq7M1XGooE3n4iXl0h7dy2RgWYNAdFKLktZqbiBpdC63FHAKGGJOFs+FKEP8S1nNVR2bgKxWdd8GK3jL1aBgjntCIJWlFhRR05TltkwD/qdraGrmoHdF8AlI7nyZdYettFWcb7uhQnZn4eYd3d9U4FmLgHdzl2/YvNihObpImDUlvRjNlYHVtBSvDFR2YWtODHWRvsxHNx+hmc7wLLA+P5At97+5KwpEy9nPABUYWKtBYBRo6dAcK4TFalssvFY/hzrBwPHjDpHcwhi3dSJQM7ibb1eRdbMtRdst4rcFLf/kV5z/2xXcsE75xX38NnqVXYbctX0a3iuwwImxgjw0Ws1wFw8UBk780OL9FRRJlVT8tR/n6OskooJU/iykeBeEUKBxoatyhpR1kbDs6n1ywnVOTnm2TW41qfIYHOgMUW8HxO3y+sxjWdiyNHTzpGYzqH3VEM8R+zRKvVcHi2YqlMeoQsD5GA19yM4A6uh0cD82pSc/YetvG+647gIPYD8DQz0nAb49Uf0aivBuK3lJnimAZBlea44Qx7hrLL2F9s51vi0WwNcY2fnA0tfeVjFstWVOJzGKVUDwBloq6P/Jlo1u1W7t6jAa83Eoy4g+LVcWjWHpxWR0NTPV/FyvaVmnyC6vrz0Inv7qIyFsO7/KHLM2jlZa3xuCBuYtDDpkNu35OzltwX2bkLpjSRrcBo5qLcdhlggo8apIjlc2tLmMCGBzisB7Shsck6C0zwVM+W3qPVg9lBhZVT5VJB9h6W8CgOkOEADkBW+6ieUHa+1mZcHdNCOfoPkEPpnawMbRi720AHXZgroVdYkXlCObbtuE52jmW/IDSzU2dZiTn65ipjDhIWl3LtExNZ/6Vty5naHjJbBlbawc2N59r59mC8ZuKi7H/Z/RQ0hhTbTJXARJi6g7IholFxDuVzFXa/Rm4UjonJm0vnsyV9L7L/9kzjopLeGAZBzokcwXRA3woY/Dt4EPZCGZiRdn8mDTf1Vk+Db/Zw24wePBE9As94Hb8WKEii9KDuroj5G1cLLlH5aS0MaoNkUpV0YZ8Zh3Yqez1VkOaYb1bzbGNbWQxAXCSF/Wa5qDWMrgICnFzD2XDugBqKz4NYg2Wd2IGzpITd3fY6DLfJLEhH41hpYxEwaw5iUO+ECvBvZiLl2AC7w4zXdF/3xd5vVFzEgMoZSNrDmKRAuzD9G3r9kxZ/+diPGA+TJoeam2DEGu4xkDINEOeQnw1mGXMt6V8B3R9XoE3mg9jvtsC9YvhF72WxAw4vArGIJdxH8jXW8KCkjHMqsOJ82PSPK2wGCt+KVbmT9JBwBArUjgbNgQRm79EF0h/U/ViBkZSUdek+XHNhTlKex4YgwXkot20ecB9n43rdvLE0OWs/1pGt+hDgntTPPWJ8KWMp6ylevWAreDyNgTcoOIZg+3jUqOhzMC0RnNo0o/Fnz0AR9JIPit+apbxXNzbtAawLiizt5RvR4NYimlH82bSCVph2c2d3nepeZSDk27vthfoPN7dYUFJz+fa4cW52CVm07nPCZATMNwuusxJez8r2+2gq9z75KY6iorV1Rnu8l1UotX3pLobOEjGLpp6EFt2VWy4UteMTCxC3B8usMKwVzPwnuE0GHEiiGFxxhwpET0L6fgFrKViSletUd0gwJ6yVbC8DDUayow8rZxDk350dbaLh7+yS8yOkUdVZ+Pmcau7o4iaD2Yppgbn06Qzo4rLqq2f8wqZnJEGOKnKSkCsVVYG7+6wpqTncymr4lxs/xnpHD3g5XOWYwRlt360tndVJYgNAXgbhlQ2t1NWepORzMCtJvO3ExZ8aCBmioC2prHYA88/Ng25LRixWZuk4nfJhrqfq4k0BgNTgbcQVmm/x1h3Z3uBOz7DeoXnYfs3l67fzZm5YxUdX4ygVUxna4uDG4CyXEu4entYEBzCjJwIzpFJ+z2CZd0LSDc2xh4rHHRAZwMes7nPSjizm7Inc+nOMhqb8tRmCzxXzlFVF9k5+qNGJpERMDisDjCQdpoz2MSO6cyqMcyiLavmaSf05KHThnJPViF40F5IAWilmeS02aOoqJg3q5V3M4o6sJYyBrfTVORNye8P5U9Kh9ssDHo2ixqinQqTXgy1FlSRuZFotw1pjcmZcDf3D+0wluDXndxFhFHo3CpkFSbn1F30tdANYgk23UHPi7M8Tb/lFR5FG2BNPhxk5YPquV55HTAdEQdulYZI0RTErUPnt45hDYYyA88azJ0R2/a1llPS71D8I6/5nMXCZ7nSbogAVOLBulYqvWnrkPYgjHHruN12eDOwvu18GykZfOUFrSlxXWBS3J5FT9TKeJolBK/uXkdRC7atjCvYmVdUjZlfbAQ5mRl1ZhZzicEMGPWDqbc1XNhd4QlsY8ojMgQmvOl0d27YPMCtuqWxvFC2Hd0C7K+bb5Mu8XUXWw1kWu/x3H/Mb6+Y34RnpAtAUQfieQbEhs9VrUA2Ra7zW8fZBuOZgZkN5s6kF1zVrWBfrZ0NAp6IYXfTsKYawcy8uZPmNCMu1HGfJde5cttWvGqwEKPtLIPRfCIX9XUZF0nzYLhZljWwijRlDAttnToGbmqh1GvKzszAaHri7wTb4fHeRxX6hErilH91UuRrLd8p6sAJ4VlwuzTw8obmZzuD3sxhQtUT36QXbL1tYb7L3Jb1hhqTMh7TzOJsJ/ZlfqYTyW7Sh6HWcmeKm5skxV/Qlc6nRoAETxMdkNVZQsA8e+4raRfmOAnICGt0Nl3YafAgTrlM0GRQilMpBA6fS1P760kJestk6ssfFdTjmOV0qponGz2O1FvO5aPKC/J8VrKOiqfjx/guym7ROV5qR3WBm4if5L4fupqgEwipZOX5oW0FZN2279OIQuM+zcCGxrNg0hcFmu1gUPqHHWeOqoRnyTH6hXkR7MzcTAgS3IL7RvUXY7vDKP5xmuG+xD/sDCy6ihALSurYMKW22Z2yPJuOZgbeNp3PnbDcyAaj8/PU1JuZp3fR+dNwLAsy9A66gv6jRjVaHa+jJD2oqii+o/fuJ4nieCWvAjExCG3DwormbJ82X5qD9UOZgXn102dkyEwWPHExQ7hqBhKr8xHLKmgY1pFNx00ATDrq89bJWd1I5uVRcL5MusDW2wZOZRYby2J2Yo8lzHzClm0VYGfFitkqblaPaDHRC8ypSV+Yaoux9+l6kxcV7tsN3T7iO7SqU3QZlT+kfC2vAjH0CNqGkRXNQMks2J5PY2XQd2gGBtQT36QTbb0kuyU1F2O+40dr5pNXgZ8PdWQ+RTPLMJ++QzMwn574O8d8uKE0b1yVOzZR84RYQc54A6w97wHtQHqoisGX37p1Q5mNZ+WzZnjEv5nvtHSM61RPuE6Fa6CiWzXrqKi+XP8LxRUpQo+YEWJqSouyLK8oll+/lugoLQh/lL+/rIpaZGaC+gJV7Kta5csXzXeGr9pHzAQe5apHj0dRhW7zIkEglr78SYsL/yLhjRCatkiL4mMeR2nyJ1q1Mwh3iofSd+1jlN3W0S2MrS0z6xy6qAoaw1lS5pN2j4PTIj9DxTopy6R7bxlCzMNokY6fPRcRju/GdT3M0xTsFf5uVLkJW5Wh6KKHDYekGo4WSetLAdKkdz/RdYScaSWrpikzWDFtUplPqLrLwSkfQ+gRYimC8Kq4x6IV7NkIwJjYVFxllYrmLYgW5WGa35JHBCFcXZmemxqDLchKneVcg6J7pAbCMbxLpaOPSnQay82zJK7qAsTRFpkSRIVpBGFMHpJNKinQWjL7AJgeNUqTe1Q8XSZrsKdsuenAhwQ5irGzaYds0fZhxidJWkmEmKaOaaNKphrDGPBWA/8pyuqbiE69qu8smBa1DqcVsrYDFxsUJzdJTHWhnn6KHsMV9HISrPaF3iCBYlMB79iYeTOmxLuMQN1rKDVF9C0qkigbQuuP8vV1kkUy4uhraRv+Rx3RL1+zBJQzbLnrKCy6btqECW5rpHxnaLIEi863CTF0Uri9owMlcH9/pzs9JKjAxzhY1egLtWg+o4dSJru7Mi2S40csY7MoPairO3JmaxaRXJlWwWsb699ChjAzD1SboJGe4Ni3qPWIIhkGs17Qt+mlvaClWkQ0AwKEo00nYahzMG9kwJsg/NSjBjvwAgeMHX5ExRC7CqEZ/WQLgXnXygQNeeNBiqZ5aUODRsztDtMLzAFvcPCR6ZRDUmtDJDKKjdNva0fLJE+FhznKbavt2zhDGdw/PpOc7rjB57wBjx1iiiJbtNIdR5Z8SUtbKNWEjJskqUQs2zDBrbcTDKHboKmAjag3R0WNe2p0Ta4HrT0K7JVJb4QITqnkHzu9aTUYNlYK1lnGcWk6qnURMyC9hhAk3Wpk7jnAlTi6/dHOZCrXKphgFg2ar4UCTV+o33hQhrCSpRQJPIxer7vDR3mqV17DhsMRgIFVKoeNEq3PutYKRZ2oJVaW3i/dpBOfIipxpX1py/VWLNj5EDRqyRxILY7Xl2i9SSWrAIIzNVw9qRDzMMbHUAVODsRAqhOwlcLeN4bQD7zIyxJXTBUoeRj9wJvtRG7FHQEYHIYBDxX4VAx6Gpmj1yDVn7kY9y7wjDXysdPy5HoTJbfgIurK9Kum2ViUC2YMYnQ6/YgqfFrULXUY0qDPUYlX73eU3N6BZBwBmKJ7l2BuKCU95WG0SEdOKxBGzklIJ4yfslgli4dig3P/+HYYPuvzd/lGSBXD5W7tOXTMtbD6+vBquHxk6ijuEYcK/C02mztHUa93deiHLL3YFC7KTZvovBzYJoYLWN5PYUwsU0Ky1796KsLQmvGBlaT042+tddSDsU9MOv4G+Yq7IRbJp6khH6S6IkRG4ApcQUQNfoCQ3Fj9iZmnqZL1xgCKobBwIGWaq3cVNUYopuaiIXN1c/kPD50FUfecgZQNv3ce0BCBRQWQAaSkAwnG9/JXWAZiWRhjRZRxCxCpYlBLPjp9ZYh2gouBgn4GLUDLahixP1lZ94Sr3o0CoCQIqBgaBA/Si/OgUJELxDkxhbrMIQra8CDyEXCQED0YVxkFKXhEMxGB82aRk2IMqB/HeGq9yTJGBxBHzXUOFOrDepmOiuQBoOSDEYEhwjD+WQrCALgAqkiJ7EOQwyRNmYfFVFThQA2GM64RgD4cwpmI1DpzDVkCFFQSYPWj4quo6DS4nhmQS0Cs0AJD0KtzWFNqgSKQfCACLEQaxoVOQRMR1cRaIWnwKF9TD+3Bkw+mhwCnHgcP7s0wIFKAPlJSu2jNIw+8q94TD1CeYUiF4gtWAFVp3lFQpU7DWCGtWoIuAJU6E6yeSlDuB+V4uKQPgajEJXUQsQYR1K1tXsVFAoxCfnKg4Ppi3BJVophHNS27dK4gVwebTZqg1WXO9lMkihJePipVNYhYjHexglZKrNDWJZ2CIJQb+NOEbj20zfi6SiFp1uOcdsn1DcOu0QqSgRUMRgjVC0A4EC1AO3icoQT81djNWyrex3BaMTwCV4h2I5k+RgaRaOzKHoo2Uhdy9vVfOcmMqmsHb4JFQWCV67ye8kaNy9f7KBwg/Ky0ndDrJtIqFgQY1zSiuCWJuRamFaNi61ZkdCHglKSblWi9bjWEf0hpJsJqhyVUUVDMUBuUYp5au+Eb1jOZAGo+Kj2DOZJrHu5iPYhUBwoQTj4YCBwiERdHpCAPiHGW4+io5XFgk5Sh5HW0DCCtqmAxczJqGwFIqsQeiK76NQqC2w1Uv1b9CDnrhgDHixkQUlNRO2R1fQVxpbFwejJr2pyW4KoAwCtZ2B5wbemARnEBaY8NvPJUB0OqbkAdOgDdfpkxRfB1oo7qtF08Smyu3K2m8IzLTNkR4zmVMJWHGaHzKKaxr3IrAgumP/cz0N42BBaXXEY1/Q9FFUUorqndxRiFlgKmmBSUVgck6yfBuAvz2XhUXdLv3ya1vaii38mnnJNZlShVR8Zx507TMULhRRUW00ITM+oCMDtcHP9UU+SwQtyXhcNacKLzslzfta5UX43rOg9eqahOQW2lYsrDTEh/Gkl7JSm0mAs1HmsyKdFZzFEXUmw/UeoeyCdNyyUeSqfhtmAp+00FvJneuZCovrqI1psUDSlE5NThIPVDGlfwphCHDtL9hiQp3vTp06GM388D6COBlA8IrgDRh03YoqCQBOEMMRVDy4otXgQyGYpiG7cmy6xb9Tm6T9CDgc7DAWpXwBje26kOxjojiT6gdHNTpxnxlB0VaGkmr2k4XCmCsFSVN6NYm7JmHMjdZWNSOryKQPLRCbAQvZj8UApCiagmdnglDXZOpEOaKpgeApx6HDy4NxeBSAH6SEnt43KvDTKUQCq8DMEKAfztZw8iVKUhU/rfm1WUD9moPkRRTaI1BZXNmpzYZ7/PzqakLgAlH5cIDNGNzRmnIBKAbA6K0DxzepJwYJphjKGlROky4OmowqGbgyxXbPo7CU1YGM0IGFAJNSI9GVgkAA1G6fxCMkebTVDJGQ2M0Tw2YwnDEw0umBgCRb2EBpuUUSU2GDiTtc6MJYToYNDJOAQmsANtaEZBg7A6EE4+GAgcok2XdFJBFxDVxKF0TZsqecpB6LqvkqLGNJhLdkIZOq+GtOpyZ3m4gnxYynoqZ3k+DaiB5zzchsJzfjJKtvlUDcnYQFuOr+GXCQnYNABQD2Zm1yVIDzpSevEgmvUzQEpXof6cymOahwQK65EIZNJ5hdXImhDz2Iq6PLxXZ7jHd1GJVt+T6o7JqyuSRldFPjhNTYhsTA5hBdV0iGXsFOpUD6VHvhpSHMtpCFfQDxSsp6KfhWRStwGQUjpHoSj5lZ1QY3KOa1mOd1R5SsKOG5pc7pEs1xrRz4Bo5NQAKZV4bcptncRjME1JAiAtt1IdVcLLh6SqBlFKklVcQTRlCxPrsVDb2hWqr2Q3WMdlY4nXda4ck2dRS7SUhByEfFxjQFkCLDB2VYVnyoXJpqW/GpLdy4kwBtSPYQSvIoleRYNRQu6+Mio7H527ZGRaiwIPqDsDc/Be52ke18RGhdEbBBppDkKqBARUAZYK45cSlFIHRDqx0B46r2chKazBXZEBI1nePs3MTvwDFJrwchW4SoRIa8GySXg4Qymf5MjnCg8X+qBal3Jgi0GqVqcn/eZao0LDCvuEFNZiXAprhSfFZvJzydP0W17RdMv0upR9MhVwblGAK1xN5LX83VgUuCV5WiUpX112BPDdmivgqRxghzCtq5DvhihAIsse51HtJ6btQYsceGMogN4yfnjn6jRLyKvaiiOUqoJK4VDUg5UZ4dkgpT6jQj/taR58FulKfNJIT0xpXfOBy1CYkNj01G/YIkB17aS6pJwdHme6Eh5qEmmuApcPWlELTEI7ep5KQUwVXkjHFN6qCko+lX4EwpkNTKUVOVFqLl1IRxNDWuhooB37nGMW3hbTux7B0BpvCLCS1MWCe+BF52oBI5+YcuMX1K5OinytIp0KXKGtyWvBYRfcu29Kf2Y56nlJd5lbEI4BNh7bUCcw0RjEE5Osf2PvSmFEEYEUApaHBcU18+yfSlgLuCY2mPSv+2ljeCSQqo0HqgDvZamJs60E4QwuzRfkJT18Lk7WUfF0/BjfRdktOsfTNLzVBxzytZUUZ3JdXfihh1yX2lePF6Tm8FphWFLSP4xpOIY2HOSoUgiqjRFOTS7JW4tK3UJbRz5SXVUwebv05UgFSbUNTZ3nXtK+wminq2I/WIUJLyhR5zHogW83Xp0k8HaigJYPUF4JoqHs1UkFBRUNTBxMy7R8NX5kUkm7MazRwEZVNHQzptYYKUAr7mHNibht9NSnKdOxlWxZgyXBlCzItgOGvsunzIG4o6cvr8ZvSYtUVUDLhymvBNGRf6xTQT8FYuiWf/yOdoDgNxvKKaDlA5RXgmPbjCmnQDwb5YYXTa/OuqdIZXQDYHWDE6vIaTZ6gFVLNgAzJAWVs6Gg2m+vGyzkZipKMlT0Zb+9JjOxjtoPv73GIDHaVHWUfspXKC27gu512qFm++XFxSaKySXL/7h4+eJxnWbl7y/vqmrz6+vXJUVdvloncZGX+U31Ks7Xr6NV/vrtTz/9++s3b16vGxyv49ER+Teut31LWHnGx32ulDyltkInSVFW76Iquo5KPB9Hq7UAdoEV8OrL9b9QXNErukduy/utJ3XXYJuDoYkMEueQQBODcAdOfrfnFNIU1fVfkT69AgOfBhqe4GGRyacjRMyMS+rhmhdxlEbFWftgaid5V3jkeVqvs+FvnvfktS+eygqtye8xFva7ObbTLE7rFcIaZYJrRxuuZ0KpBebyLCrLB3yUxwUVIg8ycsghAHP8XeUx0uGrOabLpEo5YrafzHEc5qunMYrmizmGT6iK/gM9PTQmEhbTuMQO4zvEvOjNIx0V2uEFaMZ8Nsf1MVlj1lpd5t0RncUoFJrjPUfZChUH5fdkReUzi5YvM8fa1PhnnnFDZ7/bYvteRJvWFwFCOiq2xX1xlz8AMyUU2uI9zMkNMb+g+TKLtVwkeYGlKbeW+6+Wa/kyugWWM/0qYvrtNSfe+R3ktbCFcBs6vyGZbVfRo/INJ4tdq8ckGMxM9i5V7Wl2MHHvst213iXlJo2eWkcMFtO4ZGtmG38gTkR+E90icZhkac1tnWDq+jNG0X6yUJQIEfiB9B+3hjU+5rjzyZ9o1XbfVxzw+FyEggGOaTin6QOPY/hqoVi0KZN4XOx3C2yEIAgrYW1OjRFGrswBqwShPS5g3YwKtofr+4xWXrwuSdVlwuLSqtsqE7seH9Vp89YNxNZ9oTner1nyR40uUE4O6GOsXJE5zpM0uj1d4/4Qs7o4dKDYQrWvUk6fJx+WP3Kc1ddpUt7xWjHz+RkrOI2UuagK6ipdUntbgH2Mw+i6lWnRTLPmw+5BXe/F5TQusccI7BpckY3Zh/hGnaX1bcJZHMYlNhgv8zoW1hXzeWtWwRkq1klZJkNiOZ8VwGNz4H49im3d7cKaOYenB1lcw9et4SBdKklz7lF5cBlwjrr6tnLNSYFQF47IqRyjEguDUvR4/IjWG844x3y2wtVu340DPodwVGaOlTp/c9i6bzZStllZjWvgWNCyJVOv4KUkd56mntIaY3CR0GC1XdBHQsn49moDYpK+aBktnFjIv2QfsBg8o0mjRx3kyizWa5rmD+9pGPpl/i2v+KUrFs9xbpBb0TCbYwZHX6uYN6WxJTY2nhWIj/0+32luQXnTRYr6Sh04QtZQ9sgqTyOBSIs8hu7bnJLnc72+RsWXm29N8qMRqnHRMz6zC5HQvozIRko7sqMaxXRM2SwDiDWHkvATp6ExPuLi319u/ptMt29n7r976Pfd3fJMtO6a5bGw3y2U1k0f3zPq0vDZRgE+2GyK/F60MwzfLcZZILyXrb5kwjY3LrEw025WEozjktm5lGfOwzS/bR9tcOBLZe2JeLJp7pK4mo2nii2w8AXCQyD5rPlesd8XnyXlezEmwlpdfyJJ3TQqiOnh87xeX83oRcZhv1tgiyrBbNF9M8fSPrbzXwgfH6qIuyoRCq3xfs7laPuy7eJu5vkhX0ZXopqU55v2JZw/FFp4ckVlOxrOi4v5vvg8Mg8AOUydsva0e4koXMYly+1O3VtJ/DjZ71t3RAljCvdQk+fWj9/XiURDbkos9MaSvF7EH5iHrxaGmybkamSzaT4t4bXd1TnJi3UkqgRCqT3miyitYKxNiYXJb7VOsk4Sja19oxKrS1H4YmJUYNHDLikBT8hRwdyXEu9QioS4gf6j/eVGH28J3W/0hUtdUn6M8NkAPtFyRUueQ0lXPua3SQYaccVSO8xdJiMpcgFga/aqIWWHz14lyUVisFVJa06zU9FQLpH1mc92My+iGr7Ou0dhcbyJMt5XoPtogweLk0LwZmU+W13DVAh/u0+yGHBq5got+ihEXRxZRly0bPeG39e6r9aY3oKY3tpg+meyIYaWKBVdGrkiC63gLs9QcznAKQVsgcX6iR4hbMznOaT8Uno9XQO+ru7tSnJR62U1p5GVomyzlWv0VrhsH4MDLoyHIlucsLsMX2ZhV3vIP6KqQsVpCXgUi6UWmO8KhFS4gXKrK0FUJDGImS+zkNs1jWS+zL9FnNo5Ltk1N+Nndh3fMfonVN3lnm6bY1wu0VoaBNsqo6Sxxo5xxuG58/Q2I4m97kjSBv7OcFy0PZzJ6naejMmicuFLdf2pDKrhlOgA2/D1dYHum+Qd3IY5Ktk1cb4Qc3c3on583WFxvG2Gq07DzWE9MLcjcQVVFVsTZglokUORBc7W5aKteyRa7WAIC1mQV/pGpEA2Pt+3HFGaL8/HA3OfuCRkFOHOHeXPuuySAe7r3a/nZ76NJyOR3MV3RTb3TQUeGY0epxH3oIuLBMa8lW9JmVyn6DRbJffJqo7SlJP7IMCs4QB3NMeaZN2LpRa2tjpNpYiFwiXv9TomQmusr4m3cUDx0uEMXR258glD7DcYDW9R3amLwqMOPqB2xUPYK1mNj9lFvYY1LKbYSb2SoIch7HtPXc5g+oAQTmOQNyIFcrARHsTc1dW4ZHnl5OJHzXWQfLBYH1FW30QxyVBR4B2tgi5AZDDmrbyv+NDz5ouNv8DwNPzYVWD4btGftg6kNPBlNr6nf9RJgb5Ud6jodTDOCxWCsG5hUDdg/KNyi/VbY7mFF35MFI2D1YrDxq9lLbTN7HZJvfnZHb5bGF3aOvzMst8tvLWytFmeTN7xkd8WUG6z/h678CYJfhjCnhrHj5ukoMawd9FTCVOGh7FvhTqDUAzQ2pJDWWg3UXkRYWULwSwDFFtI01FN4VJfKLXqNXHwO7gtEBJ1U7HUzpuwryj6pALFNuuyfwKPX5hMgY38aisdPcUp+oiy2+qOl2AQhGsLZ6hIcmEeZTAOrVAFg6IRJDEEYeUfd5dsjrMIn/8EoTgqssEpT8bAl1n5iCRkJUdpV7u5shF8RiRQy3lYnpbHpUBb+snGmNgn6+TZjCuy0smI4Tm7xysWV24uHXnsUiAbM2Ye//hHHVHTDW/HHBVZX3jQ+gf3UZJG10kqoJdDubUEDwKGsJiHJFNgF0stTgP5QzP21nVSuHwAyq1OScnNE7V3nORF179DhM+mwklJDmhxYRHFP2iKYZL5Xgix4wstz9vyJ3mEg7f56z3yNqkpBE9tsq7X8LzDELYtRI+6FngIS7qhVVs14bcmoNhq1ZF957B+OqyrSnArEEqtMX9Pyrs0KSsFeh7EgjKNrEkRZvczfA4TLWMwhMVlAT4N0apJzEcbjUps7I8CKmscX9IVgGb4am0NPSI3tJAdtCmw2IM2KE6iFOjduMQNY38dd5msgcs6JaRbi+2FnbY9Hs6Cw1rz4nFWoaKEGA0CsNr1iOAZYUEQ+ygBrU7Ahu2pAK1OYpcJatYhJxm5Iqv9HJXDU/dH+fo6yej5FhiHFthqLFgmNq97HWw2acKfFUAAc/zfUXJ7xz+C0H6zoA5wzrM/2X1PVjyS9pMFvYDxfLAeT79HqMWLAsyhLZVgkQIt6dkX1I0qUNTZNuUAUYw1uUfFE5lEwX7GlZlj7fTIr1ki3GTzZTaaf4na7QwwVIilDpg/oaisC0T6JsE+gnBo4WAtuhkJhQ54yQ8lbhbAAn+drVJELy9Fa59QaIv3DBUk5Bw2EklAHNsgNFA30UM4jyJvTE1Y/ipHwoJtjRtPv3F7+fF0WBwceeRVp/HkIf96+ru3PQbvHrgyKws75pQYk0hw0uCK7HsqQwyV22OHxA9f9nzcHtsbyPIorzd5JsYMQeVWbu8gVjds3SzQq3+q/Mv4dQxh6XHQ3LFhbQHyOGALt0bqBXqCy+P9rd17fIv4y4lYhq82ymfYgKHDvMKHdSlWoNjGzJlEgmmTfrIx1zVzLfPug8r3bnJqXI1jrOxWTCy1wJxQJyQB5fDZppe36CL5k7es9l8d3QLLy/wCn0vjCsavg7Xv/xfoOkQotDQ3n0eZEI43KljcxXViw9fuOABuo5klvDFpNww3N1GdVt8S9PBJ0BKFwq1RuFrp6Rkt0iBxiRaR1ZxG2WqbO0yyiH97hiuyuTFZI/H5kOGrhdhBOXl/LRN0uVGBjYfLZ8Td+befrLxuiigrE8FpbVQwx8JbNpwqzDoZ4XIPrpp71QTM8htSidu58OpOJ2lNEU0UbgjzHoPQw9KnxDINa3VNy8wfbtiYgZzXKZJlBTQAt3IDgC1uowKLs3OTP0iSbkEstdHJWxdMGDVQbGOEKyt8UqDaLPtimeiEK4dzae1MGhIBQTi1ED0RBukS0sha4aBcWuomADzbKcDcZgl8lRsEsDDsPlZFRAQUZ9cdPm+PSGbuVD1lMYPJRQgrq2+r7RHXzYsP6BF4sZQrst7Wm4diwX29K3rGqkIrrpqbSnJNGUTzHNC5K58qHNuvf7ZXvzBGodDewwL2rXBRYmBdZXtuGf2Fx4T5BZK0QkXvZMftoWKphekxWaHLu3p9nQl5bbkic5xtoocxtv7jQsbAZ23E2xah3vNgw5KBZTyHPYTI16KcZgcYOgCLbPk7GdJrm24gQn59tsABH1V3pEj7UhsL3FmBmpsiMRZwVLRtfB7oMn+MzeVOX4th+/UW2YWq21XqaXmCRW49xOvzfCUUP39Vm81zEYRhWYTuTKvGsv2My/YfiGIdle0Z2ImBw3GuJ8suwav+x5DQXk8sFWSeRTKYvXeRGtfeB2j3fID2vi/7Y/O43E1BowG0N0lMHZcZ600AVQ1G7a60meLbfvUNHkkjVYSbQw2spUBtTAxCjBBfZuuM2PlQSPwRR8XPWGlUTZbnswYKzC6vHFihm2ZFwX0w4/8FoqCeAydOwYPBuG+HDje7xg2t/KVJ+QPs7BiP+zYOVl56ppedl29RkUQZmL8iyHwp8LvPoxXSiXYQz0y9/jl058n1GyJf0XPLGLKruQYOyjK5zdCqt67w8XZAudWRe2cyXZyW0PvZw9dlzmaDrvSfa84XgSuykFMTZIWkJ7YvdfXlhqKgGgeUlU4E2Zrdj2Udv32OxeSwo6mrL62bzGVi3y4XQbnUaOO+eLHRf94a/pYoSpOodOH0uF00qLWVpEYEqNwcO0n7gz8JOZLZ7zYM3KX15zl4+O6wXR3lWVXkKZSmRQbz/A+8YddcgMU2+yoLkdFhua1quwQ39TObRHo37vzBRLgE3aRynO+DRARDYLvGzTQI4+KPOipQeX57DURosIWWx/yD1b/qshLfsxAKLQ7t9HAtQyyWzuNGOZ/KSJcDtDGOCmzu3rMf0jcMhcIpwx2Wiu/qH83xkYX9azL2ck9edRoZ17X3vk54o8qoxMJfoOxfvflacHYGvsy+nwJKT3xQbge+zEZ+ZhVqsgbyspMpsApMAfI7uCR3OH7E7ZeClsJ8tpGN+0QRYrmlpGFyh/uImh6Ng6xR1N3+gzEYiOwQfKwKO3cPNQ8X32dzQ7IQK+OlX/rHnHZYHBhZXnUaPv4YZbc1eVCJ15CY7xZGGNHZ1drRlUY/AgGRVptNnXJ7a/NlGRfLbc8f2zjmNi9Egw/79kUWONuXaNu6R+IVHQxhcUQcnruVNyIFWt6JN7x76/N2jF9oRyBqZZFF6UFd3eFGW3+ocxRTWvrsEirMDjuHHbppdpNOy5BpH5bJVNZCpHn7ye6wQKhyuiI0uUl4cwVUbo+9NXroGgHAzNv6Qib2Mv+BuGXIfrfEdhBjnb2U4RyVWmnG98kKFbLcU1D51qz2k7yo12d56XnV3aNxWMeKutMs2st8k8Q8iv7jUotffOPD+hHfs4PVCm/K3F7IfN7HotguDcoWAdYGxeO6OCSVp1kdtEUeRf9xsdVBSABZy0cFFkeUJrszdzrpPloo5Z30HGvh/VeL+4AEn4S5m4Dmk83htqxIw+Lhdvhuj002k1C5PXaaBQ3E25TsZZadzIr8hZWrnJpVRL0v8noDyqm+ZBecCRSCpd17eMnSfV5CSJGlCapPo4K9wDLkmX0I8URqGxUBAdQ2isdVHEoqTyMTt0+CPW/unsp6utCqad3wfRZMM532a0VSb5plQhuD4qFGBZb4RAcM5vNy16JhTl5t/vHWhiAk5RNKbS6+mrTWEtRAseW8XFRRVQt4uSL7/sJoxVILE2KTSxxGLBRa421ylEvtkzIge447qosCZfET8HgiCGHTQlPvHAtlHjNbYt/ny+ix3Y8g84IcysbTD4zAZD7b8nV9XeVVlJ5mcYo7BrE3D+HYwvGjroUewr6FS1K/f/5BNRYY0rNF5dhgSNsWW5GgGBsP4diCYiw8hGMLuK649mAIR/mE5XxCdMwoPUEIJJkBeIi2QWIagIdoGySzAbiFJbWpwmmmw1dL/oC5zoXT4GzoXJFt78gyhvrXfDfHdo5u6myFVlDsNV9mg/UhKlZneZJV5XdUIDy3vAOOBMRin7tD8Y+8HoIbpOc8NaRHi2LQsgTEfveWuXNB5RbOPDc3SZoAyRxHBQ4a+UaikW+s3ZfIuQALXXzwxeLgCLMIpGKoIS36j2sBjtH9VztMogo6fLXEBIzZbYSfovIHWqmpKYOx6/PR/f1bscfNVztMx4+bpKBOL5/yjE+AAQK44v8vFAFU5svdOPhdUqC4eoeuE95fTgZkY4Lqqx3EdAf5kKeAOUoG5dMSxEFyKKeWDqPsh3jUAgGc8YuLFQRww396JEdNypywtq+ZSTH35U7YT68j3jzKF9rvC1Qnaf014R1iDGGx0mqsMxbJn3SZ0uiMKIYSOqrg/FsTmVQN6d/iOSqFbAw6WBvpuCFpcBT0hCF8WoBGJIey8oTotTzFgBRgNk7mRXwXlUhqhQUBbM5VCewnPiqwt/g1Jy/Y3teV2WMlBy68pDd11ezRSiudcSWbC58wmZB34VqGPUado3WUZEK2WQmIeRsfIhJJ1x7WP+dVn0xy3I4CzELuxTHaVJd3Ce5xhD9TP+EPUbb6ci8cAtSgW3MF1R3yv5b4vPYhKSv/92QAlC6PypihmebCKuyzuVSughciltuT85JfiLneJzf0zBaQuQCULsxlhmYa5ura5rGw3y2kdolW35PqDmQyodAOL/DqKPP5L8C4YXjVgz9nCzRuVUCGW4DH4ORQ9twP3fvxZRY7M2AhtrcMn5ZdD2jiyQjIZAIA2I8dH4Y30PkMKrfRtuJkQ9IAiHosV+SAE4iS4sssdHGUkZOGqG4z322xAR0cFVhYJVFZCo8X9B9tuGkgO1U4oTRvAsAzlqq9xAjg8uQY+K6oO6HrE2lQ4q40FM2vT54FzC4RLn8DybxKEzaAt9FiqQNm8K5ZLLWhpKy/rn2V99O1j+Alt8NVdncGarZXcNASENc2QDJIQCxUBu3VrO+V7BT5o7tUS0AWAq7IZqPqqkrVHgDAxjs3RtmQyEpMkyYUW/Qdy8/vQBZ59ruF2yV97ZjsMpzDJfPdWr4ekShbSMI2Bdu1PWNFxPOE3qNx3Z7hulNuz/5bKe3zOBAgtxar1NEx7y6fQO99tvgZa4qsVTiM3UjE6GI2MsIyDaeG9S9vhsBJpfabLZZDXJIJKbfGRXZ2J+g0z36f/yy3cyuI3Fw16ZR8Vk2HxeVhL2nV7Zbkl0UU/8ADgy5K+TILrMT1EdJTRgWWt5kIvnbly6zuJumzMCBaofAvsHr8jRUsJo9VNKfJom8TuHvvvjsYQOC1aW0/3pnMyOeoqouMPOSAfBP9jFA5qS3K+hOxUaDAutAMFFaRCmf3OkdRmWcnedHMFm9r5wpt8NJpR/Tszps4hEJnvHLvESWg/bzBKSTFUhtOjW5umoMvx6zDdwurz2qdZKBb4bjEhtLM8oXDFSUgu+lOtNimn9M/j7DwDbHxj7E5bf46FNstuc+iolVdxOhatsTWPgdhHJfYKDsDjaEraah8sUP7lr9t2I0KszAqyMMXQiJuGGKJ3XYv74BwMC+BJ6BzkHgGOKYReeRfzn4b2XkhbPPLcNQiEF2nqBUVMG45lE2/L6PH40ckkGFUYHVpeYTXzm1ePAk5CMdFDqJvS9/Jk9M21CO5O5ouR5AOAV5hg3GGEF2zvrwmtC5oHBDAnPJx/6KZn1RYzIoV1wUJBm1DJ0LdxkFY3W7kDDFNs+745sVzuVi+Dxyw5rqw3BaAzWaT609xij6i7FaINGcLLPGdoSLJBTccrsjybovW5tObsAVW5rjAD9mE05xCRe+dZkmVRCm4wPmyZ7zO6QTggo/5rd8SZxA5rG5l7WkWNtMkeP4Qi5eyOsER8vZW7F1mToI5GIPS7C1eTApjmMgYTKOc22edOHE8LprzNHGcEUMF15/+49awkLdcc5NnM8ox3NRHdI9SwamX+W5ljS8q0GdrXGKOkbzIByIcFVhs3Bv4uZeNy3MvYW8H8EiEx377jzZHmhtUFKgQcI0KdtPSLuWSgtycZjF3scB8ttkv6cvFH6KSV9nZgq0RUTRpOptnJEACdxadax53NY6J9jqmTTFeSyxdaqmHei0o2Jtcu6banRU0iqWV/34cP8blwO46BNt9yX9S5GsZd/NlNpwpwzkusVrbQd6eCvBiXHmOIuBeL7K8aWvNDodPTYotIWSLL3bC3UcoS9EzEM9YYvQ5SD2PgR0alwOgvO5ENgr4iQCnpwFCGc4gQ57cjLeUJ1l8h1Z1ii6j8oenFxmDycWDTFl9Gq7xP9wfYC4XXginn2wETJ4dP24Io4opl7kyC+EvpOO1TcVra7hQbN+bL9lxUfCCf1RgMWt4EzuvRXHMfrewCkTUC7WoBHzjEjuMx9kKxNd9t+xfTV8ehnvIlFn2UZwR5rPNFvwhWa34F5OHr1Zeg7eE1c9QEQMX71yhPV7QuiIUWtgf8odvqBBXLft9ayT9QZyGeKS9R+NkF5bWnUbAN23zOIavtpjEDYP9bn/GPs9TaWb6rsxmIZ6uUuHmsPm2NWz4tQjChj0aBzZU1P1rseFFWnM5Tpsvizj6SR5qUD/QsFTSPZShIokDuSPz2FyS8GlRbDtn/wd6at7DHGEavlphEpDY1AdSSVqnkbQ1WS3Ex5d3aI2+RUVClHo/Jh6hcuBgTf1p2Jc2yp2Smk9zHiL/QgzXOmp72RnITxcDA1xvWy0Lwp2m5V3mRZmCB3j2uwU24lQo3tkyn81xfRCfGv9g/c54fpufJTF53AC4zmeLlgyD+FCt08N8JeyP7Hfr29ouucVnVD3kxQ/w4laAsXJ+xwvuia6W7ulMMYwPhrFu5fgxvsP6HaKPFqgbk4FujWhrO+Xrb96NzSWuQ1p1W4Wc6j1Yt3dgxbyRDkkjyWL/mONC4WWjUZHtef8kL9ZRVSX8wxJi6XzxUdIFWl+nSXnH7x7M5yUF6y446rzLyRMhx9kKzyg3L1zR1ogwyibtpwBKWgvmqqtJq2/7UTawkWY3NPvDKP5xmuF+xT/CuWhJkDpwlDGmaZgrWJ6KgHkVAvve75qbxKcoq28iek4oLtEa70O+djwIowOrmqHZVpXuW0LeruJjwIavc6gNS50A2hj/MNzEY3M5EGhR7Llo67ioleVhmIhD5uTyqcGwZ6GtY6FzRKZq1U6db0Qyi8spHlmNYBr+6TWiNxJN6Y0TtrcSbFZvc+8cPx0VeVleoDQNwlE8NpeNTYvi+XLVUttSk02geU/Wc1NiUblsSer6E00926jsEV4pkF0UoPSZF6Fwa9jjHzWq0Yo+YHZQVVF8558LA0TpwC6GeKZhG6ZxHhFXZGdMiG4RMUOLLCIU2rA4ry3ZakoniejP1n2z2B+FR21sn7Px1x4/JWskulAPX7dx4QVbbn6LbL7NOMmLhM+wOXy1CwwTw8FsMYhMN3y1CSfjg8jsaou96L5ZXKKhTfrEd6T/aI1H7NKowMLGeMRZFo9sah/GnGGSfpg/LI/c/HMdoV+WvP66wGv6kuZK4/w7+s92uICuMZ8trlLorh2DL5zzZVY9XBEra5SmT0InmZKtEfLsUP2kPIvJQcyrq090MSc+l2v9UG57sBYl0ajAzodJdGGy2r3ygg9woV8srqxLVGTCgIavNopfWYo5c4avtjFjFyU/X8Nnq/G9QzdRnVZYqq0wNyZRWgqDhUC2Zt0eRetNlNx63oJ2WJxuAGRVt9Vs+5x32R1NZtxGxgW6JB0jc7kf1WHYVtbG+mazfzZR6TwDAMW7uGzChQRNo/xN5THnrlTKMA7WqjeQYQUodsH9Vo37rQ/un9W4f5bjXkjUfUYP5UdUVagI58QE43QQfKaIJpJ/YOti7ikV3LxKv10CjhkP27vh0fcJRWVdoCYvve+mz6By2vKV9bd1w58iIek5MesLHu6JlV1u5y6g2/l/l+AmS2+BzGNzZ0gFij1PPnOePF1v8oK8l3ST+MbOjlA5cKOm/ray4kme4ikV8bDf7e4eofzk7HdbH34I37jE1jQWIJ3Sj4QLkm++WFwSRT/4KyL6xTZa5kvGn3TY7+bYsMQ5SVC6In9x5zGuyJ4bjvLsJrmtC+CmXAJiMaOPVRGJl9XMZ5vgToKgC3nhYjpHRTbXb2WdVqfZjXCTN3y34LsmQRvuA0nRJqivQunWCOqLpywOE9Q0IHKJaVLVnujmJFhI00VeFzESMmswn5cKj6IRz4+ViG5UYDtSMSU2+902xuZUeJNi+GyL66IqJAGkXYktxsM8TyF8zXcbzTKLwXPyqGBrxMLxI1Ga3qFNmgd4uInH5nK3qkUxjZRo9UYgpuzG2vTnrxSGUpPCbn7DrEAqoVi627e4vrlHPlTV5rKIsnKd0LylEM1kMH6t6NuwVSKbQ7HoGsmXWV3VNCcc4bKm+2x7RQLfJ7lfJtGa4I3SuGTpqxzC28k9+iRkjxgVWK1FwXWh+7Zl+1YQu8MIlfOOtbc7UPJiMVBhoS1KiVGhraFO7KVbWAH+8z7Bo5NGLgDlNne+rShsWYG7+uUKF7CZeJ9dFbOPJ1bQyJnPVnNE5KpgqGC/2894Y94QzRRQ+VLq1ZebmxJxW033zfJmH7jPt/J/iKr47iL5k+Nh5rPFDODlRJPOjenef116+zzK1xv6qoFKi5AC2d6g/jPZHBTxnXAjK5ZaYE5RlPE5Q/uP82/ZB2WZxwk1JIoBdKho1dAm+fAVm+gZ01MRL6epKcTHcfAsOKAvrES2VTR31RhCAG3BaK8XkEMsS8jc98q7w5dRcYugA7lRh1lclp397TXID+Ysc3GX0D+beMIrLFurIiHvuB+Rxd745SvMlQa1BSPlqE4DKOSTA6ZA35Yn33ANBOAagy57ck5DvbmZpmPYK0ZWq9INitBiXsEGpns3Uk9bEavn9PcIA0w80Dm/iW7xLDbRh0lKlNf+WU+D2earyKbcZq7HOD1pyiELOe0caj/GtO4YVrdXCZnPF6fl5zpNf395E6Ulr27qRu/NPO+SkmqMVwebTZoQj9c2WZBmU1HX49mog+4SERmwk6oBz7nqUQfgJmU3PfeNllhzyxNxSGddug8rhhhqydjhDM7aYUTmDvtW80LfST9OaNHMzQhds+3/l9Gt+ngCgUvSSA0wJgcREbHv6cOJoMadCzLbGNNimoTxkVRWQ6ZHWB5BYfTbqEM+l6PmOXqIitVZnmRV+SHB/cD7z9cSrb4n1V3reanKEaatLGYFE6oY8IW2Ic8ZGOMKwCf6Dm+j+qkjQziB0x3Cbc4uQp0QhxcOqScf8dhCShwe9zYykH78ehbqbK3EjytKMlTwIL0xt/3S/112H9pcSOQaOC2HeuS+Zx1RgpSbKKammhU6SYqyIpx2HZWoAXn5orsk6a7dWsvaH+lR2rwj3QF8irLkBpXVZf4DZb+/fPvTm7cvX9CnYEn8X3rz8sXjOs3KX2M6jVGW5RUd+u8v76pq8+vr1yVtsXy1TuIiL/Ob6lWcr19Hq/w1xvXz6zdvXqPV+jVfvUVrhOWnf++wlOVqlKWHuTTuXITzTRKPeeq3/0ACM3RMco5uXsj46bfXfMXfAJ4kbf/+MiEkpcuZPmhG80Y2N6cECtFevnxB2I5cAfas91qJnr3SbJrJ7qMivouK/7aOHv87i68qxFeXhN5mcVqv0Gl2kWC00aZDek0umiy7dlp27jm4oEIxfWHaHd3g6xNgnJdJlYahWBNBHADRJ1RFbXhKGQzhKJ9eIJzhaCcEJLtzxznKsKA6KL8nK7K1eWBqMPwzz8KMsUH3vYg27fNFkr6Z47q4yx9Gc+A+ysOcKEGe67LPScaIOUscdDjk3GxPcfZWUy31o0fYUrezsh+S+i9ffIoeP6Lstrr7/eXffvrJGuk4IM10So1nAWtDlRAk8Hxn4K3DDLTPI2pWgpl20MVdB59H+kxU8ifqVelnMqND/AjTCK+Y/nqKN5vH31/+X7TSry9O//NKoMcV8S3J62z1by/oWvr1xZsX/7d1d9gHgoN36G8uHaLPgw0v1/K8H6ZnP5OeeQqyvqdTdfJtsE46r3jz5dry0TNZpVq5+8ZlLloaHdUpeZ5GI9et0X/Nkj9qdIHy5tU/FXJbvfAkjW5P17jrnUeuEv0vP9niP69SHw0xoJrPPNHnjmRiFadZ003A2jkqAQvWzq47j+2or9nLz58ctp+OnpMoXh3ygArYaUnSJJ+l9W2SeRz8TsvLvI7lbO9xLOL9C58JpxpZxDwtbGOG++UXa9T9edQPsfFcyy8qn9U8e8/LSYFQZ+X32Wguo8fjR7TeeNmjMJJ2w2oi/MD9ykSIdNnBfCy6zWKg/OOBx21J+Qi5PE2fCcMvvgWHlqx9SqYAxtYg6iGxr37JPuQksO3Wi88P0jR/eF+jssL797e88kLmprVCNqGoIFeBiGYsaPCQxDZVQubVjt7H2SoQJuczgpUMOMjKB/7qfGclARmQvRRoam2JBPhcr69R8eWGrI3Sh6knPtL1nl7tFc7zYCD2LWQ7JhpqejHS6WbkGmN2lDM7Lx1sNkV+77cRjFOgyuWbmfmHZg9zQmbNps+MP5v0uk1DNbWiJRTlTUIWu+089MlHlCYya5ZrE/iGRSrzeQiF9yQv1lFlc0Ekx3URpVXofh6s1kl2lK/XzL25n39LGeRMdnBzk6QJ5nI/0vmfyN4hGr48QiGX3w4Suj3xda/iTHToU3bZj4U+RmVlts+8sUcedIcgPf2Y3yZZKJUa46MchmWvAUqjPgKxW3aag4BApkCYjBDyxLTrjojBvD/G2zIYWrqzuzJ1JTU6ApgyaTBk4B7shIlc9kZZGMe+NlTaQcvuKnqtEYweUUfeLHYwGnHVfXpyxDiJeRG0XU9vQiJ7GwTZP5PNWV5WUQrd8bpZtO7yDDXH5CD4TqLHgNg89j/zQw2Up2FnpeckngvUzli2W6u3wbIMcvXxkDfvpJyWEzg8XN4VCJnj/9kWP14iqEhiDreTtbVJJ3iZf4u8tOcFHR+WMNaO85f8Vda+k79uYA//UDxyepth6h3dkVCzSTiEVUyeCYNMpykuuDddXxfoPokgBvU9fu+CN9hhmt8SnfCZsOji189m0UdmxgyT0C1zvam1U3r5N3bXFi2uI9bo5iKFP+dVaJRDxgzPnWY7L6JVYXiqzXrrIvJ8OxvQa3eOY+IZlHZnZ8VsOxrSgu/tTIG7QIM1aCwDweqH8VtSJhgaC/jkPlm1776688YkSvHFHX1sIuxKO8HQoXEGv+DqGKd9P9pvqsN5QnU4Qp0u9lJ6pHl0fr1Y0UYPQRQQ8sRhdntRrwNpH0HwdcgusZaZcoP17F8olL295yDelpiZix918CUSZfVNFJMwqQLvMBVsUja/13XowvsqAc6TEzZ4Wr5PbqqjqPA6bXY4/Hf2c/RHnRToS3WHirNRLkPXDAoU36AkaG/L7YVVTSanSmKiNBysVlyTXt0/Ld/lD1maR37GgBaH39R8zdJm+XbovEb2qbuy+XIj4HPylWyRHD9ukuZdyHfRkwyjkcmwRUidDShCf+7+EJUXEUnOH2JWx5gcLkG5+j63oHhgxAXt4LZAiNX6XMY1QnSJHkP5SJ2juC4KzxugHsnRU5yiRmz4yTsW3xkqktxzmfYY6eZP0XotrFN6c9a/4OIjy0JFIGEhS/OZRWmHrbHI96dwFCfrKCWpvvCvkubsevN3fJ4lIax4m3ToehAHu9PyuPQiIZPtxY9JsKpDjJbZPV5iGFlzIeV5rKry+Mc/6qg1DXhI8uY0RfEd3EcJrpukDE4PczjYR6fdK8mCjfdj/tCMtfVq85sGrP0nN0/0BH6SF13/DhE+UPmgPYziHzStEsnM5+kfSg53BN9pQ0N8uOiPpV4qBd2+8Mwk63odYmIafNFjKHx04GjVokpQeFUUszkBPqyfDuuqymXB2aZLhkB/T8q7NCkrf4TtWk4R5kssmEfWEif7K9a3Kaok9rLijBAE3zq+pKtpG2iPGUfkNm6qNi42GE+UOg/E6OKEaaO/RLlM1iGuP1jc7aVKIMydkeo4q1BRevNiK71GWNHEDNRKuFnbxOeFywQ1a9dL5uPNE5XVQVUVyXVdoaN8fZ1k9MQzKbPi/ndPCJTtEwI+o/iOktu76Zbv+JgSHP33ZDUh9g/T0qbflULLnB5xWIET6q4hjJfJxIE62xKrCI48uUfFE5lVe9PLuLaP4aXTS79mCX8DatCPcW2ffhxGJWq3V29DQY/rE4rKukCkd0pV2T73V9/EwZr1HAm9TfTNkB/jphwskod1tkpRk1UWMIv5XgU06M9QcVqhdQgzzQghIUNIfBd3eWP5wVJ7AkfMbnd/Jp4XWn8El8xiHY387eJfSzKtMR6c5418/1yVgC244tA15S5BdjixSXvHVR7l9SbP2HACp5O8gCWUL203SfTime6Cfow64CFbt4Vd3TwwaJ/NW4uU+CzZIjbilsD+MYd5hTWw4N5V5ImS0AulY7swLlZ7R6aRo2KYO5uzhLqmOCRH6ir6KPjEz+0i+RP5jGDk1lVe5hf4+BhXPGa3Z1QaHF/GtwihMu9QHf48ym41NnsHDgnojRjWJrWFPl87YbMIZaDZZtvHTVSn1bcEPXxyiwg3939vRNczUYba0RwmWTSkx8VUu6Yf3HYuPIEEj0Ys2a9+lJO065lOy/rZxeXwM3rwWcGn5WURZWXC+xkZbIX9OrhikHi9ZaFeWd5d0geT2caSPLMlBQZ/mChFXUWvuL3pdLJtiPLs1In2tL9+Pkkiu5E5mO77mr4zx5P2vE5R4IcPLzZocgNVm6ADjvK23XU6d74QyM5RWWG9neqebI54KVYzw1eH9EzqUe40VQzi6ImwQ+OTFxp5R2HJQckTe6vke1H4+LEqIvJm65R6JXsB90xEmtYg9ou9FniUp3nxAT2Cb4f4Im83r+ZhmcD3exNvjO3aby6kyG3UM+GhhbWp9oLPtRNcda+uBHJkjqe8XZZfds0Rsmxo3JogYjlJMZv3Ll9eFqUPyQpd3tXr64zJseiCqI0fX9669cysT+4iuWeQhl+eiYQexucqH5u6XsKxJ61nIECHhqoXfrhOSyxUm/sEmwVky17P7HZ24f1eci9mkhqsr+n3VEN5gsVcPQTrzi96bTmQDVLfc2EILmQpat+Hce2/DDc+QzacxFUluKcCQ/e9r0ZYX429f4W2d7vnX7F3P/hrHQBpqN9NElNC9ieMZ7JFLawpwbRtZIJD/hElNr8DKhGEzbnfM+agcc/qrs79vdOn1MlUBH0mCwAeYije25oXJBdhlGfCIjszC61QuYxunzHlLQ9QtsT7FhVJlIHx38+EqBPkOYSTDM6TO9HziDZlTL97lLwJducgeaN0BM4x8s82WPagLJPbDK/A7nA+QbadfQw4Z7/0f/pz4RPOcKv8n+swb+4FSxNGzzRf6urLDUVJBznFEzQsPzyTPXQK1yiFWTOM79IW+UVBS70NApnuqpVX7Z4JL/oKuO7/ni5XnyLq0TlIwCt2lr2CG/i2nHsd6ohN8l7gT0weTu+ksE2yZp89izl9ZFWRpwST352MdJnOwCdvg0ecPLclPEkAdkCzhdeFm6+YblyFn8dES4boLAVBWskKQ8rx5TjW7CnpNC8u/qijApXnt9eh+9kcXVf/wtPEpiYPnwaPHsJnaMjR122SbSYIS+u3HLHvtCH/nfZjkv0I9ISU/SHPPFyley7gecjVbjjv66RvqM6SP2qUUJQ3CdK9emJ7AV32Dx18LbwcvQE0fi/TtvhCBoUTPRQ1eblCedyDkeBOyI4fcd/KUN5A+2DyZYLJ++y9z0QkLe1ZoQybNHPYkYe4mjhUj2qHsEb7jCVk8mljlsaCpHxGsXzP6U1jGkcWBhXJHRcGUxB3wO1M7/hXevl5O14EBm7U/5p+2cbimuiRRRalB3V1R0Rk4810jmJMsGciwrtN2X079xThx2smLNZb6yezdcoc8AKibc0KgbF/Ibx1mf9AYVYIRXcQx6gswyHFf97jU3PhlTnGeNGd5EW9PiNPzTyPFXaZb5LYfnm11fyCH5de3EZPK5qdoc8OViu8d4bPSrtrQQh0fVDueCYLhA7InkXbaju+QMg8+puY20SqarXIwQPlcyOJvR73/Ob7MvjHqKxILzyt8C0WyZQ7YqPJnTxPQrspgZ6T8Hlf5PXGUQK1dYNHdfg/nRH48PW53XW81nIIgULWHqRU7cXKJH54uyme6LJ8JjJqJ8TDc2O3gFY8Y85tiPc8mJaOBQ7RcbNsEHzq2/zlLsQWOz+0mXHbs7FDvi6uvt8NY5v41bk3AgKfS0Ja8wK3VfM9cRxWCFxt4t2AqJocvoGTOnfceVQXBcriJ+jdLEfEDcJzLGQML1//7rwqL6PHdlfyP2F/iyTxhu7CDB/aK7wW0tMsTnFXJ/PlGzV2/DhPY5eksT6R+UwjHDU6z0hb2TDPCNvGZh0Zbshisdo3NhJjWPAnZG+I0hOEpqapvOWpCSxveWpqt/jDJGWnfDI5I/qkODZvhYqOyZrAmnqNtZ3VxO8knKOHqFid5XiDK7+jAmHW8nMdObpD8Y+8HtyrQ58ahQaCBcV2SoDER8nWyeTmJkkT70R4vda/CTJG6kNDjjHkPYsCYTlyhOd/rKc4TTvGQmqHmQjSpWCKqTA+z1SH5Q+0kpHOu6dH9/dvgyE7ftwkBT05fcqzIc9DQLz/haIwY2f58l2ChVv1Dl0PD6y7xZH0aA5iuh98yNNVoLkSkQdkBAb5YZT9CHaU4vAGW2Is3tOj0CjbZ3dCoz29jgJtSK2EpkpB6/8XZk3UWNkrkj+bl52J33wUj5PaTYI+GLvJGjhHJROj7ymNNiSPSnjiiIgD9hqfO3udKHzXz2pSt0ShzalnURLKk7c7KTbHmTA0bVGSswpehJu6MRlOYuuaOlvq9l5osMeFc7SOkozJZOmQD+lDRCKl2sPt57zq8/95uXfHMdpUl3cJ7mmEP1N/0Q9Rtvpyb6PkWj9I+LXEh4YPCeaDZ/MUw+IvEtKq9q231fw8vdxXoTHrvE9u6CniGbJONzT7yRtqes3f1xKtvifVnSMLcdW9uzJ6rS64HWdOZn0mDNrpScwsOz1gJMPjcxPZkdr/Fmpi++Fp2XWVZtmLPHM/dMjwmW8T8CRyjge7IUHHwVTAHmO4YJQLlBGFPVQPG3ThuvcJlSWTadw7w1U3I1Tp87Q5zyD8+oX9TKRfP56gDjILqmtny8bKhwgwJ8ktaZD51PeYfUOTX1vOMZpZRjL1NWt3Xmr266lJNm5tatpNdhcY/g6wS3zjHcB9WnaogihGHzGLZ0O6IK+3zInU/z5tbm76pqfjc+lS0X5EIjzD8Kjdto91lOeSUWbBDZpSUccOptKLuujl3dXLtiuPrNn0eVl5Fndnbojq5RVLMRxGaZQN6X2c1LdycnedkIewsLcMrP3jytPadmVjPDF/X+gu2dC72eex8haU5JdFFP9IstuAl4vUmW5apYTeEKJQV5jdQxmB0M2xC3UL4BlZMfohOQWONDUD3E+JduPJrQcWukdVFxnJKI+eTYKUAHFYgSZucf0nhN3pHEVlnp3kRcMrYZT0luMQPf8anPddkJr6NlgFMHEJ5jzf2YlubsjBJwy6g9U6ySQuZnxCM+u0NiNBESLcbFvcWCw2yJz6EB1FxbPaJP2l5VlUtIqDl+WpsQu5XbOydX2uVtlJ9r9eXVz+///tXVtv3LiS/iuDeVwsTjazOMBikbOA7TgTA8nEYzsZ7L4ISjfdFqKWeiV1Eh9g//tS1I2X4lWkbsnLTNwsFquKn3gtVs187RHuFQb+OlFRB8H3F3Q3zOy5umFOeEGzkXEOujq3zPQKbEyXmNuJbGVr+vYj9sHzpnyIv19/R5SmLmwwkyvcnYe8eB7puDJd/iprS43PKBk+MojwlW8pB5KgnMP8K7IIG5HLZfT5mSXI78dscWqyOxf1Y7TWN31btza8dvZfj8jBu5v3Iu/xWK03Aoer512K2ETjTj1Qs7lFRZLL/RPM1hD1ZQDhNupizzSLwWSTPvRuyFaMLKmSOHW8/GFrL/51BLE4/uldftjIl0ZpBKxnDXpQYLDuYMamz2WXCMn6143Asnkf2eb1UC5iXzosYrUrY5dglNdZTWwxlhp38XZGG6zJO/QVpWOD2eVFZep4YsSxznbk9TnBySTS/28Okf4Djo9mT64PyFdKR4xUVBSo8MVvlmNPo7yQRX2Fle3GHeo2ySrfxqUQNoXb1HvyTiIhiOnAABsZg2iVvD41mX3tAiWhsN7ojI5JPsWi57YgDuzdkL0RYI6/FX1T5Ed3GLK1R+YEcReDrjsulm+gjCIeMwKVdygeeUnS7uEvn5voNp6Y9c8hl+7J3ke128go4Bx42k/8aw8nQrbHUubeMLsntD+n6CEuv2ykt7U70b877EQv8Peg3nn83SXHWJ5dfz/VEAOc5/gtuTV7cnYhMDWZLWp8/B/T/n942r0btP4HngruzlnUVh+V1Rxj6/Qhuy6KcYN4K5JranSZRg6bxToTDDnydpWlZ0D+dZ2Nlgaz8C/LS0fLnEmKSw/7SyIQDRtvX+VN+TbZ79Go4Ff4z0M9atyiYketJxyc9TpOkpOa0dre5d8+oYIe3or829fuF8nQ34R2q0cOX+e56aaS8zYKjtvWNDyC+DN0W427PHVx2WBqj1p43eCVWxpi1fSx+AmoCQF1n54P3pl6cfByCCJuHomr7t9ktzUHTh9Ywyo0idx8gwIz9s6TiQk37mzD+jjLGGwPT+iIPsVFUrPaCNKITjbdaZTu0nKAM+EJIoS/1rLmGg4shPNGQBJkwgKu38ZPgmVqfR0OcKmd26DrxrEYfkunb/W2fXiXH/LbZFdH6V6GW/jb6phe5ntq/hp3v9rcQHbvzP9A1be8+OK7b26L5BgXz+TT6TKtubwlgriMfNdEWF5/x1pmB0TCeo+VD2ZmIaa5833L/UcZCR2udVSJAlne9u7NIVMFtsPFu7xmYGEUc3+TN3lxxF1GRVb3xN750QnweZ4/p0n5NIcPv9eLL2CC9hkv/3Vex8i/zva4M8dOgXZrsPcxeeW6kQFo6acQwdbTl/Huy02G+e++bNE5yMPL9HnfP8/uhTTFpf/7ODs/xmSpXTygIx6zN3PkFGQ4+JTUOU3EhGujGQd+9NY93P3Zx9vt43bY+9nF2+3iO5QSqzY9vZEe7qfrl15WC7+N4RJ6GC7ysrxHafqzB/30oPngSGfZ3orZaZ3kifCU4cOMX/UoArxbxicz7rM/z+iM9iTPyUVVxbunDT3MpnSz32Awlcd5dmO14gOqj7zonnULJKaZT12uAt4kLt4kTa0xPm50gHusy+cki4tnp8N+7Rfp8kTyPd4Cwo6eVoxdvsSNfH+3RZIXI8OD1c88vPsc10wNDuXt/YJz76I+5CEEvUOn9NlOWgu2ISS+EhJjj+V4udv5ZmnyhsdlhK7vRf3civo86r/Hw8JDkYyMMoKZeHm32ixhdo75Vtna46Z7lO3rI784TftO8+ghSUu6kXkCzLLHTrN/t7/GbLeZ+rUBy9vUJcO7vLd5IfM3N7t5K3EvWCpruOgsS21kCxfGjdfNfZmafyag2q/RY3xOKzy4EQTiWgG+u6v4eIqTw1bujqDPwtF1Ep703JgZzXTT32qHjzfZPhP50Q6WXTZmeOHWTILNG1IxOPzYuCWLX8b58LBXrJem+jh8rZ1EPsNR1kvgdMPO2AOv3zzy+ndLXsYDyR/oW/kO1R/uBj0eYOW8BkaRLEaNHqN+K1NBuGZrEbW+Guw7zJHrp/GDgN/9IOCO4ttsv/lzMn2P4vJcoDZr1ka+D9186xKALmx4u7saF6HdTUPdNbYYeo0/l6zc0DD7E0aTwujmeMKbf9zmY7KZN2JBMPQmT+uEwkFYY9PXLH14yfrgM/5B0pfkNEaEh/gLGlO/8QT/kI1byWMUv0lQuq//8v8Kquv0qzx7TA7nIoau3p22aNffqyL2lsL7Kk/Px6xzSvfB8Q6V57S6yR6FSyC3TG1NtCMsXR3vyH4RyNcP8aro/jnb/QB+/SbWHiwRXT43XAaL/+svN+VHso36z18ecN+73IHk52KHwDff1uI1vNTi0cOAw2Mq1duGseZ8KcprqipQdfQ0R95Ffq/C6Pqbu65A1fErTtLE6CC0gBGJnW5GxQdqeNxXha8RvWF4meey6xSjcRz3UeDnENff68Xua3RK8y1lAWlX704vaR6VvlTzLbc9rEI9rzMG2IxfYxtdmLo8Cje5L3XgaxBbwD64QFWdHoo4K48JCZU43qoQx1EOYPjbaI4ObB8TGF71NHtK34wtbqhc+o2wNwoZ7Cq6j5uh+ktIvqL31Bt1xyt+KycBy6no57mLr3MXl9c89fdd/6vV3PsHbuyybz3MF/nXBFsl5KOAm7Id+lqIjrnv9XCiNN8mH8QNxouvxTP+sx5KfZ3+dNhozpW8HQN5Xkx9eHws0SifM3L1P4bBZVztnu6Tf45aItzij5AEzlqEO0QdY5WEJbdbAri58v5PcrrA7MZeA6cozoagfx5nWNw17TqwCetJxyB1mXB5fpE4A4P16HaBOvpZG2zYcqMnsvASPKHXyU4crnqQE9c60TYeopvXc1f1t+WWBJZlZNzpTYNO/Q20aDnZ8QxGZvobVLHs5r5mmEhdDYi6DGIOfduxMO3VtimnXmXacvtcxvYkLb6dBEPNED35OinJ5NdFsXDpyo6H8QfatuXUl0xjdpakqo77Kmn5LeE0VA3Zm7fub+BtO7Ntaq19SYtvfYDb1gzRky339n8PsdPdpbxvVOS4Mbe10rSmlArQye8kQ1M55Jw5di1sO3GOXgMvYPZc/pr3Nk/TT3kdfnLWRAYm6cxHfmRY0Yus/OYSvY2uG6ITap/iLjn2Vu3f6veQVGIgVUcno4ahScJAo6gMuBNuKsQn+TR0CW9qhgDHZZoffhRw+OrL2ma3eenwsnqoGXB1dIe+JujbW5SeHs9p5rgRXUXHMgo7L2666qNE+SsuW4uHODikBd16b84VftXf3NH0k7fhpvbmH252nNwQGmT+NyofsBVTD6z+yC05ybB+UZb5LiE9212jkGjojS/YHSqJ21rUpUDiwH+d7X+pl69DjqROonuUPv5t+PH9Oa2SU5rssAj/+PXlr/wn8yFr0qf+ckHuverTjHIX70VzYDX2UhkAyVl5QAJWtn8RmsSfMSqat/xXeVZWRYzNLX7zSbZLTnHK24MjNBweak17lnzJa3RCWX0lr9LbpF06sZXYft8M1wM6e7x6QYHKAGvJP0mASyLbioBGiy2ijC3dBsQYnVaBL+GWi95zl9E98KlQ3czXZnpZLJwEeso7TJV8LGEQQAommQCY5ne6kvbNLnEXANaHuDggfpM4AEMKBFXH/4AgtQbI3ADVnJROBc48TdcxOdeSsiAjP6x+CiZqrGPW7Q9UI1Fo254KiJJGRkGC7ucwc6RpL3pAS6uI0SyIyWfDC+edgrcweCuzq1DrpJPoV2osB6ZH+aJJ0AX7GsnF6imCYI4zwQToM/C1kjRs4ly1EEzqFmRAr0v6+YdAoQ0KZoOf3EtvKtxVcYWIx3a2QxHspbgojNHysthiS1aPKUYdCyzNBqXORc8jiP7tb397KfTcwKlzvKQ59b+tHQCgV+nCu14B2vHf8OLAYP+JTggJRrjZgNG7PvVPQXRL664GeKY00STDO3ZDogQeZzqFJ0CV0o1d0qTa/W4mZGkWyBYDxGZxZdPHM8BK/rxhalRdJmn9VhN+2uGEKc30ZTXurRcNRm2xxp8fDSRuUFZFsBILnbxaoUFR+rLNTF6dRjaT12y46twC13HB0UnLyDD8uPqLjl4Vk7bmv+xon8QkJKd21P5ffufRljOXDt1v09x8MAKzgnBFYW5AIAuFQRKnj0mLLe1i4AQ+mRM6E+rFHwVOkIWWA6fBp3aeFVP3stPjuKRbOHdvdpmFTv/j2scU+EmyrPtnHk3696gXJ2zyOi1KK77BnWpXl+nH4cdJBhfhHTgkS2Bs9SpPAC71u3dJm+q30bPDTHdNajNcbBhmVl0+B8wU8RFmg1k7va5qLIOWbELZZkYym+XZEgeyHmGaYWz+hfj8+JpwKe4CL2XciUnR1f3jDv3vOSlQ/ThWfqS9pLGLEhgUhynfzBhGa2Uzjs19iBDhnk++ouL5oY6YLu1bmojpVKbAag9oMRr6woVU1TDYsJrXKNnmxsTlOdunqI5ncFFVRfL5XKEmeGk0lOgmOYoS6GC6dMoTKKlmaiEF4pBTo8zGQTEq19VEhqH2cqDbYnU1h6OuH8z61mVuOGe6c0Ew8wow7Wz5EyXqRheCj/4cRBKccllHW5zQELI2dLDFa2RzrrUYXK1mWpsRVNMPVnb3PjMPVXXa+seY3D0VE1790c0y7NiCtV8BMtoYwWEZTgUMKuRKjO3QsIOOVCSwPAjUrPvfzwhk3SxdYVHYW80MtwCwTT/T2Q9vy1iY35/QLnlMdqSoP+pYD9hg+SG5ZJQbAaBEvTVAERb9w6n+MzLRi36Wp8eDGRBCPf1V6GogZUcY5imgO4TGvhNWKWvSPsxgoYOrubahgfNDjs2jwTb7gK3SYG7MU9kX9LECl7KKGGQGfbGp0o2sFiiVLFYIM0ZSA8Bl5lwk6VOgM38EiJl2+Jwog5PWTAu0T3GRxFnVj61X+fFzkhHC2R1EFLJB0FKSb8mtRKWoiRRL8jhR4W81e/PFA3X6eXcsRufeqhvA889zTILmf8wSOUYZIhoLbMEmh0e5gRYNPVrspeFvvWOiCSQ3O/qtecjrIKc/khQIgS6f+hhysWic9NjRAywXc9bIK/EpTs89SNUahsHFtMgl6pqI2RKGxLATnsJAudHWAs88g7lhPftycsLnGDMtDFe3+ovu4+MpRa/zb1max3x+VuoMpiNgzl/6HxeOBli9+ZHAGn82LDwkqMD61fGe5VmYKbdil0wvBBNBZrFeekYW6tdNpHEZ9DFpjJZuAbBa/pZyHhBNuG20w8/cG8Uu5/E6AuJ10jIyDD+uPiBer4pJW7MHxOtDtlrl2ltUpE7dBDthmrJJQlpBatlMc/NHhF1vrrwlgm6qRdVo4NUV5gPf9fcKFVmcXpyrp5pj48TEJW9c9Iin0oCRS024+hFQqZ4NIGfD4pu8OB9JDGPfwJMfJPRtMpyoX1ePi0GX9YDgIT8lu6lRQBoVYdD+vA0cNMqsBwgR+e/vRX4+SVFAkQid1/48yUREGhRFCAQdmWECgseooUGuJQwhgNz2PRYSLzMMOuZ9Oe2IQ8gXsPiQiT2m60JCaPLVi2W/Trp+IWLNBqIPxT5Inh/V2oW0yTBpf1l7ep9GDZOGWIPP3Pur2DRPC5opl7nmqJl9hdvF2PxYxgf0NsHSFM8RHBx0obFUaclBeViCzURTZdQyaXf2cKog1oBvxX6M+AEgZj6mzIUvIuG8Ex+5a10yonopRQkCXrJOhp1BkTUBZvluHfPAZkK3DjvgzO3W8XvyWF3FxT66PRe7p7hE+7+S6kmig3s3avwPOykYZsOP4UaSqeIu97oYYQLsitkhwqx1YIVcuzTQWANJDsozwaLHCgGe0Ga97ukqLgtrH+lPYaGroSVBbbI1kjPOmB6dd9n0R16h5a+zaylFCZpf142hQZHlr7Pv0DeM9tscMyi7wWkV55OA4Iw4YPnqzy4hrVZxkgnhzOskqFmOLwcukw1DrlhhumU+v8P7p+RUJ6Na9EzWCckG9Ot/XDeAej2WP411opITI1hu114LjBzhwIEtCBNQ0aZjPYHI+FiirzDvJW0txsnvHb1mivp5Tf8rZ/QZVyvVucjqjIgogK9xoPUwJTK3tGFKNrAGpvVZxeq3f8Az/ahihc31jS1WwOOsP+faNieCXMVFRaX6C5mWUgMTXiJuScIXbid9pKCbSZsLSBcpQGgVs9QSYDblXOWErtmnKwFby7+DXwKwJryRd8LV3BfzV09o9yU/8yHPhJ/lI5hAyQxlYuk0b5tBtdSihQxqprFnGEBKNDQa7viqM277ducCa3y4jZ/J0eNNltR8PZ1Aqk6n2Ya5/RtfuO5jRUEfozapnlgMPrqLDLVGvvo52MEBqJRStLA3JG4A8QtKmwsTvu5s+Kxh8BX3xLv8EFH/rjtSftLA0TEnDnzZJICkWpVJE+rcQmWzMLijlTJpjhNxEVBbxc5zPlRNud+0hdPsW03/+AkXA5KHzjYgsxqokGe+9+fP5a5ImlSTE4f/oNsWn1OzpauHhajTKkCC9fsaV+g9KmsHzuhNkR+nQwnbOHcaxhatHh+cQiYt0p2xFIA85D/hsRB4DF0x36r28TFJ8S8omig0Q98gy2j4de33s4MqRrubmf0+LnYpF9iw1kI7MhCimSNs9qJzu5t0kvCFopkCwanXx2Y1Uted7zKtyos6OHlyjIvn6++7pzg7oDv8RVydC9zE7lkBr5aAhVb3o/koQ0Rgb8SaXwJhAtIrDB4aPUwaUnTAMqBB/viJiRkwwVh+NjBcxrsvNxmWZfcl6CY3yOwjEZ4RSUqz+sWuTLNV7JhluFu+z8fyQDehB8gYzM3tCPLnGZ3R/voYJ+lFVcW7J3LZ/iZRLLXtU0MFgRwoOZfADKRYfcYpWC+jY59kxtU3JXbUCL9TB09jiJjg8EzBJAMcJbwMY4GQJTdVcHQZNUfLtwRsUcOYVJVxHftDDGi2MJhtTKNqzga/m+MpLyos2yMeYKP73RPan1P0EJdf5A8faSJm78cUmO8iGRkYjlxJmHeMUp3D4IXVyaTBVsIkO9QyzpjDZX6oMDJwaVe2BxVWp9VBBTeU5o0PLKjD+H4NluuHFR8QiC4Ms3iy7nsvYKP0MtwEPs65Mr/N0/RTXmG8t1cudUcNTMnVfX/4hX+vajA+5BFfT3su1taF0411ZRa3uHz7zAGIUGjwCm/U+VanwQQwU1veqs058FX/cJGV3xRHqRQJ36vdz5OMYqMw5ussS2KuBWFrEHHWHLJX+ZHMlIYDGFVl6rGLbppPI9v/vqERS2pqq+YmhhGcCNc1E3DAbMSWSPI0LlnlCZ4YP51s813mpPnBcjiiqkw9HNFNM5c29O8bGo6kprZqbmIY1f8WkytxvShkmBp+nOYq0B5JnoYj2DzLwE8n24zOtuSi8Q59TdC3tyg9PZ7TrI68YrrXk9SffM8nkwO4/AaINjSEmfWIVdtz4pAp0Lk6tFTSPp8NUX69FyBjLBBMTOVlIMtpWJt1LDMH83ZGrRUNVStwwnKE0/ocruwxNK2b1TWuUz3XHxWugYrOvTDfozdJUVav4yr+HJfiPU5d6x5V/SMckpu2+ZnqzPb3+pLqGP/j1/3nHPd1/DkdqggDDsc4/n4VV+hAwkaI7OlSsBGaQNMU/ld9kAg005dATfSFGvbv8l2cJv9E+67HgYYAGqhJgEzXeJwdzuSJmNhmXwQ21ZeaqIfuq4KcxZb5udiBrYFkUiUFSo0Ut6g4JmWJMd4dcgsSiCRQ6yKVpmX27Y7QKlsMtchS6PTM0xTSjfwM6kNKDLh2FxYg765Q1kJXbmirfhkiNVdPobJYT2TYrKI9dUPaFvqnfEIDfQnEvy/UKVD7rYEDYV8Cit8V6gbANs7pe1Q95dCnwxOAwyFHo2uzwsMzHsa+4ukU+m64crBFlkTT4HDCJLQ1FEHNDKW6r6hbSImfUFcCfj9doYb9kJFX4D8UQQ0MpTqYySdc9WxrPNXeJrvqXED93ZeAJuoKzXpA0QpHoOiPqCWK3scE18b9U0eaTgp0hAc4kErVawyhTgSUJl9R8fyQHCHt2WKwUYbCzNp0DGGZwWkahc1pMtvG+3CAb5K0gicybRUj0YRaZpIqPi6BQgXLjsoYl23F93F2fowJpBXGYalUctCUxrJohNC3zlKYaX5/QrvkMdmRLRAV7VNmAxm9yhpwHWO7wNU/tG+PxMlRSQ7OlcoaTtIZy2UjkWmfPsTQfo0uVPQWKTdr51NcJHE2xBq9yo+fkyyW9ItJJYVcynoaef88x+SXj1kCTTtsMSQDS+FmHXOTaKbe5v/23xFfUS6QmSTWuOT1bAP3mpqlJbfASFtDtzDoX76Ji4K+CFwQ9KW685oEFbdFAq7lqTLwrGYo1jQyOK0IbQxFUBN1qZb79Xc8nWdxenGunuoTtWZokp4nqMkhKdQ1NNKRAFuSDQxVBrVLisvIaBNDaGWnenShoiGzEz5CLGtEyb+lMOH/e5GfT7JG2kJFSy2FpqU2frPQSPs7xL8tMtxSsBlzpXsKlky1qWApNVLAeXsFKWAySAqY0lAKRcvq1sy6UTK8UGXS7jTat1BZa+FGmjJpI02xphEwO6XQHEgFNQwSGpwpSbbAQ5HsHMlos8smvpO2Iu80lkJrUiYrFmBMphw2I0OiVY9PQgGoyJPAavJUumMgMTGCeB4k0oAHQyKZbeOyFZSM0EgMs1WTNIw50PsSShgHEmJLcQzkMBNAf7xNBz0Wj7jpUvCYmyYwb6qJIa5qrqHQNNkQaW+xIM2kGploAkR5hZchLI1iNcISajckbIhIYAvCEqhWjhyprg+HeIRi7w1lYL8NxbrxkXlXKo6NTDE4LjIUWlim0o0AVQZDMTVc4n8s5I1QZVAjVLFuHYUyhDdbqsFdJAHXVQKVbouIeSCyf/0MXpVy5eBWkSXR3jHl4C1A+zt8p5Qb3DMMAdTEaakvgi8Uu1IT0ftTD1iDvliqiOmxiTTqkdCwlBK8OZMRW5wSP6DjKYWHEphMd2o8UBrehikkEElUt2PGLXfHlPKGBQrVaadps3eoJtvLrzF5AnhtwdLojFzkZYmZp/JWRRLQyAKVzsjN2kd6582VgwZmSbQntGBgFOCoFqSDz2xBUnNB1M1rG9UfqTFBkMQjNKYYPDJjKLRf7PEUJwdo3BqK4C+0K9WNTM0KSDUo8RTgeMQTGZx7vkNVhQrN8CwjlJ2JQrRaE8QlHkf/QsnhCepTrhxWnyExa/B1gsFdwmqLJIpmKSpNy1xMGqFZrhxqkyPRzfrP2U4x6dOl4JxPE2gPu/nYFsABN08CH2rzVEYty63KlcvbNLUq79Kn8dhTk5v4DEY1qfndL+M4JXf1ktDpfbHuUImX6MRn28DVrNVW7tEmUKg851oipG+4O5pW+EmIJKoz7ujidEoTtH/IW/rEQgr5ckSgMJOhJddL0LkoGdxzG153RwOdMSh7F0sz71ZzL1cmUr75va4Qw0K0iUAic1ZlqQym3v5FKDjf9qWySbYnMPBQlDfFlMr8FE2bkr8Uk2ENIFVADqC2kUgnhkHbQoPUMwi183o0uL5TdRRu7EMF/tUGlQuM88PHIsh97NmaCv/6houJv/wLVn1T09BvDfR2gamDGQV6UzFYRPlMwt4cwhxPj2NldN+aXbSMWUW5qrKXDURT3WsFJSdobAe5Kl8oeDflQ1wcUOVgyrai3ABShVUKLtOEeBpTfo8sQYhPkH6J0qgMvTRxU615ThI1HGHlaJKRQgo12KcwfT3JIxd7FdnledStz1G7qE+U44l5ZbmS8IMPoqj6GYeCC7dxAZjJnqMEMaB8FDGvrBhJAGUl6i3FZPRjmajnC5gHJAxgCugBUGMC5bsee9XbPahKaZ7Ev7rcfprUkz3/cleRM51cUZbQey9NqHq/seyPGhSDp5xYv2oAVwxKxSVnKQyDoIaQD4JyYi/9uAQzXCZpHR2556wwAkcazgRGGBqhdBc0ZGAt11qgDfkJcC9+GQayZ7z2ZuieaiqXxiJRiOUx/9yU1JQ+JXVYJjMvGaP+RSOwWoYpFUtg9vlks/6FX2Cy9cDXl011rsi7+t1hrF59OAKIqAYk/2LU7x8BKvpdoPHf4/xZffNZyx4D26upOspXzPIm1eRK8e+siVKyN9RgTdAsYmFY88jnfpNqnvp8YebpLmGssMNXCokcaOwRykIaxgY1fCX/Q+pMZgGe3oPrR3WFkDgBYgwwTFRBA5wn24iNDiCdalk6hTJAtIJGC0UUAldcuastffkf0eEKpNYwqq5VUQyyQCsrj5ug5iWJmQCz1sU98GDg1iT61Zy0SrBlnWsHeDGLlUE2aIp+KTusDqSWEGlDrGUkQT1oQ3hcyfCq6fEgkAZDw0QmYMKNKHY9IJ3/nQ8UyIRUVgYoce55LtgK3YQUAvI6ntWCUCFlBJYHMZP+IwHJg30o85hEEphGbxxNxWBmUgebobkZRpBxuCtTBMuRRPoBb9Ic2ChuYvR2MTOIAVc28JCCtySAkG+0qhu3hbCS28SmWugXIHqOKl2c5NThjlkF/1jmpJUqDWIMhZOSnFqrlEQbQI1lmEQRsMr0sMCYxQTnCAbBvmiL2oTrCmJi/SRuUjvYJ7o2c7IR0JxsyrCQGwgK1kZMoQrCtlYTO8B0emyaWCu8iZTLSeO6WuWVWputGxdpRhLVJZI2a2xSNZ9JjGLCl4mMo2IOR7txXwgZDpSTj4Zh7wGi+/h4StEQEVGuOEepWLpwcRmbZYsk5uKEKvchGSPWcV1UWUKp9whyd8IXQk2S6vIokmPUV8xdIpH/2Sq8ql3cTKXLlEgkF9ndZYqP7klqDj/68ww0fbqjrqCH+Ah3Od1H4vk9iURTA//RyZ/gzGUiVfBWpb+lWcUQaDIJaEu4WQWmtTddH55WaSeAKoRRhDi6pCr1qx91SQxcvb4cWTCFmYi+g8ZwpF5HlSM6qK5EX5pGIzITuncQGQzJK9YWK/rt2ZalslsbmtGCztOb5Euk2aq+WIrOo+gTf7ckvq7BswGQTi6465sBJqo0qQUHjXZVVDU2cRQhhqVw6kFBrqPBvU7uxwdXUOzeRvvxQSG1GS7KENmeTNPa3dAuDfXofl2EOfqw3XIb8CR+FRdCjg/1PG16BvkVW1mRyP9WNryqXaD36Bbvz57iEu3/SqonqgVRcV0Vb+owdflQ9qSqNEy9uyGY73bgLzcDXMGTImBN6RdvlCPAk2k+0v1ubB+2lt9xYS7z9CkGNAMiRRJgQKTTIwz1wMwH9ioCyQyUSyElfYiFkSJ5A2Fiko3Bj1m034W+kl90zGOaLk+E/JvgKPwqzWfSuO/CRHh63UGnwYgGvnItWUJPgoM1hflVmbLDdSPUhdzQbvl4wjXt+ZjkI5oBD6QMM9QBSVXaL1mVK2XEAbdBZ0tp/fd36LgAfO4XzZMlFbl2R+DliZIs60379Wsy2XgwkOrLkBOH+DhmN4Viwyil9b9vnNoMcK6gCMhkBIwcpnUVeJElX2qAo8unpOYleowYZkZymW/YHEPRTZZUSZwq1pKqCr7XkXAmpXbq0WRHGm+Mbq0sNqW3i7RuIHWVnOBFuFl6K4coV0NSp0hI8CRaTkWumMrhBFTNlK7OKiXjI+Ph2SSqqQukCzFrTaO2Tt3gavLq+VFLSAemv0CGqYPdI0P5J4aLOGUiCRfHOzrtWPSmyI8qe6jIQxgEzrDWLmyUCdNGm+IhtzAERbx2M/Tp4SLF/k0k8r9xE1LcNTWl2etcRvYUiHIMj+0gpb6v3Z2whHR47RgvS0LnEq80J9G6kmNcPF9/3z3F2QHdYdMOKdSAbYm2ksoobE631iBwvjZ2h0KnmWu2JWAWuZFGIH8Ya89Sr0ttSd445WSorRNi6NPkzSOMTJPg+TOTYt+uq+J/+z6vicAEadGbBB5IFdQK3wVnF35lQrj75gGESX63UWaJ2AxrSqOwtHLFoKxvRB9VNjeZaWQGCYYOpg1TkNCVvKq1EMQwecwiNnWuaCMFtWI+ARL6NtOKIlEvwwHM10ZYqNOwuTiD25hDQR3SHGCiteY7VOZPczXHkAMu6lnLjAHQelcE4CHmu6PYKHLYuaWxoBNNRfWB0VWelVURJ/Wsgnen/eKkC8X8kEdigipgf+eLt35N5BC3mV02SBJ3NesHXTIuD2anE3cYWJIiVytllSNkVpNQKchMgcJkLROtNpJjeNQBSdvuu8dkshRp4ww7PFNTm6unUwtv/AxuBrWpPHOmvc+kpgN2JeM4hscTkJmv2bgoUu6NM+wQKV5trp5OLbxxJPoZ1G53kULKQONZTVJf8UDbf1sTzKRSNcUduT79osduYgqsjM7WNFRdru96zOcEN61p3bhOjFzz7httZH3cGY7Q/+mXf9VfvWiY1IbHvYyKvuzViyYpavsD/rPKi/iA3ud7lJbk11cv7s649hE1f71GZXIYWLzCPDNE8kQPTDuam+yxDvhO0mlyEnUkXXGft72K93EVXxRVUse1xMU7/C2RdL/Ey6Q+D/qM9jfZh3N1OldYZXT8nDKHzq9eqNt/9UKQ+VUTaK/0oQIWM8EqoA/Z5TlJ973cb+K05LabMhZX2Pq/I/x705f406zQ4bnn9EeeGTJqzYc3qSjb40/uAR1PKWZWfsju46/IRbaPJXqHDvHu+bZO40WcZmRM9B3Bmv3V6yQ+FPGxbHkM9fGfGMP74/f/+n+Jqu+Y4rUIAA== + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index 715e87aaf6..ff7fd39e2b 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -404,6 +404,10 @@ 201605201911421_ExportRevision.cs + + + 201607111548571_FixExportDeploymentIsPublic.cs + @@ -752,6 +756,9 @@ 201605201911421_ExportRevision.cs + + 201607111548571_FixExportDeploymentIsPublic.cs + From e6ef17b71e5c8f07cf965c7cb730d63bce4c3d68 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 13 Jul 2016 11:05:59 +0200 Subject: [PATCH 167/185] Export: Projected customer id was ignored in price calculation --- .../DataExchange/Export/DataExporter.cs | 14 +++++++++++--- .../Export/Internal/DataExporterContext.cs | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index e6b182c270..387b360a06 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -969,9 +969,12 @@ private List Init(DataExporterContext ctx, int? totalRecords = null) ctx.ContextCurrency = _services.WorkContext.WorkingCurrency; if (ctx.Projection.CustomerId.HasValue) - ctx.ContextCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); - else - ctx.ContextCustomer = _services.WorkContext.CurrentCustomer; + { + ctx.OldCurrentCustomer = _services.WorkContext.CurrentCustomer; + _services.WorkContext.CurrentCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); + } + + ctx.ContextCustomer = _services.WorkContext.CurrentCustomer; if (ctx.Projection.LanguageId.HasValue) ctx.ContextLanguage = _languageService.Value.GetLanguageById(ctx.Projection.LanguageId.Value); @@ -1286,6 +1289,11 @@ private void ExportCoreOuter(DataExporterContext ctx) _exportProfileService.Value.UpdateExportProfile(ctx.Request.Profile); } + + if (ctx.OldCurrentCustomer != null) + { + _services.WorkContext.CurrentCustomer = ctx.OldCurrentCustomer; + } } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs index 5c6c1315dd..4c41efd0d4 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs @@ -89,6 +89,7 @@ public bool Supports(ExportFeatures feature) public ExportFilter Filter { get; private set; } public ExportProjection Projection { get; private set; } public Currency ContextCurrency { get; set; } + public Customer OldCurrentCustomer { get; set; } public Customer ContextCustomer { get; set; } public Language ContextLanguage { get; set; } From e654f1530f540ba1d540afecd1080b29109aa60e Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 13 Jul 2016 11:41:44 +0200 Subject: [PATCH 168/185] Revert "Export: Projected customer id was ignored in price calculation" This reverts commit e6ef17b71e5c8f07cf965c7cb730d63bce4c3d68. --- .../DataExchange/Export/DataExporter.cs | 14 +++----------- .../Export/Internal/DataExporterContext.cs | 1 - 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 387b360a06..e6b182c270 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -969,12 +969,9 @@ private List Init(DataExporterContext ctx, int? totalRecords = null) ctx.ContextCurrency = _services.WorkContext.WorkingCurrency; if (ctx.Projection.CustomerId.HasValue) - { - ctx.OldCurrentCustomer = _services.WorkContext.CurrentCustomer; - _services.WorkContext.CurrentCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); - } - - ctx.ContextCustomer = _services.WorkContext.CurrentCustomer; + ctx.ContextCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); + else + ctx.ContextCustomer = _services.WorkContext.CurrentCustomer; if (ctx.Projection.LanguageId.HasValue) ctx.ContextLanguage = _languageService.Value.GetLanguageById(ctx.Projection.LanguageId.Value); @@ -1289,11 +1286,6 @@ private void ExportCoreOuter(DataExporterContext ctx) _exportProfileService.Value.UpdateExportProfile(ctx.Request.Profile); } - - if (ctx.OldCurrentCustomer != null) - { - _services.WorkContext.CurrentCustomer = ctx.OldCurrentCustomer; - } } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs index 4c41efd0d4..5c6c1315dd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs @@ -89,7 +89,6 @@ public bool Supports(ExportFeatures feature) public ExportFilter Filter { get; private set; } public ExportProjection Projection { get; private set; } public Currency ContextCurrency { get; set; } - public Customer OldCurrentCustomer { get; set; } public Customer ContextCustomer { get; set; } public Language ContextLanguage { get; set; } From 53d614128ee9922d0d319224480dd3c51279b823 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 12 Aug 2016 15:08:21 +0200 Subject: [PATCH 169/185] Awarded reward points should be rounded towards zero rather than to nearest integer --- .../SmartStore.Services/Orders/OrderProcessingService.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs index d92e6212a0..0993597da4 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs @@ -242,12 +242,8 @@ protected void AwardRewardPoints(Order order, decimal? amount = null) if (order.RewardPointsWereAdded) return; - // Truncate increases the risk of inaccuracy of rounding - //int points = (int)Math.Truncate((amount ?? order.OrderTotal) / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); - - // why are points awarded for OrderTotal? wouldn't be OrderSubtotalInclTax better? - - int points = (int)Math.Round((amount ?? order.OrderTotal) / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); + // Trucate same as Floor for positive amounts + int points = (int)Math.Truncate((amount ?? order.OrderTotal) / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); if (points == 0) return; From 972e2170f9c9874a3895259e0ade9ca0218b935c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 17 Aug 2016 15:32:34 +0200 Subject: [PATCH 170/185] Added PayPal partner attribution Id as request header --- src/Plugins/SmartStore.PayPal/Description.txt | 2 +- src/Plugins/SmartStore.PayPal/Services/PayPalService.cs | 1 + src/Plugins/SmartStore.PayPal/changelog.md | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Plugins/SmartStore.PayPal/Description.txt b/src/Plugins/SmartStore.PayPal/Description.txt index 75a43e5adb..84ba16461c 100644 --- a/src/Plugins/SmartStore.PayPal/Description.txt +++ b/src/Plugins/SmartStore.PayPal/Description.txt @@ -2,7 +2,7 @@ Description: Provides the PayPal payment methods PayPal Standard, PayPal Direct, PayPal Express and PayPal PLUS. SystemName: SmartStore.PayPal Group: Payment -Version: 2.6.0 +Version: 2.6.0.1 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.PayPal.dll diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index f5819da195..0b41486b94 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -451,6 +451,7 @@ public PayPalResponse CallApi(string method, string path, string accessToken, Pa request.Headers["Authorization"] = "Bearer " + accessToken.EmptyNull(); } + request.Headers["PayPal-Partner-Attribution-Id"] = "SmartStoreAG_Cart_PayPalPlus"; if (data.HasValue() && (method.IsCaseInsensitiveEqual("POST") || method.IsCaseInsensitiveEqual("PUT") || method.IsCaseInsensitiveEqual("PATCH"))) { diff --git a/src/Plugins/SmartStore.PayPal/changelog.md b/src/Plugins/SmartStore.PayPal/changelog.md index 70ed0765b7..80c08c683b 100644 --- a/src/Plugins/SmartStore.PayPal/changelog.md +++ b/src/Plugins/SmartStore.PayPal/changelog.md @@ -1,5 +1,9 @@ #Release Notes +##PayPal 2.6.0.1 +###Improvements +* Added PayPal partner attribution Id as request header + ##Paypal 2.5.0.2 ###New Features * PayPal PLUS payment provider From 6cf721ef06b7cc2f1e04754afca5e672049c096b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 1 Sep 2016 16:11:29 +0200 Subject: [PATCH 171/185] PayPal PLUS: Integration review through PayPal --- .../Controllers/PayPalPlusController.cs | 26 ++ src/Plugins/SmartStore.PayPal/Description.txt | 2 +- .../Services/IPayPalService.cs | 6 + .../Services/PayPalService.cs | 237 +++++++++++------- .../PayPalPlus/PaymentWallScripting.cshtml | 18 +- src/Plugins/SmartStore.PayPal/changelog.md | 4 + 6 files changed, 200 insertions(+), 93 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index 1c20f4b032..d2fc262e9b 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -194,6 +194,9 @@ public ActionResult Configure(PayPalPlusConfigurationModel model, FormCollection var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); var settings = Services.Settings.LoadSetting(storeScope); + var oldClientId = settings.ClientId; + var oldSecret = settings.Secret; + var oldProfileId = settings.ExperienceProfileId; var validator = new PayPalPlusConfigValidator(Services.Localization, x => { @@ -209,6 +212,15 @@ public ActionResult Configure(PayPalPlusConfigurationModel model, FormCollection model.Copy(settings, false); + // credentials changed: reset profile and webhook id to avoid errors + if (!oldClientId.IsCaseInsensitiveEqual(settings.ClientId) || !oldSecret.IsCaseInsensitiveEqual(settings.Secret)) + { + if (oldProfileId.IsCaseInsensitiveEqual(settings.ExperienceProfileId)) + settings.ExperienceProfileId = null; + + settings.WebhookId = null; + } + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); Services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); @@ -314,6 +326,20 @@ public ActionResult PaymentWall() return View(model); } + [HttpPost] + public ActionResult PatchShipping() + { + var store = Services.StoreContext.CurrentStore; + var customer = Services.WorkContext.CurrentCustomer; + var settings = Services.Settings.LoadSetting(store.Id); + var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); + var session = HttpContext.GetPayPalSessionData(); + + var apiResult = PayPalService.PatchShipping(settings, session, cart, PayPalPlusProvider.SystemName); + + return new JsonResult { Data = new { success = apiResult.Success, error = apiResult.ErrorMessage } }; + } + public ActionResult CheckoutCompleted() { var instruct = _httpContext.Session[PayPalPlusProvider.CheckoutCompletedKey] as string; diff --git a/src/Plugins/SmartStore.PayPal/Description.txt b/src/Plugins/SmartStore.PayPal/Description.txt index 84ba16461c..7123a24071 100644 --- a/src/Plugins/SmartStore.PayPal/Description.txt +++ b/src/Plugins/SmartStore.PayPal/Description.txt @@ -2,7 +2,7 @@ Description: Provides the PayPal payment methods PayPal Standard, PayPal Direct, PayPal Express and PayPal PLUS. SystemName: SmartStore.PayPal Group: Payment -Version: 2.6.0.1 +Version: 2.6.0.2 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.PayPal.dll diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs index 6a799e16a1..2f4b94c1c9 100644 --- a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -36,6 +36,12 @@ PayPalResponse CreatePayment( string returnUrl, string cancelUrl); + PayPalResponse PatchShipping( + PayPalApiSettingsBase settings, + PayPalSessionData session, + List cart, + string providerSystemName); + PayPalResponse ExecutePayment(PayPalApiSettingsBase settings, PayPalSessionData session); PayPalResponse Refund(PayPalApiSettingsBase settings, PayPalSessionData session, RefundPaymentRequest request); diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 0b41486b94..3d5d66d20b 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -41,6 +41,7 @@ public class PayPalService : IPayPalService private readonly IOrderService _orderService; private readonly IOrderProcessingService _orderProcessingService; private readonly IOrderTotalCalculationService _orderTotalCalculationService; + private readonly IGenericAttributeService _genericAttributeService; private readonly IPaymentService _paymentService; private readonly IPriceCalculationService _priceCalculationService; private readonly ITaxService _taxService; @@ -54,6 +55,7 @@ public PayPalService( IOrderService orderService, IOrderProcessingService orderProcessingService, IOrderTotalCalculationService orderTotalCalculationService, + IGenericAttributeService genericAttributeService, IPaymentService paymentService, IPriceCalculationService priceCalculationService, ITaxService taxService, @@ -66,6 +68,7 @@ public PayPalService( _orderService = orderService; _orderProcessingService = orderProcessingService; _orderTotalCalculationService = orderTotalCalculationService; + _genericAttributeService = genericAttributeService; _paymentService = paymentService; _priceCalculationService = priceCalculationService; _taxService = taxService; @@ -113,6 +116,99 @@ private Dictionary CreateAddress(Address addr, bool addRecipient return dic; } + private Dictionary CreateAmount( + Store store, + Customer customer, + List cart, + string providerSystemName, + List> items) + { + var amount = new Dictionary(); + var amountDetails = new Dictionary(); + var language = _services.WorkContext.WorkingLanguage; + var currencyCode = store.PrimaryStoreCurrency.CurrencyCode; + var includingTax = (_services.WorkContext.GetTaxDisplayTypeFor(customer, store.Id) == TaxDisplayType.IncludingTax); + + Discount orderAppliedDiscount; + List appliedGiftCards; + int redeemedRewardPoints = 0; + decimal redeemedRewardPointsAmount; + decimal orderDiscountInclTax; + decimal totalOrderItems = decimal.Zero; + + var shipping = (_orderTotalCalculationService.GetShoppingCartShippingTotal(cart) ?? decimal.Zero); + + var paymentFee = _paymentService.GetAdditionalHandlingFee(cart, providerSystemName); + + var total = (_orderTotalCalculationService.GetShoppingCartTotal(cart, out orderDiscountInclTax, out orderAppliedDiscount, out appliedGiftCards, + out redeemedRewardPoints, out redeemedRewardPointsAmount) ?? decimal.Zero); + + // line items + foreach (var item in cart) + { + decimal unitPriceTaxRate = decimal.Zero; + decimal unitPrice = _priceCalculationService.GetUnitPrice(item, true); + decimal productPrice = _taxService.GetProductPrice(item.Item.Product, unitPrice, includingTax, customer, out unitPriceTaxRate); + + if (items != null) + { + var line = new Dictionary(); + line.Add("quantity", item.Item.Quantity); + line.Add("name", item.Item.Product.GetLocalized(x => x.Name, language.Id, true, false).Truncate(127)); + line.Add("price", productPrice.FormatInvariant()); + line.Add("currency", currencyCode); + line.Add("sku", item.Item.Product.Sku.Truncate(50)); + items.Add(line); + } + + totalOrderItems += (productPrice * item.Item.Quantity); + } + + var itemsPlusMisc = (totalOrderItems + shipping + paymentFee); + + if (total != itemsPlusMisc) + { + if (items != null) + { + // e.g. discount applied to cart total + var line = new Dictionary(); + line.Add("quantity", "1"); + line.Add("name", T("Plugins.SmartStore.PayPal.Other").Text.Truncate(127)); + line.Add("price", (total - itemsPlusMisc).FormatInvariant()); + line.Add("currency", currencyCode); + items.Add(line); + } + + totalOrderItems += (total - itemsPlusMisc); + } + + // fill amount object + amountDetails.Add("shipping", shipping.FormatInvariant()); + amountDetails.Add("subtotal", totalOrderItems.FormatInvariant()); + if (!includingTax) + { + // "To avoid rounding errors we recommend not submitting tax amounts on line item basis. + // Calculated tax amounts for the entire shopping basket may be submitted in the amount objects. + // In this case the item amounts will be treated as amounts excluding tax. + // In a B2C scenario, where taxes are included, no taxes should be submitted to PayPal." + + SortedDictionary taxRates = null; + var taxTotal = _orderTotalCalculationService.GetTaxTotal(cart, out taxRates); + + amountDetails.Add("tax", taxTotal.FormatInvariant()); + } + if (paymentFee != decimal.Zero) + { + amountDetails.Add("handling_fee", paymentFee.FormatInvariant()); + } + + amount.Add("total", total.FormatInvariant()); + amount.Add("currency", currencyCode); + amount.Add("details", amountDetails); + + return amount; + } + private string ToInfoString(dynamic json) { var sb = new StringBuilder(); @@ -412,7 +508,7 @@ public PaymentStatus GetPaymentStatus(string state, string reasonCode, PaymentSt public PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data) { - var isJson = (data.HasValue() && data.StartsWith("{")); + var isJson = (data.HasValue() && (data.StartsWith("{") || data.StartsWith("["))); var encoding = (isJson ? Encoding.UTF8 : Encoding.ASCII); var result = new PayPalResponse(); HttpWebResponse webResponse = null; @@ -600,34 +696,16 @@ public PayPalResponse CreatePayment( { var store = _services.StoreContext.CurrentStore; var customer = _services.WorkContext.CurrentCustomer; - var language = _services.WorkContext.WorkingLanguage; - var currencyCode = store.PrimaryStoreCurrency.CurrencyCode; - - var dateOfBirth = customer.GetAttribute(SystemCustomerAttributeNames.DateOfBirth); - Discount orderAppliedDiscount; - List appliedGiftCards; - int redeemedRewardPoints = 0; - decimal redeemedRewardPointsAmount; - decimal orderDiscountInclTax; - decimal totalOrderItems = decimal.Zero; + //var dateOfBirth = customer.GetAttribute(SystemCustomerAttributeNames.DateOfBirth); - var includingTax = (_services.WorkContext.GetTaxDisplayTypeFor(customer, store.Id) == TaxDisplayType.IncludingTax); - - var shipping = (_orderTotalCalculationService.GetShoppingCartShippingTotal(cart) ?? decimal.Zero); - - var paymentFee = _paymentService.GetAdditionalHandlingFee(cart, providerSystemName); - - var total = (_orderTotalCalculationService.GetShoppingCartTotal(cart, out orderDiscountInclTax, out orderAppliedDiscount, out appliedGiftCards, - out redeemedRewardPoints, out redeemedRewardPointsAmount) ?? decimal.Zero); + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, PayPalPlusProvider.SystemName, store.Id); var data = new Dictionary(); var redirectUrls = new Dictionary(); var payer = new Dictionary(); - var payerInfo = new Dictionary(); + //var payerInfo = new Dictionary(); var transaction = new Dictionary(); - var amount = new Dictionary(); - var amountDetails = new Dictionary(); var items = new List>(); var itemList = new Dictionary(); @@ -651,80 +729,23 @@ public PayPalResponse CreatePayment( data.Add("redirect_urls", redirectUrls); // payer, payer_info - if (dateOfBirth.HasValue) - { - payerInfo.Add("birth_date", dateOfBirth.Value.ToString("yyyy-MM-dd")); - } - if (customer.BillingAddress != null) - { - payerInfo.Add("billing_address", CreateAddress(customer.BillingAddress, false)); - } + // paypal review: do not transmit + //if (dateOfBirth.HasValue) + //{ + // payerInfo.Add("birth_date", dateOfBirth.Value.ToString("yyyy-MM-dd")); + //} + //if (customer.BillingAddress != null) + //{ + // payerInfo.Add("billing_address", CreateAddress(customer.BillingAddress, false)); + //} payer.Add("payment_method", "paypal"); - payer.Add("payer_info", payerInfo); + //payer.Add("payer_info", payerInfo); data.Add("payer", payer); - // line items - foreach (var item in cart) - { - decimal unitPriceTaxRate = decimal.Zero; - decimal unitPrice = _priceCalculationService.GetUnitPrice(item, true); - decimal productPrice = _taxService.GetProductPrice(item.Item.Product, unitPrice, includingTax, customer, out unitPriceTaxRate); - - var line = new Dictionary(); - line.Add("quantity", item.Item.Quantity); - line.Add("name", item.Item.Product.GetLocalized(x => x.Name, language.Id, true, false).Truncate(127)); - line.Add("price", productPrice.FormatInvariant()); - line.Add("currency", currencyCode); - line.Add("sku", item.Item.Product.Sku.Truncate(50)); - items.Add(line); - - totalOrderItems += (productPrice * item.Item.Quantity); - } - - var itemsPlusMisc = (totalOrderItems + shipping + paymentFee); - - if (total != itemsPlusMisc) - { - var line = new Dictionary(); - line.Add("quantity", "1"); - line.Add("name", T("Plugins.SmartStore.PayPal.Other").Text.Truncate(127)); - line.Add("price", (total - itemsPlusMisc).FormatInvariant()); - line.Add("currency", currencyCode); - items.Add(line); - - totalOrderItems += (total - itemsPlusMisc); - } + var amount = CreateAmount(store, customer, cart, providerSystemName, items); itemList.Add("items", items); - if (customer.ShippingAddress != null) - { - itemList.Add("shipping_address", CreateAddress(customer.ShippingAddress, true)); - } - - // transactions - amountDetails.Add("shipping", shipping.FormatInvariant()); - amountDetails.Add("subtotal", totalOrderItems.FormatInvariant()); - if (!includingTax) - { - // "To avoid rounding errors we recommend not submitting tax amounts on line item basis. - // Calculated tax amounts for the entire shopping basket may be submitted in the amount objects. - // In this case the item amounts will be treated as amounts excluding tax. - // In a B2C scenario, where taxes are included, no taxes should be submitted to PayPal." - - SortedDictionary taxRates = null; - var taxTotal = _orderTotalCalculationService.GetTaxTotal(cart, out taxRates); - - amountDetails.Add("tax", taxTotal.FormatInvariant()); - } - if (paymentFee != decimal.Zero) - { - amountDetails.Add("handling_fee", paymentFee.FormatInvariant()); - } - - amount.Add("total", total.FormatInvariant()); - amount.Add("currency", currencyCode); - amount.Add("details", amountDetails); transaction.Add("amount", amount); transaction.Add("item_list", itemList); @@ -744,6 +765,42 @@ public PayPalResponse CreatePayment( return result; } + public PayPalResponse PatchShipping( + PayPalApiSettingsBase settings, + PayPalSessionData session, + List cart, + string providerSystemName) + { + var data = new List>(); + var amountTotal = new Dictionary(); + + var store = _services.StoreContext.CurrentStore; + var customer = _services.WorkContext.CurrentCustomer; + + if (customer.ShippingAddress != null) + { + var shippingAddress = new Dictionary(); + shippingAddress.Add("op", "add"); + shippingAddress.Add("path", "/transactions/0/item_list/shipping_address"); + shippingAddress.Add("value", CreateAddress(customer.ShippingAddress, true)); + data.Add(shippingAddress); + } + + // update of whole amount object required. patching single amount values not possible (MALFORMED_REQUEST). + var amount = CreateAmount(store, customer, cart, providerSystemName, null); + + amountTotal.Add("op", "replace"); + amountTotal.Add("path", "/transactions/0/amount"); + amountTotal.Add("value", amount); + data.Add(amountTotal); + + var result = CallApi("PATCH", "/v1/payments/payment/{0}".FormatInvariant(session.PaymentId), session.AccessToken, settings, JsonConvert.SerializeObject(data)); + + //Logger.InsertLog(LogLevel.Information, "PayPal PLUS", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + (result.Json != null ? result.Json.ToString() : "")); + + return result; + } + public PayPalResponse ExecutePayment(PayPalApiSettingsBase settings, PayPalSessionData session) { var data = new Dictionary(); diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml index 212ae653cc..c1f2cdf6f0 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml @@ -1,4 +1,5 @@ -@using SmartStore.PayPal.Models; +@using SmartStore.PayPal +@using SmartStore.PayPal.Models @model PayPalPlusCheckoutModel